VAFM ↔ DMR Bridge
vafm-homebrew-bridge
carries voice between a VAFM reflector and a DMR network that
uses the MMDVM Homebrew protocol. The DMR side can be a Vexillum DMR instance or
an outside Homebrew master.
It is the only bridge that transcodes audio. VAFM carries Opus and DMR carries AMBE+2, so every call is decoded and re-encoded.
How it works
Section titled “How it works”Calls from DMR are decoded with OpenDMR, resampled, encoded as Opus, and sent to VAFM. Calls from VAFM are decoded, resampled to 8 kHz, encoded as AMBE+2, built into DMR voice bursts, and sent over the Homebrew connection.
VAFM calls are sent to DMR with the source ID, destination, slot, and call type you configure. They are not routed based on the VAFM callsign.
Building
Section titled “Building”This bridge needs cgo and three native codec components:
| Component | Used for | Build tag |
|---|---|---|
| OpenDMR | Decoding DMR audio (required); optionally encoding it | opendmr |
| libopus | Encoding and decoding VAFM audio | opus |
| BLIP25 | Encoding DMR audio (recommended) | blip25 |
You need:
- Go 1.25 or newer
- OpenDMR, built and installed from source (installs to
/usr/localby default) - your operating system’s Opus development package (for example
libopus-dev) - Rust 1.85 or newer, to build the BLIP25 encoder
Build the complete bridge with the recommended encoder:
If OpenDMR is installed somewhere other than /usr/local, point cgo at it with
CGO_CFLAGS and CGO_LDFLAGS. The repository’s
codec guide
covers installing each codec and building BLIP25.
Choosing the DMR encoder
Section titled “Choosing the DMR encoder”OpenDMR is the default encoder, but BLIP25 is recommended. In live testing,
BLIP25 was much cleaner and easier to understand, without the swirly,
over-compressed sound OpenDMR produced from the same input. Select it with
-dmr-tx-codec blip25. OpenDMR is still used for decoding.
This sends VAFM calls to DMR talkgroup 7 on slot 2. If -dmr-tx-destination
is left out or set to 0, only DMR-to-VAFM audio is relayed. Use
-vafm-transport tcp if the VAFM instance uses its TCP transport.
Once it’s running, designate the bridge
on the VAFM instance by its -vafm-callsign (or -callsign, if that isn’t set).
If -master is a Vexillum DMR instance, designate it there too, by its -id
repeater ID, with the same label. Don’t reuse your hotspot’s DMR ID or callsign
for the bridge: the designation would match your hotspot as well (see
One designation, one session).
Homebrew (DMR) connection
Section titled “Homebrew (DMR) connection”| Flag | Default | Description |
|---|---|---|
-master |
Homebrew master, host:port |
|
-id |
0 |
Repeater ID |
-password-env |
HOMEBREW_PASSWORD |
Environment variable holding the master password |
-callsign |
Repeater callsign, max 8 characters | |
-local |
(OS picks) | Local UDP address to bind |
-color-code |
1 |
Color code, 0–15 |
-rx-frequency, -tx-frequency |
0 |
Reported frequencies, in Hz |
-power |
0 |
Reported power in watts, 0–99 |
-latitude, -longitude |
0 |
Reported position |
-height |
0 |
Reported antenna height in meters, 0–999 |
-location |
Reported location, max 20 characters | |
-description |
VAFM DMR bridge |
Reported description, max 19 characters |
-url |
Reported URL, max 124 characters | |
-duplex |
false |
Report duplex operation |
-slot1, -slot2 |
true |
Report which slots are enabled when duplex |
-options |
Homebrew RPTO options string |
VAFM connection
Section titled “VAFM connection”| Flag | Default | Description |
|---|---|---|
-vafm-server |
VAFM instance, host:port |
|
-vafm-transport |
udp |
udp or tcp |
-vafm-callsign |
same as -callsign |
Callsign presented to VAFM |
-vafm-passphrase-env |
VAFM_PASSPHRASE |
Environment variable holding the VAFM passphrase, if one is required |
VAFM to DMR
Section titled “VAFM to DMR”| Flag | Default | Description |
|---|---|---|
-dmr-tx-destination |
0 |
Talkgroup (or private-call ID) for VAFM calls. 0 disables VAFM-to-DMR. |
-dmr-tx-source |
same as -id |
DMR source ID for VAFM calls |
-dmr-tx-slot |
2 |
Slot for VAFM calls, 1 or 2 |
-dmr-tx-private |
false |
Send VAFM calls as private calls instead of group calls |
-dmr-tx-codec |
opendmr |
Encoder: opendmr or blip25 (recommended) |
-dmr-tx-gain |
0 |
OpenDMR encoder gain in dB, -20–20 |
Diagnostics
Section titled “Diagnostics”These help you tell which side of a codec is causing bad audio.
| Flag | Description |
|---|---|
-decode-dir PATH |
Record DMR-to-VAFM audio just after AMBE decoding, one file per call |
-dmr-tx-pcm-dir PATH |
Record VAFM-to-DMR audio just before AMBE encoding |
Both write headerless 8 kHz, signed 16-bit little-endian mono PCM. You can play
the files with, for example, aplay -f S16_LE -r 8000 -c 1 FILE.
Limitations
Section titled “Limitations”- Only one DMR call at a time is forwarded to VAFM.
- Only one VAFM stream at a time is sent to DMR. Streams that overlap are not mixed.
- Embedded link control isn’t sent in voice bursts A–D yet. DMR radios that tune in partway through a call may not show who is talking.
- VAFM-to-DMR audio is timed with a fixed-size queue and the system clock. It isn’t a full network jitter buffer.
- The bridge does not use VAFM callsigns to prevent loops. Callsigns don’t uniquely identify VAFM clients, and treating them as if they did would drop legitimate traffic from FM users.