fix(codec): bound keyframe bursts with VBV and microsecond time_base

The first real sender/receiver run failed permanently: the receiver
reported 'non-existing PPS 0' for every frame. Without a VBV, a
2256x1504 IDR keyframe bursts hundreds of kilobytes of back-to-back
FU-A packets, overflowing the ~208KB default UDP receive buffer; the
resulting sequence gap made the depacketizer drop whole keyframes
including their in-band SPS/PPS, so the decoder never initialized and
never recovered, because every keyframe burst overflowed again.

- Encoder: add rc_max_rate = bitrate and rc_buffer_size =
  bitrate*2/fps, capping any single frame to about two frame periods
  of bytes (~40KB at the 4Mbps default).
- Encoder: switch the time_base to microseconds. It was derived from
  the configured frame rate, which quantized capture-rate timestamps
  and duplicated pts; RTP timestamps are now lossless.
- Transport: request a 4MB SO_RCVBUF on the receive socket
  (best-effort; the kernel clamps to net.core.rmem_max).

meson test 4/4, valgrind clean (loopback + codec).
This commit is contained in:
2026-09-07 11:06:03 +02:00
parent 10870bc6c9
commit 3f8e92c6af
4 changed files with 40 additions and 10 deletions
+10 -1
View File
@@ -389,10 +389,19 @@ CodecResult<std::unique_ptr<Encoder>> EncoderFactory::create(const EncoderConfig
ctx->codec_type = AVMEDIA_TYPE_VIDEO;
ctx->width = config.width;
ctx->height = config.height;
ctx->time_base = AVRational{config.frame_rate_den, config.frame_rate_num};
// Microsecond resolution: the capture rate varies at runtime (monitor
// refresh) and must not be quantized to the configured frame rate, or
// consecutive frames get duplicate timestamps.
ctx->time_base = AVRational{1, 1'000'000};
ctx->framerate = AVRational{config.frame_rate_num, config.frame_rate_den};
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
ctx->bit_rate = static_cast<int64_t>(config.bitrate_kbps) * 1000;
// VBV keeps the stream CBR-ish: without it a keyframe may take many
// times the average frame size in one burst, overflowing the receiver's
// UDP socket buffer and dropping the packets that carry SPS/PPS. Two
// frame periods of budget keep latency low while bounding the burst.
ctx->rc_max_rate = ctx->bit_rate;
ctx->rc_buffer_size = static_cast<int>(ctx->bit_rate * 2 / config.frame_rate_num);
ctx->gop_size = config.frame_rate_num;
ctx->max_b_frames = 0;
ctx->thread_count = 1;