diff --git a/common_video/BUILD.gn b/common_video/BUILD.gn index 6b42a3721e..c7a8df5b3e 100644 --- a/common_video/BUILD.gn +++ b/common_video/BUILD.gn @@ -8,6 +8,18 @@ import("../webrtc.gni") +rtc_library("corruption_detection_converters") { + sources = [ + "corruption_detection_converters.cc", + "corruption_detection_converters.h", + ] + deps = [ + ":corruption_detection_message", + ":frame_instrumentation_data", + "../rtc_base:checks", + ] +} + rtc_library("corruption_detection_message") { sources = [ "corruption_detection_message.h" ] deps = [ @@ -125,6 +137,17 @@ if (rtc_include_tests && !build_with_chromium) { } } + rtc_library("corruption_detection_converters_unittest") { + testonly = true + sources = [ "corruption_detection_converters_unittest.cc" ] + deps = [ + ":corruption_detection_converters", + ":corruption_detection_message", + ":frame_instrumentation_data", + "../test:test_support", + ] + } + rtc_library("corruption_detection_message_unittest") { testonly = true sources = [ "corruption_detection_message_unittest.cc" ] @@ -161,6 +184,7 @@ if (rtc_include_tests && !build_with_chromium) { deps = [ ":common_video", + ":corruption_detection_converters_unittest", ":corruption_detection_message_unittest", "../api:scoped_refptr", "../api/units:time_delta", diff --git a/common_video/corruption_detection_converters.cc b/common_video/corruption_detection_converters.cc new file mode 100644 index 0000000000..74e978dd23 --- /dev/null +++ b/common_video/corruption_detection_converters.cc @@ -0,0 +1,69 @@ +/* + * 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 "common_video/corruption_detection_converters.h" + +#include + +#include "common_video/corruption_detection_message.h" +#include "common_video/frame_instrumentation_data.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +int GetSequenceIndexForMessage(int sequence_index, + bool communicate_upper_bits) { + return communicate_upper_bits ? (sequence_index >> 7) + : (sequence_index & 0b0111'1111); +} + +} // namespace + +std::optional +ConvertFrameInstrumentationDataToCorruptionDetectionMessage( + const FrameInstrumentationData& data) { + if (data.sequence_index < 0 || data.sequence_index > 0b0011'1111'1111'1111) { + return std::nullopt; + } + // Frame instrumentation data must have sample values. + if (data.sample_values.empty()) { + return std::nullopt; + } + return CorruptionDetectionMessage::Builder() + .WithSequenceIndex(GetSequenceIndexForMessage( + data.sequence_index, data.communicate_upper_bits)) + .WithInterpretSequenceIndexAsMostSignificantBits( + data.communicate_upper_bits) + .WithStdDev(data.std_dev) + .WithLumaErrorThreshold(data.luma_error_threshold) + .WithChromaErrorThreshold(data.chroma_error_threshold) + .WithSampleValues(data.sample_values) + .Build(); +} + +std::optional +ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage( + const FrameInstrumentationSyncData& data) { + RTC_DCHECK(data.communicate_upper_bits) + << "FrameInstrumentationSyncData data must always send the upper bits."; + + if (data.sequence_index < 0 || data.sequence_index > 0b0011'1111'1111'1111) { + return std::nullopt; + } + return CorruptionDetectionMessage::Builder() + .WithSequenceIndex(GetSequenceIndexForMessage( + data.sequence_index, data.communicate_upper_bits)) + .WithInterpretSequenceIndexAsMostSignificantBits(true) + .Build(); +} + +} // namespace webrtc diff --git a/common_video/corruption_detection_converters.h b/common_video/corruption_detection_converters.h new file mode 100644 index 0000000000..7387fee437 --- /dev/null +++ b/common_video/corruption_detection_converters.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 COMMON_VIDEO_CORRUPTION_DETECTION_CONVERTERS_H_ +#define COMMON_VIDEO_CORRUPTION_DETECTION_CONVERTERS_H_ + +#include + +#include "common_video/corruption_detection_message.h" +#include "common_video/frame_instrumentation_data.h" + +namespace webrtc { + +std::optional +ConvertFrameInstrumentationDataToCorruptionDetectionMessage( + const FrameInstrumentationData& frame_instrumentation_data); +std::optional +ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage( + const FrameInstrumentationSyncData& frame_instrumentation_sync_data); +} // namespace webrtc + +#endif // COMMON_VIDEO_CORRUPTION_DETECTION_CONVERTERS_H_ diff --git a/common_video/corruption_detection_converters_unittest.cc b/common_video/corruption_detection_converters_unittest.cc new file mode 100644 index 0000000000..eccd5f8160 --- /dev/null +++ b/common_video/corruption_detection_converters_unittest.cc @@ -0,0 +1,184 @@ +/* + * 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 "common_video/corruption_detection_converters.h" + +#include + +#include "common_video/corruption_detection_message.h" +#include "common_video/frame_instrumentation_data.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::_; +using ::testing::ElementsAre; + +TEST(CorruptionDetectionConvertersTest, ConvertsValidData) { + FrameInstrumentationData data = {.sequence_index = 1, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {1.0, 2.0, 3.0, 4.0, 5.0}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_TRUE(message.has_value()); + EXPECT_EQ(message->sequence_index(), 1); + EXPECT_FALSE(message->interpret_sequence_index_as_most_significant_bits()); + EXPECT_EQ(message->std_dev(), 1.0); + EXPECT_EQ(message->luma_error_threshold(), 5); + EXPECT_EQ(message->chroma_error_threshold(), 5); + EXPECT_THAT(message->sample_values(), ElementsAre(1.0, 2.0, 3.0, 4.0, 5.0)); +} + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenSequenceIndexIsNegative) { + FrameInstrumentationData data = {.sequence_index = -1, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {1.0, 2.0, 3.0, 4.0, 5.0}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenSequenceIndexIsTooLarge) { + // Sequence index must be at max 14 bits. + FrameInstrumentationData data = {.sequence_index = 0x4000, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {1.0, 2.0, 3.0, 4.0, 5.0}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenThereAreNoSampleValues) { + // FrameInstrumentationData must by definition have at least one sample value. + FrameInstrumentationData data = {.sequence_index = 1, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenNotSpecifyingSampleValues) { + FrameInstrumentationData data = {.sequence_index = 1, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +TEST(CorruptionDetectionConvertersTest, + ConvertsSequenceIndexWhenSetToUseUpperBits) { + FrameInstrumentationData data = {.sequence_index = 0b0000'0110'0000'0101, + .communicate_upper_bits = true, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {1.0, 2.0, 3.0, 4.0, 5.0}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_TRUE(message.has_value()); + EXPECT_EQ(message->sequence_index(), 0b0000'1100); + EXPECT_TRUE(message->interpret_sequence_index_as_most_significant_bits()); + EXPECT_EQ(message->std_dev(), 1.0); + EXPECT_EQ(message->luma_error_threshold(), 5); + EXPECT_EQ(message->chroma_error_threshold(), 5); + EXPECT_THAT(message->sample_values(), ElementsAre(1.0, 2.0, 3.0, 4.0, 5.0)); +} + +TEST(CorruptionDetectionConvertersTest, + ConvertsSequenceIndexWhenSetToUseLowerBits) { + FrameInstrumentationData data = {.sequence_index = 0b0000'0110'0000'0101, + .communicate_upper_bits = false, + .std_dev = 1.0, + .luma_error_threshold = 5, + .chroma_error_threshold = 5, + .sample_values = {1.0, 2.0, 3.0, 4.0, 5.0}}; + + std::optional message = + ConvertFrameInstrumentationDataToCorruptionDetectionMessage(data); + ASSERT_TRUE(message.has_value()); + EXPECT_EQ(message->sequence_index(), 0b0000'0101); + EXPECT_FALSE(message->interpret_sequence_index_as_most_significant_bits()); + EXPECT_EQ(message->std_dev(), 1.0); + EXPECT_EQ(message->luma_error_threshold(), 5); + EXPECT_EQ(message->chroma_error_threshold(), 5); + EXPECT_THAT(message->sample_values(), ElementsAre(1.0, 2.0, 3.0, 4.0, 5.0)); +} + +TEST(CorruptionDetectionConvertersTest, ConvertsValidSyncData) { + FrameInstrumentationSyncData data = {.sequence_index = 1, + .communicate_upper_bits = true}; + + std::optional message = + ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage(data); + ASSERT_TRUE(message.has_value()); + EXPECT_EQ(message->sequence_index(), 0); + EXPECT_TRUE(message->interpret_sequence_index_as_most_significant_bits()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(CorruptionDetectionConvertersTest, FailsWhenSetToNotCommunicateUpperBits) { + FrameInstrumentationSyncData data = {.sequence_index = 1, + .communicate_upper_bits = false}; + + EXPECT_DEATH( + ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage(data), _); +} +#endif // GTEST_HAS_DEATH_TEST + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenSyncSequenceIndexIsNegative) { + FrameInstrumentationSyncData data = {.sequence_index = -1, + .communicate_upper_bits = true}; + + std::optional message = + ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +TEST(CorruptionDetectionConvertersTest, + ReturnsNulloptWhenSyncSequenceIndexIsTooLarge) { + FrameInstrumentationSyncData data = {.sequence_index = 0x4000, + .communicate_upper_bits = true}; + + std::optional message = + ConvertFrameInstrumentationSyncDataToCorruptionDetectionMessage(data); + ASSERT_FALSE(message.has_value()); +} + +} // namespace +} // namespace webrtc