#include "media/base/android/media_codec_loop.h"
#include <memory>
#include "base/android/android_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "media/base/android/media_codec_bridge.h"
#include "media/base/android/mock_media_codec_bridge.h"
#include "media/base/waiting.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
namespace media {
class MockMediaCodecLoopClient : public StrictMock<MediaCodecLoop::Client> {
public:
MOCK_CONST_METHOD0(IsAnyInputPending, bool());
MOCK_METHOD0(ProvideInputData, scoped_refptr<DecoderBuffer>());
MOCK_METHOD1(OnInputDataQueued, void(bool));
MOCK_METHOD1(OnDecodedEos, bool(const MediaCodecLoop::OutputBuffer&));
MOCK_METHOD1(OnDecodedFrame, bool(const MediaCodecLoop::OutputBuffer&));
MOCK_METHOD1(OnWaiting, void(WaitingReason reason));
MOCK_METHOD0(OnOutputFormatChanged, bool());
MOCK_METHOD0(OnCodecLoopError, void());
};
class MediaCodecLoopTest : public testing::Test {
public:
MediaCodecLoopTest()
: task_runner_current_default_handle_(mock_task_runner_),
client_(std::make_unique<MockMediaCodecLoopClient>()) {}
MediaCodecLoopTest(const MediaCodecLoopTest&) = delete;
MediaCodecLoopTest& operator=(const MediaCodecLoopTest&) = delete;
~MediaCodecLoopTest() override {}
protected:
enum IdleExpectation {
ShouldBeIdle,
ShouldNotBeIdle,
};
void WaitUntilIdle(IdleExpectation idleExpectation = ShouldBeIdle) {
switch (idleExpectation) {
case ShouldBeIdle:
EXPECT_CALL(*client_, IsAnyInputPending()).Times(0);
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)).Times(0);
break;
case ShouldNotBeIdle:
EXPECT_CALL(*client_, IsAnyInputPending())
.Times(AtLeast(1))
.WillRepeatedly(Return(false));
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(Return(MediaCodecResult::Codes::kTryAgainLater));
break;
}
EXPECT_CALL(Codec(), DequeueInputBuffer(_, _)).Times(0);
mock_task_runner_->FastForwardBy(base::Seconds(30));
}
void ConstructCodecLoop() {
int sdk_int = base::android::android_info::SDK_VERSION_NOUGAT;
auto codec = std::make_unique<MockMediaCodecBridge>();
EXPECT_CALL(*client_, OnCodecLoopError()).Times(0);
codec_loop_ = std::make_unique<MediaCodecLoop>(
sdk_int, client_.get(), std::move(codec), mock_task_runner_);
codec_loop_->SetTestTickClock(mock_task_runner_->GetMockTickClock());
Mock::VerifyAndClearExpectations(client_.get());
}
void ExpectEmptyIOLoop() {
ExpectIsAnyInputPending(false);
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
}
void ExpectIsAnyInputPending(bool pending) {
EXPECT_CALL(*client_, IsAnyInputPending()).WillOnce(Return(pending));
}
void ExpectDequeueInputBuffer(int input_buffer_index,
MediaCodecResult status = OkStatus()) {
EXPECT_CALL(Codec(), DequeueInputBuffer(_, _))
.WillOnce(DoAll(SetArgPointee<1>(input_buffer_index), Return(status)));
}
void ExpectInputDataQueued(bool success) {
EXPECT_CALL(*client_, OnInputDataQueued(success)).Times(1);
}
void ExpectQueueInputBuffer(int input_buffer_index,
scoped_refptr<DecoderBuffer> data,
MediaCodecResult status = OkStatus()) {
EXPECT_CALL(Codec(), QueueInputBuffer(input_buffer_index, base::span(*data),
data->timestamp()))
.Times(1)
.WillOnce(Return(status));
}
void ExpectProvideInputData(scoped_refptr<DecoderBuffer> data) {
EXPECT_CALL(*client_, ProvideInputData()).WillOnce(Return(std::move(data)));
}
scoped_refptr<DecoderBuffer> BigBuckBunny() {
auto data = DecoderBuffer::CopyFrom(base::as_byte_span("big buck bunny"));
data->set_timestamp(base::Seconds(1));
return data;
}
struct OutputBuffer {
int index = 1;
size_t offset = 0;
size_t size = 1024;
base::TimeDelta pts = base::Seconds(1);
bool eos = false;
bool key_frame = true;
};
struct EosOutputBuffer : public OutputBuffer {
EosOutputBuffer() { eos = true; }
};
void ExpectDequeueOutputBuffer(MediaCodecResult status) {
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(status));
}
void ExpectDequeueOutputBuffer(const OutputBuffer& buffer) {
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(DoAll(
SetArgPointee<1>(buffer.index), SetArgPointee<2>(buffer.offset),
SetArgPointee<3>(buffer.size), SetArgPointee<4>(buffer.pts),
SetArgPointee<5>(buffer.eos), SetArgPointee<6>(buffer.key_frame),
Return(OkStatus())));
}
void ExpectOnDecodedFrame(const OutputBuffer& buf) {
EXPECT_CALL(*client_,
OnDecodedFrame(
Field(&MediaCodecLoop::OutputBuffer::index, Eq(buf.index))))
.Times(1)
.WillOnce(Return(true));
}
MockMediaCodecBridge& Codec() {
return *static_cast<MockMediaCodecBridge*>(codec_loop_->GetCodec());
}
public:
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_ =
new base::TestMockTimeTaskRunner;
base::SingleThreadTaskRunner::CurrentDefaultHandle
task_runner_current_default_handle_;
std::unique_ptr<MediaCodecLoop> codec_loop_;
std::unique_ptr<MockMediaCodecLoopClient> client_;
};
TEST_F(MediaCodecLoopTest, TestConstructionWithNullCodec) {
std::unique_ptr<MediaCodecBridge> codec;
EXPECT_CALL(*client_, OnCodecLoopError()).Times(1);
const int sdk_int = base::android::android_info::SDK_VERSION_NOUGAT;
codec_loop_ = std::make_unique<MediaCodecLoop>(
sdk_int, client_.get(), std::move(codec),
scoped_refptr<base::SingleThreadTaskRunner>());
ASSERT_FALSE(codec_loop_->GetCodec());
}
TEST_F(MediaCodecLoopTest, TestConstructionWithCodec) {
ConstructCodecLoop();
ASSERT_EQ(codec_loop_->GetCodec(), &Codec());
WaitUntilIdle(ShouldBeIdle);
}
TEST_F(MediaCodecLoopTest, TestPendingWorkWithoutInput) {
ConstructCodecLoop();
ExpectIsAnyInputPending(false);
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
codec_loop_->ExpectWork();
WaitUntilIdle(ShouldNotBeIdle);
}
TEST_F(MediaCodecLoopTest, TestPendingWorkWithInput) {
ConstructCodecLoop();
ExpectIsAnyInputPending(true);
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)).Times(1);
EXPECT_CALL(Codec(), DequeueInputBuffer(_, _)).Times(1);
codec_loop_->ExpectWork();
WaitUntilIdle(ShouldNotBeIdle);
}
TEST_F(MediaCodecLoopTest, TestPendingWorkWithOutputBuffer) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(false);
OutputBuffer buf;
ExpectDequeueOutputBuffer(buf);
ExpectOnDecodedFrame(buf);
ExpectEmptyIOLoop();
}
codec_loop_->ExpectWork();
WaitUntilIdle(ShouldNotBeIdle);
}
TEST_F(MediaCodecLoopTest, TestQueueEos) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(true);
int input_buffer_index = 123;
ExpectDequeueInputBuffer(input_buffer_index);
auto data = DecoderBuffer::CreateEOSBuffer();
ExpectProvideInputData(data);
EXPECT_CALL(Codec(), QueueEOS(input_buffer_index));
ExpectInputDataQueued(true);
EosOutputBuffer eos;
ExpectDequeueOutputBuffer(eos);
EXPECT_CALL(Codec(), ReleaseOutputBuffer(eos.index, false));
EXPECT_CALL(*client_, OnDecodedEos(_)).Times(1).WillOnce(Return(true));
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
}
codec_loop_->ExpectWork();
}
TEST_F(MediaCodecLoopTest, TestQueueEosFailure) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(true);
int input_buffer_index = 123;
ExpectDequeueInputBuffer(input_buffer_index);
auto data = DecoderBuffer::CreateEOSBuffer();
ExpectProvideInputData(data);
EXPECT_CALL(Codec(), QueueEOS(input_buffer_index));
ExpectInputDataQueued(true);
EosOutputBuffer eos;
ExpectDequeueOutputBuffer(eos);
EXPECT_CALL(Codec(), ReleaseOutputBuffer(eos.index, false));
EXPECT_CALL(*client_, OnDecodedEos(_)).Times(1).WillOnce(Return(false));
EXPECT_CALL(*client_, OnCodecLoopError()).Times(1);
}
codec_loop_->ExpectWork();
}
TEST_F(MediaCodecLoopTest, TestQueueInputData) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(true);
int input_buffer_index = 123;
ExpectDequeueInputBuffer(input_buffer_index);
auto data = BigBuckBunny();
ExpectProvideInputData(data);
ExpectQueueInputBuffer(input_buffer_index, data);
ExpectInputDataQueued(true);
EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
ExpectEmptyIOLoop();
}
codec_loop_->ExpectWork();
WaitUntilIdle(ShouldNotBeIdle);
}
TEST_F(MediaCodecLoopTest, TestQueueInputDataFails) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(true);
int input_buffer_index = 123;
ExpectDequeueInputBuffer(input_buffer_index);
auto data = BigBuckBunny();
ExpectProvideInputData(data);
ExpectQueueInputBuffer(input_buffer_index, data,
MediaCodecResult::Codes::kError);
ExpectInputDataQueued(false);
EXPECT_CALL(*client_, OnCodecLoopError()).Times(1);
}
codec_loop_->ExpectWork();
}
TEST_F(MediaCodecLoopTest, TestQueueInputDataTryAgain) {
ConstructCodecLoop();
{
InSequence _s;
ExpectIsAnyInputPending(true);
ExpectDequeueInputBuffer(-1, MediaCodecResult::Codes::kTryAgainLater);
ExpectDequeueOutputBuffer(MediaCodecResult::Codes::kTryAgainLater);
}
codec_loop_->ExpectWork();
WaitUntilIdle(ShouldNotBeIdle);
}
TEST_F(MediaCodecLoopTest, TestSeveralPendingIOBuffers) {
ConstructCodecLoop();
int input_buffer_index = 123;
const int num_loops = 4;
InSequence _s;
for (int i = 0; i < num_loops; i++, input_buffer_index++) {
ExpectIsAnyInputPending(true);
ExpectDequeueInputBuffer(input_buffer_index);
auto data = BigBuckBunny();
ExpectProvideInputData(data);
ExpectQueueInputBuffer(input_buffer_index, data);
ExpectInputDataQueued(true);
OutputBuffer buffer;
buffer.index = i;
buffer.size += i;
buffer.pts = base::Seconds(i + 1);
ExpectDequeueOutputBuffer(buffer);
ExpectOnDecodedFrame(buffer);
}
ExpectEmptyIOLoop();
codec_loop_->ExpectWork();
}
TEST_F(MediaCodecLoopTest, TestOnKeyAdded) {
ConstructCodecLoop();
int input_buffer_index = 123;
auto data = BigBuckBunny();
{
InSequence _s;
ExpectIsAnyInputPending(true);
ExpectDequeueInputBuffer(input_buffer_index);
ExpectProvideInputData(data);
ExpectQueueInputBuffer(input_buffer_index, data,
MediaCodecResult::Codes::kNoKey);
EXPECT_CALL(*client_, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(1);
ExpectDequeueOutputBuffer(MediaCodecResult::Codes::kTryAgainLater);
ExpectDequeueOutputBuffer(MediaCodecResult::Codes::kTryAgainLater);
}
codec_loop_->ExpectWork();
{
InSequence _s;
ExpectDequeueOutputBuffer(MediaCodecResult::Codes::kTryAgainLater);
}
codec_loop_->ExpectWork();
{
InSequence _s;
ExpectQueueInputBuffer(input_buffer_index, data);
ExpectInputDataQueued(true);
ExpectDequeueOutputBuffer(MediaCodecResult::Codes::kTryAgainLater);
ExpectEmptyIOLoop();
}
codec_loop_->OnKeyAdded();
WaitUntilIdle(ShouldNotBeIdle);
}
}