diff options
-rw-r--r-- | media/base/pipeline.cc | 2 | ||||
-rw-r--r-- | media/base/pipeline_status.h | 1 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.cc | 61 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.h | 3 | ||||
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 165 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.cc | 60 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.h | 12 | ||||
-rw-r--r-- | media/filters/source_buffer_stream_unittest.cc | 95 | ||||
-rw-r--r-- | webkit/media/webmediaplayer_impl.cc | 7 |
9 files changed, 294 insertions, 112 deletions
diff --git a/media/base/pipeline.cc b/media/base/pipeline.cc index b585355..658b414 100644 --- a/media/base/pipeline.cc +++ b/media/base/pipeline.cc @@ -785,7 +785,7 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { // kStarting (for each filter) // kStarted SetState(kPausing); - seek_timestamp_ = time; + seek_timestamp_ = std::max(time, demuxer_->GetStartTime()); seek_cb_ = seek_cb; // Kick off seeking! diff --git a/media/base/pipeline_status.h b/media/base/pipeline_status.h index 8f1a677..cfce0b4 100644 --- a/media/base/pipeline_status.h +++ b/media/base/pipeline_status.h @@ -12,6 +12,7 @@ namespace media { // Status states for pipeline. All codes except PIPELINE_OK indicate errors. +// TODO(vrk/scherkus): Trim the unused status codes. (crbug.com/126070) enum PipelineStatus { PIPELINE_OK, PIPELINE_ERROR_URL_NOT_FOUND, diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index ead7956..06a8d629 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -5,6 +5,7 @@ #include "media/filters/chunk_demuxer.h" #include "base/bind.h" +#include "base/callback_helpers.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/string_util.h" @@ -167,6 +168,9 @@ class ChunkDemuxerStream : public DemuxerStream { // Append() belong to a media segment that starts at |start_timestamp|. void OnNewMediaSegment(TimeDelta start_timestamp); + // Notifies the stream that it begins at |start_time|. + void SetStartTime(TimeDelta start_time); + // 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. @@ -265,6 +269,11 @@ void ChunkDemuxerStream::OnNewMediaSegment(TimeDelta start_timestamp) { stream_->OnNewMediaSegment(start_timestamp); } +void ChunkDemuxerStream::SetStartTime(TimeDelta start_time) { + base::AutoLock auto_lock(lock_); + stream_->SetStartTime(start_time); +} + bool ChunkDemuxerStream::Append(const StreamParser::BufferQueue& buffers) { if (buffers.empty()) return false; @@ -459,7 +468,8 @@ void ChunkDemuxerStream::CreateReadDoneClosures_Locked(ClosureQueue* closures) { ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client) : state_(WAITING_FOR_INIT), host_(NULL), - client_(client) { + client_(client), + start_time_(kNoTimestamp()) { DCHECK(client); } @@ -486,6 +496,7 @@ void ChunkDemuxer::Stop(const base::Closure& callback) { void ChunkDemuxer::Seek(TimeDelta time, const PipelineStatusCB& cb) { DVLOG(1) << "Seek(" << time.InSecondsF() << ")"; + DCHECK(time >= start_time_); PipelineStatus status = PIPELINE_ERROR_INVALID_STATE; { @@ -534,10 +545,9 @@ scoped_refptr<DemuxerStream> ChunkDemuxer::GetStream( } TimeDelta ChunkDemuxer::GetStartTime() const { - DVLOG(1) << "GetStartTime()"; - // TODO(acolwell) : Fix this so it uses the time on the first packet. - // (crbug.com/132815) - return TimeDelta(); + DCHECK(start_time_ != kNoTimestamp()); + DVLOG(1) << "GetStartTime(): " << start_time_.InSecondsF(); + return start_time_; } void ChunkDemuxer::StartWaitingForSeek() { @@ -693,9 +703,9 @@ bool ChunkDemuxer::AppendData(const std::string& id, switch (state_) { case INITIALIZING: + case WAITING_FOR_START_TIME: DCHECK_GT(stream_parser_map_.count(id), 0u); if (!stream_parser_map_[id]->Parse(data, length)) { - DCHECK_EQ(state_, INITIALIZING); ReportError_Locked(DEMUXER_ERROR_COULD_NOT_OPEN); return true; } @@ -880,7 +890,7 @@ bool ChunkDemuxer::CanEndOfStream_Locked() const { } void ChunkDemuxer::OnStreamParserInitDone(bool success, TimeDelta duration) { - DVLOG(1) << "OnSourceBufferInitDone(" << success << ", " + DVLOG(1) << "OnStreamParserInitDone(" << success << ", " << duration.InSecondsF() << ")"; lock_.AssertAcquired(); DCHECK_EQ(state_, INITIALIZING); @@ -899,10 +909,7 @@ void ChunkDemuxer::OnStreamParserInitDone(bool success, TimeDelta duration) { host_->SetDuration(duration_); - ChangeState_Locked(INITIALIZED); - PipelineStatusCB cb; - std::swap(cb, init_cb_); - cb.Run(PIPELINE_OK); + ChangeState_Locked(WAITING_FOR_START_TIME); } bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video, @@ -950,6 +957,7 @@ bool ChunkDemuxer::OnNewConfigs(bool has_audio, bool has_video, } bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) { + lock_.AssertAcquired(); DCHECK_NE(state_, SHUTDOWN); if (!audio_) @@ -959,6 +967,7 @@ bool ChunkDemuxer::OnAudioBuffers(const StreamParser::BufferQueue& buffers) { } bool ChunkDemuxer::OnVideoBuffers(const StreamParser::BufferQueue& buffers) { + lock_.AssertAcquired(); DCHECK_NE(state_, SHUTDOWN); if (!video_) @@ -975,13 +984,37 @@ bool ChunkDemuxer::OnNeedKey(scoped_array<uint8> init_data, void ChunkDemuxer::OnNewMediaSegment(const std::string& source_id, 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) + DVLOG(2) << "OnNewMediaSegment(" << source_id << ", " + << start_timestamp.InSecondsF() << ")"; + lock_.AssertAcquired(); + + if (start_time_ == kNoTimestamp()) { + DCHECK(state_ == INITIALIZING || state_ == WAITING_FOR_START_TIME); + // Use the first reported media segment start time as the |start_time_| + // for the demuxer. + start_time_ = start_timestamp; + } + if (audio_ && source_id == source_id_audio_) audio_->OnNewMediaSegment(start_timestamp); if (video_ && source_id == source_id_video_) video_->OnNewMediaSegment(start_timestamp); + + if (state_ != WAITING_FOR_START_TIME) + return; + + if (audio_) { + audio_->SetStartTime(start_time_); + audio_->Seek(start_time_); + } + if (video_) { + video_->SetStartTime(start_time_); + video_->Seek(start_time_); + } + + // The demuxer is now initialized after the |start_timestamp_| was set. + ChangeState_Locked(INITIALIZED); + base::ResetAndReturn(&init_cb_).Run(PIPELINE_OK); } } // namespace media diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h index ecd2d77..ced6d97 100644 --- a/media/filters/chunk_demuxer.h +++ b/media/filters/chunk_demuxer.h @@ -87,6 +87,7 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { enum State { WAITING_FOR_INIT, INITIALIZING, + WAITING_FOR_START_TIME, INITIALIZED, ENDED, PARSE_ERROR, @@ -143,6 +144,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { std::string source_id_audio_; std::string source_id_video_; + base::TimeDelta start_time_; + DISALLOW_COPY_AND_ASSIGN(ChunkDemuxer); }; diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index cde3417..85e98f6 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -273,14 +273,28 @@ class ChunkDemuxerTest : public testing::Test { return AppendData(source_id, info_tracks.get(), info_tracks_size); } + bool AppendGarbage() { + // Fill up an array with gibberish. + int garbage_cluster_size = 10; + scoped_array<uint8> garbage_cluster(new uint8[garbage_cluster_size]); + for (int i = 0; i < garbage_cluster_size; ++i) + garbage_cluster[i] = i; + return AppendData(garbage_cluster.get(), garbage_cluster_size); + } + void InitDoneCalled(PipelineStatus expected_status, PipelineStatus status) { EXPECT_EQ(status, expected_status); } + bool AppendEmptyCluster(int timecode) { + scoped_ptr<Cluster> empty_cluster = GenerateEmptyCluster(timecode); + return AppendData(empty_cluster->data(), empty_cluster->size()); + } + PipelineStatusCB CreateInitDoneCB(const base::TimeDelta& expected_duration, PipelineStatus expected_status) { - if (expected_status == PIPELINE_OK) + if (expected_duration != kNoTimestamp()) EXPECT_CALL(host_, SetDuration(expected_duration)); return CreateInitDoneCB(expected_status); } @@ -296,9 +310,13 @@ class ChunkDemuxerTest : public testing::Test { PipelineStatus expected_status = (has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN; + base::TimeDelta expected_duration = kNoTimestamp(); + if (expected_status == PIPELINE_OK) + expected_duration = kDefaultDuration(); + EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Initialize( - &host_, CreateInitDoneCB(kDefaultDuration(), expected_status)); + &host_, CreateInitDoneCB(expected_duration, expected_status)); if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk) return false; @@ -322,6 +340,18 @@ class ChunkDemuxerTest : public testing::Test { return success; } + bool InitDemuxer_ExpectInitFailure() { + EXPECT_CALL(*client_, DemuxerOpened(_)); + demuxer_->Initialize( + &host_, CreateInitDoneCB( + kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN)); + + if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk) + return false; + + return AppendInitSegment(true, true, false); + } + void ShutdownDemuxer() { if (demuxer_) { EXPECT_CALL(*client_, DemuxerClosed()); @@ -451,8 +481,9 @@ class ChunkDemuxerTest : public testing::Test { } } - void GenerateExpectedReads(int timecode, int block_count, - DemuxerStream* stream, int block_duration) { + void GenerateSingleStreamExpectedReads( + int timecode, int block_count, DemuxerStream* stream, + int block_duration) { CHECK_GT(block_count, 0); int stream_timecode = timecode; @@ -624,30 +655,29 @@ TEST_F(ChunkDemuxerTest, TestInit) { } } -// Makes sure that Seek() reports an error if Shutdown() +// Makes sure that pipeline reports an error if Shutdown() // is called before the first cluster is passed to the demuxer. -TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstSeekCompletes) { - ASSERT_TRUE(InitDemuxer(true, true, false)); - - demuxer_->Seek(base::TimeDelta::FromSeconds(0), - NewExpectedStatusCB(PIPELINE_ERROR_ABORT)); +TEST_F(ChunkDemuxerTest, TestShutdownBeforeFirstCluster) { + ASSERT_TRUE(InitDemuxer_ExpectInitFailure()); } // Test that Seek() completes successfully when the first cluster // arrives. TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size())); InSequence s; EXPECT_CALL(*this, Checkpoint(1)); - demuxer_->Seek(base::TimeDelta::FromSeconds(0), + demuxer_->Seek(base::TimeDelta::FromMilliseconds(46), NewExpectedStatusCB(PIPELINE_OK)); EXPECT_CALL(*this, Checkpoint(2)); - scoped_ptr<Cluster> cluster(kDefaultFirstCluster()); + scoped_ptr<Cluster> cluster(kDefaultSecondCluster()); Checkpoint(1); @@ -656,6 +686,24 @@ TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) { Checkpoint(2); } +// Test that parsing errors are handled for clusters appended before init +// completes. +TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterBeforeInitCompletes) { + ASSERT_TRUE(InitDemuxer_ExpectInitFailure()); + + ASSERT_TRUE(AppendGarbage()); +} + +// Test that parsing errors are handled for clusters appended after init. +TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterAfterInit) { + ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size())); + + EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE)); + ASSERT_TRUE(AppendGarbage()); +} + // Test the case where a Seek() is requested while the parser // is in the middle of cluster. This is to verify that the parser // does not reset itself on a seek. @@ -715,6 +763,9 @@ TEST_F(ChunkDemuxerTest, TestAppendDataBeforeInit) { TEST_F(ChunkDemuxerTest, TestRead) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); + scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); scoped_refptr<DemuxerStream> video = @@ -725,24 +776,20 @@ TEST_F(ChunkDemuxerTest, TestRead) { audio->Read(base::Bind(&OnReadDone, base::TimeDelta::FromMilliseconds(0), &audio_read_done)); - video->Read(base::Bind(&OnReadDone, base::TimeDelta::FromMilliseconds(0), &video_read_done)); - scoped_ptr<Cluster> cluster(kDefaultFirstCluster()); - - ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); - EXPECT_TRUE(audio_read_done); EXPECT_TRUE(video_read_done); } TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); scoped_ptr<Cluster> cluster_a(GenerateCluster(10, 4)); - ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size())); // Cluster B starts before cluster_a and has data @@ -760,6 +807,8 @@ TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) { TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size())); ClusterBuilder cb; @@ -783,6 +832,8 @@ TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) { TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size())); ClusterBuilder cb; @@ -807,6 +858,8 @@ TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) { TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster()); + ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size())); ClusterBuilder cb; @@ -929,6 +982,9 @@ class EndOfStreamHelper { TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> cluster(GenerateCluster(0, 2)); + ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); + scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); scoped_refptr<DemuxerStream> video = @@ -950,10 +1006,6 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) { end_of_stream_helper_1.RequestReads(); end_of_stream_helper_2.RequestReads(); - scoped_ptr<Cluster> cluster(GenerateCluster(0, 2)); - - ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); - EXPECT_TRUE(audio_read_done_1); EXPECT_TRUE(video_read_done_1); end_of_stream_helper_1.CheckIfReadDonesWereCalled(false); @@ -970,6 +1022,9 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) { TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) { ASSERT_TRUE(InitDemuxer(true, true, false)); + scoped_ptr<Cluster> cluster(GenerateCluster(0, 2)); + ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); + scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); scoped_refptr<DemuxerStream> video = @@ -991,10 +1046,6 @@ TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) { end_of_stream_helper_1.RequestReads(); - scoped_ptr<Cluster> cluster(GenerateCluster(0, 2)); - - ASSERT_TRUE(AppendData(cluster->data(), cluster->size())); - EXPECT_TRUE(audio_read_done_1); EXPECT_TRUE(video_read_done_1); end_of_stream_helper_1.CheckIfReadDonesWereCalled(false); @@ -1130,6 +1181,7 @@ TEST_F(ChunkDemuxerTest, TestWebMFile_AltRefFrames) { // Verify that we output buffers before the entire cluster has been parsed. TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) { ASSERT_TRUE(InitDemuxer(true, true, false)); + ASSERT_TRUE(AppendEmptyCluster(0)); scoped_ptr<Cluster> cluster(GenerateCluster(0, 6)); scoped_refptr<DemuxerStream> audio = @@ -1192,13 +1244,11 @@ TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) { EXPECT_TRUE(video_read_done); } - TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) { - EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE)); - EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Initialize( - &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK)); + &host_, CreateInitDoneCB( + kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN)); ASSERT_EQ(AddId(), ChunkDemuxer::kOk); @@ -1211,7 +1261,7 @@ TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) { TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) { EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Initialize( - &host_, CreateInitDoneCB(kDefaultDuration(), + &host_, CreateInitDoneCB(kNoTimestamp(), DEMUXER_ERROR_COULD_NOT_OPEN)); std::vector<std::string> codecs(1); @@ -1225,7 +1275,7 @@ TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) { TEST_F(ChunkDemuxerTest, TestAVHeadersWithVideoOnlyType) { EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Initialize( - &host_, CreateInitDoneCB(kDefaultDuration(), + &host_, CreateInitDoneCB(kNoTimestamp(), DEMUXER_ERROR_COULD_NOT_OPEN)); std::vector<std::string> codecs(1); @@ -1274,9 +1324,9 @@ TEST_F(ChunkDemuxerTest, TestAddSeparateSourcesForAudioAndVideo) { // Append audio and video data into separate source ids. ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size())); - GenerateExpectedReads(0, 4, audio, kAudioBlockDuration); + GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration); ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size())); - GenerateExpectedReads(0, 4, video, kVideoBlockDuration); + GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration); } TEST_F(ChunkDemuxerTest, TestAddIdFailures) { @@ -1317,7 +1367,7 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) { // Read() from audio should return normal buffers. scoped_refptr<DemuxerStream> audio = demuxer_->GetStream(DemuxerStream::AUDIO); - GenerateExpectedReads(0, 4, audio, kAudioBlockDuration); + GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration); // Remove the audio id. demuxer_->RemoveId(audio_id); @@ -1331,7 +1381,7 @@ TEST_F(ChunkDemuxerTest, TestRemoveId) { // Read() from video should still return normal buffers. scoped_refptr<DemuxerStream> video = demuxer_->GetStream(DemuxerStream::VIDEO); - GenerateExpectedReads(0, 4, video, kVideoBlockDuration); + GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration); } // Test that Seek() successfully seeks to all source IDs. @@ -1571,14 +1621,12 @@ TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) { 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)); - + scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 25, 8)); ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size())); + + demuxer_->Seek(base::TimeDelta::FromSeconds(0), + NewExpectedStatusCB(PIPELINE_OK)); GenerateExpectedReads(0, 25, 8, audio, video); // Seek to 5 seconds. @@ -1593,12 +1641,39 @@ TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) { GenerateExpectedReads(5025, 5000, 8, audio, video); } +TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesSeparateSources) { + std::string audio_id = "audio1"; + std::string video_id = "video1"; + ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id)); + + scoped_refptr<DemuxerStream> audio = + demuxer_->GetStream(DemuxerStream::AUDIO); + scoped_refptr<DemuxerStream> video = + demuxer_->GetStream(DemuxerStream::VIDEO); + + // Generate two streams where the video stream starts 5ms after the audio + // stream and append them. + scoped_ptr<Cluster> cluster_v( + GenerateSingleStreamCluster(30, 4 * kVideoBlockDuration + 30, + kVideoTrackNum, kVideoBlockDuration)); + scoped_ptr<Cluster> cluster_a( + GenerateSingleStreamCluster(25, 4 * kAudioBlockDuration + 25, + kAudioTrackNum, kAudioBlockDuration)); + ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size())); + ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size())); + + // Both streams should be able to fulfill a seek to 25. + demuxer_->Seek(base::TimeDelta::FromMilliseconds(25), + NewExpectedStatusCB(PIPELINE_OK)); + GenerateSingleStreamExpectedReads(25, 4, audio, kAudioBlockDuration); + GenerateSingleStreamExpectedReads(30, 4, video, kVideoBlockDuration); +} + TEST_F(ChunkDemuxerTest, TestClusterWithNoBuffers) { ASSERT_TRUE(InitDemuxer(true, true, false)); - // Create an empty cluster beginning at 0. - scoped_ptr<Cluster> empty_cluster(GenerateEmptyCluster(0)); - ASSERT_TRUE(AppendData(empty_cluster->data(), empty_cluster->size())); + // Generate and append an empty cluster beginning at 0. + ASSERT_TRUE(AppendEmptyCluster(0)); // Sanity check that data can be appended after this cluster correctly. scoped_ptr<Cluster> media_data(GenerateCluster(0, 2)); diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc index c80504f..b72aeb5 100644 --- a/media/filters/source_buffer_stream.cc +++ b/media/filters/source_buffer_stream.cc @@ -59,6 +59,9 @@ class SourceBufferRange { // |timestamp|. void SeekAheadPast(base::TimeDelta timestamp); + // Seeks to the beginning of the range. + void SeekToStart(); + // Finds the next keyframe from |buffers_| after |timestamp|, and creates and // returns a new SourceBufferRange with the buffers from that keyframe onward. // The buffers in the new SourceBufferRange are moved out of this range. If @@ -226,7 +229,8 @@ static int kDefaultBufferDurationInMs = 125; namespace media { SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config) - : seek_pending_(false), + : stream_start_time_(kNoTimestamp()), + seek_pending_(false), seek_buffer_timestamp_(kNoTimestamp()), selected_range_(NULL), end_of_stream_(false), @@ -239,7 +243,8 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config) } SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config) - : seek_pending_(false), + : stream_start_time_(kNoTimestamp()), + seek_pending_(false), seek_buffer_timestamp_(kNoTimestamp()), selected_range_(NULL), end_of_stream_(false), @@ -269,6 +274,15 @@ void SourceBufferStream::OnNewMediaSegment( last_buffer_timestamp_ = kNoTimestamp(); } +void SourceBufferStream::SetStartTime(base::TimeDelta stream_start_time) { + DCHECK(stream_start_time_ == kNoTimestamp()); + DCHECK(stream_start_time != kNoTimestamp()); + stream_start_time_ = stream_start_time; + + DCHECK(ranges_.empty() || + ranges_.front()->GetStartTimestamp() >= stream_start_time_); +} + bool SourceBufferStream::Append( const SourceBufferStream::BufferQueue& buffers) { DCHECK(!buffers.empty()); @@ -286,10 +300,15 @@ bool SourceBufferStream::Append( return false; } + if (stream_start_time_ != kNoTimestamp() && + media_segment_start_time_ < stream_start_time_) { + DVLOG(1) << "Cannot append a media segment before the start of stream."; + return false; + } + UpdateMaxInterbufferDistance(buffers); - // Save a snapshot of the |selected_range_| state before range modifications - // are made. + // Save a snapshot of stream state before range modifications are made. base::TimeDelta next_buffer_timestamp = GetNextBufferTimestamp(); base::TimeDelta end_buffer_timestamp = GetEndBufferTimestamp(); @@ -322,14 +341,6 @@ bool SourceBufferStream::Append( range_for_new_buffers, &deleted_next_buffer, &deleted_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_) { - SetSelectedRange(*range_for_new_buffers); - selected_range_->Seek(buffers.front()->GetDecodeTimestamp()); - } - // Seek to try to fulfill a previous call to Seek(). if (seek_pending_) { DCHECK(!selected_range_); @@ -360,8 +371,14 @@ bool SourceBufferStream::Append( return true; } +bool SourceBufferStream::IsBeforeFirstRange(base::TimeDelta timestamp) const { + if (ranges_.empty()) + return false; + return timestamp < ranges_.front()->GetStartTimestamp(); +} + bool SourceBufferStream::IsMonotonicallyIncreasing( - const BufferQueue& buffers) { + const BufferQueue& buffers) const { DCHECK(!buffers.empty()); base::TimeDelta prev_timestamp = last_buffer_timestamp_; for (BufferQueue::const_iterator itr = buffers.begin(); @@ -605,14 +622,24 @@ void SourceBufferStream::MergeWithAdjacentRangeIfNecessary( } void SourceBufferStream::Seek(base::TimeDelta timestamp) { + DCHECK(stream_start_time_ != kNoTimestamp()); + DCHECK(timestamp >= stream_start_time_); SetSelectedRange(NULL); track_buffer_.clear(); + if (IsBeforeFirstRange(timestamp)) { + SetSelectedRange(ranges_.front()); + ranges_.front()->SeekToStart(); + seek_pending_ = false; + end_of_stream_ = false; + return; + } + seek_buffer_timestamp_ = timestamp; seek_pending_ = true; RangeList::iterator itr = ranges_.end(); - for (itr = ranges_.begin(); itr != ranges_.end(); itr++) { + for (itr = ranges_.begin(); itr != ranges_.end(); ++itr) { if ((*itr)->CanSeekTo(timestamp)) break; } @@ -802,6 +829,11 @@ void SourceBufferRange::SeekAhead(base::TimeDelta timestamp, DCHECK_LT(next_buffer_index_, static_cast<int>(buffers_.size())); } +void SourceBufferRange::SeekToStart() { + DCHECK(!buffers_.empty()); + next_buffer_index_ = 0; +} + SourceBufferRange* SourceBufferRange::SplitRange(base::TimeDelta timestamp) { // Find the first keyframe after |timestamp|, not including |timestamp|. KeyframeMap::iterator new_beginning_keyframe = diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h index e1b56e5..a0151ea 100644 --- a/media/filters/source_buffer_stream.h +++ b/media/filters/source_buffer_stream.h @@ -37,6 +37,9 @@ class MEDIA_EXPORT SourceBufferStream { // starting at |media_segment_start_time|. void OnNewMediaSegment(base::TimeDelta media_segment_start_time); + // Sets the start time of the stream. + void SetStartTime(base::TimeDelta stream_start_time); + // Add the |buffers| to the SourceBufferStream. Buffers within the queue are // 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 @@ -159,9 +162,13 @@ class MEDIA_EXPORT SourceBufferStream { // for the previous |selected_range_|. void SetSelectedRange(SourceBufferRange* range); + // Returns true if |timestamp| occurs before the start timestamp of the first + // range in |ranges_|, false otherwise or if |ranges_| is empty. + bool IsBeforeFirstRange(base::TimeDelta timestamp) const; + // Returns true if the timestamps of |buffers| are monotonically increasing // since the previous append to the media segment, false otherwise. - bool IsMonotonicallyIncreasing(const BufferQueue& buffers); + bool IsMonotonicallyIncreasing(const BufferQueue& buffers) const; // Returns true if |selected_range_| is the only range in |ranges_| that // HasNextBufferPosition(). @@ -176,6 +183,9 @@ class MEDIA_EXPORT SourceBufferStream { AudioDecoderConfig audio_config_; VideoDecoderConfig video_config_; + // The starting time of the stream. + base::TimeDelta stream_start_time_; + // True if more data needs to be appended before the Seek() can complete, // false if no Seek() has been requested or the Seek() is completed. bool seek_pending_; diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc index dfecbf3..95a81ff 100644 --- a/media/filters/source_buffer_stream_unittest.cc +++ b/media/filters/source_buffer_stream_unittest.cc @@ -21,6 +21,9 @@ class SourceBufferStreamTest : public testing::Test { SourceBufferStreamTest() { stream_.reset(new SourceBufferStream(config_)); SetStreamInfo(kDefaultFramesPerSecond, kDefaultKeyframesPerSecond); + + // Set start time to the beginning of the stream. + stream_->SetStartTime(base::TimeDelta()); } void SetStreamInfo(int frames_per_second, int keyframes_per_second) { @@ -1155,6 +1158,7 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) { // Append 5 buffers at position (5 + |bump|) through 9, where the media // segment begins at position 5. + Seek(5); NewSegmentAppend_OffsetFirstBuffer(5, 5, bump); scoped_refptr<StreamParserBuffer> buffer; @@ -1180,6 +1184,17 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) { CheckExpectedBuffers(16, 19); } +TEST_F(SourceBufferStreamTest, Seek_BeforeStartOfSegment) { + // Append 10 buffers at positions 5 through 14. + NewSegmentAppend(5, 10); + + // Seek to a time before the first buffer in the range. + Seek(0); + + // Should return buffers from the beginning of the range. + CheckExpectedBuffers(5, 14); +} + TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap) { // Append 5 buffers at positions 0 through 4. NewSegmentAppend(0, 4); @@ -1200,18 +1215,21 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap) { } TEST_F(SourceBufferStreamTest, OldSeekPoint_CompleteOverlap_Pending) { - // Append 5 buffers at positions 10 through 14 and seek to beginning of the + // Append 2 buffers at positions 0 through 1. + NewSegmentAppend(0, 2); + + // Append 5 buffers at positions 15 through 19 and seek to beginning of the // range. - NewSegmentAppend(10, 5); - Seek(10); + NewSegmentAppend(15, 5); + Seek(15); - // Now seek to the beginning of the stream. - Seek(0); + // Now seek position 5. + Seek(5); // Completely overlap the old seek point. - NewSegmentAppend(5, 15); + NewSegmentAppend(10, 15); - // The seek at time 0 should still be pending. + // The seek at position 5 should still be pending. CheckNoNextBuffer(); } @@ -1235,18 +1253,21 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap) { } TEST_F(SourceBufferStreamTest, OldSeekPoint_MiddleOverlap_Pending) { - // Append 15 buffers at positions 5 through 19 and seek to position 15. - NewSegmentAppend(5, 15); - Seek(15); + // Append 2 buffers at positions 0 through 1. + NewSegmentAppend(0, 2); - // Now seek to the beginning of the stream. - Seek(0); + // Append 15 buffers at positions 10 through 24 and seek to position 20. + NewSegmentAppend(10, 15); + Seek(20); - // Overlap the middle of the range such that there are now two ranges. - NewSegmentAppend(10, 3); - CheckExpectedRanges("{ [5,12) [15,19) }"); + // Now seek to position 5. + Seek(5); - // The seek at time 0 should still be pending. + // Overlap the middle of the range such that it is now split into two ranges. + NewSegmentAppend(15, 3); + CheckExpectedRanges("{ [0,1) [10,17) [20,24) }"); + + // The seek at position 5 should still be pending. CheckNoNextBuffer(); } @@ -1269,15 +1290,18 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap) { } TEST_F(SourceBufferStreamTest, OldSeekPoint_StartOverlap_Pending) { - // Append 15 buffers at positions 5 through 19 and seek to position 15. - NewSegmentAppend(5, 15); - Seek(15); + // Append 2 buffers at positions 0 through 1. + NewSegmentAppend(0, 2); - // Now seek to the beginning of the stream. - Seek(0); + // Append 15 buffers at positions 10 through 24 and seek to position 20. + NewSegmentAppend(10, 15); + Seek(20); + + // Now seek to position 5. + Seek(5); // Start overlap the old seek point. - NewSegmentAppend(10, 10); + NewSegmentAppend(15, 10); // The seek at time 0 should still be pending. CheckNoNextBuffer(); @@ -1302,15 +1326,18 @@ TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap) { } TEST_F(SourceBufferStreamTest, OldSeekPoint_EndOverlap_Pending) { - // Append 15 buffers at positions 10 through 24 and seek to start of range. - NewSegmentAppend(10, 15); - Seek(10); + // Append 2 buffers at positions 0 through 1. + NewSegmentAppend(0, 2); - // Now seek to the beginning of the stream. - Seek(0); + // Append 15 buffers at positions 15 through 29 and seek to start of range. + NewSegmentAppend(15, 15); + Seek(15); + + // Now seek to position 5 + Seek(5); // End overlap the old seek point. - NewSegmentAppend(5, 10); + NewSegmentAppend(10, 10); // The seek at time 0 should still be pending. CheckNoNextBuffer(); @@ -1361,6 +1388,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenAppend) { TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenStartOverlap) { // Append 10 buffers at positions 0 through 9 and exhaust the buffers. NewSegmentAppend(0, 10, &kDataA); + Seek(0); CheckExpectedBuffers(0, 9, &kDataA); // Next buffer is at position 10, so should not be able to fulfill request. @@ -1389,6 +1417,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenStartOverlap) { TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenCompleteOverlap) { // Append 5 buffers at positions 10 through 14 and exhaust the buffers. NewSegmentAppend(10, 5, &kDataA); + Seek(10); CheckExpectedBuffers(10, 14, &kDataA); // Next buffer is at position 15, so should not be able to fulfill request. @@ -1416,6 +1445,7 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenCompleteOverlap) { TEST_F(SourceBufferStreamTest, GetNextBuffer_ExhaustThenEndOverlap) { // Append 5 buffers at positions 10 through 14 and exhaust the buffers. NewSegmentAppend(10, 5, &kDataA); + Seek(10); CheckExpectedBuffers(10, 14, &kDataA); CheckExpectedRanges("{ [10,14) }"); @@ -1467,17 +1497,10 @@ TEST_F(SourceBufferStreamTest, GetNextBuffer_Overlap_Selected_Complete) { CheckExpectedBuffers(10, 14, &kDataB); } -TEST_F(SourceBufferStreamTest, GetNextBuffer_NoSeek) { - // Append 5 buffers at position 5. - NewSegmentAppend(5, 5); - - // Should receive buffers from the start without needing to seek. - CheckExpectedBuffers(5, 5); -} - TEST_F(SourceBufferStreamTest, PresentationTimestampIndependence) { // Append 20 buffers at position 0. NewSegmentAppend(0, 20); + Seek(0); int last_keyframe_idx = -1; base::TimeDelta last_keyframe_presentation_timestamp; diff --git a/webkit/media/webmediaplayer_impl.cc b/webkit/media/webmediaplayer_impl.cc index 41bcf11..0281177 100644 --- a/webkit/media/webmediaplayer_impl.cc +++ b/webkit/media/webmediaplayer_impl.cc @@ -776,7 +776,9 @@ void WebMediaPlayerImpl::Repaint() { void WebMediaPlayerImpl::OnPipelineInitialize(PipelineStatus status) { DCHECK_EQ(main_loop_, MessageLoop::current()); if (status != media::PIPELINE_OK) { - OnPipelineError(status); + // Any error that occurs before the pipeline can initialize should be + // considered a format error. + SetNetworkState(WebMediaPlayer::NetworkStateFormatError); // Repaint to trigger UI update. Repaint(); return; @@ -840,6 +842,9 @@ void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) { SetNetworkState(WebMediaPlayer::NetworkStateNetworkError); break; + // TODO(vrk): Because OnPipelineInitialize() directly reports the + // NetworkStateFormatError instead of calling OnPipelineError(), I believe + // this block can be deleted. Should look into it! (crbug.com/126070) case media::PIPELINE_ERROR_INITIALIZATION_FAILED: case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING: case media::PIPELINE_ERROR_COULD_NOT_RENDER: |