commit 6c2e49c88221012b7c5a928d59c26b054a3c9e23
parent f3a967c6177f79346082dd5a97dac16af7f36f44
Author: Dan Baker <dbaker@mozilla.com>
Date: Mon, 27 Oct 2025 13:33:51 -0600
Bug 1995393 - Vendor libwebrtc from 9aa4278cf5
Upstream commit: https://webrtc.googlesource.com/src/+/9aa4278cf59fd1b6a4076046253df9a72713e170
Fix simulcast stats bug with missing outbound-rtps.
If simulcast is negotiated, we should see all SSRCs' outbound-rtps.
The bug is that if "substreams" have not been populated yet because we
have not started sending yet (e.g. ICE not connected), we return a
single dummy "info" object. This CL updates this early return path by
correctly setting the metrics that should always be present: ssrc, rid,
encodingIndex and active. Packet counters and other stuff will be zero
which makes sense prior to sending. Full stack test in
peer_connection_encodings_integrationtest.cc.
The code path that is changed no longer copies frames_encoded,
total_encode_time_ms, total_encoded_bytes_target, frames_sent and
huge_frames_sent which require us to update some expectations in
webrtc_video_engine_unittest.cc. We should not copy these because
outside of testing these would all be zero before sending and after
sending, substreams exist making this code path N/A.
Bug: chromium:406585888
Change-Id: I2f7ad8c147254c69510c396b8736d8bb9293db4e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/406280
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45447}
Diffstat:
4 files changed, 78 insertions(+), 31 deletions(-)
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor
@@ -1,4 +1,4 @@
# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
-libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-27T19:31:26.560039+00:00.
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-27T19:33:32.697294+00:00.
# base of lastest vendoring
-4bafeff6ae
+9aa4278cf5
diff --git a/third_party/libwebrtc/media/engine/webrtc_video_engine.cc b/third_party/libwebrtc/media/engine/webrtc_video_engine.cc
@@ -2423,6 +2423,11 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos(
common_info.codec_name = parameters_.codec_settings->codec.name;
common_info.codec_payload_type = parameters_.codec_settings->codec.id;
}
+ // If SVC is used, one stream is configured but multiple encodings exist. This
+ // is not spec-compliant, but it is how we've implemented SVC so this affects
+ // how the RTP stream's "active" value is determined.
+ bool is_svc = (parameters_.encoder_config.number_of_streams == 1 &&
+ rtp_parameters_.encodings.size() > 1);
std::vector<VideoSenderInfo> infos;
VideoSendStream::Stats stats;
if (stream_ == nullptr) {
@@ -2466,24 +2471,23 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos(
common_info.aggregated_huge_frames_sent = stats.huge_frames_sent;
common_info.power_efficient_encoder = stats.power_efficient_encoder;
- // The normal case is that substreams are present, handled below. But if
- // substreams are missing (can happen before negotiated/connected where we
- // have no stats yet) a single outbound-rtp is created representing any and
- // all layers.
+ // The "typical case" where `substreams` exist because we have negotiated
+ // and connected is handled below, but prior to that `substreams` is empty.
+ // In this case we still need to return one "info" per SSRC and set a few
+ // stats that should never be missing.
if (stats.substreams.empty()) {
+ size_t encoding_index = 0;
for (uint32_t ssrc : parameters_.config.rtp.ssrcs) {
- common_info.add_ssrc(ssrc);
+ auto info = common_info;
+ info.add_ssrc(ssrc);
+ info.rid = parameters_.config.rtp.GetRidForSsrc(ssrc);
+ info.encoding_index = encoding_index;
+ info.active = IsActiveFromEncodings(
+ !is_svc ? std::optional<uint32_t>(ssrc) : std::nullopt,
+ rtp_parameters_.encodings);
+ ++encoding_index;
+ infos.push_back(info);
}
- common_info.encoding_index = 0;
- common_info.active =
- IsActiveFromEncodings(std::nullopt, rtp_parameters_.encodings);
- common_info.framerate_sent = stats.encode_frame_rate;
- common_info.frames_encoded = stats.frames_encoded;
- common_info.total_encode_time_ms = stats.total_encode_time_ms;
- common_info.total_encoded_bytes_target = stats.total_encoded_bytes_target;
- common_info.frames_sent = stats.frames_encoded;
- common_info.huge_frames_sent = stats.huge_frames_sent;
- infos.push_back(common_info);
return infos;
}
}
@@ -2498,11 +2502,6 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos(
for (size_t i = 0; i < parameters_.config.rtp.ssrcs.size(); ++i) {
encoding_index_by_ssrc[parameters_.config.rtp.ssrcs[i]] = i;
}
- // If SVC is used, one stream is configured but multiple encodings exist. This
- // is not spec-compliant, but it is how we've implemented SVC so this affects
- // how the RTP stream's "active" value is determined.
- bool is_svc = (parameters_.encoder_config.number_of_streams == 1 &&
- rtp_parameters_.encodings.size() > 1);
for (const auto& pair : outbound_rtp_substreams) {
auto info = common_info;
uint32_t ssrc = pair.first;
diff --git a/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc b/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc
@@ -5883,7 +5883,7 @@ TEST_F(WebRtcVideoChannelTest, GetStatsReportsCpuOveruseMetrics) {
TEST_F(WebRtcVideoChannelTest, GetStatsReportsFramesEncoded) {
FakeVideoSendStream* stream = AddSendStream();
VideoSendStream::Stats stats;
- stats.frames_encoded = 13;
+ stats.substreams[123].frames_encoded = 13;
stream->SetStats(stats);
VideoMediaSendInfo send_info;
@@ -5891,7 +5891,7 @@ TEST_F(WebRtcVideoChannelTest, GetStatsReportsFramesEncoded) {
EXPECT_TRUE(send_channel_->GetStats(&send_info));
EXPECT_TRUE(receive_channel_->GetStats(&receive_info));
- EXPECT_EQ(stats.frames_encoded, send_info.senders[0].frames_encoded);
+ EXPECT_EQ(13u, send_info.senders[0].frames_encoded);
}
TEST_F(WebRtcVideoChannelTest, GetStatsReportsKeyFramesEncoded) {
@@ -6007,9 +6007,10 @@ TEST_F(WebRtcVideoChannelTest, GetAggregatedStatsReportWithoutSubStreams) {
EXPECT_EQ(sender.nacks_received, 0u);
EXPECT_EQ(sender.send_frame_width, 0);
EXPECT_EQ(sender.send_frame_height, 0);
+ EXPECT_EQ(sender.framerate_sent, 0);
+ EXPECT_EQ(sender.frames_encoded, 0u);
EXPECT_EQ(sender.framerate_input, stats.input_frame_rate);
- EXPECT_EQ(sender.framerate_sent, stats.encode_frame_rate);
EXPECT_EQ(sender.nominal_bitrate, stats.media_bitrate_bps);
EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_CPU, 0);
EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_BANDWIDTH, 0);
@@ -6021,22 +6022,21 @@ TEST_F(WebRtcVideoChannelTest, GetAggregatedStatsReportWithoutSubStreams) {
stats.quality_limitation_resolution_changes);
EXPECT_EQ(sender.avg_encode_ms, stats.avg_encode_time_ms);
EXPECT_EQ(sender.encode_usage_percent, stats.encode_usage_percent);
- EXPECT_EQ(sender.frames_encoded, stats.frames_encoded);
// Comes from substream only.
EXPECT_EQ(sender.key_frames_encoded, 0u);
+ EXPECT_EQ(sender.total_encode_time_ms, 0u);
- EXPECT_EQ(sender.total_encode_time_ms, stats.total_encode_time_ms);
EXPECT_EQ(sender.total_encoded_bytes_target,
stats.total_encoded_bytes_target);
// Comes from substream only.
EXPECT_EQ(sender.total_packet_send_delay, TimeDelta::Zero());
EXPECT_EQ(sender.qp_sum, std::nullopt);
+ EXPECT_EQ(sender.frames_sent, 0u);
+ EXPECT_EQ(sender.huge_frames_sent, 0u);
EXPECT_EQ(sender.has_entered_low_resolution,
stats.has_entered_low_resolution);
EXPECT_EQ(sender.content_type, VideoContentType::SCREENSHARE);
- EXPECT_EQ(sender.frames_sent, stats.frames_encoded);
- EXPECT_EQ(sender.huge_frames_sent, stats.huge_frames_sent);
EXPECT_EQ(sender.rid, std::nullopt);
}
@@ -7030,7 +7030,7 @@ TEST_F(WebRtcVideoChannelTest, ReportsSsrcGroupsInStats) {
EXPECT_TRUE(send_channel_->GetStats(&send_info));
EXPECT_TRUE(receive_channel_->GetStats(&receive_info));
- ASSERT_EQ(1u, send_info.senders.size());
+ ASSERT_EQ(3u, send_info.senders.size());
ASSERT_EQ(1u, receive_info.receivers.size());
EXPECT_NE(sender_sp.ssrc_groups, receiver_sp.ssrc_groups);
diff --git a/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc b/third_party/libwebrtc/pc/peer_connection_encodings_integrationtest.cc
@@ -1306,7 +1306,7 @@ TEST_F(PeerConnectionEncodingsIntegrationTest, VP9_TargetBitrate_StandardL1T3) {
}
TEST_F(PeerConnectionEncodingsIntegrationTest,
- SimulcastProducesUniqueSsrcAndRtxSsrcs) {
+ SimulcastProducesUniqueSsrcAndRtxSsrcsWhenConnected) {
scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
ExchangeIceCandidates(local_pc_wrapper, remote_pc_wrapper);
@@ -1347,6 +1347,54 @@ TEST_F(PeerConnectionEncodingsIntegrationTest,
EXPECT_EQ(rtx_ssrcs.size(), 3u);
}
+// Similar to the above test, but we never exchange ICE candidates such that
+// simulcast never has a chance to "ramp up". Despite this, we should see one
+// outbound-rtp per encoding.
+TEST_F(PeerConnectionEncodingsIntegrationTest,
+ SimulcastProducesUniqueSsrcAndRtxSsrcsWhenDisconnected) {
+ scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();
+ scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = CreatePc();
+
+ std::vector<SimulcastLayer> layers =
+ CreateLayers({"f", "h", "q"}, /*active=*/true);
+ scoped_refptr<RtpTransceiverInterface> transceiver =
+ AddTransceiverWithSimulcastLayers(local_pc_wrapper, remote_pc_wrapper,
+ layers);
+
+ // Inactive the middle layer.
+ auto sender = transceiver->sender();
+ auto parameters = sender->GetParameters();
+ parameters.encodings[0].active = true;
+ parameters.encodings[1].active = false;
+ parameters.encodings[2].active = true;
+ EXPECT_TRUE(sender->SetParameters(parameters).ok());
+
+ NegotiateWithSimulcastTweaks(local_pc_wrapper, remote_pc_wrapper);
+
+ // We have three outbound-rtps.
+ scoped_refptr<const RTCStatsReport> report = GetStats(local_pc_wrapper);
+ std::vector<const RTCOutboundRtpStreamStats*> outbound_rtps =
+ report->GetStatsOfType<RTCOutboundRtpStreamStats>();
+ ASSERT_EQ(outbound_rtps.size(), 3u);
+ // SSRC and RTX is unique.
+ std::set<uint32_t> ssrcs;
+ std::set<uint32_t> rtx_ssrcs;
+ for (const auto& outbound_rtp : outbound_rtps) {
+ ASSERT_TRUE(outbound_rtp->ssrc.has_value());
+ ASSERT_TRUE(outbound_rtp->rtx_ssrc.has_value());
+ ssrcs.insert(*outbound_rtp->ssrc);
+ rtx_ssrcs.insert(*outbound_rtp->rtx_ssrc);
+ }
+ EXPECT_EQ(ssrcs.size(), 3u);
+ EXPECT_EQ(rtx_ssrcs.size(), 3u);
+ // RIDs and active are set.
+ auto outbound_rtp_by_rid = GetOutboundRtpStreamStatsByRid(report);
+ EXPECT_THAT(
+ outbound_rtp_by_rid,
+ UnorderedElementsAre(Pair("q", Active()), Pair("h", Not(Active())),
+ Pair("f", Active())));
+}
+
TEST_F(PeerConnectionEncodingsIntegrationTest,
EncodingParameterCodecIsEmptyWhenCreatedAudio) {
scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper = CreatePc();