diff options
Diffstat (limited to 'chromium/media/filters/chunk_demuxer_unittest.cc')
-rw-r--r-- | chromium/media/filters/chunk_demuxer_unittest.cc | 1297 |
1 files changed, 1018 insertions, 279 deletions
diff --git a/chromium/media/filters/chunk_demuxer_unittest.cc b/chromium/media/filters/chunk_demuxer_unittest.cc index 87c9f7074b6..2326de2de66 100644 --- a/chromium/media/filters/chunk_demuxer_unittest.cc +++ b/chromium/media/filters/chunk_demuxer_unittest.cc @@ -16,9 +16,9 @@ #include "media/base/test_data_util.h" #include "media/base/test_helpers.h" #include "media/filters/chunk_demuxer.h" -#include "media/webm/cluster_builder.h" -#include "media/webm/webm_constants.h" -#include "media/webm/webm_crypto_helpers.h" +#include "media/formats/webm/cluster_builder.h" +#include "media/formats/webm/webm_constants.h" +#include "media/formats/webm/webm_crypto_helpers.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::AnyNumber; @@ -32,47 +32,55 @@ using ::testing::_; namespace media { -static const uint8 kTracksHeader[] = { +const uint8 kTracksHeader[] = { 0x16, 0x54, 0xAE, 0x6B, // Tracks ID 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0) }; // WebM Block bytes that represent a VP8 keyframe. -static const uint8 kVP8Keyframe[] = { +const uint8 kVP8Keyframe[] = { 0x010, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0x00, 0x10, 0x00, 0x10, 0x00 }; // WebM Block bytes that represent a VP8 interframe. -static const uint8 kVP8Interframe[] = { 0x11, 0x00, 0x00 }; +const uint8 kVP8Interframe[] = { 0x11, 0x00, 0x00 }; -static const int kTracksHeaderSize = sizeof(kTracksHeader); -static const int kTracksSizeOffset = 4; +static const uint8 kCuesHeader[] = { + 0x1C, 0x53, 0xBB, 0x6B, // Cues ID + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cues(size = 0) +}; + +const int kTracksHeaderSize = sizeof(kTracksHeader); +const int kTracksSizeOffset = 4; // The size of TrackEntry element in test file "webm_vorbis_track_entry" starts // at index 1 and spans 8 bytes. -static const int kAudioTrackSizeOffset = 1; -static const int kAudioTrackSizeWidth = 8; -static const int kAudioTrackEntryHeaderSize = kAudioTrackSizeOffset + - kAudioTrackSizeWidth; +const int kAudioTrackSizeOffset = 1; +const int kAudioTrackSizeWidth = 8; +const int kAudioTrackEntryHeaderSize = + kAudioTrackSizeOffset + kAudioTrackSizeWidth; // The size of TrackEntry element in test file "webm_vp8_track_entry" starts at // index 1 and spans 8 bytes. -static const int kVideoTrackSizeOffset = 1; -static const int kVideoTrackSizeWidth = 8; -static const int kVideoTrackEntryHeaderSize = kVideoTrackSizeOffset + - kVideoTrackSizeWidth; - -static const int kVideoTrackNum = 1; -static const int kAudioTrackNum = 2; - -static const int kAudioBlockDuration = 23; -static const int kVideoBlockDuration = 33; -static const int kBlockSize = 10; - -static const char kSourceId[] = "SourceId"; -static const char kDefaultFirstClusterRange[] = "{ [0,46) }"; -static const int kDefaultFirstClusterEndTimestamp = 66; -static const int kDefaultSecondClusterEndTimestamp = 132; +const int kVideoTrackSizeOffset = 1; +const int kVideoTrackSizeWidth = 8; +const int kVideoTrackEntryHeaderSize = + kVideoTrackSizeOffset + kVideoTrackSizeWidth; + +const int kVideoTrackNum = 1; +const int kAudioTrackNum = 2; +const int kTextTrackNum = 3; +const int kAlternateTextTrackNum = 4; + +const int kAudioBlockDuration = 23; +const int kVideoBlockDuration = 33; +const int kTextBlockDuration = 100; +const int kBlockSize = 10; + +const char kSourceId[] = "SourceId"; +const char kDefaultFirstClusterRange[] = "{ [0,46) }"; +const int kDefaultFirstClusterEndTimestamp = 66; +const int kDefaultSecondClusterEndTimestamp = 132; base::TimeDelta kDefaultDuration() { return base::TimeDelta::FromMilliseconds(201224); @@ -82,7 +90,7 @@ base::TimeDelta kDefaultDuration() { // The data pointed by |buffer| should be at least 8 bytes long. // |number| should be in the range 0 <= number < 0x00FFFFFFFFFFFFFF. static void WriteInt64(uint8* buffer, int64 number) { - DCHECK(number >= 0 && number < GG_LONGLONG(0x00FFFFFFFFFFFFFF)); + DCHECK(number >= 0 && number < 0x00FFFFFFFFFFFFFFLL); buffer[0] = 0x01; int64 tmp = number; for (int i = 7; i > 0; i--) { @@ -128,7 +136,9 @@ static void OnSeekDone_OKExpected(bool* called, PipelineStatus status) { *called = true; } -class ChunkDemuxerTest : public testing::Test { +static void LogFunc(const std::string& str) { DVLOG(1) << str; } + +class ChunkDemuxerTest : public ::testing::Test { protected: enum CodecsIndex { AUDIO, @@ -150,7 +160,8 @@ class ChunkDemuxerTest : public testing::Test { return GenerateCluster(46, 66, 5); } - ChunkDemuxerTest() { + ChunkDemuxerTest() + : append_window_end_for_next_append_(kInfiniteDuration()) { CreateNewDemuxer(); } @@ -159,17 +170,44 @@ class ChunkDemuxerTest : public testing::Test { base::Bind(&ChunkDemuxerTest::DemuxerOpened, base::Unretained(this)); Demuxer::NeedKeyCB need_key_cb = base::Bind(&ChunkDemuxerTest::DemuxerNeedKey, base::Unretained(this)); - demuxer_.reset(new ChunkDemuxer(open_cb, need_key_cb, LogCB())); + demuxer_.reset( + new ChunkDemuxer(open_cb, need_key_cb, base::Bind(&LogFunc), true)); } virtual ~ChunkDemuxerTest() { ShutdownDemuxer(); } - void CreateInitSegment(bool has_audio, bool has_video, bool has_text, - bool is_audio_encrypted, bool is_video_encrypted, + void CreateInitSegment(int stream_flags, + bool is_audio_encrypted, + bool is_video_encrypted, scoped_ptr<uint8[]>* buffer, int* size) { + CreateInitSegmentInternal( + stream_flags, is_audio_encrypted, is_video_encrypted, buffer, false, + size); + } + + void CreateInitSegmentWithAlternateTextTrackNum(int stream_flags, + bool is_audio_encrypted, + bool is_video_encrypted, + scoped_ptr<uint8[]>* buffer, + int* size) { + DCHECK(stream_flags & HAS_TEXT); + CreateInitSegmentInternal( + stream_flags, is_audio_encrypted, is_video_encrypted, buffer, true, + size); + } + + void CreateInitSegmentInternal(int stream_flags, + bool is_audio_encrypted, + bool is_video_encrypted, + scoped_ptr<uint8[]>* buffer, + bool use_alternate_text_track_id, + 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; @@ -209,13 +247,18 @@ class ChunkDemuxerTest : public testing::Test { // // This is the track entry for a text track, // TrackEntry [AE], size=30 - // TrackNum [D7], size=1, val=3 - // TrackUID [73] [C5], size=1, value=3 + // TrackNum [D7], size=1, val=3 (or 4 if use_alternate_text_track_id) + // TrackUID [73] [C5], size=1, value=3 (must remain constant for same + // track, even if TrackNum changes) // TrackType [83], size=1, val=0x11 // CodecId [86], size=18, val="D_WEBVTT/SUBTITLES" - const char str[] = "\xAE\x9E\xD7\x81\x03\x73\xC5\x81\x03" - "\x83\x81\x11\x86\x92" - "D_WEBVTT/SUBTITLES"; + char str[] = "\xAE\x9E\xD7\x81\x03\x73\xC5\x81\x03" + "\x83\x81\x11\x86\x92" + "D_WEBVTT/SUBTITLES"; + DCHECK_EQ(str[4], kTextTrackNum); + if (use_alternate_text_track_id) + str[4] = kAlternateTextTrackNum; + const int len = strlen(str); DCHECK_EQ(len, 32); const uint8* const buf = reinterpret_cast<const uint8*>(str); @@ -281,11 +324,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; @@ -300,12 +344,20 @@ 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); } + ChunkDemuxer::Status AddIdForMp2tSource(const std::string& source_id) { + std::vector<std::string> codecs; + std::string type = "video/mp2t"; + codecs.push_back("mp4a.40.2"); + codecs.push_back("avc1.640028"); + return demuxer_->AddId(source_id, type, codecs); + } + void AppendData(const uint8* data, size_t length) { AppendData(kSourceId, data, length); } @@ -326,13 +378,17 @@ class ChunkDemuxerTest : public testing::Test { void AppendSingleStreamCluster(const std::string& source_id, int track_number, int timecode, int block_count) { int block_duration = 0; - switch(track_number) { + switch (track_number) { case kVideoTrackNum: block_duration = kVideoBlockDuration; break; case kAudioTrackNum: block_duration = kAudioBlockDuration; break; + case kTextTrackNum: // Fall-through. + case kAlternateTextTrackNum: + block_duration = kTextBlockDuration; + break; } ASSERT_NE(block_duration, 0); int end_timecode = timecode + block_count * block_duration; @@ -341,6 +397,12 @@ class ChunkDemuxerTest : public testing::Test { timecode, end_timecode, track_number, block_duration)); } + // |cluster_description| - A space delimited string of buffer info that + // is used to construct a cluster. Each buffer info is a timestamp in + // milliseconds and optionally followed by a 'K' to indicate that a buffer + // should be marked as a keyframe. For example "0K 30 60" should constuct + // a cluster with 3 blocks: a keyframe with timestamp 0 and 2 non-keyframes + // at 30ms and 60ms. void AppendSingleStreamCluster(const std::string& source_id, int track_number, const std::string& cluster_description) { std::vector<std::string> timestamps; @@ -362,8 +424,14 @@ class ChunkDemuxerTest : public testing::Test { if (i == 0) cb.SetClusterTimecode(timestamp_in_ms); - cb.AddSimpleBlock(track_number, timestamp_in_ms, block_flags, - &data[0], data.size()); + if (track_number == kTextTrackNum || + track_number == kAlternateTextTrackNum) { + cb.AddBlockGroup(track_number, timestamp_in_ms, kTextBlockDuration, + block_flags, &data[0], data.size()); + } else { + cb.AddSimpleBlock(track_number, timestamp_in_ms, block_flags, + &data[0], data.size()); + } } AppendCluster(source_id, cb.Finish()); } @@ -371,7 +439,11 @@ class ChunkDemuxerTest : public testing::Test { void AppendData(const std::string& source_id, const uint8* data, size_t length) { EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber()); - demuxer_->AppendData(source_id, data, length); + + demuxer_->AppendData(source_id, data, length, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[source_id]); } void AppendDataInPieces(const uint8* data, size_t length) { @@ -389,29 +461,22 @@ class ChunkDemuxerTest : public testing::Test { } } - void AppendInitSegment(bool has_audio, bool has_video) { - AppendInitSegmentWithSourceId(kSourceId, has_audio, has_video, false); - } - - void AppendInitSegmentText(bool has_audio, bool has_video) { - AppendInitSegmentWithSourceId(kSourceId, has_audio, has_video, true); + 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); @@ -448,21 +513,21 @@ class ChunkDemuxerTest : public testing::Test { expected_status); } - bool InitDemuxer(bool has_audio, bool has_video) { - return InitDemuxerWithEncryptionInfo(has_audio, has_video, false, - false, false); - } + enum StreamFlags { + HAS_AUDIO = 1 << 0, + HAS_VIDEO = 1 << 1, + HAS_TEXT = 1 << 2 + }; - bool InitDemuxerText(bool has_audio, bool has_video) { - return InitDemuxerWithEncryptionInfo(has_audio, has_video, true, - false, false); + bool InitDemuxer(int stream_flags) { + 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) @@ -472,11 +537,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; } @@ -488,13 +553,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; } @@ -511,30 +584,40 @@ class ChunkDemuxerTest : public testing::Test { // bear-640x360.webm VideoDecoderConfig returns 640x360 for its natural_size() // The resulting video stream returns data from each file for the following // time ranges. - // bear-320x240.webm : [0-501) [801-2737) + // bear-320x240.webm : [0-501) [801-2736) // bear-640x360.webm : [527-793) // // bear-320x240.webm AudioDecoderConfig returns 3863 for its extra_data_size() // bear-640x360.webm AudioDecoderConfig returns 3935 for its extra_data_size() // The resulting audio stream returns data from each file for the following // time ranges. - // bear-320x240.webm : [0-524) [779-2737) + // bear-320x240.webm : [0-524) [779-2736) // bear-640x360.webm : [527-759) bool InitDemuxerWithConfigChangeData() { scoped_refptr<DecoderBuffer> bear1 = ReadTestDataFile("bear-320x240.webm"); scoped_refptr<DecoderBuffer> bear2 = ReadTestDataFile("bear-640x360.webm"); EXPECT_CALL(*this, DemuxerOpened()); + demuxer_->Initialize( &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. + // TODO(wolenetz/acolwell): Remove this extra SetDuration expectation once + // the files are fixed to have the correct duration in their init segments, + // and the CreateInitDoneCB() call, above, is fixed to used that duration. + // See http://crbug.com/354284. + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2746))); AppendData(bear1->data(), bear1->data_size()); - CheckExpectedRanges(kSourceId, "{ [0,2737) }"); + // Last audio frame has timestamp 2721 and duration 24 (estimated from max + // seen so far for audio track). + // Last video frame has timestamp 2703 and duration 33 (from TrackEntry + // DefaultDuration for video track). + CheckExpectedRanges(kSourceId, "{ [0,2736) }"); // Append initialization segment for bear2. // Note: Offsets here and below are derived from @@ -546,13 +629,13 @@ class ChunkDemuxerTest : public testing::Test { // Append a media segment that goes from [0.527000, 1.014000). AppendData(bear2->data() + 55290, 18785); - CheckExpectedRanges(kSourceId, "{ [0,1028) [1201,2737) }"); + CheckExpectedRanges(kSourceId, "{ [0,1027) [1201,2736) }"); // Append initialization segment for bear1 & fill gap with [779-1197) // segment. AppendData(bear1->data(), 4370); AppendData(bear1->data() + 72737, 28183); - CheckExpectedRanges(kSourceId, "{ [0,2737) }"); + CheckExpectedRanges(kSourceId, "{ [0,2736) }"); MarkEndOfStream(PIPELINE_OK); return true; @@ -586,6 +669,13 @@ class ChunkDemuxerTest : public testing::Test { scoped_ptr<Cluster> GenerateCluster(int first_audio_timecode, int first_video_timecode, int block_count) { + return GenerateCluster(first_audio_timecode, first_video_timecode, + block_count, false); + } + scoped_ptr<Cluster> GenerateCluster(int first_audio_timecode, + int first_video_timecode, + int block_count, + bool unknown_size) { CHECK_GT(block_count, 0); int size = 10; @@ -635,7 +725,7 @@ class ChunkDemuxerTest : public testing::Test { kWebMFlagKeyframe, data.get(), size); } - return cb.Finish(); + return unknown_size ? cb.FinishWithUnknownSize() : cb.Finish(); } scoped_ptr<Cluster> GenerateSingleStreamCluster(int timecode, @@ -650,14 +740,12 @@ class ChunkDemuxerTest : public testing::Test { cb.SetClusterTimecode(timecode); // Create simple blocks for everything except the last block. - for (int i = 0; timecode < (end_timecode - block_duration); i++) { + while (timecode < (end_timecode - block_duration)) { cb.AddSimpleBlock(track_number, timecode, kWebMFlagKeyframe, &data[0], data.size()); timecode += block_duration; } - // Make the last block a BlockGroup so that it doesn't get delayed by the - // block duration calculation logic. if (track_number == kVideoTrackNum) { AddVideoBlockGroup(&cb, track_number, timecode, block_duration, kWebMFlagKeyframe); @@ -665,6 +753,7 @@ class ChunkDemuxerTest : public testing::Test { cb.AddBlockGroup(track_number, timecode, block_duration, kWebMFlagKeyframe, &data[0], data.size()); } + return cb.Finish(); } @@ -754,7 +843,7 @@ class ChunkDemuxerTest : public testing::Test { << r.end(i).InMilliseconds() << ") "; } ss << "}"; - EXPECT_EQ(ss.str(), expected); + EXPECT_EQ(expected, ss.str()); } MOCK_METHOD2(ReadDone, void(DemuxerStream::Status status, @@ -812,7 +901,10 @@ class ChunkDemuxerTest : public testing::Test { base::SplitString(expected, ' ', ×tamps); std::stringstream ss; for (size_t i = 0; i < timestamps.size(); ++i) { - DemuxerStream::Status status; + // Initialize status to kAborted since it's possible for Read() to return + // without calling StoreStatusAndBuffer() if it doesn't have any buffers + // left to return. + DemuxerStream::Status status = DemuxerStream::kAborted; scoped_refptr<DecoderBuffer> buffer; stream->Read(base::Bind(&ChunkDemuxerTest::StoreStatusAndBuffer, base::Unretained(this), &status, &buffer)); @@ -823,6 +915,13 @@ class ChunkDemuxerTest : public testing::Test { if (i > 0) ss << " "; ss << buffer->timestamp().InMilliseconds(); + + // Handle preroll buffers. + if (EndsWith(timestamps[i], "P", true)) { + ASSERT_EQ(kInfiniteDuration(), buffer->discard_padding().first); + ASSERT_EQ(base::TimeDelta(), buffer->discard_padding().second); + ss << "P"; + } } EXPECT_EQ(expected, ss.str()); } @@ -844,18 +943,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. @@ -915,11 +1014,27 @@ class ChunkDemuxerTest : public testing::Test { message_loop_.RunUntilIdle(); } + bool SetTimestampOffset(const std::string& id, + base::TimeDelta timestamp_offset) { + if (demuxer_->IsParsingMediaSegment(id)) + return false; + + timestamp_offset_map_[id] = timestamp_offset; + return true; + } + base::MessageLoop message_loop_; MockDemuxerHost host_; scoped_ptr<ChunkDemuxer> demuxer_; + base::TimeDelta append_window_start_for_next_append_; + base::TimeDelta append_window_end_for_next_append_; + + // Map of source id to timestamp offset to use for the next AppendData() + // operation for that source id. + std::map<std::string, base::TimeDelta> timestamp_offset_map_; + private: DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest); }; @@ -949,8 +1064,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) { @@ -966,6 +1088,8 @@ TEST_F(ChunkDemuxerTest, Init) { EXPECT_EQ(kSampleFormatPlanarF32, config.sample_format()); EXPECT_EQ(is_audio_encrypted, audio_stream->audio_decoder_config().is_encrypted()); + EXPECT_TRUE(static_cast<ChunkDemuxerStream*>(audio_stream) + ->supports_partial_append_window_trimming()); } else { EXPECT_FALSE(audio_stream); } @@ -975,6 +1099,8 @@ TEST_F(ChunkDemuxerTest, Init) { EXPECT_TRUE(video_stream); EXPECT_EQ(is_video_encrypted, video_stream->video_decoder_config().is_encrypted()); + EXPECT_FALSE(static_cast<ChunkDemuxerStream*>(video_stream) + ->supports_partial_append_window_trimming()); } else { EXPECT_FALSE(video_stream); } @@ -984,6 +1110,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. @@ -997,15 +1125,24 @@ TEST_F(ChunkDemuxerTest, InitText) { DemuxerStream* text_stream = NULL; TextTrackConfig text_config; - EXPECT_CALL(host_, AddTextStream(_,_)) + EXPECT_CALL(host_, AddTextStream(_, _)) .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()); + EXPECT_FALSE(static_cast<ChunkDemuxerStream*>(text_stream) + ->supports_partial_append_window_trimming()); DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); if (has_audio) { @@ -1021,6 +1158,8 @@ TEST_F(ChunkDemuxerTest, InitText) { EXPECT_EQ(kSampleFormatPlanarF32, config.sample_format()); EXPECT_EQ(is_audio_encrypted, audio_stream->audio_decoder_config().is_encrypted()); + EXPECT_TRUE(static_cast<ChunkDemuxerStream*>(audio_stream) + ->supports_partial_append_window_trimming()); } else { EXPECT_FALSE(audio_stream); } @@ -1030,6 +1169,8 @@ TEST_F(ChunkDemuxerTest, InitText) { EXPECT_TRUE(video_stream); EXPECT_EQ(is_video_encrypted, video_stream->video_decoder_config().is_encrypted()); + EXPECT_FALSE(static_cast<ChunkDemuxerStream*>(video_stream) + ->supports_partial_append_window_trimming()); } else { EXPECT_FALSE(video_stream); } @@ -1039,39 +1180,153 @@ TEST_F(ChunkDemuxerTest, InitText) { } } +TEST_F(ChunkDemuxerTest, SingleTextTrackIdChange) { + // Test with 1 video stream, 1 audio, and 1 text stream. Send a second init + // segment in which the text track ID changes. Verify appended buffers before + // and after the second init segment map to the same underlying track buffers. + CreateNewDemuxer(); + DemuxerStream* text_stream = NULL; + TextTrackConfig text_config; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(DoAll(SaveArg<0>(&text_stream), + SaveArg<1>(&text_config))); + ASSERT_TRUE(InitDemuxerWithEncryptionInfo( + HAS_TEXT | HAS_AUDIO | HAS_VIDEO, false, false)); + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + ASSERT_TRUE(audio_stream); + ASSERT_TRUE(video_stream); + ASSERT_TRUE(text_stream); + + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0K 23K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0K 30"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "10K"); + CheckExpectedRanges(kSourceId, "{ [0,46) }"); + + scoped_ptr<uint8[]> info_tracks; + int info_tracks_size = 0; + CreateInitSegmentWithAlternateTextTrackNum(HAS_TEXT | HAS_AUDIO | HAS_VIDEO, + false, false, + &info_tracks, &info_tracks_size); + demuxer_->AppendData(kSourceId, info_tracks.get(), info_tracks_size, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); + + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "46K 69K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "60K"); + AppendSingleStreamCluster(kSourceId, kAlternateTextTrackNum, "45K"); + + CheckExpectedRanges(kSourceId, "{ [0,92) }"); + CheckExpectedBuffers(audio_stream, "0 23 46 69"); + CheckExpectedBuffers(video_stream, "0 30 60"); + CheckExpectedBuffers(text_stream, "10 45"); + + ShutdownDemuxer(); +} + +TEST_F(ChunkDemuxerTest, InitSegmentSetsNeedRandomAccessPointFlag) { + // Tests that non-keyframes following an init segment are allowed + // and dropped, as expected if the initialization segment received + // algorithm correctly sets the needs random access point flag to true for all + // track buffers. Note that the first initialization segment is insufficient + // to fully test this since needs random access point flag initializes to + // true. + CreateNewDemuxer(); + DemuxerStream* text_stream = NULL; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxerWithEncryptionInfo( + HAS_TEXT | HAS_AUDIO | HAS_VIDEO, false, false)); + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + ASSERT_TRUE(audio_stream && video_stream && text_stream); + + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0 23K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0 30K"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "0 40K"); + CheckExpectedRanges(kSourceId, "{ [30,46) }"); + + AppendInitSegment(HAS_TEXT | HAS_AUDIO | HAS_VIDEO); + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "46 69K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "60 90K"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "80 90K"); + CheckExpectedRanges(kSourceId, "{ [30,92) }"); + + CheckExpectedBuffers(audio_stream, "23 69"); + CheckExpectedBuffers(video_stream, "30 90"); + + // WebM parser marks all text buffers as keyframes. + CheckExpectedBuffers(text_stream, "0 40 80 90"); +} + // 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", true, false, false); + AppendInitSegmentWithSourceId("audio", HAS_AUDIO); + + 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(_,_)) + 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 // arrives. TEST_F(ChunkDemuxerTest, AppendDataAfterSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); InSequence s; @@ -1093,7 +1348,7 @@ TEST_F(ChunkDemuxerTest, AppendDataAfterSeek) { // Test that parsing errors are handled for clusters appended after init. TEST_F(ChunkDemuxerTest, ErrorWhileParsingClusterAfterInit) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE)); @@ -1104,7 +1359,7 @@ TEST_F(ChunkDemuxerTest, ErrorWhileParsingClusterAfterInit) { // is in the middle of cluster. This is to verify that the parser // does not reset itself on a seek. TEST_F(ChunkDemuxerTest, SeekWhileParsingCluster) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); InSequence s; @@ -1120,10 +1375,6 @@ TEST_F(ChunkDemuxerTest, SeekWhileParsingCluster) { ExpectRead(DemuxerStream::AUDIO, 0); ExpectRead(DemuxerStream::VIDEO, 0); ExpectRead(DemuxerStream::AUDIO, kAudioBlockDuration); - // Note: We skip trying to read a video buffer here because computing - // the duration for this block relies on successfully parsing the last block - // in the cluster the cluster. - ExpectRead(DemuxerStream::AUDIO, 2 * kAudioBlockDuration); Seek(base::TimeDelta::FromSeconds(5)); @@ -1140,15 +1391,17 @@ 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); + demuxer_->AppendData(kSourceId, info_tracks.get(), info_tracks_size, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); } // Make sure Read() callbacks are dispatched with the proper data. TEST_F(ChunkDemuxerTest, Read) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); @@ -1166,7 +1419,7 @@ TEST_F(ChunkDemuxerTest, Read) { } TEST_F(ChunkDemuxerTest, OutOfOrderClusters) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); AppendCluster(GenerateCluster(10, 4)); @@ -1176,11 +1429,14 @@ TEST_F(ChunkDemuxerTest, OutOfOrderClusters) { // Verify that AppendData() can still accept more data. scoped_ptr<Cluster> cluster_c(GenerateCluster(45, 2)); - demuxer_->AppendData(kSourceId, cluster_c->data(), cluster_c->size()); + demuxer_->AppendData(kSourceId, cluster_c->data(), cluster_c->size(), + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); } TEST_F(ChunkDemuxerTest, NonMonotonicButAboveClusterTimecode) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); ClusterBuilder cb; @@ -1198,11 +1454,14 @@ TEST_F(ChunkDemuxerTest, NonMonotonicButAboveClusterTimecode) { // Verify that AppendData() ignores data after the error. scoped_ptr<Cluster> cluster_b(GenerateCluster(20, 2)); - demuxer_->AppendData(kSourceId, cluster_b->data(), cluster_b->size()); + demuxer_->AppendData(kSourceId, cluster_b->data(), cluster_b->size(), + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); } TEST_F(ChunkDemuxerTest, BackwardsAndBeforeClusterTimecode) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); ClusterBuilder cb; @@ -1220,12 +1479,15 @@ TEST_F(ChunkDemuxerTest, BackwardsAndBeforeClusterTimecode) { // Verify that AppendData() ignores data after the error. scoped_ptr<Cluster> cluster_b(GenerateCluster(6, 2)); - demuxer_->AppendData(kSourceId, cluster_b->data(), cluster_b->size()); + demuxer_->AppendData(kSourceId, cluster_b->data(), cluster_b->size(), + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); } TEST_F(ChunkDemuxerTest, PerStreamMonotonicallyIncreasingTimestamps) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); ClusterBuilder cb; @@ -1278,7 +1540,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWithNoAppend) { } TEST_F(ChunkDemuxerTest, EndOfStreamWithNoMediaAppend) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); CheckExpectedRanges("{ }"); MarkEndOfStream(PIPELINE_OK); @@ -1286,7 +1548,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWithNoMediaAppend) { } TEST_F(ChunkDemuxerTest, DecodeErrorEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); CheckExpectedRanges(kDefaultFirstClusterRange); @@ -1297,7 +1559,7 @@ TEST_F(ChunkDemuxerTest, DecodeErrorEndOfStream) { } TEST_F(ChunkDemuxerTest, NetworkErrorEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); CheckExpectedRanges(kDefaultFirstClusterRange); @@ -1357,7 +1619,7 @@ class EndOfStreamHelper { // Make sure that all pending reads that we don't have media data for get an // "end of stream" buffer when MarkEndOfStream() is called. TEST_F(ChunkDemuxerTest, EndOfStreamWithPendingReads) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(GenerateCluster(0, 2)); @@ -1392,7 +1654,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWithPendingReads) { // Make sure that all Read() calls after we get an MarkEndOfStream() // call return an "end of stream" buffer. TEST_F(ChunkDemuxerTest, ReadsAfterEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(GenerateCluster(0, 2)); @@ -1431,7 +1693,7 @@ TEST_F(ChunkDemuxerTest, ReadsAfterEndOfStream) { } TEST_F(ChunkDemuxerTest, EndOfStreamDuringCanceledSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(0, 10); EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(138))); @@ -1460,6 +1722,45 @@ TEST_F(ChunkDemuxerTest, EndOfStreamDuringCanceledSeek) { ASSERT_EQ(status, DemuxerStream::kOk); } +// Verify buffered range change behavior for audio/video/text tracks. +TEST_F(ChunkDemuxerTest, EndOfStreamRangeChanges) { + DemuxerStream* text_stream = NULL; + + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO | HAS_TEXT)); + + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0K 33"); + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0K 23K"); + + // Check expected ranges and verify that an empty text track does not + // affect the expected ranges. + CheckExpectedRanges(kSourceId, "{ [0,46) }"); + + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(66))); + MarkEndOfStream(PIPELINE_OK); + + // Check expected ranges and verify that an empty text track does not + // affect the expected ranges. + CheckExpectedRanges(kSourceId, "{ [0,66) }"); + + // Unmark end of stream state and verify that the ranges return to + // their pre-"end of stream" values. + demuxer_->UnmarkEndOfStream(); + CheckExpectedRanges(kSourceId, "{ [0,46) }"); + + // Add text track data and verify that the buffered ranges don't change + // since the intersection of all the tracks doesn't change. + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(200))); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "0K 100K"); + CheckExpectedRanges(kSourceId, "{ [0,46) }"); + + // Mark end of stream and verify that text track data is reflected in + // the new range. + MarkEndOfStream(PIPELINE_OK); + CheckExpectedRanges(kSourceId, "{ [0,200) }"); +} + // Make sure AppendData() will accept elements that span multiple calls. TEST_F(ChunkDemuxerTest, AppendingInPieces) { EXPECT_CALL(*this, DemuxerOpened()); @@ -1470,7 +1771,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()); @@ -1503,6 +1804,11 @@ TEST_F(ChunkDemuxerTest, WebMFile_AudioAndVideo) { {kSkip, kSkip}, }; + // TODO(wolenetz/acolwell): Remove this SetDuration expectation and update the + // ParseWebMFile() call's expected duration, below, once the file is fixed to + // have the correct duration in the init segment. See http://crbug.com/354284. + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2746))); + ASSERT_TRUE(ParseWebMFile("bear-320x240.webm", buffer_timestamps, base::TimeDelta::FromMilliseconds(2744))); } @@ -1531,9 +1837,14 @@ TEST_F(ChunkDemuxerTest, WebMFile_AudioOnly) { {kSkip, kSkip}, }; + // TODO(wolenetz/acolwell): Remove this SetDuration expectation and update the + // ParseWebMFile() call's expected duration, below, once the file is fixed to + // have the correct duration in the init segment. See http://crbug.com/354284. + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2746))); + ASSERT_TRUE(ParseWebMFile("bear-320x240-audio-only.webm", buffer_timestamps, base::TimeDelta::FromMilliseconds(2744), - true, false)); + HAS_AUDIO)); } TEST_F(ChunkDemuxerTest, WebMFile_VideoOnly) { @@ -1546,9 +1857,14 @@ TEST_F(ChunkDemuxerTest, WebMFile_VideoOnly) { {kSkip, kSkip}, }; + // TODO(wolenetz/acolwell): Remove this SetDuration expectation and update the + // ParseWebMFile() call's expected duration, below, once the file is fixed to + // have the correct duration in the init segment. See http://crbug.com/354284. + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(2736))); + ASSERT_TRUE(ParseWebMFile("bear-320x240-video-only.webm", buffer_timestamps, base::TimeDelta::FromMilliseconds(2703), - false, true)); + HAS_VIDEO)); } TEST_F(ChunkDemuxerTest, WebMFile_AltRefFrames) { @@ -1567,7 +1883,7 @@ TEST_F(ChunkDemuxerTest, WebMFile_AltRefFrames) { // Verify that we output buffers before the entire cluster has been parsed. TEST_F(ChunkDemuxerTest, IncrementalClusterParsing) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendEmptyCluster(0); scoped_ptr<Cluster> cluster(GenerateCluster(0, 6)); @@ -1585,27 +1901,17 @@ TEST_F(ChunkDemuxerTest, IncrementalClusterParsing) { EXPECT_FALSE(audio_read_done); EXPECT_FALSE(video_read_done); - // Append data one byte at a time until the audio read completes. + // Append data one byte at a time until one or both reads complete. int i = 0; - for (; i < cluster->size() && !audio_read_done; ++i) { + for (; i < cluster->size() && !(audio_read_done || video_read_done); ++i) { AppendData(cluster->data() + i, 1); message_loop_.RunUntilIdle(); } - EXPECT_TRUE(audio_read_done); - EXPECT_FALSE(video_read_done); + EXPECT_TRUE(audio_read_done || video_read_done); EXPECT_GT(i, 0); EXPECT_LT(i, cluster->size()); - // Append data one byte at a time until the video read completes. - for (; i < cluster->size() && !video_read_done; ++i) { - AppendData(cluster->data() + i, 1); - message_loop_.RunUntilIdle(); - } - - EXPECT_TRUE(video_read_done); - EXPECT_LT(i, cluster->size()); - audio_read_done = false; video_read_done = false; ReadAudio(base::Bind(&OnReadDone, @@ -1638,7 +1944,10 @@ TEST_F(ChunkDemuxerTest, ParseErrorDuringInit) { ASSERT_EQ(AddId(), ChunkDemuxer::kOk); uint8 tmp = 0; - demuxer_->AppendData(kSourceId, &tmp, 1); + demuxer_->AppendData(kSourceId, &tmp, 1, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); } TEST_F(ChunkDemuxerTest, AVHeadersWithAudioOnlyType) { @@ -1652,7 +1961,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) { @@ -1666,16 +1975,16 @@ 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) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); // Append another identical initialization segment. - AppendInitSegment(true, true); + AppendInitSegment(HAS_AUDIO | HAS_VIDEO); AppendCluster(kDefaultSecondCluster()); @@ -1703,7 +2012,7 @@ TEST_F(ChunkDemuxerTest, AddSeparateSourcesForAudioAndVideoText) { std::string audio_id = "audio1"; std::string video_id = "video1"; - EXPECT_CALL(host_, AddTextStream(_,_)) + EXPECT_CALL(host_, AddTextStream(_, _)) .Times(Exactly(2)); ASSERT_TRUE(InitDemuxerAudioAndVideoSourcesText(audio_id, video_id, true)); @@ -1724,15 +2033,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. @@ -1767,15 +2076,15 @@ 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) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Append cluster at the beginning of the stream. AppendCluster(GenerateCluster(0, 4)); @@ -1805,7 +2114,7 @@ TEST_F(ChunkDemuxerTest, SeekCanceled) { } TEST_F(ChunkDemuxerTest, SeekCanceledWhileWaitingForSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Append cluster at the beginning of the stream. AppendCluster(GenerateCluster(0, 4)); @@ -1891,7 +2200,7 @@ TEST_F(ChunkDemuxerTest, SeekAudioAndVideoSources) { // This scenario might be useful if seeking past the end of stream // of either audio or video (or both). TEST_F(ChunkDemuxerTest, EndOfStreamAfterPastEosSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(GenerateSingleStreamCluster(0, 120, kAudioTrackNum, 10)); AppendCluster(GenerateSingleStreamCluster(0, 100, kVideoTrackNum, 5)); @@ -1920,7 +2229,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamAfterPastEosSeek) { // Test that EndOfStream is ignored if coming during a pending seek // whose seek time is before some existing ranges. TEST_F(ChunkDemuxerTest, EndOfStreamDuringPendingSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(GenerateSingleStreamCluster(0, 120, kAudioTrackNum, 10)); AppendCluster(GenerateSingleStreamCluster(0, 100, kVideoTrackNum, 5)); @@ -1960,8 +2269,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( @@ -1982,8 +2291,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( @@ -1999,7 +2308,7 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_VideoIdOnly) { } TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioVideo) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Audio: 0 -> 23 // Video: 0 -> 33 @@ -2055,25 +2364,93 @@ TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioVideo) { CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }"); } +TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioVideoText) { + EXPECT_CALL(host_, AddTextStream(_, _)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO | HAS_TEXT)); + + // Append audio & video data + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0K 23"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0K 33"); + + // Verify that a text track with no cues does not result in an empty buffered + // range. + CheckExpectedRanges("{ [0,46) }"); + + // Add some text cues. + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "0K 100K"); + + // Verify that the new cues did not affect the buffered ranges. + CheckExpectedRanges("{ [0,46) }"); + + // Remove the buffered range. + demuxer_->Remove(kSourceId, base::TimeDelta(), + base::TimeDelta::FromMilliseconds(46)); + CheckExpectedRanges("{ }"); +} + // Once MarkEndOfStream() is called, GetBufferedRanges should not cut off any // over-hanging tails at the end of the ranges as this is likely due to block // duration differences. TEST_F(ChunkDemuxerTest, GetBufferedRanges_EndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0K 23K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0K 33"); + + CheckExpectedRanges("{ [0,46) }"); + + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(66))); + MarkEndOfStream(PIPELINE_OK); + + // Verify that the range extends to the end of the video data. + CheckExpectedRanges("{ [0,66) }"); + + // Verify that the range reverts to the intersection when end of stream + // has been cancelled. + demuxer_->UnmarkEndOfStream(); + CheckExpectedRanges("{ [0,46) }"); + + // Append and remove data so that the 2 streams' end ranges do not overlap. + + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(246))); + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(398))); + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "200K 223K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "200K 233 266 299 332K 365"); + + // At this point, the per-stream ranges are as follows: + // Audio: [0,46) [200,246) + // Video: [0,66) [200,398) + CheckExpectedRanges("{ [0,46) [200,246) }"); + + demuxer_->Remove(kSourceId, base::TimeDelta::FromMilliseconds(200), + base::TimeDelta::FromMilliseconds(300)); - AppendCluster(GenerateSingleStreamCluster(0, 90, kAudioTrackNum, 90)); - AppendCluster(GenerateSingleStreamCluster(0, 100, kVideoTrackNum, 100)); + // At this point, the per-stream ranges are as follows: + // Audio: [0,46) + // Video: [0,66) [332,398) + CheckExpectedRanges("{ [0,46) }"); - CheckExpectedRanges("{ [0,90) }"); + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "200K 223K"); + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "200K 233"); + + // At this point, the per-stream ranges are as follows: + // Audio: [0,46) [200,246) + // Video: [0,66) [200,266) [332,398) + // NOTE: The last range on each stream do not overlap in time. + CheckExpectedRanges("{ [0,46) [200,246) }"); - EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(100))); MarkEndOfStream(PIPELINE_OK); - CheckExpectedRanges("{ [0,100) }"); + // NOTE: The last range on each stream gets extended to the highest + // end timestamp according to the spec. The last audio range gets extended + // from [200,246) to [200,398) which is why the intersection results in the + // middle range getting larger AND the new range appearing. + CheckExpectedRanges("{ [0,46) [200,266) [332,398) }"); } TEST_F(ChunkDemuxerTest, DifferentStreamTimecodes) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Create a cluster where the video timecode begins 25ms after the audio. AppendCluster(GenerateCluster(0, 25, 8)); @@ -2130,7 +2507,7 @@ TEST_F(ChunkDemuxerTest, DifferentStreamTimecodesOutOfRange) { } TEST_F(ChunkDemuxerTest, ClusterWithNoBuffers) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Generate and append an empty cluster beginning at 0. AppendEmptyCluster(0); @@ -2184,7 +2561,7 @@ TEST_F(ChunkDemuxerTest, CodecIDsThatAreNotRFC6381Compliant) { } TEST_F(ChunkDemuxerTest, EndOfStreamStillSetAfterSeek) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); EXPECT_CALL(host_, SetDuration(_)) .Times(AnyNumber()); @@ -2223,8 +2600,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", "{ }"); @@ -2235,7 +2612,7 @@ TEST_F(ChunkDemuxerTest, GetBufferedRangesBeforeInitSegment) { TEST_F(ChunkDemuxerTest, EndOfStreamDuringSeek) { InSequence s; - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); @@ -2322,8 +2699,9 @@ TEST_F(ChunkDemuxerTest, ConfigChange_Audio) { ExpectRead(DemuxerStream::AUDIO, 0); + // The first config change seen is from a splice frame representing an overlap + // of buffer from config 1 by buffers from config 2. ReadUntilNotOkOrEndOfStream(DemuxerStream::AUDIO, &status, &last_timestamp); - ASSERT_EQ(status, DemuxerStream::kConfigChanged); EXPECT_EQ(last_timestamp.InMilliseconds(), 524); @@ -2333,22 +2711,18 @@ TEST_F(ChunkDemuxerTest, ConfigChange_Audio) { EXPECT_EQ(audio_config_2.samples_per_second(), 44100); EXPECT_EQ(audio_config_2.extra_data_size(), 3935u); - ExpectRead(DemuxerStream::AUDIO, 527); - - // Read until the next config change. + // The next config change is from a splice frame representing an overlap of + // buffers from config 2 by buffers from config 1. ReadUntilNotOkOrEndOfStream(DemuxerStream::AUDIO, &status, &last_timestamp); ASSERT_EQ(status, DemuxerStream::kConfigChanged); - EXPECT_EQ(last_timestamp.InMilliseconds(), 759); - - // Get the new config and verify that it matches the first one. + EXPECT_EQ(last_timestamp.InMilliseconds(), 782); ASSERT_TRUE(audio_config_1.Matches(audio->audio_decoder_config())); - ExpectRead(DemuxerStream::AUDIO, 779); - // Read until the end of the stream just to make sure there aren't any other // config changes. ReadUntilNotOkOrEndOfStream(DemuxerStream::AUDIO, &status, &last_timestamp); ASSERT_EQ(status, DemuxerStream::kOk); + EXPECT_EQ(last_timestamp.InMilliseconds(), 2744); } TEST_F(ChunkDemuxerTest, ConfigChange_Seek) { @@ -2399,10 +2773,9 @@ TEST_F(ChunkDemuxerTest, ConfigChange_Seek) { } TEST_F(ChunkDemuxerTest, TimestampPositiveOffset) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); - ASSERT_TRUE(demuxer_->SetTimestampOffset( - kSourceId, base::TimeDelta::FromSeconds(30))); + ASSERT_TRUE(SetTimestampOffset(kSourceId, base::TimeDelta::FromSeconds(30))); AppendCluster(GenerateCluster(0, 2)); Seek(base::TimeDelta::FromMilliseconds(30000)); @@ -2411,10 +2784,9 @@ TEST_F(ChunkDemuxerTest, TimestampPositiveOffset) { } TEST_F(ChunkDemuxerTest, TimestampNegativeOffset) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); - ASSERT_TRUE(demuxer_->SetTimestampOffset( - kSourceId, base::TimeDelta::FromSeconds(-1))); + ASSERT_TRUE(SetTimestampOffset(kSourceId, base::TimeDelta::FromSeconds(-1))); AppendCluster(GenerateCluster(1000, 2)); GenerateExpectedReads(0, 2); @@ -2425,9 +2797,9 @@ TEST_F(ChunkDemuxerTest, TimestampOffsetSeparateStreams) { std::string video_id = "video1"; ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id)); - ASSERT_TRUE(demuxer_->SetTimestampOffset( + ASSERT_TRUE(SetTimestampOffset( audio_id, base::TimeDelta::FromMilliseconds(-2500))); - ASSERT_TRUE(demuxer_->SetTimestampOffset( + ASSERT_TRUE(SetTimestampOffset( video_id, base::TimeDelta::FromMilliseconds(-2500))); AppendCluster(audio_id, GenerateSingleStreamCluster(2500, 2500 + kAudioBlockDuration * 4, kAudioTrackNum, kAudioBlockDuration)); @@ -2438,9 +2810,9 @@ TEST_F(ChunkDemuxerTest, TimestampOffsetSeparateStreams) { Seek(base::TimeDelta::FromMilliseconds(27300)); - ASSERT_TRUE(demuxer_->SetTimestampOffset( + ASSERT_TRUE(SetTimestampOffset( audio_id, base::TimeDelta::FromMilliseconds(27300))); - ASSERT_TRUE(demuxer_->SetTimestampOffset( + ASSERT_TRUE(SetTimestampOffset( video_id, base::TimeDelta::FromMilliseconds(27300))); AppendCluster(audio_id, GenerateSingleStreamCluster( 0, kAudioBlockDuration * 4, kAudioTrackNum, kAudioBlockDuration)); @@ -2450,27 +2822,114 @@ TEST_F(ChunkDemuxerTest, TimestampOffsetSeparateStreams) { GenerateAudioStreamExpectedReads(27300, 4); } -TEST_F(ChunkDemuxerTest, TimestampOffsetMidParse) { - ASSERT_TRUE(InitDemuxer(true, true)); +TEST_F(ChunkDemuxerTest, IsParsingMediaSegmentMidMediaSegment) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); scoped_ptr<Cluster> cluster = GenerateCluster(0, 2); // Append only part of the cluster data. AppendData(cluster->data(), cluster->size() - 13); - // Setting a timestamp should fail because we're in the middle of a cluster. - ASSERT_FALSE(demuxer_->SetTimestampOffset( - kSourceId, base::TimeDelta::FromSeconds(25))); + // Confirm we're in the middle of parsing a media segment. + ASSERT_TRUE(demuxer_->IsParsingMediaSegment(kSourceId)); + + demuxer_->Abort(kSourceId, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); + + // After Abort(), parsing should no longer be in the middle of a media + // segment. + ASSERT_FALSE(demuxer_->IsParsingMediaSegment(kSourceId)); +} + +#if defined(USE_PROPRIETARY_CODECS) +#if defined(ENABLE_MPEG2TS_STREAM_PARSER) +TEST_F(ChunkDemuxerTest, EmitBuffersDuringAbort) { + EXPECT_CALL(*this, DemuxerOpened()); + demuxer_->Initialize( + &host_, CreateInitDoneCB(kInfiniteDuration(), PIPELINE_OK), true); + EXPECT_EQ(ChunkDemuxer::kOk, AddIdForMp2tSource(kSourceId)); + + // For info: + // DTS/PTS derived using dvbsnoop -s ts -if bear-1280x720.ts -tssubdecode + // Video: first PES: + // PTS: 126912 (0x0001efc0) [= 90 kHz-Timestamp: 0:00:01.4101] + // DTS: 123909 (0x0001e405) [= 90 kHz-Timestamp: 0:00:01.3767] + // Audio: first PES: + // PTS: 126000 (0x0001ec30) [= 90 kHz-Timestamp: 0:00:01.4000] + // DTS: 123910 (0x0001e406) [= 90 kHz-Timestamp: 0:00:01.3767] + // Video: last PES: + // PTS: 370155 (0x0005a5eb) [= 90 kHz-Timestamp: 0:00:04.1128] + // DTS: 367152 (0x00059a30) [= 90 kHz-Timestamp: 0:00:04.0794] + // Audio: last PES: + // PTS: 353788 (0x000565fc) [= 90 kHz-Timestamp: 0:00:03.9309] + + scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("bear-1280x720.ts"); + AppendData(kSourceId, buffer->data(), buffer->data_size()); + + // Confirm we're in the middle of parsing a media segment. + ASSERT_TRUE(demuxer_->IsParsingMediaSegment(kSourceId)); + + // Abort on the Mpeg2 TS parser triggers the emission of the last video + // buffer which is pending in the stream parser. + Ranges<base::TimeDelta> range_before_abort = + demuxer_->GetBufferedRanges(kSourceId); + demuxer_->Abort(kSourceId, + append_window_start_for_next_append_, + append_window_end_for_next_append_, + ×tamp_offset_map_[kSourceId]); + Ranges<base::TimeDelta> range_after_abort = + demuxer_->GetBufferedRanges(kSourceId); + + ASSERT_EQ(range_before_abort.size(), 1u); + ASSERT_EQ(range_after_abort.size(), 1u); + EXPECT_EQ(range_after_abort.start(0), range_before_abort.start(0)); + EXPECT_GT(range_after_abort.end(0), range_before_abort.end(0)); +} +#endif +#endif + +TEST_F(ChunkDemuxerTest, WebMIsParsingMediaSegmentDetection) { + const uint8 kBuffer[] = { + 0x1F, 0x43, 0xB6, 0x75, 0x83, // CLUSTER (size = 3) + 0xE7, 0x81, 0x01, // Cluster TIMECODE (value = 1) + + 0x1F, 0x43, 0xB6, 0x75, 0xFF, // CLUSTER (size = unknown; really 3 due to:) + 0xE7, 0x81, 0x02, // Cluster TIMECODE (value = 2) + /* e.g. put some blocks here... */ + 0x1A, 0x45, 0xDF, 0xA3, 0x8A, // EBMLHEADER (size = 10, not fully appended) + }; + + // This array indicates expected return value of IsParsingMediaSegment() + // following each incrementally appended byte in |kBuffer|. + const bool kExpectedReturnValues[] = { + false, false, false, false, true, + true, true, false, + + false, false, false, false, true, + true, true, true, + + true, true, true, true, false, + }; + + COMPILE_ASSERT(arraysize(kBuffer) == arraysize(kExpectedReturnValues), + test_arrays_out_of_sync); + COMPILE_ASSERT(arraysize(kBuffer) == sizeof(kBuffer), not_one_byte_per_index); + + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); - demuxer_->Abort(kSourceId); - // After Abort(), setting a timestamp should succeed since we're no longer - // in the middle of a cluster - ASSERT_TRUE(demuxer_->SetTimestampOffset( - kSourceId, base::TimeDelta::FromSeconds(25))); + for (size_t i = 0; i < sizeof(kBuffer); i++) { + DVLOG(3) << "Appending and testing index " << i; + AppendData(kBuffer + i, 1); + bool expected_return_value = kExpectedReturnValues[i]; + EXPECT_EQ(expected_return_value, + demuxer_->IsParsingMediaSegment(kSourceId)); + } } TEST_F(ChunkDemuxerTest, DurationChange) { - ASSERT_TRUE(InitDemuxer(true, true)); - static const int kStreamDuration = kDefaultDuration().InMilliseconds(); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + const int kStreamDuration = kDefaultDuration().InMilliseconds(); // Add data leading up to the currently set duration. AppendCluster(GenerateCluster(kStreamDuration - kAudioBlockDuration, @@ -2479,38 +2938,41 @@ TEST_F(ChunkDemuxerTest, DurationChange) { CheckExpectedRanges(kSourceId, "{ [201191,201224) }"); - // Add data at the currently set duration. The duration should not increase. + // Add data beginning at the currently set duration and expect a new duration + // to be signaled. Note that the last video block will have a higher end + // timestamp than the last audio block. + const int kNewStreamDurationVideo = kStreamDuration + kVideoBlockDuration; + EXPECT_CALL(host_, SetDuration( + base::TimeDelta::FromMilliseconds(kNewStreamDurationVideo))); AppendCluster(GenerateCluster(kDefaultDuration().InMilliseconds(), 2)); - // Range should not be affected. - CheckExpectedRanges(kSourceId, "{ [201191,201224) }"); + CheckExpectedRanges(kSourceId, "{ [201191,201247) }"); - // Now add data past the duration and expect a new duration to be signalled. - static const int kNewStreamDuration = - kStreamDuration + kAudioBlockDuration * 2; + // Add more data to the end of each media type. Note that the last audio block + // will have a higher end timestamp than the last video block. + const int kFinalStreamDuration = kStreamDuration + kAudioBlockDuration * 3; EXPECT_CALL(host_, SetDuration( - base::TimeDelta::FromMilliseconds(kNewStreamDuration))); + base::TimeDelta::FromMilliseconds(kFinalStreamDuration))); AppendCluster(GenerateCluster(kStreamDuration + kAudioBlockDuration, kStreamDuration + kVideoBlockDuration, - 2)); + 3)); - // See that the range has increased appropriately. - CheckExpectedRanges(kSourceId, "{ [201191,201270) }"); + // See that the range has increased appropriately (but not to the full + // duration of 201293, since there is not enough video appended for that). + CheckExpectedRanges(kSourceId, "{ [201191,201290) }"); } TEST_F(ChunkDemuxerTest, DurationChangeTimestampOffset) { - ASSERT_TRUE(InitDemuxer(true, true)); - - ASSERT_TRUE(demuxer_->SetTimestampOffset(kSourceId, kDefaultDuration())); - + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + ASSERT_TRUE(SetTimestampOffset(kSourceId, kDefaultDuration())); EXPECT_CALL(host_, SetDuration( kDefaultDuration() + base::TimeDelta::FromMilliseconds( - kAudioBlockDuration * 2))); + kVideoBlockDuration * 2))); AppendCluster(GenerateCluster(0, 4)); } TEST_F(ChunkDemuxerTest, EndOfStreamTruncateDuration) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(kDefaultFirstCluster()); @@ -2521,12 +2983,12 @@ TEST_F(ChunkDemuxerTest, EndOfStreamTruncateDuration) { TEST_F(ChunkDemuxerTest, ZeroLengthAppend) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendData(NULL, 0); } TEST_F(ChunkDemuxerTest, AppendAfterEndOfStream) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); EXPECT_CALL(host_, SetDuration(_)) .Times(AnyNumber()); @@ -2543,43 +3005,22 @@ 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); message_loop_.RunUntilIdle(); } -TEST_F(ChunkDemuxerTest, ReadAfterAudioDisabled) { - ASSERT_TRUE(InitDemuxer(true, true)); - AppendCluster(kDefaultFirstCluster()); - - DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::AUDIO); - ASSERT_TRUE(stream); - - // The stream should no longer be present. - demuxer_->OnAudioRendererDisabled(); - ASSERT_FALSE(demuxer_->GetStream(DemuxerStream::AUDIO)); - - // Normally this would return an audio buffer at timestamp zero, but - // all reads should return EOS buffers when disabled. - bool audio_read_done = false; - stream->Read(base::Bind(&OnReadDone_EOSExpected, &audio_read_done)); - message_loop_.RunUntilIdle(); - - EXPECT_TRUE(audio_read_done); -} - -// Verifies that signalling end of stream while stalled at a gap +// Verifies that signaling end of stream while stalled at a gap // boundary does not trigger end of stream buffers to be returned. TEST_F(ChunkDemuxerTest, EndOfStreamWhileWaitingForGapToBeFilled) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); AppendCluster(0, 10); AppendCluster(300, 10); CheckExpectedRanges(kSourceId, "{ [0,132) [300,432) }"); - GenerateExpectedReads(0, 10); bool audio_read_done = false; @@ -2604,18 +3045,18 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWhileWaitingForGapToBeFilled) { demuxer_->UnmarkEndOfStream(); - AppendCluster(138, 24); + AppendCluster(138, 22); message_loop_.RunUntilIdle(); - CheckExpectedRanges(kSourceId, "{ [0,438) }"); + CheckExpectedRanges(kSourceId, "{ [0,435) }"); // Verify that the reads have completed. EXPECT_TRUE(audio_read_done); EXPECT_TRUE(video_read_done); // Read the rest of the buffers. - GenerateExpectedReads(161, 171, 22); + GenerateExpectedReads(161, 171, 20); // Verify that reads block because the append cleared the end of stream state. audio_read_done = false; @@ -2629,6 +3070,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWhileWaitingForGapToBeFilled) { EXPECT_FALSE(audio_read_done); EXPECT_FALSE(video_read_done); + EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(437))); MarkEndOfStream(PIPELINE_OK); EXPECT_TRUE(audio_read_done); @@ -2636,7 +3078,7 @@ TEST_F(ChunkDemuxerTest, EndOfStreamWhileWaitingForGapToBeFilled) { } TEST_F(ChunkDemuxerTest, CanceledSeekDuringInitialPreroll) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); // Cancel preroll. base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(200); @@ -2650,7 +3092,7 @@ TEST_F(ChunkDemuxerTest, CanceledSeekDuringInitialPreroll) { } TEST_F(ChunkDemuxerTest, GCDuringSeek) { - ASSERT_TRUE(InitDemuxer(true, false)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); demuxer_->SetMemoryLimitsForTesting(5 * kBlockSize); @@ -2692,54 +3134,351 @@ TEST_F(ChunkDemuxerTest, GCDuringSeek) { CheckExpectedRanges(kSourceId, "{ [500,592) [792,815) }"); } -TEST_F(ChunkDemuxerTest, RemoveBeforeInitSegment) { - EXPECT_CALL(*this, DemuxerOpened()); - demuxer_->Initialize( - &host_, CreateInitDoneCB(kNoTimestamp(), PIPELINE_OK), true); +TEST_F(ChunkDemuxerTest, AppendWindow_Video) { + ASSERT_TRUE(InitDemuxer(HAS_VIDEO)); + DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); + + // Set the append window to [50,280). + append_window_start_for_next_append_ = base::TimeDelta::FromMilliseconds(50); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(280); - EXPECT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, true, true)); + // Append a cluster that starts before and ends after the append window. + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "0K 30 60 90 120K 150 180 210 240K 270 300 330K"); - demuxer_->Remove(kSourceId, base::TimeDelta::FromMilliseconds(0), - base::TimeDelta::FromMilliseconds(1)); + // Verify that GOPs that start outside the window are not included + // in the buffer. Also verify that buffers that start inside the + // window and extend beyond the end of the window are not included. + CheckExpectedRanges(kSourceId, "{ [120,270) }"); + CheckExpectedBuffers(stream, "120 150 180 210 240"); + + // Extend the append window to [50,650). + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(650); + + // Append more data and verify that adding buffers start at the next + // keyframe. + AppendSingleStreamCluster(kSourceId, kVideoTrackNum, + "360 390 420K 450 480 510 540K 570 600 630K"); + CheckExpectedRanges(kSourceId, "{ [120,270) [420,630) }"); } -TEST_F(ChunkDemuxerTest, AppendWindow) { - ASSERT_TRUE(InitDemuxer(false, true)); - DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::VIDEO); +TEST_F(ChunkDemuxerTest, AppendWindow_Audio) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); + DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::AUDIO); - // Set the append window to [20,280). - demuxer_->SetAppendWindowStart(kSourceId, - base::TimeDelta::FromMilliseconds(20)); - demuxer_->SetAppendWindowEnd(kSourceId, - base::TimeDelta::FromMilliseconds(280)); + // Set the append window to [50,280). + append_window_start_for_next_append_ = base::TimeDelta::FromMilliseconds(50); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(280); + + // Append a cluster that starts before and ends after the append window. + AppendSingleStreamCluster( + kSourceId, kAudioTrackNum, + "0K 30K 60K 90K 120K 150K 180K 210K 240K 270K 300K 330K"); + + // Verify that frames that end outside the window are not included + // in the buffer. Also verify that buffers that start inside the + // window and extend beyond the end of the window are not included. + // + // The first 50ms of the range should be truncated since it overlaps + // the start of the append window. + CheckExpectedRanges(kSourceId, "{ [50,270) }"); + + // The "50P" buffer is the "0" buffer marked for complete discard. The next + // "50" buffer is the "30" buffer marked with 20ms of start discard. + CheckExpectedBuffers(stream, "50P 50 60 90 120 150 180 210 240"); + + // Extend the append window to [50,650). + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(650); + + // Append more data and verify that a new range is created. + AppendSingleStreamCluster( + kSourceId, kAudioTrackNum, + "360K 390K 420K 450K 480K 510K 540K 570K 600K 630K"); + CheckExpectedRanges(kSourceId, "{ [50,270) [360,630) }"); +} + +TEST_F(ChunkDemuxerTest, AppendWindow_AudioOverlapStartAndEnd) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); + + // Set the append window to [10,20). + append_window_start_for_next_append_ = base::TimeDelta::FromMilliseconds(10); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(20); // Append a cluster that starts before and ends after the append window. + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, "0K"); + + // Verify that everything is dropped in this case. No partial append should + // be generated. + CheckExpectedRanges(kSourceId, "{ }"); +} + +TEST_F(ChunkDemuxerTest, AppendWindow_WebMFile_AudioOnly) { + EXPECT_CALL(*this, DemuxerOpened()); + demuxer_->Initialize( + &host_, + CreateInitDoneCB(base::TimeDelta::FromMilliseconds(2744), PIPELINE_OK), + true); + ASSERT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, HAS_AUDIO)); + + // Set the append window to [50,150). + append_window_start_for_next_append_ = base::TimeDelta::FromMilliseconds(50); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(150); + + // Read a WebM file into memory and send the data to the demuxer. The chunk + // size has been chosen carefully to ensure the preroll buffer used by the + // partial append window trim must come from a previous Append() call. + scoped_refptr<DecoderBuffer> buffer = + ReadTestDataFile("bear-320x240-audio-only.webm"); + AppendDataInPieces(buffer->data(), buffer->data_size(), 128); + + DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::AUDIO); + CheckExpectedBuffers(stream, "50P 50 62 86 109 122 125 128"); +} + +TEST_F(ChunkDemuxerTest, AppendWindow_AudioConfigUpdateRemovesPreroll) { + EXPECT_CALL(*this, DemuxerOpened()); + demuxer_->Initialize( + &host_, + CreateInitDoneCB(base::TimeDelta::FromMilliseconds(2744), PIPELINE_OK), + true); + ASSERT_EQ(ChunkDemuxer::kOk, AddId(kSourceId, HAS_AUDIO)); + + // Set the append window such that the first file is completely before the + // append window. + // TODO(wolenetz/acolwell): Update this duration once the files are fixed to + // have the correct duration in their init segments, and the + // CreateInitDoneCB() call, above, is fixed to used that duration. See + // http://crbug.com/354284. + const base::TimeDelta duration_1 = base::TimeDelta::FromMilliseconds(2746); + append_window_start_for_next_append_ = duration_1; + + // Read a WebM file into memory and append the data. + scoped_refptr<DecoderBuffer> buffer = + ReadTestDataFile("bear-320x240-audio-only.webm"); + AppendDataInPieces(buffer->data(), buffer->data_size(), 512); + CheckExpectedRanges(kSourceId, "{ }"); + + DemuxerStream* stream = demuxer_->GetStream(DemuxerStream::AUDIO); + AudioDecoderConfig config_1 = stream->audio_decoder_config(); + + // Read a second WebM with a different config in and append the data. + scoped_refptr<DecoderBuffer> buffer2 = + ReadTestDataFile("bear-320x240-audio-only-48khz.webm"); + EXPECT_CALL(host_, SetDuration(_)).Times(AnyNumber()); + ASSERT_TRUE(SetTimestampOffset(kSourceId, duration_1)); + AppendDataInPieces(buffer2->data(), buffer2->data_size(), 512); + CheckExpectedRanges(kSourceId, "{ [2746,5519) }"); + + Seek(duration_1); + ExpectConfigChanged(DemuxerStream::AUDIO); + ASSERT_FALSE(config_1.Matches(stream->audio_decoder_config())); + CheckExpectedBuffers(stream, "2746 2767 2789 2810"); +} + +TEST_F(ChunkDemuxerTest, AppendWindow_Text) { + DemuxerStream* text_stream = NULL; + EXPECT_CALL(host_, AddTextStream(_, _)) + .WillOnce(SaveArg<0>(&text_stream)); + ASSERT_TRUE(InitDemuxer(HAS_VIDEO | HAS_TEXT)); + DemuxerStream* video_stream = demuxer_->GetStream(DemuxerStream::VIDEO); + + // Set the append window to [20,280). + append_window_start_for_next_append_ = base::TimeDelta::FromMilliseconds(20); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(280); + + // Append a cluster that starts before and ends after the append + // window. AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "0K 30 60 90 120K 150 180 210 240K 270 300 330K"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "0K 100K 200K 300K"); - // Verify that GOPs that start outside the window are not included - // in the buffer. Also verify that buffers that extend beyond the + // Verify that text cues that start outside the window are not included + // in the buffer. Also verify that cues that extend beyond the // window are not included. - CheckExpectedRanges(kSourceId, "{ [120,300) }"); - CheckExpectedBuffers(stream, "120 150 180 210 240 270"); + CheckExpectedRanges(kSourceId, "{ [120,270) }"); + CheckExpectedBuffers(video_stream, "120 150 180 210 240"); + CheckExpectedBuffers(text_stream, "100"); // Extend the append window to [20,650). - demuxer_->SetAppendWindowEnd(kSourceId, - base::TimeDelta::FromMilliseconds(650)); + append_window_end_for_next_append_ = base::TimeDelta::FromMilliseconds(650); - // Append more data and verify that adding buffers start at the next - // keyframe. + // Append more data and verify that a new range is created. AppendSingleStreamCluster(kSourceId, kVideoTrackNum, "360 390 420K 450 480 510 540K 570 600 630K"); - CheckExpectedRanges(kSourceId, "{ [120,300) [420,660) }"); + AppendSingleStreamCluster(kSourceId, kTextTrackNum, "400K 500K 600K 700K"); + CheckExpectedRanges(kSourceId, "{ [120,270) [420,630) }"); + + // Seek to the new range and verify that the expected buffers are returned. + Seek(base::TimeDelta::FromMilliseconds(420)); + CheckExpectedBuffers(video_stream, "420 450 480 510 540 570 600"); + CheckExpectedBuffers(text_stream, "400 500"); } TEST_F(ChunkDemuxerTest, StartWaitingForSeekAfterParseError) { - ASSERT_TRUE(InitDemuxer(true, true)); + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE)); AppendGarbage(); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(50); 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"); +} + +TEST_F(ChunkDemuxerTest, Remove_StartAtDuration) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); + DemuxerStream* audio_stream = demuxer_->GetStream(DemuxerStream::AUDIO); + + // Set the duration to something small so that the append that + // follows updates the duration to reflect the end of the appended data. + EXPECT_CALL(host_, SetDuration( + base::TimeDelta::FromMilliseconds(1))); + demuxer_->SetDuration(0.001); + + EXPECT_CALL(host_, SetDuration( + base::TimeDelta::FromMilliseconds(160))); + AppendSingleStreamCluster(kSourceId, kAudioTrackNum, + "0K 20K 40K 60K 80K 100K 120K 140K"); + + CheckExpectedRanges(kSourceId, "{ [0,160) }"); + CheckExpectedBuffers(audio_stream, "0 20 40 60 80 100 120 140"); + + demuxer_->Remove(kSourceId, + base::TimeDelta::FromSecondsD(demuxer_->GetDuration()), + kInfiniteDuration()); + + Seek(base::TimeDelta()); + CheckExpectedRanges(kSourceId, "{ [0,160) }"); + CheckExpectedBuffers(audio_stream, "0 20 40 60 80 100 120 140"); +} + +// 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 continue to return expected values. + CheckExpectedBuffers(audio_stream, "160 180"); + CheckExpectedBuffers(video_stream, "180 210"); +} + +TEST_F(ChunkDemuxerTest, ClusterWithUnknownSize) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + + AppendCluster(GenerateCluster(0, 0, 4, true)); + CheckExpectedRanges(kSourceId, "{ [0,46) }"); + + // A new cluster indicates end of the previous cluster with unknown size. + AppendCluster(GenerateCluster(46, 66, 5, true)); + CheckExpectedRanges(kSourceId, "{ [0,115) }"); +} + +TEST_F(ChunkDemuxerTest, CuesBetweenClustersWithUnknownSize) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + + // Add two clusters separated by Cues in a single Append() call. + scoped_ptr<Cluster> cluster = GenerateCluster(0, 0, 4, true); + std::vector<uint8> data(cluster->data(), cluster->data() + cluster->size()); + data.insert(data.end(), kCuesHeader, kCuesHeader + sizeof(kCuesHeader)); + cluster = GenerateCluster(46, 66, 5, true); + data.insert(data.end(), cluster->data(), cluster->data() + cluster->size()); + AppendData(&*data.begin(), data.size()); + + CheckExpectedRanges(kSourceId, "{ [0,115) }"); +} + +TEST_F(ChunkDemuxerTest, CuesBetweenClusters) { + ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); + + AppendCluster(GenerateCluster(0, 0, 4)); + AppendData(kCuesHeader, sizeof(kCuesHeader)); + AppendCluster(GenerateCluster(46, 66, 5)); + CheckExpectedRanges(kSourceId, "{ [0,115) }"); +} + } // namespace media |