// 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 #include #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 { 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 timestamps; base::SplitString(buffers_to_append, ' ', ×tamps); 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(&time_in_ms); scoped_refptr 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_, ×tamp_offset_)); } void CheckExpectedRangesByTimestamp(ChunkDemuxerStream* stream, const std::string& expected) { // Note, DemuxerStream::TEXT streams return [0,duration (==infinity here)) Ranges 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 timestamps; base::SplitString(expected, ' ', ×tamps); 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(sizeof(original_time_in_ms)), last_read_buffer_->data_size()); original_time_in_ms = *(reinterpret_cast( 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 callbacks_; scoped_ptr frame_processor_; base::TimeDelta append_window_start_; base::TimeDelta append_window_end_; bool new_media_segment_; base::TimeDelta timestamp_offset_; scoped_ptr audio_; scoped_ptr 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 last_read_buffer_; bool read_callback_called_; private: void StoreStatusAndBuffer(DemuxerStream::Status status, const scoped_refptr& 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, DemuxerStream::LIVENESS_UNKNOWN, 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, DemuxerStream::LIVENESS_UNKNOWN, 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_, ×tamp_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_, ×tamp_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