blob: 2abcd6c459af4d22062f1cecb95d4ae6e3a6c23f [file] [log] [blame]
// Copyright 2017 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 <memory>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "media/base/android/media_codec_bridge.h"
#include "media/base/android/mock_media_codec_bridge.h"
#include "media/base/encryption_scheme.h"
#include "media/base/subsample_entry.h"
#include "media/gpu/android/codec_wrapper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::DoAll;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
using testing::SetArgPointee;
using testing::_;
namespace media {
class CodecWrapperTest : public testing::Test {
public:
CodecWrapperTest() : other_thread_("Other thread") {
auto codec = std::make_unique<NiceMock<MockMediaCodecBridge>>();
codec_ = codec.get();
surface_bundle_ = base::MakeRefCounted<CodecSurfaceBundle>();
wrapper_ = std::make_unique<CodecWrapper>(
CodecSurfacePair(std::move(codec), surface_bundle_),
output_buffer_release_cb_.Get(),
// Unrendered output buffers are released on our thread.
base::SequencedTaskRunnerHandle::Get());
ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK));
ON_CALL(*codec_, DequeueInputBuffer(_, _))
.WillByDefault(DoAll(SetArgPointee<1>(12), Return(MEDIA_CODEC_OK)));
ON_CALL(*codec_, QueueInputBuffer(_, _, _, _))
.WillByDefault(Return(MEDIA_CODEC_OK));
uint8_t data = 0;
fake_decoder_buffer_ = DecoderBuffer::CopyFrom(&data, 1);
// May fail.
other_thread_.Start();
}
~CodecWrapperTest() override {
// ~CodecWrapper asserts that the codec was taken.
wrapper_->TakeCodecSurfacePair();
}
std::unique_ptr<CodecOutputBuffer> DequeueCodecOutputBuffer() {
std::unique_ptr<CodecOutputBuffer> codec_buffer;
wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
return codec_buffer;
}
// So that we can get the thread's task runner.
base::test::ScopedTaskEnvironment scoped_task_environment_;
NiceMock<MockMediaCodecBridge>* codec_;
std::unique_ptr<CodecWrapper> wrapper_;
scoped_refptr<CodecSurfaceBundle> surface_bundle_;
NiceMock<base::MockCallback<CodecWrapper::OutputReleasedCB>>
output_buffer_release_cb_;
scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
base::Thread other_thread_;
};
TEST_F(CodecWrapperTest, TakeCodecReturnsTheCodecFirstAndNullLater) {
ASSERT_EQ(wrapper_->TakeCodecSurfacePair().first.get(), codec_);
ASSERT_EQ(wrapper_->TakeCodecSurfacePair().first, nullptr);
}
TEST_F(CodecWrapperTest, NoCodecOutputBufferReturnedIfDequeueFails) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_ERROR));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer, nullptr);
}
TEST_F(CodecWrapperTest, InitiallyThereAreNoValidCodecOutputBuffers) {
ASSERT_FALSE(wrapper_->HasUnreleasedOutputBuffers());
}
TEST_F(CodecWrapperTest, FlushInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->Flush();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, TakingTheCodecInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodecSurfacePair();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, SetSurfaceInvalidatesCodecOutputBuffers) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->SetSurface(base::MakeRefCounted<CodecSurfaceBundle>());
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAreAllInvalidatedTogether) {
auto codec_buffer1 = DequeueCodecOutputBuffer();
auto codec_buffer2 = DequeueCodecOutputBuffer();
wrapper_->Flush();
ASSERT_FALSE(codec_buffer1->ReleaseToSurface());
ASSERT_FALSE(codec_buffer2->ReleaseToSurface());
ASSERT_FALSE(wrapper_->HasUnreleasedOutputBuffers());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAfterFlushAreValid) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->Flush();
codec_buffer = DequeueCodecOutputBuffer();
ASSERT_TRUE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBufferReleaseUsesCorrectIndex) {
// The second arg is the buffer index pointer.
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<1>(42), Return(MEDIA_CODEC_OK)));
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(42, true));
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, CodecOutputBuffersAreInvalidatedByRelease) {
auto codec_buffer = DequeueCodecOutputBuffer();
codec_buffer->ReleaseToSurface();
ASSERT_FALSE(codec_buffer->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersReleaseOnDestruction) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, false));
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, CodecOutputBuffersDoNotReleaseIfAlreadyReleased) {
auto codec_buffer = DequeueCodecOutputBuffer();
codec_buffer->ReleaseToSurface();
EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, _)).Times(0);
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, ReleasingCodecOutputBuffersAfterTheCodecIsSafe) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodecSurfacePair();
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, DeletingCodecOutputBuffersAfterTheCodecIsSafe) {
auto codec_buffer = DequeueCodecOutputBuffer();
wrapper_->TakeCodecSurfacePair();
// This test ensures the destructor doesn't crash.
codec_buffer = nullptr;
}
TEST_F(CodecWrapperTest, CodecOutputBufferReleaseDoesNotInvalidateEarlierOnes) {
auto codec_buffer1 = DequeueCodecOutputBuffer();
auto codec_buffer2 = DequeueCodecOutputBuffer();
codec_buffer2->ReleaseToSurface();
EXPECT_TRUE(codec_buffer1->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, CodecOutputBufferReleaseDoesNotInvalidateLaterOnes) {
auto codec_buffer1 = DequeueCodecOutputBuffer();
auto codec_buffer2 = DequeueCodecOutputBuffer();
codec_buffer1->ReleaseToSurface();
ASSERT_TRUE(codec_buffer2->ReleaseToSurface());
}
TEST_F(CodecWrapperTest, FormatChangedStatusIsSwallowed) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
.WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::kTryAgainLater);
}
TEST_F(CodecWrapperTest, BuffersChangedStatusIsSwallowed) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED))
.WillOnce(Return(MEDIA_CODEC_TRY_AGAIN_LATER));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::kTryAgainLater);
}
TEST_F(CodecWrapperTest, MultipleFormatChangedStatusesIsAnError) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillRepeatedly(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::kError);
}
TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MEDIA_CODEC_OUTPUT_FORMAT_CHANGED))
.WillOnce(Return(MEDIA_CODEC_OK));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(
DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(MEDIA_CODEC_OK)));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
}
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenRendering) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run(false)).Times(1);
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenDestructing) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run(false)).Times(1);
}
TEST_F(CodecWrapperTest, OutputBufferReflectsDrainingOrDrainedStatus) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
auto eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos, EncryptionScheme());
ASSERT_TRUE(wrapper_->IsDraining());
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run(true)).Times(1);
}
TEST_F(CodecWrapperTest, CodecStartsInFlushedState) {
ASSERT_TRUE(wrapper_->IsFlushed());
ASSERT_FALSE(wrapper_->IsDraining());
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, CodecIsNotInFlushedStateAfterAnInputIsQueued) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
ASSERT_FALSE(wrapper_->IsFlushed());
ASSERT_FALSE(wrapper_->IsDraining());
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, FlushTransitionsToFlushedState) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
wrapper_->Flush();
ASSERT_TRUE(wrapper_->IsFlushed());
}
TEST_F(CodecWrapperTest, EosTransitionsToDrainingState) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
auto eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos, EncryptionScheme());
ASSERT_TRUE(wrapper_->IsDraining());
}
TEST_F(CodecWrapperTest, DequeuingEosTransitionsToDrainedState) {
// Set EOS on next dequeue.
codec_->ProduceOneOutput(MockMediaCodecBridge::kEos);
DequeueCodecOutputBuffer();
ASSERT_FALSE(wrapper_->IsFlushed());
ASSERT_TRUE(wrapper_->IsDrained());
wrapper_->Flush();
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, RejectedInputBuffersAreReused) {
// If we get a MEDIA_CODEC_NO_KEY status, the next time we try to queue a
// buffer the previous input buffer should be reused.
EXPECT_CALL(*codec_, DequeueInputBuffer(_, _))
.WillOnce(DoAll(SetArgPointee<1>(666), Return(MEDIA_CODEC_OK)));
EXPECT_CALL(*codec_, QueueInputBuffer(666, _, _, _))
.WillOnce(Return(MEDIA_CODEC_NO_KEY))
.WillOnce(Return(MEDIA_CODEC_OK));
auto status =
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
ASSERT_EQ(status, CodecWrapper::QueueStatus::kNoKey);
wrapper_->QueueInputBuffer(*fake_decoder_buffer_, EncryptionScheme());
}
TEST_F(CodecWrapperTest, SurfaceBundleIsInitializedByConstructor) {
ASSERT_EQ(surface_bundle_.get(), wrapper_->SurfaceBundle());
}
TEST_F(CodecWrapperTest, SurfaceBundleIsUpdatedBySetSurface) {
auto new_bundle = base::MakeRefCounted<CodecSurfaceBundle>();
EXPECT_CALL(*codec_, SetSurface(_)).WillOnce(Return(true));
wrapper_->SetSurface(new_bundle);
ASSERT_EQ(new_bundle.get(), wrapper_->SurfaceBundle());
}
TEST_F(CodecWrapperTest, SurfaceBundleIsTaken) {
ASSERT_EQ(wrapper_->TakeCodecSurfacePair().second, surface_bundle_);
ASSERT_EQ(wrapper_->SurfaceBundle(), nullptr);
}
TEST_F(CodecWrapperTest, EOSWhileFlushedOrDrainedIsElided) {
// Nothing should call QueueEOS.
EXPECT_CALL(*codec_, QueueEOS(_)).Times(0);
// Codec starts in the flushed state.
auto eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos, EncryptionScheme());
std::unique_ptr<CodecOutputBuffer> codec_buffer;
bool is_eos = false;
wrapper_->DequeueOutputBuffer(nullptr, &is_eos, &codec_buffer);
ASSERT_TRUE(is_eos);
// Since we also just got the codec into the drained state, make sure that
// it is elided here too.
ASSERT_TRUE(wrapper_->IsDrained());
eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos, EncryptionScheme());
is_eos = false;
wrapper_->DequeueOutputBuffer(nullptr, &is_eos, &codec_buffer);
ASSERT_TRUE(is_eos);
}
TEST_F(CodecWrapperTest, CodecWrapperPostsReleaseToProvidedThread) {
// Releasing an output buffer without rendering on some other thread should
// post back to the main thread.
scoped_refptr<base::SequencedTaskRunner> task_runner =
other_thread_.task_runner();
// If the thread failed to start, pass.
if (!task_runner)
return;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
auto cb = base::BindOnce(
[](std::unique_ptr<CodecOutputBuffer> codec_buffer,
base::WaitableEvent* event) {
codec_buffer.reset();
event->Signal();
},
DequeueCodecOutputBuffer(), base::Unretained(&event));
task_runner->PostTask(FROM_HERE, std::move(cb));
// Wait until the CodecOutputBuffer is released. It should not release the
// underlying buffer, but should instead post a task to release it.
event.Wait();
// The underlying buffer should not be released until we RunUntilIdle.
EXPECT_CALL(*codec_, ReleaseOutputBuffer(_, false));
base::RunLoop().RunUntilIdle();
}
} // namespace media