Add header extension reader/writer for automatic corruption detection

R=sprang@webrtc.org

Bug: b/358039777
Change-Id: I84f447edf0524d4ac6c55cfd96cffe6abb77aaa9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359760
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Fanny Linderborg <linderborg@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42791}
This commit is contained in:
Fanny Linderborg 2024-08-16 14:16:20 +02:00 committed by WebRTC LUCI CQ
parent 97ba6afb37
commit aa9e557c81
4 changed files with 457 additions and 0 deletions

View File

@ -523,6 +523,21 @@ rtc_library("rtp_packetizer_av1_test_helper") {
]
}
rtc_library("corruption_detection_extension") {
visibility = [ "*" ]
sources = [
"source/corruption_detection_extension.cc",
"source/corruption_detection_extension.h",
]
deps = [
":rtp_rtcp_format",
"../../api:array_view",
"../../common_video:common_video",
"//third_party/abseil-cpp/absl/container:inlined_vector",
"//third_party/abseil-cpp/absl/strings:string_view",
]
}
if (rtc_include_tests) {
if (!build_with_chromium) {
rtc_executable("test_packet_masks_metrics") {
@ -652,6 +667,7 @@ if (rtc_include_tests) {
}
deps = [
":corruption_detection_extension_unittest",
":fec_test_helper",
":frame_transformer_factory_unittest",
":leb128",
@ -752,4 +768,15 @@ if (rtc_include_tests) {
"//third_party/abseil-cpp/absl/memory",
]
}
rtc_library("corruption_detection_extension_unittest") {
testonly = true
sources = [ "source/corruption_detection_extension_unittest.cc" ]
deps = [
":corruption_detection_extension",
"../../common_video:common_video",
"../../test:test_support",
"//third_party/abseil-cpp/absl/types:optional",
]
}
}

View File

@ -0,0 +1,126 @@
/*
* 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 "modules/rtp_rtcp/source/corruption_detection_extension.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include "absl/container/inlined_vector.h"
#include "api/array_view.h"
#include "common_video/corruption_detection_message.h"
namespace webrtc {
namespace {
constexpr size_t kMandatoryPayloadBytes = 1;
constexpr size_t kConfigurationBytes = 3;
constexpr double kMaxValueForStdDev = 40.0;
} // namespace
// The message format of the header extension:
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |B| seq# index | kernel size | Y err | UV err| sample 0 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | sample 1 | sample 2 | … up to sample <=12
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// * B (1 bit): If the sequence number should be interpreted as the MSB or LSB
// of the full size 14 bit sequence index described in the next point.
// * seq# index (7 bits): The index into the Halton sequence (used to locate
// where the samples should be drawn from).
// * If B is set: the 7 most significant bits of the true index. The 7 least
// significant bits of the true index shall be interpreted as 0. This is
// because this is the point where we can guarantee that the sender and
// receiver has the same full index). For this reason, B must only be set
// for key frames.
// * If B is not set: The 7 LSB of the true index. The 7 most significant bits
// should be inferred based on the most recent message.
// * kernel size (8 bits): The standard deviation of the gaussian filter used
// to weigh the samples. The value is scaled using a linear map:
// 0 = 0.0 to 255 = 40.0. A kernel size of 0 is interpreted as directly using
// just the sample value at the desired coordinate, without any weighting.
// * Y err (4 bits): The allowed error for the luma channel.
// * UV err (4 bits): The allowed error for the chroma channels.
// * Sample N (8 bits): The N:th filtered sample from the input image. Each
// sample represents a new point in one of the image planes, the plane and
// coordinates being determined by index into the Halton sequence (starting at
// seq# index and is incremented by one for each sample). Each sample has gone
// through a Gaussian filter with the kernel size specified above. The samples
// have been floored to the nearest integer.
//
// A special case is so called "synchronization" messages. These are messages
// that only contains the first byte. They always have B set and are used to
// keep the sender and receiver in sync even if no "full" messages have been
// sent for a while.
bool CorruptionDetectionExtension::Parse(rtc::ArrayView<const uint8_t> data,
CorruptionDetectionMessage* message) {
if (message == nullptr) {
return false;
}
if ((data.size() != kMandatoryPayloadBytes &&
data.size() <= kConfigurationBytes) ||
data.size() > 16) {
return false;
}
message->interpret_sequence_index_as_most_significant_bits_ = data[0] >> 7;
message->sequence_index_ = data[0] & 0b0111'1111;
if (data.size() == kMandatoryPayloadBytes) {
return true;
}
message->std_dev_ = data[1] * kMaxValueForStdDev / 255.0;
uint8_t channel_error_thresholds = data[2];
message->luma_error_threshold_ = channel_error_thresholds >> 4;
message->chroma_error_threshold_ = channel_error_thresholds & 0xF;
message->sample_values_.assign(data.cbegin() + kConfigurationBytes,
data.cend());
return true;
}
bool CorruptionDetectionExtension::Write(
rtc::ArrayView<uint8_t> data,
const CorruptionDetectionMessage& message) {
if (data.size() != ValueSize(message)) {
return false;
}
data[0] = message.sequence_index() & 0b0111'1111;
if (message.interpret_sequence_index_as_most_significant_bits()) {
data[0] |= 0b1000'0000;
}
if (message.sample_values().empty()) {
return true;
}
data[1] = static_cast<uint8_t>(
std::round(message.std_dev() / kMaxValueForStdDev * 255.0));
data[2] = (message.luma_error_threshold() << 4) |
(message.chroma_error_threshold() & 0xF);
rtc::ArrayView<uint8_t> sample_values = data.subview(kConfigurationBytes);
for (size_t i = 0; i < message.sample_values().size(); ++i) {
sample_values[i] = std::floor(message.sample_values()[i]);
}
return true;
}
size_t CorruptionDetectionExtension::ValueSize(
const CorruptionDetectionMessage& message) {
if (message.sample_values_.empty()) {
return kMandatoryPayloadBytes;
}
return kConfigurationBytes + message.sample_values_.size();
}
} // namespace webrtc

View File

@ -0,0 +1,50 @@
/*
* 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 MODULES_RTP_RTCP_SOURCE_CORRUPTION_DETECTION_EXTENSION_H_
#define MODULES_RTP_RTCP_SOURCE_CORRUPTION_DETECTION_EXTENSION_H_
#include <cstddef>
#include <cstdint>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "common_video/corruption_detection_message.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
namespace webrtc {
// RTP Corruption Detection Header Extension.
//
// The class reads and writes the corruption detection RTP header extension.
// The class implements traits so that the class is compatible with being an
// argument to the templated `RtpPacket::GetExtension` and
// `RtpPacketToSend::SetExtension` methods.
class CorruptionDetectionExtension {
public:
using value_type = CorruptionDetectionMessage;
// TODO: b/358039777 - Change to our `RTPExtensionType` when available.
static constexpr RTPExtensionType kId = kRtpExtensionNumberOfExtensions;
static constexpr char kCorruptionDetectionUri[] =
"http://www.webrtc.org/experiments/rtp-hdrext/corruption-detection";
static constexpr absl::string_view Uri() { return kCorruptionDetectionUri; }
static bool Parse(rtc::ArrayView<const uint8_t> data,
CorruptionDetectionMessage* message);
static bool Write(rtc::ArrayView<uint8_t> data,
const CorruptionDetectionMessage& message);
// Size of the header extension in bytes.
static size_t ValueSize(const CorruptionDetectionMessage& message);
};
} // namespace webrtc
#endif // MODULES_RTP_RTCP_SOURCE_CORRUPTION_DETECTION_EXTENSION_H_

View File

@ -0,0 +1,254 @@
/*
* 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 "modules/rtp_rtcp/source/corruption_detection_extension.h"
#include <cstddef>
#include <cstdint>
#include "absl/types/optional.h"
#include "common_video/corruption_detection_message.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
using ::testing::DoubleEq;
using ::testing::ElementsAre;
TEST(CorruptionDetectionExtensionTest, ValueSizeIs1UnlessSamplesAreSpecified) {
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.WithStdDev(8.0)
.WithSampleValues({})
.Build();
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_EQ(CorruptionDetectionExtension::ValueSize(*kMessage), size_t{1});
}
TEST(CorruptionDetectionExtensionTest,
GivenSamplesTheValueSizeIsTheSumOfTheNumberOfSamplesPlus3) {
const double kSampleValues[] = {1.0, 2.0, 3.0, 4.0};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSampleValues(kSampleValues)
.Build();
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_EQ(CorruptionDetectionExtension::ValueSize(*kMessage), size_t{7});
}
TEST(CorruptionDetectionExtensionTest,
WritesMandatoryWhenEnoughMemoryIsAllocatedWithoutSamples) {
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.Build();
uint8_t data[] = {0};
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_TRUE(CorruptionDetectionExtension::Write(data, *kMessage));
EXPECT_THAT(data, ElementsAre(0b1110'1111));
}
TEST(CorruptionDetectionExtensionTest,
FailsToWriteWhenTooMuchMemoryIsAllocatedWithoutSamples) {
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.Build();
uint8_t data[] = {0, 0, 0};
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_FALSE(CorruptionDetectionExtension::Write(data, *kMessage));
}
TEST(CorruptionDetectionExtensionTest,
FailsToWriteWhenTooMuchMemoryIsAllocatedWithSamples) {
const double kSampleValues[] = {1.0};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.WithStdDev(8.0)
.WithSampleValues(kSampleValues)
.Build();
uint8_t data[] = {0, 0, 0, 0, 0};
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_FALSE(CorruptionDetectionExtension::Write(data, *kMessage));
}
TEST(CorruptionDetectionExtensionTest,
WritesEverythingWhenEnoughMemoryIsAllocatedWithSamples) {
const double kSampleValues[] = {1.0};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.WithStdDev(8.0)
.WithSampleValues(kSampleValues)
.Build();
uint8_t data[] = {0, 0, 0, 0};
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_TRUE(CorruptionDetectionExtension::Write(data, *kMessage));
EXPECT_THAT(data, ElementsAre(0b1110'1111, 51, 0, 1));
}
TEST(CorruptionDetectionExtensionTest,
WritesEverythingToExtensionWhenUpperBitsAreUsedForSequenceIndex) {
const double kSampleValues[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0,
8.0, 9.0, 10.0, 11.0, 12.0, 13.0};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(true)
.WithStdDev(34.5098) // 220 / (255.0 * 40.0)
.WithLumaErrorThreshold(0b1110)
.WithChromaErrorThreshold(0b1111)
.WithSampleValues(kSampleValues)
.Build();
uint8_t data[16];
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_TRUE(CorruptionDetectionExtension::Write(data, *kMessage));
EXPECT_THAT(data, ElementsAre(0b1110'1111, 220, 0b1110'1111, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13));
}
TEST(CorruptionDetectionExtensionTest,
WritesEverythingToExtensionWhenLowerBitsAreUsedForSequenceIndex) {
const double kSampleValues[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0,
8.0, 9.0, 10.0, 11.0, 12.0, 13.0};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSequenceIndex(0b0110'1111)
.WithInterpretSequenceIndexAsMostSignificantBits(false)
.WithStdDev(34.5098) // 220 / (255.0 * 40.0)
.WithLumaErrorThreshold(0b1110)
.WithChromaErrorThreshold(0b1111)
.WithSampleValues(kSampleValues)
.Build();
uint8_t data[16];
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_TRUE(CorruptionDetectionExtension::Write(data, *kMessage));
EXPECT_THAT(data, ElementsAre(0b0110'1111, 220, 0b1110'1111, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13));
}
TEST(CorruptionDetectionExtensionTest, TruncatesSampleValuesWhenWriting) {
const double kSampleValues[] = {1.4, 2.5, 3.6};
const absl::optional<CorruptionDetectionMessage> kMessage =
CorruptionDetectionMessage::Builder()
.WithSampleValues(kSampleValues)
.Build();
uint8_t data[6];
ASSERT_NE(kMessage, absl::nullopt);
EXPECT_TRUE(CorruptionDetectionExtension::Write(data, *kMessage));
EXPECT_THAT(data, ElementsAre(0, 0, 0, 1, 2, 3));
}
TEST(CorruptionDetectionExtensionTest, ParsesMandatoryFieldsFromExtension) {
CorruptionDetectionMessage message;
const uint8_t kData[] = {0b1110'1111};
EXPECT_TRUE(CorruptionDetectionExtension::Parse(kData, &message));
EXPECT_EQ(message.sequence_index(), 0b0110'1111);
EXPECT_TRUE(message.interpret_sequence_index_as_most_significant_bits());
EXPECT_THAT(message.std_dev(), DoubleEq(0.0));
EXPECT_EQ(message.luma_error_threshold(), 0);
EXPECT_EQ(message.chroma_error_threshold(), 0);
EXPECT_THAT(message.sample_values(), ElementsAre());
}
TEST(CorruptionDetectionExtensionTest, FailsToParseWhenGivenTooFewFields) {
CorruptionDetectionMessage message;
const uint8_t kData[] = {0b1110'1111, 8, 0};
EXPECT_FALSE(CorruptionDetectionExtension::Parse(kData, &message));
}
TEST(CorruptionDetectionExtensionTest,
ParsesEverythingFromExtensionWhenUpperBitsAreUsedForSequenceIndex) {
CorruptionDetectionMessage message;
const uint8_t kSampleValues[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
const uint8_t kData[] = {0b1100'0100, 220,
0b1110'1111, kSampleValues[0],
kSampleValues[1], kSampleValues[2],
kSampleValues[3], kSampleValues[4],
kSampleValues[5], kSampleValues[6],
kSampleValues[7], kSampleValues[8],
kSampleValues[9], kSampleValues[10],
kSampleValues[11], kSampleValues[12]};
EXPECT_TRUE(CorruptionDetectionExtension::Parse(kData, &message));
EXPECT_EQ(message.sequence_index(), 0b0100'0100);
EXPECT_TRUE(message.interpret_sequence_index_as_most_significant_bits());
EXPECT_THAT(message.std_dev(),
DoubleEq(34.509803921568626)); // 220 / (255.0 * 40.0)
EXPECT_EQ(message.luma_error_threshold(), 0b1110);
EXPECT_EQ(message.chroma_error_threshold(), 0b1111);
EXPECT_EQ(message.sample_values().size(), sizeof(kSampleValues));
for (size_t i = 0; i < sizeof(kSampleValues); ++i) {
EXPECT_EQ(message.sample_values()[i], kSampleValues[i]);
}
}
TEST(CorruptionDetectionExtensionTest,
ParsesEverythingFromExtensionWhenLowerBitsAreUsedForSequenceIndex) {
CorruptionDetectionMessage message;
const uint8_t kSampleValues[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
const uint8_t kData[] = {0b0100'0100, 220,
0b1110'1111, kSampleValues[0],
kSampleValues[1], kSampleValues[2],
kSampleValues[3], kSampleValues[4],
kSampleValues[5], kSampleValues[6],
kSampleValues[7], kSampleValues[8],
kSampleValues[9], kSampleValues[10],
kSampleValues[11], kSampleValues[12]};
EXPECT_TRUE(CorruptionDetectionExtension::Parse(kData, &message));
EXPECT_EQ(message.sequence_index(), 0b0100'0100);
EXPECT_FALSE(message.interpret_sequence_index_as_most_significant_bits());
EXPECT_THAT(message.std_dev(),
DoubleEq(34.509803921568626)); // 220 / (255.0 * 40.0)
EXPECT_EQ(message.luma_error_threshold(), 0b1110);
EXPECT_EQ(message.chroma_error_threshold(), 0b1111);
EXPECT_EQ(message.sample_values().size(), sizeof(kSampleValues));
for (size_t i = 0; i < sizeof(kSampleValues); ++i) {
EXPECT_EQ(message.sample_values()[i], kSampleValues[i]);
}
}
TEST(CorruptionDetectionExtensionTest, FailsToParseWhenGivenNullptrAsOutput) {
const uint8_t kData[] = {0, 0, 0};
EXPECT_FALSE(CorruptionDetectionExtension::Parse(kData, nullptr));
}
TEST(CorruptionDetectionExtensionTest,
FailsToParseWhenTooManySamplesAreSpecified) {
CorruptionDetectionMessage message;
uint8_t data[17];
EXPECT_FALSE(CorruptionDetectionExtension::Parse(data, &message));
}
} // namespace
} // namespace webrtc