Add helper for comparing FrameInstrumentationData with a VideoFrame
Bug: webrtc:358039777
Change-Id: Ibe597160658dbc66aba427f4e30dade4d6fe56e2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/363701
Auto-Submit: Fanny Linderborg <linderborg@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43134}
diff --git a/video/corruption_detection/BUILD.gn b/video/corruption_detection/BUILD.gn
index d37e359..55ab651 100644
--- a/video/corruption_detection/BUILD.gn
+++ b/video/corruption_detection/BUILD.gn
@@ -21,6 +21,23 @@
]
}
+rtc_library("frame_instrumentation_evaluation") {
+ sources = [
+ "frame_instrumentation_evaluation.cc",
+ "frame_instrumentation_evaluation.h",
+ ]
+ deps = [
+ ":corruption_classifier",
+ ":halton_frame_sampler",
+ "../../api:array_view",
+ "../../api:scoped_refptr",
+ "../../api/video:video_frame",
+ "../../common_video:frame_instrumentation_data",
+ "../../rtc_base:checks",
+ "../../rtc_base:logging",
+ ]
+}
+
rtc_library("frame_instrumentation_generator") {
sources = [
"frame_instrumentation_generator.cc",
@@ -89,6 +106,18 @@
]
}
+ rtc_library("frame_instrumentation_evaluation_unittest") {
+ testonly = true
+ sources = [ "frame_instrumentation_evaluation_unittest.cc" ]
+ deps = [
+ ":frame_instrumentation_evaluation",
+ "../../api:scoped_refptr",
+ "../../api/video:video_frame",
+ "../../common_video:frame_instrumentation_data",
+ "../../test:test_support",
+ ]
+ }
+
rtc_library("frame_instrumentation_generator_unittest") {
testonly = true
sources = [ "frame_instrumentation_generator_unittest.cc" ]
@@ -139,6 +168,7 @@
sources = []
deps = [
":corruption_classifier_unittest",
+ ":frame_instrumentation_evaluation_unittest",
":frame_instrumentation_generator_unittest",
":generic_mapping_functions_unittest",
":halton_frame_sampler_unittest",
diff --git a/video/corruption_detection/frame_instrumentation_evaluation.cc b/video/corruption_detection/frame_instrumentation_evaluation.cc
new file mode 100644
index 0000000..f3367ad
--- /dev/null
+++ b/video/corruption_detection/frame_instrumentation_evaluation.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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 "video/corruption_detection/frame_instrumentation_evaluation.h"
+
+#include <cstddef>
+#include <optional>
+#include <vector>
+
+#include "api/array_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/video_frame.h"
+#include "api/video/video_frame_buffer.h"
+#include "common_video/frame_instrumentation_data.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "video/corruption_detection/corruption_classifier.h"
+#include "video/corruption_detection/halton_frame_sampler.h"
+
+namespace webrtc {
+
+namespace {
+
+std::vector<FilteredSample> ConvertSampleValuesToFilteredSamples(
+ rtc::ArrayView<const double> values,
+ rtc::ArrayView<const FilteredSample> samples) {
+ RTC_CHECK_EQ(values.size(), samples.size())
+ << "values and samples must have the same size";
+ std::vector<FilteredSample> filtered_samples;
+ filtered_samples.reserve(values.size());
+ for (size_t i = 0; i < values.size(); ++i) {
+ filtered_samples.push_back({.value = values[i], .plane = samples[i].plane});
+ }
+ return filtered_samples;
+}
+
+} // namespace
+
+std::optional<double> GetCorruptionScore(const FrameInstrumentationData& data,
+ const VideoFrame& frame) {
+ if (data.sample_values.empty()) {
+ RTC_LOG(LS_WARNING)
+ << "Samples are needed to calculate a corruption score.";
+ return std::nullopt;
+ }
+
+ scoped_refptr<I420BufferInterface> frame_buffer_as_i420 =
+ frame.video_frame_buffer()->ToI420();
+ if (!frame_buffer_as_i420) {
+ RTC_LOG(LS_ERROR) << "Failed to convert "
+ << VideoFrameBufferTypeToString(
+ frame.video_frame_buffer()->type())
+ << " image to I420";
+ return std::nullopt;
+ }
+
+ HaltonFrameSampler frame_sampler;
+ frame_sampler.SetCurrentIndex(data.sequence_index);
+ std::vector<HaltonFrameSampler::Coordinates> sample_coordinates =
+ frame_sampler.GetSampleCoordinatesForFrame(data.sample_values.size());
+ if (sample_coordinates.empty()) {
+ RTC_LOG(LS_ERROR) << "Failed to get sample coordinates for frame.";
+ return std::nullopt;
+ }
+
+ std::vector<FilteredSample> samples =
+ GetSampleValuesForFrame(frame_buffer_as_i420, sample_coordinates,
+ frame.width(), frame.height(), data.std_dev);
+ if (samples.empty()) {
+ RTC_LOG(LS_ERROR) << "Failed to get sample values for frame";
+ return std::nullopt;
+ }
+
+ std::vector<FilteredSample> data_samples =
+ ConvertSampleValuesToFilteredSamples(data.sample_values, samples);
+ if (data_samples.empty()) {
+ RTC_LOG(LS_ERROR) << "Failed to convert sample values to filtered samples";
+ return std::nullopt;
+ }
+
+ // TODO: bugs.webrtc.org/358039777 - Update before rollout. Which variant of
+ // classifier should we use? What input parameters should it have?
+ CorruptionClassifier classifier(2.5);
+
+ return classifier.CalculateCorruptionProbablility(
+ data_samples, samples, data.luma_error_threshold,
+ data.chroma_error_threshold);
+}
+
+} // namespace webrtc
diff --git a/video/corruption_detection/frame_instrumentation_evaluation.h b/video/corruption_detection/frame_instrumentation_evaluation.h
new file mode 100644
index 0000000..8bd3e1c
--- /dev/null
+++ b/video/corruption_detection/frame_instrumentation_evaluation.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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 VIDEO_CORRUPTION_DETECTION_FRAME_INSTRUMENTATION_EVALUATION_H_
+#define VIDEO_CORRUPTION_DETECTION_FRAME_INSTRUMENTATION_EVALUATION_H_
+
+#include <optional>
+
+#include "api/video/video_frame.h"
+#include "common_video/frame_instrumentation_data.h"
+
+namespace webrtc {
+
+std::optional<double> GetCorruptionScore(const FrameInstrumentationData& data,
+ const VideoFrame& frame);
+
+} // namespace webrtc
+
+#endif // VIDEO_CORRUPTION_DETECTION_FRAME_INSTRUMENTATION_EVALUATION_H_
diff --git a/video/corruption_detection/frame_instrumentation_evaluation_unittest.cc b/video/corruption_detection/frame_instrumentation_evaluation_unittest.cc
new file mode 100644
index 0000000..cfc17e4
--- /dev/null
+++ b/video/corruption_detection/frame_instrumentation_evaluation_unittest.cc
@@ -0,0 +1,144 @@
+/*
+ * Copyright 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 "video/corruption_detection/frame_instrumentation_evaluation.h"
+
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame.h"
+#include "common_video/frame_instrumentation_data.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+scoped_refptr<I420Buffer> MakeI420FrameBufferWithDifferentPixelValues() {
+ // Create an I420 frame of size 4x4.
+ const int kDefaultLumaWidth = 4;
+ const int kDefaultLumaHeight = 4;
+ const int kDefaultChromaWidth = 2;
+ std::vector<uint8_t> kDefaultYContent = {1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16};
+ std::vector<uint8_t> kDefaultUContent = {17, 18, 19, 20};
+ std::vector<uint8_t> kDefaultVContent = {21, 22, 23, 24};
+
+ return I420Buffer::Copy(kDefaultLumaWidth, kDefaultLumaHeight,
+ kDefaultYContent.data(), kDefaultLumaWidth,
+ kDefaultUContent.data(), kDefaultChromaWidth,
+ kDefaultVContent.data(), kDefaultChromaWidth);
+}
+
+TEST(FrameInstrumentationEvaluationTest,
+ HaveNoCorruptionScoreWhenNoSampleValuesAreProvided) {
+ FrameInstrumentationData data = {.sequence_index = 0,
+ .communicate_upper_bits = false,
+ .std_dev = 0.0,
+ .luma_error_threshold = 0,
+ .chroma_error_threshold = 0,
+ .sample_values = {}};
+ VideoFrame frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(MakeI420FrameBufferWithDifferentPixelValues())
+ .build();
+
+ std::optional<double> corruption_score = GetCorruptionScore(data, frame);
+
+ EXPECT_FALSE(corruption_score.has_value());
+}
+
+TEST(FrameInstrumentationEvaluationTest,
+ HaveACorruptionScoreWhenSampleValuesAreProvided) {
+ FrameInstrumentationData data = {
+ .sequence_index = 0,
+ .communicate_upper_bits = false,
+ .std_dev = 0.0,
+ .luma_error_threshold = 0,
+ .chroma_error_threshold = 0,
+ .sample_values = {12, 12, 12, 12, 12, 12, 12, 12}};
+ VideoFrame frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(MakeI420FrameBufferWithDifferentPixelValues())
+ .build();
+
+ std::optional<double> corruption_score = GetCorruptionScore(data, frame);
+
+ ASSERT_TRUE(corruption_score.has_value());
+ EXPECT_DOUBLE_EQ(*corruption_score, 1.0);
+}
+
+TEST(FrameInstrumentationEvaluationTest,
+ ApplyThresholdsWhenNonNegativeThresholdsAreProvided) {
+ FrameInstrumentationData data = {
+ .sequence_index = 0,
+ .communicate_upper_bits = false,
+ .std_dev = 0.0,
+ .luma_error_threshold = 8,
+ .chroma_error_threshold = 8,
+ .sample_values = {12, 12, 12, 12, 12, 12, 12, 12}};
+ VideoFrame frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(MakeI420FrameBufferWithDifferentPixelValues())
+ .build();
+
+ std::optional<double> corruption_score = GetCorruptionScore(data, frame);
+
+ ASSERT_TRUE(corruption_score.has_value());
+ EXPECT_DOUBLE_EQ(*corruption_score, 0.55);
+}
+
+TEST(FrameInstrumentationEvaluationTest,
+ ApplyStdDevWhenNonNegativeStdDevIsProvided) {
+ FrameInstrumentationData data = {
+ .sequence_index = 0,
+ .communicate_upper_bits = false,
+ .std_dev = 0.6,
+ .luma_error_threshold = 8,
+ .chroma_error_threshold = 8,
+ .sample_values = {12, 12, 12, 12, 12, 12, 12, 12}};
+
+ std::vector<double> sample_values = {12, 12, 12, 12, 12, 12, 12, 12};
+ VideoFrame frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(MakeI420FrameBufferWithDifferentPixelValues())
+ .build();
+
+ std::optional<double> corruption_score = GetCorruptionScore(data, frame);
+
+ ASSERT_TRUE(corruption_score.has_value());
+ EXPECT_DOUBLE_EQ(*corruption_score, 0.3302493109581533);
+}
+
+TEST(FrameInstrumentationEvaluationTest, ApplySequenceIndexWhenProvided) {
+ FrameInstrumentationData data = {
+ .sequence_index = 1,
+ .communicate_upper_bits = false,
+ .std_dev = 0.6,
+ .luma_error_threshold = 8,
+ .chroma_error_threshold = 8,
+ .sample_values = {12, 12, 12, 12, 12, 12, 12, 12}};
+
+ std::vector<double> sample_values = {12, 12, 12, 12, 12, 12, 12, 12};
+ VideoFrame frame =
+ VideoFrame::Builder()
+ .set_video_frame_buffer(MakeI420FrameBufferWithDifferentPixelValues())
+ .build();
+
+ std::optional<double> corruption_score = GetCorruptionScore(data, frame);
+
+ ASSERT_TRUE(corruption_score.has_value());
+ EXPECT_DOUBLE_EQ(*corruption_score, 0.12983429453668965);
+}
+
+} // namespace
+} // namespace webrtc
diff --git a/video/corruption_detection/frame_instrumentation_generator.cc b/video/corruption_detection/frame_instrumentation_generator.cc
index b4f0377..cc2b112 100644
--- a/video/corruption_detection/frame_instrumentation_generator.cc
+++ b/video/corruption_detection/frame_instrumentation_generator.cc
@@ -142,7 +142,7 @@
contexts_[layer_id]
.frame_sampler.GetSampleCoordinatesForFrameIfFrameShouldBeSampled(
is_key_frame, captured_frame.rtp_timestamp(),
- /*sample_size=*/13);
+ /*num_samples=*/13);
if (sample_coordinates.empty()) {
if (!is_key_frame) {
return std::nullopt;