mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00

This reverts commit56e6309749
. Reason for revert: Preparing for reland Original change's description: > Revert "New video encoder API." > > This reverts commit42f12d5183
. > > Reason for revert: tests fails downstream > > Original change's description: > > New video encoder API. > > > > Also initial implementation wrapping the libaom AV1 encoder. > > > > Note that for now this is intended for prototype purposes. > > > > Bug: none > > Change-Id: Iac42ca4aecb6a204601c9f00bfb300e3eda3c4f4 > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/306181 > > Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> > > Commit-Queue: Philip Eliasson <philipel@webrtc.org> > > Reviewed-by: Erik Språng <sprang@webrtc.org> > > Cr-Commit-Position: refs/heads/main@{#42108} > > Bug: none > Change-Id: I927260353afb91df6c7650364baee4f13a098efd > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/347883 > Commit-Queue: Philip Eliasson <philipel@webrtc.org> > Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com> > Reviewed-by: Philip Eliasson <philipel@webrtc.org> > Owners-Override: Philip Eliasson <philipel@webrtc.org> > Auto-Submit: Danil Chapovalov <danilchap@webrtc.org> > Cr-Commit-Position: refs/heads/main@{#42111} Bug: none Change-Id: Ib72ef5359ead697d27301e2ca2408e8b27165931 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/349001 Commit-Queue: Philip Eliasson <philipel@webrtc.org> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42172}
822 lines
31 KiB
C++
822 lines
31 KiB
C++
/*
|
|
* 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 "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 CallBack() {
|
|
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 std::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;
|
|
}
|
|
|
|
VideoEncoderInterface::FrameEncodeSettings Build() {
|
|
return frame_encode_settings_;
|
|
}
|
|
|
|
private:
|
|
VideoEncoderInterface::FrameEncodeSettings frame_encode_settings_;
|
|
};
|
|
|
|
using Fb = FrameEncoderSettingsBuilder;
|
|
|
|
// 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)},
|
|
{Fb().Rate(kCbr).Res(640, 360).Upd(5).Key().Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(640, 360).Ref({0}).Build()}, res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({0}).Build()},
|
|
res.CallBack());
|
|
ASSERT_THAT(res.FrameAt(2), NotNull());
|
|
|
|
enc->Encode(frame_reader->PullFrame(),
|
|
{.presentation_timestamp = Timestamp::Millis(100)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Ref({0}).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Upd(1).Key().Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({0}).Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(320, 180).Upd(0).Key().Build()},
|
|
res.CallBack());
|
|
|
|
rtc::scoped_refptr<I420Buffer> in1 = frame_reader->PullFrame();
|
|
enc->Encode(in1, {.presentation_timestamp = Timestamp::Millis(100)},
|
|
{Fb().Rate(kCbr).Res(640, 360).Ref({0}).Build()}, res.CallBack());
|
|
|
|
rtc::scoped_refptr<I420Buffer> in2 = frame_reader->PullFrame();
|
|
enc->Encode(in2, {.presentation_timestamp = Timestamp::Millis(200)},
|
|
{Fb().Rate(kCbr).Res(160, 90).Ref({0}).Build()}, res.CallBack());
|
|
|
|
EXPECT_THAT(res.FrameAt(0), Field(&EncodedData::spatial_id, 0));
|
|
EXPECT_THAT(res.FrameAt(1), Field(&EncodedData::spatial_id, 0));
|
|
EXPECT_THAT(res.FrameAt(2), Field(&EncodedData::spatial_id, 0));
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).Upd(0).Key().Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).Ref({0}).Build()}, res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).Ref({0}).Build()}, res.CallBack());
|
|
|
|
EXPECT_THAT(res.FrameAt(0), Field(&EncodedData::spatial_id, 0));
|
|
EXPECT_THAT(res.FrameAt(1), Field(&EncodedData::spatial_id, 0));
|
|
EXPECT_THAT(res.FrameAt(2), Field(&EncodedData::spatial_id, 0));
|
|
|
|
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)},
|
|
{Fb().Rate(k10Fps).Res(160, 90).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(k10Fps).Res(320, 180).S(1).Ref({0}).Upd(1).Build(),
|
|
Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
enc->Encode(frame_reader->PullFrame(),
|
|
{.presentation_timestamp = Timestamp::Millis(50)},
|
|
{Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
|
|
enc->Encode(frame, {.presentation_timestamp = Timestamp::Millis(100)},
|
|
{Fb().Rate(k10Fps).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(k10Fps).Res(320, 180).S(1).Ref({0, 1}).Upd(1).Build(),
|
|
Fb().Rate(k20Fps).Res(640, 360).S(2).Ref({1, 2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(320, 180).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(1).Ref({0}).Upd(1).Build()},
|
|
res.CallBack());
|
|
|
|
enc->Encode(frame_reader->PullFrame(),
|
|
{.presentation_timestamp = Timestamp::Millis(100)},
|
|
{Fb().Rate(kCbr).Res(320, 180).S(0).Ref({0}).Upd(0).Build()},
|
|
res.CallBack());
|
|
|
|
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
|
|
enc->Encode(frame, {.presentation_timestamp = Timestamp::Millis(200)},
|
|
{Fb().Rate(kCbr).Res(320, 180).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(1).Ref({1, 0}).Upd(1).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
enc->Encode(frame_reader->PullFrame(),
|
|
{.presentation_timestamp = Timestamp::Millis(100)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
rtc::scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
|
|
enc->Encode(frame, {.presentation_timestamp = Timestamp::Millis(200)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0, 1}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1, 2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1, 0}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2, 1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1, 0}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2, 1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Upd(0).Key().Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({0}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({1}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Start().Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Start().Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Start().Upd(2).Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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)},
|
|
{Fb().Rate(kCbr).Res(160, 90).S(0).Ref({0}).Upd(0).Build(),
|
|
Fb().Rate(kCbr).Res(320, 180).S(1).Ref({1}).Upd(1).Build(),
|
|
Fb().Rate(kCbr).Res(640, 360).S(2).Ref({2}).Upd(2).Build()},
|
|
res.CallBack());
|
|
|
|
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},
|
|
{Fb().Rate(kCbr).Res(640, 360).Upd(0).Key().Build()}, res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Upd(0).Key().Build()},
|
|
res_key.CallBack());
|
|
enc_start->Encode(
|
|
frame_in, {.presentation_timestamp = Timestamp::Millis(0)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Start().Upd(0).Build()},
|
|
res_start.CallBack());
|
|
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)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Build()},
|
|
res_key.CallBack());
|
|
enc_start->Encode(
|
|
frame_in, {.presentation_timestamp = Timestamp::Millis(f * 100)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Build()},
|
|
res_start.CallBack());
|
|
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)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Upd(0).Key().Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(kRate).Res(640, 360).S(sid).Ref({0}).Upd(0).Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(Cqp{.target_qp = kQp})
|
|
.Res(640, 360)
|
|
.S(sid)
|
|
.Upd(0)
|
|
.Key()
|
|
.Build()},
|
|
res.CallBack());
|
|
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)},
|
|
{Fb().Rate(Cqp{.target_qp = kQp - f})
|
|
.Res(640, 360)
|
|
.S(sid)
|
|
.Ref({0})
|
|
.Upd(0)
|
|
.Build()},
|
|
res.CallBack());
|
|
EXPECT_THAT(res.FrameAt(f)->encoded_qp, Eq(kQp - f));
|
|
dec.Decode(*res.FrameAt(f));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace webrtc
|