commit c254549b06daf5f008e96f1628c105a48ba8676a
parent f733bcd005ace3843b9ccae7a93f836db583c545
Author: Dan Baker <dbaker@mozilla.com>
Date: Mon, 27 Oct 2025 12:23:10 -0600
Bug 1995393 - Vendor libwebrtc from 59f9ca8c73
Upstream commit: https://webrtc.googlesource.com/src/+/59f9ca8c7344a926a5cdb699b00ecb2caad3a4a1
Make inbound-rtp stats object lifetime spec-compliant (behind flag).
When field trial "WebRTC-RTP-Lifetime" is enabled, the creation of the
inbound-rtp stats object is delayed until the first packet is received.
- This aligns with spec and Firefox behavior.
To aid testing, PeerConnectionTestWrapper has new negotiation methods
added to give the test more control without having to write a lot of
boilerplate code.
(A separate CL will deal with outbound-rtp which, while the plan is
they continue to be created before first packet is sent, should be
delayed until after O/A has completed.)
Bug: chromium:406585888
Change-Id: Ibac2128e80e0153659b68cc0f00869e5d1f27a69
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/405740
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45428}
Diffstat:
9 files changed, 231 insertions(+), 17 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-27T18:19:48.086610+00:00.
+libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-27T18:22:53.208935+00:00.
# base of lastest vendoring
-a3fbc13447
+59f9ca8c73
diff --git a/third_party/libwebrtc/experiments/field_trials.py b/third_party/libwebrtc/experiments/field_trials.py
@@ -221,6 +221,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([
FieldTrial('WebRTC-H265-QualityScaling',
402154973,
date(2026, 1, 1)),
+ FieldTrial('WebRTC-RTP-Lifetime',
+ 440975167,
+ date(2026, 1, 1)),
# keep-sorted end
]) # yapf: disable
diff --git a/third_party/libwebrtc/moz-patch-stack/s0102.patch b/third_party/libwebrtc/moz-patch-stack/s0102.patch
@@ -601,7 +601,7 @@ index b7561e53b6..fe7eb57423 100644
import("../../webrtc.gni")
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
-index f48ba6e784..1122d4b66e 100644
+index 47e5c4d2ca..10fdbd3c1c 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -30,8 +30,8 @@
diff --git a/third_party/libwebrtc/pc/BUILD.gn b/third_party/libwebrtc/pc/BUILD.gn
@@ -2841,6 +2841,7 @@ if (rtc_include_tests && !build_with_chromium) {
":rtp_transport_internal",
":sctp_data_channel",
":sctp_utils",
+ ":sdp_utils",
":session_description",
":simulcast_description",
":stream_collection",
diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.cc b/third_party/libwebrtc/pc/rtc_stats_collector.cc
@@ -1717,11 +1717,12 @@ void RTCStatsCollector::ProduceRTPStreamStats_n(
RTC_DCHECK_RUN_ON(network_thread_);
Thread::ScopedDisallowBlockingCalls no_blocking_calls;
+ bool spec_lifetime = env_.field_trials().IsEnabled("WebRTC-RTP-Lifetime");
for (const RtpTransceiverStatsInfo& stats : transceiver_stats_infos) {
if (stats.media_type == MediaType::AUDIO) {
- ProduceAudioRTPStreamStats_n(timestamp, stats, report);
+ ProduceAudioRTPStreamStats_n(timestamp, stats, spec_lifetime, report);
} else if (stats.media_type == MediaType::VIDEO) {
- ProduceVideoRTPStreamStats_n(timestamp, stats, report);
+ ProduceVideoRTPStreamStats_n(timestamp, stats, spec_lifetime, report);
} else {
RTC_DCHECK_NOTREACHED();
}
@@ -1731,6 +1732,7 @@ void RTCStatsCollector::ProduceRTPStreamStats_n(
void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
Timestamp timestamp,
const RtpTransceiverStatsInfo& stats,
+ bool spec_lifetime,
RTCStatsReport* report) const {
RTC_DCHECK_RUN_ON(network_thread_);
Thread::ScopedDisallowBlockingCalls no_blocking_calls;
@@ -1747,8 +1749,12 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
// remote endpoint providing metrics about the remote outbound streams.
for (const VoiceReceiverInfo& voice_receiver_info :
stats.track_media_info_map.voice_media_info()->receivers) {
- if (!voice_receiver_info.connected())
+ if (!voice_receiver_info.connected()) {
continue;
+ }
+ if (spec_lifetime && voice_receiver_info.packets_received == 0) {
+ continue;
+ }
// Inbound.
auto inbound_audio = CreateInboundAudioStreamStats(
*stats.track_media_info_map.voice_media_info(), voice_receiver_info,
@@ -1836,6 +1842,7 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
void RTCStatsCollector::ProduceVideoRTPStreamStats_n(
Timestamp timestamp,
const RtpTransceiverStatsInfo& stats,
+ bool spec_lifetime,
RTCStatsReport* report) const {
RTC_DCHECK_RUN_ON(network_thread_);
Thread::ScopedDisallowBlockingCalls no_blocking_calls;
@@ -1850,8 +1857,12 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n(
// Inbound and remote-outbound.
for (const VideoReceiverInfo& video_receiver_info :
stats.track_media_info_map.video_media_info()->receivers) {
- if (!video_receiver_info.connected())
+ if (!video_receiver_info.connected()) {
continue;
+ }
+ if (spec_lifetime && video_receiver_info.packets_received == 0) {
+ continue;
+ }
auto inbound_video = CreateInboundRTPStreamStatsFromVideoReceiverInfo(
transport_id, mid, *stats.track_media_info_map.video_media_info(),
video_receiver_info, timestamp, report);
diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.h b/third_party/libwebrtc/pc/rtc_stats_collector.h
@@ -214,9 +214,11 @@ class RTCStatsCollector : public RefCountInterface {
RTCStatsReport* report) const;
void ProduceAudioRTPStreamStats_n(Timestamp timestamp,
const RtpTransceiverStatsInfo& stats,
+ bool spec_lifetime,
RTCStatsReport* report) const;
void ProduceVideoRTPStreamStats_n(Timestamp timestamp,
const RtpTransceiverStatsInfo& stats,
+ bool spec_lifetime,
RTCStatsReport* report) const;
// Produces `RTCTransportStats`.
void ProduceTransportStats_n(
diff --git a/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc b/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc
@@ -21,7 +21,9 @@
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/audio_options.h"
#include "api/data_channel_interface.h"
+#include "api/field_trials.h"
#include "api/make_ref_counted.h"
+#include "api/media_stream_interface.h"
#include "api/peer_connection_interface.h"
#include "api/rtp_receiver_interface.h"
#include "api/rtp_sender_interface.h"
@@ -45,6 +47,8 @@
#include "test/wait_until.h"
using ::testing::Contains;
+using ::testing::IsTrue;
+using ::testing::SizeIs;
namespace webrtc {
@@ -130,10 +134,10 @@ class RTCStatsIntegrationTest : public ::testing::Test {
PeerConnectionInterface* pc) {
scoped_refptr<RTCStatsObtainer> stats_obtainer = RTCStatsObtainer::Create();
pc->GetStats(stats_obtainer.get());
- EXPECT_THAT(WaitUntil([&] { return stats_obtainer->report() != nullptr; },
- ::testing::IsTrue(),
- {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
- IsRtcOk());
+ EXPECT_THAT(
+ WaitUntil([&] { return stats_obtainer->report() != nullptr; }, IsTrue(),
+ {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
+ IsRtcOk());
return stats_obtainer->report();
}
@@ -143,10 +147,10 @@ class RTCStatsIntegrationTest : public ::testing::Test {
scoped_refptr<T> selector) {
scoped_refptr<RTCStatsObtainer> stats_obtainer = RTCStatsObtainer::Create();
pc->GetStats(selector, stats_obtainer);
- EXPECT_THAT(WaitUntil([&] { return stats_obtainer->report() != nullptr; },
- ::testing::IsTrue(),
- {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
- IsRtcOk());
+ EXPECT_THAT(
+ WaitUntil([&] { return stats_obtainer->report() != nullptr; }, IsTrue(),
+ {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
+ IsRtcOk());
return stats_obtainer->report();
}
@@ -1061,8 +1065,7 @@ TEST_F(RTCStatsIntegrationTest, GetStatsFromCallee) {
};
EXPECT_THAT(
WaitUntil([&] { return GetStatsReportAndReturnTrueIfRttIsDefined(); },
- ::testing::IsTrue(),
- {.timeout = TimeDelta::Millis(kMaxWaitMs)}),
+ IsTrue(), {.timeout = TimeDelta::Millis(kMaxWaitMs)}),
IsRtcOk());
RTCStatsReportVerifier(report.get()).VerifyReport({});
}
@@ -1232,6 +1235,90 @@ TEST_F(RTCStatsIntegrationTest, ExperimentalPsnrStats) {
}
}
}
+
+class RTCStatsRtpLifetimeTest : public RTCStatsIntegrationTest {
+ public:
+ RTCStatsRtpLifetimeTest() : RTCStatsIntegrationTest() {
+ FieldTrials field_trials =
+ CreateTestFieldTrials("WebRTC-RTP-Lifetime/Enabled/");
+ EXPECT_TRUE(caller_->CreatePc({}, CreateBuiltinAudioEncoderFactory(),
+ CreateBuiltinAudioDecoderFactory(),
+ std::make_unique<FieldTrials>(field_trials)));
+ EXPECT_TRUE(callee_->CreatePc({}, CreateBuiltinAudioEncoderFactory(),
+ CreateBuiltinAudioDecoderFactory(),
+ std::make_unique<FieldTrials>(field_trials)));
+ }
+};
+
+TEST_F(RTCStatsRtpLifetimeTest, AudioInboundRtpMissingBeforeFirstPacket) {
+ // Caller to send audio.
+ scoped_refptr<MediaStreamInterface> stream = caller_->GetUserMedia(
+ /*audio=*/true, {}, /*video=*/false);
+ scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0];
+ caller_->pc()->AddTransceiver(track, {});
+
+ caller_->ListenForRemoteIceCandidates(callee_);
+ callee_->ListenForRemoteIceCandidates(caller_);
+ PeerConnectionTestWrapper::AwaitNegotiation(caller_.get(), callee_.get());
+
+ // The m-section has been negotiated but no inbound-rtp should be present
+ // since no packets have been received yet.
+ scoped_refptr<const RTCStatsReport> report = GetStats(callee_->pc());
+ std::vector<const RTCInboundRtpStreamStats*> inbound_rtps =
+ report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ EXPECT_THAT(inbound_rtps, SizeIs(0));
+
+ caller_->AwaitAddRemoteIceCandidates();
+ callee_->AwaitAddRemoteIceCandidates();
+
+ // Nothing is preventing packets from flowing, wait for inbound-rtp to appear.
+ EXPECT_THAT(WaitUntil(
+ [&] {
+ report = GetStats(callee_->pc());
+ inbound_rtps =
+ report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ return inbound_rtps.size() > 0;
+ },
+ IsTrue(), {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
+ IsRtcOk());
+ ASSERT_THAT(inbound_rtps, SizeIs(1));
+ EXPECT_GT(inbound_rtps[0]->packets_received.value_or(0), 0u);
+}
+
+TEST_F(RTCStatsRtpLifetimeTest, VideoInboundRtpMissingBeforeFirstPacket) {
+ // Caller to send video.
+ scoped_refptr<MediaStreamInterface> stream = caller_->GetUserMedia(
+ /*audio=*/false, {}, /*video=*/true);
+ scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0];
+ caller_->pc()->AddTransceiver(track, {});
+
+ caller_->ListenForRemoteIceCandidates(callee_);
+ callee_->ListenForRemoteIceCandidates(caller_);
+ PeerConnectionTestWrapper::AwaitNegotiation(caller_.get(), callee_.get());
+
+ // The m-section has been negotiated but no inbound-rtp should be present
+ // since no packets have been received yet.
+ scoped_refptr<const RTCStatsReport> report = GetStats(callee_->pc());
+ std::vector<const RTCInboundRtpStreamStats*> inbound_rtps =
+ report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ EXPECT_THAT(inbound_rtps, SizeIs(0));
+
+ caller_->AwaitAddRemoteIceCandidates();
+ callee_->AwaitAddRemoteIceCandidates();
+
+ // Nothing is preventing packets from flowing, wait for inbound-rtp to appear.
+ EXPECT_THAT(WaitUntil(
+ [&] {
+ report = GetStats(callee_->pc());
+ inbound_rtps =
+ report->GetStatsOfType<RTCInboundRtpStreamStats>();
+ return inbound_rtps.size() > 0;
+ },
+ IsTrue(), {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}),
+ IsRtcOk());
+ ASSERT_THAT(inbound_rtps, SizeIs(1));
+ EXPECT_GT(inbound_rtps[0]->packets_received.value_or(0), 0u);
+}
#endif // WEBRTC_HAVE_SCTP
} // namespace
diff --git a/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.cc b/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.cc
@@ -57,6 +57,7 @@
#include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h"
#include "media/engine/simulcast_encoder_adapter.h"
#include "p2p/test/fake_port_allocator.h"
+#include "pc/sdp_utils.h"
#include "pc/test/fake_audio_capture_module.h"
#include "pc/test/fake_periodic_video_source.h"
#include "pc/test/fake_periodic_video_track_source.h"
@@ -140,6 +141,17 @@ void PeerConnectionTestWrapper::Connect(PeerConnectionTestWrapper* caller,
caller, &PeerConnectionTestWrapper::ReceiveAnswerSdp);
}
+void PeerConnectionTestWrapper::AwaitNegotiation(
+ PeerConnectionTestWrapper* caller,
+ PeerConnectionTestWrapper* callee) {
+ auto offer = caller->AwaitCreateOffer();
+ caller->AwaitSetLocalDescription(offer.get());
+ callee->AwaitSetRemoteDescription(offer.get());
+ auto answer = callee->AwaitCreateAnswer();
+ callee->AwaitSetLocalDescription(answer.get());
+ caller->AwaitSetRemoteDescription(answer.get());
+}
+
PeerConnectionTestWrapper::PeerConnectionTestWrapper(
const std::string& name,
webrtc::SocketServer* socket_server,
@@ -262,6 +274,83 @@ void PeerConnectionTestWrapper::WaitForNegotiation() {
webrtc::IsRtcOk());
}
+std::unique_ptr<webrtc::SessionDescriptionInterface>
+PeerConnectionTestWrapper::AwaitCreateOffer() {
+ auto observer =
+ webrtc::make_ref_counted<webrtc::MockCreateSessionDescriptionObserver>();
+ peer_connection_->CreateOffer(observer.get(), {});
+ EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); },
+ ::testing::IsTrue()),
+ webrtc::IsRtcOk());
+ return observer->MoveDescription();
+}
+
+std::unique_ptr<webrtc::SessionDescriptionInterface>
+PeerConnectionTestWrapper::AwaitCreateAnswer() {
+ auto observer =
+ webrtc::make_ref_counted<webrtc::MockCreateSessionDescriptionObserver>();
+ peer_connection_->CreateAnswer(observer.get(), {});
+ EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); },
+ ::testing::IsTrue()),
+ webrtc::IsRtcOk());
+ return observer->MoveDescription();
+}
+
+void PeerConnectionTestWrapper::AwaitSetLocalDescription(
+ webrtc::SessionDescriptionInterface* sdp) {
+ auto observer =
+ webrtc::make_ref_counted<webrtc::MockSetSessionDescriptionObserver>();
+ peer_connection_->SetLocalDescription(
+ observer.get(), webrtc::CloneSessionDescription(sdp).release());
+ EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); },
+ ::testing::IsTrue()),
+ webrtc::IsRtcOk());
+}
+
+void PeerConnectionTestWrapper::AwaitSetRemoteDescription(
+ webrtc::SessionDescriptionInterface* sdp) {
+ auto observer =
+ webrtc::make_ref_counted<webrtc::MockSetSessionDescriptionObserver>();
+ peer_connection_->SetRemoteDescription(
+ observer.get(), webrtc::CloneSessionDescription(sdp).release());
+ EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); },
+ ::testing::IsTrue()),
+ webrtc::IsRtcOk());
+}
+
+void PeerConnectionTestWrapper::ListenForRemoteIceCandidates(
+ webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper) {
+ remote_wrapper_ = remote_wrapper;
+ remote_wrapper_->SignalOnIceCandidateReady.connect(
+ this, &PeerConnectionTestWrapper::OnRemoteIceCandidate);
+}
+
+void PeerConnectionTestWrapper::AwaitAddRemoteIceCandidates() {
+ EXPECT_TRUE(remote_wrapper_);
+ EXPECT_THAT(
+ webrtc::WaitUntil(
+ [&] {
+ return remote_wrapper_->pc()->ice_gathering_state() ==
+ webrtc::PeerConnectionInterface::kIceGatheringComplete;
+ },
+ ::testing::IsTrue(),
+ {.timeout = webrtc::TimeDelta::Millis(kMaxWait)}),
+ webrtc::IsRtcOk());
+ for (const auto& remote_ice_candidate : remote_ice_candidates_) {
+ peer_connection_->AddIceCandidate(remote_ice_candidate.get());
+ }
+ remote_wrapper_ = nullptr;
+ remote_ice_candidates_.clear();
+}
+
+void PeerConnectionTestWrapper::OnRemoteIceCandidate(
+ const std::string& sdp_mid,
+ int sdp_mline_index,
+ const std::string& candidate) {
+ remote_ice_candidates_.emplace_back(
+ webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, candidate, nullptr));
+}
+
void PeerConnectionTestWrapper::OnSignalingChange(
webrtc::PeerConnectionInterface::SignalingState new_state) {
if (new_state == webrtc::PeerConnectionInterface::SignalingState::kStable) {
diff --git a/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.h b/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.h
@@ -46,8 +46,13 @@ class PeerConnectionTestWrapper
public webrtc::CreateSessionDescriptionObserver,
public sigslot::has_slots<> {
public:
+ // Asynchronously negotiates and exchanges ICE candidates between `caller` and
+ // `callee`. See also WaitForNegotiation() and other "WaitFor..." methods.
static void Connect(PeerConnectionTestWrapper* caller,
PeerConnectionTestWrapper* callee);
+ // Synchronously negotiates. ICE candidates needs to be exchanged separately.
+ static void AwaitNegotiation(PeerConnectionTestWrapper* caller,
+ PeerConnectionTestWrapper* callee);
PeerConnectionTestWrapper(const std::string& name,
webrtc::SocketServer* socket_server,
@@ -84,6 +89,17 @@ class PeerConnectionTestWrapper
void WaitForNegotiation();
+ // Synchronous negotiation methods.
+ std::unique_ptr<webrtc::SessionDescriptionInterface> AwaitCreateOffer();
+ std::unique_ptr<webrtc::SessionDescriptionInterface> AwaitCreateAnswer();
+ void AwaitSetLocalDescription(webrtc::SessionDescriptionInterface* sdp);
+ void AwaitSetRemoteDescription(webrtc::SessionDescriptionInterface* sdp);
+ // Listen for remote ICE candidates but don't add them until
+ // AwaitAddRemoteIceCandidates().
+ void ListenForRemoteIceCandidates(
+ webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper);
+ void AwaitAddRemoteIceCandidates();
+
// Implements PeerConnectionObserver.
void OnSignalingChange(
webrtc::PeerConnectionInterface::SignalingState new_state) override;
@@ -143,6 +159,9 @@ class PeerConnectionTestWrapper
bool CheckForConnection();
bool CheckForAudio();
bool CheckForVideo();
+ void OnRemoteIceCandidate(const std::string& sdp_mid,
+ int sdp_mline_index,
+ const std::string& candidate);
std::string name_;
webrtc::SocketServer* const socket_server_;
@@ -158,6 +177,8 @@ class PeerConnectionTestWrapper
bool pending_negotiation_;
std::vector<webrtc::scoped_refptr<webrtc::FakePeriodicVideoTrackSource>>
fake_video_sources_;
+ webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper_;
+ std::vector<std::unique_ptr<webrtc::IceCandidate>> remote_ice_candidates_;
};
#endif // PC_TEST_PEER_CONNECTION_TEST_WRAPPER_H_