// Copyright 2016 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 "media/filters/pipeline_controller.h" #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/notreached.h" #include "base/run_loop.h" #include "base/test/gmock_callback_support.h" #include "base/test/gmock_move_support.h" #include "base/test/task_environment.h" #include "base/time/time.h" #include "media/base/mock_filters.h" #include "media/base/pipeline.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::base::test::RunOnceClosure; using ::testing::_; using ::testing::AnyNumber; using ::testing::DoAll; using ::testing::Mock; using ::testing::NiceMock; using ::testing::Return; using ::testing::SaveArg; using ::testing::StrictMock; namespace media { class PipelineControllerTest : public ::testing::Test, public Pipeline::Client { public: PipelineControllerTest() : pipeline_(new StrictMock()), pipeline_controller_( std::unique_ptr(pipeline_), base::BindRepeating(&PipelineControllerTest::OnSeeked, base::Unretained(this)), base::BindRepeating(&PipelineControllerTest::OnSuspended, base::Unretained(this)), base::BindRepeating(&PipelineControllerTest::OnBeforeResume, base::Unretained(this)), base::BindRepeating(&PipelineControllerTest::OnResumed, base::Unretained(this)), base::BindRepeating(&PipelineControllerTest::OnError, base::Unretained(this))) {} ~PipelineControllerTest() override = default; PipelineStatusCallback StartPipeline(bool is_streaming, bool is_static) { EXPECT_FALSE(pipeline_controller_.IsStable()); PipelineStatusCallback start_cb; EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)) .WillOnce(MoveArg<3>(&start_cb)); pipeline_controller_.Start(Pipeline::StartType::kNormal, &demuxer_, this, is_streaming, is_static); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()) .Times(AnyNumber()) .WillOnce(Return(false)); EXPECT_FALSE(pipeline_controller_.IsStable()); return start_cb; } PipelineStatusCallback StartPipeline() { return StartPipeline(false, true); } PipelineStatusCallback StartPipeline_WithDynamicData() { return StartPipeline(false, false); } PipelineStatusCallback StartPipeline_WithStreamingData() { return StartPipeline(true, false); } PipelineStatusCallback SeekPipeline(base::TimeDelta time) { EXPECT_TRUE(pipeline_controller_.IsStable()); PipelineStatusCallback seek_cb; EXPECT_CALL(*pipeline_, OnSeek(time, _)).WillOnce(MoveArg<1>(&seek_cb)); pipeline_controller_.Seek(time, true); Mock::VerifyAndClear(pipeline_); EXPECT_FALSE(pipeline_controller_.IsStable()); return seek_cb; } PipelineStatusCallback SuspendPipeline() { EXPECT_TRUE(pipeline_controller_.IsStable()); PipelineStatusCallback suspend_cb; EXPECT_CALL(*pipeline_, OnSuspend(_)).WillOnce(MoveArg<0>(&suspend_cb)); pipeline_controller_.Suspend(); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()) .Times(AnyNumber()) .WillOnce(Return(true)); EXPECT_TRUE(pipeline_controller_.IsSuspended()); EXPECT_FALSE(pipeline_controller_.IsStable()); EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); return suspend_cb; } PipelineStatusCallback ResumePipeline() { EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended()); PipelineStatusCallback resume_cb; EXPECT_CALL(*pipeline_, OnResume(_, _)) .WillOnce( DoAll(SaveArg<0>(&last_resume_time_), MoveArg<1>(&resume_cb))); EXPECT_CALL(*pipeline_, GetMediaTime()) .WillRepeatedly(Return(base::TimeDelta())); pipeline_controller_.Resume(); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()) .Times(AnyNumber()) .WillOnce(Return(false)); EXPECT_FALSE(pipeline_controller_.IsSuspended()); EXPECT_FALSE(pipeline_controller_.IsStable()); EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); return resume_cb; } void Complete(PipelineStatusCallback cb) { std::move(cb).Run(PIPELINE_OK); base::RunLoop().RunUntilIdle(); } protected: void OnSeeked(bool time_updated) { was_seeked_ = true; last_seeked_time_updated_ = time_updated; } void OnSuspended() { was_suspended_ = true; } void OnBeforeResume() { was_resuming_ = true; } void OnResumed() { was_resumed_ = true; } // Pipeline::Client overrides void OnError(PipelineStatus status) override { NOTREACHED(); } void OnEnded() override {} void OnMetadata(const PipelineMetadata& metadata) override {} void OnBufferingStateChange(BufferingState state, BufferingStateChangeReason reason) override {} void OnDurationChange() override {} void OnAddTextTrack(const TextTrackConfig& config, AddTextTrackDoneCB done_cb) override {} void OnWaiting(WaitingReason reason) override {} void OnVideoNaturalSizeChange(const gfx::Size& size) override {} void OnAudioConfigChange(const AudioDecoderConfig& config) override {} void OnVideoConfigChange(const VideoDecoderConfig& config) override {} void OnVideoOpacityChange(bool opaque) override {} void OnVideoFrameRateChange(base::Optional) override {} void OnVideoAverageKeyframeDistanceUpdate() override {} void OnAudioDecoderChange(const PipelineDecoderInfo& info) override {} void OnVideoDecoderChange(const PipelineDecoderInfo& info) override {} base::test::SingleThreadTaskEnvironment task_environment_; NiceMock demuxer_; StrictMock* pipeline_; PipelineController pipeline_controller_; bool was_seeked_ = false; bool last_seeked_time_updated_ = false; bool was_suspended_ = false; bool was_resuming_ = false; bool was_resumed_ = false; base::TimeDelta last_resume_time_; DISALLOW_COPY_AND_ASSIGN(PipelineControllerTest); }; TEST_F(PipelineControllerTest, Startup) { PipelineStatusCallback start_cb = StartPipeline(); EXPECT_FALSE(was_seeked_); Complete(std::move(start_cb)); EXPECT_TRUE(was_seeked_); EXPECT_FALSE(last_seeked_time_updated_); EXPECT_FALSE(was_suspended_); EXPECT_TRUE(pipeline_controller_.IsStable()); } TEST_F(PipelineControllerTest, StartSuspendedSeekAndResume) { EXPECT_FALSE(pipeline_controller_.IsStable()); PipelineStatusCallback start_cb; EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)).WillOnce(MoveArg<3>(&start_cb)); pipeline_controller_.Start(Pipeline::StartType::kSuspendAfterMetadata, &demuxer_, this, false, true); Mock::VerifyAndClear(pipeline_); // Initiate a seek before the pipeline completes suspended startup. base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time)); pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(was_seeked_); PipelineStatusCallback resume_cb; EXPECT_CALL(*pipeline_, OnResume(_, _)) .WillOnce(DoAll(MoveArg<1>(&resume_cb))); EXPECT_CALL(*pipeline_, GetMediaTime()) .WillRepeatedly(Return(base::TimeDelta())); EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(true)); EXPECT_FALSE(pipeline_controller_.IsStable()); Complete(std::move(start_cb)); EXPECT_FALSE(pipeline_controller_.IsStable()); EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); EXPECT_FALSE(pipeline_controller_.IsSuspended()); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(false)); Complete(std::move(resume_cb)); EXPECT_TRUE(was_seeked_); was_seeked_ = false; EXPECT_TRUE(pipeline_controller_.IsStable()); EXPECT_FALSE(pipeline_controller_.IsPipelineSuspended()); EXPECT_FALSE(pipeline_controller_.IsSuspended()); Mock::VerifyAndClear(pipeline_); } TEST_F(PipelineControllerTest, StartSuspendedAndResume) { EXPECT_FALSE(pipeline_controller_.IsStable()); PipelineStatusCallback start_cb; EXPECT_CALL(*pipeline_, OnStart(_, _, _, _)).WillOnce(MoveArg<3>(&start_cb)); pipeline_controller_.Start(Pipeline::StartType::kSuspendAfterMetadata, &demuxer_, this, false, true); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(true)); EXPECT_FALSE(pipeline_controller_.IsStable()); Complete(std::move(start_cb)); EXPECT_TRUE(was_seeked_); was_seeked_ = false; EXPECT_FALSE(pipeline_controller_.IsStable()); EXPECT_TRUE(pipeline_controller_.IsPipelineSuspended()); EXPECT_TRUE(pipeline_controller_.IsSuspended()); Mock::VerifyAndClear(pipeline_); EXPECT_CALL(*pipeline_, IsSuspended()).WillRepeatedly(Return(false)); PipelineStatusCallback resume_cb = ResumePipeline(); EXPECT_TRUE(was_resuming_); EXPECT_FALSE(was_resumed_); Complete(std::move(resume_cb)); EXPECT_TRUE(was_resumed_); EXPECT_TRUE(pipeline_controller_.IsStable()); // |was_seeked_| should not be affected by Suspend()/Resume() at all. EXPECT_FALSE(was_seeked_); } TEST_F(PipelineControllerTest, SuspendResume) { Complete(StartPipeline()); EXPECT_TRUE(was_seeked_); was_seeked_ = false; Complete(SuspendPipeline()); EXPECT_TRUE(was_suspended_); EXPECT_FALSE(pipeline_controller_.IsStable()); PipelineStatusCallback resume_cb = ResumePipeline(); EXPECT_TRUE(was_resuming_); EXPECT_FALSE(was_resumed_); Complete(std::move(resume_cb)); EXPECT_TRUE(was_resumed_); EXPECT_TRUE(pipeline_controller_.IsStable()); // |was_seeked_| should not be affected by Suspend()/Resume() at all. EXPECT_FALSE(was_seeked_); } TEST_F(PipelineControllerTest, Seek) { // Normal seeking should not result in a cancel. EXPECT_CALL(demuxer_, CancelPendingSeek(_)).Times(0); Complete(StartPipeline()); was_seeked_ = false; base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time)); PipelineStatusCallback seek_cb = SeekPipeline(seek_time); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(was_seeked_); Complete(std::move(seek_cb)); EXPECT_TRUE(was_seeked_); EXPECT_TRUE(pipeline_controller_.IsStable()); } // Makes sure OnDecoderStateLost() triggers a seek to the current media time. TEST_F(PipelineControllerTest, DecoderStateLost) { Complete(StartPipeline()); constexpr auto kCurrentMediaTime = base::TimeDelta::FromSeconds(7); EXPECT_CALL(*pipeline_, GetMediaTime()) .WillRepeatedly(Return(kCurrentMediaTime)); EXPECT_CALL(demuxer_, StartWaitingForSeek(kCurrentMediaTime)); EXPECT_CALL(*pipeline_, OnSeek(kCurrentMediaTime, _)); pipeline_controller_.OnDecoderStateLost(); base::RunLoop().RunUntilIdle(); } // Makes sure OnDecoderStateLost() does not trigger a seek during pending seek. TEST_F(PipelineControllerTest, DecoderStateLost_DuringPendingSeek) { Complete(StartPipeline()); // Create a pending seek. base::TimeDelta kSeekTime = base::TimeDelta::FromSeconds(5); EXPECT_CALL(demuxer_, StartWaitingForSeek(kSeekTime)); PipelineStatusCallback seek_cb = SeekPipeline(kSeekTime); base::RunLoop().RunUntilIdle(); Mock::VerifyAndClear(&demuxer_); // OnDecoderStateLost() should not trigger another seek. EXPECT_CALL(*pipeline_, GetMediaTime()).Times(0); pipeline_controller_.OnDecoderStateLost(); base::RunLoop().RunUntilIdle(); Complete(std::move(seek_cb)); } TEST_F(PipelineControllerTest, SuspendResumeTime) { Complete(StartPipeline()); Complete(SuspendPipeline()); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); Complete(ResumePipeline()); EXPECT_EQ(seek_time, last_resume_time_); } TEST_F(PipelineControllerTest, SuspendResumeTime_WithStreamingData) { Complete(StartPipeline_WithStreamingData()); Complete(SuspendPipeline()); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); Complete(ResumePipeline()); EXPECT_EQ(base::TimeDelta(), last_resume_time_); } TEST_F(PipelineControllerTest, SeekAborted) { Complete(StartPipeline()); // Create a first pending seek. base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5); EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_1)); PipelineStatusCallback seek_cb_1 = SeekPipeline(seek_time_1); base::RunLoop().RunUntilIdle(); Mock::VerifyAndClear(&demuxer_); // Create a second seek; the first should be aborted. base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10); EXPECT_CALL(demuxer_, CancelPendingSeek(seek_time_2)); pipeline_controller_.Seek(seek_time_2, true); base::RunLoop().RunUntilIdle(); Mock::VerifyAndClear(&demuxer_); // When the first seek is completed (or aborted) the second should be issued. EXPECT_CALL(demuxer_, StartWaitingForSeek(seek_time_2)); EXPECT_CALL(*pipeline_, OnSeek(seek_time_2, _)); Complete(std::move(seek_cb_1)); } TEST_F(PipelineControllerTest, PendingSuspend) { Complete(StartPipeline()); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); PipelineStatusCallback seek_cb = SeekPipeline(seek_time); base::RunLoop().RunUntilIdle(); // While the seek is ongoing, request a suspend. // It will be a mock failure if pipeline_.Suspend() is called. pipeline_controller_.Suspend(); base::RunLoop().RunUntilIdle(); // Expect the suspend to trigger when the seek is completed. EXPECT_CALL(*pipeline_, OnSuspend(_)); Complete(std::move(seek_cb)); } TEST_F(PipelineControllerTest, SeekMergesWithResume) { Complete(StartPipeline()); Complete(SuspendPipeline()); // Pipeline startup always completes with a seek. EXPECT_TRUE(was_seeked_); was_seeked_ = false; // Request a seek while suspended. // It will be a mock failure if pipeline_.Seek() is called. base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(was_seeked_); // Resume and verify the resume time includes the seek. Complete(ResumePipeline()); EXPECT_EQ(seek_time, last_resume_time_); EXPECT_TRUE(last_seeked_time_updated_); } TEST_F(PipelineControllerTest, SeekMergesWithSeek) { Complete(StartPipeline()); base::TimeDelta seek_time_1 = base::TimeDelta::FromSeconds(5); PipelineStatusCallback seek_cb_1 = SeekPipeline(seek_time_1); base::RunLoop().RunUntilIdle(); // Request another seek while the first is ongoing. base::TimeDelta seek_time_2 = base::TimeDelta::FromSeconds(10); pipeline_controller_.Seek(seek_time_2, true); base::RunLoop().RunUntilIdle(); // Request a third seek. (It should replace the second.) base::TimeDelta seek_time_3 = base::TimeDelta::FromSeconds(15); pipeline_controller_.Seek(seek_time_3, true); base::RunLoop().RunUntilIdle(); // Expect the third seek to trigger when the first seek completes. EXPECT_CALL(*pipeline_, OnSeek(seek_time_3, _)); Complete(std::move(seek_cb_1)); } TEST_F(PipelineControllerTest, SeekToSeekTimeElided) { Complete(StartPipeline()); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); PipelineStatusCallback seek_cb_1 = SeekPipeline(seek_time); base::RunLoop().RunUntilIdle(); // Request a seek to the same time again. pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); // Complete the first seek. // It would be a mock error if the second seek was dispatched here. Complete(std::move(seek_cb_1)); EXPECT_TRUE(pipeline_controller_.IsStable()); } TEST_F(PipelineControllerTest, SeekToSeekTimeNotElided) { Complete(StartPipeline_WithDynamicData()); base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5); PipelineStatusCallback seek_cb_1 = SeekPipeline(seek_time); base::RunLoop().RunUntilIdle(); // Request a seek to the same time again. pipeline_controller_.Seek(seek_time, true); base::RunLoop().RunUntilIdle(); // Expect the second seek to trigger when the first seek completes. EXPECT_CALL(*pipeline_, OnSeek(seek_time, _)); Complete(std::move(seek_cb_1)); } TEST_F(PipelineControllerTest, VideoTrackChangeWhileSuspending) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, OnSuspend(_)); EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)).Times(0); pipeline_controller_.Suspend(); pipeline_controller_.OnSelectedVideoTrackChanged({}); } TEST_F(PipelineControllerTest, AudioTrackChangeWhileSuspending) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, OnSuspend(_)); EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)).Times(0); pipeline_controller_.Suspend(); pipeline_controller_.OnEnabledAudioTracksChanged({}); } TEST_F(PipelineControllerTest, AudioTrackChangeDuringVideoTrackChange) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)); pipeline_controller_.OnSelectedVideoTrackChanged({}); pipeline_controller_.OnEnabledAudioTracksChanged({}); EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)); pipeline_controller_.FireOnTrackChangeCompleteForTesting( PipelineController::State::PLAYING); pipeline_controller_.FireOnTrackChangeCompleteForTesting( PipelineController::State::PLAYING); } TEST_F(PipelineControllerTest, SuspendDuringVideoTrackChange) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)); was_resumed_ = false; pipeline_controller_.OnSelectedVideoTrackChanged({}); pipeline_controller_.Suspend(); base::RunLoop loop; EXPECT_CALL(*pipeline_, OnSuspend(_)) .WillOnce(RunOnceClosure(loop.QuitClosure())); pipeline_controller_.FireOnTrackChangeCompleteForTesting( PipelineController::State::PLAYING); loop.Run(); EXPECT_FALSE(was_resumed_); } TEST_F(PipelineControllerTest, SuspendDuringAudioTrackChange) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, OnEnabledAudioTracksChanged(_, _)); was_resumed_ = false; pipeline_controller_.OnEnabledAudioTracksChanged({}); pipeline_controller_.Suspend(); base::RunLoop loop; EXPECT_CALL(*pipeline_, OnSuspend(_)) .WillOnce(RunOnceClosure(loop.QuitClosure())); pipeline_controller_.FireOnTrackChangeCompleteForTesting( PipelineController::State::PLAYING); loop.Run(); EXPECT_FALSE(was_resumed_); } TEST_F(PipelineControllerTest, ResumePlaybackDuringSwitchingTracksState) { Complete(StartPipeline()); Complete(SuspendPipeline()); EXPECT_CALL(*pipeline_, OnSelectedVideoTrackChanged(_, _)).Times(1); EXPECT_CALL(*pipeline_, GetMediaTime()).Times(1); EXPECT_CALL(*pipeline_, OnResume(_, _)).Times(1); pipeline_controller_.OnSelectedVideoTrackChanged({}); pipeline_controller_.Resume(); pipeline_controller_.FireOnTrackChangeCompleteForTesting( PipelineController::State::SUSPENDED); } TEST_F(PipelineControllerTest, PreservesPitch) { Complete(StartPipeline()); EXPECT_CALL(*pipeline_, SetPreservesPitch(false)); pipeline_controller_.SetPreservesPitch(false); EXPECT_CALL(*pipeline_, SetPreservesPitch(true)); pipeline_controller_.SetPreservesPitch(true); } } // namespace media