| // 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/avda_codec_allocator.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/time/tick_clock.h" |
| #include "media/base/android/mock_android_overlay.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::Invoke; |
| using testing::NiceMock; |
| using testing::ReturnRef; |
| using testing::_; |
| |
| namespace media { |
| namespace { |
| template <typename ReturnType> |
| void RunAndSignalTask(base::WaitableEvent* event, |
| ReturnType* return_value, |
| const base::Callback<ReturnType(void)>& cb) { |
| *return_value = cb.Run(); |
| event->Signal(); |
| } |
| |
| void WaitUntilRestarted(base::WaitableEvent* about_to_wait_event, |
| base::WaitableEvent* wait_event) { |
| // Notify somebody that we've started. |
| if (about_to_wait_event) |
| about_to_wait_event->Signal(); |
| wait_event->Wait(); |
| } |
| |
| void SignalImmediately(base::WaitableEvent* event) { |
| event->Signal(); |
| } |
| } // namespace |
| |
| class MockClient : public AVDACodecAllocatorClient { |
| public: |
| MockClient() |
| : codec_arrived_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED), |
| weak_factory_(this) {} |
| |
| // Gmock doesn't let us mock methods taking move-only types. |
| MOCK_METHOD1(OnCodecConfiguredMock, void(MediaCodecBridge* media_codec)); |
| void OnCodecConfigured( |
| std::unique_ptr<MediaCodecBridge> media_codec, |
| scoped_refptr<AVDASurfaceBundle> surface_bundle) override { |
| media_codec_ = std::move(media_codec); |
| OnCodecConfiguredMock(media_codec.get()); |
| codec_arrived_event_.Signal(); |
| } |
| |
| base::WeakPtr<AVDACodecAllocatorClient> GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| // Most recently provided codec. |
| std::unique_ptr<MediaCodecBridge> media_codec_; |
| |
| base::WaitableEvent codec_arrived_event_; |
| |
| base::WeakPtrFactory<AVDACodecAllocatorClient> weak_factory_; |
| }; |
| |
| class AVDACodecAllocatorTest : public testing::Test { |
| public: |
| AVDACodecAllocatorTest() |
| : allocator_thread_("AllocatorThread"), |
| stop_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) { |
| // Don't start the clock at null. |
| tick_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| } |
| |
| ~AVDACodecAllocatorTest() override {} |
| |
| // Utility fn to test out threading. |
| void AllocateCodec() { |
| allocator_->StartThread(avda1_); |
| scoped_refptr<CodecConfig> codec_config(new CodecConfig); |
| codec_config->surface_bundle = surface_bundle_; |
| EXPECT_CALL(*avda1_, OnCodecConfiguredMock(_)); |
| allocator_->CreateMediaCodecAsync(avda1_->GetWeakPtr(), codec_config); |
| } |
| |
| void DestroyCodec() { |
| // Make sure that we got a codec. |
| ASSERT_NE(avda1_->media_codec_, nullptr); |
| base::WaitableEvent destruction_event( |
| base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| static_cast<MockMediaCodecBridge*>(avda1_->media_codec_.get()) |
| ->SetCodecDestroyedEvent(&destruction_event); |
| allocator_->ReleaseMediaCodec(std::move(avda1_->media_codec_), |
| surface_bundle_); |
| |
| // This won't wait for the threads to stop, which means that the release |
| // might not have completed yet. Even once we are signalled that the codec |
| // has been destroyed, we can't be sure that OnMediaCodecReleased has run |
| // on the allocator thread. To get around this, one should wait on |
| // |stop_event_|, but not here. If we're run on the allocator's thread, |
| // then that's where |stop_event_| will be signalled from. |
| allocator_->StopThread(avda1_); |
| // The codec destruction should be async with respect to us. |
| destruction_event.Wait(); |
| |
| // Important: we don't know that OnMediaCodecReleased has completed. |
| // If we clean up the test and post the allocator's destruction to the |
| // allocator thread, before the "and reply" posts the codec release, then |
| // the codec release will be run on a destructed allocator. Either we |
| // should synchronize on that, or quit using base::Unretained(). |
| // Waiting for |stop_event_| and then for |allocator_thread_| should be |
| // sufficent to avoid this. |
| // Waiting for the overlay to be released is probably also enough, since |
| // that happens to be run on OnMediaCodecReleased also. |
| } |
| |
| void WaitForSurfaceDestruction() { |
| // This may be called from any thread. |
| PostAndWait(FROM_HERE, |
| base::Bind( |
| [](AVDACodecAllocator* allocator, AndroidOverlay* overlay) { |
| allocator->WaitForPendingRelease(overlay); |
| return true; |
| }, |
| allocator_, surface_bundle_->overlay.get())); |
| } |
| |
| protected: |
| void SetUp() override { |
| // Start the main thread for the allocator. This would normally be the GPU |
| // main thread. |
| ASSERT_TRUE(allocator_thread_.Start()); |
| |
| AVDACodecAllocator::CodecFactoryCB factory_cb( |
| base::BindRepeating(&MockMediaCodecBridge::CreateVideoDecoder)); |
| |
| // Create the first allocator on the allocator thread. |
| allocator_ = PostAndWait( |
| FROM_HERE, base::Bind( |
| [](AVDACodecAllocator::CodecFactoryCB factory_cb, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| base::TickClock* clock, base::WaitableEvent* event) { |
| return new AVDACodecAllocator(factory_cb, task_runner, |
| clock, event); |
| }, |
| factory_cb, allocator_thread_.task_runner(), |
| &tick_clock_, &stop_event_)); |
| allocator2_ = new AVDACodecAllocator( |
| factory_cb, base::SequencedTaskRunnerHandle::Get()); |
| |
| // Create a SurfaceBundle that provides an overlay. It will provide a null |
| // java ref if requested. |
| std::unique_ptr<MockAndroidOverlay> overlay = |
| std::make_unique<NiceMock<MockAndroidOverlay>>(); |
| scoped_refptr<CodecConfig> codec_config(new CodecConfig); |
| ON_CALL(*overlay, GetJavaSurface()) |
| .WillByDefault(ReturnRef(null_java_ref_)); |
| surface_bundle_ = new AVDASurfaceBundle(std::move(overlay)); |
| } |
| |
| void TearDown() override { |
| // 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. |
| PostAndWait(FROM_HERE, base::Bind( |
| [](AVDACodecAllocator* allocator) { |
| delete allocator; |
| return true; |
| }, |
| allocator_)); |
| |
| allocator_thread_.Stop(); |
| delete allocator2_; |
| } |
| |
| protected: |
| // Start / stop the threads for |avda| on the right thread. |
| void StartThread(AVDACodecAllocatorClient* avda) { |
| PostAndWait(FROM_HERE, base::Bind( |
| [](AVDACodecAllocator* allocator, |
| AVDACodecAllocatorClient* avda) { |
| allocator->StartThread(avda); |
| return true; // void won't work. |
| }, |
| allocator_, avda)); |
| } |
| |
| void StopThread(AVDACodecAllocatorClient* avda) { |
| // Note that we also wait for the stop event, so that we know that the |
| // stop has completed. It's async with respect to the allocator thread. |
| PostAndWait(FROM_HERE, base::Bind( |
| [](AVDACodecAllocator* allocator, |
| AVDACodecAllocatorClient* avda) { |
| allocator->StopThread(avda); |
| return true; |
| }, |
| allocator_, avda)); |
| // Note that we don't do this on the allocator thread, since that's the |
| // thread that will signal it. |
| stop_event_.Wait(); |
| } |
| |
| // Return the running state of |task_type|, doing the necessary thread hops. |
| bool IsThreadRunning(TaskType task_type) { |
| return PostAndWait( |
| FROM_HERE, |
| base::Bind( |
| [](AVDACodecAllocator* allocator, TaskType task_type) { |
| return allocator->GetThreadForTesting(task_type).IsRunning(); |
| }, |
| allocator_, task_type)); |
| } |
| |
| base::Optional<TaskType> TaskTypeForAllocation( |
| bool software_codec_forbidden) { |
| return PostAndWait( |
| FROM_HERE, |
| base::Bind(&AVDACodecAllocator::TaskTypeForAllocation, |
| base::Unretained(allocator_), software_codec_forbidden)); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> TaskRunnerFor( |
| TaskType task_type) { |
| return PostAndWait(FROM_HERE, |
| base::Bind(&AVDACodecAllocator::TaskRunnerFor, |
| base::Unretained(allocator_), task_type)); |
| } |
| |
| // Post |cb| to the allocator thread, and wait for a response. Note that we |
| // don't have a specialization for void, and void won't work as written. So, |
| // be sure to return something. |
| template <typename ReturnType> |
| ReturnType PostAndWait(const base::Location& from_here, |
| const base::Callback<ReturnType(void)>& cb) { |
| base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| ReturnType return_value = ReturnType(); |
| allocator_thread_.task_runner()->PostTask( |
| from_here, |
| base::Bind(&RunAndSignalTask<ReturnType>, &event, &return_value, cb)); |
| event.Wait(); |
| return return_value; |
| } |
| |
| // So that we can get the thread's task runner. |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| base::Thread allocator_thread_; |
| |
| // The test params for |allocator_|. |
| base::SimpleTestTickClock tick_clock_; |
| base::WaitableEvent stop_event_; |
| |
| // Allocators that we own. The first is intialized to be used on the allocator |
| // thread and the second one is initialized on the test thread. Each test |
| // should only be using one of the two. They are not unique_ptrs because the |
| // destructor is private and they need to be destructed on the right thread. |
| AVDACodecAllocator* allocator_ = nullptr; |
| AVDACodecAllocator* allocator2_ = nullptr; |
| |
| NiceMock<MockClient> client1_, client2_, client3_; |
| NiceMock<MockClient>* avda1_ = &client1_; |
| NiceMock<MockClient>* avda2_ = &client2_; |
| NiceMock<MockClient>* avda3_ = &client3_; |
| |
| // Surface bundle that has an overlay. |
| scoped_refptr<AVDASurfaceBundle> surface_bundle_; |
| base::android::JavaRef<jobject> null_java_ref_; |
| }; |
| |
| TEST_F(AVDACodecAllocatorTest, ThreadsStartWhenClientsStart) { |
| ASSERT_FALSE(IsThreadRunning(AUTO_CODEC)); |
| ASSERT_FALSE(IsThreadRunning(SW_CODEC)); |
| StartThread(avda1_); |
| ASSERT_TRUE(IsThreadRunning(AUTO_CODEC)); |
| ASSERT_TRUE(IsThreadRunning(SW_CODEC)); |
| } |
| |
| TEST_F(AVDACodecAllocatorTest, ThreadsStopAfterAllClientsStop) { |
| StartThread(avda1_); |
| StartThread(avda2_); |
| StopThread(avda1_); |
| ASSERT_TRUE(IsThreadRunning(AUTO_CODEC)); |
| StopThread(avda2_); |
| ASSERT_FALSE(IsThreadRunning(AUTO_CODEC)); |
| ASSERT_FALSE(IsThreadRunning(SW_CODEC)); |
| } |
| |
| TEST_F(AVDACodecAllocatorTest, TestHangThread) { |
| StartThread(avda1_); |
| ASSERT_EQ(AUTO_CODEC, TaskTypeForAllocation(false)); |
| |
| // Hang the AUTO_CODEC thread. |
| base::WaitableEvent about_to_wait_event( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| base::WaitableEvent wait_event( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TaskRunnerFor(AUTO_CODEC) |
| ->PostTask(FROM_HERE, base::Bind(&WaitUntilRestarted, |
| &about_to_wait_event, &wait_event)); |
| // Wait until the task starts, so that |allocator_| starts the hang timer. |
| about_to_wait_event.Wait(); |
| |
| // Verify that we've failed over after a long time has passed. |
| tick_clock_.Advance(base::TimeDelta::FromSeconds(1)); |
| ASSERT_EQ(SW_CODEC, TaskTypeForAllocation(false)); |
| |
| // Un-hang the thread and wait for it to let another task run. This will |
| // notify |allocator_| that the thread is no longer hung. |
| base::WaitableEvent done_waiting_event( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| TaskRunnerFor(AUTO_CODEC) |
| ->PostTask(FROM_HERE, |
| base::Bind(&SignalImmediately, &done_waiting_event)); |
| wait_event.Signal(); |
| done_waiting_event.Wait(); |
| |
| // Verify that we've un-failed over. |
| ASSERT_EQ(AUTO_CODEC, TaskTypeForAllocation(false)); |
| } |
| |
| TEST_F(AVDACodecAllocatorTest, AllocateAndDestroyCodecOnAllocatorThread) { |
| // Make sure that allocating / freeing a codec on the allocator's thread |
| // completes, and doesn't DCHECK. |
| allocator_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&AVDACodecAllocatorTest::AllocateCodec, |
| base::Unretained(this))); |
| |
| // Wait for the codec on this thread, rather than the allocator thread, since |
| // that's where the codec will be posted. |
| avda1_->codec_arrived_event_.Wait(); |
| |
| allocator_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&AVDACodecAllocatorTest::DestroyCodec, |
| base::Unretained(this))); |
| |
| // Note that TearDown will join |allocator_thread_|. |
| WaitForSurfaceDestruction(); |
| |
| // Wait for threads to stop, now that we're not on the allocator thread. |
| stop_event_.Wait(); |
| } |
| |
| TEST_F(AVDACodecAllocatorTest, AllocateAndDestroyCodecOnNewThread) { |
| // Make sure that allocating / freeing a codec on a random thread completes, |
| // and doesn't DCHECK. |
| base::Thread new_thread("NewThreadForTesting"); |
| ASSERT_TRUE(new_thread.Start()); |
| new_thread.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&AVDACodecAllocatorTest::AllocateCodec, |
| base::Unretained(this))); |
| |
| // Wait for the codec on this thread, rather than |new_thread|, since that's |
| // where the codec will be posted. |
| avda1_->codec_arrived_event_.Wait(); |
| |
| new_thread.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&AVDACodecAllocatorTest::DestroyCodec, |
| base::Unretained(this))); |
| new_thread.Stop(); |
| WaitForSurfaceDestruction(); |
| stop_event_.Wait(); |
| } |
| |
| } // namespace media |