summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorvrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-04 19:59:03 +0000
committervrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-04 19:59:03 +0000
commit589082288dd0cb0d50ba83fc48d8d1b1ae024887 (patch)
tree9efb1fec29875c0508f77623acaa9713314641dc /media
parentbd6a62634d728ba1e2ccc6fbbdc92a28ea78bb73 (diff)
downloadchromium_src-589082288dd0cb0d50ba83fc48d8d1b1ae024887.zip
chromium_src-589082288dd0cb0d50ba83fc48d8d1b1ae024887.tar.gz
chromium_src-589082288dd0cb0d50ba83fc48d8d1b1ae024887.tar.bz2
Tweak Garbage Collection algorithm in MediaSource to save last GOP appended
This CL changes the GC algorithm to also preserve the last GOP appended to prevent crashing/garbage output. BUG=150946 Review URL: https://codereview.chromium.org/11035013 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160203 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/filters/source_buffer_stream.cc245
-rw-r--r--media/filters/source_buffer_stream.h6
-rw-r--r--media/filters/source_buffer_stream_unittest.cc321
3 files changed, 413 insertions, 159 deletions
diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc
index 888934a..acdcebf 100644
--- a/media/filters/source_buffer_stream.cc
+++ b/media/filters/source_buffer_stream.cc
@@ -87,14 +87,16 @@ class SourceBufferRange {
// Deletes all buffers in range.
bool DeleteAll(BufferQueue* deleted_buffers);
- // Attempts to free |bytes| data from the range, preferring to delete at the
- // beginning of the range. Deletes data in GOPS at a time so that the range
- // always begins with a keyframe. Returns the number of bytes freed.
- int FreeFromStart(int bytes);
+ // Deletes a GOP from the front or back of the range and moves these
+ // buffers into |deleted_buffers|. Returns the number of bytes deleted from
+ // the range (i.e. the size in bytes of |deleted_buffers|).
+ int DeleteGOPFromFront(BufferQueue* deleted_buffers);
+ int DeleteGOPFromBack(BufferQueue* deleted_buffers);
- // Attempts to free |bytes| data from the range, preferring to delete at the
- // end of the range. Returns the number of bytes freed.
- int FreeFromEnd(int bytes);
+ // Indicates whether the GOP at the beginning or end of the range contains the
+ // next buffer position.
+ bool FirstGOPContainsNextBufferPosition() const;
+ bool LastGOPContainsNextBufferPosition() const;
// Updates |out_buffer| with the next buffer in presentation order. Seek()
// must be called before calls to GetNextBuffer(), and buffers are returned
@@ -157,7 +159,7 @@ class SourceBufferRange {
int size_in_bytes() const { return size_in_bytes_; }
private:
- typedef std::map<base::TimeDelta, size_t> KeyframeMap;
+ typedef std::map<base::TimeDelta, int> KeyframeMap;
// Seeks the range to the next keyframe after |timestamp|. If
// |skip_given_timestamp| is true, the seek will go to a keyframe with a
@@ -532,34 +534,87 @@ void SourceBufferStream::GarbageCollectIfNeeded() {
int bytes_to_free = ranges_size - memory_limit_;
// Begin deleting from the front.
- while (!ranges_.empty() && bytes_to_free > 0) {
- SourceBufferRange* current_range = ranges_.front();
- bytes_to_free -= current_range->FreeFromStart(bytes_to_free);
+ int bytes_freed = FreeBuffers(bytes_to_free, false);
- // If the |current_range| still has data left after freeing, we should not
- // delete any more data in this direction.
- if (current_range->size_in_bytes() > 0)
- break;
+ // Begin deleting from the back.
+ if (bytes_to_free - bytes_freed > 0)
+ FreeBuffers(bytes_to_free - bytes_freed, true);
+}
- DCHECK_NE(current_range, selected_range_);
- delete current_range;
- ranges_.pop_front();
- }
+int SourceBufferStream::FreeBuffers(int total_bytes_to_free,
+ bool reverse_direction) {
+ DCHECK_GT(total_bytes_to_free, 0);
+ int bytes_to_free = total_bytes_to_free;
+ int bytes_freed = 0;
+
+ // This range will save the last GOP appended to |range_for_next_append_|
+ // if the buffers surrounding it get deleted during garbage collection.
+ SourceBufferRange* new_range_for_append = NULL;
- // Begin deleting from the back.
while (!ranges_.empty() && bytes_to_free > 0) {
- SourceBufferRange* current_range = ranges_.back();
- bytes_to_free -= current_range->FreeFromEnd(bytes_to_free);
- // If the |current_range| still has data left after freeing, we should not
- // delete any more data in this direction.
- if (current_range->size_in_bytes() > 0)
- break;
+ SourceBufferRange* current_range = NULL;
+ BufferQueue buffers;
+ int bytes_deleted = 0;
+
+ if (reverse_direction) {
+ current_range = ranges_.back();
+ if (current_range->LastGOPContainsNextBufferPosition()) {
+ DCHECK_EQ(current_range, selected_range_);
+ break;
+ }
+ bytes_deleted = current_range->DeleteGOPFromBack(&buffers);
+ } else {
+ current_range = ranges_.front();
+ if (current_range->FirstGOPContainsNextBufferPosition()) {
+ DCHECK_EQ(current_range, selected_range_);
+ break;
+ }
+ bytes_deleted = current_range->DeleteGOPFromFront(&buffers);
+ }
- DCHECK_NE(current_range, selected_range_);
- delete current_range;
- ranges_.pop_back();
+ // Check to see if we've just deleted the GOP that was last appended.
+ if (buffers.back()->GetDecodeTimestamp() == last_buffer_timestamp_) {
+ DCHECK(last_buffer_timestamp_ != kNoTimestamp());
+ DCHECK(!new_range_for_append);
+ // Create a new range containing these buffers.
+ new_range_for_append = new SourceBufferRange(
+ buffers, kNoTimestamp(),
+ base::Bind(&SourceBufferStream::GetMaxInterbufferDistance,
+ base::Unretained(this)));
+ range_for_next_append_ = ranges_.end();
+ } else {
+ bytes_to_free -= bytes_deleted;
+ bytes_freed += bytes_deleted;
+ }
+
+ if (current_range->size_in_bytes() == 0) {
+ DCHECK_NE(current_range, selected_range_);
+ DCHECK(range_for_next_append_ == ranges_.end() ||
+ *range_for_next_append_ != current_range);
+ delete current_range;
+ reverse_direction ? ranges_.pop_back() : ranges_.pop_front();
+ }
}
+
+ // Insert |new_range_for_append| into |ranges_|, if applicable.
+ if (new_range_for_append) {
+ range_for_next_append_ = AddToRanges(new_range_for_append);
+ DCHECK(range_for_next_append_ != ranges_.end());
+
+ // Check to see if we need to merge |new_range_for_append| with the range
+ // before or after it. |new_range_for_append| is created whenever the last
+ // GOP appended is encountered, regardless of whether any buffers after it
+ // are ultimately deleted. Merging is necessary if there were no buffers
+ // (or very few buffers) deleted after creating |new_range_for_append|.
+ if (range_for_next_append_ != ranges_.begin()) {
+ RangeList::iterator range_before_next = range_for_next_append_;
+ --range_before_next;
+ MergeWithAdjacentRangeIfNecessary(range_before_next);
+ }
+ MergeWithAdjacentRangeIfNecessary(range_for_next_append_);
+ }
+ return bytes_freed;
}
void SourceBufferStream::InsertIntoExistingRange(
@@ -794,6 +849,9 @@ void SourceBufferStream::MergeWithAdjacentRangeIfNecessary(
if (transfer_current_position)
SetSelectedRange(range_with_new_buffers);
+ if (next_range_itr == range_for_next_append_)
+ range_for_next_append_ = range_with_new_buffers_itr;
+
delete *next_range_itr;
ranges_.erase(next_range_itr);
}
@@ -1226,60 +1284,40 @@ bool SourceBufferRange::TruncateAt(
return TruncateAt(starting_point, removed_buffers);
}
-int SourceBufferRange::FreeFromStart(int bytes) {
- KeyframeMap::iterator deletion_limit = keyframe_map_.end();
- if (HasNextBufferPosition()) {
- base::TimeDelta next_timestamp = GetNextTimestamp();
- if (next_timestamp != kNoTimestamp()) {
- deletion_limit = GetFirstKeyframeBefore(next_timestamp);
- } else {
- // If |next_timestamp| is kNoTimestamp(), that means that the next buffer
- // hasn't been buffered yet, so just save the keyframe before the end.
- --deletion_limit;
- }
- }
+int SourceBufferRange::DeleteGOPFromFront(BufferQueue* deleted_buffers) {
+ DCHECK(!FirstGOPContainsNextBufferPosition());
+ DCHECK(deleted_buffers);
int buffers_deleted = 0;
int total_bytes_deleted = 0;
- while (total_bytes_deleted < bytes) {
- KeyframeMap::iterator front = keyframe_map_.begin();
- if (front == deletion_limit)
- break;
- DCHECK(front != keyframe_map_.end());
-
- // If we haven't reached |deletion_limit|, we begin by deleting the
- // keyframe at the start of |keyframe_map_|.
- keyframe_map_.erase(front);
- front = keyframe_map_.begin();
-
- // Now we need to delete all the buffers that depend on the keyframe we've
- // just deleted. Determine the index in |buffers_| at which we should stop
- // deleting.
- int end_index = buffers_.size();
- if (front != keyframe_map_.end()) {
- // The indexes in |keyframe_map_| will be out of date after the first GOP
- // is deleted, so adjust |front->second| by the |buffers_deleted| to get
- // the proper index value.
- end_index = front->second - buffers_deleted;
- }
+ KeyframeMap::iterator front = keyframe_map_.begin();
+ DCHECK(front != keyframe_map_.end());
- // Delete buffers from the beginning of the buffered range up until (but not
- // including) the next keyframe.
- for (int i = 0; i < end_index; i++) {
- int bytes_deleted = buffers_.front()->GetDataSize();
- size_in_bytes_ -= bytes_deleted;
- total_bytes_deleted += bytes_deleted;
- buffers_.pop_front();
- ++buffers_deleted;
- }
+ // Delete the keyframe at the start of |keyframe_map_|.
+ keyframe_map_.erase(front);
+
+ // Now we need to delete all the buffers that depend on the keyframe we've
+ // just deleted.
+ int end_index = keyframe_map_.size() > 0 ?
+ keyframe_map_.begin()->second : buffers_.size();
+
+ // Delete buffers from the beginning of the buffered range up until (but not
+ // including) the next keyframe.
+ for (int i = 0; i < end_index; i++) {
+ int bytes_deleted = buffers_.front()->GetDataSize();
+ size_in_bytes_ -= bytes_deleted;
+ total_bytes_deleted += bytes_deleted;
+ deleted_buffers->push_back(buffers_.front());
+ buffers_.pop_front();
+ ++buffers_deleted;
}
// Update indices to account for the deleted buffers.
for (KeyframeMap::iterator itr = keyframe_map_.begin();
itr != keyframe_map_.end(); ++itr) {
itr->second -= buffers_deleted;
- DCHECK_GE(itr->second, 0u);
+ DCHECK_GE(itr->second, 0);
}
if (next_buffer_index_ > -1) {
next_buffer_index_ -= buffers_deleted;
@@ -1294,36 +1332,61 @@ int SourceBufferRange::FreeFromStart(int bytes) {
return total_bytes_deleted;
}
-int SourceBufferRange::FreeFromEnd(int bytes) {
- // Don't delete anything if we're stalled waiting for more data.
- if (HasNextBufferPosition() && !HasNextBuffer())
- return 0;
+int SourceBufferRange::DeleteGOPFromBack(BufferQueue* deleted_buffers) {
+ DCHECK(!LastGOPContainsNextBufferPosition());
+ DCHECK(deleted_buffers);
+
+ // Remove the last GOP's keyframe from the |keyframe_map_|.
+ KeyframeMap::iterator back = keyframe_map_.end();
+ DCHECK_GT(keyframe_map_.size(), 0u);
+ --back;
- // Delete until the current buffer or until we reach the beginning of the
- // range.
- int deletion_limit = next_buffer_index_;
- DCHECK(HasNextBufferPosition() || next_buffer_index_ == -1);
+ // The index of the first buffer in the last GOP is equal to the new size of
+ // |buffers_| after that GOP is deleted.
+ size_t goal_size = back->second;
+ keyframe_map_.erase(back);
int total_bytes_deleted = 0;
- while (total_bytes_deleted < bytes &&
- static_cast<int>(buffers_.size() - 1) > deletion_limit) {
+ while (buffers_.size() != goal_size) {
int bytes_deleted = buffers_.back()->GetDataSize();
size_in_bytes_ -= bytes_deleted;
total_bytes_deleted += bytes_deleted;
-
- // Delete the corresponding |keyframe_map_| entry if we're about to delete a
- // keyframe buffer.
- if (buffers_.back()->IsKeyframe()) {
- DCHECK(keyframe_map_.rbegin()->first ==
- buffers_.back()->GetDecodeTimestamp());
- keyframe_map_.erase(buffers_.back()->GetDecodeTimestamp());
- }
-
+ // We're removing buffers from the back, so push each removed buffer to the
+ // front of |deleted_buffers| so that |deleted_buffers| are in nondecreasing
+ // order.
+ deleted_buffers->push_front(buffers_.back());
buffers_.pop_back();
}
+
return total_bytes_deleted;
}
+bool SourceBufferRange::FirstGOPContainsNextBufferPosition() const {
+ if (!HasNextBufferPosition())
+ return false;
+
+ // If there is only one GOP, it must contain the next buffer position.
+ if (keyframe_map_.size() == 1u)
+ return true;
+
+ KeyframeMap::const_iterator second_gop = keyframe_map_.begin();
+ ++second_gop;
+ return !waiting_for_keyframe_ && next_buffer_index_ < second_gop->second;
+}
+
+bool SourceBufferRange::LastGOPContainsNextBufferPosition() const {
+ if (!HasNextBufferPosition())
+ return false;
+
+ // If there is only one GOP, it must contain the next buffer position.
+ if (keyframe_map_.size() == 1u)
+ return true;
+
+ KeyframeMap::const_iterator last_gop = keyframe_map_.end();
+ --last_gop;
+ return waiting_for_keyframe_ || last_gop->second <= next_buffer_index_;
+}
+
void SourceBufferRange::FreeBufferRange(
const BufferQueue::iterator& starting_point,
const BufferQueue::iterator& ending_point) {
diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h
index 1c3514b..a3757cb 100644
--- a/media/filters/source_buffer_stream.h
+++ b/media/filters/source_buffer_stream.h
@@ -112,6 +112,12 @@ class MEDIA_EXPORT SourceBufferStream {
// Frees up space if the SourceBufferStream is taking up too much memory.
void GarbageCollectIfNeeded();
+ // Attempts to delete approximately |total_bytes_to_free| amount of data
+ // |ranges_|, starting at the front of |ranges_| and moving linearly forward
+ // through the buffers. Deletes starting from the back if |reverse_direction|
+ // is true. Returns the number of bytes freed.
+ int FreeBuffers(int total_bytes_to_free, bool reverse_direction);
+
// Appends |new_buffers| into |range_for_new_buffers_itr|, handling start and
// end overlaps if necessary.
// |deleted_next_buffer| is an output parameter that is true if the next
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index e46a703..dd272d6 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -44,7 +44,7 @@ class SourceBufferStreamTest : public testing::Test {
void NewSegmentAppend(int starting_position, int number_of_buffers) {
AppendBuffers(starting_position, number_of_buffers, true,
- base::TimeDelta(), true, NULL, 0);
+ base::TimeDelta(), true, &kDataA, kDataSize);
}
void NewSegmentAppend(int starting_position, int number_of_buffers,
@@ -57,18 +57,18 @@ class SourceBufferStreamTest : public testing::Test {
int starting_position, int number_of_buffers,
base::TimeDelta first_buffer_offset) {
AppendBuffers(starting_position, number_of_buffers, true,
- first_buffer_offset, true, NULL, 0);
+ first_buffer_offset, true, &kDataA, kDataSize);
}
void NewSegmentAppend_ExpectFailure(
int starting_position, int number_of_buffers) {
AppendBuffers(starting_position, number_of_buffers, true,
- base::TimeDelta(), false, NULL, 0);
+ base::TimeDelta(), false, &kDataA, kDataSize);
}
void AppendBuffers(int starting_position, int number_of_buffers) {
AppendBuffers(starting_position, number_of_buffers, false,
- base::TimeDelta(), true, NULL, 0);
+ base::TimeDelta(), true, &kDataA, kDataSize);
}
void AppendBuffers(int starting_position, int number_of_buffers,
@@ -77,12 +77,20 @@ class SourceBufferStreamTest : public testing::Test {
base::TimeDelta(), true, data, kDataSize);
}
+ void NewSegmentAppend(const std::string& buffers_to_append) {
+ AppendBuffers(buffers_to_append, true, false);
+ }
+
+ void AppendBuffers(const std::string& buffers_to_append) {
+ AppendBuffers(buffers_to_append, false, false);
+ }
+
void NewSegmentAppendOneByOne(const std::string& buffers_to_append) {
- AppendBuffersOneByOne(buffers_to_append, true);
+ AppendBuffers(buffers_to_append, true, true);
}
void AppendBuffersOneByOne(const std::string& buffers_to_append) {
- AppendBuffersOneByOne(buffers_to_append, false);
+ AppendBuffers(buffers_to_append, false, true);
}
void Seek(int position) {
@@ -263,13 +271,14 @@ class SourceBufferStreamTest : public testing::Test {
EXPECT_EQ(expect_success, stream_->Append(queue));
}
- void AppendBuffersOneByOne(const std::string& buffers_to_append,
- bool start_new_segment) {
+ void AppendBuffers(const std::string& buffers_to_append,
+ bool start_new_segment, bool one_by_one) {
std::vector<std::string> timestamps;
base::SplitString(buffers_to_append, ' ', &timestamps);
CHECK_GT(timestamps.size(), 0u);
+ SourceBufferStream::BufferQueue buffers;
for (size_t i = 0; i < timestamps.size(); i++) {
bool is_keyframe = false;
if (EndsWith(timestamps[i], "K", true)) {
@@ -290,9 +299,19 @@ class SourceBufferStreamTest : public testing::Test {
if (i == 0u && start_new_segment)
stream_->OnNewMediaSegment(timestamp);
- SourceBufferStream::BufferQueue queue;
- queue.push_back(buffer);
- EXPECT_TRUE(stream_->Append(queue));
+ buffers.push_back(buffer);
+ }
+
+ if (!one_by_one) {
+ EXPECT_TRUE(stream_->Append(buffers));
+ return;
+ }
+
+ // Append each buffer one by one.
+ for (size_t i = 0; i < buffers.size(); i++) {
+ SourceBufferStream::BufferQueue wrapper;
+ wrapper.push_back(buffers[i]);
+ EXPECT_TRUE(stream_->Append(wrapper));
}
}
@@ -1932,8 +1951,8 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteBack) {
// Append 20 buffers at positions 0 through 19.
NewSegmentAppend(0, 20, &kDataA);
- // Should leave the first 5 buffers from 0 to 4.
- CheckExpectedRanges("{ [0,4) }");
+ // Should leave the first 5 buffers from 0 to 4 and the last GOP appended.
+ CheckExpectedRanges("{ [0,4) [15,19) }");
CheckExpectedBuffers(0, 4, &kDataA);
}
@@ -1944,72 +1963,67 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteFrontAndBack) {
// Seek to position 15.
Seek(15);
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataA);
+ // Append 40 buffers at positions 0 through 39.
+ NewSegmentAppend(0, 40, &kDataA);
- // Should leave 3 buffers, starting at the seek position.
- CheckExpectedRanges("{ [15,17) }");
- CheckExpectedBuffers(15, 17, &kDataA);
- CheckNoNextBuffer();
-}
-
-TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteFrontAndBack2) {
- // Set memory limit to 1 buffer.
- SetMemoryLimit(1);
-
- // Seek to position 15.
- Seek(15);
-
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataA);
-
- // Should leave just the buffer at position 15.
- CheckExpectedRanges("{ [15,15) }");
- CheckExpectedBuffers(15, 15, &kDataA);
+ // Should leave the GOP containing the seek position and the last GOP
+ // appended.
+ CheckExpectedRanges("{ [15,19) [35,39) }");
+ CheckExpectedBuffers(15, 19, &kDataA);
CheckNoNextBuffer();
}
TEST_F(SourceBufferStreamTest, GarbageCollection_DeleteSeveralRanges) {
// Append 5 buffers at positions 0 through 4.
- NewSegmentAppend(0, 5, &kDataA);
+ NewSegmentAppend(0, 5);
// Append 5 buffers at positions 10 through 14.
- NewSegmentAppend(10, 5, &kDataA);
+ NewSegmentAppend(10, 5);
// Append 5 buffers at positions 20 through 24.
- NewSegmentAppend(20, 5, &kDataA);
+ NewSegmentAppend(20, 5);
// Append 5 buffers at positions 30 through 34.
- NewSegmentAppend(30, 5, &kDataA);
+ NewSegmentAppend(30, 5);
CheckExpectedRanges("{ [0,4) [10,14) [20,24) [30,34) }");
// Seek to position 21.
Seek(20);
- CheckExpectedBuffers(20, 20, &kDataA);
+ CheckExpectedBuffers(20, 20);
// Set memory limit to 1 buffer.
SetMemoryLimit(1);
// Append 5 buffers at positions 40 through 44. This will trigger GC.
- NewSegmentAppend(40, 5, &kDataA);
+ NewSegmentAppend(40, 5);
- // Should delete everything except current buffer and the keyframe before it.
- CheckExpectedRanges("{ [20,21) }");
- CheckExpectedBuffers(21, 21, &kDataA);
+ // Should delete everything except the GOP containing the current buffer and
+ // the last GOP appended.
+ CheckExpectedRanges("{ [20,24) [40,44) }");
+ CheckExpectedBuffers(21, 24);
CheckNoNextBuffer();
+ // Continue appending into the last range to make sure it didn't break.
+ AppendBuffers(45, 10);
+ // Should only save last GOP appended.
+ CheckExpectedRanges("{ [20,24) [50,54) }");
+
// Make sure appending before and after the ranges didn't somehow break.
SetMemoryLimit(100);
- NewSegmentAppend(0, 10, &kDataA);
- CheckExpectedRanges("{ [0,9) [20,21) }");
+ NewSegmentAppend(0, 10);
+ CheckExpectedRanges("{ [0,9) [20,24) [50,54) }");
Seek(0);
- CheckExpectedBuffers(0, 9, &kDataA);
+ CheckExpectedBuffers(0, 9);
- NewSegmentAppend(30, 10, &kDataA);
- CheckExpectedRanges("{ [0,9) [20,21) [30,39) }");
- Seek(30);
- CheckExpectedBuffers(30, 39, &kDataA);
+ NewSegmentAppend(90, 10);
+ CheckExpectedRanges("{ [0,9) [20,24) [50,54) [90,99) }");
+ Seek(50);
+ CheckExpectedBuffers(50, 54);
+ CheckNoNextBuffer();
+ Seek(90);
+ CheckExpectedBuffers(90, 99);
+ CheckNoNextBuffer();
}
TEST_F(SourceBufferStreamTest, GarbageCollection_NoSeek) {
@@ -2063,8 +2077,8 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_PendingSeek) {
}
TEST_F(SourceBufferStreamTest, GarbageCollection_NeedsMoreData) {
- // Set memory limit to 10 buffers.
- SetMemoryLimit(10);
+ // Set memory limit to 15 buffers.
+ SetMemoryLimit(15);
// Append 10 buffers at positions 0 through 9.
NewSegmentAppend(0, 10, &kDataA);
@@ -2078,13 +2092,14 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_NeedsMoreData) {
NewSegmentAppend(15, 20, &kDataA);
// GC should have saved the keyframe before the current seek position and the
- // data closest to the current seek position.
- CheckExpectedRanges("{ [5,9) [15,19) }");
+ // data closest to the current seek position. It will also save the last GOP
+ // appended.
+ CheckExpectedRanges("{ [5,9) [15,19) [30,34) }");
// Now fulfill the seek at position 10. This will make GC delete the data
// before position 10 to keep it within cap.
NewSegmentAppend(10, 5, &kDataA);
- CheckExpectedRanges("{ [10,19) }");
+ CheckExpectedRanges("{ [10,19) [30,34) }");
CheckExpectedBuffers(10, 19, &kDataA);
}
@@ -2095,27 +2110,21 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_TrackBuffer) {
// Seek to position 15.
Seek(15);
- // Append 20 buffers at positions 0 through 19.
- NewSegmentAppend(0, 20, &kDataA);
+ // Append 18 buffers at positions 0 through 17.
+ NewSegmentAppend(0, 18, &kDataA);
- // Should leave 3 buffers starting at 15.
+ // Should leave GOP containing seek position.
CheckExpectedRanges("{ [15,17) }");
// Seek ahead to position 16.
CheckExpectedBuffers(15, 15, &kDataA);
- // Add 5 buffers from position 20 to 24.
- NewSegmentAppend(20, 5, &kDataA);
-
- // The newly added buffers should be garbage collected immediately.
- CheckExpectedRanges("{ [15,17) }");
-
// Completely overlap the existing buffers.
NewSegmentAppend(0, 20, &kDataB);
// Because buffers 16 and 17 are not keyframes, they are moved to the track
// buffer upon overlap. The source buffer (i.e. not the track buffer) is now
- // waiting for the next keyframe, which is why buffers 18 and 19 are not GC'd.
+ // waiting for the next keyframe.
CheckExpectedRanges("{ [15,19) }");
CheckExpectedBuffers(16, 17, &kDataA);
CheckNoNextBuffer();
@@ -2123,10 +2132,186 @@ TEST_F(SourceBufferStreamTest, GarbageCollection_TrackBuffer) {
// Now add a keyframe at position 20.
AppendBuffers(20, 5, &kDataB);
- // Should garbage collect such that there are 3 frames remaining, starting at
+ // Should garbage collect such that there are 5 frames remaining, starting at
// the keyframe.
- CheckExpectedRanges("{ [20,22) }");
- CheckExpectedBuffers(20, 22, &kDataB);
+ CheckExpectedRanges("{ [20,24) }");
+ CheckExpectedBuffers(20, 24, &kDataB);
+ CheckNoNextBuffer();
+}
+
+// Test saving the last GOP appended when this GOP is the only GOP in its range.
+TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP) {
+ // Set memory limit to 3 and make sure the 4-byte GOP is not garbage
+ // collected.
+ SetMemoryLimit(3);
+ NewSegmentAppend("0K 30 60 90");
+ CheckExpectedRangesByTimestamp("{ [0,120) }");
+
+ // Make sure you can continue appending data to this GOP; again, GC should not
+ // wipe out anything.
+ AppendBuffers("120");
+ CheckExpectedRangesByTimestamp("{ [0,150) }");
+
+ // Set memory limit to 100 and append a 2nd range after this without
+ // triggering GC.
+ SetMemoryLimit(100);
+ NewSegmentAppend("200K 230 260 290K 320 350");
+ CheckExpectedRangesByTimestamp("{ [0,150) [200,380) }");
+
+ // Seek to 290ms.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(290));
+
+ // Now set memory limit to 3 and append a GOP in a separate range after the
+ // selected range. Because it is after 290ms, this tests that the GOP is saved
+ // when deleting from the back.
+ SetMemoryLimit(3);
+ NewSegmentAppend("500K 530 560 590");
+
+ // Should save GOP with 290ms and last GOP appended.
+ CheckExpectedRangesByTimestamp("{ [290,380) [500,620) }");
+
+ // Continue appending to this GOP after GC.
+ AppendBuffers("620");
+ CheckExpectedRangesByTimestamp("{ [290,380) [500,650) }");
+}
+
+// Test saving the last GOP appended when this GOP is in the middle of a
+// non-selected range.
+TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Middle) {
+ // Append 3 GOPs starting at 0ms, 30ms apart.
+ NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
+ CheckExpectedRangesByTimestamp("{ [0,270) }");
+
+ // Now set the memory limit to 1 and overlap the middle of the range with a
+ // new GOP.
+ SetMemoryLimit(1);
+ NewSegmentAppend("80K 110 140");
+
+ // This whole GOP should be saved, and should be able to continue appending
+ // data to it.
+ CheckExpectedRangesByTimestamp("{ [80,170) }");
+ AppendBuffers("170");
+ CheckExpectedRangesByTimestamp("{ [80,200) }");
+
+ // Set memory limit to 100 and append a 2nd range after this without
+ // triggering GC.
+ SetMemoryLimit(100);
+ NewSegmentAppend("400K 430 460 490K 520 550 580K 610 640");
+ CheckExpectedRangesByTimestamp("{ [80,200) [400,670) }");
+
+ // Seek to 80ms to make the first range the selected range.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(80));
+
+ // Now set memory limit to 3 and append a GOP in the middle of the second
+ // range. Because it is after the selected range, this tests that the GOP is
+ // saved when deleting from the back.
+ SetMemoryLimit(3);
+ NewSegmentAppend("500K 530 560 590");
+
+ // Should save the GOP containing the seek point and GOP that was last
+ // appended.
+ CheckExpectedRangesByTimestamp("{ [80,200) [500,620) }");
+
+ // Continue appending to this GOP after GC.
+ AppendBuffers("620");
+ CheckExpectedRangesByTimestamp("{ [80,200) [500,650) }");
+}
+
+// Test saving the last GOP appended when the GOP containing the next buffer is
+// adjacent to the last GOP appended.
+TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected1) {
+ // Append 3 GOPs at 0ms, 90ms, and 180ms.
+ NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
+ CheckExpectedRangesByTimestamp("{ [0,270) }");
+
+ // Seek to the GOP at 90ms.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(90));
+
+ // Set the memory limit to 1, then overlap the GOP at 0.
+ SetMemoryLimit(1);
+ NewSegmentAppend("0K 30 60");
+
+ // Should save the GOP at 0ms and 90ms.
+ CheckExpectedRangesByTimestamp("{ [0,180) }");
+
+ // Seek to 0 and check all buffers.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
+ CheckExpectedBuffers("0K 30 60 90K 120 150");
+ CheckNoNextBuffer();
+
+ // Now seek back to 90ms and append a GOP at 180ms.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(90));
+ NewSegmentAppend("180K 210 240");
+
+ // Should save the GOP at 90ms and the GOP at 180ms.
+ CheckExpectedRangesByTimestamp("{ [90,270) }");
+ CheckExpectedBuffers("90K 120 150 180K 210 240");
+ CheckNoNextBuffer();
+}
+
+// Test saving the last GOP appended when it is at the beginning or end of the
+// selected range. This tests when the last GOP appended is before or after the
+// GOP containing the next buffer, but not directly adjacent to this GOP.
+TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected2) {
+ // Append 4 GOPs starting at positions 0ms, 90ms, 180ms, 270ms.
+ NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240 270K 300 330");
+ CheckExpectedRangesByTimestamp("{ [0,360) }");
+
+ // Seek to the last GOP at 270ms.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(270));
+
+ // Set the memory limit to 1, then overlap the GOP at 90ms.
+ SetMemoryLimit(1);
+ NewSegmentAppend("90K 120 150");
+
+ // Should save the GOP at 90ms and the GOP at 270ms.
+ CheckExpectedRangesByTimestamp("{ [90,180) [270,360) }");
+
+ // Set memory limit to 100 and add 3 GOPs to the end of the selected range
+ // at 360ms, 450ms, and 540ms.
+ SetMemoryLimit(100);
+ NewSegmentAppend("360K 390 420 450K 480 510 540K 570 600");
+ CheckExpectedRangesByTimestamp("{ [90,180) [270,630) }");
+
+ // Constrain the memory limit again and overlap the GOP at 450ms to test
+ // deleting from the back.
+ SetMemoryLimit(1);
+ NewSegmentAppend("450K 480 510");
+
+ // Should save GOP at 270ms and the GOP at 450ms.
+ CheckExpectedRangesByTimestamp("{ [270,360) [450,540) }");
+}
+
+// Test saving the last GOP appended when it is the same as the GOP containing
+// the next buffer.
+TEST_F(SourceBufferStreamTest, GarbageCollection_SaveAppendGOP_Selected3) {
+ // Seek to start of stream.
+ SeekToTimestamp(base::TimeDelta::FromMilliseconds(0));
+
+ // Append 3 GOPs starting at 0ms, 90ms, 180ms.
+ NewSegmentAppend("0K 30 60 90K 120 150 180K 210 240");
+ CheckExpectedRangesByTimestamp("{ [0,270) }");
+
+ // Set the memory limit to 1 then begin appending the start of a GOP starting
+ // at 0ms.
+ SetMemoryLimit(1);
+ NewSegmentAppend("0K 30");
+
+ // Should save the newly appended GOP, which is also the next GOP that will be
+ // returned from the seek request.
+ CheckExpectedRangesByTimestamp("{ [0,60) }");
+
+ // Check the buffers in the range.
+ CheckExpectedBuffers("0K 30");
+ CheckNoNextBuffer();
+
+ // Continue appending to this buffer.
+ AppendBuffers("60 90");
+
+ // Should still save the rest of this GOP and should be able to fulfill the
+ // read.
+ CheckExpectedRangesByTimestamp("{ [0,120) }");
+ CheckExpectedBuffers("60 90");
CheckNoNextBuffer();
}