Evens out audio across modes and stations, and moves ambed's settings into
a config file so levels can be tuned without a rebuild or restart.
Audio chain:
- Every path now runs decode -> <MODE>_TO_PCM -> bandpass filter -> AGC ->
PCM_TO_<MODE> -> encode, including M17 <-> P25.
- One decode and one encode gain per mode replace the per-path gains.
- Signal processing is rebuilt for each stream, so a stream starts clean and
doesn't inherit the previous station's gain.
- The bandpass filter clamps its output to the short range.
AGC:
- Levels the speaker, not the syllable. It works like the AGC on an HF
receiver: fast down, hold through gaps, then up, faster the further a
station is below the target.
- Follows speech peaks, moving the gain between syllables and leaving the
shape of the speech alone for the vocoders.
- Pauses and noise below AGC_GATE leave the gain where it is.
- The gain ramps across each frame, so a change can't click.
- A peak limiter leaves room for the highest encode gain, so a boost can't
clip an encoder.
ambed.ini:
- The command line is now ambed [ambed.ini], and main.h holds the defaults.
The old ambed <ip> form is refused, so it can't bind to the wrong address.
- Holds the listen IP and port, DSCP marking, stream timeout, logging, gains,
AGC and filter settings. USE_AGC and USE_BANDPASSFILTER are now runtime
options.
- Checked every 3 minutes. Most changes apply from the next stream, DSCP
changes at once, and the listen IP and port need a restart. xlxd's
TRANSCODER_PORT must match LISTEN_PORT.
- A setting left out uses its default. A bad value is logged and the value
in use stays.
- make install copies the sample only if there is no ambed.ini, and the run
script and systemd service pass its path.
Tuning log: when a stream closes, ambed logs voice level, peak and clipping
after decode, after the decode gain, after the AGC and after each encode
gain, plus the speech peaks, AGC gain and limiter activity. With
AGC_CLAMPING=0 the audio is untouched and the levels are still logged.
TUNING_LOG turns the log on or off.
The readme is now README.md, rewritten to cover the audio chain, every
setting, how to tune and an example systemd unit.
First-class M17 protocol support:
- Full M17 client handling: connect (CONN/LSTN), disconnect (DISC),
keepalive (PING/PONG), voice streaming with 40ms→20ms frame split
- M17 base-40 callsign encoding/decoding per specification
- M17 wire stream ID generation (random-seeded per module)
- Codec2 3200bps frame pairing (two 8-byte frames per 40ms packet)
- Synthetic EOT detection (500ms timer) for clients that don't send
proper end-of-transmission, with cross-mode distribution via
CDvLastFramePacket pushed to reflector stream
- Byte-swapped stream ID fallback for M17 EOT packets
- DISC packet sent to all M17 clients on reflector shutdown
- Listen-only client support
P25 and NXDN peering:
- P25 peer protocol with IMBE voice frame handling
- NXDN peer protocol with AMBE2+ voice frame handling
- AMBE2+↔AMBE2+ gain levelling for NXDN within xlxd
- AMBE2+↔AMBE2+ gain levelling for YSF mode within xlxd
DCS, DPlus (REF), and DExtra (XRF) peer link management:
- Automatic peer connect/disconnect from gatekeeper peer list
- Peer keepalives with timeout detection and reconnection
- Connection state machine (connecting/connected/disconnected)
- Peer client objects for voice routing through existing reflector
- Peer mirror prevention via full IP:port owner matching on DCS,
DPlus, and DExtra peer send paths
AMBEd software vocoder support:
- Async Codec2 encoder thread in CVocodecChannel with condition
variable wake-up, PCM queue, and encoded output queue
- Async IMBE encoder thread (same pattern) using imbe_vocoder library
- Embedded Codec2 library for M17 codec support
- Embedded IMBE vocoder library for P25 codec support
- Staging mechanism in CStream::Task(): hold hardware AMBE result
until software codecs catch up (1s timeout safety net)
- Three-codec output per stream (e.g., AMBEPLUS→AMBE2PLUS+CODEC2+IMBE)
- Precomputed gain multipliers to avoid pow() per packet
- Codec2 decode gain (+6dB) applied in AMBEd stream processing
- IMBE encode gain (-4dB) applied to all PCM→IMBE paths
- Software queue depth caps (SOFTWARE_QUEUE_MAX_DEPTH=50)
Transcoding gains (tuned from live testing):
- Codec2 path: CODEC2_TO_AMBEPLUS -3dB, CODEC2_TO_AMBE2PLUS -13dB,
CODEC2_TO_IMBE -13dB
- IMBE path: IMBE_DECODE_GAIN +18dB, IMBE_TO_AMBE2PLUS -13dB,
IMBE_TO_CODEC2 -7dB
- Hardware: CODECGAIN_AMBEPLUS -10dB, CODECGAIN_AMBE2PLUS +10dB
- Cross-mode AMBE2+: NXDN↔DMR -3dB, DMR→YSF -4dB, YSF→DMR +10dB
RX/TX thread split:
- CProtocol base class: dual-thread infrastructure with RxTask/TxTask,
condition variable on m_Queue for TX wake-up, backward-compatible
via UsesSplitThreads() virtual. Shutdown joins RX first then TX
with final drain loop
- Split protocols: DCS, M17, DExtra (XRF), DPlus (REF), YSF, NXDN, P25
- Legacy single-thread: XLX interlink, DMR+ (DMRplus), DMR MMDVM
- Per-slot std::mutex on stream cache items protecting shared state
between RX and TX threads
- Prevents transmission tail loss by ensuring TX processing never
blocks inbound packet reception
XLX interlink DMR peer generalisation:
- Rename CBmPeer/CBmClient to CXlxDmrPeer/CXlxDmrClient
- Callsign prefix matching for BM (BrandMeister), FD (FreeDMR),
FS (FreeStar), PH (DVSPH) — all rev 2 AMBE2+ only
D-Star end-of-transmission fixes:
- Strip 0x40 end flag from D-Star PID (& 0x1F) in all five D-Star
protocol parsers — the flag shifted EncodeDvPacket's sequence by 1
- Preserve DCS wire format tail (bytes 58-99) via per-module cache
with m_bHasDcsTail guard for cross-mode sources
- DCS HandleQueue sends EOT reliably via independent TX thread
Thread safety and memory safety hardening:
- CPacketStream: cache owner IP at Open() time, eliminating use-after-
free on m_OwnerClient. NULL pointer under stream lock in CloseStream
- Fix ABBA deadlock in all peer management (DCS, DExtra, DPlus, YSF):
snapshot GateKeeper peer list before acquiring Peers/Clients locks
- Fix lock ordering across all protocols to prevent ABBA deadlocks
between Peers, Clients, and GateKeeper mutexes
- Collect items for removal after iteration instead of modifying
containers while iterating
- CUdpSocket::SendVoice: EAGAIN retry (5 attempts, 1ms backoff)
- AMBEd Phase 4 timeout reverted to counter-only (no packet deletion)
- Remove dead CPacketQueue shadow fields, debug RouterThread logging,
IsStreaming() stub. Make g_bDscpLogged atomic
- Client match in keepalive/header paths uses full CIp::operator==
(IP:port) preventing false matches with shared-IP peers
Security hardening:
- Send-time owner exclusion across all protocols using per-module
client cache with snapshotted owner fields under slot lock
- Echo detection for DCS, DExtra (XRF), and DPlus (REF) to reject
reflected audio from peers
- Reject tracker with progressive backoff for invalid connections,
capped map size to prevent memory exhaustion from spoofed IPs
- Sanitized log output to prevent terminal injection
- std::random_device for DMR authentication seed
- Bounds checks on module indices across all protocol handlers
- Use-after-free fixes in OnDvHeaderPacketIn across all protocols
- ARM-portable unaligned access fixes (memcpy for uint16/uint32)
Performance:
- Per-module client cache for DExtra/DPlus/DCS outbound packets
- DSCP EF marking on voice packet sockets for QoS
- Peer lookup maps built once per drain cycle instead of per-packet
- Skip M17 idle packet encoding when no clients connected
Small watchdog script to check the connectivity to the ambed hardware.
If the usb port gets unresponsive and you get persistent timeouts. The script restarts the ambed service.
You can run the script every "x" via a cronjob.