diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc')
-rw-r--r-- | third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc | 811 |
1 files changed, 811 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc b/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc new file mode 100644 index 0000000000..1c8fe97e84 --- /dev/null +++ b/third_party/libwebrtc/modules/video_coding/codecs/test/video_codec_test.cc @@ -0,0 +1,811 @@ +/* + * 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/flags/flag.h" +#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/test_flags.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 false; + } + + for (auto [layer_id, layer] : layer_settings) { + const auto& other_layer = other.layer_settings.at(layer_id); + if (layer.resolution != other_layer.resolution) { + return false; + } + } + + return true; + } + + 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 false; + } + } + + return true; + } +}; + +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 { + { + MutexLock lock(&mutex_); + 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 { + MutexLock lock(&mutex_); + auto cb = callbacks_.find(encoded_image.RtpTimestamp()); + 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_ RTC_GUARDED_BY(mutex_); + Mutex mutex_; +}; + +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 { + { + MutexLock lock(&mutex_); + callbacks_[frame.RtpTimestamp()] = std::move(callback); + } + + decoder_->Decode(frame, /*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 { + MutexLock lock(&mutex_); + 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_ RTC_GUARDED_BY(mutex_); + Mutex mutex_; +}; + +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); + if (absl::GetFlag(FLAGS_webrtc_quick_perf_test)) { + 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, 28), + 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, 32), + std::make_tuple(1280, 720, 30, 512, 34), + 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); + if (absl::GetFlag(FLAGS_webrtc_quick_perf_test)) { + 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); + if (absl::GetFlag(FLAGS_webrtc_quick_perf_test)) { + 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 |