When a WebRTC call goes bad, the browser already recorded what happened. Chrome keeps a live record of every peer connection at chrome://webrtc-internals, and you can export it to a JSON dump. The problem is that the dump is dense, undocumented, and easy to misread. This is a field guide to reading one by hand: what each section is, what to look at first, and the failure signatures that tell you where the call broke.
It assumes you have a dump already. If you do not: open chrome://webrtc-internals in a new tab, then in another tab start a WebRTC session, reproduce the issue, then go back to the webrtc-internals tab and use Create a dump at the top to download the .json. (In production you usually get these from a user's support ticket. Ask them to capture it while the call is bad - or have it collected automatically in your system, though that's for another time).
The shape of a dump
A dump has two top-level parts:
-
getUserMediacalls. Every microphone and camera request, with the constraints asked for and what the browser returned. This is where you confirm the call even had the right media to begin with -
One block per
RTCPeerConnection. Each block has the connection config (ICE servers), an event log (a timestamped list of signaling and ICE state changes), and stats graphs (everygetStats()metric sampled over the life of the call)
Read them in that order. Most people jump straight to the graphs and miss that the call never negotiated in the first place.
1. The getUserMedia section: did you get the media you asked for?
Each entry shows the requested constraints and the resolved track. Two quick checks:
- Asked for video, got audio only? A constraint failed or a permission was denied. The rest of the call will look "broken" but the root cause is here
- Resolution or frame rate lower than requested? The device could not satisfy the constraint and the browser silently downgraded. Worth knowing before you blame the network
Besides that, knowing the device brand (what microphone and webcam) sometimes sheds additional light that can be highly relevant to your problem at hand.
2. The connection config: is there a TURN server at all?
In the peer connection block, look at the iceServers config. If there is no turn: entry, the call can only connect when both sides have a direct path. On real networks (corporate firewalls, symmetric NAT, mobile), that fails a meaningful fraction of the time. A missing or misconfigured TURN server is one of the most common production failures, and it is invisible unless you look here.
The rest of the data needed to debug and troubleshoot connections will be found in the event log and the candidate-pair table (below).
3. The event log: how far did negotiation get?
The event log is the timeline. Read it top to bottom and watch two state machines:
-
Signaling:
setLocalDescriptionandsetRemoteDescriptionfiring in order, ending instable. If you never reachstable, the offer/answer exchange broke (often an SDP or m-line mismatch). No media will flow, no matter what the network looks like -
ICE connection state: the sequence you want is
newtocheckingtoconnectedtocompleted. The failure signatures:-
Stuck in
checking, thenfailed: connectivity. The two sides could not find a working path. Go to the candidate pairs (next section) -
connectedthendisconnectedthen back: an unstable network mid-call, not a setup problem -
completedcleanly: negotiation is fine. Your problem is media quality, not connectivity. Skip to section 5, but first stop to check what the active candidate pair looks like in the next section
-
Stuck in
4. The selected candidate pair: how did the call connect (or not)?
Filter the stats for candidate-pair and find the nominated/selected pair. The local and remote candidate types tell a story:
-
hosttohost: direct local network. Best case -
srflx(server-reflexive): connected through NAT via STUN. Normal -
relay: the call is going through your TURN server. It works, but it costs you bandwidth and adds latency. If most of your calls arerelay, that is a finding - No selected pair at all: ICE failed. Cross-reference the config (section 2): if there were no relay candidates and the network needed one, that is your answer
Other things you can figure out here are is this a UDP, TCP or TLS relay? Were TURN pairs ever exchanged in the process properly? Things that are important to understand the dynamics of how things got connected (if at all).
On the selected pair, currentRoundTripTime is your real network latency. Anything consistently above ~300ms will be felt as lag.
5. The RTP stats: where the quality actually lives
This is the part most people mean when they say "read the dump." Look at inbound-rtp (media you received) and outbound-rtp (media you sent), per track. The metrics that matter, and what they mean when they go wrong:
-
packetsLost/ packet loss rate. Rising loss on inbound means the network dropped data on the way to you. A few percent is survivable; sustained double digits is not -
jitter. Variation in packet arrival timing. High jitter forces the jitter buffer to grow, which you hear as delay or robotic audio -
framesPerSecond,frameWidth/frameHeight. If these drop mid-call, the video encoder is shedding quality to cope with something. The next metric tells you what -
qualityLimitationReason(on outbound video). This is the single most useful field in the dump for a video call.bandwidthmeans the network could not carry the bitrate, so the encoder dropped resolution or frame rate.cpumeans the machine could not encode fast enough.nonemeans the encoder was unconstrained and the problem is elsewhere -
freezeCount/totalFreezesDuration(inbound video). Directly counts the visible freezes the user experienced. If this climbs, the user saw the call stutter -
nackCount,pliCount,firCount. Retransmission and keyframe requests. Spikes here track loss and decode trouble -
Audio:
concealmentEvents,insertedSamplesForDeceleration. The audio equivalent of freezes. Rising concealment is the network hiding gaps, which the user hears as choppy audio
6. The graphs: read the shape over time
Every metric above is also plotted over the life of the call. The shape is the diagnosis:
-
Bitrate that climbs then collapses, with
qualityLimitationReason: bandwidth: congestion. The call ran out of network and BWE backed off - Packet loss spiking at intervals while RTT stays flat: bursty loss (Wi-Fi interference, a saturated uplink), not pure congestion
- Resolution stair-stepping down while CPU is pinned: an underpowered device, not the network
- Everything flat and healthy but the user still complained: look back at sections 3 and 4. The media was fine; the call may have taken too long to connect, or connected over a slow relay
Putting it together
The method is always the same: confirm the media exists (1-2), confirm the call negotiated and connected (3-4), then read the RTP stats and graphs to separate network problems (loss, jitter, RTT) from media problems (qualityLimitationReason: cpu, resolution drops) from connectivity problems (ICE failed, all-relay). Most "bad call" tickets resolve to one of those three buckets, and the dump tells you which.
Doing this by hand for every ticket gets old fast, which is the honest reason tools exist. If you would rather not parse JSON, rtcStats reads a webrtc-internals dump and surfaces the same findings automatically: it flags the observations above, scores the call's experience, and explains in plain language what went wrong. Upload the dump in the browser and you get the read without the manual pass. Either way, now you know what the tool is looking at, because it is the same six sections.
The open source piece of rtcStats enables you to systematically collect all webrtc-internals-like dumps (we call them rtcstats files) in your WebRTC deployment, so that each and every call is counted, logged and analyzed.
Top comments (0)