Skip to content

Pion repeatedly sends the same RTCP (NACK) packet even after successfully receiving and processing the corresponding RTX packet. #3063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
arjunshajitech opened this issue Mar 13, 2025 · 8 comments · May be fixed by pion/interceptor#317 or #3064

Comments

@arjunshajitech
Copy link

arjunshajitech commented Mar 13, 2025

Environment

  • Version: v4.0.12
  • OS: Zorin OS 17.1 Core

What I Did

I simulated packet loss by generating UDP packet drops, triggering RTCP (NACK) requests in Pion WebRTC with RTX enabled.

Expected Behavior

The RTCP (NACK) requests should stop once the missing packet is successfully received via RTX, preventing further redundant requests.

Actual Behavior

Pion continuously sends RTCP (NACK) requests for the same packet, even after the RTX packet is received and processed.

This happens because RTX packets are only logged in the receiver log of the RTX track, not the corresponding RTP track.

The tight coupling of the RTPReader within the interceptor prevents RTX packets from passing through the correct interceptor chain for the RTP track.

Image
Image
Image

Root Cause

The ReadRTP function reads an RTP packet and passes it through the interceptor chain.
However, when an RTX packet is received, it isn’t processed through the corresponding RTP track’s interceptor chain.
As a result, it isn’t logged in the RTP track’s receiver log, causing the system to treat the packet as missing and repeatedly issue NACKs.

Proposed Fix

To resolve this, I propose separating the reading and processing of RTP packets:

Read RTP: Handle packet reading independently to avoid tight coupling with the interceptor.

Process RTP: Pass the packet (including converted RTX packets) through the interceptor chain explicitly.

Currently, RTX packets are converted to RTP packets but not passed through the interceptor chain. By splitting the process, the converted RTX packets can be processed through the appropriate interceptors and logged correctly in the RTP track’s receiver log.

Image
Image
Image

@Sean-Der
Copy link
Member

That's an amazing find! Nice work :)

After work I will respond. Just need to fix those issues first :(

@3DRX
Copy link
Member

3DRX commented Apr 13, 2025

@arjunshajitech This is so cool and useful! I've read the changes you made to address this issue, which makes me wonder is there anyway we can acheve this without breaking existing interceptor API (I'd like to see the fix being merged as soon as possible)? Perhaps we can stop resending same RTX packet by detecting the original sequence number in nack generator_interceptor? And for media decoding to work, perhaps we can create a new interceptor that transforms all rtx packets back to original rtp packets?

@arjunshajitech
Copy link
Author

@3DRX I'm glad you think so!

we can create a new interceptor that transforms all rtx packets back to original rtp packets?

I think creating a new interceptor would only address the NACK issue.
However, this issue also affects TWCC and receiver reports, as they shouldn't report packet loss if the packet was successfully received via RTX.

The fix also resolves the ( TWCC + Simulcast ) issue, but there are still some packets being reported as not received. That part still needs to be addressed.

@3DRX
Copy link
Member

3DRX commented Apr 13, 2025

What if we move the new interceptor to the front, let it be the first interceptor and pass down extra attributes to mark that it's an rtp media packet that's obtained via retransmission, will this fix all the problems?

Btw, I think TWCC shouldn't be affected since it only cares about the transport wise sequence number in header extension, and rtx packet should be treated as distinct ones from it's original media packet.

I'm not familiar with simulcast, so please correct me if I'm wrong.
Again, thank you so much for the work on this!

@arjunshajitech
Copy link
Author

What if we move the new interceptor to the front, let it be the first interceptor and pass down extra attributes to mark that it's an rtp media packet that's obtained via retransmission, will this fix all the problems?

The root cause was that the RTX packet wasn't properly logged in the corresponding RTP received log.
From what I understand of the current behavior, the receiverLog is a map where the key is the SSRC.

So, are you suggesting that we map the corresponding SSRC in the StreamInfo of the RTX StreamInfo, and if the packet is flagged as an RTX via attributes, we process it, extract the original sequence number (OSN), and then log it in the receiverLog?
Is that what you have in mind, or is there a different approach you're thinking of?

Btw, I think TWCC shouldn't be affected since it only cares about the transport wise sequence number in header extension, and rtx packet should be treated as distinct ones from it's original media packet.

So, do we really need a sender interceptor to handle TWCC?
As you mentioned — and as I also currently understand — TWCC only depends on the transport-wide sequence number in the header extension.

Given that, can't we just pass every packet to a standalone function after reading it, where the TWCC logic is applied (e.g., extracting the transport-wide sequence number as done in the current sender interceptor), and then write the feedback RTCP using a random SSRC?

@arjunshajitech
Copy link
Author

arjunshajitech commented Apr 13, 2025

What if we move the new interceptor to the front, let it be the first interceptor and pass down extra attributes to mark that it's an rtp media packet that's obtained via retransmission, will this fix all the problems?

Additionally, since RTX packets only require TWCC feedback, there's no need for them to go through the NACK or other related interceptors, correct me if I'm wrong.

@3DRX
Copy link
Member

3DRX commented Apr 14, 2025

@arjunshajitech Thanks for your reply!

So, are you suggesting that we map the corresponding SSRC in the StreamInfo of the RTX StreamInfo, and if the packet is flagged as an RTX via attributes, we process it, extract the original sequence number (OSN), and then log it in the receiverLog?

Yeah that’s exactly what I’m thinking of.

Additionally, since RTX packets only require TWCC feedback, there's no need for them to go through the NACK or other related interceptors

I think by design, all packets should go through all interceptors, it’s the interceptor’s choice to whether ignore it, and whether to forward it to next interceptor.

Given that, can't we just pass every packet to a standalone function after reading it, where the TWCC logic is applied (e.g., extracting the transport-wide sequence number as done in the current sender interceptor), and then write the feedback RTCP using a random SSRC?

That’s exactly what the TWCC sender interceptor does.

@3DRX
Copy link
Member

3DRX commented Apr 14, 2025

@Sean-Der @JoeTurki @jech what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants