diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-09 04:13:07 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-09 04:13:07 +0000 |
commit | 9730c0548c8c68bce5565bc6faeee8a1840adf10 (patch) | |
tree | 51b4decb87d6ba59b92ad516eb556d8a78cabe87 /media | |
parent | 7a68a440417aa3ac19836423642482b0bf5f42f7 (diff) | |
download | chromium_src-9730c0548c8c68bce5565bc6faeee8a1840adf10.zip chromium_src-9730c0548c8c68bce5565bc6faeee8a1840adf10.tar.gz chromium_src-9730c0548c8c68bce5565bc6faeee8a1840adf10.tar.bz2 |
Fix various operations in ChunkDemuxer that were not being applied to text tracks.
- Fixed range removal.
- Fixed shutdown on error.
- Fixed memory limit setting for testing.
- Cleaned up waiting for seek logic and documented behavior.
BUG=230708
Review URL: https://codereview.chromium.org/110693007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@243756 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/chunk_demuxer.cc | 145 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.h | 6 | ||||
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 277 |
3 files changed, 320 insertions, 108 deletions
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index f5102b9..b38274a 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -114,6 +114,10 @@ class SourceState { // Aborts the current append sequence and resets the parser. void Abort(); + // Calls Remove(|start|, |end|, |duration|) on all + // ChunkDemuxerStreams managed by this object. + void Remove(TimeDelta start, TimeDelta end, TimeDelta duration); + // Sets |timestamp_offset_| if possible. // Returns if the offset was set. Returns false if the offset could not be // updated at this time. @@ -145,6 +149,11 @@ class SourceState { void OnSetDuration(TimeDelta duration); void MarkEndOfStream(); void UnmarkEndOfStream(); + void Shutdown(); + // Sets the memory limit on each stream. |memory_limit| is the + // maximum number of bytes each stream is allowed to hold in its buffer. + void SetMemoryLimitsForTesting(int memory_limit); + bool IsSeekWaitingForData() const; private: // Called by the |stream_parser_| when a new initialization segment is @@ -305,6 +314,7 @@ class ChunkDemuxerStream : public DemuxerStream { // if type() != TEXT. TextTrackConfig text_track_config(); + // Sets the memory limit, in bytes, on the SourceBufferStream. void set_memory_limit_for_testing(int memory_limit) { stream_->set_memory_limit_for_testing(memory_limit); } @@ -360,17 +370,9 @@ SourceState::SourceState(scoped_ptr<StreamParser> stream_parser, } SourceState::~SourceState() { - if (audio_) - audio_->Shutdown(); - - if (video_) - video_->Shutdown(); + Shutdown(); - for (TextStreamMap::iterator itr = text_stream_map_.begin(); - itr != text_stream_map_.end(); ++itr) { - itr->second->Shutdown(); - delete itr->second; - } + STLDeleteValues(&text_stream_map_); } void SourceState::Init(const StreamParser::InitCB& init_cb, @@ -422,6 +424,19 @@ void SourceState::Abort() { can_update_offset_ = true; } +void SourceState::Remove(TimeDelta start, TimeDelta end, TimeDelta duration) { + if (audio_) + audio_->Remove(start, end, duration); + + if (video_) + video_->Remove(start, end, duration); + + for (TextStreamMap::iterator itr = text_stream_map_.begin(); + itr != text_stream_map_.end(); ++itr) { + itr->second->Remove(start, end, duration); + } +} + Ranges<TimeDelta> SourceState::GetBufferedRanges(TimeDelta duration, bool ended) const { // TODO(acolwell): When we start allowing disabled tracks we'll need to update @@ -450,7 +465,6 @@ TimeDelta SourceState::GetMaxBufferedDuration() const { if (video_) max_duration = std::max(max_duration, video_->GetBufferedDuration()); - for (TextStreamMap::const_iterator itr = text_stream_map_.begin(); itr != text_stream_map_.end(); ++itr) { max_duration = std::max(max_duration, itr->second->GetBufferedDuration()); @@ -550,6 +564,49 @@ void SourceState::UnmarkEndOfStream() { } } +void SourceState::Shutdown() { + if (audio_) + audio_->Shutdown(); + + if (video_) + video_->Shutdown(); + + for (TextStreamMap::iterator itr = text_stream_map_.begin(); + itr != text_stream_map_.end(); ++itr) { + itr->second->Shutdown(); + } +} + +void SourceState::SetMemoryLimitsForTesting(int memory_limit) { + if (audio_) + audio_->set_memory_limit_for_testing(memory_limit); + + if (video_) + video_->set_memory_limit_for_testing(memory_limit); + + for (TextStreamMap::iterator itr = text_stream_map_.begin(); + itr != text_stream_map_.end(); ++itr) { + itr->second->set_memory_limit_for_testing(memory_limit); + } +} + +bool SourceState::IsSeekWaitingForData() const { + if (audio_ && audio_->IsSeekWaitingForData()) + return true; + + if (video_ && video_->IsSeekWaitingForData()) + return true; + + // NOTE: We are intentionally not checking the text tracks + // because text tracks are discontinuous and may not have data + // for the seek position. This is ok and playback should not be + // stalled because we don't have cues. If cues, with timestamps after + // the seek time, eventually arrive they will be delivered properly + // in response to ChunkDemuxerStream::Read() calls. + + return false; +} + void SourceState::AdjustBufferTimestamps( const StreamParser::BufferQueue& buffers) { if (timestamp_offset_ == TimeDelta()) @@ -884,6 +941,11 @@ void ChunkDemuxerStream::Shutdown() { bool ChunkDemuxerStream::IsSeekWaitingForData() const { base::AutoLock auto_lock(lock_); + + // This method should not be called for text tracks. See the note in + // SourceState::IsSeekWaitingForData(). + DCHECK_NE(type_, DemuxerStream::TEXT); + return stream_->IsSeekPending(); } @@ -1382,17 +1444,15 @@ 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) { +void ChunkDemuxer::Remove(const std::string& id, TimeDelta start, + 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_); + DCHECK(!id.empty()); + CHECK(IsValidId(id)); + source_state_map_[id]->Remove(start, end, duration_); } double ChunkDemuxer::GetDuration() { @@ -1537,11 +1597,7 @@ void ChunkDemuxer::Shutdown() { if (state_ == SHUTDOWN) return; - if (audio_) - audio_->Shutdown(); - - if (video_) - video_->Shutdown(); + ShutdownAllStreams(); ChangeState_Locked(SHUTDOWN); @@ -1550,11 +1606,10 @@ void ChunkDemuxer::Shutdown() { } void ChunkDemuxer::SetMemoryLimitsForTesting(int memory_limit) { - if (audio_) - audio_->set_memory_limit_for_testing(memory_limit); - - if (video_) - video_->set_memory_limit_for_testing(memory_limit); + for (SourceStateMap::iterator itr = source_state_map_.begin(); + itr != source_state_map_.end(); ++itr) { + itr->second->SetMemoryLimitsForTesting(memory_limit); + } } void ChunkDemuxer::ChangeState_Locked(State new_state) { @@ -1566,11 +1621,8 @@ void ChunkDemuxer::ChangeState_Locked(State new_state) { ChunkDemuxer::~ChunkDemuxer() { DCHECK_NE(state_, INITIALIZED); - for (SourceStateMap::iterator it = source_state_map_.begin(); - it != source_state_map_.end(); ++it) { - delete it->second; - } - source_state_map_.clear(); + + STLDeleteValues(&source_state_map_); } void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { @@ -1588,11 +1640,7 @@ void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { if (!seek_cb_.is_null()) std::swap(cb, seek_cb_); - if (audio_) - audio_->Shutdown(); - - if (video_) - video_->Shutdown(); + ShutdownAllStreams(); } if (!cb.is_null()) { @@ -1606,15 +1654,13 @@ void ChunkDemuxer::ReportError_Locked(PipelineStatus error) { bool ChunkDemuxer::IsSeekWaitingForData_Locked() const { lock_.AssertAcquired(); - bool waiting_for_data = false; - - if (audio_) - waiting_for_data = audio_->IsSeekWaitingForData(); - - if (!waiting_for_data && video_) - waiting_for_data = video_->IsSeekWaitingForData(); + for (SourceStateMap::const_iterator itr = source_state_map_.begin(); + itr != source_state_map_.end(); ++itr) { + if (itr->second->IsSeekWaitingForData()) + return true; + } - return waiting_for_data; + return false; } void ChunkDemuxer::OnSourceInitDone(bool success, TimeDelta duration) { @@ -1773,4 +1819,11 @@ void ChunkDemuxer::CompletePendingReadsIfPossible() { } } +void ChunkDemuxer::ShutdownAllStreams() { + for (SourceStateMap::iterator itr = source_state_map_.begin(); + itr != source_state_map_.end(); ++itr) { + itr->second->Shutdown(); + } +} + } // namespace media diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h index d050632..5f37fc2 100644 --- a/media/filters/chunk_demuxer.h +++ b/media/filters/chunk_demuxer.h @@ -137,6 +137,8 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { void Shutdown(); + // Sets the memory limit on each stream. |memory_limit| is the + // maximum number of bytes each stream is allowed to hold in its buffer. void SetMemoryLimitsForTesting(int memory_limit); // Returns the ranges representing the buffered data in the demuxer. @@ -218,6 +220,10 @@ class MEDIA_EXPORT ChunkDemuxer : public Demuxer { // Seeks all SourceBufferStreams to |seek_time|. void SeekAllSources(base::TimeDelta seek_time); + // Shuts down all DemuxerStreams by calling Shutdown() on + // all objects in |source_state_map_|. + void ShutdownAllStreams(); + mutable base::Lock lock_; State state_; bool cancel_next_seek_; diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index be8a2a2..2a88a3e 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -171,10 +171,13 @@ class ChunkDemuxerTest : public testing::Test { ShutdownDemuxer(); } - void CreateInitSegment(bool has_audio, bool has_video, bool has_text, + void CreateInitSegment(int stream_flags, bool is_audio_encrypted, bool is_video_encrypted, scoped_ptr<uint8[]>* buffer, int* size) { + bool has_audio = (stream_flags & HAS_AUDIO) != 0; + bool has_video = (stream_flags & HAS_VIDEO) != 0; + bool has_text = (stream_flags & HAS_TEXT) != 0; scoped_refptr<DecoderBuffer> ebml_header; scoped_refptr<DecoderBuffer> info; scoped_refptr<DecoderBuffer> audio_track_entry; @@ -286,11 +289,12 @@ class ChunkDemuxerTest : public testing::Test { } ChunkDemuxer::Status AddId() { - return AddId(kSourceId, true, true); + return AddId(kSourceId, HAS_AUDIO | HAS_VIDEO); } - ChunkDemuxer::Status AddId(const std::string& source_id, - bool has_audio, bool has_video) { + ChunkDemuxer::Status AddId(const std::string& source_id, int stream_flags) { + bool has_audio = (stream_flags & HAS_AUDIO) != 0; + bool has_video = (stream_flags & HAS_VIDEO) != 0; std::vector<std::string> codecs; std::string type; @@ -305,7 +309,7 @@ class ChunkDemuxerTest : public testing::Test { } if (!has_audio && !has_video) { - return AddId(kSourceId, true, true); + return AddId(kSourceId, HAS_AUDIO | HAS_VIDEO); } return demuxer_->AddId(source_id, type, codecs); @@ -408,25 +412,22 @@ class ChunkDemuxerTest : public testing::Test { } } - void AppendInitSegment(bool has_audio, bool has_video) { - AppendInitSegmentWithSourceId(kSourceId, has_audio, has_video, false); + void AppendInitSegment(int stream_flags) { + AppendInitSegmentWithSourceId(kSourceId, stream_flags); } void AppendInitSegmentWithSourceId(const std::string& source_id, - bool has_audio, bool has_video, - bool has_text) { - AppendInitSegmentWithEncryptedInfo( - source_id, has_audio, has_video, has_text, false, false); + int stream_flags) { + AppendInitSegmentWithEncryptedInfo(source_id, stream_flags, false, false); } void AppendInitSegmentWithEncryptedInfo(const std::string& source_id, - bool has_audio, bool has_video, - bool has_text, + int stream_flags, bool is_audio_encrypted, bool is_video_encrypted) { scoped_ptr<uint8[]> info_tracks; int info_tracks_size = 0; - CreateInitSegment(has_audio, has_video, has_text, + CreateInitSegment(stream_flags, is_audio_encrypted, is_video_encrypted, &info_tracks, &info_tracks_size); AppendData(source_id, info_tracks.get(), info_tracks_size); @@ -470,18 +471,14 @@ class ChunkDemuxerTest : public testing::Test { }; bool InitDemuxer(int stream_flags) { - return InitDemuxerWithEncryptionInfo( - (stream_flags & HAS_AUDIO) != 0, - (stream_flags & HAS_VIDEO) != 0, - (stream_flags & HAS_TEXT) != 0, - false, false); + return InitDemuxerWithEncryptionInfo(stream_flags, false, false); } bool InitDemuxerWithEncryptionInfo( - bool has_audio, bool has_video, bool has_text, - bool is_audio_encrypted, bool is_video_encrypted) { + int stream_flags, bool is_audio_encrypted, bool is_video_encrypted) { + PipelineStatus expected_status = - (has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN; + (stream_flags != 0) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN; base::TimeDelta expected_duration = kNoTimestamp(); if (expected_status == PIPELINE_OK) @@ -491,11 +488,11 @@ class ChunkDemuxerTest : public testing::Test { demuxer_->Initialize( &host_, CreateInitDoneCB(expected_duration, expected_status), true); - if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk) + if (AddId(kSourceId, stream_flags) != ChunkDemuxer::kOk) return false; AppendInitSegmentWithEncryptedInfo( - kSourceId, has_audio, has_video, has_text, + kSourceId, stream_flags, is_audio_encrypted, is_video_encrypted); return true; } @@ -507,13 +504,21 @@ class ChunkDemuxerTest : public testing::Test { demuxer_->Initialize( &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK), true); - if (AddId(audio_id, true, false) != ChunkDemuxer::kOk) + if (AddId(audio_id, HAS_AUDIO) != ChunkDemuxer::kOk) return false; - if (AddId(video_id, false, true) != ChunkDemuxer::kOk) + if (AddId(video_id, HAS_VIDEO) != ChunkDemuxer::kOk) return false; - AppendInitSegmentWithSourceId(audio_id, true, false, has_text); - AppendInitSegmentWithSourceId(video_id, false, true, has_text); + int audio_flags = HAS_AUDIO; + int video_flags = HAS_VIDEO; + + if (has_text) { + audio_flags |= HAS_TEXT; + video_flags |= HAS_TEXT; + } + + AppendInitSegmentWithSourceId(audio_id, audio_flags); + AppendInitSegmentWithSourceId(video_id, video_flags); return true; } @@ -548,7 +553,7 @@ class ChunkDemuxerTest : public testing::Test { &host_, CreateInitDoneCB(base::TimeDelta::FromMilliseconds(2744), PIPELINE_OK), true); - if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk) + if (AddId(kSourceId, HAS_AUDIO | HAS_VIDEO) != ChunkDemuxer::kOk) return false; // Append the whole bear1 file. @@ -863,18 +868,18 @@ class ChunkDemuxerTest : public testing::Test { bool ParseWebMFile(const std::string& filename, const BufferTimestamps* timestamps, const base::TimeDelta& duration) { - return ParseWebMFile(filename, timestamps, duration, true, true); + return ParseWebMFile(filename, timestamps, duration, HAS_AUDIO | HAS_VIDEO); } bool ParseWebMFile(const std::string& filename, const BufferTimestamps* timestamps, const base::TimeDelta& duration, - bool has_audio, bool has_video) { + int stream_flags) { EXPECT_CALL(*this, DemuxerOpened()); demuxer_->Initialize( &host_, CreateInitDoneCB(duration, PIPELINE_OK), true); - if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk) + if (AddId(kSourceId, stream_flags) != ChunkDemuxer::kOk) return false; // Read a WebM file into memory and send the data to the demuxer. @@ -968,8 +973,15 @@ TEST_F(ChunkDemuxerTest, Init) { .Times(Exactly(need_key_count)); } + int stream_flags = 0; + if (has_audio) + stream_flags |= HAS_AUDIO; + + if (has_video) + stream_flags |= HAS_VIDEO; + ASSERT_TRUE(InitDemuxerWithEncryptionInfo( - has_audio, has_video, false, is_audio_encrypted, is_video_encrypted)); + stream_flags, is_audio_encrypted, is_video_encrypted)); DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); if (has_audio) { @@ -1003,6 +1015,8 @@ TEST_F(ChunkDemuxerTest, Init) { } } +// TODO(acolwell): Fold this test into Init tests since the tests are +// almost identical. TEST_F(ChunkDemuxerTest, InitText) { // Test with 1 video stream and 1 text streams, and 0 or 1 audio streams. // No encryption cases handled here. @@ -1020,8 +1034,15 @@ TEST_F(ChunkDemuxerTest, InitText) { .WillOnce(DoAll(SaveArg<0>(&text_stream), SaveArg<1>(&text_config))); + int stream_flags = HAS_TEXT; + if (has_audio) + stream_flags |= HAS_AUDIO; + + if (has_video) + stream_flags |= HAS_VIDEO; + ASSERT_TRUE(InitDemuxerWithEncryptionInfo( - has_audio, has_video, true, is_audio_encrypted, is_video_encrypted)); + stream_flags, is_audio_encrypted, is_video_encrypted)); ASSERT_TRUE(text_stream); EXPECT_EQ(DemuxerStream::TEXT, text_stream->type()); EXPECT_EQ(kTextSubtitles, text_config.kind()); @@ -1060,31 +1081,65 @@ TEST_F(ChunkDemuxerTest, InitText) { // Make sure that the demuxer reports an error if Shutdown() // is called before all the initialization segments are appended. -TEST_F(ChunkDemuxerTest, ShutdownBeforeAllInitSegmentsAppended) { +TEST_F(ChunkDemuxerTest, Shutdown_BeforeAllInitSegmentsAppended) { EXPECT_CALL(*this, DemuxerOpened()); demuxer_->Initialize( &host_, CreateInitDoneCB( kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN), true); - EXPECT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk); - EXPECT_EQ(AddId("video", false, true), ChunkDemuxer::kOk); + EXPECT_EQ(AddId("audio", HAS_AUDIO), ChunkDemuxer::kOk); + EXPECT_EQ(AddId("video", HAS_VIDEO), ChunkDemuxer::kOk); + + AppendInitSegmentWithSourceId("audio", HAS_AUDIO); - AppendInitSegmentWithSourceId("audio", true, false, false); + ShutdownDemuxer(); } -TEST_F(ChunkDemuxerTest, ShutdownBeforeAllInitSegmentsAppendedText) { +TEST_F(ChunkDemuxerTest, Shutdown_BeforeAllInitSegmentsAppendedText) { EXPECT_CALL(*this, DemuxerOpened()); demuxer_->Initialize( &host_, CreateInitDoneCB( kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN), true); - EXPECT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk); - EXPECT_EQ(AddId("video", false, true), ChunkDemuxer::kOk); + EXPECT_EQ(AddId("audio", HAS_AUDIO), ChunkDemuxer::kOk); + EXPECT_EQ(AddId("video_and_text", HAS_VIDEO), ChunkDemuxer::kOk); EXPECT_CALL(host_, AddTextStream(_, _)) .Times(Exactly(1)); - AppendInitSegmentWithSourceId("video", false, true, true); + AppendInitSegmentWithSourceId("video_and_text", HAS_VIDEO | HAS_TEXT); + + ShutdownDemuxer(); +} + +// Verifies that all streams waiting for data receive an end of stream +// buffer when Shutdown() is called. +TEST_F(ChunkDemuxerTest, Shutdown_EndOfStreamWhileWaitingForData) { + DemuxerStream* text_stream = NULL; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO | HAS_TEXT)); + + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + + bool audio_read_done = false; + bool video_read_done = false; + bool text_read_done = false; + audio_stream->Read(base::Bind(&OnReadDone_EOSExpected, &audio_read_done)); + video_stream->Read(base::Bind(&OnReadDone_EOSExpected, &video_read_done)); + text_stream->Read(base::Bind(&OnReadDone_EOSExpected, &text_read_done)); + message_loop_.RunUntilIdle(); + + EXPECT_FALSE(audio_read_done); + EXPECT_FALSE(video_read_done); + EXPECT_FALSE(text_read_done); + + ShutdownDemuxer(); + + EXPECT_TRUE(audio_read_done); + EXPECT_TRUE(video_read_done); + EXPECT_TRUE(text_read_done); } // Test that Seek() completes successfully when the first cluster @@ -1159,7 +1214,7 @@ TEST_F(ChunkDemuxerTest, SeekWhileParsingCluster) { TEST_F(ChunkDemuxerTest, AppendDataBeforeInit) { scoped_ptr<uint8[]> info_tracks; int info_tracks_size = 0; - CreateInitSegment(true, true, false, + CreateInitSegment(HAS_AUDIO | HAS_VIDEO, false, false, &info_tracks, &info_tracks_size); demuxer_->AppendData(kSourceId, info_tracks.get(), info_tracks_size); @@ -1528,7 +1583,7 @@ TEST_F(ChunkDemuxerTest, AppendingInPieces) { scoped_ptr<uint8[]> info_tracks; int info_tracks_size = 0; - CreateInitSegment(true, true, false, + CreateInitSegment(HAS_AUDIO | HAS_VIDEO, false, false, &info_tracks, &info_tracks_size); scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster()); @@ -1591,7 +1646,7 @@ TEST_F(ChunkDemuxerTest, WebMFile_AudioOnly) { ASSERT_TRUE(ParseWebMFile("bear-320x240-audio-only.webm", buffer_timestamps, base::TimeDelta::FromMilliseconds(2744), - true, false)); + HAS_AUDIO)); } TEST_F(ChunkDemuxerTest, WebMFile_VideoOnly) { @@ -1606,7 +1661,7 @@ TEST_F(ChunkDemuxerTest, WebMFile_VideoOnly) { ASSERT_TRUE(ParseWebMFile("bear-320x240-video-only.webm", buffer_timestamps, base::TimeDelta::FromMilliseconds(2703), - false, true)); + HAS_VIDEO)); } TEST_F(ChunkDemuxerTest, WebMFile_AltRefFrames) { @@ -1710,7 +1765,7 @@ TEST_F(ChunkDemuxerTest, AVHeadersWithAudioOnlyType) { ASSERT_EQ(demuxer_->AddId(kSourceId, "audio/webm", codecs), ChunkDemuxer::kOk); - AppendInitSegment(true, true); + AppendInitSegment(HAS_AUDIO | HAS_VIDEO); } TEST_F(ChunkDemuxerTest, AVHeadersWithVideoOnlyType) { @@ -1724,7 +1779,7 @@ TEST_F(ChunkDemuxerTest, AVHeadersWithVideoOnlyType) { ASSERT_EQ(demuxer_->AddId(kSourceId, "video/webm", codecs), ChunkDemuxer::kOk); - AppendInitSegment(true, true); + AppendInitSegment(HAS_AUDIO | HAS_VIDEO); } TEST_F(ChunkDemuxerTest, MultipleHeaders) { @@ -1733,7 +1788,7 @@ TEST_F(ChunkDemuxerTest, MultipleHeaders) { AppendCluster(kDefaultFirstCluster()); // Append another identical initialization segment. - AppendInitSegment(true, true); + AppendInitSegment(HAS_AUDIO | HAS_VIDEO); AppendCluster(kDefaultSecondCluster()); @@ -1782,15 +1837,15 @@ TEST_F(ChunkDemuxerTest, AddIdFailures) { std::string audio_id = "audio1"; std::string video_id = "video1"; - ASSERT_EQ(AddId(audio_id, true, false), ChunkDemuxer::kOk); + ASSERT_EQ(AddId(audio_id, HAS_AUDIO), ChunkDemuxer::kOk); // Adding an id with audio/video should fail because we already added audio. ASSERT_EQ(AddId(), ChunkDemuxer::kReachedIdLimit); - AppendInitSegmentWithSourceId(audio_id, true, false, false); + AppendInitSegmentWithSourceId(audio_id, HAS_AUDIO); // Adding an id after append should fail. - ASSERT_EQ(AddId(video_id, false, true), ChunkDemuxer::kReachedIdLimit); + ASSERT_EQ(AddId(video_id, HAS_VIDEO), ChunkDemuxer::kReachedIdLimit); } // Test that Read() calls after a RemoveId() return "end of stream" buffers. @@ -1825,11 +1880,11 @@ TEST_F(ChunkDemuxerTest, RemoveId) { // quota for new IDs in the future. TEST_F(ChunkDemuxerTest, RemoveAndAddId) { std::string audio_id_1 = "audio1"; - ASSERT_TRUE(AddId(audio_id_1, true, false) == ChunkDemuxer::kOk); + ASSERT_TRUE(AddId(audio_id_1, HAS_AUDIO) == ChunkDemuxer::kOk); demuxer_->RemoveId(audio_id_1); std::string audio_id_2 = "audio2"; - ASSERT_TRUE(AddId(audio_id_2, true, false) == ChunkDemuxer::kOk); + ASSERT_TRUE(AddId(audio_id_2, HAS_AUDIO) == ChunkDemuxer::kOk); } TEST_F(ChunkDemuxerTest, SeekCanceled) { @@ -2018,8 +2073,8 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioIdOnly) { demuxer_->Initialize( &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK), true); - ASSERT_EQ(AddId(kSourceId, true, false), ChunkDemuxer::kOk); - AppendInitSegment(true, false); + ASSERT_EQ(AddId(kSourceId, HAS_AUDIO), ChunkDemuxer::kOk); + AppendInitSegment(HAS_AUDIO); // Test a simple cluster. AppendCluster( @@ -2040,8 +2095,8 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_VideoIdOnly) { demuxer_->Initialize( &host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK), true); - ASSERT_EQ(AddId(kSourceId, false, true), ChunkDemuxer::kOk); - AppendInitSegment(false, true); + ASSERT_EQ(AddId(kSourceId, HAS_VIDEO), ChunkDemuxer::kOk); + AppendInitSegment(HAS_VIDEO); // Test a simple cluster. AppendCluster( @@ -2349,8 +2404,8 @@ TEST_F(ChunkDemuxerTest, EndOfStreamStillSetAfterSeek) { TEST_F(ChunkDemuxerTest, GetBufferedRangesBeforeInitSegment) { EXPECT_CALL(*this, DemuxerOpened()); demuxer_->Initialize(&host_, CreateInitDoneCB(PIPELINE_OK), true); - ASSERT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk); - ASSERT_EQ(AddId("video", false, true), ChunkDemuxer::kOk); + ASSERT_EQ(AddId("audio", HAS_AUDIO), ChunkDemuxer::kOk); + ASSERT_EQ(AddId("video", HAS_VIDEO), ChunkDemuxer::kOk); CheckExpectedRanges("audio", "{ }"); CheckExpectedRanges("video", "{ }"); @@ -2668,7 +2723,7 @@ TEST_F(ChunkDemuxerTest, AppendAfterEndOfStream) { // Test receiving a Shutdown() call before we get an Initialize() // call. This can happen if video element gets destroyed before // the pipeline has a chance to initialize the demuxer. -TEST_F(ChunkDemuxerTest, ShutdownBeforeInitialize) { +TEST_F(ChunkDemuxerTest, Shutdown_BeforeInitialize) { demuxer_->Shutdown(); demuxer_->Initialize( &host_, CreateInitDoneCB(DEMUXER_ERROR_COULD_NOT_OPEN), true); @@ -2822,7 +2877,7 @@ TEST_F(ChunkDemuxerTest, RemoveBeforeInitSegment) { demuxer_->Initialize( &host_, CreateInitDoneCB(kNoTimestamp(), PIPELINE_OK), true); - EXPECT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, true, true)); + EXPECT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, HAS_AUDIO | HAS_VIDEO)); demuxer_->Remove(kSourceId, base::TimeDelta::FromMilliseconds(0), base::TimeDelta::FromMilliseconds(1)); @@ -2941,4 +2996,102 @@ TEST_F(ChunkDemuxerTest, StartWaitingForSeekAfterParseError) { demuxer_->StartWaitingForSeek(seek_time); } +TEST_F(ChunkDemuxerTest, Remove_AudioVideoText) { + DemuxerStream* text_stream = NULL; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO | HAS_TEXT)); + + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, + "0K 20K 40K 60K 80K 100K 120K 140K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "0K 30 60 90 120K 150 180"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "0K 100K 200K"); + + CheckExpectedBuffers(audio_stream, "0 20 40 60 80 100 120 140"); + CheckExpectedBuffers(video_stream, "0 30 60 90 120 150 180"); + CheckExpectedBuffers(text_stream, "0 100 200"); + + // Remove the buffers that were added. + demuxer_->Remove(kSourceId, base::TimeDelta(), + base::TimeDelta::FromMilliseconds(300)); + + // Verify that all the appended data has been removed. + CheckExpectedRanges(kSourceId, "{ }"); + + // Append new buffers that are clearly different than the original + // ones and verify that only the new buffers are returned. + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, + "1K 21K 41K 61K 81K 101K 121K 141K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "1K 31 61 91 121K 151 181"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "1K 101K 201K"); + + Seek(base::TimeDelta()); + CheckExpectedBuffers(audio_stream, "1 21 41 61 81 101 121 141"); + CheckExpectedBuffers(video_stream, "1 31 61 91 121 151 181"); + CheckExpectedBuffers(text_stream, "1 101 201"); +} + +// Verifies that a Seek() will complete without text cues for +// the seek point and will return cues after the seek position +// when they are eventually appended. +TEST_F(ChunkDemuxerTest, SeekCompletesWithoutTextCues) { + DemuxerStream* text_stream = NULL; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO | HAS_TEXT)); + + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + + base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(120); + bool seek_cb_was_called = false; + demuxer_->StartWaitingForSeek(seek_time); + demuxer_->Seek(seek_time, + base::Bind(OnSeekDone_OKExpected, &seek_cb_was_called)); + message_loop_.RunUntilIdle(); + + EXPECT_FALSE(seek_cb_was_called); + + bool text_read_done = false; + text_stream->Read(base::Bind(&OnReadDone, + base::TimeDelta::FromMilliseconds(125), + &text_read_done)); + + // Append audio & video data so the seek completes. + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, + "0K 20K 40K 60K 80K 100K 120K 140K 160K 180K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "0K 30 60 90 120K 150 180 210"); + + message_loop_.RunUntilIdle(); + EXPECT_TRUE(seek_cb_was_called); + EXPECT_FALSE(text_read_done); + + // Read some audio & video buffers to further verify seek completion. + CheckExpectedBuffers(audio_stream, "120 140"); + CheckExpectedBuffers(video_stream, "120 150"); + + EXPECT_FALSE(text_read_done); + + // Append text cues that start after the seek point and verify that + // they are returned by Read() calls. + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "125K 175K 225K"); + + message_loop_.RunUntilIdle(); + EXPECT_TRUE(text_read_done); + + // NOTE: we start at 175 here because the buffer at 125 was returned + // to the pending read initiated above. + CheckExpectedBuffers(text_stream, "175 225"); + + // Verify that audio & video streams contiue to return expected values. + CheckExpectedBuffers(audio_stream, "160 180"); + CheckExpectedBuffers(video_stream, "180 210"); +} + } // namespace media |