blob: 5fd141d496896bbbc6bcad7d25de99802f6d0001 [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 <stddef.h>
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "gpu/config/gpu_info.h"
#include "media/mojo/clients/mojo_video_encode_accelerator.h"
#include "media/mojo/mojom/video_encode_accelerator.mojom.h"
#include "media/video/video_encode_accelerator.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
namespace media {
static const gfx::Size kInputVisibleSize(64, 48);
// Mock implementation of the Mojo "service" side of the VEA dialogue. Upon an
// Initialize() call, checks |initialization_success_| and responds to |client|
// with a RequireBitstreamBuffers() if so configured; upon Encode(), responds
// with a BitstreamBufferReady() with the bitstream buffer id previously
// configured by a call to UseOutputBitstreamBuffer(). This mock class only
// allows for one bitstream buffer in flight.
class MockMojoVideoEncodeAccelerator : public mojom::VideoEncodeAccelerator {
public:
MockMojoVideoEncodeAccelerator() = default;
// mojom::VideoEncodeAccelerator impl.
void Initialize(
const media::VideoEncodeAccelerator::Config& config,
mojo::PendingRemote<mojom::VideoEncodeAcceleratorClient> client,
InitializeCallback success_callback) override {
if (initialization_success_) {
ASSERT_TRUE(client);
client_.Bind(std::move(client));
const size_t allocation_size = VideoFrame::AllocationSize(
config.input_format, config.input_visible_size);
client_->RequireBitstreamBuffers(1, config.input_visible_size,
allocation_size);
DoInitialize(config.input_format, config.input_visible_size,
config.output_profile, config.bitrate, config.content_type,
&client_);
}
std::move(success_callback).Run(initialization_success_);
}
MOCK_METHOD6(DoInitialize,
void(media::VideoPixelFormat,
const gfx::Size&,
media::VideoCodecProfile,
media::Bitrate,
media::VideoEncodeAccelerator::Config::ContentType,
mojo::Remote<mojom::VideoEncodeAcceleratorClient>*));
void Encode(const scoped_refptr<VideoFrame>& frame,
bool keyframe,
EncodeCallback callback) override {
EXPECT_NE(-1, configured_bitstream_buffer_id_);
EXPECT_TRUE(client_);
client_->BitstreamBufferReady(
configured_bitstream_buffer_id_,
BitstreamBufferMetadata(0, keyframe, frame->timestamp()));
configured_bitstream_buffer_id_ = -1;
DoEncode(frame, keyframe);
std::move(callback).Run();
}
MOCK_METHOD2(DoEncode, void(const scoped_refptr<VideoFrame>&, bool));
void UseOutputBitstreamBuffer(
int32_t bitstream_buffer_id,
mojo::ScopedSharedBufferHandle buffer) override {
EXPECT_EQ(-1, configured_bitstream_buffer_id_);
configured_bitstream_buffer_id_ = bitstream_buffer_id;
DoUseOutputBitstreamBuffer(bitstream_buffer_id, &buffer);
}
MOCK_METHOD2(DoUseOutputBitstreamBuffer,
void(int32_t, mojo::ScopedSharedBufferHandle*));
MOCK_METHOD2(RequestEncodingParametersChangeWithLayers,
void(const media::VideoBitrateAllocation&, uint32_t));
MOCK_METHOD2(RequestEncodingParametersChangeWithBitrate,
void(const media::Bitrate&, uint32_t));
void IsFlushSupported(IsFlushSupportedCallback callback) override {
DoIsFlushSupported();
std::move(callback).Run(true);
}
MOCK_METHOD0(DoIsFlushSupported, void());
void Flush(FlushCallback callback) override {
FlushCallback mock_callback;
DoFlush(std::move(mock_callback));
// Actually, this callback should run on DoFlush, but in test, manally run
// it on Flush.
std::move(callback).Run(true);
}
MOCK_METHOD1(DoFlush, void(FlushCallback));
void set_initialization_success(bool success) {
initialization_success_ = success;
}
private:
mojo::Remote<mojom::VideoEncodeAcceleratorClient> client_;
int32_t configured_bitstream_buffer_id_ = -1;
bool initialization_success_ = true;
DISALLOW_COPY_AND_ASSIGN(MockMojoVideoEncodeAccelerator);
};
// Mock implementation of the client of MojoVideoEncodeAccelerator.
class MockVideoEncodeAcceleratorClient : public VideoEncodeAccelerator::Client {
public:
MockVideoEncodeAcceleratorClient() = default;
MOCK_METHOD3(RequireBitstreamBuffers,
void(unsigned int, const gfx::Size&, size_t));
MOCK_METHOD2(BitstreamBufferReady,
void(int32_t, const media::BitstreamBufferMetadata&));
MOCK_METHOD1(NotifyError, void(VideoEncodeAccelerator::Error));
MOCK_METHOD1(NotifyEncoderInfoChange, void(const media::VideoEncoderInfo&));
private:
DISALLOW_COPY_AND_ASSIGN(MockVideoEncodeAcceleratorClient);
};
// Test wrapper for a MojoVideoEncodeAccelerator, which translates between a
// pipe to a remote mojom::MojoVideoEncodeAccelerator, and a local
// media::VideoEncodeAccelerator::Client.
class MojoVideoEncodeAcceleratorTest : public ::testing::Test {
public:
MojoVideoEncodeAcceleratorTest() = default;
void SetUp() override {
mojo::PendingRemote<mojom::VideoEncodeAccelerator> mojo_vea;
mojo_vea_receiver_ = mojo::MakeSelfOwnedReceiver(
std::make_unique<MockMojoVideoEncodeAccelerator>(),
mojo_vea.InitWithNewPipeAndPassReceiver());
mojo_vea_ =
base::WrapUnique<VideoEncodeAccelerator>(new MojoVideoEncodeAccelerator(
std::move(mojo_vea),
media::VideoEncodeAccelerator::SupportedProfiles()));
}
void TearDown() override {
// The destruction of a mojo::SelfOwnedReceiver closes the bound message
// pipe but does not destroy the implementation object(s): this needs to
// happen manually by Close()ing it.
mojo_vea_receiver_->Close();
}
MockMojoVideoEncodeAccelerator* mock_mojo_vea() {
return static_cast<media::MockMojoVideoEncodeAccelerator*>(
mojo_vea_receiver_->impl());
}
VideoEncodeAccelerator* mojo_vea() { return mojo_vea_.get(); }
// This method calls Initialize() with semantically correct parameters and
// verifies that the appropriate message goes through the mojo pipe and is
// responded by a RequireBitstreamBuffers() on |mock_vea_client|.
void Initialize(MockVideoEncodeAcceleratorClient* mock_vea_client) {
constexpr VideoCodecProfile kOutputProfile = VIDEO_CODEC_PROFILE_UNKNOWN;
constexpr Bitrate kInitialBitrate = Bitrate::ConstantBitrate(100000u);
constexpr VideoEncodeAccelerator::Config::ContentType kContentType =
VideoEncodeAccelerator::Config::ContentType::kDisplay;
EXPECT_CALL(*mock_mojo_vea(),
DoInitialize(PIXEL_FORMAT_I420, kInputVisibleSize,
kOutputProfile, kInitialBitrate, kContentType, _));
EXPECT_CALL(
*mock_vea_client,
RequireBitstreamBuffers(
_, kInputVisibleSize,
VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize)));
const VideoEncodeAccelerator::Config config(
PIXEL_FORMAT_I420, kInputVisibleSize, kOutputProfile, kInitialBitrate,
absl::nullopt, absl::nullopt, absl::nullopt, false, absl::nullopt,
kContentType);
EXPECT_TRUE(mojo_vea()->Initialize(config, mock_vea_client));
base::RunLoop().RunUntilIdle();
}
private:
base::test::SingleThreadTaskEnvironment task_environment_;
// This member holds on to the mock implementation of the "service" side.
mojo::SelfOwnedReceiverRef<mojom::VideoEncodeAccelerator> mojo_vea_receiver_;
// The class under test, as a generic media::VideoEncodeAccelerator.
std::unique_ptr<VideoEncodeAccelerator> mojo_vea_;
DISALLOW_COPY_AND_ASSIGN(MojoVideoEncodeAcceleratorTest);
};
TEST_F(MojoVideoEncodeAcceleratorTest, CreateAndDestroy) {}
// This test verifies the Initialize() communication prologue in isolation.
TEST_F(MojoVideoEncodeAcceleratorTest, InitializeAndRequireBistreamBuffers) {
std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client =
std::make_unique<MockVideoEncodeAcceleratorClient>();
Initialize(mock_vea_client.get());
}
// This test verifies the Initialize() communication prologue followed by a
// sharing of a single bitstream buffer and the Encode() of one frame.
TEST_F(MojoVideoEncodeAcceleratorTest, EncodeOneFrame) {
std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client =
std::make_unique<MockVideoEncodeAcceleratorClient>();
Initialize(mock_vea_client.get());
const int32_t kBitstreamBufferId = 17;
{
const int32_t kShMemSize = 10;
auto shmem = base::UnsafeSharedMemoryRegion::Create(kShMemSize);
EXPECT_CALL(*mock_mojo_vea(),
DoUseOutputBitstreamBuffer(kBitstreamBufferId, _));
mojo_vea()->UseOutputBitstreamBuffer(BitstreamBuffer(
kBitstreamBufferId,
base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(shmem)),
kShMemSize, 0 /* offset */, base::TimeDelta()));
base::RunLoop().RunUntilIdle();
}
{
base::UnsafeSharedMemoryRegion shmem =
base::UnsafeSharedMemoryRegion::Create(
VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize) *
2);
ASSERT_TRUE(shmem.IsValid());
base::WritableSharedMemoryMapping mapping = shmem.Map();
ASSERT_TRUE(mapping.IsValid());
const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize),
kInputVisibleSize, mapping.GetMemoryAsSpan<uint8_t>().data(),
mapping.size(), base::TimeDelta());
video_frame->BackWithSharedMemory(&shmem);
const bool is_keyframe = true;
// The remote end of the mojo Pipe doesn't receive |video_frame| itself.
EXPECT_CALL(*mock_mojo_vea(), DoEncode(_, is_keyframe));
EXPECT_CALL(*mock_vea_client, BitstreamBufferReady(kBitstreamBufferId, _))
.WillOnce(Invoke([is_keyframe, &video_frame](
int32_t, const BitstreamBufferMetadata& metadata) {
EXPECT_EQ(is_keyframe, metadata.key_frame);
EXPECT_EQ(metadata.timestamp, video_frame->timestamp());
}));
mojo_vea()->Encode(video_frame, is_keyframe);
base::RunLoop().RunUntilIdle();
}
}
// Tests that a RequestEncodingParametersChange() ripples through correctly.
TEST_F(MojoVideoEncodeAcceleratorTest, EncodingParametersChange) {
const uint32_t kNewFramerate = 321321u;
const uint32_t kNewBitrate = 123123u;
Bitrate bitrate = Bitrate::ConstantBitrate(kNewBitrate);
// In a real world scenario, we should go through an Initialize() prologue,
// but we can skip that in unit testing.
EXPECT_CALL(*mock_mojo_vea(), RequestEncodingParametersChangeWithBitrate(
bitrate, kNewFramerate));
mojo_vea()->RequestEncodingParametersChange(bitrate, kNewFramerate);
base::RunLoop().RunUntilIdle();
}
// Tests that a RequestEncodingParametersChange() works with multi-dimensional
// bitrate allocatio.
TEST_F(MojoVideoEncodeAcceleratorTest,
EncodingParametersWithBitrateAllocation) {
const uint32_t kNewFramerate = 321321u;
const size_t kMaxNumBitrates = VideoBitrateAllocation::kMaxSpatialLayers *
VideoBitrateAllocation::kMaxTemporalLayers;
// Verify translation of VideoBitrateAllocation into vector of bitrates for
// everything from empty array up to max number of layers.
VideoBitrateAllocation bitrate_allocation;
for (size_t i = 0; i <= kMaxNumBitrates; ++i) {
if (i > 0) {
int layer_bitrate = i * 1000;
const size_t si = (i - 1) / VideoBitrateAllocation::kMaxTemporalLayers;
const size_t ti = (i - 1) % VideoBitrateAllocation::kMaxTemporalLayers;
bitrate_allocation.SetBitrate(si, ti, layer_bitrate);
}
EXPECT_CALL(*mock_mojo_vea(), RequestEncodingParametersChangeWithLayers(
bitrate_allocation, kNewFramerate));
mojo_vea()->RequestEncodingParametersChange(bitrate_allocation,
kNewFramerate);
base::RunLoop().RunUntilIdle();
}
}
// This test verifies the Initialize() communication prologue fails when the
// FakeVEA is configured to do so.
TEST_F(MojoVideoEncodeAcceleratorTest, InitializeFailure) {
std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client =
std::make_unique<MockVideoEncodeAcceleratorClient>();
constexpr Bitrate kInitialBitrate = Bitrate::ConstantBitrate(100000u);
mock_mojo_vea()->set_initialization_success(false);
const VideoEncodeAccelerator::Config config(
PIXEL_FORMAT_I420, kInputVisibleSize, VIDEO_CODEC_PROFILE_UNKNOWN,
kInitialBitrate);
EXPECT_FALSE(mojo_vea()->Initialize(config, mock_vea_client.get()));
base::RunLoop().RunUntilIdle();
}
// This test verifies the IsFlushSupported() and Flush() communication.
TEST_F(MojoVideoEncodeAcceleratorTest, IsFlushSupportedAndFlush) {
std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client =
std::make_unique<MockVideoEncodeAcceleratorClient>();
Initialize(mock_vea_client.get());
EXPECT_CALL(*mock_mojo_vea(), DoIsFlushSupported());
bool ret = mojo_vea()->IsFlushSupported();
base::RunLoop().RunUntilIdle();
if (ret) {
EXPECT_CALL(*mock_mojo_vea(), DoFlush(_));
auto flush_callback =
base::BindOnce([](bool status) { EXPECT_EQ(status, true); });
mojo_vea()->Flush(std::move(flush_callback));
base::RunLoop().RunUntilIdle();
}
}
} // namespace media