Add support for frame pair corruption score calculation.

With this changes users can calculate the corruption score on two frames e.g. in test scenarios where one has access to the input and output file.

Bug: webrtc:358039777
Change-Id: Id864010115aa040284ec09b42d0279ccb45960b9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/364161
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Fanny Linderborg <linderborg@webrtc.org>
Commit-Queue: Emil Vardar (xWF) <vardar@google.com>
Cr-Commit-Position: refs/heads/main@{#43222}
diff --git a/BUILD.gn b/BUILD.gn
index 580d34e..98faecf 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -713,6 +713,7 @@
 
   # TODO(pbos): Rename test suite, this is no longer "just" for video targets.
   video_engine_tests_resources = [
+    "resources/ConferenceMotion_1280_720_50.yuv",
     "resources/foreman_cif_short.yuv",
     "resources/voice_engine/audio_long16.pcm",
   ]
diff --git a/video/corruption_detection/BUILD.gn b/video/corruption_detection/BUILD.gn
index 55ab651..c7ab70b 100644
--- a/video/corruption_detection/BUILD.gn
+++ b/video/corruption_detection/BUILD.gn
@@ -61,6 +61,23 @@
   ]
 }
 
+rtc_library("frame_pair_corruption_score") {
+  sources = [
+    "frame_pair_corruption_score.cc",
+    "frame_pair_corruption_score.h",
+  ]
+  deps = [
+    ":corruption_classifier",
+    ":generic_mapping_functions",
+    ":halton_frame_sampler",
+    ":utils",
+    "../../api:scoped_refptr",
+    "../../api/video:video_frame",
+    "../../rtc_base:checks",
+    "//third_party/abseil-cpp/absl/strings:string_view",
+  ]
+}
+
 rtc_library("generic_mapping_functions") {
   sources = [
     "generic_mapping_functions.cc",
@@ -95,6 +112,19 @@
   deps = [ "../../rtc_base:checks" ]
 }
 
+rtc_library("utils") {
+  sources = [
+    "utils.cc",
+    "utils.h",
+  ]
+  deps = [
+    "../../api:scoped_refptr",
+    "../../api/video:video_frame",
+    "//third_party/abseil-cpp/absl/strings:string_view",
+    "//third_party/abseil-cpp/absl/strings:strings",
+  ]
+}
+
 if (rtc_include_tests) {
   rtc_library("corruption_classifier_unittest") {
     testonly = true
@@ -133,6 +163,21 @@
     ]
   }
 
+  rtc_library("frame_pair_corruption_score_unittest") {
+    testonly = true
+    sources = [ "frame_pair_corruption_score_unittest.cc" ]
+    deps = [
+      ":frame_pair_corruption_score",
+      "../../api:scoped_refptr",
+      "../../api/video:video_frame",
+      "../../test:fileutils",
+      "../../test:test_support",
+      "../../test:video_test_support",
+      "//third_party/abseil-cpp/absl/strings:string_view",
+    ]
+    data = [ "../../resources/ConferenceMotion_1280_720_50.yuv" ]
+  }
+
   rtc_library("generic_mapping_functions_unittest") {
     testonly = true
     sources = [ "generic_mapping_functions_unittest.cc" ]
@@ -163,6 +208,16 @@
     ]
   }
 
+  rtc_library("utils_unittest") {
+    testonly = true
+    sources = [ "utils_unittest.cc" ]
+    deps = [
+      ":utils",
+      "../../api/video:video_frame",
+      "../../test:test_support",
+    ]
+  }
+
   rtc_library("corruption_detection_tests") {
     testonly = true
     sources = []
@@ -170,9 +225,11 @@
       ":corruption_classifier_unittest",
       ":frame_instrumentation_evaluation_unittest",
       ":frame_instrumentation_generator_unittest",
+      ":frame_pair_corruption_score_unittest",
       ":generic_mapping_functions_unittest",
       ":halton_frame_sampler_unittest",
       ":halton_sequence_unittest",
+      ":utils_unittest",
     ]
   }
 }
diff --git a/video/corruption_detection/frame_pair_corruption_score.cc b/video/corruption_detection/frame_pair_corruption_score.cc
new file mode 100644
index 0000000..084005a
--- /dev/null
+++ b/video/corruption_detection/frame_pair_corruption_score.cc
@@ -0,0 +1,99 @@
+/*
+ * 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_pair_corruption_score.h"
+
+#include <optional>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame_buffer.h"
+#include "rtc_base/checks.h"
+#include "video/corruption_detection/generic_mapping_functions.h"
+#include "video/corruption_detection/halton_frame_sampler.h"
+#include "video/corruption_detection/utils.h"
+
+namespace webrtc {
+namespace {
+
+constexpr float kDefaultSampleFraction = 0.5;
+
+}
+
+FramePairCorruptionScorer::FramePairCorruptionScorer(
+    absl::string_view codec_name,
+    float scale_factor,
+    std::optional<float> sample_fraction)
+    : codec_type_(GetVideoCodecType(codec_name)),
+      sample_fraction_(sample_fraction.value_or(kDefaultSampleFraction)),
+      corruption_classifier_(scale_factor) {
+  RTC_CHECK_GE(sample_fraction_, 0) << "Sample fraction must be non-negative.";
+  RTC_CHECK_LE(sample_fraction_, 1) << "Sample fraction must be less than or "
+                                       "equal to 1.";
+}
+
+FramePairCorruptionScorer::FramePairCorruptionScorer(
+    absl::string_view codec_name,
+    float growth_rate,
+    float midpoint,
+    std::optional<float> sample_fraction)
+    : codec_type_(GetVideoCodecType(codec_name)),
+      sample_fraction_(sample_fraction.value_or(kDefaultSampleFraction)),
+      corruption_classifier_(growth_rate, midpoint) {
+  RTC_CHECK_GE(sample_fraction_, 0) << "Sample fraction must be non-negative.";
+  RTC_CHECK_LE(sample_fraction_, 1) << "Sample fraction must be less than or "
+                                       "equal to 1.";
+}
+
+double FramePairCorruptionScorer::CalculateScore(
+    int qp,
+    I420BufferInterface& reference_buffer,
+    I420BufferInterface& test_buffer) {
+  RTC_CHECK_GE(reference_buffer.width(), test_buffer.width());
+  RTC_CHECK_GE(reference_buffer.height(), test_buffer.height());
+  // Adapted for VP9 and AV1.
+  RTC_DCHECK_GE(qp, 0);
+  RTC_DCHECK_LE(qp, 255);
+
+  // We calculate corruption score per "sample" rather than per "pixel", hence
+  // times "3/2".
+  const int num_samples = static_cast<int>(
+      (test_buffer.width() * test_buffer.height() * 3 / 2) * sample_fraction_);
+  std::vector<HaltonFrameSampler::Coordinates> halton_samples =
+      halton_frame_sampler_.GetSampleCoordinatesForFrame(num_samples);
+  RTC_DCHECK_EQ(halton_samples.size(), num_samples);
+
+  scoped_refptr<I420Buffer> reference_i420_buffer =
+      GetAsI420Buffer(reference_buffer.ToI420());
+  scoped_refptr<I420Buffer> test_i420_buffer =
+      GetAsI420Buffer(test_buffer.ToI420());
+
+  FilterSettings filter_settings = GetCorruptionFilterSettings(qp, codec_type_);
+
+  const std::vector<FilteredSample> filtered_reference_sample_values =
+      GetSampleValuesForFrame(
+          reference_i420_buffer, halton_samples, test_i420_buffer->width(),
+          test_i420_buffer->height(), filter_settings.std_dev);
+  const std::vector<FilteredSample> filtered_test_sample_values =
+      GetSampleValuesForFrame(
+          test_i420_buffer, halton_samples, test_i420_buffer->width(),
+          test_i420_buffer->height(), filter_settings.std_dev);
+  RTC_CHECK_EQ(filtered_reference_sample_values.size(),
+               filtered_test_sample_values.size());
+
+  return corruption_classifier_.CalculateCorruptionProbability(
+      filtered_reference_sample_values, filtered_test_sample_values,
+      filter_settings.luma_error_threshold,
+      filter_settings.chroma_error_threshold);
+}
+
+}  // namespace webrtc
diff --git a/video/corruption_detection/frame_pair_corruption_score.h b/video/corruption_detection/frame_pair_corruption_score.h
new file mode 100644
index 0000000..31a70a6
--- /dev/null
+++ b/video/corruption_detection/frame_pair_corruption_score.h
@@ -0,0 +1,81 @@
+/*
+ * 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_PAIR_CORRUPTION_SCORE_H_
+#define VIDEO_CORRUPTION_DETECTION_FRAME_PAIR_CORRUPTION_SCORE_H_
+
+#include <optional>
+
+#include "absl/strings/string_view.h"
+#include "api/video/video_codec_type.h"
+#include "api/video/video_frame_buffer.h"
+#include "video/corruption_detection/corruption_classifier.h"
+#include "video/corruption_detection/halton_frame_sampler.h"
+
+namespace webrtc {
+
+// Given a `reference_buffer` and a `test_buffer`, calculates the corruption
+// score of the a frame pair. The score is calculated by comparing the sample
+// values (each pixel has 3 sample values, the Y, U and V samples) of the
+// reference buffer and the test buffer at a set of sampled coordinates.
+//
+// TODO: bugs.webrtc.org/358039777 - Remove one of the constructors based on
+// which mapping function works best in practice.
+// There are two constructors for this class. The first one takes a
+// `scale_factor` as a parameter, which is used to calculate the scaling
+// function. The second one takes a `growth_rate` and a `midpoint` as
+// parameters, which are used to calculate the logistic function.
+// `sample_fraction` is the fraction of pixels to sample. E.g. if
+// `sample_fraction` = 0.5, then we sample 50% of the samples.
+//
+// The dimension of the `reference_buffer` and `test_buffer` does not need to be
+// the same, in order to support downscaling caused by e.g. simulcast and
+// scalable encoding. However, the dimensions of the `reference_buffer` must be
+// larger than or equal to the dimensions of the `test_buffer`.
+class FramePairCorruptionScorer {
+ public:
+  // `scale_factor` is the parameter constructing the scaling function, which is
+  // used to calculate the corruption score. `sample_fraction` is the fraction
+  // of pixels to sample.
+  FramePairCorruptionScorer(absl::string_view codec_name,
+                            float scale_factor,
+                            std::optional<float> sample_fraction);
+
+  // `growth_rate` and `midpoint` are parameters constructing a logistic
+  // function, which is used to calculate the corruption score.
+  // `sample_fraction` is the fraction of pixels to sample.
+  FramePairCorruptionScorer(absl::string_view codec_name,
+                            float growth_rate,
+                            float midpoint,
+                            std::optional<float> sample_fraction);
+
+  ~FramePairCorruptionScorer() = default;
+
+  // Returns the corruption score as a probability value between 0 and 1, where
+  // 0 means no corruption and 1 means that the compressed frame is corrupted.
+  //
+  // However, note that the corruption score may not accurately reflect
+  // corruption. E.g. even if the corruption score is 0, the compressed frame
+  // may still be corrupted and vice versa.
+  double CalculateScore(int qp,
+                        I420BufferInterface& reference_buffer,
+                        I420BufferInterface& test_buffer);
+
+ private:
+  const VideoCodecType codec_type_;
+  const float sample_fraction_;
+
+  HaltonFrameSampler halton_frame_sampler_;
+  CorruptionClassifier corruption_classifier_;
+};
+
+}  // namespace webrtc
+
+#endif  // VIDEO_CORRUPTION_DETECTION_FRAME_PAIR_CORRUPTION_SCORE_H_
diff --git a/video/corruption_detection/frame_pair_corruption_score_unittest.cc b/video/corruption_detection/frame_pair_corruption_score_unittest.cc
new file mode 100644
index 0000000..cf6de29
--- /dev/null
+++ b/video/corruption_detection/frame_pair_corruption_score_unittest.cc
@@ -0,0 +1,193 @@
+/*
+ * 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_pair_corruption_score.h"
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_frame_buffer.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+#include "test/testsupport/frame_reader.h"
+
+namespace webrtc {
+namespace {
+
+using test::FrameReader;
+
+// Input video.
+constexpr absl::string_view kFilename = "ConferenceMotion_1280_720_50";
+constexpr int kWidth = 1280;
+constexpr int kHeight = 720;
+
+constexpr absl::string_view kCodecName = "VP8";
+
+// Scale function parameters.
+constexpr float kScaleFactor = 14;
+
+// Logistic function parameters.
+constexpr float kGrowthRate = 0.5;
+constexpr float kMidpoint = 3;
+
+std::unique_ptr<FrameReader> GetFrameGenerator() {
+  std::string clip_path = test::ResourcePath(kFilename, "yuv");
+  EXPECT_TRUE(test::FileExists(clip_path));
+  return CreateYuvFrameReader(clip_path, {.width = kWidth, .height = kHeight},
+                              test::YuvFrameReaderImpl::RepeatMode::kPingPong);
+}
+
+scoped_refptr<I420BufferInterface> GetDowscaledFrame(
+    scoped_refptr<I420BufferInterface> frame,
+    float downscale_factor) {
+  scoped_refptr<I420Buffer> downscaled_frame =
+      I420Buffer::Create(kWidth * downscale_factor, kHeight * downscale_factor);
+  downscaled_frame->ScaleFrom(*frame);
+  return downscaled_frame;
+}
+
+TEST(FramePairCorruptionScorerTest, SameFrameReturnsNoCorruptionScaleFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kScaleFactor, std::nullopt);
+  EXPECT_LT(
+      frame_pair_corruption_score.CalculateScore(/*qp=*/1, *frame, *frame),
+      0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     SameFrameReturnsNoCorruptionLogisticFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kGrowthRate, kMidpoint, std::nullopt);
+  EXPECT_LT(
+      frame_pair_corruption_score.CalculateScore(/*qp=*/1, *frame, *frame),
+      0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     HalfScaledFrameReturnsNoCorruptionScaleFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kScaleFactor, std::nullopt);
+  EXPECT_LT(frame_pair_corruption_score.CalculateScore(
+                /*qp=*/1, *frame,
+                *GetDowscaledFrame(frame, /*downscale_factor=*/0.5)),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     HalfScaledFrameReturnsNoCorruptionLogisticFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kGrowthRate, kMidpoint, std::nullopt);
+  EXPECT_LT(frame_pair_corruption_score.CalculateScore(
+                /*qp=*/1, *frame,
+                *GetDowscaledFrame(frame, /*downscale_factor=*/0.5)),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest, QuarterScaledFrameReturnsNoCorruption) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kScaleFactor, std::nullopt);
+  EXPECT_LT(frame_pair_corruption_score.CalculateScore(
+                /*qp=*/1, *frame,
+                *GetDowscaledFrame(frame, /*downscale_factor=*/0.25)),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     DifferentFrameResultsInCorruptionScaleFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  // Get frame number 5, which should be different from the first frame, and
+  // hence, indicate a corruption.
+  scoped_refptr<I420Buffer> different_frame =
+      frame_reader->ReadFrame(/*frame_num=*/5);
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kScaleFactor, std::nullopt);
+  EXPECT_GT(frame_pair_corruption_score.CalculateScore(/*qp=*/1, *frame,
+                                                       *different_frame),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     DifferentFrameResultsInCorruptionLogisticFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  // Get frame number 5, which should be different from the first frame, and
+  // hence, indicate a corruption.
+  scoped_refptr<I420Buffer> different_frame =
+      frame_reader->ReadFrame(/*frame_num=*/5);
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kGrowthRate, kMidpoint, std::nullopt);
+  EXPECT_GT(frame_pair_corruption_score.CalculateScore(/*qp=*/1, *frame,
+                                                       *different_frame),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     HalfScaledDifferentFrameResultsInCorruptionScaleFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  // Get frame number 5, which should be different from the first frame, and
+  // hence, indicate a corruption.
+  scoped_refptr<I420Buffer> different_frame =
+      frame_reader->ReadFrame(/*frame_num=*/5);
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kScaleFactor, std::nullopt);
+  EXPECT_GT(frame_pair_corruption_score.CalculateScore(
+                /*qp=*/1, *frame,
+                *GetDowscaledFrame(different_frame, /*downscale_factor=*/0.25)),
+            0.5);
+}
+
+TEST(FramePairCorruptionScorerTest,
+     HalfScaledDifferentFrameResultsInCorruptionLogisticFunction) {
+  std::unique_ptr<FrameReader> frame_reader = GetFrameGenerator();
+  scoped_refptr<I420Buffer> frame = frame_reader->PullFrame();
+
+  // Get frame number 5, which should be different from the first frame, and
+  // hence, indicate a corruption.
+  scoped_refptr<I420Buffer> different_frame =
+      frame_reader->ReadFrame(/*frame_num=*/5);
+
+  FramePairCorruptionScorer frame_pair_corruption_score(
+      kCodecName, kGrowthRate, kMidpoint, std::nullopt);
+  EXPECT_GT(frame_pair_corruption_score.CalculateScore(
+                /*qp=*/1, *frame,
+                *GetDowscaledFrame(different_frame, /*downscale_factor=*/0.25)),
+            0.5);
+}
+
+}  // namespace
+}  // namespace webrtc
diff --git a/video/corruption_detection/utils.cc b/video/corruption_detection/utils.cc
new file mode 100644
index 0000000..b213dfb
--- /dev/null
+++ b/video/corruption_detection/utils.cc
@@ -0,0 +1,70 @@
+/*
+ * 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/utils.h"
+
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_codec_type.h"
+#include "api/video/video_frame_buffer.h"
+
+namespace webrtc {
+namespace {
+
+constexpr char kPayloadNameVp8[] = "VP8";
+constexpr char kPayloadNameVp9[] = "VP9";
+constexpr char kPayloadNameAv1[] = "AV1";
+constexpr char kPayloadNameH264[] = "H264";
+constexpr char kPayloadNameH265[] = "H265";
+constexpr char kPayloadNameGeneric[] = "Generic";
+
+}  // namespace
+
+// Returns the `VideoCodecType` corresponding to the given `codec_name`.
+// The `codec_name` does not need to "exactly" match the namings
+// `kPayloadNameXXX`. For example, "VP8", "vp8" and "libvxp_vp8" are all
+// valid, and will return the `kVideoCodecVP8`.
+// I.e. it does the best to match a codec name to a `VideoCodecType`.
+VideoCodecType GetVideoCodecType(absl::string_view codec_name) {
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameVp8))
+    return kVideoCodecVP8;
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameVp9))
+    return kVideoCodecVP9;
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameAv1))
+    return kVideoCodecAV1;
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameH264))
+    return kVideoCodecH264;
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameH265))
+    return kVideoCodecH265;
+  if (absl::StrContainsIgnoreCase(codec_name, kPayloadNameGeneric))
+    return kVideoCodecGeneric;
+  RTC_FATAL() << "Codec name " << codec_name << " is not supported.";
+}
+
+// Creates a new buffer and copies the pixel data. While the copying is done,
+// the type of the buffer is changed from `I420BufferInterface` to `I420Buffer`.
+// Observe also that the padding bytes are removed.
+scoped_refptr<I420Buffer> GetAsI420Buffer(
+    const scoped_refptr<I420BufferInterface> i420_buffer_interface) {
+  // Note: `I420Buffer::Copy` removes padding bytes.
+  // I.e. if the input is to the left the output will be as to the right.
+  // +------+--+      +------+
+  // |      |  |      |      |
+  // |  Y   |P |  --> |  Y   |
+  // |      |  |      |      |
+  // +------+--+      +------+
+  scoped_refptr<I420Buffer> frame_as_i420_buffer =
+      I420Buffer::Copy(*i420_buffer_interface);
+  RTC_DCHECK_EQ(frame_as_i420_buffer->StrideY(), frame_as_i420_buffer->width());
+  return frame_as_i420_buffer;
+}
+
+}  // namespace webrtc
diff --git a/video/corruption_detection/utils.h b/video/corruption_detection/utils.h
new file mode 100644
index 0000000..7c1f896
--- /dev/null
+++ b/video/corruption_detection/utils.h
@@ -0,0 +1,29 @@
+/*
+ * 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_UTILS_H_
+#define VIDEO_CORRUPTION_DETECTION_UTILS_H_
+
+#include "absl/strings/string_view.h"
+#include "api/scoped_refptr.h"
+#include "api/video/i420_buffer.h"
+#include "api/video/video_codec_type.h"
+#include "api/video/video_frame_buffer.h"
+
+namespace webrtc {
+
+VideoCodecType GetVideoCodecType(absl::string_view codec_name);
+
+scoped_refptr<I420Buffer> GetAsI420Buffer(
+    scoped_refptr<I420BufferInterface> i420_buffer_interface);
+
+}  // namespace webrtc
+
+#endif  // VIDEO_CORRUPTION_DETECTION_UTILS_H_
diff --git a/video/corruption_detection/utils_unittest.cc b/video/corruption_detection/utils_unittest.cc
new file mode 100644
index 0000000..1325f14
--- /dev/null
+++ b/video/corruption_detection/utils_unittest.cc
@@ -0,0 +1,38 @@
+/*
+ * 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/utils.h"
+
+#include "api/video/video_codec_type.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace {
+
+#if GTEST_HAS_DEATH_TEST
+using ::testing::_;
+#endif  // GTEST_HAS_DEATH_TEST
+
+TEST(UtilsTest, FindCodecFromString) {
+  EXPECT_EQ(GetVideoCodecType(/*codec_name=*/"VP8"), kVideoCodecVP8);
+  EXPECT_EQ(GetVideoCodecType(/*codec_name=*/"libvpx-vp9"), kVideoCodecVP9);
+  EXPECT_EQ(GetVideoCodecType(/*codec_name=*/"ImprovedAV1"), kVideoCodecAV1);
+  EXPECT_EQ(GetVideoCodecType(/*codec_name=*/"lets_use_h264"), kVideoCodecH264);
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(UtilsTest, IfCodecDoesNotExistRaiseError) {
+  EXPECT_DEATH(GetVideoCodecType(/*codec_name=*/"Not_a_codec"), _);
+}
+#endif  // GTEST_HAS_DEATH_TEST
+
+}  // namespace
+}  // namespace webrtc