Files
fenrir/docs/audio-recovery.md

112 lines
5.9 KiB
Markdown

# Audio startup and recovery
Fenrir handles speech and sound connections independently. The recovery code
uses Linux processes, sockets and deadlines; it does not require systemd or a
particular service manager. Explicit server, speech driver and sound driver
choices remain authoritative. Failures never select a different audio driver.
## Speech
Before opening console keyboards or an X11 terminal session, Fenrir retries
temporary speech connection failures for up to 20 seconds. Permission and
configuration errors fail without waiting through that retry window. Speech
Dispatcher's own configuration decides whether to allow daemon autostart.
When a running Speech Dispatcher connection fails, the speech worker attempts
to reconnect for up to 20 seconds. Keyboard forwarding, interruption, mute and
quit commands remain responsive. Pending speech is discarded during recovery;
it is not replayed when the connection returns. Numeric speech settings and
mute state survive recovery. Later output uses the restored connection.
If recovery fails, Fenrir reports the error and exits. Keyboard grabs are
released before waiting for UI tasks, speech or sound shutdown. Native hardware
speech drivers retain their existing failure handling; they do not start a
Speech Dispatcher daemon or select a different synthesizer.
## Root at boot and sudo sessions
A boot service running as root without `SUDO_UID` uses root's Speech Dispatcher
environment and configuration. It does not choose a logged-in user's daemon.
Root's speech and sound connections receive the same retry/recovery handling.
The audio server must be configured to start independently of graphical login
and permit the required socket and output-device access.
For `sudo fenrir`, `SUDO_UID` identifies the invoking user. If the selected local
Speech Dispatcher is missing, Fenrir looks up that account and starts only the
daemon child with that account's UID, primary group and supplementary groups.
The child receives the user's home and an owned runtime directory when present;
it does not inherit root's session bus or unrelated environment variables.
An owned `XDG_RUNTIME_DIR` is preferred for the default speech socket, followed
by the user's owned `/run/user/UID` directory, or the user's cache directory
when no runtime directory exists. Explicit speech socket/address settings
still take precedence. Fenrir retains its own console/device privileges.
Logging out may stop user-owned Speech Dispatcher or audio services. Recovery
can reconnect when the same services return; it does not keep a login session
alive or migrate to another user's audio. No session-owned daemon is killed
or restarted to force recovery.
## Sound effects
Sound effects retry failed initialization and disconnected playback in a
background worker for up to 20 seconds per outage. While unavailable, sound
cues are rejected so Fenrir can use their speech equivalents. Cues and progress
tones from an outage are discarded. The latest sound volume is applied to a
restored connection.
Each sound-driver open attempt has a three-second deadline. Some native/library
calls cannot be interrupted: if an attempt remains stuck, Fenrir stops opening
new connections for that player. At most one abandoned opener remains, and it
closes its result if it eventually returns. Working speech and keyboard input
continue. Exhausted sound recovery disables sound effects for that run.
## Limits
Retries cannot repair missing services, incorrect permissions, a wrong server,
or a missing playback device. A successful Speech Dispatcher protocol exchange
also does not prove audible output: an output module can accept a request and
fail inside its own audio backend. PipeWire, PulseAudio and Speech Dispatcher
still need appropriate configuration. In particular, PulseAudio's libpulse
autospawn behavior does not imply that a PipeWire service will start itself.
## Verification
Automated checks use private sockets and fake audio backends, without taking
over the active reader:
```sh
go test -race ./internal/audio ./internal/speech ./internal/sound ./internal/app
go test ./...
```
For optional isolated Speech Dispatcher startup checks using its dummy module:
```sh
FENRIR_NATIVE_SPEECH_STARTUP=1 go test ./internal/speech -run TestNativeSpeechDispatcherColdStart -count=1
```
Before submission, review the diff and test on an affected machine with a
recovery path, such as SSH or another accessible console. Keep only one reader
on a console. Source tests do not prove installed behavior or audible output.
1. Boot without a GUI and before user login, with Fenrir started by the normal
boot service. Verify speech, startup sounds, typing, review and quit. Repeat
with the audio server delayed briefly: speech should recover if the required
service becomes available inside the retry window.
2. Check ordinary `sudo fenrir` startup with the user's Speech Dispatcher both
already running and initially absent. Check daemon ownership and confirm
speech and sound. No root-owned daemon should be created in the user's
runtime directory. Test a console session without systemd as well.
3. With a recovery connection available, interrupt the configured speech/audio
service using that system's normal service controls. While it is unavailable,
check ordinary typing, Control, mute and Fenrir's quit shortcut. Restore the
service and check fresh review output, volume/rate/pitch and absence of old
queued announcements. A permanent speech failure must release the keyboard;
a sound-only failure must leave speech working.
4. Repeat ordinary X11 terminal startup, review, interruption and exit, plus
explicit ALSA sound output where available. Check settings changes during
recovery and verify that saved settings are not silently rewritten.
5. For user-owned audio, log out and back in with the recovery connection still
available. Verify behavior against that machine's session-lifetime policy;
this is separate from root-owned pre-login audio.