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

Side by Side Diff: media/filters/chunk_demuxer.cc

Issue 10558011: Fix ChunkDemuxer so it properly outputs buffered ranges. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Changed everything to use media::Ranges. Created 8 years, 6 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 | Annotate | Revision Log
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/filters/chunk_demuxer.h" 5 #include "media/filters/chunk_demuxer.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/message_loop.h" 9 #include "base/message_loop.h"
10 #include "base/string_util.h" 10 #include "base/string_util.h"
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 return new mp4::MP4StreamParser(); 66 return new mp4::MP4StreamParser();
67 } 67 }
68 68
69 static const SupportedTypeInfo kSupportedTypeInfo[] = { 69 static const SupportedTypeInfo kSupportedTypeInfo[] = {
70 { "video/webm", &BuildWebMParser, kVideoWebMCodecs }, 70 { "video/webm", &BuildWebMParser, kVideoWebMCodecs },
71 { "audio/webm", &BuildWebMParser, kAudioWebMCodecs }, 71 { "audio/webm", &BuildWebMParser, kAudioWebMCodecs },
72 { "video/mp4", &BuildMP4Parser, kVideoMP4Codecs }, 72 { "video/mp4", &BuildMP4Parser, kVideoMP4Codecs },
73 { "audio/mp4", &BuildMP4Parser, kAudioMP4Codecs }, 73 { "audio/mp4", &BuildMP4Parser, kAudioMP4Codecs },
74 }; 74 };
75 75
76
77 // The fake total size we use for converting times to bytes
78 // for AddBufferedByteRange() calls.
79 enum { kFakeTotalBytes = 1000000 };
Ami GONE FROM CHROMIUM 2012/06/19 17:40:37 TODO to drop this in favor of teaching Pipeline to
acolwell GONE FROM CHROMIUM 2012/06/19 19:50:15 Done.
80
76 // Checks to see if the specified |type| and |codecs| list are supported. 81 // Checks to see if the specified |type| and |codecs| list are supported.
77 // Returns true if |type| and all codecs listed in |codecs| are supported. 82 // Returns true if |type| and all codecs listed in |codecs| are supported.
78 // |factory_function| contains a function that can build a StreamParser 83 // |factory_function| contains a function that can build a StreamParser
79 // for this type. 84 // for this type.
80 // |has_audio| is true if an audio codec was specified. 85 // |has_audio| is true if an audio codec was specified.
81 // |has_video| is true if a video codec was specified. 86 // |has_video| is true if a video codec was specified.
82 // Returns false otherwise. The values of |factory_function|, |has_audio|, 87 // Returns false otherwise. The values of |factory_function|, |has_audio|,
83 // and |has_video| are undefined. 88 // and |has_video| are undefined.
84 static bool IsSupported(const std::string& type, 89 static bool IsSupported(const std::string& type,
85 std::vector<std::string>& codecs, 90 std::vector<std::string>& codecs,
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 class ChunkDemuxerStream : public DemuxerStream { 145 class ChunkDemuxerStream : public DemuxerStream {
141 public: 146 public:
142 typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue; 147 typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
143 typedef std::deque<ReadCB> ReadCBQueue; 148 typedef std::deque<ReadCB> ReadCBQueue;
144 typedef std::deque<base::Closure> ClosureQueue; 149 typedef std::deque<base::Closure> ClosureQueue;
145 150
146 explicit ChunkDemuxerStream(const AudioDecoderConfig& audio_config); 151 explicit ChunkDemuxerStream(const AudioDecoderConfig& audio_config);
147 explicit ChunkDemuxerStream(const VideoDecoderConfig& video_config); 152 explicit ChunkDemuxerStream(const VideoDecoderConfig& video_config);
148 153
149 void StartWaitingForSeek(); 154 void StartWaitingForSeek();
150 void Seek(base::TimeDelta time); 155 void Seek(base::TimeDelta time);
Ami GONE FROM CHROMIUM 2012/06/19 17:40:37 At 21 occurrences in this file, I'd say base::Time
acolwell GONE FROM CHROMIUM 2012/06/19 19:50:15 Done.
151 bool IsSeekPending() const; 156 bool IsSeekPending() const;
152 void Flush(); 157 void Flush();
153 158
154 // Add buffers to this stream. Buffers are stored in SourceBufferStreams, 159 // Add buffers to this stream. Buffers are stored in SourceBufferStreams,
155 // which handle ordering and overlap resolution. 160 // which handle ordering and overlap resolution.
156 // Returns true if buffers were successfully added. 161 // Returns true if buffers were successfully added.
157 bool Append(const StreamParser::BufferQueue& buffers); 162 bool Append(const StreamParser::BufferQueue& buffers);
158 163
159 // Returns a list of the buffered time ranges. 164 // Returns a list of the buffered time ranges.
160 SourceBufferStream::TimespanList GetBufferedTime() const; 165 Ranges<base::TimeDelta> GetBufferedTime() const;
161 166
162 // Signal to the stream that buffers handed in through subsequent calls to 167 // Signal to the stream that buffers handed in through subsequent calls to
163 // Append() belong to a media segment that starts at |start_timestamp|. 168 // Append() belong to a media segment that starts at |start_timestamp|.
164 void OnNewMediaSegment(base::TimeDelta start_timestamp); 169 void OnNewMediaSegment(base::TimeDelta start_timestamp);
165 170
166 // Called when mid-stream config updates occur. 171 // Called when mid-stream config updates occur.
167 // Returns true if the new config is accepted. 172 // Returns true if the new config is accepted.
168 // Returns false if the new config should trigger an error. 173 // Returns false if the new config should trigger an error.
169 bool UpdateAudioConfig(const AudioDecoderConfig& config); 174 bool UpdateAudioConfig(const AudioDecoderConfig& config);
170 bool UpdateVideoConfig(const VideoDecoderConfig& config); 175 bool UpdateVideoConfig(const VideoDecoderConfig& config);
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
283 } 288 }
284 CreateReadDoneClosures_Locked(&closures); 289 CreateReadDoneClosures_Locked(&closures);
285 } 290 }
286 291
287 for (ClosureQueue::iterator it = closures.begin(); it != closures.end(); ++it) 292 for (ClosureQueue::iterator it = closures.begin(); it != closures.end(); ++it)
288 it->Run(); 293 it->Run();
289 294
290 return true; 295 return true;
291 } 296 }
292 297
293 SourceBufferStream::TimespanList ChunkDemuxerStream::GetBufferedTime() const { 298 Ranges<base::TimeDelta> ChunkDemuxerStream::GetBufferedTime() const {
294 base::AutoLock auto_lock(lock_); 299 base::AutoLock auto_lock(lock_);
295 return stream_->GetBufferedTime(); 300 return stream_->GetBufferedTime();
296 } 301 }
297 302
298 bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config) { 303 bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config) {
299 DCHECK(config.IsValidConfig()); 304 DCHECK(config.IsValidConfig());
300 DCHECK_EQ(type_, AUDIO); 305 DCHECK_EQ(type_, AUDIO);
301 306
302 const AudioDecoderConfig& current_config = 307 const AudioDecoderConfig& current_config =
303 stream_->GetCurrentAudioDecoderConfig(); 308 stream_->GetCurrentAudioDecoderConfig();
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
456 if (!stream_->GetNextBuffer(&buffer)) 461 if (!stream_->GetNextBuffer(&buffer))
457 return; 462 return;
458 closures->push_back(base::Bind(read_cbs_.front(), buffer)); 463 closures->push_back(base::Bind(read_cbs_.front(), buffer));
459 read_cbs_.pop_front(); 464 read_cbs_.pop_front();
460 } 465 }
461 } 466 }
462 467
463 ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client) 468 ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client)
464 : state_(WAITING_FOR_INIT), 469 : state_(WAITING_FOR_INIT),
465 host_(NULL), 470 host_(NULL),
466 client_(client), 471 client_(client) {
467 buffered_bytes_(0) {
468 DCHECK(client); 472 DCHECK(client);
469 } 473 }
470 474
471 void ChunkDemuxer::Initialize(DemuxerHost* host, 475 void ChunkDemuxer::Initialize(DemuxerHost* host,
472 const PipelineStatusCB& cb) { 476 const PipelineStatusCB& cb) {
473 DVLOG(1) << "Init()"; 477 DVLOG(1) << "Init()";
474 { 478 {
475 base::AutoLock auto_lock(lock_); 479 base::AutoLock auto_lock(lock_);
476 DCHECK_EQ(state_, WAITING_FOR_INIT); 480 DCHECK_EQ(state_, WAITING_FOR_INIT);
477 host_ = host; 481 host_ = host;
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
620 delete stream_parser_map_[id]; 624 delete stream_parser_map_[id];
621 stream_parser_map_.erase(id); 625 stream_parser_map_.erase(id);
622 626
623 if (source_id_audio_ == id && audio_) 627 if (source_id_audio_ == id && audio_)
624 audio_->Shutdown(); 628 audio_->Shutdown();
625 629
626 if (source_id_video_ == id && video_) 630 if (source_id_video_ == id && video_)
627 video_->Shutdown(); 631 video_->Shutdown();
628 } 632 }
629 633
630 bool ChunkDemuxer::GetBufferedRanges(const std::string& id, 634 Ranges<base::TimeDelta> ChunkDemuxer::GetBufferedRanges(
631 Ranges* ranges_out) const { 635 const std::string& id) const {
632 DCHECK(!id.empty()); 636 DCHECK(!id.empty());
633 DCHECK_GT(stream_parser_map_.count(id), 0u); 637 DCHECK_GT(stream_parser_map_.count(id), 0u);
634 DCHECK(id == source_id_audio_ || id == source_id_video_); 638 DCHECK(id == source_id_audio_ || id == source_id_video_);
635 DCHECK(ranges_out);
636 639
637 base::AutoLock auto_lock(lock_); 640 base::AutoLock auto_lock(lock_);
638 641
639 if (id == source_id_audio_ && id != source_id_video_) { 642 if (id == source_id_audio_ && id != source_id_video_) {
640 // Only include ranges that have been buffered in |audio_| 643 // Only include ranges that have been buffered in |audio_|
641 return CopyIntoRanges(audio_->GetBufferedTime(), ranges_out); 644 return audio_->GetBufferedTime();
642 } 645 }
643 646
644 if (id != source_id_audio_ && id == source_id_video_) { 647 if (id != source_id_audio_ && id == source_id_video_) {
645 // Only include ranges that have been buffered in |video_| 648 // Only include ranges that have been buffered in |video_|
646 return CopyIntoRanges(video_->GetBufferedTime(), ranges_out); 649 return video_->GetBufferedTime();
647 } 650 }
648 651
652 return ComputeIntersection();
653 }
654
655 Ranges<base::TimeDelta> ChunkDemuxer::ComputeIntersection() const {
656 lock_.AssertAcquired();
657
658 if (!audio_ || !video_)
659 return Ranges<base::TimeDelta>();
660
649 // Include ranges that have been buffered in both |audio_| and |video_|. 661 // Include ranges that have been buffered in both |audio_| and |video_|.
650 SourceBufferStream::TimespanList audio_ranges = audio_->GetBufferedTime(); 662 Ranges<base::TimeDelta> audio_ranges = audio_->GetBufferedTime();
651 SourceBufferStream::TimespanList video_ranges = video_->GetBufferedTime(); 663 Ranges<base::TimeDelta> video_ranges = video_->GetBufferedTime();
652 SourceBufferStream::TimespanList::const_iterator video_ranges_itr = 664 Ranges<base::TimeDelta> result = audio_ranges.IntersectionWith(video_ranges);
653 video_ranges.begin();
654 SourceBufferStream::TimespanList::const_iterator audio_ranges_itr =
655 audio_ranges.begin();
656 bool success = false;
657 665
658 while (audio_ranges_itr != audio_ranges.end() && 666 if (state_ == ENDED) {
659 video_ranges_itr != video_ranges.end()) { 667 // If appending has ended, extend the intersection to include all of the
660 // If this is the last range and EndOfStream() was called (i.e. all data 668 // data from the last ranges for each stream. This allows the buffered
661 // has been appended), choose the max end point of the ranges. 669 // information to match the actual time range that will get played out if
662 bool last_range_after_ended = 670 // the streams have slightly different lengths. We only do this if the
663 state_ == ENDED && 671 // final ranges overlap because this indicates there is actually a location
664 (audio_ranges_itr + 1) == audio_ranges.end() && 672 // you can seek to, near the end, where we have data for both streams.
665 (video_ranges_itr + 1) == video_ranges.end(); 673 base::TimeDelta audio_start = audio_ranges.start(audio_ranges.size() - 1);
674 base::TimeDelta audio_end = audio_ranges.end(audio_ranges.size() - 1);
675 base::TimeDelta video_start = video_ranges.start(video_ranges.size() - 1);
676 base::TimeDelta video_end = video_ranges.end(video_ranges.size() - 1);
666 677
667 // Audio range start time is within the video range. 678 if ((audio_start <= video_start && video_start <= audio_end) ||
668 if ((*audio_ranges_itr).first >= (*video_ranges_itr).first && 679 (video_start <= audio_start && audio_start <= video_end)) {
669 (*audio_ranges_itr).first <= (*video_ranges_itr).second) { 680 result.Add(std::max(audio_start, video_start),
Ami GONE FROM CHROMIUM 2012/06/19 17:40:37 should this be a "min" instead of a max? Otherwis
acolwell GONE FROM CHROMIUM 2012/06/19 19:50:15 I don't want min here. I only want the intersectio
670 AddIntersectionRange(*audio_ranges_itr, *video_ranges_itr, 681 std::max(audio_end, video_end));
671 last_range_after_ended, ranges_out);
672 audio_ranges_itr++;
673 success = true;
674 continue;
675 } 682 }
676
677 // Video range start time is within the audio range.
678 if ((*video_ranges_itr).first >= (*audio_ranges_itr).first &&
679 (*video_ranges_itr).first <= (*audio_ranges_itr).second) {
680 AddIntersectionRange(*video_ranges_itr, *audio_ranges_itr,
681 last_range_after_ended, ranges_out);
682 video_ranges_itr++;
683 success = true;
684 continue;
685 }
686
687 // No overlap was found. Increment the earliest one and keep looking.
688 if ((*audio_ranges_itr).first < (*video_ranges_itr).first)
689 audio_ranges_itr++;
690 else
691 video_ranges_itr++;
692 } 683 }
693 684
694 return success; 685 return result;
695 }
696
697 bool ChunkDemuxer::CopyIntoRanges(
698 const SourceBufferStream::TimespanList& timespans,
699 Ranges* ranges_out) const {
700 for (SourceBufferStream::TimespanList::const_iterator itr = timespans.begin();
701 itr != timespans.end(); ++itr) {
702 ranges_out->push_back(*itr);
703 }
704 return !timespans.empty();
705 }
706
707 void ChunkDemuxer::AddIntersectionRange(
708 SourceBufferStream::Timespan timespan_a,
709 SourceBufferStream::Timespan timespan_b,
710 bool last_range_after_ended,
711 Ranges* ranges_out) const {
712 base::TimeDelta start = timespan_a.first;
713
714 // If this is the last range after EndOfStream() was called, choose the later
715 // end point of the ranges, otherwise choose the earlier.
716 base::TimeDelta end;
717 if (last_range_after_ended)
718 end = std::max(timespan_a.second, timespan_b.second);
719 else
720 end = std::min(timespan_a.second, timespan_b.second);
721
722 ranges_out->push_back(std::make_pair(start, end));
723 } 686 }
724 687
725 bool ChunkDemuxer::AppendData(const std::string& id, 688 bool ChunkDemuxer::AppendData(const std::string& id,
726 const uint8* data, 689 const uint8* data,
727 size_t length) { 690 size_t length) {
728 DVLOG(1) << "AppendData(" << id << ", " << length << ")"; 691 DVLOG(1) << "AppendData(" << id << ", " << length << ")";
729 692
730 DCHECK(!id.empty()); 693 DCHECK(!id.empty());
731 DCHECK(data); 694 DCHECK(data);
732 DCHECK_GT(length, 0u); 695 DCHECK_GT(length, 0u);
733 696
734 int64 buffered_bytes = 0; 697 Ranges<base::TimeDelta> ranges;
735 698
736 PipelineStatusCB cb; 699 PipelineStatusCB cb;
737 { 700 {
738 base::AutoLock auto_lock(lock_); 701 base::AutoLock auto_lock(lock_);
739 702
740 // Capture if the SourceBuffer has a pending seek before we start parsing. 703 // Capture if the SourceBuffer has a pending seek before we start parsing.
741 bool old_seek_pending = IsSeekPending_Locked(); 704 bool old_seek_pending = IsSeekPending_Locked();
742 705
743 switch (state_) { 706 switch (state_) {
744 case INITIALIZING: 707 case INITIALIZING:
(...skipping 20 matching lines...) Expand all
765 DVLOG(1) << "AppendData(): called in unexpected state " << state_; 728 DVLOG(1) << "AppendData(): called in unexpected state " << state_;
766 return false; 729 return false;
767 } 730 }
768 731
769 // Check to see if data was appended at the pending seek point. This 732 // Check to see if data was appended at the pending seek point. This
770 // indicates we have parsed enough data to complete the seek. 733 // indicates we have parsed enough data to complete the seek.
771 if (old_seek_pending && !IsSeekPending_Locked() && !seek_cb_.is_null()) { 734 if (old_seek_pending && !IsSeekPending_Locked() && !seek_cb_.is_null()) {
772 std::swap(cb, seek_cb_); 735 std::swap(cb, seek_cb_);
773 } 736 }
774 737
775 buffered_bytes_ += length; 738 if (duration_ > base::TimeDelta() && duration_ != kInfiniteDuration()) {
776 buffered_bytes = buffered_bytes_; 739 if (audio_ && !video_) {
740 ranges = audio_->GetBufferedTime();
741 } else if (!audio_ && video_) {
742 ranges = video_->GetBufferedTime();
743 } else {
744 ranges = ComputeIntersection();
745 }
746 }
777 } 747 }
778 748
779 // Notify the host of 'network activity' because we got data, using a bogus 749 for (size_t i = 0; i < ranges.size(); ++i) {
780 // range. 750 DCHECK(duration_ > base::TimeDelta());
Ami GONE FROM CHROMIUM 2012/06/19 17:40:37 silly to do this inside the for-loop; move up as:
acolwell GONE FROM CHROMIUM 2012/06/19 19:50:15 Done. Used !ranges.size() since empty() doesn't ex
781 host_->AddBufferedByteRange(0, buffered_bytes); 751
752 // Notify the host of 'network activity' because we got data.
753 int64 start =
754 kFakeTotalBytes * ranges.start(i).InSecondsF() / duration_.InSecondsF();
755 int64 end =
756 kFakeTotalBytes * ranges.end(i).InSecondsF() / duration_.InSecondsF();
757 host_->AddBufferedByteRange(start, end);
758 }
782 759
783 if (!cb.is_null()) 760 if (!cb.is_null())
784 cb.Run(PIPELINE_OK); 761 cb.Run(PIPELINE_OK);
785 762
786 return true; 763 return true;
787 } 764 }
788 765
789 void ChunkDemuxer::Abort(const std::string& id) { 766 void ChunkDemuxer::Abort(const std::string& id) {
790 DVLOG(1) << "Abort(" << id << ")"; 767 DVLOG(1) << "Abort(" << id << ")";
791 DCHECK(!id.empty()); 768 DCHECK(!id.empty());
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after
940 } 917 }
941 918
942 if (duration > duration_) 919 if (duration > duration_)
943 duration_ = duration; 920 duration_ = duration;
944 921
945 // Wait until all streams have initialized. 922 // Wait until all streams have initialized.
946 if ((!source_id_audio_.empty() && !audio_) || 923 if ((!source_id_audio_.empty() && !audio_) ||
947 (!source_id_video_.empty() && !video_)) 924 (!source_id_video_.empty() && !video_))
948 return; 925 return;
949 926
927 if (duration_ > base::TimeDelta() && duration_ != kInfiniteDuration())
928 host_->SetTotalBytes(kFakeTotalBytes);
950 host_->SetDuration(duration_); 929 host_->SetDuration(duration_);
951 930
952 ChangeState_Locked(INITIALIZED); 931 ChangeState_Locked(INITIALIZED);
953 PipelineStatusCB cb; 932 PipelineStatusCB cb;
954 std::swap(cb, init_cb_); 933 std::swap(cb, init_cb_);
955 cb.Run(PIPELINE_OK); 934 cb.Run(PIPELINE_OK);
956 } 935 }
957 936
958 bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video, 937 bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video,
959 const AudioDecoderConfig& audio_config, 938 const AudioDecoderConfig& audio_config,
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after
1028 // TODO(vrk): There should be a special case for the first appends where all 1007 // TODO(vrk): There should be a special case for the first appends where all
1029 // streams (for both demuxed and muxed case) begin at the earliest stream 1008 // streams (for both demuxed and muxed case) begin at the earliest stream
1030 // timestamp. (crbug.com/132815) 1009 // timestamp. (crbug.com/132815)
1031 if (audio_ && source_id == source_id_audio_) 1010 if (audio_ && source_id == source_id_audio_)
1032 audio_->OnNewMediaSegment(start_timestamp); 1011 audio_->OnNewMediaSegment(start_timestamp);
1033 if (video_ && source_id == source_id_video_) 1012 if (video_ && source_id == source_id_video_)
1034 video_->OnNewMediaSegment(start_timestamp); 1013 video_->OnNewMediaSegment(start_timestamp);
1035 } 1014 }
1036 1015
1037 } // namespace media 1016 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698