[Stats] Explicit RTP-RTX and RTP-FEC mappings. Unblocks simulcast stats.
--- Background ---
The webrtc::VideoSendStream::StreamStats are converted into
VideoSenderInfo objects which turn into "outbound-rtp" stats objects in
getStats() (or "ssrc" objects in legacy getStats()).
StreamStats are created for each type of substream: RTP media streams,
RTX streams and FlexFEC streams - each with individual packet counters.
The RTX stream is responsible for retransmissions of a referenced media
stream and the FlexFEC stream is responsible for FEC of a referenced
media stream. RTX/FEC streams do not show up as separate objects in
getStats(). Only the media streams become "outbound-rtp" objects, but
their packet and byte counters have to include the RTX and FEC counters.
--- Overview of this CL ---
This CL adds MergeInfoAboutOutboundRtpSubstreams(). It takes
StreamStats of all kinds as input, and outputs media-only StreamStats
- incorporating the RTX and FEC counters into the relevant media
StreamStats.
The merged StreamStats objects is a smaller set of objects than the
non-merged counterparts, but when aggregating all packet counters
together we end up with exact same packet and count as before.
Because WebRtcVideoSendStream::GetVideoSenderInfo() currently aggregates
the StreamStats into a single VideoSenderInfo (single "outbound-rtp"),
this CL should not have any observable side-effects. Prior to this CL:
aggregate StreamStats. After this CL: merge StreamStats and then
aggregate them.
However, when simulcast stats are implemented (WIP CL:
https://webrtc-review.googlesource.com/c/src/+/168120) each RTP media
stream should turn into an individual "outbound-rtp" object. We will
then no longer aggregate all StreamStats into a single "info". This CL
unblocks simulcast stats by providing StreamStats objects that could be
turned into individual VideoSenderInfos.
--- The Changes ---
1. Methods added to RtpConfig to be able to easily tell the relationship
between RTP, RTX and FEC ssrcs.
2. StreamStats gets a StreamType (kMedia, kRtx or kFlexfec) that
replaces the booleans (is_rtx, is_flexfec).
3. "referenced_media_ssrc" is added to StreamStats, making it possible
to tell which kRtx/kFlexFec stream stats need to be merged with which
kMedia StreamStats.
4. MergeInfoAboutOutboundRtpSubstreams() added and used.
Bug: webrtc:11439
Change-Id: Iaf9002041169a054ddfd32c7ea06bd1dc36c6bca
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/170826
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30869}
diff --git a/call/BUILD.gn b/call/BUILD.gn
index a2e8c05..a33df15 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -97,7 +97,9 @@
"../api/transport:bitrate_settings",
"../api/units:timestamp",
"../modules/rtp_rtcp:rtp_rtcp_format",
+ "../rtc_base:checks",
"../rtc_base:rtc_base_approved",
+ "//third_party/abseil-cpp/absl/algorithm:container",
"//third_party/abseil-cpp/absl/types:optional",
]
}
diff --git a/call/rtp_config.cc b/call/rtp_config.cc
index a23351b..d1db867 100644
--- a/call/rtp_config.cc
+++ b/call/rtp_config.cc
@@ -12,11 +12,29 @@
#include <cstdint>
+#include "absl/algorithm/container.h"
#include "api/array_view.h"
+#include "rtc_base/checks.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
+namespace {
+
+uint32_t FindAssociatedSsrc(uint32_t ssrc,
+ const std::vector<uint32_t>& ssrcs,
+ const std::vector<uint32_t>& associated_ssrcs) {
+ RTC_DCHECK_EQ(ssrcs.size(), associated_ssrcs.size());
+ for (size_t i = 0; i < ssrcs.size(); ++i) {
+ if (ssrcs[i] == ssrc)
+ return associated_ssrcs[i];
+ }
+ RTC_NOTREACHED();
+ return 0;
+}
+
+} // namespace
+
std::string LntfConfig::ToString() const {
return enabled ? "{enabled: true}" : "{enabled: false}";
}
@@ -124,4 +142,51 @@
ss << '}';
return ss.str();
}
+
+bool RtpConfig::IsMediaSsrc(uint32_t ssrc) const {
+ return absl::c_linear_search(ssrcs, ssrc);
+}
+
+bool RtpConfig::IsRtxSsrc(uint32_t ssrc) const {
+ return absl::c_linear_search(rtx.ssrcs, ssrc);
+}
+
+bool RtpConfig::IsFlexfecSsrc(uint32_t ssrc) const {
+ return flexfec.payload_type != -1 && ssrc == flexfec.ssrc;
+}
+
+absl::optional<uint32_t> RtpConfig::GetRtxSsrcAssociatedWithMediaSsrc(
+ uint32_t media_ssrc) const {
+ RTC_DCHECK(IsMediaSsrc(media_ssrc));
+ // If we don't use RTX there is no association.
+ if (rtx.ssrcs.empty())
+ return absl::nullopt;
+ // If we use RTX there MUST be an association ssrcs[i] <-> rtx.ssrcs[i].
+ RTC_DCHECK_EQ(ssrcs.size(), rtx.ssrcs.size());
+ return FindAssociatedSsrc(media_ssrc, ssrcs, rtx.ssrcs);
+}
+
+uint32_t RtpConfig::GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const {
+ RTC_DCHECK(IsRtxSsrc(rtx_ssrc));
+ // If we use RTX there MUST be an association ssrcs[i] <-> rtx.ssrcs[i].
+ RTC_DCHECK_EQ(ssrcs.size(), rtx.ssrcs.size());
+ return FindAssociatedSsrc(rtx_ssrc, rtx.ssrcs, ssrcs);
+}
+
+uint32_t RtpConfig::GetMediaSsrcAssociatedWithFlexfecSsrc(
+ uint32_t flexfec_ssrc) const {
+ RTC_DCHECK(IsFlexfecSsrc(flexfec_ssrc));
+ // If we use FlexFEC there MUST be an associated media ssrc.
+ //
+ // TODO(brandtr/hbos): The current implementation only supports an association
+ // with a single media ssrc. If multiple ssrcs are to be supported in the
+ // future, in order not to break GetStats()'s packet and byte counters, we
+ // must be able to tell how many packets and bytes have contributed to which
+ // SSRC.
+ RTC_DCHECK_EQ(1u, flexfec.protected_media_ssrcs.size());
+ uint32_t media_ssrc = flexfec.protected_media_ssrcs[0];
+ RTC_DCHECK(IsMediaSsrc(media_ssrc));
+ return media_ssrc;
+}
+
} // namespace webrtc
diff --git a/call/rtp_config.h b/call/rtp_config.h
index a0596a8..d9caeb0 100644
--- a/call/rtp_config.h
+++ b/call/rtp_config.h
@@ -17,6 +17,7 @@
#include <string>
#include <vector>
+#include "absl/types/optional.h"
#include "api/rtp_headers.h"
#include "api/rtp_parameters.h"
@@ -157,6 +158,14 @@
// RTCP CNAME, see RFC 3550.
std::string c_name;
+
+ bool IsMediaSsrc(uint32_t ssrc) const;
+ bool IsRtxSsrc(uint32_t ssrc) const;
+ bool IsFlexfecSsrc(uint32_t ssrc) const;
+ absl::optional<uint32_t> GetRtxSsrcAssociatedWithMediaSsrc(
+ uint32_t media_ssrc) const;
+ uint32_t GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const;
+ uint32_t GetMediaSsrcAssociatedWithFlexfecSsrc(uint32_t flexfec_ssrc) const;
};
} // namespace webrtc
#endif // CALL_RTP_CONFIG_H_
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
index cf9af9f..cb32085 100644
--- a/call/rtp_video_sender.cc
+++ b/call/rtp_video_sender.cc
@@ -231,7 +231,7 @@
std::vector<RtpStreamSender> rtp_streams;
RTC_DCHECK(rtp_config.rtx.ssrcs.empty() ||
- rtp_config.rtx.ssrcs.size() == rtp_config.rtx.ssrcs.size());
+ rtp_config.rtx.ssrcs.size() == rtp_config.ssrcs.size());
for (size_t i = 0; i < rtp_config.ssrcs.size(); ++i) {
RTPSenderVideo::Config video_config;
configuration.local_media_ssrc = rtp_config.ssrcs[i];
@@ -241,9 +241,10 @@
configuration.fec_generator = fec_generator.get();
video_config.fec_generator = fec_generator.get();
- if (rtp_config.rtx.ssrcs.size() > i) {
- configuration.rtx_send_ssrc = rtp_config.rtx.ssrcs[i];
- }
+ configuration.rtx_send_ssrc =
+ rtp_config.GetRtxSsrcAssociatedWithMediaSsrc(rtp_config.ssrcs[i]);
+ RTC_DCHECK_EQ(configuration.rtx_send_ssrc.has_value(),
+ !rtp_config.rtx.ssrcs.empty());
configuration.need_rtp_packet_infos = rtp_config.lntf.enabled;
diff --git a/call/video_send_stream.cc b/call/video_send_stream.cc
index f495d08..a4b6744 100644
--- a/call/video_send_stream.cc
+++ b/call/video_send_stream.cc
@@ -17,12 +17,31 @@
namespace webrtc {
+namespace {
+
+const char* StreamTypeToString(VideoSendStream::StreamStats::StreamType type) {
+ switch (type) {
+ case VideoSendStream::StreamStats::StreamType::kMedia:
+ return "media";
+ case VideoSendStream::StreamStats::StreamType::kRtx:
+ return "rtx";
+ case VideoSendStream::StreamStats::StreamType::kFlexfec:
+ return "flexfec";
+ }
+}
+
+} // namespace
+
VideoSendStream::StreamStats::StreamStats() = default;
VideoSendStream::StreamStats::~StreamStats() = default;
std::string VideoSendStream::StreamStats::ToString() const {
char buf[1024];
rtc::SimpleStringBuilder ss(buf);
+ ss << "type: " << StreamTypeToString(type);
+ if (referenced_media_ssrc.has_value())
+ ss << " (for: " << referenced_media_ssrc.value() << ")";
+ ss << ", ";
ss << "width: " << width << ", ";
ss << "height: " << height << ", ";
ss << "key: " << frame_counts.key_frames << ", ";
@@ -64,7 +83,8 @@
ss << "#quality_adaptations: " << number_of_quality_adapt_changes;
ss << '}';
for (const auto& substream : substreams) {
- if (!substream.second.is_rtx && !substream.second.is_flexfec) {
+ if (substream.second.type ==
+ VideoSendStream::StreamStats::StreamType::kMedia) {
ss << " {ssrc: " << substream.first << ", ";
ss << substream.second.ToString();
ss << '}';
diff --git a/call/video_send_stream.h b/call/video_send_stream.h
index 962578d..08806ec 100644
--- a/call/video_send_stream.h
+++ b/call/video_send_stream.h
@@ -40,15 +40,35 @@
class VideoSendStream {
public:
+ // Multiple StreamStats objects are present if simulcast is used (multiple
+ // kMedia streams) or if RTX or FlexFEC is negotiated. Multiple SVC layers, on
+ // the other hand, does not cause additional StreamStats.
struct StreamStats {
+ enum class StreamType {
+ // A media stream is an RTP stream for audio or video. Retransmissions and
+ // FEC is either sent over the same SSRC or negotiated to be sent over
+ // separate SSRCs, in which case separate StreamStats objects exist with
+ // references to this media stream's SSRC.
+ kMedia,
+ // RTX streams are streams dedicated to retransmissions. They have a
+ // dependency on a single kMedia stream: |referenced_media_ssrc|.
+ kRtx,
+ // FlexFEC streams are streams dedicated to FlexFEC. They have a
+ // dependency on a single kMedia stream: |referenced_media_ssrc|.
+ kFlexfec,
+ };
+
StreamStats();
~StreamStats();
std::string ToString() const;
+ StreamType type = StreamType::kMedia;
+ // If |type| is kRtx or kFlexfec this value is present. The referenced SSRC
+ // is the kMedia stream that this stream is performing retransmissions or
+ // FEC for. If |type| is kMedia, this value is null.
+ absl::optional<uint32_t> referenced_media_ssrc;
FrameCounts frame_counts;
- bool is_rtx = false;
- bool is_flexfec = false;
int width = 0;
int height = 0;
// TODO(holmer): Move bitrate_bps out to the webrtc::Call layer.
@@ -63,6 +83,13 @@
// A snapshot of the most recent Report Block with additional data of
// interest to statistics. Used to implement RTCRemoteInboundRtpStreamStats.
absl::optional<ReportBlockData> report_block_data;
+
+ // These booleans are redundant; this information is already exposed in
+ // |type|.
+ // TODO(hbos): Update downstream projects to use |type| instead and delete
+ // these members.
+ bool is_flexfec = false;
+ bool is_rtx = false;
};
struct Stats {