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

Side by Side 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 unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "media/mp4/mp4_stream_parser.h" 5 #include "media/mp4/mp4_stream_parser.h"
6 6
7 #include "base/callback.h" 7 #include "base/callback.h"
8 #include "base/callback_helpers.h" 8 #include "base/callback_helpers.h"
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/time.h" 10 #include "base/time.h"
(...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after
150 track != moov_->tracks.end(); ++track) { 150 track != moov_->tracks.end(); ++track) {
151 // TODO(strobe): Only the first audio and video track present in a file are 151 // TODO(strobe): Only the first audio and video track present in a file are
152 // used. (Track selection is better accomplished via Source IDs, though, so 152 // used. (Track selection is better accomplished via Source IDs, though, so
153 // adding support for track selection within a stream is low-priority.) 153 // adding support for track selection within a stream is low-priority.)
154 const SampleDescription& samp_descr = 154 const SampleDescription& samp_descr =
155 track->media.information.sample_table.description; 155 track->media.information.sample_table.description;
156 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) { 156 if (track->media.handler.type == kAudio && !audio_config.IsValidConfig()) {
157 RCHECK(!samp_descr.audio_entries.empty()); 157 RCHECK(!samp_descr.audio_entries.empty());
158 const AudioSampleEntry& entry = samp_descr.audio_entries[0]; 158 const AudioSampleEntry& entry = samp_descr.audio_entries[0];
159 159
160 // TODO(strobe): We accept all format values, pending clarification on 160 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
161 // the formats used for encrypted media (http://crbug.com/132351). 161 (entry.format == FOURCC_ENCA &&
162 // RCHECK(entry.format == FOURCC_MP4A || 162 entry.sinf.format.format == FOURCC_MP4A));
163 // (entry.format == FOURCC_ENCA && 163 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
164 // entry.sinf.format.format == FOURCC_MP4A));
165 164
166 // Check if it is MPEG4 AAC defined in ISO 14496 Part 3. 165 // Check if it is MPEG4 AAC defined in ISO 14496 Part 3.
167 RCHECK(entry.esds.object_type == kISO_14496_3); 166 RCHECK(entry.esds.object_type == kISO_14496_3);
ddorwin 2012/07/17 01:14:21 same
168 aac_ = entry.esds.aac; 167 aac_ = entry.esds.aac;
169 audio_config.Initialize(kCodecAAC, entry.samplesize, 168 audio_config.Initialize(kCodecAAC, entry.samplesize,
170 aac_.channel_layout(), aac_.frequency(), 169 aac_.channel_layout(), aac_.frequency(),
171 NULL, 0, false); 170 NULL, 0, false);
172 171
173 has_audio_ = true; 172 has_audio_ = true;
174 audio_track_id_ = track->header.track_id; 173 audio_track_id_ = track->header.track_id;
175 } 174 }
176 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) { 175 if (track->media.handler.type == kVideo && !video_config.IsValidConfig()) {
177 RCHECK(!samp_descr.video_entries.empty()); 176 RCHECK(!samp_descr.video_entries.empty());
178 const VideoSampleEntry& entry = samp_descr.video_entries[0]; 177 const VideoSampleEntry& entry = samp_descr.video_entries[0];
179 178
180 // RCHECK(entry.format == FOURCC_AVC1 || 179 RCHECK(entry.format == FOURCC_AVC1 ||
ddorwin 2012/07/17 01:14:21 same
181 // (entry.format == FOURCC_ENCV && 180 (entry.format == FOURCC_ENCV &&
182 // entry.sinf.format.format == FOURCC_AVC1)); 181 entry.sinf.format.format == FOURCC_AVC1));
182 RCHECK(EmitKeyNeeded(entry.sinf.info.track_encryption));
183 183
184 // TODO(strobe): Recover correct crop box 184 // TODO(strobe): Recover correct crop box
185 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12, 185 video_config.Initialize(kCodecH264, H264PROFILE_MAIN, VideoFrame::YV12,
186 gfx::Size(entry.width, entry.height), 186 gfx::Size(entry.width, entry.height),
187 gfx::Rect(0, 0, entry.width, entry.height), 187 gfx::Rect(0, 0, entry.width, entry.height),
188 // Framerate of zero is provided to signal that 188 // Framerate of zero is provided to signal that
189 // the decoder should trust demuxer timestamps 189 // the decoder should trust demuxer timestamps
190 0, 1, 190 0, 1,
ddorwin 2012/07/17 01:14:21 what is the 1? if it's not paired with the 0 (or e
strobe_ 2012/07/19 02:43:35 Framerate numerator and denominator.
191 entry.pixel_aspect.h_spacing, 191 entry.pixel_aspect.h_spacing,
192 entry.pixel_aspect.v_spacing, 192 entry.pixel_aspect.v_spacing,
193 // No decoder-specific buffer needed for AVC; 193 // No decoder-specific buffer needed for AVC;
194 // SPS/PPS are embedded in the video stream 194 // SPS/PPS are embedded in the video stream
195 NULL, 0, false); 195 NULL, 0, false);
196 has_video_ = true; 196 has_video_ = true;
197 video_track_id_ = track->header.track_id; 197 video_track_id_ = track->header.track_id;
198 198
199 size_of_nalu_length_ = entry.avcc.length_size; 199 size_of_nalu_length_ = entry.avcc.length_size;
200 } 200 }
201 } 201 }
202 202
203 // TODO(strobe): For now, we avoid sending new configs on a new 203 // TODO(strobe): For now, we avoid sending new configs on a new
204 // reinitialization segment, and instead simply embed the updated parameter 204 // reinitialization segment, and instead simply embed the updated parameter
205 // sets into the video stream. The conditional should be removed when 205 // sets into the video stream. The conditional should be removed when
206 // http://crbug.com/122913 is fixed. 206 // http://crbug.com/122913 is fixed.
207 if (!init_cb_.is_null()) 207 if (!init_cb_.is_null())
ddorwin 2012/07/17 01:14:21 checking wrong callback?
strobe_ 2012/07/19 02:43:35 No, I think this is correct. We currently use null
ddorwin 2012/07/19 06:11:10 Hmm, might be worth a comment explaining that here
strobe_ 2012/07/19 16:55:02 Done.
208 RCHECK(config_cb_.Run(audio_config, video_config)); 208 RCHECK(config_cb_.Run(audio_config, video_config));
209 209
210 base::TimeDelta duration; 210 base::TimeDelta duration;
211 if (moov_->extends.header.fragment_duration > 0) { 211 if (moov_->extends.header.fragment_duration > 0) {
212 duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration, 212 duration = TimeDeltaFromFrac(moov_->extends.header.fragment_duration,
213 moov_->header.timescale); 213 moov_->header.timescale);
214 } else if (moov_->header.duration > 0) { 214 } else if (moov_->header.duration > 0) {
215 duration = TimeDeltaFromFrac(moov_->header.duration, 215 duration = TimeDeltaFromFrac(moov_->header.duration,
216 moov_->header.timescale); 216 moov_->header.timescale);
217 } else { 217 } else {
218 duration = kInfiniteDuration(); 218 duration = kInfiniteDuration();
219 } 219 }
220 220
221 if (!init_cb_.is_null()) 221 if (!init_cb_.is_null())
222 base::ResetAndReturn(&init_cb_).Run(true, duration); 222 base::ResetAndReturn(&init_cb_).Run(true, duration);
223 return true; 223 return true;
224 } 224 }
225 225
226 bool MP4StreamParser::ParseMoof(BoxReader* reader) { 226 bool MP4StreamParser::ParseMoof(BoxReader* reader) {
227 RCHECK(moov_.get()); // Must already have initialization segment 227 RCHECK(moov_.get()); // Must already have initialization segment
228 MovieFragment moof; 228 MovieFragment moof;
229 RCHECK(moof.Parse(reader)); 229 RCHECK(moof.Parse(reader));
230 RCHECK(runs_.Init(*moov_, moof)); 230 RCHECK(runs_.Init(*moov_, moof));
231 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp()); 231 new_segment_cb_.Run(runs_.GetMinDecodeTimestamp());
232 ChangeState(kEmittingSamples); 232 ChangeState(kEmittingSamples);
233 return true; 233 return true;
234 } 234 }
235 235
236 bool MP4StreamParser::EmitKeyNeeded(const TrackEncryption& track_encryption) {
237 // TODO(strobe): Send the correct value for initData. The format of initData
238 // has not yet been defined; see
239 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673.
240 if (!track_encryption.is_encrypted) return true;
241 scoped_array<uint8> kid(new uint8[track_encryption.default_kid.size()]);
242 memcpy(kid.get(), &track_encryption.default_kid[0],
243 track_encryption.default_kid.size());
244 return need_key_cb_.Run(kid.Pass(), track_encryption.default_kid.size());
245 }
246
247 bool MP4StreamParser::PrepareAVCBuffer(
248 std::vector<uint8>* frame_buf,
249 std::vector<SubsampleEntry>* subsamples) {
250 // Convert the AVC NALU length fields to Annex B headers, as expected by
251 // decoding libraries. Since this may enlarge the size of the buffer, we also
252 // update the clear byte count for each subsample if encryption is used.
253 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.
254 if (!subsamples->empty()) {
255 const int nalu_size_diff = 4 - size_of_nalu_length_;
256 size_t expected_size = runs_.sample_size() +
257 subsamples->size() * nalu_size_diff;
258 RCHECK(frame_buf->size() == expected_size);
259 for (size_t i = 0; i < subsamples->size(); i++)
260 (*subsamples)[i].clear_bytes += nalu_size_diff;
261 }
262
263 if (runs_.is_keyframe()) {
264 // If this is a keyframe, we (re-)inject SPS and PPS headers at the start of
265 // 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.
266 // count for that first subsample.
267 const AVCDecoderConfigurationRecord* avc_config = NULL;
268 for (size_t t = 0; t < moov_->tracks.size(); t++) {
269 if (moov_->tracks[t].header.track_id == runs_.track_id()) {
270 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!
271 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.
272 break;
273 }
274 }
275 RCHECK(avc_config != NULL);
276 std::vector<uint8> param_sets;
277 RCHECK(AVC::ConvertParameterSets(*avc_config, &param_sets));
ddorwin 2012/07/17 01:14:21 ConvertConfigToParameterSets?
strobe_ 2012/07/19 02:43:35 Done.
278 frame_buf->insert(frame_buf->begin(),
279 param_sets.begin(), param_sets.end());
280 if (!subsamples->empty())
281 (*subsamples)[0].clear_bytes += param_sets.size();
282 }
283 return true;
284 }
285
236 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, 286 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
237 BufferQueue* video_buffers, 287 BufferQueue* video_buffers,
238 bool* err) { 288 bool* err) {
239 if (!runs_.RunValid()) { 289 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.
240 // Flush any buffers we've gotten in this chunk so that buffers don't 290 // Flush any buffers we've gotten in this chunk so that buffers don't
241 // cross NewSegment() calls 291 // cross NewSegment() calls
242 *err = !SendAndFlushSamples(audio_buffers, video_buffers); 292 *err = !SendAndFlushSamples(audio_buffers, video_buffers);
243 if (*err) return false; 293 if (*err) return false;
244 ChangeState(kParsingBoxes); 294 ChangeState(kParsingBoxes);
245 return true; 295 return true;
246 } 296 }
247 297
248 if (!runs_.SampleValid()) { 298 if (!runs_.SampleIsValid()) {
ddorwin 2012/07/17 01:14:21 same
strobe_ 2012/07/19 02:43:35 Done.
249 runs_.AdvanceRun(); 299 runs_.AdvanceRun();
250 return true; 300 return true;
251 } 301 }
252 302
253 DCHECK(!(*err)); 303 DCHECK(!(*err));
254 304
255 const uint8* buf; 305 const uint8* buf;
256 int size; 306 int buf_size;
257 queue_.Peek(&buf, &size); 307 queue_.Peek(&buf, &buf_size);
258 if (!size) return false; 308 if (!buf_size) return false;
259 309
260 bool audio = has_audio_ && audio_track_id_ == runs_.track_id(); 310 bool audio = has_audio_ && audio_track_id_ == runs_.track_id();
261 bool video = has_video_ && video_track_id_ == runs_.track_id(); 311 bool video = has_video_ && video_track_id_ == runs_.track_id();
262 312
263 // Skip this entire track if it's not one we're interested in 313 // Skip this entire track if it's not one we're interested in
264 if (!audio && !video) runs_.AdvanceRun(); 314 if (!audio && !video) runs_.AdvanceRun();
265 315
266 // Attempt to cache the auxiliary information first. Aux info is usually 316 // Attempt to cache the auxiliary information first. Aux info is usually
267 // placed in a contiguous block before the sample data, rather than being 317 // placed in a contiguous block before the sample data, rather than being
268 // interleaved. If we didn't cache it, this would require that we retain the 318 // interleaved. If we didn't cache it, this would require that we retain the
269 // start of the segment buffer while reading samples. Aux info is typically 319 // start of the segment buffer while reading samples. Aux info is typically
270 // quite small compared to sample data, so this pattern is useful on 320 // quite small compared to sample data, so this pattern is useful on
271 // memory-constrained devices where the source buffer consumes a substantial 321 // memory-constrained devices where the source buffer consumes a substantial
272 // portion of the total system memory. 322 // portion of the total system memory.
273 if (runs_.NeedsCENC()) { 323 if (runs_.AuxInfoNeedsToBeCached()) {
274 queue_.PeekAt(runs_.cenc_offset() + moof_head_, &buf, &size); 324 queue_.PeekAt(runs_.aux_info_offset() + moof_head_, &buf, &buf_size);
275 return runs_.CacheCENC(buf, size); 325 if (buf_size < runs_.aux_info_size()) return false;
326 *err = !runs_.CacheAuxInfo(buf, buf_size);
327 return !*err;
276 } 328 }
277 329
278 queue_.PeekAt(runs_.offset() + moof_head_, &buf, &size); 330 queue_.PeekAt(runs_.sample_offset() + moof_head_, &buf, &buf_size);
279 if (size < runs_.size()) return false; 331 if (buf_size < runs_.sample_size()) return false;
280 332
281 std::vector<uint8> frame_buf(buf, buf + runs_.size()); 333 scoped_ptr<DecryptConfig> decrypt_config;
334 if (runs_.is_encrypted())
335 decrypt_config = runs_.GetDecryptConfig();
336
337 std::vector<uint8> frame_buf(buf, buf + runs_.sample_size());
282 if (video) { 338 if (video) {
283 RCHECK(AVC::ConvertToAnnexB(size_of_nalu_length_, &frame_buf)); 339 std::vector<SubsampleEntry> subsamples;
284 if (runs_.is_keyframe()) { 340 if (decrypt_config.get())
285 const AVCDecoderConfigurationRecord* avc_config = NULL; 341 subsamples = decrypt_config->subsamples();
286 for (size_t t = 0; t < moov_->tracks.size(); t++) { 342 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.
287 if (moov_->tracks[t].header.track_id == runs_.track_id()) { 343 if (!subsamples.empty()) {
288 avc_config = &moov_->tracks[t].media.information. 344 decrypt_config.reset(new DecryptConfig(
289 sample_table.description.video_entries[0].avcc; 345 decrypt_config->key_id(),
290 break; 346 decrypt_config->key_id_size(),
291 } 347 decrypt_config->iv(),
292 } 348 subsamples));
293 RCHECK(avc_config != NULL);
294 RCHECK(AVC::InsertParameterSets(*avc_config, &frame_buf));
295 } 349 }
296 } 350 }
297 351
298 if (audio) { 352 if (audio) {
299 aac_.ConvertEsdsToADTS(&frame_buf); 353 aac_.ConvertEsdsToADTS(&frame_buf);
300 } 354 }
301 355
302 scoped_refptr<StreamParserBuffer> stream_buf = 356 scoped_refptr<StreamParserBuffer> stream_buf =
303 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(), 357 StreamParserBuffer::CopyFrom(&frame_buf[0], frame_buf.size(),
304 runs_.is_keyframe()); 358 runs_.is_keyframe());
305 359
360 if (runs_.is_encrypted())
361 stream_buf->SetDecryptConfig(decrypt_config.Pass());
362
306 stream_buf->SetDuration(runs_.duration()); 363 stream_buf->SetDuration(runs_.duration());
307 stream_buf->SetTimestamp(runs_.cts()); 364 stream_buf->SetTimestamp(runs_.cts());
308 stream_buf->SetDecodeTimestamp(runs_.dts()); 365 stream_buf->SetDecodeTimestamp(runs_.dts());
309 366
310 DVLOG(3) << "Pushing frame: aud=" << audio 367 DVLOG(3) << "Pushing frame: aud=" << audio
311 << ", key=" << runs_.is_keyframe() 368 << ", key=" << runs_.is_keyframe()
312 << ", dur=" << runs_.duration().InMilliseconds() 369 << ", dur=" << runs_.duration().InMilliseconds()
313 << ", dts=" << runs_.dts().InMilliseconds() 370 << ", dts=" << runs_.dts().InMilliseconds()
314 << ", cts=" << runs_.cts().InMilliseconds() 371 << ", 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
315 << ", size=" << runs_.size(); 372 << ", size=" << runs_.sample_size();
316 373
317 if (audio) { 374 if (audio) {
318 audio_buffers->push_back(stream_buf); 375 audio_buffers->push_back(stream_buf);
319 } else { 376 } else {
320 video_buffers->push_back(stream_buf); 377 video_buffers->push_back(stream_buf);
321 } 378 }
322 379
323 runs_.AdvanceSample(); 380 runs_.AdvanceSample();
324 return true; 381 return true;
325 } 382 }
326 383
327 bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers, 384 bool MP4StreamParser::SendAndFlushSamples(BufferQueue* audio_buffers,
328 BufferQueue* video_buffers) { 385 BufferQueue* video_buffers) {
329 if (!audio_buffers->empty()) { 386 if (!audio_buffers->empty()) {
330 if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers)) 387 if (audio_cb_.is_null() || !audio_cb_.Run(*audio_buffers))
331 return false; 388 return false;
332 audio_buffers->clear(); 389 audio_buffers->clear();
ddorwin 2012/07/17 01:14:21 Don't clear the buffers if there is no cb or it fa
strobe_ 2012/07/19 02:43:35 Fixed.
ddorwin 2012/07/19 06:11:10 Where?
strobe_ 2012/07/19 16:55:02 Apparently the fix was also lost in the failed reb
ddorwin 2012/07/24 01:00:09 I don't understand. Are you intentionally keeping
strobe_ 2012/07/25 01:05:13 Done.
333 } 390 }
334 if (!video_buffers->empty()) { 391 if (!video_buffers->empty()) {
335 if (video_cb_.is_null() || !video_cb_.Run(*video_buffers)) 392 if (video_cb_.is_null() || !video_cb_.Run(*video_buffers))
336 return false; 393 return false;
337 video_buffers->clear(); 394 video_buffers->clear();
338 } 395 }
339 return true; 396 return true;
340 } 397 }
341 398
342 bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) { 399 bool MP4StreamParser::ReadMDATsUntil(const int64 tgt_offset) {
(...skipping 21 matching lines...) Expand all
364 return true; 421 return true;
365 } 422 }
366 423
367 void MP4StreamParser::ChangeState(State new_state) { 424 void MP4StreamParser::ChangeState(State new_state) {
368 DVLOG(2) << "Changing state: " << new_state; 425 DVLOG(2) << "Changing state: " << new_state;
369 state_ = new_state; 426 state_ = new_state;
370 } 427 }
371 428
372 } // namespace mp4 429 } // namespace mp4
373 } // namespace media 430 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698