SIP Signaling From the Wire Up: Part 5 of 12
SIP handles the signaling. RTP handles the audio. But before any audio can flow, the two endpoints need to agree on a set of parameters: what codec to use, what IP addresses to send audio to, what ports to listen on, and which direction the audio should flow. This negotiation happens through SDP, the Session Description Protocol, which rides inside the body of SIP messages.
If you've been following the call flow examples in this series, you've already seen SDP. It's the block of text at the bottom of an INVITE or a 200 OK that starts with v=0. This post takes that block apart line by line, because SDP is where most media problems become visible. If the audio isn't working, the answer is almost always in the SDP.
Where SDP appears in the call flow
SDP uses an offer/answer model. The caller includes an SDP offer in the INVITE, describing what codecs it supports and where it wants to receive media. The callee includes an SDP answer in the 200 OK, selecting from the offered codecs and providing its own media receiving address.
After both sides have exchanged SDP, each one knows the other's IP address, port, and chosen codec. RTP can start flowing.
SDP can also appear in re-INVITEs (used for hold, transfer, and session changes) and occasionally in 183 Session Progress responses (used for early media, like playing a ringback tone or an IVR prompt before the call is fully answered).
Taking apart an SDP offer
Here's a complete SDP body from an INVITE:
v=0
o=alice 2890844526 2890844526 IN IP4 10.0.0.50
s=Call
c=IN IP4 10.0.0.50
t=0 0
m=audio 49170 RTP/AVP 0 8 18 97
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:18 G729/8000
a=rtpmap:97 opus/48000/2
a=fmtp:18 annexb=no
a=ptime:20
a=sendrecv
Let's go through each line.
v=0
Protocol version. This is always 0. It has been 0 since SDP was created and will likely remain 0 forever. It's there because the specification says there has to be a version field. You can ignore it.
o=alice 2890844526 2890844526 IN IP4 10.0.0.50
The origin line. It identifies who created this session description. The fields are: username (alice), session ID (2890844526), session version (2890844526), network type (IN for internet), address type (IP4), and the originator's IP address.
The session version is the important part here. When SDP is used in a re-INVITE to modify a session, the session version must be incremented. The recipient uses this to determine whether the SDP has actually changed from the previous offer. If you're debugging a hold or transfer issue and the re-INVITE's SDP has the same session version as the original, some endpoints will ignore it because they think nothing changed.
s=Call
The session name. Required by the spec but essentially meaningless in VoIP. It might say "Call," "Phone Call," "SIP Call," or just a dash. Nobody looks at this field for any practical purpose.
c=IN IP4 10.0.0.50
The connection information line. This is one of the most important lines in the entire SDP. It specifies the IP address where the sender wants to receive media. This is the address that the remote end will use as the destination for RTP packets.
If this line contains a private IP address (like 10.x.x.x, 172.16.x.x through 172.31.x.x, or 192.168.x.x) and the remote end is on a different network, the RTP packets will never arrive. This is the most visible symptom of the NAT traversal problem that we'll cover in the next post.
t=0 0
Timing. The two zeros mean the session is not bounded by a specific start and end time. In VoIP, this is always t=0 0. The field exists because SDP was originally designed for multimedia session announcements that might have scheduled start and end times, but phone calls don't work that way.
m=audio 49170 RTP/AVP 0 8 18 97
The media description line. This is the other critically important line. Let's break it down:
audiois the media type (as opposed to video, text, or application)49170is the port number where this endpoint wants to receive RTP packetsRTP/AVPis the transport protocol (RTP using the Audio/Video Profile)0 8 18 97are the payload type numbers for the codecs being offered
Those payload type numbers are defined by IANA for standard codecs and by the sender for dynamic codecs. The standard ones are: 0 = PCMU (G.711 mu-law), 8 = PCMA (G.711 A-law), 18 = G.729. Payload type 97 is dynamically assigned, and you need to look at the a=rtpmap line to find out what it represents.
The order of the payload types matters. It indicates the sender's preference. In this case, Alice prefers PCMU first, then PCMA, then G.729, then whatever codec 97 turns out to be. The answerer is supposed to select the first codec from this list that it can support, though not all implementations follow this convention perfectly.
a=rtpmap lines
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:18 G729/8000
a=rtpmap:97 opus/48000/2
The rtpmap attribute maps payload type numbers to codec names and clock rates. For the standard payload types (0, 8, 18), these lines are technically redundant because the mapping is well known. But including them is good practice because it removes any ambiguity.
The dynamic payload type 97 is mapped to opus/48000/2, which is the Opus codec at 48000 Hz with 2 channels. Without this rtpmap line, the remote end would have no way to know what payload type 97 means.
a=fmtp:18 annexb=no
The fmtp attribute provides format specific parameters. Here, it's saying that for G.729 (payload type 18), Annex B (silence suppression) should not be used. These parameters vary by codec and can affect interoperability if two sides have different expectations.
a=ptime:20
The packetization time in milliseconds. This says each RTP packet should contain 20 milliseconds of audio. This is the most common value for VoIP. Some systems use 30 milliseconds. The value affects both bandwidth usage and how sensitive the call is to packet loss. Shorter ptime means more packets per second (more overhead but each lost packet represents less audio). Longer ptime means fewer packets but each lost packet takes a bigger chunk out of the conversation.
a=sendrecv
The media direction attribute. sendrecv means this endpoint will both send and receive audio. This is the normal state for an active call. The other possible values are:
sendonlymeans the endpoint will send audio but not receive it (used when putting someone on hold and playing hold music)recvonlymeans the endpoint will receive audio but not send itinactivemeans no audio in either direction
When you see the direction change in a re-INVITE's SDP, you're looking at a hold or unhold operation. We'll cover this in detail in post 9.
The SDP answer
When Bob answers the call, his 200 OK includes an SDP answer:
v=0
o=bob 2808844564 2808844564 IN IP4 192.168.1.100
s=Call
c=IN IP4 192.168.1.100
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=ptime:20
a=sendrecv
The answer is simpler than the offer because a lot of the negotiation is resolved. Bob selected PCMU (payload type 0) from Alice's list. He listed only that one codec in his media line. He provided his own IP address (192.168.1.100) and port (3456) for receiving audio.
After this exchange, both sides know everything they need:
- Alice sends RTP to 192.168.1.100 port 3456, encoded as PCMU
- Bob sends RTP to 10.0.0.50 port 49170, encoded as PCMU
When SDP negotiation fails
If the two endpoints can't find a common codec, the call fails. The callee sends a 488 Not Acceptable Here response instead of a 200 OK. In the trace, you'll see the INVITE with an SDP offer listing certain codecs, and then a 488 response indicating that none of those codecs are acceptable.
This happens most often when one side is configured for only G.729 and the other side doesn't have G.729 support, or when one side requires a specific codec that the other side hasn't been configured to offer. The fix is usually a configuration change on one or both endpoints to ensure they have at least one codec in common.
A subtler problem occurs when both sides agree on a codec in SDP but one side can't actually use it correctly. For example, G.729 is a licensed codec, and some implementations have a limited number of G.729 channels. If all channels are in use, the endpoint might negotiate G.729 in SDP but then fail to encode or decode the audio properly. The call connects, but the audio is garbled or silent. The SDP looks fine, which makes this problem harder to diagnose without checking the endpoint's codec licensing or channel capacity.
Codec preference and transcoding
When a call passes through a PBX or SBC that bridges two different networks, the codec negotiation can get more complex. The PBX might negotiate one codec with the internal phone and a different codec with the SIP trunk provider. In this case, the PBX has to transcode, converting the audio from one codec format to another in real time.
Transcoding adds latency, uses CPU resources on the PBX, and can reduce audio quality (each encoding and decoding cycle introduces some degradation). Minimizing transcoding by ensuring that all endpoints and trunks support a common preferred codec is one of the easier optimizations you can make to improve call quality. (For more on how codec choice affects perceived quality, see Understanding MOS Scores.)
If you're looking at a call quality problem on a call that traverses a PBX, check the SDP on both legs of the call. If the inbound leg negotiated Opus and the outbound leg negotiated G.711, the PBX is transcoding, and that could be contributing to quality issues, especially if the PBX is under heavy load. Uploading a packet capture to a PCAP analyzer can extract and display the SDP from both legs side by side, making codec mismatches and addressing issues immediately visible.
Next up: NAT Traversal: Why SIP and NAT Don't Get Along, covering the reason one way audio exists and the collection of ugly hacks the industry built to work around it.
Share
Want to know when we publish new articles? Sign up for updates