Skip to content

Commit 1a06e26

Browse files
committed
Harden capture recovery, forced TURN, and peer lifecycle cleanup
1 parent 73c2cc9 commit 1a06e26

6 files changed

Lines changed: 491 additions & 181 deletions

File tree

audio_devices.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Resolve explicitly selected USB audio identity on each publisher launch."""
2+
import os
3+
import re
4+
5+
6+
def resolve_alsa_device(device):
7+
"""Accept legacy ALSA names or a persistent udev sound-card symlink.
8+
9+
Persistent paths select PCM device zero of that card. Missing paths are
10+
errors, never a request to fall back to another microphone or disable audio.
11+
"""
12+
if not device or not device.startswith(('/dev/snd/by-id/', '/dev/snd/by-path/')):
13+
return device
14+
if not os.path.exists(device):
15+
raise ValueError('Selected USB microphone is unavailable: ' + device
16+
+ '. Reconnect it; an unattended service will retry.')
17+
target = os.path.realpath(device)
18+
match = re.fullmatch(r'/dev/snd/controlC([0-9]+)', target)
19+
if not match:
20+
raise ValueError('USB microphone path must resolve to an ALSA control device: ' + device)
21+
return 'hw:' + match.group(1) + ',0'

docs/operations-guide.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ invocation, not the JSON file or an already-running service.
218218

219219
## Run unattended with systemd
220220

221+
For a portable Pi with a UVC camera, use a stable `/dev/v4l/by-id/` capture
222+
path and validate its advertised formats before installing the service. Start
223+
with 640x360 at 15 fps and 500 kbps, then tune against the actual uplink and
224+
encoder load. Audio and transport overhead also need upload capacity.
225+
If a selected `by-id` or `by-path` camera disappears, startup fails and the
226+
service retries that selection. It does not substitute another camera.
227+
228+
The asyncio runtime services GLib events so media bus errors and queued decoder
229+
fallbacks are delivered. Unhandled terminal media errors log the failing element and GStreamer
230+
details, then exit with status 1. Shutdown has an independent eight-second
231+
deadline in case a camera driver blocks during cleanup. Existing Jetson decoder
232+
and display fallback handlers still run first. A direct CLI invocation exits;
233+
automatic process recovery requires a supervisor such as the service below.
234+
This detects reported media errors, not every possible silent camera freeze.
235+
221236
First prove the exact command interactively. Then use `tools/install_unattended.py` to create a validated receiver or sender unit whose user and working directory match the installed clone. Complete examples are in the [Pi Zero 2 W guide](pi-zero-2-w-unattended-webrtc.md#8-make-the-receiver-start-on-boot).
222237

223238
Useful service commands:
@@ -231,6 +246,31 @@ journalctl -u raspberry-ninja-viewer.service -f
231246

232247
The helper uses `Restart=always`, a small `RestartSec`, unbuffered Python output, and `network-online.target`. It stores credentials in a restricted JSON config instead of the unit command. Running the installer again validates the replacement unit and restarts the existing service so new settings take effect.
233248

249+
Newly generated units retry every five seconds without a start-rate limit, so
250+
a camera missing for several minutes does not permanently disable the service.
251+
Reinstall an existing unit to apply this policy. Retries also continue for
252+
configuration errors; inspect the journal and stop the service while correcting
253+
them. Ordinary signaling reconnection continues inside the running application.
254+
255+
For USB camera/microphone recovery, select the camera under `/dev/v4l/by-id/`
256+
and pass the microphone's `/dev/snd/by-id/` symlink to `--audio-device` (installer)
257+
or `--alsa` (publisher). `/dev/v4l/by-path/` and `/dev/snd/by-path/` select a port
258+
instead. The publisher resolves the sound-card symlink on every launch and new capture pipeline, opening
259+
PCM device zero of that card even if its numeric card index has changed. A missing
260+
explicit microphone is a startup error; the service retries instead of disabling
261+
audio or selecting another microphone. Existing ALSA names remain supported;
262+
use one when the required PCM device is not zero. Automatic audio discovery can
263+
still disable audio when no mic is present, so use an explicit device unattended.
264+
265+
The WebRTC publisher monitors buffers from `v4l2src`, `alsasrc`, and `pulsesrc`.
266+
If an active capture source produces no buffers for 30 seconds, it exits for
267+
supervised recovery. `--capture-timeout SECONDS` adjusts the timeout (`0` disables
268+
it). This also covers a source that never produces its first buffer. Paused/idle
269+
pipelines do not expire, and quiet audio still counts as healthy capture when
270+
buffers continue. Other camera backends retain their existing behavior. Capture
271+
failure restarts the whole publisher, so removing the mic can also interrupt video.
272+
This requires a service supervisor; the standalone script does not relaunch itself.
273+
234274
`--service-name` accepts up to 247 letters, digits, underscores, dots, hyphens,
235275
or `@` characters before the generated `.service` suffix. It must not start with
236276
`-` or `@`. Use a concrete instance such as `camera@front`, rather than `camera@`.
@@ -302,6 +342,18 @@ Increase one dimension at a time: resolution, then frame rate, then bitrate, the
302342

303343
## Stability checks
304344

345+
For mobile connections, peer negotiation is limited to 60 seconds by default.
346+
A stalled attempt is released so the viewer's existing reconnect schedule can
347+
request a fresh connection. Use `--peer-connect-timeout SECONDS` to adjust this
348+
window (`0` disables it). Established connections are not expired by this timer.
349+
350+
To exercise relay-only operation, configure `--ice-transport-policy relay` and
351+
`--turn-server` (or the equivalent configuration-file keys). Startup rejects
352+
missing or invalid TURN configuration, and applying the relay policy must succeed
353+
before ICE servers are configured. TURN credentials are hidden in setup logs.
354+
Test both signaling reconnection and actual TURN transport outages: a working
355+
WebSocket alone does not demonstrate that video has recovered.
356+
305357
Run at least a short sender-off/sender-on recovery test and a longer steady-state soak before unattended deployment:
306358

307359
```bash

0 commit comments

Comments
 (0)