diff options
author | kqyang@chromium.org <kqyang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-09 11:59:28 +0000 |
---|---|---|
committer | kqyang@chromium.org <kqyang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-09 11:59:28 +0000 |
commit | 32181c608fdd05eb6f6aab668607d71e7b890fcc (patch) | |
tree | f254e698f56db81ca3deabef701a3342cfa55a6f /media | |
parent | 7499b25162978b51acb0a98899977936d6deaa2d (diff) | |
download | chromium_src-32181c608fdd05eb6f6aab668607d71e7b890fcc.zip chromium_src-32181c608fdd05eb6f6aab668607d71e7b890fcc.tar.gz chromium_src-32181c608fdd05eb6f6aab668607d71e7b890fcc.tar.bz2 |
Support CENC Sample Groups in MP4 Parser
Required for key rotation support.
BUG=367366
Review URL: https://codereview.chromium.org/263773005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269241 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/formats/mp4/mp4_stream_parser.cc | 6 | ||||
-rw-r--r-- | media/formats/mp4/mp4_stream_parser_unittest.cc | 6 | ||||
-rw-r--r-- | media/formats/mp4/track_run_iterator.cc | 99 | ||||
-rw-r--r-- | media/formats/mp4/track_run_iterator.h | 9 | ||||
-rw-r--r-- | media/formats/mp4/track_run_iterator_unittest.cc | 115 |
5 files changed, 217 insertions, 18 deletions
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc index 76fdeaf..a46178b 100644 --- a/media/formats/mp4/mp4_stream_parser.cc +++ b/media/formats/mp4/mp4_stream_parser.cc @@ -428,12 +428,6 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, if (!audio && !video) runs_->AdvanceRun(); - // AuxInfo is required for encrypted samples. - // See ISO Common Encryption spec: ISO/IEC FDIS 23001-7:2011(E); - // Section 7: Common Encryption Sample Auxiliary Information. - if (runs_->is_encrypted() && !runs_->aux_info_size()) - return false; - // Attempt to cache the auxiliary information first. Aux info is usually // placed in a contiguous block before the sample data, rather than being // interleaved. If we didn't cache it, this would require that we retain the diff --git a/media/formats/mp4/mp4_stream_parser_unittest.cc b/media/formats/mp4/mp4_stream_parser_unittest.cc index d19e47c..ef0bd44 100644 --- a/media/formats/mp4/mp4_stream_parser_unittest.cc +++ b/media/formats/mp4/mp4_stream_parser_unittest.cc @@ -209,7 +209,11 @@ TEST_F(MP4StreamParserTest, NoMoovAfterFlush) { // SampleAuxiliaryInformation{Sizes|Offsets}Box (saiz|saio) are missing. // The parser should fail instead of crash. See http://crbug.com/361347 TEST_F(MP4StreamParserTest, MissingSampleAuxInfo) { - ParseMP4File("bear-1280x720-a_frag-cenc_missing-saiz-saio.mp4", 512); + InitializeParser(); + + scoped_refptr<DecoderBuffer> buffer = + ReadTestDataFile("bear-1280x720-a_frag-cenc_missing-saiz-saio.mp4"); + EXPECT_FALSE(AppendDataInPieces(buffer->data(), buffer->data_size(), 512)); } // Test a file where all video samples start with an Access Unit diff --git a/media/formats/mp4/track_run_iterator.cc b/media/formats/mp4/track_run_iterator.cc index bc38352..2fff4b3 100644 --- a/media/formats/mp4/track_run_iterator.cc +++ b/media/formats/mp4/track_run_iterator.cc @@ -9,6 +9,7 @@ #include "media/base/buffers.h" #include "media/base/stream_parser_buffer.h" #include "media/formats/mp4/rcheck.h" +#include "media/formats/mp4/sample_to_group_iterator.h" namespace { static const uint32 kSampleIsDifferenceSampleFlagMask = 0x10000; @@ -22,6 +23,7 @@ struct SampleInfo { int duration; int cts_offset; bool is_keyframe; + uint32 cenc_group_description_index; }; struct TrackRunInfo { @@ -40,6 +42,8 @@ struct TrackRunInfo { std::vector<uint8> aux_info_sizes; // Populated if default_size == 0. int aux_info_total_size; + std::vector<CencSampleEncryptionInfoEntry> sample_encryption_info; + TrackRunInfo(); ~TrackRunInfo(); }; @@ -215,6 +219,9 @@ bool TrackRunIterator::Init(const MovieFragment& moof) { } } + SampleToGroupIterator sample_to_group_itr(traf.sample_to_group); + bool is_sample_to_group_valid = sample_to_group_itr.IsValid(); + int64 run_start_dts = traf.decode_time.decode_time; int sample_count_sum = 0; bool is_sync_sample_box_present = @@ -226,6 +233,7 @@ bool TrackRunIterator::Init(const MovieFragment& moof) { tri.timescale = trak->media.header.timescale; tri.start_dts = run_start_dts; tri.sample_start_offset = trun.data_offset; + tri.sample_encryption_info = traf.sample_group_description.entries; tri.is_audio = (stsd.type == kAudio); if (tri.is_audio) { @@ -289,10 +297,41 @@ bool TrackRunIterator::Init(const MovieFragment& moof) { // and downstream code's "is keyframe" concept. if (!is_sync_sample_box_present) tri.samples[k].is_keyframe = true; + + if (!is_sample_to_group_valid) { + // Set group description index to 0 to read encryption information + // from TrackEncryption Box. + tri.samples[k].cenc_group_description_index = 0; + continue; + } + + // ISO-14496-12 Section 8.9.2.3 and 8.9.4 : group description index + // (1) ranges from 1 to the number of sample group entries in the track + // level SampleGroupDescription Box, or (2) takes the value 0 to + // indicate that this sample is a member of no group, in this case, the + // sample is associated with the default values specified in + // TrackEncryption Box, or (3) starts at 0x10001, i.e. the index value + // 1, with the value 1 in the top 16 bits, to reference fragment-local + // SampleGroupDescription Box. + // Case (1) is not supported currently. We might not need it either as + // the same functionality can be better achieved using (2). + uint32 index = sample_to_group_itr.group_description_index(); + if (index >= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase) { + index -= SampleToGroupEntry::kFragmentGroupDescriptionIndexBase; + RCHECK(index != 0 && index <= tri.sample_encryption_info.size()); + } else if (index != 0) { + NOTIMPLEMENTED() << "'sgpd' box in 'moov' is not supported."; + return false; + } + tri.samples[k].cenc_group_description_index = index; + is_sample_to_group_valid = sample_to_group_itr.Advance(); } runs_.push_back(tri); sample_count_sum += trun.sample_count; } + + // We should have iterated through all samples in SampleToGroup Box. + RCHECK(!sample_to_group_itr.IsValid()); } std::sort(runs_.begin(), runs_.end(), CompareMinTrackRunDataOffset()); @@ -325,7 +364,7 @@ void TrackRunIterator::AdvanceSample() { // info is available in the stream. bool TrackRunIterator::AuxInfoNeedsToBeCached() { DCHECK(IsRunValid()); - return is_encrypted() && aux_info_size() > 0 && cenc_info_.size() == 0; + return aux_info_size() > 0 && cenc_info_.size() == 0; } // This implementation currently only caches CENC auxiliary info. @@ -339,8 +378,10 @@ bool TrackRunIterator::CacheAuxInfo(const uint8* buf, int buf_size) { if (!info_size) info_size = run_itr_->aux_info_sizes[i]; - BufferReader reader(buf + pos, info_size); - RCHECK(cenc_info_[i].Parse(track_encryption().default_iv_size, &reader)); + if (IsSampleEncrypted(i)) { + BufferReader reader(buf + pos, info_size); + RCHECK(cenc_info_[i].Parse(GetIvSize(i), &reader)); + } pos += info_size; } @@ -387,8 +428,8 @@ uint32 TrackRunIterator::track_id() const { } bool TrackRunIterator::is_encrypted() const { - DCHECK(IsRunValid()); - return track_encryption().is_encrypted; + DCHECK(IsSampleValid()); + return IsSampleEncrypted(sample_itr_ - run_itr_->samples.begin()); } int64 TrackRunIterator::aux_info_offset() const { @@ -454,10 +495,17 @@ const TrackEncryption& TrackRunIterator::track_encryption() const { } scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { + DCHECK(is_encrypted()); + + if (cenc_info_.empty()) { + DCHECK_EQ(0, aux_info_size()); + MEDIA_LOG(log_cb_) << "Aux Info is not available."; + return scoped_ptr<DecryptConfig>(); + } + size_t sample_idx = sample_itr_ - run_itr_->samples.begin(); - DCHECK(sample_idx < cenc_info_.size()); + DCHECK_LT(sample_idx, cenc_info_.size()); const FrameCENCInfo& cenc_info = cenc_info_[sample_idx]; - DCHECK(is_encrypted() && !AuxInfoNeedsToBeCached()); size_t total_size = 0; if (!cenc_info.subsamples.empty() && @@ -467,7 +515,7 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { return scoped_ptr<DecryptConfig>(); } - const std::vector<uint8>& kid = track_encryption().default_kid; + const std::vector<uint8>& kid = GetKeyId(sample_idx); return scoped_ptr<DecryptConfig>(new DecryptConfig( std::string(reinterpret_cast<const char*>(&kid[0]), kid.size()), std::string(reinterpret_cast<const char*>(cenc_info.iv), @@ -475,5 +523,40 @@ scoped_ptr<DecryptConfig> TrackRunIterator::GetDecryptConfig() { cenc_info.subsamples)); } +uint32 TrackRunIterator::GetGroupDescriptionIndex(uint32 sample_index) const { + DCHECK(IsRunValid()); + DCHECK_LT(sample_index, run_itr_->samples.size()); + return run_itr_->samples[sample_index].cenc_group_description_index; +} + +const CencSampleEncryptionInfoEntry& +TrackRunIterator::GetSampleEncryptionInfoEntry( + uint32 group_description_index) const { + DCHECK(IsRunValid()); + DCHECK_NE(group_description_index, 0u); + DCHECK_LE(group_description_index, run_itr_->sample_encryption_info.size()); + // |group_description_index| is 1-based. Subtract by 1 to index the vector. + return run_itr_->sample_encryption_info[group_description_index - 1]; +} + +bool TrackRunIterator::IsSampleEncrypted(size_t sample_index) const { + uint32 index = GetGroupDescriptionIndex(sample_index); + return (index == 0) ? track_encryption().is_encrypted + : GetSampleEncryptionInfoEntry(index).is_encrypted; +} + +const std::vector<uint8>& TrackRunIterator::GetKeyId( + size_t sample_index) const { + uint32 index = GetGroupDescriptionIndex(sample_index); + return (index == 0) ? track_encryption().default_kid + : GetSampleEncryptionInfoEntry(index).key_id; +} + +uint8 TrackRunIterator::GetIvSize(size_t sample_index) const { + uint32 index = GetGroupDescriptionIndex(sample_index); + return (index == 0) ? track_encryption().default_iv_size + : GetSampleEncryptionInfoEntry(index).iv_size; +} + } // namespace mp4 } // namespace media diff --git a/media/formats/mp4/track_run_iterator.h b/media/formats/mp4/track_run_iterator.h index 829dd11..8167478 100644 --- a/media/formats/mp4/track_run_iterator.h +++ b/media/formats/mp4/track_run_iterator.h @@ -87,6 +87,15 @@ class MEDIA_EXPORT TrackRunIterator { void ResetRun(); const TrackEncryption& track_encryption() const; + uint32 GetGroupDescriptionIndex(uint32 sample_index) const; + const CencSampleEncryptionInfoEntry& GetSampleEncryptionInfoEntry( + uint32 group_description_index) const; + + // Sample encryption information. + bool IsSampleEncrypted(size_t sample_index) const; + uint8 GetIvSize(size_t sample_index) const; + const std::vector<uint8>& GetKeyId(size_t sample_index) const; + const Movie* moov_; LogCB log_cb_; diff --git a/media/formats/mp4/track_run_iterator_unittest.cc b/media/formats/mp4/track_run_iterator_unittest.cc index ea37bab..2ef3f83 100644 --- a/media/formats/mp4/track_run_iterator_unittest.cc +++ b/media/formats/mp4/track_run_iterator_unittest.cc @@ -37,6 +37,11 @@ static const uint8 kKeyId[] = { 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x49, 0x44 }; +static const uint8 kCencSampleGroupKeyId[] = { + 0x46, 0x72, 0x61, 0x67, 0x53, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b +}; + namespace media { namespace mp4 { @@ -133,9 +138,28 @@ class TrackRunIteratorTest : public testing::Test { sinf->type.type = FOURCC_CENC; sinf->info.track_encryption.is_encrypted = true; sinf->info.track_encryption.default_iv_size = 8; - sinf->info.track_encryption.default_kid.insert( - sinf->info.track_encryption.default_kid.begin(), - kKeyId, kKeyId + arraysize(kKeyId)); + sinf->info.track_encryption.default_kid.assign(kKeyId, + kKeyId + arraysize(kKeyId)); + } + + // Add SampleGroupDescription Box with two entries (an unencrypted entry and + // an encrypted entry). Populate SampleToGroup Box from input array. + void AddCencSampleGroup(TrackFragment* frag, + const SampleToGroupEntry* sample_to_group_entries, + size_t num_entries) { + frag->sample_group_description.grouping_type = FOURCC_SEIG; + frag->sample_group_description.entries.resize(2); + frag->sample_group_description.entries[0].is_encrypted = false; + frag->sample_group_description.entries[0].iv_size = 0; + frag->sample_group_description.entries[1].is_encrypted = true; + frag->sample_group_description.entries[1].iv_size = 8; + frag->sample_group_description.entries[1].key_id.assign( + kCencSampleGroupKeyId, + kCencSampleGroupKeyId + arraysize(kCencSampleGroupKeyId)); + + frag->sample_to_group.grouping_type = FOURCC_SEIG; + frag->sample_to_group.entries.assign(sample_to_group_entries, + sample_to_group_entries + num_entries); } // Add aux info covering the first track run to a TrackFragment, and update @@ -149,6 +173,20 @@ class TrackRunIteratorTest : public testing::Test { frag->runs[0].sample_sizes[1] = 10; } + bool InitMoofWithArbitraryAuxInfo(MovieFragment* moof) { + // Add aux info header (equal sized aux info for every sample). + for (uint32 i = 0; i < moof->tracks.size(); ++i) { + moof->tracks[i].auxiliary_offset.offsets.push_back(50); + moof->tracks[i].auxiliary_size.sample_count = 10; + moof->tracks[i].auxiliary_size.default_sample_info_size = 8; + } + + // We don't care about the actual data in aux. + std::vector<uint8> aux_info(1000); + return iter_->Init(*moof) && + iter_->CacheAuxInfo(&aux_info[0], aux_info.size()); + } + void SetAscending(std::vector<uint32>* vec) { vec->resize(10); for (size_t i = 0; i < vec->size(); i++) @@ -356,6 +394,77 @@ TEST_F(TrackRunIteratorTest, DecryptConfigTest) { EXPECT_EQ(config->subsamples()[1].cypher_bytes, 4u); } +TEST_F(TrackRunIteratorTest, CencSampleGroupTest) { + MovieFragment moof = CreateFragment(); + + const SampleToGroupEntry kSampleToGroupTable[] = { + // Associated with the second entry in SampleGroupDescription Box. + {1, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 2}, + // Associated with the first entry in SampleGroupDescription Box. + {1, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 1}}; + AddCencSampleGroup( + &moof.tracks[0], kSampleToGroupTable, arraysize(kSampleToGroupTable)); + + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + ASSERT_TRUE(InitMoofWithArbitraryAuxInfo(&moof)); + + std::string cenc_sample_group_key_id( + kCencSampleGroupKeyId, + kCencSampleGroupKeyId + arraysize(kCencSampleGroupKeyId)); + // The first sample is encrypted and the second sample is unencrypted. + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(cenc_sample_group_key_id, iter_->GetDecryptConfig()->key_id()); + iter_->AdvanceSample(); + EXPECT_FALSE(iter_->is_encrypted()); +} + +TEST_F(TrackRunIteratorTest, CencSampleGroupWithTrackEncryptionBoxTest) { + // Add TrackEncryption Box. + AddEncryption(&moov_.tracks[0]); + + MovieFragment moof = CreateFragment(); + + const SampleToGroupEntry kSampleToGroupTable[] = { + // Associated with the second entry in SampleGroupDescription Box. + {2, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 2}, + // Associated with the default values specified in TrackEncryption Box. + {4, 0}, + // Associated with the first entry in SampleGroupDescription Box. + {3, SampleToGroupEntry::kFragmentGroupDescriptionIndexBase + 1}}; + AddCencSampleGroup( + &moof.tracks[0], kSampleToGroupTable, arraysize(kSampleToGroupTable)); + + iter_.reset(new TrackRunIterator(&moov_, log_cb_)); + ASSERT_TRUE(InitMoofWithArbitraryAuxInfo(&moof)); + + std::string track_encryption_key_id(kKeyId, kKeyId + arraysize(kKeyId)); + std::string cenc_sample_group_key_id( + kCencSampleGroupKeyId, + kCencSampleGroupKeyId + arraysize(kCencSampleGroupKeyId)); + + for (size_t i = 0; i < kSampleToGroupTable[0].sample_count; ++i) { + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(cenc_sample_group_key_id, iter_->GetDecryptConfig()->key_id()); + iter_->AdvanceSample(); + } + + for (size_t i = 0; i < kSampleToGroupTable[1].sample_count; ++i) { + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(track_encryption_key_id, iter_->GetDecryptConfig()->key_id()); + iter_->AdvanceSample(); + } + + for (size_t i = 0; i < kSampleToGroupTable[2].sample_count; ++i) { + EXPECT_FALSE(iter_->is_encrypted()); + iter_->AdvanceSample(); + } + + // The remaining samples should be associated with the default values + // specified in TrackEncryption Box. + EXPECT_TRUE(iter_->is_encrypted()); + EXPECT_EQ(track_encryption_key_id, iter_->GetDecryptConfig()->key_id()); +} + // It is legal for aux info blocks to be shared among multiple formats. TEST_F(TrackRunIteratorTest, SharedAuxInfoTest) { AddEncryption(&moov_.tracks[0]); |