Remote audio

RX:  radio USB codec -> arecord -> gain -> shared track -> MediaRelay -> Opus (per listener) -> browser
TX:  browser mic -> Opus -> decode -> mono 48 kHz -> [SERVER GATE] -> gain + limiter -> aplay -> radio USB codec

Internal format: mono, s16, 48 kHz, 20 ms frames. Opus runs at 32 kbps (aiortc defaults to 96 kbps stereo); configurable with audio.opus_bitrate. The audio runs in the same process as the rest of the server (a separate process was considered; the measured CPU cost on a Pi 4 makes it unnecessary).

Using it

Safety design

What has been verified

On a real FT-991A with a Raspberry Pi 4:

Automated tests (no radio) cover real WebRTC sessions in-process: RX audio and level meter, mic audio reaching the sink only while the gate is open, wrong connection, viewer role, peer limit, device loss and recovery.

Not measured yet: the latency of either transport (WebSocket audio, receive and transmit, and the automatic fallback with UDP blocked were verified on a real FT-991A with a Raspberry Pi 4), figure (budget: capture 20-80 ms + Opus 20 ms + network + the browser's jitter buffer).

When WebRTC cannot connect: WebSocket audio

WebRTC sends the audio as UDP packets. Some networks block that (hotel, office or school Wi-Fi, some mobile carriers, strict firewalls). The page then works but the audio does not connect. For that case the same Opus audio can travel over the page's own HTTPS/WebSocket connection (TCP).

Pi setup notes

sudo apt install alsa-utils libopus0          # the installer does this
sudo usermod -aG audio radio-remote           # service user needs the audio group (the installer does this)
arecord -l ; aplay -l                          # note the CARD=<id> of the USB Audio CODEC

Use Raspberry Pi OS Lite (no PipeWire/PulseAudio grabbing the device). Set audio.input_device / audio.output_device to plughw:CARD=<id>,DEV=0 (a full ALSA name, never a bare number) only if auto-detection finds zero or several codecs. The admin-only GET /api/audio/devices shows what the server sees.

If there is no audio

  1. arecord -l / aplay -l: confirm the codec appears when the radio is on, and what its card ID is.
  2. arecord -D plughw:CARD=<id>,DEV=0 -f S16_LE -r 48000 -c 1 -d 5 /tmp/rx.wav with a signal on the radio: play it back. If that is silent the problem is on the radio side (menus, USB level), not in the app.
  3. In the app: Listen. Check the RX meter and what you hear. Adjust audio.rx_gain_db.
  4. For transmit, test with a dummy load: enable Mic, speak, watch "Your mic" move; then hold PTT and watch "TX audio". Tune audio.tx_gain_db and the radio's data-out level until the ALC is just touching.
  5. The connection can drop and come back for a second when the browser renegotiates (reloading the page, toggling Listen, a network change): that is normal.

Receive volume too low

Three places, in this order: 1. Browser Volume slider (Audio tab) at 100 %. 2. The radio's own USB output level menus (0-100, default 50): 107 SSB OUT LEVEL, 073 DATA OUT LEVEL, 046 AM OUT LEVEL, 075 FM OUT LEVEL, 054 CW OUT LEVEL, 099 RTTY OUT LEVEL (CAT manual p.9). This is the cleanest fix because it improves the signal-to-noise ratio instead of just amplifying. (On the tested FT-991A, menu 107 = 90 fixed a quiet receive level.) 3. Admin > Config > Audio > rx_gain_db: boost in dB (-30..30, live, no restart). +6 dB doubles the amplitude, +12 dB is four times. A boost above 0 dB runs through the same soft limiter as the transmit path, so loud signals do not clip hard.

The radio's AF gain knob is believed not to affect the USB audio (not verified separately); the app therefore does not offer an AF gain slider.