Repository navigation
fix(rtmp): read the live video pipe greedily so the stream drops to live instead of falling behind - #515
Merged
Conversation
…ive instead of falling behind Owner live incident 2026-09-13: a YouTube stream read as "poor / not receiving enough data." The core was healthy (60fps, 0 dropped), but FFmpeg's stderr showed it falling progressively behind wallclock — "Resumed reading ... after a lag of 123s", the lag growing 0.7s -> 124s over ~8 minutes. It received 60fps and pushed only ~47 through to the destination, losing ~13fps continuously. Cause: `-re` on the LIVE rawvideo pipe input. That pipe is already realtime — the sender paces writes at the 60Hz video-output tick — so -re double-throttled it. When the RTMP push could not sustain the bitrate, FFmpeg kept reading input at wallclock into its internal buffer while the output drained slower, accumulating unbounded lag rather than staying current. A live encoder must drop to stay live. Fix: remove -re from the video pipe so FFmpeg reads it greedily. Under backpressure the pipe fills, the sender's write blocks, and AsyncOutputSender's existing newest-wins DROPS stale frames to stay live — the standard live-encoder behaviour. It also gets the first video keyframe + decoder config to the muxer sooner, so it cannot reintroduce the audio-races-ahead / no-video connection close that the AUDIO -re guards. The audio -re is DELIBERATELY KEPT (both the real PCM pipe and the anullsrc fallback): audio is ~256 kbps and never backs up the link, so pacing it costs no live-lag, and pacing it is what preserves the A/V startup ordering; aresample= async=1 holds sync while video drops. The asymmetry is now commented at both sites so it is not "fixed" back to symmetric. Not yet validated against a live endpoint — the audio-ordering interaction only shows against a real RTMP ingest, so this needs a live soak before it is trusted. Regression test VideoPipeIsGreedyWhileAudioIsPaced pins the arg shape. All output/sender suites green (RtmpFfmpegArgs 9, supervisor 16, lifecycle 19, AsyncEncoderSink 27). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ
iamfatness
added a commit
that referenced
this pull request
Sep 14, 2026
…ist) (#520) Metrics from a ~5h real test-meeting session (10->16 participants, 8-member wall at 1080p, streaming to YouTube), read live from /snapshot and the logs. Records the session numbers and the prioritized list, cross-referencing the issues filed from it: #515/#514 (P0 in flight), #516/#517/#509 (P1 load), #518/#519 (P2 verify/cleanup), plus the cap-vs-9-camera-on note and the two lying counters. Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live incident
A YouTube stream read as "poor / not receiving enough data." The core was healthy (60 fps, 0 dropped), but FFmpeg's stderr showed it falling progressively behind wallclock:
The lag grew from 0.7 s to 124 s over ~8 minutes — FFmpeg received 60 fps and pushed only ~47 to the destination, losing ~13 fps continuously. YouTube saw an increasingly delayed, under-rate stream.
Two counters that lie (noted so no one chases them)
bytesSentis an estimate (estimatedFrameBytes, hard-assumes 30 fps) — not a real bitrate. The "15.6 Mbps" it implied is fictional.latencyMs = 2100is a hard-coded constant, not a measurement.The only real signal was the frame rate: 47/60 reaching the destination.
Cause
-reon the live rawvideo pipe input. That pipe is already realtime (the sender paces writes at the 60 Hz video-output tick), so-redouble-throttled it. When the RTMP push couldn't sustain the bitrate, FFmpeg kept reading input at wallclock into its internal buffer while the output drained slower — accumulating unbounded lag instead of staying live.Fix
Remove
-refrom the video pipe so FFmpeg reads it greedily. Under backpressure the pipe fills, the sender's write blocks, andAsyncOutputSender's existing newest-wins drops stale frames to stay live — the standard live-encoder behaviour (OBS drops "network" frames the same way). It also gets the first video keyframe + decoder config to the muxer sooner.Audio
-reis deliberately kept (both the real PCM pipe and theanullsrcfallback). Audio is ~256 kbps and never backs up the link, so pacing it costs no live-lag — and pacing it is what preserves the A/V startup ordering that prevents the documented "connects, perfect audio, no video, endpoint closes" failure.aresample=async=1holds sync while video drops. The asymmetry is commented at both sites so it isn't "fixed" back to symmetric.Not yet validated live
The audio-ordering interaction only manifests against a real RTMP ingest, so this needs a live soak against YouTube before it's trusted — it has not been run against an endpoint yet. Unit coverage:
VideoPipeIsGreedyWhileAudioIsPacedpins the arg shape; all output/sender suites green (RtmpFfmpegArgs 9, supervisor 16, lifecycle 19, AsyncEncoderSink 27).Not addressed here (environmental)
If the upload genuinely can't sustain the configured bitrate, this makes the stream stay live rather than fall behind, but it will drop frames to do so. The durable answer to sustained under-capacity is a lower target bitrate — separate from this bug.
🤖 Generated with Claude Code
https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ