in_progress — so this is always a media-plane problem, not a
connection problem. (If the call never answers at all, see
Call does not connect.)
Work it from the symptom. Which direction is dead tells you which half of the
media path to inspect first.
Which way is dead?
| Symptom | Dead leg | Look here first |
|---|---|---|
| Caller hears the agent; you hear silence | Uplink (caller → you) | NAT / RTP source address — the far end can’t reach the address you advertised. |
| You hear the caller; caller hears silence | Downlink (you → caller) | A dropped AudioBridge handle, or an SDP a=sendonly / early-media direction. |
| Both connect but one leg is noise, not silence | Either | Codec / payload-type mismatch on the negotiated leg. |
call_sid — voice/<sid>/uplink (caller audio) and voice/<sid>/downlink
(audio back to the caller). They fail independently, which is exactly why one
direction can die while the other keeps working. See
Sessions, calls, tracks & streams
for the track model.
Cause 1 — NAT / RTP path (the classic one-way)
The most common cause. The SDP you answered with advertises the address the far end should send RTP to. Behind cloud NAT the engine binds on a private address but must advertise its public one — if the public RTP address is missing or wrong, the carrier streams caller audio into a black hole. Your egress still reaches the address they advertised, so the caller hears you and you hear nothing.| Cause | Fix |
|---|---|
Trunk advertises a private RTP IP in the SDP c= line (private bind leaked into the answer). | Set the trunk’s external RTP IP to the engine’s public address so the SDP carries a reachable c=. The private bind stays internal. |
| Asymmetric RTP: the far end sources media from a different port than it signalled, so latching never happens. | The media plane latches egress to the source of the first inbound RTP packet. If no inbound packet ever arrives (see above), there’s nothing to latch to — fix the advertised address first. |
| A firewall or SBC in front of the trunk drops inbound RTP. | Open the RTP port range to the carrier’s media IPs; confirm the SBC rewrites SDP to its own public media address. |
Cause 2 — SDP direction attribute
An SDP media line carries a direction:sendrecv (both ways), sendonly,
recvonly, or inactive. If early media (the 183 Session Progress) or the
final answer negotiated anything other than sendrecv, one direction is
intentionally muted by the negotiation.
| Cause | Fix |
|---|---|
Far end offered a=sendonly (announcement / hold-style early media) and never renegotiated to sendrecv. | Expected during ringback. If it persists after answer, the caller PBX is holding the leg — check for a stray hold/re-INVITE in the trace. |
Answer went out on the 183 with early media but the 200 OK SDP differs. | The answer sequence must be 100 → 183 with SDP → 200 with the same SDP. A mismatched or SDP-less 200 desyncs the media direction (and can trigger a carrier BYE). |
Call is on hold — a re-INVITE flipped the leg to inactive/sendonly. | Resume the leg; look for a reinvited event in the call lifecycle. |
Cause 3 — Codec / payload-type mismatch
When one leg is dead as silence it’s usually a path problem (Cause 1/2). When it’s dead as noise or static, suspect the codec. The far end is streaming a payload type you didn’t answer with, so frames arrive but decode to garbage. The bridge transcodes automatically across everything it negotiated — µ-law (PCMU, PT 0), A-law (PCMA, PT 8), and Opus — including µ-law ⇄ A-law between two mismatched G.711 legs. One-way noise means the far end is sending a format that was never in the answer, so there’s nothing to transcode it to.| Cause | Fix |
|---|---|
| Carrier streams a payload type absent from your SDP answer. | Offer both PCMU and PCMA so any PSTN carrier can pick one; let the bridge transcode to your chosen AudioCodec. |
| Static PT number reused for a different codec (non-standard carrier). | Pin the codec on the trunk and verify the negotiated rtpmap in the trace matches what the carrier actually sends. |
Cause 4 — the dropped AudioBridge handle
If the caller stops hearing you mid-call — but the SIP dialog is still up and
your onUplink callback is still firing — you almost certainly dropped the
downlink publisher. Retain the bridge for the whole lifetime of the call and
close it explicitly on hangup.
- TypeScript
- Python
In the synchronous cores (Rust, Go) the bridge closes on drop (RAII): letting
the value fall out of scope is what tears both tracks down. Bind it to a variable
that lives as long as the call — e.g.
let bridge = voice.audio_bridge().attach(...)?;
held in your session struct — not a temporary.Diagnose it in order
Confirm the dialog is actually up
Check the call reached
in_progress and is still there. If it dropped, this
is a connection problem, not one-way audio — go to
Call does not connect.Establish which direction is dead
Caller-hears-you-only ⇒ uplink dead ⇒ start at Cause 1 (NAT/RTP path).
Caller-hears-silence ⇒ downlink dead ⇒ start at Cause 4 (bridge handle),
then Cause 2 (SDP direction).
Read the packet trace
In the SIP/RTP trace viewer, look for RTP flow on each leg separately. No
inbound RTP on a leg = the far end can’t reach your advertised address
(Cause 1). Inbound RTP present but audio is static = codec mismatch
(Cause 3). See SIP/RTP debugging.
Inspect the negotiated SDP
Verify the
c= line carries a reachable address and the media line is
sendrecv. A private c= IP or a sendonly/inactive direction is your
answer.Audit your bridge lifetime
If the dead direction is the one your code publishes, confirm you hold the
AudioBridge handle for the whole call and only close() it on hangup.Check MOS if audio is present but poor
One-way audio is binary (present or absent). If audio flows both ways but is
choppy or clipped, that’s a quality problem — see
MOS, jitter & loss.
Human-agent downlink (browser softphones). When you hand a call to a human
agent, the human hears the caller over a media-over-QUIC track in the browser
softphone. That return-audio (downlink) path is still being hardened — the
caller-audio origin publish is being bridged into the relay fan-out. If a
browser human agent reports one-way audio (they can talk but can’t hear the
caller), validate that leg in staging; real SIP deskphone agents are not
affected — their media rides the normal RTP bridge. See
AI ⇄ human handoff.
Related
SIP/RTP debugging
Read per-leg SIP and RTP traces to pinpoint the dead direction.
Codec guide
Pick a codec per leg; where transcoding is free and where mismatches bite.
Call does not connect
When the dialog never reaches in_progress in the first place.
Sessions, calls, tracks & streams
Why uplink and downlink are separate tracks that fail independently.
SIP trunking
Trunk fields for internal vs external RTP IP behind cloud NAT.
AI ⇄ human handoff
The human-agent downlink caveat and how the return-audio path is wired.