@leaperone/remote-au
v0.3.0
Published
N→1 LAN audio aggregation CLI — multiple senders capture audio, one receiver mixes and plays it back. Go + miniaudio, UDP (default) / TCP, auto-discovery.
Downloads
699
Readme
remote-au
N→1 LAN audio aggregation, from the command line.
Run remote-au send on several machines and remote-au recv on one. Each sender
captures its microphone or system audio and streams it over the local network; the
receiver mixes every stream together and plays the result through its speakers.
It is a small, dependency-light reimplementation of the core idea behind AudioRelay — without the hardest part (a kernel virtual-microphone driver), because the goal here is simply to play the combined audio out of one machine's speakers, not to expose it as a virtual mic.
Windows ──capture──┐
(loopback) │
▼
phone / laptop ──► UDP / TCP ──► receiver ──► mix ──► speakers
(mic) (LAN) (any OS)
▲
another box ────────┘- Roles, not platforms.
recvandsendboth run on Windows, Linux and macOS. Any machine can be the receiver; any machine can be a sender. - Zero-config discovery.
sendfinds the receiver on the LAN automatically — no IP addresses to type. - Low latency by default. Audio travels over UDP (connectionless, no head-of-line blocking); TCP is available as a reliable fallback.
Features
- N→1 aggregation — many senders → one receiver that mixes and plays back.
- Cross-platform — Windows / Linux / macOS, sender and receiver.
- Auto-discovery — UDP broadcast;
sendwith no--toconnects to the receiver it finds on the LAN. - UDP (default) or TCP —
--transport udp|tcp. UDP for low latency, TCP for reliability / restrictive networks. - Resilient — UDP is connectionless: restart the receiver and senders keep emitting and the stream resumes on its own.
- Per-stream jitter buffer — priming, bounded depth, drop-oldest, loss
concealment with silence;
--verboseprints per-stream queue / discarded / underrun / latency stats. - Bounded & defensive — every network length is validated before allocation; capped concurrent streams; rate-limited discovery replies; idle streams expire.
Install
From npm (prebuilt binary for your platform is fetched automatically):
npm install -g @leaperone/remote-au
remote-au --help # or the short alias:
rau --helpThe CLI is available under two command names — the full remote-au and the short
rau — they are identical.
Supported targets: darwin-arm64, linux-x64, linux-arm64, win32-x64. The @leaperone/remote-au package is a thin launcher; its install
script downloads the matching prebuilt binary from the GitHub Release. This needs
network access at install time and does not work with --ignore-scripts. If you
prefer no install scripts, build from source instead (below).
Build from source
Requirements:
- Go 1.24+
- A C toolchain (the audio backend is miniaudio via
gen2brain/malgo, which needs cgo):- macOS — Xcode Command Line Tools (
xcode-select --install). - Linux — GCC/Clang and ALSA/PulseAudio/PipeWire dev headers.
- Windows — MinGW-w64 (
gcc). MSVC is not supported by cgo.
- macOS — Xcode Command Line Tools (
git clone https://github.com/leaperone/remote-au.git
cd remote-au
CGO_ENABLED=1 go build -o remote-au ./cmd/remote-auCross-compiling needs a target C compiler and CGO_ENABLED=1; building natively on
each platform is the simplest path.
Quick start
On the machine that should play the audio (the receiver):
remote-au recvOn each machine that should send audio:
remote-au send # capture the microphone, auto-find the receiver
remote-au send --source loopback # capture system audio (Windows: native WASAPI)That's it — no IP addresses. Start two senders and the receiver mixes them.
If discovery is blocked on your network (some corporate VLANs isolate broadcast), point the sender at the receiver directly:
remote-au send --to 192.168.1.20:47000Commands
remote-au devices
List the local playback and capture devices with their indices, so you can pass
--device <index> to recv/send. Also notes per-platform loopback support.
remote-au selftest
Capture the local microphone and play it straight back through the local speakers — a quick check that the audio backend works on this machine.
remote-au recv — the aggregator
Listens for senders (TCP and UDP on the same port), mixes all active streams, and plays the result.
| Flag | Default | Description |
|------|---------|-------------|
| --addr | :47000 | Audio listen address (TCP + UDP). |
| --device | auto | Playback device index (see devices). |
| --discovery-port | 0 | UDP discovery port. 0 auto-selects the first available of 47001, 48001, 49001; explicit N forces one port. |
| --name | hostname | Name advertised to senders during discovery. |
| --no-discovery | off | Disable the LAN discovery responder. |
remote-au send — a capture source
Captures audio and streams it to a receiver.
| Flag | Default | Description |
|------|---------|-------------|
| --to | (discover) | Receiver address, e.g. 192.168.1.20:47000. Skips discovery. |
| --peer | (none) | Require a discovered receiver with this name (safe disambiguator). |
| --source | mic | Capture source: mic or loopback (system audio). |
| --transport | udp | Audio transport: udp (low latency) or tcp (reliable). |
| --device | auto | Capture device index (see devices). |
| --discover-timeout | 1.5s | How long to wait for discovery replies. |
| --discovery-port | 0 | UDP discovery port. 0 queries 47001, 48001, 49001; explicit N forces one port. |
| --name | hostname | Name this sender uses during discovery. |
Global flags
Apply to any subcommand, placed before it (e.g. remote-au --verbose recv):
| Flag | Default | Description |
|------|---------|-------------|
| --rate | 48000 | Sample rate (Hz). |
| --channels | 2 | Channel count. |
| --frame-ms | 10 | PCM packet duration (ms). |
| --verbose | off | Verbose audio-backend + per-stream stats logging. |
All endpoints must agree on rate / channels; the receiver rejects senders whose
format does not match.
How discovery works
- The receiver runs a UDP responder. By default it listens on the first
available discovery port from
47001,48001,49001. When it receives a discovery query, it responds with an announce containing its name, a stable per-run instance ID, and the audio port. - The sender, when given no
--to, queries all default discovery ports and collects replies for--discover-timeout:- one receiver found → connects automatically (and prints which one);
- several found → it lists them and asks you to pick with
--toor--peer; - none found → it tells you to use
--to.
Trust model: discovery trusts the local network — a single discovered receiver
is connected to without a prompt. On an untrusted LAN, pin the target with --to
or --peer. (Cryptographic peer authentication is intentionally out of scope.)
Transports
| | UDP (default) | TCP (--transport tcp) |
|---|---|---|
| Latency | Lower — no retransmit stalls, no head-of-line blocking | Slightly higher under loss |
| Packet loss | Dropped; concealed with silence (jitter buffer) | Retransmitted (can stall) |
| Connection | Connectionless — survives receiver restarts | Connection-oriented |
| When to use | Default; LAN audio | Lossy / firewalled networks needing reliability |
The receiver always listens on both, so senders can choose freely.
Platform notes
recv and send both work on all three desktop OSes. The only platform-specific
concern is capturing system audio on the sender:
| OS | Microphone | System audio (--source loopback) |
|----|------------|------------------------------------|
| Windows | ✅ | ✅ native WASAPI loopback |
| Linux | ✅ | Select a PulseAudio/PipeWire monitor source as the capture device |
| macOS | ✅ | Needs a virtual device such as BlackHole |
Playback (what the receiver does) works everywhere with no extra setup.
Audio format
Fixed for now: 48 kHz, 16-bit signed little-endian, 2 channels, 10 ms packets
(480 frames). Override with the global --rate / --channels / --frame-ms flags
(all endpoints must match).
Protocol (summary)
- TCP audio — magic
RAU1: a handshake (format + name) then length-prefixed audio frames (big-endian header,seq,captureFrame, payload). Stream framed. - UDP audio — magic
RAUU: self-contained datagrams,HELLO(handshake) sent periodically +AUDIO(seq / captureFrame / PCM). Payload capped to 960 bytes (5 ms) to stay under the Ethernet MTU and avoid IP fragmentation. - Discovery — magic
RAUD: boundedquery/announcedatagrams.
All length fields are validated and capped before any allocation.
Project layout
cmd/remote-au/ CLI entry point and subcommand wiring
internal/audio/ miniaudio (malgo) capture / playback / device enumeration
internal/protocol/ TCP stream framing + UDP datagram codec
internal/transport/ sender + receiver (TCP accept loop & UDP receive loop)
internal/mixer/ per-stream jitter buffer + N-stream mix engine
internal/discovery/ UDP query/announce responder + finder
internal/stats/ per-stream / aggregate statisticsThe mix engine runs inside the playback device callback (the audio clock): it pulls exactly the requested number of frames from each stream's jitter buffer, sums them with per-source gain and a saturating limiter, and is strictly non-blocking and allocation-free in steady state.
Limitations / roadmap
- Raw PCM on the wire (~1.5 Mbps per stream). Opus compression is a natural next step for weak links and many simultaneous streams.
- No per-stream volume / mute controls yet.
- Mobile senders (Android / iOS) are not covered — that's a separate, larger effort.
- macOS system-audio capture needs a virtual device (see platform notes).
Releasing (maintainers)
Releases are automated by .github/workflows/release.yml
and published to npm via Trusted Publishing (OIDC) — no npm token or secret.
One-time setup on npmjs.com → @leaperone/remote-au → Settings → Trusted Publisher,
add a GitHub Actions publisher:
- Organization/owner:
leaperone - Repository:
remote-au - Workflow filename:
release.yml
Then cut a release by pushing a semver tag:
git tag v0.1.0
git push origin v0.1.0CI builds the cgo binary on each platform's native runner, attaches them to a
GitHub Release, and publishes @leaperone/remote-au to npm via OIDC (with build
provenance). The package's install script then downloads the matching binary from
that Release.
License
MIT © leaperone
