Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1606)

Unified Diff: media/mp4/mp4_stream_parser.cc

Issue 10651006: Add Common Encryption support to BMFF, including subsample decryption. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Add wrong subsample size test Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/mp4/mp4_stream_parser.cc
diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc
index ebaa2a2b4af6f40a3637fa6ca33d77a04265827a..92207277c7e2d58beaf3fff2a8f8429ee4fbcfa2 100644
--- a/media/mp4/mp4_stream_parser.cc
+++ b/media/mp4/mp4_stream_parser.cc
@@ -157,11 +157,10 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
RCHECK(!samp_descr.audio_entries.empty());
const AudioSampleEntry& entry = samp_descr.audio_entries[0];
- // TODO(strobe): We accept all format values, pending clarification on
- // the formats used for encrypted media (http://crbug.com/132351).
- // RCHECK(entry.format == FOURCC_MP4A ||
- // (entry.format == FOURCC_ENCA &&
- // entry.sinf.format.format == FOURCC_MP4A));
+ RCHECK(entry.format == FOURCC_MP4A ||
ddorwin 2012/07/17 01:14:21 Is this worth logging since it means the file form
strobe_ 2012/07/19 02:43:35 I'd prefer to leave it in. DLOG() is cheaper than
ddorwin 2012/07/19 06:11:10 I think I meant should we ALWAYS log it.
strobe_ 2012/07/19 16:55:02 Oh! RCHECK() does a DLOG(ERROR) on false. Should w
ddorwin 2012/07/24 01:00:09 I was thinking a separate LOG() for these cases si
+ (entry.format == FOURCC_ENCA &&
+ entry.sinf.format.format == FOURCC_MP4A));
+ RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
// Check if it is MPEG4 AAC defined in ISO 14496 Part 3.
RCHECK(entry.esds.object_type == kISO_14496_3);
ddorwin 2012/07/17 01:14:21 same
@@ -177,9 +176,10 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
RCHECK(!samp_descr.video_entries.empty());
const VideoSampleEntry& entry = samp_descr.video_entries[0];
- // RCHECK(entry.format == FOURCC_AVC1 ||
- // (entry.format == FOURCC_ENCV &&
- // entry.sinf.format.format == FOURCC_AVC1));
+ RCHECK(entry.format == FOURCC_AVC1 ||
ddorwin 2012/07/17 01:14:21 same
+ (entry.format == FOURCC_ENCV &&
+ entry.sinf.format.format == FOURCC_AVC1));
+ RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
// TODO(strobe): Recover correct crop box
video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
@@ -233,10 +233,60 @@ bool MP4StreamParser::ParseMoof(BoxReader* reader) {
return true;
}
+bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) {
+ // TODO(strobe): Send the correct value for initData. The format of initData
+ // has not yet been defined; see
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673.
+ if (!track_encryption.is_encrypted) return true;
+ scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]);
+ memcpy(kid.get(), &track_encryption.default_kid[0],
+ track_encryption.default_kid.size());
+ return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size());
+}
+
+bool MP4StreamParser::PrepareAVCBuffer(
+ std::vector<uint8>* frame_buf,
+ std::vector<SubsampleEntry>* subsamples) {
+ // Convert the AVC NALU length fields to Annex B headers, as expected by
+ // decoding libraries. Since this may enlarge the size of the buffer, we also
+ // update the clear byte count for each subsample if encryption is used.
+ RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, frame_buf));
ddorwin 2012/07/17 01:14:21 Is there a [D]CHECK that size_of_nalu_length_ <= 4
strobe_ 2012/07/19 02:43:35 Yes, in ConvertToAnnexB.
+ if (!subsamples->empty()) {
+ const int nalu_size_diff = 4 - size_of_nalu_length_;
+ size_t expected_size = runs_.sample_size() +
+ subsamples->size() * nalu_size_diff;
+ RCHECK(frame_buf->size() == expected_size);
+ for (size_t i = 0; i < subsamples->size(); i++)
+ (*subsamples)[i].clear_bytes += nalu_size_diff;
+ }
+
+ if (runs_.is_keyframe()) {
+ // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of
+ // a frame. If subsample info is present, we also update the clear byte
ddorwin 2012/07/17 01:14:21 update to what?
strobe_ 2012/07/19 02:43:35 Commented.
ddorwin 2012/07/19 06:11:10 Where?
ddorwin 2012/07/24 01:00:09 Seem to have missed this one. :)
strobe_ 2012/07/25 01:05:13 Done.
+ // count for that first subsample.
+ const AVCDecoderConfigurationRecord* avc_config = NULL;
+ for (size_t t = 0; t < moov_->tracks.size(); t++) {
+ if (moov_->tracks[t].header.track_id == runs_.track_id()) {
+ avc_config = &moov_->tracks[t].media.information.
ddorwin 2012/07/17 01:14:21 strange line break. I think everything after = sho
strobe_ 2012/07/19 02:43:35 Rebased away.
ddorwin 2012/07/19 06:11:10 Still here.
strobe_ 2012/07/19 16:55:02 Sorry, *really* fixed. Thanks for your dilligence!
+ sample_table.description.video_entries[0].avcc;
ddorwin 2012/07/17 01:14:21 is video_entries size guaranteed to be >= 1?
strobe_ 2012/07/19 02:43:35 Rebased away.
ddorwin 2012/07/19 06:11:10 Still here.
strobe_ 2012/07/19 16:55:02 Done.
+ break;
+ }
+ }
+ RCHECK(avc_config != NULL);
+ std::vector<uint8> param_sets;
+ RCHECK(AVC::ConvertParameterSets(*avc_config, &param_sets));
ddorwin 2012/07/17 01:14:21 ConvertConfigToParameterSets?
strobe_ 2012/07/19 02:43:35 Done.
+ frame_buf->insert(frame_buf->begin(),
+ param_sets.begin(), param_sets.end());
+ if (!subsamples->empty())
+ (*subsamples)[0].clear_bytes += param_sets.size();
+ }
+ return true;
+}
+
bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
BufferQueue* video_buffers,
bool* err) {
- if (!runs_.RunValid()) {
+ if (!runs_.RunIsValid()) {
ddorwin 2012/07/17 01:14:21 Why isn't this IsRunValid? Unless you really are r
strobe_ 2012/07/19 02:43:35 Evidence of my functional bias. Renamed.
// Flush any buffers we've gotten in this chunk so that buffers don't
// cross NewSegment() calls
*err = !SendAndFlushSamples(audio_buffers, video_buffers);
@@ -245,7 +295,7 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
return true;
}
- if (!runs_.SampleValid()) {
+ if (!runs_.SampleIsValid()) {
ddorwin 2012/07/17 01:14:21 same
strobe_ 2012/07/19 02:43:35 Done.
runs_.AdvanceRun();
return true;
}
@@ -253,9 +303,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
DCHECK(!(*err));
const uint8* buf;
- int size;
- queue_.Peek(&buf, &size);
- if (!size) return false;
+ int buf_size;
+ queue_.Peek(&buf, &buf_size);
+ if (!buf_size) return false;
bool audio = has_audio_ && audio_track_id_ == runs_.track_id();
bool video = has_video_ && video_track_id_ == runs_.track_id();
@@ -270,28 +320,32 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
// quite small compared to sample data, so this pattern is useful on
// memory-constrained devices where the source buffer consumes a substantial
// portion of the total system memory.
- if (runs_.NeedsCENC()) {
- queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size);
- return runs_.CacheCENC(buf, size);
+ if (runs_.AuxInfoNeedsToBeCached()) {
+ queue_.PeekAt(runs_.aux_info_offset() + moof_head_, &buf, &buf_size);
+ if (buf_size < runs_.aux_info_size()) return false;
+ *err = !runs_.CacheAuxInfo(buf, buf_size);
+ return !*err;
}
- queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size);
- if (size < runs_.size()) return false;
+ queue_.PeekAt(runs_.sample_offset() + moof_head_, &buf, &buf_size);
+ if (buf_size < runs_.sample_size()) return false;
+
+ scoped_ptr<DecryptConfig> decrypt_config;
+ if (runs_.is_encrypted())
+ decrypt_config = runs_.GetDecryptConfig();
- std::vector<uint8> frame_buf(buf, buf + runs_.size());
+ std::vector<uint8> frame_buf(buf, buf + runs_.sample_size());
if (video) {
- RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf));
- if (runs_.is_keyframe()) {
- const AVCDecoderConfigurationRecord* avc_config = NULL;
- for (size_t t = 0; t < moov_->tracks.size(); t++) {
- if (moov_->tracks[t].header.track_id == runs_.track_id()) {
- avc_config = &moov_->tracks[t].media.information.
- sample_table.description.video_entries[0].avcc;
- break;
- }
- }
- RCHECK(avc_config != NULL);
- RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf));
+ std::vector<SubsampleEntry> subsamples;
+ if (decrypt_config.get())
+ subsamples = decrypt_config->subsamples();
+ RCHECK(PrepareAVCBuffer(&frame_buf, &subsamples));
ddorwin 2012/07/17 01:14:21 If else above, we want to run this for the key fra
strobe_ 2012/07/19 02:43:35 Sorry, can you clarify this comment?
ddorwin 2012/07/19 06:11:10 If line 340 - if (decrypt_config.get()) - is not t
strobe_ 2012/07/19 16:55:02 No. H.264 NALUs are stored in "AVC" format (length
ddorwin 2012/07/24 01:00:09 Ahh, I overlooked the conversion call.
+ if (!subsamples.empty()) {
+ decrypt_config.reset(new DecryptConfig(
+ decrypt_config->key_id(),
+ decrypt_config->key_id_size(),
+ decrypt_config->iv(),
+ subsamples));
}
}
@@ -303,6 +357,9 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
runs_.is_keyframe());
+ if (runs_.is_encrypted())
+ stream_buf->SetDecryptConfig(decrypt_config.Pass());
+
stream_buf->SetDuration(runs_.duration());
stream_buf->SetTimestamp(runs_.cts());
stream_buf->SetDecodeTimestamp(runs_.dts());
@@ -312,7 +369,7 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
<< ", dur=" << runs_.duration().InMilliseconds()
<< ", dts=" << runs_.dts().InMilliseconds()
<< ", cts=" << runs_.cts().InMilliseconds()
ddorwin 2012/07/17 01:14:21 set cts before dts above. should it appear that wa
strobe_ 2012/07/19 02:43:35 Timestamp is the more important field, since Decod
- << ", size=" << runs_.size();
+ << ", size=" << runs_.sample_size();
if (audio) {
audio_buffers->push_back(stream_buf);

Powered by Google App Engine
This is Rietveld 408576698