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> pli_count;
|
||||||
std::optional<uint32_t> nack_count;
|
std::optional<uint32_t> nack_count;
|
||||||
std::optional<uint64_t> qp_sum;
|
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"
|
// This is a remnant of the legacy getStats() API. When the "video-timing"
|
||||||
// header extension is used,
|
// header extension is used,
|
||||||
// https://webrtc.github.io/webrtc-org/experiments/rtp-hdrext/video-timing/,
|
// https://webrtc.github.io/webrtc-org/experiments/rtp-hdrext/video-timing/,
|
||||||
|
|||||||
@ -4065,6 +4065,151 @@ TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
|
|||||||
PeerConnectionInterface::kStable);
|
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
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -624,6 +624,16 @@ CreateInboundRTPStreamStatsFromVideoReceiverInfo(
|
|||||||
if (video_receiver_info.qp_sum.has_value()) {
|
if (video_receiver_info.qp_sum.has_value()) {
|
||||||
inbound_video->qp_sum = *video_receiver_info.qp_sum;
|
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()) {
|
if (video_receiver_info.timing_frame_info.has_value()) {
|
||||||
inbound_video->goog_timing_frame_info =
|
inbound_video->goog_timing_frame_info =
|
||||||
video_receiver_info.timing_frame_info->ToString();
|
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].key_frames_decoded = 3;
|
||||||
video_media_info.receivers[0].frames_dropped = 13;
|
video_media_info.receivers[0].frames_dropped = 13;
|
||||||
video_media_info.receivers[0].qp_sum = std::nullopt;
|
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_decode_time = TimeDelta::Seconds(9);
|
||||||
video_media_info.receivers[0].total_processing_delay = TimeDelta::Millis(600);
|
video_media_info.receivers[0].total_processing_delay = TimeDelta::Millis(600);
|
||||||
video_media_info.receivers[0].total_assembly_time = TimeDelta::Millis(500);
|
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.key_frames_decoded = 3;
|
||||||
expected_video.frames_dropped = 13;
|
expected_video.frames_dropped = 13;
|
||||||
// `expected_video.qp_sum` should be undefined.
|
// `expected_video.qp_sum` should be undefined.
|
||||||
|
// `corruption_score` related metrics should be undefined.
|
||||||
expected_video.total_decode_time = 9.0;
|
expected_video.total_decode_time = 9.0;
|
||||||
expected_video.total_processing_delay = 0.6;
|
expected_video.total_processing_delay = 0.6;
|
||||||
expected_video.total_assembly_time = 0.5;
|
expected_video.total_assembly_time = 0.5;
|
||||||
@ -2453,6 +2456,12 @@ TEST_F(RTCStatsCollectorTest, CollectRTCInboundRtpStreamStats_Video) {
|
|||||||
// Set previously undefined values and "GetStats" again.
|
// Set previously undefined values and "GetStats" again.
|
||||||
video_media_info.receivers[0].qp_sum = 9;
|
video_media_info.receivers[0].qp_sum = 9;
|
||||||
expected_video.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);
|
video_media_info.receivers[0].last_packet_received = Timestamp::Seconds(1);
|
||||||
expected_video.last_packet_received_timestamp = 1000.0;
|
expected_video.last_packet_received_timestamp = 1000.0;
|
||||||
video_media_info.receivers[0].content_type = VideoContentType::SCREENSHARE;
|
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.decoder_implementation);
|
||||||
verifier.TestAttributeIsUndefined(inbound_stream.power_efficient_decoder);
|
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>(
|
verifier.TestAttributeIsNonNegative<uint32_t>(
|
||||||
inbound_stream.packets_received);
|
inbound_stream.packets_received);
|
||||||
if (inbound_stream.kind.has_value() && *inbound_stream.kind == "audio") {
|
if (inbound_stream.kind.has_value() && *inbound_stream.kind == "audio") {
|
||||||
|
|||||||
@ -650,6 +650,46 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
|
|||||||
return observer->error().ok();
|
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:
|
private:
|
||||||
// Constructor used by friend class PeerConnectionIntegrationBaseTest.
|
// Constructor used by friend class PeerConnectionIntegrationBaseTest.
|
||||||
explicit PeerConnectionIntegrationWrapper(const std::string& debug_name)
|
explicit PeerConnectionIntegrationWrapper(const std::string& debug_name)
|
||||||
@ -997,6 +1037,14 @@ class PeerConnectionIntegrationWrapper : public PeerConnectionObserver,
|
|||||||
data_observers_.push_back(
|
data_observers_.push_back(
|
||||||
std::make_unique<MockDataChannelObserver>(data_channel.get()));
|
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_;
|
std::string debug_name_;
|
||||||
|
|
||||||
|
|||||||
@ -267,6 +267,9 @@ WEBRTC_RTCSTATS_IMPL(
|
|||||||
AttributeInit("pliCount", &pli_count),
|
AttributeInit("pliCount", &pli_count),
|
||||||
AttributeInit("nackCount", &nack_count),
|
AttributeInit("nackCount", &nack_count),
|
||||||
AttributeInit("qpSum", &qp_sum),
|
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("googTimingFrameInfo", &goog_timing_frame_info),
|
||||||
AttributeInit("powerEfficientDecoder", &power_efficient_decoder),
|
AttributeInit("powerEfficientDecoder", &power_efficient_decoder),
|
||||||
AttributeInit("jitterBufferFlushes", &jitter_buffer_flushes),
|
AttributeInit("jitterBufferFlushes", &jitter_buffer_flushes),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user