blob: 689506089bf76e99a5aa5b9628d1c0a0dd804125 [file] [log] [blame]
// Copyright 2020 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/vaapi/vp9_encoder.h"
#include <memory>
#include <numeric>
#include <tuple>
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "media/filters/vp9_parser.h"
#include "media/gpu/vaapi/vp9_rate_control.h"
#include "media/gpu/vaapi/vp9_temporal_layers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libvpx/source/libvpx/vp9/common/vp9_blockd.h"
#include "third_party/libvpx/source/libvpx/vp9/ratectrl_rtc.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
namespace media {
namespace {
constexpr size_t kDefaultMaxNumRefFrames = kVp9NumRefsPerFrame;
AcceleratedVideoEncoder::Config kDefaultAcceleratedVideoEncoderConfig{
kDefaultMaxNumRefFrames,
AcceleratedVideoEncoder::BitrateControl::kConstantBitrate};
VideoEncodeAccelerator::Config kDefaultVideoEncodeAcceleratorConfig(
PIXEL_FORMAT_I420,
gfx::Size(1280, 720),
VP9PROFILE_PROFILE0,
14000000 /* = maximum bitrate in bits per second for level 3.1 */,
VideoEncodeAccelerator::kDefaultFramerate,
base::nullopt /* gop_length */,
base::nullopt /* h264 output level*/,
false /* is_constrained_h264 */,
VideoEncodeAccelerator::Config::StorageType::kShmem);
constexpr std::array<bool, kVp9NumRefsPerFrame> kRefFramesUsedForKeyFrame = {
false, false, false};
constexpr std::array<bool, kVp9NumRefsPerFrame> kRefFramesUsedForInterFrame = {
true, true, true};
void GetTemporalLayer(bool keyframe,
int index,
size_t num_temporal_layers,
std::array<bool, kVp9NumRefsPerFrame>* ref_frames_used,
base::Optional<uint8_t>* temporal_layer_id) {
switch (num_temporal_layers) {
case 1:
*ref_frames_used =
keyframe ? kRefFramesUsedForKeyFrame : kRefFramesUsedForInterFrame;
break;
case 2:
if (keyframe) {
*temporal_layer_id = 0;
*ref_frames_used = kRefFramesUsedForKeyFrame;
return;
}
{
// 2 temporal layers structure. See https://imgur.com/vBvHtdp.
constexpr std::tuple<uint8_t, std::array<bool, kVp9NumRefsPerFrame>>
kTwoTemporalLayersDescription[] = {
{0, {true, false, false}}, {1, {true, false, false}},
{0, {true, false, false}}, {1, {true, true, false}},
{0, {true, false, false}}, {1, {true, true, false}},
{0, {true, false, false}}, {1, {true, true, false}},
};
const auto& layer_info = kTwoTemporalLayersDescription
[index % base::size(kTwoTemporalLayersDescription)];
std::tie(*temporal_layer_id, *ref_frames_used) = layer_info;
}
break;
case 3:
if (keyframe) {
*temporal_layer_id = 0u;
*ref_frames_used = kRefFramesUsedForKeyFrame;
return;
}
{
// 3 temporal layers structure. See https://imgur.com/pURAGvp.
constexpr std::tuple<uint8_t, std::array<bool, kVp9NumRefsPerFrame>>
kThreeTemporalLayersDescription[] = {
{0, {true, false, false}}, {2, {true, false, false}},
{1, {true, false, false}}, {2, {true, true, false}},
{0, {true, false, false}}, {2, {true, true, false}},
{1, {true, true, false}}, {2, {true, true, false}},
};
const auto& layer_info = kThreeTemporalLayersDescription
[index % base::size(kThreeTemporalLayersDescription)];
std::tie(*temporal_layer_id, *ref_frames_used) = layer_info;
}
break;
}
}
VideoBitrateAllocation GetDefaultVideoBitrateAllocation(
size_t num_temporal_layers,
uint32_t bitrate) {
VideoBitrateAllocation bitrate_allocation;
if (num_temporal_layers == 1u) {
bitrate_allocation.SetBitrate(0, 0, bitrate);
return bitrate_allocation;
}
LOG_ASSERT(num_temporal_layers <=
VP9TemporalLayers::kMaxSupportedTemporalLayers);
constexpr double kTemporalLayersBitrateScaleFactors
[][VP9TemporalLayers::kMaxSupportedTemporalLayers] = {
{0.50, 0.50, 0.00}, // For two temporal layers.
{0.25, 0.25, 0.50}, // For three temporal layers.
};
for (size_t i = 0; i < num_temporal_layers; i++) {
const double factor =
kTemporalLayersBitrateScaleFactors[num_temporal_layers - 2][i];
bitrate_allocation.SetBitrate(0 /* spatial_index */, i,
base::checked_cast<int>(bitrate * factor));
}
return bitrate_allocation;
}
MATCHER_P4(MatchRtcConfigWithRates,
size,
bitrate_allocation,
framerate,
num_temporal_layers,
"") {
if (arg.target_bandwidth != bitrate_allocation.GetSumBps() / 1000)
return false;
if (arg.framerate != static_cast<double>(framerate))
return false;
int bitrate_sum = 0;
for (size_t i = 0; i < num_temporal_layers; i++) {
bitrate_sum += bitrate_allocation.GetBitrateBps(0, i);
if (arg.layer_target_bitrate[i] != bitrate_sum / 1000)
return false;
if (arg.ts_rate_decimator[i] != (1 << (num_temporal_layers - i - 1)))
return false;
}
return arg.width == size.width() && arg.height == size.height() &&
base::checked_cast<size_t>(arg.ts_number_layers) ==
num_temporal_layers &&
arg.ss_number_layers == 1 && arg.scaling_factor_num[0] == 1 &&
arg.scaling_factor_den[0] == 1;
}
MATCHER_P2(MatchFrameParam, frame_type, temporal_layer_id, "") {
return arg.frame_type == frame_type &&
(!temporal_layer_id || arg.temporal_layer_id == *temporal_layer_id);
}
class MockVP9Accelerator : public VP9Encoder::Accelerator {
public:
MockVP9Accelerator() = default;
~MockVP9Accelerator() override = default;
MOCK_METHOD1(GetPicture,
scoped_refptr<VP9Picture>(AcceleratedVideoEncoder::EncodeJob*));
MOCK_METHOD5(SubmitFrameParameters,
bool(AcceleratedVideoEncoder::EncodeJob*,
const VP9Encoder::EncodeParams&,
scoped_refptr<VP9Picture>,
const Vp9ReferenceFrameVector&,
const std::array<bool, kVp9NumRefsPerFrame>&));
};
class MockVP9RateControl : public VP9RateControl {
public:
MockVP9RateControl() = default;
~MockVP9RateControl() override = default;
MOCK_METHOD1(UpdateRateControl, void(const libvpx::VP9RateControlRtcConfig&));
MOCK_CONST_METHOD0(GetQP, int());
MOCK_CONST_METHOD0(GetLoopfilterLevel, int());
MOCK_METHOD1(ComputeQP, void(const libvpx::VP9FrameParamsQpRTC&));
MOCK_METHOD1(PostEncodeUpdate, void(uint64_t));
};
} // namespace
struct VP9EncoderTestParam;
class VP9EncoderTest : public ::testing::TestWithParam<VP9EncoderTestParam> {
public:
using BitrateControl = AcceleratedVideoEncoder::BitrateControl;
VP9EncoderTest() = default;
~VP9EncoderTest() override = default;
void SetUp() override;
protected:
void InitializeVP9Encoder(BitrateControl bitrate_control,
size_t num_temporal_layers);
void EncodeSequence(bool is_keyframe);
void EncodeConstantQuantizationParameterSequence(
bool is_keyframe,
base::Optional<std::array<bool, kVp9NumRefsPerFrame>>
expected_ref_frames_used,
base::Optional<uint8_t> expected_temporal_layer_id = base::nullopt);
void UpdateRatesTest(BitrateControl bitrate_control,
size_t num_temporal_layers);
private:
std::unique_ptr<AcceleratedVideoEncoder::EncodeJob> CreateEncodeJob(
bool keyframe);
void UpdateRatesSequence(const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate,
BitrateControl bitrate_control,
size_t num_temporal_layers);
std::unique_ptr<VP9Encoder> encoder_;
MockVP9Accelerator* mock_accelerator_ = nullptr;
MockVP9RateControl* mock_rate_ctrl_ = nullptr;
};
void VP9EncoderTest::SetUp() {
auto mock_accelerator = std::make_unique<MockVP9Accelerator>();
mock_accelerator_ = mock_accelerator.get();
encoder_ = std::make_unique<VP9Encoder>(std::move(mock_accelerator));
}
std::unique_ptr<AcceleratedVideoEncoder::EncodeJob>
VP9EncoderTest::CreateEncodeJob(bool keyframe) {
auto input_frame = VideoFrame::CreateFrame(
kDefaultVideoEncodeAcceleratorConfig.input_format,
kDefaultVideoEncodeAcceleratorConfig.input_visible_size,
gfx::Rect(kDefaultVideoEncodeAcceleratorConfig.input_visible_size),
kDefaultVideoEncodeAcceleratorConfig.input_visible_size,
base::TimeDelta());
LOG_ASSERT(input_frame) << " Failed to create VideoFrame";
return std::make_unique<AcceleratedVideoEncoder::EncodeJob>(
input_frame, keyframe, base::DoNothing());
}
void VP9EncoderTest::InitializeVP9Encoder(BitrateControl bitrate_control,
size_t num_temporal_layers) {
auto config = kDefaultVideoEncodeAcceleratorConfig;
auto ave_config = kDefaultAcceleratedVideoEncoderConfig;
ave_config.bitrate_control = bitrate_control;
if (bitrate_control == BitrateControl::kConstantQuantizationParameter) {
auto rate_ctrl = std::make_unique<MockVP9RateControl>();
mock_rate_ctrl_ = rate_ctrl.get();
encoder_->set_rate_ctrl_for_testing(std::move(rate_ctrl));
VideoBitrateAllocation initial_bitrate_allocation;
initial_bitrate_allocation.SetBitrate(
0, 0, kDefaultVideoEncodeAcceleratorConfig.initial_bitrate);
if (num_temporal_layers > 1u) {
VideoEncodeAccelerator::Config::SpatialLayer spatial_layer;
spatial_layer.width = config.input_visible_size.width();
spatial_layer.height = config.input_visible_size.height();
spatial_layer.bitrate_bps = config.initial_bitrate;
spatial_layer.framerate = *config.initial_framerate;
spatial_layer.max_qp = 30;
spatial_layer.num_of_temporal_layers = num_temporal_layers;
config.spatial_layers.push_back(spatial_layer);
}
EXPECT_CALL(
*mock_rate_ctrl_,
UpdateRateControl(MatchRtcConfigWithRates(
kDefaultVideoEncodeAcceleratorConfig.input_visible_size,
GetDefaultVideoBitrateAllocation(num_temporal_layers,
config.initial_bitrate),
VideoEncodeAccelerator::kDefaultFramerate, num_temporal_layers)))
.Times(1)
.WillOnce(Return());
} else {
// VP9Encoder doesn't support temporal layer encoding in
// BitrateControl::kConstantQuantizationParameter.
ASSERT_EQ(num_temporal_layers, 1u);
}
EXPECT_TRUE(encoder_->Initialize(config, ave_config));
EXPECT_EQ(num_temporal_layers > 1u, !!encoder_->temporal_layers_);
}
void VP9EncoderTest::EncodeSequence(bool is_keyframe) {
InSequence seq;
auto encode_job = CreateEncodeJob(is_keyframe);
scoped_refptr<VP9Picture> picture(new VP9Picture);
EXPECT_CALL(*mock_accelerator_, GetPicture(encode_job.get()))
.WillOnce(Invoke(
[picture](AcceleratedVideoEncoder::EncodeJob*) { return picture; }));
const auto& expected_ref_frames_used =
is_keyframe ? kRefFramesUsedForKeyFrame : kRefFramesUsedForInterFrame;
EXPECT_CALL(*mock_accelerator_,
SubmitFrameParameters(
encode_job.get(), _, _, _,
::testing::ElementsAreArray(expected_ref_frames_used)))
.WillOnce(Return(true));
EXPECT_TRUE(encoder_->PrepareEncodeJob(encode_job.get()));
// TODO(hiroh): Test for encoder_->reference_frames_.
}
void VP9EncoderTest::EncodeConstantQuantizationParameterSequence(
bool is_keyframe,
base::Optional<std::array<bool, kVp9NumRefsPerFrame>>
expected_ref_frames_used,
base::Optional<uint8_t> expected_temporal_layer_id) {
InSequence seq;
auto encode_job = CreateEncodeJob(is_keyframe);
scoped_refptr<VP9Picture> picture(new VP9Picture);
EXPECT_CALL(*mock_accelerator_, GetPicture(encode_job.get()))
.WillOnce(Invoke(
[picture](AcceleratedVideoEncoder::EncodeJob*) { return picture; }));
FRAME_TYPE libvpx_frame_type =
is_keyframe ? FRAME_TYPE::KEY_FRAME : FRAME_TYPE::INTER_FRAME;
EXPECT_CALL(
*mock_rate_ctrl_,
ComputeQP(MatchFrameParam(libvpx_frame_type, expected_temporal_layer_id)))
.WillOnce(Return());
constexpr int kDefaultQP = 34;
constexpr int kDefaultLoopFilterLevel = 8;
EXPECT_CALL(*mock_rate_ctrl_, GetQP()).WillOnce(Return(kDefaultQP));
EXPECT_CALL(*mock_rate_ctrl_, GetLoopfilterLevel())
.WillOnce(Return(kDefaultLoopFilterLevel));
if (expected_ref_frames_used) {
EXPECT_CALL(*mock_accelerator_,
SubmitFrameParameters(
encode_job.get(), _, _, _,
::testing::ElementsAreArray(*expected_ref_frames_used)))
.WillOnce(Return(true));
} else {
EXPECT_CALL(*mock_accelerator_,
SubmitFrameParameters(encode_job.get(), _, _, _, _))
.WillOnce(Return(true));
}
EXPECT_TRUE(encoder_->PrepareEncodeJob(encode_job.get()));
// TODO(hiroh): Test for encoder_->reference_frames_.
constexpr size_t kDefaultEncodedFrameSize = 123456;
// For BitrateControlUpdate sequence.
EXPECT_CALL(*mock_rate_ctrl_, PostEncodeUpdate(kDefaultEncodedFrameSize))
.WillOnce(Return());
encoder_->BitrateControlUpdate(kDefaultEncodedFrameSize);
}
void VP9EncoderTest::UpdateRatesSequence(
const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate,
BitrateControl bitrate_control,
size_t num_temporal_layers) {
ASSERT_TRUE(encoder_->current_params_.bitrate_allocation !=
bitrate_allocation ||
encoder_->current_params_.framerate != framerate);
if (bitrate_control == BitrateControl::kConstantQuantizationParameter) {
EXPECT_CALL(*mock_rate_ctrl_,
UpdateRateControl(MatchRtcConfigWithRates(
encoder_->visible_size_, bitrate_allocation, framerate,
num_temporal_layers)))
.Times(1)
.WillOnce(Return());
}
EXPECT_TRUE(encoder_->UpdateRates(bitrate_allocation, framerate));
EXPECT_EQ(encoder_->current_params_.bitrate_allocation, bitrate_allocation);
EXPECT_EQ(encoder_->current_params_.framerate, framerate);
}
void VP9EncoderTest::UpdateRatesTest(BitrateControl bitrate_control,
size_t num_temporal_layers) {
ASSERT_TRUE(num_temporal_layers <=
VP9TemporalLayers::kMaxSupportedTemporalLayers);
const auto update_rates_and_encode =
[this, bitrate_control, num_temporal_layers](
bool is_keyframe, const VideoBitrateAllocation& bitrate_allocation,
uint32_t framerate) {
UpdateRatesSequence(bitrate_allocation, framerate, bitrate_control,
num_temporal_layers);
if (bitrate_control == BitrateControl::kConstantQuantizationParameter) {
EncodeConstantQuantizationParameterSequence(is_keyframe, {},
base::nullopt);
} else {
EncodeSequence(is_keyframe);
}
};
const uint32_t kBitrate =
kDefaultVideoEncodeAcceleratorConfig.initial_bitrate;
const uint32_t kFramerate =
*kDefaultVideoEncodeAcceleratorConfig.initial_framerate;
// Call UpdateRates before Encode.
update_rates_and_encode(
true, GetDefaultVideoBitrateAllocation(num_temporal_layers, kBitrate / 2),
kFramerate);
// Bitrate change only.
update_rates_and_encode(
false, GetDefaultVideoBitrateAllocation(num_temporal_layers, kBitrate),
kFramerate);
// Framerate change only.
update_rates_and_encode(
false, GetDefaultVideoBitrateAllocation(num_temporal_layers, kBitrate),
kFramerate + 2);
// Bitrate + Frame changes.
update_rates_and_encode(
false,
GetDefaultVideoBitrateAllocation(num_temporal_layers, kBitrate * 3 / 4),
kFramerate - 5);
}
struct VP9EncoderTestParam {
VP9EncoderTest::BitrateControl bitrate_control;
size_t num_temporal_layers;
} kTestCasesForVP9EncoderTest[] = {
{VP9EncoderTest::BitrateControl::kConstantBitrate, 1u},
{VP9EncoderTest::BitrateControl::kConstantQuantizationParameter, 1u},
{VP9EncoderTest::BitrateControl::kConstantQuantizationParameter,
VP9TemporalLayers::kMinSupportedTemporalLayers},
{VP9EncoderTest::BitrateControl::kConstantQuantizationParameter,
VP9TemporalLayers::kMaxSupportedTemporalLayers},
};
TEST_P(VP9EncoderTest, Initialize) {
InitializeVP9Encoder(GetParam().bitrate_control,
GetParam().num_temporal_layers);
}
TEST_P(VP9EncoderTest, EncodeWithoutSoftwareBitrateControl) {
const auto& bitrate_control = GetParam().bitrate_control;
if (bitrate_control != BitrateControl::kConstantBitrate)
GTEST_SKIP() << "Test only for without software bitrate control";
const size_t num_temporal_layers = GetParam().num_temporal_layers;
InitializeVP9Encoder(bitrate_control, num_temporal_layers);
EncodeSequence(true);
EncodeSequence(false);
}
TEST_P(VP9EncoderTest, EncodeWithSoftwareBitrateControl) {
const auto& bitrate_control = GetParam().bitrate_control;
if (bitrate_control != BitrateControl::kConstantQuantizationParameter)
GTEST_SKIP() << "Test only for with software bitrate control";
const size_t num_temporal_layers = GetParam().num_temporal_layers;
InitializeVP9Encoder(bitrate_control, num_temporal_layers);
constexpr size_t kEncodeFrames = 20;
for (size_t i = 0; i < kEncodeFrames; i++) {
const bool is_keyframe = i == 0;
std::array<bool, kVp9NumRefsPerFrame> ref_frames_used;
base::Optional<uint8_t> temporal_layer_id;
GetTemporalLayer(is_keyframe, i, num_temporal_layers, &ref_frames_used,
&temporal_layer_id);
EncodeConstantQuantizationParameterSequence(is_keyframe, ref_frames_used,
temporal_layer_id);
}
}
TEST_P(VP9EncoderTest, ForceKeyFrameWithoutSoftwareBitrateControl) {
const auto& bitrate_control = GetParam().bitrate_control;
if (bitrate_control != BitrateControl::kConstantBitrate)
GTEST_SKIP() << "Test only for with software bitrate control";
const size_t num_temporal_layers = GetParam().num_temporal_layers;
InitializeVP9Encoder(bitrate_control, num_temporal_layers);
EncodeSequence(true /* is_keyframe */);
EncodeSequence(false /* is_keyframe */);
EncodeSequence(true /* is_keyframe */);
EncodeSequence(false /* is_keyframe */);
}
TEST_P(VP9EncoderTest, ForceKeyFrameWithSoftwareBitrateControl) {
const auto& bitrate_control = GetParam().bitrate_control;
if (bitrate_control != BitrateControl::kConstantQuantizationParameter)
GTEST_SKIP() << "Test only for with software bitrate control";
const size_t num_temporal_layers = GetParam().num_temporal_layers;
InitializeVP9Encoder(bitrate_control, num_temporal_layers);
constexpr size_t kNumKeyFrames = 3;
constexpr size_t kKeyFrameInterval = 20;
for (size_t j = 0; j < kNumKeyFrames; j++) {
for (size_t i = 0; i < kKeyFrameInterval; i++) {
const bool is_keyframe = i == 0;
std::array<bool, kVp9NumRefsPerFrame> ref_frames_used;
base::Optional<uint8_t> temporal_layer_id;
GetTemporalLayer(is_keyframe, i, num_temporal_layers, &ref_frames_used,
&temporal_layer_id);
EncodeConstantQuantizationParameterSequence(is_keyframe, ref_frames_used,
temporal_layer_id);
}
}
}
TEST_P(VP9EncoderTest, UpdateRates) {
const auto& bitrate_control = GetParam().bitrate_control;
const size_t num_temporal_layers = GetParam().num_temporal_layers;
InitializeVP9Encoder(bitrate_control, num_temporal_layers);
UpdateRatesTest(bitrate_control, num_temporal_layers);
}
INSTANTIATE_TEST_SUITE_P(,
VP9EncoderTest,
::testing::ValuesIn(kTestCasesForVP9EncoderTest));
} // namespace media