Commit 9730c054 authored by acolwell@chromium.org's avatar acolwell@chromium.org

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
parent 7a68a440
......@@ -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
......@@ -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_;
......
......@@ -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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment