summaryrefslogtreecommitdiff
path: root/chromium/media/gpu/android/avda_codec_allocator_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/media/gpu/android/avda_codec_allocator_unittest.cc')
-rw-r--r--chromium/media/gpu/android/avda_codec_allocator_unittest.cc384
1 files changed, 384 insertions, 0 deletions
diff --git a/chromium/media/gpu/android/avda_codec_allocator_unittest.cc b/chromium/media/gpu/android/avda_codec_allocator_unittest.cc
new file mode 100644
index 00000000000..6e7fe85599f
--- /dev/null
+++ b/chromium/media/gpu/android/avda_codec_allocator_unittest.cc
@@ -0,0 +1,384 @@
+// 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 =
+ base::MakeUnique<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