Commit graph imsd/implementations/media.cpp
Author SHA1 Message Date
29d624fc55
media: RFC 4733 telephone-event uplink, the tones from dtmf <tones> lines on stdin
The keypad during a call (a voicemail menu, an IVR) needs DTMF, and on an
IMS network it travels as RFC 4733 telephone-event packets on the call's
RTP stream. imsd-media reads `dtmf <tones>` lines on its stdin: each
queues the tones (0-9, *, #, A-D), and a tone takes seven 20 ms slots in
place of the audio frames — the event's five (100 ms at -10 dBm0: the
marker bit on the first packet, the duration growing a slot at a time,
the end bit on the fifth packet) and two in which the end packet goes out
again (§2.5.1.4) — then three slots of audio before the next tone. Events
and audio share the stream's sequence and timestamp space (§2.1), and one
sender keeps it: the mic thread hands each captured frame to the DTMF
state, which under one lock sends either the event packet the slot
carries or the frame (the main loop's silence tick does the same without
a mic, and so does the latch burst), so one packet leaves per slot, the
clock stays the capture's, and no frame can land inside an event
whichever thread holds the slot. The payload type comes from DTMF_PT in
the environment; unset means no DTMF and a dtmf line is dropped with a
message; a line of more than 4096 bytes is dropped through its end and
the next line goes. Nothing in imsd supplies the lines or the variable at
this commit; a hand run or the loopback does here, and a hand run behind
a job-control terminal is not stopped by pending input (the reader blocks
SIGTTIN, its read fails and it ends). pw-record no longer inherits stdin.
The stats sidecar gains tx_dtmf and dtmf_pt.

Tested: --selftest, and the fp6 journal's utilities/media_loopback.py
--dtmf arm on a host build of this commit's tree
(journal/ims/captures/2026-10-15-dtmf-loopback-c1-tx.txt; the plain, the
two overflow, the no-mic and the early-tones runs beside it) and its
ttyin.py pty test of the background hand run
(journal/ims/captures/2026-10-16-dtmf-ttyin-hand-run/); the dev phone's
dtmf benches ran in builds of this series.
2026-10-16 22:25:48 +02:00
0a91050635
imsd, media: aim call audio at logind's seat0 session, not an account named user
imsd-media defaulted AUDIO_USER to `user`, postmarketOS's default
account name, and with no such account a call had no audio on the phone
and noise at the far end, the message only in imsd-media's stderr, at
each call. A field user found it by trial (alyx report 14, 2026-09-20;
ims ledger F14).

Have imsd, as packaged (root, on the system bus), resolve the session
from logind when a call's media starts — the user session active on
seat0 (a greeter is not one) when it has a PipeWire socket — and hand it
to imsd-media as AUDIO_USER; set in imsd's own environment to an
account's name, AUDIO_USER pins that account instead. imsd as packaged
(root, on the system bus) logs the resolution at start, before the first
call, and when a call's media starts.

No session is an empty AUDIO_USER. imsd-media started without the
variable at all keeps `user`: that is an imsd older than 0.3.10, still
running while an upgrade replaces the helper under it, whose calls would
otherwise be silent until the restart, or a hand run.

Tested on the dev phone (KPN) in builds of this series; not every path
has run there.
2026-10-06 14:01:33 +02:00
b8d439246a media: resolve the audio user once, thread-safely
SpawnPw() looked the desktop user up with getpwnam() — one static buffer —
from the mic thread and the playout thread at the same instant. When the
race made both lookups fail, uid stayed 0 and both pw helpers were started
with XDG_RUNTIME_DIR=/run/user/0, where no PipeWire session lives: pw-record
hit EOF at once, pw-play never connected, and a call that was up with RTP
flowing both ways was answered into static (tx_mic=0 rx_played=0 qdrop=883,
dev phone 2026-09-08 20:01, first time in years of calls — timing).

AudioUid() resolves AUDIO_USER once under std::call_once with getpwnam_r and
a private buffer, complains when the user does not exist, and logs the
session the helpers are aimed at on every call, so a field journal answers
"where did the audio go" directly.
2026-09-08 20:06:58 +02:00
649923d36f 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).
2026-09-02 03:18:58 +02:00
6e77823933 imsd 0.2.7 — initial public snapshot
Userspace VoLTE/IMS daemon for mainline Linux phones (developed on the
Fairphone 6): a GLib-free core (SIP, SDP, USIM AKA, IPsec SA setup, RTP
media, call engine) behind a GDBus control daemon, a standalone AMR-WB
media leg, and a Plasma Dialer backend. C++26 modules built with Crafter
Build; the tree is clean under the project's house-style linter
(crafter-build lint) across all three build products.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
2026-07-22 23:04:21 +02:00