diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-25 00:07:26 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-25 00:07:26 +0000 |
commit | 721d4385180621453687bc10bef9c2b20c2201c0 (patch) | |
tree | 3817bf01f85f4357d7add625873c5b1f1f99d9f9 /media | |
parent | 2b3c7b35d213aa98dfddf54e075e01a41646e794 (diff) | |
download | chromium_src-721d4385180621453687bc10bef9c2b20c2201c0.zip chromium_src-721d4385180621453687bc10bef9c2b20c2201c0.tar.gz chromium_src-721d4385180621453687bc10bef9c2b20c2201c0.tar.bz2 |
Add Chromium-side functionality for SourceBuffer.remove().
BUG=166465
TEST=SourceBufferStreamTest.Remove*
Review URL: https://chromiumcodereview.appspot.com/19826004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@213553 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/chunk_demuxer.cc | 28 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.h | 7 | ||||
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 11 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.cc | 58 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.h | 10 | ||||
-rw-r--r-- | media/filters/source_buffer_stream_unittest.cc | 139 |
6 files changed, 252 insertions, 1 deletions
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 670a8b0..29bfc6f 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -144,6 +144,15 @@ class ChunkDemuxerStream : public DemuxerStream { // Returns true if buffers were successfully added. bool Append(const StreamParser::BufferQueue& buffers); + // Removes buffers between |start| and |end| according to the steps + // in the "Coded Frame Removal Algorithm" in the Media Source + // Extensions Spec. + // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-coded-frame-removal + // + // |duration| is the current duration of the presentation. It is + // required by the computation outlined in the spec. + void Remove(TimeDelta start, TimeDelta end, TimeDelta duration); + // Signal to the stream that duration has changed to |duration|. void OnSetDuration(TimeDelta duration); @@ -477,6 +486,12 @@ bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) { return true; } +void ChunkDemuxerStream::Remove(TimeDelta start, TimeDelta end, + TimeDelta duration) { + base::AutoLock auto_lock(lock_); + stream_->Remove(start, end, duration); +} + void ChunkDemuxerStream::OnSetDuration(TimeDelta duration) { base::AutoLock auto_lock(lock_); stream_->OnSetDuration(duration); @@ -943,6 +958,19 @@ void ChunkDemuxer::Abort(const std::string& id) { source_state_map_[id]->Abort(); } +void ChunkDemuxer::Remove(const std::string& id, base::TimeDelta start, + base::TimeDelta end) { + DVLOG(1) << "Remove(" << id << ", " << start.InSecondsF() + << ", " << end.InSecondsF() << ")"; + base::AutoLock auto_lock(lock_); + + if (id == source_id_audio_ && audio_) + audio_->Remove(start, end, duration_); + + if (id == source_id_video_ && video_) + video_->Remove(start, end, duration_); +} + double ChunkDemuxer::GetDuration() { base::AutoLock auto_lock(lock_); return GetDuration_Locked(); diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h index 5983199..36177fb 100644 --- a/media/filters/chunk_demuxer.h +++ b/media/filters/chunk_demuxer.h @@ -110,6 +110,11 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { // it can accept a new segment. void Abort(const std::string& id); + // Remove buffers between |start| and |end| for the source buffer + // associated with |id|. + void Remove(const std::string& id, base::TimeDelta start, + base::TimeDelta end); + // Returns the current presentation duration. double GetDuration(); double GetDuration_Locked(); @@ -119,7 +124,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { void SetDuration(double duration); // Sets a time |offset| to be applied to subsequent buffers appended to the - // source buffer assicated with |id|. Returns true if the offset is set + // source buffer associated with |id|. Returns true if the offset is set // properly, false if the offset cannot be applied because we're in the // middle of parsing a media segment. bool SetTimestampOffset(const std::string& id, base::TimeDelta offset); diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index 9620eff..c83f4bb 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -2659,4 +2659,15 @@ TEST_F(ChunkDemuxerTest, TestGCDuringSeek) { CheckExpectedRanges(kSourceId, "{ [500,592) [792,815) }"); } +TEST_F(ChunkDemuxerTest, RemoveBeforeInitSegment) { + EXPECT_CALL(*this, DemuxerOpened()); + demuxer_->Initialize( + &host_, CreateInitDoneCB(kNoTimestamp(), PIPELINE_OK)); + + EXPECT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, true, true)); + + demuxer_->Remove(kSourceId, base::TimeDelta::FromMilliseconds(0), + base::TimeDelta::FromMilliseconds(1)); +} + } // namespace media diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc index 948ea01..b0038dd 100644 --- a/media/filters/source_buffer_stream.cc +++ b/media/filters/source_buffer_stream.cc @@ -469,6 +469,64 @@ bool SourceBufferStream::Append( return true; } +void SourceBufferStream::Remove(base::TimeDelta start, base::TimeDelta end, + base::TimeDelta duration) { + DCHECK(start >= base::TimeDelta()) << start.InSecondsF(); + DCHECK(start < end) << "start " << start.InSecondsF() + << " end " << end.InSecondsF(); + DCHECK(duration != kNoTimestamp()); + + base::TimeDelta remove_end_timestamp = duration; + base::TimeDelta keyframe_timestamp = FindKeyframeAfterTimestamp(end); + if (keyframe_timestamp != kNoTimestamp()) { + remove_end_timestamp = keyframe_timestamp; + } else if (end < remove_end_timestamp) { + remove_end_timestamp = end; + } + + RangeList::iterator itr = ranges_.begin(); + + while (itr != ranges_.end()) { + SourceBufferRange* range = *itr; + if (range->GetStartTimestamp() >= remove_end_timestamp) + break; + + // Split off any remaining end piece and add it to |ranges_|. + SourceBufferRange* new_range = + range->SplitRange(remove_end_timestamp, false); + if (new_range) { + itr = ranges_.insert(++itr, new_range); + --itr; + } + + // If the current range now is completely covered by the removal + // range then delete it and move on. + if (start <= range->GetStartTimestamp()) { + if (selected_range_ == range) + SetSelectedRange(NULL); + + delete range; + itr = ranges_.erase(itr); + continue; + } + + // Truncate the current range so that it only contains data before + // the removal range. + BufferQueue saved_buffers; + range->TruncateAt(start, &saved_buffers, false); + + // Check to see if the current playback position was removed and + // update the selected range appropriately. + if (!saved_buffers.empty()) { + SetSelectedRange(NULL); + SetSelectedRangeIfNeeded(saved_buffers.front()->GetDecodeTimestamp()); + } + + // Move on to the next range. + ++itr; + } +} + void SourceBufferStream::ResetSeekState() { SetSelectedRange(NULL); track_buffer_.clear(); diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h index b372e13..6167e51 100644 --- a/media/filters/source_buffer_stream.h +++ b/media/filters/source_buffer_stream.h @@ -64,6 +64,16 @@ class MEDIA_EXPORT SourceBufferStream { // TODO(vrk): Implement garbage collection. (crbug.com/125070) bool Append(const BufferQueue& buffers); + // Removes buffers between |start| and |end| according to the steps + // in the "Coded Frame Removal Algorithm" in the Media Source + // Extensions Spec. + // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-coded-frame-removal + // + // |duration| is the current duration of the presentation. It is + // required by the computation outlined in the spec. + void Remove(base::TimeDelta start, base::TimeDelta end, + base::TimeDelta duration); + // Changes the SourceBufferStream's state so that it will start returning // buffers starting from the closest keyframe before |timestamp|. void Seek(base::TimeDelta timestamp); diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc index f0d2e38..3c12074 100644 --- a/media/filters/source_buffer_stream_unittest.cc +++ b/media/filters/source_buffer_stream_unittest.cc @@ -108,6 +108,17 @@ class SourceBufferStreamTest : public testing::Test { stream_->Seek(timestamp); } + void RemoveInMs(int start, int end, int duration) { + Remove(base::TimeDelta::FromMilliseconds(start), + base::TimeDelta::FromMilliseconds(end), + base::TimeDelta::FromMilliseconds(duration)); + } + + void Remove(base::TimeDelta start, base::TimeDelta end, + base::TimeDelta duration) { + stream_->Remove(start, end, duration); + } + void CheckExpectedRanges(const std::string& expected) { Ranges<base::TimeDelta> r = stream_->GetBufferedTime(); @@ -2825,6 +2836,134 @@ TEST_F(SourceBufferStreamTest, EndNotSelected_During_PendingSeek) { EXPECT_TRUE(stream_->IsSeekPending()); } + +// Removing exact start & end of a range. +TEST_F(SourceBufferStreamTest, Remove_WholeRange1) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + CheckExpectedRangesByTimestamp("{ [10,160) }"); + RemoveInMs(10, 160, 160); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Removal range starts before range and ends exactly at end. +TEST_F(SourceBufferStreamTest, Remove_WholeRange2) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + CheckExpectedRangesByTimestamp("{ [10,160) }"); + RemoveInMs(0, 160, 160); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Removal range starts at the start of a range and ends beyond the +// range end. +TEST_F(SourceBufferStreamTest, Remove_WholeRange3) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + CheckExpectedRangesByTimestamp("{ [10,160) }"); + RemoveInMs(10, 200, 200); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Removal range starts before range start and ends after the range end. +TEST_F(SourceBufferStreamTest, Remove_WholeRange4) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + CheckExpectedRangesByTimestamp("{ [10,160) }"); + RemoveInMs(0, 200, 200); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Removes multiple ranges. +TEST_F(SourceBufferStreamTest, Remove_WholeRange5) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + NewSegmentAppend("2000K 2030 2060K 2090 2120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) [2000,2150) }"); + RemoveInMs(10, 3000, 3000); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Verifies a [0-infinity) range removes everything. +TEST_F(SourceBufferStreamTest, Remove_ZeroToInfinity) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + NewSegmentAppend("2000K 2030 2060K 2090 2120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) [2000,2150) }"); + Remove(base::TimeDelta(), kInfiniteDuration(), kInfiniteDuration()); + CheckExpectedRangesByTimestamp("{ }"); +} + +// Removal range starts at the beginning of the range and ends in the +// middle of the range. This test verifies that full GOPs are removed. +TEST_F(SourceBufferStreamTest, Remove_Partial1) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) }"); + RemoveInMs(0, 80, 2200); + CheckExpectedRangesByTimestamp("{ [130,160) [1000,1150) }"); +} + +// Removal range starts in the middle of a range and ends at the exact +// end of the range. +TEST_F(SourceBufferStreamTest, Remove_Partial2) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) }"); + RemoveInMs(40, 160, 2200); + CheckExpectedRangesByTimestamp("{ [10,40) [1000,1150) }"); +} + +// Removal range starts and ends within a range. +TEST_F(SourceBufferStreamTest, Remove_Partial3) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) }"); + RemoveInMs(40, 120, 2200); + CheckExpectedRangesByTimestamp("{ [10,40) [130,160) [1000,1150) }"); +} + +// Removal range starts in the middle of one range and ends in the +// middle of another range. +TEST_F(SourceBufferStreamTest, Remove_Partial4) { + Seek(0); + NewSegmentAppend("10K 40 70K 100 130K"); + NewSegmentAppend("1000K 1030 1060K 1090 1120K"); + NewSegmentAppend("2000K 2030 2060K 2090 2120K"); + CheckExpectedRangesByTimestamp("{ [10,160) [1000,1150) [2000,2150) }"); + RemoveInMs(40, 2030, 2200); + CheckExpectedRangesByTimestamp("{ [10,40) [2060,2150) }"); +} + +// Test behavior when the current positing is removed and new buffers +// are appended over the removal range. +TEST_F(SourceBufferStreamTest, Remove_CurrentPosition) { + Seek(0); + NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240 270K 300 330"); + CheckExpectedRangesByTimestamp("{ [0,360) }"); + CheckExpectedBuffers("0K 30 60 90K 120"); + + // Remove a range that includes the next buffer (i.e., 150). + RemoveInMs(150, 210, 360); + CheckExpectedRangesByTimestamp("{ [0,150) [270,360) }"); + + // Verify that no next buffer is returned. + CheckNoNextBuffer(); + + // Append some buffers to fill the gap that was created. + NewSegmentAppend("120K 150 180 210K 240"); + CheckExpectedRangesByTimestamp("{ [0,360) }"); + + // Verify that buffers resume at the next keyframe after the + // current position. + CheckExpectedBuffers("210K 240 270K 300 330"); +} + // TODO(vrk): Add unit tests where keyframes are unaligned between streams. // (crbug.com/133557) |