blob: deb03aad62fd248cd77d446de543d4e5f9a7e50d [file] [log] [blame]
/*
* Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "api/video_codecs/libaom_av1_encoder_factory.h"
#include <cstdio>
#include <utility>
#include <vector>
#include "absl/types/variant.h"
#include "api/video/i420_buffer.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder_interface.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "modules/video_coding/codecs/av1/dav1d_decoder.h"
#include "rtc_base/logging.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
#include "test/testsupport/frame_writer.h"
namespace webrtc {
namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::IsEmpty;
using ::testing::Lt;
using ::testing::MockFunction;
using ::testing::NotNull;
using Cbr = VideoEncoderInterface::FrameEncodeSettings::Cbr;
using Cqp = VideoEncoderInterface::FrameEncodeSettings::Cqp;
using EncodedData = VideoEncoderInterface::EncodedData;
using EncodeResult = VideoEncoderInterface::EncodeResult;
using EncodeResultCallback = VideoEncoderInterface::EncodeResultCallback;
using FrameType = VideoEncoderInterface::FrameType;
std::unique_ptr<test::FrameReader> CreateFrameReader() {
return CreateY4mFrameReader(
test::ResourcePath("reference_video_640x360_30fps", "y4m"),
test::YuvFrameReaderImpl::RepeatMode::kPingPong);
}
std::string OutPath() {
std::string res = test::OutputPath();
res += "frame_dump/";
RTC_CHECK(test::DirExists(res) || test::CreateDir(res));
return res;
}
class EncodeResults {
public:
EncodeResultCallback Cb() {
return [&](const EncodeResult& result) { results_.push_back(result); };
}
EncodedData* FrameAt(int index) {
if (index < 0 || index > static_cast<int>(results_.size())) {
RTC_CHECK(false);
return nullptr;
}
return absl::get_if<EncodedData>(&results_[index]);
}
private:
std::vector<EncodeResult> results_;
};
class Av1Decoder : public DecodedImageCallback {
public:
Av1Decoder() : Av1Decoder("") {}
explicit Av1Decoder(const std::string& name)
: decoder_(CreateDav1dDecoder()), file_name_(name) {
decoder_->Configure({});
decoder_->RegisterDecodeCompleteCallback(this);
if (!file_name_.empty()) {
std::string out = OutPath();
out += file_name_;
out += "_raw.av1";
RTC_CHECK(raw_out_file_ = fopen(out.c_str(), "wb"));
RTC_LOG(LS_INFO) << "Recording bitstream to " << out;
}
}
~Av1Decoder() {
if (raw_out_file_) {
fclose(raw_out_file_);
}
}
// DecodedImageCallback
int32_t Decoded(VideoFrame& frame) override {
decode_result_ = std::make_unique<VideoFrame>(std::move(frame));
return 0;
}
VideoFrame Decode(const EncodedData& encoded_data) {
EncodedImage img;
img.SetEncodedData(encoded_data.bitstream_data);
if (raw_out_file_) {
fwrite(encoded_data.bitstream_data->data(), 1,
encoded_data.bitstream_data->size(), raw_out_file_);
}
decoder_->Decode(img, /*dont_care=*/0);
VideoFrame res(std::move(*decode_result_));
return res;
}
private:
std::unique_ptr<VideoDecoder> decoder_;
std::unique_ptr<VideoFrame> decode_result_;
std::string file_name_;
FILE* raw_out_file_ = nullptr;
};
class FrameEncoderSettingsBuilder {
public:
FrameEncoderSettingsBuilder& Key() {
frame_encode_settings_.frame_type = FrameType::kKeyframe;
return *this;
}
FrameEncoderSettingsBuilder& Start() {
frame_encode_settings_.frame_type = FrameType::kStartFrame;
return *this;
}
FrameEncoderSettingsBuilder& Delta() {
frame_encode_settings_.frame_type = FrameType::kStartFrame;
return *this;
}
FrameEncoderSettingsBuilder& Rate(
const absl::variant<Cqp, Cbr>& rate_options) {
frame_encode_settings_.rate_options = rate_options;
return *this;
}
FrameEncoderSettingsBuilder& T(int id) {
frame_encode_settings_.temporal_id = id;
return *this;
}
FrameEncoderSettingsBuilder& S(int id) {
frame_encode_settings_.spatial_id = id;
return *this;
}
FrameEncoderSettingsBuilder& Res(int width, int height) {
frame_encode_settings_.resolution = {width, height};
return *this;
}
FrameEncoderSettingsBuilder& Ref(const std::vector<int>& ref) {
frame_encode_settings_.reference_buffers = ref;
return *this;
}
FrameEncoderSettingsBuilder& Upd(int upd) {
frame_encode_settings_.update_buffer = upd;
return *this;
}
FrameEncoderSettingsBuilder& Cb(EncodeResultCallback cb) {
frame_encode_settings_.result_callback = std::move(cb);
return *this;
}
operator VideoEncoderInterface::FrameEncodeSettings&&() {
return std::move(frame_encode_settings_);
}
private:
VideoEncoderInterface::FrameEncodeSettings frame_encode_settings_;
};
using Fb = FrameEncoderSettingsBuilder;
// Since FrameEncodeSettings is move only, initalizer-list initialization won't
// work, so instead a C-style array can be used to do aggregate initialization.
template <int N>
std::vector<VideoEncoderInterface::FrameEncodeSettings> ToVec(
VideoEncoderInterface::FrameEncodeSettings (&&settings)[N]) {
return std::vector<VideoEncoderInterface::FrameEncodeSettings>(
std::make_move_iterator(std::begin(settings)),
std::make_move_iterator(std::end(settings)));
}
// For reasonable debug printout when an EXPECT fail.
struct Resolution {
explicit Resolution(const VideoFrame& frame)
: width(frame.width()), height(frame.height()) {}
friend void PrintTo(const Resolution& res, std::ostream* os) {
*os << "(width: " << res.width << " height: " << res.height << ")";
}
int width;
int height;
};
MATCHER_P2(ResolutionIs, width, height, "") {
return arg.width == width && arg.height == height;
}
double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
const VideoFrame& decoded_frame) {
return I420PSNR(*ref_buffer, *decoded_frame.video_frame_buffer()->ToI420());
}
static constexpr VideoEncoderFactoryInterface::StaticEncoderSettings
kCbrEncoderSettings{
.max_encode_dimensions = {.width = 1920, .height = 1080},
.encoding_format = {.sub_sampling = EncodingFormat::SubSampling::k420,
.bit_depth = 8},
.rc_mode =
VideoEncoderFactoryInterface::StaticEncoderSettings::Cbr{
.max_buffer_size = TimeDelta::Millis(1000),
.target_buffer_size = TimeDelta::Millis(600)},
.max_number_of_threads = 1,
};
static constexpr VideoEncoderFactoryInterface::StaticEncoderSettings
kCqpEncoderSettings{
.max_encode_dimensions = {.width = 1920, .height = 1080},
.encoding_format = {.sub_sampling = EncodingFormat::SubSampling::k420,
.bit_depth = 8},
.rc_mode = VideoEncoderFactoryInterface::StaticEncoderSettings::Cqp(),
.max_number_of_threads = 1,
};
static constexpr Cbr kCbr{.duration = TimeDelta::Millis(100),
.target_bitrate = DataRate::KilobitsPerSec(1000)};
TEST(LibaomAv1EncoderFactory, CodecName) {
EXPECT_THAT(LibaomAv1EncoderFactory().CodecName(), Eq("AV1"));
}
TEST(LibaomAv1EncoderFactory, CodecSpecifics) {
EXPECT_THAT(LibaomAv1EncoderFactory().CodecSpecifics(), IsEmpty());
}
TEST(LibaomAv1EncoderFactory, QpRange) {
const std::pair<int, int> kMinMaxQp = {0, 63};
EXPECT_THAT(
LibaomAv1EncoderFactory().GetEncoderCapabilities().rate_control.qp_range,
Eq(kMinMaxQp));
}
TEST(LibaomAv1Encoder, KeyframeUpdatesSpecifiedBuffer) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
Av1Decoder dec;
auto raw_key = frame_reader->PullFrame();
auto raw_delta = frame_reader->PullFrame();
enc->Encode(raw_key, {.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(640, 360).Upd(5).Key().Cb(res.Cb())}));
ASSERT_THAT(res.FrameAt(0), NotNull());
VideoFrame decoded_key = dec.Decode(*res.FrameAt(0));
EXPECT_THAT(Resolution(decoded_key), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(raw_key, decoded_key), Gt(40));
enc->Encode(raw_delta, {.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(640, 360).Ref({0}).Cb(res.Cb())}));
ASSERT_THAT(res.FrameAt(1), Eq(nullptr));
}
TEST(LibaomAv1Encoder, MidTemporalUnitKeyframeResetsBuffers) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
Av1Decoder dec;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({0}).Cb(res.Cb())}));
ASSERT_THAT(res.FrameAt(2), NotNull());
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Ref({0}).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Upd(1).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({0}).Cb(res.Cb())}));
ASSERT_THAT(res.FrameAt(3), Eq(nullptr));
}
TEST(LibaomAv1Encoder, ResolutionSwitching) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
rtc::scoped_refptr<I420Buffer> in0 = frame_reader->PullFrame();
enc->Encode(in0, {.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(320, 180).Upd(0).Key().Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> in1 = frame_reader->PullFrame();
enc->Encode(in1, {.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(640, 360).Ref({0}).Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> in2 = frame_reader->PullFrame();
enc->Encode(in2, {.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).Ref({0}).Cb(res.Cb())}));
Av1Decoder dec;
VideoFrame f0 = dec.Decode(*res.FrameAt(0));
EXPECT_THAT(Resolution(f0), ResolutionIs(320, 180));
// TD:
// EXPECT_THAT(Psnr(in0, f0), Gt(40));
VideoFrame f1 = dec.Decode(*res.FrameAt(1));
EXPECT_THAT(Resolution(f1), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(in1, f1), Gt(40));
VideoFrame f2 = dec.Decode(*res.FrameAt(2));
EXPECT_THAT(Resolution(f2), ResolutionIs(160, 90));
// TD:
// EXPECT_THAT(Psnr(in2, f2), Gt(40));
}
TEST(LibaomAv1Encoder, InputResolutionSwitching) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
rtc::scoped_refptr<I420Buffer> in0 = frame_reader->PullFrame();
enc->Encode(in0, {.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).Upd(0).Key().Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> in1 = frame_reader->PullFrame(
/*frame_num=*/nullptr,
/*resolution=*/{320, 180},
/*framerate_scale=*/{1, 1});
enc->Encode(in1, {.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).Ref({0}).Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> in2 = frame_reader->PullFrame(
/*frame_num=*/nullptr,
/*resolution=*/{160, 90},
/*framerate_scale=*/{1, 1});
enc->Encode(in2, {.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).Ref({0}).Cb(res.Cb())}));
Av1Decoder dec;
VideoFrame f0 = dec.Decode(*res.FrameAt(0));
EXPECT_THAT(Resolution(f0), ResolutionIs(160, 90));
// TD:
// EXPECT_THAT(Psnr(in0, f0), Gt(40));
VideoFrame f1 = dec.Decode(*res.FrameAt(1));
EXPECT_THAT(Resolution(f1), ResolutionIs(160, 90));
// TD:
// EXPECT_THAT(Psnr(in1, f1), Gt(40));
VideoFrame f2 = dec.Decode(*res.FrameAt(2));
EXPECT_THAT(Resolution(f2), ResolutionIs(160, 90));
EXPECT_THAT(Psnr(in2, f2), Gt(40));
}
TEST(LibaomAv1Encoder, TempoSpatial) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
const Cbr k10Fps{.duration = TimeDelta::Millis(100),
.target_bitrate = DataRate::KilobitsPerSec(500)};
const Cbr k20Fps{.duration = TimeDelta::Millis(50),
.target_bitrate = DataRate::KilobitsPerSec(500)};
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec(
{Fb().Rate(k10Fps).Res(160, 90).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(k10Fps).Res(320, 180).S(1).Ref({0}).Upd(1).Cb(res.Cb()),
Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({1}).Upd(2).Cb(res.Cb())}));
enc->Encode(frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(50)},
ToVec({Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(
res.Cb())}));
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
enc->Encode(
frame, {.presentation_timestamp = Timestamp::Millis(100)},
ToVec(
{Fb().Rate(k10Fps).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(k10Fps).Res(320, 180).S(1).Ref({0, 1}).Upd(1).Cb(res.Cb()),
Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({1, 2}).Upd(2).Cb(
res.Cb())}));
Av1Decoder dec;
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(0))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(1))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(2))), ResolutionIs(640, 360));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(3))), ResolutionIs(640, 360));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(4))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(5))), ResolutionIs(320, 180));
VideoFrame f = dec.Decode(*res.FrameAt(6));
EXPECT_THAT(Resolution(f), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(frame, f), Gt(40));
}
TEST(DISABLED_LibaomAv1Encoder, InvertedTempoSpatial) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(320, 180).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(1).Ref({0}).Upd(1).Cb(res.Cb())}));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(320, 180).S(0).Ref({0}).Upd(0).Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
enc->Encode(
frame, {.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(320, 180).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(1).Ref({1, 0}).Upd(1).Cb(
res.Cb())}));
Av1Decoder dec;
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(0))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(1))), ResolutionIs(640, 360));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(2))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(3))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(4))), ResolutionIs(640, 360));
}
TEST(LibaomAv1Encoder, SkipMidLayer) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Cb(res.Cb())}));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(res.Cb())}));
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
enc->Encode(
frame, {.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0, 1}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1, 2}).Upd(2).Cb(
res.Cb())}));
Av1Decoder dec;
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(0))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(1))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(2))), ResolutionIs(640, 360));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(3))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(4))), ResolutionIs(640, 360));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(5))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(6))), ResolutionIs(320, 180));
VideoFrame f = dec.Decode(*res.FrameAt(7));
EXPECT_THAT(Resolution(f), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(frame, f), Gt(40));
}
TEST(LibaomAv1Encoder, L3T1) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
Av1Decoder dec;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(0))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(1))), ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(2))), ResolutionIs(640, 360));
auto tu1_frame = frame_reader->PullFrame();
enc->Encode(
tu1_frame, {.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1, 0}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2, 1}).Upd(2).Cb(
res.Cb())}));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(3))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(4))), ResolutionIs(320, 180));
VideoFrame f_tu1 = dec.Decode(*res.FrameAt(5));
EXPECT_THAT(Resolution(f_tu1), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(tu1_frame, f_tu1), Gt(40));
auto tu2_frame = frame_reader->PullFrame();
enc->Encode(
tu2_frame, {.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1, 0}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2, 1}).Upd(2).Cb(
res.Cb())}));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(6))), ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec.Decode(*res.FrameAt(7))), ResolutionIs(320, 180));
VideoFrame f_tu2 = dec.Decode(*res.FrameAt(8));
EXPECT_THAT(Resolution(f_tu2), ResolutionIs(640, 360));
EXPECT_THAT(Psnr(tu2_frame, f_tu2), Gt(40));
}
TEST(LibaomAv1Encoder, L3T1_KEY) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
Av1Decoder dec_s0;
Av1Decoder dec_s1;
Av1Decoder dec_s2;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(0))),
ResolutionIs(160, 90));
dec_s1.Decode(*res.FrameAt(0));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(1))),
ResolutionIs(320, 180));
dec_s2.Decode(*res.FrameAt(0));
dec_s2.Decode(*res.FrameAt(1));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(2))),
ResolutionIs(640, 360));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(3))),
ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(4))),
ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(5))),
ResolutionIs(640, 360));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(6))),
ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(7))),
ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(8))),
ResolutionIs(640, 360));
}
TEST(LibaomAv1Encoder, S3T1) {
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
Av1Decoder dec_s0;
Av1Decoder dec_s1;
Av1Decoder dec_s2;
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Start().Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Start().Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Start().Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(0))),
ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(1))),
ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(2))),
ResolutionIs(640, 360));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(100)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(3))),
ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(4))),
ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(5))),
ResolutionIs(640, 360));
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(200)},
ToVec({Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Cb(res.Cb()),
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Cb(res.Cb()),
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Cb(res.Cb())}));
EXPECT_THAT(Resolution(dec_s0.Decode(*res.FrameAt(6))),
ResolutionIs(160, 90));
EXPECT_THAT(Resolution(dec_s1.Decode(*res.FrameAt(7))),
ResolutionIs(320, 180));
EXPECT_THAT(Resolution(dec_s2.Decode(*res.FrameAt(8))),
ResolutionIs(640, 360));
}
TEST(LibaomAv1Encoder, HigherEffortLevelYieldsHigherQualityFrames) {
auto frame_in = CreateFrameReader()->PullFrame();
std::pair<int, int> effort_range = LibaomAv1EncoderFactory()
.GetEncoderCapabilities()
.performance.min_max_effort_level;
// Cbr rc{.duration = TimeDelta::Millis(100),
// .target_bitrate = DataRate::KilobitsPerSec(100)};
absl::optional<double> psnr_last;
Av1Decoder dec;
for (int i = effort_range.first; i <= effort_range.second; ++i) {
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
EncodeResults res;
enc->Encode(
frame_in,
{.presentation_timestamp = Timestamp::Millis(0), .effort_level = i},
ToVec({Fb().Rate(kCbr).Res(640, 360).Upd(0).Key().Cb(res.Cb())}));
double psnr = Psnr(frame_in, dec.Decode(*res.FrameAt(0)));
EXPECT_THAT(psnr, Gt(psnr_last));
psnr_last = psnr;
}
}
TEST(LibaomAv1Encoder, KeyframeAndStartrameAreApproximatelyEqual) {
int max_spatial_layers = LibaomAv1EncoderFactory()
.GetEncoderCapabilities()
.prediction_constraints.max_spatial_layers;
const Cbr kRate{.duration = TimeDelta::Millis(100),
.target_bitrate = DataRate::KilobitsPerSec(500)};
for (int sid = 0; sid < max_spatial_layers; ++sid) {
std::string key_name = "cbr_key_sl_";
key_name += std::to_string(sid);
Av1Decoder dec_key(key_name);
std::string start_name = "cbr_start_sl_";
start_name += std::to_string(sid);
Av1Decoder dec_start(start_name);
auto frame_reader = CreateFrameReader();
auto enc_key =
LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
auto enc_start =
LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
DataSize total_size_key = DataSize::Zero();
DataSize total_size_start = DataSize::Zero();
TimeDelta total_duration = TimeDelta::Zero();
EncodeResults res_key;
EncodeResults res_start;
auto frame_in = frame_reader->PullFrame();
enc_key->Encode(
frame_in, {.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Upd(0).Key().Cb(
res_key.Cb())}));
enc_start->Encode(
frame_in, {.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Start().Upd(0).Cb(
res_start.Cb())}));
total_size_key +=
DataSize::Bytes(res_key.FrameAt(0)->bitstream_data->size());
total_size_start +=
DataSize::Bytes(res_start.FrameAt(0)->bitstream_data->size());
total_duration += kRate.duration;
dec_key.Decode(*res_key.FrameAt(0));
dec_start.Decode(*res_start.FrameAt(0));
EXPECT_NEAR(total_size_key.bytes(), total_size_start.bytes(),
0.1 * total_size_key.bytes());
for (int f = 1; f < 10; ++f) {
frame_in = frame_reader->PullFrame();
enc_key->Encode(
frame_in, {.presentation_timestamp = Timestamp::Millis(f * 100)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Cb(
res_key.Cb())}));
enc_start->Encode(
frame_in, {.presentation_timestamp = Timestamp::Millis(f * 100)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Cb(
res_start.Cb())}));
total_size_key +=
DataSize::Bytes(res_key.FrameAt(f)->bitstream_data->size());
total_size_start +=
DataSize::Bytes(res_start.FrameAt(f)->bitstream_data->size());
total_duration += kRate.duration;
dec_key.Decode(*res_key.FrameAt(f));
dec_start.Decode(*res_start.FrameAt(f));
}
double key_encode_kbps = (total_size_key / total_duration).kbps();
double start_encode_kbps = (total_size_start / total_duration).kbps();
EXPECT_NEAR(key_encode_kbps, start_encode_kbps, start_encode_kbps * 0.05);
}
}
TEST(LibaomAv1Encoder, BitrateConsistentAcrossSpatialLayers) {
int max_spatial_layers = LibaomAv1EncoderFactory()
.GetEncoderCapabilities()
.prediction_constraints.max_spatial_layers;
const Cbr kRate{.duration = TimeDelta::Millis(100),
.target_bitrate = DataRate::KilobitsPerSec(500)};
for (int sid = 0; sid < max_spatial_layers; ++sid) {
std::string out_name = "cbr_sl_";
out_name += std::to_string(sid);
Av1Decoder dec(out_name);
auto frame_reader = CreateFrameReader();
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCbrEncoderSettings, {});
DataSize total_size = DataSize::Zero();
TimeDelta total_duration = TimeDelta::Zero();
EncodeResults res;
enc->Encode(frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Upd(0).Key().Cb(
res.Cb())}));
total_size += DataSize::Bytes(res.FrameAt(0)->bitstream_data->size());
total_duration += kRate.duration;
dec.Decode(*res.FrameAt(0));
for (int f = 1; f < 30; ++f) {
enc->Encode(
frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(f * 100)},
ToVec({Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Cb(
res.Cb())}));
total_size += DataSize::Bytes(res.FrameAt(f)->bitstream_data->size());
total_duration += kRate.duration;
dec.Decode(*res.FrameAt(f));
}
double encode_kbps = (total_size / total_duration).kbps();
double target_kbps = kRate.target_bitrate.kbps();
EXPECT_NEAR(encode_kbps, target_kbps, target_kbps * 0.1);
}
}
TEST(LibaomAv1Encoder, ConstantQp) {
int max_spatial_layers = LibaomAv1EncoderFactory()
.GetEncoderCapabilities()
.prediction_constraints.max_spatial_layers;
constexpr int kQp = 30;
for (int sid = 0; sid < max_spatial_layers; ++sid) {
auto enc = LibaomAv1EncoderFactory().CreateEncoder(kCqpEncoderSettings, {});
std::string out_name = "cqp_sl_";
out_name += std::to_string(sid);
Av1Decoder dec(out_name);
DataSize total_size = DataSize::Zero();
auto frame_reader = CreateFrameReader();
EncodeResults res;
enc->Encode(frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(0)},
ToVec({Fb().Rate(Cqp{.target_qp = kQp})
.Res(640, 360)
.S(sid)
.Upd(0)
.Key()
.Cb(res.Cb())}));
EXPECT_THAT(res.FrameAt(0)->encoded_qp, Eq(kQp));
total_size += DataSize::Bytes(res.FrameAt(0)->bitstream_data->size());
dec.Decode(*res.FrameAt(0));
for (int f = 1; f < 10; ++f) {
enc->Encode(frame_reader->PullFrame(),
{.presentation_timestamp = Timestamp::Millis(f * 100)},
ToVec({Fb().Rate(Cqp{.target_qp = kQp - f})
.Res(640, 360)
.S(sid)
.Ref({0})
.Upd(0)
.Cb(res.Cb())}));
EXPECT_THAT(res.FrameAt(f)->encoded_qp, Eq(kQp - f));
dec.Decode(*res.FrameAt(f));
}
}
}
} // namespace
} // namespace webrtc