summaryrefslogtreecommitdiffstats
path: root/chromium/media/filters/chunk_demuxer_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/filters/chunk_demuxer_unittest.cc')
-rw-r--r--chromium/media/filters/chunk_demuxer_unittest.cc1297
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_,
+ &timestamp_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, ' ', &timestamps);
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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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_,
+ &timestamp_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