do not NACK on fec recovered packet

This commit is contained in:
David Chen
2026-09-22 10:41:13 -07:00
parent fd47ab4319
commit b08f67f61a
2 changed files with 116 additions and 18 deletions
+42 -18
View File
@@ -279,22 +279,17 @@ func (b *Buffer) Write(pkt []byte) (n int, err error) {
return
}
rtcpPackets := b.calc(pkt, &rtpPacket, now, false, false)
b.ingestLocked(pkt, &rtpPacket, now, false, false)
var fecDelta fecRecoveryDelta
var onFECRecovery func(received int, recovered int, discarded int, bytesReceived int)
if b.fecDecoder != nil {
// feed media into the FEC decoder, a media arrival can complete a
// previously unrecoverable FEC window
// previously unrecoverable FEC window. Recovered packets update NACK
// state before feedback is generated below, so a packet repaired by
// FEC is not needlessly NACKed.
fecDelta, onFECRecovery = b.feedFECLocked(&rtpPacket, now)
}
b.Unlock()
fecDelta.invoke(onFECRecovery)
if len(rtcpPackets) != 0 {
if cb := b.getOnRtcpFeedback(); cb != nil {
cb(rtcpPackets)
}
}
b.finishWriteLocked(now, fecDelta, onFECRecovery)
return
}
@@ -412,14 +407,13 @@ func (b *Buffer) writeRTX(rtxPkt *rtp.Packet, arrivalTime int64) {
return
}
b.calc(b.rtxPktBuf[:n], &repairedPkt, arrivalTime, false, true)
b.ingestLocked(b.rtxPktBuf[:n], &repairedPkt, arrivalTime, false, true)
var fecDelta fecRecoveryDelta
var onFECRecovery func(received int, recovered int, discarded int, bytesReceived int)
if b.fecDecoder != nil {
fecDelta, onFECRecovery = b.feedFECLocked(&repairedPkt, arrivalTime)
}
b.Unlock()
fecDelta.invoke(onFECRecovery)
b.finishWriteLocked(arrivalTime, fecDelta, onFECRecovery)
}
func (b *Buffer) SetPrimaryBufferForFEC(primaryBuffer *Buffer) {
@@ -565,8 +559,7 @@ func (b *Buffer) writeFEC(fecPkt *rtp.Packet, arrivalTime int64) {
}
fecDelta, onFECRecovery := b.feedFECLocked(fecPkt, arrivalTime)
b.Unlock()
fecDelta.invoke(onFECRecovery)
b.finishWriteLocked(arrivalTime, fecDelta, onFECRecovery)
}
// feedFECLocked runs a media or FEC packet through the FEC decoder and
@@ -592,8 +585,9 @@ func (b *Buffer) feedFECLocked(
// recovered packets flow through the regular pipeline: they are
// forwarded downstream and stop NACKs for the lost sequence numbers.
// They do not re-enter the decoder because chained recovery already
// completed within DecodeFEC.
b.calc(b.fecPktBuf[:n], rp, arrivalTime, false, true)
// completed within DecodeFEC. RTCP is generated once by the caller
// after all recovered packets have updated NACK state.
b.ingestLocked(b.fecPktBuf[:n], rp, arrivalTime, false, true)
}
if cb := b.onFECRecovery; cb != nil {
@@ -683,7 +677,11 @@ func (b *Buffer) sendPLI() {
}
}
func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime int64, isBuffered bool, isRTX bool) []rtcp.Packet {
// ingestLocked runs a packet through the incoming packet pipeline, updating RTP
// stats, NACK state and the packet bucket. It intentionally does not generate
// RTCP so that FEC recovery can update NACK state before feedback is generated
// once. Must be called with the buffer lock held.
func (b *Buffer) ingestLocked(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime int64, isBuffered bool, isRTX bool) {
b.BufferBase.HandleIncomingPacketLocked(
rawPkt,
rtpPacket,
@@ -693,10 +691,36 @@ func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime int64, i
nil,
0,
)
}
func (b *Buffer) calc(rawPkt []byte, rtpPacket *rtp.Packet, arrivalTime int64, isBuffered bool, isRTX bool) []rtcp.Packet {
b.ingestLocked(rawPkt, rtpPacket, arrivalTime, isBuffered, isRTX)
return b.getRTCPPackets(arrivalTime)
}
// finishWriteLocked generates RTCP feedback from the current NACK state, which
// already reflects any FEC recovery performed during this write, releases the
// buffer lock, then invokes the FEC recovery callback and delivers RTCP
// feedback. Generating RTCP after recovery ensures sequence numbers repaired by
// FEC are not needlessly NACKed. Must be called with the buffer lock held; it
// releases the lock before returning.
func (b *Buffer) finishWriteLocked(
arrivalTime int64,
fecDelta fecRecoveryDelta,
onFECRecovery func(received int, recovered int, discarded int, bytesReceived int),
) {
rtcpPackets := b.getRTCPPackets(arrivalTime)
b.Unlock()
fecDelta.invoke(onFECRecovery)
if len(rtcpPackets) != 0 {
if cb := b.getOnRtcpFeedback(); cb != nil {
cb(rtcpPackets)
}
}
}
func (b *Buffer) getRTCPPackets(arrivalTime int64) []rtcp.Packet {
var pkts []rtcp.Packet
if nackPkt := b.getNACKPacket(); nackPkt != nil {