summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authortomfinegan@chromium.org <tomfinegan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-25 09:26:17 +0000
committertomfinegan@chromium.org <tomfinegan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-25 09:26:17 +0000
commitc54e1aa3c39589b125f9e1c0bddf0cbf9353fdd1 (patch)
treec8b8db2cbf571746cecfe27d8c06276d5a97f51c /media
parent063d9430289fdf33106cc23d7225391ec56c9e93 (diff)
downloadchromium_src-c54e1aa3c39589b125f9e1c0bddf0cbf9353fdd1.zip
chromium_src-c54e1aa3c39589b125f9e1c0bddf0cbf9353fdd1.tar.gz
chromium_src-c54e1aa3c39589b125f9e1c0bddf0cbf9353fdd1.tar.bz2
Add wrapper class to media for support of VP9 video, and add a command line flag to enable the support.
This initial version of the wrapper provides support for decoding VP9 video in WebM container files, and is disabled by default. New flag added: --enable-vp9-playback TBR=brettw,scherkus,xhwang BUG=166094 TEST=VP9 video in WebM containers plays back in <video> elements when --enable-vp9-playback is specified on the command line. Review URL: https://chromiumcodereview.appspot.com/12045060 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@178799 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/DEPS1
-rw-r--r--media/base/media_switches.cc3
-rw-r--r--media/base/media_switches.h2
-rw-r--r--media/base/video_decoder_config.h8
-rw-r--r--media/ffmpeg/ffmpeg_common.cc25
-rw-r--r--media/filters/vpx_video_decoder.cc341
-rw-r--r--media/filters/vpx_video_decoder.h86
-rw-r--r--media/media.gyp16
8 files changed, 477 insertions, 5 deletions
diff --git a/media/DEPS b/media/DEPS
index a72ca3e..9a92ab7 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -1,6 +1,7 @@
include_rules = [
"+jni",
"+third_party/ffmpeg",
+ "+third_party/libvpx",
"+third_party/openmax",
"+third_party/opus",
"+third_party/skia",
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index f2950e1..acd696e 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -60,4 +60,7 @@ const char kEnableEncryptedMedia[] = "enable-encrypted-media";
// Enables Opus playback in media elements.
const char kEnableOpusPlayback[] = "enable-opus-playback";
+// Enables VP9 playback in media elements.
+const char kEnableVp9Playback[] = "enable-vp9-playback";
+
} // namespace switches
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 21c5879..435ca36 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -47,6 +47,8 @@ MEDIA_EXPORT extern const char kEnableEncryptedMedia[];
MEDIA_EXPORT extern const char kEnableOpusPlayback[];
+MEDIA_EXPORT extern const char kEnableVp9Playback[];
+
} // namespace switches
#endif // MEDIA_BASE_MEDIA_SWITCHES_H_
diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h
index b5ac792..958192c 100644
--- a/media/base/video_decoder_config.h
+++ b/media/base/video_decoder_config.h
@@ -27,12 +27,13 @@ enum VideoCodec {
kCodecMPEG4,
kCodecTheora,
kCodecVP8,
+ kCodecVP9,
// DO NOT ADD RANDOM VIDEO CODECS!
//
// The only acceptable time to add a new codec is if there is production code
// that uses said codec in the same CL.
- kVideoCodecMax = kCodecVP8 // Must equal the last "real" codec above.
+ kVideoCodecMax = kCodecVP9 // Must equal the last "real" codec above.
};
// Video stream profile. This *must* match PP_VideoDecoder_Profile.
@@ -58,7 +59,10 @@ enum VideoCodecProfile {
VP8PROFILE_MIN = 11,
VP8PROFILE_MAIN = VP8PROFILE_MIN,
VP8PROFILE_MAX = VP8PROFILE_MAIN,
- VIDEO_CODEC_PROFILE_MAX = VP8PROFILE_MAX,
+ VP9PROFILE_MIN = 12,
+ VP9PROFILE_MAIN = VP9PROFILE_MIN,
+ VP9PROFILE_MAX = VP9PROFILE_MAIN,
+ VIDEO_CODEC_PROFILE_MAX = VP9PROFILE_MAX,
};
class MEDIA_EXPORT VideoDecoderConfig {
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index 8b81e01..2ae0b1b 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -147,6 +147,8 @@ VideoCodec CodecIDToVideoCodec(CodecID codec_id) {
return kCodecMPEG4;
case CODEC_ID_VP8:
return kCodecVP8;
+ case AV_CODEC_ID_VP9:
+ return kCodecVP9;
default:
DVLOG(1) << "Unknown video CodecID: " << codec_id;
}
@@ -163,6 +165,8 @@ static CodecID VideoCodecToCodecID(VideoCodec video_codec) {
return CODEC_ID_MPEG4;
case kCodecVP8:
return CODEC_ID_VP8;
+ case kCodecVP9:
+ return AV_CODEC_ID_VP9;
default:
DVLOG(1) << "Unknown VideoCodec: " << video_codec;
}
@@ -335,13 +339,28 @@ void AVStreamToVideoDecoderConfig(
aspect_ratio = stream->codec->sample_aspect_ratio;
VideoCodec codec = CodecIDToVideoCodec(stream->codec->codec_id);
- VideoCodecProfile profile = (codec == kCodecVP8) ? VP8PROFILE_MAIN :
- ProfileIDToVideoCodecProfile(stream->codec->profile);
+
+ VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
+ if (codec == kCodecVP8)
+ profile = VP8PROFILE_MAIN;
+ else if (codec == kCodecVP9)
+ profile = VP9PROFILE_MAIN;
+ else
+ profile = ProfileIDToVideoCodecProfile(stream->codec->profile);
+
gfx::Size natural_size = GetNaturalSize(
visible_rect.size(), aspect_ratio.num, aspect_ratio.den);
+
+ VideoFrame::Format format = PixelFormatToVideoFormat(stream->codec->pix_fmt);
+ if (codec == kCodecVP9) {
+ // TODO(tomfinegan): libavcodec doesn't know about VP9.
+ format = VideoFrame::YV12;
+ coded_size = natural_size;
+ }
+
config->Initialize(codec,
profile,
- PixelFormatToVideoFormat(stream->codec->pix_fmt),
+ format,
coded_size, visible_rect, natural_size,
stream->codec->extradata, stream->codec->extradata_size,
false, // Not encrypted.
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc
new file mode 100644
index 0000000..0b92a7d
--- /dev/null
+++ b/media/filters/vpx_video_decoder.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/filters/vpx_video_decoder.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop_proxy.h"
+#include "base/string_number_conversions.h"
+#include "media/base/bind_to_loop.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/demuxer_stream.h"
+#include "media/base/media_switches.h"
+#include "media/base/pipeline.h"
+#include "media/base/video_decoder_config.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+
+// Include libvpx header files.
+// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide
+// backwards compatibility for legacy applications using the library.
+#define VPX_CODEC_DISABLE_COMPAT 1
+extern "C" {
+#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h"
+#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
+}
+
+namespace media {
+
+// Always try to use three threads for video decoding. There is little reason
+// not to since current day CPUs tend to be multi-core and we measured
+// performance benefits on older machines such as P4s with hyperthreading.
+static const int kDecodeThreads = 2;
+static const int kMaxDecodeThreads = 16;
+
+// Returns the number of threads.
+static int GetThreadCount() {
+ // TODO(scherkus): De-duplicate this function and the one used by
+ // FFmpegVideoDecoder.
+
+ // Refer to http://crbug.com/93932 for tsan suppressions on decoding.
+ int decode_threads = kDecodeThreads;
+
+ const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads));
+ if (threads.empty() || !base::StringToInt(threads, &decode_threads))
+ return decode_threads;
+
+ decode_threads = std::max(decode_threads, 0);
+ decode_threads = std::min(decode_threads, kMaxDecodeThreads);
+ return decode_threads;
+}
+
+VpxVideoDecoder::VpxVideoDecoder(
+ const scoped_refptr<base::MessageLoopProxy>& message_loop)
+ : message_loop_(message_loop),
+ state_(kUninitialized),
+ vpx_codec_(NULL) {
+}
+
+VpxVideoDecoder::~VpxVideoDecoder() {
+ DCHECK_EQ(kUninitialized, state_);
+ CloseDecoder();
+}
+
+void VpxVideoDecoder::Initialize(
+ const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb,
+ const StatisticsCB& statistics_cb) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK(!demuxer_stream_) << "Already initialized.";
+
+ if (!stream) {
+ status_cb.Run(PIPELINE_ERROR_DECODE);
+ return;
+ }
+
+ demuxer_stream_ = stream;
+ statistics_cb_ = statistics_cb;
+
+ if (!ConfigureDecoder()) {
+ status_cb.Run(DECODER_ERROR_NOT_SUPPORTED);
+ return;
+ }
+
+ // Success!
+ state_ = kNormal;
+ status_cb.Run(PIPELINE_OK);
+}
+
+bool VpxVideoDecoder::ConfigureDecoder() {
+ const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config();
+ if (!config.IsValidConfig()) {
+ DLOG(ERROR) << "Invalid video stream config: "
+ << config.AsHumanReadableString();
+ return false;
+ }
+
+ if (config.codec() != kCodecVP9)
+ return false;
+
+ CloseDecoder();
+
+ vpx_codec_ = new vpx_codec_ctx();
+ vpx_codec_dec_cfg_t vpx_config = {0};
+ vpx_config.w = config.coded_size().width();
+ vpx_config.h = config.coded_size().height();
+ vpx_config.threads = GetThreadCount();
+
+ vpx_codec_err_t status = vpx_codec_dec_init(vpx_codec_,
+ vpx_codec_vp9_dx(),
+ &vpx_config,
+ 0);
+ if (status != VPX_CODEC_OK) {
+ LOG(ERROR) << "vpx_codec_dec_init failed, status=" << status;
+ delete vpx_codec_;
+ vpx_codec_ = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+void VpxVideoDecoder::CloseDecoder() {
+ if (vpx_codec_) {
+ vpx_codec_destroy(vpx_codec_);
+ delete vpx_codec_;
+ vpx_codec_ = NULL;
+ }
+}
+
+void VpxVideoDecoder::Read(const ReadCB& read_cb) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK(!read_cb.is_null());
+ CHECK_NE(state_, kUninitialized);
+ CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported.";
+ read_cb_ = BindToCurrentLoop(read_cb);
+
+ // Return empty frames if decoding has finished.
+ if (state_ == kDecodeFinished) {
+ read_cb.Run(kOk, VideoFrame::CreateEmptyFrame());
+ return;
+ }
+
+ ReadFromDemuxerStream();
+}
+
+void VpxVideoDecoder::Reset(const base::Closure& closure) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK(reset_cb_.is_null());
+ reset_cb_ = BindToCurrentLoop(closure);
+
+ // Defer the reset if a read is pending.
+ if (!read_cb_.is_null())
+ return;
+
+ DoReset();
+}
+
+void VpxVideoDecoder::Stop(const base::Closure& closure) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+
+ if (state_ == kUninitialized) {
+ closure.Run();
+ return;
+ }
+
+ if (!read_cb_.is_null())
+ base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
+
+ state_ = kUninitialized;
+ closure.Run();
+}
+
+void VpxVideoDecoder::ReadFromDemuxerStream() {
+ DCHECK_NE(state_, kUninitialized);
+ DCHECK_NE(state_, kDecodeFinished);
+ DCHECK(!read_cb_.is_null());
+
+ demuxer_stream_->Read(base::Bind(
+ &VpxVideoDecoder::DoDecryptOrDecodeBuffer, this));
+}
+
+void VpxVideoDecoder::DoDecryptOrDecodeBuffer(
+ DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& buffer) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK_NE(state_, kDecodeFinished);
+ DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status;
+
+ if (state_ == kUninitialized)
+ return;
+
+ DCHECK(!read_cb_.is_null());
+
+ if (!reset_cb_.is_null()) {
+ base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
+ DoReset();
+ return;
+ }
+
+ if (status == DemuxerStream::kAborted) {
+ base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
+ return;
+ }
+
+ if (status == DemuxerStream::kConfigChanged) {
+ if (!ConfigureDecoder()) {
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ ReadFromDemuxerStream();
+ return;
+ }
+
+ DCHECK_EQ(status, DemuxerStream::kOk);
+ DecodeBuffer(buffer);
+}
+
+void VpxVideoDecoder::DecodeBuffer(
+ const scoped_refptr<DecoderBuffer>& buffer) {
+ DCHECK(message_loop_->BelongsToCurrentThread());
+ DCHECK_NE(state_, kUninitialized);
+ DCHECK_NE(state_, kDecodeFinished);
+ DCHECK(reset_cb_.is_null());
+ DCHECK(!read_cb_.is_null());
+ DCHECK(buffer);
+
+ // Transition to kDecodeFinished on the first end of stream buffer.
+ if (state_ == kNormal && buffer->IsEndOfStream()) {
+ state_ = kDecodeFinished;
+ base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame());
+ return;
+ }
+
+ scoped_refptr<VideoFrame> video_frame;
+ if (!Decode(buffer, &video_frame)) {
+ state_ = kDecodeFinished;
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ // Any successful decode counts!
+ if (buffer->GetDataSize()) {
+ PipelineStatistics statistics;
+ statistics.video_bytes_decoded = buffer->GetDataSize();
+ statistics_cb_.Run(statistics);
+ }
+
+ // If we didn't get a frame we need more data.
+ if (!video_frame) {
+ ReadFromDemuxerStream();
+ return;
+ }
+
+ base::ResetAndReturn(&read_cb_).Run(kOk, video_frame);
+}
+
+bool VpxVideoDecoder::Decode(
+ const scoped_refptr<DecoderBuffer>& buffer,
+ scoped_refptr<VideoFrame>* video_frame) {
+ DCHECK(video_frame);
+ DCHECK(!buffer->IsEndOfStream());
+
+ // Pass |buffer| to libvpx.
+ int64 timestamp = buffer->GetTimestamp().InMicroseconds();
+ void* user_priv = reinterpret_cast<void*>(&timestamp);
+ vpx_codec_err_t status = vpx_codec_decode(vpx_codec_,
+ buffer->GetData(),
+ buffer->GetDataSize(),
+ user_priv,
+ 0);
+ if (status != VPX_CODEC_OK) {
+ LOG(ERROR) << "vpx_codec_decode() failed, status=" << status;
+ return false;
+ }
+
+ // Gets pointer to decoded data.
+ vpx_codec_iter_t iter = NULL;
+ const vpx_image_t* vpx_image = vpx_codec_get_frame(vpx_codec_, &iter);
+ if (!vpx_image) {
+ *video_frame = NULL;
+ return true;
+ }
+
+ if (vpx_image->user_priv != reinterpret_cast<void*>(&timestamp)) {
+ LOG(ERROR) << "Invalid output timestamp.";
+ return false;
+ }
+
+ CopyVpxImageTo(vpx_image, video_frame);
+ (*video_frame)->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp));
+ return true;
+}
+
+void VpxVideoDecoder::DoReset() {
+ DCHECK(read_cb_.is_null());
+
+ state_ = kNormal;
+ reset_cb_.Run();
+ reset_cb_.Reset();
+}
+
+void VpxVideoDecoder::CopyVpxImageTo(
+ const vpx_image* vpx_image,
+ scoped_refptr<VideoFrame>* video_frame) {
+ CHECK(vpx_image);
+ CHECK_EQ(vpx_image->d_w % 2, 0U);
+ CHECK_EQ(vpx_image->d_h % 2, 0U);
+ CHECK(vpx_image->fmt == VPX_IMG_FMT_I420 ||
+ vpx_image->fmt == VPX_IMG_FMT_YV12);
+
+ gfx::Size size(vpx_image->d_w, vpx_image->d_h);
+ gfx::Size natural_size =
+ demuxer_stream_->video_decoder_config().natural_size();
+
+ *video_frame = VideoFrame::CreateFrame(VideoFrame::YV12,
+ size,
+ gfx::Rect(size),
+ natural_size,
+ kNoTimestamp());
+ CopyYPlane(vpx_image->planes[VPX_PLANE_Y],
+ vpx_image->stride[VPX_PLANE_Y],
+ vpx_image->d_h,
+ *video_frame);
+ CopyUPlane(vpx_image->planes[VPX_PLANE_U],
+ vpx_image->stride[VPX_PLANE_U],
+ vpx_image->d_h / 2,
+ *video_frame);
+ CopyVPlane(vpx_image->planes[VPX_PLANE_V],
+ vpx_image->stride[VPX_PLANE_V],
+ vpx_image->d_h / 2,
+ *video_frame);
+}
+
+} // namespace media
diff --git a/media/filters/vpx_video_decoder.h b/media/filters/vpx_video_decoder.h
new file mode 100644
index 0000000..77578fd
--- /dev/null
+++ b/media/filters/vpx_video_decoder.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FILTERS_VPX_VIDEO_DECODER_H_
+#define MEDIA_FILTERS_VPX_VIDEO_DECODER_H_
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/demuxer_stream.h"
+#include "media/base/video_decoder.h"
+
+struct vpx_codec_ctx;
+struct vpx_image;
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace media {
+
+class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder {
+ public:
+ explicit VpxVideoDecoder(
+ const scoped_refptr<base::MessageLoopProxy>& message_loop);
+
+ // VideoDecoder implementation.
+ virtual void Initialize(const scoped_refptr<DemuxerStream>& stream,
+ const PipelineStatusCB& status_cb,
+ const StatisticsCB& statistics_cb) OVERRIDE;
+ virtual void Read(const ReadCB& read_cb) OVERRIDE;
+ virtual void Reset(const base::Closure& closure) OVERRIDE;
+ virtual void Stop(const base::Closure& closure) OVERRIDE;
+
+ protected:
+ virtual ~VpxVideoDecoder();
+
+ private:
+ enum DecoderState {
+ kUninitialized,
+ kNormal,
+ kFlushCodec,
+ kDecodeFinished
+ };
+
+ // Handles (re-)initializing the decoder with a (new) config.
+ // Returns true when initialization was successful.
+ bool ConfigureDecoder();
+
+ void CloseDecoder();
+ void ReadFromDemuxerStream();
+
+ // Carries out the buffer processing operation scheduled by
+ // DecryptOrDecodeBuffer().
+ void DoDecryptOrDecodeBuffer(DemuxerStream::Status status,
+ const scoped_refptr<DecoderBuffer>& buffer);
+
+ void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer);
+ bool Decode(const scoped_refptr<DecoderBuffer>& buffer,
+ scoped_refptr<VideoFrame>* video_frame);
+
+ // Reset decoder and call |reset_cb_|.
+ void DoReset();
+
+ void CopyVpxImageTo(const vpx_image* vpx_image,
+ scoped_refptr<VideoFrame>* video_frame);
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_;
+
+ DecoderState state_;
+
+ StatisticsCB statistics_cb_;
+ ReadCB read_cb_;
+ base::Closure reset_cb_;
+
+ // Pointer to the demuxer stream that will feed us compressed buffers.
+ scoped_refptr<DemuxerStream> demuxer_stream_;
+
+ vpx_codec_ctx* vpx_codec_;
+
+ DISALLOW_COPY_AND_ASSIGN(VpxVideoDecoder);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_VPX_VIDEO_DECODER_H_
diff --git a/media/media.gyp b/media/media.gyp
index 78f949c..8b33bd8 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -13,8 +13,11 @@
['OS == "android" or OS == "ios"', {
# Android and iOS don't use ffmpeg.
'use_ffmpeg%': 0,
+ # Android and iOS don't use libvpx.
+ 'use_libvpx%': 0,
}, { # 'OS != "android" and OS != "ios"'
'use_ffmpeg%': 1,
+ 'use_libvpx%': 1,
}],
],
},
@@ -313,6 +316,8 @@
'filters/video_frame_generator.h',
'filters/video_renderer_base.cc',
'filters/video_renderer_base.h',
+ 'filters/vpx_video_decoder.cc',
+ 'filters/vpx_video_decoder.h',
'video/capture/fake_video_capture_device.cc',
'video/capture/fake_video_capture_device.h',
'video/capture/linux/video_capture_device_linux.cc',
@@ -413,6 +418,17 @@
'webm/webm_stream_parser.h',
],
}],
+ ['use_libvpx == 1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx',
+ ],
+ }, { # use_libvpx == 0
+ # Exclude the sources that depend on libvpx.
+ 'sources!': [
+ 'filters/vpx_video_decoder.cc',
+ 'filters/vpx_video_decoder.h',
+ ],
+ }],
['OS == "ios"', {
'includes': [
# For shared_memory_support_sources variable.