User Tools

Site Tools


api:pbx:quality

API : PBX : Call audio quality

Finds calls where somebody could not hear properly, so a customer can be contacted before they contact us. The analysis is computed from the per-leg RTP counters the PBX writes to PbxCDR.rtpStats. Counters are captured when a bridge tears down and, since 02-09-2026, also at the end of a call that never had a bridge - an IVR, an announcement, voicemail - which produces a single caller leg.

API Description
Calls List calls with audio problems, worst first
Summary Aggregated counts grouped by customer, PBX server, party or phone model
Call quality Full per-leg analysis of a single call

Access and scope

Audio quality is internal - TX staff only (changed 27-08-2026). Every endpoint here requires ADMIN, or RESELLER_ADMIN with the TELE employee feature. Customers and white-label resellers have no access at all and get 403 access_denied.

The same rule removes the audio quality badge from CDR and the Lydkvalitet column from its CSV and Excel exports. Customers and resellers were reading these numbers without the context to act on them, and every yellow badge became a support case.

With no access level left to branch on, scope is simply:

Request Sees
customer given That one customer.
customer omitted All customers - the cross-customer worklist.

Verdicts

Every leg, direction and call carries one of five verdicts. severity is their numeric rank and exists for sorting only - it should never be displayed.

Verdict Severity Meaning
NOT_MEASURED 0 Nothing could be judged. Not the same as OK - see below.
OK 1 Every signal that could be read was within normal range.
DEGRADED 2 At least one signal was outside normal range, but the audio was probably usable.
BAD 3 At least one signal was bad enough that somebody had trouble hearing.
NO_AUDIO 4 An answered partys audio never reached us at all. The strongest and most actionable finding.

NOT_MEASURED is not OK. A call comes back unmeasured when it was never answered by the callee, when it predates the rtpStats rollout, when it consists only of Local/ channels (blind transfers and SYSTEM calls have no RTP session), or when every leg carried less than 10 seconds of media. Presenting an unmeasured call as fine is the single most common mistake made with this data.

Note that since 02-09-2026 a system-answered call (an IVR, an announcement, voicemail) does carry rtpStats: the counters exist, this API simply does not grade them, because a call no person answered is not a customer audio problem. Only the call test asks the analyser to measure them.

Findings

Findings are machine-readable codes plus the measured values behind them - never prose, so the caller can phrase them in the users own language. A healthy call has an empty findings array; “outbound was not measured” is carried by that directions own measured/reason fields and is not a finding.

Code Level Meaning
NO_INBOUND_AUDIO Leg That partys audio never reached us at all.
INBOUND_DELIVERY_LOW Leg Measurably less audio arrived from the device than the bridge sent to it.
INBOUND_LOSS_HIGH Leg Packet loss on the way from the device to us.
INBOUND_JITTER_HIGH Leg Uneven arrival timing from the device, which sounds choppy.
INBOUND_MES_LOW Leg Low Media Experience Score for what reached us.
OUTBOUND_LOSS_HIGH Leg The far end reported packet loss on what we sent it.
OUTBOUND_JITTER_HIGH Leg The far end reported uneven timing on what we sent it.
OUTBOUND_MES_LOW Leg The far end reported a low Media Experience Score for what we sent it.
RTT_HIGH Leg Round-trip time high enough to make the conversation awkward.
ALL_LEGS_INBOUND_BAD Call Every measurable party had bad inbound audio - look at the platform or the customers site, one shared cause.
SINGLE_LEG_INBOUND_BAD Call One party had bad inbound audio while another was fine - look at that device, its LAN or its carrier.

Thresholds

Signal OK Degraded Bad
deliveryRatio (inbound packets over this legs own outbound reference) >= 0.95 0.80 - 0.95 < 0.80
Loss ratio < 1 % 1 - 3 % > 3 %
Jitter < 20 ms 20 - 40 ms > 40 ms
MES (0-100) >= 80 70 - 80 < 70
rtt < 150 ms 150 - 300 ms > 300 ms

These are starting points from a 22-leg live sample, not calibrated boundaries. MES in particular has only ever been observed at the healthy end. Calibrate against a week of production before switching on any customer-facing alerting.

How a leg is judged

The bridge is an Asterisk softmix bridge, which writes a frame to every participant on a fixed timer regardless of what anybody contributes - and keeps sending even into a dead address. That makes outbound.packets useless as a quality signal but excellent as a reference: it says what a full-rate stream looks like for this particular leg, whatever its packetisation.

deliveryRatio = inbound.packets / outbound.packets

Healthy legs sit at about 1.0. A phone sending every second frame lands near 0.5. Dead audio is 0. The ratio is independent of packetisation (both 50 pps and 33 pps legs are healthy at ~1.0) and is unaffected by whether the counters were read live or sampled.

Two guards apply: the ratio is only graded on legs with at least 30 seconds of media, and a ratio above 1.1 means the reference itself is unreliable (the leg was not fully bridged for part of its life), in which case loss, jitter and MES decide instead.

Directions

inbound and outbound are named from the PBXs point of view, per leg. For a leg whose party is a customers desk phone, inbound is that phones microphone reaching us and outbound is what we sent to that phones speaker.

outbound.lost, outbound.jitter, outbound.mes and rtt all come from the far ends RTCP receiver reports. Many carriers and SBCs never send RTCP at all, and then all four are 0 - meaning unknown, not perfect. The API gates every one of them on remoteReported and reports the direction as measured: false with reason NO_RTCP_FROM_FAR_END. Never render that as a green tick.

Naming a party

Each leg carries a party object describing who was on it. Identity comes from the CDRs own caller and callee sections, never from the device on the leg.

A legs phoneInfo describes equipment, not a person. For a caller it is only a caller type and a phone model id - for example SIPPHONE/6345463df6774412db14d239 - and for a SIP phone that device can be short-lived: a WebRTC softphone is created fresh on every client connect, so the device behind a leg differs from call to call for the same person. Naming a party after it would give a different “who” every time, and grouping by it would scatter one persons calls across many rows.

The model id is resolved against PbxSipPhoneModels before it is shown. A model that no longer exists is reported as no model at all rather than as a bare id.

Field Comes from
name caller.name or callee.name on the CDR.
number caller.number or callee.number on the CDR.
kind SIP_PHONE, MOBILE, EXTERNAL, EXTENSION, SYSTEM or UNKNOWN - derived from the callers type, or from whether the callee has an extension.
model The phone model, resolved from the legs phoneInfo. This is equipment, kept for the technical panel and the MODEL grouping - it never identifies anybody.
raw The legs raw phoneInfo, for support escalations.

phoneInfo is still used for one identity purpose: telling several callee legs apart on a transfer or a group call, where the single callee section cannot describe three legs. Each such leg then falls back to the extension it rang.

Note that a callee leg identifies the extension that was called, not the individual handset that answered it. An extension ringing three phones produces one leg named by extension.

Because parties are named from the CDR, everything that returns them applies the same privacy rules as CDR first - a suppressed number stays suppressed here too.

Limits worth knowing

  • The counters describe the PBX-to-party hop only. A problem inside the customers LAN, or beyond the carriers edge, shows up as bad inbound quality here but cannot be localised from this data.
  • inbound.lost is cumulative as of the last RTCP report Asterisk generated, so it lags the very end of a call slightly. Use it as a ratio, not an exact count.
  • rtt is frequently 0 because it needs usable RTCP timestamps from the far end, and is occasionally wild (65535 s has been observed). Values above 5 s are treated as unknown rather than as a catastrophic finding.
  • Recurring problems are only visible by aggregating - see Summary.
api/pbx/quality.txt · Last modified: by Per Møller

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki