blob: ff2f16c4a586f990c368933bce7ec45615e6ade5 [file] [log] [blame]
// Copyright 2014 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 <map>
#include <string>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/filters/chunk_demuxer.h"
#include "media/filters/frame_processor.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::InSequence;
using ::testing::StrictMock;
using ::testing::Values;
namespace media {
typedef StreamParser::BufferQueue BufferQueue;
typedef StreamParser::TextBufferQueueMap TextBufferQueueMap;
typedef StreamParser::TrackId TrackId;
static void LogFunc(const std::string& str) { DVLOG(1) << str; }
// Used for setting expectations on callbacks. Using a StrictMock also lets us
// test for missing or extra callbacks.
class FrameProcessorTestCallbackHelper {
public:
FrameProcessorTestCallbackHelper() {}
virtual ~FrameProcessorTestCallbackHelper() {}
MOCK_METHOD1(PossibleDurationIncrease, void(base::TimeDelta new_duration));
// Helper that calls the mock method as well as does basic sanity checks on
// |new_duration|.
void OnPossibleDurationIncrease(base::TimeDelta new_duration) {
PossibleDurationIncrease(new_duration);
ASSERT_NE(kNoTimestamp(), new_duration);
ASSERT_NE(kInfiniteDuration(), new_duration);
}
private:
DISALLOW_COPY_AND_ASSIGN(FrameProcessorTestCallbackHelper);
};
// Test parameter determines indicates if the TEST_P instance is targeted for
// sequence mode (if true), or segments mode (if false).
class FrameProcessorTest : public testing::TestWithParam<bool> {
protected:
FrameProcessorTest()
: frame_processor_(new FrameProcessor(base::Bind(
&FrameProcessorTestCallbackHelper::OnPossibleDurationIncrease,
base::Unretained(&callbacks_)))),
append_window_end_(kInfiniteDuration()),
new_media_segment_(false),
audio_id_(FrameProcessor::kAudioTrackId),
video_id_(FrameProcessor::kVideoTrackId),
frame_duration_(base::TimeDelta::FromMilliseconds(10)) {
}
enum StreamFlags {
HAS_AUDIO = 1 << 0,
HAS_VIDEO = 1 << 1
};
void AddTestTracks(int stream_flags) {
const bool has_audio = (stream_flags & HAS_AUDIO) != 0;
const bool has_video = (stream_flags & HAS_VIDEO) != 0;
ASSERT_TRUE(has_audio || has_video);
if (has_audio) {
CreateAndConfigureStream(DemuxerStream::AUDIO);
ASSERT_TRUE(audio_);
EXPECT_TRUE(frame_processor_->AddTrack(audio_id_, audio_.get()));
audio_->Seek(base::TimeDelta());
audio_->StartReturningData();
}
if (has_video) {
CreateAndConfigureStream(DemuxerStream::VIDEO);
ASSERT_TRUE(video_);
EXPECT_TRUE(frame_processor_->AddTrack(video_id_, video_.get()));
video_->Seek(base::TimeDelta());
video_->StartReturningData();
}
}
void SetTimestampOffset(base::TimeDelta new_offset) {
timestamp_offset_ = new_offset;
frame_processor_->SetGroupStartTimestampIfInSequenceMode(timestamp_offset_);
}
BufferQueue StringToBufferQueue(const std::string& buffers_to_append,
const TrackId track_id,
const DemuxerStream::Type type) {
std::vector<std::string> timestamps;
base::SplitString(buffers_to_append, ' ', &timestamps);
BufferQueue buffers;
for (size_t i = 0; i < timestamps.size(); i++) {
bool is_keyframe = false;
if (EndsWith(timestamps[i], "K", true)) {
is_keyframe = true;
// Remove the "K" off of the token.
timestamps[i] = timestamps[i].substr(0, timestamps[i].length() - 1);
}
double time_in_ms;
CHECK(base::StringToDouble(timestamps[i], &time_in_ms));
// Create buffer. Encode the original time_in_ms as the buffer's data to
// enable later verification of possible buffer relocation in presentation
// timeline due to coded frame processing.
const uint8* timestamp_as_data = reinterpret_cast<uint8*>(&time_in_ms);
scoped_refptr<StreamParserBuffer> buffer =
StreamParserBuffer::CopyFrom(timestamp_as_data, sizeof(time_in_ms),
is_keyframe, type, track_id);
base::TimeDelta timestamp = base::TimeDelta::FromSecondsD(
time_in_ms / base::Time::kMillisecondsPerSecond);
buffer->set_timestamp(timestamp);
buffer->set_duration(frame_duration_);
buffers.push_back(buffer);
}
return buffers;
}
void ProcessFrames(const std::string& audio_timestamps,
const std::string& video_timestamps) {
ASSERT_TRUE(frame_processor_->ProcessFrames(
StringToBufferQueue(audio_timestamps, audio_id_, DemuxerStream::AUDIO),
StringToBufferQueue(video_timestamps, video_id_, DemuxerStream::VIDEO),
empty_text_buffers_,
append_window_start_, append_window_end_,
&new_media_segment_, &timestamp_offset_));
}
void CheckExpectedRangesByTimestamp(ChunkDemuxerStream* stream,
const std::string& expected) {
// Note, DemuxerStream::TEXT streams return [0,duration (==infinity here))
Ranges<base::TimeDelta> r = stream->GetBufferedRanges(kInfiniteDuration());
std::stringstream ss;
ss << "{ ";
for (size_t i = 0; i < r.size(); ++i) {
int64 start = r.start(i).InMilliseconds();
int64 end = r.end(i).InMilliseconds();
ss << "[" << start << "," << end << ") ";
}
ss << "}";
EXPECT_EQ(expected, ss.str());
}
void CheckReadStalls(ChunkDemuxerStream* stream) {
int loop_count = 0;
do {
read_callback_called_ = false;
stream->Read(base::Bind(&FrameProcessorTest::StoreStatusAndBuffer,
base::Unretained(this)));
message_loop_.RunUntilIdle();
} while (++loop_count < 2 && read_callback_called_ &&
last_read_status_ == DemuxerStream::kAborted);
ASSERT_FALSE(read_callback_called_ &&
last_read_status_ == DemuxerStream::kAborted)
<< "2 kAborted reads in a row. Giving up.";
EXPECT_FALSE(read_callback_called_);
}
// Format of |expected| is a space-delimited sequence of
// timestamp_in_ms:original_timestamp_in_ms
// original_timestamp_in_ms (and the colon) must be omitted if it is the same
// as timestamp_in_ms.
void CheckReadsThenReadStalls(ChunkDemuxerStream* stream,
const std::string& expected) {
std::vector<std::string> timestamps;
base::SplitString(expected, ' ', &timestamps);
std::stringstream ss;
for (size_t i = 0; i < timestamps.size(); ++i) {
int loop_count = 0;
do {
read_callback_called_ = false;
stream->Read(base::Bind(&FrameProcessorTest::StoreStatusAndBuffer,
base::Unretained(this)));
message_loop_.RunUntilIdle();
EXPECT_TRUE(read_callback_called_);
} while (++loop_count < 2 &&
last_read_status_ == DemuxerStream::kAborted);
ASSERT_FALSE(last_read_status_ == DemuxerStream::kAborted)
<< "2 kAborted reads in a row. Giving up.";
EXPECT_EQ(DemuxerStream::kOk, last_read_status_);
EXPECT_FALSE(last_read_buffer_->end_of_stream());
if (i > 0)
ss << " ";
int time_in_ms = last_read_buffer_->timestamp().InMilliseconds();
ss << time_in_ms;
// Decode the original_time_in_ms from the buffer's data.
double original_time_in_ms;
ASSERT_EQ(static_cast<int>(sizeof(original_time_in_ms)),
last_read_buffer_->data_size());
original_time_in_ms = *(reinterpret_cast<const double*>(
last_read_buffer_->data()));
if (original_time_in_ms != time_in_ms)
ss << ":" << original_time_in_ms;
// Detect full-discard preroll buffer.
if (last_read_buffer_->discard_padding().first == kInfiniteDuration() &&
last_read_buffer_->discard_padding().second == base::TimeDelta()) {
ss << "P";
}
}
EXPECT_EQ(expected, ss.str());
CheckReadStalls(stream);
}
base::MessageLoop message_loop_;
StrictMock<FrameProcessorTestCallbackHelper> callbacks_;
scoped_ptr<FrameProcessor> frame_processor_;
base::TimeDelta append_window_start_;
base::TimeDelta append_window_end_;
bool new_media_segment_;
base::TimeDelta timestamp_offset_;
scoped_ptr<ChunkDemuxerStream> audio_;
scoped_ptr<ChunkDemuxerStream> video_;
const TrackId audio_id_;
const TrackId video_id_;
const base::TimeDelta frame_duration_; // Currently the same for all streams.
const BufferQueue empty_queue_;
const TextBufferQueueMap empty_text_buffers_;
// StoreStatusAndBuffer's most recent result.
DemuxerStream::Status last_read_status_;
scoped_refptr<DecoderBuffer> last_read_buffer_;
bool read_callback_called_;
private:
void StoreStatusAndBuffer(DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
if (status == DemuxerStream::kOk && buffer.get()) {
DVLOG(3) << __FUNCTION__ << "status: " << status << " ts: "
<< buffer->timestamp().InSecondsF();
} else {
DVLOG(3) << __FUNCTION__ << "status: " << status << " ts: n/a";
}
read_callback_called_ = true;
last_read_status_ = status;
last_read_buffer_ = buffer;
}
void CreateAndConfigureStream(DemuxerStream::Type type) {
// TODO(wolenetz/dalecurtis): Also test with splicing disabled?
switch (type) {
case DemuxerStream::AUDIO: {
ASSERT_FALSE(audio_);
audio_.reset(new ChunkDemuxerStream(DemuxerStream::AUDIO, true));
AudioDecoderConfig decoder_config(kCodecVorbis,
kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO,
1000,
NULL,
0,
false);
frame_processor_->OnPossibleAudioConfigUpdate(decoder_config);
ASSERT_TRUE(
audio_->UpdateAudioConfig(decoder_config, base::Bind(&LogFunc)));
break;
}
case DemuxerStream::VIDEO: {
ASSERT_FALSE(video_);
video_.reset(new ChunkDemuxerStream(DemuxerStream::VIDEO, true));
ASSERT_TRUE(video_->UpdateVideoConfig(TestVideoConfig::Normal(),
base::Bind(&LogFunc)));
break;
}
// TODO(wolenetz): Test text coded frame processing.
case DemuxerStream::TEXT:
case DemuxerStream::UNKNOWN:
case DemuxerStream::NUM_TYPES: {
ASSERT_FALSE(true);
}
}
}
DISALLOW_COPY_AND_ASSIGN(FrameProcessorTest);
};
TEST_F(FrameProcessorTest, WrongTypeInAppendedBuffer) {
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
ASSERT_FALSE(frame_processor_->ProcessFrames(
StringToBufferQueue("0K", audio_id_, DemuxerStream::VIDEO),
empty_queue_,
empty_text_buffers_,
append_window_start_, append_window_end_,
&new_media_segment_, &timestamp_offset_));
EXPECT_TRUE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ }");
CheckReadStalls(audio_.get());
}
TEST_F(FrameProcessorTest, NonMonotonicallyIncreasingTimestampInOneCall) {
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
ASSERT_FALSE(frame_processor_->ProcessFrames(
StringToBufferQueue("10K 0K", audio_id_, DemuxerStream::AUDIO),
empty_queue_,
empty_text_buffers_,
append_window_start_, append_window_end_,
&new_media_segment_, &timestamp_offset_));
EXPECT_TRUE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ }");
CheckReadStalls(audio_.get());
}
TEST_P(FrameProcessorTest, AudioOnly_SingleFrame) {
// Tests A: P(A) -> (a)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_));
ProcessFrames("0K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,10) }");
CheckReadsThenReadStalls(audio_.get(), "0");
}
TEST_P(FrameProcessorTest, VideoOnly_SingleFrame) {
// Tests V: P(V) -> (v)
InSequence s;
AddTestTracks(HAS_VIDEO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_));
ProcessFrames("", "0K");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,10) }");
CheckReadsThenReadStalls(video_.get(), "0");
}
TEST_P(FrameProcessorTest, AudioOnly_TwoFrames) {
// Tests A: P(A0, A10) -> (a0, a10)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
ProcessFrames("0K 10K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
CheckReadsThenReadStalls(audio_.get(), "0 10");
}
TEST_P(FrameProcessorTest, AudioOnly_SetOffsetThenSingleFrame) {
// Tests A: STSO(50)+P(A0) -> TSO==50,(a0@50)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
const base::TimeDelta fifty_ms = base::TimeDelta::FromMilliseconds(50);
SetTimestampOffset(fifty_ms);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ + fifty_ms));
ProcessFrames("0K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(fifty_ms, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [50,60) }");
// We do not stall on reading without seeking to 50ms due to
// SourceBufferStream::kSeekToStartFudgeRoom().
CheckReadsThenReadStalls(audio_.get(), "50:0");
}
TEST_P(FrameProcessorTest, AudioOnly_SetOffsetThenFrameTimestampBelowOffset) {
// Tests A: STSO(50)+P(A20) ->
// if sequence mode: TSO==30,(a20@50)
// if segments mode: TSO==50,(a20@70)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
bool using_sequence_mode = GetParam();
if (using_sequence_mode)
frame_processor_->SetSequenceMode(true);
const base::TimeDelta fifty_ms = base::TimeDelta::FromMilliseconds(50);
const base::TimeDelta twenty_ms = base::TimeDelta::FromMilliseconds(20);
SetTimestampOffset(fifty_ms);
if (using_sequence_mode) {
EXPECT_CALL(callbacks_, PossibleDurationIncrease(
fifty_ms + frame_duration_));
} else {
EXPECT_CALL(callbacks_, PossibleDurationIncrease(
fifty_ms + twenty_ms + frame_duration_));
}
ProcessFrames("20K", "");
EXPECT_FALSE(new_media_segment_);
// We do not stall on reading without seeking to 50ms / 70ms due to
// SourceBufferStream::kSeekToStartFudgeRoom().
if (using_sequence_mode) {
EXPECT_EQ(fifty_ms - twenty_ms, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [50,60) }");
CheckReadsThenReadStalls(audio_.get(), "50:20");
} else {
EXPECT_EQ(fifty_ms, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [70,80) }");
CheckReadsThenReadStalls(audio_.get(), "70:20");
}
}
TEST_P(FrameProcessorTest, AudioOnly_SequentialProcessFrames) {
// Tests A: P(A0,A10)+P(A20,A30) -> (a0,a10,a20,a30)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
ProcessFrames("0K 10K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 4));
ProcessFrames("20K 30K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,40) }");
CheckReadsThenReadStalls(audio_.get(), "0 10 20 30");
}
TEST_P(FrameProcessorTest, AudioOnly_NonSequentialProcessFrames) {
// Tests A: P(A20,A30)+P(A0,A10) ->
// if sequence mode: TSO==-20 after first P(), 20 after second P(), and
// a(20@0,a30@10,a0@20,a10@30)
// if segments mode: TSO==0,(a0,a10,a20,a30)
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
bool using_sequence_mode = GetParam();
if (using_sequence_mode) {
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
} else {
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 4));
}
ProcessFrames("20K 30K", "");
EXPECT_FALSE(new_media_segment_);
if (using_sequence_mode) {
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
EXPECT_EQ(frame_duration_ * -2, timestamp_offset_);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 4));
} else {
CheckExpectedRangesByTimestamp(audio_.get(), "{ [20,40) }");
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
}
ProcessFrames("0K 10K", "");
EXPECT_FALSE(new_media_segment_);
if (using_sequence_mode) {
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,40) }");
EXPECT_EQ(frame_duration_ * 2, timestamp_offset_);
CheckReadsThenReadStalls(audio_.get(), "0:20 10:30 20:0 30:10");
} else {
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,40) }");
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
// TODO(wolenetz): Fix this need to seek to 0ms, possibly by having
// SourceBufferStream defer initial seek until next read. See
// http://crbug.com/371493.
audio_->AbortReads();
audio_->Seek(base::TimeDelta());
audio_->StartReturningData();
CheckReadsThenReadStalls(audio_.get(), "0 10 20 30");
}
}
TEST_P(FrameProcessorTest, AudioVideo_SequentialProcessFrames) {
// Tests AV: P(A0,A10;V0k,V10,V20)+P(A20,A30,A40,V30) ->
// (a0,a10,a20,a30,a40);(v0,v10,v20,v30)
InSequence s;
AddTestTracks(HAS_AUDIO | HAS_VIDEO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 3));
ProcessFrames("0K 10K", "0K 10 20");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,30) }");
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 5));
ProcessFrames("20K 30K 40K", "30");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,50) }");
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,40) }");
CheckReadsThenReadStalls(audio_.get(), "0 10 20 30 40");
CheckReadsThenReadStalls(video_.get(), "0 10 20 30");
}
TEST_P(FrameProcessorTest, AudioVideo_Discontinuity) {
// Tests AV: P(A0,A10,A30,A40,A50;V0k,V10,V40,V50key) ->
// if sequence mode: TSO==10,(a0,a10,a30,a40,a50@60);(v0,v10,v50@60)
// if segments mode: TSO==0,(a0,a10,a30,a40,a50);(v0,v10,v50)
// This assumes A40K is processed before V40, which depends currently on
// MergeBufferQueues() behavior.
InSequence s;
AddTestTracks(HAS_AUDIO | HAS_VIDEO);
new_media_segment_ = true;
bool using_sequence_mode = GetParam();
if (using_sequence_mode) {
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 7));
} else {
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 6));
}
ProcessFrames("0K 10K 30K 40K 50K", "0K 10 40 50K");
EXPECT_FALSE(new_media_segment_);
if (using_sequence_mode) {
EXPECT_EQ(frame_duration_, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,70) }");
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,70) }");
CheckReadsThenReadStalls(audio_.get(), "0 10 30 40 60:50");
CheckReadsThenReadStalls(video_.get(), "0 10 60:50");
} else {
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,60) }");
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,20) [50,60) }");
CheckReadsThenReadStalls(audio_.get(), "0 10 30 40 50");
CheckReadsThenReadStalls(video_.get(), "0 10");
video_->AbortReads();
video_->Seek(frame_duration_ * 5);
video_->StartReturningData();
CheckReadsThenReadStalls(video_.get(), "50");
}
}
TEST_P(FrameProcessorTest,
AppendWindowFilterOfNegativeBufferTimestampsWithPrerollDiscard) {
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
SetTimestampOffset(frame_duration_ * -2);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_));
ProcessFrames("0K 10K 20K", "");
EXPECT_FALSE(new_media_segment_);
EXPECT_EQ(frame_duration_ * -2, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,10) }");
CheckReadsThenReadStalls(audio_.get(), "0:10P 0:20");
}
TEST_P(FrameProcessorTest, AppendWindowFilterWithInexactPreroll) {
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
SetTimestampOffset(-frame_duration_);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
ProcessFrames("0K 9.75K 20K", "");
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
CheckReadsThenReadStalls(audio_.get(), "0P 0:9.75 10:20");
}
TEST_P(FrameProcessorTest, AppendWindowFilterWithInexactPreroll_2) {
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
SetTimestampOffset(-frame_duration_);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 2));
ProcessFrames("0K 10.25K 20K", "");
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,20) }");
CheckReadsThenReadStalls(audio_.get(), "0P 0:10.25 10:20");
}
TEST_P(FrameProcessorTest, AllowNegativeFramePTSAndDTSBeforeOffsetAdjustment) {
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
bool using_sequence_mode = GetParam();
if (using_sequence_mode) {
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_ * 3));
} else {
EXPECT_CALL(callbacks_,
PossibleDurationIncrease((frame_duration_ * 5) / 2));
}
ProcessFrames("-5K 5K 15K", "");
if (using_sequence_mode) {
EXPECT_EQ(frame_duration_ / 2, timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,30) }");
CheckReadsThenReadStalls(audio_.get(), "0:-5 10:5 20:15");
} else {
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,25) }");
CheckReadsThenReadStalls(audio_.get(), "0:-5 5 15");
}
}
TEST_P(FrameProcessorTest, PartialAppendWindowFilterNoDiscontinuity) {
// Tests that spurious discontinuity is not introduced by a partially
// trimmed frame.
InSequence s;
AddTestTracks(HAS_AUDIO);
new_media_segment_ = true;
if (GetParam())
frame_processor_->SetSequenceMode(true);
EXPECT_CALL(callbacks_,
PossibleDurationIncrease(base::TimeDelta::FromMilliseconds(29)));
append_window_start_ = base::TimeDelta::FromMilliseconds(7);
ProcessFrames("0K 19K", "");
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [7,29) }");
CheckReadsThenReadStalls(audio_.get(), "7:0 19");
}
TEST_P(FrameProcessorTest, PartialAppendWindowFilterNoNewMediaSegment) {
// Tests that a new media segment is not forcibly signalled for audio frame
// partial front trim, to prevent incorrect introduction of a discontinuity
// and potentially a non-keyframe video frame to be processed next after the
// discontinuity.
InSequence s;
AddTestTracks(HAS_AUDIO | HAS_VIDEO);
new_media_segment_ = true;
frame_processor_->SetSequenceMode(GetParam());
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_));
ProcessFrames("", "0K");
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_));
ProcessFrames("-5K", "");
EXPECT_CALL(callbacks_, PossibleDurationIncrease(frame_duration_* 2));
ProcessFrames("", "10");
EXPECT_EQ(base::TimeDelta(), timestamp_offset_);
EXPECT_FALSE(new_media_segment_);
CheckExpectedRangesByTimestamp(audio_.get(), "{ [0,5) }");
CheckExpectedRangesByTimestamp(video_.get(), "{ [0,20) }");
CheckReadsThenReadStalls(audio_.get(), "0:-5");
CheckReadsThenReadStalls(video_.get(), "0 10");
}
INSTANTIATE_TEST_CASE_P(SequenceMode, FrameProcessorTest, Values(true));
INSTANTIATE_TEST_CASE_P(SegmentsMode, FrameProcessorTest, Values(false));
} // namespace media