media: play AMR narrowband and G.711 as well as AMR-WB

imsd-media spoke exactly one codec, AMR-WB. A landline caller reaches the
IMS core through the PSTN gateway, which offers narrowband — AMR (NB)
and/or G.711 — so with the engine now accepting those offers the media leg
has to play them.

CODEC (set by the daemon from the negotiated SDP) selects AMR-WB (the
default, unchanged), AMR, PCMA or PCMU. AMR narrowband rides the same RFC
4867 payload code as AMR-WB with its own frame-size table (RFC 4867 table
1) and libopencore-amrnb dlopen'd like the wideband pair — same package as
the AMR-WB decoder, no new dependency; AMR_MODE defaults to 7 (12.2 kbit/s)
for it. G.711 is the ITU-T table codec, raw samples in the payload, digital
zero as keepalive. The narrowband path runs pw-record/pw-play at 8 kHz and
steps the RTP clock by 160 per frame.

Two test seams so the leg can be driven against a synthetic RTP peer with
no PipeWire and no network: MIC_SRC=<file> feeds raw PCM through the
encoder in real time instead of pw-record, PCM_DUMP=1 writes the decoded
downlink to <out>.pcm. --selftest now covers both AMR tables (both payload
formats) and G.711 (digital zero, idempotence over the full 16-bit range,
1 kHz sine SNR >= 30 dB for both laws).

Verified on the workstation with a Python gateway stand-in for PCMA, PCMU
and AMR (octet-aligned and bandwidth-efficient): uplink RTP shape (pt, seq,
ts step 160, payload sizes 160 / 33 / 32) and a 440 Hz mic tone recovered
from our packets by an independent decoder; a 1 kHz gateway tone recovered
from our decoded downlink. The AMR-WB default path keeps its legacy
keepalive shape (ts step 320, FT0 payloads 19/18 bytes).
This commit is contained in:
Jorijn van der Graaf 2026-09-02 03:18:58 +02:00
commit 649923d36f
2 changed files with 416 additions and 141 deletions

View file

@ -1257,6 +1257,7 @@ private:
setenv("AMR_MODE", EnvOr("AMR_MODE", "2").c_str(), 1);
setenv("DTX", EnvOr("DTX", "0").c_str(), 1);
setenv("OCTET_ALIGN", leg.octetAlign ? "1" : "0", 1);
setenv("CODEC", leg.codec.c_str(), 1);
std::vector<char*> c;
for (auto& s : argv) c.push_back(const_cast<char*>(s.c_str()));
c.push_back(nullptr);