Skip to content

feat(camera): cap the H.264 transcode bitrate and make it optional - #39

Merged
equake merged 1 commit into
mainfrom
feat/transcode-options
Jul 26, 2026
Merged

equake merged 1 commit into
mainfrom
feat/transcode-options

Conversation

@equake

@equake equake commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Follow-up to #37 and #38. The H.265 path now works end to end — camera/capabilities returns ["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 an ffmpeg process; the others sit at bytes_recv=0).

Inspecting the live go2rtc API with the camera open turned up a bad number.

The problem

Sampling bytes_recv on both producers of ch0 over 20 s:

Source H.265 from the DVR 0.73 Mbit/s @ 2560×1440
Transcoded H.264 producer 6.60 Mbit/s @ 2560×1440

Nine times the source. go2rtc's built-in h264 template (internal/ffmpeg/ffmpeg.go) is:

-c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency -pix_fmt:v yuv420p

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

parseArgs supports #bitrate=, applied after the codec args:

if query["bitrate"] != nil {
    b := query["bitrate"][0]
    args.AddCodec("-b:v " + b + " -maxrate " + b + " -bufsize " + b)
}

The ffmpeg source becomes ffmpeg:<id>#video=h264#audio=opus#bitrate=<N>k.

Two per-device options, added to 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 then made deliberately.
  • H.264 transcode bitrate cap — slider, 256–8192 kbit/s in steps of 256, default 2048.

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__.py already 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_sources keeps 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 --check and manifest key order all pass. Post-deploy checks to run:

  1. Re-measure — the transcode should land near 2 Mbit/s instead of 6.6.
  2. Only one ffmpeg, only for the open channel; the others still at bytes_recv=0.
  3. VLC on rtsp://<ha>:18554/<identifier> still gets untouched H.265 2560×1440.
  4. Move the slider, reopen: /api/streams should show the new #bitrate= in the producer source.
  5. Untick the toggle: the xmeye_…_camera streams 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

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
@equake
equake merged commit bed51b2 into main Jul 26, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant