| // 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/gpu/android/codec_allocator.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| |
| #include "base/android/build_info.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/check.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread.h" |
| #include "base/time/tick_clock.h" |
| #include "media/base/android/mock_media_codec_bridge.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") { |
| // Don't start the clock at null. |
| tick_clock_.Advance(base::Seconds(1)); |
| allocator_ = new CodecAllocator( |
| base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder), |
| base::SequencedTaskRunnerHandle::Get()); |
| allocator_->tick_clock_ = &tick_clock_; |
| } |
| |
| CodecAllocatorTest(const CodecAllocatorTest&) = delete; |
| CodecAllocatorTest& operator=(const CodecAllocatorTest&) = delete; |
| |
| ~CodecAllocatorTest() override { |
| if (allocator_thread_.IsRunning()) { |
| // Don't leave any threads hung, or this will hang too. It would be nice |
| // if we could let a unique ptr handle this, but the destructor is |
| // private. We also have to destroy it on the right thread. |
| allocator_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce([](CodecAllocator* allocator) { delete allocator; }, |
| allocator_)); |
| |
| allocator_thread_.Stop(); |
| return; |
| } |
| |
| delete allocator_; |
| } |
| |
| void CreateAllocatorOnAnotherThread() { |
| delete allocator_; |
| |
| // Start a thread for the allocator. This would normally be the GPU main |
| // thread. |
| 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) { |
| // This should always be called on the main thread, despite whatever thread |
| // the allocator happens to be running on. |
| 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) { |
| // This should always be called on the main thread, despite whatever thread |
| // the allocator happens to be running on. |
| 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()); |
| |
| // Allocate and return a config that allows any codec, and is suitable for |
| // hardware decode. |
| 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: |
| // So that we can get the thread's task runner. |
| base::test::TaskEnvironment task_environment_; |
| |
| base::Thread allocator_thread_; |
| |
| // The test params for |allocator_|. |
| base::SimpleTestTickClock tick_clock_; |
| |
| // Allocators that we own. They are not unique_ptrs because the destructor is |
| // private and they need to be destructed on the right thread. |
| 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)); |
| |
| // Advance some time, but not enough to trigger hang detection. |
| 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())); |
| |
| // Advance some time, but not enough to trigger hang detection. |
| 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()); |
| |
| // Release null codec, but don't pump message loop. |
| allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(), |
| base::DoNothing()); |
| tick_clock_.Advance(base::Seconds(1)); |
| ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung()); |
| } |
| |
| TEST_F(CodecAllocatorTest, StalledCreateCountsAsHung) { |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Create codec, but don't pump message loop. |
| 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()); |
| |
| // Release null codec, but don't pump message loop. |
| allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(), |
| base::DoNothing()); |
| tick_clock_.Advance(base::Seconds(1)); |
| ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Secure creation should fail since we're now using software codecs. |
| 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(); |
| |
| // QuitClosure may run before the initial release processes, so RunUntilIdle |
| // here such that hung status is cleared. |
| task_environment_.RunUntilIdle(); |
| |
| // Running the loop should clear hung status. |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| } |
| |
| TEST_F(CodecAllocatorTest, SoftwareCodecUsedWhenHung) { |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Release null codec, but don't pump message loop. |
| allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(), |
| base::DoNothing()); |
| tick_clock_.Advance(base::Seconds(1)); |
| ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Creation should fall back to software. |
| 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(); |
| |
| // QuitClosure may run before the initial release processes, so RunUntilIdle |
| // here such that hung status is cleared. |
| task_environment_.RunUntilIdle(); |
| |
| // Running the loop should clear hung status. |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| } |
| |
| // Verifies that software codecs are released on the secondary task runner when |
| // hung and that non-sw codecs are always released on the primary task runner. |
| TEST_F(CodecAllocatorTest, CodecReleasedOnRightTaskRunnerWhenHung) { |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Release null codec, but don't pump message loop. |
| allocator_->ReleaseMediaCodec(std::make_unique<MockMediaCodecBridge>(), |
| base::DoNothing()); |
| tick_clock_.Advance(base::Seconds(1)); |
| ASSERT_TRUE(IsPrimaryTaskRunnerLikelyHung()); |
| |
| // Release software codec, ensure it runs on secondary task runner. |
| 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()); |
| |
| // Release hardware codec, ensure it runs on primary task runner. |
| 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()); |
| |
| // Release secure (hardware) codec, ensure it runs on primary task runner. |
| 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()); |
| |
| // QuitClosure may run before the initial release processes, so RunUntilIdle |
| // here such that hung status is cleared. |
| task_environment_.RunUntilIdle(); |
| |
| // Running the loop should clear hung status. |
| ASSERT_FALSE(IsPrimaryTaskRunnerLikelyHung()); |
| } |
| |
| // Make sure that allocating / freeing a codec on the allocator's thread |
| // completes, and doesn't DCHECK. |
| 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)); |
| |
| bool lollipop = base::android::BuildInfo::GetInstance()->sdk_int() < |
| base::android::SDK_VERSION_MARSHMALLOW; |
| if (lollipop) |
| EXPECT_CALL(*this, OnCodecCreated(CodecType::kAny)); |
| else |
| EXPECT_CALL(*this, OnCodecCreated(CodecType::kSoftware)); |
| |
| run_loop.Run(); |
| } |
| |
| } // namespace media |