Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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/formats/mp4/mp4_stream_parser.h" | 5 #include "media/formats/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/time.h" | 10 #include "base/time/time.h" |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 70 | 70 |
| 71 void MP4StreamParser::Reset() { | 71 void MP4StreamParser::Reset() { |
| 72 queue_.Reset(); | 72 queue_.Reset(); |
| 73 runs_.reset(); | 73 runs_.reset(); |
| 74 moof_head_ = 0; | 74 moof_head_ = 0; |
| 75 mdat_tail_ = 0; | 75 mdat_tail_ = 0; |
| 76 } | 76 } |
| 77 | 77 |
| 78 void MP4StreamParser::Flush() { | 78 void MP4StreamParser::Flush() { |
| 79 DCHECK_NE(state_, kWaitingForInit); | 79 DCHECK_NE(state_, kWaitingForInit); |
| 80 Reset(); | 80 Reset(); |
|
wolenetz
2014/06/19 22:01:14
Before Reset(), we now have the possibility we're
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
As discussed offline, I think we should just drop
wolenetz
2014/06/20 00:17:29
I agree.
| |
| 81 ChangeState(kParsingBoxes); | 81 ChangeState(kParsingBoxes); |
| 82 } | 82 } |
| 83 | 83 |
| 84 bool MP4StreamParser::Parse(const uint8* buf, int size) { | 84 bool MP4StreamParser::Parse(const uint8* buf, int size) { |
| 85 DCHECK_NE(state_, kWaitingForInit); | 85 DCHECK_NE(state_, kWaitingForInit); |
| 86 | 86 |
| 87 if (state_ == kError) | 87 if (state_ == kError) |
| 88 return false; | 88 return false; |
| 89 | 89 |
| 90 queue_.Push(buf, size); | 90 queue_.Push(buf, size); |
| 91 | 91 |
| 92 BufferQueue audio_buffers; | 92 BufferQueue audio_buffers; |
| 93 BufferQueue video_buffers; | 93 BufferQueue video_buffers; |
| 94 | 94 |
| 95 bool result, err = false; | 95 bool result = false; |
| 96 bool err = false; | |
| 96 | 97 |
| 97 do { | 98 do { |
| 98 if (state_ == kParsingBoxes) { | 99 switch(state_) { |
|
wolenetz
2014/06/19 22:01:14
lint nit: insert space before (
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
Done.
| |
| 99 result = ParseBox(&err); | 100 case kWaitingForInit: |
| 100 } else { | 101 case kError: |
| 101 DCHECK_EQ(kEmittingSamples, state_); | 102 NOTREACHED(); |
| 102 result = EnqueueSample(&audio_buffers, &video_buffers, &err); | 103 return false; |
| 103 if (result) { | 104 |
|
wolenetz
2014/06/19 22:01:14
nit: add break; ? I'm not sure of code style here
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
I don't believe it is necessary if all paths in th
wolenetz
2014/06/20 00:17:29
Yeah, it was just a style question, not a correctn
| |
| 104 int64 max_clear = runs_->GetMaxClearOffset() + moof_head_; | 105 case kParsingBoxes: |
| 105 err = !ReadAndDiscardMDATsUntil(max_clear); | 106 result = ParseBox(&err); |
| 106 } | 107 break; |
| 108 | |
| 109 case kWaitingForSampleData: | |
| 110 result = HaveEnoughDataToEnqueueSamples(); | |
| 111 if (result) | |
| 112 ChangeState(kEmittingSamples); | |
| 113 break; | |
| 114 | |
| 115 case kEmittingSamples: | |
| 116 result = EnqueueSample(&audio_buffers, &video_buffers, &err); | |
| 117 if (result) { | |
| 118 int64 max_clear = runs_->GetMaxClearOffset() + moof_head_; | |
| 119 err = !ReadAndDiscardMDATsUntil(max_clear); | |
| 120 } | |
| 121 break; | |
| 107 } | 122 } |
| 108 } while (result && !err); | 123 } while (result && !err); |
| 109 | 124 |
| 110 if (!err) | 125 if (!err) |
| 111 err = !SendAndFlushSamples(&audio_buffers, &video_buffers); | 126 err = !SendAndFlushSamples(&audio_buffers, &video_buffers); |
| 112 | 127 |
| 113 if (err) { | 128 if (err) { |
| 114 DLOG(ERROR) << "Error while parsing MP4"; | 129 DLOG(ERROR) << "Error while parsing MP4"; |
| 115 moov_.reset(); | 130 moov_.reset(); |
| 116 Reset(); | 131 Reset(); |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 305 return true; | 320 return true; |
| 306 } | 321 } |
| 307 | 322 |
| 308 bool MP4StreamParser::ParseMoof(BoxReader* reader) { | 323 bool MP4StreamParser::ParseMoof(BoxReader* reader) { |
| 309 RCHECK(moov_.get()); // Must already have initialization segment | 324 RCHECK(moov_.get()); // Must already have initialization segment |
| 310 MovieFragment moof; | 325 MovieFragment moof; |
| 311 RCHECK(moof.Parse(reader)); | 326 RCHECK(moof.Parse(reader)); |
| 312 if (!runs_) | 327 if (!runs_) |
| 313 runs_.reset(new TrackRunIterator(moov_.get(), log_cb_)); | 328 runs_.reset(new TrackRunIterator(moov_.get(), log_cb_)); |
| 314 RCHECK(runs_->Init(moof)); | 329 RCHECK(runs_->Init(moof)); |
| 330 RCHECK(ComputeHighestEndOffset(moof)); | |
| 315 EmitNeedKeyIfNecessary(moof.pssh); | 331 EmitNeedKeyIfNecessary(moof.pssh); |
| 316 new_segment_cb_.Run(); | 332 new_segment_cb_.Run(); |
| 317 ChangeState(kEmittingSamples); | 333 ChangeState(kWaitingForSampleData); |
| 318 return true; | 334 return true; |
| 319 } | 335 } |
| 320 | 336 |
| 321 void MP4StreamParser::EmitNeedKeyIfNecessary( | 337 void MP4StreamParser::EmitNeedKeyIfNecessary( |
| 322 const std::vector<ProtectionSystemSpecificHeader>& headers) { | 338 const std::vector<ProtectionSystemSpecificHeader>& headers) { |
| 323 // TODO(strobe): ensure that the value of init_data (all PSSH headers | 339 // TODO(strobe): ensure that the value of init_data (all PSSH headers |
| 324 // concatenated in arbitrary order) matches the EME spec. | 340 // concatenated in arbitrary order) matches the EME spec. |
| 325 // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673. | 341 // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17673. |
| 326 if (headers.empty()) | 342 if (headers.empty()) |
| 327 return; | 343 return; |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 386 subsamples->push_back(entry); | 402 subsamples->push_back(entry); |
| 387 } else { | 403 } else { |
| 388 (*subsamples)[0].clear_bytes += kADTSHeaderMinSize; | 404 (*subsamples)[0].clear_bytes += kADTSHeaderMinSize; |
| 389 } | 405 } |
| 390 return true; | 406 return true; |
| 391 } | 407 } |
| 392 | 408 |
| 393 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, | 409 bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers, |
| 394 BufferQueue* video_buffers, | 410 BufferQueue* video_buffers, |
| 395 bool* err) { | 411 bool* err) { |
| 412 DCHECK_EQ(state_, kEmittingSamples); | |
| 413 | |
| 396 if (!runs_->IsRunValid()) { | 414 if (!runs_->IsRunValid()) { |
| 397 // Flush any buffers we've gotten in this chunk so that buffers don't | 415 // Flush any buffers we've gotten in this chunk so that buffers don't |
| 398 // cross NewSegment() calls | 416 // cross NewSegment() calls |
| 399 *err = !SendAndFlushSamples(audio_buffers, video_buffers); | 417 *err = !SendAndFlushSamples(audio_buffers, video_buffers); |
| 400 if (*err) | 418 if (*err) |
| 401 return false; | 419 return false; |
| 402 | 420 |
| 403 // Remain in kEnqueueingSamples state, discarding data, until the end of | 421 // Remain in kEnqueueingSamples state, discarding data, until the end of |
|
wolenetz
2014/06/19 22:01:14
nit: seems a good time to fix this comment: s/kEnq
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
Done.
| |
| 404 // the current 'mdat' box has been appended to the queue. | 422 // the current 'mdat' box has been appended to the queue. |
| 405 if (!queue_.Trim(mdat_tail_)) | 423 if (!queue_.Trim(mdat_tail_)) |
| 406 return false; | 424 return false; |
| 407 | 425 |
| 408 ChangeState(kParsingBoxes); | 426 ChangeState(kParsingBoxes); |
| 409 end_of_segment_cb_.Run(); | 427 end_of_segment_cb_.Run(); |
| 410 return true; | 428 return true; |
| 411 } | 429 } |
| 412 | 430 |
| 413 if (!runs_->IsSampleValid()) { | 431 if (!runs_->IsSampleValid()) { |
| 414 runs_->AdvanceRun(); | 432 runs_->AdvanceRun(); |
| 415 return true; | 433 return true; |
| 416 } | 434 } |
| 417 | 435 |
| 418 DCHECK(!(*err)); | 436 DCHECK(!(*err)); |
| 419 | 437 |
| 420 const uint8* buf; | 438 const uint8* buf; |
| 421 int buf_size; | 439 int buf_size; |
| 422 queue_.Peek(&buf, &buf_size); | 440 queue_.Peek(&buf, &buf_size); |
| 423 if (!buf_size) return false; | 441 if (!buf_size) return false; |
| 424 | 442 |
| 425 bool audio = has_audio_ && audio_track_id_ == runs_->track_id(); | 443 bool audio = has_audio_ && audio_track_id_ == runs_->track_id(); |
| 426 bool video = has_video_ && video_track_id_ == runs_->track_id(); | 444 bool video = has_video_ && video_track_id_ == runs_->track_id(); |
| 427 | 445 |
| 428 // Skip this entire track if it's not one we're interested in | 446 // Skip this entire track if it's not one we're interested in |
| 429 if (!audio && !video) | 447 if (!audio && !video) |
| 430 runs_->AdvanceRun(); | 448 runs_->AdvanceRun(); |
|
wolenetz
2014/06/19 22:01:14
I'm confused. What if there isn't another run here
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
Yeah. I saw this too. I was planning on fixing tha
wolenetz
2014/06/20 00:17:29
nit: (sgtm) Maybe add a TODO?
| |
| 431 | 449 |
| 432 // Attempt to cache the auxiliary information first. Aux info is usually | 450 // Attempt to cache the auxiliary information first. Aux info is usually |
| 433 // placed in a contiguous block before the sample data, rather than being | 451 // placed in a contiguous block before the sample data, rather than being |
| 434 // interleaved. If we didn't cache it, this would require that we retain the | 452 // interleaved. If we didn't cache it, this would require that we retain the |
| 435 // start of the segment buffer while reading samples. Aux info is typically | 453 // start of the segment buffer while reading samples. Aux info is typically |
| 436 // quite small compared to sample data, so this pattern is useful on | 454 // quite small compared to sample data, so this pattern is useful on |
| 437 // memory-constrained devices where the source buffer consumes a substantial | 455 // memory-constrained devices where the source buffer consumes a substantial |
| 438 // portion of the total system memory. | 456 // portion of the total system memory. |
| 439 if (runs_->AuxInfoNeedsToBeCached()) { | 457 if (runs_->AuxInfoNeedsToBeCached()) { |
| 440 queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size); | 458 queue_.PeekAt(runs_->aux_info_offset() + moof_head_, &buf, &buf_size); |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 570 } | 588 } |
| 571 queue_.Trim(std::min(mdat_tail_, offset)); | 589 queue_.Trim(std::min(mdat_tail_, offset)); |
| 572 return !err; | 590 return !err; |
| 573 } | 591 } |
| 574 | 592 |
| 575 void MP4StreamParser::ChangeState(State new_state) { | 593 void MP4StreamParser::ChangeState(State new_state) { |
| 576 DVLOG(2) << "Changing state: " << new_state; | 594 DVLOG(2) << "Changing state: " << new_state; |
| 577 state_ = new_state; | 595 state_ = new_state; |
| 578 } | 596 } |
| 579 | 597 |
| 598 bool MP4StreamParser::HaveEnoughDataToEnqueueSamples() { | |
| 599 DCHECK_EQ(state_, kWaitingForSampleData); | |
| 600 // For muxed content, make sure we have data up to |highest_end_offset_| | |
| 601 // so we can ensure proper equeuing behavior. Otherwise assume we have enough | |
|
wolenetz
2014/06/19 22:01:15
nit: s/eq/enq
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:46
Done.
| |
| 602 // data and allow per sample offset checks to meter sample enqueuing. | |
| 603 // TODO(acolwell): Fix trun box handling so we don't have to special case | |
| 604 // muxed content. | |
| 605 return !(has_audio_ && has_video_ && | |
| 606 queue_.tail() < highest_end_offset_ + moof_head_); | |
| 607 } | |
| 608 | |
| 609 bool MP4StreamParser::ComputeHighestEndOffset(const MovieFragment& moof) { | |
| 610 highest_end_offset_ = 0; | |
|
wolenetz
2014/06/19 22:01:14
nit: Can we safely short-cut and return true after
acolwell GONE FROM CHROMIUM
2014/06/19 23:46:47
I suppose, but that would then mean that highest_e
| |
| 611 | |
| 612 TrackRunIterator runs(moov_.get(), log_cb_); | |
| 613 RCHECK(runs.Init(moof)); | |
| 614 | |
| 615 while (runs.IsRunValid()) { | |
| 616 int64 aux_info_end_offset = runs.aux_info_offset() + runs.aux_info_size(); | |
| 617 if (aux_info_end_offset > highest_end_offset_) | |
| 618 highest_end_offset_ = aux_info_end_offset; | |
| 619 | |
| 620 while (runs.IsSampleValid()) { | |
| 621 int64 sample_end_offset = runs.sample_offset() + runs.sample_size(); | |
| 622 if (sample_end_offset > highest_end_offset_) | |
| 623 highest_end_offset_ = sample_end_offset; | |
| 624 | |
| 625 runs.AdvanceSample(); | |
| 626 } | |
| 627 runs.AdvanceRun(); | |
| 628 } | |
| 629 | |
| 630 return true; | |
| 631 } | |
| 632 | |
| 580 } // namespace mp4 | 633 } // namespace mp4 |
| 581 } // namespace media | 634 } // namespace media |
| OLD | NEW |