blob: 6dfe7a8d4ea5c5940dd5bedbf5e4c70fb4c40908 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/message_loop.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/mock_demuxer_host.h"
#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 "testing/gtest/include/gtest/gtest.h"
using ::testing::AnyNumber;
using ::testing::Exactly;
using ::testing::InSequence;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgumentPointee;
using ::testing::_;
namespace media {
static const uint8 kTracksHeader[] = {
0x16, 0x54, 0xAE, 0x6B, // Tracks ID
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // tracks(size = 0)
};
static const int kTracksHeaderSize = sizeof(kTracksHeader);
static 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;
// 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 char* kSourceId = "SourceId";
static const char* kDefaultFirstClusterRange = "{ [0,46) }";
static const int kDefaultFirstClusterEndTimestamp = 66;
static const int kDefaultSecondClusterEndTimestamp = 132;
static const char kWebMInitDataType[] = "video/webm";
base::TimeDelta kDefaultDuration() {
return base::TimeDelta::FromMilliseconds(201224);
}
// Write an integer into buffer in the form of vint that spans 8 bytes.
// 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));
buffer[0] = 0x01;
int64 tmp = number;
for (int i = 7; i > 0; i--) {
buffer[i] = tmp & 0xff;
tmp >>= 8;
}
}
MATCHER_P(HasTimestamp, timestamp_in_ms, "") {
return arg && !arg->IsEndOfStream() &&
arg->GetTimestamp().InMilliseconds() == timestamp_in_ms;
}
MATCHER(IsEndOfStream, "") {
return arg && arg->IsEndOfStream();
}
static void OnReadDone(const base::TimeDelta& expected_time,
bool* called,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
EXPECT_EQ(status, DemuxerStream::kOk);
EXPECT_EQ(expected_time, buffer->GetTimestamp());
*called = true;
}
static void OnReadDone_AbortExpected(
bool* called, DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
EXPECT_EQ(status, DemuxerStream::kAborted);
EXPECT_EQ(NULL, buffer.get());
*called = true;
}
static void OnReadDone_EOSExpected(bool* called,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
EXPECT_EQ(status, DemuxerStream::kOk);
EXPECT_TRUE(buffer->IsEndOfStream());
*called = true;
}
static void StoreStatusAndBuffer(DemuxerStream::Status* status_out,
scoped_refptr<DecoderBuffer>* buffer_out,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
*status_out = status;
*buffer_out = buffer;
}
static void ReadUntilNotOkOrEndOfStream(
const scoped_refptr<DemuxerStream>& stream,
DemuxerStream::Status* status,
base::TimeDelta* last_timestamp) {
scoped_refptr<DecoderBuffer> buffer;
*last_timestamp = kNoTimestamp();
do {
stream->Read(base::Bind(&StoreStatusAndBuffer, status, &buffer));
if (*status == DemuxerStream::kOk && !buffer->IsEndOfStream())
*last_timestamp = buffer->GetTimestamp();
} while (*status == DemuxerStream::kOk && !buffer->IsEndOfStream());
}
class ChunkDemuxerTest : public testing::Test {
protected:
enum CodecsIndex {
AUDIO,
VIDEO,
MAX_CODECS_INDEX
};
// Default cluster to append first for simple tests.
scoped_ptr<Cluster> kDefaultFirstCluster() {
return GenerateCluster(0, 4);
}
// Default cluster to append after kDefaultFirstCluster()
// has been appended. This cluster starts with blocks that
// have timestamps consistent with the end times of the blocks
// in kDefaultFirstCluster() so that these two clusters represent
// a continuous region.
scoped_ptr<Cluster> kDefaultSecondCluster() {
return GenerateCluster(46, 66, 5);
}
ChunkDemuxerTest() {
CreateNewDemuxer();
}
void CreateNewDemuxer() {
base::Closure open_cb =
base::Bind(&ChunkDemuxerTest::DemuxerOpened, base::Unretained(this));
ChunkDemuxer::NeedKeyCB need_key_cb =
base::Bind(&ChunkDemuxerTest::DemuxerNeedKey, base::Unretained(this));
demuxer_ = new ChunkDemuxer(open_cb, need_key_cb, LogCB());
}
virtual ~ChunkDemuxerTest() {
ShutdownDemuxer();
}
void CreateInitSegment(bool has_audio, bool has_video,
bool is_audio_encrypted, bool is_video_encrypted,
scoped_array<uint8>* buffer,
int* size) {
scoped_refptr<DecoderBuffer> ebml_header;
scoped_refptr<DecoderBuffer> info;
scoped_refptr<DecoderBuffer> audio_track_entry;
scoped_refptr<DecoderBuffer> video_track_entry;
scoped_refptr<DecoderBuffer> audio_content_encodings;
scoped_refptr<DecoderBuffer> video_content_encodings;
ebml_header = ReadTestDataFile("webm_ebml_element");
info = ReadTestDataFile("webm_info_element");
int tracks_element_size = 0;
if (has_audio) {
audio_track_entry = ReadTestDataFile("webm_vorbis_track_entry");
tracks_element_size += audio_track_entry->GetDataSize();
if (is_audio_encrypted) {
audio_content_encodings = ReadTestDataFile("webm_content_encodings");
tracks_element_size += audio_content_encodings->GetDataSize();
}
}
if (has_video) {
video_track_entry = ReadTestDataFile("webm_vp8_track_entry");
tracks_element_size += video_track_entry->GetDataSize();
if (is_video_encrypted) {
video_content_encodings = ReadTestDataFile("webm_content_encodings");
tracks_element_size += video_content_encodings->GetDataSize();
}
}
*size = ebml_header->GetDataSize() + info->GetDataSize() +
kTracksHeaderSize + tracks_element_size;
buffer->reset(new uint8[*size]);
uint8* buf = buffer->get();
memcpy(buf, ebml_header->GetData(), ebml_header->GetDataSize());
buf += ebml_header->GetDataSize();
memcpy(buf, info->GetData(), info->GetDataSize());
buf += info->GetDataSize();
memcpy(buf, kTracksHeader, kTracksHeaderSize);
WriteInt64(buf + kTracksSizeOffset, tracks_element_size);
buf += kTracksHeaderSize;
// TODO(xhwang): Simplify this! Probably have test data files that contain
// ContentEncodings directly instead of trying to create one at run-time.
if (has_audio) {
memcpy(buf, audio_track_entry->GetData(),
audio_track_entry->GetDataSize());
if (is_audio_encrypted) {
memcpy(buf + audio_track_entry->GetDataSize(),
audio_content_encodings->GetData(),
audio_content_encodings->GetDataSize());
WriteInt64(buf + kAudioTrackSizeOffset,
audio_track_entry->GetDataSize() +
audio_content_encodings->GetDataSize() -
kAudioTrackEntryHeaderSize);
buf += audio_content_encodings->GetDataSize();
}
buf += audio_track_entry->GetDataSize();
}
if (has_video) {
memcpy(buf, video_track_entry->GetData(),
video_track_entry->GetDataSize());
if (is_video_encrypted) {
memcpy(buf + video_track_entry->GetDataSize(),
video_content_encodings->GetData(),
video_content_encodings->GetDataSize());
WriteInt64(buf + kVideoTrackSizeOffset,
video_track_entry->GetDataSize() +
video_content_encodings->GetDataSize() -
kVideoTrackEntryHeaderSize);
buf += video_content_encodings->GetDataSize();
}
buf += video_track_entry->GetDataSize();
}
}
ChunkDemuxer::Status AddId() {
return AddId(kSourceId, true, true);
}
ChunkDemuxer::Status AddId(const std::string& source_id,
bool has_audio, bool has_video) {
std::vector<std::string> codecs;
std::string type;
if (has_audio) {
codecs.push_back("vorbis");
type = "audio/webm";
}
if (has_video) {
codecs.push_back("vp8");
type = "video/webm";
}
if (!has_audio && !has_video) {
return AddId(kSourceId, true, true);
}
return demuxer_->AddId(source_id, type, codecs);
}
bool AppendData(const uint8* data, size_t length) {
return AppendData(kSourceId, data, length);
}
bool AppendCluster(int timecode, int block_count) {
scoped_ptr<Cluster> cluster(GenerateCluster(timecode, block_count));
return AppendData(kSourceId, cluster->data(), cluster->size());
}
bool AppendData(const std::string& source_id,
const uint8* data, size_t length) {
EXPECT_CALL(host_, AddBufferedTimeRange(_, _)).Times(AnyNumber());
return demuxer_->AppendData(source_id, data, length);
}
bool AppendDataInPieces(const uint8* data, size_t length) {
return AppendDataInPieces(data, length, 7);
}
bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
const uint8* start = data;
const uint8* end = data + length;
while (start < end) {
size_t append_size = std::min(piece_size,
static_cast<size_t>(end - start));
if (!AppendData(start, append_size))
return false;
start += append_size;
}
return true;
}
bool AppendInitSegment(bool has_audio, bool has_video) {
return AppendInitSegmentWithSourceId(kSourceId, has_audio, has_video);
}
bool AppendInitSegmentWithSourceId(const std::string& source_id,
bool has_audio, bool has_video) {
return AppendInitSegmentWithEncryptedInfo(
source_id, has_audio, has_video, false, false);
}
bool AppendInitSegmentWithEncryptedInfo(const std::string& source_id,
bool has_audio, bool has_video,
bool is_audio_encrypted,
bool is_video_encrypted) {
scoped_array<uint8> info_tracks;
int info_tracks_size = 0;
CreateInitSegment(has_audio, has_video,
is_audio_encrypted, is_video_encrypted,
&info_tracks, &info_tracks_size);
return AppendData(source_id, info_tracks.get(), info_tracks_size);
}
bool AppendGarbage() {
// Fill up an array with gibberish.
int garbage_cluster_size = 10;
scoped_array<uint8> garbage_cluster(new uint8[garbage_cluster_size]);
for (int i = 0; i < garbage_cluster_size; ++i)
garbage_cluster[i] = i;
return AppendData(garbage_cluster.get(), garbage_cluster_size);
}
void InitDoneCalled(PipelineStatus expected_status,
PipelineStatus status) {
EXPECT_EQ(status, expected_status);
}
bool AppendEmptyCluster(int timecode) {
scoped_ptr<Cluster> empty_cluster = GenerateEmptyCluster(timecode);
return AppendData(empty_cluster->data(), empty_cluster->size());
}
PipelineStatusCB CreateInitDoneCB(const base::TimeDelta& expected_duration,
PipelineStatus expected_status) {
if (expected_duration != kNoTimestamp())
EXPECT_CALL(host_, SetDuration(expected_duration));
return CreateInitDoneCB(expected_status);
}
PipelineStatusCB CreateInitDoneCB(PipelineStatus expected_status) {
return base::Bind(&ChunkDemuxerTest::InitDoneCalled,
base::Unretained(this),
expected_status);
}
bool InitDemuxer(bool has_audio, bool has_video) {
return InitDemuxerWithEncryptionInfo(has_audio, has_video, false, false);
}
bool InitDemuxerWithEncryptionInfo(
bool has_audio, bool has_video,
bool is_audio_encrypted, bool is_video_encrypted) {
PipelineStatus expected_status =
(has_audio || has_video) ? PIPELINE_OK : DEMUXER_ERROR_COULD_NOT_OPEN;
base::TimeDelta expected_duration = kNoTimestamp();
if (expected_status == PIPELINE_OK)
expected_duration = kDefaultDuration();
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(expected_duration, expected_status));
if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk)
return false;
return AppendInitSegmentWithEncryptedInfo(
kSourceId, has_audio, has_video,
is_audio_encrypted, is_video_encrypted);
}
bool InitDemuxerAudioAndVideoSources(const std::string& audio_id,
const std::string& video_id) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
if (AddId(audio_id, true, false) != ChunkDemuxer::kOk)
return false;
if (AddId(video_id, false, true) != ChunkDemuxer::kOk)
return false;
bool success = AppendInitSegmentWithSourceId(audio_id, true, false);
success &= AppendInitSegmentWithSourceId(video_id, false, true);
return success;
}
// Initializes the demuxer with data from 2 files with different
// decoder configurations. This is used to test the decoder config change
// logic.
//
// bear-320x240.webm VideoDecoderConfig returns 320x240 for its natural_size()
// 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-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-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));
if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk)
return false;
// Append the whole bear1 file.
if (!AppendData(bear1->GetData(), bear1->GetDataSize()))
return false;
CheckExpectedRanges(kSourceId, "{ [0,2737) }");
// Append initialization segment for bear2.
// Note: Offsets here and below are derived from
// media/test/data/bear-640x360-manifest.js and
// media/test/data/bear-320x240-manifest.js which were
// generated from media/test/data/bear-640x360.webm and
// media/test/data/bear-320x240.webm respectively.
if (!AppendData(bear2->GetData(), 4340))
return false;
// Append a media segment that goes from [0.527000, 1.014000).
if (!AppendData(bear2->GetData() + 55290, 18785))
return false;
CheckExpectedRanges(kSourceId, "{ [0,1028) [1201,2737) }");
// Append initialization segment for bear1 & fill gap with [779-1197)
// segment.
if (!AppendData(bear1->GetData(), 4370) ||
!AppendData(bear1->GetData() + 72737, 28183)) {
return false;
}
CheckExpectedRanges(kSourceId, "{ [0,2737) }");
return demuxer_->EndOfStream(PIPELINE_OK);
}
void ShutdownDemuxer() {
if (demuxer_)
demuxer_->Shutdown();
}
void AddSimpleBlock(ClusterBuilder* cb, int track_num, int64 timecode) {
uint8 data[] = { 0x00 };
cb->AddSimpleBlock(track_num, timecode, 0, data, sizeof(data));
}
scoped_ptr<Cluster> GenerateCluster(int timecode, int block_count) {
return GenerateCluster(timecode, timecode, block_count);
}
scoped_ptr<Cluster> GenerateCluster(int first_audio_timecode,
int first_video_timecode,
int block_count) {
CHECK_GT(block_count, 0);
int size = 10;
scoped_array<uint8> data(new uint8[size]);
ClusterBuilder cb;
cb.SetClusterTimecode(std::min(first_audio_timecode, first_video_timecode));
if (block_count == 1) {
cb.AddBlockGroup(kAudioTrackNum, first_audio_timecode,
kAudioBlockDuration, kWebMFlagKeyframe,
data.get(), size);
return cb.Finish();
}
int audio_timecode = first_audio_timecode;
int video_timecode = first_video_timecode;
// Create simple blocks for everything except the last 2 blocks.
// The first video frame must be a keyframe.
uint8 video_flag = kWebMFlagKeyframe;
for (int i = 0; i < block_count - 2; i++) {
if (audio_timecode <= video_timecode) {
cb.AddSimpleBlock(kAudioTrackNum, audio_timecode, kWebMFlagKeyframe,
data.get(), size);
audio_timecode += kAudioBlockDuration;
continue;
}
cb.AddSimpleBlock(kVideoTrackNum, video_timecode, video_flag, data.get(),
size);
video_timecode += kVideoBlockDuration;
video_flag = 0;
}
// Make the last 2 blocks BlockGroups so that they don't get delayed by the
// block duration calculation logic.
if (audio_timecode <= video_timecode) {
cb.AddBlockGroup(kAudioTrackNum, audio_timecode, kAudioBlockDuration,
kWebMFlagKeyframe, data.get(), size);
cb.AddBlockGroup(kVideoTrackNum, video_timecode, kVideoBlockDuration,
video_flag, data.get(), size);
} else {
cb.AddBlockGroup(kVideoTrackNum, video_timecode, kVideoBlockDuration,
video_flag, data.get(), size);
cb.AddBlockGroup(kAudioTrackNum, audio_timecode, kAudioBlockDuration,
kWebMFlagKeyframe, data.get(), size);
}
return cb.Finish();
}
scoped_ptr<Cluster> GenerateSingleStreamCluster(int timecode,
int end_timecode,
int track_number,
int block_duration) {
CHECK_GT(end_timecode, timecode);
int size = 10;
scoped_array<uint8> data(new uint8[size]);
ClusterBuilder cb;
cb.SetClusterTimecode(timecode);
// Create simple blocks for everything except the last block.
for (int i = 0; timecode < (end_timecode - block_duration); i++) {
cb.AddSimpleBlock(track_number, timecode, kWebMFlagKeyframe,
data.get(), size);
timecode += block_duration;
}
// Make the last block a BlockGroup so that it doesn't get delayed by the
// block duration calculation logic.
cb.AddBlockGroup(track_number, timecode, block_duration,
kWebMFlagKeyframe, data.get(), size);
return cb.Finish();
}
void GenerateExpectedReads(int timecode, int block_count,
DemuxerStream* audio,
DemuxerStream* video) {
GenerateExpectedReads(timecode, timecode, block_count, audio, video);
}
void GenerateExpectedReads(int start_audio_timecode,
int start_video_timecode,
int block_count, DemuxerStream* audio,
DemuxerStream* video) {
CHECK_GT(block_count, 0);
if (block_count == 1) {
ExpectRead(audio, start_audio_timecode);
return;
}
int audio_timecode = start_audio_timecode;
int video_timecode = start_video_timecode;
for (int i = 0; i < block_count; i++) {
if (audio_timecode <= video_timecode) {
ExpectRead(audio, audio_timecode);
audio_timecode += kAudioBlockDuration;
continue;
}
ExpectRead(video, video_timecode);
video_timecode += kVideoBlockDuration;
}
}
void GenerateSingleStreamExpectedReads(
int timecode, int block_count, DemuxerStream* stream,
int block_duration) {
CHECK_GT(block_count, 0);
int stream_timecode = timecode;
for (int i = 0; i < block_count; i++) {
ExpectRead(stream, stream_timecode);
stream_timecode += block_duration;
}
}
scoped_ptr<Cluster> GenerateEmptyCluster(int timecode) {
ClusterBuilder cb;
cb.SetClusterTimecode(timecode);
return cb.Finish();
}
void CheckExpectedRanges(const std::string& expected) {
CheckExpectedRanges(kSourceId, expected);
}
void CheckExpectedRanges(const std::string& id,
const std::string& expected) {
Ranges<base::TimeDelta> r = demuxer_->GetBufferedRanges(id);
std::stringstream ss;
ss << "{ ";
for (size_t i = 0; i < r.size(); ++i) {
ss << "[" << r.start(i).InMilliseconds() << ","
<< r.end(i).InMilliseconds() << ") ";
}
ss << "}";
EXPECT_EQ(ss.str(), expected);
}
MOCK_METHOD2(ReadDone, void(DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>&));
void ExpectEndOfStream(DemuxerStream* stream) {
EXPECT_CALL(*this, ReadDone(DemuxerStream::kOk, IsEndOfStream()));
stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
base::Unretained(this)));
}
void ExpectRead(DemuxerStream* stream, int64 timestamp_in_ms) {
EXPECT_CALL(*this, ReadDone(DemuxerStream::kOk,
HasTimestamp(timestamp_in_ms)));
stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
base::Unretained(this)));
}
void ExpectConfigChanged(DemuxerStream* stream) {
EXPECT_CALL(*this, ReadDone(DemuxerStream::kConfigChanged, _));
stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone,
base::Unretained(this)));
}
MOCK_METHOD1(Checkpoint, void(int id));
struct BufferTimestamps {
int video_time_ms;
int audio_time_ms;
};
static const int kSkip = -1;
// Test parsing a WebM file.
// |filename| - The name of the file in media/test/data to parse.
// |timestamps| - The expected timestamps on the parsed buffers.
// a timestamp of kSkip indicates that a Read() call for that stream
// shouldn't be made on that iteration of the loop. If both streams have
// a kSkip then the loop will terminate.
bool ParseWebMFile(const std::string& filename,
const BufferTimestamps* timestamps,
const base::TimeDelta& duration) {
return ParseWebMFile(filename, timestamps, duration, true, true);
}
bool ParseWebMFile(const std::string& filename,
const BufferTimestamps* timestamps,
const base::TimeDelta& duration,
bool has_audio, bool has_video) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(duration, PIPELINE_OK));
if (AddId(kSourceId, has_audio, has_video) != ChunkDemuxer::kOk)
return false;
// Read a WebM file into memory and send the data to the demuxer.
scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
if (!AppendDataInPieces(buffer->GetData(), buffer->GetDataSize(), 512))
return false;
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Verify that the timestamps on the first few packets match what we
// expect.
for (size_t i = 0;
(timestamps[i].audio_time_ms != kSkip ||
timestamps[i].video_time_ms != kSkip);
i++) {
bool audio_read_done = false;
bool video_read_done = false;
if (timestamps[i].audio_time_ms != kSkip) {
DCHECK(audio);
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(
timestamps[i].audio_time_ms),
&audio_read_done));
EXPECT_TRUE(audio_read_done);
}
if (timestamps[i].video_time_ms != kSkip) {
DCHECK(video);
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(
timestamps[i].video_time_ms),
&video_read_done));
EXPECT_TRUE(video_read_done);
}
}
return true;
}
MOCK_METHOD0(DemuxerOpened, void());
// TODO(xhwang): This is a workaround of the issue that move-only parameters
// are not supported in mocked methods. Remove this when the issue is fixed
// (http://code.google.com/p/googletest/issues/detail?id=395) or when we use
// std::string instead of scoped_array<uint8> (http://crbug.com/130689).
MOCK_METHOD3(NeedKeyMock, void(const std::string& type,
const uint8* init_data, int init_data_size));
void DemuxerNeedKey(const std::string& type,
scoped_array<uint8> init_data, int init_data_size) {
NeedKeyMock(type, init_data.get(), init_data_size);
}
MessageLoop message_loop_;
MockDemuxerHost host_;
scoped_refptr<ChunkDemuxer> demuxer_;
private:
DISALLOW_COPY_AND_ASSIGN(ChunkDemuxerTest);
};
TEST_F(ChunkDemuxerTest, TestInit) {
// Test no streams, audio-only, video-only, and audio & video scenarios.
// Audio and video streams can be encrypted or not encrypted.
for (int i = 0; i < 16; i++) {
bool has_audio = (i & 0x1) != 0;
bool has_video = (i & 0x2) != 0;
bool is_audio_encrypted = (i & 0x4) != 0;
bool is_video_encrypted = (i & 0x8) != 0;
// No test on invalid combination.
if ((!has_audio && is_audio_encrypted) ||
(!has_video && is_video_encrypted)) {
continue;
}
CreateNewDemuxer();
if (is_audio_encrypted || is_video_encrypted) {
int need_key_count = (is_audio_encrypted ? 1 : 0) +
(is_video_encrypted ? 1 : 0);
EXPECT_CALL(*this, NeedKeyMock(kWebMInitDataType, NotNull(), 16))
.Times(Exactly(need_key_count));
}
ASSERT_TRUE(InitDemuxerWithEncryptionInfo(
has_audio, has_video, is_audio_encrypted, is_video_encrypted));
scoped_refptr<DemuxerStream> audio_stream =
demuxer_->GetStream(DemuxerStream::AUDIO);
if (has_audio) {
ASSERT_TRUE(audio_stream);
const AudioDecoderConfig& config = audio_stream->audio_decoder_config();
EXPECT_EQ(kCodecVorbis, config.codec());
EXPECT_EQ(16, config.bits_per_channel());
EXPECT_EQ(CHANNEL_LAYOUT_STEREO, config.channel_layout());
EXPECT_EQ(44100, config.samples_per_second());
EXPECT_TRUE(config.extra_data());
EXPECT_GT(config.extra_data_size(), 0u);
EXPECT_EQ(is_audio_encrypted,
audio_stream->audio_decoder_config().is_encrypted());
} else {
EXPECT_FALSE(audio_stream);
}
scoped_refptr<DemuxerStream> video_stream =
demuxer_->GetStream(DemuxerStream::VIDEO);
if (has_video) {
EXPECT_TRUE(video_stream);
EXPECT_EQ(is_video_encrypted,
video_stream->video_decoder_config().is_encrypted());
} else {
EXPECT_FALSE(video_stream);
}
ShutdownDemuxer();
demuxer_ = NULL;
}
}
// Make sure that the demuxer reports an error if Shutdown()
// is called before all the initialization segments are appended.
TEST_F(ChunkDemuxerTest, TestShutdownBeforeAllInitSegmentsAppended) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(
kDefaultDuration(), DEMUXER_ERROR_COULD_NOT_OPEN));
EXPECT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk);
EXPECT_EQ(AddId("video", false, true), ChunkDemuxer::kOk);
EXPECT_TRUE(AppendInitSegmentWithSourceId("audio", true, false));
}
// Test that Seek() completes successfully when the first cluster
// arrives.
TEST_F(ChunkDemuxerTest, TestAppendDataAfterSeek) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
InSequence s;
EXPECT_CALL(*this, Checkpoint(1));
demuxer_->Seek(base::TimeDelta::FromMilliseconds(46),
NewExpectedStatusCB(PIPELINE_OK));
EXPECT_CALL(*this, Checkpoint(2));
scoped_ptr<Cluster> cluster(kDefaultSecondCluster());
Checkpoint(1);
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
Checkpoint(2);
}
// Test that parsing errors are handled for clusters appended after init.
TEST_F(ChunkDemuxerTest, TestErrorWhileParsingClusterAfterInit) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
ASSERT_TRUE(AppendGarbage());
}
// Test the case where a Seek() is requested while the parser
// is in the middle of cluster. This is to verify that the parser
// does not reset itself on a seek.
TEST_F(ChunkDemuxerTest, TestSeekWhileParsingCluster) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
InSequence s;
scoped_ptr<Cluster> cluster_a(GenerateCluster(0, 6));
// Split the cluster into two appends at an arbitrary point near the end.
int first_append_size = cluster_a->size() - 11;
int second_append_size = cluster_a->size() - first_append_size;
// Append the first part of the cluster.
ASSERT_TRUE(AppendData(cluster_a->data(), first_append_size));
ExpectRead(audio, 0);
ExpectRead(video, 0);
ExpectRead(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(audio, 2 * kAudioBlockDuration);
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(5),
NewExpectedStatusCB(PIPELINE_OK));
// Append the rest of the cluster.
ASSERT_TRUE(AppendData(cluster_a->data() + first_append_size,
second_append_size));
// Append the new cluster and verify that only the blocks
// in the new cluster are returned.
scoped_ptr<Cluster> cluster_b(GenerateCluster(5000, 6));
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
GenerateExpectedReads(5000, 6, audio, video);
}
// Test the case where AppendData() is called before Init().
TEST_F(ChunkDemuxerTest, TestAppendDataBeforeInit) {
scoped_array<uint8> info_tracks;
int info_tracks_size = 0;
CreateInitSegment(true, true, false, false, &info_tracks, &info_tracks_size);
EXPECT_FALSE(demuxer_->AppendData(kSourceId, info_tracks.get(),
info_tracks_size));
}
// Make sure Read() callbacks are dispatched with the proper data.
TEST_F(ChunkDemuxerTest, TestRead) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done));
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
}
TEST_F(ChunkDemuxerTest, TestOutOfOrderClusters) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_ptr<Cluster> cluster_a(GenerateCluster(10, 4));
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
// Cluster B starts before cluster_a and has data
// that overlaps.
scoped_ptr<Cluster> cluster_b(GenerateCluster(5, 4));
// Make sure that AppendData() does not fail.
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
// Verify that AppendData() can still accept more data.
scoped_ptr<Cluster> cluster_c(GenerateCluster(45, 2));
ASSERT_TRUE(demuxer_->AppendData(kSourceId, cluster_c->data(),
cluster_c->size()));
}
TEST_F(ChunkDemuxerTest, TestNonMonotonicButAboveClusterTimecode) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
// Test the case where block timecodes are not monotonically
// increasing but stay above the cluster timecode.
cb.SetClusterTimecode(5);
AddSimpleBlock(&cb, kAudioTrackNum, 5);
AddSimpleBlock(&cb, kVideoTrackNum, 10);
AddSimpleBlock(&cb, kAudioTrackNum, 7);
AddSimpleBlock(&cb, kVideoTrackNum, 15);
scoped_ptr<Cluster> cluster_a(cb.Finish());
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
// Verify that AppendData() doesn't accept more data now.
scoped_ptr<Cluster> cluster_b(GenerateCluster(20, 2));
EXPECT_FALSE(demuxer_->AppendData(kSourceId, cluster_b->data(),
cluster_b->size()));
}
TEST_F(ChunkDemuxerTest, TestBackwardsAndBeforeClusterTimecode) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
// Test timecodes going backwards and including values less than the cluster
// timecode.
cb.SetClusterTimecode(5);
AddSimpleBlock(&cb, kAudioTrackNum, 5);
AddSimpleBlock(&cb, kVideoTrackNum, 5);
AddSimpleBlock(&cb, kAudioTrackNum, 3);
AddSimpleBlock(&cb, kVideoTrackNum, 3);
scoped_ptr<Cluster> cluster_a(cb.Finish());
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
// Verify that AppendData() doesn't accept more data now.
scoped_ptr<Cluster> cluster_b(GenerateCluster(6, 2));
EXPECT_FALSE(demuxer_->AppendData(kSourceId, cluster_b->data(),
cluster_b->size()));
}
TEST_F(ChunkDemuxerTest, TestPerStreamMonotonicallyIncreasingTimestamps) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> first_cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
ClusterBuilder cb;
// Test monotonic increasing timestamps on a per stream
// basis.
cb.SetClusterTimecode(5);
AddSimpleBlock(&cb, kAudioTrackNum, 5);
AddSimpleBlock(&cb, kVideoTrackNum, 5);
AddSimpleBlock(&cb, kAudioTrackNum, 4);
AddSimpleBlock(&cb, kVideoTrackNum, 7);
scoped_ptr<Cluster> cluster(cb.Finish());
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
}
// Test the case where a cluster is passed to AppendData() before
// INFO & TRACKS data.
TEST_F(ChunkDemuxerTest, TestClusterBeforeInitSegment) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
scoped_ptr<Cluster> cluster(GenerateCluster(0, 1));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
}
// Test cases where we get an EndOfStream() call during initialization.
TEST_F(ChunkDemuxerTest, TestEOSDuringInit) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
demuxer_->EndOfStream(PIPELINE_OK);
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamWithNoAppend) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, NewExpectedStatusCB(DEMUXER_ERROR_COULD_NOT_OPEN));
ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
CheckExpectedRanges("{ }");
demuxer_->EndOfStream(PIPELINE_OK);
ShutdownDemuxer();
CheckExpectedRanges("{ }");
demuxer_->RemoveId(kSourceId);
demuxer_ = NULL;
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamWithNoMediaAppend) {
ASSERT_TRUE(InitDemuxer(true, true));
CheckExpectedRanges("{ }");
demuxer_->EndOfStream(PIPELINE_OK);
CheckExpectedRanges("{ }");
}
TEST_F(ChunkDemuxerTest, TestDecodeErrorEndOfStream) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
CheckExpectedRanges(kDefaultFirstClusterRange);
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_DECODE));
demuxer_->EndOfStream(PIPELINE_ERROR_DECODE);
CheckExpectedRanges(kDefaultFirstClusterRange);
}
TEST_F(ChunkDemuxerTest, TestNetworkErrorEndOfStream) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
CheckExpectedRanges(kDefaultFirstClusterRange);
EXPECT_CALL(host_, OnDemuxerError(PIPELINE_ERROR_NETWORK));
demuxer_->EndOfStream(PIPELINE_ERROR_NETWORK);
}
// Helper class to reduce duplicate code when testing end of stream
// Read() behavior.
class EndOfStreamHelper {
public:
explicit EndOfStreamHelper(const scoped_refptr<Demuxer> demuxer)
: demuxer_(demuxer),
audio_read_done_(false),
video_read_done_(false) {
}
// Request a read on the audio and video streams.
void RequestReads() {
EXPECT_FALSE(audio_read_done_);
EXPECT_FALSE(video_read_done_);
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
audio->Read(base::Bind(&OnEndOfStreamReadDone,
&audio_read_done_));
video->Read(base::Bind(&OnEndOfStreamReadDone,
&video_read_done_));
}
// Check to see if |audio_read_done_| and |video_read_done_| variables
// match |expected|.
void CheckIfReadDonesWereCalled(bool expected) {
EXPECT_EQ(expected, audio_read_done_);
EXPECT_EQ(expected, video_read_done_);
}
private:
static void OnEndOfStreamReadDone(
bool* called,
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
EXPECT_EQ(status, DemuxerStream::kOk);
EXPECT_TRUE(buffer->IsEndOfStream());
*called = true;
}
scoped_refptr<Demuxer> demuxer_;
bool audio_read_done_;
bool video_read_done_;
DISALLOW_COPY_AND_ASSIGN(EndOfStreamHelper);
};
// Make sure that all pending reads that we don't have media data for get an
// "end of stream" buffer when EndOfStream() is called.
TEST_F(ChunkDemuxerTest, TestEndOfStreamWithPendingReads) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
bool audio_read_done_1 = false;
bool video_read_done_1 = false;
EndOfStreamHelper end_of_stream_helper_1(demuxer_);
EndOfStreamHelper end_of_stream_helper_2(demuxer_);
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done_1));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done_1));
end_of_stream_helper_1.RequestReads();
end_of_stream_helper_2.RequestReads();
EXPECT_TRUE(audio_read_done_1);
EXPECT_TRUE(video_read_done_1);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
end_of_stream_helper_2.CheckIfReadDonesWereCalled(false);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
demuxer_->EndOfStream(PIPELINE_OK);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
end_of_stream_helper_2.CheckIfReadDonesWereCalled(true);
}
// Make sure that all Read() calls after we get an EndOfStream()
// call return an "end of stream" buffer.
TEST_F(ChunkDemuxerTest, TestReadsAfterEndOfStream) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
bool audio_read_done_1 = false;
bool video_read_done_1 = false;
EndOfStreamHelper end_of_stream_helper_1(demuxer_);
EndOfStreamHelper end_of_stream_helper_2(demuxer_);
EndOfStreamHelper end_of_stream_helper_3(demuxer_);
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done_1));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done_1));
end_of_stream_helper_1.RequestReads();
EXPECT_TRUE(audio_read_done_1);
EXPECT_TRUE(video_read_done_1);
end_of_stream_helper_1.CheckIfReadDonesWereCalled(false);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kVideoBlockDuration)));
EXPECT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
end_of_stream_helper_1.CheckIfReadDonesWereCalled(true);
// Request a few more reads and make sure we immediately get
// end of stream buffers.
end_of_stream_helper_2.RequestReads();
end_of_stream_helper_2.CheckIfReadDonesWereCalled(true);
end_of_stream_helper_3.RequestReads();
end_of_stream_helper_3.CheckIfReadDonesWereCalled(true);
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringCanceledSeek) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(AppendCluster(0, 10));
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(138)));
EXPECT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
// Start the first seek.
demuxer_->StartWaitingForSeek();
// Simulate the pipeline finally calling Seek().
demuxer_->Seek(base::TimeDelta::FromMilliseconds(20),
NewExpectedStatusCB(PIPELINE_OK));
// Simulate another seek being requested before the first
// seek has finished prerolling.
demuxer_->CancelPendingSeek();
// Finish second seek.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(30),
NewExpectedStatusCB(PIPELINE_OK));
DemuxerStream::Status status;
base::TimeDelta last_timestamp;
// Make sure audio can reach end of stream.
ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kOk);
// Make sure video can reach end of stream.
ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kOk);
}
// Make sure AppendData() will accept elements that span multiple calls.
TEST_F(ChunkDemuxerTest, TestAppendingInPieces) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
scoped_array<uint8> info_tracks;
int info_tracks_size = 0;
CreateInitSegment(true, true, false, false, &info_tracks, &info_tracks_size);
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
size_t buffer_size = info_tracks_size + cluster_a->size() + cluster_b->size();
scoped_array<uint8> buffer(new uint8[buffer_size]);
uint8* dst = buffer.get();
memcpy(dst, info_tracks.get(), info_tracks_size);
dst += info_tracks_size;
memcpy(dst, cluster_a->data(), cluster_a->size());
dst += cluster_a->size();
memcpy(dst, cluster_b->data(), cluster_b->size());
dst += cluster_b->size();
ASSERT_TRUE(AppendDataInPieces(buffer.get(), buffer_size));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(audio);
ASSERT_TRUE(video);
GenerateExpectedReads(0, 9, audio, video);
}
TEST_F(ChunkDemuxerTest, TestWebMFile_AudioAndVideo) {
struct BufferTimestamps buffer_timestamps[] = {
{0, 0},
{33, 3},
{67, 6},
{100, 9},
{133, 12},
{kSkip, kSkip},
};
ASSERT_TRUE(ParseWebMFile("bear-320x240.webm", buffer_timestamps,
base::TimeDelta::FromMilliseconds(2744)));
}
TEST_F(ChunkDemuxerTest, TestWebMFile_LiveAudioAndVideo) {
struct BufferTimestamps buffer_timestamps[] = {
{0, 0},
{33, 3},
{67, 6},
{100, 9},
{133, 12},
{kSkip, kSkip},
};
ASSERT_TRUE(ParseWebMFile("bear-320x240-live.webm", buffer_timestamps,
kInfiniteDuration()));
}
TEST_F(ChunkDemuxerTest, TestWebMFile_AudioOnly) {
struct BufferTimestamps buffer_timestamps[] = {
{kSkip, 0},
{kSkip, 3},
{kSkip, 6},
{kSkip, 9},
{kSkip, 12},
{kSkip, kSkip},
};
ASSERT_TRUE(ParseWebMFile("bear-320x240-audio-only.webm", buffer_timestamps,
base::TimeDelta::FromMilliseconds(2744),
true, false));
}
TEST_F(ChunkDemuxerTest, TestWebMFile_VideoOnly) {
struct BufferTimestamps buffer_timestamps[] = {
{0, kSkip},
{33, kSkip},
{67, kSkip},
{100, kSkip},
{133, kSkip},
{kSkip, kSkip},
};
ASSERT_TRUE(ParseWebMFile("bear-320x240-video-only.webm", buffer_timestamps,
base::TimeDelta::FromMilliseconds(2703),
false, true));
}
TEST_F(ChunkDemuxerTest, TestWebMFile_AltRefFrames) {
struct BufferTimestamps buffer_timestamps[] = {
{0, 0},
{33, 3},
{33, 6},
{67, 9},
{100, 12},
{kSkip, kSkip},
};
ASSERT_TRUE(ParseWebMFile("bear-320x240-altref.webm", buffer_timestamps,
base::TimeDelta::FromMilliseconds(2767)));
}
// Verify that we output buffers before the entire cluster has been parsed.
TEST_F(ChunkDemuxerTest, TestIncrementalClusterParsing) {
ASSERT_TRUE(InitDemuxer(true, true));
ASSERT_TRUE(AppendEmptyCluster(0));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 6));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done));
// Make sure the reads haven't completed yet.
EXPECT_FALSE(audio_read_done);
EXPECT_FALSE(video_read_done);
// Append data one byte at a time until the audio read completes.
int i = 0;
for (; i < cluster->size() && !audio_read_done; ++i) {
ASSERT_TRUE(AppendData(cluster->data() + i, 1));
}
EXPECT_TRUE(audio_read_done);
EXPECT_FALSE(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) {
ASSERT_TRUE(AppendData(cluster->data() + i, 1));
}
EXPECT_TRUE(video_read_done);
EXPECT_LT(i, cluster->size());
audio_read_done = false;
video_read_done = false;
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(23),
&audio_read_done));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(33),
&video_read_done));
// Make sure the reads haven't completed yet.
EXPECT_FALSE(audio_read_done);
EXPECT_FALSE(video_read_done);
// Append the remaining data.
ASSERT_LT(i, cluster->size());
ASSERT_TRUE(AppendData(cluster->data() + i, cluster->size() - i));
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
}
TEST_F(ChunkDemuxerTest, TestParseErrorDuringInit) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(
kNoTimestamp(), DEMUXER_ERROR_COULD_NOT_OPEN));
ASSERT_EQ(AddId(), ChunkDemuxer::kOk);
uint8 tmp = 0;
ASSERT_TRUE(demuxer_->AppendData(kSourceId, &tmp, 1));
}
TEST_F(ChunkDemuxerTest, TestAVHeadersWithAudioOnlyType) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kNoTimestamp(),
DEMUXER_ERROR_COULD_NOT_OPEN));
std::vector<std::string> codecs(1);
codecs[0] = "vorbis";
ASSERT_EQ(demuxer_->AddId(kSourceId, "audio/webm", codecs),
ChunkDemuxer::kOk);
ASSERT_TRUE(AppendInitSegment(true, true));
}
TEST_F(ChunkDemuxerTest, TestAVHeadersWithVideoOnlyType) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kNoTimestamp(),
DEMUXER_ERROR_COULD_NOT_OPEN));
std::vector<std::string> codecs(1);
codecs[0] = "vp8";
ASSERT_EQ(demuxer_->AddId(kSourceId, "video/webm", codecs),
ChunkDemuxer::kOk);
ASSERT_TRUE(AppendInitSegment(true, true));
}
TEST_F(ChunkDemuxerTest, TestMultipleHeaders) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
// Append another identical initialization segment.
ASSERT_TRUE(AppendInitSegment(true, true));
scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
GenerateExpectedReads(0, 9, audio, video);
}
TEST_F(ChunkDemuxerTest, TestAddSeparateSourcesForAudioAndVideo) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
// Append audio and video data into separate source ids.
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
}
TEST_F(ChunkDemuxerTest, TestAddIdFailures) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_EQ(AddId(audio_id, true, false), ChunkDemuxer::kOk);
// Adding an id with audio/video should fail because we already added audio.
ASSERT_EQ(AddId(), ChunkDemuxer::kReachedIdLimit);
ASSERT_TRUE(AppendInitSegmentWithSourceId(audio_id, true, false));
// Adding an id after append should fail.
ASSERT_EQ(AddId(video_id, false, true), ChunkDemuxer::kReachedIdLimit);
}
// Test that Read() calls after a RemoveId() return "end of stream" buffers.
TEST_F(ChunkDemuxerTest, TestRemoveId) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
// Append audio and video data into separate source ids.
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
// Read() from audio should return normal buffers.
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
// Remove the audio id.
demuxer_->RemoveId(audio_id);
// Read() from audio should return "end of stream" buffers.
bool audio_read_done = false;
audio->Read(base::Bind(&OnReadDone_EOSExpected,
&audio_read_done));
EXPECT_TRUE(audio_read_done);
// Read() from video should still return normal buffers.
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
}
// Test that removing an ID immediately after adding it does not interfere with
// quota for new IDs in the future.
TEST_F(ChunkDemuxerTest, TestRemoveAndAddId) {
std::string audio_id_1 = "audio1";
ASSERT_TRUE(AddId(audio_id_1, true, false) == ChunkDemuxer::kOk);
demuxer_->RemoveId(audio_id_1);
std::string audio_id_2 = "audio2";
ASSERT_TRUE(AddId(audio_id_2, true, false) == ChunkDemuxer::kOk);
}
TEST_F(ChunkDemuxerTest, TestSeekCanceled) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Append cluster at the beginning of the stream.
scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
// Seek to an unbuffered region.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(50),
NewExpectedStatusCB(PIPELINE_OK));
// Attempt to read in unbuffered area; should not fulfill the read.
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
EXPECT_FALSE(audio_read_done);
EXPECT_FALSE(video_read_done);
// Now cancel the pending seek, which should flush the reads with empty
// buffers.
demuxer_->CancelPendingSeek();
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
// A seek back to the buffered region should succeed.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 4, audio, video);
}
TEST_F(ChunkDemuxerTest, TestSeekCanceledWhileWaitingForSeek) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Append cluster at the beginning of the stream.
scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 4));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
// Start waiting for a seek.
demuxer_->StartWaitingForSeek();
// Now cancel the upcoming seek to an unbuffered region.
demuxer_->CancelPendingSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(50),
NewExpectedStatusCB(PIPELINE_OK));
// Read requests should be fulfilled with empty buffers.
bool audio_read_done = false;
bool video_read_done = false;
audio->Read(base::Bind(&OnReadDone_AbortExpected, &audio_read_done));
video->Read(base::Bind(&OnReadDone_AbortExpected, &video_read_done));
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
// A seek back to the buffered region should succeed.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 4, audio, video);
}
// Test that Seek() successfully seeks to all source IDs.
TEST_F(ChunkDemuxerTest, TestSeekAudioAndVideoSources) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_ptr<Cluster> cluster_a1(
GenerateSingleStreamCluster(0, 92, kAudioTrackNum, kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v1(
GenerateSingleStreamCluster(0, 132, kVideoTrackNum, kVideoBlockDuration));
ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
// Read() should return buffers at 0.
bool audio_read_done = false;
bool video_read_done = false;
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&audio_read_done));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromMilliseconds(0),
&video_read_done));
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
// Seek to 3 (an unbuffered region).
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(3),
NewExpectedStatusCB(PIPELINE_OK));
audio_read_done = false;
video_read_done = false;
audio->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromSeconds(3),
&audio_read_done));
video->Read(base::Bind(&OnReadDone,
base::TimeDelta::FromSeconds(3),
&video_read_done));
// Read()s should not return until after data is appended at the Seek point.
EXPECT_FALSE(audio_read_done);
EXPECT_FALSE(video_read_done);
scoped_ptr<Cluster> cluster_a2(
GenerateSingleStreamCluster(3000, 3092, kAudioTrackNum,
kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v2(
GenerateSingleStreamCluster(3000, 3132, kVideoTrackNum,
kVideoBlockDuration));
ASSERT_TRUE(AppendData(audio_id, cluster_a2->data(), cluster_a2->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
// Read() should return buffers at 3.
EXPECT_TRUE(audio_read_done);
EXPECT_TRUE(video_read_done);
}
// Test ranges in an audio-only stream.
TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioIdOnly) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
ASSERT_EQ(AddId(kSourceId, true, false), ChunkDemuxer::kOk);
ASSERT_TRUE(AppendInitSegment(true, false));
// Test a simple cluster.
scoped_ptr<Cluster> cluster_1(GenerateSingleStreamCluster(0, 92,
kAudioTrackNum, kAudioBlockDuration));
ASSERT_TRUE(AppendData(cluster_1->data(), cluster_1->size()));
CheckExpectedRanges("{ [0,92) }");
// Append a disjoint cluster to check for two separate ranges.
scoped_ptr<Cluster> cluster_2(GenerateSingleStreamCluster(150, 219,
kAudioTrackNum, kAudioBlockDuration));
ASSERT_TRUE(AppendData(cluster_2->data(), cluster_2->size()));
CheckExpectedRanges("{ [0,92) [150,219) }");
}
// Test ranges in a video-only stream.
TEST_F(ChunkDemuxerTest, GetBufferedRanges_VideoIdOnly) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(
&host_, CreateInitDoneCB(kDefaultDuration(), PIPELINE_OK));
ASSERT_EQ(AddId(kSourceId, false, true), ChunkDemuxer::kOk);
ASSERT_TRUE(AppendInitSegment(false, true));
// Test a simple cluster.
scoped_ptr<Cluster> cluster_1(GenerateSingleStreamCluster(0, 132,
kVideoTrackNum, kVideoBlockDuration));
ASSERT_TRUE(AppendData(cluster_1->data(), cluster_1->size()));
CheckExpectedRanges("{ [0,132) }");
// Append a disjoint cluster to check for two separate ranges.
scoped_ptr<Cluster> cluster_2(GenerateSingleStreamCluster(200, 299,
kVideoTrackNum, kVideoBlockDuration));
ASSERT_TRUE(AppendData(cluster_2->data(), cluster_2->size()));
CheckExpectedRanges("{ [0,132) [200,299) }");
}
TEST_F(ChunkDemuxerTest, GetBufferedRanges_AudioVideo) {
ASSERT_TRUE(InitDemuxer(true, true));
// Audio: 0 -> 23
// Video: 0 -> 33
// Buffered Range: 0 -> 23
// Audio block duration is smaller than video block duration,
// so the buffered ranges should correspond to the audio blocks.
scoped_ptr<Cluster> cluster_a0(
GenerateSingleStreamCluster(0, kAudioBlockDuration, kAudioTrackNum,
kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v0(
GenerateSingleStreamCluster(0, kVideoBlockDuration, kVideoTrackNum,
kVideoBlockDuration));
ASSERT_TRUE(AppendData(cluster_a0->data(), cluster_a0->size()));
ASSERT_TRUE(AppendData(cluster_v0->data(), cluster_v0->size()));
CheckExpectedRanges("{ [0,23) }");
// Audio: 300 -> 400
// Video: 320 -> 420
// Buffered Range: 320 -> 400 (end overlap)
scoped_ptr<Cluster> cluster_a1(
GenerateSingleStreamCluster(300, 400, kAudioTrackNum, 50));
scoped_ptr<Cluster> cluster_v1(
GenerateSingleStreamCluster(320, 420, kVideoTrackNum, 50));
ASSERT_TRUE(AppendData(cluster_a1->data(), cluster_a1->size()));
ASSERT_TRUE(AppendData(cluster_v1->data(), cluster_v1->size()));
CheckExpectedRanges("{ [0,23) [320,400) }");
// Audio: 520 -> 590
// Video: 500 -> 570
// Buffered Range: 520 -> 570 (front overlap)
scoped_ptr<Cluster> cluster_a2(
GenerateSingleStreamCluster(520, 590, kAudioTrackNum, 70));
scoped_ptr<Cluster> cluster_v2(
GenerateSingleStreamCluster(500, 570, kVideoTrackNum, 70));
ASSERT_TRUE(AppendData(cluster_a2->data(), cluster_a2->size()));
ASSERT_TRUE(AppendData(cluster_v2->data(), cluster_v2->size()));
CheckExpectedRanges("{ [0,23) [320,400) [520,570) }");
// Audio: 720 -> 750
// Video: 700 -> 770
// Buffered Range: 720 -> 750 (complete overlap, audio)
scoped_ptr<Cluster> cluster_a3(
GenerateSingleStreamCluster(720, 750, kAudioTrackNum, 30));
scoped_ptr<Cluster> cluster_v3(
GenerateSingleStreamCluster(700, 770, kVideoTrackNum, 70));
ASSERT_TRUE(AppendData(cluster_a3->data(), cluster_a3->size()));
ASSERT_TRUE(AppendData(cluster_v3->data(), cluster_v3->size()));
CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) }");
// Audio: 900 -> 970
// Video: 920 -> 950
// Buffered Range: 920 -> 950 (complete overlap, video)
scoped_ptr<Cluster> cluster_a4(
GenerateSingleStreamCluster(900, 970, kAudioTrackNum, 70));
scoped_ptr<Cluster> cluster_v4(
GenerateSingleStreamCluster(920, 950, kVideoTrackNum, 30));
ASSERT_TRUE(AppendData(cluster_a4->data(), cluster_a4->size()));
ASSERT_TRUE(AppendData(cluster_v4->data(), cluster_v4->size()));
CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
// Appending within buffered range should not affect buffered ranges.
scoped_ptr<Cluster> cluster_a5(
GenerateSingleStreamCluster(930, 950, kAudioTrackNum, 20));
ASSERT_TRUE(AppendData(cluster_a5->data(), cluster_a5->size()));
CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
// Appending to single stream outside buffered ranges should not affect
// buffered ranges.
scoped_ptr<Cluster> cluster_v5(
GenerateSingleStreamCluster(1230, 1240, kVideoTrackNum, 10));
ASSERT_TRUE(AppendData(cluster_v5->data(), cluster_v5->size()));
CheckExpectedRanges("{ [0,23) [320,400) [520,570) [720,750) [920,950) }");
}
// Once EndOfStream() 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));
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(0, 90, kAudioTrackNum, 90));
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(0, 100, kVideoTrackNum, 100));
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
ASSERT_TRUE(AppendData(cluster_v->data(), cluster_v->size()));
CheckExpectedRanges("{ [0,90) }");
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(100)));
demuxer_->EndOfStream(PIPELINE_OK);
CheckExpectedRanges("{ [0,100) }");
}
TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodes) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Create a cluster where the video timecode begins 25ms after the audio.
scoped_ptr<Cluster> start_cluster(GenerateCluster(0, 25, 8));
ASSERT_TRUE(AppendData(start_cluster->data(), start_cluster->size()));
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
GenerateExpectedReads(0, 25, 8, audio, video);
// Seek to 5 seconds.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromSeconds(5),
NewExpectedStatusCB(PIPELINE_OK));
// Generate a cluster to fulfill this seek, where audio timecode begins 25ms
// after the video.
scoped_ptr<Cluster> middle_cluster(GenerateCluster(5025, 5000, 8));
ASSERT_TRUE(AppendData(middle_cluster->data(), middle_cluster->size()));
GenerateExpectedReads(5025, 5000, 8, audio, video);
}
TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesSeparateSources) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Generate two streams where the video stream starts 5ms after the audio
// stream and append them.
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(30, 4 * kVideoBlockDuration + 30,
kVideoTrackNum, kVideoBlockDuration));
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(25, 4 * kAudioBlockDuration + 25,
kAudioTrackNum, kAudioBlockDuration));
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
// Both streams should be able to fulfill a seek to 25.
demuxer_->Seek(base::TimeDelta::FromMilliseconds(25),
NewExpectedStatusCB(PIPELINE_OK));
GenerateSingleStreamExpectedReads(25, 4, audio, kAudioBlockDuration);
GenerateSingleStreamExpectedReads(30, 4, video, kVideoBlockDuration);
}
TEST_F(ChunkDemuxerTest, TestDifferentStreamTimecodesOutOfRange) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Generate two streams where the video stream starts 10s after the audio
// stream and append them.
scoped_ptr<Cluster> cluster_v(
GenerateSingleStreamCluster(10000, 4 * kVideoBlockDuration + 10000,
kVideoTrackNum, kVideoBlockDuration));
scoped_ptr<Cluster> cluster_a(
GenerateSingleStreamCluster(0, 4 * kAudioBlockDuration + 0,
kAudioTrackNum, kAudioBlockDuration));
ASSERT_TRUE(AppendData(audio_id, cluster_a->data(), cluster_a->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v->data(), cluster_v->size()));
// Should not be able to fulfill a seek to 0.
demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
NewExpectedStatusCB(PIPELINE_ERROR_ABORT));
ExpectRead(audio, 0);
ExpectEndOfStream(video);
}
TEST_F(ChunkDemuxerTest, TestClusterWithNoBuffers) {
ASSERT_TRUE(InitDemuxer(true, true));
// Generate and append an empty cluster beginning at 0.
ASSERT_TRUE(AppendEmptyCluster(0));
// Sanity check that data can be appended after this cluster correctly.
scoped_ptr<Cluster> media_data(GenerateCluster(0, 2));
ASSERT_TRUE(AppendData(media_data->data(), media_data->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
ExpectRead(audio, 0);
ExpectRead(video, 0);
}
TEST_F(ChunkDemuxerTest, TestCodecPrefixMatching) {
ChunkDemuxer::Status expected = ChunkDemuxer::kNotSupported;
#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
expected = ChunkDemuxer::kOk;
#endif
std::vector<std::string> codecs;
codecs.push_back("avc1.4D4041");
codecs.push_back("mp4a.40.2");
EXPECT_EQ(demuxer_->AddId("source_id", "video/mp4", codecs), expected);
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamFailures) {
std::string audio_id = "audio";
std::string video_id = "video";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_ptr<Cluster> cluster_a1(
GenerateSingleStreamCluster(0, 35, kAudioTrackNum, 35));
scoped_ptr<Cluster> cluster_v1(
GenerateSingleStreamCluster(0, 10, kVideoTrackNum, 5));
scoped_ptr<Cluster> cluster_v2(
GenerateSingleStreamCluster(10, 25, kVideoTrackNum, 5));
scoped_ptr<Cluster> cluster_v3(
GenerateSingleStreamCluster(30, 50, kVideoTrackNum, 10));
ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v3->data(), cluster_v3->size()));
CheckExpectedRanges(audio_id, "{ [0,35) }");
CheckExpectedRanges(video_id, "{ [0,10) [30,50) }");
// Make sure that end of stream fails because there is a gap between
// the current position(0) and the end of the appended data.
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(50)));
ASSERT_FALSE(demuxer_->EndOfStream(PIPELINE_OK));
// Seek to an time that is inside the last ranges for both streams
// and verify that the EndOfStream() is successful.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(30),
NewExpectedStatusCB(PIPELINE_OK));
ASSERT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
// Append an zero length buffer to transition out of the end of stream state.
ASSERT_TRUE(AppendData(NULL, 0));
// Seek back to 0 and verify that EndOfStream() fails again.
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
NewExpectedStatusCB(PIPELINE_OK));
ASSERT_FALSE(demuxer_->EndOfStream(PIPELINE_OK));
// Append the missing range and verify that EndOfStream() succeeds now.
ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
CheckExpectedRanges(audio_id, "{ [0,35) }");
CheckExpectedRanges(video_id, "{ [0,50) }");
ASSERT_TRUE(demuxer_->EndOfStream(PIPELINE_OK));
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamStillSetAfterSeek) {
ASSERT_TRUE(InitDemuxer(true, true));
EXPECT_CALL(host_, SetDuration(_))
.Times(AnyNumber());
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
base::TimeDelta kLastAudioTimestamp = base::TimeDelta::FromMilliseconds(92);
base::TimeDelta kLastVideoTimestamp = base::TimeDelta::FromMilliseconds(99);
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
demuxer_->EndOfStream(PIPELINE_OK);
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
DemuxerStream::Status status;
base::TimeDelta last_timestamp;
// Verify that we can read audio & video to the end w/o problems.
ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
EXPECT_EQ(DemuxerStream::kOk, status);
EXPECT_EQ(kLastAudioTimestamp, last_timestamp);
ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
EXPECT_EQ(DemuxerStream::kOk, status);
EXPECT_EQ(kLastVideoTimestamp, last_timestamp);
// Seek back to 0 and verify that we can read to the end again..
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
NewExpectedStatusCB(PIPELINE_OK));
ReadUntilNotOkOrEndOfStream(audio, &status, &last_timestamp);
EXPECT_EQ(DemuxerStream::kOk, status);
EXPECT_EQ(kLastAudioTimestamp, last_timestamp);
ReadUntilNotOkOrEndOfStream(video, &status, &last_timestamp);
EXPECT_EQ(DemuxerStream::kOk, status);
EXPECT_EQ(kLastVideoTimestamp, last_timestamp);
}
TEST_F(ChunkDemuxerTest, TestGetBufferedRangesBeforeInitSegment) {
EXPECT_CALL(*this, DemuxerOpened());
demuxer_->Initialize(&host_, CreateInitDoneCB(PIPELINE_OK));
ASSERT_EQ(AddId("audio", true, false), ChunkDemuxer::kOk);
ASSERT_EQ(AddId("video", false, true), ChunkDemuxer::kOk);
CheckExpectedRanges("audio", "{ }");
CheckExpectedRanges("video", "{ }");
}
// Test that Seek() completes successfully when the first cluster
// arrives.
TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringSeek) {
InSequence s;
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
demuxer_->StartWaitingForSeek();
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kDefaultSecondClusterEndTimestamp)));
demuxer_->EndOfStream(PIPELINE_OK);
demuxer_->Seek(base::TimeDelta::FromSeconds(0),
NewExpectedStatusCB(PIPELINE_OK));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateExpectedReads(0, 4, audio, video);
GenerateExpectedReads(46, 66, 5, audio, video);
EndOfStreamHelper end_of_stream_helper(demuxer_);
end_of_stream_helper.RequestReads();
end_of_stream_helper.CheckIfReadDonesWereCalled(true);
}
TEST_F(ChunkDemuxerTest, TestConfigChange_Video) {
InSequence s;
ASSERT_TRUE(InitDemuxerWithConfigChangeData());
scoped_refptr<DemuxerStream> stream =
demuxer_->GetStream(DemuxerStream::VIDEO);
DemuxerStream::Status status;
base::TimeDelta last_timestamp;
// Fetch initial video config and verify it matches what we expect.
const VideoDecoderConfig& video_config_1 = stream->video_decoder_config();
ASSERT_TRUE(video_config_1.IsValidConfig());
EXPECT_EQ(video_config_1.natural_size().width(), 320);
EXPECT_EQ(video_config_1.natural_size().height(), 240);
ExpectRead(stream, 0);
ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 501);
// Fetch the new decoder config.
const VideoDecoderConfig& video_config_2 = stream->video_decoder_config();
ASSERT_TRUE(video_config_2.IsValidConfig());
EXPECT_EQ(video_config_2.natural_size().width(), 640);
EXPECT_EQ(video_config_2.natural_size().height(), 360);
ExpectRead(stream, 527);
// Read until the next config change.
ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 793);
// Get the new config and verify that it matches the first one.
ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
ExpectRead(stream, 801);
// Read until the end of the stream just to make sure there aren't any other
// config changes.
ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kOk);
}
TEST_F(ChunkDemuxerTest, TestConfigChange_Audio) {
InSequence s;
ASSERT_TRUE(InitDemuxerWithConfigChangeData());
scoped_refptr<DemuxerStream> stream =
demuxer_->GetStream(DemuxerStream::AUDIO);
DemuxerStream::Status status;
base::TimeDelta last_timestamp;
// Fetch initial audio config and verify it matches what we expect.
const AudioDecoderConfig& audio_config_1 = stream->audio_decoder_config();
ASSERT_TRUE(audio_config_1.IsValidConfig());
EXPECT_EQ(audio_config_1.samples_per_second(), 44100);
EXPECT_EQ(audio_config_1.extra_data_size(), 3863u);
ExpectRead(stream, 0);
ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 524);
// Fetch the new decoder config.
const AudioDecoderConfig& audio_config_2 = stream->audio_decoder_config();
ASSERT_TRUE(audio_config_2.IsValidConfig());
EXPECT_EQ(audio_config_2.samples_per_second(), 44100);
EXPECT_EQ(audio_config_2.extra_data_size(), 3935u);
ExpectRead(stream, 527);
// Read until the next config change.
ReadUntilNotOkOrEndOfStream(stream, &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.
ASSERT_TRUE(audio_config_1.Matches(stream->audio_decoder_config()));
ExpectRead(stream, 779);
// Read until the end of the stream just to make sure there aren't any other
// config changes.
ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp);
ASSERT_EQ(status, DemuxerStream::kOk);
}
TEST_F(ChunkDemuxerTest, TestConfigChange_Seek) {
InSequence s;
ASSERT_TRUE(InitDemuxerWithConfigChangeData());
scoped_refptr<DemuxerStream> stream =
demuxer_->GetStream(DemuxerStream::VIDEO);
// Fetch initial video config and verify it matches what we expect.
const VideoDecoderConfig& video_config_1 = stream->video_decoder_config();
ASSERT_TRUE(video_config_1.IsValidConfig());
EXPECT_EQ(video_config_1.natural_size().width(), 320);
EXPECT_EQ(video_config_1.natural_size().height(), 240);
ExpectRead(stream, 0);
// Seek to a location with a different config.
demuxer_->Seek(base::TimeDelta::FromMilliseconds(527),
NewExpectedStatusCB(PIPELINE_OK));
// Verify that the config change is signalled.
ExpectConfigChanged(stream);
// Fetch the new decoder config and verify it is what we expect.
const VideoDecoderConfig& video_config_2 = stream->video_decoder_config();
ASSERT_TRUE(video_config_2.IsValidConfig());
EXPECT_EQ(video_config_2.natural_size().width(), 640);
EXPECT_EQ(video_config_2.natural_size().height(), 360);
// Verify that Read() will return a buffer now.
ExpectRead(stream, 527);
// Seek back to the beginning and verify we get another config change.
demuxer_->Seek(base::TimeDelta::FromMilliseconds(0),
NewExpectedStatusCB(PIPELINE_OK));
ExpectConfigChanged(stream);
ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
ExpectRead(stream, 0);
// Seek to a location that requires a config change and then
// seek to a new location that has the same configuration as
// the start of the file without a Read() in the middle.
demuxer_->Seek(base::TimeDelta::FromMilliseconds(527),
NewExpectedStatusCB(PIPELINE_OK));
demuxer_->Seek(base::TimeDelta::FromMilliseconds(801),
NewExpectedStatusCB(PIPELINE_OK));
// Verify that no config change is signalled.
ExpectRead(stream, 801);
ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config()));
}
TEST_F(ChunkDemuxerTest, TestTimestampPositiveOffset) {
ASSERT_TRUE(InitDemuxer(true, true));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
kSourceId, base::TimeDelta::FromSeconds(30)));
scoped_ptr<Cluster> cluster(GenerateCluster(0, 2));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(30000),
NewExpectedStatusCB(PIPELINE_OK));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateExpectedReads(30000, 2, audio, video);
}
TEST_F(ChunkDemuxerTest, TestTimestampNegativeOffset) {
ASSERT_TRUE(InitDemuxer(true, true));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
kSourceId, base::TimeDelta::FromSeconds(-1)));
scoped_ptr<Cluster> cluster = GenerateCluster(1000, 2);
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
GenerateExpectedReads(0, 2, audio, video);
}
TEST_F(ChunkDemuxerTest, TestTimestampOffsetSeparateStreams) {
std::string audio_id = "audio1";
std::string video_id = "video1";
ASSERT_TRUE(InitDemuxerAudioAndVideoSources(audio_id, video_id));
scoped_refptr<DemuxerStream> audio =
demuxer_->GetStream(DemuxerStream::AUDIO);
scoped_refptr<DemuxerStream> video =
demuxer_->GetStream(DemuxerStream::VIDEO);
scoped_ptr<Cluster> cluster_a1(
GenerateSingleStreamCluster(
2500, 2500 + kAudioBlockDuration * 4, kAudioTrackNum,
kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v1(
GenerateSingleStreamCluster(
2500, 2500 + kVideoBlockDuration * 4, kVideoTrackNum,
kVideoBlockDuration));
scoped_ptr<Cluster> cluster_a2(
GenerateSingleStreamCluster(
0, kAudioBlockDuration * 4, kAudioTrackNum, kAudioBlockDuration));
scoped_ptr<Cluster> cluster_v2(
GenerateSingleStreamCluster(
0, kVideoBlockDuration * 4, kVideoTrackNum, kVideoBlockDuration));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
audio_id, base::TimeDelta::FromMilliseconds(-2500)));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
video_id, base::TimeDelta::FromMilliseconds(-2500)));
ASSERT_TRUE(AppendData(audio_id, cluster_a1->data(), cluster_a1->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v1->data(), cluster_v1->size()));
GenerateSingleStreamExpectedReads(0, 4, audio, kAudioBlockDuration);
GenerateSingleStreamExpectedReads(0, 4, video, kVideoBlockDuration);
demuxer_->StartWaitingForSeek();
demuxer_->Seek(base::TimeDelta::FromMilliseconds(27300),
NewExpectedStatusCB(PIPELINE_OK));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
audio_id, base::TimeDelta::FromMilliseconds(27300)));
ASSERT_TRUE(demuxer_->SetTimestampOffset(
video_id, base::TimeDelta::FromMilliseconds(27300)));
ASSERT_TRUE(AppendData(audio_id, cluster_a2->data(), cluster_a2->size()));
ASSERT_TRUE(AppendData(video_id, cluster_v2->data(), cluster_v2->size()));
GenerateSingleStreamExpectedReads(27300, 4, video, kVideoBlockDuration);
GenerateSingleStreamExpectedReads(27300, 4, audio, kAudioBlockDuration);
}
TEST_F(ChunkDemuxerTest, TestTimestampOffsetMidParse) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster = GenerateCluster(0, 2);
// Append only part of the cluster data.
ASSERT_TRUE(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)));
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)));
}
TEST_F(ChunkDemuxerTest, TestDurationChange) {
ASSERT_TRUE(InitDemuxer(true, true));
static const int kStreamDuration = kDefaultDuration().InMilliseconds();
// Add data leading up to the currently set duration.
scoped_ptr<Cluster> first_cluster = GenerateCluster(
kStreamDuration - kAudioBlockDuration,
kStreamDuration - kVideoBlockDuration, 2);
ASSERT_TRUE(AppendData(first_cluster->data(), first_cluster->size()));
CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
// Add data at the currently set duration. The duration should not increase.
scoped_ptr<Cluster> second_cluster = GenerateCluster(
kDefaultDuration().InMilliseconds(), 2);
ASSERT_TRUE(AppendData(second_cluster->data(), second_cluster->size()));
// Range should not be affected.
CheckExpectedRanges(kSourceId, "{ [201191,201224) }");
// Now add data past the duration and expect a new duration to be signalled.
static const int kNewStreamDuration =
kStreamDuration + kAudioBlockDuration * 2;
scoped_ptr<Cluster> third_cluster = GenerateCluster(
kStreamDuration + kAudioBlockDuration,
kStreamDuration + kVideoBlockDuration, 2);
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kNewStreamDuration)));
ASSERT_TRUE(AppendData(third_cluster->data(), third_cluster->size()));
// See that the range has increased appropriately.
CheckExpectedRanges(kSourceId, "{ [201191,201270) }");
}
TEST_F(ChunkDemuxerTest, TestDurationChangeTimestampOffset) {
ASSERT_TRUE(InitDemuxer(true, true));
ASSERT_TRUE(demuxer_->SetTimestampOffset(kSourceId, kDefaultDuration()));
scoped_ptr<Cluster> cluster = GenerateCluster(0, 4);
EXPECT_CALL(host_, SetDuration(
kDefaultDuration() + base::TimeDelta::FromMilliseconds(
kAudioBlockDuration * 2)));
ASSERT_TRUE(AppendData(cluster->data(), cluster->size()));
}
TEST_F(ChunkDemuxerTest, TestEndOfStreamTruncateDuration) {
ASSERT_TRUE(InitDemuxer(true, true));
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
EXPECT_CALL(host_, SetDuration(
base::TimeDelta::FromMilliseconds(kDefaultFirstClusterEndTimestamp)));
demuxer_->EndOfStream(PIPELINE_OK);
}
TEST_F(ChunkDemuxerTest, TestZeroLengthAppend) {
ASSERT_TRUE(InitDemuxer(true, true));
ASSERT_TRUE(AppendData(NULL, 0));
}
TEST_F(ChunkDemuxerTest, TestAppendAfterEndOfStream) {
ASSERT_TRUE(InitDemuxer(true, true));
EXPECT_CALL(host_, SetDuration(_))
.Times(AnyNumber());
scoped_ptr<Cluster> cluster_a(kDefaultFirstCluster());
ASSERT_TRUE(AppendData(cluster_a->data(), cluster_a->size()));
demuxer_->EndOfStream(PIPELINE_OK);
scoped_ptr<Cluster> cluster_b(kDefaultSecondCluster());
ASSERT_TRUE(AppendData(cluster_b->data(), cluster_b->size()));
demuxer_->EndOfStream(PIPELINE_OK);
}
// 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, TestShutdownBeforeInitialize) {
demuxer_->Shutdown();
demuxer_->Initialize(
&host_, CreateInitDoneCB(DEMUXER_ERROR_COULD_NOT_OPEN));
message_loop_.RunUntilIdle();
}
} // namespace media