diff options
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 122 | ||||
-rw-r--r-- | media/media.gyp | 5 | ||||
-rw-r--r-- | media/webm/webm_constants.h | 2 | ||||
-rw-r--r-- | media/webm/webm_content_encodings.cc | 19 | ||||
-rw-r--r-- | media/webm/webm_content_encodings.h | 65 | ||||
-rw-r--r-- | media/webm/webm_content_encodings_client.cc | 231 | ||||
-rw-r--r-- | media/webm/webm_content_encodings_client.h | 40 | ||||
-rw-r--r-- | media/webm/webm_content_encodings_client_unittest.cc | 201 | ||||
-rw-r--r-- | media/webm/webm_tracks_parser.cc | 22 | ||||
-rw-r--r-- | media/webm/webm_tracks_parser.h | 9 |
10 files changed, 674 insertions, 42 deletions
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index 6dcd12b..087fafe 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -21,16 +21,36 @@ using ::testing::_; namespace media { static const uint8 kTracksHeader[] = { - 0x16, 0x54, 0xAE, 0x6B, // Tracks ID - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0) + 0x16, 0x54, 0xAE, 0x6B, // Tracks ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0) }; static const int kTracksHeaderSize = sizeof(kTracksHeader); static const int kTracksSizeOffset = 4; +// The size of TrackEntry element in test file "webm_vp8_track_entry" starts at +// index 1 and spans 8 bytes. +static const int kVideoTrackSizeOffset = 1; +static const int kVideoTrackSizeWidth = 8; +static const int kVideoTrackEntryHeaderSize = kVideoTrackSizeOffset + + kVideoTrackSizeWidth; + static const int kVideoTrackNum = 1; static const int kAudioTrackNum = 2; +// Write an integer into buffer in the form of vint that spans 8 bytes. +// The data pointed by |buffer| should be at least 8 bytes long. +// |number| should be in the range 0 <= number < 0x00FFFFFFFFFFFFFF. +static void WriteInt64(uint8* buffer, int64 number) { + DCHECK(number >= 0 && number < GG_LONGLONG(0x00FFFFFFFFFFFFFF)); + buffer[0] = 0x01; + int64 tmp = number; + for (int i = 7; i > 0; i--) { + buffer[i] = tmp & 0xff; + tmp >>= 8; + } +} + MATCHER_P(HasTimestamp, timestamp_in_ms, "") { return !arg->IsEndOfStream() && arg->GetTimestamp().InMilliseconds() == timestamp_in_ms; @@ -73,27 +93,37 @@ class ChunkDemuxerTest : public testing::Test { } void CreateInfoTracks(bool has_audio, bool has_video, - scoped_array<uint8>* buffer, int* size) { + bool video_content_encoded, scoped_array<uint8>* buffer, + int* size) { scoped_array<uint8> info; int info_size = 0; scoped_array<uint8> audio_track_entry; int audio_track_entry_size = 0; scoped_array<uint8> video_track_entry; int video_track_entry_size = 0; + scoped_array<uint8> video_content_encodings; + int video_content_encodings_size = 0; ReadTestDataFile("webm_info_element", &info, &info_size); - ReadTestDataFile("webm_vorbis_track_entry", &audio_track_entry, - &audio_track_entry_size); - ReadTestDataFile("webm_vp8_track_entry", &video_track_entry, - &video_track_entry_size); int tracks_element_size = 0; - if (has_audio) + if (has_audio) { + ReadTestDataFile("webm_vorbis_track_entry", &audio_track_entry, + &audio_track_entry_size); tracks_element_size += audio_track_entry_size; + } - if (has_video) + if (has_video) { + ReadTestDataFile("webm_vp8_track_entry", &video_track_entry, + &video_track_entry_size); tracks_element_size += video_track_entry_size; + if (video_content_encoded) { + ReadTestDataFile("webm_content_encodings", &video_content_encodings, + &video_content_encodings_size); + tracks_element_size += video_content_encodings_size; + } + } *size = info_size + kTracksHeaderSize + tracks_element_size; @@ -104,13 +134,7 @@ class ChunkDemuxerTest : public testing::Test { buf += info_size; memcpy(buf, kTracksHeader, kTracksHeaderSize); - - int tmp = tracks_element_size; - for (int i = 7; i > 0; i--) { - buf[kTracksSizeOffset + i] = tmp & 0xff; - tmp >>= 8; - } - + WriteInt64(buf + kTracksSizeOffset, tracks_element_size); buf += kTracksHeaderSize; if (has_audio) { @@ -120,6 +144,13 @@ class ChunkDemuxerTest : public testing::Test { if (has_video) { memcpy(buf, video_track_entry.get(), video_track_entry_size); + if (video_content_encoded) { + memcpy(buf + video_track_entry_size, video_content_encodings.get(), + video_content_encodings_size); + video_track_entry_size += video_content_encodings_size; + WriteInt64(buf + kVideoTrackSizeOffset, + video_track_entry_size - kVideoTrackEntryHeaderSize); + } buf += video_track_entry_size; } } @@ -149,10 +180,12 @@ class ChunkDemuxerTest : public testing::Test { return true; } - bool AppendInfoTracks(bool has_audio, bool has_video) { + bool AppendInfoTracks(bool has_audio, bool has_video, + bool video_content_encoded) { scoped_array<uint8> info_tracks; int info_tracks_size = 0; - CreateInfoTracks(has_audio, has_video, &info_tracks, &info_tracks_size); + CreateInfoTracks(has_audio, has_video, video_content_encoded, + &info_tracks, &info_tracks_size); return AppendData(info_tracks.get(), info_tracks_size); } @@ -186,14 +219,15 @@ class ChunkDemuxerTest : public testing::Test { call_set_host); } - bool InitDemuxer(bool has_audio, bool has_video) { + bool InitDemuxer(bool has_audio, bool has_video, + bool video_content_encoded) { PipelineStatus expected_status = (has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN; EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Init(CreateInitDoneCB(201224, expected_status)); - return AppendInfoTracks(has_audio, has_video); + return AppendInfoTracks(has_audio, has_video, video_content_encoded); } void ShutdownDemuxer() { @@ -297,19 +331,25 @@ class ChunkDemuxerTest : public testing::Test { }; TEST_F(ChunkDemuxerTest, TestInit) { - // Test no streams, audio-only, video-only, and audio & video scenarios. - for (int i = 0; i < 4; i++) { + // Test no streams, audio-only, video-only, and audio & video scenarios, + // with video content encoded or not. + for (int i = 0; i < 8; i++) { bool has_audio = (i & 0x1) != 0; bool has_video = (i & 0x2) != 0; + bool video_content_encoded = (i & 0x4) != 0; + + // No test on invalid combination. + if (!has_video && video_content_encoded) + continue; client_.reset(new MockChunkDemuxerClient()); demuxer_ = new ChunkDemuxer(client_.get()); - ASSERT_TRUE(InitDemuxer(has_audio, has_video)); + ASSERT_TRUE(InitDemuxer(has_audio, has_video, video_content_encoded)); scoped_refptr<DemuxerStream> audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); if (has_audio) { - EXPECT_TRUE(audio_stream); + ASSERT_TRUE(audio_stream); const AudioDecoderConfig& config = audio_stream->audio_decoder_config(); EXPECT_EQ(kCodecVorbis, config.codec()); @@ -338,7 +378,7 @@ TEST_F(ChunkDemuxerTest, TestInit) { // Makes sure that Seek() reports an error if Shutdown() // is called before the first cluster is passed to the demuxer. TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); demuxer_->Seek(base::TimeDelta::FromSeconds(0), NewExpectedStatusCB(PIPELINE_ERROR_ABORT)); @@ -347,7 +387,7 @@ TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { // Test that Seek() completes successfully when the first cluster // arrives. TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); InSequence s; @@ -375,7 +415,7 @@ TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) { // resets itself on seek and is in the right state when data from // the new seek point arrives. TEST_F(ChunkDemuxerTest, TestSeekWhileParsingCluster) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); @@ -425,14 +465,14 @@ TEST_F(ChunkDemuxerTest, TestSeekWhileParsingCluster) { TEST_F(ChunkDemuxerTest, TestAppendDataBeforeInit) { scoped_array<uint8> info_tracks; int info_tracks_size = 0; - CreateInfoTracks(true, true, &info_tracks, &info_tracks_size); + CreateInfoTracks(true, true, false, &info_tracks, &info_tracks_size); EXPECT_FALSE(demuxer_->AppendData(info_tracks.get(), info_tracks_size)); } // Make sure Read() callbacks are dispatched with the proper data. TEST_F(ChunkDemuxerTest, TestRead) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); @@ -462,7 +502,7 @@ TEST_F(ChunkDemuxerTest, TestRead) { } TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; @@ -498,7 +538,7 @@ TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { } TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; @@ -523,7 +563,7 @@ TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) { } TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; @@ -549,7 +589,7 @@ TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) { TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; @@ -567,7 +607,7 @@ TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) { } TEST_F(ChunkDemuxerTest, TestMonotonicallyIncreasingTimestampsAcrossClusters) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; @@ -618,7 +658,7 @@ TEST_F(ChunkDemuxerTest, TestEOSDuringInit) { } TEST_F(ChunkDemuxerTest, TestDecodeErrorEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; cb.SetClusterTimecode(0); @@ -634,7 +674,7 @@ TEST_F(ChunkDemuxerTest, TestDecodeErrorEndOfStream) { } TEST_F(ChunkDemuxerTest, TestNetworkErrorEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; cb.SetClusterTimecode(0); @@ -700,7 +740,7 @@ class EndOfStreamHelper { // Make sure that all pending reads that we don't have media data for get an // "end of stream" buffer when EndOfStream() is called. TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); @@ -745,7 +785,7 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) { // Make sure that all Read() calls after we get an EndOfStream() // call return an "end of stream" buffer. TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); @@ -801,7 +841,7 @@ TEST_F(ChunkDemuxerTest, TestAppendingInPieces) { scoped_array<uint8> info_tracks; int info_tracks_size = 0; - CreateInfoTracks(true, true, &info_tracks, &info_tracks_size); + CreateInfoTracks(true, true, false, &info_tracks, &info_tracks_size); ClusterBuilder cb; cb.SetClusterTimecode(0); @@ -906,7 +946,7 @@ TEST_F(ChunkDemuxerTest, TestWebMFile_VideoOnly) { // Verify that we output buffers before the entire cluster has been parsed. TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(true, true, false)); ClusterBuilder cb; cb.SetClusterTimecode(0); @@ -979,7 +1019,7 @@ TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) { TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) { EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Init(CreateInitDoneCB(201224, PIPELINE_OK, false)); - ASSERT_TRUE(AppendInfoTracks(true, true)); + ASSERT_TRUE(AppendInfoTracks(true, true, false)); uint8 tmp = 0; ASSERT_TRUE(demuxer_->AppendData(&tmp, 1)); diff --git a/media/media.gyp b/media/media.gyp index 55d21dd..5b91aad 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -240,6 +240,10 @@ 'webm/webm_constants.h', 'webm/webm_cluster_parser.cc', 'webm/webm_cluster_parser.h', + 'webm/webm_content_encodings.cc', + 'webm/webm_content_encodings.h', + 'webm/webm_content_encodings_client.cc', + 'webm/webm_content_encodings_client.h', 'webm/webm_info_parser.cc', 'webm/webm_info_parser.h', 'webm/webm_parser.cc', @@ -616,6 +620,7 @@ 'webm/cluster_builder.cc', 'webm/cluster_builder.h', 'webm/webm_parser_unittest.cc', + 'webm/webm_content_encodings_client_unittest.cc', ], 'conditions': [ ['os_posix==1 and OS!="mac"', { diff --git a/media/webm/webm_constants.h b/media/webm/webm_constants.h index 433b31c..ecd01df 100644 --- a/media/webm/webm_constants.h +++ b/media/webm/webm_constants.h @@ -5,6 +5,8 @@ #ifndef MEDIA_WEBM_WEBM_CONSTANTS_H_ #define MEDIA_WEBM_WEBM_CONSTANTS_H_ +#include "base/basictypes.h" + namespace media { // WebM element IDs. diff --git a/media/webm/webm_content_encodings.cc b/media/webm/webm_content_encodings.cc new file mode 100644 index 0000000..53cd171 --- /dev/null +++ b/media/webm/webm_content_encodings.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_content_encodings.h" + +namespace media { + +ContentEncoding::ContentEncoding() + : order_(kOrderInvalid), + scope_(kScopeInvalid), + type_(kTypeInvalid), + encryption_algo_(kEncAlgoInvalid), + encryption_key_id_size_(0) { +} + +ContentEncoding::~ContentEncoding() {} + +} // namespace media diff --git a/media/webm/webm_content_encodings.h b/media/webm/webm_content_encodings.h new file mode 100644 index 0000000..4a5f62f --- /dev/null +++ b/media/webm/webm_content_encodings.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ +#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" + +namespace media { + +struct MEDIA_EXPORT ContentEncoding : public base::RefCounted<ContentEncoding> { + // The following enum definitions are based on the ContentEncoding element + // specified in the Matroska spec. + + static const int kOrderInvalid = -1; + + enum Scope { + kScopeInvalid = 0, + kScopeAllFrameContents = 1, + kScopeTrackPrivateData = 2, + kScopeNextContentEncodingData = 4, + kScopeMax = 7, + }; + + enum Type { + kTypeInvalid = -1, + kTypeCompression = 0, + kTypeEncryption = 1, + }; + + enum EncryptionAlgo { + kEncAlgoInvalid = -1, + kEncAlgoNotEncrypted = 0, + kEncAlgoDes = 1, + kEncAlgo3des = 2, + kEncAlgoTwofish = 3, + kEncAlgoBlowfish = 4, + kEncAlgoAes = 5, + }; + + ContentEncoding(); + virtual ~ContentEncoding(); + + int64 order_; + Scope scope_; + Type type_; + EncryptionAlgo encryption_algo_; + scoped_array<uint8> encryption_key_id_; + int encryption_key_id_size_; + + private: + DISALLOW_COPY_AND_ASSIGN(ContentEncoding); +}; + +typedef std::vector<scoped_refptr<ContentEncoding> > ContentEncodings; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_H_ diff --git a/media/webm/webm_content_encodings_client.cc b/media/webm/webm_content_encodings_client.cc new file mode 100644 index 0000000..4e09ff8 --- /dev/null +++ b/media/webm/webm_content_encodings_client.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_content_encodings_client.h" + +#include "base/logging.h" +#include "media/webm/webm_constants.h" + +namespace media { + +WebMContentEncodingsClient::WebMContentEncodingsClient() + : content_encryption_encountered_(false) { +} + +WebMContentEncodingsClient::~WebMContentEncodingsClient() {} + +const ContentEncodings& WebMContentEncodingsClient::content_encodings() const { + return content_encodings_; +} + +WebMParserClient* WebMContentEncodingsClient::OnListStart(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(!cur_content_encoding_); + DCHECK(!content_encryption_encountered_); + content_encodings_.clear(); + return this; + } + + if (id == kWebMIdContentEncoding) { + DCHECK(!cur_content_encoding_); + DCHECK(!content_encryption_encountered_); + cur_content_encoding_ = new ContentEncoding; + return this; + } + + if (id == kWebMIdContentEncryption) { + DCHECK(cur_content_encoding_); + if (content_encryption_encountered_) { + DVLOG(1) << "Unexpected multiple ContentEncryption."; + return NULL; + } + content_encryption_encountered_ = true; + return this; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return NULL; +} + +// Mandatory occurrence restriction is checked in this function. Multiple +// occurrence restriction is checked in OnUInt and OnBinary. +bool WebMContentEncodingsClient::OnListEnd(int id) { + if (id == kWebMIdContentEncodings) { + // ContentEncoding element is mandatory. Check this! + if (content_encodings_.empty()) { + DVLOG(1) << "Missing ContentEncoding."; + return false; + } + return true; + } + + if (id == kWebMIdContentEncoding) { + DCHECK(cur_content_encoding_); + + // + // Specify default values to missing mandatory elements. + // + + if (cur_content_encoding_->order_ == ContentEncoding::kOrderInvalid) { + // Default value of encoding order is 0, which should only be used on the + // first ContentEncoding. + if (!content_encodings_.empty()) { + DVLOG(1) << "Missing ContentEncodingOrder."; + return false; + } + cur_content_encoding_->order_ = 0; + } + + if (cur_content_encoding_->scope_ == ContentEncoding::kScopeInvalid) + cur_content_encoding_->scope_ = ContentEncoding::kScopeAllFrameContents; + + if (cur_content_encoding_->type_ == ContentEncoding::kTypeInvalid) + cur_content_encoding_->type_ = ContentEncoding::kTypeCompression; + + // Check for elements valid in spec but not supported for now. + if (cur_content_encoding_->type_ == ContentEncoding::kTypeCompression) { + DVLOG(1) << "ContentCompression not supported."; + return false; + } + + // Enforce mandatory elements without default values. + DCHECK(cur_content_encoding_->type_ == ContentEncoding::kTypeEncryption); + if (!content_encryption_encountered_) { + DVLOG(1) << "ContentEncodingType is encryption but ContentEncryption " + "is missing."; + return false; + } + + content_encodings_.push_back(cur_content_encoding_.release()); + content_encryption_encountered_ = false; + return true; + } + + if (id == kWebMIdContentEncryption) { + DCHECK(cur_content_encoding_); + // Specify default value for elements that are not present. + if (cur_content_encoding_->encryption_algo_ == + ContentEncoding::kEncAlgoInvalid) { + cur_content_encoding_->encryption_algo_ = + ContentEncoding::kEncAlgoNotEncrypted; + } + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +// Multiple occurrence restriction and range are checked in this function. +// Mandatory occurrence restriction is checked in OnListEnd. +bool WebMContentEncodingsClient::OnUInt(int id, int64 val) { + DCHECK(cur_content_encoding_); + + if (id == kWebMIdContentEncodingOrder) { + if (cur_content_encoding_->order_ != ContentEncoding::kOrderInvalid) { + DVLOG(1) << "Unexpected multiple ContentEncodingOrder."; + return false; + } + + if (val != static_cast<int64>(content_encodings_.size())) { + // According to the spec, encoding order starts with 0 and counts upwards. + DVLOG(1) << "Unexpected ContentEncodingOrder."; + return false; + } + + cur_content_encoding_->order_ = val; + return true; + } + + if (id == kWebMIdContentEncodingScope) { + if (cur_content_encoding_->scope_ != ContentEncoding::kScopeInvalid) { + DVLOG(1) << "Unexpected multiple ContentEncodingScope."; + return false; + } + + if (val == ContentEncoding::kScopeInvalid || + val > ContentEncoding::kScopeMax) { + DVLOG(1) << "Unexpected ContentEncodingScope."; + return false; + } + + if (val & ContentEncoding::kScopeNextContentEncodingData) { + DVLOG(1) << "Encoded next ContentEncoding is not supported."; + return false; + } + + cur_content_encoding_->scope_ = static_cast<ContentEncoding::Scope>(val); + return true; + } + + if (id == kWebMIdContentEncodingType) { + if (cur_content_encoding_->type_ != ContentEncoding::kTypeInvalid) { + DVLOG(1) << "Unexpected multiple ContentEncodingType."; + return false; + } + + if (val == ContentEncoding::kTypeCompression) { + DVLOG(1) << "ContentCompression not supported."; + return false; + } + + if (val != ContentEncoding::kTypeEncryption) { + DVLOG(1) << "Unexpected ContentEncodingType " << val << "."; + return false; + } + + cur_content_encoding_->type_ = static_cast<ContentEncoding::Type>(val); + return true; + } + + if (id == kWebMIdContentEncAlgo) { + if (cur_content_encoding_->encryption_algo_ != + ContentEncoding::kEncAlgoInvalid) { + DVLOG(1) << "Unexpected multiple ContentEncAlgo."; + return false; + } + + if (val < ContentEncoding::kEncAlgoNotEncrypted || + val > ContentEncoding::kEncAlgoAes) { + DVLOG(1) << "Unexpected ContentEncAlgo " << val << "."; + return false; + } + + cur_content_encoding_->encryption_algo_ = + static_cast<ContentEncoding::EncryptionAlgo>(val); + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +// Multiple occurrence restriction is checked in this function. Mandatory +// restriction is checked in OnListEnd. +bool WebMContentEncodingsClient::OnBinary(int id, const uint8* data, int size) { + DCHECK(cur_content_encoding_); + DCHECK(data); + DCHECK_GT(size, 0); + + if (id == kWebMIdContentEncKeyID) { + if (cur_content_encoding_->encryption_key_id_.get() || + cur_content_encoding_->encryption_key_id_size_) { + DVLOG(1) << "Unexpected multiple ContentEncKeyID"; + return false; + } + cur_content_encoding_->encryption_key_id_.reset(new uint8[size]); + memcpy(cur_content_encoding_->encryption_key_id_.get(), data, size); + cur_content_encoding_->encryption_key_id_size_ = size; + return true; + } + + // This should not happen if WebMListParser is working properly. + DCHECK(false); + return false; +} + +} // namespace media diff --git a/media/webm/webm_content_encodings_client.h b/media/webm/webm_content_encodings_client.h new file mode 100644 index 0000000..9a4c876 --- /dev/null +++ b/media/webm/webm_content_encodings_client.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ +#define MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" +#include "media/webm/webm_content_encodings.h" +#include "media/webm/webm_parser.h" + +namespace media { + +// Parser for WebM ContentEncodings element. +class MEDIA_EXPORT WebMContentEncodingsClient : public WebMParserClient { + public: + WebMContentEncodingsClient(); + virtual ~WebMContentEncodingsClient(); + + const ContentEncodings& content_encodings() const; + + // WebMParserClient methods + virtual WebMParserClient* OnListStart(int id) OVERRIDE; + virtual bool OnListEnd(int id) OVERRIDE; + virtual bool OnUInt(int id, int64 val) OVERRIDE; + virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE; + + private: + scoped_refptr<ContentEncoding> cur_content_encoding_; + bool content_encryption_encountered_; + ContentEncodings content_encodings_; + + DISALLOW_COPY_AND_ASSIGN(WebMContentEncodingsClient); +}; + +} // namespace media + +#endif // MEDIA_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_ diff --git a/media/webm/webm_content_encodings_client_unittest.cc b/media/webm/webm_content_encodings_client_unittest.cc new file mode 100644 index 0000000..b3e8daa --- /dev/null +++ b/media/webm/webm_content_encodings_client_unittest.cc @@ -0,0 +1,201 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/webm/webm_constants.h" +#include "media/webm/webm_content_encodings_client.h" +#include "media/webm/webm_parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class WebMContentEncodingsClientTest : public testing::Test { + public: + WebMContentEncodingsClientTest() + : parser_(kWebMIdContentEncodings, &client_) {} + + void ParseAndExpectToFail(const uint8* buf, int size) { + int result = parser_.Parse(buf, size); + EXPECT_EQ(-1, result); + const ContentEncodings content_encodings = client_.content_encodings(); + EXPECT_TRUE(content_encodings.empty()); + } + + protected: + WebMContentEncodingsClient client_; + WebMListParser parser_; +}; + +TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x80, // ContentEncodings (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x83, // ContentEncodings (size = 3) + 0x63, 0x40, 0x80, // ContentEncoding (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33) + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings content_encodings = client_.content_encodings(); + ASSERT_EQ(1u, content_encodings.size()); + EXPECT_EQ(0, content_encodings[0]->order_); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents, + content_encodings[0]->scope_); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type_); + EXPECT_EQ(ContentEncoding::kEncAlgoAes, + content_encodings[0]->encryption_algo_); + EXPECT_TRUE(content_encodings[0]->encryption_key_id_.get()); + EXPECT_EQ(8, content_encodings[0]->encryption_key_id_size_); +} + +TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0xC2, // ContentEncodings (size = 66) + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x62, 0x40, 0x9e, // ContentEncoding (size = 30) + 0x50, 0x31, 0x81, 0x01, // ContentEncodingOrder (size = 1) + 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0x01, // ContentEncAlgo (size = 1) + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings content_encodings = client_.content_encodings(); + ASSERT_EQ(2u, content_encodings.size()); + + for (int i = 0; i < 2; ++i) { + EXPECT_EQ(i, content_encodings[i]->order_); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents | + ContentEncoding::kScopeTrackPrivateData, + content_encodings[i]->scope_); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type_); + EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes, + content_encodings[i]->encryption_algo_); + EXPECT_TRUE(content_encodings[i]->encryption_key_id_.get()); + EXPECT_EQ(8, content_encodings[i]->encryption_key_id_size_); + } +} + +TEST_F(WebMContentEncodingsClientTest, DefaultValues) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8A, // ContentEncodings (size = 10) + 0x62, 0x40, 0x87, // ContentEncoding (size = 7) + // ContentEncodingOrder missing + // ContentEncodingScope missing + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + // ContentEncAlgo missing + }; + int size = sizeof(kContentEncodings); + + int result = parser_.Parse(kContentEncodings, size); + ASSERT_EQ(size, result); + const ContentEncodings content_encodings = client_.content_encodings(); + ASSERT_EQ(1u, content_encodings.size()); + EXPECT_EQ(0, content_encodings[0]->order_); + EXPECT_EQ(ContentEncoding::kScopeAllFrameContents, + content_encodings[0]->scope_); + EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type_); + EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted, + content_encodings[0]->encryption_algo_); + EXPECT_FALSE(content_encodings[0]->encryption_key_id_.get()); + EXPECT_EQ(0, content_encodings[0]->encryption_key_id_size_); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x31, 0x81, 0xEE, // ContentEncodingOrder (size = 1), invalid + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x32, 0x81, 0xEE, // ContentEncodingScope (size = 1), invalid + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14) + 0x62, 0x40, 0x8B, // ContentEncoding (size = 11) + 0x50, 0x33, 0x81, 0x00, // ContentEncodingType (size = 1), invalid + 0x50, 0x35, 0x80, // ContentEncryption (size = 0) + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +// ContentEncodingType is encryption but no ContentEncryption present. +TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x87, // ContentEncodings (size = 7) + 0x62, 0x40, 0x84, // ContentEncoding (size = 4) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + // ContentEncryption missing + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo) { + const uint8 kContentEncodings[] = { + 0x6D, 0x80, 0x99, // ContentEncodings (size = 25) + 0x62, 0x40, 0x96, // ContentEncoding (size = 22) + 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1) + 0x50, 0x35, 0x8F, // ContentEncryption (size = 15) + 0x47, 0xE1, 0x81, 0xEE, // ContentEncAlgo (size = 1), invalid + 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8) + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + }; + int size = sizeof(kContentEncodings); + ParseAndExpectToFail(kContentEncodings, size); +} + +} // media diff --git a/media/webm/webm_tracks_parser.cc b/media/webm/webm_tracks_parser.cc index d340b66..9757b10 100644 --- a/media/webm/webm_tracks_parser.cc +++ b/media/webm/webm_tracks_parser.cc @@ -45,16 +45,28 @@ int WebMTracksParser::Parse(const uint8* buf, int size) { WebMParserClient* WebMTracksParser::OnListStart(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(!track_content_encodings_client_.get()); + track_content_encodings_client_.reset(new WebMContentEncodingsClient); + return track_content_encodings_client_->OnListStart(id); + } + if (id == kWebMIdTrackEntry) { track_type_ = -1; track_num_ = -1; track_default_duration_ = -1; + return this; } return this; } bool WebMTracksParser::OnListEnd(int id) { + if (id == kWebMIdContentEncodings) { + DCHECK(track_content_encodings_client_.get()); + return track_content_encodings_client_->OnListEnd(id); + } + if (id == kWebMIdTrackEntry) { if (track_type_ == -1 || track_num_ == -1) { DVLOG(1) << "Missing TrackEntry data" @@ -74,9 +86,17 @@ bool WebMTracksParser::OnListEnd(int id) { if (track_type_ == kWebMTrackTypeVideo) { video_track_num_ = track_num_; video_default_duration_ = default_duration; + if (track_content_encodings_client_.get()) { + video_content_encodings_ = + track_content_encodings_client_->content_encodings(); + } } else if (track_type_ == kWebMTrackTypeAudio) { audio_track_num_ = track_num_; audio_default_duration_ = default_duration; + if (track_content_encodings_client_.get()) { + audio_content_encodings_ = + track_content_encodings_client_->content_encodings(); + } } else { DVLOG(1) << "Unexpected TrackType " << track_type_; return false; @@ -84,6 +104,8 @@ bool WebMTracksParser::OnListEnd(int id) { track_type_ = -1; track_num_ = -1; + track_content_encodings_client_.reset(); + return true; } return true; diff --git a/media/webm/webm_tracks_parser.h b/media/webm/webm_tracks_parser.h index 5c5aeda..4626e8b 100644 --- a/media/webm/webm_tracks_parser.h +++ b/media/webm/webm_tracks_parser.h @@ -6,7 +6,9 @@ #define MEDIA_WEBM_WEBM_TRACKS_PARSER_H_ #include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" #include "base/time.h" +#include "media/webm/webm_content_encodings_client.h" #include "media/webm/webm_parser.h" namespace media { @@ -14,7 +16,7 @@ namespace media { // Parser for WebM Tracks element. class WebMTracksParser : public WebMParserClient { public: - WebMTracksParser(int64 timecode_scale); + explicit WebMTracksParser(int64 timecode_scale); virtual ~WebMTracksParser(); // Parses a WebM Tracks element in |buf|. @@ -48,10 +50,15 @@ class WebMTracksParser : public WebMParserClient { int64 track_type_; int64 track_num_; int64 track_default_duration_; + scoped_ptr<WebMContentEncodingsClient> track_content_encodings_client_; + int64 audio_track_num_; base::TimeDelta audio_default_duration_; + ContentEncodings audio_content_encodings_; + int64 video_track_num_; base::TimeDelta video_default_duration_; + ContentEncodings video_content_encodings_; DISALLOW_IMPLICIT_CONSTRUCTORS(WebMTracksParser); }; |