Skip to content

stream: a video session holds HDMI capture while it runs (RustDesk re-park) - #356

Merged
dormancygrace merged 1 commit into
mainfrom
stream/video-session-capture-demand
Oct 21, 2026
Merged

dormancygrace merged 1 commit into
mainfrom
stream/video-session-capture-demand

Conversation

@dormancygrace

Copy link
Copy Markdown
Owner

What

Encoding without viewers already stops. Capture parks 20 s after its last viewer (scheduleHdmiParkTimerLocked): VPSS outputs are disabled and the H.264/H.265 and JPEG encoder channels are deleted. The H.264/H.265 direct and WebRTC streamers count their viewers for this, and MJPEG reports its clients.

RustDesk's media bridge (service/rustdesk/bridge.go, serveMedia) subscribes to the same video source but was not counted. With only RustDesk connected, this happened:

  1. capture parked under it;
  2. its next frame read resumed the native side by itself (kvmv_read_img_locked);
  3. the server's hdmiCaptureParked flag stayed true.

That flag is cleared only when demand changes, so nothing parked capture again after the RustDesk session. VPSS kept converting every input frame, and the encoder channel stayed allocated, until some other viewer connected and left.

Now every video session holds an HDMI capture lease (vm.AcquireHdmiCaptureLease) from its start to its end, whoever subscribed. main passes the lease function via stream.SetCaptureDemand in initialize, before the router or the RustDesk bridge can subscribe. Fixing it in the shared source rather than in RustDesk alone covers any future subscriber. MJPEG and MCP/PicoClaw already count or lease.

Side effects

  • A RustDesk session now also resumes HDMI input stopped by the user's HDMI idle timeout (off by default), like any other viewer.
  • RustDesk is demand, not a counted viewer. The viewer badge is unchanged.

Tests

  • New TestVideoSessionHoldsCaptureWhileItRuns: one hold per session, kept while any subscriber remains, released when the session ends. Run with -race.
  • CI's go build/go vet/go test -tags teststub,nomsgpack on the touched packages.
  • tests/test-video-recovery-native.py: recovery still touches no video ABI.
  • Stand: streaming unchanged with browser viewers (H.265 VI-direct, MJPEG 4:2:2 on its own output).

Not verified on hardware

The stand always had a browser viewer connected, so neither the park under a RustDesk-only session nor the re-park after it was observed. Expected evidence in a no-viewer window: VENC channel 1 destroyed and frozen VPSS SendOK counters about 20 s after the last viewer, and a resume on reconnect.

🤖 Generated with Claude Code

Capture parks 20 s after its last viewer: VPSS outputs off, the video
and JPEG encoders deleted. The direct and WebRTC streamers count their
viewers for that; RustDesk's media bridge subscribes to the same video
source but counted nothing. With only RustDesk connected, capture parked
under it, its next read resumed the native side, and the server still
took capture for parked. Nothing parked it again after the session, so
VPSS kept converting and the encoder channel stayed allocated until
another viewer came and went.

Every video session now holds an HDMI capture lease
(vm.AcquireHdmiCaptureLease, wired in main) from its start to its end,
whoever subscribed. Counted viewers keep working as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 0fac4f4. No actionable defects found. The lease is acquired outside the VideoSource mutex and before either capture loop; runStream closes/drains the native stream before returning, then the deferred lease release runs before session.done closes. Thus a replacement session cannot start before the old lease is released. Native-to-paced fallback stays within the same lease, and multiple subscribers share one lease. Initialization installs the callback before router/RustDesk subscriptions; counted viewers remain independent.

Local validation passed: go test -race -tags teststub,nomsgpack for service/stream, service/vm and service/rustdesk; go vet on those packages and main; real native-binding recovery test plus its positive control. The new session-lifetime test and existing park/resume/lease tests pass. No device test was performed in this review; the RustDesk-only park/re-park hardware observation remains the limitation already recorded in the description.

14 CI checks are currently green on this head; the full firmware build is still running. Merge will wait for that final check.

@github-actions

Copy link
Copy Markdown

Build outputs

Compared with the build of main at 196efd0.

1 changed, 0 added, 0 removed of 541 outputs.

directory files
changed server server/NanoKVM-Server
Hashes: outputs-sha256 artifact of this run.

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final verification: all 15 checks on 0fac4f4 are now successful, including the full firmware build. No code changes since the reviewed/tested commit. Merging under the user's authorization.

@dormancygrace
dormancygrace merged commit 3a5263d into main Oct 21, 2026
15 checks passed
@dormancygrace
dormancygrace deleted the stream/video-session-capture-demand branch October 21, 2026 08:17
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