blob: d64f867e4562e74a05a32f16409d825538894fa6 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/base/waiting.h"
#include "media/mojo/clients/mojo_audio_decoder.h"
#include "media/mojo/mojom/audio_decoder.mojom.h"
#include "media/mojo/services/mojo_audio_decoder_service.h"
#include "media/mojo/services/mojo_cdm_service_context.h"
#include "media/mojo/services/mojo_media_client.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::InvokeWithoutArgs;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
const SampleFormat kSampleFormat = kSampleFormatPlanarF32;
const int kChannels = 2;
const ChannelLayout kChannelLayout = CHANNEL_LAYOUT_STEREO;
const int kDefaultSampleRate = 44100;
const int kDefaultFrameSize = 100;
const int kOutputPerDecode = 3;
namespace {
// Proxies CreateAudioDecoder() to a callback.
class FakeMojoMediaClient : public MojoMediaClient {
public:
using CreateAudioDecoderCB =
base::RepeatingCallback<std::unique_ptr<AudioDecoder>()>;
explicit FakeMojoMediaClient(CreateAudioDecoderCB create_audio_decoder_cb)
: create_audio_decoder_cb_(std::move(create_audio_decoder_cb)) {}
FakeMojoMediaClient(const FakeMojoMediaClient&) = delete;
FakeMojoMediaClient& operator=(const FakeMojoMediaClient&) = delete;
std::unique_ptr<AudioDecoder> CreateAudioDecoder(
scoped_refptr<base::SequencedTaskRunner> task_runner,
std::unique_ptr<media::MediaLog> media_log) override {
return create_audio_decoder_cb_.Run();
}
private:
const CreateAudioDecoderCB create_audio_decoder_cb_;
};
} // namespace
// Tests MojoAudioDecoder (client) and MojoAudioDecoderService (service).
// To better simulate how they are used in production, the client and service
// are running on two different threads.
class MojoAudioDecoderTest : public ::testing::Test {
public:
MojoAudioDecoderTest()
: input_timestamp_helper_(kDefaultSampleRate),
service_thread_("Service Thread") {
input_timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
service_thread_.Start();
service_task_runner_ = service_thread_.task_runner();
// Setup the mojo connection.
mojo::PendingRemote<mojom::AudioDecoder> remote_audio_decoder;
service_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MojoAudioDecoderTest::ConnectToService,
base::Unretained(this),
remote_audio_decoder.InitWithNewPipeAndPassReceiver()));
mojo_audio_decoder_ = std::make_unique<MojoAudioDecoder>(
task_environment_.GetMainThreadTaskRunner(), &media_log_,
std::move(remote_audio_decoder));
}
MojoAudioDecoderTest(const MojoAudioDecoderTest&) = delete;
MojoAudioDecoderTest& operator=(const MojoAudioDecoderTest&) = delete;
~MojoAudioDecoderTest() override {
// Destroy |mojo_audio_decoder_| first so that the service will be
// destructed. Then stop the service thread. Otherwise we'll leak memory.
mojo_audio_decoder_.reset();
service_thread_.Stop();
RunLoopUntilIdle();
}
// Completion callbacks.
MOCK_METHOD1(OnInitialized, void(DecoderStatus));
MOCK_METHOD1(OnOutput, void(scoped_refptr<AudioBuffer>));
MOCK_METHOD1(OnWaiting, void(WaitingReason));
MOCK_METHOD1(OnDecoded, void(DecoderStatus));
MOCK_METHOD0(OnReset, void());
// Always create a new RunLoop (and destroy the old loop if it exists) before
// running the loop because we cannot run the same loop more than once.
void RunLoop() {
DVLOG(1) << __func__;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
void RunLoopUntilIdle() {
DVLOG(1) << __func__;
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->RunUntilIdle();
}
void QuitLoop() {
DVLOG(1) << __func__;
run_loop_->QuitWhenIdle();
}
std::unique_ptr<AudioDecoder> CreateAudioDecoder() {
CHECK_NE(owned_mock_audio_decoder_, nullptr);
return std::move(owned_mock_audio_decoder_);
}
void ConnectToService(mojo::PendingReceiver<mojom::AudioDecoder> receiver) {
DCHECK(service_task_runner_->BelongsToCurrentThread());
EXPECT_CALL(*mock_audio_decoder_, Initialize_(_, _, _, _, _))
.WillRepeatedly(DoAll(SaveArg<3>(&output_cb_), SaveArg<4>(&waiting_cb_),
RunOnceCallback<2>(DecoderStatus::Codes::kOk)));
EXPECT_CALL(*mock_audio_decoder_, Decode(_, _))
.WillRepeatedly([&](scoped_refptr<DecoderBuffer> buffer,
AudioDecoder::DecodeCB decode_cb) {
ReturnOutput();
std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
});
EXPECT_CALL(*mock_audio_decoder_, Reset_(_))
.WillRepeatedly(RunOnceCallback<0>());
mojo::MakeSelfOwnedReceiver(
std::make_unique<MojoAudioDecoderService>(&mojo_media_client_,
&mojo_cdm_service_context_),
std::move(receiver));
}
void SetWriterCapacity(uint32_t capacity) {
mojo_audio_decoder_->set_writer_capacity_for_testing(capacity);
}
void InitializeAndExpect(DecoderStatus status) {
DVLOG(1) << __func__ << ": success=" << static_cast<int>(status.code());
EXPECT_CALL(*this, OnInitialized(SameStatusCode(status)))
.WillOnce(InvokeWithoutArgs(this, &MojoAudioDecoderTest::QuitLoop));
AudioDecoderConfig audio_config(
AudioCodec::kVorbis, kSampleFormat, kChannelLayout, kDefaultSampleRate,
EmptyExtraData(), EncryptionScheme::kUnencrypted);
mojo_audio_decoder_->Initialize(
audio_config, nullptr,
base::BindOnce(&MojoAudioDecoderTest::OnInitialized,
base::Unretained(this)),
base::BindRepeating(&MojoAudioDecoderTest::OnOutput,
base::Unretained(this)),
base::BindRepeating(&MojoAudioDecoderTest::OnWaiting,
base::Unretained(this)));
RunLoop();
}
void Initialize() { InitializeAndExpect(DecoderStatus::Codes::kOk); }
void Decode() {
scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(100));
mojo_audio_decoder_->Decode(
buffer, base::BindRepeating(&MojoAudioDecoderTest::OnDecoded,
base::Unretained(this)));
}
void Reset() {
mojo_audio_decoder_->Reset(
base::BindOnce(&MojoAudioDecoderTest::OnReset, base::Unretained(this)));
}
void ResetAndWaitUntilFinish() {
DVLOG(1) << __func__;
EXPECT_CALL(*this, OnReset())
.WillOnce(InvokeWithoutArgs(this, &MojoAudioDecoderTest::QuitLoop));
Reset();
RunLoop();
}
void ReturnOutput() {
for (int i = 0; i < kOutputPerDecode; ++i) {
scoped_refptr<AudioBuffer> audio_buffer = MakeAudioBuffer<float>(
kSampleFormat, kChannelLayout, kChannels, kDefaultSampleRate, 1.0,
0.0f, 100, input_timestamp_helper_.GetTimestamp());
input_timestamp_helper_.AddFrames(kDefaultFrameSize);
output_cb_.Run(audio_buffer);
}
}
void WaitForKey() { waiting_cb_.Run(WaitingReason::kNoDecryptionKey); }
void DecodeMultipleTimes(int num_of_decodes) {
num_of_decodes_ = num_of_decodes;
KeepDecodingOrQuit();
RunLoop();
}
void KeepDecodingOrQuit() {
if (decode_count_ >= num_of_decodes_) {
QuitLoop();
return;
}
decode_count_++;
InSequence s; // Make sure OnOutput() and OnDecoded() are called in order.
EXPECT_CALL(*this, OnOutput(_)).Times(kOutputPerDecode);
EXPECT_CALL(*this, OnDecoded(IsOkStatus()))
.WillOnce(
InvokeWithoutArgs(this, &MojoAudioDecoderTest::KeepDecodingOrQuit));
Decode();
}
void DecodeAndReset() {
InSequence s; // Make sure all callbacks are fired in order.
EXPECT_CALL(*this, OnOutput(_)).Times(kOutputPerDecode);
EXPECT_CALL(*this, OnDecoded(IsOkStatus()));
EXPECT_CALL(*this, OnReset())
.WillOnce(InvokeWithoutArgs(this, &MojoAudioDecoderTest::QuitLoop));
Decode();
Reset();
RunLoop();
}
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<base::RunLoop> run_loop_;
NullMediaLog media_log_;
// The MojoAudioDecoder that we are testing.
std::unique_ptr<MojoAudioDecoder> mojo_audio_decoder_;
MojoCdmServiceContext mojo_cdm_service_context_;
AudioDecoder::OutputCB output_cb_;
WaitingCB waiting_cb_;
AudioTimestampHelper input_timestamp_helper_;
// The thread where the service runs. This provides test coverage in an
// environment similar to what we use in production. Also, some race
// conditions can only be reproduced when we run the service and client on
// different threads. See http://crbug.com/646054
base::Thread service_thread_;
scoped_refptr<base::SingleThreadTaskRunner> service_task_runner_;
// Owned by the connection on the service thread.
raw_ptr<MojoAudioDecoderService> mojo_audio_decoder_service_ = nullptr;
FakeMojoMediaClient mojo_media_client_{
base::BindRepeating(&MojoAudioDecoderTest::CreateAudioDecoder,
base::Unretained(this))};
// Service side mock.
std::unique_ptr<MockAudioDecoder> owned_mock_audio_decoder_{
std::make_unique<StrictMock<MockAudioDecoder>>()};
raw_ptr<MockAudioDecoder> mock_audio_decoder_{
owned_mock_audio_decoder_.get()};
int num_of_decodes_ = 0;
int decode_count_ = 0;
};
TEST_F(MojoAudioDecoderTest, Initialize_Success) {
Initialize();
}
TEST_F(MojoAudioDecoderTest, Reinitialize_Success) {
Initialize();
DecodeMultipleTimes(10);
ResetAndWaitUntilFinish();
// Reinitialize MojoAudioDecoder.
Initialize();
}
// Makes sure all callbacks and client calls are called in order. See
// http://crbug.com/646054
TEST_F(MojoAudioDecoderTest, Decode_MultipleTimes) {
Initialize();
// Choose a large number of decodes per test on purpose to expose potential
// out of order delivery of mojo messages. See http://crbug.com/646054
DecodeMultipleTimes(100);
}
TEST_F(MojoAudioDecoderTest, Reset_DuringDecode) {
Initialize();
DecodeAndReset();
}
TEST_F(MojoAudioDecoderTest, Reset_DuringDecode_ChunkedWrite) {
// Use a small writer capacity to force chunked write.
SetWriterCapacity(10);
Initialize();
DecodeAndReset();
}
TEST_F(MojoAudioDecoderTest, WaitingForKey) {
Initialize();
EXPECT_CALL(*mock_audio_decoder_, Decode(_, _))
.WillOnce([&](scoped_refptr<DecoderBuffer> buffer,
AudioDecoder::DecodeCB decode_cb) {
WaitForKey();
std::move(decode_cb).Run(DecoderStatus::Codes::kOk);
});
EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(1);
EXPECT_CALL(*this, OnDecoded(IsOkStatus()))
.WillOnce(InvokeWithoutArgs(this, &MojoAudioDecoderTest::QuitLoop));
Decode();
RunLoop();
}
// TODO(xhwang): Add more tests.
} // namespace media