#1898·mediasoup

Fix downlink FEC rate calculation polluted by unrecovered uplink packet loss

Author: RTC-KaiCreated Sep 2, 2026Updated Sep 17, 2026
Labelsfeature

Background

In mediasoup's SFU architecture, the downlink fractionLost used to drive FEC redundancy is derived from the RTCP Receiver Report sent by the downlink subscriber back to the server. This RR reflects packet loss after mediasoup forwards packets to the subscriber.

However, the packets mediasoup forwards are sourced from the uplink RTP stream received from the publisher. If packets are lost on the uplink and not recovered (e.g., NACK failed), mediasoup simply has no packet to forward — those sequence numbers are never sent downlink.

The Problem

The downlink RR's fractionLost and totalLost are calculated by the subscriber based on sequence number gaps in what they receive. When mediasoup skips a sequence number because the uplink packet was never received, the subscriber sees a gap and counts it as a loss — indistinguishable from a real downlink loss.

This means the fractionLost fed into UpdateProtectionParameters() in RtpStreamSend is inflated by uplink losses:

  • packetsLost = report->GetTotalLost();
  • fractionLost = report->GetFractionLost(); // ← contains uplink loss contamination
  • flexfecSender->UpdateProtectionParameters(fractionLost);

Consequence

  • FEC redundancy is increased in response to uplink packet loss, which FEC on the downlink cannot fix
  • Downlink FEC packets consume bandwidth but provide zero recovery benefit for uplink-originated gaps
  • In the worst case, under sustained uplink loss, the FEC overhead wastes bandwidth while the actual downlink loss rate (loss caused purely by the downlink path) remains near zero and goes unaddressed

Expected Behavior

fractionLost used for downlink FEC should reflect only losses that occurred on the downlink path — i.e., packets mediasoup actually sent but the subscriber did not receive.

The correct calculation:

  downlink_fraction_lost = (subscriber_total_lost - producer_unrecovered_lost) / consumer_packets_sent                                   

Where producer_unrecovered_lost is the count of sequence numbers mediasoup never forwarded because the uplink packet was missing and not recovered.