Commit ab9d34c3 authored by Hirokazu Honda's avatar Hirokazu Honda Committed by Commit Bot

media/gpu/vaapi: Introduce BitrateControl to AcceleratedVideoEncoder

We want to be able to run a VA-API driver in Constant
Quantization Parameter mode for vp9 encoding.
VaapiVideoEncodeAccelerator needs to tell VP9Encoder about the
bitrate control.
This CL adds enum of BitrateControl in AcceleratedVideoEncoder
interface so that VaapiVEA can configure vp9 encoder with the
enum.

Bug: 1060775
Test: video.EncodeAccel.*
Change-Id: I16db7bafcee0642e71bc36ef3e2bce257e33c7b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2159669Reviewed-by: default avatarMiguel Casas <mcasas@chromium.org>
Commit-Queue: Hirokazu Honda <hiroh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769033}
parent af6cc997
......@@ -40,12 +40,23 @@ class AcceleratedVideoEncoder {
AcceleratedVideoEncoder() = default;
virtual ~AcceleratedVideoEncoder() = default;
enum class BitrateControl {
kConstantBitrate, // Constant Bitrate mode. This class relies on other
// parts (e.g. driver) to achieve the specified bitrate.
kConstantQuantizationParameter // Constant Quantization Parameter mode.
// This class needs to compute a proper
// quantization parameter and give other
// parts (e.g. the driver) the value.
};
struct Config {
// Maxium number of reference frames.
// For H.264 encoding, the value represents the maximum number of reference
// frames for both the reference picture list 0 (bottom 16 bits) and the
// reference picture list 1 (top 16 bits).
size_t max_num_ref_frames;
BitrateControl bitrate_control = BitrateControl::kConstantBitrate;
};
// An abstraction of an encode job for one frame. Parameters required for an
......
......@@ -168,6 +168,8 @@ struct VaapiVideoEncodeAccelerator::BitstreamBufferRef {
VideoEncodeAccelerator::SupportedProfiles
VaapiVideoEncodeAccelerator::GetSupportedProfiles() {
if (IsConfiguredForTesting())
return supported_profiles_for_testing_;
return VaapiWrapper::GetSupportedEncodeProfiles();
}
......@@ -341,13 +343,17 @@ bool VaapiVideoEncodeAccelerator::Initialize(const Config& config,
return false;
}
vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec(
VaapiWrapper::kEncode, config.output_profile,
base::Bind(&ReportToUMA, VAAPI_ERROR));
if (!vaapi_wrapper_) {
VLOGF(1) << "Failed initializing VAAPI for profile "
<< GetProfileName(config.output_profile);
return false;
DCHECK_EQ(IsConfiguredForTesting(), !!vaapi_wrapper_);
if (!IsConfiguredForTesting()) {
// TODO(crbug.com/10607750): Support kConstantQuantizationParameter on vp9.
vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec(
VaapiWrapper::kEncode, config.output_profile,
base::Bind(&ReportToUMA, VAAPI_ERROR));
if (!vaapi_wrapper_) {
VLOGF(1) << "Failed initializing VAAPI for profile "
<< GetProfileName(config.output_profile);
return false;
}
}
// Finish remaining initialization on the encoder thread.
......@@ -363,33 +369,43 @@ void VaapiVideoEncodeAccelerator::InitializeTask(const Config& config) {
VLOGF(2);
VideoCodec codec = VideoCodecProfileToVideoCodec(config.output_profile);
switch (codec) {
case kCodecH264:
encoder_ = std::make_unique<H264Encoder>(
std::make_unique<H264Accelerator>(this));
break;
case kCodecVP8:
encoder_ =
std::make_unique<VP8Encoder>(std::make_unique<VP8Accelerator>(this));
break;
case kCodecVP9:
encoder_ =
std::make_unique<VP9Encoder>(std::make_unique<VP9Accelerator>(this));
break;
default:
NOTREACHED() << "Unsupported codec type " << GetCodecName(codec);
return;
AcceleratedVideoEncoder::Config ave_config{};
DCHECK_EQ(IsConfiguredForTesting(), !!encoder_);
if (!IsConfiguredForTesting()) {
switch (codec) {
case kCodecH264:
encoder_ = std::make_unique<H264Encoder>(
std::make_unique<H264Accelerator>(this));
DCHECK_EQ(ave_config.bitrate_control,
AcceleratedVideoEncoder::BitrateControl::kConstantBitrate);
break;
case kCodecVP8:
encoder_ = std::make_unique<VP8Encoder>(
std::make_unique<VP8Accelerator>(this));
DCHECK_EQ(ave_config.bitrate_control,
AcceleratedVideoEncoder::BitrateControl::kConstantBitrate);
break;
case kCodecVP9:
encoder_ = std::make_unique<VP9Encoder>(
std::make_unique<VP9Accelerator>(this));
// TODO(crbug.com/10607750): Support kConstantQuantizationParameter.
DCHECK_EQ(ave_config.bitrate_control,
AcceleratedVideoEncoder::BitrateControl::kConstantBitrate);
break;
default:
NOTREACHED() << "Unsupported codec type " << GetCodecName(codec);
return;
}
}
AcceleratedVideoEncoder::Config ave_config;
if (!vaapi_wrapper_->GetVAEncMaxNumOfRefFrames(
config.output_profile, &ave_config.max_num_ref_frames))
config.output_profile, &ave_config.max_num_ref_frames)) {
NOTIFY_ERROR(kPlatformFailureError,
"Failed getting max number of reference frames"
"supported by the driver");
return;
}
DCHECK_GT(ave_config.max_num_ref_frames, 0u);
if (!encoder_->Initialize(config, ave_config)) {
NOTIFY_ERROR(kInvalidArgumentError, "Failed initializing encoder");
return;
......@@ -409,13 +425,16 @@ void VaapiVideoEncodeAccelerator::InitializeTask(const Config& config) {
expected_input_coded_size_.width() <= encoder_->GetCodedSize().width() &&
expected_input_coded_size_.height() <= encoder_->GetCodedSize().height());
// The aligned surface size must be the same as a size of a native graphic
// buffer.
aligned_va_surface_size_ =
GetInputFrameSize(config.input_format, config.input_visible_size);
if (aligned_va_surface_size_.IsEmpty()) {
NOTIFY_ERROR(kPlatformFailureError, "Failed to get frame size");
return;
DCHECK_EQ(IsConfiguredForTesting(), !aligned_va_surface_size_.IsEmpty());
if (!IsConfiguredForTesting()) {
// The aligned surface size must be the same as a size of a native graphic
// buffer.
aligned_va_surface_size_ =
GetInputFrameSize(config.input_format, config.input_visible_size);
if (aligned_va_surface_size_.IsEmpty()) {
NOTIFY_ERROR(kPlatformFailureError, "Failed to get frame size");
return;
}
}
va_surfaces_per_video_frame_ =
......
......@@ -35,7 +35,7 @@ class MEDIA_GPU_EXPORT VaapiVideoEncodeAccelerator
~VaapiVideoEncodeAccelerator() override;
// VideoEncodeAccelerator implementation.
VideoEncodeAccelerator::SupportedProfiles GetSupportedProfiles() override;
SupportedProfiles GetSupportedProfiles() override;
bool Initialize(const Config& config, Client* client) override;
void Encode(scoped_refptr<VideoFrame> frame, bool force_keyframe) override;
void UseOutputBitstreamBuffer(BitstreamBuffer buffer) override;
......@@ -49,6 +49,7 @@ class MEDIA_GPU_EXPORT VaapiVideoEncodeAccelerator
bool IsFlushSupported() override;
private:
friend class VaapiVideoEncodeAcceleratorTest;
class H264Accelerator;
class VP8Accelerator;
class VP9Accelerator;
......@@ -148,6 +149,10 @@ class MEDIA_GPU_EXPORT VaapiVideoEncodeAccelerator
// Submits a H264BitstreamBuffer |buffer| to the driver.
void SubmitH264BitstreamBuffer(scoped_refptr<H264BitstreamBuffer> buffer);
bool IsConfiguredForTesting() const {
return !supported_profiles_for_testing_.empty();
}
// The unchanged values are filled upon the construction. The varied values
// (e.g. ScalingSettings) are filled properly during encoding.
VideoEncoderInfo encoder_info_;
......@@ -240,6 +245,9 @@ class MEDIA_GPU_EXPORT VaapiVideoEncodeAccelerator
// The completion callback of the Flush() function.
FlushCallback flush_callback_;
// Supported profiles that are filled if and only if in a unit test.
SupportedProfiles supported_profiles_for_testing_;
// WeakPtr of this, bound to |child_task_runner_|.
base::WeakPtr<VaapiVideoEncodeAccelerator> child_weak_this_;
// WeakPtr of this, bound to |encoder_task_runner_|.
......
......@@ -4,22 +4,42 @@
#include "media/gpu/vaapi/vaapi_video_encode_accelerator.h"
#include <memory>
#include <vector>
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "media/video/video_encode_accelerator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::test::RunClosure;
using ::testing::_;
using ::testing::Return;
using ::testing::WithArgs;
namespace media {
namespace {
constexpr gfx::Size kDefaultEncodeSize(1280, 720);
constexpr uint32_t kDefaultBitrateBps = 4 * 1000 * 1000;
constexpr uint32_t kDefaultFramerate = 30;
const VideoEncodeAccelerator::Config kDefaultVEAConfig(PIXEL_FORMAT_I420,
kDefaultEncodeSize,
VP8PROFILE_ANY,
kDefaultBitrateBps,
kDefaultFramerate);
constexpr size_t kMaxNumOfRefFrames = 3u;
const VideoEncodeAccelerator::Config kDefaultVideoEncodeAcceleratorConfig(
PIXEL_FORMAT_I420,
kDefaultEncodeSize,
VP9PROFILE_PROFILE0,
kDefaultBitrateBps,
kDefaultFramerate);
MATCHER_P2(MatchesAcceleratedVideoEncoderConfig,
max_ref_frames,
bitrate_control,
"") {
return arg.max_num_ref_frames == max_ref_frames &&
arg.bitrate_control == bitrate_control;
}
class MockVideoEncodeAcceleratorClient : public VideoEncodeAccelerator::Client {
public:
......@@ -34,28 +54,122 @@ class MockVideoEncodeAcceleratorClient : public VideoEncodeAccelerator::Client {
MOCK_METHOD1(NotifyEncoderInfoChange, void(const VideoEncoderInfo& info));
};
struct VaapiVEAInitializeTestParam {
uint8_t num_of_temporal_layers = 0;
uint8_t num_of_spatial_layers = 0;
bool expected_result;
class MockVaapiWrapper : public VaapiWrapper {
public:
MockVaapiWrapper(CodecMode mode) : VaapiWrapper(mode) {}
MOCK_METHOD2(GetVAEncMaxNumOfRefFrames, bool(VideoCodecProfile, size_t*));
MOCK_METHOD5(CreateContextAndSurfaces,
bool(unsigned int,
const gfx::Size&,
SurfaceUsageHint,
size_t,
std::vector<VASurfaceID>*));
MOCK_METHOD0(DestroyContext, void());
MOCK_METHOD1(DestroySurfaces, void(std::vector<VASurfaceID> va_surface_ids));
private:
~MockVaapiWrapper() override = default;
};
class VaapiVEAInitializeTest
: public ::testing::TestWithParam<VaapiVEAInitializeTestParam> {
class MockAcceleratedVideoEncoder : public AcceleratedVideoEncoder {
public:
MOCK_METHOD2(Initialize,
bool(const VideoEncodeAccelerator::Config&,
const AcceleratedVideoEncoder::Config&));
MOCK_CONST_METHOD0(GetCodedSize, gfx::Size());
MOCK_CONST_METHOD0(GetBitstreamBufferSize, size_t());
MOCK_CONST_METHOD0(GetMaxNumOfRefFrames, size_t());
bool UpdateRates(const VideoBitrateAllocation&, uint32_t) override {
return false;
}
ScalingSettings GetScalingSettings() const override {
return ScalingSettings();
}
bool PrepareEncodeJob(EncodeJob* encode_job) override { return false; }
};
} // namespace
struct VaapiVideoEncodeAcceleratorTestParam;
class VaapiVideoEncodeAcceleratorTest
: public ::testing::TestWithParam<VaapiVideoEncodeAcceleratorTestParam> {
protected:
VaapiVEAInitializeTest() = default;
~VaapiVEAInitializeTest() override = default;
VaapiVideoEncodeAcceleratorTest() = default;
~VaapiVideoEncodeAcceleratorTest() override = default;
void SetUp() override {
mock_vaapi_wrapper_ =
base::MakeRefCounted<MockVaapiWrapper>(VaapiWrapper::kEncode);
encoder_.reset(new VaapiVideoEncodeAccelerator);
auto* vaapi_encoder =
reinterpret_cast<VaapiVideoEncodeAccelerator*>(encoder_.get());
vaapi_encoder->vaapi_wrapper_ = mock_vaapi_wrapper_;
vaapi_encoder->encoder_ = std::make_unique<MockAcceleratedVideoEncoder>();
mock_encoder_ = reinterpret_cast<MockAcceleratedVideoEncoder*>(
vaapi_encoder->encoder_.get());
}
void SetDefaultMocksBehavior(const VideoEncodeAccelerator::Config& config) {
ASSERT_TRUE(mock_vaapi_wrapper_);
ASSERT_TRUE(mock_encoder_);
ON_CALL(*mock_vaapi_wrapper_, GetVAEncMaxNumOfRefFrames)
.WillByDefault(WithArgs<1>([](size_t* max_ref_frames) {
*max_ref_frames = kMaxNumOfRefFrames;
return true;
}));
ON_CALL(*mock_encoder_, GetBitstreamBufferSize)
.WillByDefault(Return(config.input_visible_size.GetArea()));
ON_CALL(*mock_encoder_, GetCodedSize())
.WillByDefault(Return(config.input_visible_size));
ON_CALL(*mock_encoder_, GetMaxNumOfRefFrames())
.WillByDefault(Return(kMaxNumOfRefFrames));
}
bool InitializeVideoEncodeAccelerator(
const VideoEncodeAccelerator::Config& config) {
VideoEncodeAccelerator::SupportedProfile profile(config.output_profile,
config.input_visible_size);
auto* vaapi_encoder =
reinterpret_cast<VaapiVideoEncodeAccelerator*>(encoder_.get());
vaapi_encoder->supported_profiles_for_testing_.push_back(profile);
vaapi_encoder->aligned_va_surface_size_ = config.input_visible_size;
if (config.input_visible_size.IsEmpty())
return false;
return encoder_->Initialize(config, &client_);
}
base::test::TaskEnvironment task_environment_;
MockVideoEncodeAcceleratorClient client_;
std::unique_ptr<VideoEncodeAccelerator> encoder_;
scoped_refptr<MockVaapiWrapper> mock_vaapi_wrapper_;
MockAcceleratedVideoEncoder* mock_encoder_ = nullptr;
};
TEST_P(VaapiVEAInitializeTest, SpatialLayerAndTemporalLayerEncoding) {
VideoEncodeAccelerator::Config config = kDefaultVEAConfig;
const uint8_t num_of_temporal_layers = GetParam().num_of_temporal_layers;
struct VaapiVideoEncodeAcceleratorTestParam {
uint8_t num_of_spatial_layers = 0;
uint8_t num_of_temporal_layers = 0;
} kTestCases[]{
{1u, 1u}, // Single spatial layer, single temporal layer.
{1u, 3u}, // Single spatial layer, multiple temporal layers.
{3u, 1u}, // Multiple spatial layers, single temporal layer.
{3u, 3u}, // Multiple spatial layers, multiple temporal layers.
};
TEST_P(VaapiVideoEncodeAcceleratorTest,
InitializeVP9WithMultipleSpatialLayers) {
const uint8_t num_of_spatial_layers = GetParam().num_of_spatial_layers;
if (num_of_spatial_layers <= 1)
GTEST_SKIP() << "Test only meant for multiple spatial layers configuration";
VideoEncodeAccelerator::Config config = kDefaultVideoEncodeAcceleratorConfig;
const uint8_t num_of_temporal_layers = GetParam().num_of_temporal_layers;
constexpr int kDenom[] = {4, 2, 1};
for (uint8_t i = 0; i < num_of_spatial_layers; ++i) {
VideoEncodeAccelerator::Config::SpatialLayer spatial_layer;
int denom = kDenom[i];
const int denom = kDenom[i];
spatial_layer.width = kDefaultEncodeSize.width() / denom;
spatial_layer.height = kDefaultEncodeSize.height() / denom;
spatial_layer.bitrate_bps = kDefaultBitrateBps / denom;
......@@ -65,18 +179,50 @@ TEST_P(VaapiVEAInitializeTest, SpatialLayerAndTemporalLayerEncoding) {
config.spatial_layers.push_back(spatial_layer);
}
VaapiVideoEncodeAccelerator vea;
MockVideoEncodeAcceleratorClient client;
EXPECT_EQ(vea.Initialize(config, &client), GetParam().expected_result);
EXPECT_FALSE(InitializeVideoEncodeAccelerator(config));
}
constexpr VaapiVEAInitializeTestParam kTestCases[] = {
{1u, 3u, false}, // Spatial Layer only.
{3u, 3u, false}, // Temporal + Spatial Layer.
};
TEST_P(VaapiVideoEncodeAcceleratorTest, InitializeVP9WithSingleSpatialLayer) {
if (GetParam().num_of_spatial_layers > 1u)
GTEST_SKIP() << "Test only meant for single spatial layer configuration";
VideoEncodeAccelerator::Config config = kDefaultVideoEncodeAcceleratorConfig;
VideoEncodeAccelerator::Config::SpatialLayer spatial_layer;
spatial_layer.width = kDefaultEncodeSize.width();
spatial_layer.height = kDefaultEncodeSize.height();
spatial_layer.bitrate_bps = kDefaultBitrateBps;
spatial_layer.framerate = kDefaultFramerate;
spatial_layer.max_qp = 30;
spatial_layer.num_of_temporal_layers = GetParam().num_of_temporal_layers;
config.spatial_layers.push_back(spatial_layer);
SetDefaultMocksBehavior(config);
INSTANTIATE_TEST_SUITE_P(SpatialLayerAndTemporalLayerEncoding,
VaapiVEAInitializeTest,
base::RunLoop run_loop;
base::Closure quit_closure = run_loop.QuitClosure();
::testing::InSequence s;
constexpr auto kBitrateControl =
AcceleratedVideoEncoder::BitrateControl::kConstantBitrate;
EXPECT_CALL(*mock_encoder_,
Initialize(_, MatchesAcceleratedVideoEncoderConfig(
kMaxNumOfRefFrames, kBitrateControl)))
.WillOnce(Return(true));
EXPECT_CALL(*mock_vaapi_wrapper_,
CreateContextAndSurfaces(
_, kDefaultEncodeSize,
VaapiWrapper::SurfaceUsageHint::kVideoEncoder, _, _))
.WillOnce(WithArgs<3, 4>(
[](size_t num_surfaces, std::vector<VASurfaceID>* va_surface_ids) {
va_surface_ids->resize(num_surfaces);
return true;
}));
EXPECT_CALL(client_, RequireBitstreamBuffers(_, kDefaultEncodeSize, _))
.WillOnce(RunClosure(quit_closure));
ASSERT_TRUE(InitializeVideoEncodeAccelerator(config));
run_loop.Run();
}
INSTANTIATE_TEST_SUITE_P(Initialize,
VaapiVideoEncodeAcceleratorTest,
::testing::ValuesIn(kTestCases));
} // namespace
} // namespace media
......@@ -374,8 +374,8 @@ class MEDIA_GPU_EXPORT VaapiWrapper
// For H.264 encoding, the value represents the maximum number of reference
// frames for both the reference picture list 0 (bottom 16 bits) and the
// reference picture list 1 (top 16 bits).
bool GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile,
size_t* max_ref_frames);
virtual bool GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile,
size_t* max_ref_frames);
// Blits a VASurface |va_surface_src| into another VASurface
// |va_surface_dest| applying pixel format conversion, cropping and scaling
......@@ -390,7 +390,7 @@ class MEDIA_GPU_EXPORT VaapiWrapper
static void PreSandboxInitialization();
// vaDestroySurfaces() a vector or a single VASurfaceID.
void DestroySurfaces(std::vector<VASurfaceID> va_surfaces);
virtual void DestroySurfaces(std::vector<VASurfaceID> va_surfaces);
virtual void DestroySurface(VASurfaceID va_surface_id);
protected:
......
......@@ -74,10 +74,13 @@ bool VP9Encoder::Initialize(const VideoEncodeAccelerator::Config& config,
return false;
}
// TODO(crbug.com/10607750): Support kConstantQuantizationParameter.
DCHECK_NE(ave_config.bitrate_control,
BitrateControl::kConstantQuantizationParameter);
accelerator_->set_bitrate_control(ave_config.bitrate_control);
visible_size_ = config.input_visible_size;
coded_size_ = gfx::Size(base::bits::Align(visible_size_.width(), 16),
base::bits::Align(visible_size_.height(), 16));
Reset();
VideoBitrateAllocation initial_bitrate_allocation;
......
......@@ -71,6 +71,11 @@ class VP9Encoder : public AcceleratedVideoEncoder {
const Vp9ReferenceFrameVector& ref_frames,
const std::array<bool, kVp9NumRefsPerFrame>& ref_frames_used) = 0;
void set_bitrate_control(BitrateControl bc) { bitrate_control_ = bc; }
protected:
BitrateControl bitrate_control_ = BitrateControl::kConstantBitrate;
DISALLOW_COPY_AND_ASSIGN(Accelerator);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment