Aggregate and log corruption score.
Bug: webrtc:358039777
Change-Id: I4dade8e6daecf41e5b1f156416935c320f513d0b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/359160
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Emil Vardar (xWF) <vardar@google.com>
Cr-Commit-Position: refs/heads/main@{#42867}
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index 59ffd39..4bdf81f 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -129,6 +129,19 @@
int64_t first_frame_received_to_decoded_ms = -1;
absl::optional<uint64_t> qp_sum;
+ // TODO(webrtc:357636606): Propagate this score upwards in the chain.
+ // Corruption score, indicating the probability of corruption. Its value is
+ // 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.
+ absl::optional<double> corruption_score_sum;
+ absl::optional<double> corruption_score_squared_sum;
+ // Number of frames the `corruption_score` was calculated on. This is
+ // usually not the same as `frames_decoded`.
+ uint32_t corruption_score_count = 0;
+
int current_payload_type = -1;
int total_bitrate_bps = 0;
diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc
index 75512a2..b5c03bf 100644
--- a/video/receive_statistics_proxy.cc
+++ b/video/receive_statistics_proxy.cc
@@ -311,6 +311,17 @@
<< " " << *height << '\n';
}
+ absl::optional<double> corruption_score = stats.corruption_score.GetMean();
+ if (corruption_score) {
+ // Granularity level: 2e-3.
+ RTC_HISTOGRAM_COUNTS_SPARSE(uma_prefix + ".CorruptionLikelihoodPermille",
+ static_cast<int>(*corruption_score * 1000),
+ /*min=*/0, /*max=*/1000,
+ /*bucket_count=*/500);
+ log_stream << uma_prefix << ".CorruptionLikelihoodPermille" << " "
+ << static_cast<int>(*corruption_score * 1000) << '\n';
+ }
+
if (content_type != VideoContentType::UNSPECIFIED) {
// Don't report these 3 metrics unsliced, as more precise variants
// are reported separately in this method.
@@ -823,6 +834,25 @@
avg_rtt_ms_ = avg_rtt_ms;
}
+void ReceiveStatisticsProxy::OnCorruptionScore(double corruption_score,
+ VideoContentType content_type) {
+ RTC_DCHECK_RUN_ON(&main_thread_);
+
+ if (!stats_.corruption_score_sum.has_value()) {
+ RTC_DCHECK(!stats_.corruption_score_squared_sum.has_value());
+ RTC_DCHECK_EQ(stats_.corruption_score_count, 0);
+ stats_.corruption_score_sum = 0;
+ stats_.corruption_score_squared_sum = 0;
+ }
+ *stats_.corruption_score_sum += corruption_score;
+ *stats_.corruption_score_squared_sum += corruption_score * corruption_score;
+ ++stats_.corruption_score_count;
+
+ ContentSpecificStats* content_specific_stats =
+ &content_specific_stats_[content_type];
+ content_specific_stats->corruption_score.AddSample(corruption_score);
+}
+
void ReceiveStatisticsProxy::DecoderThreadStarting() {
RTC_DCHECK_RUN_ON(&main_thread_);
}
@@ -849,6 +879,7 @@
frame_counts.key_frames += other.frame_counts.key_frames;
frame_counts.delta_frames += other.frame_counts.delta_frames;
interframe_delay_percentiles.Add(other.interframe_delay_percentiles);
+ corruption_score.MergeStatistics(other.corruption_score);
}
} // namespace internal
diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h
index 8e4941f..a678602 100644
--- a/video/receive_statistics_proxy.h
+++ b/video/receive_statistics_proxy.h
@@ -26,6 +26,7 @@
#include "modules/include/module_common_types.h"
#include "rtc_base/numerics/histogram_percentile_counter.h"
#include "rtc_base/numerics/moving_max_counter.h"
+#include "rtc_base/numerics/running_statistics.h"
#include "rtc_base/numerics/sample_counter.h"
#include "rtc_base/rate_statistics.h"
#include "rtc_base/rate_tracker.h"
@@ -105,6 +106,9 @@
// Implements RtcpCnameCallback.
void OnCname(uint32_t ssrc, absl::string_view cname) override;
+ void OnCorruptionScore(double corruption_score,
+ VideoContentType content_type);
+
// Implements RtcpPacketTypeCounterObserver.
void RtcpPacketTypesCounterUpdated(
uint32_t ssrc,
@@ -143,6 +147,7 @@
rtc::SampleCounter qp_counter;
FrameCounts frame_counts;
rtc::HistogramPercentileCounter interframe_delay_percentiles;
+ webrtc_impl::RunningStatistics<double> corruption_score;
};
// Removes info about old frames and then updates the framerate.
diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc
index 276c113..4b5af07 100644
--- a/video/receive_statistics_proxy_unittest.cc
+++ b/video/receive_statistics_proxy_unittest.cc
@@ -26,6 +26,7 @@
#include "api/video/video_rotation.h"
#include "rtc_base/thread.h"
#include "system_wrappers/include/metrics.h"
+#include "test/gmock.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
#include "test/time_controller/simulated_time_controller.h"
@@ -34,6 +35,8 @@
namespace webrtc {
namespace internal {
namespace {
+using ::testing::DoubleEq;
+
const TimeDelta kFreqOffsetProcessInterval = TimeDelta::Seconds(40);
const uint32_t kRemoteSsrc = 456;
const int kMinRequiredSamples = 200;
@@ -536,6 +539,30 @@
}
}
+TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnCorruptionScore) {
+ EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().corruption_score_sum);
+ EXPECT_EQ(absl::nullopt,
+ statistics_proxy_->GetStats().corruption_score_squared_sum);
+ EXPECT_EQ(0u, statistics_proxy_->GetStats().corruption_score_count);
+
+ const std::vector<double> corruption_scores = {0.5, 0.25, 0.80};
+ const double kExpectedCorruptionScoreSum = 0.5 + 0.25 + 0.80;
+ const double kExpectedCorruptionScoreSquaredSum =
+ 0.5 * 0.5 + 0.25 * 0.25 + 0.80 * 0.80;
+ for (size_t i = 0; i < corruption_scores.size(); ++i) {
+ statistics_proxy_->OnCorruptionScore(
+ /*corruption_score=*/corruption_scores[i],
+ VideoContentType::UNSPECIFIED);
+ }
+
+ VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats();
+ EXPECT_THAT(kExpectedCorruptionScoreSum,
+ DoubleEq(*stats.corruption_score_sum));
+ EXPECT_THAT(kExpectedCorruptionScoreSquaredSum,
+ DoubleEq(*stats.corruption_score_squared_sum));
+ EXPECT_EQ(3u, stats.corruption_score_count);
+}
+
TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsSsrc) {
EXPECT_EQ(kRemoteSsrc, statistics_proxy_->GetStats().ssrc);
}
@@ -1480,6 +1507,42 @@
}
}
+TEST_P(ReceiveStatisticsProxyTestWithContent, CorruptionScore) {
+ const std::vector<double> corruption_scores = {0.5, 0.25, 0.80};
+ const int kCorruptionLikelihoodPermille =
+ static_cast<int>((0.5 + 0.25 + 0.80) / 3 * 1000);
+ for (size_t i = 0; i < corruption_scores.size(); ++i) {
+ statistics_proxy_->OnCorruptionScore(
+ /*corruption_score=*/corruption_scores[i], content_type_);
+ }
+
+ FlushAndGetStats();
+ EXPECT_EQ(3u, statistics_proxy_->GetStats().corruption_score_count);
+
+ statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(),
+ nullptr);
+ if (videocontenttypehelpers::IsScreenshare(content_type_)) {
+ EXPECT_METRIC_EQ(
+ 1, metrics::NumSamples(
+ "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille"));
+ EXPECT_METRIC_EQ(
+ 1, metrics::NumEvents(
+ "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille",
+ kCorruptionLikelihoodPermille));
+ EXPECT_METRIC_EQ(
+ 0, metrics::NumSamples("WebRTC.Video.CorruptionLikelihoodPermille"));
+ } else {
+ EXPECT_METRIC_EQ(
+ 1, metrics::NumSamples("WebRTC.Video.CorruptionLikelihoodPermille"));
+ EXPECT_METRIC_EQ(
+ 1, metrics::NumEvents("WebRTC.Video.CorruptionLikelihoodPermille",
+ kCorruptionLikelihoodPermille));
+ EXPECT_METRIC_EQ(
+ 0, metrics::NumSamples(
+ "WebRTC.Video.Screenshare.CorruptionLikelihoodPermille"));
+ }
+}
+
TEST_P(ReceiveStatisticsProxyTestWithContent, FreezesAreReported) {
const TimeDelta kInterFrameDelay = TimeDelta::Millis(33);
const TimeDelta kFreezeDelay = TimeDelta::Millis(200);