summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2013-08-12 14:20:17 +0100
committerBen Murdoch <benm@google.com>2013-08-12 14:20:17 +0100
commitba5b9a6411cb1792fd21f0a078d7a25cd1ceec16 (patch)
treeaa3b1013e823cb7bdee9ece936928292f57b31f4 /media
parentf7fa989080f1e63c6a8aa24d5434922d52d9f51e (diff)
downloadchromium_org-ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16.tar.gz
Merge from Chromium at DEPS revision r216972
This commit was generated by merge_to_master.py. Change-Id: I01cb28d94e3fcf99e3624d75cafa50d929787ddd
Diffstat (limited to 'media')
-rw-r--r--media/audio/OWNERS3
-rw-r--r--media/audio/audio_manager_base.cc15
-rw-r--r--media/audio/audio_power_monitor.cc3
-rw-r--r--media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java25
-rw-r--r--media/base/android/media_player_bridge.cc10
-rw-r--r--media/base/android/webaudio_media_codec_bridge.cc25
-rw-r--r--media/base/android/webaudio_media_codec_bridge.h4
-rw-r--r--media/base/media_file_checker.cc110
-rw-r--r--media/base/media_file_checker.h41
-rw-r--r--media/base/media_file_checker_unittest.cc48
-rw-r--r--media/base/media_switches.cc6
-rw-r--r--media/base/media_switches.h1
-rw-r--r--media/base/video_frame.cc69
-rw-r--r--media/base/video_frame.h18
-rw-r--r--media/base/video_util.cc3
-rw-r--r--media/cdm/DEPS (renamed from media/crypto/DEPS)0
-rw-r--r--media/cdm/aes_decryptor.cc (renamed from media/crypto/aes_decryptor.cc)4
-rw-r--r--media/cdm/aes_decryptor.h (renamed from media/crypto/aes_decryptor.h)2
-rw-r--r--media/cdm/aes_decryptor_unittest.cc (renamed from media/crypto/aes_decryptor_unittest.cc)4
-rw-r--r--media/cdm/ppapi/DEPS7
-rw-r--r--media/cdm/ppapi/cdm_video_decoder.cc56
-rw-r--r--media/cdm/ppapi/cdm_video_decoder.h43
-rw-r--r--media/cdm/ppapi/cdm_wrapper.cc1198
-rw-r--r--media/cdm/ppapi/clear_key_cdm.cc558
-rw-r--r--media/cdm/ppapi/clear_key_cdm.h166
-rw-r--r--media/cdm/ppapi/fake_cdm_video_decoder.cc91
-rw-r--r--media/cdm/ppapi/fake_cdm_video_decoder.h41
-rw-r--r--media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc412
-rw-r--r--media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h99
-rw-r--r--media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc344
-rw-r--r--media/cdm/ppapi/ffmpeg_cdm_video_decoder.h58
-rw-r--r--media/cdm/ppapi/libvpx_cdm_video_decoder.cc195
-rw-r--r--media/cdm/ppapi/libvpx_cdm_video_decoder.h55
-rw-r--r--media/cdm/ppapi/linked_ptr.h185
-rw-r--r--media/filters/file_data_source.cc11
-rw-r--r--media/filters/file_data_source.h2
-rw-r--r--media/filters/gpu_video_decoder.cc2
-rw-r--r--media/filters/pipeline_integration_test.cc2
-rw-r--r--media/media.gyp18
-rw-r--r--media/media.target.darwin-arm.mk4
-rw-r--r--media/media.target.darwin-mips.mk4
-rw-r--r--media/media.target.darwin-x86.mk4
-rw-r--r--media/media.target.linux-arm.mk4
-rw-r--r--media/media.target.linux-mips.mk4
-rw-r--r--media/media.target.linux-x86.mk4
-rw-r--r--media/media_cdm.gypi136
-rw-r--r--media/video/capture/win/video_capture_device_win.cc6
47 files changed, 4046 insertions, 54 deletions
diff --git a/media/audio/OWNERS b/media/audio/OWNERS
index b0a755874b..17c8eccedb 100644
--- a/media/audio/OWNERS
+++ b/media/audio/OWNERS
@@ -3,9 +3,6 @@ tommi@chromium.org
# Linux/Pulse
xians@chromium.org
-# OSX
-crogers@google.com
-
# Windows
henrika@chromium.org
diff --git a/media/audio/audio_manager_base.cc b/media/audio/audio_manager_base.cc
index 7ffc7d311b..db77f004e3 100644
--- a/media/audio/audio_manager_base.cc
+++ b/media/audio/audio_manager_base.cc
@@ -269,18 +269,15 @@ AudioOutputStream* AudioManagerBase::MakeAudioOutputStreamProxy(
const base::TimeDelta kCloseDelay =
base::TimeDelta::FromSeconds(kStreamCloseDelaySeconds);
-
+ scoped_refptr<AudioOutputDispatcher> dispatcher;
if (output_params.format() != AudioParameters::AUDIO_FAKE) {
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputResampler(this, params, output_params, input_device_id,
- kCloseDelay);
- dispatcher_params->dispatcher = dispatcher;
- return new AudioOutputProxy(dispatcher.get());
+ dispatcher = new AudioOutputResampler(this, params, output_params,
+ input_device_id, kCloseDelay);
+ } else {
+ dispatcher = new AudioOutputDispatcherImpl(this, output_params,
+ input_device_id, kCloseDelay);
}
- scoped_refptr<AudioOutputDispatcher> dispatcher =
- new AudioOutputDispatcherImpl(this, output_params, input_device_id,
- kCloseDelay);
dispatcher_params->dispatcher = dispatcher;
output_dispatchers_.push_back(dispatcher_params);
return new AudioOutputProxy(dispatcher.get());
diff --git a/media/audio/audio_power_monitor.cc b/media/audio/audio_power_monitor.cc
index 4dc151708d..fa76828cc4 100644
--- a/media/audio/audio_power_monitor.cc
+++ b/media/audio/audio_power_monitor.cc
@@ -84,7 +84,8 @@ void AudioPowerMonitor::Scan(const AudioBus& buffer, int num_frames) {
(average_power_ < kInsignificantPower) ? 0.0f : average_power_;
if (power != last_reported_power_ ||
clipped_since_last_notification_ != last_reported_clipped_) {
- const float power_dbfs = 10.0f * log10f(power);
+ const float power_dbfs =
+ power > 0.0f ? 10.0f * log10f(power) : zero_power();
// Try to post a task to run the callback with the dBFS result. The
// posting of the task is guaranteed to be non-blocking, and therefore
// could fail. However, in the common case, failures should be rare (and
diff --git a/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
index ac752d4d06..1de7e42b8d 100644
--- a/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
+++ b/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
@@ -61,7 +61,14 @@ class WebAudioMediaCodecBridge {
MediaFormat format = extractor.getTrackFormat(0);
- int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ // Number of channels specified in the file
+ int inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+
+ // Number of channels the decoder will provide. (Not
+ // necessarily the same as inputChannelCount. See
+ // crbug.com/266006.)
+ int outputChannelCount = inputChannelCount;
+
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
String mime = format.getString(MediaFormat.KEY_MIME);
@@ -77,13 +84,13 @@ class WebAudioMediaCodecBridge {
if (DEBUG) {
Log.d(LOG_TAG, "Tracks: " + extractor.getTrackCount()
+ " Rate: " + sampleRate
- + " Channels: " + channelCount
+ + " Channels: " + inputChannelCount
+ " Mime: " + mime
+ " Duration: " + durationMicroseconds + " microsec");
}
nativeInitializeDestination(nativeMediaCodecBridge,
- channelCount,
+ inputChannelCount,
sampleRate,
durationMicroseconds);
@@ -139,7 +146,8 @@ class WebAudioMediaCodecBridge {
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
if (info.size > 0) {
- nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size);
+ nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
+ inputChannelCount, outputChannelCount);
}
buf.clear();
@@ -150,6 +158,10 @@ class WebAudioMediaCodecBridge {
}
} else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
+ } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat newFormat = codec.getOutputFormat();
+ outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ Log.d(LOG_TAG, "output format changed to " + newFormat);
}
}
@@ -163,11 +175,12 @@ class WebAudioMediaCodecBridge {
}
private static native void nativeOnChunkDecoded(
- int nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size);
+ int nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size,
+ int inputChannelCount, int outputChannelCount);
private static native void nativeInitializeDestination(
int nativeWebAudioMediaCodecBridge,
- int channelCount,
+ int inputChannelCount,
int sampleRate,
long durationMicroseconds);
}
diff --git a/media/base/android/media_player_bridge.cc b/media/base/android/media_player_bridge.cc
index e0d9868827..342ceaa790 100644
--- a/media/base/android/media_player_bridge.cc
+++ b/media/base/android/media_player_bridge.cc
@@ -390,6 +390,16 @@ void MediaPlayerBridge::PendingSeekInternal(base::TimeDelta time) {
}
void MediaPlayerBridge::SeekInternal(base::TimeDelta time) {
+ if (time > duration_)
+ time = duration_;
+
+ // Seeking to an invalid position may cause media player to stuck in an
+ // error state.
+ if (time < base::TimeDelta()) {
+ DCHECK_EQ(-1.0, time.InMillisecondsF());
+ return;
+ }
+
JNIEnv* env = base::android::AttachCurrentThread();
CHECK(env);
diff --git a/media/base/android/webaudio_media_codec_bridge.cc b/media/base/android/webaudio_media_codec_bridge.cc
index 6024a5916f..94f059a9a1 100644
--- a/media/base/android/webaudio_media_codec_bridge.cc
+++ b/media/base/android/webaudio_media_codec_bridge.cc
@@ -17,6 +17,7 @@
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
+#include "base/stl_util.h"
#include "jni/WebAudioMediaCodecBridge_jni.h"
#include "media/base/android/webaudio_media_codec_info.h"
@@ -148,15 +149,35 @@ void WebAudioMediaCodecBridge::OnChunkDecoded(
JNIEnv* env,
jobject /*java object*/,
jobject buf,
- jint buf_size) {
+ jint buf_size,
+ jint input_channel_count,
+ jint output_channel_count) {
if (buf_size <= 0 || !buf)
return;
int8_t* buffer =
static_cast<int8_t*>(env->GetDirectBufferAddress(buf));
-
size_t count = static_cast<size_t>(buf_size);
+ std::vector<int16_t> decoded_data;
+
+ if (input_channel_count == 1 && output_channel_count == 2) {
+ // See crbug.com/266006. The file has one channel, but the
+ // decoder decided to return two channels. To be consistent with
+ // the number of channels in the file, only send one channel (the
+ // first).
+ int16_t* data = static_cast<int16_t*>(env->GetDirectBufferAddress(buf));
+ int frame_count = buf_size / sizeof(*data) / 2;
+
+ decoded_data.resize(frame_count);
+ for (int k = 0; k < frame_count; ++k) {
+ decoded_data[k] = *data;
+ data += 2;
+ }
+ buffer = reinterpret_cast<int8_t*>(vector_as_array(&decoded_data));
+ DCHECK(buffer);
+ count = frame_count * sizeof(*data);
+ }
// Write out the data to the pipe in small chunks if necessary.
while (count > 0) {
diff --git a/media/base/android/webaudio_media_codec_bridge.h b/media/base/android/webaudio_media_codec_bridge.h
index abf7f3ff0d..fda612683f 100644
--- a/media/base/android/webaudio_media_codec_bridge.h
+++ b/media/base/android/webaudio_media_codec_bridge.h
@@ -41,7 +41,9 @@ class MEDIA_EXPORT WebAudioMediaCodecBridge {
void OnChunkDecoded(JNIEnv* env,
jobject /*java object*/,
jobject buf,
- jint buf_size);
+ jint buf_size,
+ jint input_channel_count,
+ jint output_channel_count);
void InitializeDestination(JNIEnv* env,
jobject /*java object*/,
diff --git a/media/base/media_file_checker.cc b/media/base/media_file_checker.cc
new file mode 100644
index 0000000000..d4708e506c
--- /dev/null
+++ b/media/base/media_file_checker.cc
@@ -0,0 +1,110 @@
+// Copyright 2013 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/base/media_file_checker.h"
+
+#include <map>
+
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/filters/blocking_url_protocol.h"
+#include "media/filters/ffmpeg_glue.h"
+#include "media/filters/file_data_source.h"
+
+namespace media {
+
+static const int64 kMaxCheckTimeInSeconds = 5;
+
+static void OnError(bool* called) {
+ *called = false;
+}
+
+MediaFileChecker::MediaFileChecker(const base::PlatformFile& file)
+ : file_(file),
+ file_closer_(&file_) {
+}
+
+MediaFileChecker::~MediaFileChecker() {
+}
+
+bool MediaFileChecker::Start(base::TimeDelta check_time) {
+ media::FileDataSource source;
+ bool read_ok = true;
+ media::BlockingUrlProtocol protocol(&source, base::Bind(&OnError, &read_ok));
+ media::FFmpegGlue glue(&protocol);
+ source.InitializeFromPlatformFile(file_);
+ AVFormatContext* format_context = glue.format_context();
+
+ if (!glue.OpenContext())
+ return false;
+
+ if (avformat_find_stream_info(format_context, NULL) < 0)
+ return false;
+
+ // Remember the codec context for any decodable audio or video streams.
+ std::map<int, AVCodecContext*> stream_contexts;
+ for (size_t i = 0; i < format_context->nb_streams; ++i) {
+ AVCodecContext* c = format_context->streams[i]->codec;
+ if (c->codec_type == AVMEDIA_TYPE_AUDIO ||
+ c->codec_type == AVMEDIA_TYPE_VIDEO) {
+ AVCodec* codec = avcodec_find_decoder(c->codec_id);
+ if (codec && avcodec_open2(c, codec, NULL) >= 0)
+ stream_contexts[i] = c;
+ }
+ }
+
+ if (stream_contexts.size() == 0)
+ return false;
+
+ AVPacket packet;
+ scoped_ptr_malloc<AVFrame, media::ScopedPtrAVFree> frame(
+ avcodec_alloc_frame());
+ int result = 0;
+
+ base::Time deadline = base::Time::Now() +
+ std::min(check_time,
+ base::TimeDelta::FromSeconds(kMaxCheckTimeInSeconds));
+ do {
+ result = av_read_frame(glue.format_context(), &packet);
+ if (result < 0)
+ break;
+ result = av_dup_packet(&packet);
+ if (result < 0)
+ break;
+
+ std::map<int, AVCodecContext*>::const_iterator it =
+ stream_contexts.find(packet.stream_index);
+ if (it == stream_contexts.end()) {
+ av_free_packet(&packet);
+ continue;
+ }
+ AVCodecContext* av_context = it->second;
+
+ int frame_decoded = 0;
+ if (av_context->codec_type == AVMEDIA_TYPE_AUDIO) {
+ // A shallow copy of packet so we can slide packet.data as frames are
+ // decoded; otherwise av_free_packet() will corrupt memory.
+ AVPacket temp_packet = packet;
+ do {
+ avcodec_get_frame_defaults(frame.get());
+ result = avcodec_decode_audio4(av_context, frame.get(), &frame_decoded,
+ &temp_packet);
+ if (result < 0)
+ break;
+ temp_packet.size -= result;
+ temp_packet.data += result;
+ } while (temp_packet.size > 0);
+ } else if (av_context->codec_type == AVMEDIA_TYPE_VIDEO) {
+ avcodec_get_frame_defaults(frame.get());
+ result = avcodec_decode_video2(av_context, frame.get(), &frame_decoded,
+ &packet);
+ }
+ av_free_packet(&packet);
+ } while (base::Time::Now() < deadline && read_ok && result >= 0);
+
+ return read_ok && (result == AVERROR_EOF || result >= 0);
+}
+
+} // namespace media
diff --git a/media/base/media_file_checker.h b/media/base/media_file_checker.h
new file mode 100644
index 0000000000..6e8fc9f285
--- /dev/null
+++ b/media/base/media_file_checker.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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_BASE_MEDIA_FILE_CHECKER_H_
+#define MEDIA_BASE_MEDIA_FILE_CHECKER_H_
+
+#include "base/basictypes.h"
+#include "base/files/scoped_platform_file_closer.h"
+#include "base/platform_file.h"
+#include "media/base/media_export.h"
+
+namespace base {
+class TimeDelta;
+}
+
+namespace media {
+
+// This class tries to determine if a file is a valid media file. The entire
+// file is not decoded so a positive result from this class does not make the
+// file safe to use in the browser process.
+class MEDIA_EXPORT MediaFileChecker {
+ public:
+ explicit MediaFileChecker(const base::PlatformFile& file);
+ ~MediaFileChecker();
+
+ // After opening |file|, up to |check_time| amount of wall-clock time is spent
+ // decoding the file. The amount of audio/video data decoded will depend on
+ // the bitrate of the file and the speed of the CPU.
+ bool Start(base::TimeDelta check_time);
+
+ private:
+ base::PlatformFile file_;
+ base::ScopedPlatformFileCloser file_closer_;
+
+ DISALLOW_COPY_AND_ASSIGN(MediaFileChecker);
+};
+
+} // namespace media
+
+#endif // MEDIA_BASE_MEDIA_FILE_CHECKER_H_
diff --git a/media/base/media_file_checker_unittest.cc b/media/base/media_file_checker_unittest.cc
new file mode 100644
index 0000000000..f43c846776
--- /dev/null
+++ b/media/base/media_file_checker_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright 2013 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 "base/logging.h"
+#include "build/build_config.h"
+#include "media/base/media_file_checker.h"
+#include "media/base/test_data_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+static void RunMediaFileChecker(const std::string& filename, bool expectation) {
+ base::PlatformFileError error;
+ base::PlatformFile file = base::CreatePlatformFile(
+ GetTestDataFilePath(filename),
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
+ NULL,
+ &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+
+ MediaFileChecker checker(file);
+ const base::TimeDelta check_time = base::TimeDelta::FromMilliseconds(100);
+ bool result = checker.Start(check_time);
+ EXPECT_EQ(expectation, result);
+
+ base::ClosePlatformFile(file);
+}
+
+TEST(MediaFileCheckerTest, InvalidFile) {
+ RunMediaFileChecker("ten_byte_file", false);
+}
+
+TEST(MediaFileCheckerTest, Video) {
+ RunMediaFileChecker("bear.ogv", true);
+}
+
+TEST(MediaFileCheckerTest, Audio) {
+ RunMediaFileChecker("sfx.ogg", true);
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+TEST(MediaFileCheckerTest, MP3) {
+ RunMediaFileChecker("sfx.mp3", true);
+}
+#endif
+
+} // namespace media
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 153c22a4c3..3c1dc169d9 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -57,6 +57,12 @@ const char kDisableMainThreadAudio[] = "disable-main-thread-audio";
// for details.
const char kEnableExclusiveAudio[] = "enable-exclusive-audio";
+// Used to troubleshoot problems with different video capture implementations
+// on Windows. By default we use the Media Foundation API on Windows 7 and up,
+// but specifying this switch will force use of DirectShow always.
+// See bug: http://crbug.com/268412
+const char kForceDirectShowVideoCapture[] = "force-directshow";
+
// Use Windows WaveOut/In audio API even if Core Audio is supported.
const char kForceWaveAudio[] = "force-wave-audio";
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 0462ada9c4..885839de17 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -39,6 +39,7 @@ MEDIA_EXPORT extern const char kDisableMainThreadAudio[];
#if defined(OS_WIN)
MEDIA_EXPORT extern const char kEnableExclusiveAudio[];
+MEDIA_EXPORT extern const char kForceDirectShowVideoCapture[];
MEDIA_EXPORT extern const char kForceWaveAudio[];
MEDIA_EXPORT extern const char kWaveOutBuffers[];
#endif
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 10ad0511ff..08e7e1ad7a 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -34,6 +34,7 @@ scoped_refptr<VideoFrame> VideoFrame::CreateFrame(
case VideoFrame::YV12:
case VideoFrame::YV12A:
case VideoFrame::YV16:
+ case VideoFrame::I420:
frame->AllocateYUV();
break;
default:
@@ -117,20 +118,52 @@ void VideoFrame::ReadPixelsFromNativeTexture(const SkBitmap& pixels) {
}
// static
+scoped_refptr<VideoFrame> VideoFrame::WrapExternalSharedMemory(
+ Format format,
+ const gfx::Size& coded_size,
+ const gfx::Rect& visible_rect,
+ const gfx::Size& natural_size,
+ uint8* data,
+ base::SharedMemoryHandle handle,
+ base::TimeDelta timestamp,
+ const base::Closure& no_longer_needed_cb) {
+ switch (format) {
+ case I420: {
+ scoped_refptr<VideoFrame> frame(new VideoFrame(
+ format, coded_size, visible_rect, natural_size, timestamp));
+ frame->shared_memory_handle_ = handle;
+ frame->strides_[kYPlane] = coded_size.width();
+ frame->strides_[kUPlane] = coded_size.width() / 2;
+ frame->strides_[kVPlane] = coded_size.width() / 2;
+ frame->data_[kYPlane] = data;
+ frame->data_[kUPlane] = data + coded_size.GetArea();
+ frame->data_[kVPlane] = data + (coded_size.GetArea() * 5 / 4);
+ frame->no_longer_needed_cb_ = no_longer_needed_cb;
+ return frame;
+ }
+ default:
+ NOTIMPLEMENTED();
+ return NULL;
+ }
+}
+
+// static
scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
Format format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
- int32 y_stride, int32 u_stride, int32 v_stride,
- uint8* y_data, uint8* u_data, uint8* v_data,
+ int32 y_stride,
+ int32 u_stride,
+ int32 v_stride,
+ uint8* y_data,
+ uint8* u_data,
+ uint8* v_data,
base::TimeDelta timestamp,
- base::SharedMemoryHandle shm_handle,
const base::Closure& no_longer_needed_cb) {
DCHECK(format == YV12 || format == YV16 || format == I420) << format;
scoped_refptr<VideoFrame> frame(new VideoFrame(
format, coded_size, visible_rect, natural_size, timestamp));
- frame->shared_memory_handle_ = shm_handle;
frame->strides_[kYPlane] = y_stride;
frame->strides_[kUPlane] = u_stride;
frame->strides_[kVPlane] = v_stride;
@@ -198,11 +231,11 @@ size_t VideoFrame::NumPlanes(Format format) {
return 1;
case VideoFrame::YV12:
case VideoFrame::YV16:
+ case VideoFrame::I420:
return 3;
case VideoFrame::YV12A:
return 4;
case VideoFrame::EMPTY:
- case VideoFrame::I420:
case VideoFrame::INVALID:
break;
}
@@ -239,7 +272,7 @@ void VideoFrame::AllocateRGB(size_t bytes_per_pixel) {
void VideoFrame::AllocateYUV() {
DCHECK(format_ == VideoFrame::YV12 || format_ == VideoFrame::YV16 ||
- format_ == VideoFrame::YV12A);
+ format_ == VideoFrame::YV12A || format_ == VideoFrame::I420);
// Align Y rows at least at 16 byte boundaries. The stride for both
// YV12 and YV16 is 1/2 of the stride of Y. For YV12, every row of bytes for
// U and V applies to two rows of Y (one byte of UV for 4 bytes of Y), so in
@@ -259,9 +292,11 @@ void VideoFrame::AllocateYUV() {
// and then the size needs to be a multiple of two macroblocks (vertically).
// See libavcodec/utils.c:avcodec_align_dimensions2().
size_t y_height = RoundUp(coded_size_.height(), kFrameSizeAlignment * 2);
- size_t uv_height = (format_ == VideoFrame::YV12 ||
- format_ == VideoFrame::YV12A) ?
- y_height / 2 : y_height;
+ size_t uv_height =
+ (format_ == VideoFrame::YV12 || format_ == VideoFrame::YV12A ||
+ format_ == VideoFrame::I420)
+ ? y_height / 2
+ : y_height;
size_t y_bytes = y_height * y_stride;
size_t uv_bytes = uv_height * uv_stride;
size_t a_bytes = format_ == VideoFrame::YV12A ? y_bytes : 0;
@@ -327,10 +362,14 @@ int VideoFrame::row_bytes(size_t plane) const {
return width * 4;
// Planar, 8bpp.
+ case YV12A:
+ if (plane == kAPlane)
+ return width;
+ // Fallthrough.
case YV12:
case YV16:
- case YV12A:
- if (plane == kYPlane || plane == kAPlane)
+ case I420:
+ if (plane == kYPlane)
return width;
return RoundUp(width, 2) / 2;
@@ -351,9 +390,13 @@ int VideoFrame::rows(size_t plane) const {
case YV16:
return height;
- case YV12:
case YV12A:
- if (plane == kYPlane || plane == kAPlane)
+ if (plane == kAPlane)
+ return height;
+ // Fallthrough.
+ case YV12:
+ case I420:
+ if (plane == kYPlane)
return height;
return RoundUp(height, 2) / 2;
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index 8207d9be1d..82a08a986d 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -137,9 +137,26 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
// least as large as 4*visible_rect().width()*visible_rect().height().
void ReadPixelsFromNativeTexture(const SkBitmap& pixels);
+ // Wraps image data in a buffer backed by a base::SharedMemoryHandle with a
+ // VideoFrame. The image data resides in |data| and is assumed to be packed
+ // tightly in a buffer of logical dimensions |coded_size| with the appropriate
+ // bit depth and plane count as given by |format|. When the frame is
+ // destroyed |no_longer_needed_cb.Run()| will be called.
+ static scoped_refptr<VideoFrame> WrapExternalSharedMemory(
+ Format format,
+ const gfx::Size& coded_size,
+ const gfx::Rect& visible_rect,
+ const gfx::Size& natural_size,
+ uint8* data,
+ base::SharedMemoryHandle handle,
+ base::TimeDelta timestamp,
+ const base::Closure& no_longer_needed_cb);
+
// Wraps external YUV data of the given parameters with a VideoFrame.
// The returned VideoFrame does not own the data passed in. When the frame
// is destroyed |no_longer_needed_cb.Run()| will be called.
+ // TODO(sheu): merge this into WrapExternalSharedMemory().
+ // http://crbug.com/270217
static scoped_refptr<VideoFrame> WrapExternalYuvData(
Format format,
const gfx::Size& coded_size,
@@ -152,7 +169,6 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> {
uint8* u_data,
uint8* v_data,
base::TimeDelta timestamp,
- base::SharedMemoryHandle shm_handle, // may be NULLHandle()
const base::Closure& no_longer_needed_cb);
// Creates a frame with format equals to VideoFrame::EMPTY, width, height,
diff --git a/media/base/video_util.cc b/media/base/video_util.cc
index 9da651fffa..fda758efec 100644
--- a/media/base/video_util.cc
+++ b/media/base/video_util.cc
@@ -143,7 +143,8 @@ void LetterboxYUV(VideoFrame* frame, const gfx::Rect& view_area) {
DCHECK(!(view_area.y() & 1));
DCHECK(!(view_area.width() & 1));
DCHECK(!(view_area.height() & 1));
- DCHECK_EQ(frame->format(), VideoFrame::YV12);
+ DCHECK(frame->format() == VideoFrame::YV12 ||
+ frame->format() == VideoFrame::I420);
LetterboxPlane(frame, VideoFrame::kYPlane, view_area, 0x00);
gfx::Rect half_view_area(view_area.x() / 2,
view_area.y() / 2,
diff --git a/media/crypto/DEPS b/media/cdm/DEPS
index 4ef4138e48..4ef4138e48 100644
--- a/media/crypto/DEPS
+++ b/media/cdm/DEPS
diff --git a/media/crypto/aes_decryptor.cc b/media/cdm/aes_decryptor.cc
index fd7c628066..da11442a37 100644
--- a/media/crypto/aes_decryptor.cc
+++ b/media/cdm/aes_decryptor.cc
@@ -1,8 +1,8 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2013 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/crypto/aes_decryptor.h"
+#include "media/cdm/aes_decryptor.h"
#include <vector>
diff --git a/media/crypto/aes_decryptor.h b/media/cdm/aes_decryptor.h
index aec55d757d..fda5a0faca 100644
--- a/media/crypto/aes_decryptor.h
+++ b/media/cdm/aes_decryptor.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2013 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.
diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc
index 8feaec4b1a..1edb8e8222 100644
--- a/media/crypto/aes_decryptor_unittest.cc
+++ b/media/cdm/aes_decryptor_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2013 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.
@@ -10,7 +10,7 @@
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "media/base/mock_filters.h"
-#include "media/crypto/aes_decryptor.h"
+#include "media/cdm/aes_decryptor.h"
#include "media/webm/webm_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/media/cdm/ppapi/DEPS b/media/cdm/ppapi/DEPS
new file mode 100644
index 0000000000..473873d766
--- /dev/null
+++ b/media/cdm/ppapi/DEPS
@@ -0,0 +1,7 @@
+# Since media/ does not depend on anything in media/cdm/ppapi/, these extra
+# dependencies are okay.
+include_rules = [
+ "+ppapi/c",
+ "+ppapi/cpp",
+ "+ppapi/utility",
+]
diff --git a/media/cdm/ppapi/cdm_video_decoder.cc b/media/cdm/ppapi/cdm_video_decoder.cc
new file mode 100644
index 0000000000..95523d4c78
--- /dev/null
+++ b/media/cdm/ppapi/cdm_video_decoder.cc
@@ -0,0 +1,56 @@
+// Copyright 2013 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/cdm/ppapi/cdm_video_decoder.h"
+
+#if defined(CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER)
+#include "media/cdm/ppapi/fake_cdm_video_decoder.h"
+#endif
+
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+#include "media/cdm/ppapi/ffmpeg_cdm_video_decoder.h"
+#endif
+
+#if defined(CLEAR_KEY_CDM_USE_LIBVPX_DECODER)
+#include "media/cdm/ppapi/libvpx_cdm_video_decoder.h"
+#endif
+
+namespace media {
+
+scoped_ptr<CdmVideoDecoder> CreateVideoDecoder(
+ cdm::Host* host, const cdm::VideoDecoderConfig& config) {
+ scoped_ptr<CdmVideoDecoder> video_decoder;
+#if defined(CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER)
+ video_decoder.reset(new FakeCdmVideoDecoder(host));
+
+ if (!video_decoder->Initialize(config))
+ video_decoder.reset();
+#else
+
+#if defined(CLEAR_KEY_CDM_USE_LIBVPX_DECODER)
+ if (config.codec == cdm::VideoDecoderConfig::kCodecVp8) {
+ video_decoder.reset(new LibvpxCdmVideoDecoder(host));
+
+ if (!video_decoder->Initialize(config))
+ video_decoder.reset();
+
+ return video_decoder.Pass();
+ }
+#endif
+
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ video_decoder.reset(new FFmpegCdmVideoDecoder(host));
+
+ if (!video_decoder->Initialize(config))
+ video_decoder.reset();
+#endif
+
+#endif // CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER
+
+ return video_decoder.Pass();
+}
+
+} // namespace media
diff --git a/media/cdm/ppapi/cdm_video_decoder.h b/media/cdm/ppapi/cdm_video_decoder.h
new file mode 100644
index 0000000000..25d48f46b9
--- /dev/null
+++ b/media/cdm/ppapi/cdm_video_decoder.h
@@ -0,0 +1,43 @@
+// Copyright 2013 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_CDM_PPAPI_CDM_VIDEO_DECODER_H_
+#define MEDIA_CDM_PPAPI_CDM_VIDEO_DECODER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+
+namespace media {
+
+class CdmVideoDecoder {
+ public:
+ virtual ~CdmVideoDecoder() {}
+ virtual bool Initialize(const cdm::VideoDecoderConfig& config) = 0;
+ virtual void Deinitialize() = 0;
+ virtual void Reset() = 0;
+ virtual bool is_initialized() const = 0;
+
+ // Decodes |compressed_frame|. Stores output frame in |decoded_frame| and
+ // returns |cdm::kSuccess| when an output frame is available. Returns
+ // |cdm::kNeedMoreData| when |compressed_frame| does not produce an output
+ // frame. Returns |cdm::kDecodeError| when decoding fails.
+ //
+ // A null |compressed_frame| will attempt to flush the decoder of any
+ // remaining frames. |compressed_frame_size| and |timestamp| are ignored.
+ virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) = 0;
+};
+
+// Initializes appropriate video decoder based on GYP flags and the value of
+// |config.codec|. Returns a scoped_ptr containing a non-null initialized
+// CdmVideoDecoder* upon success.
+scoped_ptr<CdmVideoDecoder> CreateVideoDecoder(
+ cdm::Host* host, const cdm::VideoDecoderConfig& config);
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_CDM_VIDEO_DECODER_H_
diff --git a/media/cdm/ppapi/cdm_wrapper.cc b/media/cdm/ppapi/cdm_wrapper.cc
new file mode 100644
index 0000000000..6348f1954d
--- /dev/null
+++ b/media/cdm/ppapi/cdm_wrapper.cc
@@ -0,0 +1,1198 @@
+// Copyright 2013 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 <cstring>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+#include "media/cdm/ppapi/linked_ptr.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/private/pp_content_decryptor.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/core.h"
+#include "ppapi/cpp/dev/buffer_dev.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/logging.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/pass_ref.h"
+#include "ppapi/cpp/private/content_decryptor_private.h"
+#include "ppapi/cpp/resource.h"
+#include "ppapi/cpp/var.h"
+#include "ppapi/cpp/var_array_buffer.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+#if defined(CHECK_DOCUMENT_URL)
+#include "ppapi/cpp/dev/url_util_dev.h"
+#include "ppapi/cpp/instance_handle.h"
+#endif // defined(CHECK_DOCUMENT_URL)
+
+namespace {
+
+bool IsMainThread() {
+ return pp::Module::Get()->core()->IsMainThread();
+}
+
+// Posts a task to run |cb| on the main thread. The task is posted even if the
+// current thread is the main thread.
+void PostOnMain(pp::CompletionCallback cb) {
+ pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
+}
+
+// Ensures |cb| is called on the main thread, either because the current thread
+// is the main thread or by posting it to the main thread.
+void CallOnMain(pp::CompletionCallback cb) {
+ // TODO(tomfinegan): This is only necessary because PPAPI doesn't allow calls
+ // off the main thread yet. Remove this once the change lands.
+ if (IsMainThread())
+ cb.Run(PP_OK);
+ else
+ PostOnMain(cb);
+}
+
+// Configures a cdm::InputBuffer. |subsamples| must exist as long as
+// |input_buffer| is in use.
+void ConfigureInputBuffer(
+ const pp::Buffer_Dev& encrypted_buffer,
+ const PP_EncryptedBlockInfo& encrypted_block_info,
+ std::vector<cdm::SubsampleEntry>* subsamples,
+ cdm::InputBuffer* input_buffer) {
+ PP_DCHECK(subsamples);
+ PP_DCHECK(!encrypted_buffer.is_null());
+
+ input_buffer->data = static_cast<uint8_t*>(encrypted_buffer.data());
+ input_buffer->data_size = encrypted_block_info.data_size;
+ PP_DCHECK(encrypted_buffer.size() >=
+ static_cast<uint32_t>(input_buffer->data_size));
+ input_buffer->data_offset = encrypted_block_info.data_offset;
+
+ PP_DCHECK(encrypted_block_info.key_id_size <=
+ arraysize(encrypted_block_info.key_id));
+ input_buffer->key_id_size = encrypted_block_info.key_id_size;
+ input_buffer->key_id = input_buffer->key_id_size > 0 ?
+ encrypted_block_info.key_id : NULL;
+
+ PP_DCHECK(encrypted_block_info.iv_size <= arraysize(encrypted_block_info.iv));
+ input_buffer->iv_size = encrypted_block_info.iv_size;
+ input_buffer->iv = encrypted_block_info.iv_size > 0 ?
+ encrypted_block_info.iv : NULL;
+
+ input_buffer->num_subsamples = encrypted_block_info.num_subsamples;
+ if (encrypted_block_info.num_subsamples > 0) {
+ subsamples->reserve(encrypted_block_info.num_subsamples);
+
+ for (uint32_t i = 0; i < encrypted_block_info.num_subsamples; ++i) {
+ subsamples->push_back(cdm::SubsampleEntry(
+ encrypted_block_info.subsamples[i].clear_bytes,
+ encrypted_block_info.subsamples[i].cipher_bytes));
+ }
+
+ input_buffer->subsamples = &(*subsamples)[0];
+ }
+
+ input_buffer->timestamp = encrypted_block_info.tracking_info.timestamp;
+}
+
+PP_DecryptResult CdmStatusToPpDecryptResult(cdm::Status status) {
+ switch (status) {
+ case cdm::kSuccess:
+ return PP_DECRYPTRESULT_SUCCESS;
+ case cdm::kNoKey:
+ return PP_DECRYPTRESULT_DECRYPT_NOKEY;
+ case cdm::kNeedMoreData:
+ return PP_DECRYPTRESULT_NEEDMOREDATA;
+ case cdm::kDecryptError:
+ return PP_DECRYPTRESULT_DECRYPT_ERROR;
+ case cdm::kDecodeError:
+ return PP_DECRYPTRESULT_DECODE_ERROR;
+ default:
+ PP_NOTREACHED();
+ return PP_DECRYPTRESULT_DECODE_ERROR;
+ }
+}
+
+PP_DecryptedFrameFormat CdmVideoFormatToPpDecryptedFrameFormat(
+ cdm::VideoFormat format) {
+ switch (format) {
+ case cdm::kYv12:
+ return PP_DECRYPTEDFRAMEFORMAT_YV12;
+ case cdm::kI420:
+ return PP_DECRYPTEDFRAMEFORMAT_I420;
+ default:
+ return PP_DECRYPTEDFRAMEFORMAT_UNKNOWN;
+ }
+}
+
+cdm::AudioDecoderConfig::AudioCodec PpAudioCodecToCdmAudioCodec(
+ PP_AudioCodec codec) {
+ switch (codec) {
+ case PP_AUDIOCODEC_VORBIS:
+ return cdm::AudioDecoderConfig::kCodecVorbis;
+ case PP_AUDIOCODEC_AAC:
+ return cdm::AudioDecoderConfig::kCodecAac;
+ default:
+ return cdm::AudioDecoderConfig::kUnknownAudioCodec;
+ }
+}
+
+cdm::VideoDecoderConfig::VideoCodec PpVideoCodecToCdmVideoCodec(
+ PP_VideoCodec codec) {
+ switch (codec) {
+ case PP_VIDEOCODEC_VP8:
+ return cdm::VideoDecoderConfig::kCodecVp8;
+ case PP_VIDEOCODEC_H264:
+ return cdm::VideoDecoderConfig::kCodecH264;
+ default:
+ return cdm::VideoDecoderConfig::kUnknownVideoCodec;
+ }
+}
+
+cdm::VideoDecoderConfig::VideoCodecProfile PpVCProfileToCdmVCProfile(
+ PP_VideoCodecProfile profile) {
+ switch (profile) {
+ case PP_VIDEOCODECPROFILE_VP8_MAIN:
+ return cdm::VideoDecoderConfig::kVp8ProfileMain;
+ case PP_VIDEOCODECPROFILE_H264_BASELINE:
+ return cdm::VideoDecoderConfig::kH264ProfileBaseline;
+ case PP_VIDEOCODECPROFILE_H264_MAIN:
+ return cdm::VideoDecoderConfig::kH264ProfileMain;
+ case PP_VIDEOCODECPROFILE_H264_EXTENDED:
+ return cdm::VideoDecoderConfig::kH264ProfileExtended;
+ case PP_VIDEOCODECPROFILE_H264_HIGH:
+ return cdm::VideoDecoderConfig::kH264ProfileHigh;
+ case PP_VIDEOCODECPROFILE_H264_HIGH_10:
+ return cdm::VideoDecoderConfig::kH264ProfileHigh10;
+ case PP_VIDEOCODECPROFILE_H264_HIGH_422:
+ return cdm::VideoDecoderConfig::kH264ProfileHigh422;
+ case PP_VIDEOCODECPROFILE_H264_HIGH_444_PREDICTIVE:
+ return cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive;
+ default:
+ return cdm::VideoDecoderConfig::kUnknownVideoCodecProfile;
+ }
+}
+
+cdm::VideoFormat PpDecryptedFrameFormatToCdmVideoFormat(
+ PP_DecryptedFrameFormat format) {
+ switch (format) {
+ case PP_DECRYPTEDFRAMEFORMAT_YV12:
+ return cdm::kYv12;
+ case PP_DECRYPTEDFRAMEFORMAT_I420:
+ return cdm::kI420;
+ default:
+ return cdm::kUnknownVideoFormat;
+ }
+}
+
+cdm::StreamType PpDecryptorStreamTypeToCdmStreamType(
+ PP_DecryptorStreamType stream_type) {
+ switch (stream_type) {
+ case PP_DECRYPTORSTREAMTYPE_AUDIO:
+ return cdm::kStreamTypeAudio;
+ case PP_DECRYPTORSTREAMTYPE_VIDEO:
+ return cdm::kStreamTypeVideo;
+ }
+
+ PP_NOTREACHED();
+ return cdm::kStreamTypeVideo;
+}
+
+} // namespace
+
+namespace media {
+
+// cdm::Buffer implementation that provides access to memory owned by a
+// pp::Buffer_Dev.
+// This class holds a reference to the Buffer_Dev throughout its lifetime.
+// TODO(xhwang): Find a better name. It's confusing to have PpbBuffer,
+// pp::Buffer_Dev and PPB_Buffer_Dev.
+class PpbBuffer : public cdm::Buffer {
+ public:
+ static PpbBuffer* Create(const pp::Buffer_Dev& buffer, uint32_t buffer_id) {
+ PP_DCHECK(buffer.data());
+ PP_DCHECK(buffer.size());
+ PP_DCHECK(buffer_id);
+ return new PpbBuffer(buffer, buffer_id);
+ }
+
+ // cdm::Buffer implementation.
+ virtual void Destroy() OVERRIDE { delete this; }
+
+ virtual int32_t Capacity() const OVERRIDE { return buffer_.size(); }
+
+ virtual uint8_t* Data() OVERRIDE {
+ return static_cast<uint8_t*>(buffer_.data());
+ }
+
+ virtual void SetSize(int32_t size) OVERRIDE {
+ PP_DCHECK(size >= 0);
+ PP_DCHECK(size < Capacity());
+ if (size < 0 || size > Capacity()) {
+ size_ = 0;
+ return;
+ }
+
+ size_ = size;
+ }
+
+ virtual int32_t Size() const OVERRIDE { return size_; }
+
+ pp::Buffer_Dev buffer_dev() const { return buffer_; }
+
+ uint32_t buffer_id() const { return buffer_id_; }
+
+ private:
+ PpbBuffer(pp::Buffer_Dev buffer, uint32_t buffer_id)
+ : buffer_(buffer),
+ buffer_id_(buffer_id),
+ size_(0) {}
+ virtual ~PpbBuffer() {}
+
+ pp::Buffer_Dev buffer_;
+ uint32_t buffer_id_;
+ int32_t size_;
+
+ DISALLOW_COPY_AND_ASSIGN(PpbBuffer);
+};
+
+class PpbBufferAllocator {
+ public:
+ explicit PpbBufferAllocator(pp::Instance* instance)
+ : instance_(instance),
+ next_buffer_id_(1) {}
+ ~PpbBufferAllocator() {}
+
+ cdm::Buffer* Allocate(int32_t capacity);
+
+ // Releases the buffer with |buffer_id|. A buffer can be recycled after
+ // it is released.
+ void Release(uint32_t buffer_id);
+
+ private:
+ typedef std::map<uint32_t, pp::Buffer_Dev> AllocatedBufferMap;
+ typedef std::multimap<int, std::pair<uint32_t, pp::Buffer_Dev> >
+ FreeBufferMap;
+
+ // Always pad new allocated buffer so that we don't need to reallocate
+ // buffers frequently if requested sizes fluctuate slightly.
+ static const int kBufferPadding = 512;
+
+ // Maximum number of free buffers we can keep when allocating new buffers.
+ static const int kFreeLimit = 3;
+
+ pp::Buffer_Dev AllocateNewBuffer(int capacity);
+
+ pp::Instance* const instance_;
+ uint32_t next_buffer_id_;
+ AllocatedBufferMap allocated_buffers_;
+ FreeBufferMap free_buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(PpbBufferAllocator);
+};
+
+cdm::Buffer* PpbBufferAllocator::Allocate(int32_t capacity) {
+ PP_DCHECK(IsMainThread());
+
+ if (capacity <= 0)
+ return NULL;
+
+ pp::Buffer_Dev buffer;
+ uint32_t buffer_id = 0;
+
+ // Reuse a buffer in the free list if there is one that fits |capacity|.
+ // Otherwise, create a new one.
+ FreeBufferMap::iterator found = free_buffers_.lower_bound(capacity);
+ if (found == free_buffers_.end()) {
+ // TODO(xhwang): Report statistics about how many new buffers are allocated.
+ buffer = AllocateNewBuffer(capacity);
+ if (buffer.is_null())
+ return NULL;
+ buffer_id = next_buffer_id_++;
+ } else {
+ buffer = found->second.second;
+ buffer_id = found->second.first;
+ free_buffers_.erase(found);
+ }
+
+ allocated_buffers_.insert(std::make_pair(buffer_id, buffer));
+
+ return PpbBuffer::Create(buffer, buffer_id);
+}
+
+void PpbBufferAllocator::Release(uint32_t buffer_id) {
+ if (!buffer_id)
+ return;
+
+ AllocatedBufferMap::iterator found = allocated_buffers_.find(buffer_id);
+ if (found == allocated_buffers_.end())
+ return;
+
+ pp::Buffer_Dev& buffer = found->second;
+ free_buffers_.insert(
+ std::make_pair(buffer.size(), std::make_pair(buffer_id, buffer)));
+
+ allocated_buffers_.erase(found);
+}
+
+pp::Buffer_Dev PpbBufferAllocator::AllocateNewBuffer(int32_t capacity) {
+ // Destroy the smallest buffer before allocating a new bigger buffer if the
+ // number of free buffers exceeds a limit. This mechanism helps avoid ending
+ // up with too many small buffers, which could happen if the size to be
+ // allocated keeps increasing.
+ if (free_buffers_.size() >= static_cast<uint32_t>(kFreeLimit))
+ free_buffers_.erase(free_buffers_.begin());
+
+ // Creation of pp::Buffer_Dev is expensive! It involves synchronous IPC calls.
+ // That's why we try to avoid AllocateNewBuffer() as much as we can.
+ return pp::Buffer_Dev(instance_, capacity + kBufferPadding);
+}
+
+class DecryptedBlockImpl : public cdm::DecryptedBlock {
+ public:
+ DecryptedBlockImpl() : buffer_(NULL), timestamp_(0) {}
+ virtual ~DecryptedBlockImpl() { if (buffer_) buffer_->Destroy(); }
+
+ virtual void SetDecryptedBuffer(cdm::Buffer* buffer) OVERRIDE {
+ buffer_ = static_cast<PpbBuffer*>(buffer);
+ }
+ virtual cdm::Buffer* DecryptedBuffer() OVERRIDE { return buffer_; }
+
+ virtual void SetTimestamp(int64_t timestamp) OVERRIDE {
+ timestamp_ = timestamp;
+ }
+ virtual int64_t Timestamp() const OVERRIDE { return timestamp_; }
+
+ private:
+ PpbBuffer* buffer_;
+ int64_t timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(DecryptedBlockImpl);
+};
+
+class VideoFrameImpl : public cdm::VideoFrame {
+ public:
+ VideoFrameImpl();
+ virtual ~VideoFrameImpl();
+
+ virtual void SetFormat(cdm::VideoFormat format) OVERRIDE {
+ format_ = format;
+ }
+ virtual cdm::VideoFormat Format() const OVERRIDE { return format_; }
+
+ virtual void SetSize(cdm::Size size) OVERRIDE { size_ = size; }
+ virtual cdm::Size Size() const OVERRIDE { return size_; }
+
+ virtual void SetFrameBuffer(cdm::Buffer* frame_buffer) OVERRIDE {
+ frame_buffer_ = static_cast<PpbBuffer*>(frame_buffer);
+ }
+ virtual cdm::Buffer* FrameBuffer() OVERRIDE { return frame_buffer_; }
+
+ virtual void SetPlaneOffset(cdm::VideoFrame::VideoPlane plane,
+ int32_t offset) OVERRIDE {
+ PP_DCHECK(0 <= plane && plane < kMaxPlanes);
+ PP_DCHECK(offset >= 0);
+ plane_offsets_[plane] = offset;
+ }
+ virtual int32_t PlaneOffset(VideoPlane plane) OVERRIDE {
+ PP_DCHECK(0 <= plane && plane < kMaxPlanes);
+ return plane_offsets_[plane];
+ }
+
+ virtual void SetStride(VideoPlane plane, int32_t stride) OVERRIDE {
+ PP_DCHECK(0 <= plane && plane < kMaxPlanes);
+ strides_[plane] = stride;
+ }
+ virtual int32_t Stride(VideoPlane plane) OVERRIDE {
+ PP_DCHECK(0 <= plane && plane < kMaxPlanes);
+ return strides_[plane];
+ }
+
+ virtual void SetTimestamp(int64_t timestamp) OVERRIDE {
+ timestamp_ = timestamp;
+ }
+ virtual int64_t Timestamp() const OVERRIDE { return timestamp_; }
+
+ private:
+ // The video buffer format.
+ cdm::VideoFormat format_;
+
+ // Width and height of the video frame.
+ cdm::Size size_;
+
+ // The video frame buffer.
+ PpbBuffer* frame_buffer_;
+
+ // Array of data pointers to each plane in the video frame buffer.
+ int32_t plane_offsets_[kMaxPlanes];
+
+ // Array of strides for each plane, typically greater or equal to the width
+ // of the surface divided by the horizontal sampling period. Note that
+ // strides can be negative.
+ int32_t strides_[kMaxPlanes];
+
+ // Presentation timestamp in microseconds.
+ int64_t timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(VideoFrameImpl);
+};
+
+VideoFrameImpl::VideoFrameImpl()
+ : format_(cdm::kUnknownVideoFormat),
+ frame_buffer_(NULL),
+ timestamp_(0) {
+ for (int32_t i = 0; i < kMaxPlanes; ++i) {
+ plane_offsets_[i] = 0;
+ strides_[i] = 0;
+ }
+}
+
+VideoFrameImpl::~VideoFrameImpl() {
+ if (frame_buffer_)
+ frame_buffer_->Destroy();
+}
+
+class AudioFramesImpl : public cdm::AudioFrames {
+ public:
+ AudioFramesImpl() : buffer_(NULL) {}
+ virtual ~AudioFramesImpl() {
+ if (buffer_)
+ buffer_->Destroy();
+ }
+
+ // AudioFrames implementation.
+ virtual void SetFrameBuffer(cdm::Buffer* buffer) OVERRIDE {
+ buffer_ = static_cast<PpbBuffer*>(buffer);
+ }
+ virtual cdm::Buffer* FrameBuffer() OVERRIDE {
+ return buffer_;
+ }
+
+ private:
+ PpbBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioFramesImpl);
+};
+
+// GetCdmHostFunc implementation.
+void* GetCdmHost(int host_interface_version, void* user_data);
+
+// A wrapper class for abstracting away PPAPI interaction and threading for a
+// Content Decryption Module (CDM).
+class CdmWrapper : public pp::Instance,
+ public pp::ContentDecryptor_Private,
+ public cdm::Host {
+ public:
+ CdmWrapper(PP_Instance instance, pp::Module* module);
+ virtual ~CdmWrapper();
+
+ // pp::Instance implementation.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
+ return true;
+ }
+
+ // PPP_ContentDecryptor_Private implementation.
+ // Note: Results of calls to these methods must be reported through the
+ // PPB_ContentDecryptor_Private interface.
+ virtual void GenerateKeyRequest(const std::string& key_system,
+ const std::string& type,
+ pp::VarArrayBuffer init_data) OVERRIDE;
+ virtual void AddKey(const std::string& session_id,
+ pp::VarArrayBuffer key,
+ pp::VarArrayBuffer init_data) OVERRIDE;
+ virtual void CancelKeyRequest(const std::string& session_id) OVERRIDE;
+ virtual void Decrypt(
+ pp::Buffer_Dev encrypted_buffer,
+ const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE;
+ virtual void InitializeAudioDecoder(
+ const PP_AudioDecoderConfig& decoder_config,
+ pp::Buffer_Dev extra_data_buffer) OVERRIDE;
+ virtual void InitializeVideoDecoder(
+ const PP_VideoDecoderConfig& decoder_config,
+ pp::Buffer_Dev extra_data_buffer) OVERRIDE;
+ virtual void DeinitializeDecoder(PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) OVERRIDE;
+ virtual void ResetDecoder(PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) OVERRIDE;
+ virtual void DecryptAndDecode(
+ PP_DecryptorStreamType decoder_type,
+ pp::Buffer_Dev encrypted_buffer,
+ const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE;
+
+ // cdm::Host implementation.
+ virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE;
+ virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE;
+ virtual double GetCurrentWallTimeInSeconds() OVERRIDE;
+ virtual void SendKeyMessage(
+ const char* session_id, int32_t session_id_length,
+ const char* message, int32_t message_length,
+ const char* default_url, int32_t default_url_length) OVERRIDE;
+ virtual void SendKeyError(const char* session_id,
+ int32_t session_id_length,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code) OVERRIDE;
+ virtual void GetPrivateData(int32_t* instance,
+ GetPrivateInterface* get_interface) OVERRIDE;
+
+ private:
+ struct SessionInfo {
+ SessionInfo(const std::string& key_system_in,
+ const std::string& session_id_in)
+ : key_system(key_system_in),
+ session_id(session_id_in) {}
+ const std::string key_system;
+ const std::string session_id;
+ };
+
+ typedef linked_ptr<DecryptedBlockImpl> LinkedDecryptedBlock;
+ typedef linked_ptr<VideoFrameImpl> LinkedVideoFrame;
+ typedef linked_ptr<AudioFramesImpl> LinkedAudioFrames;
+
+ bool CreateCdmInstance(const std::string& key_system);
+
+ void SendUnknownKeyError(const std::string& key_system,
+ const std::string& session_id);
+
+ void SendKeyAdded(const std::string& key_system,
+ const std::string& session_id);
+
+ void SendKeyErrorInternal(const std::string& key_system,
+ const std::string& session_id,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code);
+
+ // <code>PPB_ContentDecryptor_Private</code> dispatchers. These are passed to
+ // <code>callback_factory_</code> to ensure that calls into
+ // <code>PPP_ContentDecryptor_Private</code> are asynchronous.
+ void KeyAdded(int32_t result, const SessionInfo& session_info);
+ void KeyMessage(int32_t result,
+ const SessionInfo& session_info,
+ const std::vector<uint8>& message,
+ const std::string& default_url);
+ void KeyError(int32_t result,
+ const SessionInfo& session_info,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code);
+ void DeliverBlock(int32_t result,
+ const cdm::Status& status,
+ const LinkedDecryptedBlock& decrypted_block,
+ const PP_DecryptTrackingInfo& tracking_info);
+ void DecoderInitializeDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id,
+ bool success);
+ void DecoderDeinitializeDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id);
+ void DecoderResetDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id);
+ void DeliverFrame(int32_t result,
+ const cdm::Status& status,
+ const LinkedVideoFrame& video_frame,
+ const PP_DecryptTrackingInfo& tracking_info);
+ void DeliverSamples(int32_t result,
+ const cdm::Status& status,
+ const LinkedAudioFrames& audio_frames,
+ const PP_DecryptTrackingInfo& tracking_info);
+
+ // Helper for SetTimer().
+ void TimerExpired(int32_t result, void* context);
+
+ bool IsValidVideoFrame(const LinkedVideoFrame& video_frame);
+
+ PpbBufferAllocator allocator_;
+ pp::CompletionCallbackFactory<CdmWrapper> callback_factory_;
+ cdm::ContentDecryptionModule* cdm_;
+ std::string key_system_;
+
+ DISALLOW_COPY_AND_ASSIGN(CdmWrapper);
+};
+
+CdmWrapper::CdmWrapper(PP_Instance instance, pp::Module* module)
+ : pp::Instance(instance),
+ pp::ContentDecryptor_Private(this),
+ allocator_(this),
+ cdm_(NULL) {
+ callback_factory_.Initialize(this);
+}
+
+CdmWrapper::~CdmWrapper() {
+ if (cdm_)
+ cdm_->Destroy();
+}
+
+bool CdmWrapper::CreateCdmInstance(const std::string& key_system) {
+ PP_DCHECK(!cdm_);
+ cdm_ = static_cast<cdm::ContentDecryptionModule*>(
+ ::CreateCdmInstance(cdm::kCdmInterfaceVersion,
+ key_system.data(), key_system.size(),
+ GetCdmHost, this));
+
+ return (cdm_ != NULL);
+}
+
+void CdmWrapper::GenerateKeyRequest(const std::string& key_system,
+ const std::string& type,
+ pp::VarArrayBuffer init_data) {
+ PP_DCHECK(!key_system.empty());
+ PP_DCHECK(key_system_.empty() || key_system_ == key_system);
+
+#if defined(CHECK_DOCUMENT_URL)
+ PP_URLComponents_Dev url_components = {};
+ pp::Var href = pp::URLUtil_Dev::Get()->GetDocumentURL(
+ pp::InstanceHandle(pp_instance()), &url_components);
+ PP_DCHECK(href.is_string());
+ PP_DCHECK(!href.AsString().empty());
+ PP_DCHECK(url_components.host.begin);
+ PP_DCHECK(0 < url_components.host.len);
+#endif // defined(CHECK_DOCUMENT_URL)
+
+ if (!cdm_) {
+ if (!CreateCdmInstance(key_system)) {
+ SendUnknownKeyError(key_system, std::string());
+ return;
+ }
+ }
+ PP_DCHECK(cdm_);
+
+ // Must be set here in case the CDM synchronously calls a cdm::Host method.
+ // Clear below on error.
+ // TODO(ddorwin): Set/clear key_system_ & cdm_ at same time; clear both on
+ // error below.
+ key_system_ = key_system;
+ cdm::Status status = cdm_->GenerateKeyRequest(
+ type.data(), type.size(),
+ static_cast<const uint8_t*>(init_data.Map()),
+ init_data.ByteLength());
+ PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError);
+ if (status != cdm::kSuccess) {
+ key_system_.clear(); // See comment above.
+ return;
+ }
+
+ key_system_ = key_system;
+}
+
+void CdmWrapper::AddKey(const std::string& session_id,
+ pp::VarArrayBuffer key,
+ pp::VarArrayBuffer init_data) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+ if (!cdm_) {
+ SendUnknownKeyError(key_system_, session_id);
+ return;
+ }
+
+ const uint8_t* key_ptr = static_cast<const uint8_t*>(key.Map());
+ int key_size = key.ByteLength();
+ const uint8_t* init_data_ptr = static_cast<const uint8_t*>(init_data.Map());
+ int init_data_size = init_data.ByteLength();
+ PP_DCHECK(!init_data_ptr == !init_data_size);
+
+ if (!key_ptr || key_size <= 0) {
+ SendUnknownKeyError(key_system_, session_id);
+ return;
+ }
+
+ cdm::Status status = cdm_->AddKey(session_id.data(), session_id.size(),
+ key_ptr, key_size,
+ init_data_ptr, init_data_size);
+ PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError);
+ if (status != cdm::kSuccess) {
+ SendUnknownKeyError(key_system_, session_id);
+ return;
+ }
+
+ SendKeyAdded(key_system_, session_id);
+}
+
+void CdmWrapper::CancelKeyRequest(const std::string& session_id) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+ if (!cdm_) {
+ SendUnknownKeyError(key_system_, session_id);
+ return;
+ }
+
+ cdm::Status status = cdm_->CancelKeyRequest(session_id.data(),
+ session_id.size());
+ PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError);
+ if (status != cdm::kSuccess)
+ SendUnknownKeyError(key_system_, session_id);
+}
+
+// Note: In the following decryption/decoding related functions, errors are NOT
+// reported via KeyError, but are reported via corresponding PPB calls.
+
+void CdmWrapper::Decrypt(pp::Buffer_Dev encrypted_buffer,
+ const PP_EncryptedBlockInfo& encrypted_block_info) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+ PP_DCHECK(!encrypted_buffer.is_null());
+
+ // Release a buffer that the caller indicated it is finished with.
+ allocator_.Release(encrypted_block_info.tracking_info.buffer_id);
+
+ cdm::Status status = cdm::kDecryptError;
+ LinkedDecryptedBlock decrypted_block(new DecryptedBlockImpl());
+
+ if (cdm_) {
+ cdm::InputBuffer input_buffer;
+ std::vector<cdm::SubsampleEntry> subsamples;
+ ConfigureInputBuffer(encrypted_buffer, encrypted_block_info, &subsamples,
+ &input_buffer);
+ status = cdm_->Decrypt(input_buffer, decrypted_block.get());
+ PP_DCHECK(status != cdm::kSuccess ||
+ (decrypted_block->DecryptedBuffer() &&
+ decrypted_block->DecryptedBuffer()->Size()));
+ }
+
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DeliverBlock,
+ status,
+ decrypted_block,
+ encrypted_block_info.tracking_info));
+}
+
+void CdmWrapper::InitializeAudioDecoder(
+ const PP_AudioDecoderConfig& decoder_config,
+ pp::Buffer_Dev extra_data_buffer) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+
+ cdm::Status status = cdm::kSessionError;
+ if (cdm_) {
+ cdm::AudioDecoderConfig cdm_decoder_config;
+ cdm_decoder_config.codec =
+ PpAudioCodecToCdmAudioCodec(decoder_config.codec);
+ cdm_decoder_config.channel_count = decoder_config.channel_count;
+ cdm_decoder_config.bits_per_channel = decoder_config.bits_per_channel;
+ cdm_decoder_config.samples_per_second = decoder_config.samples_per_second;
+ cdm_decoder_config.extra_data =
+ static_cast<uint8_t*>(extra_data_buffer.data());
+ cdm_decoder_config.extra_data_size =
+ static_cast<int32_t>(extra_data_buffer.size());
+ status = cdm_->InitializeAudioDecoder(cdm_decoder_config);
+ }
+
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DecoderInitializeDone,
+ PP_DECRYPTORSTREAMTYPE_AUDIO,
+ decoder_config.request_id,
+ status == cdm::kSuccess));
+}
+
+void CdmWrapper::InitializeVideoDecoder(
+ const PP_VideoDecoderConfig& decoder_config,
+ pp::Buffer_Dev extra_data_buffer) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+
+ cdm::Status status = cdm::kSessionError;
+ if (cdm_) {
+ cdm::VideoDecoderConfig cdm_decoder_config;
+ cdm_decoder_config.codec =
+ PpVideoCodecToCdmVideoCodec(decoder_config.codec);
+ cdm_decoder_config.profile =
+ PpVCProfileToCdmVCProfile(decoder_config.profile);
+ cdm_decoder_config.format =
+ PpDecryptedFrameFormatToCdmVideoFormat(decoder_config.format);
+ cdm_decoder_config.coded_size.width = decoder_config.width;
+ cdm_decoder_config.coded_size.height = decoder_config.height;
+ cdm_decoder_config.extra_data =
+ static_cast<uint8_t*>(extra_data_buffer.data());
+ cdm_decoder_config.extra_data_size =
+ static_cast<int32_t>(extra_data_buffer.size());
+ status = cdm_->InitializeVideoDecoder(cdm_decoder_config);
+ }
+
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DecoderInitializeDone,
+ PP_DECRYPTORSTREAMTYPE_VIDEO,
+ decoder_config.request_id,
+ status == cdm::kSuccess));
+}
+
+void CdmWrapper::DeinitializeDecoder(PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+ if (cdm_) {
+ cdm_->DeinitializeDecoder(
+ PpDecryptorStreamTypeToCdmStreamType(decoder_type));
+ }
+
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DecoderDeinitializeDone,
+ decoder_type,
+ request_id));
+}
+
+void CdmWrapper::ResetDecoder(PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+ if (cdm_)
+ cdm_->ResetDecoder(PpDecryptorStreamTypeToCdmStreamType(decoder_type));
+
+ CallOnMain(callback_factory_.NewCallback(&CdmWrapper::DecoderResetDone,
+ decoder_type,
+ request_id));
+}
+
+void CdmWrapper::DecryptAndDecode(
+ PP_DecryptorStreamType decoder_type,
+ pp::Buffer_Dev encrypted_buffer,
+ const PP_EncryptedBlockInfo& encrypted_block_info) {
+ PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded.
+
+ // Release a buffer that the caller indicated it is finished with.
+ allocator_.Release(encrypted_block_info.tracking_info.buffer_id);
+
+ cdm::InputBuffer input_buffer;
+ std::vector<cdm::SubsampleEntry> subsamples;
+ if (cdm_ && !encrypted_buffer.is_null()) {
+ ConfigureInputBuffer(encrypted_buffer,
+ encrypted_block_info,
+ &subsamples,
+ &input_buffer);
+ }
+
+ cdm::Status status = cdm::kDecodeError;
+
+ switch (decoder_type) {
+ case PP_DECRYPTORSTREAMTYPE_VIDEO: {
+ LinkedVideoFrame video_frame(new VideoFrameImpl());
+ if (cdm_)
+ status = cdm_->DecryptAndDecodeFrame(input_buffer, video_frame.get());
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DeliverFrame,
+ status,
+ video_frame,
+ encrypted_block_info.tracking_info));
+ return;
+ }
+
+ case PP_DECRYPTORSTREAMTYPE_AUDIO: {
+ LinkedAudioFrames audio_frames(new AudioFramesImpl());
+ if (cdm_) {
+ status = cdm_->DecryptAndDecodeSamples(input_buffer,
+ audio_frames.get());
+ }
+ CallOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::DeliverSamples,
+ status,
+ audio_frames,
+ encrypted_block_info.tracking_info));
+ return;
+ }
+
+ default:
+ PP_NOTREACHED();
+ return;
+ }
+}
+
+cdm::Buffer* CdmWrapper::Allocate(int32_t capacity) {
+ return allocator_.Allocate(capacity);
+}
+
+void CdmWrapper::SetTimer(int64_t delay_ms, void* context) {
+ // NOTE: doesn't really need to run on the main thread; could just as well run
+ // on a helper thread if |cdm_| were thread-friendly and care was taken. We
+ // only use CallOnMainThread() here to get delayed-execution behavior.
+ pp::Module::Get()->core()->CallOnMainThread(
+ delay_ms,
+ callback_factory_.NewCallback(&CdmWrapper::TimerExpired, context),
+ PP_OK);
+}
+
+void CdmWrapper::TimerExpired(int32_t result, void* context) {
+ PP_DCHECK(result == PP_OK);
+ cdm_->TimerExpired(context);
+}
+
+double CdmWrapper::GetCurrentWallTimeInSeconds() {
+ return pp::Module::Get()->core()->GetTime();
+}
+
+void CdmWrapper::SendKeyMessage(
+ const char* session_id, int32_t session_id_length,
+ const char* message, int32_t message_length,
+ const char* default_url, int32_t default_url_length) {
+ PP_DCHECK(!key_system_.empty());
+ PostOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::KeyMessage,
+ SessionInfo(key_system_,
+ std::string(session_id, session_id_length)),
+ std::vector<uint8>(message, message + message_length),
+ std::string(default_url, default_url_length)));
+}
+
+void CdmWrapper::SendKeyError(const char* session_id,
+ int32_t session_id_length,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code) {
+ SendKeyErrorInternal(key_system_,
+ std::string(session_id, session_id_length),
+ error_code,
+ system_code);
+}
+
+void CdmWrapper::GetPrivateData(int32_t* instance,
+ cdm::Host::GetPrivateInterface* get_interface) {
+ *instance = pp_instance();
+ *get_interface = pp::Module::Get()->get_browser_interface();
+}
+
+void CdmWrapper::SendUnknownKeyError(const std::string& key_system,
+ const std::string& session_id) {
+ SendKeyErrorInternal(key_system, session_id, cdm::kUnknownError, 0);
+}
+
+void CdmWrapper::SendKeyAdded(const std::string& key_system,
+ const std::string& session_id) {
+ PostOnMain(callback_factory_.NewCallback(
+ &CdmWrapper::KeyAdded,
+ SessionInfo(key_system_, session_id)));
+}
+
+void CdmWrapper::SendKeyErrorInternal(const std::string& key_system,
+ const std::string& session_id,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code) {
+ PP_DCHECK(!key_system.empty());
+ PostOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyError,
+ SessionInfo(key_system_, session_id),
+ error_code,
+ system_code));
+}
+
+void CdmWrapper::KeyAdded(int32_t result, const SessionInfo& session_info) {
+ PP_DCHECK(result == PP_OK);
+ PP_DCHECK(!session_info.key_system.empty());
+ pp::ContentDecryptor_Private::KeyAdded(session_info.key_system,
+ session_info.session_id);
+}
+
+void CdmWrapper::KeyMessage(int32_t result,
+ const SessionInfo& session_info,
+ const std::vector<uint8>& message,
+ const std::string& default_url) {
+ PP_DCHECK(result == PP_OK);
+ PP_DCHECK(!session_info.key_system.empty());
+
+ pp::VarArrayBuffer message_array_buffer(message.size());
+ if (message.size() > 0) {
+ memcpy(message_array_buffer.Map(), message.data(), message.size());
+ }
+
+ pp::ContentDecryptor_Private::KeyMessage(
+ session_info.key_system, session_info.session_id,
+ message_array_buffer, default_url);
+}
+
+void CdmWrapper::KeyError(int32_t result,
+ const SessionInfo& session_info,
+ cdm::MediaKeyError error_code,
+ uint32_t system_code) {
+ PP_DCHECK(result == PP_OK);
+ PP_DCHECK(!session_info.key_system.empty());
+ pp::ContentDecryptor_Private::KeyError(
+ session_info.key_system, session_info.session_id,
+ error_code, system_code);
+}
+
+void CdmWrapper::DeliverBlock(int32_t result,
+ const cdm::Status& status,
+ const LinkedDecryptedBlock& decrypted_block,
+ const PP_DecryptTrackingInfo& tracking_info) {
+ PP_DCHECK(result == PP_OK);
+ PP_DecryptedBlockInfo decrypted_block_info;
+ decrypted_block_info.tracking_info = tracking_info;
+ decrypted_block_info.tracking_info.timestamp = decrypted_block->Timestamp();
+ decrypted_block_info.tracking_info.buffer_id = 0;
+ decrypted_block_info.data_size = 0;
+ decrypted_block_info.result = CdmStatusToPpDecryptResult(status);
+
+ pp::Buffer_Dev buffer;
+
+ if (decrypted_block_info.result == PP_DECRYPTRESULT_SUCCESS) {
+ PP_DCHECK(decrypted_block.get() && decrypted_block->DecryptedBuffer());
+ if (!decrypted_block.get() || !decrypted_block->DecryptedBuffer()) {
+ PP_NOTREACHED();
+ decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR;
+ } else {
+ PpbBuffer* ppb_buffer =
+ static_cast<PpbBuffer*>(decrypted_block->DecryptedBuffer());
+ buffer = ppb_buffer->buffer_dev();
+ decrypted_block_info.tracking_info.buffer_id = ppb_buffer->buffer_id();
+ decrypted_block_info.data_size = ppb_buffer->Size();
+ }
+ }
+
+ pp::ContentDecryptor_Private::DeliverBlock(buffer, decrypted_block_info);
+}
+
+void CdmWrapper::DecoderInitializeDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id,
+ bool success) {
+ PP_DCHECK(result == PP_OK);
+ pp::ContentDecryptor_Private::DecoderInitializeDone(decoder_type,
+ request_id,
+ success);
+}
+
+void CdmWrapper::DecoderDeinitializeDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) {
+ pp::ContentDecryptor_Private::DecoderDeinitializeDone(decoder_type,
+ request_id);
+}
+
+void CdmWrapper::DecoderResetDone(int32_t result,
+ PP_DecryptorStreamType decoder_type,
+ uint32_t request_id) {
+ pp::ContentDecryptor_Private::DecoderResetDone(decoder_type, request_id);
+}
+
+void CdmWrapper::DeliverFrame(
+ int32_t result,
+ const cdm::Status& status,
+ const LinkedVideoFrame& video_frame,
+ const PP_DecryptTrackingInfo& tracking_info) {
+ PP_DCHECK(result == PP_OK);
+ PP_DecryptedFrameInfo decrypted_frame_info;
+ decrypted_frame_info.tracking_info.request_id = tracking_info.request_id;
+ decrypted_frame_info.tracking_info.buffer_id = 0;
+ decrypted_frame_info.result = CdmStatusToPpDecryptResult(status);
+
+ pp::Buffer_Dev buffer;
+
+ if (decrypted_frame_info.result == PP_DECRYPTRESULT_SUCCESS) {
+ if (!IsValidVideoFrame(video_frame)) {
+ PP_NOTREACHED();
+ decrypted_frame_info.result = PP_DECRYPTRESULT_DECODE_ERROR;
+ } else {
+ PpbBuffer* ppb_buffer =
+ static_cast<PpbBuffer*>(video_frame->FrameBuffer());
+
+ buffer = ppb_buffer->buffer_dev();
+
+ decrypted_frame_info.tracking_info.timestamp = video_frame->Timestamp();
+ decrypted_frame_info.tracking_info.buffer_id = ppb_buffer->buffer_id();
+ decrypted_frame_info.format =
+ CdmVideoFormatToPpDecryptedFrameFormat(video_frame->Format());
+ decrypted_frame_info.width = video_frame->Size().width;
+ decrypted_frame_info.height = video_frame->Size().height;
+ decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_Y] =
+ video_frame->PlaneOffset(cdm::VideoFrame::kYPlane);
+ decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_U] =
+ video_frame->PlaneOffset(cdm::VideoFrame::kUPlane);
+ decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_V] =
+ video_frame->PlaneOffset(cdm::VideoFrame::kVPlane);
+ decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_Y] =
+ video_frame->Stride(cdm::VideoFrame::kYPlane);
+ decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_U] =
+ video_frame->Stride(cdm::VideoFrame::kUPlane);
+ decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_V] =
+ video_frame->Stride(cdm::VideoFrame::kVPlane);
+ }
+ }
+ pp::ContentDecryptor_Private::DeliverFrame(buffer, decrypted_frame_info);
+}
+
+void CdmWrapper::DeliverSamples(int32_t result,
+ const cdm::Status& status,
+ const LinkedAudioFrames& audio_frames,
+ const PP_DecryptTrackingInfo& tracking_info) {
+ PP_DCHECK(result == PP_OK);
+
+ PP_DecryptedBlockInfo decrypted_block_info;
+ decrypted_block_info.tracking_info = tracking_info;
+ decrypted_block_info.tracking_info.timestamp = 0;
+ decrypted_block_info.tracking_info.buffer_id = 0;
+ decrypted_block_info.data_size = 0;
+ decrypted_block_info.result = CdmStatusToPpDecryptResult(status);
+
+ pp::Buffer_Dev buffer;
+
+ if (decrypted_block_info.result == PP_DECRYPTRESULT_SUCCESS) {
+ PP_DCHECK(audio_frames.get() && audio_frames->FrameBuffer());
+ if (!audio_frames.get() || !audio_frames->FrameBuffer()) {
+ PP_NOTREACHED();
+ decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR;
+ } else {
+ PpbBuffer* ppb_buffer =
+ static_cast<PpbBuffer*>(audio_frames->FrameBuffer());
+ buffer = ppb_buffer->buffer_dev();
+ decrypted_block_info.tracking_info.buffer_id = ppb_buffer->buffer_id();
+ decrypted_block_info.data_size = ppb_buffer->Size();
+ }
+ }
+
+ pp::ContentDecryptor_Private::DeliverSamples(buffer, decrypted_block_info);
+}
+
+bool CdmWrapper::IsValidVideoFrame(const LinkedVideoFrame& video_frame) {
+ if (!video_frame.get() ||
+ !video_frame->FrameBuffer() ||
+ (video_frame->Format() != cdm::kI420 &&
+ video_frame->Format() != cdm::kYv12)) {
+ return false;
+ }
+
+ PpbBuffer* ppb_buffer = static_cast<PpbBuffer*>(video_frame->FrameBuffer());
+
+ for (int i = 0; i < cdm::VideoFrame::kMaxPlanes; ++i) {
+ int plane_height = (i == cdm::VideoFrame::kYPlane) ?
+ video_frame->Size().height : (video_frame->Size().height + 1) / 2;
+ cdm::VideoFrame::VideoPlane plane =
+ static_cast<cdm::VideoFrame::VideoPlane>(i);
+ if (ppb_buffer->Size() < video_frame->PlaneOffset(plane) +
+ plane_height * video_frame->Stride(plane)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void* GetCdmHost(int host_interface_version, void* user_data) {
+ if (!host_interface_version || !user_data)
+ return NULL;
+
+ if (host_interface_version != cdm::kHostInterfaceVersion)
+ return NULL;
+
+ CdmWrapper* cdm_wrapper = static_cast<CdmWrapper*>(user_data);
+ return static_cast<cdm::Host*>(cdm_wrapper);
+}
+
+// This object is the global object representing this plugin library as long
+// as it is loaded.
+class CdmWrapperModule : public pp::Module {
+ public:
+ CdmWrapperModule() : pp::Module() {
+ // This function blocks the renderer thread (PluginInstance::Initialize()).
+ // Move this call to other places if this may be a concern in the future.
+ INITIALIZE_CDM_MODULE();
+ }
+ virtual ~CdmWrapperModule() {
+ DeinitializeCdmModule();
+ }
+
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new CdmWrapper(instance, this);
+ }
+};
+
+} // namespace media
+
+namespace pp {
+
+// Factory function for your specialization of the Module object.
+Module* CreateModule() {
+ return new media::CdmWrapperModule();
+}
+
+} // namespace pp
diff --git a/media/cdm/ppapi/clear_key_cdm.cc b/media/cdm/ppapi/clear_key_cdm.cc
new file mode 100644
index 0000000000..cd6a61ff31
--- /dev/null
+++ b/media/cdm/ppapi/clear_key_cdm.cc
@@ -0,0 +1,558 @@
+// Copyright 2013 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/cdm/ppapi/clear_key_cdm.h"
+
+#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
+#include "media/cdm/ppapi/cdm_video_decoder.h"
+
+#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+#include "base/basictypes.h"
+static const int64 kNoTimestamp = kint64min;
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+#include "base/at_exit.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "media/base/media.h"
+#include "media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h"
+#include "media/cdm/ppapi/ffmpeg_cdm_video_decoder.h"
+
+// Include FFmpeg avformat.h for av_register_all().
+extern "C" {
+// Temporarily disable possible loss of data warning.
+MSVC_PUSH_DISABLE_WARNING(4244);
+#include <libavformat/avformat.h>
+MSVC_POP_WARNING();
+} // extern "C"
+
+// TODO(tomfinegan): When COMPONENT_BUILD is not defined an AtExitManager must
+// exist before the call to InitializeFFmpegLibraries(). This should no longer
+// be required after http://crbug.com/91970 because we'll be able to get rid of
+// InitializeFFmpegLibraries().
+#if !defined COMPONENT_BUILD
+static base::AtExitManager g_at_exit_manager;
+#endif
+
+// TODO(tomfinegan): InitializeFFmpegLibraries() and |g_cdm_module_initialized|
+// are required for running in the sandbox, and should no longer be required
+// after http://crbug.com/91970 is fixed.
+static bool InitializeFFmpegLibraries() {
+ base::FilePath file_path;
+ CHECK(PathService::Get(base::DIR_MODULE, &file_path));
+ CHECK(media::InitializeMediaLibrary(file_path));
+ return true;
+}
+
+static bool g_ffmpeg_lib_initialized = InitializeFFmpegLibraries();
+#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
+
+static const char kClearKeyCdmVersion[] = "0.1.0.1";
+static const char kExternalClearKey[] = "org.chromium.externalclearkey";
+static const int64 kSecondsPerMinute = 60;
+static const int64 kMsPerSecond = 1000;
+static const int64 kInitialTimerDelayMs = 200;
+static const int64 kMaxTimerDelayMs = 1 * kSecondsPerMinute * kMsPerSecond;
+// Heart beat message header. If a key message starts with |kHeartBeatHeader|,
+// it's a heart beat message. Otherwise, it's a key request.
+static const char kHeartBeatHeader[] = "HEARTBEAT";
+
+// Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is
+// empty, an empty (end-of-stream) media::DecoderBuffer is returned.
+static scoped_refptr<media::DecoderBuffer> CopyDecoderBufferFrom(
+ const cdm::InputBuffer& input_buffer) {
+ if (!input_buffer.data) {
+ DCHECK_EQ(input_buffer.data_size, 0);
+ return media::DecoderBuffer::CreateEOSBuffer();
+ }
+
+ // TODO(tomfinegan): Get rid of this copy.
+ scoped_refptr<media::DecoderBuffer> output_buffer =
+ media::DecoderBuffer::CopyFrom(input_buffer.data, input_buffer.data_size);
+
+ std::vector<media::SubsampleEntry> subsamples;
+ for (int32_t i = 0; i < input_buffer.num_subsamples; ++i) {
+ media::SubsampleEntry subsample;
+ subsample.clear_bytes = input_buffer.subsamples[i].clear_bytes;
+ subsample.cypher_bytes = input_buffer.subsamples[i].cipher_bytes;
+ subsamples.push_back(subsample);
+ }
+
+ scoped_ptr<media::DecryptConfig> decrypt_config(new media::DecryptConfig(
+ std::string(reinterpret_cast<const char*>(input_buffer.key_id),
+ input_buffer.key_id_size),
+ std::string(reinterpret_cast<const char*>(input_buffer.iv),
+ input_buffer.iv_size),
+ input_buffer.data_offset,
+ subsamples));
+
+ output_buffer->set_decrypt_config(decrypt_config.Pass());
+ output_buffer->set_timestamp(
+ base::TimeDelta::FromMicroseconds(input_buffer.timestamp));
+
+ return output_buffer;
+}
+
+template<typename Type>
+class ScopedResetter {
+ public:
+ explicit ScopedResetter(Type* object) : object_(object) {}
+ ~ScopedResetter() { object_->Reset(); }
+
+ private:
+ Type* const object_;
+};
+
+void INITIALIZE_CDM_MODULE() {
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ DVLOG(2) << "FFmpeg libraries initialized: " << g_ffmpeg_lib_initialized;
+ av_register_all();
+#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
+}
+
+void DeinitializeCdmModule() {
+}
+
+void* CreateCdmInstance(
+ int cdm_interface_version,
+ const char* key_system, int key_system_size,
+ GetCdmHostFunc get_cdm_host_func, void* user_data) {
+ DVLOG(1) << "CreateCdmInstance()";
+
+ if (cdm_interface_version != cdm::kCdmInterfaceVersion)
+ return NULL;
+
+ cdm::Host* host = static_cast<cdm::Host*>(
+ get_cdm_host_func(cdm::kHostInterfaceVersion, user_data));
+ if (!host)
+ return NULL;
+
+ return static_cast<cdm::ContentDecryptionModule*>(
+ new media::ClearKeyCdm(host));
+}
+
+const char* GetCdmVersion() {
+ return kClearKeyCdmVersion;
+}
+
+namespace media {
+
+ClearKeyCdm::Client::Client() : status_(kKeyError) {}
+
+ClearKeyCdm::Client::~Client() {}
+
+void ClearKeyCdm::Client::Reset() {
+ status_ = kKeyError;
+ session_id_.clear();
+ key_message_.clear();
+ default_url_.clear();
+}
+
+void ClearKeyCdm::Client::KeyAdded(const std::string& session_id) {
+ status_ = kKeyAdded;
+ session_id_ = session_id;
+}
+
+void ClearKeyCdm::Client::KeyError(const std::string& session_id,
+ media::MediaKeys::KeyError error_code,
+ int system_code) {
+ status_ = kKeyError;
+ session_id_ = session_id;
+}
+
+void ClearKeyCdm::Client::KeyMessage(const std::string& session_id,
+ const std::vector<uint8>& message,
+ const std::string& default_url) {
+ status_ = kKeyMessage;
+ session_id_ = session_id;
+ key_message_ = message;
+ default_url_ = default_url;
+}
+
+ClearKeyCdm::ClearKeyCdm(cdm::Host* host)
+ : decryptor_(base::Bind(&Client::KeyAdded, base::Unretained(&client_)),
+ base::Bind(&Client::KeyError, base::Unretained(&client_)),
+ base::Bind(&Client::KeyMessage, base::Unretained(&client_))),
+ host_(host),
+ timer_delay_ms_(kInitialTimerDelayMs),
+ timer_set_(false) {
+#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ channel_count_ = 0;
+ bits_per_channel_ = 0;
+ samples_per_second_ = 0;
+ output_timestamp_base_in_microseconds_ = kNoTimestamp;
+ total_samples_generated_ = 0;
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+}
+
+ClearKeyCdm::~ClearKeyCdm() {}
+
+cdm::Status ClearKeyCdm::GenerateKeyRequest(const char* type, int type_size,
+ const uint8_t* init_data,
+ int init_data_size) {
+ DVLOG(1) << "GenerateKeyRequest()";
+ base::AutoLock auto_lock(client_lock_);
+ ScopedResetter<Client> auto_resetter(&client_);
+ decryptor_.GenerateKeyRequest(std::string(type, type_size),
+ init_data, init_data_size);
+
+ if (client_.status() != Client::kKeyMessage) {
+ host_->SendKeyError(NULL, 0, cdm::kUnknownError, 0);
+ return cdm::kSessionError;
+ }
+
+ host_->SendKeyMessage(
+ client_.session_id().data(), client_.session_id().size(),
+ reinterpret_cast<const char*>(&client_.key_message()[0]),
+ client_.key_message().size(),
+ client_.default_url().data(), client_.default_url().size());
+
+ // Only save the latest session ID for heartbeat messages.
+ heartbeat_session_id_ = client_.session_id();
+
+ return cdm::kSuccess;
+}
+
+cdm::Status ClearKeyCdm::AddKey(const char* session_id,
+ int session_id_size,
+ const uint8_t* key,
+ int key_size,
+ const uint8_t* key_id,
+ int key_id_size) {
+ DVLOG(1) << "AddKey()";
+ base::AutoLock auto_lock(client_lock_);
+ ScopedResetter<Client> auto_resetter(&client_);
+ decryptor_.AddKey(key, key_size, key_id, key_id_size,
+ std::string(session_id, session_id_size));
+
+ if (client_.status() != Client::kKeyAdded)
+ return cdm::kSessionError;
+
+ if (!timer_set_) {
+ ScheduleNextHeartBeat();
+ timer_set_ = true;
+ }
+
+ return cdm::kSuccess;
+}
+
+cdm::Status ClearKeyCdm::CancelKeyRequest(const char* session_id,
+ int session_id_size) {
+ DVLOG(1) << "CancelKeyRequest()";
+ base::AutoLock auto_lock(client_lock_);
+ ScopedResetter<Client> auto_resetter(&client_);
+ decryptor_.CancelKeyRequest(std::string(session_id, session_id_size));
+ return cdm::kSuccess;
+}
+
+void ClearKeyCdm::TimerExpired(void* context) {
+ std::string heartbeat_message;
+ if (!next_heartbeat_message_.empty() &&
+ context == &next_heartbeat_message_[0]) {
+ heartbeat_message = next_heartbeat_message_;
+ } else {
+ heartbeat_message = "ERROR: Invalid timer context found!";
+ }
+
+ // This URL is only used for testing the code path for defaultURL.
+ // There is no service at this URL, so applications should ignore it.
+ const char url[] = "http://test.externalclearkey.chromium.org";
+
+ host_->SendKeyMessage(
+ heartbeat_session_id_.data(), heartbeat_session_id_.size(),
+ heartbeat_message.data(), heartbeat_message.size(),
+ url, arraysize(url) - 1);
+
+ ScheduleNextHeartBeat();
+}
+
+static void CopyDecryptResults(
+ media::Decryptor::Status* status_copy,
+ scoped_refptr<media::DecoderBuffer>* buffer_copy,
+ media::Decryptor::Status status,
+ const scoped_refptr<media::DecoderBuffer>& buffer) {
+ *status_copy = status;
+ *buffer_copy = buffer;
+}
+
+cdm::Status ClearKeyCdm::Decrypt(
+ const cdm::InputBuffer& encrypted_buffer,
+ cdm::DecryptedBlock* decrypted_block) {
+ DVLOG(1) << "Decrypt()";
+ DCHECK(encrypted_buffer.data);
+
+ scoped_refptr<media::DecoderBuffer> buffer;
+ cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
+
+ if (status != cdm::kSuccess)
+ return status;
+
+ DCHECK(buffer->data());
+ decrypted_block->SetDecryptedBuffer(
+ host_->Allocate(buffer->data_size()));
+ memcpy(reinterpret_cast<void*>(decrypted_block->DecryptedBuffer()->Data()),
+ buffer->data(),
+ buffer->data_size());
+ decrypted_block->DecryptedBuffer()->SetSize(buffer->data_size());
+ decrypted_block->SetTimestamp(buffer->timestamp().InMicroseconds());
+
+ return cdm::kSuccess;
+}
+
+cdm::Status ClearKeyCdm::InitializeAudioDecoder(
+ const cdm::AudioDecoderConfig& audio_decoder_config) {
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ if (!audio_decoder_)
+ audio_decoder_.reset(new media::FFmpegCdmAudioDecoder(host_));
+
+ if (!audio_decoder_->Initialize(audio_decoder_config))
+ return cdm::kSessionError;
+
+ return cdm::kSuccess;
+#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ channel_count_ = audio_decoder_config.channel_count;
+ bits_per_channel_ = audio_decoder_config.bits_per_channel;
+ samples_per_second_ = audio_decoder_config.samples_per_second;
+ return cdm::kSuccess;
+#else
+ NOTIMPLEMENTED();
+ return cdm::kSessionError;
+#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
+}
+
+cdm::Status ClearKeyCdm::InitializeVideoDecoder(
+ const cdm::VideoDecoderConfig& video_decoder_config) {
+ if (video_decoder_ && video_decoder_->is_initialized()) {
+ DCHECK(!video_decoder_->is_initialized());
+ return cdm::kSessionError;
+ }
+
+ // Any uninitialized decoder will be replaced.
+ video_decoder_ = CreateVideoDecoder(host_, video_decoder_config);
+ if (!video_decoder_)
+ return cdm::kSessionError;
+
+ return cdm::kSuccess;
+}
+
+void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type) {
+ DVLOG(1) << "ResetDecoder()";
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ switch (decoder_type) {
+ case cdm::kStreamTypeVideo:
+ video_decoder_->Reset();
+ break;
+ case cdm::kStreamTypeAudio:
+ audio_decoder_->Reset();
+ break;
+ default:
+ NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType";
+ }
+#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ if (decoder_type == cdm::kStreamTypeAudio) {
+ output_timestamp_base_in_microseconds_ = kNoTimestamp;
+ total_samples_generated_ = 0;
+ }
+#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
+}
+
+void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type) {
+ DVLOG(1) << "DeinitializeDecoder()";
+ switch (decoder_type) {
+ case cdm::kStreamTypeVideo:
+ video_decoder_->Deinitialize();
+ break;
+ case cdm::kStreamTypeAudio:
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ audio_decoder_->Deinitialize();
+#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ output_timestamp_base_in_microseconds_ = kNoTimestamp;
+ total_samples_generated_ = 0;
+#endif
+ break;
+ default:
+ NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType";
+ }
+}
+
+cdm::Status ClearKeyCdm::DecryptAndDecodeFrame(
+ const cdm::InputBuffer& encrypted_buffer,
+ cdm::VideoFrame* decoded_frame) {
+ DVLOG(1) << "DecryptAndDecodeFrame()";
+ TRACE_EVENT0("eme", "ClearKeyCdm::DecryptAndDecodeFrame");
+
+ scoped_refptr<media::DecoderBuffer> buffer;
+ cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
+
+ if (status != cdm::kSuccess)
+ return status;
+
+ const uint8_t* data = NULL;
+ int32_t size = 0;
+ int64_t timestamp = 0;
+ if (!buffer->end_of_stream()) {
+ data = buffer->data();
+ size = buffer->data_size();
+ timestamp = encrypted_buffer.timestamp;
+ }
+
+ return video_decoder_->DecodeFrame(data, size, timestamp, decoded_frame);
+}
+
+cdm::Status ClearKeyCdm::DecryptAndDecodeSamples(
+ const cdm::InputBuffer& encrypted_buffer,
+ cdm::AudioFrames* audio_frames) {
+ DVLOG(1) << "DecryptAndDecodeSamples()";
+
+ scoped_refptr<media::DecoderBuffer> buffer;
+ cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
+
+ if (status != cdm::kSuccess)
+ return status;
+
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ const uint8_t* data = NULL;
+ int32_t size = 0;
+ int64_t timestamp = 0;
+ if (!buffer->end_of_stream()) {
+ data = buffer->data();
+ size = buffer->data_size();
+ timestamp = encrypted_buffer.timestamp;
+ }
+
+ return audio_decoder_->DecodeBuffer(data, size, timestamp, audio_frames);
+#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ int64 timestamp_in_microseconds = kNoTimestamp;
+ if (!buffer->end_of_stream()) {
+ timestamp_in_microseconds = buffer->GetTimestamp().InMicroseconds();
+ DCHECK(timestamp_in_microseconds != kNoTimestamp);
+ }
+ return GenerateFakeAudioFrames(timestamp_in_microseconds, audio_frames);
+#else
+ return cdm::kSuccess;
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+}
+
+void ClearKeyCdm::Destroy() {
+ DVLOG(1) << "Destroy()";
+ delete this;
+}
+
+void ClearKeyCdm::ScheduleNextHeartBeat() {
+ // Prepare the next heartbeat message and set timer.
+ std::ostringstream msg_stream;
+ msg_stream << kHeartBeatHeader << " from ClearKey CDM set at time "
+ << host_->GetCurrentWallTimeInSeconds() << ".";
+ next_heartbeat_message_ = msg_stream.str();
+
+ host_->SetTimer(timer_delay_ms_, &next_heartbeat_message_[0]);
+
+ // Use a smaller timer delay at start-up to facilitate testing. Increase the
+ // timer delay up to a limit to avoid message spam.
+ if (timer_delay_ms_ < kMaxTimerDelayMs)
+ timer_delay_ms_ = std::min(2 * timer_delay_ms_, kMaxTimerDelayMs);
+}
+
+cdm::Status ClearKeyCdm::DecryptToMediaDecoderBuffer(
+ const cdm::InputBuffer& encrypted_buffer,
+ scoped_refptr<media::DecoderBuffer>* decrypted_buffer) {
+ DCHECK(decrypted_buffer);
+ scoped_refptr<media::DecoderBuffer> buffer =
+ CopyDecoderBufferFrom(encrypted_buffer);
+
+ if (buffer->end_of_stream()) {
+ *decrypted_buffer = buffer;
+ return cdm::kSuccess;
+ }
+
+ // Callback is called synchronously, so we can use variables on the stack.
+ media::Decryptor::Status status = media::Decryptor::kError;
+ // The AesDecryptor does not care what the stream type is. Pass kVideo
+ // for both audio and video decryption.
+ decryptor_.Decrypt(
+ media::Decryptor::kVideo,
+ buffer,
+ base::Bind(&CopyDecryptResults, &status, decrypted_buffer));
+
+ if (status == media::Decryptor::kError)
+ return cdm::kDecryptError;
+
+ if (status == media::Decryptor::kNoKey)
+ return cdm::kNoKey;
+
+ DCHECK_EQ(status, media::Decryptor::kSuccess);
+ return cdm::kSuccess;
+}
+
+#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+int64 ClearKeyCdm::CurrentTimeStampInMicroseconds() const {
+ return output_timestamp_base_in_microseconds_ +
+ base::Time::kMicrosecondsPerSecond *
+ total_samples_generated_ / samples_per_second_;
+}
+
+int ClearKeyCdm::GenerateFakeAudioFramesFromDuration(
+ int64 duration_in_microseconds,
+ cdm::AudioFrames* audio_frames) const {
+ int64 samples_to_generate = static_cast<double>(samples_per_second_) *
+ duration_in_microseconds / base::Time::kMicrosecondsPerSecond + 0.5;
+ if (samples_to_generate <= 0)
+ return 0;
+
+ int64 bytes_per_sample = channel_count_ * bits_per_channel_ / 8;
+ // |frame_size| must be a multiple of |bytes_per_sample|.
+ int64 frame_size = bytes_per_sample * samples_to_generate;
+
+ int64 timestamp = CurrentTimeStampInMicroseconds();
+
+ const int kHeaderSize = sizeof(timestamp) + sizeof(frame_size);
+ audio_frames->SetFrameBuffer(host_->Allocate(kHeaderSize + frame_size));
+ uint8_t* data = audio_frames->FrameBuffer()->Data();
+
+ memcpy(data, &timestamp, sizeof(timestamp));
+ data += sizeof(timestamp);
+ memcpy(data, &frame_size, sizeof(frame_size));
+ data += sizeof(frame_size);
+ // You won't hear anything because we have all zeros here. But the video
+ // should play just fine!
+ memset(data, 0, frame_size);
+
+ audio_frames->FrameBuffer()->SetSize(kHeaderSize + frame_size);
+
+ return samples_to_generate;
+}
+
+cdm::Status ClearKeyCdm::GenerateFakeAudioFrames(
+ int64 timestamp_in_microseconds,
+ cdm::AudioFrames* audio_frames) {
+ if (timestamp_in_microseconds == kNoTimestamp)
+ return cdm::kNeedMoreData;
+
+ // Return kNeedMoreData for the first frame because duration is unknown.
+ if (output_timestamp_base_in_microseconds_ == kNoTimestamp) {
+ output_timestamp_base_in_microseconds_ = timestamp_in_microseconds;
+ return cdm::kNeedMoreData;
+ }
+
+ int samples_generated = GenerateFakeAudioFramesFromDuration(
+ timestamp_in_microseconds - CurrentTimeStampInMicroseconds(),
+ audio_frames);
+ total_samples_generated_ += samples_generated;
+
+ return samples_generated == 0 ? cdm::kNeedMoreData : cdm::kSuccess;
+}
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+
+} // namespace media
diff --git a/media/cdm/ppapi/clear_key_cdm.h b/media/cdm/ppapi/clear_key_cdm.h
new file mode 100644
index 0000000000..67637eb8d3
--- /dev/null
+++ b/media/cdm/ppapi/clear_key_cdm.h
@@ -0,0 +1,166 @@
+// Copyright 2013 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_CDM_PPAPI_CLEAR_KEY_CDM_H_
+#define MEDIA_CDM_PPAPI_CLEAR_KEY_CDM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/cdm/aes_decryptor.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+
+// Enable this to use the fake decoder for testing.
+// TODO(tomfinegan): Move fake audio decoder into a separate class.
+#if 0
+#define CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+#endif
+
+namespace media {
+class CdmVideoDecoder;
+class DecoderBuffer;
+class FFmpegCdmAudioDecoder;
+
+// Clear key implementation of the cdm::ContentDecryptionModule interface.
+class ClearKeyCdm : public cdm::ContentDecryptionModule {
+ public:
+ explicit ClearKeyCdm(cdm::Host* host);
+ virtual ~ClearKeyCdm();
+
+ // ContentDecryptionModule implementation.
+ virtual cdm::Status GenerateKeyRequest(
+ const char* type, int type_size,
+ const uint8_t* init_data, int init_data_size) OVERRIDE;
+ virtual cdm::Status AddKey(const char* session_id, int session_id_size,
+ const uint8_t* key, int key_size,
+ const uint8_t* key_id, int key_id_size) OVERRIDE;
+ virtual cdm::Status CancelKeyRequest(const char* session_id,
+ int session_id_size) OVERRIDE;
+ virtual void TimerExpired(void* context) OVERRIDE;
+ virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer,
+ cdm::DecryptedBlock* decrypted_block) OVERRIDE;
+ virtual cdm::Status InitializeAudioDecoder(
+ const cdm::AudioDecoderConfig& audio_decoder_config) OVERRIDE;
+ virtual cdm::Status InitializeVideoDecoder(
+ const cdm::VideoDecoderConfig& video_decoder_config) OVERRIDE;
+ virtual void DeinitializeDecoder(cdm::StreamType decoder_type) OVERRIDE;
+ virtual void ResetDecoder(cdm::StreamType decoder_type) OVERRIDE;
+ virtual cdm::Status DecryptAndDecodeFrame(
+ const cdm::InputBuffer& encrypted_buffer,
+ cdm::VideoFrame* video_frame) OVERRIDE;
+ virtual cdm::Status DecryptAndDecodeSamples(
+ const cdm::InputBuffer& encrypted_buffer,
+ cdm::AudioFrames* audio_frames) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+
+ private:
+ // TODO(xhwang): After we removed DecryptorClient. We probably can also remove
+ // this Client class as well. Investigate this possibility.
+ class Client {
+ public:
+ enum Status {
+ kKeyAdded,
+ kKeyError,
+ kKeyMessage
+ };
+
+ Client();
+ virtual ~Client();
+
+ Status status() { return status_; }
+ const std::string& session_id() { return session_id_; }
+ const std::vector<uint8>& key_message() { return key_message_; }
+ const std::string& default_url() { return default_url_; }
+
+ // Resets the Client to a clean state.
+ void Reset();
+
+ void KeyAdded(const std::string& session_id);
+ void KeyError(const std::string& session_id,
+ MediaKeys::KeyError error_code,
+ int system_code);
+ void KeyMessage(const std::string& session_id,
+ const std::vector<uint8>& message,
+ const std::string& default_url);
+
+ private:
+ Status status_;
+ std::string session_id_;
+ std::vector<uint8> key_message_;
+ std::string default_url_;
+ };
+
+ // Prepares next heartbeat message and sets a timer for it.
+ void ScheduleNextHeartBeat();
+
+ // Decrypts the |encrypted_buffer| and puts the result in |decrypted_buffer|.
+ // Returns cdm::kSuccess if decryption succeeded. The decrypted result is
+ // put in |decrypted_buffer|. If |encrypted_buffer| is empty, the
+ // |decrypted_buffer| is set to an empty (EOS) buffer.
+ // Returns cdm::kNoKey if no decryption key was available. In this case
+ // |decrypted_buffer| should be ignored by the caller.
+ // Returns cdm::kDecryptError if any decryption error occurred. In this case
+ // |decrypted_buffer| should be ignored by the caller.
+ cdm::Status DecryptToMediaDecoderBuffer(
+ const cdm::InputBuffer& encrypted_buffer,
+ scoped_refptr<DecoderBuffer>* decrypted_buffer);
+
+#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ int64 CurrentTimeStampInMicroseconds() const;
+
+ // Generates fake video frames with |duration_in_microseconds|.
+ // Returns the number of samples generated in the |audio_frames|.
+ int GenerateFakeAudioFramesFromDuration(int64 duration_in_microseconds,
+ cdm::AudioFrames* audio_frames) const;
+
+ // Generates fake video frames given |input_timestamp|.
+ // Returns cdm::kSuccess if any audio frame is successfully generated.
+ cdm::Status GenerateFakeAudioFrames(int64 timestamp_in_microseconds,
+ cdm::AudioFrames* audio_frames);
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+
+ Client client_;
+ AesDecryptor decryptor_;
+
+ // Protects the |client_| from being accessed by the |decryptor_|
+ // simultaneously.
+ base::Lock client_lock_;
+
+ cdm::Host* host_;
+
+ std::string heartbeat_session_id_;
+ std::string next_heartbeat_message_;
+
+ // Timer delay in milliseconds for the next host_->SetTimer() call.
+ int64 timer_delay_ms_;
+
+ // Indicates whether a timer has been set to prevent multiple timers from
+ // running.
+ bool timer_set_;
+
+#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
+ int channel_count_;
+ int bits_per_channel_;
+ int samples_per_second_;
+ int64 output_timestamp_base_in_microseconds_;
+ int total_samples_generated_;
+#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
+
+#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
+ scoped_ptr<FFmpegCdmAudioDecoder> audio_decoder_;
+#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
+
+ scoped_ptr<CdmVideoDecoder> video_decoder_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClearKeyCdm);
+};
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_CLEAR_KEY_CDM_H_
diff --git a/media/cdm/ppapi/fake_cdm_video_decoder.cc b/media/cdm/ppapi/fake_cdm_video_decoder.cc
new file mode 100644
index 0000000000..b23e5a721c
--- /dev/null
+++ b/media/cdm/ppapi/fake_cdm_video_decoder.cc
@@ -0,0 +1,91 @@
+// Copyright 2013 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/cdm/ppapi/fake_cdm_video_decoder.h"
+
+#include "base/logging.h"
+
+namespace media {
+
+FakeCdmVideoDecoder::FakeCdmVideoDecoder(cdm::Host* host)
+ : is_initialized_(false),
+ host_(host) {
+}
+
+FakeCdmVideoDecoder::~FakeCdmVideoDecoder() {
+ Deinitialize();
+}
+
+bool FakeCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) {
+ DVLOG(1) << "Initialize()";
+
+ video_size_ = config.coded_size;
+ is_initialized_ = true;
+ return true;
+}
+
+void FakeCdmVideoDecoder::Deinitialize() {
+ DVLOG(1) << "Deinitialize()";
+ is_initialized_ = false;
+}
+
+void FakeCdmVideoDecoder::Reset() {
+ DVLOG(1) << "Reset()";
+}
+
+// Creates a YV12 video frame.
+cdm::Status FakeCdmVideoDecoder::DecodeFrame(const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) {
+ DVLOG(1) << "DecodeFrame()";
+
+ // The fake decoder does not buffer any frames internally. So if the input is
+ // empty (EOS), just return kNeedMoreData.
+ if (!decoded_frame)
+ return cdm::kNeedMoreData;
+
+ // Choose non-zero alignment and padding on purpose for testing.
+ const int kAlignment = 8;
+ const int kPadding = 16;
+ const int kPlanePadding = 128;
+
+ int width = video_size_.width;
+ int height = video_size_.height;
+ DCHECK_EQ(width % 2, 0);
+ DCHECK_EQ(height % 2, 0);
+
+ int y_stride = (width + kAlignment - 1) / kAlignment * kAlignment + kPadding;
+ int uv_stride =
+ (width / 2 + kAlignment - 1) / kAlignment * kAlignment + kPadding;
+ int y_rows = height;
+ int uv_rows = height / 2;
+ int y_offset = 0;
+ int v_offset = y_stride * y_rows + kPlanePadding;
+ int u_offset = v_offset + uv_stride * uv_rows + kPlanePadding;
+ int frame_size = u_offset + uv_stride * uv_rows + kPlanePadding;
+
+ decoded_frame->SetFrameBuffer(host_->Allocate(frame_size));
+ decoded_frame->FrameBuffer()->SetSize(frame_size);
+
+ decoded_frame->SetFormat(cdm::kYv12);
+ decoded_frame->SetSize(video_size_);
+ decoded_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, y_offset);
+ decoded_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane, v_offset);
+ decoded_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, u_offset);
+ decoded_frame->SetStride(cdm::VideoFrame::kYPlane, y_stride);
+ decoded_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride);
+ decoded_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride);
+ decoded_frame->SetTimestamp(timestamp);
+
+ static unsigned char color = 0;
+ color += 10;
+
+ memset(reinterpret_cast<void*>(decoded_frame->FrameBuffer()->Data()),
+ color, frame_size);
+
+ return cdm::kSuccess;
+}
+
+} // namespace media
diff --git a/media/cdm/ppapi/fake_cdm_video_decoder.h b/media/cdm/ppapi/fake_cdm_video_decoder.h
new file mode 100644
index 0000000000..05b16ad056
--- /dev/null
+++ b/media/cdm/ppapi/fake_cdm_video_decoder.h
@@ -0,0 +1,41 @@
+// Copyright 2013 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_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_
+#define MEDIA_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+#include "media/cdm/ppapi/cdm_video_decoder.h"
+
+namespace media {
+
+class FakeCdmVideoDecoder : public CdmVideoDecoder {
+ public:
+ explicit FakeCdmVideoDecoder(cdm::Host* host);
+ virtual ~FakeCdmVideoDecoder();
+
+ // CdmVideoDecoder implementation.
+ virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE;
+ virtual void Deinitialize() OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) OVERRIDE;
+ virtual bool is_initialized() const OVERRIDE { return is_initialized_; }
+
+ private:
+ bool is_initialized_;
+ cdm::Size video_size_;
+
+ cdm::Host* const host_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeCdmVideoDecoder);
+};
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_
diff --git a/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc
new file mode 100644
index 0000000000..ed619b2bef
--- /dev/null
+++ b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc
@@ -0,0 +1,412 @@
+// Copyright 2013 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/cdm/ppapi/ffmpeg_cdm_audio_decoder.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/buffers.h"
+#include "media/base/data_buffer.h"
+#include "media/base/limits.h"
+
+// Include FFmpeg header files.
+extern "C" {
+// Temporarily disable possible loss of data warning.
+MSVC_PUSH_DISABLE_WARNING(4244);
+#include <libavcodec/avcodec.h>
+MSVC_POP_WARNING();
+} // extern "C"
+
+namespace media {
+
+// Maximum number of channels with defined layout in src/media.
+static const int kMaxChannels = 8;
+
+static AVCodecID CdmAudioCodecToCodecID(
+ cdm::AudioDecoderConfig::AudioCodec audio_codec) {
+ switch (audio_codec) {
+ case cdm::AudioDecoderConfig::kCodecVorbis:
+ return AV_CODEC_ID_VORBIS;
+ case cdm::AudioDecoderConfig::kCodecAac:
+ return AV_CODEC_ID_AAC;
+ case cdm::AudioDecoderConfig::kUnknownAudioCodec:
+ default:
+ NOTREACHED() << "Unsupported cdm::AudioCodec: " << audio_codec;
+ return AV_CODEC_ID_NONE;
+ }
+}
+
+static void CdmAudioDecoderConfigToAVCodecContext(
+ const cdm::AudioDecoderConfig& config,
+ AVCodecContext* codec_context) {
+ codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
+ codec_context->codec_id = CdmAudioCodecToCodecID(config.codec);
+
+ switch (config.bits_per_channel) {
+ case 8:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_U8;
+ break;
+ case 16:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_S16;
+ break;
+ case 32:
+ codec_context->sample_fmt = AV_SAMPLE_FMT_S32;
+ break;
+ default:
+ DVLOG(1) << "CdmAudioDecoderConfigToAVCodecContext() Unsupported bits "
+ "per channel: " << config.bits_per_channel;
+ codec_context->sample_fmt = AV_SAMPLE_FMT_NONE;
+ }
+
+ codec_context->channels = config.channel_count;
+ codec_context->sample_rate = config.samples_per_second;
+
+ if (config.extra_data) {
+ codec_context->extradata_size = config.extra_data_size;
+ codec_context->extradata = reinterpret_cast<uint8_t*>(
+ av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE));
+ memcpy(codec_context->extradata, config.extra_data,
+ config.extra_data_size);
+ memset(codec_context->extradata + config.extra_data_size, '\0',
+ FF_INPUT_BUFFER_PADDING_SIZE);
+ } else {
+ codec_context->extradata = NULL;
+ codec_context->extradata_size = 0;
+ }
+}
+
+FFmpegCdmAudioDecoder::FFmpegCdmAudioDecoder(cdm::Host* host)
+ : is_initialized_(false),
+ host_(host),
+ codec_context_(NULL),
+ av_frame_(NULL),
+ bits_per_channel_(0),
+ samples_per_second_(0),
+ channels_(0),
+ av_sample_format_(0),
+ bytes_per_frame_(0),
+ last_input_timestamp_(kNoTimestamp()),
+ output_bytes_to_drop_(0) {
+}
+
+FFmpegCdmAudioDecoder::~FFmpegCdmAudioDecoder() {
+ ReleaseFFmpegResources();
+}
+
+bool FFmpegCdmAudioDecoder::Initialize(const cdm::AudioDecoderConfig& config) {
+ DVLOG(1) << "Initialize()";
+
+ if (!IsValidConfig(config)) {
+ LOG(ERROR) << "Initialize(): invalid audio decoder configuration.";
+ return false;
+ }
+
+ if (is_initialized_) {
+ LOG(ERROR) << "Initialize(): Already initialized.";
+ return false;
+ }
+
+ // Initialize AVCodecContext structure.
+ codec_context_ = avcodec_alloc_context3(NULL);
+ CdmAudioDecoderConfigToAVCodecContext(config, codec_context_);
+
+ // MP3 decodes to S16P which we don't support, tell it to use S16 instead.
+ if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P)
+ codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16;
+
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) {
+ DLOG(ERROR) << "Could not initialize audio decoder: "
+ << codec_context_->codec_id;
+ return false;
+ }
+
+ // Ensure avcodec_open2() respected our format request.
+ if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) {
+ DLOG(ERROR) << "Unable to configure a supported sample format: "
+ << codec_context_->sample_fmt;
+ return false;
+ }
+
+ // Some codecs will only output float data, so we need to convert to integer
+ // before returning the decoded buffer.
+ if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP ||
+ codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) {
+ // Preallocate the AudioBus for float conversions. We can treat interleaved
+ // float data as a single planar channel since our output is expected in an
+ // interleaved format anyways.
+ int channels = codec_context_->channels;
+ if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT)
+ channels = 1;
+ converter_bus_ = AudioBus::CreateWrapper(channels);
+ }
+
+ // Success!
+ av_frame_ = avcodec_alloc_frame();
+ bits_per_channel_ = config.bits_per_channel;
+ samples_per_second_ = config.samples_per_second;
+ bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8;
+ output_timestamp_helper_.reset(
+ new AudioTimestampHelper(config.samples_per_second));
+ serialized_audio_frames_.reserve(bytes_per_frame_ * samples_per_second_);
+ is_initialized_ = true;
+
+ // Store initial values to guard against midstream configuration changes.
+ channels_ = codec_context_->channels;
+ av_sample_format_ = codec_context_->sample_fmt;
+
+ return true;
+}
+
+void FFmpegCdmAudioDecoder::Deinitialize() {
+ DVLOG(1) << "Deinitialize()";
+ ReleaseFFmpegResources();
+ is_initialized_ = false;
+ ResetTimestampState();
+}
+
+void FFmpegCdmAudioDecoder::Reset() {
+ DVLOG(1) << "Reset()";
+ avcodec_flush_buffers(codec_context_);
+ ResetTimestampState();
+}
+
+// static
+bool FFmpegCdmAudioDecoder::IsValidConfig(
+ const cdm::AudioDecoderConfig& config) {
+ return config.codec != cdm::AudioDecoderConfig::kUnknownAudioCodec &&
+ config.channel_count > 0 &&
+ config.channel_count <= kMaxChannels &&
+ config.bits_per_channel > 0 &&
+ config.bits_per_channel <= limits::kMaxBitsPerSample &&
+ config.samples_per_second > 0 &&
+ config.samples_per_second <= limits::kMaxSampleRate;
+}
+
+cdm::Status FFmpegCdmAudioDecoder::DecodeBuffer(
+ const uint8_t* compressed_buffer,
+ int32_t compressed_buffer_size,
+ int64_t input_timestamp,
+ cdm::AudioFrames* decoded_frames) {
+ DVLOG(1) << "DecodeBuffer()";
+ const bool is_end_of_stream = !compressed_buffer;
+ base::TimeDelta timestamp =
+ base::TimeDelta::FromMicroseconds(input_timestamp);
+
+ bool is_vorbis = codec_context_->codec_id == AV_CODEC_ID_VORBIS;
+ if (!is_end_of_stream) {
+ if (last_input_timestamp_ == kNoTimestamp()) {
+ if (is_vorbis && timestamp < base::TimeDelta()) {
+ // Dropping frames for negative timestamps as outlined in section A.2
+ // in the Vorbis spec. http://xiph.org/vorbis/doc/Vorbis_I_spec.html
+ int frames_to_drop = floor(
+ 0.5 + -timestamp.InSecondsF() * samples_per_second_);
+ output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop;
+ } else {
+ last_input_timestamp_ = timestamp;
+ }
+ } else if (timestamp != kNoTimestamp()) {
+ if (timestamp < last_input_timestamp_) {
+ base::TimeDelta diff = timestamp - last_input_timestamp_;
+ DVLOG(1) << "Input timestamps are not monotonically increasing! "
+ << " ts " << timestamp.InMicroseconds() << " us"
+ << " diff " << diff.InMicroseconds() << " us";
+ return cdm::kDecodeError;
+ }
+
+ last_input_timestamp_ = timestamp;
+ }
+ }
+
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = const_cast<uint8_t*>(compressed_buffer);
+ packet.size = compressed_buffer_size;
+
+ // Each audio packet may contain several frames, so we must call the decoder
+ // until we've exhausted the packet. Regardless of the packet size we always
+ // want to hand it to the decoder at least once, otherwise we would end up
+ // skipping end of stream packets since they have a size of zero.
+ do {
+ // Reset frame to default values.
+ avcodec_get_frame_defaults(av_frame_);
+
+ int frame_decoded = 0;
+ int result = avcodec_decode_audio4(
+ codec_context_, av_frame_, &frame_decoded, &packet);
+
+ if (result < 0) {
+ DCHECK(!is_end_of_stream)
+ << "End of stream buffer produced an error! "
+ << "This is quite possibly a bug in the audio decoder not handling "
+ << "end of stream AVPackets correctly.";
+
+ DLOG(ERROR)
+ << "Error decoding an audio frame with timestamp: "
+ << timestamp.InMicroseconds() << " us, duration: "
+ << timestamp.InMicroseconds() << " us, packet size: "
+ << compressed_buffer_size << " bytes";
+
+ return cdm::kDecodeError;
+ }
+
+ // Update packet size and data pointer in case we need to call the decoder
+ // with the remaining bytes from this packet.
+ packet.size -= result;
+ packet.data += result;
+
+ if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() &&
+ !is_end_of_stream) {
+ DCHECK(timestamp != kNoTimestamp());
+ if (output_bytes_to_drop_ > 0) {
+ // Currently Vorbis is the only codec that causes us to drop samples.
+ // If we have to drop samples it always means the timeline starts at 0.
+ DCHECK_EQ(codec_context_->codec_id, AV_CODEC_ID_VORBIS);
+ output_timestamp_helper_->SetBaseTimestamp(base::TimeDelta());
+ } else {
+ output_timestamp_helper_->SetBaseTimestamp(timestamp);
+ }
+ }
+
+ int decoded_audio_size = 0;
+ if (frame_decoded) {
+ if (av_frame_->sample_rate != samples_per_second_ ||
+ av_frame_->channels != channels_ ||
+ av_frame_->format != av_sample_format_) {
+ DLOG(ERROR) << "Unsupported midstream configuration change!"
+ << " Sample Rate: " << av_frame_->sample_rate << " vs "
+ << samples_per_second_
+ << ", Channels: " << av_frame_->channels << " vs "
+ << channels_
+ << ", Sample Format: " << av_frame_->format << " vs "
+ << av_sample_format_;
+ return cdm::kDecodeError;
+ }
+
+ decoded_audio_size = av_samples_get_buffer_size(
+ NULL, codec_context_->channels, av_frame_->nb_samples,
+ codec_context_->sample_fmt, 1);
+ // If we're decoding into float, adjust audio size.
+ if (converter_bus_ && bits_per_channel_ / 8 != sizeof(float)) {
+ DCHECK(codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT ||
+ codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP);
+ decoded_audio_size *=
+ static_cast<float>(bits_per_channel_ / 8) / sizeof(float);
+ }
+ }
+
+ int start_sample = 0;
+ if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) {
+ DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0)
+ << "Decoder didn't output full frames";
+
+ int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_);
+ start_sample = dropped_size / bytes_per_frame_;
+ decoded_audio_size -= dropped_size;
+ output_bytes_to_drop_ -= dropped_size;
+ }
+
+ scoped_refptr<DataBuffer> output;
+ if (decoded_audio_size > 0) {
+ DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0)
+ << "Decoder didn't output full frames";
+
+ // Convert float data using an AudioBus.
+ if (converter_bus_) {
+ // Setup the AudioBus as a wrapper of the AVFrame data and then use
+ // AudioBus::ToInterleaved() to convert the data as necessary.
+ int skip_frames = start_sample;
+ int total_frames = av_frame_->nb_samples;
+ int frames_to_interleave = decoded_audio_size / bytes_per_frame_;
+ if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) {
+ DCHECK_EQ(converter_bus_->channels(), 1);
+ total_frames *= codec_context_->channels;
+ skip_frames *= codec_context_->channels;
+ frames_to_interleave *= codec_context_->channels;
+ }
+
+ converter_bus_->set_frames(total_frames);
+ for (int i = 0; i < converter_bus_->channels(); ++i) {
+ converter_bus_->SetChannelData(i, reinterpret_cast<float*>(
+ av_frame_->extended_data[i]));
+ }
+
+ output = new DataBuffer(decoded_audio_size);
+ output->set_data_size(decoded_audio_size);
+
+ DCHECK_EQ(frames_to_interleave, converter_bus_->frames() - skip_frames);
+ converter_bus_->ToInterleavedPartial(
+ skip_frames, frames_to_interleave, bits_per_channel_ / 8,
+ output->writable_data());
+ } else {
+ output = DataBuffer::CopyFrom(
+ av_frame_->extended_data[0] + start_sample * bytes_per_frame_,
+ decoded_audio_size);
+ }
+
+ base::TimeDelta output_timestamp =
+ output_timestamp_helper_->GetTimestamp();
+ output_timestamp_helper_->AddFrames(decoded_audio_size /
+ bytes_per_frame_);
+
+ // Serialize the audio samples into |serialized_audio_frames_|.
+ SerializeInt64(output_timestamp.InMicroseconds());
+ SerializeInt64(output->data_size());
+ serialized_audio_frames_.insert(
+ serialized_audio_frames_.end(),
+ output->data(),
+ output->data() + output->data_size());
+ }
+ } while (packet.size > 0);
+
+ if (!serialized_audio_frames_.empty()) {
+ decoded_frames->SetFrameBuffer(
+ host_->Allocate(serialized_audio_frames_.size()));
+ if (!decoded_frames->FrameBuffer()) {
+ LOG(ERROR) << "DecodeBuffer() cdm::Host::Allocate failed.";
+ return cdm::kDecodeError;
+ }
+ memcpy(decoded_frames->FrameBuffer()->Data(),
+ &serialized_audio_frames_[0],
+ serialized_audio_frames_.size());
+ decoded_frames->FrameBuffer()->SetSize(serialized_audio_frames_.size());
+ serialized_audio_frames_.clear();
+
+ return cdm::kSuccess;
+ }
+
+ return cdm::kNeedMoreData;
+}
+
+void FFmpegCdmAudioDecoder::ResetTimestampState() {
+ output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp());
+ last_input_timestamp_ = kNoTimestamp();
+ output_bytes_to_drop_ = 0;
+}
+
+void FFmpegCdmAudioDecoder::ReleaseFFmpegResources() {
+ DVLOG(1) << "ReleaseFFmpegResources()";
+
+ if (codec_context_) {
+ av_free(codec_context_->extradata);
+ avcodec_close(codec_context_);
+ av_free(codec_context_);
+ codec_context_ = NULL;
+ }
+ if (av_frame_) {
+ av_free(av_frame_);
+ av_frame_ = NULL;
+ }
+}
+
+void FFmpegCdmAudioDecoder::SerializeInt64(int64 value) {
+ int previous_size = serialized_audio_frames_.size();
+ serialized_audio_frames_.resize(previous_size + sizeof(value));
+ memcpy(&serialized_audio_frames_[0] + previous_size, &value, sizeof(value));
+}
+
+} // namespace media
diff --git a/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h
new file mode 100644
index 0000000000..1b4fb8f2af
--- /dev/null
+++ b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h
@@ -0,0 +1,99 @@
+// Copyright 2013 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_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_
+#define MEDIA_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+
+struct AVCodecContext;
+struct AVFrame;
+
+namespace media {
+class AudioBus;
+class AudioTimestampHelper;
+}
+
+namespace media {
+
+// TODO(xhwang): This class is partially cloned from FFmpegAudioDecoder. When
+// FFmpegAudioDecoder is updated, it's a pain to keep this class in sync with
+// FFmpegAudioDecoder. We need a long term sustainable solution for this. See
+// http://crbug.com/169203
+class FFmpegCdmAudioDecoder {
+ public:
+ explicit FFmpegCdmAudioDecoder(cdm::Host* host);
+ ~FFmpegCdmAudioDecoder();
+ bool Initialize(const cdm::AudioDecoderConfig& config);
+ void Deinitialize();
+ void Reset();
+
+ // Returns true when |config| is a valid audio decoder configuration.
+ static bool IsValidConfig(const cdm::AudioDecoderConfig& config);
+
+ // Decodes |compressed_buffer|. Returns |cdm::kSuccess| after storing
+ // output in |decoded_frames| when output is available. Returns
+ // |cdm::kNeedMoreData| when |compressed_frame| does not produce output.
+ // Returns |cdm::kDecodeError| when decoding fails.
+ //
+ // A null |compressed_buffer| will attempt to flush the decoder of any
+ // remaining frames. |compressed_buffer_size| and |timestamp| are ignored.
+ cdm::Status DecodeBuffer(const uint8_t* compressed_buffer,
+ int32_t compressed_buffer_size,
+ int64_t timestamp,
+ cdm::AudioFrames* decoded_frames);
+
+ private:
+ void ResetTimestampState();
+ void ReleaseFFmpegResources();
+
+ base::TimeDelta GetNextOutputTimestamp() const;
+
+ void SerializeInt64(int64_t value);
+
+ bool is_initialized_;
+
+ cdm::Host* const host_;
+
+ // FFmpeg structures owned by this object.
+ AVCodecContext* codec_context_;
+ AVFrame* av_frame_;
+
+ // Audio format.
+ int bits_per_channel_;
+ int samples_per_second_;
+ int channels_;
+
+ // AVSampleFormat initially requested; not Chrome's SampleFormat.
+ int av_sample_format_;
+
+ // Used for computing output timestamps.
+ scoped_ptr<AudioTimestampHelper> output_timestamp_helper_;
+ int bytes_per_frame_;
+ base::TimeDelta last_input_timestamp_;
+
+ // We may need to convert the audio data coming out of FFmpeg from planar
+ // float to integer.
+ scoped_ptr<AudioBus> converter_bus_;
+
+ // Number of output sample bytes to drop before generating output buffers.
+ // This is required for handling negative timestamps when decoding Vorbis
+ // audio, for example.
+ int output_bytes_to_drop_;
+
+ typedef std::vector<uint8_t> SerializedAudioFrames;
+ SerializedAudioFrames serialized_audio_frames_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegCdmAudioDecoder);
+};
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_
diff --git a/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc
new file mode 100644
index 0000000000..9a2439d299
--- /dev/null
+++ b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc
@@ -0,0 +1,344 @@
+// Copyright 2013 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/cdm/ppapi/ffmpeg_cdm_video_decoder.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/buffers.h"
+#include "media/base/limits.h"
+
+// Include FFmpeg header files.
+extern "C" {
+// Temporarily disable possible loss of data warning.
+MSVC_PUSH_DISABLE_WARNING(4244);
+#include <libavcodec/avcodec.h>
+MSVC_POP_WARNING();
+} // extern "C"
+
+namespace media {
+
+static const int kDecodeThreads = 1;
+
+static cdm::VideoFormat PixelFormatToCdmVideoFormat(PixelFormat pixel_format) {
+ switch (pixel_format) {
+ case PIX_FMT_YUV420P:
+ return cdm::kYv12;
+ default:
+ DVLOG(1) << "Unsupported PixelFormat: " << pixel_format;
+ }
+ return cdm::kUnknownVideoFormat;
+}
+
+static PixelFormat CdmVideoFormatToPixelFormat(cdm::VideoFormat video_format) {
+ switch (video_format) {
+ case cdm::kYv12:
+ case cdm::kI420:
+ return PIX_FMT_YUV420P;
+ case cdm::kUnknownVideoFormat:
+ default:
+ DVLOG(1) << "Unsupported cdm::VideoFormat: " << video_format;
+ }
+ return PIX_FMT_NONE;
+}
+
+static AVCodecID CdmVideoCodecToCodecID(
+ cdm::VideoDecoderConfig::VideoCodec video_codec) {
+ switch (video_codec) {
+ case cdm::VideoDecoderConfig::kCodecVp8:
+ return AV_CODEC_ID_VP8;
+ case cdm::VideoDecoderConfig::kCodecH264:
+ return AV_CODEC_ID_H264;
+ case cdm::VideoDecoderConfig::kUnknownVideoCodec:
+ default:
+ NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec;
+ return AV_CODEC_ID_NONE;
+ }
+}
+
+static int CdmVideoCodecProfileToProfileID(
+ cdm::VideoDecoderConfig::VideoCodecProfile profile) {
+ switch (profile) {
+ case cdm::VideoDecoderConfig::kVp8ProfileMain:
+ return FF_PROFILE_UNKNOWN; // VP8 does not define an FFmpeg profile.
+ case cdm::VideoDecoderConfig::kH264ProfileBaseline:
+ return FF_PROFILE_H264_BASELINE;
+ case cdm::VideoDecoderConfig::kH264ProfileMain:
+ return FF_PROFILE_H264_MAIN;
+ case cdm::VideoDecoderConfig::kH264ProfileExtended:
+ return FF_PROFILE_H264_EXTENDED;
+ case cdm::VideoDecoderConfig::kH264ProfileHigh:
+ return FF_PROFILE_H264_HIGH;
+ case cdm::VideoDecoderConfig::kH264ProfileHigh10:
+ return FF_PROFILE_H264_HIGH_10;
+ case cdm::VideoDecoderConfig::kH264ProfileHigh422:
+ return FF_PROFILE_H264_HIGH_422;
+ case cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive:
+ return FF_PROFILE_H264_HIGH_444_PREDICTIVE;
+ case cdm::VideoDecoderConfig::kUnknownVideoCodecProfile:
+ default:
+ NOTREACHED() << "Unknown cdm::VideoCodecProfile: " << profile;
+ return FF_PROFILE_UNKNOWN;
+ }
+}
+
+static void CdmVideoDecoderConfigToAVCodecContext(
+ const cdm::VideoDecoderConfig& config,
+ AVCodecContext* codec_context) {
+ codec_context->codec_type = AVMEDIA_TYPE_VIDEO;
+ codec_context->codec_id = CdmVideoCodecToCodecID(config.codec);
+ codec_context->profile = CdmVideoCodecProfileToProfileID(config.profile);
+ codec_context->coded_width = config.coded_size.width;
+ codec_context->coded_height = config.coded_size.height;
+ codec_context->pix_fmt = CdmVideoFormatToPixelFormat(config.format);
+
+ if (config.extra_data) {
+ codec_context->extradata_size = config.extra_data_size;
+ codec_context->extradata = reinterpret_cast<uint8_t*>(
+ av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE));
+ memcpy(codec_context->extradata, config.extra_data,
+ config.extra_data_size);
+ memset(codec_context->extradata + config.extra_data_size, 0,
+ FF_INPUT_BUFFER_PADDING_SIZE);
+ } else {
+ codec_context->extradata = NULL;
+ codec_context->extradata_size = 0;
+ }
+}
+
+static void CopyPlane(const uint8_t* source,
+ int32_t source_stride,
+ int32_t target_stride,
+ int32_t rows,
+ int32_t copy_bytes_per_row,
+ uint8_t* target) {
+ DCHECK(source);
+ DCHECK(target);
+ DCHECK_LE(copy_bytes_per_row, source_stride);
+ DCHECK_LE(copy_bytes_per_row, target_stride);
+
+ for (int i = 0; i < rows; ++i) {
+ const int source_offset = i * source_stride;
+ const int target_offset = i * target_stride;
+ memcpy(target + target_offset,
+ source + source_offset,
+ copy_bytes_per_row);
+ }
+}
+
+FFmpegCdmVideoDecoder::FFmpegCdmVideoDecoder(cdm::Host* host)
+ : codec_context_(NULL),
+ av_frame_(NULL),
+ is_initialized_(false),
+ host_(host) {
+}
+
+FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() {
+ ReleaseFFmpegResources();
+}
+
+bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) {
+ DVLOG(1) << "Initialize()";
+
+ if (!IsValidOutputConfig(config.format, config.coded_size)) {
+ LOG(ERROR) << "Initialize(): invalid video decoder configuration.";
+ return false;
+ }
+
+ if (is_initialized_) {
+ LOG(ERROR) << "Initialize(): Already initialized.";
+ return false;
+ }
+
+ // Initialize AVCodecContext structure.
+ codec_context_ = avcodec_alloc_context3(NULL);
+ CdmVideoDecoderConfigToAVCodecContext(config, codec_context_);
+
+ // Enable motion vector search (potentially slow), strong deblocking filter
+ // for damaged macroblocks, and set our error detection sensitivity.
+ codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
+ codec_context_->err_recognition = AV_EF_CAREFUL;
+ codec_context_->thread_count = kDecodeThreads;
+ codec_context_->opaque = this;
+ codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
+
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ if (!codec) {
+ LOG(ERROR) << "Initialize(): avcodec_find_decoder failed.";
+ return false;
+ }
+
+ int status;
+ if ((status = avcodec_open2(codec_context_, codec, NULL)) < 0) {
+ LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status;
+ return false;
+ }
+
+ av_frame_ = avcodec_alloc_frame();
+ is_initialized_ = true;
+
+ return true;
+}
+
+void FFmpegCdmVideoDecoder::Deinitialize() {
+ DVLOG(1) << "Deinitialize()";
+ ReleaseFFmpegResources();
+ is_initialized_ = false;
+}
+
+void FFmpegCdmVideoDecoder::Reset() {
+ DVLOG(1) << "Reset()";
+ avcodec_flush_buffers(codec_context_);
+}
+
+// static
+bool FFmpegCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format,
+ const cdm::Size& data_size) {
+ return ((format == cdm::kYv12 || format == cdm::kI420) &&
+ (data_size.width % 2) == 0 && (data_size.height % 2) == 0 &&
+ data_size.width > 0 && data_size.height > 0 &&
+ data_size.width <= limits::kMaxDimension &&
+ data_size.height <= limits::kMaxDimension &&
+ data_size.width * data_size.height <= limits::kMaxCanvas);
+}
+
+cdm::Status FFmpegCdmVideoDecoder::DecodeFrame(
+ const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) {
+ DVLOG(1) << "DecodeFrame()";
+ DCHECK(decoded_frame);
+
+ // Create a packet for input data.
+ AVPacket packet;
+ av_init_packet(&packet);
+
+ // The FFmpeg API does not allow us to have const read-only pointers.
+ packet.data = const_cast<uint8_t*>(compressed_frame);
+ packet.size = compressed_frame_size;
+
+ // Let FFmpeg handle presentation timestamp reordering.
+ codec_context_->reordered_opaque = timestamp;
+
+ // Reset frame to default values.
+ avcodec_get_frame_defaults(av_frame_);
+
+ // This is for codecs not using get_buffer to initialize
+ // |av_frame_->reordered_opaque|
+ av_frame_->reordered_opaque = codec_context_->reordered_opaque;
+
+ int frame_decoded = 0;
+ int result = avcodec_decode_video2(codec_context_,
+ av_frame_,
+ &frame_decoded,
+ &packet);
+ // Log the problem when we can't decode a video frame and exit early.
+ if (result < 0) {
+ LOG(ERROR) << "DecodeFrame(): Error decoding video frame with timestamp: "
+ << timestamp << " us, packet size: " << packet.size << " bytes";
+ return cdm::kDecodeError;
+ }
+
+ // If no frame was produced then signal that more data is required to produce
+ // more frames.
+ if (frame_decoded == 0)
+ return cdm::kNeedMoreData;
+
+ // The decoder is in a bad state and not decoding correctly.
+ // Checking for NULL avoids a crash.
+ if (!av_frame_->data[cdm::VideoFrame::kYPlane] ||
+ !av_frame_->data[cdm::VideoFrame::kUPlane] ||
+ !av_frame_->data[cdm::VideoFrame::kVPlane]) {
+ LOG(ERROR) << "DecodeFrame(): Video frame has invalid frame data.";
+ return cdm::kDecodeError;
+ }
+
+ if (!CopyAvFrameTo(decoded_frame)) {
+ LOG(ERROR) << "DecodeFrame() could not copy video frame to output buffer.";
+ return cdm::kDecodeError;
+ }
+
+ return cdm::kSuccess;
+}
+
+bool FFmpegCdmVideoDecoder::CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame) {
+ DCHECK(cdm_video_frame);
+ DCHECK_EQ(av_frame_->format, PIX_FMT_YUV420P);
+ DCHECK_EQ(av_frame_->width % 2, 0);
+ DCHECK_EQ(av_frame_->height % 2, 0);
+
+ const int y_size = av_frame_->width * av_frame_->height;
+ const int uv_size = y_size / 2;
+ const int space_required = y_size + (uv_size * 2);
+
+ DCHECK(!cdm_video_frame->FrameBuffer());
+ cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required));
+ if (!cdm_video_frame->FrameBuffer()) {
+ LOG(ERROR) << "CopyAvFrameTo() cdm::Host::Allocate failed.";
+ return false;
+ }
+ cdm_video_frame->FrameBuffer()->SetSize(space_required);
+
+ CopyPlane(av_frame_->data[cdm::VideoFrame::kYPlane],
+ av_frame_->linesize[cdm::VideoFrame::kYPlane],
+ av_frame_->width,
+ av_frame_->height,
+ av_frame_->width,
+ cdm_video_frame->FrameBuffer()->Data());
+
+ const int uv_stride = av_frame_->width / 2;
+ const int uv_rows = av_frame_->height / 2;
+ CopyPlane(av_frame_->data[cdm::VideoFrame::kUPlane],
+ av_frame_->linesize[cdm::VideoFrame::kUPlane],
+ uv_stride,
+ uv_rows,
+ uv_stride,
+ cdm_video_frame->FrameBuffer()->Data() + y_size);
+
+ CopyPlane(av_frame_->data[cdm::VideoFrame::kVPlane],
+ av_frame_->linesize[cdm::VideoFrame::kVPlane],
+ uv_stride,
+ uv_rows,
+ uv_stride,
+ cdm_video_frame->FrameBuffer()->Data() + y_size + uv_size);
+
+ PixelFormat format = static_cast<PixelFormat>(av_frame_->format);
+ cdm_video_frame->SetFormat(PixelFormatToCdmVideoFormat(format));
+
+ cdm::Size video_frame_size;
+ video_frame_size.width = av_frame_->width;
+ video_frame_size.height = av_frame_->height;
+ cdm_video_frame->SetSize(video_frame_size);
+
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0);
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size);
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane,
+ y_size + uv_size);
+
+ cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane, av_frame_->width);
+ cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride);
+ cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride);
+
+ cdm_video_frame->SetTimestamp(av_frame_->reordered_opaque);
+
+ return true;
+}
+
+void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() {
+ DVLOG(1) << "ReleaseFFmpegResources()";
+
+ if (codec_context_) {
+ av_free(codec_context_->extradata);
+ avcodec_close(codec_context_);
+ av_free(codec_context_);
+ codec_context_ = NULL;
+ }
+ if (av_frame_) {
+ av_free(av_frame_);
+ av_frame_ = NULL;
+ }
+}
+
+} // namespace media
diff --git a/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h
new file mode 100644
index 0000000000..17e2b5783b
--- /dev/null
+++ b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h
@@ -0,0 +1,58 @@
+// Copyright 2013 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_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_
+#define MEDIA_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+#include "media/cdm/ppapi/cdm_video_decoder.h"
+
+struct AVCodecContext;
+struct AVFrame;
+
+namespace media {
+
+class FFmpegCdmVideoDecoder : public CdmVideoDecoder {
+ public:
+ explicit FFmpegCdmVideoDecoder(cdm::Host* host);
+ virtual ~FFmpegCdmVideoDecoder();
+
+ // CdmVideoDecoder implementation.
+ virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE;
+ virtual void Deinitialize() OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) OVERRIDE;
+ virtual bool is_initialized() const OVERRIDE { return is_initialized_; }
+
+ // Returns true when |format| and |data_size| specify a supported video
+ // output configuration.
+ static bool IsValidOutputConfig(cdm::VideoFormat format,
+ const cdm::Size& data_size);
+
+ private:
+ // Allocates storage, then copies video frame stored in |av_frame_| to
+ // |cdm_video_frame|. Returns true when allocation and copy succeed.
+ bool CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame);
+
+ void ReleaseFFmpegResources();
+
+ // FFmpeg structures owned by this object.
+ AVCodecContext* codec_context_;
+ AVFrame* av_frame_;
+
+ bool is_initialized_;
+
+ cdm::Host* const host_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegCdmVideoDecoder);
+};
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_
diff --git a/media/cdm/ppapi/libvpx_cdm_video_decoder.cc b/media/cdm/ppapi/libvpx_cdm_video_decoder.cc
new file mode 100644
index 0000000000..a81c48558d
--- /dev/null
+++ b/media/cdm/ppapi/libvpx_cdm_video_decoder.cc
@@ -0,0 +1,195 @@
+// Copyright 2013 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/cdm/ppapi/libvpx_cdm_video_decoder.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/buffers.h"
+#include "media/base/limits.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" {
+// Note: vpx_decoder.h must be first or compile will fail.
+#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" // NOLINT
+#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h"
+}
+
+// Enable USE_COPYPLANE_WITH_LIBVPX to use |CopyPlane()| instead of memcpy to
+// copy video frame data.
+// #define USE_COPYPLANE_WITH_LIBVPX 1
+
+namespace media {
+
+static const int kDecodeThreads = 2;
+
+LibvpxCdmVideoDecoder::LibvpxCdmVideoDecoder(cdm::Host* host)
+ : is_initialized_(false),
+ host_(host),
+ vpx_codec_(NULL),
+ vpx_image_(NULL) {
+}
+
+LibvpxCdmVideoDecoder::~LibvpxCdmVideoDecoder() {
+ Deinitialize();
+}
+
+bool LibvpxCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) {
+ DVLOG(1) << "Initialize()";
+
+ if (!IsValidOutputConfig(config.format, config.coded_size)) {
+ LOG(ERROR) << "Initialize(): invalid video decoder configuration.";
+ return false;
+ }
+
+ if (is_initialized_) {
+ LOG(ERROR) << "Initialize(): Already initialized.";
+ return false;
+ }
+
+ 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 = kDecodeThreads;
+
+ vpx_codec_err_t status = vpx_codec_dec_init(vpx_codec_,
+ vpx_codec_vp8_dx(),
+ &vpx_config,
+ 0);
+ if (status != VPX_CODEC_OK) {
+ LOG(ERROR) << "InitializeLibvpx(): vpx_codec_dec_init failed, ret="
+ << status;
+ delete vpx_codec_;
+ vpx_codec_ = NULL;
+ }
+
+ is_initialized_ = true;
+ return true;
+}
+
+void LibvpxCdmVideoDecoder::Deinitialize() {
+ DVLOG(1) << "Deinitialize()";
+
+ if (vpx_codec_) {
+ vpx_codec_destroy(vpx_codec_);
+ vpx_codec_ = NULL;
+ }
+
+ is_initialized_ = false;
+}
+
+void LibvpxCdmVideoDecoder::Reset() {
+ DVLOG(1) << "Reset()";
+}
+
+// static
+bool LibvpxCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format,
+ const cdm::Size& data_size) {
+ return ((format == cdm::kYv12 || format == cdm::kI420) &&
+ (data_size.width % 2) == 0 && (data_size.height % 2) == 0 &&
+ data_size.width > 0 && data_size.height > 0 &&
+ data_size.width <= limits::kMaxDimension &&
+ data_size.height <= limits::kMaxDimension &&
+ data_size.width * data_size.height <= limits::kMaxCanvas);
+}
+
+cdm::Status LibvpxCdmVideoDecoder::DecodeFrame(
+ const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) {
+ DVLOG(1) << "DecodeFrame()";
+ DCHECK(decoded_frame);
+
+ // Pass |compressed_frame| to libvpx.
+ void* user_priv = reinterpret_cast<void*>(&timestamp);
+ vpx_codec_err_t status = vpx_codec_decode(vpx_codec_,
+ compressed_frame,
+ compressed_frame_size,
+ user_priv,
+ 0);
+ if (status != VPX_CODEC_OK) {
+ LOG(ERROR) << "DecodeFrameLibvpx(): vpx_codec_decode failed, status="
+ << status;
+ return cdm::kDecodeError;
+ }
+
+ // Gets pointer to decoded data.
+ vpx_codec_iter_t iter = NULL;
+ vpx_image_ = vpx_codec_get_frame(vpx_codec_, &iter);
+ if (!vpx_image_)
+ return cdm::kNeedMoreData;
+
+ if (vpx_image_->user_priv != reinterpret_cast<void*>(&timestamp)) {
+ LOG(ERROR) << "DecodeFrameLibvpx() invalid output timestamp.";
+ return cdm::kDecodeError;
+ }
+ decoded_frame->SetTimestamp(timestamp);
+
+ if (!CopyVpxImageTo(decoded_frame)) {
+ LOG(ERROR) << "DecodeFrameLibvpx() could not copy vpx image to output "
+ << "buffer.";
+ return cdm::kDecodeError;
+ }
+
+ return cdm::kSuccess;
+}
+
+bool LibvpxCdmVideoDecoder::CopyVpxImageTo(cdm::VideoFrame* cdm_video_frame) {
+ DCHECK(cdm_video_frame);
+ DCHECK_EQ(vpx_image_->fmt, VPX_IMG_FMT_I420);
+ DCHECK_EQ(vpx_image_->d_w % 2, 0U);
+ DCHECK_EQ(vpx_image_->d_h % 2, 0U);
+
+ const int y_size = vpx_image_->stride[VPX_PLANE_Y] * vpx_image_->d_h;
+ const int uv_rows = vpx_image_->d_h / 2;
+ const int u_size = vpx_image_->stride[VPX_PLANE_U] * uv_rows;
+ const int v_size = vpx_image_->stride[VPX_PLANE_V] * uv_rows;
+ const int space_required = y_size + u_size + v_size;
+
+ DCHECK(!cdm_video_frame->FrameBuffer());
+ cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required));
+ if (!cdm_video_frame->FrameBuffer()) {
+ LOG(ERROR) << "CopyVpxImageTo() cdm::Host::Allocate failed.";
+ return false;
+ }
+ cdm_video_frame->FrameBuffer()->SetSize(space_required);
+
+ memcpy(cdm_video_frame->FrameBuffer()->Data(),
+ vpx_image_->planes[VPX_PLANE_Y],
+ y_size);
+ memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size,
+ vpx_image_->planes[VPX_PLANE_U],
+ u_size);
+ memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size + u_size,
+ vpx_image_->planes[VPX_PLANE_V],
+ v_size);
+
+ cdm_video_frame->SetFormat(cdm::kYv12);
+
+ cdm::Size video_frame_size;
+ video_frame_size.width = vpx_image_->d_w;
+ video_frame_size.height = vpx_image_->d_h;
+ cdm_video_frame->SetSize(video_frame_size);
+
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0);
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size);
+ cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane,
+ y_size + u_size);
+
+ cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane,
+ vpx_image_->stride[VPX_PLANE_Y]);
+ cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane,
+ vpx_image_->stride[VPX_PLANE_U]);
+ cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane,
+ vpx_image_->stride[VPX_PLANE_V]);
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/cdm/ppapi/libvpx_cdm_video_decoder.h b/media/cdm/ppapi/libvpx_cdm_video_decoder.h
new file mode 100644
index 0000000000..3edaa07622
--- /dev/null
+++ b/media/cdm/ppapi/libvpx_cdm_video_decoder.h
@@ -0,0 +1,55 @@
+// Copyright 2013 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_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_
+#define MEDIA_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/cdm/ppapi/api/content_decryption_module.h"
+#include "media/cdm/ppapi/cdm_video_decoder.h"
+
+struct vpx_codec_ctx;
+struct vpx_image;
+
+namespace media {
+
+class LibvpxCdmVideoDecoder : public CdmVideoDecoder {
+ public:
+ explicit LibvpxCdmVideoDecoder(cdm::Host* host);
+ virtual ~LibvpxCdmVideoDecoder();
+
+ // CdmVideoDecoder implementation.
+ virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE;
+ virtual void Deinitialize() OVERRIDE;
+ virtual void Reset() OVERRIDE;
+ virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame,
+ int32_t compressed_frame_size,
+ int64_t timestamp,
+ cdm::VideoFrame* decoded_frame) OVERRIDE;
+ virtual bool is_initialized() const OVERRIDE { return is_initialized_; }
+
+ // Returns true when |format| and |data_size| specify a supported video
+ // output configuration.
+ static bool IsValidOutputConfig(cdm::VideoFormat format,
+ const cdm::Size& data_size);
+
+ private:
+ // Allocates storage, then copies video frame stored in |vpx_image_| to
+ // |cdm_video_frame|. Returns true when allocation and copy succeed.
+ bool CopyVpxImageTo(cdm::VideoFrame* cdm_video_frame);
+
+ bool is_initialized_;
+
+ cdm::Host* const host_;
+
+ vpx_codec_ctx* vpx_codec_;
+ vpx_image* vpx_image_;
+
+ DISALLOW_COPY_AND_ASSIGN(LibvpxCdmVideoDecoder);
+};
+
+} // namespace media
+
+#endif // MEDIA_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_
diff --git a/media/cdm/ppapi/linked_ptr.h b/media/cdm/ppapi/linked_ptr.h
new file mode 100644
index 0000000000..f3eccbbcf0
--- /dev/null
+++ b/media/cdm/ppapi/linked_ptr.h
@@ -0,0 +1,185 @@
+// Copyright 2013 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.
+//
+// This is a copy of base/linked_ptr.h with CHECKS/DCHECKS replaced with
+// PP_DCHECKs.
+//
+// A "smart" pointer type with reference tracking. Every pointer to a
+// particular object is kept on a circular linked list. When the last pointer
+// to an object is destroyed or reassigned, the object is deleted.
+//
+// Used properly, this deletes the object when the last reference goes away.
+// There are several caveats:
+// - Like all reference counting schemes, cycles lead to leaks.
+// - Each smart pointer is actually two pointers (8 bytes instead of 4).
+// - Every time a pointer is released, the entire list of pointers to that
+// object is traversed. This class is therefore NOT SUITABLE when there
+// will often be more than two or three pointers to a particular object.
+// - References are only tracked as long as linked_ptr<> objects are copied.
+// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS
+// will happen (double deletion).
+//
+// A good use of this class is storing object references in STL containers.
+// You can safely put linked_ptr<> in a vector<>.
+// Other uses may not be as good.
+//
+// Note: If you use an incomplete type with linked_ptr<>, the class
+// *containing* linked_ptr<> must have a constructor and destructor (even
+// if they do nothing!).
+//
+// Thread Safety:
+// A linked_ptr is NOT thread safe. Copying a linked_ptr object is
+// effectively a read-write operation.
+//
+// Alternative: to linked_ptr is shared_ptr, which
+// - is also two pointers in size (8 bytes for 32 bit addresses)
+// - is thread safe for copying and deletion
+// - supports weak_ptrs
+
+#ifndef MEDIA_CDM_PPAPI_LINKED_PTR_H_
+#define MEDIA_CDM_PPAPI_LINKED_PTR_H_
+
+#include "ppapi/cpp/logging.h"
+
+// This is used internally by all instances of linked_ptr<>. It needs to be
+// a non-template class because different types of linked_ptr<> can refer to
+// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)).
+// So, it needs to be possible for different types of linked_ptr to participate
+// in the same circular linked list, so we need a single class type here.
+//
+// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>.
+class linked_ptr_internal {
+ public:
+ // Create a new circle that includes only this instance.
+ void join_new() {
+ next_ = this;
+ }
+
+ // Join an existing circle.
+ void join(linked_ptr_internal const* ptr) {
+ next_ = ptr->next_;
+ ptr->next_ = this;
+ }
+
+ // Leave whatever circle we're part of. Returns true iff we were the
+ // last member of the circle. Once this is done, you can join() another.
+ bool depart() {
+ if (next_ == this) return true;
+ linked_ptr_internal const* p = next_;
+ while (p->next_ != this) p = p->next_;
+ p->next_ = next_;
+ return false;
+ }
+
+ private:
+ mutable linked_ptr_internal const* next_;
+};
+
+template <typename T>
+class linked_ptr {
+ public:
+ typedef T element_type;
+
+ // Take over ownership of a raw pointer. This should happen as soon as
+ // possible after the object is created.
+ explicit linked_ptr(T* ptr = NULL) { capture(ptr); }
+ ~linked_ptr() { depart(); }
+
+ // Copy an existing linked_ptr<>, adding ourselves to the list of references.
+ template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); }
+
+ linked_ptr(linked_ptr const& ptr) {
+ PP_DCHECK(&ptr != this);
+ copy(&ptr);
+ }
+
+ // Assignment releases the old value and acquires the new.
+ template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) {
+ depart();
+ copy(&ptr);
+ return *this;
+ }
+
+ linked_ptr& operator=(linked_ptr const& ptr) {
+ if (&ptr != this) {
+ depart();
+ copy(&ptr);
+ }
+ return *this;
+ }
+
+ // Smart pointer members.
+ void reset(T* ptr = NULL) {
+ depart();
+ capture(ptr);
+ }
+ T* get() const { return value_; }
+ T* operator->() const { return value_; }
+ T& operator*() const { return *value_; }
+ // Release ownership of the pointed object and returns it.
+ // Sole ownership by this linked_ptr object is required.
+ T* release() {
+ bool last = link_.depart();
+ PP_DCHECK(last);
+ (void)last;
+ T* v = value_;
+ value_ = NULL;
+ return v;
+ }
+
+ bool operator==(const T* p) const { return value_ == p; }
+ bool operator!=(const T* p) const { return value_ != p; }
+ template <typename U>
+ bool operator==(linked_ptr<U> const& ptr) const {
+ return value_ == ptr.get();
+ }
+ template <typename U>
+ bool operator!=(linked_ptr<U> const& ptr) const {
+ return value_ != ptr.get();
+ }
+
+ private:
+ template <typename U>
+ friend class linked_ptr;
+
+ T* value_;
+ linked_ptr_internal link_;
+
+ void depart() {
+ if (link_.depart()) delete value_;
+ }
+
+ void capture(T* ptr) {
+ value_ = ptr;
+ link_.join_new();
+ }
+
+ template <typename U> void copy(linked_ptr<U> const* ptr) {
+ value_ = ptr->get();
+ if (value_)
+ link_.join(&ptr->link_);
+ else
+ link_.join_new();
+ }
+};
+
+template<typename T> inline
+bool operator==(T* ptr, const linked_ptr<T>& x) {
+ return ptr == x.get();
+}
+
+template<typename T> inline
+bool operator!=(T* ptr, const linked_ptr<T>& x) {
+ return ptr != x.get();
+}
+
+// A function to convert T* into linked_ptr<T>
+// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation
+// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg))
+template <typename T>
+linked_ptr<T> make_linked_ptr(T* ptr) {
+ return linked_ptr<T>(ptr);
+}
+
+#endif // MEDIA_CDM_PPAPI_LINKED_PTR_H_
diff --git a/media/filters/file_data_source.cc b/media/filters/file_data_source.cc
index 2f34718a47..341347e78a 100644
--- a/media/filters/file_data_source.cc
+++ b/media/filters/file_data_source.cc
@@ -25,6 +25,17 @@ bool FileDataSource::Initialize(const base::FilePath& file_path) {
return true;
}
+bool FileDataSource::InitializeFromPlatformFile(
+ const base::PlatformFile& file) {
+ DCHECK(!file_.IsValid());
+
+ if (!file_.Initialize(file))
+ return false;
+
+ UpdateHostBytes();
+ return true;
+}
+
void FileDataSource::set_host(DataSourceHost* host) {
DataSource::set_host(host);
UpdateHostBytes();
diff --git a/media/filters/file_data_source.h b/media/filters/file_data_source.h
index e55ecdf021..c0164dac57 100644
--- a/media/filters/file_data_source.h
+++ b/media/filters/file_data_source.h
@@ -9,6 +9,7 @@
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
+#include "base/platform_file.h"
#include "media/base/data_source.h"
namespace media {
@@ -21,6 +22,7 @@ class MEDIA_EXPORT FileDataSource : public DataSource {
virtual ~FileDataSource();
bool Initialize(const base::FilePath& file_path);
+ bool InitializeFromPlatformFile(const base::PlatformFile& file);
// Implementation of DataSource.
virtual void set_host(DataSourceHost* host) OVERRIDE;
diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc
index a106ab51db..30c6dfa94f 100644
--- a/media/filters/gpu_video_decoder.cc
+++ b/media/filters/gpu_video_decoder.cc
@@ -137,7 +137,7 @@ void GpuVideoDecoder::Initialize(const VideoDecoderConfig& config,
BindToCurrentLoop(orig_status_cb));
bool previously_initialized = config_.IsValidConfig();
-#if !defined(OS_CHROMEOS) || !defined(ARCH_CPU_X86_FAMILY)
+#if !defined(OS_CHROMEOS)
if (previously_initialized) {
// TODO(xhwang): Make GpuVideoDecoder reinitializable.
// See http://crbug.com/233608
diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc
index f260708ec7..26f65b9602 100644
--- a/media/filters/pipeline_integration_test.cc
+++ b/media/filters/pipeline_integration_test.cc
@@ -11,7 +11,7 @@
#include "media/base/decoder_buffer.h"
#include "media/base/media_keys.h"
#include "media/base/test_data_util.h"
-#include "media/crypto/aes_decryptor.h"
+#include "media/cdm/aes_decryptor.h"
#include "media/filters/chunk_demuxer.h"
using testing::AnyNumber;
diff --git a/media/media.gyp b/media/media.gyp
index b2bde3dc87..c685090fd1 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -261,6 +261,8 @@
'base/filter_collection.h',
'base/media.cc',
'base/media.h',
+ 'base/media_file_checker.cc',
+ 'base/media_file_checker.h',
'base/media_keys.cc',
'base/media_keys.h',
'base/media_log.cc',
@@ -312,8 +314,8 @@
'base/video_util.h',
'base/yuv_convert.cc',
'base/yuv_convert.h',
- 'crypto/aes_decryptor.cc',
- 'crypto/aes_decryptor.h',
+ 'cdm/aes_decryptor.cc',
+ 'cdm/aes_decryptor.h',
'ffmpeg/ffmpeg_common.cc',
'ffmpeg/ffmpeg_common.h',
'filters/audio_decoder_selector.cc',
@@ -466,6 +468,8 @@
'sources!': [
'base/container_names.cc',
'base/container_names.h',
+ 'base/media_file_checker.cc',
+ 'base/media_file_checker.h',
'base/media_posix.cc',
'ffmpeg/ffmpeg_common.cc',
'ffmpeg/ffmpeg_common.h',
@@ -926,6 +930,7 @@
'base/decoder_buffer_unittest.cc',
'base/djb2_unittest.cc',
'base/gmock_callback_support_unittest.cc',
+ 'base/media_file_checker_unittest.cc',
'base/multi_channel_resampler_unittest.cc',
'base/pipeline_unittest.cc',
'base/ranges_unittest.cc',
@@ -940,7 +945,7 @@
'base/video_frame_unittest.cc',
'base/video_util_unittest.cc',
'base/yuv_convert_unittest.cc',
- 'crypto/aes_decryptor_unittest.cc',
+ 'cdm/aes_decryptor_unittest.cc',
'ffmpeg/ffmpeg_common_unittest.cc',
'filters/audio_decoder_selector_unittest.cc',
'filters/audio_file_reader_unittest.cc',
@@ -998,6 +1003,10 @@
'dependencies': [
'../third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
],
+ }, { # media_use_ffmpeg== 0
+ 'sources!': [
+ 'base/media_file_checker_unittest.cc',
+ ],
}],
['os_posix==1 and OS!="mac" and OS!="ios"', {
'conditions': [
@@ -1285,6 +1294,9 @@
], # targets
}],
['OS!="ios"', {
+ 'includes': [
+ 'media_cdm.gypi',
+ ],
'targets': [
{
# Minimal target for NaCl and other renderer side media clients which
diff --git a/media/media.target.darwin-arm.mk b/media/media.target.darwin-arm.mk
index 9a453af052..4c15e9f8cc 100644
--- a/media/media.target.darwin-arm.mk
+++ b/media/media.target.darwin-arm.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -220,6 +220,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
@@ -331,6 +332,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
diff --git a/media/media.target.darwin-mips.mk b/media/media.target.darwin-mips.mk
index 0df376193d..e18928c9b5 100644
--- a/media/media.target.darwin-mips.mk
+++ b/media/media.target.darwin-mips.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -219,6 +219,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
@@ -329,6 +330,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
diff --git a/media/media.target.darwin-x86.mk b/media/media.target.darwin-x86.mk
index 665877b4ea..d4ee0acd92 100644
--- a/media/media.target.darwin-x86.mk
+++ b/media/media.target.darwin-x86.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -223,6 +223,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
'-D__STDC_FORMAT_MACROS' \
@@ -336,6 +337,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
'-D__STDC_FORMAT_MACROS' \
diff --git a/media/media.target.linux-arm.mk b/media/media.target.linux-arm.mk
index 9a453af052..4c15e9f8cc 100644
--- a/media/media.target.linux-arm.mk
+++ b/media/media.target.linux-arm.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -220,6 +220,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
@@ -331,6 +332,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
diff --git a/media/media.target.linux-mips.mk b/media/media.target.linux-mips.mk
index 0df376193d..e18928c9b5 100644
--- a/media/media.target.linux-mips.mk
+++ b/media/media.target.linux-mips.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -219,6 +219,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
@@ -329,6 +330,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DPOSIX_AVOID_MMAP' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
diff --git a/media/media.target.linux-x86.mk b/media/media.target.linux-x86.mk
index 665877b4ea..d4ee0acd92 100644
--- a/media/media.target.linux-x86.mk
+++ b/media/media.target.linux-x86.mk
@@ -114,7 +114,7 @@ LOCAL_SRC_FILES := \
media/base/video_renderer.cc \
media/base/video_util.cc \
media/base/yuv_convert.cc \
- media/crypto/aes_decryptor.cc \
+ media/cdm/aes_decryptor.cc \
media/filters/audio_decoder_selector.cc \
media/filters/audio_renderer_algorithm.cc \
media/filters/audio_renderer_impl.cc \
@@ -223,6 +223,7 @@ MY_DEFS_Debug := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
'-D__STDC_FORMAT_MACROS' \
@@ -336,6 +337,7 @@ MY_DEFS_Release := \
'-DSK_BUILD_FOR_ANDROID' \
'-DUSE_CHROMIUM_SKIA' \
'-DSK_USE_POSIX_THREADS' \
+ '-DSK_DEFERRED_CANVAS_USES_FACTORIES=1' \
'-DU_USING_ICU_NAMESPACE=0' \
'-D__STDC_CONSTANT_MACROS' \
'-D__STDC_FORMAT_MACROS' \
diff --git a/media/media_cdm.gypi b/media/media_cdm.gypi
new file mode 100644
index 0000000000..a4a94f79db
--- /dev/null
+++ b/media/media_cdm.gypi
@@ -0,0 +1,136 @@
+# Copyright 2013 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.
+
+{
+ 'variables': {
+ 'conditions': [
+ ['OS == "android" or OS == "ios"', {
+ # Android and iOS don't use ffmpeg.
+ 'use_ffmpeg%': 0,
+ }, { # 'OS != "android" and OS != "ios"'
+ 'use_ffmpeg%': 1,
+ }],
+ ],
+ # Set |use_fake_video_decoder| to 1 to ignore input frames in |clearkeycdm|,
+ # and produce video frames filled with a solid color instead.
+ 'use_fake_video_decoder%': 0,
+ # Set |use_libvpx| to 1 to use libvpx for VP8 decoding in |clearkeycdm|.
+ 'use_libvpx%': 0,
+ },
+ 'targets': [
+ {
+ 'target_name': 'clearkeycdm',
+ 'type': 'none',
+ # TODO(tomfinegan): Simplify this by unconditionally including all the
+ # decoders, and changing clearkeycdm to select which decoder to use
+ # based on environment variables.
+ 'conditions': [
+ ['use_fake_video_decoder == 1' , {
+ 'defines': ['CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER'],
+ 'sources': [
+ 'cdm/ppapi/fake_cdm_video_decoder.cc',
+ 'cdm/ppapi/fake_cdm_video_decoder.h',
+ ],
+ }],
+ ['use_ffmpeg == 1' , {
+ 'defines': ['CLEAR_KEY_CDM_USE_FFMPEG_DECODER'],
+ 'dependencies': [
+ '<(DEPTH)/third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
+ ],
+ 'sources': [
+ 'cdm/ppapi/ffmpeg_cdm_audio_decoder.cc',
+ 'cdm/ppapi/ffmpeg_cdm_audio_decoder.h',
+ ],
+ }],
+ ['use_ffmpeg == 1 and use_fake_video_decoder == 0' , {
+ 'sources': [
+ 'cdm/ppapi/ffmpeg_cdm_video_decoder.cc',
+ 'cdm/ppapi/ffmpeg_cdm_video_decoder.h',
+ ],
+ }],
+ ['use_libvpx == 1 and use_fake_video_decoder == 0' , {
+ 'defines': ['CLEAR_KEY_CDM_USE_LIBVPX_DECODER'],
+ 'dependencies': [
+ '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx',
+ ],
+ 'sources': [
+ 'cdm/ppapi/libvpx_cdm_video_decoder.cc',
+ 'cdm/ppapi/libvpx_cdm_video_decoder.h',
+ ],
+ }],
+ ['os_posix == 1 and OS != "mac" and enable_pepper_cdms==1', {
+ 'type': 'loadable_module', # Must be in PRODUCT_DIR for ASAN bots.
+ }],
+ ['(OS == "mac" or OS == "win") and enable_pepper_cdms==1', {
+ 'type': 'shared_library',
+ }],
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'DYLIB_INSTALL_NAME_BASE': '@loader_path',
+ },
+ }]
+ ],
+ 'defines': ['CDM_IMPLEMENTATION'],
+ 'dependencies': [
+ 'media',
+ # Include the following for media::AudioBus.
+ 'shared_memory_support',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ 'cdm/ppapi/cdm_video_decoder.cc',
+ 'cdm/ppapi/cdm_video_decoder.h',
+ 'cdm/ppapi/clear_key_cdm.cc',
+ 'cdm/ppapi/clear_key_cdm.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ },
+ {
+ 'target_name': 'clearkeycdmadapter',
+ 'type': 'none',
+ # Check whether the plugin's origin URL is valid.
+ 'defines': ['CHECK_DOCUMENT_URL'],
+ 'dependencies': [
+ '<(DEPTH)/ppapi/ppapi.gyp:ppapi_cpp',
+ 'clearkeycdm',
+ ],
+ 'sources': [
+ 'cdm/ppapi/api/content_decryption_module.h',
+ 'cdm/ppapi/cdm_wrapper.cc',
+ 'cdm/ppapi/linked_ptr.h',
+ ],
+ 'conditions': [
+ ['os_posix == 1 and OS != "mac" and enable_pepper_cdms==1', {
+ 'cflags': ['-fvisibility=hidden'],
+ 'type': 'loadable_module',
+ # Allow the plugin wrapper to find the CDM in the same directory.
+ 'ldflags': ['-Wl,-rpath=\$$ORIGIN'],
+ 'libraries': [
+ # Built by clearkeycdm.
+ '<(PRODUCT_DIR)/libclearkeycdm.so',
+ ],
+ }],
+ ['OS == "win" and enable_pepper_cdms==1', {
+ 'type': 'shared_library',
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+ }],
+ ['OS == "mac" and enable_pepper_cdms==1', {
+ 'type': 'loadable_module',
+ 'product_extension': 'plugin',
+ 'xcode_settings': {
+ 'OTHER_LDFLAGS': [
+ # Not to strip important symbols by -Wl,-dead_strip.
+ '-Wl,-exported_symbol,_PPP_GetInterface',
+ '-Wl,-exported_symbol,_PPP_InitializeModule',
+ '-Wl,-exported_symbol,_PPP_ShutdownModule'
+ ],
+ 'DYLIB_INSTALL_NAME_BASE': '@loader_path',
+ },
+ }],
+ ],
+ }
+ ],
+}
diff --git a/media/video/capture/win/video_capture_device_win.cc b/media/video/capture/win/video_capture_device_win.cc
index 81f057ad98..1a7c3b7b96 100644
--- a/media/video/capture/win/video_capture_device_win.cc
+++ b/media/video/capture/win/video_capture_device_win.cc
@@ -7,9 +7,11 @@
#include <algorithm>
#include <list>
+#include "base/command_line.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/win/scoped_variant.h"
+#include "media/base/media_switches.h"
#include "media/video/capture/win/video_capture_device_mf_win.h"
using base::win::ScopedComPtr;
@@ -150,7 +152,9 @@ void DeleteMediaType(AM_MEDIA_TYPE* mt) {
void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
Names::iterator it;
- if (VideoCaptureDeviceMFWin::PlatformSupported()) {
+ const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ if (VideoCaptureDeviceMFWin::PlatformSupported() &&
+ !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) {
VideoCaptureDeviceMFWin::GetDeviceNames(device_names);
}
// Retrieve the devices with DirectShow (DS) interface. They might (partially)