webrtc/modules/video_coding/codecs/test/video_codec_test.cc
Sergey Silkin 26d1b26277 Log metrics even if test failed
Set of codecs for testing is hardcoded to AV1, VP8, VP9, H264, H265. Some codecs may not be available due to lack of support on the platform or due to some issue in our code which would be a regression. Reporting zero metrics for failed tests would allow the perf tool to detect such a regression.

This also enables codec tests by default. The tests should not run on bots since video_codec_perf_tests binary is not included in any test suits yet.

Bug: webrtc:14852
Change-Id: I967160069055036f93e595d328c4d5f1ca483be9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/300868
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39840}
2023-04-13 08:49:37 +00:00

793 lines
29 KiB
C++

/*
* Copyright (c) 2022 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/video_codec.h"
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "api/test/create_video_codec_tester.h"
#include "api/test/metrics/global_metrics_logger_and_exporter.h"
#include "api/test/video_codec_tester.h"
#include "api/test/videocodec_test_stats.h"
#include "api/units/data_rate.h"
#include "api/units/frequency.h"
#include "api/video/encoded_image.h"
#include "api/video/i420_buffer.h"
#include "api/video/resolution.h"
#include "api/video/video_frame.h"
#include "api/video_codecs/scalability_mode.h"
#include "api/video_codecs/video_decoder.h"
#include "api/video_codecs/video_encoder.h"
#include "media/engine/internal_decoder_factory.h"
#include "media/engine/internal_encoder_factory.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
#include "modules/video_coding/include/video_error_codes.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
#if defined(WEBRTC_ANDROID)
#include "modules/video_coding/codecs/test/android_codec_factory_helper.h"
#endif
#include "rtc_base/logging.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
namespace webrtc {
namespace test {
namespace {
using ::testing::Combine;
using ::testing::Values;
using PacingMode = VideoCodecTester::PacingSettings::PacingMode;
struct VideoInfo {
std::string name;
Resolution resolution;
Frequency framerate;
};
struct LayerId {
int spatial_idx;
int temporal_idx;
bool operator==(const LayerId& o) const {
return spatial_idx == o.spatial_idx && temporal_idx == o.temporal_idx;
}
bool operator<(const LayerId& o) const {
if (spatial_idx < o.spatial_idx)
return true;
if (spatial_idx == o.spatial_idx && temporal_idx < o.temporal_idx)
return true;
return false;
}
};
struct EncodingSettings {
ScalabilityMode scalability_mode;
struct LayerSettings {
Resolution resolution;
Frequency framerate;
DataRate bitrate;
};
std::map<LayerId, LayerSettings> layer_settings;
bool IsSameSettings(const EncodingSettings& other) const {
if (scalability_mode != other.scalability_mode) {
return true;
}
for (auto [layer_id, layer] : layer_settings) {
const auto& other_layer = other.layer_settings.at(layer_id);
if (layer.resolution != other_layer.resolution) {
return true;
}
}
return false;
}
bool IsSameRate(const EncodingSettings& other) const {
for (auto [layer_id, layer] : layer_settings) {
const auto& other_layer = other.layer_settings.at(layer_id);
if (layer.bitrate != other_layer.bitrate ||
layer.framerate != other_layer.framerate) {
return true;
}
}
return false;
}
};
const VideoInfo kFourPeople_1280x720_30 = {
.name = "FourPeople_1280x720_30",
.resolution = {.width = 1280, .height = 720},
.framerate = Frequency::Hertz(30)};
class TestRawVideoSource : public VideoCodecTester::RawVideoSource {
public:
static constexpr Frequency k90kHz = Frequency::Hertz(90000);
TestRawVideoSource(VideoInfo video_info,
const std::map<int, EncodingSettings>& frame_settings,
int num_frames)
: video_info_(video_info),
frame_settings_(frame_settings),
num_frames_(num_frames),
frame_num_(0),
// Start with non-zero timestamp to force using frame RTP timestamps in
// IvfFrameWriter.
timestamp_rtp_(90000) {
// Ensure settings for the first frame are provided.
RTC_CHECK_GT(frame_settings_.size(), 0u);
RTC_CHECK_EQ(frame_settings_.begin()->first, 0);
frame_reader_ = CreateYuvFrameReader(
ResourcePath(video_info_.name, "yuv"), video_info_.resolution,
YuvFrameReaderImpl::RepeatMode::kPingPong);
RTC_CHECK(frame_reader_);
}
// Pulls next frame. Frame RTP timestamp is set accordingly to
// `EncodingSettings::framerate`.
absl::optional<VideoFrame> PullFrame() override {
if (frame_num_ >= num_frames_) {
return absl::nullopt; // End of stream.
}
const EncodingSettings& encoding_settings =
std::prev(frame_settings_.upper_bound(frame_num_))->second;
Resolution resolution =
encoding_settings.layer_settings.begin()->second.resolution;
Frequency framerate =
encoding_settings.layer_settings.begin()->second.framerate;
int pulled_frame;
auto buffer = frame_reader_->PullFrame(
&pulled_frame, resolution,
{.num = static_cast<int>(framerate.millihertz()),
.den = static_cast<int>(video_info_.framerate.millihertz())});
RTC_CHECK(buffer) << "Cannot pull frame " << frame_num_;
auto frame = VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(timestamp_rtp_)
.set_timestamp_us((timestamp_rtp_ / k90kHz).us())
.build();
pulled_frames_[timestamp_rtp_] = pulled_frame;
timestamp_rtp_ += k90kHz / framerate;
++frame_num_;
return frame;
}
// Reads frame specified by `timestamp_rtp`, scales it to `resolution` and
// returns. Frame with the given `timestamp_rtp` is expected to be pulled
// before.
VideoFrame GetFrame(uint32_t timestamp_rtp, Resolution resolution) override {
RTC_CHECK(pulled_frames_.find(timestamp_rtp) != pulled_frames_.end())
<< "Frame with RTP timestamp " << timestamp_rtp
<< " was not pulled before";
auto buffer =
frame_reader_->ReadFrame(pulled_frames_[timestamp_rtp], resolution);
return VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(timestamp_rtp)
.build();
}
protected:
VideoInfo video_info_;
std::unique_ptr<FrameReader> frame_reader_;
const std::map<int, EncodingSettings>& frame_settings_;
int num_frames_;
int frame_num_;
uint32_t timestamp_rtp_;
std::map<uint32_t, int> pulled_frames_;
};
class TestEncoder : public VideoCodecTester::Encoder,
public EncodedImageCallback {
public:
TestEncoder(std::unique_ptr<VideoEncoder> encoder,
const std::string codec_type,
const std::map<int, EncodingSettings>& frame_settings)
: encoder_(std::move(encoder)),
codec_type_(codec_type),
frame_settings_(frame_settings),
frame_num_(0) {
// Ensure settings for the first frame is provided.
RTC_CHECK_GT(frame_settings_.size(), 0u);
RTC_CHECK_EQ(frame_settings_.begin()->first, 0);
encoder_->RegisterEncodeCompleteCallback(this);
}
void Initialize() override {
const EncodingSettings& first_frame_settings = frame_settings_.at(0);
Configure(first_frame_settings);
SetRates(first_frame_settings);
}
void Encode(const VideoFrame& frame, EncodeCallback callback) override {
callbacks_[frame.timestamp()] = std::move(callback);
if (auto fs = frame_settings_.find(frame_num_);
fs != frame_settings_.begin() && fs != frame_settings_.end()) {
if (!fs->second.IsSameSettings(std::prev(fs)->second)) {
Configure(fs->second);
} else if (!fs->second.IsSameRate(std::prev(fs)->second)) {
SetRates(fs->second);
}
}
encoder_->Encode(frame, nullptr);
++frame_num_;
}
void Flush() override {
// TODO(webrtc:14852): For codecs which buffer frames we need a to
// flush them to get last frames. Add such functionality to VideoEncoder
// API. On Android it will map directly to `MediaCodec.flush()`.
encoder_->Release();
}
VideoEncoder* encoder() { return encoder_.get(); }
protected:
Result OnEncodedImage(const EncodedImage& encoded_image,
const CodecSpecificInfo* codec_specific_info) override {
auto cb = callbacks_.find(encoded_image.Timestamp());
RTC_CHECK(cb != callbacks_.end());
cb->second(encoded_image);
callbacks_.erase(callbacks_.begin(), cb);
return Result(Result::Error::OK);
}
void Configure(const EncodingSettings& es) {
VideoCodec vc;
const EncodingSettings::LayerSettings& layer_settings =
es.layer_settings.begin()->second;
vc.width = layer_settings.resolution.width;
vc.height = layer_settings.resolution.height;
const DataRate& bitrate = layer_settings.bitrate;
vc.startBitrate = bitrate.kbps();
vc.maxBitrate = bitrate.kbps();
vc.minBitrate = 0;
vc.maxFramerate = static_cast<uint32_t>(layer_settings.framerate.hertz());
vc.active = true;
vc.qpMax = 63;
vc.numberOfSimulcastStreams = 0;
vc.mode = webrtc::VideoCodecMode::kRealtimeVideo;
vc.SetFrameDropEnabled(true);
vc.SetScalabilityMode(es.scalability_mode);
vc.codecType = PayloadStringToCodecType(codec_type_);
if (vc.codecType == kVideoCodecVP8) {
*(vc.VP8()) = VideoEncoder::GetDefaultVp8Settings();
} else if (vc.codecType == kVideoCodecVP9) {
*(vc.VP9()) = VideoEncoder::GetDefaultVp9Settings();
} else if (vc.codecType == kVideoCodecH264) {
*(vc.H264()) = VideoEncoder::GetDefaultH264Settings();
}
VideoEncoder::Settings ves(
VideoEncoder::Capabilities(/*loss_notification=*/false),
/*number_of_cores=*/1,
/*max_payload_size=*/1440);
int result = encoder_->InitEncode(&vc, ves);
ASSERT_EQ(result, WEBRTC_VIDEO_CODEC_OK);
SetRates(es);
}
void SetRates(const EncodingSettings& es) {
VideoEncoder::RateControlParameters rc;
int num_spatial_layers =
ScalabilityModeToNumSpatialLayers(es.scalability_mode);
int num_temporal_layers =
ScalabilityModeToNumSpatialLayers(es.scalability_mode);
for (int sidx = 0; sidx < num_spatial_layers; ++sidx) {
for (int tidx = 0; tidx < num_temporal_layers; ++tidx) {
auto layer_settings =
es.layer_settings.find({.spatial_idx = sidx, .temporal_idx = tidx});
RTC_CHECK(layer_settings != es.layer_settings.end())
<< "Bitrate for layer S=" << sidx << " T=" << tidx << " is not set";
rc.bitrate.SetBitrate(sidx, tidx, layer_settings->second.bitrate.bps());
}
}
rc.framerate_fps =
es.layer_settings.begin()->second.framerate.millihertz() / 1000.0;
encoder_->SetRates(rc);
}
std::unique_ptr<VideoEncoder> encoder_;
const std::string codec_type_;
const std::map<int, EncodingSettings>& frame_settings_;
int frame_num_;
std::map<uint32_t, EncodeCallback> callbacks_;
};
class TestDecoder : public VideoCodecTester::Decoder,
public DecodedImageCallback {
public:
TestDecoder(std::unique_ptr<VideoDecoder> decoder,
const std::string codec_type)
: decoder_(std::move(decoder)), codec_type_(codec_type) {
decoder_->RegisterDecodeCompleteCallback(this);
}
void Initialize() override {
VideoDecoder::Settings ds;
ds.set_codec_type(PayloadStringToCodecType(codec_type_));
ds.set_number_of_cores(1);
ds.set_max_render_resolution({1280, 720});
bool result = decoder_->Configure(ds);
ASSERT_TRUE(result);
}
void Decode(const EncodedImage& frame, DecodeCallback callback) override {
callbacks_[frame.Timestamp()] = std::move(callback);
decoder_->Decode(frame, /*missing_frames=*/false,
/*render_time_ms=*/0);
}
void Flush() override {
// TODO(webrtc:14852): For codecs which buffer frames we need a to
// flush them to get last frames. Add such functionality to VideoDecoder
// API. On Android it will map directly to `MediaCodec.flush()`.
decoder_->Release();
}
VideoDecoder* decoder() { return decoder_.get(); }
protected:
int Decoded(VideoFrame& decoded_frame) override {
auto cb = callbacks_.find(decoded_frame.timestamp());
RTC_CHECK(cb != callbacks_.end());
cb->second(decoded_frame);
callbacks_.erase(callbacks_.begin(), cb);
return WEBRTC_VIDEO_CODEC_OK;
}
std::unique_ptr<VideoDecoder> decoder_;
const std::string codec_type_;
std::map<uint32_t, DecodeCallback> callbacks_;
};
std::unique_ptr<TestRawVideoSource> CreateVideoSource(
const VideoInfo& video,
const std::map<int, EncodingSettings>& frame_settings,
int num_frames) {
return std::make_unique<TestRawVideoSource>(video, frame_settings,
num_frames);
}
std::unique_ptr<TestEncoder> CreateEncoder(
std::string type,
std::string impl,
const std::map<int, EncodingSettings>& frame_settings) {
std::unique_ptr<VideoEncoderFactory> factory;
if (impl == "builtin") {
factory = std::make_unique<InternalEncoderFactory>();
} else if (impl == "mediacodec") {
#if defined(WEBRTC_ANDROID)
InitializeAndroidObjects();
factory = CreateAndroidEncoderFactory();
#endif
}
std::unique_ptr<VideoEncoder> encoder =
factory->CreateVideoEncoder(SdpVideoFormat(type));
if (encoder == nullptr) {
return nullptr;
}
return std::make_unique<TestEncoder>(std::move(encoder), type,
frame_settings);
}
std::unique_ptr<TestDecoder> CreateDecoder(std::string type, std::string impl) {
std::unique_ptr<VideoDecoderFactory> factory;
if (impl == "builtin") {
factory = std::make_unique<InternalDecoderFactory>();
} else if (impl == "mediacodec") {
#if defined(WEBRTC_ANDROID)
InitializeAndroidObjects();
factory = CreateAndroidDecoderFactory();
#endif
}
std::unique_ptr<VideoDecoder> decoder =
factory->CreateVideoDecoder(SdpVideoFormat(type));
if (decoder == nullptr) {
return nullptr;
}
return std::make_unique<TestDecoder>(std::move(decoder), type);
}
void SetTargetRates(const std::map<int, EncodingSettings>& frame_settings,
std::vector<VideoCodecStats::Frame>& frames) {
for (VideoCodecStats::Frame& f : frames) {
const EncodingSettings& encoding_settings =
std::prev(frame_settings.upper_bound(f.frame_num))->second;
LayerId layer_id = {.spatial_idx = f.spatial_idx,
.temporal_idx = f.temporal_idx};
RTC_CHECK(encoding_settings.layer_settings.find(layer_id) !=
encoding_settings.layer_settings.end())
<< "Frame frame_num=" << f.frame_num
<< " belongs to spatial_idx=" << f.spatial_idx
<< " temporal_idx=" << f.temporal_idx
<< " but settings for this layer are not provided.";
const EncodingSettings::LayerSettings& layer_settings =
encoding_settings.layer_settings.at(layer_id);
f.target_bitrate = layer_settings.bitrate;
f.target_framerate = layer_settings.framerate;
}
}
std::string TestOutputPath() {
std::string output_path =
OutputPath() +
::testing::UnitTest::GetInstance()->current_test_info()->name();
std::string output_dir = DirName(output_path);
bool result = CreateDir(output_dir);
RTC_CHECK(result) << "Cannot create " << output_dir;
return output_path;
}
} // namespace
std::unique_ptr<VideoCodecStats> RunEncodeDecodeTest(
std::string codec_type,
std::string codec_impl,
const VideoInfo& video_info,
const std::map<int, EncodingSettings>& frame_settings,
int num_frames,
bool save_codec_input,
bool save_codec_output) {
std::unique_ptr<TestRawVideoSource> video_source =
CreateVideoSource(video_info, frame_settings, num_frames);
std::unique_ptr<TestEncoder> encoder =
CreateEncoder(codec_type, codec_impl, frame_settings);
if (encoder == nullptr) {
return nullptr;
}
std::unique_ptr<TestDecoder> decoder = CreateDecoder(codec_type, codec_impl);
if (decoder == nullptr) {
// If platform decoder is not available try built-in one.
if (codec_impl == "builtin") {
return nullptr;
}
decoder = CreateDecoder(codec_type, "builtin");
if (decoder == nullptr) {
return nullptr;
}
}
RTC_LOG(LS_INFO) << "Encoder implementation: "
<< encoder->encoder()->GetEncoderInfo().implementation_name;
RTC_LOG(LS_INFO) << "Decoder implementation: "
<< decoder->decoder()->GetDecoderInfo().implementation_name;
VideoCodecTester::EncoderSettings encoder_settings;
encoder_settings.pacing.mode =
encoder->encoder()->GetEncoderInfo().is_hardware_accelerated
? PacingMode::kRealTime
: PacingMode::kNoPacing;
VideoCodecTester::DecoderSettings decoder_settings;
decoder_settings.pacing.mode =
decoder->decoder()->GetDecoderInfo().is_hardware_accelerated
? PacingMode::kRealTime
: PacingMode::kNoPacing;
std::string output_path = TestOutputPath();
if (save_codec_input) {
encoder_settings.encoder_input_base_path = output_path + "_enc_input";
decoder_settings.decoder_input_base_path = output_path + "_dec_input";
}
if (save_codec_output) {
encoder_settings.encoder_output_base_path = output_path + "_enc_output";
decoder_settings.decoder_output_base_path = output_path + "_dec_output";
}
std::unique_ptr<VideoCodecTester> tester = CreateVideoCodecTester();
return tester->RunEncodeDecodeTest(video_source.get(), encoder.get(),
decoder.get(), encoder_settings,
decoder_settings);
}
std::unique_ptr<VideoCodecStats> RunEncodeTest(
std::string codec_type,
std::string codec_impl,
const VideoInfo& video_info,
const std::map<int, EncodingSettings>& frame_settings,
int num_frames,
bool save_codec_input,
bool save_codec_output) {
std::unique_ptr<TestRawVideoSource> video_source =
CreateVideoSource(video_info, frame_settings, num_frames);
std::unique_ptr<TestEncoder> encoder =
CreateEncoder(codec_type, codec_impl, frame_settings);
if (encoder == nullptr) {
return nullptr;
}
RTC_LOG(LS_INFO) << "Encoder implementation: "
<< encoder->encoder()->GetEncoderInfo().implementation_name;
VideoCodecTester::EncoderSettings encoder_settings;
encoder_settings.pacing.mode =
encoder->encoder()->GetEncoderInfo().is_hardware_accelerated
? PacingMode::kRealTime
: PacingMode::kNoPacing;
std::string output_path = TestOutputPath();
if (save_codec_input) {
encoder_settings.encoder_input_base_path = output_path + "_enc_input";
}
if (save_codec_output) {
encoder_settings.encoder_output_base_path = output_path + "_enc_output";
}
std::unique_ptr<VideoCodecTester> tester = CreateVideoCodecTester();
return tester->RunEncodeTest(video_source.get(), encoder.get(),
encoder_settings);
}
class SpatialQualityTest : public ::testing::TestWithParam<
std::tuple</*codec_type=*/std::string,
/*codec_impl=*/std::string,
VideoInfo,
std::tuple</*width=*/int,
/*height=*/int,
/*framerate_fps=*/double,
/*bitrate_kbps=*/int,
/*min_psnr=*/double>>> {
public:
static std::string TestParamsToString(
const ::testing::TestParamInfo<SpatialQualityTest::ParamType>& info) {
auto [codec_type, codec_impl, video_info, coding_settings] = info.param;
auto [width, height, framerate_fps, bitrate_kbps, psnr] = coding_settings;
return std::string(codec_type + codec_impl + video_info.name +
std::to_string(width) + "x" + std::to_string(height) +
"p" +
std::to_string(static_cast<int>(1000 * framerate_fps)) +
"mhz" + std::to_string(bitrate_kbps) + "kbps");
}
};
TEST_P(SpatialQualityTest, SpatialQuality) {
auto [codec_type, codec_impl, video_info, coding_settings] = GetParam();
auto [width, height, framerate_fps, bitrate_kbps, psnr] = coding_settings;
std::map<int, EncodingSettings> frame_settings = {
{0,
{.scalability_mode = ScalabilityMode::kL1T1,
.layer_settings = {
{LayerId{.spatial_idx = 0, .temporal_idx = 0},
{.resolution = {.width = width, .height = height},
.framerate = Frequency::MilliHertz(1000 * framerate_fps),
.bitrate = DataRate::KilobitsPerSec(bitrate_kbps)}}}}}};
int duration_s = 10;
int num_frames = duration_s * framerate_fps;
std::unique_ptr<VideoCodecStats> stats = RunEncodeDecodeTest(
codec_type, codec_impl, video_info, frame_settings, num_frames,
/*save_codec_input=*/false, /*save_codec_output=*/false);
VideoCodecStats::Stream stream;
if (stats != nullptr) {
std::vector<VideoCodecStats::Frame> frames = stats->Slice();
SetTargetRates(frame_settings, frames);
stream = stats->Aggregate(frames);
EXPECT_GE(stream.psnr.y.GetAverage(), psnr);
}
stream.LogMetrics(
GetGlobalMetricsLogger(),
::testing::UnitTest::GetInstance()->current_test_info()->name(),
/*metadata=*/
{{"codec_type", codec_type},
{"codec_impl", codec_impl},
{"video_name", video_info.name}});
}
INSTANTIATE_TEST_SUITE_P(
All,
SpatialQualityTest,
Combine(Values("AV1", "VP9", "VP8", "H264", "H265"),
#if defined(WEBRTC_ANDROID)
Values("builtin", "mediacodec"),
#else
Values("builtin"),
#endif
Values(kFourPeople_1280x720_30),
Values(std::make_tuple(320, 180, 30, 32, 29),
std::make_tuple(320, 180, 30, 64, 30),
std::make_tuple(320, 180, 30, 128, 33),
std::make_tuple(320, 180, 30, 256, 36),
std::make_tuple(640, 360, 30, 128, 31),
std::make_tuple(640, 360, 30, 256, 33),
std::make_tuple(640, 360, 30, 384, 35),
std::make_tuple(640, 360, 30, 512, 36),
std::make_tuple(1280, 720, 30, 256, 33),
std::make_tuple(1280, 720, 30, 512, 35),
std::make_tuple(1280, 720, 30, 1024, 37),
std::make_tuple(1280, 720, 30, 2048, 39))),
SpatialQualityTest::TestParamsToString);
class BitrateAdaptationTest
: public ::testing::TestWithParam<
std::tuple</*codec_type=*/std::string,
/*codec_impl=*/std::string,
VideoInfo,
std::pair</*bitrate_kbps=*/int, /*bitrate_kbps=*/int>>> {
public:
static std::string TestParamsToString(
const ::testing::TestParamInfo<BitrateAdaptationTest::ParamType>& info) {
auto [codec_type, codec_impl, video_info, bitrate_kbps] = info.param;
return std::string(codec_type + codec_impl + video_info.name +
std::to_string(bitrate_kbps.first) + "kbps" +
std::to_string(bitrate_kbps.second) + "kbps");
}
};
TEST_P(BitrateAdaptationTest, BitrateAdaptation) {
auto [codec_type, codec_impl, video_info, bitrate_kbps] = GetParam();
int duration_s = 10; // Duration of fixed rate interval.
int first_frame = duration_s * video_info.framerate.millihertz() / 1000;
int num_frames = 2 * duration_s * video_info.framerate.millihertz() / 1000;
std::map<int, EncodingSettings> frame_settings = {
{0,
{.layer_settings = {{LayerId{.spatial_idx = 0, .temporal_idx = 0},
{.resolution = {.width = 640, .height = 360},
.framerate = video_info.framerate,
.bitrate = DataRate::KilobitsPerSec(
bitrate_kbps.first)}}}}},
{first_frame,
{.layer_settings = {
{LayerId{.spatial_idx = 0, .temporal_idx = 0},
{.resolution = {.width = 640, .height = 360},
.framerate = video_info.framerate,
.bitrate = DataRate::KilobitsPerSec(bitrate_kbps.second)}}}}}};
std::unique_ptr<VideoCodecStats> stats = RunEncodeTest(
codec_type, codec_impl, video_info, frame_settings, num_frames,
/*save_codec_input=*/false, /*save_codec_output=*/false);
VideoCodecStats::Stream stream;
if (stats != nullptr) {
std::vector<VideoCodecStats::Frame> frames =
stats->Slice(VideoCodecStats::Filter{.first_frame = first_frame});
SetTargetRates(frame_settings, frames);
stream = stats->Aggregate(frames);
EXPECT_NEAR(stream.bitrate_mismatch_pct.GetAverage(), 0, 10);
EXPECT_NEAR(stream.framerate_mismatch_pct.GetAverage(), 0, 10);
}
stream.LogMetrics(
GetGlobalMetricsLogger(),
::testing::UnitTest::GetInstance()->current_test_info()->name(),
/*metadata=*/
{{"codec_type", codec_type},
{"codec_impl", codec_impl},
{"video_name", video_info.name},
{"rate_profile", std::to_string(bitrate_kbps.first) + "," +
std::to_string(bitrate_kbps.second)}});
}
INSTANTIATE_TEST_SUITE_P(All,
BitrateAdaptationTest,
Combine(Values("AV1", "VP9", "VP8", "H264", "H265"),
#if defined(WEBRTC_ANDROID)
Values("builtin", "mediacodec"),
#else
Values("builtin"),
#endif
Values(kFourPeople_1280x720_30),
Values(std::pair(1024, 512),
std::pair(512, 1024))),
BitrateAdaptationTest::TestParamsToString);
class FramerateAdaptationTest
: public ::testing::TestWithParam<std::tuple</*codec_type=*/std::string,
/*codec_impl=*/std::string,
VideoInfo,
std::pair<double, double>>> {
public:
static std::string TestParamsToString(
const ::testing::TestParamInfo<FramerateAdaptationTest::ParamType>&
info) {
auto [codec_type, codec_impl, video_info, framerate_fps] = info.param;
return std::string(
codec_type + codec_impl + video_info.name +
std::to_string(static_cast<int>(1000 * framerate_fps.first)) + "mhz" +
std::to_string(static_cast<int>(1000 * framerate_fps.second)) + "mhz");
}
};
TEST_P(FramerateAdaptationTest, FramerateAdaptation) {
auto [codec_type, codec_impl, video_info, framerate_fps] = GetParam();
int duration_s = 10; // Duration of fixed rate interval.
int first_frame = static_cast<int>(duration_s * framerate_fps.first);
int num_frames = static_cast<int>(
duration_s * (framerate_fps.first + framerate_fps.second));
std::map<int, EncodingSettings> frame_settings = {
{0,
{.layer_settings = {{LayerId{.spatial_idx = 0, .temporal_idx = 0},
{.resolution = {.width = 640, .height = 360},
.framerate = Frequency::MilliHertz(
1000 * framerate_fps.first),
.bitrate = DataRate::KilobitsPerSec(512)}}}}},
{first_frame,
{.layer_settings = {
{LayerId{.spatial_idx = 0, .temporal_idx = 0},
{.resolution = {.width = 640, .height = 360},
.framerate = Frequency::MilliHertz(1000 * framerate_fps.second),
.bitrate = DataRate::KilobitsPerSec(512)}}}}}};
std::unique_ptr<VideoCodecStats> stats = RunEncodeTest(
codec_type, codec_impl, video_info, frame_settings, num_frames,
/*save_codec_input=*/false, /*save_codec_output=*/false);
VideoCodecStats::Stream stream;
if (stats != nullptr) {
std::vector<VideoCodecStats::Frame> frames =
stats->Slice(VideoCodecStats::Filter{.first_frame = first_frame});
SetTargetRates(frame_settings, frames);
stream = stats->Aggregate(frames);
EXPECT_NEAR(stream.bitrate_mismatch_pct.GetAverage(), 0, 10);
EXPECT_NEAR(stream.framerate_mismatch_pct.GetAverage(), 0, 10);
}
stream.LogMetrics(
GetGlobalMetricsLogger(),
::testing::UnitTest::GetInstance()->current_test_info()->name(),
/*metadata=*/
{{"codec_type", codec_type},
{"codec_impl", codec_impl},
{"video_name", video_info.name},
{"rate_profile", std::to_string(framerate_fps.first) + "," +
std::to_string(framerate_fps.second)}});
}
INSTANTIATE_TEST_SUITE_P(All,
FramerateAdaptationTest,
Combine(Values("AV1", "VP9", "VP8", "H264", "H265"),
#if defined(WEBRTC_ANDROID)
Values("builtin", "mediacodec"),
#else
Values("builtin"),
#endif
Values(kFourPeople_1280x720_30),
Values(std::pair(30, 15), std::pair(15, 30))),
FramerateAdaptationTest::TestParamsToString);
} // namespace test
} // namespace webrtc