| // Copyright 2018 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 <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "media/base/media_util.h" |
| #include "media/base/mock_filters.h" |
| #include "media/filters/decoder_stream.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::base::test::RunCallback; |
| using ::base::test::RunOnceCallback; |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::DoAll; |
| using testing::Invoke; |
| using testing::Return; |
| using testing::SaveArg; |
| |
| namespace media { |
| |
| MATCHER(IsRegularDecoderBuffer, "") { |
| return !arg->end_of_stream(); |
| } |
| |
| MATCHER(IsEOSDecoderBuffer, "") { |
| return arg->end_of_stream(); |
| } |
| |
| static void OnAudioDecoderStreamInitialized(base::OnceClosure closure, |
| bool success) { |
| ASSERT_TRUE(success); |
| std::move(closure).Run(); |
| } |
| |
| class AudioDecoderStreamTest : public testing::Test { |
| public: |
| AudioDecoderStreamTest() |
| : audio_decoder_stream_( |
| std::make_unique<AudioDecoderStream::StreamTraits>( |
| &media_log_, |
| CHANNEL_LAYOUT_STEREO), |
| task_environment_.GetMainThreadTaskRunner(), |
| base::BindRepeating(&AudioDecoderStreamTest::CreateMockAudioDecoder, |
| base::Unretained(this)), |
| &media_log_) { |
| // Any valid config will do. |
| demuxer_stream_.set_audio_decoder_config({AudioCodec::kAAC, |
| kSampleFormatS16, |
| CHANNEL_LAYOUT_STEREO, |
| 44100, |
| {}, |
| {}}); |
| EXPECT_CALL(demuxer_stream_, SupportsConfigChanges()) |
| .WillRepeatedly(Return(true)); |
| |
| base::RunLoop run_loop; |
| audio_decoder_stream_.Initialize( |
| &demuxer_stream_, |
| base::BindOnce(&OnAudioDecoderStreamInitialized, |
| run_loop.QuitClosure()), |
| nullptr, base::DoNothing(), base::DoNothing()); |
| run_loop.Run(); |
| } |
| |
| MockDemuxerStream* demuxer_stream() { return &demuxer_stream_; } |
| MockAudioDecoder* decoder() { return decoder_; } |
| |
| void ReadAudioBuffer(base::OnceClosure closure) { |
| audio_decoder_stream_.Read( |
| base::BindOnce(&AudioDecoderStreamTest::OnAudioBufferReadDone, |
| base::Unretained(this), std::move(closure))); |
| } |
| |
| void ProduceDecoderOutput(scoped_refptr<DecoderBuffer> buffer, |
| AudioDecoder::DecodeCB decode_cb) { |
| // Make sure successive AudioBuffers have increasing timestamps. |
| last_timestamp_ += base::Milliseconds(27); |
| const auto& config = demuxer_stream_.audio_decoder_config(); |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| decoder_output_cb_, |
| AudioBuffer::CreateEmptyBuffer( |
| config.channel_layout(), config.channels(), |
| config.samples_per_second(), 1221, last_timestamp_))); |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(decode_cb), DecodeStatus::OK)); |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| private: |
| std::vector<std::unique_ptr<AudioDecoder>> CreateMockAudioDecoder() { |
| auto decoder = std::make_unique<MockAudioDecoder>(); |
| EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _)) |
| .Times(AnyNumber()) |
| .WillRepeatedly(DoAll(SaveArg<3>(&decoder_output_cb_), |
| RunOnceCallback<2>(OkStatus()))); |
| decoder_ = decoder.get(); |
| |
| std::vector<std::unique_ptr<AudioDecoder>> result; |
| result.push_back(std::move(decoder)); |
| return result; |
| } |
| |
| void OnAudioBufferReadDone(base::OnceClosure closure, |
| AudioDecoderStream::ReadResult result) { |
| std::move(closure).Run(); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| NullMediaLog media_log_; |
| testing::NiceMock<MockDemuxerStream> demuxer_stream_{DemuxerStream::AUDIO}; |
| AudioDecoderStream audio_decoder_stream_; |
| |
| MockAudioDecoder* decoder_ = nullptr; |
| AudioDecoder::OutputCB decoder_output_cb_; |
| base::TimeDelta last_timestamp_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AudioDecoderStreamTest); |
| }; |
| |
| TEST_F(AudioDecoderStreamTest, FlushOnConfigChange) { |
| MockAudioDecoder* first_decoder = decoder(); |
| ASSERT_NE(first_decoder, nullptr); |
| |
| // Make a regular DemuxerStream::Read(). |
| EXPECT_CALL(*demuxer_stream(), OnRead(_)) |
| .WillOnce(RunOnceCallback<0>(DemuxerStream::kOk, new DecoderBuffer(12))); |
| EXPECT_CALL(*decoder(), Decode(IsRegularDecoderBuffer(), _)) |
| .WillOnce(Invoke(this, &AudioDecoderStreamTest::ProduceDecoderOutput)); |
| base::RunLoop run_loop0; |
| ReadAudioBuffer(run_loop0.QuitClosure()); |
| run_loop0.Run(); |
| |
| // Make a config-change DemuxerStream::Read(). |
| // Expect the decoder to be flushed. Upon flushing, the decoder releases |
| // internally buffered output. |
| EXPECT_CALL(*demuxer_stream(), OnRead(_)) |
| .WillOnce(RunOnceCallback<0>(DemuxerStream::kConfigChanged, nullptr)); |
| EXPECT_CALL(*decoder(), Decode(IsEOSDecoderBuffer(), _)) |
| .WillOnce(Invoke(this, &AudioDecoderStreamTest::ProduceDecoderOutput)); |
| base::RunLoop run_loop1; |
| ReadAudioBuffer(run_loop1.QuitClosure()); |
| run_loop1.Run(); |
| |
| // Expect the decoder to be re-initialized when AudioDecoderStream finishes |
| // processing the last decode. |
| EXPECT_CALL(*decoder(), Initialize_(_, _, _, _, _)); |
| RunUntilIdle(); |
| } |
| |
| } // namespace media |