Propagate the corruption_score metric to RTCInboundRtpStreamStats.
Bug: webrtc:358039777 Change-Id: I7e956188a5ef913cbe1647d00ca02b5a46a99b3a Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/362083 Reviewed-by: Erik Språng <sprang@webrtc.org> Commit-Queue: Emil Vardar (xWF) <vardar@google.com> Reviewed-by: Henrik Boström <hbos@webrtc.org> Cr-Commit-Position: refs/heads/main@{#43281}
This commit is contained in:
parent
3f1a04acf9
commit
a2205e3943
@ -297,6 +297,10 @@ class RTC_EXPORT RTCInboundRtpStreamStats final
|
||||
std::optional<uint32_t> pli_count;
|
||||
std::optional<uint32_t> nack_count;
|
||||
std::optional<uint64_t> qp_sum;
|
||||
// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/corruption-detection
|
||||
std::optional<double> corruption_score_sum;
|
||||
std::optional<double> corruption_score_squared_sum;
|
||||
std::optional<uint32_t> corruption_score_count;
|
||||
// This is a remnant of the legacy getStats() API. When the "video-timing"
|
||||
// header extension is used,
|
||||
// https://webrtc.github.io/webrtc-org/experiments/rtp-hdrext/video-timing/,
|
||||
|
||||
@ -4065,6 +4065,151 @@ TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
|
||||
PeerConnectionInterface::kStable);
|
||||
}
|
||||
|
||||
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
|
||||
OnlyOnePairWantsCorruptionScorePlumbing) {
|
||||
// In order for corruption score to be logged, encryption of RTP header
|
||||
// extensions must be allowed.
|
||||
CryptoOptions crypto_options;
|
||||
crypto_options.srtp.enable_encrypted_rtp_header_extensions = true;
|
||||
PeerConnectionInterface::RTCConfiguration config;
|
||||
config.crypto_options = crypto_options;
|
||||
config.offer_extmap_allow_mixed = true;
|
||||
ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
|
||||
ConnectFakeSignaling();
|
||||
|
||||
// Munge the corruption detection header extension into the SDP.
|
||||
// If caller adds corruption detection header extension to its SDP offer, it
|
||||
// will receive it from the callee.
|
||||
caller()->AddCorruptionDetectionHeader();
|
||||
|
||||
// Do normal offer/answer and wait for some frames to be received in each
|
||||
// direction, and `corruption_score` to be aggregated.
|
||||
caller()->AddAudioVideoTracks();
|
||||
callee()->AddAudioVideoTracks();
|
||||
caller()->CreateAndSetAndSignalOffer();
|
||||
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
|
||||
ASSERT_TRUE_WAIT(caller()->GetCorruptionScoreCount() > 0, kMaxWaitForStatsMs);
|
||||
ASSERT_TRUE_WAIT(callee()->GetCorruptionScoreCount() == 0,
|
||||
kMaxWaitForStatsMs);
|
||||
|
||||
for (const auto& pair : {caller(), callee()}) {
|
||||
rtc::scoped_refptr<const RTCStatsReport> report = pair->NewGetStats();
|
||||
ASSERT_TRUE(report);
|
||||
auto inbound_stream_stats =
|
||||
report->GetStatsOfType<RTCInboundRtpStreamStats>();
|
||||
for (const auto& stat : inbound_stream_stats) {
|
||||
if (*stat->kind == "video") {
|
||||
if (pair == caller()) {
|
||||
EXPECT_TRUE(stat->corruption_score_sum.has_value());
|
||||
EXPECT_TRUE(stat->corruption_score_squared_sum.has_value());
|
||||
|
||||
double average_corruption_score =
|
||||
(*stat->corruption_score_sum) /
|
||||
static_cast<int32_t>(*stat->corruption_score_count);
|
||||
EXPECT_GE(average_corruption_score, 0.0);
|
||||
EXPECT_LE(average_corruption_score, 1.0);
|
||||
}
|
||||
if (pair == callee()) {
|
||||
// Since only `caller` requests corruption score calculation the
|
||||
// callee should not aggregate it.
|
||||
EXPECT_FALSE(stat->corruption_score_sum.has_value());
|
||||
EXPECT_FALSE(stat->corruption_score_squared_sum.has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
|
||||
BothPairsWantCorruptionScorePlumbing) {
|
||||
// In order for corruption score to be logged, encryption of RTP header
|
||||
// extensions must be allowed.
|
||||
CryptoOptions crypto_options;
|
||||
crypto_options.srtp.enable_encrypted_rtp_header_extensions = true;
|
||||
PeerConnectionInterface::RTCConfiguration config;
|
||||
config.crypto_options = crypto_options;
|
||||
config.offer_extmap_allow_mixed = true;
|
||||
ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
|
||||
ConnectFakeSignaling();
|
||||
|
||||
// Munge the corruption detection header extension into the SDP.
|
||||
// If caller adds corruption detection header extension to its SDP offer, it
|
||||
// will receive it from the callee.
|
||||
caller()->AddCorruptionDetectionHeader();
|
||||
callee()->AddCorruptionDetectionHeader();
|
||||
|
||||
// Do normal offer/answer and wait for some frames to be received in each
|
||||
// direction, and `corruption_score` to be aggregated.
|
||||
caller()->AddAudioVideoTracks();
|
||||
callee()->AddAudioVideoTracks();
|
||||
caller()->CreateAndSetAndSignalOffer();
|
||||
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
|
||||
ASSERT_TRUE_WAIT(caller()->GetCorruptionScoreCount() > 0, kMaxWaitForStatsMs);
|
||||
ASSERT_TRUE_WAIT(callee()->GetCorruptionScoreCount() > 0, kMaxWaitForStatsMs);
|
||||
|
||||
for (const auto& pair : {caller(), callee()}) {
|
||||
rtc::scoped_refptr<const RTCStatsReport> report = pair->NewGetStats();
|
||||
ASSERT_TRUE(report);
|
||||
auto inbound_stream_stats =
|
||||
report->GetStatsOfType<RTCInboundRtpStreamStats>();
|
||||
for (const auto& stat : inbound_stream_stats) {
|
||||
if (*stat->kind == "video") {
|
||||
EXPECT_TRUE(stat->corruption_score_sum.has_value());
|
||||
EXPECT_TRUE(stat->corruption_score_squared_sum.has_value());
|
||||
|
||||
double average_corruption_score =
|
||||
(*stat->corruption_score_sum) /
|
||||
static_cast<int32_t>(*stat->corruption_score_count);
|
||||
EXPECT_GE(average_corruption_score, 0.0);
|
||||
EXPECT_LE(average_corruption_score, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
|
||||
CorruptionScorePlumbingShouldNotWorkWhenEncryptionIsOff) {
|
||||
// In order for corruption score to be logged, encryption of RTP header
|
||||
// extensions must be allowed.
|
||||
CryptoOptions crypto_options;
|
||||
crypto_options.srtp.enable_encrypted_rtp_header_extensions = false;
|
||||
PeerConnectionInterface::RTCConfiguration config;
|
||||
config.crypto_options = crypto_options;
|
||||
config.offer_extmap_allow_mixed = true;
|
||||
ASSERT_TRUE(CreatePeerConnectionWrappersWithConfig(config, config));
|
||||
ConnectFakeSignaling();
|
||||
|
||||
// Munge the corruption detection header extension into the SDP.
|
||||
// If caller adds corruption detection header extension to its SDP offer, it
|
||||
// will receive it from the callee.
|
||||
caller()->AddCorruptionDetectionHeader();
|
||||
callee()->AddCorruptionDetectionHeader();
|
||||
|
||||
// Do normal offer/answer and wait for some frames to be received in each
|
||||
// direction, and `corruption_score` to be aggregated.
|
||||
caller()->AddAudioVideoTracks();
|
||||
callee()->AddAudioVideoTracks();
|
||||
caller()->CreateAndSetAndSignalOffer();
|
||||
ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
|
||||
ASSERT_TRUE_WAIT(caller()->GetCorruptionScoreCount() == 0,
|
||||
kMaxWaitForStatsMs);
|
||||
ASSERT_TRUE_WAIT(callee()->GetCorruptionScoreCount() == 0,
|
||||
kMaxWaitForStatsMs);
|
||||
|
||||
for (const auto& pair : {caller(), callee()}) {
|
||||
rtc::scoped_refptr<const RTCStatsReport> report = pair->NewGetStats();
|
||||
ASSERT_TRUE(report);
|
||||
auto inbound_stream_stats =
|
||||
report->GetStatsOfType<RTCInboundRtpStreamStats>();
|
||||
for (const auto& stat : inbound_stream_stats) {
|
||||
if (*stat->kind == "video") {
|
||||
EXPECT_FALSE(stat->corruption_score_sum.has_value());
|
||||
EXPECT_FALSE(stat->corruption_score_squared_sum.has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
@ -624,6 +624,16 @@ CreateInboundRTPStreamStatsFromVideoReceiverInfo(
|
||||
if (video_receiver_info.qp_sum.has_value()) {
|
||||
inbound_video->qp_sum = *video_receiver_info.qp_sum;
|
||||
}
|
||||
if (video_receiver_info.corruption_score_sum.has_value()) {
|
||||
RTC_CHECK(video_receiver_info.corruption_score_squared_sum.has_value());
|
||||
RTC_CHECK_GT(video_receiver_info.corruption_score_count, 0);
|
||||
inbound_video->corruption_score_sum =
|
||||
*video_receiver_info.corruption_score_sum;
|
||||
inbound_video->corruption_score_squared_sum =
|
||||
*video_receiver_info.corruption_score_squared_sum;
|
||||
inbound_video->corruption_score_count =
|
||||
video_receiver_info.corruption_score_count;
|
||||
}
|
||||
if (video_receiver_info.timing_frame_info.has_value()) {
|
||||
inbound_video->goog_timing_frame_info =
|
||||
video_receiver_info.timing_frame_info->ToString();
|
||||
|
||||
@ -2346,6 +2346,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Video) {
|
||||
video_media_info.receivers[0].key_frames_decoded = 3;
|
||||
video_media_info.receivers[0].frames_dropped = 13;
|
||||
video_media_info.receivers[0].qp_sum = std::nullopt;
|
||||
video_media_info.receivers[0].corruption_score_sum = std::nullopt;
|
||||
video_media_info.receivers[0].corruption_score_squared_sum = std::nullopt;
|
||||
video_media_info.receivers[0].total_decode_time = TimeDelta::Seconds(9);
|
||||
video_media_info.receivers[0].total_processing_delay = TimeDelta::Millis(600);
|
||||
video_media_info.receivers[0].total_assembly_time = TimeDelta::Millis(500);
|
||||
@ -2417,6 +2419,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Video) {
|
||||
expected_video.key_frames_decoded = 3;
|
||||
expected_video.frames_dropped = 13;
|
||||
// `expected_video.qp_sum` should be undefined.
|
||||
// `corruption_score` related metrics should be undefined.
|
||||
expected_video.total_decode_time = 9.0;
|
||||
expected_video.total_processing_delay = 0.6;
|
||||
expected_video.total_assembly_time = 0.5;
|
||||
@ -2453,6 +2456,12 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Video) {
|
||||
// Set previously undefined values and "GetStats" again.
|
||||
video_media_info.receivers[0].qp_sum = 9;
|
||||
expected_video.qp_sum = 9;
|
||||
video_media_info.receivers[0].corruption_score_sum = 0.5;
|
||||
video_media_info.receivers[0].corruption_score_squared_sum = 0.25;
|
||||
video_media_info.receivers[0].corruption_score_count = 5;
|
||||
expected_video.corruption_score_sum = 0.5;
|
||||
expected_video.corruption_score_squared_sum = 0.25;
|
||||
expected_video.corruption_score_count = 5;
|
||||
video_media_info.receivers[0].last_packet_received = Timestamp::Seconds(1);
|
||||
expected_video.last_packet_received_timestamp = 1000.0;
|
||||
video_media_info.receivers[0].content_type = VideoContentType::SCREENSHARE;
|
||||
|
||||
@ -560,6 +560,13 @@ class RTCStatsReportVerifier {
|
||||
verifier.TestAttributeIsUndefined(inbound_stream.decoder_implementation);
|
||||
verifier.TestAttributeIsUndefined(inbound_stream.power_efficient_decoder);
|
||||
}
|
||||
// As long as the corruption detection RTP header extension is not activated
|
||||
// it should not aggregate any corruption score. The tests where this header
|
||||
// extension is enabled are located in pc/peer_connection_integrationtest.cc
|
||||
verifier.TestAttributeIsUndefined(inbound_stream.corruption_score_sum);
|
||||
verifier.TestAttributeIsUndefined(
|
||||
inbound_stream.corruption_score_squared_sum);
|
||||
verifier.TestAttributeIsUndefined(inbound_stream.corruption_score_count);
|
||||
verifier.TestAttributeIsNonNegative<uint32_t>(
|
||||
inbound_stream.packets_received);
|
||||
if (inbound_stream.kind.has_value() && *inbound_stream.kind == "audio") {
|
||||
|
||||
@ -650,6 +650,46 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
|
||||
return observer->error().ok();
|
||||
}
|
||||
|
||||
void AddCorruptionDetectionHeader() {
|
||||
SetGeneratedSdpMunger(
|
||||
[&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
|
||||
for (ContentInfo& content : sdp->description()->contents()) {
|
||||
cricket::MediaContentDescription* media =
|
||||
content.media_description();
|
||||
// Corruption detection is only a valid RTP header extension for
|
||||
// video stream.
|
||||
if (media->type() != cricket::MediaType::MEDIA_TYPE_VIDEO) {
|
||||
continue;
|
||||
}
|
||||
cricket::RtpHeaderExtensions extensions =
|
||||
media->rtp_header_extensions();
|
||||
|
||||
// Find a valid id.
|
||||
int id = extensions.size();
|
||||
while (IdExists(extensions, id)) {
|
||||
++id;
|
||||
}
|
||||
|
||||
extensions.push_back(RtpExtension(
|
||||
RtpExtension::kCorruptionDetectionUri, id, /*encrypt=*/true));
|
||||
media->set_rtp_header_extensions(extensions);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t GetCorruptionScoreCount() {
|
||||
rtc::scoped_refptr<const RTCStatsReport> report = NewGetStats();
|
||||
auto inbound_stream_stats =
|
||||
report->GetStatsOfType<RTCInboundRtpStreamStats>();
|
||||
for (const auto& stat : inbound_stream_stats) {
|
||||
if (*stat->kind == "video") {
|
||||
return stat->corruption_score_count.value_or(0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor used by friend class PeerConnectionIntegrationBaseTest.
|
||||
explicit PeerConnectionIntegrationWrapper(const std::string& debug_name)
|
||||
@ -997,6 +1037,14 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
|
||||
data_observers_.push_back(
|
||||
std::make_unique<MockDataChannelObserver>(data_channel.get()));
|
||||
}
|
||||
bool IdExists(const cricket::RtpHeaderExtensions& extensions, int id) {
|
||||
for (const auto& extension : extensions) {
|
||||
if (extension.id == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string debug_name_;
|
||||
|
||||
|
||||
@ -267,6 +267,9 @@ WEBRTC_RTCSTATS_IMPL(
|
||||
AttributeInit("pliCount", &pli_count),
|
||||
AttributeInit("nackCount", &nack_count),
|
||||
AttributeInit("qpSum", &qp_sum),
|
||||
AttributeInit("corruptionScoreSum", &corruption_score_sum),
|
||||
AttributeInit("corruptionScoreSumSquared", &corruption_score_squared_sum),
|
||||
AttributeInit("corruptionScoreCount", &corruption_score_count),
|
||||
AttributeInit("googTimingFrameInfo", &goog_timing_frame_info),
|
||||
AttributeInit("powerEfficientDecoder", &power_efficient_decoder),
|
||||
AttributeInit("jitterBufferFlushes", &jitter_buffer_flushes),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user