Skip to content

Latest commit

 

History

History
169 lines (123 loc) · 5.68 KB

File metadata and controls

169 lines (123 loc) · 5.68 KB

lox-linein-bridge

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.

Download

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/.

Install (systemd)

sudo lox-linein-bridge install

This writes the systemd unit, reloads systemd, and enables + starts the service. The systemd unit uses a higher scheduling priority for smoother audio timing.

Run (systemd)

lox-linein-bridge

This is only meant for systemd or manual troubleshooting; you do not need to run it after install.

Troubleshooting

Start manually with logs:

lox-linein-bridge --log-level info

Log 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)

Audio ingest protocol

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).

Voice activity detection (VAD)

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.0 when unset)
  • vad_hold_ms (default: 2000 when unset)
  • ingest_sample_rate (default: 48000 when unset)
  • ingest_resampler (default: sinc when 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"
}

Configuration

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 the mac TXT record, any separator/case)
  • on_command (optional script, run for every command the server sends)

Commands from the server (hook)

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" ;;
esac

start 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.

Systemd unit

The wizard writes /etc/systemd/system/lox-linein-bridge.service.

Example: examples/lox-linein-bridge.service

Build (optional)

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-bridge

Check service status:

systemctl status lox-linein-bridge