Add helper class for determining ACD filter settings.

Further, add use of it in libvpx_vp8_encoder and with tuning for keyframes and lower bound of std_dev = 1.25 to work around some edge cases. Plus some minor cleanup.

Bug: webrtc:358039777
Change-Id: I6f624a6a8c7ccfe2fe656e4c089c225296f0264f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/370061
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43513}
This commit is contained in:
Erik Språng 2024-12-09 10:25:17 +01:00 committed by WebRTC LUCI CQ
parent 768f78f097
commit 4c56c9ff9a
7 changed files with 517 additions and 14 deletions

View File

@ -383,6 +383,8 @@ rtc_library("video_coding_utility") {
sources = [
"utility/bandwidth_quality_scaler.cc",
"utility/bandwidth_quality_scaler.h",
"utility/corruption_detection_settings_generator.cc",
"utility/corruption_detection_settings_generator.h",
"utility/decoded_frames_history.cc",
"utility/decoded_frames_history.h",
"utility/frame_dropper.cc",
@ -419,6 +421,7 @@ rtc_library("video_coding_utility") {
"../../api/environment",
"../../api/units:data_rate",
"../../api/units:time_delta",
"../../api/video:corruption_detection_filter_settings",
"../../api/video:encoded_frame",
"../../api/video:encoded_image",
"../../api/video:video_adaptation",
@ -454,6 +457,7 @@ rtc_library("video_coding_utility") {
"svc:scalability_mode_util",
"//third_party/abseil-cpp/absl/numeric:bits",
"//third_party/abseil-cpp/absl/strings:string_view",
"//third_party/abseil-cpp/absl/types:variant",
]
}
@ -1133,6 +1137,7 @@ if (rtc_include_tests) {
"rtp_vp8_ref_finder_unittest.cc",
"rtp_vp9_ref_finder_unittest.cc",
"utility/bandwidth_quality_scaler_unittest.cc",
"utility/corruption_detection_settings_generator_unittest.cc",
"utility/decoded_frames_history_unittest.cc",
"utility/frame_dropper_unittest.cc",
"utility/framerate_controller_deprecated_unittest.cc",

View File

@ -742,6 +742,31 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst,
UpdateVpxConfiguration(stream_idx);
}
corruption_detection_settings_generator_ =
std::make_unique<CorruptionDetectionSettingsGenerator>(
CorruptionDetectionSettingsGenerator::ExponentialFunctionParameters{
.scale = 0.006,
.exponent_factor = 0.01857465,
.exponent_offset = -4.26470513},
CorruptionDetectionSettingsGenerator::ErrorThresholds{.luma = 5,
.chroma = 6},
// On large changes, increase error threshold by one and std_dev
// by 2.0. Trigger on qp changes larger than 30, and fade down the
// adjusted value over 4 * num_temporal_layers to allow the base layer
// to converge somewhat. Set a minim filter size of 1.25 since some
// outlier pixels deviate a bit from truth even at very low QP,
// seeminly by bleeding into neighbours.
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{
.max_qp = 127,
.keyframe_threshold_offset = 1,
.keyframe_stddev_offset = 2.0,
.keyframe_offset_duration_frames =
std::max(1,
SimulcastUtility::NumberOfTemporalLayers(*inst, 0)) *
4,
.large_qp_change_threshold = 30,
.std_dev_lower_bound = 1.25});
return InitAndSetControlSettings();
}
@ -1261,6 +1286,12 @@ int LibvpxVp8Encoder::GetEncodedPartitions(const VideoFrame& input_image,
last_encoder_output_time_[stream_idx] =
Timestamp::Micros(input_image.timestamp_us());
encoded_images_[encoder_idx].set_corruption_detection_filter_settings(
corruption_detection_settings_generator_->OnFrame(
encoded_images_[encoder_idx].FrameType() ==
VideoFrameType::kVideoFrameKey,
qp_128));
encoded_complete_callback_->OnEncodedImage(encoded_images_[encoder_idx],
&codec_specific);
const size_t steady_state_size = SteadyStateSize(

View File

@ -29,6 +29,7 @@
#include "modules/video_coding/codecs/interface/libvpx_interface.h"
#include "modules/video_coding/codecs/vp8/include/vp8.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "modules/video_coding/utility/corruption_detection_settings_generator.h"
#include "modules/video_coding/utility/framerate_controller_deprecated.h"
#include "modules/video_coding/utility/vp8_constants.h"
#include "rtc_base/experiments/encoder_info_settings.h"
@ -148,6 +149,9 @@ class LibvpxVp8Encoder : public VideoEncoder {
std::optional<TimeDelta> max_frame_drop_interval_;
bool android_specific_threading_settings_;
std::unique_ptr<CorruptionDetectionSettingsGenerator>
corruption_detection_settings_generator_;
};
} // namespace webrtc

View File

@ -44,6 +44,8 @@ using ::testing::Field;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Values;
using ::testing::WithParamInterface;
using EncoderInfo = webrtc::VideoEncoder::EncoderInfo;
using FramerateFractions =
absl::InlinedVector<uint8_t, webrtc::kMaxTemporalStreams>;
@ -633,6 +635,19 @@ TEST_F(TestVp8Impl, KeepsTimestampOnReencode) {
encoder.Encode(NextInputFrame(), &delta_frame);
}
TEST_F(TestVp8Impl, PopulatesFilterSettings) {
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
encoder_->InitEncode(&codec_settings_, kSettings));
EncodedImage encoded_frame;
CodecSpecificInfo codec_specific_info;
EncodeAndWaitForFrame(NextInputFrame(), &encoded_frame, &codec_specific_info);
ASSERT_TRUE(encoded_frame.corruption_detection_filter_settings().has_value());
EXPECT_GT(encoded_frame.corruption_detection_filter_settings()->std_dev, 0.0);
}
TEST(LibvpxVp8EncoderTest, GetEncoderInfoReturnsStaticInformation) {
auto* const vpx = new NiceMock<MockLibvpxInterface>();
LibvpxVp8Encoder encoder(CreateEnvironment(), {}, absl::WrapUnique(vpx));
@ -678,7 +693,7 @@ TEST(LibvpxVp8EncoderTest, ResolutionBitrateLimitsFromFieldTrial) {
EXPECT_THAT(
encoder.GetEncoderInfo().resolution_bitrate_limits,
::testing::ElementsAre(
ElementsAre(
VideoEncoder::ResolutionBitrateLimits{123, 11000, 44000, 77000},
VideoEncoder::ResolutionBitrateLimits{456, 22000, 55000, 88000},
VideoEncoder::ResolutionBitrateLimits{789, 33000, 66000, 99000}));
@ -719,7 +734,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationNoLayers) {
FramerateFractions(1, EncoderInfo::kMaxFramerateFraction)};
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
}
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationTwoTemporalLayers) {
@ -737,7 +752,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationTwoTemporalLayers) {
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
}
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationThreeTemporalLayers) {
@ -756,7 +771,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationThreeTemporalLayers) {
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
}
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationScreenshareLayers) {
@ -777,7 +792,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationScreenshareLayers) {
// Expect empty vector, since this mode doesn't have a fixed framerate.
FramerateFractions expected_fps_allocation[kMaxSpatialLayers];
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
}
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
@ -809,7 +824,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
expected_fps_allocation[1] = expected_fps_allocation[0];
expected_fps_allocation[2] = expected_fps_allocation[0];
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
// Release encoder and re-init without temporal layers.
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
@ -818,7 +833,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
FramerateFractions default_fps_fraction[kMaxSpatialLayers];
default_fps_fraction[0].push_back(EncoderInfo::kMaxFramerateFraction);
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(default_fps_fraction));
ElementsAreArray(default_fps_fraction));
for (int i = 0; i < codec_settings_.numberOfSimulcastStreams; ++i) {
codec_settings_.simulcastStream[i].numberOfTemporalLayers = 1;
@ -831,13 +846,12 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
expected_fps_allocation[i].push_back(EncoderInfo::kMaxFramerateFraction);
}
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
::testing::ElementsAreArray(expected_fps_allocation));
ElementsAreArray(expected_fps_allocation));
}
class TestVp8ImplWithMaxFrameDropTrial
: public TestVp8Impl,
public ::testing::WithParamInterface<
std::tuple<std::string, TimeDelta, TimeDelta>> {
public WithParamInterface<std::tuple<std::string, TimeDelta, TimeDelta>> {
public:
TestVp8ImplWithMaxFrameDropTrial()
: TestVp8Impl(), trials_(std::get<0>(GetParam())) {}
@ -960,7 +974,7 @@ TEST_P(TestVp8ImplWithMaxFrameDropTrial, EnforcesMaxFrameDropInterval) {
INSTANTIATE_TEST_SUITE_P(
All,
TestVp8ImplWithMaxFrameDropTrial,
::testing::Values(
Values(
// Tuple of {
// trial string,
// configured max frame interval,
@ -976,7 +990,7 @@ INSTANTIATE_TEST_SUITE_P(
class TestVp8ImplForPixelFormat
: public TestVp8Impl,
public ::testing::WithParamInterface<VideoFrameBuffer::Type> {
public WithParamInterface<VideoFrameBuffer::Type> {
public:
TestVp8ImplForPixelFormat() : TestVp8Impl(), mappable_type_(GetParam()) {}
@ -1049,7 +1063,7 @@ TEST_P(TestVp8ImplForPixelFormat, EncodeNativeFrameSimulcast) {
INSTANTIATE_TEST_SUITE_P(All,
TestVp8ImplForPixelFormat,
::testing::Values(VideoFrameBuffer::Type::kI420,
Values(VideoFrameBuffer::Type::kI420,
VideoFrameBuffer::Type::kNV12));
} // namespace webrtc

View File

@ -0,0 +1,128 @@
/*
* 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 "modules/video_coding/utility/corruption_detection_settings_generator.h"
#include <algorithm>
#include <cmath>
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
void ValidateParameters(
const CorruptionDetectionSettingsGenerator::ErrorThresholds&
default_error_thresholds,
const CorruptionDetectionSettingsGenerator::TransientParameters&
transient_params) {
int offset = transient_params.keyframe_threshold_offset;
RTC_DCHECK_GE(offset, 0);
RTC_DCHECK_LE(offset, 15);
RTC_DCHECK_GE(default_error_thresholds.chroma, 0);
RTC_DCHECK_LE(default_error_thresholds.chroma + offset, 15);
RTC_DCHECK_GE(default_error_thresholds.luma, 0);
RTC_DCHECK_LE(default_error_thresholds.luma + offset, 15);
RTC_DCHECK_GE(transient_params.max_qp, 0);
RTC_DCHECK_GE(transient_params.keyframe_stddev_offset, 0.0);
RTC_DCHECK_GE(transient_params.keyframe_offset_duration_frames, 0);
RTC_DCHECK_GE(transient_params.large_qp_change_threshold, 0);
RTC_DCHECK_LE(transient_params.large_qp_change_threshold,
transient_params.max_qp);
RTC_DCHECK_GE(transient_params.std_dev_lower_bound, 0.0);
RTC_DCHECK_LE(transient_params.std_dev_lower_bound, 40.0);
}
} // namespace
CorruptionDetectionSettingsGenerator::CorruptionDetectionSettingsGenerator(
const RationalFunctionParameters& function_params,
const ErrorThresholds& default_error_thresholds,
const TransientParameters& transient_params)
: function_params_(function_params),
error_thresholds_(default_error_thresholds),
transient_params_(transient_params),
frames_since_keyframe_(0) {
ValidateParameters(default_error_thresholds, transient_params);
}
CorruptionDetectionSettingsGenerator::CorruptionDetectionSettingsGenerator(
const ExponentialFunctionParameters& function_params,
const ErrorThresholds& default_error_thresholds,
const TransientParameters& transient_params)
: function_params_(function_params),
error_thresholds_(default_error_thresholds),
transient_params_(transient_params),
frames_since_keyframe_(0) {
ValidateParameters(default_error_thresholds, transient_params);
}
CorruptionDetectionFilterSettings CorruptionDetectionSettingsGenerator::OnFrame(
bool is_keyframe,
int qp) {
double std_dev = CalculateStdDev(qp);
int y_err = error_thresholds_.luma;
int uv_err = error_thresholds_.chroma;
if (is_keyframe || (transient_params_.large_qp_change_threshold > 0 &&
std::abs(previous_qp_.value_or(qp) - qp) >=
transient_params_.large_qp_change_threshold)) {
frames_since_keyframe_ = 0;
}
previous_qp_ = qp;
if (frames_since_keyframe_ <=
transient_params_.keyframe_offset_duration_frames) {
// The progress, from the start at the keyframe at 0.0 to completely back to
// normal at 1.0.
double progress = transient_params_.keyframe_offset_duration_frames == 0
? 1.0
: (static_cast<double>(frames_since_keyframe_) /
transient_params_.keyframe_offset_duration_frames);
double adjusted_std_dev =
std::min(std_dev + transient_params_.keyframe_stddev_offset, 40.0);
double adjusted_y_err =
std::min(y_err + transient_params_.keyframe_threshold_offset, 15);
double adjusted_uv_err =
std::min(uv_err + transient_params_.keyframe_threshold_offset, 15);
std_dev = ((1.0 - progress) * adjusted_std_dev) + (progress * std_dev);
y_err = static_cast<int>(((1.0 - progress) * adjusted_y_err) +
(progress * y_err) + 0.5);
uv_err = static_cast<int>(((1.0 - progress) * adjusted_uv_err) +
(progress * uv_err) + 0.5);
}
++frames_since_keyframe_;
std_dev = std::max(std_dev, transient_params_.std_dev_lower_bound);
std_dev = std::min(std_dev, 40.0);
return CorruptionDetectionFilterSettings{.std_dev = std_dev,
.luma_error_threshold = y_err,
.chroma_error_threshold = uv_err};
}
double CorruptionDetectionSettingsGenerator::CalculateStdDev(int qp) const {
if (absl::holds_alternative<RationalFunctionParameters>(function_params_)) {
const auto& params =
absl::get<RationalFunctionParameters>(function_params_);
return (qp * params.numerator_factor) / (qp + params.denumerator_term) +
params.offset;
}
RTC_DCHECK(
absl::holds_alternative<ExponentialFunctionParameters>(function_params_));
const auto& params =
absl::get<ExponentialFunctionParameters>(function_params_);
return params.scale *
std::exp(params.exponent_factor * qp - params.exponent_offset);
}
} // namespace webrtc

View File

@ -0,0 +1,92 @@
/*
* 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.
*/
#ifndef MODULES_VIDEO_CODING_UTILITY_CORRUPTION_DETECTION_SETTINGS_GENERATOR_H_
#define MODULES_VIDEO_CODING_UTILITY_CORRUPTION_DETECTION_SETTINGS_GENERATOR_H_
#include <optional>
#include "absl/types/variant.h"
#include "api/video/corruption_detection_filter_settings.h"
namespace webrtc {
class CorruptionDetectionSettingsGenerator {
public:
// A struct with the parameters for a ration function used to determine the
// standard deviation as function of the qp. It has the form f(qp) =
// (-numerator_factor * qp) / (denumerator_term + qp) + offset.
struct RationalFunctionParameters {
double numerator_factor = 0.0;
double denumerator_term = 0.0;
double offset = 0.0;
};
// A struct with the parameters for an exponential function used to determine
// the standard deviation as a function of the qp. It has the form f(qp) =
// scale * std::exp(exponent_factor * qp - exponent_offset).
struct ExponentialFunctionParameters {
double scale = 0.0;
double exponent_factor = 0.0;
double exponent_offset = 0.0;
};
// Allowed error thresholds for luma (Y) and chroma (UV) channels.
struct ErrorThresholds {
int luma = 0;
int chroma = 0;
};
// Settings relating to transient events like key-frames.
struct TransientParameters {
// The max qp for the codec in use (e.g. 255 for AV1).
int max_qp = 0;
// Temporary increase to error thresholds on keyframes.
int keyframe_threshold_offset = 0;
// Temporary increase to std dev on keyframes.
double keyframe_stddev_offset = 0.0;
// Fade-out time (in frames) for temporary keyframe offsets.
int keyframe_offset_duration_frames = 0;
// How many QP points count as a "large change", or 0 to disable.
// A large change will trigger the same compensation as a keyframe.
int large_qp_change_threshold = 0;
// Don't use a filter kernel smaller than this.
double std_dev_lower_bound = 0.0;
};
CorruptionDetectionSettingsGenerator(
const RationalFunctionParameters& function_params,
const ErrorThresholds& default_error_thresholds,
const TransientParameters& transient_params);
CorruptionDetectionSettingsGenerator(
const ExponentialFunctionParameters& function_params,
const ErrorThresholds& default_error_thresholds,
const TransientParameters& transient_params);
CorruptionDetectionFilterSettings OnFrame(bool is_keyframe, int qp);
private:
double CalculateStdDev(int qp) const;
const absl::variant<RationalFunctionParameters, ExponentialFunctionParameters>
function_params_;
const ErrorThresholds error_thresholds_;
const TransientParameters transient_params_;
int frames_since_keyframe_;
std::optional<int> previous_qp_;
};
} // namespace webrtc
#endif // MODULES_VIDEO_CODING_UTILITY_CORRUPTION_DETECTION_SETTINGS_GENERATOR_H_

View File

@ -0,0 +1,229 @@
/*
* 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 "modules/video_coding/utility/corruption_detection_settings_generator.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::AllOf;
using ::testing::DoubleEq;
using ::testing::DoubleNear;
using ::testing::Eq;
using ::testing::Field;
namespace webrtc {
TEST(CorruptionDetectionSettingsGenerator, ExponentialFunctionStdDev) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::ExponentialFunctionParameters{
.scale = 0.006,
.exponent_factor = 0.01857465,
.exponent_offset = -4.26470513},
CorruptionDetectionSettingsGenerator::ErrorThresholds{},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{});
// 0.006 * e^(0.01857465 * 20 + 4.26470513) ~= 0.612
CorruptionDetectionFilterSettings settings =
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/20);
EXPECT_THAT(settings.std_dev, DoubleNear(0.612, 0.01));
// 0.006 * e^(0.01857465 * 20 + 4.26470513) ~= 1.886
settings = settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/80);
EXPECT_THAT(settings.std_dev, DoubleNear(1.886, 0.01));
}
TEST(CorruptionDetectionSettingsGenerator, ExponentialFunctionThresholds) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::ExponentialFunctionParameters{
.scale = 0.006,
.exponent_factor = 0.01857465,
.exponent_offset = -4.26470513},
CorruptionDetectionSettingsGenerator::ErrorThresholds{.luma = 5,
.chroma = 6},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{});
CorruptionDetectionFilterSettings settings =
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/20);
EXPECT_EQ(settings.chroma_error_threshold, 6);
EXPECT_EQ(settings.luma_error_threshold, 5);
}
TEST(CorruptionDetectionSettingsGenerator, RationalFunctionStdDev) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = -5.5, .denumerator_term = -97, .offset = -1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{});
// (20 * -5.5) / (20 - 97) - 1 ~= 0.429
CorruptionDetectionFilterSettings settings =
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/20);
EXPECT_THAT(settings.std_dev, DoubleNear(0.429, 0.01));
// (40 * -5.5) / (40 - 97) - 1 ~= 2.860
settings = settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/40);
EXPECT_THAT(settings.std_dev, DoubleNear(2.860, 0.01));
}
TEST(CorruptionDetectionSettingsGenerator, RationalFunctionThresholds) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = -5.5, .denumerator_term = -97, .offset = -1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{.luma = 5,
.chroma = 6},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{});
CorruptionDetectionFilterSettings settings =
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/20);
EXPECT_EQ(settings.chroma_error_threshold, 6);
EXPECT_EQ(settings.luma_error_threshold, 5);
}
TEST(CorruptionDetectionSettingsGenerator, TransientStdDevOffset) {
CorruptionDetectionSettingsGenerator settings_generator(
// (1 * qp) / (qp - 0) + 1 = 2, for all values of qp.
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = 1, .denumerator_term = 0, .offset = 1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{},
// Two frames with adjusted settings, including the keyframe.
// Adjust the keyframe std_dev by 2.
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{
.keyframe_stddev_offset = 2.0,
.keyframe_offset_duration_frames = 2,
});
EXPECT_THAT(settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev,
DoubleNear(4.0, 0.001)));
// Second frame has std_dev ofset interpolated halfway between keyframe
// (2.0 + 2.0) and default (2.0) => 3.0
EXPECT_THAT(settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev,
DoubleNear(3.0, 0.001)));
EXPECT_THAT(settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev,
DoubleNear(2.0, 0.001)));
EXPECT_THAT(settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev,
DoubleNear(2.0, 0.001)));
}
TEST(CorruptionDetectionSettingsGenerator, TransientThresholdOffsets) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = 1, .denumerator_term = 0, .offset = 1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{.luma = 2,
.chroma = 3},
// Two frames with adjusted settings, including the keyframe.
// Adjust the error thresholds by 2.
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{
.keyframe_threshold_offset = 2,
.keyframe_offset_duration_frames = 2,
});
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/1),
AllOf(Field(&CorruptionDetectionFilterSettings::chroma_error_threshold,
Eq(5)),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold,
Eq(4))));
// Second frame has offset interpolated halfway between keyframe and default.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
AllOf(Field(&CorruptionDetectionFilterSettings::chroma_error_threshold,
Eq(4)),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold,
Eq(3))));
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
AllOf(Field(&CorruptionDetectionFilterSettings::chroma_error_threshold,
Eq(3)),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold,
Eq(2))));
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/1),
AllOf(Field(&CorruptionDetectionFilterSettings::chroma_error_threshold,
Eq(3)),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold,
Eq(2))));
}
TEST(CorruptionDetectionSettingsGenerator, StdDevUpperBound) {
CorruptionDetectionSettingsGenerator settings_generator(
// (1 * qp) / (qp - 0) + 41 = 42, for all values of qp.
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = 1, .denumerator_term = 0, .offset = 41},
CorruptionDetectionSettingsGenerator::ErrorThresholds{},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{});
// `std_dev` capped at max 40.0, which is the limit for the protocol.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev, DoubleEq(40.0)));
}
TEST(CorruptionDetectionSettingsGenerator, StdDevLowerBound) {
CorruptionDetectionSettingsGenerator settings_generator(
// (1 * qp) / (qp - 0) + 1 = 2, for all values of qp.
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = 1, .denumerator_term = 0, .offset = 1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{},
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{
.std_dev_lower_bound = 5.0});
// `std_dev` capped at lower bound of 5.0.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/1),
Field(&CorruptionDetectionFilterSettings::std_dev, DoubleEq(5.0)));
}
TEST(CorruptionDetectionSettingsGenerator, TreatsLargeQpChangeAsKeyFrame) {
CorruptionDetectionSettingsGenerator settings_generator(
CorruptionDetectionSettingsGenerator::RationalFunctionParameters{
.numerator_factor = 1, .denumerator_term = 0, .offset = 1},
CorruptionDetectionSettingsGenerator::ErrorThresholds{.luma = 2,
.chroma = 3},
// Two frames with adjusted settings, including the keyframe.
// Adjust the error thresholds by 2.
webrtc::CorruptionDetectionSettingsGenerator::TransientParameters{
.max_qp = 100,
.keyframe_threshold_offset = 2,
.keyframe_offset_duration_frames = 1,
.large_qp_change_threshold = 20});
// +2 offset due to keyframe.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/true, /*qp=*/10),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold, Eq(4)));
// Back to normal.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/10),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold, Eq(2)));
// Large change in qp, treat as keyframe => add +2 offset.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/30),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold, Eq(4)));
// Back to normal.
EXPECT_THAT(
settings_generator.OnFrame(/*is_keyframe=*/false, /*qp=*/30),
Field(&CorruptionDetectionFilterSettings::luma_error_threshold, Eq(2)));
}
} // namespace webrtc