summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorvrk@google.com <vrk@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-14 20:59:38 +0000
committervrk@google.com <vrk@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-14 20:59:38 +0000
commitbd1ec999e09bc86c614b8a0cb95981e336f688d0 (patch)
tree438357e1101dbe76390d93912c8df0e37ae8ab5d /media
parent6bc2ce62a92837f600e0f8e7fcd59daeba1e88e2 (diff)
downloadchromium_src-bd1ec999e09bc86c614b8a0cb95981e336f688d0.zip
chromium_src-bd1ec999e09bc86c614b8a0cb95981e336f688d0.tar.gz
chromium_src-bd1ec999e09bc86c614b8a0cb95981e336f688d0.tar.bz2
Notify SourceBufferStream of each cluster's start time as it gets parsed
Passes cluster start time information from WebMStreamParser to ChunkDemuxer to SourceBufferStream. SourceBufferStream can then use this information when fulfilling seeks. BUG=131438 TEST=media_unittests, go/vekbm Review URL: https://chromiumcodereview.appspot.com/10540122 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142221 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/stream_parser.h7
-rw-r--r--media/filters/chunk_demuxer.cc42
-rw-r--r--media/filters/chunk_demuxer.h2
-rw-r--r--media/filters/chunk_demuxer_unittest.cc57
-rw-r--r--media/filters/source_buffer_stream.cc54
-rw-r--r--media/filters/source_buffer_stream.h10
-rw-r--r--media/filters/source_buffer_stream_unittest.cc69
-rw-r--r--media/webm/webm_stream_parser.cc31
-rw-r--r--media/webm/webm_stream_parser.h8
9 files changed, 245 insertions, 35 deletions
diff --git a/media/base/stream_parser.h b/media/base/stream_parser.h
index 22fc0c2..cdf146d 100644
--- a/media/base/stream_parser.h
+++ b/media/base/stream_parser.h
@@ -53,6 +53,10 @@ class MEDIA_EXPORT StreamParser {
// error should be signalled.
typedef base::Callback<bool(const BufferQueue&)> NewBuffersCB;
+ // Signals the beginning of a new media segment.
+ // First parameter - The earliest timestamp of all the streams in the segment.
+ typedef base::Callback<void(base::TimeDelta)> NewMediaSegmentCB;
+
// A new potentially encrypted stream has been parsed.
// First parameter - The initialization data associated with the stream.
// Second parameter - Number of bytes of the initialization data.
@@ -69,7 +73,8 @@ class MEDIA_EXPORT StreamParser {
const NewConfigCB& config_cb,
const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb,
- const KeyNeededCB& key_needed_cb) = 0;
+ const KeyNeededCB& key_needed_cb,
+ const NewMediaSegmentCB& new_segment_cb) = 0;
// Called when a seek occurs. This flushes the current parser state
// and puts the parser in a state where it can receive data for the new seek
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 9a27f52..2d47cc2 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -127,6 +127,7 @@ class ChunkDemuxerStream : public DemuxerStream {
void StartWaitingForSeek();
void Seek(base::TimeDelta time);
bool IsSeekPending() const;
+ void Flush();
// Add buffers to this stream. Buffers are stored in SourceBufferStreams,
// which handle ordering and overlap resolution.
@@ -136,6 +137,10 @@ class ChunkDemuxerStream : public DemuxerStream {
// Returns a list of the buffered time ranges.
SourceBufferStream::TimespanList GetBufferedTime() const;
+ // Signal to the stream that buffers handed in through subsequent calls to
+ // Append() belong to a media segment that starts at |start_timestamp|.
+ void OnNewMediaSegment(base::TimeDelta start_timestamp);
+
// Called when mid-stream config updates occur.
// Returns true if the new config is accepted.
// Returns false if the new config should trigger an error.
@@ -183,12 +188,17 @@ class ChunkDemuxerStream : public DemuxerStream {
State state_;
ReadCBQueue read_cbs_;
+ // The timestamp of the current media segment being parsed by
+ // |stream_parser_|.
+ base::TimeDelta media_segment_start_time_;
+
DISALLOW_IMPLICIT_CONSTRUCTORS(ChunkDemuxerStream);
};
ChunkDemuxerStream::ChunkDemuxerStream(const AudioDecoderConfig& audio_config)
: type_(AUDIO),
- state_(RETURNING_DATA_FOR_READS) {
+ state_(RETURNING_DATA_FOR_READS),
+ media_segment_start_time_(kNoTimestamp()) {
stream_.reset(new SourceBufferStream(audio_config));
}
@@ -228,6 +238,14 @@ bool ChunkDemuxerStream::IsSeekPending() const {
return stream_->IsSeekPending();
}
+void ChunkDemuxerStream::Flush() {
+ media_segment_start_time_ = kNoTimestamp();
+}
+
+void ChunkDemuxerStream::OnNewMediaSegment(base::TimeDelta start_timestamp) {
+ media_segment_start_time_ = start_timestamp;
+}
+
bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
if (buffers.empty())
return false;
@@ -236,7 +254,8 @@ bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) {
{
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, SHUTDOWN);
- if (!stream_->Append(buffers)) {
+ DCHECK(media_segment_start_time_ != kNoTimestamp());
+ if (!stream_->Append(buffers, media_segment_start_time_)) {
DVLOG(1) << "ChunkDemuxerStream::Append() : stream append failed";
return false;
}
@@ -500,6 +519,7 @@ scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream(
base::TimeDelta ChunkDemuxer::GetStartTime() const {
DVLOG(1) << "GetStartTime()";
// TODO(acolwell) : Fix this so it uses the time on the first packet.
+ // (crbug.com/132815)
return base::TimeDelta();
}
@@ -563,7 +583,8 @@ ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
has_audio, has_video),
audio_cb,
video_cb,
- base::Bind(&ChunkDemuxer::OnKeyNeeded, base::Unretained(this)));
+ base::Bind(&ChunkDemuxer::OnKeyNeeded, base::Unretained(this)),
+ base::Bind(&ChunkDemuxer::OnNewMediaSegment, base::Unretained(this), id));
stream_parser_map_[id] = stream_parser.release();
@@ -749,6 +770,10 @@ void ChunkDemuxer::Abort(const std::string& id) {
DCHECK_GT(stream_parser_map_.count(id), 0u);
stream_parser_map_[id]->Flush();
+ if (audio_ && source_id_audio_ == id)
+ audio_->Flush();
+ if (video_ && source_id_video_ == id)
+ video_->Flush();
}
bool ChunkDemuxer::EndOfStream(PipelineStatus status) {
@@ -976,4 +1001,15 @@ bool ChunkDemuxer::OnKeyNeeded(scoped_array<uint8> init_data,
return true;
}
+void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id,
+ base::TimeDelta start_timestamp) {
+ // TODO(vrk): There should be a special case for the first appends where all
+ // streams (for both demuxed and muxed case) begin at the earliest stream
+ // timestamp. (crbug.com/132815)
+ if (audio_ && source_id == source_id_audio_)
+ audio_->OnNewMediaSegment(start_timestamp);
+ if (video_ && source_id == source_id_video_)
+ video_->OnNewMediaSegment(start_timestamp);
+}
+
} // namespace media
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index e53bcd5..5945526 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -118,6 +118,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer {
bool OnAudioBuffers(const StreamParser::BufferQueue& buffers);
bool OnVideoBuffers(const StreamParser::BufferQueue& buffers);
bool OnKeyNeeded(scoped_array<uint8> init_data, int init_data_size);
+ void OnNewMediaSegment(const std::string& source_id,
+ base::TimeDelta start_timestamp);
// Helper functions for calculating GetBufferedRanges().
bool CopyIntoRanges(
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index 0b518cd..d242aae 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -338,7 +338,8 @@ class ChunkDemuxerTest : public testing::Test {
return GenerateCluster(timecode, timecode, block_count);
}
- scoped_ptr<Cluster> GenerateCluster(int audio_timecode, int video_timecode,
+ scoped_ptr<Cluster> GenerateCluster(int first_audio_timecode,
+ int first_video_timecode,
int block_count) {
CHECK_GT(block_count, 0);
@@ -346,14 +347,18 @@ class ChunkDemuxerTest : public testing::Test {
scoped_array<uint8> data(new uint8[size]);
ClusterBuilder cb;
- cb.SetClusterTimecode(std::min(audio_timecode, video_timecode));
+ cb.SetClusterTimecode(std::min(first_audio_timecode, first_video_timecode));
if (block_count == 1) {
- cb.AddBlockGroup(kAudioTrackNum, audio_timecode, kAudioBlockDuration,
- kWebMFlagKeyframe, data.get(), size);
+ cb.AddBlockGroup(kAudioTrackNum, first_audio_timecode,
+ kAudioBlockDuration, kWebMFlagKeyframe,
+ data.get(), size);
return cb.Finish();
}
+ int audio_timecode = first_audio_timecode;
+ int video_timecode = first_video_timecode;
+
// Create simple blocks for everything except the last 2 blocks.
// The first video frame must be a keyframe.
uint8 video_flag = kWebMFlagKeyframe;
@@ -417,15 +422,23 @@ class ChunkDemuxerTest : public testing::Test {
void GenerateExpectedReads(int timecode, int block_count,
DemuxerStream* audio,
DemuxerStream* video) {
+ GenerateExpectedReads(timecode, timecode, block_count, audio, video);
+ }
+
+ void GenerateExpectedReads(int start_audio_timecode,
+ int start_video_timecode,
+ int block_count, DemuxerStream* audio,
+ DemuxerStream* video) {
CHECK_GT(block_count, 0);
- int audio_timecode = timecode;
- int video_timecode = timecode;
if (block_count == 1) {
- ExpectRead(audio, audio_timecode);
+ ExpectRead(audio, start_audio_timecode);
return;
}
+ int audio_timecode = start_audio_timecode;
+ int video_timecode = start_video_timecode;
+
for (int i = 0; i < block_count; i++) {
if (audio_timecode <= video_timecode) {
ExpectRead(audio, audio_timecode);
@@ -1579,4 +1592,34 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_EndOfStream) {
CheckExpectedRanges(expected);
}
+TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) {
+ ASSERT_TRUE(InitDemuxer(true, true, false));
+
+ scoped_refptr<DemuxerStream> audio =
+ demuxer_->GetStream(DemuxerStream::AUDIO);
+ scoped_refptr<DemuxerStream> video =
+ demuxer_->GetStream(DemuxerStream::VIDEO);
+
+ demuxer_->Seek(base::TimeDelta::FromSeconds(0),
+ NewExpectedStatusCB(PIPELINE_OK));
+
+ // Create a cluster where the video timecode begins 25ms after the audio.
+ scoped_ptr<Cluster> start_cluster(
+ GenerateCluster(0, 25, 8));
+
+ ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
+ GenerateExpectedReads(0, 25, 8, audio, video);
+
+ // Seek to 5 seconds.
+ demuxer_->StartWaitingForSeek();
+ demuxer_->Seek(base::TimeDelta::FromSeconds(5),
+ NewExpectedStatusCB(PIPELINE_OK));
+
+ // Generate a cluster to fulfill this seek, where audio timecode begins 25ms
+ // after the video.
+ scoped_ptr<Cluster> middle_cluster(GenerateCluster(5025, 5000, 8));
+ ASSERT_TRUE(AppendData(middle_cluster->data(), middle_cluster->size()));
+ GenerateExpectedReads(5025, 5000, 8, audio, video);
+}
+
} // namespace media
diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc
index cf7233e..47899ee 100644
--- a/media/filters/source_buffer_stream.cc
+++ b/media/filters/source_buffer_stream.cc
@@ -20,7 +20,10 @@ class SourceBufferRange {
// Creates a source buffer range with |new_buffers|. |new_buffers| cannot be
// empty and the front of |new_buffers| must be a keyframe.
- explicit SourceBufferRange(const BufferQueue& new_buffers);
+ // |media_segment_start_time| refers to the starting timestamp for the media
+ // segment to which these buffers belong.
+ SourceBufferRange(const BufferQueue& new_buffers,
+ base::TimeDelta media_segment_start_time);
// Appends |buffers| to the end of the range and updates |keyframe_map_| as
// it encounters new keyframes. Assumes |buffers| belongs at the end of the
@@ -138,6 +141,16 @@ class SourceBufferRange {
// keyframe with timestamp >= |next_keyframe_timestamp_|.
base::TimeDelta next_keyframe_timestamp_;
+ // If the first buffer in this range is the beginning of a media segment,
+ // |media_segment_start_time_| is the time when the media segment begins.
+ // |media_segment_start_time_| may be <= the timestamp of the first buffer in
+ // |buffers_|. |media_segment_start_time_| is kNoTimestamp() if this range
+ // does not start at the beginning of a media segment, which can only happen
+ // garbage collection or after an end overlap that results in a split range
+ // (we don't have a way of knowing the media segment timestamp for the new
+ // range).
+ base::TimeDelta media_segment_start_time_;
+
DISALLOW_COPY_AND_ASSIGN(SourceBufferRange);
};
@@ -188,14 +201,14 @@ static bool IsNextInSequence(
namespace media {
SourceBufferStream::SourceBufferStream()
- : seek_pending_(true),
+ : seek_pending_(false),
seek_buffer_timestamp_(base::TimeDelta()),
selected_range_(NULL),
end_of_stream_(false) {
}
SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
- : seek_pending_(true),
+ : seek_pending_(false),
seek_buffer_timestamp_(base::TimeDelta()),
selected_range_(NULL),
end_of_stream_(false) {
@@ -203,7 +216,7 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
}
SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config)
- : seek_pending_(true),
+ : seek_pending_(false),
seek_buffer_timestamp_(base::TimeDelta()),
selected_range_(NULL),
end_of_stream_(false) {
@@ -218,8 +231,10 @@ SourceBufferStream::~SourceBufferStream() {
}
bool SourceBufferStream::Append(
- const SourceBufferStream::BufferQueue& buffers) {
+ const SourceBufferStream::BufferQueue& buffers,
+ base::TimeDelta media_segment_start_time) {
DCHECK(!buffers.empty());
+ DCHECK(media_segment_start_time != kNoTimestamp());
RangeList::iterator range_for_new_buffers = ranges_.end();
RangeList::iterator itr = ranges_.end();
@@ -244,7 +259,8 @@ bool SourceBufferStream::Append(
if (!buffers.front()->IsKeyframe())
return false;
range_for_new_buffers =
- ranges_.insert(itr, new SourceBufferRange(buffers));
+ ranges_.insert(itr, new SourceBufferRange(
+ buffers, media_segment_start_time));
} else {
InsertIntoExistingRange(range_for_new_buffers, buffers);
}
@@ -254,6 +270,14 @@ bool SourceBufferStream::Append(
ResolveEndOverlap(range_for_new_buffers);
MergeWithAdjacentRangeIfNecessary(range_for_new_buffers);
+ // If these were the first buffers appended to the stream, seek to the
+ // beginning of the range.
+ // TODO(vrk): This should be done by ChunkDemuxer. (crbug.com/132815)
+ if (!seek_pending_ && !selected_range_) {
+ selected_range_ = *range_for_new_buffers;
+ selected_range_->Seek(start_timestamp);
+ }
+
// Finally, try to complete pending seek if one exists.
if (seek_pending_)
Seek(seek_buffer_timestamp_);
@@ -497,10 +521,12 @@ bool SourceBufferStream::CanEndOfStream() const {
return ranges_.empty() || selected_range_ == ranges_.back();
}
-SourceBufferRange::SourceBufferRange(const BufferQueue& new_buffers)
+SourceBufferRange::SourceBufferRange(const BufferQueue& new_buffers,
+ base::TimeDelta media_segment_start_time)
: next_buffer_index_(-1),
waiting_for_keyframe_(false),
- next_keyframe_timestamp_(kNoTimestamp()) {
+ next_keyframe_timestamp_(kNoTimestamp()),
+ media_segment_start_time_(media_segment_start_time) {
DCHECK(!new_buffers.empty());
DCHECK(new_buffers.front()->IsKeyframe());
AppendToEnd(new_buffers);
@@ -537,8 +563,8 @@ void SourceBufferRange::Seek(base::TimeDelta timestamp) {
// lower_bound() returns the first element >= |timestamp|, so we want the
// previous element if it did not return the element exactly equal to
// |timestamp|.
- if (result == keyframe_map_.end() || result->first != timestamp) {
- DCHECK(result != keyframe_map_.begin());
+ if (result != keyframe_map_.begin() &&
+ (result == keyframe_map_.end() || result->first != timestamp)) {
result--;
}
next_buffer_index_ = result->second;
@@ -582,7 +608,8 @@ SourceBufferRange* SourceBufferRange::SplitRange(base::TimeDelta timestamp) {
DeleteAfter(
buffers_.begin() + keyframe_index, &removed_buffers, &next_buffer);
- SourceBufferRange* split_range = new SourceBufferRange(removed_buffers);
+ SourceBufferRange* split_range =
+ new SourceBufferRange(removed_buffers, kNoTimestamp());
if (next_buffer != removed_buffers.end()) {
split_range->next_buffer_index_ = next_buffer - removed_buffers.begin();
}
@@ -734,7 +761,10 @@ bool SourceBufferRange::EndOverlaps(const SourceBufferRange& range) const {
base::TimeDelta SourceBufferRange::GetStartTimestamp() const {
DCHECK(!buffers_.empty());
- return buffers_.front()->GetTimestamp();
+ base::TimeDelta start_timestamp = media_segment_start_time_;
+ if (start_timestamp == kNoTimestamp())
+ start_timestamp = buffers_.front()->GetTimestamp();
+ return start_timestamp;
}
base::TimeDelta SourceBufferRange::GetEndTimestamp() const {
diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h
index c67ecf6..6d7c179 100644
--- a/media/filters/source_buffer_stream.h
+++ b/media/filters/source_buffer_stream.h
@@ -39,9 +39,12 @@ class MEDIA_EXPORT SourceBufferStream {
// expected to be in order, but multiple calls to Append() may add buffers out
// of order or overlapping. Assumes all buffers within |buffers| are in
// presentation order and are non-overlapping.
+ // |media_segment_start_time| refers to the starting timestamp for the media
+ // segment to which these buffers belong.
// Returns true if Append() was successful, false if |buffers| are not added.
// TODO(vrk): Implement garbage collection. (crbug.com/125070)
- bool Append(const BufferQueue& buffers);
+ bool Append(const BufferQueue& buffers,
+ base::TimeDelta media_segment_start_time);
// Changes the SourceBufferStream's state so that it will start returning
// buffers starting from the closest keyframe before |timestamp|.
@@ -51,8 +54,9 @@ class MEDIA_EXPORT SourceBufferStream {
// buffered data and is waiting for more data to be appended.
bool IsSeekPending() const;
- // Fills |out_buffer| with a new buffer. Seek() must be called before calling
- // this method. Buffers are presented in order from the last call to Seek().
+ // Fills |out_buffer| with a new buffer. Buffers are presented in order from
+ // the last call to Seek(), or starting with the first buffer appended if
+ // Seek() has not been called yet.
// |out_buffer|'s timestamp may be earlier than the |timestamp| passed to
// the last Seek() call.
// Returns true if |out_buffer| is filled with a valid buffer, false if
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index 07fc967..eaf1ef5 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -29,17 +29,26 @@ class SourceBufferStreamTest : public testing::Test {
}
void AppendBuffers(int starting_position, int number_of_buffers) {
- AppendBuffers(starting_position, number_of_buffers, true, NULL, 0);
+ AppendBuffers(starting_position, number_of_buffers,
+ base::TimeDelta(), true, NULL, 0);
}
void AppendBuffers(int starting_position, int number_of_buffers,
const uint8* data) {
- AppendBuffers(starting_position, number_of_buffers, true, data, kDataSize);
+ AppendBuffers(starting_position, number_of_buffers,
+ base::TimeDelta(), true, data, kDataSize);
+ }
+
+ void AppendBuffers(int starting_position, int number_of_buffers,
+ base::TimeDelta first_buffer_offset) {
+ AppendBuffers(starting_position, number_of_buffers,
+ first_buffer_offset, true, NULL, 0);
}
void AppendBuffers_ExpectFailure(
int starting_position, int number_of_buffers) {
- AppendBuffers(starting_position, number_of_buffers, false, NULL, 0);
+ AppendBuffers(starting_position, number_of_buffers,
+ base::TimeDelta(), false, NULL, 0);
}
void Seek(int position) {
@@ -142,8 +151,11 @@ class SourceBufferStreamTest : public testing::Test {
}
void AppendBuffers(int starting_position, int number_of_buffers,
+ base::TimeDelta first_buffer_offset,
bool expect_success, const uint8* data, int size) {
int keyframe_interval = frames_per_second_ / keyframes_per_second_;
+ base::TimeDelta media_segment_start_time =
+ starting_position * frame_duration_;
SourceBufferStream::BufferQueue queue;
for (int i = 0; i < number_of_buffers; i++) {
@@ -151,11 +163,17 @@ class SourceBufferStreamTest : public testing::Test {
bool is_keyframe = position % keyframe_interval == 0;
scoped_refptr<StreamParserBuffer> buffer =
StreamParserBuffer::CopyFrom(data, size, is_keyframe);
- buffer->SetDuration(frame_duration_);
- buffer->SetTimestamp(frame_duration_ * position);
+ base::TimeDelta duration = frame_duration_;
+ base::TimeDelta timestamp = frame_duration_ * position;
+ if (i == 0) {
+ duration -= first_buffer_offset;
+ timestamp += first_buffer_offset;
+ }
+ buffer->SetDuration(duration);
+ buffer->SetTimestamp(timestamp);
queue.push_back(buffer);
}
- EXPECT_EQ(stream_.Append(queue), expect_success);
+ EXPECT_EQ(stream_.Append(queue, media_segment_start_time), expect_success);
}
int frames_per_second_;
@@ -1076,6 +1094,37 @@ TEST_F(SourceBufferStreamTest, Seek_After_TrackBuffer_Filled) {
CheckExpectedTimespan(0, 19);
}
+TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) {
+ base::TimeDelta bump = frame_duration() / 4;
+ CHECK(bump > base::TimeDelta());
+
+ // Append 5 buffers at position (5 + |bump|) through 9, where the media
+ // segment begins at position 5.
+ AppendBuffers(5, 5, bump);
+ scoped_refptr<StreamParserBuffer> buffer;
+
+ // GetNextBuffer() should return the next buffer at position (5 + |bump|).
+ EXPECT_TRUE(stream_.GetNextBuffer(&buffer));
+ EXPECT_EQ(buffer->GetTimestamp(), 5 * frame_duration() + bump);
+
+ // Check rest of buffers.
+ CheckExpectedBuffers(6, 9);
+
+ // Seek to position 15.
+ Seek(15);
+
+ // Append 5 buffers at positions (15 + |bump|) through 19, where the media
+ // segment begins at 15.
+ AppendBuffers(15, 5, bump);
+
+ // GetNextBuffer() should return the next buffer at position (15 + |bump|).
+ EXPECT_TRUE(stream_.GetNextBuffer(&buffer));
+ EXPECT_EQ(buffer->GetTimestamp(), 15 * frame_duration() + bump);
+
+ // Check rest of buffers.
+ CheckExpectedBuffers(16, 19);
+}
+
TEST_F(SourceBufferStreamTest, GetNextBuffer_AfterMerges) {
// Append 5 buffers at positions 10 through 14.
AppendBuffers(10, 5);
@@ -1144,4 +1193,12 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_Overlap_Selected_Complete) {
CheckExpectedBuffers(10, 14, &kDataB);
}
+TEST_F(SourceBufferStreamTest, GetNextBuffer_NoSeek) {
+ // Append 5 buffers at position 5.
+ AppendBuffers(5, 5);
+
+ // Should receive buffers from the start without needing to seek.
+ CheckExpectedBuffers(5, 5);
+}
+
} // namespace media
diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc
index c8ca10e..b063a26 100644
--- a/media/webm/webm_stream_parser.cc
+++ b/media/webm/webm_stream_parser.cc
@@ -181,7 +181,8 @@ bool FFmpegConfigHelper::SetupStreamConfigs() {
}
WebMStreamParser::WebMStreamParser()
- : state_(kWaitingForInit) {
+ : state_(kWaitingForInit),
+ waiting_for_buffers_(false) {
}
WebMStreamParser::~WebMStreamParser() {}
@@ -190,13 +191,15 @@ void WebMStreamParser::Init(const InitCB& init_cb,
const NewConfigCB& config_cb,
const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb,
- const KeyNeededCB& key_needed_cb) {
+ const KeyNeededCB& key_needed_cb,
+ const NewMediaSegmentCB& new_segment_cb) {
DCHECK_EQ(state_, kWaitingForInit);
DCHECK(init_cb_.is_null());
DCHECK(!init_cb.is_null());
DCHECK(!config_cb.is_null());
DCHECK(!audio_cb.is_null() || !video_cb.is_null());
DCHECK(!key_needed_cb.is_null());
+ DCHECK(!new_segment_cb.is_null());
ChangeState(kParsingHeaders);
init_cb_ = init_cb;
@@ -204,6 +207,7 @@ void WebMStreamParser::Init(const InitCB& init_cb,
audio_cb_ = audio_cb;
video_cb_ = video_cb;
key_needed_cb_ = key_needed_cb;
+ new_segment_cb_ = new_segment_cb;
}
void WebMStreamParser::Flush() {
@@ -381,6 +385,9 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) {
if (result <= 0)
return result;
+ if (id == kWebMIdCluster)
+ waiting_for_buffers_ = true;
+
if (id == kWebMIdCues) {
if (size < (result + element_size)) {
// We don't have the whole element yet. Signal we need more data.
@@ -403,6 +410,26 @@ int WebMStreamParser::ParseCluster(const uint8* data, int size) {
const BufferQueue& audio_buffers = cluster_parser_->audio_buffers();
const BufferQueue& video_buffers = cluster_parser_->video_buffers();
+ if (waiting_for_buffers_) {
+ base::TimeDelta audio_start_timestamp = kInfiniteDuration();
+ base::TimeDelta video_start_timestamp = kInfiniteDuration();
+
+ if (!audio_buffers.empty())
+ audio_start_timestamp = audio_buffers.front()->GetTimestamp();
+ if (!video_buffers.empty())
+ video_start_timestamp = video_buffers.front()->GetTimestamp();
+
+ base::TimeDelta cluster_start_timestamp =
+ std::min(audio_start_timestamp, video_start_timestamp);
+
+ // If we haven't gotten any buffers yet, return early.
+ if (cluster_start_timestamp == kInfiniteDuration())
+ return bytes_parsed;
+
+ new_segment_cb_.Run(cluster_start_timestamp);
+ waiting_for_buffers_ = false;
+ }
+
if (!audio_buffers.empty() && !audio_cb_.Run(audio_buffers))
return -1;
diff --git a/media/webm/webm_stream_parser.h b/media/webm/webm_stream_parser.h
index 0d37992..003171e 100644
--- a/media/webm/webm_stream_parser.h
+++ b/media/webm/webm_stream_parser.h
@@ -25,7 +25,8 @@ class WebMStreamParser : public StreamParser {
virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb,
const NewBuffersCB& audio_cb,
const NewBuffersCB& video_cb,
- const KeyNeededCB& key_needed_cb) OVERRIDE;
+ const KeyNeededCB& key_needed_cb,
+ const NewMediaSegmentCB& new_segment_cb) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual bool Parse(const uint8* buf, int size) OVERRIDE;
@@ -64,6 +65,11 @@ class WebMStreamParser : public StreamParser {
NewBuffersCB audio_cb_;
NewBuffersCB video_cb_;
KeyNeededCB key_needed_cb_;
+ NewMediaSegmentCB new_segment_cb_;
+
+ // True if a new cluster id has been seen, but no audio or video buffers have
+ // been parsed yet.
+ bool waiting_for_buffers_;
scoped_ptr<WebMClusterParser> cluster_parser_;
ByteQueue byte_queue_;