The ringback in the browser sounded stretched, with long gaps between bits of audio. As soon as the customer answered, it sounded normal. The customer-leg recording was clean throughout.
That last detail sent us in the wrong direction at first. If the recording was fine, surely the media was fine too? But the recording and the browser were listening at different points in the call. The recording never saw the broken part.
Here's how we tracked it down, including the packet measurements before and after the fix.
The failure stopped at answer
This was a browser-based outbound dialer with FreeSWITCH between the browser and the carrier:
EXAMPLE
carrier <-> FreeSWITCH <-> WebRTC browser
|
+-> customer-leg recording
The useful description of the bug was quite specific:
EXAMPLE
direction: customer -> browser
phase: early media only
browser: broken
recording: clean
answered call: clean
repeatability: consistent
SIP signalling succeeded. The problem affected ringback and pre-answer announcements, then disappeared at answer. That gave us a reason to compare the early-media bridge with the answered bridge.
AVMD was an easy suspect because it had recently been added to the system. The audio defect predated it, though, so we left AVMD alone.
Capture both media legs for the same call
I used browser statistics, a SIP trace, packet captures near FreeSWITCH and the server's channel logs. Each answered a different question.
In chrome://webrtc-internals, I looked at received and lost packets, jitter, concealed samples and jitter-buffer delay. These show what reached the browser and how much work it did to compensate for missing or irregular audio.
The SIP trace established the phase boundaries: 183 Session Progress with SDP, the early-media codec, 200 OK, and any change to the media address or codec at answer.
For packet timing, capturing both sides near FreeSWITCH let us compare them on the same clock. For RTP streams that Wireshark can identify, this is a useful starting point:
EXAMPLE
tshark -r call.pcapng -q -z rtp,streams
The browser payload was encrypted, but we could still compare packet arrival times, addresses, ports and sizes. We tied those streams to the agent and customer channel UUIDs in the FreeSWITCH logs. With several calls running, it's very easy to compare the right symptom with the wrong stream.
Twenty milliseconds in, seventy-five out
The carrier/customer leg looked healthy:
EXAMPLE
723 packets over 14.4666 s
0% packet loss
mean delta 20.037 ms
mean jitter 0.342 ms
Those were 20 ms PCMU packets arriving at roughly the expected cadence. Toward the browser, the same early-media interval looked like this:
EXAMPLE
191 packets over 14.3426 s
mean delta 75.487 ms
most gaps between 70 and 130 ms
FreeSWITCH was receiving regularly spaced RTP and sending audio toward the browser much too slowly. The clean recording now made sense: the audio had arrived correctly at the server, before the faulty output path.
In the logs for the internal WebRTC agent leg, we found:
EXAMPLE
Not using a timer
The early-media path needed an RTP timer
The call used bridge_early_media=true so the agent could hear ringback and announcements before answer. In this path, FreeSWITCH buffers early media and emits it from the A-leg read loop. Without an RTP timer on the browser leg, sparse reads slowed the outgoing packet cadence.
After answer, the normal bridge used a different timing path. That explained why an answered conversation sounded fine on the same call, with the same browser and carrier.
We set the timer in the internal WebRTC Sofia profile:
EXAMPLE
<param name="rtp-timer-name" value="soft"/>
After rebuilding the runtime configuration, the log showed:
EXAMPLE
Starting timer [soft] 160 bytes per 20ms
The next browser-bound capture showed:
EXAMPLE
290 packets over 5.7809 s
mean delta 20.003 ms
no gaps >= 70 ms
The ringback sounded normal as well. We had both an audible improvement and a measurement of the mechanism that changed. This was a fix for this particular bridge path; I wouldn't apply the parameter to an unrelated audio incident without checking its timing first.
What I check on the next audio bug
I start by writing down who can't hear whom and when: ringing, answered conversation, transfer, or AI playback. Then I collect the application call ID, SIP Call-ID, both channel UUIDs and the browser peer connection.
Before interpreting packet statistics, I also check that the expected legs actually exist. In another dialer issue, customer signalling succeeded but no browser media session had been created. There was nothing for a browser setting to repair.
Once the topology is confirmed, I compare media entering and leaving the server. Bad input sends the investigation toward the carrier or network. Clean input and irregular output send it toward server processing and pacing. If the server output looks healthy, browser loss, concealment and playback statistics become the next useful evidence.
I change one thing and repeat the same scenario. Reordering codecs, increasing jitter buffers and changing browser constraints in one deployment can make a call sound better without explaining which change mattered.
For this incident, the decisive evidence was the gap between 20 ms packets entering FreeSWITCH and 75 ms packets leaving it. The recording helped locate that gap once we stopped treating it as a recording of the entire call path.