diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-18 14:37:08 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-18 14:37:08 +0200 |
| commit | d22ce0ba5aebfff5ffaea97982472f876af5da22 (patch) | |
| tree | 073b7ad96bc58aa834cd8f781d81915fdaa6e338 /packages/meshbay-node/src/meshbay_node/config.py | |
| parent | 0d56cd33b975afe7648dd6f6380a30a7b5230c90 (diff) | |
| download | meshbay-d22ce0ba5aebfff5ffaea97982472f876af5da22.tar.gz | |
feat: decode and re-encode video on the GPU where there is one
A 1080p film is decoded by whoever watches it and re-encoded by the node
when no browser can decode the source. Both were on the CPU, and on an
Atom or Celeron mini-PC neither reaches real time — which is what
`transcode_incompatible_video` exists to refuse. This adds the mechanism
that makes refusing it unnecessary.
Node — `hwaccel.py`: VA-API on Linux, Quick Sync or NVENC on Windows,
established by encoding 1080p and reading the file back with ffprobe.
Nothing is accepted that does not produce the exact profile and level
`stream_init` announces, since the client checks that string before it
trusts a byte: an encoder that wrote another level would make the node's
own codec string a lie, and ffmpeg takes `-level 4.1` and `-level 41`
from h264_qsv without saying which it understood. Three modes per
stream — hardware decode and encode, hardware encode alone, libx264 —
demoted per source codec, because a GPU that decodes HEVC may have no
decoder for MPEG-4 Part 2 and only asking it finds out. A mode that
fails is detected on an empty stdout before `stream_init` goes out, so
the viewer sees one working stream and never an error.
Client — Chromium ships VA-API off on Linux. It is enabled where a
render node and a driver are present, then verified through
`navigator.mediaCapabilities`: a no moves to the next GL backend on the
next launch and an exhausted list drops the switches, so a renamed
feature cannot pass for a feature that is on and `--ignore-gpu-blocklist`
cannot survive on a machine it did not help. Feature lists now merge
rather than overwrite — `appendSwitch` replaces the value, and a second
caller would have silently cancelled the mDNS switch aiortc depends on.
Packaging — the drivers are weak dependencies on all four formats, so a
machine without a GPU installs exactly as before. `dpkg -i` and
`rpm -ivh` ignore weak deps; `packaging/README.md` now says so. Windows
needs no driver: the bundled ffmpeg already carries h264_qsv and
h264_nvenc, and a re-pin that dropped them would cost every low-power
Windows node its hardware encoding silently.
AMD on Windows (AMF) and macOS (VideoToolbox) are named gaps, not
oversights: neither could be tried anywhere in this project, and both
re-encode in software as before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/config.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/config.py | 17 |
1 files changed, 17 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/config.py b/packages/meshbay-node/src/meshbay_node/config.py index 7351b1c..086d800 100644 --- a/packages/meshbay-node/src/meshbay_node/config.py +++ b/packages/meshbay-node/src/meshbay_node/config.py @@ -72,6 +72,15 @@ max_concurrent_uploads = 8 # Set to false only if every viewer's client is known to decode HEVC itself. transcode_incompatible_video = true +# Use the GPU for that re-encode when there is one that works — VA-API here, +# Quick Sync or NVENC on Windows. Established by encoding 1080p and checking +# the result, never guessed from the hardware, and ignored where the test +# fails: this is on by default and the only reason to turn it off is a driver +# that misbehaves in a way the test does not catch. On an Intel mini-PC it is +# the difference between transcoding in real time and having to set +# transcode_incompatible_video = false. +hardware_video_encode = true + # ICE candidate gathering. By default, virtual/VPN interfaces (Tailscale, # libvirt, Docker) are auto-excluded — a STUN request that can't reach the # server holds the WebRTC answer for 5 seconds. Set this to restrict @@ -180,6 +189,12 @@ class NodeConfig: # that already decode the source codec directly, since transcoding costs # real CPU per concurrent viewer, unlike the copy path. transcode_incompatible_video: bool = True + # Whether that re-encode may run on the GPU. See hwaccel.py: the capability + # is established by encoding and reading the result back, so `true` here + # means "use it if it works", never "assume it does". Off returns the node + # to libx264 on every stream, which is where it was before hardware + # encoding existed. + hardware_video_encode: bool = True # ICE candidate gathering: which network interfaces to include or exclude. # By default, virtual and VPN interfaces (Tailscale, libvirt, Docker) are # auto-excluded because a STUN request that can't reach the server holds @@ -355,6 +370,8 @@ def load_config(path: Path = DEFAULT_CONFIG_PATH) -> Config: cfg.node.max_concurrent_uploads, "max_concurrent_uploads") cfg.node.transcode_incompatible_video = bool( nd.get("transcode_incompatible_video", cfg.node.transcode_incompatible_video)) + cfg.node.hardware_video_encode = bool( + nd.get("hardware_video_encode", cfg.node.hardware_video_encode)) ice_if = nd.get("ice_interfaces") if isinstance(ice_if, list): cfg.node.ice_interfaces = [str(s) for s in ice_if] |