engine/sdp: accept AMR-NB and G.711 offers; name the offered codecs on 488
An incoming INVITE whose offer carried no AMR-WB was refused with 488
before ringing. A landline caller arrives through the PSTN gateway, which
offers narrowband — AMR (NB) and/or G.711 — so every landline call was
refused, deterministically. Field report 2026-09-01 (Telia Norge): a
doctor's office called twice, both 488; a mobile caller rang fine.
The parser now recognises PCMA/PCMU by rtpmap name or by static payload
type (a G.711 offer may carry no rtpmap line at all, RFC 3551 §6), matches
encoding names case-insensitively (RFC 4566 §6), and prefers
AMR-WB > AMR > PCMA > PCMU with octet-aligned first within AMR — a mobile
caller offering everything still lands on AMR-WB. The engine accepts any
codec the media leg plays and answers at the offer's own payload type with
the DTMF clock matching the codec (8 kHz for narrowband). A refused offer
now logs what WAS offered ('offered: 18 G729, 101 TELEPHONE-EVENT'), so a
field journal answers 'which codec did the gateway want' without a raw SIP
dump.
The media leg gains the matching codecs in the next commit.
This commit is contained in:
parent
4558f2afd8
commit
daaba2d657
4 changed files with 295 additions and 57 deletions
|
|
@ -52,7 +52,8 @@ export namespace imsd::engine {
|
|||
return d == Direction::Incoming ? "incoming" : "outgoing";
|
||||
}
|
||||
|
||||
// Parsed SDP answer needed to bring up the media leg.
|
||||
// Parsed SDP answer needed to bring up the media leg. `codec` is one of
|
||||
// imsd-media's: "AMR-WB", "AMR", "PCMA", "PCMU" (empty = none we play).
|
||||
struct MediaLeg {
|
||||
std::string remoteIp;
|
||||
int remotePort = 0;
|
||||
|
|
@ -156,16 +157,20 @@ export namespace imsd::engine {
|
|||
|
||||
// UAS: answer the inbound INVITE with 100 Trying + 180 Ringing (made
|
||||
// reliable only when the caller Requires 100rel) and arm the ring
|
||||
// timeout. An offer we cannot serve — no audio line, or no AMR-WB
|
||||
// (the media plane is AMR-WB-only) — is refused with 488 up front,
|
||||
// before the UI ever rings.
|
||||
// timeout. An offer we cannot serve — no audio line, or none of the
|
||||
// codecs imsd-media plays (AMR-WB, AMR, PCMA, PCMU) — is refused
|
||||
// with 488 up front, before the UI ever rings; the log line names
|
||||
// what WAS offered, so a field journal answers "which codec did the
|
||||
// gateway want" without a raw SIP dump.
|
||||
std::vector<Action> OnInvite() {
|
||||
std::vector<Action> a;
|
||||
if (direction_ != Direction::Incoming || state_ != CallState::Incoming) return a;
|
||||
MediaLeg leg = ParseAnswer();
|
||||
if (!leg.Valid() || leg.codec != "AMR-WB") {
|
||||
if (!leg.Valid() || leg.codec.empty()) {
|
||||
a.push_back(Respond(imsd::msg::BuildInviteResponse(ctx_, invite_, 488, "Not Acceptable Here", d_.itag)));
|
||||
Append(a, Finish("error", "incoming offer unusable (need AMR-WB audio); 488 sent"));
|
||||
std::string why = !leg.Valid() ? "no usable audio line"
|
||||
: std::format("no playable codec, offered: {}", imsd::sdp::OfferedCodecs(answerSdp_));
|
||||
Append(a, Finish("error", std::format("incoming offer unusable ({}); 488 sent", why)));
|
||||
return a;
|
||||
}
|
||||
a.push_back(Respond(imsd::msg::BuildInviteResponse(ctx_, invite_, 100, "Trying", d_.itag)));
|
||||
|
|
@ -196,7 +201,7 @@ export namespace imsd::engine {
|
|||
spec.payloadType = leg.payloadType;
|
||||
spec.octetAlign = leg.octetAlign;
|
||||
spec.codec = leg.codec;
|
||||
spec.dtmfPt = imsd::sdp::TelephoneEventPt(Body(invite_), 16000);
|
||||
spec.dtmfPt = imsd::sdp::TelephoneEventPt(Body(invite_), imsd::sdp::CodecRate(leg.codec));
|
||||
localSdp_ = imsd::sdp::BuildAnswer(spec);
|
||||
// session timers: echo the caller's Session-Expires; with no
|
||||
// refresher stated, make the caller (uac) the refresher — its
|
||||
|
|
|
|||
Loading…
Reference in a new issue