lox-linein-bridge is a tiny Linux CLI that captures ALSA audio and streams it to a lox-audioserver line-in ingest over TCP or WebSocket. It is designed for Raspberry Pi / SBC and keeps configuration fully automatic through server-side discovery and registration.
Prebuilt binaries are available for Linux (including Raspberry Pi / SBC). Targets:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
- arm-unknown-linux-gnueabihf (Pi 1 / Zero)
Raspberry Pi mapping:
- Pi 5 / 4 (64-bit OS): aarch64-unknown-linux-gnu
- Pi 3 (64-bit OS): aarch64-unknown-linux-gnu
- Pi 3 / 2 (32-bit OS): armv7-unknown-linux-gnueabihf
- Pi 1 / Zero: arm-unknown-linux-gnueabihf
Download the latest release for your device and place the lox-linein-bridge binary in /usr/local/bin/.
sudo lox-linein-bridge installThis writes the systemd unit, reloads systemd, and enables + starts the service. The systemd unit uses a higher scheduling priority for smoother audio timing.
lox-linein-bridgeThis is only meant for systemd or manual troubleshooting; you do not need to run it after install.
Start manually with logs:
lox-linein-bridge --log-level infoLog levels: off (default), error, warn, info, debug, trace.
mDNS discovery looks for _sonncore._tcp and uses TXT fields:
api(default/api)linein_register(default/api/linein/bridges/register)linein_status(default/api/linein/bridges/{bridge_id}/status)
The bridge prefers the WebSocket ingest when the server offers ingest_ws_url, because that socket is
bidirectional: binary frames carry audio upstream, text frames carry commands back down. It falls back
to raw TCP otherwise.
Over TCP the bridge streams raw PCM:
- Connect to
ingest_tcp_host:ingest_tcp_port - First line:
<assigned_input_id>\n - Then continuous raw PCM
s16le,48 kHz,2 channels(rate and resampler can be overridden by server)
Status updates are sent separately and must not reset the audio stream.
The bridge also reports observed_rate in status updates (measured input rate).
To reduce bandwidth, the bridge uses a simple RMS-based gate. It only streams when audio is above the threshold, then holds the stream for a short time after the signal drops.
Tuning comes from the server's line-in ingest settings:
vad_threshold_db(default:-45.0when unset)vad_hold_ms(default:2000when unset)ingest_sample_rate(default:48000when unset)ingest_resampler(default:sincwhen unset, options:linear,sinc-fast,sinc)
Example GET /api/linein/{id}/ingest response:
{
"linein_id": "linein-mke63267",
"ingest_tcp_host": "192.168.1.209",
"ingest_tcp_port": 7080,
"vad_threshold_db": -45.0,
"vad_hold_ms": 2000,
"ingest_sample_rate": 48000,
"ingest_resampler": "sinc"
}The bridge writes:
/etc/lox-linein-bridge/config.toml(preferred)~/.config/lox-linein-bridge/config.toml(fallback)
Example: examples/config.toml
Config fields:
bridge_id(auto-generated if missing)preferred_server_name(optional; matches the advertised mDNS instance name, e.g.Test Audioserver)preferred_server_mac(optional; matches themacTXT record, any separator/case)on_command(optional script, run for every command the server sends)
The VAD only streams once there is audio, so a source that has to be switched on manually never
starts by itself: nothing produces audio until it is on, and it is never turned on because nothing
asked for it. The on_command hook closes that loop, and carries transport control too.
on_command = "/home/rudy/code/scripts/ml-cmd.sh"The script is called as <script> <command> [args...]:
#!/bin/sh
case "$1" in
start) power_on ;;
stop) power_off ;;
play|pause|next|previous) ml-send "$1" ;;
esacstart and stop follow the server's selection: choosing this input in any client (the app, a
remote, a scene) sends start, and deselecting it, switching the zone to another source, or turning
the zone off sends stop. Stopping the service sends stop too, if the source was still active.
Both are edge-triggered, so a repeated poll does not re-send them.
play, pause, next and previous arrive when a client presses them while this input is the
zone's active source. The server decides that -- it is the only thing that knows which source a zone
is on -- so the bridge keeps no state of its own and simply forwards what it is told. Commands it has
never heard of are passed through unchanged, so the server can add to the vocabulary without a bridge
release.
Commands travel down the ingest WebSocket, so they arrive in a round trip -- about 10ms on a LAN. When the bridge is on the plain TCP ingest, or is momentarily reconnecting, the server falls back to queueing them on the status poll and they arrive within the poll interval instead. Either way the device's own start-up time is on top. A hook that fails is logged and ignored; it never takes the audio stream down.
The wizard writes /etc/systemd/system/lox-linein-bridge.service.
Example: examples/lox-linein-bridge.service
If you want to build from source on Raspberry Pi / SBC:
sudo apt-get install -y libasound2-dev pkg-config
cargo build --release
sudo cp target/release/lox-linein-bridge /usr/local/bin/Then enable the service:
sudo systemctl daemon-reload
sudo systemctl enable --now lox-linein-bridgeCheck service status:
systemctl status lox-linein-bridge