Skip to content

fix(rtp): associate repair SSRC with base stream RTX parameters (closes #12)#72

Open
nightness wants to merge 8 commits into
webrtc-rs:masterfrom
Brainwires:fix/rrid-support
Open

fix(rtp): associate repair SSRC with base stream RTX parameters (closes #12)#72
nightness wants to merge 8 commits into
webrtc-rs:masterfrom
Brainwires:fix/rrid-support

Conversation

@nightness
Copy link
Copy Markdown

@nightness nightness commented Apr 1, 2026

Summary

Implements rrid (urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id) handling, closing #12.

What was missing: The rrid extension was already parsed from RTP header extensions and the URI already registered in configure_simulcast_extension_headers(). The handler body at endpoint.rs:450 was a TODO stub.

What rrid means: when an RTP packet carries rrid = "X", it is a repair/RTX stream for the base stream whose rid = "X". The repair packet's SSRC should be stored in the base stream's RTCRtpCodingParameters.rtx.ssrc field.

Fix: when rrid is non-empty, look up the base stream's coding parameters using get_coding_parameter_mut_by_rid(rrid) and set the RTX SSRC — creating or updating the RTCRtpRtxParameters as needed. The repair stream is also registered with the interceptor via interceptor_remote_stream_op so RTX packets are actually demuxed and forwarded.

Review feedback addressed

  • Guard interceptor registration: Skip binding the repair stream when no base coding parameters exist for the rrid, preventing invalid/unknown rrid values from creating orphan remote streams with no way to route them.
  • Fix RTX PT lookup: Use codec.payload_type directly instead of find_rtx_payload_type(codec.payload_type, ...). The incoming packet is already an RTX packet, so codec.payload_type is already the RTX PT (e.g. 97). Calling find_rtx_payload_type searched for apt=97 which doesn't exist, falling back to PT 0.
  • Fix test poll_read loop: Changed while let Some(RTCMessage::RtpPacket(..)) to drain all message types, preventing stalling on non-RTP messages (e.g. RTCP).
  • None base coding params: Emit warn! log when rrid has no matching base stream and skip interceptor registration entirely.
  • Return track_id from rrid branch: The rrid code path now returns Some(track_id) so RTX packets are routed to the correct receiver instead of being silently dropped.
  • Stats accumulator update: Added update_inbound_rtx_ssrc() on RTCStatsAccumulator so rrid-discovered RTX SSRCs update both the rtx_ssrc_to_primary reverse-lookup map and the existing inbound stream's rtx_ssrc field. Without this, on_rtx_packet_received_if_rtx() wouldn't recognize late-arriving RTX SSRCs and getStats() would report rtxSsrc=0.

Test plan

  • cargo build passes
  • cargo clippy passes (no warnings)
  • cargo fmt --check passes
  • cargo test -p rtc passes (167 lib tests)
  • Integration test simulcast_rtx_rrid: sends 3 simulcast layers with RID, verifies exactly 3 tracks are created (no spurious tracks for RTX SSRCs).
  • Unit tests test_update_inbound_rtx_ssrc and test_update_inbound_rtx_ssrc_no_existing_stream: verify the stats accumulator correctly updates rtx_ssrc field and rtx_ssrc_to_primary reverse lookup when RTX SSRC is discovered via rrid after base stream creation.

🤖 Generated with Claude Code

@rainliu rainliu requested a review from Copilot April 4, 2026 14:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds runtime handling for the RTP rrid header extension so incoming RTX/repair packets can be associated with the correct base simulcast stream.

Changes:

  • Implements rrid handling by mapping a repair packet’s SSRC into the base stream’s RTCRtpCodingParameters.rtx.ssrc.
  • Extends imports to include RTCRtpRtxParameters to support RTX parameter creation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rtc/src/peer_connection/handler/endpoint.rs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 4, 2026

Codecov Report

❌ Patch coverage is 0% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.21%. Comparing base (9feb4a3) to head (2b4b96b).

Files with missing lines Patch % Lines
rtc/src/peer_connection/handler/endpoint.rs 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #72      +/-   ##
==========================================
+ Coverage   71.17%   71.21%   +0.03%     
==========================================
  Files         442      442              
  Lines       67330    67333       +3     
==========================================
+ Hits        47922    47950      +28     
+ Misses      19408    19383      -25     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@rainliu rainliu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks this code change doesn't have any actual support of handling repair rtp stream id (rrid)

@nightness
Copy link
Copy Markdown
Author

Storing the RTX SSRC in coding parameters alone was insufficient — the interceptor also needs to know about the repair stream so RTX packets are actually demuxed and forwarded. Added an interceptor_remote_stream_op call after storing the RTX SSRC, using find_rtx_payload_type to resolve the correct RTX payload type. This mirrors the existing RTX handling in interceptor_remote_streams_op.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rtc/src/peer_connection/handler/endpoint.rs Outdated
Comment thread rtc/src/peer_connection/handler/endpoint.rs Outdated
Comment thread rtc/tests/simulcast_rtx_rrid.rs Outdated
Comment thread rtc/src/peer_connection/handler/endpoint.rs Outdated
nightness added a commit to Brainwires/webrtc-rs-rtc that referenced this pull request Apr 8, 2026
- Guard interceptor registration behind has_base_coding: skip binding
  repair stream when no base coding parameters exist for the rrid,
  preventing invalid/unknown rrid values from creating orphan remote
  streams
- Fix RTX payload type lookup: use codec.payload_type directly since
  the packet is already an RTX packet (find_rtx_payload_type was
  searching for apt=<rtx_pt> which never matches)
- Fix test poll_read loop: drain all message types to prevent stalling
  on non-RTP messages (e.g. RTCP)
- Remove unused import (RTCIceConnectionState) and find_rtx_payload_type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nightness nightness requested a review from Copilot April 8, 2026 07:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 452 to +490
if !rrid.is_empty() {
//TODO: Add support of handling repair rtp stream id (rrid) #12
// rrid identifies the base stream (rid) that this repair/RTX packet belongs to.
// Associate the repair SSRC with the base stream's RTX parameters.
let has_base_coding =
match receiver.get_coding_parameter_mut_by_rid(rrid.as_str()) {
Some(coding) => {
match coding.rtx.as_mut() {
Some(rtx) => rtx.ssrc = ssrc,
None => coding.rtx = Some(RTCRtpRtxParameters { ssrc }),
}
true
}
None => {
warn!(
"dropping repair/RTX SSRC association: no base coding \
parameters found for rrid='{}' (repair_ssrc={}, mid='{}', \
rid='{}')",
rrid, ssrc, mid, rid,
);
false
}
};

if has_base_coding {
// Register the repair stream with the interceptor so RTX
// packets are actually demuxed and forwarded. Use the
// actual packet payload type here: in this branch `codec`
// corresponds to the repair/RTX packet, so looking up an
// RTX PT from `codec.payload_type` would fail (it is
// already the RTX PT).
let parameters = receiver.get_parameters(self.media_engine);
RTCRtpReceiverInternal::interceptor_remote_stream_op(
self.interceptor,
true,
ssrc,
codec.payload_type,
&codec.rtp_codec,
&parameters.rtp_parameters.header_extensions,
);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When associating rrid -> coding.rtx.ssrc, the stats accumulator isn’t updated. RTCStatsAccumulator builds rtx_ssrc_to_primary only when the inbound stream accumulator is created (on base-stream OnOpen), so if RTX SSRCs are discovered later via rrid, RTX packets won’t be attributed/tracked (and rtx_ssrc in the inbound stats report will stay 0). Consider adding a pub(crate) stats API to register/update the RTX SSRC for an existing inbound stream (when base coding.ssrc is known), and call it here after setting coding.rtx.

Copilot uses AI. Check for mistakes.
Comment thread rtc/tests/simulcast_rtx_rrid.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +464 to +471
None => {
warn!(
"dropping repair/RTX SSRC association: no base coding \
parameters found for rrid='{}' (repair_ssrc={}, mid='{}', \
rid='{}')",
rrid, ssrc, mid, rid,
);
false
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning message says we're "dropping" the rrid association when no base coding parameters exist, but the function still returns the receiver's track_id afterwards, which will forward this (unknown) repair/RTX packet to the receiver anyway. Either return None here to actually drop the packet when rrid can't be resolved, or change the log message/flow so behavior matches the message and avoids misrouting unknown rrid values.

Copilot uses AI. Check for mistakes.
Comment thread rtc/src/statistics/accumulator/mod.rs Outdated
Comment on lines +314 to +340
// After we've sent enough base packets, send RTX packets with rrid
if seq_num > 30 && !rtx_sent {
for rid in &rids {
rtx_seq_num += 1;
let mut header = rtp::header::Header {
version: 2,
payload_type: 97, // RTX
sequence_number: rtx_seq_num,
timestamp: (start.elapsed().as_millis() * 90) as u32,
ssrc: rid2rtx_ssrc[rid], // Different SSRC
..Default::default()
};
if let Some(id) = mid_id {
header.set_extension(id, bytes::Bytes::from(mid.as_bytes().to_vec()))?;
}
if let Some(id) = rrid_id {
// rrid = base rid value
header.set_extension(id, bytes::Bytes::from(rid.as_bytes().to_vec()))?;
}
let _ = rtp_sender.write_rtp(rtp::packet::Packet {
header,
payload: bytes::Bytes::from(dummy.clone()),
});
}
rtx_sent = true;
log::info!("Sent RTX packets with rrid for all 3 layers");
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test sets rtx_sent = true unconditionally, but RTCRtpSender::write_rtp rejects packets whose SSRC isn't one of the track's SSRCs (it returns ErrSenderWithNoSSRCs). Since the RTX packets use separate SSRCs, these writes are expected to fail, meaning the test can pass without ever exercising the rrid/RTX code path. Capture and assert the write_rtp result (only set rtx_sent if the writes succeed), and adjust the test setup to send real rrid-bearing RTX packets through a supported API path so this test actually validates the behavior it claims to.

Copilot uses AI. Check for mistakes.
Comment on lines +343 to +357
// Check completion: enough base packets received and RTX was attempted
if packets_received >= 20 && rtx_sent {
// Drain any remaining events
while let Some(event) = answerer_pc.poll_event() {
if let RTCPeerConnectionEvent::OnTrack(RTCTrackEvent::OnOpen(init)) = event {
track_count += 1;
log::info!(
"Late track opened: rid={:?} (total={})",
init.rid,
track_count
);
}
}
break;
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The completion condition breaks once packets_received >= 20 && rtx_sent, but track_count may still be < 3 at that point (track open events can arrive later). This can make the test flaky by asserting track_count == 3 before all OnTrack::OnOpen events have been processed. Consider waiting for track_count == 3 (or draining events until it reaches 3) before breaking/ asserting, while still respecting the overall timeout.

Copilot uses AI. Check for mistakes.
Comment thread rtc/src/statistics/statistics_tests.rs Outdated
nightness and others added 6 commits April 10, 2026 00:15
webrtc-rs#12)

Implements rrid (urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id)
handling, resolving webrtc-rs#12.

rrid is already parsed from RTP header extensions and the URI is
registered — the handler body was a TODO stub.  When an RTP packet
carries rrid, it is a repair/RTX stream for the base stream identified
by that rrid value (= the base stream's rid).  Map the repair SSRC into
the base stream's coding parameters' RTX field so downstream routing and
stats can correlate the repair stream with its source stream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Storing the RTX SSRC in coding parameters alone was insufficient —
the interceptor also needs to know about the repair stream so RTX
packets are actually demuxed and forwarded. This mirrors the existing
RTX handling in interceptor_remote_streams_op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If the base stream's coding parameters don't exist yet when a repair/RTX
packet arrives, the SSRC association is silently dropped. Add a warn!
log so this failure mode is observable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exercises the rrid code path in endpoint.rs::find_track_id_by_rid():
- Sends base RTP packets with rid extension for 3 simulcast layers
- Sends RTX packets with rrid extension (different SSRC, RTX payload type)
- Asserts exactly 3 tracks are created (no spurious tracks for RTX SSRCs)

This verifies the RTX SSRC→base stream association and interceptor
registration work correctly through the full pipeline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Guard interceptor registration behind has_base_coding: skip binding
  repair stream when no base coding parameters exist for the rrid,
  preventing invalid/unknown rrid values from creating orphan remote
  streams
- Fix RTX payload type lookup: use codec.payload_type directly since
  the packet is already an RTX packet (find_rtx_payload_type was
  searching for apt=<rtx_pt> which never matches)
- Fix test poll_read loop: drain all message types to prevent stalling
  on non-RTP messages (e.g. RTCP)
- Remove unused import (RTCIceConnectionState) and find_rtx_payload_type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add `update_inbound_rtx_ssrc` to RTCStatsAccumulator so rrid-discovered
  RTX SSRCs are reflected in stats (rtx_ssrc_to_primary map + inbound
  stream's rtx_ssrc field)
- Return `Some(track_id)` from the rrid branch in find_track_id_by_rid
  so RTX packets are routed to the correct receiver instead of dropped
- Add unit tests for update_inbound_rtx_ssrc (with and without existing
  inbound stream)
- Fix clippy warnings in test and stats_tests
- Add note in integration test explaining write_rtp limitation for RTX

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nightness
Copy link
Copy Markdown
Author

Rebased onto upstream/master so this PR contains only its own changes. Previous branch structure caused merge conflicts when PRs were merged in sequence. Each PR is now independently mergeable.

nightness and others added 2 commits April 26, 2026 23:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants