Implement L4S inbound-rtp stats
described in
https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithect1
https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithce
This largely mirrors the send side stats:
https://webrtc-review.googlesource.com/c/src/+/390866
The statistics on the remote-inbound-rtp that are sent to the sender via RFC 8888 feedback are not implemented yet.
Bug: webrtc:42225697
Change-Id: Id680c4361d4d7e563b069446c5365388322af55b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/403188
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@meta.com>
Cr-Commit-Position: refs/heads/main@{#45311}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 46fe7cc..6f5d108 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -196,6 +196,10 @@
std::optional<double> jitter;
std::optional<int32_t> packets_lost; // Signed per RFC 3550
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithect1
+ std::optional<int64_t> packets_received_with_ect1;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithce
+ std::optional<int64_t> packets_received_with_ce;
protected:
RTCReceivedRtpStreamStats(std::string id, Timestamp timestamp);
@@ -371,7 +375,7 @@
std::optional<uint32_t> rtx_ssrc;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-packetssentwithect1
- std::optional<uint64_t> packets_sent_with_ect1;
+ std::optional<int64_t> packets_sent_with_ect1;
};
// https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict*
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index ab2383f..bbe8480 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -280,6 +280,8 @@
stats.header_and_padding_bytes_received =
call_stats.header_and_padding_bytes_received;
stats.packets_received = call_stats.packets_received;
+ stats.packets_received_with_ect1 = call_stats.packets_received_with_ect1;
+ stats.packets_received_with_ce = call_stats.packets_received_with_ce;
stats.packets_lost = call_stats.packets_lost;
stats.jitter_ms = call_stats.jitter_ms;
stats.nacks_sent = call_stats.nacks_sent;
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
index 724fb32..658266e 100644
--- a/audio/channel_receive.cc
+++ b/audio/channel_receive.cc
@@ -851,6 +851,9 @@
rtp_stats.packet_counter.header_bytes +
rtp_stats.packet_counter.padding_bytes;
stats.packets_received = rtp_stats.packet_counter.packets;
+ stats.packets_received_with_ect1 =
+ rtp_stats.packet_counter.packets_with_ect1;
+ stats.packets_received_with_ce = rtp_stats.packet_counter.packets_with_ce;
stats.last_packet_received = rtp_stats.last_packet_received;
}
diff --git a/audio/channel_receive.h b/audio/channel_receive.h
index 6179c4f..0783363 100644
--- a/audio/channel_receive.h
+++ b/audio/channel_receive.h
@@ -55,6 +55,10 @@
int64_t payload_bytes_received = 0;
int64_t header_and_padding_bytes_received = 0;
int packets_received = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithect1
+ int64_t packets_received_with_ect1 = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithce
+ int64_t packets_received_with_ce = 0;
uint32_t nacks_sent = 0;
// The capture NTP time (in local timebase) of the first played out audio
// frame.
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index 4ae9ba0..d7ab975 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -44,6 +44,10 @@
int64_t payload_bytes_received = 0;
int64_t header_and_padding_bytes_received = 0;
uint32_t packets_received = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithect1
+ int64_t packets_received_with_ect1 = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithce
+ int64_t packets_received_with_ce = 0;
uint64_t fec_packets_received = 0;
uint64_t fec_packets_discarded = 0;
int32_t packets_lost = 0;
diff --git a/call/rtp_transport_controller_send.cc b/call/rtp_transport_controller_send.cc
index cbef660..fdd7bcc 100644
--- a/call/rtp_transport_controller_send.cc
+++ b/call/rtp_transport_controller_send.cc
@@ -668,9 +668,9 @@
// TODO: bugs.webrtc.org/42225697 - adapt to ECN feedback and continue to
// send packets as ECT(1) if transport is ECN capable.
sending_packets_as_ect1_ = false;
- RTC_LOG(LS_INFO) << " Transport is "
- << (feedback.transport_supports_ecn ? "" : " not ")
- << " ECN capable. Stop sending ECT(1).";
+ RTC_LOG(LS_INFO) << "Transport is "
+ << (feedback.transport_supports_ecn ? "" : "not ")
+ << "ECN capable. Stop sending ECT(1).";
packet_router_.ConfigureForRfc8888Feedback(sending_packets_as_ect1_);
}
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 17ad9ad..373f160 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -367,7 +367,7 @@
uint64_t retransmitted_bytes_sent = 0;
int packets_sent = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-packetssentwithect1
- int packets_sent_with_ect1 = 0;
+ int64_t packets_sent_with_ect1 = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-retransmittedpacketssent
uint64_t retransmitted_packets_sent = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-nackcount
@@ -431,6 +431,10 @@
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-headerbytesreceived
int64_t header_and_padding_bytes_received = 0;
int packets_received = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithect1
+ int64_t packets_received_with_ect1 = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsreceivedwithce
+ int64_t packets_received_with_ce = 0;
int packets_lost = 0;
std::optional<uint64_t> retransmitted_bytes_received;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index cef81f9..faa378f 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -3787,6 +3787,10 @@
stats.rtp_stats.packet_counter.header_bytes +
stats.rtp_stats.packet_counter.padding_bytes;
info.packets_received = stats.rtp_stats.packet_counter.packets;
+ info.packets_received_with_ect1 =
+ stats.rtp_stats.packet_counter.packets_with_ect1;
+ info.packets_received_with_ce =
+ stats.rtp_stats.packet_counter.packets_with_ce;
info.packets_lost = stats.rtp_stats.packets_lost;
info.jitter_ms = stats.rtp_stats.jitter / (kVideoCodecClockrate / 1000);
diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.cc b/modules/rtp_rtcp/include/rtp_rtcp_defines.cc
index 1ac853b..f61db06 100644
--- a/modules/rtp_rtcp/include/rtp_rtcp_defines.cc
+++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.cc
@@ -15,8 +15,10 @@
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
+#include "api/transport/ecn_marking.h"
#include "api/units/time_delta.h"
#include "modules/rtp_rtcp/source/rtp_packet.h"
+#include "modules/rtp_rtcp/source/rtp_packet_received.h"
#include "modules/rtp_rtcp/source/rtp_packet_to_send.h"
namespace webrtc {
@@ -52,7 +54,8 @@
payload_bytes(packet.payload_size()),
padding_bytes(packet.padding_size()),
packets(1),
- packets_with_ect1(0) {}
+ packets_with_ect1(0),
+ packets_with_ce(0) {}
RtpPacketCounter::RtpPacketCounter(const RtpPacketToSend& packet_to_send)
: RtpPacketCounter(static_cast<const RtpPacket&>(packet_to_send)) {
@@ -63,6 +66,16 @@
}
}
+RtpPacketCounter::RtpPacketCounter(const RtpPacketReceived& packet_received)
+ : RtpPacketCounter(static_cast<const RtpPacket&>(packet_received)) {
+ EcnMarking ecn = packet_received.ecn();
+ if (ecn == EcnMarking::kEct1) {
+ ++packets_with_ect1;
+ } else if (ecn == EcnMarking::kCe) {
+ ++packets_with_ce;
+ }
+}
+
void RtpPacketCounter::AddPacket(const RtpPacket& packet) {
++packets;
header_bytes += packet.headers_size();
@@ -79,4 +92,14 @@
}
}
+void RtpPacketCounter::AddPacket(const RtpPacketReceived& packet_received) {
+ AddPacket(static_cast<const RtpPacket&>(packet_received));
+ EcnMarking ecn = packet_received.ecn();
+ if (ecn == EcnMarking::kEct1) {
+ ++packets_with_ect1;
+ } else if (ecn == EcnMarking::kCe) {
+ ++packets_with_ce;
+ }
+}
+
} // namespace webrtc
diff --git a/modules/rtp_rtcp/include/rtp_rtcp_defines.h b/modules/rtp_rtcp/include/rtp_rtcp_defines.h
index 2552792..cb4decc 100644
--- a/modules/rtp_rtcp/include/rtp_rtcp_defines.h
+++ b/modules/rtp_rtcp/include/rtp_rtcp_defines.h
@@ -38,6 +38,8 @@
namespace webrtc {
class RtpPacket;
class RtpPacketToSend;
+class RtpPacketReceived;
+
namespace rtcp {
class TransportFeedback;
} // namespace rtcp
@@ -272,10 +274,12 @@
payload_bytes(0),
padding_bytes(0),
packets(0),
- packets_with_ect1(0) {}
+ packets_with_ect1(0),
+ packets_with_ce(0) {}
explicit RtpPacketCounter(const RtpPacket& packet);
explicit RtpPacketCounter(const RtpPacketToSend& packet_to_send);
+ explicit RtpPacketCounter(const RtpPacketReceived& packet_received);
void Add(const RtpPacketCounter& other) {
header_bytes += other.header_bytes;
@@ -283,6 +287,7 @@
padding_bytes += other.padding_bytes;
packets += other.packets;
packets_with_ect1 += other.packets_with_ect1;
+ packets_with_ce += other.packets_with_ce;
total_packet_delay += other.total_packet_delay;
}
@@ -291,12 +296,14 @@
payload_bytes == other.payload_bytes &&
padding_bytes == other.padding_bytes && packets == other.packets &&
packets_with_ect1 == other.packets_with_ect1 &&
+ packets_with_ce == other.packets_with_ce &&
total_packet_delay == other.total_packet_delay;
}
// Not inlined, since use of RtpPacket would result in circular includes.
void AddPacket(const RtpPacket& packet);
void AddPacket(const RtpPacketToSend& packet_to_send);
+ void AddPacket(const RtpPacketReceived& packet_received);
size_t TotalBytes() const {
return header_bytes + payload_bytes + padding_bytes;
@@ -307,6 +314,7 @@
size_t padding_bytes; // Number of padding bytes.
size_t packets; // Number of packets.
size_t packets_with_ect1; // Number of packets with ECT1 flag set to true.
+ size_t packets_with_ce; // Number of packets with CE flag set to true.
// The total delay of all `packets`. For RtpPacketToSend packets, this is
// `time_in_send_queue()`. For receive packets, this is zero.
TimeDelta total_packet_delay = TimeDelta::Zero();
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 451d9ce..be90539 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -397,6 +397,10 @@
inbound_stats->ssrc = media_receiver_info.ssrc();
inbound_stats->packets_received =
static_cast<uint32_t>(media_receiver_info.packets_received);
+ inbound_stats->packets_received_with_ect1 =
+ media_receiver_info.packets_received_with_ect1;
+ inbound_stats->packets_received_with_ce =
+ media_receiver_info.packets_received_with_ce;
inbound_stats->bytes_received =
static_cast<uint64_t>(media_receiver_info.payload_bytes_received);
inbound_stats->header_bytes_received = static_cast<uint64_t>(
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 737b71f..19264d5 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -2147,6 +2147,8 @@
voice_media_info.receivers[0].packets_lost = -1; // Signed per RFC3550
voice_media_info.receivers[0].packets_discarded = 7788;
voice_media_info.receivers[0].packets_received = 2;
+ voice_media_info.receivers[0].packets_received_with_ect1 = 7;
+ voice_media_info.receivers[0].packets_received_with_ce = 5;
voice_media_info.receivers[0].nacks_sent = 5;
voice_media_info.receivers[0].fec_packets_discarded = 5566;
voice_media_info.receivers[0].fec_packets_received = 6677;
@@ -2204,6 +2206,8 @@
expected_audio.transport_id = "TTransportName1";
expected_audio.codec_id = "CITTransportName1_42";
expected_audio.packets_received = 2;
+ expected_audio.packets_received_with_ect1 = 7;
+ expected_audio.packets_received_with_ce = 5;
expected_audio.nack_count = 5;
expected_audio.fec_packets_discarded = 5566;
expected_audio.fec_packets_received = 6677;
@@ -2300,6 +2304,8 @@
video_media_info.receivers[0].local_stats.push_back(SsrcReceiverInfo());
video_media_info.receivers[0].local_stats[0].ssrc = 1;
video_media_info.receivers[0].packets_received = 2;
+ video_media_info.receivers[0].packets_received_with_ect1 = 7;
+ video_media_info.receivers[0].packets_received_with_ce = 5;
video_media_info.receivers[0].packets_lost = 42;
video_media_info.receivers[0].payload_bytes_received = 3;
video_media_info.receivers[0].header_and_padding_bytes_received = 12;
@@ -2377,6 +2383,8 @@
expected_video.pli_count = 6;
expected_video.nack_count = 7;
expected_video.packets_received = 2;
+ expected_video.packets_received_with_ect1 = 7;
+ expected_video.packets_received_with_ce = 5;
expected_video.bytes_received = 3;
expected_video.header_bytes_received = 12;
expected_video.packets_lost = 42;
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index 3b7f616..342637d 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -556,6 +556,12 @@
inbound_stream.remote_id, RTCRemoteOutboundRtpStreamStats::kType);
verifier.TestAttributeIsDefined(inbound_stream.mid);
verifier.TestAttributeIsDefined(inbound_stream.track_identifier);
+ // TODO: bugs.webrtc.org/42225697 - move to RTCReceivedRtpStreamStats
+ // when wiring the RFC 8888 feedback to stats.
+ verifier.TestAttributeIsNonNegative<int64_t>(
+ inbound_stream.packets_received_with_ect1);
+ verifier.TestAttributeIsNonNegative<int64_t>(
+ inbound_stream.packets_received_with_ce);
if (inbound_stream.kind.has_value() && *inbound_stream.kind == "video") {
verifier.TestAttributeIsNonNegative<uint64_t>(inbound_stream.qp_sum);
verifier.TestAttributeIsDefined(inbound_stream.decoder_implementation);
@@ -799,7 +805,7 @@
verifier.TestAttributeIsNonNegative<uint64_t>(
outbound_stream.retransmitted_bytes_sent);
verifier.TestAttributeIsNonNegative<double>(outbound_stream.target_bitrate);
- verifier.TestAttributeIsNonNegative<uint64_t>(
+ verifier.TestAttributeIsNonNegative<int64_t>(
outbound_stream.packets_sent_with_ect1);
if (outbound_stream.kind.has_value() && *outbound_stream.kind == "video") {
verifier.TestAttributeIsDefined(outbound_stream.frames_encoded);
@@ -896,6 +902,12 @@
remote_inbound_stream.total_round_trip_time);
verifier.TestAttributeIsNonNegative<int32_t>(
remote_inbound_stream.round_trip_time_measurements);
+ // TODO: bugs.webrtc.org/42225697 - move to RTCReceivedRtpStreamStats
+ // when wiring the RFC 8888 feedback to stats.
+ verifier.TestAttributeIsUndefined<int64_t>(
+ remote_inbound_stream.packets_received_with_ect1);
+ verifier.TestAttributeIsUndefined<int64_t>(
+ remote_inbound_stream.packets_received_with_ce);
return verifier.ExpectAllAttributesSuccessfullyTested();
}
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 7d2bb64..ff383d9 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -186,7 +186,9 @@
WEBRTC_RTCSTATS_IMPL(
RTCReceivedRtpStreamStats, RTCRtpStreamStats, "received-rtp",
AttributeInit("jitter", &jitter),
- AttributeInit("packetsLost", &packets_lost))
+ AttributeInit("packetsLost", &packets_lost),
+ AttributeInit("packetsReceivedWithEct1", &packets_received_with_ect1),
+ AttributeInit("packetsReceivedWithCn", &packets_received_with_ce))
// clang-format on
RTCReceivedRtpStreamStats::RTCReceivedRtpStreamStats(std::string id,
diff --git a/test/peer_scenario/tests/l4s_test.cc b/test/peer_scenario/tests/l4s_test.cc
index 0cd0315..293c6bf 100644
--- a/test/peer_scenario/tests/l4s_test.cc
+++ b/test/peer_scenario/tests/l4s_test.cc
@@ -124,7 +124,7 @@
return DataRate::BitsPerSec(*stats[0]->available_outgoing_bitrate);
}
-std::optional<uint64_t> GetPacketsSentWithEct1(
+std::optional<int64_t> GetPacketsSentWithEct1(
const scoped_refptr<const RTCStatsReport>& report) {
auto stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>();
if (stats.empty()) {
@@ -133,6 +133,24 @@
return stats[0]->packets_sent_with_ect1;
}
+std::optional<int64_t> GetPacketsReceivedWithEct1(
+ const scoped_refptr<const RTCStatsReport>& report) {
+ auto stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ if (stats.empty()) {
+ return std::nullopt;
+ }
+ return stats[0]->packets_received_with_ect1;
+}
+
+std::optional<int64_t> GetPacketsReceivedWithCe(
+ const scoped_refptr<const RTCStatsReport>& report) {
+ auto stats = report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ if (stats.empty()) {
+ return std::nullopt;
+ }
+ return stats[0]->packets_received_with_ce;
+}
+
TEST(L4STest, NegotiateAndUseCcfbIfEnabled) {
PeerScenario s(*test_info_);
@@ -380,11 +398,11 @@
feedback_counter.Count(packet);
if (feedback_counter.ect1() > 0) {
seen_ect1_feedback = true;
- RTC_LOG(LS_INFO) << " ect 1: " << feedback_counter.ect1();
+ RTC_LOG(LS_INFO) << "ect 1 feedback: " << feedback_counter.ect1();
}
if (feedback_counter.not_ect() > 0) {
seen_not_ect_feedback = true;
- RTC_LOG(LS_INFO) << " not ect: " << feedback_counter.not_ect();
+ RTC_LOG(LS_INFO) << "not ect feedback: " << feedback_counter.not_ect();
}
});
@@ -447,12 +465,12 @@
wifi_feedback_counter.Count(packet);
if (wifi_feedback_counter.ect1() > 0) {
seen_ect1_on_wifi_feedback = true;
- RTC_LOG(LS_INFO) << " ect 1 feedback on wifi: "
+ RTC_LOG(LS_INFO) << "ect 1 feedback on wifi: "
<< wifi_feedback_counter.ect1();
}
if (wifi_feedback_counter.not_ect() > 0) {
seen_not_ect_on_wifi_feedback = true;
- RTC_LOG(LS_INFO) << " not ect feedback on wifi: "
+ RTC_LOG(LS_INFO) << "not ect feedback on wifi: "
<< wifi_feedback_counter.not_ect();
}
});
@@ -487,7 +505,7 @@
cellular_feedback_counter.Count(packet);
if (cellular_feedback_counter.ect1() > 0) {
seen_ect1_on_cellular_feedback = true;
- RTC_LOG(LS_INFO) << " ect 1 feedback on cellular: "
+ RTC_LOG(LS_INFO) << "ect 1 feedback on cellular: "
<< cellular_feedback_counter.ect1();
}
});
@@ -496,10 +514,20 @@
s.net()->DisableEndpoint(callee->endpoint(0));
EXPECT_TRUE(
s.WaitAndProcess(&seen_ect1_on_cellular_feedback, TimeDelta::Seconds(5)));
+
+ // Check statistics.
auto packets_sent_with_ect1_stats =
GetPacketsSentWithEct1(GetStatsAndProcess(s, caller));
EXPECT_EQ(packets_sent_with_ect1_stats,
wifi_feedback_counter.ect1() + cellular_feedback_counter.ect1());
+
+ auto callee_stats = GetStatsAndProcess(s, callee);
+ auto packets_received_with_ect1_stats =
+ GetPacketsReceivedWithEct1(callee_stats);
+ auto packets_received_with_ce_stats = GetPacketsReceivedWithCe(callee_stats);
+ EXPECT_EQ(packets_received_with_ect1_stats, wifi_feedback_counter.ect1());
+ // TODO: bugs.webrtc.org/42225697 - testing CE would be useful.
+ EXPECT_EQ(packets_received_with_ce_stats, 0);
}
} // namespace