summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfgalligan@chromium.org <fgalligan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-14 23:52:41 +0000
committerfgalligan@chromium.org <fgalligan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-14 23:52:41 +0000
commit4d98e445c98768bb5eb44f8947bea55c1da8334b (patch)
treeb6cf720b8d7ac51b47ab9196bbcaa7c5349977f9
parent76e5e6d404fa7d608a9da91e3903a9a2c9165a77 (diff)
downloadchromium_src-4d98e445c98768bb5eb44f8947bea55c1da8334b.zip
chromium_src-4d98e445c98768bb5eb44f8947bea55c1da8334b.tar.gz
chromium_src-4d98e445c98768bb5eb44f8947bea55c1da8334b.tar.bz2
Support for parsing encrypted WebM streams by src.
- Note: Only looking for comments on direction. A lot of work still needs to be done before committing. - Added support to FFmpegDemuxer to decrypt encrypted WebM streams. - Added support to FFmpegDemuxer to handle the needKey and keyAdded messages. - Added support to WebMediaPlayerImpl to handle the needKey and keyAdded messages. BUG=123426 TEST=All media_unittests pass Review URL: https://chromiumcodereview.appspot.com/10829470 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188228 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--media/ffmpeg/ffmpeg_common.cc23
-rw-r--r--media/ffmpeg/ffmpeg_common.h4
-rw-r--r--media/filters/chunk_demuxer_unittest.cc7
-rw-r--r--media/filters/ffmpeg_demuxer.cc53
-rw-r--r--media/filters/ffmpeg_demuxer.h20
-rw-r--r--media/filters/ffmpeg_demuxer_unittest.cc32
-rw-r--r--media/filters/pipeline_integration_test.cc17
-rw-r--r--media/filters/pipeline_integration_test_base.cc31
-rw-r--r--media/filters/pipeline_integration_test_base.h10
-rw-r--r--media/media.gyp2
-rw-r--r--media/tools/demuxer_bench/demuxer_bench.cc9
-rw-r--r--media/tools/player_x11/player_x11.cc9
-rw-r--r--media/tools/seek_tester/seek_tester.cc9
-rw-r--r--media/webm/webm_cluster_parser.cc49
-rw-r--r--media/webm/webm_cluster_parser_unittest.cc57
-rw-r--r--media/webm/webm_crypto_helpers.cc60
-rw-r--r--media/webm/webm_crypto_helpers.h32
-rw-r--r--media/webm/webm_stream_parser.cc11
-rw-r--r--webkit/media/filter_helpers.cc5
-rw-r--r--webkit/media/filter_helpers.h5
-rw-r--r--webkit/media/webmediaplayer_impl.cc8
21 files changed, 375 insertions, 78 deletions
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index a2c4efd..8a69a86 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -269,8 +269,9 @@ static AVSampleFormat SampleFormatToAVSampleFormat(SampleFormat sample_format) {
return AV_SAMPLE_FMT_NONE;
}
-void AVCodecContextToAudioDecoderConfig(
+static void AVCodecContextToAudioDecoderConfig(
const AVCodecContext* codec_context,
+ bool is_encrypted,
AudioDecoderConfig* config) {
DCHECK_EQ(codec_context->codec_type, AVMEDIA_TYPE_AUDIO);
@@ -294,7 +295,7 @@ void AVCodecContextToAudioDecoderConfig(
codec_context->sample_rate,
codec_context->extradata,
codec_context->extradata_size,
- false, // Not encrypted.
+ is_encrypted,
true);
if (codec != kCodecOpus) {
DCHECK_EQ(av_get_bytes_per_sample(codec_context->sample_fmt) * 8,
@@ -302,6 +303,17 @@ void AVCodecContextToAudioDecoderConfig(
}
}
+void AVStreamToAudioDecoderConfig(
+ const AVStream* stream,
+ AudioDecoderConfig* config) {
+ bool is_encrypted = false;
+ AVDictionaryEntry* key = av_dict_get(stream->metadata, "enc_key_id", NULL, 0);
+ if (key)
+ is_encrypted = true;
+ return AVCodecContextToAudioDecoderConfig(stream->codec,
+ is_encrypted, config);
+}
+
void AudioDecoderConfigToAVCodecContext(const AudioDecoderConfig& config,
AVCodecContext* codec_context) {
codec_context->codec_type = AVMEDIA_TYPE_AUDIO;
@@ -365,12 +377,17 @@ void AVStreamToVideoDecoderConfig(
coded_size = natural_size;
}
+ bool is_encrypted = false;
+ AVDictionaryEntry* key = av_dict_get(stream->metadata, "enc_key_id", NULL, 0);
+ if (key)
+ is_encrypted = true;
+
config->Initialize(codec,
profile,
format,
coded_size, visible_rect, natural_size,
stream->codec->extradata, stream->codec->extradata_size,
- false, // Not encrypted.
+ is_encrypted,
true);
}
diff --git a/media/ffmpeg/ffmpeg_common.h b/media/ffmpeg/ffmpeg_common.h
index 38f70d0..62c6385 100644
--- a/media/ffmpeg/ffmpeg_common.h
+++ b/media/ffmpeg/ffmpeg_common.h
@@ -72,8 +72,8 @@ MEDIA_EXPORT base::TimeDelta ConvertFromTimeBase(const AVRational& time_base,
MEDIA_EXPORT int64 ConvertToTimeBase(const AVRational& time_base,
const base::TimeDelta& timestamp);
-void AVCodecContextToAudioDecoderConfig(
- const AVCodecContext* codec_context,
+void AVStreamToAudioDecoderConfig(
+ const AVStream* stream,
AudioDecoderConfig* config);
void AudioDecoderConfigToAVCodecContext(
const AudioDecoderConfig& config,
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index 2200e4b..2a32ca6 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -6,12 +6,14 @@
#include "base/message_loop.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
#include "media/base/mock_demuxer_host.h"
#include "media/base/test_data_util.h"
#include "media/base/test_helpers.h"
#include "media/filters/chunk_demuxer.h"
#include "media/webm/cluster_builder.h"
#include "media/webm/webm_constants.h"
+#include "media/webm/webm_crypto_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::AnyNumber;
@@ -66,8 +68,6 @@ static const char kDefaultFirstClusterRange[] = "{ [0,46) }";
static const int kDefaultFirstClusterEndTimestamp = 66;
static const int kDefaultSecondClusterEndTimestamp = 132;
-static const char kWebMInitDataType[] = "video/webm";
-
base::TimeDelta kDefaultDuration() {
return base::TimeDelta::FromMilliseconds(201224);
}
@@ -784,7 +784,8 @@ TEST_F(ChunkDemuxerTest, TestInit) {
if (is_audio_encrypted || is_video_encrypted) {
int need_key_count = (is_audio_encrypted ? 1 : 0) +
(is_video_encrypted ? 1 : 0);
- EXPECT_CALL(*this, NeedKeyMock(kWebMInitDataType, NotNull(), 16))
+ EXPECT_CALL(*this, NeedKeyMock(kWebMEncryptInitDataType, NotNull(),
+ DecryptConfig::kDecryptionKeySize))
.Times(Exactly(need_key_count));
}
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index f1b0385..ee899f0 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include <string>
+#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
@@ -20,12 +21,14 @@
#include "media/base/audio_decoder_config.h"
#include "media/base/bind_to_loop.h"
#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
#include "media/base/video_decoder_config.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/ffmpeg_h264_to_annex_b_bitstream_converter.h"
+#include "media/webm/webm_crypto_helpers.h"
namespace media {
@@ -45,15 +48,19 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(
bitstream_converter_enabled_(false) {
DCHECK(demuxer_);
+ bool is_encrypted = false;
+
// Determine our media format.
switch (stream->codec->codec_type) {
case AVMEDIA_TYPE_AUDIO:
type_ = AUDIO;
- AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_);
+ AVStreamToAudioDecoderConfig(stream, &audio_config_);
+ is_encrypted = audio_config_.is_encrypted();
break;
case AVMEDIA_TYPE_VIDEO:
type_ = VIDEO;
AVStreamToVideoDecoderConfig(stream, &video_config_);
+ is_encrypted = video_config_.is_encrypted();
break;
default:
NOTREACHED();
@@ -67,6 +74,24 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(
bitstream_converter_.reset(
new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec));
}
+
+ if (is_encrypted) {
+ AVDictionaryEntry* key = av_dict_get(stream->metadata, "enc_key_id", NULL,
+ 0);
+ DCHECK(key);
+ DCHECK(key->value);
+ if (!key || !key->value)
+ return;
+ base::StringPiece base64_key_id(key->value);
+ std::string enc_key_id;
+ base::Base64Decode(base64_key_id, &enc_key_id);
+ DCHECK(!enc_key_id.empty());
+ if (enc_key_id.empty())
+ return;
+
+ encryption_key_id_.assign(enc_key_id);
+ demuxer_->FireNeedKey(kWebMEncryptInitDataType, enc_key_id);
+ }
}
void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
@@ -88,6 +113,18 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
// into memory we control.
scoped_refptr<DecoderBuffer> buffer;
buffer = DecoderBuffer::CopyFrom(packet->data, packet->size);
+
+ if ((type() == DemuxerStream::AUDIO && audio_config_.is_encrypted()) ||
+ (type() == DemuxerStream::VIDEO && video_config_.is_encrypted())) {
+ scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig(
+ packet->data, packet->size,
+ reinterpret_cast<const uint8*>(encryption_key_id_.data()),
+ encryption_key_id_.size()));
+ if (!config)
+ LOG(ERROR) << "Creation of DecryptConfig failed.";
+ buffer->SetDecryptConfig(config.Pass());
+ }
+
buffer->SetTimestamp(ConvertStreamTimestamp(
stream_->time_base, packet->pts));
buffer->SetDuration(ConvertStreamTimestamp(
@@ -231,7 +268,8 @@ base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
//
FFmpegDemuxer::FFmpegDemuxer(
const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const scoped_refptr<DataSource>& data_source)
+ const scoped_refptr<DataSource>& data_source,
+ const FFmpegNeedKeyCB& need_key_cb)
: host_(NULL),
message_loop_(message_loop),
blocking_thread_("FFmpegDemuxer"),
@@ -243,7 +281,8 @@ FFmpegDemuxer::FFmpegDemuxer(
audio_disabled_(false),
duration_known_(false),
url_protocol_(data_source, BindToLoop(message_loop_, base::Bind(
- &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))) {
+ &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))),
+ need_key_cb_(need_key_cb) {
DCHECK(message_loop_);
DCHECK(data_source_);
}
@@ -661,6 +700,14 @@ void FFmpegDemuxer::StreamHasEnded() {
}
}
+void FFmpegDemuxer::FireNeedKey(const std::string& init_data_type,
+ const std::string& encryption_key_id) {
+ int key_id_size = encryption_key_id.size();
+ scoped_array<uint8> key_id_local(new uint8[key_id_size]);
+ memcpy(key_id_local.get(), encryption_key_id.data(), key_id_size);
+ need_key_cb_.Run(init_data_type, key_id_local.Pass(), key_id_size);
+}
+
void FFmpegDemuxer::NotifyCapacityAvailable() {
DCHECK(message_loop_->BelongsToCurrentThread());
ReadFrameIfNeeded();
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index 1056d07..8a340ef 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -22,6 +22,7 @@
#ifndef MEDIA_FILTERS_FFMPEG_DEMUXER_H_
#define MEDIA_FILTERS_FFMPEG_DEMUXER_H_
+#include <string>
#include <vector>
#include "base/callback.h"
@@ -42,6 +43,14 @@ struct AVStream;
namespace media {
+// A new potentially encrypted stream has been parsed.
+// First parameter - The type of initialization data.
+// Second parameter - The initialization data associated with the stream.
+// Third parameter - Number of bytes of the initialization data.
+typedef base::Callback<void(const std::string& type,
+ scoped_array<uint8> init_data,
+ int init_data_size)> FFmpegNeedKeyCB;
+
class FFmpegDemuxer;
class FFmpegGlue;
class FFmpegH264ToAnnexBBitstreamConverter;
@@ -121,13 +130,16 @@ class FFmpegDemuxerStream : public DemuxerStream {
scoped_ptr<FFmpegH264ToAnnexBBitstreamConverter> bitstream_converter_;
bool bitstream_converter_enabled_;
+ std::string encryption_key_id_;
+
DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerStream);
};
class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
public:
FFmpegDemuxer(const scoped_refptr<base::MessageLoopProxy>& message_loop,
- const scoped_refptr<DataSource>& data_source);
+ const scoped_refptr<DataSource>& data_source,
+ const FFmpegNeedKeyCB& need_key_cb);
// Demuxer implementation.
virtual void Initialize(DemuxerHost* host,
@@ -140,6 +152,10 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
DemuxerStream::Type type) OVERRIDE;
virtual base::TimeDelta GetStartTime() const OVERRIDE;
+ // Calls |need_key_cb_| with the initialization data encountered in the file.
+ void FireNeedKey(const std::string& init_data_type,
+ const std::string& encryption_key_id);
+
// Allow FFmpegDemuxerStream to notify us when there is updated information
// about capacity and what buffered data is available.
void NotifyCapacityAvailable();
@@ -233,6 +249,8 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer {
BlockingUrlProtocol url_protocol_;
scoped_ptr<FFmpegGlue> glue_;
+ const FFmpegNeedKeyCB need_key_cb_;
+
DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxer);
};
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index af2cd92..d2f02ae 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -11,17 +11,21 @@
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/threading/thread.h"
+#include "media/base/decrypt_config.h"
#include "media/base/mock_demuxer_host.h"
#include "media/base/test_helpers.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "media/filters/file_data_source.h"
+#include "media/webm/webm_crypto_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::AnyNumber;
using ::testing::DoAll;
+using ::testing::Exactly;
using ::testing::InSequence;
using ::testing::Invoke;
+using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
@@ -77,8 +81,12 @@ class FFmpegDemuxerTest : public testing::Test {
EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
CreateDataSource(name);
+
+ media::FFmpegNeedKeyCB need_key_cb =
+ base::Bind(&FFmpegDemuxerTest::NeedKeyCB, base::Unretained(this));
demuxer_ = new FFmpegDemuxer(message_loop_.message_loop_proxy(),
- data_source_);
+ data_source_,
+ need_key_cb);
}
MOCK_METHOD1(CheckPoint, void(int v));
@@ -121,6 +129,17 @@ class FFmpegDemuxerTest : public testing::Test {
location, size, timestampInMicroseconds);
}
+ // TODO(xhwang): This is a workaround of the issue that move-only parameters
+ // are not supported in mocked methods. Remove this when the issue is fixed
+ // (http://code.google.com/p/googletest/issues/detail?id=395) or when we use
+ // std::string instead of scoped_array<uint8> (http://crbug.com/130689).
+ MOCK_METHOD3(NeedKeyCBMock, void(const std::string& type,
+ const uint8* init_data, int init_data_size));
+ void NeedKeyCB(const std::string& type,
+ scoped_array<uint8> init_data, int init_data_size) {
+ NeedKeyCBMock(type, init_data.get(), init_data_size);
+ }
+
// Accessor to demuxer internals.
void set_duration_known(bool duration_known) {
demuxer_->duration_known_ = duration_known;
@@ -281,6 +300,17 @@ TEST_F(FFmpegDemuxerTest, Initialize_Multitrack) {
EXPECT_FALSE(demuxer_->GetStream(DemuxerStream::UNKNOWN));
}
+// TODO(fgalligan): Enable test when code to parse encrypted WebM files lands
+// in Chromium's FFmpeg. crbug.com/189221
+TEST_F(FFmpegDemuxerTest, DISABLED_Initialize_Encrypted) {
+ EXPECT_CALL(*this, NeedKeyCBMock(kWebMEncryptInitDataType, NotNull(),
+ DecryptConfig::kDecryptionKeySize))
+ .Times(Exactly(2));
+
+ CreateDemuxer("bear-320x240-av_enc-av.webm");
+ InitializeDemuxer();
+}
+
TEST_F(FFmpegDemuxerTest, Read_Audio) {
// We test that on a successful audio packet read.
CreateDemuxer("bear-320x240.webm");
diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc
index 3d6c16b..d1720c1 100644
--- a/media/filters/pipeline_integration_test.cc
+++ b/media/filters/pipeline_integration_test.cc
@@ -313,6 +313,7 @@ class MockMediaSource {
scoped_array<uint8> init_data, int init_data_size) {
DCHECK(init_data.get());
DCHECK_GT(init_data_size, 0);
+ CHECK(!need_key_cb_.is_null());
need_key_cb_.Run("", "", type, init_data.Pass(), init_data_size);
}
@@ -424,6 +425,22 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) {
// EXPECT_EQ(GetAudioHash(), "");
}
+// TODO(fgalligan): Enable test when code to parse encrypted WebM files lands
+// in Chromium's FFmpeg. crbug.com/189221
+TEST_F(PipelineIntegrationTest, DISABLED_BasicPlaybackEncrypted) {
+ FakeEncryptedMedia encrypted_media(new KeyProvidingApp());
+ set_need_key_cb(base::Bind(&FakeEncryptedMedia::NeedKey,
+ base::Unretained(&encrypted_media)));
+
+ ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-av_enc-av.webm"),
+ encrypted_media.decryptor()));
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ Stop();
+}
+
TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) {
MockMediaSource source("bear-320x240.webm", kWebM, 219229);
StartPipelineWithMediaSource(&source);
diff --git a/media/filters/pipeline_integration_test_base.cc b/media/filters/pipeline_integration_test_base.cc
index c43cc92..ed47c41 100644
--- a/media/filters/pipeline_integration_test_base.cc
+++ b/media/filters/pipeline_integration_test_base.cc
@@ -59,6 +59,16 @@ PipelineStatusCB PipelineIntegrationTestBase::QuitOnStatusCB(
expected_status);
}
+void PipelineIntegrationTestBase::DemuxerNeedKeyCB(
+ const std::string& type,
+ scoped_array<uint8> init_data,
+ int init_data_size) {
+ DCHECK(init_data.get());
+ DCHECK_GT(init_data_size, 0);
+ CHECK(!need_key_cb_.is_null());
+ need_key_cb_.Run("", "", type, init_data.Pass(), init_data_size);
+}
+
void PipelineIntegrationTestBase::OnEnded() {
DCHECK(!ended_);
ended_ = true;
@@ -94,7 +104,7 @@ bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
.Times(AtMost(1));
pipeline_->Start(
- CreateFilterCollection(file_path),
+ CreateFilterCollection(file_path, NULL),
base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
QuitOnStatusCB(expected_status),
@@ -113,12 +123,17 @@ bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
}
bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path) {
+ return Start(file_path, NULL);
+}
+
+bool PipelineIntegrationTestBase::Start(const base::FilePath& file_path,
+ Decryptor* decryptor) {
EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata))
.Times(AtMost(1));
EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted))
.Times(AtMost(1));
pipeline_->Start(
- CreateFilterCollection(file_path),
+ CreateFilterCollection(file_path, decryptor),
base::Bind(&PipelineIntegrationTestBase::OnEnded, base::Unretained(this)),
base::Bind(&PipelineIntegrationTestBase::OnError, base::Unretained(this)),
base::Bind(&PipelineIntegrationTestBase::OnStatusCallback,
@@ -186,12 +201,18 @@ bool PipelineIntegrationTestBase::WaitUntilCurrentTimeIsAfter(
scoped_ptr<FilterCollection>
PipelineIntegrationTestBase::CreateFilterCollection(
- const base::FilePath& file_path) {
+ const base::FilePath& file_path,
+ Decryptor* decryptor) {
scoped_refptr<FileDataSource> data_source = new FileDataSource();
CHECK(data_source->Initialize(file_path));
+ media::FFmpegNeedKeyCB need_key_cb =
+ base::Bind(&PipelineIntegrationTestBase::DemuxerNeedKeyCB,
+ base::Unretained(this));
return CreateFilterCollection(
- new FFmpegDemuxer(message_loop_.message_loop_proxy(), data_source),
- NULL);
+ new FFmpegDemuxer(message_loop_.message_loop_proxy(),
+ data_source,
+ need_key_cb),
+ decryptor);
}
scoped_ptr<FilterCollection>
diff --git a/media/filters/pipeline_integration_test_base.h b/media/filters/pipeline_integration_test_base.h
index 23c0339..05e2e7e 100644
--- a/media/filters/pipeline_integration_test_base.h
+++ b/media/filters/pipeline_integration_test_base.h
@@ -49,6 +49,7 @@ class PipelineIntegrationTestBase {
// Initialize the pipeline and ignore any status updates. Useful for testing
// invalid audio/video clips which don't have deterministic results.
bool Start(const base::FilePath& file_path);
+ bool Start(const base::FilePath& file_path, Decryptor* decryptor);
void Play();
void Pause();
@@ -56,7 +57,7 @@ class PipelineIntegrationTestBase {
void Stop();
bool WaitUntilCurrentTimeIsAfter(const base::TimeDelta& wait_time);
scoped_ptr<FilterCollection> CreateFilterCollection(
- const base::FilePath& file_path);
+ const base::FilePath& file_path, Decryptor* decryptor);
// Returns the MD5 hash of all video frames seen. Should only be called once
// after playback completes. First time hashes should be generated with
@@ -77,11 +78,18 @@ class PipelineIntegrationTestBase {
scoped_refptr<NullAudioSink> audio_sink_;
bool ended_;
PipelineStatus pipeline_status_;
+ NeedKeyCB need_key_cb_;
void OnStatusCallbackChecked(PipelineStatus expected_status,
PipelineStatus status);
void OnStatusCallback(PipelineStatus status);
PipelineStatusCB QuitOnStatusCB(PipelineStatus expected_status);
+ void DemuxerNeedKeyCB(const std::string& type,
+ scoped_array<uint8> init_data, int init_data_size);
+ void set_need_key_cb(const NeedKeyCB& need_key_cb) {
+ need_key_cb_ = need_key_cb;
+ }
+
void OnEnded();
void OnError(PipelineStatus status);
void QuitAfterCurrentTimeTask(const base::TimeDelta& quit_time);
diff --git a/media/media.gyp b/media/media.gyp
index d506272..2b64447 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -408,6 +408,8 @@
'webm/webm_content_encodings.h',
'webm/webm_content_encodings_client.cc',
'webm/webm_content_encodings_client.h',
+ 'webm/webm_crypto_helpers.cc',
+ 'webm/webm_crypto_helpers.h',
'webm/webm_info_parser.cc',
'webm/webm_info_parser.h',
'webm/webm_parser.cc',
diff --git a/media/tools/demuxer_bench/demuxer_bench.cc b/media/tools/demuxer_bench/demuxer_bench.cc
index c874c88..6d49743 100644
--- a/media/tools/demuxer_bench/demuxer_bench.cc
+++ b/media/tools/demuxer_bench/demuxer_bench.cc
@@ -43,6 +43,11 @@ void QuitLoopWithStatus(MessageLoop* message_loop,
message_loop->PostTask(FROM_HERE, MessageLoop::QuitWhenIdleClosure());
}
+static void NeedKey(const std::string& type, scoped_array<uint8> init_data,
+ int init_data_size) {
+ LOG(INFO) << "File is encrypted.";
+}
+
typedef std::vector<scoped_refptr<media::DemuxerStream> > Streams;
// Simulates playback reading requirements by reading from each stream
@@ -177,8 +182,10 @@ int main(int argc, char** argv) {
new media::FileDataSource();
CHECK(data_source->Initialize(file_path));
+ media::FFmpegNeedKeyCB need_key_cb = base::Bind(&NeedKey);
scoped_refptr<media::FFmpegDemuxer> demuxer =
- new media::FFmpegDemuxer(message_loop.message_loop_proxy(), data_source);
+ new media::FFmpegDemuxer(message_loop.message_loop_proxy(), data_source,
+ need_key_cb);
demuxer->Initialize(&demuxer_host, base::Bind(
&QuitLoopWithStatus, &message_loop));
diff --git a/media/tools/player_x11/player_x11.cc b/media/tools/player_x11/player_x11.cc
index 96f07b1..6fc4e47 100644
--- a/media/tools/player_x11/player_x11.cc
+++ b/media/tools/player_x11/player_x11.cc
@@ -94,6 +94,11 @@ void Paint(MessageLoop* message_loop, const PaintCB& paint_cb,
static void OnBufferingState(media::Pipeline::BufferingState buffering_state) {}
+static void NeedKey(const std::string& type, scoped_array<uint8> init_data,
+ int init_data_size) {
+ std::cout << "File is encrypted." << std::endl;
+}
+
// TODO(vrk): Re-enabled audio. (crbug.com/112159)
bool InitPipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop,
const scoped_refptr<media::DataSource>& data_source,
@@ -104,7 +109,9 @@ bool InitPipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop,
// Create our filter factories.
scoped_ptr<media::FilterCollection> collection(
new media::FilterCollection());
- collection->SetDemuxer(new media::FFmpegDemuxer(message_loop, data_source));
+ media::FFmpegNeedKeyCB need_key_cb = base::Bind(&NeedKey);
+ collection->SetDemuxer(new media::FFmpegDemuxer(message_loop, data_source,
+ need_key_cb));
collection->GetAudioDecoders()->push_back(new media::FFmpegAudioDecoder(
message_loop));
collection->GetVideoDecoders()->push_back(new media::FFmpegVideoDecoder(
diff --git a/media/tools/seek_tester/seek_tester.cc b/media/tools/seek_tester/seek_tester.cc
index ba9f6fb..29e7ef8 100644
--- a/media/tools/seek_tester/seek_tester.cc
+++ b/media/tools/seek_tester/seek_tester.cc
@@ -53,6 +53,11 @@ void TimestampExtractor(uint64* timestamp_ms,
loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
+static void NeedKey(const std::string& type, scoped_array<uint8> init_data,
+ int init_data_size) {
+ LOG(INFO) << "File is encrypted.";
+}
+
int main(int argc, char** argv) {
base::AtExitManager at_exit;
media::InitializeMediaLibraryForTesting();
@@ -67,8 +72,10 @@ int main(int argc, char** argv) {
DemuxerHostImpl host;
MessageLoop loop;
media::PipelineStatusCB quitter = base::Bind(&QuitMessageLoop, &loop);
+ media::FFmpegNeedKeyCB need_key_cb = base::Bind(&NeedKey);
scoped_refptr<media::FFmpegDemuxer> demuxer(
- new media::FFmpegDemuxer(loop.message_loop_proxy(), file_data_source));
+ new media::FFmpegDemuxer(loop.message_loop_proxy(), file_data_source,
+ need_key_cb));
demuxer->Initialize(&host, quitter);
loop.Run();
diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc
index 8a984c8..a0b95a8 100644
--- a/media/webm/webm_cluster_parser.cc
+++ b/media/webm/webm_cluster_parser.cc
@@ -10,19 +10,10 @@
#include "media/base/buffers.h"
#include "media/base/decrypt_config.h"
#include "media/webm/webm_constants.h"
+#include "media/webm/webm_crypto_helpers.h"
namespace media {
-// Generates a 16 byte CTR counter block. The CTR counter block format is a
-// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
-// |iv_size| is the size of |iv| in btyes. Returns a string of
-// kDecryptionKeySize bytes.
-static std::string GenerateCounterBlock(const uint8* iv, int iv_size) {
- std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
- counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
- return counter_block;
-}
-
WebMClusterParser::TextTrackIterator::TextTrackIterator(
const TextTrackMap& text_track_map) :
iterator_(text_track_map.begin()),
@@ -294,39 +285,13 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, int track_num,
// encrypted WebM request for comments specification is here
// http://wiki.webmproject.org/encryption/webm-encryption-rfc
if (!encryption_key_id.empty()) {
- DCHECK_EQ(kWebMSignalByteSize, 1);
- if (size < kWebMSignalByteSize) {
- MEDIA_LOG(log_cb_)
- << "Got a block from an encrypted stream with no data.";
+ scoped_ptr<DecryptConfig> config(WebMCreateDecryptConfig(
+ data, size,
+ reinterpret_cast<const uint8*>(encryption_key_id.data()),
+ encryption_key_id.size()));
+ if (!config)
return false;
- }
- uint8 signal_byte = data[0];
- int data_offset = sizeof(signal_byte);
-
- // Setting the DecryptConfig object of the buffer while leaving the
- // initialization vector empty will tell the decryptor that the frame is
- // unencrypted.
- std::string counter_block;
-
- if (signal_byte & kWebMFlagEncryptedFrame) {
- if (size < kWebMSignalByteSize + kWebMIvSize) {
- MEDIA_LOG(log_cb_) << "Got an encrypted block with not enough data "
- << size;
- return false;
- }
- counter_block = GenerateCounterBlock(data + data_offset, kWebMIvSize);
- data_offset += kWebMIvSize;
- }
-
- // TODO(fgalligan): Revisit if DecryptConfig needs to be set on unencrypted
- // frames after the CDM API is finalized.
- // Unencrypted frames of potentially encrypted streams currently set
- // DecryptConfig.
- buffer->SetDecryptConfig(scoped_ptr<DecryptConfig>(new DecryptConfig(
- encryption_key_id,
- counter_block,
- data_offset,
- std::vector<SubsampleEntry>())));
+ buffer->SetDecryptConfig(config.Pass());
}
buffer->SetTimestamp(timestamp);
diff --git a/media/webm/webm_cluster_parser_unittest.cc b/media/webm/webm_cluster_parser_unittest.cc
index 07deb05..dc73329 100644
--- a/media/webm/webm_cluster_parser_unittest.cc
+++ b/media/webm/webm_cluster_parser_unittest.cc
@@ -6,6 +6,7 @@
#include "base/bind.h"
#include "base/logging.h"
+#include "media/base/decrypt_config.h"
#include "media/webm/cluster_builder.h"
#include "media/webm/webm_cluster_parser.h"
#include "media/webm/webm_constants.h"
@@ -32,7 +33,7 @@ struct BlockInfo {
bool use_simple_block;
};
-const BlockInfo kDefaultBlockInfo[] = {
+static const BlockInfo kDefaultBlockInfo[] = {
{ kAudioTrackNum, 0, 23, true },
{ kAudioTrackNum, 23, 23, true },
{ kVideoTrackNum, 33, 34, true },
@@ -42,6 +43,11 @@ const BlockInfo kDefaultBlockInfo[] = {
{ kVideoTrackNum, 100, 33, false },
};
+static const uint8 kEncryptedFrame[] = {
+ 0x01, // Block is encrypted
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 // IV
+};
+
static scoped_ptr<Cluster> CreateCluster(int timecode,
const BlockInfo* block_info,
int block_count) {
@@ -67,6 +73,18 @@ static scoped_ptr<Cluster> CreateCluster(int timecode,
return cb.Finish();
}
+// Creates a Cluster with one encrypted Block. |bytes_to_write| is number of
+// bytes of the encrypted frame to write.
+static scoped_ptr<Cluster> CreateEncryptedCluster(int bytes_to_write) {
+ CHECK_GT(bytes_to_write, 0);
+ CHECK_LE(bytes_to_write, static_cast<int>(sizeof(kEncryptedFrame)));
+
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+ cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kEncryptedFrame, bytes_to_write);
+ return cb.Finish();
+}
+
static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
const WebMClusterParser::BufferQueue& video_buffers,
const WebMClusterParser::BufferQueue& text_buffers,
@@ -165,6 +183,15 @@ static bool VerifyTextBuffers(
return true;
}
+static bool VerifyEncryptedBuffer(
+ scoped_refptr<StreamParserBuffer> buffer) {
+ EXPECT_TRUE(buffer->GetDecryptConfig());
+ EXPECT_EQ(static_cast<unsigned long>(DecryptConfig::kDecryptionKeySize),
+ buffer->GetDecryptConfig()->iv().length());
+ const uint8* data = buffer->GetData();
+ return data[0] & kWebMFlagEncryptedFrame;
+}
+
static void AppendToEnd(const WebMClusterParser::BufferQueue& src,
WebMClusterParser::BufferQueue* dest) {
for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin();
@@ -440,4 +467,32 @@ TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) {
}
}
+TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
+ scoped_ptr<Cluster> cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame)));
+
+ parser_.reset(new WebMClusterParser(
+ kTimecodeScale, kAudioTrackNum, kVideoTrackNum,
+ std::set<int>(),
+ std::set<int64>(), "", "video_key_id",
+ LogCB()));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_EQ(1UL, parser_->video_buffers().size());
+ scoped_refptr<StreamParserBuffer> buffer = parser_->video_buffers()[0];
+ EXPECT_TRUE(VerifyEncryptedBuffer(buffer));
+}
+
+TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
+ scoped_ptr<Cluster> cluster(
+ CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1));
+
+ parser_.reset(new WebMClusterParser(
+ kTimecodeScale, kAudioTrackNum, kVideoTrackNum,
+ std::set<int>(),
+ std::set<int64>(), "", "video_key_id",
+ LogCB()));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(-1, result);
+}
+
} // namespace media
diff --git a/media/webm/webm_crypto_helpers.cc b/media/webm/webm_crypto_helpers.cc
new file mode 100644
index 0000000..ea63f87
--- /dev/null
+++ b/media/webm/webm_crypto_helpers.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 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/webm/webm_crypto_helpers.h"
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "media/base/decrypt_config.h"
+#include "media/webm/webm_constants.h"
+
+namespace media {
+namespace {
+
+// Generates a 16 byte CTR counter block. The CTR counter block format is a
+// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
+// |iv_size| is the size of |iv| in btyes. Returns a string of
+// kDecryptionKeySize bytes.
+std::string GenerateWebMCounterBlock(const uint8* iv, int iv_size) {
+ std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
+ counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
+ return counter_block;
+}
+
+} // namespace anonymous
+
+scoped_ptr<DecryptConfig> WebMCreateDecryptConfig(
+ const uint8* data, int data_size,
+ const uint8* key_id, int key_id_size) {
+ if (data_size < kWebMSignalByteSize) {
+ DVLOG(1) << "Got a block from an encrypted stream with no data.";
+ return scoped_ptr<DecryptConfig>(NULL);
+ }
+
+ uint8 signal_byte = data[0];
+ int frame_offset = sizeof(signal_byte);
+
+ // Setting the DecryptConfig object of the buffer while leaving the
+ // initialization vector empty will tell the decryptor that the frame is
+ // unencrypted.
+ std::string counter_block;
+
+ if (signal_byte & kWebMFlagEncryptedFrame) {
+ if (data_size < kWebMSignalByteSize + kWebMIvSize) {
+ DVLOG(1) << "Got an encrypted block with not enough data " << data_size;
+ return scoped_ptr<DecryptConfig>(NULL);
+ }
+ counter_block = GenerateWebMCounterBlock(data + frame_offset, kWebMIvSize);
+ frame_offset += kWebMIvSize;
+ }
+
+ scoped_ptr<DecryptConfig> config(new DecryptConfig(
+ std::string(reinterpret_cast<const char*>(key_id), key_id_size),
+ counter_block,
+ frame_offset,
+ std::vector<SubsampleEntry>()));
+ return config.Pass();
+}
+
+} // namespace media
diff --git a/media/webm/webm_crypto_helpers.h b/media/webm/webm_crypto_helpers.h
new file mode 100644
index 0000000..c5f1f15
--- /dev/null
+++ b/media/webm/webm_crypto_helpers.h
@@ -0,0 +1,32 @@
+// Copyright (c) 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_WEBM_WEBM_CRYPTO_HELPERS_H_
+#define MEDIA_WEBM_WEBM_CRYPTO_HELPERS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/decoder_buffer.h"
+
+namespace media {
+
+// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
+// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=19096 for more
+// information.
+const char kWebMEncryptInitDataType[] = "video/webm";
+
+// Returns an initialized DecryptConfig, which can be sent to the Decryptor if
+// the stream has potentially encrypted frames. Every encrypted Block has a
+// signal byte, and if the frame is encrypted, an initialization vector
+// prepended to the frame. Leaving the IV empty will tell the decryptor that the
+// frame is unencrypted. Returns NULL if |data| is invalid. Current encrypted
+// WebM request for comments specification is here
+// http://wiki.webmproject.org/encryption/webm-encryption-rfc
+scoped_ptr<DecryptConfig> WebMCreateDecryptConfig(
+ const uint8* data, int data_size,
+ const uint8* key_id, int key_id_size);
+
+} // namespace media
+
+#endif // MEDIA_WEBM_WEBM_CRYPT_HELPERS_H_
diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc
index b60de5d..f47fa41 100644
--- a/media/webm/webm_stream_parser.cc
+++ b/media/webm/webm_stream_parser.cc
@@ -14,14 +14,12 @@
#include "media/webm/webm_cluster_parser.h"
#include "media/webm/webm_constants.h"
#include "media/webm/webm_content_encodings.h"
+#include "media/webm/webm_crypto_helpers.h"
#include "media/webm/webm_info_parser.h"
#include "media/webm/webm_tracks_parser.h"
namespace media {
-// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
-static const char kWebMInitDataType[] = "video/webm";
-
// Helper class that uses FFmpeg to create AudioDecoderConfig &
// VideoDecoderConfig objects.
//
@@ -150,13 +148,12 @@ bool FFmpegConfigHelper::SetupStreamConfigs() {
bool no_supported_streams = true;
for (size_t i = 0; i < format_context->nb_streams; ++i) {
AVStream* stream = format_context->streams[i];
- AVCodecContext* codec_context = stream->codec;
- AVMediaType codec_type = codec_context->codec_type;
+ AVMediaType codec_type = stream->codec->codec_type;
if (codec_type == AVMEDIA_TYPE_AUDIO &&
stream->codec->codec_id == CODEC_ID_VORBIS &&
!audio_config_.IsValidConfig()) {
- AVCodecContextToAudioDecoderConfig(stream->codec, &audio_config_);
+ AVStreamToAudioDecoderConfig(stream, &audio_config_);
no_supported_streams = false;
continue;
}
@@ -473,7 +470,7 @@ void WebMStreamParser::FireNeedKey(const std::string& key_id) {
DCHECK_GT(key_id_size, 0);
scoped_array<uint8> key_id_array(new uint8[key_id_size]);
memcpy(key_id_array.get(), key_id.data(), key_id_size);
- need_key_cb_.Run(kWebMInitDataType, key_id_array.Pass(), key_id_size);
+ need_key_cb_.Run(kWebMEncryptInitDataType, key_id_array.Pass(), key_id_size);
}
} // namespace media
diff --git a/webkit/media/filter_helpers.cc b/webkit/media/filter_helpers.cc
index 4308f9a..df584a4 100644
--- a/webkit/media/filter_helpers.cc
+++ b/webkit/media/filter_helpers.cc
@@ -72,9 +72,10 @@ void BuildMediaSourceCollection(
void BuildDefaultCollection(
const scoped_refptr<media::DataSource>& data_source,
const scoped_refptr<base::MessageLoopProxy>& message_loop,
- media::FilterCollection* filter_collection) {
+ media::FilterCollection* filter_collection,
+ const media::FFmpegNeedKeyCB& need_key_cb) {
filter_collection->SetDemuxer(new media::FFmpegDemuxer(
- message_loop, data_source));
+ message_loop, data_source, need_key_cb));
AddDefaultDecodersToCollection(message_loop, filter_collection);
}
diff --git a/webkit/media/filter_helpers.h b/webkit/media/filter_helpers.h
index bfa8b89..b30ecd2 100644
--- a/webkit/media/filter_helpers.h
+++ b/webkit/media/filter_helpers.h
@@ -7,6 +7,8 @@
#include "base/basictypes.h"
#include "base/memory/ref_counted.h"
+// TODO(fgalligan): Remove the dependency on FFmpeg.
+#include "media/filters/ffmpeg_demuxer.h"
namespace base {
class MessageLoopProxy;
@@ -33,7 +35,8 @@ void BuildMediaSourceCollection(
void BuildDefaultCollection(
const scoped_refptr<media::DataSource>& data_source,
const scoped_refptr<base::MessageLoopProxy>& message_loop,
- media::FilterCollection* filter_collection);
+ media::FilterCollection* filter_collection,
+ const media::FFmpegNeedKeyCB& need_key_cb);
} // webkit_media
diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc
index a4bfe39..981ed30 100644
--- a/webkit/media/webmediaplayer_impl.cc
+++ b/webkit/media/webmediaplayer_impl.cc
@@ -277,9 +277,11 @@ void WebMediaPlayerImpl::load(const WebKit::WebURL& url, CORSMode cors_mode) {
is_local_source_ = !gurl.SchemeIs("http") && !gurl.SchemeIs("https");
- BuildDefaultCollection(data_source_,
- media_thread_.message_loop_proxy(),
- filter_collection_.get());
+ BuildDefaultCollection(
+ data_source_,
+ media_thread_.message_loop_proxy(),
+ filter_collection_.get(),
+ BIND_TO_RENDER_LOOP_2(&WebMediaPlayerImpl::OnNeedKey, "", ""));
}
void WebMediaPlayerImpl::load(const WebKit::WebURL& url,