Repository navigation
feat(camera): cap the H.264 transcode bitrate and make it optional - #39
Merged
Merged
Conversation
Inspecting the live go2rtc API with the camera open showed the transcode spending nine times the source bitrate: 0.73 Mbit/s of H.265 in, 6.60 Mbit/s of H.264 out, both at 2560x1440. go2rtc's built-in h264 template carries no rate control at all, so x264 falls back to CRF 23 and spends bits preserving the compression artefacts the DVR already introduced. The WebRTC consumer was on a public IPv6, so that was 6.6 Mbit/s going out over the internet for a camera whose source fits in under one. go2rtc's parseArgs supports #bitrate=, applied after the codec args as -b:v/-maxrate/-bufsize, so it overrides the CRF. The ffmpeg source becomes ffmpeg:<id>#video=h264#audio=opus#bitrate=<N>k. Two per-device options, both in the existing options flow: - Transcode H.265 to H.264 (toggle, default on). Off registers nothing with go2rtc and clears both repair advisories, since the trade-off was made deliberately. - H.264 transcode bitrate cap (slider, 256-8192 kbit/s, default 2048). Resolution is deliberately left alone: clients that decode HEVC keep receiving the original 2560x1440, and the cap only affects the browser-facing path. The existing options update listener reloads the entry, so changing either value tears down the old entities (unregistering the stream) and re-registers with the new source string. _stream_has_sources keeps matching because the connected producer's URL carries the whole source string percent-encoded in ?source=, suffix included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011UpaDKeLbpQB7EapQBpKoJ
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.
Follow-up to #37 and #38. The H.265 path now works end to end —
camera/capabilitiesreturns["hls","web_rtc"], Chrome on Linux plays smoothly and opens near-instantly, and the transcode is genuinely lazy (of three registered H.265 channels, only the open one has anffmpegprocess; the others sit atbytes_recv=0).Inspecting the live go2rtc API with the camera open turned up a bad number.
The problem
Sampling
bytes_recvon both producers ofch0over 20 s:Nine times the source. go2rtc's built-in
h264template (internal/ffmpeg/ffmpeg.go) is:No rate control at all, so x264 falls back to CRF 23 and spends bits preserving the compression artefacts the DVR already introduced. The observed WebRTC consumer was on a public IPv6 address — so that was 6.6 Mbit/s leaving over the internet to watch a camera whose source fits in under 1.
The fix
parseArgssupports#bitrate=, applied after the codec args:The ffmpeg source becomes
ffmpeg:<id>#video=h264#audio=opus#bitrate=<N>k.Two per-device options, added to the existing options flow:
Resolution is deliberately left alone. Clients that decode HEVC keep receiving the original 2560×1440; the cap only affects the browser-facing path.
No new wiring was needed to apply changes:
__init__.pyalready registers an update listener that reloads the entry, so the old entities tear down (unregistering the stream) and the new ones register with the new source string._stream_has_sourceskeeps matching because the connected producer's URL carries the whole source string percent-encoded in?source=, suffix included — verified against the live instance.Testing
Measured before the change on the real device (0.73 → 6.60 Mbit/s, 9.0×).
ruff,compileall,helpers/i18n.py --checkand manifest key order all pass. Post-deploy checks to run:ffmpeg, only for the open channel; the others still atbytes_recv=0.rtsp://<ha>:18554/<identifier>still gets untouched H.265 2560×1440./api/streamsshould show the new#bitrate=in the producer source.xmeye_…_camerastreams disappear, both repair notices clear, and HA recreates its own default stream on the next view. Ticking it back restores both producers.🤖 Generated with Claude Code
https://claude.ai/code/session_011UpaDKeLbpQB7EapQBpKoJ