WHIP: single audio RTP packet loss stalls audio output for ~1.3s (reorder buffer is count-based, not time-based)
Which version are you using?
v1.20.0 (also reproduced ... on main and v1.20.1)
Which operating system are you using?
Linux arm64 standard
Describe the issue
When publishing via WHIP over a lossy link, losing a single audio RTP packet stalls all subsequent audio for ~1.3 seconds. Video is unaffected. Receivers see audio go silent, then receive a burst, and their jitter buffer inflates to 1000ms+ and takes tens of seconds to drain.
The stall duration is constant (1295–1302 ms measured), which points to a fixed threshold rather than network conditions.
Root cause
gortsplib/pkg/rtpreceiver reorders packets using a packet-count threshold with
no time limit:
// pkg/rtpreceiver/receiver.go
if rr.BufferSize == 0 {
rr.BufferSize = 64
}
...
// there's a missing packet and buffer is full.
// return entire buffer and clear it.
if relPos >= int16(len(rr.buffer)) { ... }MediaMTX does not set BufferSize, so the default 64 applies to every track:
// internal/protocols/webrtc/inbound_track.go
t.rtpReceiver = &rtpreceiver.Receiver{
ClockRate: int(t.track.Codec().ClockRate),
UnrealiableTransport: true,
Period: 1 * time.Second,
WritePacketRTCP: ...,
// BufferSize not set -> 64
}Because the threshold counts packets, the resulting delay depends entirely on packet rate:
| Track | Packet rate | Time to fill 64 packets |
|---|---|---|
| Opus audio (20ms frames) | 50 pkt/s | 1.28 s |
| H.265 video | ~330 pkt/s | 0.19 s |
So a single lost Opus packet holds back the following ~64 packets for 1.28 s. Video hits the same code path but drains in 0.19 s, which is why only audio is visible.
Audio also has no recovery path: webrtc.ConfigureNack() registers NACK feedback for
RTPCodecTypeVideo only, so the server never asks the publisher to retransmit lost
audio. The missing packet never arrives, and the buffer always waits for the full count.
Expected behaviour
A single lost audio packet should delay output by at most a few tens of milliseconds, not 1.3 seconds. Reordering should give up after a bounded time, not after a fixed number of packets.
Suggested fix
Either of:
Add a time bound to
rtpreceiver— give up on a missing packet after a maximum wait (e.g. 50–100 ms) regardless of how many packets have arrived. This fixes every caller and matches how jitter buffers normally behave.Set
BufferSizeper track in MediaMTX — a one-line change ininbound_track.go; for audio, 8 packets is 0.16 s and 16 is 0.32 s. Less general but immediate.
Option 1 seems preferable: the current behaviour penalises any low-packet-rate track, and audio is the common case.
Additional context
- Reordering appears to have been introduced in #5194. This report is not about that feature being wrong, but about its threshold being expressed in packets: the same count means very different wait times depending on packet rate, and audio is the worst case.
- Verified
mainand v1.20.1 still contain the same code. - Not specific to a publisher implementation — the wait happens on the server side after the packets arrive. Reproduced with FFmpeg's WHIP muxer as the publisher.
- Only affects paths that use
rtpreceiverwithUnrealiableTransport: true: WHIP ingest, and RTSP sources pulled over UDP. RTSP publishing (server side) does not use it and is unaffected.
Describe how to replicate the issue
- Publish to MediaMTX via WHIP with Opus audio (20 ms frames) + video, over a link with even a very small amount of packet loss (~0.1% is enough).
- Read the stream (WHEP or any reader).
- Capture on the server:
tcpdump -i any -n 'udp port 8889'. - Whenever an inbound audio RTP sequence number is skipped, outbound audio for that session stops for ~1.3 s and then resumes in a burst.
Measurements
3-minute capture, single WHIP publisher, 0.1% audio loss:
- inbound audio: 8,965 packets, 9 sequence gaps
- outbound audio: 9 stalls, each 1295–1302 ms
- each stall released ~1.26 s after the corresponding gap
- video during the same period: no stall (max inter-packet gap 186 ms)
- another session reading the same path was stalled too (the source itself was held back)
Repeated on a different link with 0.011% audio loss: 1 gap → 1 stall (1290 ms).
MediaMTX configuration
Default configuration; nothing in it affects this behaviour.
`BufferSize` is not exposed through mediamtx.yml, so this cannot be tuned by configuration.MediaMTX logs
2026/09/04 12:21:57 WAR [WebRTC] [session 7505399b] 1 RTP packet lost 2026/09/04 12:22:15 WAR [WebRTC] [session 7505399b] 1 RTP packet lost 2026/09/04 12:23:28 WAR [WebRTC] [session 7505399b] 1 RTP packet lost 2026/09/04 12:23:39 WAR [WebRTC] [session 7505399b] 1 RTP packet lost 2026/09/04 12:23:46 WAR [WebRTC] [session 7505399b] 1 RTP packet lost (9 warnings in total during the 3-minute capture)
Each warning matches an outbound audio stall 1:1 — same timestamps, all 9 of them.
Packet dump
Not attached: the capture contains third-party addresses. A filtered summary (RTP sequence numbers and arrival timestamps for the audio SSRC) can be provided on request.
Source: bluenviron/mediamtx