feat: low-latency RTSP mode + PR Docker CI#1
Merged
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three changes to fix unbounded RTSP latency drift, all gated behind enable_low_latency config flag (default: false): 1. Switch AppSrc set_is_live and set_do_timestamp from hardcoded false to conditional on enable_low_latency. When live, GStreamer timestamps buffers with the pipeline clock instead of synthetic 1/fps counters. 2. Disable the Paused/Playing state toggle in low-latency mode. The hysteresis that pauses consumption at 2/3 buffer capacity actively increases latency by reducing consumption rate. 3. Add drain_to_latest() helper in the factory recv loop. When the consumer falls behind, drains all queued frames and skips to the most recent I-frame, bounding latency to one GOP (~1-2s). Zero overhead when nothing is queued (normal operation). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reduces tokio mpsc channel capacities from 100 to 5 when enable_low_latency is true. At 25fps, 5 frames = 200ms of buffer. Combined with the factory drain logic, any momentary backpressure results in at most ~200ms of queued data before draining kicks in. Also passes low-latency buffer hint to start_video so the camera protocol layer uses a smaller internal buffer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Builds and pushes Docker images to GHCR when PRs with the 'run-ci' label are opened, updated, or labeled. Tags images with commit short-SHA and sanitized branch name (e.g. feat/foo -> feat-foo). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR workflow: one concurrent run per PR number (cancel-in-progress). Fixes duplicate runs when opened+labeled events fire simultaneously. GHCR workflow: one concurrent run per ref (cancel-in-progress). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an info-level log message in camera_main when enable_low_latency is true, so operators can confirm the mode is active from the logs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task 1: Reduce make_queue max-size-time from 5s to 200ms in low-latency mode with leaky=downstream to drop oldest data instead of blocking. This is the single largest latency reduction (~4.8 seconds). Task 2: Set h264parse/h265parse config-interval=-1 in low-latency mode so SPS/PPS is emitted with every IDR frame, eliminating 1-5s wait for new clients to receive codec parameters. Task 3: Reduce audio buffer from 725KB to 8KB (~500ms of 8kHz mono) in low-latency mode. Also reduce fallbackswitch timeout from 3s to 500ms. All changes gated behind enable_low_latency config flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Task 4: Reduce learning phase from 11 to 4 frames in low-latency mode. Break out of codec detection as soon as video type is known or after 4 frames max, reducing pipeline startup by 200-400ms. Audio detection no longer gates startup. Task 5: Halve video buffer_size from ~0.25s to ~1 frame in low-latency mode, reducing AppSrc max_bytes and queue max-size-bytes. All changes gated behind enable_low_latency config flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update gstreamer from 0.23 to 0.25, bump all workspace crate versions to 0.7.0-beta, and update all Cargo dependencies to latest compatible versions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The leaky=downstream queue setting dropped arbitrary P-frames when the queue filled, causing decoder corruption until the next I-frame. The drain_to_latest function already handles frame dropping at I-frame boundaries, making the leaky queue redundant and harmful. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
drain_to_latest was dropping all but the last P-frame when no I-frame was present in a drained batch. Since P-frames are sequential (each depends on the previous), this broke the decode chain and caused blocky ghosting behind moving objects that reset on every keyframe. Now keeps all frames when there is no I-frame to resync from. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The learning phase was breaking on first video type detection, before any audio frame arrived. Since the audio pipeline can only be built during initial setup, this resulted in no audio stream. Now waits for both video and audio detection (like normal mode) but with a reduced frame limit of 6 instead of 11. Co-Authored-By: Claude Opus 4.6 (1M context) <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.
Summary
enable_low_latency: true, AppSrc runs in live mode with wall-clock timestamps, the Paused/Playing state toggle is disabled, and a frame-draining mechanism skips to the latest I-frame when the consumer falls behind. Upstream channel buffers reduced from 100 to 5 frames (~200ms at 25fps)..github/workflows/docker-pr.ymlthat builds and pushes Docker images to GHCR on PRs with therun-cilabel. Tags with commit short-SHA and sanitized branch name.Test plan
enable_low_latencyunset (default false) — behavior identical to beforeenable_low_latency = true, verify stream stays within a few seconds of real-time over hoursfeat-low-latency-rtspand short SHA🤖 Generated with Claude Code