ZuidWest FM Encoder
Audio streaming software for ZuidWest FM and Radio Rucphen. Stream audio from a Raspberry Pi to multiple SRT destinations simultaneously. Built for broadcast environments with real-time monitoring and web-based configuration.
Features
- Multi-output streaming - Send to multiple SRT servers with different codecs simultaneously
- Real-time VU meters - Peak hold (1.5 s) with peak/RMS toggle, clip detection, updated via WebSocket
- Silence detection - Alerts via webhook, email, file log, or Zabbix when audio drops below threshold
- Web interface - Configure outputs, select audio input, monitor levels
- Auto-recovery - Automatic reconnection with configurable retry limits per output
- Multiple codecs - MP3, MP2, Ogg Vorbis, or uncompressed WAV per output
- Update notifications - Alerts when new versions are available
- Single binary - Web interface embedded, minimal runtime dependencies
| Platform |
Status |
Audio Capture |
| Linux (Raspberry Pi) |
Primary |
arecord (ALSA) |
| macOS |
Development only |
FFmpeg (AVFoundation) |
| Windows |
Experimental |
FFmpeg (DirectShow) |
Linux on Raspberry Pi is the primary target. macOS works for development. Windows is experimental.
Requirements
Installation
- Install Raspberry Pi OS Trixie Lite (64-bit)
- Configure HiFiBerry following the official guide
- Run the installer as root:
sudo su
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/oszuidwest/zwfm-encoder/main/deploy/install.sh)"
The web interface will be available at http://<raspberry-pi-ip>:8080
Default credentials: admin / encoder
Updating
Run the same installer script to update to the latest version:
sudo su
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/oszuidwest/zwfm-encoder/main/deploy/install.sh)"
The installer detects existing installations and asks whether to:
- Update only - Downloads the latest binary while preserving your configuration
- Fresh install - Overwrites configuration (existing config is backed up)
The web interface shows a notification when updates are available.
Connect the digital output of your audio processor to the HiFiBerry input.
Requirements:
- 48 kHz sample rate
- 16-bit depth
- Stereo (2 channels)
- S/PDIF format preferred (AES/EBU compatibility not guaranteed)
Codecs
| Codec |
Encoder |
Bitrate |
Notes |
| MP3 |
libmp3lame |
320 kbit/s |
— |
| MP2 |
libtwolame |
384 kbit/s |
Uses psymodel 4 |
| Ogg |
libvorbis |
~500 kbit/s (Q10) |
— |
| WAV |
pcm_s16le |
Uncompressed |
— |
Silence Detection
Monitors audio levels and sends alerts when silence is detected or recovered. Uses hysteresis to prevent alert flapping:
| Setting |
Default |
Range |
Description |
| Threshold |
-40 dB |
-60 to 0 |
Audio level below which silence is detected |
| Duration |
15 s |
1 to 300 |
Seconds of silence before alerting |
| Recovery |
5 s |
1 to 60 |
Seconds of audio before recovery |
Alerting options (can use multiple simultaneously):
- Webhook - POST request to a URL on silence start and recovery
- Email - Microsoft Graph API notification to configured recipients on silence start and recovery
- File Log - Append JSON Lines to a local file for each silence event
- Zabbix - Send trapper items to a Zabbix server on silence start and recovery
Configure via the web interface under Settings → Alerts.
Microsoft 365 Email Setup
Email notifications use Microsoft Graph API with app-only authentication.
- Create an App Registration → copy Client ID and Tenant ID
- Add API permissions:
Mail.Send (required), Application.Read.All (optional, for secret expiry warnings)
- Grant admin consent
- Create a client secret → copy the value immediately (won't be shown again)
- Create a shared mailbox as the sender (no license required)
The encoder warns when the secret expires within 30 days.
Zabbix Setup
- Import
zabbix/template.xml in Zabbix (Data collection → Templates → Import)
- Link the template to your encoder host
- Configure in the encoder: server, port (default 10051), host name (must match Zabbix exactly), and item key
The template creates triggers for SILENCE (Disaster), RECOVERY (Info), and TEST (Info) events.
Configuration
Configuration is stored in /etc/encoder/config.json on production systems. For development, use the -config flag to specify a custom path, or place config.json next to the binary.
{
"system": { "port": 8080, "username": "admin", "password": "encoder" },
"web": { "station_name": "ZuidWest FM" }
}
The installer creates a minimal config file. All other settings are configured through the web interface.
Event Log
The encoder logs all stream, silence, and recording events to a JSON Lines file for monitoring and debugging. Events are accessible via the web interface and REST API.
See docs/events.md for the complete event reference.
Health Endpoint
GET /health provides a public endpoint for monitoring tools (Kubernetes probes, load balancers, Prometheus, etc.).
Healthy (200 OK) requires both:
- Encoder state is
running (audio capture active)
- FFmpeg binary is available on the system
Unhealthy (503 Service Unavailable) when either:
- Encoder state is
stopped, starting, or stopping
- FFmpeg binary is not found
Note: Stream connection failures, silence detection, and recorder errors do not affect health status. These are reported in the response body for informational purposes only.
Response example:
{
"status": "healthy",
"encoder_state": "running",
"stream_count": 2,
"streams_stable": 2,
"recorder_count": 1,
"recorders_running": 1,
"uptime_seconds": 9252,
"silence_detected": false
}
No authentication required.
Architecture
flowchart LR
A[Audio Input]
subgraph Capture["Capture"]
B[arecord]
end
subgraph Processing["Audio Processing"]
D[Distributor]
subgraph Metering["Metering"]
M[RMS/Peak<br>Calculator]
PH[Peak Hold<br>1.5s Decay]
CD[Clip<br>Detect]
end
subgraph Silence["Silence Detection"]
SD[Detector<br>Hysteresis]
SDM[Dump Manager<br>Ring Buffer]
end
end
subgraph Outputs["Streaming Outputs"]
OM[Output Manager<br>Retry + Backoff]
F1[FFmpeg MP3]
F2[FFmpeg MP2]
F3[FFmpeg OGG]
S1[SRT 1]
S2[SRT 2]
S3[SRT 3]
end
subgraph Recording["Recording"]
RM[Recording Manager]
R1[Hourly]
R2[On-Demand]
ST[(Storage)]
end
subgraph Alerts["Alerting"]
SN[Silence Notifier]
N1[Webhook]
N2[Email]
N3[Log]
N4[Zabbix]
end
subgraph UI["Web UI"]
WS[WebSocket<br>10fps]
end
%% Main audio flow
A ==>|S/PDIF| B ==>|PCM| D
%% Metering branch
D ==>|PCM| M -->|dB| PH
M -->|samples| CD
PH -->|dB JSON| WS
CD -->|clip JSON| WS
%% Silence detection branch
D -->|dB| SD
D ==>|PCM| SDM
SD -->|events| SDM
SD -->|state JSON| WS
%% Alerting
SD -->|event| SN
SDM -->|MP3| SN
SN -->|HTTP| N1
SN -->|Graph API| N2
SN -->|file| N3
SN -->|trapper| N4
%% Streaming
D ==>|PCM| OM
OM ==>|PCM| F1 -->|SRT| S1
OM ==>|PCM| F2 -->|SRT| S2
OM ==>|PCM| F3 -->|SRT| S3
%% Recording
D ==>|PCM| RM
RM ==>|PCM| R1 -->|MP3| ST
RM ==>|PCM| R2 -->|MP3| ST
Audio Flow
- Capture:
arecord (Linux) or FFmpeg (macOS/Windows) captures 48kHz 16-bit stereo PCM
- Distributor: Processes PCM in ~100ms chunks, fans out to all consumers
- Metering: Calculates RMS/peak levels in Go (no FFmpeg filters), holds peaks for 1.5s, detects clipping at ±32760
- Silence Detection: Hysteresis-based detection with configurable threshold/duration/recovery. Buffers 15s audio context before/after silence events
- Alerting: Silence triggers webhook, email (MS Graph), log (JSON Lines), and/or Zabbix. Recovery includes MP3 dump attachment
- Streaming: Per-output FFmpeg processes with automatic retry and exponential backoff
- Recording: Hourly rotation or on-demand, with optional S3 upload
Post-installation
Optional hardening:
echo "dtoverlay=disable-wifi" >> /boot/firmware/config.txt # Disable WiFi
apt remove bolt bluez ntfs-3g telnet # Remove unused packages
SRT Resources
License
MIT License - See LICENSE.md