sdp: offer AMR narrowband after AMR-WB

Our offer listed AMR-WB alone. A call we place TO a landline then depends
on the far side transcoding up to wideband; a gateway that will not is
entitled to refuse. AMR narrowband is the 3GPP-mandatory codec every IMS
core and PSTN gateway can answer, so it goes second, with telephone-event
at the 8 kHz clock beside the existing 16 kHz one. AMR-WB stays first, so
a mobile-to-mobile call negotiates exactly what it did before; if the
network does answer AMR, the engine already starts the media leg on the
answered codec.

Byte pins in tests/Sdp and tests/Messages re-pinned (INVITE body 323 ->
466 bytes).

NOTE FOR RELEASE: this changes the MO INVITE on the path the arranged 112
calls used. One bench MO call on KPN before it ships.
This commit is contained in:
Jorijn van der Graaf 2026-09-02 03:21:06 +02:00
commit d5aaf57e49
3 changed files with 29 additions and 9 deletions

View file

@ -5,9 +5,9 @@
/*
Imsd:Sdp — SDP offer/answer building + parsing for the voice media plane.
The offer is a fixed AMR-WB (octet-aligned) + telephone-event shape with an
optional GSMA IR.92 QoS-precondition block; it is what commercial IMS cores
expect from a VoLTE UE, and its exact bytes are pinned by tests/Sdp. The
The offer is a fixed AMR-WB (octet-aligned) + AMR + telephone-event shape
with an optional GSMA IR.92 QoS-precondition block; it is what commercial IMS
cores expect from a VoLTE UE, and its exact bytes are pinned by tests/Sdp. The
parser extracts only what the media plane needs: remote address/port, the
negotiated payload type, the codec and octet-align mode — it reads answers
to our offers and inbound offers alike (same fields either way). The codecs
@ -57,7 +57,11 @@ export namespace imsd::sdp {
std::string currRemote = "none"; // a=curr:qos remote <this>
};
// AMR-WB (octet-aligned) offer. Byte layout is pinned by tests/Sdp.
// Our offer: AMR-WB (octet-aligned) first, AMR narrowband second — the
// 3GPP-mandatory codec every IMS core and PSTN gateway can answer, so a
// call we place TO a landline no longer depends on the far side
// transcoding up to wideband — each with telephone-event at its own
// clock. Byte layout is pinned by tests/Sdp.
std::string BuildOffer(const Offer& o) {
std::string ipver = Is6(o.local) ? "IP6" : "IP4";
std::string s;
@ -66,14 +70,18 @@ export namespace imsd::sdp {
s += "s=-\r\n";
s += std::format("c=IN {} {}\r\n", ipver, o.local);
s += "t=0 0\r\n";
s += std::format("m=audio {} RTP/AVP 97 98\r\n", o.rtpPort);
s += std::format("m=audio {} RTP/AVP 97 99 98 100\r\n", o.rtpPort);
s += "b=AS:41\r\n";
s += "b=RS:512\r\n";
s += "b=RR:1536\r\n";
s += "a=rtpmap:97 AMR-WB/16000/1\r\n";
s += "a=fmtp:97 octet-align=1;mode-change-capability=2;max-red=0\r\n";
s += "a=rtpmap:99 AMR/8000/1\r\n";
s += "a=fmtp:99 octet-align=1;mode-change-capability=2;max-red=0\r\n";
s += "a=rtpmap:98 telephone-event/16000\r\n";
s += "a=fmtp:98 0-15\r\n";
s += "a=rtpmap:100 telephone-event/8000\r\n";
s += "a=fmtp:100 0-15\r\n";
s += "a=ptime:20\r\n";
s += "a=maxptime:240\r\n";
if (o.precond) {

View file

@ -96,14 +96,18 @@ namespace {
"s=-\r\n"
"c=IN IP6 2001:db8::db43\r\n"
"t=0 0\r\n"
"m=audio 50004 RTP/AVP 97 98\r\n"
"m=audio 50004 RTP/AVP 97 99 98 100\r\n"
"b=AS:41\r\n"
"b=RS:512\r\n"
"b=RR:1536\r\n"
"a=rtpmap:97 AMR-WB/16000/1\r\n"
"a=fmtp:97 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:99 AMR/8000/1\r\n"
"a=fmtp:99 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:98 telephone-event/16000\r\n"
"a=fmtp:98 0-15\r\n"
"a=rtpmap:100 telephone-event/8000\r\n"
"a=fmtp:100 0-15\r\n"
"a=ptime:20\r\n"
"a=maxptime:240\r\n"
"a=sendrecv\r\n";
@ -129,21 +133,25 @@ namespace {
"Session-Expires: 1800;refresher=uac\r\n"
"Min-SE: 90\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: 323\r\n"
"Content-Length: 466\r\n"
"\r\n"
"v=0\r\n"
"o=- 7777777 7777777 IN IP6 2001:db8::db43\r\n"
"s=-\r\n"
"c=IN IP6 2001:db8::db43\r\n"
"t=0 0\r\n"
"m=audio 50004 RTP/AVP 97 98\r\n"
"m=audio 50004 RTP/AVP 97 99 98 100\r\n"
"b=AS:41\r\n"
"b=RS:512\r\n"
"b=RR:1536\r\n"
"a=rtpmap:97 AMR-WB/16000/1\r\n"
"a=fmtp:97 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:99 AMR/8000/1\r\n"
"a=fmtp:99 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:98 telephone-event/16000\r\n"
"a=fmtp:98 0-15\r\n"
"a=rtpmap:100 telephone-event/8000\r\n"
"a=fmtp:100 0-15\r\n"
"a=ptime:20\r\n"
"a=maxptime:240\r\n"
"a=sendrecv\r\n";

View file

@ -37,14 +37,18 @@ int main() {
"s=-\r\n"
"c=IN IP6 2001:db8:29e9:a05f::1\r\n"
"t=0 0\r\n"
"m=audio 22222 RTP/AVP 97 98\r\n"
"m=audio 22222 RTP/AVP 97 99 98 100\r\n"
"b=AS:41\r\n"
"b=RS:512\r\n"
"b=RR:1536\r\n"
"a=rtpmap:97 AMR-WB/16000/1\r\n"
"a=fmtp:97 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:99 AMR/8000/1\r\n"
"a=fmtp:99 octet-align=1;mode-change-capability=2;max-red=0\r\n"
"a=rtpmap:98 telephone-event/16000\r\n"
"a=fmtp:98 0-15\r\n"
"a=rtpmap:100 telephone-event/8000\r\n"
"a=fmtp:100 0-15\r\n"
"a=ptime:20\r\n"
"a=maxptime:240\r\n"
"a=curr:qos local sendrecv\r\n"