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:
parent
768f78f097
commit
4c56c9ff9a
@ -383,6 +383,8 @@ rtc_library("video_coding_utility") {
|
|||||||
sources = [
|
sources = [
|
||||||
"utility/bandwidth_quality_scaler.cc",
|
"utility/bandwidth_quality_scaler.cc",
|
||||||
"utility/bandwidth_quality_scaler.h",
|
"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.cc",
|
||||||
"utility/decoded_frames_history.h",
|
"utility/decoded_frames_history.h",
|
||||||
"utility/frame_dropper.cc",
|
"utility/frame_dropper.cc",
|
||||||
@ -419,6 +421,7 @@ rtc_library("video_coding_utility") {
|
|||||||
"../../api/environment",
|
"../../api/environment",
|
||||||
"../../api/units:data_rate",
|
"../../api/units:data_rate",
|
||||||
"../../api/units:time_delta",
|
"../../api/units:time_delta",
|
||||||
|
"../../api/video:corruption_detection_filter_settings",
|
||||||
"../../api/video:encoded_frame",
|
"../../api/video:encoded_frame",
|
||||||
"../../api/video:encoded_image",
|
"../../api/video:encoded_image",
|
||||||
"../../api/video:video_adaptation",
|
"../../api/video:video_adaptation",
|
||||||
@ -454,6 +457,7 @@ rtc_library("video_coding_utility") {
|
|||||||
"svc:scalability_mode_util",
|
"svc:scalability_mode_util",
|
||||||
"//third_party/abseil-cpp/absl/numeric:bits",
|
"//third_party/abseil-cpp/absl/numeric:bits",
|
||||||
"//third_party/abseil-cpp/absl/strings:string_view",
|
"//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_vp8_ref_finder_unittest.cc",
|
||||||
"rtp_vp9_ref_finder_unittest.cc",
|
"rtp_vp9_ref_finder_unittest.cc",
|
||||||
"utility/bandwidth_quality_scaler_unittest.cc",
|
"utility/bandwidth_quality_scaler_unittest.cc",
|
||||||
|
"utility/corruption_detection_settings_generator_unittest.cc",
|
||||||
"utility/decoded_frames_history_unittest.cc",
|
"utility/decoded_frames_history_unittest.cc",
|
||||||
"utility/frame_dropper_unittest.cc",
|
"utility/frame_dropper_unittest.cc",
|
||||||
"utility/framerate_controller_deprecated_unittest.cc",
|
"utility/framerate_controller_deprecated_unittest.cc",
|
||||||
|
|||||||
@ -742,6 +742,31 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst,
|
|||||||
UpdateVpxConfiguration(stream_idx);
|
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();
|
return InitAndSetControlSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1261,6 +1286,12 @@ int LibvpxVp8Encoder::GetEncodedPartitions(const VideoFrame& input_image,
|
|||||||
last_encoder_output_time_[stream_idx] =
|
last_encoder_output_time_[stream_idx] =
|
||||||
Timestamp::Micros(input_image.timestamp_us());
|
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],
|
encoded_complete_callback_->OnEncodedImage(encoded_images_[encoder_idx],
|
||||||
&codec_specific);
|
&codec_specific);
|
||||||
const size_t steady_state_size = SteadyStateSize(
|
const size_t steady_state_size = SteadyStateSize(
|
||||||
|
|||||||
@ -29,6 +29,7 @@
|
|||||||
#include "modules/video_coding/codecs/interface/libvpx_interface.h"
|
#include "modules/video_coding/codecs/interface/libvpx_interface.h"
|
||||||
#include "modules/video_coding/codecs/vp8/include/vp8.h"
|
#include "modules/video_coding/codecs/vp8/include/vp8.h"
|
||||||
#include "modules/video_coding/include/video_codec_interface.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/framerate_controller_deprecated.h"
|
||||||
#include "modules/video_coding/utility/vp8_constants.h"
|
#include "modules/video_coding/utility/vp8_constants.h"
|
||||||
#include "rtc_base/experiments/encoder_info_settings.h"
|
#include "rtc_base/experiments/encoder_info_settings.h"
|
||||||
@ -148,6 +149,9 @@ class LibvpxVp8Encoder : public VideoEncoder {
|
|||||||
std::optional<TimeDelta> max_frame_drop_interval_;
|
std::optional<TimeDelta> max_frame_drop_interval_;
|
||||||
|
|
||||||
bool android_specific_threading_settings_;
|
bool android_specific_threading_settings_;
|
||||||
|
|
||||||
|
std::unique_ptr<CorruptionDetectionSettingsGenerator>
|
||||||
|
corruption_detection_settings_generator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -44,6 +44,8 @@ using ::testing::Field;
|
|||||||
using ::testing::Invoke;
|
using ::testing::Invoke;
|
||||||
using ::testing::NiceMock;
|
using ::testing::NiceMock;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
using ::testing::Values;
|
||||||
|
using ::testing::WithParamInterface;
|
||||||
using EncoderInfo = webrtc::VideoEncoder::EncoderInfo;
|
using EncoderInfo = webrtc::VideoEncoder::EncoderInfo;
|
||||||
using FramerateFractions =
|
using FramerateFractions =
|
||||||
absl::InlinedVector<uint8_t, webrtc::kMaxTemporalStreams>;
|
absl::InlinedVector<uint8_t, webrtc::kMaxTemporalStreams>;
|
||||||
@ -633,6 +635,19 @@ TEST_F(TestVp8Impl, KeepsTimestampOnReencode) {
|
|||||||
encoder.Encode(NextInputFrame(), &delta_frame);
|
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) {
|
TEST(LibvpxVp8EncoderTest, GetEncoderInfoReturnsStaticInformation) {
|
||||||
auto* const vpx = new NiceMock<MockLibvpxInterface>();
|
auto* const vpx = new NiceMock<MockLibvpxInterface>();
|
||||||
LibvpxVp8Encoder encoder(CreateEnvironment(), {}, absl::WrapUnique(vpx));
|
LibvpxVp8Encoder encoder(CreateEnvironment(), {}, absl::WrapUnique(vpx));
|
||||||
@ -678,7 +693,7 @@ TEST(LibvpxVp8EncoderTest, ResolutionBitrateLimitsFromFieldTrial) {
|
|||||||
|
|
||||||
EXPECT_THAT(
|
EXPECT_THAT(
|
||||||
encoder.GetEncoderInfo().resolution_bitrate_limits,
|
encoder.GetEncoderInfo().resolution_bitrate_limits,
|
||||||
::testing::ElementsAre(
|
ElementsAre(
|
||||||
VideoEncoder::ResolutionBitrateLimits{123, 11000, 44000, 77000},
|
VideoEncoder::ResolutionBitrateLimits{123, 11000, 44000, 77000},
|
||||||
VideoEncoder::ResolutionBitrateLimits{456, 22000, 55000, 88000},
|
VideoEncoder::ResolutionBitrateLimits{456, 22000, 55000, 88000},
|
||||||
VideoEncoder::ResolutionBitrateLimits{789, 33000, 66000, 99000}));
|
VideoEncoder::ResolutionBitrateLimits{789, 33000, 66000, 99000}));
|
||||||
@ -719,7 +734,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationNoLayers) {
|
|||||||
FramerateFractions(1, EncoderInfo::kMaxFramerateFraction)};
|
FramerateFractions(1, EncoderInfo::kMaxFramerateFraction)};
|
||||||
|
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationTwoTemporalLayers) {
|
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationTwoTemporalLayers) {
|
||||||
@ -737,7 +752,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationTwoTemporalLayers) {
|
|||||||
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
||||||
|
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationThreeTemporalLayers) {
|
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationThreeTemporalLayers) {
|
||||||
@ -756,7 +771,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationThreeTemporalLayers) {
|
|||||||
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
expected_fps_allocation[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
||||||
|
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationScreenshareLayers) {
|
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationScreenshareLayers) {
|
||||||
@ -777,7 +792,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationScreenshareLayers) {
|
|||||||
// Expect empty vector, since this mode doesn't have a fixed framerate.
|
// Expect empty vector, since this mode doesn't have a fixed framerate.
|
||||||
FramerateFractions expected_fps_allocation[kMaxSpatialLayers];
|
FramerateFractions expected_fps_allocation[kMaxSpatialLayers];
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
|
TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
|
||||||
@ -809,7 +824,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
|
|||||||
expected_fps_allocation[1] = expected_fps_allocation[0];
|
expected_fps_allocation[1] = expected_fps_allocation[0];
|
||||||
expected_fps_allocation[2] = expected_fps_allocation[0];
|
expected_fps_allocation[2] = expected_fps_allocation[0];
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
|
|
||||||
// Release encoder and re-init without temporal layers.
|
// Release encoder and re-init without temporal layers.
|
||||||
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
|
||||||
@ -818,7 +833,7 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
|
|||||||
FramerateFractions default_fps_fraction[kMaxSpatialLayers];
|
FramerateFractions default_fps_fraction[kMaxSpatialLayers];
|
||||||
default_fps_fraction[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
default_fps_fraction[0].push_back(EncoderInfo::kMaxFramerateFraction);
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(default_fps_fraction));
|
ElementsAreArray(default_fps_fraction));
|
||||||
|
|
||||||
for (int i = 0; i < codec_settings_.numberOfSimulcastStreams; ++i) {
|
for (int i = 0; i < codec_settings_.numberOfSimulcastStreams; ++i) {
|
||||||
codec_settings_.simulcastStream[i].numberOfTemporalLayers = 1;
|
codec_settings_.simulcastStream[i].numberOfTemporalLayers = 1;
|
||||||
@ -831,13 +846,12 @@ TEST_F(TestVp8Impl, GetEncoderInfoFpsAllocationSimulcastVideo) {
|
|||||||
expected_fps_allocation[i].push_back(EncoderInfo::kMaxFramerateFraction);
|
expected_fps_allocation[i].push_back(EncoderInfo::kMaxFramerateFraction);
|
||||||
}
|
}
|
||||||
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
EXPECT_THAT(encoder_->GetEncoderInfo().fps_allocation,
|
||||||
::testing::ElementsAreArray(expected_fps_allocation));
|
ElementsAreArray(expected_fps_allocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestVp8ImplWithMaxFrameDropTrial
|
class TestVp8ImplWithMaxFrameDropTrial
|
||||||
: public TestVp8Impl,
|
: public TestVp8Impl,
|
||||||
public ::testing::WithParamInterface<
|
public WithParamInterface<std::tuple<std::string, TimeDelta, TimeDelta>> {
|
||||||
std::tuple<std::string, TimeDelta, TimeDelta>> {
|
|
||||||
public:
|
public:
|
||||||
TestVp8ImplWithMaxFrameDropTrial()
|
TestVp8ImplWithMaxFrameDropTrial()
|
||||||
: TestVp8Impl(), trials_(std::get<0>(GetParam())) {}
|
: TestVp8Impl(), trials_(std::get<0>(GetParam())) {}
|
||||||
@ -960,7 +974,7 @@ TEST_P(TestVp8ImplWithMaxFrameDropTrial, EnforcesMaxFrameDropInterval) {
|
|||||||
INSTANTIATE_TEST_SUITE_P(
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
All,
|
All,
|
||||||
TestVp8ImplWithMaxFrameDropTrial,
|
TestVp8ImplWithMaxFrameDropTrial,
|
||||||
::testing::Values(
|
Values(
|
||||||
// Tuple of {
|
// Tuple of {
|
||||||
// trial string,
|
// trial string,
|
||||||
// configured max frame interval,
|
// configured max frame interval,
|
||||||
@ -976,7 +990,7 @@ INSTANTIATE_TEST_SUITE_P(
|
|||||||
|
|
||||||
class TestVp8ImplForPixelFormat
|
class TestVp8ImplForPixelFormat
|
||||||
: public TestVp8Impl,
|
: public TestVp8Impl,
|
||||||
public ::testing::WithParamInterface<VideoFrameBuffer::Type> {
|
public WithParamInterface<VideoFrameBuffer::Type> {
|
||||||
public:
|
public:
|
||||||
TestVp8ImplForPixelFormat() : TestVp8Impl(), mappable_type_(GetParam()) {}
|
TestVp8ImplForPixelFormat() : TestVp8Impl(), mappable_type_(GetParam()) {}
|
||||||
|
|
||||||
@ -1049,7 +1063,7 @@ TEST_P(TestVp8ImplForPixelFormat, EncodeNativeFrameSimulcast) {
|
|||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(All,
|
INSTANTIATE_TEST_SUITE_P(All,
|
||||||
TestVp8ImplForPixelFormat,
|
TestVp8ImplForPixelFormat,
|
||||||
::testing::Values(VideoFrameBuffer::Type::kI420,
|
Values(VideoFrameBuffer::Type::kI420,
|
||||||
VideoFrameBuffer::Type::kNV12));
|
VideoFrameBuffer::Type::kNV12));
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -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
|
||||||
@ -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_
|
||||||
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user