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/webm/webm_cluster_parser.h" | 5 #include "media/formats/webm/webm_cluster_parser.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/sys_byteorder.h" | 10 #include "base/sys_byteorder.h" |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 35 parser_(kWebMIdCluster, this), | 35 parser_(kWebMIdCluster, this), |
| 36 last_block_timecode_(-1), | 36 last_block_timecode_(-1), |
| 37 block_data_size_(-1), | 37 block_data_size_(-1), |
| 38 block_duration_(-1), | 38 block_duration_(-1), |
| 39 block_add_id_(-1), | 39 block_add_id_(-1), |
| 40 block_additional_data_size_(-1), | 40 block_additional_data_size_(-1), |
| 41 discard_padding_(-1), | 41 discard_padding_(-1), |
| 42 cluster_timecode_(-1), | 42 cluster_timecode_(-1), |
| 43 cluster_start_time_(kNoTimestamp()), | 43 cluster_start_time_(kNoTimestamp()), |
| 44 cluster_ended_(false), | 44 cluster_ended_(false), |
| 45 audio_(audio_track_num, false, audio_default_duration), | 45 audio_(audio_track_num, false, audio_default_duration, log_cb), |
| 46 video_(video_track_num, true, video_default_duration), | 46 video_(video_track_num, true, video_default_duration, log_cb), |
| 47 ready_buffer_upper_bound_(kNoTimestamp()), | |
| 47 log_cb_(log_cb) { | 48 log_cb_(log_cb) { |
| 48 for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin(); | 49 for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin(); |
| 49 it != text_tracks.end(); | 50 it != text_tracks.end(); |
| 50 ++it) { | 51 ++it) { |
| 51 text_track_map_.insert(std::make_pair( | 52 text_track_map_.insert(std::make_pair( |
| 52 it->first, Track(it->first, false, kNoTimestamp()))); | 53 it->first, Track(it->first, false, kNoTimestamp(), log_cb_))); |
| 53 } | 54 } |
| 54 } | 55 } |
| 55 | 56 |
| 56 WebMClusterParser::~WebMClusterParser() {} | 57 WebMClusterParser::~WebMClusterParser() {} |
| 57 | 58 |
| 58 void WebMClusterParser::Reset() { | 59 void WebMClusterParser::Reset() { |
| 59 last_block_timecode_ = -1; | 60 last_block_timecode_ = -1; |
| 60 cluster_timecode_ = -1; | 61 cluster_timecode_ = -1; |
| 61 cluster_start_time_ = kNoTimestamp(); | 62 cluster_start_time_ = kNoTimestamp(); |
| 62 cluster_ended_ = false; | 63 cluster_ended_ = false; |
| 63 parser_.Reset(); | 64 parser_.Reset(); |
| 64 audio_.Reset(); | 65 audio_.Reset(); |
| 65 video_.Reset(); | 66 video_.Reset(); |
| 66 ResetTextTracks(); | 67 ResetTextTracks(); |
| 68 ready_buffer_upper_bound_ = kNoTimestamp(); | |
| 67 } | 69 } |
| 68 | 70 |
| 69 int WebMClusterParser::Parse(const uint8* buf, int size) { | 71 int WebMClusterParser::Parse(const uint8* buf, int size) { |
| 70 audio_.ClearBuffersButKeepLastIfMissingDuration(); | 72 audio_.ClearReadyBuffers(); |
| 71 video_.ClearBuffersButKeepLastIfMissingDuration(); | 73 video_.ClearReadyBuffers(); |
| 72 ResetTextTracks(); | 74 ClearTextTrackReadyBuffers(); |
| 75 ready_buffer_upper_bound_ = kNoTimestamp(); | |
| 73 | 76 |
| 74 int result = parser_.Parse(buf, size); | 77 int result = parser_.Parse(buf, size); |
| 75 | 78 |
| 76 if (result < 0) { | 79 if (result < 0) { |
| 77 cluster_ended_ = false; | 80 cluster_ended_ = false; |
| 78 return result; | 81 return result; |
| 79 } | 82 } |
| 80 | 83 |
| 81 cluster_ended_ = parser_.IsParsingComplete(); | 84 cluster_ended_ = parser_.IsParsingComplete(); |
| 82 if (cluster_ended_) { | 85 if (cluster_ended_) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 98 parser_.Reset(); | 101 parser_.Reset(); |
| 99 | 102 |
| 100 last_block_timecode_ = -1; | 103 last_block_timecode_ = -1; |
| 101 cluster_timecode_ = -1; | 104 cluster_timecode_ = -1; |
| 102 } | 105 } |
| 103 | 106 |
| 104 return result; | 107 return result; |
| 105 } | 108 } |
| 106 | 109 |
| 107 const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() { | 110 const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() { |
| 108 if (cluster_ended_) | 111 if (ready_buffer_upper_bound_ == kNoTimestamp()) |
| 109 audio_.ApplyDurationEstimateIfNeeded(); | 112 UpdateReadyBuffers(); |
| 110 return audio_.buffers(); | 113 |
| 114 return audio_.ready_buffers(); | |
| 111 } | 115 } |
| 112 | 116 |
| 113 const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() { | 117 const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() { |
| 114 if (cluster_ended_) | 118 if (ready_buffer_upper_bound_ == kNoTimestamp()) |
| 115 video_.ApplyDurationEstimateIfNeeded(); | 119 UpdateReadyBuffers(); |
| 116 return video_.buffers(); | 120 |
| 121 return video_.ready_buffers(); | |
| 117 } | 122 } |
| 118 | 123 |
| 119 const WebMClusterParser::TextBufferQueueMap& | 124 const WebMClusterParser::TextBufferQueueMap& |
| 120 WebMClusterParser::GetTextBuffers() { | 125 WebMClusterParser::GetTextBuffers() { |
| 121 // Translate our |text_track_map_| into |text_buffers_map_|, inserting rows in | 126 if (ready_buffer_upper_bound_ == kNoTimestamp()) |
| 122 // the output only for non-empty text buffer queues in |text_track_map_|. | 127 UpdateReadyBuffers(); |
| 123 text_buffers_map_.clear(); | |
|
acolwell GONE FROM CHROMIUM
2014/04/23 22:36:39
Why move this code? It seems like this is more con
wolenetz
2014/04/25 20:04:21
This was just an optimization to iterate through t
| |
| 124 for (TextTrackMap::const_iterator itr = text_track_map_.begin(); | |
| 125 itr != text_track_map_.end(); | |
| 126 ++itr) { | |
| 127 // Per OnBlock(), all text buffers should already have valid durations, so | |
| 128 // there is no need to call | |
| 129 // itr->second.ApplyDurationEstimateIfNeeded() here. | |
| 130 const BufferQueue& text_buffers = itr->second.buffers(); | |
| 131 if (!text_buffers.empty()) | |
| 132 text_buffers_map_.insert(std::make_pair(itr->first, text_buffers)); | |
| 133 } | |
| 134 | 128 |
| 135 return text_buffers_map_; | 129 return text_buffers_map_; |
| 136 } | 130 } |
| 137 | 131 |
| 138 WebMParserClient* WebMClusterParser::OnListStart(int id) { | 132 WebMParserClient* WebMClusterParser::OnListStart(int id) { |
| 139 if (id == kWebMIdCluster) { | 133 if (id == kWebMIdCluster) { |
| 140 cluster_timecode_ = -1; | 134 cluster_timecode_ = -1; |
| 141 cluster_start_time_ = kNoTimestamp(); | 135 cluster_start_time_ = kNoTimestamp(); |
| 142 } else if (id == kWebMIdBlockGroup) { | 136 } else if (id == kWebMIdBlockGroup) { |
| 143 block_data_.reset(); | 137 block_data_.reset(); |
| (...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 406 } | 400 } |
| 407 | 401 |
| 408 if (discard_padding != 0) { | 402 if (discard_padding != 0) { |
| 409 buffer->set_discard_padding(base::TimeDelta::FromMicroseconds( | 403 buffer->set_discard_padding(base::TimeDelta::FromMicroseconds( |
| 410 discard_padding / 1000)); | 404 discard_padding / 1000)); |
| 411 } | 405 } |
| 412 | 406 |
| 413 return track->AddBuffer(buffer); | 407 return track->AddBuffer(buffer); |
| 414 } | 408 } |
| 415 | 409 |
| 416 WebMClusterParser::Track::Track(int track_num, bool is_video, | 410 WebMClusterParser::Track::Track(int track_num, |
| 417 base::TimeDelta default_duration) | 411 bool is_video, |
| 412 base::TimeDelta default_duration, | |
| 413 const LogCB& log_cb) | |
| 418 : track_num_(track_num), | 414 : track_num_(track_num), |
| 419 is_video_(is_video), | 415 is_video_(is_video), |
| 420 default_duration_(default_duration), | 416 default_duration_(default_duration), |
| 421 estimated_next_frame_duration_(kNoTimestamp()) { | 417 estimated_next_frame_duration_(kNoTimestamp()), |
| 418 log_cb_(log_cb) { | |
| 422 DCHECK(default_duration_ == kNoTimestamp() || | 419 DCHECK(default_duration_ == kNoTimestamp() || |
| 423 default_duration_ > base::TimeDelta()); | 420 default_duration_ > base::TimeDelta()); |
| 424 } | 421 } |
| 425 | 422 |
| 426 WebMClusterParser::Track::~Track() {} | 423 WebMClusterParser::Track::~Track() {} |
| 427 | 424 |
| 425 base::TimeDelta WebMClusterParser::Track::GetReadyUpperBound() { | |
| 426 DCHECK(ready_buffers_.empty()); | |
| 427 if (last_added_buffer_missing_duration_) | |
| 428 return last_added_buffer_missing_duration_->GetDecodeTimestamp(); | |
| 429 | |
| 430 return kInfiniteDuration(); | |
| 431 } | |
| 432 | |
| 433 const WebMClusterParser::BufferQueue& | |
| 434 WebMClusterParser::Track::ExtractReadyBuffers( | |
| 435 const base::TimeDelta before_timestamp) { | |
| 436 DCHECK(ready_buffers_.empty()); | |
| 437 DCHECK(base::TimeDelta() <= before_timestamp); | |
| 438 DCHECK(kNoTimestamp() != before_timestamp); | |
| 439 | |
| 440 if (!buffers_.empty()) { | |
|
acolwell GONE FROM CHROMIUM
2014/04/23 22:36:39
nit: reverse condition and early return.
wolenetz
2014/04/25 20:04:21
Done.
| |
| 441 if (buffers_.back()->GetDecodeTimestamp() < before_timestamp) { | |
| 442 // All of |buffers_| are ready. | |
| 443 ready_buffers_.swap(buffers_); | |
| 444 DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " All " | |
| 445 << ready_buffers_.size() << " are ready: before upper bound ts " | |
| 446 << before_timestamp.InSecondsF(); | |
|
acolwell GONE FROM CHROMIUM
2014/04/23 22:36:39
nit:early return.
wolenetz
2014/04/25 20:04:21
Done.
| |
| 447 } else { | |
| 448 // Not all of |buffers_| are ready yet. Move any that are ready to | |
| 449 // |ready_buffers_|. | |
| 450 while (true) { | |
| 451 const scoped_refptr<StreamParserBuffer>& buffer = buffers_.front(); | |
| 452 if (buffer->GetDecodeTimestamp() >= before_timestamp) | |
| 453 break; | |
| 454 ready_buffers_.push_back(buffer); | |
| 455 buffers_.pop_front(); | |
| 456 DCHECK(!buffers_.empty()); | |
| 457 } | |
| 458 DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " Only " | |
| 459 << ready_buffers_.size() << " are ready, " << buffers_.size() | |
| 460 << " are at or after upper bound ts " | |
| 461 << before_timestamp.InSecondsF(); | |
| 462 } | |
| 463 } | |
| 464 | |
| 465 return ready_buffers_; | |
|
acolwell GONE FROM CHROMIUM
2014/04/23 22:36:39
nit: looks like this is valuable at only one call
wolenetz
2014/04/25 20:04:21
Done.
| |
| 466 } | |
| 467 | |
| 428 bool WebMClusterParser::Track::AddBuffer( | 468 bool WebMClusterParser::Track::AddBuffer( |
| 429 const scoped_refptr<StreamParserBuffer>& buffer) { | 469 const scoped_refptr<StreamParserBuffer>& buffer) { |
| 430 DVLOG(2) << "AddBuffer() : " << track_num_ | 470 DVLOG(2) << "AddBuffer() : " << track_num_ |
| 431 << " ts " << buffer->timestamp().InSecondsF() | 471 << " ts " << buffer->timestamp().InSecondsF() |
| 432 << " dur " << buffer->duration().InSecondsF() | 472 << " dur " << buffer->duration().InSecondsF() |
| 433 << " kf " << buffer->IsKeyframe() | 473 << " kf " << buffer->IsKeyframe() |
| 434 << " size " << buffer->data_size(); | 474 << " size " << buffer->data_size(); |
| 435 | 475 |
| 436 if (last_added_buffer_missing_duration_) { | 476 if (last_added_buffer_missing_duration_) { |
| 437 base::TimeDelta derived_duration = | 477 base::TimeDelta derived_duration = |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 474 << last_added_buffer_missing_duration_->duration().InSecondsF() | 514 << last_added_buffer_missing_duration_->duration().InSecondsF() |
| 475 << " kf " << last_added_buffer_missing_duration_->IsKeyframe() | 515 << " kf " << last_added_buffer_missing_duration_->IsKeyframe() |
| 476 << " size " << last_added_buffer_missing_duration_->data_size(); | 516 << " size " << last_added_buffer_missing_duration_->data_size(); |
| 477 | 517 |
| 478 // Don't use the applied duration as a future estimation (don't use | 518 // Don't use the applied duration as a future estimation (don't use |
| 479 // QueueBuffer() here.) | 519 // QueueBuffer() here.) |
| 480 buffers_.push_back(last_added_buffer_missing_duration_); | 520 buffers_.push_back(last_added_buffer_missing_duration_); |
| 481 last_added_buffer_missing_duration_ = NULL; | 521 last_added_buffer_missing_duration_ = NULL; |
| 482 } | 522 } |
| 483 | 523 |
| 484 void WebMClusterParser::Track::ClearBuffersButKeepLastIfMissingDuration() { | 524 void WebMClusterParser::Track::ClearReadyBuffers() { |
| 485 // Note that |estimated_next_frame_duration_| is not reset, so it can be | 525 // Note that |buffers_| are kept and |estimated_next_frame_duration_| is not |
| 486 // reused on subsequent buffers added to this instance. | 526 // reset here. |
| 487 buffers_.clear(); | 527 ready_buffers_.clear(); |
| 488 } | 528 } |
| 489 | 529 |
| 490 void WebMClusterParser::Track::Reset() { | 530 void WebMClusterParser::Track::Reset() { |
| 491 ClearBuffersButKeepLastIfMissingDuration(); | 531 ClearReadyBuffers(); |
| 532 buffers_.clear(); | |
| 492 last_added_buffer_missing_duration_ = NULL; | 533 last_added_buffer_missing_duration_ = NULL; |
| 493 } | 534 } |
| 494 | 535 |
| 495 bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const { | 536 bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const { |
| 496 // For now, assume that all blocks are keyframes for datatypes other than | 537 // For now, assume that all blocks are keyframes for datatypes other than |
| 497 // video. This is a valid assumption for Vorbis, WebVTT, & Opus. | 538 // video. This is a valid assumption for Vorbis, WebVTT, & Opus. |
| 498 if (!is_video_) | 539 if (!is_video_) |
| 499 return true; | 540 return true; |
| 500 | 541 |
| 501 // Make sure the block is big enough for the minimal keyframe header size. | 542 // Make sure the block is big enough for the minimal keyframe header size. |
| 502 if (size < 7) | 543 if (size < 7) |
| 503 return false; | 544 return false; |
| 504 | 545 |
| 505 // The LSb of the first byte must be a 0 for a keyframe. | 546 // The LSb of the first byte must be a 0 for a keyframe. |
| 506 // http://tools.ietf.org/html/rfc6386 Section 19.1 | 547 // http://tools.ietf.org/html/rfc6386 Section 19.1 |
| 507 if ((data[0] & 0x01) != 0) | 548 if ((data[0] & 0x01) != 0) |
| 508 return false; | 549 return false; |
| 509 | 550 |
| 510 // Verify VP8 keyframe startcode. | 551 // Verify VP8 keyframe startcode. |
| 511 // http://tools.ietf.org/html/rfc6386 Section 19.1 | 552 // http://tools.ietf.org/html/rfc6386 Section 19.1 |
| 512 if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a) | 553 if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a) |
| 513 return false; | 554 return false; |
| 514 | 555 |
| 515 return true; | 556 return true; |
| 516 } | 557 } |
| 517 | 558 |
| 518 bool WebMClusterParser::Track::QueueBuffer( | 559 bool WebMClusterParser::Track::QueueBuffer( |
| 519 const scoped_refptr<StreamParserBuffer>& buffer) { | 560 const scoped_refptr<StreamParserBuffer>& buffer) { |
| 520 DCHECK(!last_added_buffer_missing_duration_); | 561 DCHECK(!last_added_buffer_missing_duration_); |
| 562 | |
| 563 // WebMClusterParser::OnBlock() gives MEDIA_LOG and parse error on decreasing | |
| 564 // block timecode detection within a cluster. Therefore, we should not see | |
| 565 // those here. | |
| 566 base::TimeDelta previous_buffers_timestamp = buffers_.empty() ? | |
| 567 base::TimeDelta() : buffers_.back()->GetDecodeTimestamp(); | |
| 568 CHECK(previous_buffers_timestamp <= buffer->GetDecodeTimestamp()); | |
| 569 | |
| 521 base::TimeDelta duration = buffer->duration(); | 570 base::TimeDelta duration = buffer->duration(); |
| 522 if (duration < base::TimeDelta() || duration == kNoTimestamp()) { | 571 if (duration < base::TimeDelta() || duration == kNoTimestamp()) { |
| 523 DVLOG(2) << "QueueBuffer() : Invalid buffer duration: " | 572 MEDIA_LOG(log_cb_) << "Invalid buffer duration: " << duration.InSecondsF(); |
| 524 << duration.InSecondsF(); | |
| 525 return false; | 573 return false; |
| 526 } | 574 } |
| 527 | 575 |
| 528 // The estimated frame duration is the minimum non-zero duration since the | 576 // The estimated frame duration is the minimum non-zero duration since the |
| 529 // last initialization segment. The minimum is used to ensure frame durations | 577 // last initialization segment. The minimum is used to ensure frame durations |
| 530 // aren't overestimated. | 578 // aren't overestimated. |
| 531 if (duration > base::TimeDelta()) { | 579 if (duration > base::TimeDelta()) { |
| 532 if (estimated_next_frame_duration_ == kNoTimestamp()) { | 580 if (estimated_next_frame_duration_ == kNoTimestamp()) { |
| 533 estimated_next_frame_duration_ = duration; | 581 estimated_next_frame_duration_ = duration; |
| 534 } else { | 582 } else { |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 554 duration = base::TimeDelta::FromMilliseconds( | 602 duration = base::TimeDelta::FromMilliseconds( |
| 555 kDefaultAudioBufferDurationInMs); | 603 kDefaultAudioBufferDurationInMs); |
| 556 } | 604 } |
| 557 } | 605 } |
| 558 | 606 |
| 559 DCHECK(duration > base::TimeDelta()); | 607 DCHECK(duration > base::TimeDelta()); |
| 560 DCHECK(duration != kNoTimestamp()); | 608 DCHECK(duration != kNoTimestamp()); |
| 561 return duration; | 609 return duration; |
| 562 } | 610 } |
| 563 | 611 |
| 612 void WebMClusterParser::ClearTextTrackReadyBuffers() { | |
| 613 text_buffers_map_.clear(); | |
| 614 for (TextTrackMap::iterator it = text_track_map_.begin(); | |
| 615 it != text_track_map_.end(); | |
| 616 ++it) { | |
| 617 it->second.ClearReadyBuffers(); | |
| 618 } | |
| 619 } | |
| 620 | |
| 564 void WebMClusterParser::ResetTextTracks() { | 621 void WebMClusterParser::ResetTextTracks() { |
| 565 text_buffers_map_.clear(); | 622 ClearTextTrackReadyBuffers(); |
| 566 for (TextTrackMap::iterator it = text_track_map_.begin(); | 623 for (TextTrackMap::iterator it = text_track_map_.begin(); |
| 567 it != text_track_map_.end(); | 624 it != text_track_map_.end(); |
| 568 ++it) { | 625 ++it) { |
| 569 it->second.Reset(); | 626 it->second.Reset(); |
| 570 } | 627 } |
| 571 } | 628 } |
| 572 | 629 |
| 630 void WebMClusterParser::UpdateReadyBuffers() { | |
| 631 DCHECK(ready_buffer_upper_bound_ == kNoTimestamp()); | |
| 632 DCHECK(text_buffers_map_.empty()); | |
| 633 | |
| 634 if (cluster_ended_) { | |
| 635 audio_.ApplyDurationEstimateIfNeeded(); | |
| 636 video_.ApplyDurationEstimateIfNeeded(); | |
| 637 // Per OnBlock(), all text buffers should already have valid durations, so | |
| 638 // there is no need to call ApplyDurationEstimateIfNeeded() on text tracks | |
| 639 // here. | |
| 640 ready_buffer_upper_bound_ = kInfiniteDuration(); | |
| 641 DCHECK(ready_buffer_upper_bound_ == audio_.GetReadyUpperBound()); | |
| 642 DCHECK(ready_buffer_upper_bound_ == video_.GetReadyUpperBound()); | |
| 643 } else { | |
| 644 ready_buffer_upper_bound_ = std::min(audio_.GetReadyUpperBound(), | |
| 645 video_.GetReadyUpperBound()); | |
| 646 DCHECK(base::TimeDelta() <= ready_buffer_upper_bound_); | |
| 647 DCHECK(kNoTimestamp() != ready_buffer_upper_bound_); | |
| 648 } | |
| 649 | |
| 650 // Prepare each track's ready buffers for retrieval. | |
| 651 audio_.ExtractReadyBuffers(ready_buffer_upper_bound_); | |
| 652 video_.ExtractReadyBuffers(ready_buffer_upper_bound_); | |
| 653 for (TextTrackMap::iterator itr = text_track_map_.begin(); | |
| 654 itr != text_track_map_.end(); | |
| 655 ++itr) { | |
| 656 const BufferQueue& text_buffers = itr->second.ExtractReadyBuffers( | |
| 657 ready_buffer_upper_bound_); | |
| 658 if (!text_buffers.empty()) | |
| 659 text_buffers_map_.insert(std::make_pair(itr->first, text_buffers)); | |
|
acolwell GONE FROM CHROMIUM
2014/04/23 22:36:39
Why are these staged here? I would expect these to
wolenetz
2014/04/25 20:04:21
Done (see my reply to GetTextBuffers() comment, ab
| |
| 660 } | |
| 661 } | |
| 662 | |
| 573 WebMClusterParser::Track* | 663 WebMClusterParser::Track* |
| 574 WebMClusterParser::FindTextTrack(int track_num) { | 664 WebMClusterParser::FindTextTrack(int track_num) { |
| 575 const TextTrackMap::iterator it = text_track_map_.find(track_num); | 665 const TextTrackMap::iterator it = text_track_map_.find(track_num); |
| 576 | 666 |
| 577 if (it == text_track_map_.end()) | 667 if (it == text_track_map_.end()) |
| 578 return NULL; | 668 return NULL; |
| 579 | 669 |
| 580 return &it->second; | 670 return &it->second; |
| 581 } | 671 } |
| 582 | 672 |
| 583 } // namespace media | 673 } // namespace media |
| OLD | NEW |