// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.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::_;
using testing::DoAll;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
using testing::SetArgPointee;
namespace media {
constexpr gfx::Size kInitialCodedSize(640, 480);
constexpr gfx::Size kCodedSizeAlignment(16, 16);
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::SequencedTaskRunner::GetCurrentDefault(), kInitialCodedSize,
gfx::ColorSpace::CreateREC709(), kCodedSizeAlignment);
ON_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillByDefault(Return(OkStatus()));
ON_CALL(*codec_, DequeueInputBuffer(_, _))
.WillByDefault(DoAll(SetArgPointee<1>(12), Return(OkStatus())));
ON_CALL(*codec_, QueueInputBuffer(_, _, _, _))
.WillByDefault(Return(OkStatus()));
uint8_t data = 0;
fake_decoder_buffer_ = DecoderBuffer::CopyFrom(base::span_from_ref(data));
// 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::TaskEnvironment task_environment_;
raw_ptr<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(MediaCodecResult::Codes::kError));
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(OkStatus())));
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(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::Codes::kTryAgainLater);
}
TEST_F(CodecWrapperTest, BuffersChangedStatusIsSwallowed) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputBuffersChanged))
.WillOnce(Return(MediaCodecResult::Codes::kTryAgainLater));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::Codes::kTryAgainLater);
}
TEST_F(CodecWrapperTest, MultipleFormatChangedStatusesIsAnError) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillRepeatedly(Return(MediaCodecResult::Codes::kOutputFormatChanged));
std::unique_ptr<CodecOutputBuffer> codec_buffer;
auto status = wrapper_->DequeueOutputBuffer(nullptr, nullptr, &codec_buffer);
ASSERT_EQ(status, CodecWrapper::DequeueStatus::Codes::kError);
}
TEST_F(CodecWrapperTest, CodecOutputBuffersHaveTheCorrectSize) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
}
TEST_F(CodecWrapperTest, CodecOutputBuffersGuessCodedSize) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
EXPECT_TRUE(codec_buffer->CanGuessCodedSize());
EXPECT_EQ(codec_buffer->GuessCodedSize(), gfx::Size(48, 48));
}
TEST_F(CodecWrapperTest, CodecOutputBuffersGuessCodedSizeNoAlignment) {
auto surface_pair = wrapper_->TakeCodecSurfacePair();
wrapper_ = std::make_unique<CodecWrapper>(
std::move(surface_pair), output_buffer_release_cb_.Get(),
// Unrendered output buffers are released on our thread.
base::SequencedTaskRunner::GetCurrentDefault(), kInitialCodedSize,
gfx::ColorSpace::CreateREC709(), std::nullopt);
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
EXPECT_FALSE(codec_buffer->CanGuessCodedSize());
}
TEST_F(CodecWrapperTest, CodecOutputBuffersGuessCodedSizeWeirdAlignment) {
auto surface_pair = wrapper_->TakeCodecSurfacePair();
wrapper_ = std::make_unique<CodecWrapper>(
std::move(surface_pair), output_buffer_release_cb_.Get(),
// Unrendered output buffers are released on our thread.
base::SequencedTaskRunner::GetCurrentDefault(), kInitialCodedSize,
gfx::ColorSpace::CreateREC709(), gfx::Size(128, 1));
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(DoAll(SetArgPointee<0>(gfx::Size(42, 42)), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), gfx::Size(42, 42));
EXPECT_TRUE(codec_buffer->CanGuessCodedSize());
EXPECT_EQ(codec_buffer->GuessCodedSize(), gfx::Size(128, 42));
}
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenRendering) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run(true)).Times(1);
codec_buffer->ReleaseToSurface();
}
TEST_F(CodecWrapperTest, OutputBufferReleaseCbIsCalledWhenDestructing) {
auto codec_buffer = DequeueCodecOutputBuffer();
EXPECT_CALL(output_buffer_release_cb_, Run(true)).Times(1);
}
TEST_F(CodecWrapperTest, OutputBufferReflectsDrainingOrDrainedStatus) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_);
auto eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos);
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_);
ASSERT_FALSE(wrapper_->IsFlushed());
ASSERT_FALSE(wrapper_->IsDraining());
ASSERT_FALSE(wrapper_->IsDrained());
}
TEST_F(CodecWrapperTest, FlushTransitionsToFlushedState) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_);
wrapper_->Flush();
ASSERT_TRUE(wrapper_->IsFlushed());
}
TEST_F(CodecWrapperTest, EosTransitionsToDrainingState) {
wrapper_->QueueInputBuffer(*fake_decoder_buffer_);
auto eos = DecoderBuffer::CreateEOSBuffer();
wrapper_->QueueInputBuffer(*eos);
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 MediaCodecResult::Codes::kNoKey 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(OkStatus())));
EXPECT_CALL(*codec_, QueueInputBuffer(666, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kNoKey))
.WillOnce(Return(OkStatus()));
auto status = wrapper_->QueueInputBuffer(*fake_decoder_buffer_);
ASSERT_EQ(status, CodecWrapper::QueueStatus::Codes::kNoKey);
wrapper_->QueueInputBuffer(*fake_decoder_buffer_);
}
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);
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);
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();
}
TEST_F(CodecWrapperTest, RenderCallbackCalledIfRendered) {
auto codec_buffer = DequeueCodecOutputBuffer();
bool flag = false;
codec_buffer->set_render_cb(base::BindOnce([](bool* flag) { *flag = true; },
base::Unretained(&flag)));
codec_buffer->ReleaseToSurface();
EXPECT_TRUE(flag);
}
TEST_F(CodecWrapperTest, RenderCallbackIsNotCalledIfNotRendered) {
auto codec_buffer = DequeueCodecOutputBuffer();
bool flag = false;
codec_buffer->set_render_cb(base::BindOnce([](bool* flag) { *flag = true; },
base::Unretained(&flag)));
codec_buffer.reset();
EXPECT_FALSE(flag);
}
TEST_F(CodecWrapperTest, CodecWrapperGetsColorSpaceFromCodec) {
// CodecWrapper should provide the color space that's reported by the bridge.
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
gfx::ColorSpace color_space{gfx::ColorSpace::CreateHDR10()};
EXPECT_CALL(*codec_, GetOutputColorSpace(_))
.WillOnce(DoAll(SetArgPointee<0>(color_space), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->color_space(), color_space);
}
TEST_F(CodecWrapperTest, CodecWrapperDefaultsToSRGB) {
auto surface_pair = wrapper_->TakeCodecSurfacePair();
wrapper_ = std::make_unique<CodecWrapper>(
std::move(surface_pair), output_buffer_release_cb_.Get(),
// Unrendered output buffers are released on our thread.
base::SequencedTaskRunner::GetCurrentDefault(), kInitialCodedSize,
gfx::ColorSpace(), std::nullopt);
// If MediaCodec doesn't provide a color space and we don't have a valid
// config color space, then CodecWrapper should default to sRGB for sanity.
// CodecWrapper should provide the color space that's reported by the bridge.
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputColorSpace(_))
.WillOnce(Return(MediaCodecResult::Codes::kError));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->color_space(), gfx::ColorSpace::CreateSRGB());
}
TEST_F(CodecWrapperTest, CodecWrapperUseConfigColorSpace) {
auto surface_pair = wrapper_->TakeCodecSurfacePair();
wrapper_ = std::make_unique<CodecWrapper>(
std::move(surface_pair), output_buffer_release_cb_.Get(),
// Unrendered output buffers are released on our thread.
base::SequencedTaskRunner::GetCurrentDefault(), kInitialCodedSize,
gfx::ColorSpace::CreateJpeg(), std::nullopt);
// If MediaCodec doesn't provide a color space and we have a valid config
// color space, then CodecWrapper should use it.
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
EXPECT_CALL(*codec_, GetOutputColorSpace(_))
.WillOnce(Return(MediaCodecResult::Codes::kError));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->color_space(), gfx::ColorSpace::CreateJpeg());
}
TEST_F(CodecWrapperTest, CodecOutputsIgnoreZeroSize) {
EXPECT_CALL(*codec_, DequeueOutputBuffer(_, _, _, _, _, _, _))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()))
.WillOnce(Return(MediaCodecResult::Codes::kOutputFormatChanged))
.WillOnce(Return(OkStatus()));
constexpr gfx::Size kNewSize(1280, 720);
EXPECT_CALL(*codec_, GetOutputSize(_))
.WillOnce(DoAll(SetArgPointee<0>(gfx::Size()), Return(OkStatus())))
.WillOnce(DoAll(SetArgPointee<0>(kNewSize), Return(OkStatus())));
auto codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), kInitialCodedSize);
codec_buffer = DequeueCodecOutputBuffer();
ASSERT_EQ(codec_buffer->size(), kNewSize);
}
} // namespace media