blob: 35ca7d3377c7d012703abfa33c039141124c200a [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/video_encode_accelerator_adapter.h"
#include <memory>
#include <string>
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_switches.h"
#include "media/base/mock_media_log.h"
#include "media/base/video_decoder.h"
#include "media/base/video_encoder.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/video/alpha_video_encoder_wrapper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv.h"
#if BUILDFLAG(ENABLE_LIBVPX)
#include "media/filters/vpx_video_decoder.h"
#include "media/video/vpx_video_encoder.h"
#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
#include "third_party/libvpx/source/libvpx/vpx/vpx_codec.h"
#endif
namespace media {
class AlphaVideoEncoderWrapperTest
: public ::testing::TestWithParam<VideoCodecProfile> {
public:
AlphaVideoEncoderWrapperTest() = default;
void SetUp() override {
profile_ = GetParam();
codec_ = VideoCodecProfileToVideoCodec(profile_);
encoder_ = CreateEncoder();
if (!encoder_) {
GTEST_SKIP()
<< "Couldn't create encoder for this configuration - skipping test";
}
}
void TearDown() override {
encoder_.reset();
decoder_.reset();
RunUntilIdle();
}
void PrepareDecoder(
gfx::Size size,
VideoDecoder::OutputCB output_cb,
std::vector<uint8_t> extra_data = std::vector<uint8_t>()) {
VideoDecoderConfig config(
codec_, profile_, VideoDecoderConfig::AlphaMode::kHasAlpha,
VideoColorSpace::JPEG(), VideoTransformation(), size, gfx::Rect(size),
size, extra_data, EncryptionScheme::kUnencrypted);
#if BUILDFLAG(ENABLE_LIBVPX)
decoder_ = std::make_unique<VpxVideoDecoder>();
decoder_->Initialize(config, false, nullptr, base::DoNothing(),
std::move(output_cb), base::DoNothing());
#endif
ASSERT_NE(decoder_, nullptr);
RunUntilIdle();
}
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
void RunUntilQuit() { task_environment_.RunUntilQuit(); }
scoped_refptr<VideoFrame> CreateFrame(gfx::Size size,
base::TimeDelta timestamp,
uint32_t color = 0x964050) {
auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420A, size,
gfx::Rect(size), size, timestamp);
uint32_t y = color & 0xFF;
uint32_t u = (color >> 8) & 0xFF;
uint32_t v = (color >> 16) & 0xFF;
libyuv::I420Rect(frame->writable_data(VideoFrame::kYPlane),
frame->stride(VideoFrame::kYPlane),
frame->writable_data(VideoFrame::kUPlane),
frame->stride(VideoFrame::kUPlane),
frame->writable_data(VideoFrame::kVPlane),
frame->stride(VideoFrame::kVPlane),
frame->visible_rect().x(), // x
frame->visible_rect().y(), // y
frame->visible_rect().width(), // width
frame->visible_rect().height(), // height
y, // Y color
u, // U color
v); // V color
libyuv::SetPlane(frame->writable_data(VideoFrame::kAPlane),
frame->stride(VideoFrame::kAPlane),
frame->visible_rect().width(), // width
frame->visible_rect().height(), // height
color);
return frame;
}
std::unique_ptr<VideoEncoder> CreateEncoder() {
#if BUILDFLAG(ENABLE_LIBVPX)
auto yuv_encoder = std::make_unique<VpxVideoEncoder>();
auto alpha_encoder = std::make_unique<VpxVideoEncoder>();
return std::make_unique<AlphaVideoEncoderWrapper>(std::move(yuv_encoder),
std::move(alpha_encoder));
#else
return nullptr;
#endif // ENABLE_LIBVPX
}
VideoEncoder::EncoderStatusCB ValidatingStatusCB(
bool quit_run_loop_on_call = false,
base::Location loc = FROM_HERE) {
struct CallEnforcer {
bool called = false;
std::string location;
~CallEnforcer() {
EXPECT_TRUE(called) << "Callback created: " << location;
}
};
auto enforcer = std::make_unique<CallEnforcer>();
enforcer->location = loc.ToString();
auto check_callback = base::BindLambdaForTesting(
[enforcer{std::move(enforcer)}](EncoderStatus s) {
EXPECT_TRUE(s.is_ok())
<< " Callback created: " << enforcer->location
<< " Code: " << std::hex << static_cast<StatusCodeType>(s.code())
<< " Error: " << s.message();
enforcer->called = true;
});
if (quit_run_loop_on_call) {
return std::move(check_callback).Then(task_environment_.QuitClosure());
} else {
return check_callback;
}
}
protected:
VideoCodec codec_;
VideoCodecProfile profile_;
MockMediaLog media_log_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<VideoEncoder> encoder_;
std::unique_ptr<VideoDecoder> decoder_;
};
TEST_P(AlphaVideoEncoderWrapperTest, InitializeAndFlush) {
VideoEncoder::Options options;
options.frame_size = gfx::Size(640, 480);
bool output_called = false;
VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
[&](VideoEncoderOutput, std::optional<VideoEncoder::CodecDescription>) {
output_called = true;
});
encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
std::move(output_cb),
ValidatingStatusCB(
/* quit_run_loop_on_call */ true));
RunUntilQuit();
encoder_->Flush(ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
EXPECT_FALSE(output_called) << "Output callback shouldn't be called";
}
TEST_P(AlphaVideoEncoderWrapperTest, ForceAllKeyFrames) {
int outputs_count = 0;
int frames = 10;
VideoEncoder::Options options;
options.frame_size = gfx::Size(640, 480);
auto frame_duration = base::Seconds(1.0 / 60);
VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
[&](VideoEncoderOutput output,
std::optional<VideoEncoder::CodecDescription> desc) {
EXPECT_TRUE(output.key_frame);
outputs_count++;
});
encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
std::move(output_cb),
ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
for (int i = 0; i < frames; i++) {
auto timestamp = i * frame_duration;
auto frame = CreateFrame(options.frame_size, timestamp);
encoder_->Encode(frame, VideoEncoder::EncodeOptions(true),
ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
}
encoder_->Flush(ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
EXPECT_EQ(outputs_count, frames);
}
TEST_P(AlphaVideoEncoderWrapperTest, OutputCountEqualsFrameCount) {
VideoEncoder::Options options;
options.frame_size = gfx::Size(320, 200);
options.bitrate = Bitrate::VariableBitrate(1000000u, 2000000u);
options.framerate = 25;
options.keyframe_interval = options.framerate.value() * 3; // every 3s
int total_frames_count =
options.framerate.value() * 10; // total duration 20s
int outputs_count = 0;
auto frame_duration = base::Seconds(1.0 / options.framerate.value());
VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting(
[&](VideoEncoderOutput output,
std::optional<VideoEncoder::CodecDescription> desc) {
EXPECT_NE(output.data, nullptr);
EXPECT_GT(output.size, 0u);
EXPECT_NE(output.alpha_data, nullptr);
EXPECT_GT(output.alpha_size, 0u);
EXPECT_EQ(output.timestamp, frame_duration * outputs_count);
outputs_count++;
});
encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
std::move(output_cb),
ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
uint32_t color = 0x964050;
for (int frame_index = 0; frame_index < total_frames_count; frame_index++) {
auto timestamp = frame_index * frame_duration;
auto frame = CreateFrame(options.frame_size, timestamp, color);
color = (color << 1) + frame_index;
encoder_->Encode(frame, VideoEncoder::EncodeOptions(false),
ValidatingStatusCB());
}
encoder_->Flush(ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
EXPECT_EQ(outputs_count, total_frames_count);
}
TEST_P(AlphaVideoEncoderWrapperTest, EncodeAndDecode) {
VideoEncoder::Options options;
options.frame_size = gfx::Size(320, 200);
options.bitrate = Bitrate::ConstantBitrate(1000000u); // 1Mbps
options.framerate = 20;
options.keyframe_interval = options.framerate.value() * 3; // every 3s
std::vector<scoped_refptr<VideoFrame>> frames_to_encode;
std::vector<scoped_refptr<VideoFrame>> decoded_frames;
int total_frames_count = options.framerate.value();
auto frame_duration = base::Seconds(1.0 / options.framerate.value());
VideoEncoder::OutputCB encoder_output_cb = base::BindLambdaForTesting(
[&, this](VideoEncoderOutput output,
std::optional<VideoEncoder::CodecDescription> desc) {
auto buffer =
DecoderBuffer::FromArray(std::move(output.data), output.size);
buffer->set_timestamp(output.timestamp);
buffer->set_is_key_frame(output.key_frame);
EXPECT_NE(output.alpha_data, nullptr);
std::vector<uint8_t>& buf_alpha = buffer->WritableSideData().alpha_data;
// Side data id for alpha. Big endian one.
buf_alpha.assign({0, 0, 0, 0, 0, 0, 0, 1});
buf_alpha.insert(buf_alpha.end(), output.alpha_data.get(),
output.alpha_data.get() + output.alpha_size);
decoder_->Decode(std::move(buffer), base::DoNothing());
});
VideoDecoder::OutputCB decoder_output_cb =
base::BindLambdaForTesting([&](scoped_refptr<VideoFrame> frame) {
decoded_frames.push_back(frame);
});
PrepareDecoder(options.frame_size, std::move(decoder_output_cb));
encoder_->Initialize(profile_, options, /*info_cb=*/base::DoNothing(),
std::move(encoder_output_cb),
ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
uint32_t color = 0x964050;
for (int frame_index = 0; frame_index < total_frames_count; frame_index++) {
auto timestamp = frame_index * frame_duration;
auto frame = CreateFrame(options.frame_size, timestamp, color);
frames_to_encode.push_back(frame);
color = (color << 1) + frame_index;
encoder_->Encode(frame, VideoEncoder::EncodeOptions(false),
ValidatingStatusCB());
}
encoder_->Flush(ValidatingStatusCB(/* quit_run_loop_on_call */ true));
RunUntilQuit();
auto quit = task_environment_.QuitClosure();
decoder_->Decode(DecoderBuffer::CreateEOSBuffer(),
base::BindLambdaForTesting([&](DecoderStatus status) {
EXPECT_TRUE(status.is_ok());
quit.Run();
}));
RunUntilQuit();
EXPECT_EQ(decoded_frames.size(), frames_to_encode.size());
for (auto i = 0u; i < decoded_frames.size(); i++) {
auto original_frame = frames_to_encode[i];
auto decoded_frame = decoded_frames[i];
EXPECT_EQ(decoded_frame->format(), PIXEL_FORMAT_I420A);
EXPECT_EQ(decoded_frame->timestamp(), original_frame->timestamp());
EXPECT_EQ(decoded_frame->visible_rect().size(),
original_frame->visible_rect().size());
}
}
std::string PrintTestParams(
const testing::TestParamInfo<VideoCodecProfile>& info) {
auto result = GetProfileName(info.param);
// GTest doesn't like spaces, but profile names have spaces, so we need
// to replace them with underscores.
for (auto& c : result) {
if (c == ' ') {
c = '_';
}
}
return result;
}
#if BUILDFLAG(ENABLE_LIBVPX)
INSTANTIATE_TEST_SUITE_P(AlphaVideoEncoderWrapperTest,
AlphaVideoEncoderWrapperTest,
::testing::Values(VP9PROFILE_PROFILE0, VP8PROFILE_ANY),
PrintTestParams);
#endif // ENABLE_LIBVPX
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AlphaVideoEncoderWrapperTest);
} // namespace media