In-plugin H.265 → H.264 transcoding (opt-in) + thermal governor#29
Open
josha wants to merge 3 commits into
Open
In-plugin H.265 → H.264 transcoding (opt-in) + thermal governor#29josha wants to merge 3 commits into
josha wants to merge 3 commits into
Conversation
A Eufy HomeBase serves only ONE camera P2P stream at a time; opening the Home app grid (a live request per tile) plus a tap fires several startLivestreams at once, they mutually starve, and each camera's wedge logic independently recycles the shared station — a cascading "wedge". This is a set of focused fixes for that class of problem on HomeBase-mediated battery cameras. - Per-HomeBase single-stream coordinator (utils/station-stream-coordinator): serialize P2P starts per station (concurrency 1); live preempts, background thumbnail refresh is denied while busy; clean newest-tap-wins handoff that stops the old camera's P2P before the new one starts (no overlap/starve). 4G LTE cameras are their own station. - Snapshot/thumbnail cache that never wakes the camera: serve the last keyframe as the tile image (any age) and return a neutral placeholder on a true miss instead of throwing (throwing made the Snapshot plugin fall back to the waking video path). Persisted across reloads. Optional per-camera background refresh. - Cold-start: recycle a wedged/idle station P2P within the viewer's ~30s patience window (no-data threshold 45s -> 18s) instead of after it has quit. - Audio-aware fMP4 muxing: pick JMuxer both vs video mode by whether the camera delivers usable (ADTS) audio, so mic-off / config-packet-only cameras don't hang the muxer (which left live view black); backstop rebuilds a stalled `both` muxer video-only after 4s. - Report the true source codec to Scrypted so an H.265 stream isn't relabeled H.264 and `-vcodec copy`'d as-is. - Recycle-suppression guard so a signal-dead camera can't keep recycling the shared HomeBase and disrupting its healthy siblings. Unit tested across the stream server, coordinator, snapshot service, and thumbnail refresh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…WebRTC Scrypted's FFmpegInput can't request H.264 output — consumers (HomeKit, the Scrypted-UI WebRTC preview) do `-c:v copy` unless the user manually enables a per-camera "Transcoding Debug Mode". To make H.264 work out of the box (and fix the black H.265-over-WebRTC live view), the plugin now emits real H.264 itself. - h264-transcode-server.ts: a per-device local TCP relay. Each client connection spawns one ffmpeg that reads the stream server's muxed fMP4 (H.265) port and pipes fragmented-MP4 H.264 (libx264 ultrafast/zerolatency, audio copied through) to that socket. One ffmpeg per client; stop() destroys live sockets so the listener closes promptly. The relay's ffmpeg becoming a muxed-port reader is what wakes/idle-stops the livestream, so the cold-start and coordinator lifecycle is preserved. - stream-service.ts: when the toggle is on AND the source is H.265, return a MediaObject that reads the relay (advertised as h264); native H.264 passes through untouched, so a camera that sometimes sends H.264 is never needlessly re-encoded. getVideoStreamOptions advertises h264 to match. - eufy-device.ts: per-camera "Transcode to H.264" setting (Streaming group), default ON for cameras whose last-detected codec is H.265. Trade-off: one shared software encode per active stream (no per-consumer bitrate adaptation), CPU cost on the host; the coordinator caps concurrency to ~one live stream per HomeBase. Toggle off per-camera if the host runs hot. Tests: relay lifecycle/spawn/teardown + stream-service transcode branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…is hot The in-plugin H.264 transcode is a software encode per active stream, which can heat a small host (Raspberry Pi) under concurrent streams. Add a process-wide thermal governor that samples the host CPU temperature (/sys/class/thermal/thermal_zone0/temp) every 10s and: - warns (log + Scrypted alert via log.a) at >=70C, - throttles at >=78C: new streams that would transcode fall back to H.265 passthrough (no encode) until the host cools, - uses hysteresis (clear critical <72C, clear warn <66C) so it doesn't flap, - is inert when the temperature source is unreadable (non-Pi / sandbox) — it can only ever make the transcode path more conservative, never break it. Existing encodes are left to finish (short live-view sessions) rather than killed mid-frame. StreamService gains an isThrottling getter; the provider starts the governor and routes alerts to the Scrypted UI. Tests: thresholds, hysteresis, unreadable-source inertness, alert transitions, and the singleton throttle flag. Co-Authored-By: Claude Opus 4.8 (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.
An opt-in feature, separated from the bug fixes in #28 because it's a product opinion (it trades per-consumer adaptation and host CPU for plug-and-play H.264).
Why
Scrypted's
FFmpegInputcan't be told to produce H.264 — consumers (HomeKit, the Scrypted-UI WebRTC preview) do-c:v copyunless their own per-camera "Transcoding Debug Mode" is enabled. For H.265-only cameras (e.g. the 4G LTE S330s, and S340s when they auto-negotiate H.265) that means a black browser preview and finicky HomeKit setup. To make H.264 work out of the box, the plugin emits real H.264 itself.What's here
utils/h264-transcode-server: a per-device local TCP relay. Each consumer connection spawns one ffmpeg that reads the muxed fMP4 (H.265) and pipes fragmented-MP4 H.264 (libx264 ultrafast/zerolatency, audio copied through) to it. The relay's ffmpeg becoming a muxed-port reader preserves the existing wake/idle-stop lifecycle.utils/thermal-governor: the encode is a software libx264 per active stream, so on a small host (Raspberry Pi) it samples CPU temperature and auto-throttles new transcodes (falls back to H.265 passthrough) above a critical threshold, with hysteresis and a Scrypted UI alert. Inert when the temperature source is unreadable — it can only ever make the transcode path more conservative, never break it.Trade-offs (documented, intentional)
h264_v4l2m2m) is a possible later optimization, left out of v1 for portability.After enabling, users should turn OFF Scrypted's per-camera "Transcoding Debug Mode" (the stream is already H.264).
Testing
Unit-tested: relay lifecycle/spawn/teardown, the stream-service transcode branch, and the thermal governor (thresholds, hysteresis, unreadable-source inertness).
🤖 Generated with Claude Code