G.711 is the codec your voice agent hears on most phone calls. It samples audio 8,000 times a second, stores each sample in 8 bits using one of two logarithmic curves, μ-law or A-law, and sends the result at 64 kbit/s. The ITU first published it as Recommendation G.711 in 1972, and it is still the default on the public phone network, on most SIP trunks, and in Twilio’s media streams.
If you build anything that touches phone audio, you will handle G.711 bytes sooner or later. This guide covers how the codec works, how to tell μ-law from A-law, how to decode and encode it in a few lines of code, the bugs that turn a caller into static, and how to get 8 kHz audio in and out of speech models without extra conversions. It’s written for engineers wiring a phone line to speech recognition, an LLM, and text to speech.
G.711 at a glance
| Property | Value |
|---|---|
| Sample rate | 8,000 Hz |
| Bits/sample | 8 (logarithmic, not linear) |
| Bit rate | 64 kbit/s per direction |
| Audio band | About 300 to 3,400 Hz |
| Variants | μ-law (PCMU) and A-law (PCMA) |
| RTP payload | PCMU = 0, PCMA = 8, both with an 8,000 Hz clock |
| 20 ms packet | 160 samples, 160 bytes of payload |
| Codec delay | One sample (0.125 ms); no look-ahead |
| Silence bytes | μ-law 0xFF, A-law 0xD5 |
The 20 ms packet size comes from RFC 3551, which sets 20 ms as the default packetization interval for PCMU and PCMA. Most carriers and PBXs use it, so 160 bytes per packet is the number you’ll see in packet captures.
How μ-law and A-law work
A 16-bit linear sample can describe 65,536 levels. Speech doesn’t need that many evenly spaced steps. What it needs is fine detail at low volume, where most of the information in a voice sits, and coarse steps at high volume, where your ear can’t hear small errors anyway.
G.711 does exactly that. It maps each sample onto a logarithmic curve, a process called companding (compressing, then expanding on decode). Each 8-bit code has three parts: one sign bit, three bits that pick one of eight segments, and four bits that pick a step inside the segment. Every segment covers twice the range of the one below it, so step size doubles as the signal gets louder.
The two variants differ in where they put the steps:
- μ-law takes a 14-bit linear sample and is used in North America and Japan. It’s also the only format Twilio Media Streams send and accept.
- A-law takes a 13-bit linear sample and is used in Europe, India, and most other countries. It gives slightly more resolution to quiet signals.
Both store a 13- or 14-bit dynamic range in 8 bits, which is why G.711 sounds like a normal phone call instead of a walkie-talkie. When a call crosses from a μ-law country to an A-law one, the network converts it, and the standard says the link between them carries A-law.
How to tell which one you have
You usually can’t tell from the bytes alone. Both are a stream of unsigned 8-bit values with no header. Look at the metadata around them instead:
- SDP.
a=rtpmap:0 PCMU/8000is μ-law.a=rtpmap:8 PCMA/8000is A-law. - Twilio Media Streams. The
startmessage always says"encoding": "audio/x-mulaw"and"sampleRate": 8000. Twilio’s WebSocket message docs are explicit that this never changes. - WAV files. The
fmtchunk’s format tag is 7 for μ-law and 6 for A-law.ffprobereportspcm_mulaworpcm_alaw. - Silence. On a quiet line, μ-law bytes sit near
0xFFand0x7E, and A-law bytes alternate between0xD5and0x55. A buffer full of0xFFis very likely μ-law silence, not a corrupt stream.
If you guess wrong, you’ll hear it. When we encoded a 440 Hz tone as μ-law and decoded it as A-law, the output kept only about half its correlation with the original at every volume we tried, and its loudness swung between half and double depending on the input level. Wrong-law audio is intelligible enough to fool you into thinking the problem is elsewhere, and harsh enough to wreck speech recognition.
Decode and encode G.711 in code
The quickest route is FFmpeg, which reads and writes both laws as raw streams:
# Raw μ-law from a phone stream to a 16-bit WAV you can listen to
ffmpeg -f mulaw -ar 8000 -ac 1 -i call.ulaw call.wav
# Any audio file to raw 8 kHz μ-law, ready to send to Twilio
ffmpeg -i prompt.wav -ar 8000 -ac 1 -f mulaw prompt.ulaw
# A-law: swap the format name
ffmpeg -f alaw -ar 8000 -ac 1 -i call.alaw call.wav
In Python, the standard library used to do this with audioop, but audioop was removed in Python 3.13. The audioop-lts package restores it. If you’d rather not depend on it, μ-law fits in a few lines of NumPy:
import numpy as np
def linear_to_mulaw(pcm: np.ndarray) -> bytes:
"""16-bit signed PCM samples -> G.711 mu-law bytes."""
x = pcm.astype(np.int32) >> 2 # 14-bit magnitude range
mask = np.where(x < 0, 0x7F, 0xFF)
x = np.minimum(np.abs(x), 8158) + 33 # clip, then add the bias
segment = np.floor(np.log2(x)).astype(np.int32) - 5
code = (segment << 4) | ((x >> (segment + 1)) & 0x0F)
return (code ^ mask).astype(np.uint8).tobytes()
def mulaw_to_linear(data: bytes) -> np.ndarray:
"""G.711 mu-law bytes -> 16-bit signed PCM samples."""
u = ~np.frombuffer(data, dtype=np.uint8).astype(np.int32) & 0xFF
segment, mantissa = (u >> 4) & 0x07, u & 0x0F
magnitude = (((mantissa << 3) + 0x84) << segment) - 0x84
return np.where(u & 0x80, -magnitude, magnitude).astype(np.int16)
We checked both functions against audioop on every 16-bit input value and all 256 μ-law codes, and they match exactly. Encoders don’t always agree with each other at segment boundaries (FFmpeg and audioop differ on 512 of the 65,536 inputs), and that’s fine: the differences are one step on the curve and inaudible.
Bugs that turn callers into static
Most G.711 problems are a mismatch between what the bytes are and what the code thinks they are.
- The wrong law. μ-law decoded as A-law, or the reverse. The audio is loud, crunchy, and still half intelligible. Check the SDP or the stream’s
startmessage, not your assumptions about the carrier. - Treating G.711 as 8-bit linear PCM. A library that sees 8-bit samples and assumes they’re linear will play noise with speech buried in it. Decode to 16-bit linear PCM before you resample, mix, or run voice activity detection.
- A file header in the stream. Twilio says the outbound
media.payloadmust not contain audio file header bytes. Send a WAV’s 44-byte header and you get a click or a burst of noise at the start of every prompt. Strip it, or ask your speech model for raw output. - A mislabeled sample rate. 8 kHz audio labeled as 16 kHz plays twice as fast and an octave high. The reverse sounds like a slowed-down tape. Both stay perfectly decodable, which is why these bugs survive code review.
- Resampling more than once. A pipeline that takes 8 kHz μ-law, converts to 16 kHz for one service, to 24 kHz for another, then back to 8 kHz μ-law adds latency and filtering artifacts at every step. Pick formats so each audio path converts once, or not at all.
- Upsampling to “improve” quality. Converting 8 kHz audio to 16 kHz doesn’t recover anything above 4 kHz. The extra samples are interpolated. It can help a model that only accepts 16 kHz, but it won’t make the caller’s “f” sound less like an “s”.
What 8 kHz means for voice agents
The 300 to 3,400 Hz band is the part of a voice most people need to understand words, which is why phone calls work at all. It is also missing much of what separates “f” from “s”, “m” from “n”, and “fifteen” from “fifty”. A speech-to-text model that’s only been tested on clean 16 kHz browser audio can do noticeably worse on the same words over a phone line. The SIP trunking guide covers this from the carrier side.
Three habits help:
- Evaluate on phone audio. Record test calls through your actual carrier, or at least run your test set through an 8 kHz μ-law round trip, before you compare models. The WER guide explains how to read the results without fooling yourself.
- Confirm the important bits. Spell back names, emails, and account numbers, and offer the keypad for long digit strings. DTMF keypresses don’t depend on the codec getting a consonant right.
- Send speech in the format the line wants. Text to speech generated at 44.1 kHz and squeezed down to 8 kHz μ-law costs a conversion on every chunk. If the model can produce μ-law at 8 kHz directly, ask it to.
Some calls carry wideband audio. G.722 (“HD voice”) samples at 16 kHz, and WebRTC calls usually run Opus. Once a call touches the public phone network, though, plan for G.711.
G.711 with Cartesia
Cartesia’s speech models accept and produce G.711 natively, so a phone pipeline doesn’t need its own codec step.
Text to speech. Sonic returns raw μ-law or A-law at 8 kHz when you ask for it. Set the output format on any TTS request:
{
"container": "raw",
"encoding": "pcm_mulaw",
"sample_rate": 8000
}
Use pcm_alaw for A-law lines. The output format docs list every encoding and rate. Raw chunks from the TTS WebSocket arrive base64-encoded, which is the same shape Twilio expects in media.payload, so the Twilio integration guide forwards them without decoding.
Speech to text. Ink takes μ-law and A-law over the streaming API, including the turn-detection endpoint built for voice agents. Declare the format when you connect:
wss://api.cartesia.ai/stt/turns/websocket?model=ink-2&encoding=pcm_mulaw&sample_rate=8000&cartesia_version=2026-08-14
The server decodes your bytes using exactly what you declare, and it might not return an error if the declaration is wrong. The audio input docs show how to save a stream and check it with ffplay, which catches a wrong law or rate in seconds.
Managed Agents. Managed Agents answer calls on Cartesia numbers, Twilio, or your own SIP trunk and handle the call audio for you. If you bridge your own telephony into an agent instead, the agent WebSocket accepts mulaw_8000 input directly. Agent calls cost $0.06 per minute, and you can build one in the playground and call it from your phone.
If all you need is to convert a few recordings, FFmpeg already does that well, and you don’t need a speech API at all.