#include "media/gpu/android/codec_allocator.h"
#include <stdint.h>
#include <memory>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "media/base/android/mock_media_codec_bridge.h"
#include "media/base/subsample_entry.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::NiceMock;
using testing::ReturnRef;
namespace media {
class CodecAllocatorTest : public testing::Test {
public:
CodecAllocatorTest() : allocator_thread_("AllocatorThread") {
tick_clock_.Advance(base::Seconds(1));
allocator_ = new CodecAllocator(
base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder),
base::SequencedTaskRunner::GetCurrentDefault());
allocator_->tick_clock_ = &tick_clock_;
}
CodecAllocatorTest(const CodecAllocatorTest&) = delete;
CodecAllocatorTest& operator=(const CodecAllocatorTest&) = delete;
~CodecAllocatorTest() override {
if (allocator_thread_.IsRunning()) {
allocator_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce([](CodecAllocator* allocator) { delete allocator; },
allocator_));
allocator_thread_.Stop();
return;
}
delete allocator_;
}
void CreateAllocatorOnAnotherThread() {
delete allocator_;
CHECK(allocator_thread_.Start());
allocator_ = new CodecAllocator(
base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder),
allocator_thread_.task_runner());
allocator_->tick_clock_ = &tick_clock_;
}
void OnCodecCreatedInternal(base::OnceClosure quit_closure,
std::unique_ptr<MediaCodecBridge> codec) {
ASSERT_TRUE(
task_environment_.GetMainThreadTaskRunner()->BelongsToCurrentThread());
last_created_codec_.reset(
reinterpret_cast<MockMediaCodecBridge*>(codec.release()));
OnCodecCreated(last_created_codec_->GetCodecType());
std::move(quit_closure).Run();
}
void OnCodecReleasedInternal(base::OnceClosure quit_closure) {
ASSERT_TRUE(
task_environment_.GetMainThreadTaskRunner()->BelongsToCurrentThread());
OnCodecReleased();
std::move(quit_closure).Run();
}
bool IsPrimaryTaskRunnerLikelyHung() const {
CHECK(!allocator_thread_.IsRunning());
return allocator_->IsPrimaryTaskRunnerLikelyHung();
}
void VerifyOnPrimaryTaskRunner() {
ASSERT_TRUE(allocator_->primary_task_runner_->RunsTasksInCurrentSequence());
}
void VerifyOnSecondaryTaskRunner() {
ASSERT_TRUE(
allocator_->secondary_task_runner_->RunsTasksInCurrentSequence());
}
MOCK_METHOD1(OnCodecCreated, void(CodecType));
MOCK_METHOD0(OnCodecReleased, void());
std::unique_ptr<VideoCodecConfig> CreateConfig() {
auto config = std::make_unique<VideoCodecConfig>();
config->codec_type = CodecType::kAny;
config->initial_expected_coded_size =
CodecAllocator::kMinHardwareResolution;
return config;
}
protected:
base::test::TaskEnvironment task_environment_;
base::Thread allocator_thread_;
base::SimpleTestTickClock tick_clock_;
raw_ptr<CodecAllocator> allocator_ = nullptr;
std::unique_ptr<MockMediaCodecBridge> last_created_codec_;
};
TEST_F(CodecAllocatorTest, NormalCreation) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kAny));
run_loop.Run();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, NormalSecureCreation) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
config->codec_type = CodecType::kSecure;
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kSecure));
run_loop.Run();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, MultipleCreation) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), base::DoNothing()),
std::move(config));
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
tick_clock_.Advance(base::Milliseconds(400));
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
auto config_secure = CreateConfig();
config_secure->codec_type = CodecType::kSecure;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config_secure));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kAny));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kSecure));
run_loop.Run();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, MultipleRelease) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
base::RunLoop run_loop;
allocator_->ReleaseMediaCodec(
std::make_unique<MockMediaCodecBridge>(),
base::BindOnce(&CodecAllocatorTest::OnCodecReleasedInternal,
base::Unretained(this), base::DoNothing()));
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
tick_clock_.Advance(base::Milliseconds(400));
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
allocator_->ReleaseMediaCodec(
std::make_unique<MockMediaCodecBridge>(),
base::BindOnce(&CodecAllocatorTest::OnCodecReleasedInternal,
base::Unretained(this), run_loop.QuitClosure()));
EXPECT_CALL(*this, OnCodecReleased()).Times(2);
run_loop.Run();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, StalledReleaseCountsAsHung) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(),
base::DoNothing());
tick_clock_.Advance(base::Seconds(1));
ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, StalledCreateCountsAsHung) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
config->codec_type = CodecType::kSecure;
allocator_->CreateMediaCodecAsync(base::DoNothing(), std::move(config));
tick_clock_.Advance(base::Seconds(1));
ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, SecureCreationFailsWhenHung) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(),
base::DoNothing());
tick_clock_.Advance(base::Seconds(1));
ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
config->codec_type = CodecType::kSecure;
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(
[](base::OnceClosure quit_closure,
std::unique_ptr<MediaCodecBridge> codec) {
ASSERT_FALSE(codec);
std::move(quit_closure).Run();
},
run_loop.QuitClosure()),
std::move(config));
run_loop.Run();
task_environment_.RunUntilIdle();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, SoftwareCodecUsedWhenHung) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(),
base::DoNothing());
tick_clock_.Advance(base::Seconds(1));
ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kSoftware));
run_loop.Run();
task_environment_.RunUntilIdle();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, CodecReleasedOnRightTaskRunnerWhenHung) {
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(),
base::DoNothing());
tick_clock_.Advance(base::Seconds(1));
ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung());
auto config = CreateConfig();
config->codec_type = CodecType::kSoftware;
auto sw_codec = MockMediaCodecBridge::CreateVideoDecoder(*config);
reinterpret_cast<MockMediaCodecBridge*>(sw_codec.get())
->destruction_cb.ReplaceClosure(
base::BindOnce(&CodecAllocatorTest::VerifyOnSecondaryTaskRunner,
base::Unretained(this)));
allocator_->ReleaseMediaCodec(std::move(sw_codec), base::DoNothing());
config->codec_type = CodecType::kAny;
auto hw_codec = MockMediaCodecBridge::CreateVideoDecoder(*config);
reinterpret_cast<MockMediaCodecBridge*>(hw_codec.get())
->destruction_cb.ReplaceClosure(
base::BindOnce(&CodecAllocatorTest::VerifyOnPrimaryTaskRunner,
base::Unretained(this)));
allocator_->ReleaseMediaCodec(std::move(hw_codec), base::DoNothing());
config->codec_type = CodecType::kSecure;
auto secure_codec = MockMediaCodecBridge::CreateVideoDecoder(*config);
reinterpret_cast<MockMediaCodecBridge*>(secure_codec.get())
->destruction_cb.ReplaceClosure(
base::BindOnce(&CodecAllocatorTest::VerifyOnPrimaryTaskRunner,
base::Unretained(this)));
allocator_->ReleaseMediaCodec(std::move(secure_codec), base::DoNothing());
task_environment_.RunUntilIdle();
ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung());
}
TEST_F(CodecAllocatorTest, AllocateAndDestroyCodecOnAllocatorThread) {
CreateAllocatorOnAnotherThread();
{
base::RunLoop run_loop;
auto config = CreateConfig();
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kAny));
run_loop.Run();
}
{
base::RunLoop run_loop;
allocator_->ReleaseMediaCodec(
std::move(last_created_codec_),
base::BindOnce(&CodecAllocatorTest::OnCodecReleasedInternal,
base::Unretained(this), run_loop.QuitClosure()));
EXPECT_CALL(*this, OnCodecReleased());
run_loop.Run();
}
}
TEST_F(CodecAllocatorTest, LowResolutionGetsSoftware) {
auto config = CreateConfig();
config->initial_expected_coded_size =
CodecAllocator::kMinHardwareResolution - gfx::Size(1, 1);
base::RunLoop run_loop;
allocator_->CreateMediaCodecAsync(
base::BindOnce(&CodecAllocatorTest::OnCodecCreatedInternal,
base::Unretained(this), run_loop.QuitClosure()),
std::move(config));
EXPECT_CALL(*this, OnCodecCreated(CodecType::kSoftware));
run_loop.Run();
}
}