G.711 (mu-law / A-law) — codec reference
- Fetched 2026-10-07. No tool to execute code was available in the research session, so every "DERIVED" value below was hand-computed from the fetched C code. Re-check them with a unit test against the quoted algorithm.
- Primary algorithm source (Sun Microsystems g711.c, as shipped in RAT): https://sources.debian.org/data/main/r/rat/4.2.20-3/codec_g711.c
- Other copies of the same Sun file (seen, only partially extracted): https://sources.debian.org/src/sox/12.16-6/g711.c , https://gnuradio.org/cgit/gnuradio.git/plain/gr-vocoder/lib/g7xx/g711.c , https://www.askapache.com/s/s.askapache.net/wireshark-1.1.3/g711.c
- ITU-T G.191 STL: https://raw.githubusercontent.com/openitu/STL/dev/src/g711/g711.c and
g711.h(same dir) - Format background: https://en.wikipedia.org/wiki/G.711 , https://en.wikipedia.org/wiki/%CE%9C-law_algorithm
- Twilio: https://www.twilio.com/docs/voice/media-streams/websocket-messages ; Telnyx: https://developers.telnyx.com/docs/voice/programmable-voice/media-streaming
License
Sun g711.c header (quoted from the RAT copy): "This source code is a product of Sun Microsystems, Inc. and is provided
for unrestricted use. Users may copy or modify this source code without charge." (AS IS, no warranty.) Safe to port.
The ITU STL licence text was NOT fetched: UNVERIFIED. No STL code is copied: brisvox implements the
standard's behaviour independently (see "Decision" below) and credits the Sun code in codec.rs.
Sun reference algorithm (RAT copy, 16-bit short in/out, arithmetic >>)
#define SIGN_BIT (0x80) #define QUANT_MASK (0xf) #define SEG_SHIFT (4) #define SEG_MASK (0x70)
#define BIAS (0x84) #define CLIP 8159
seg_aend[8] = {0x1F,0x3F,0x7F,0xFF,0x1FF,0x3FF,0x7FF,0xFFF};
seg_uend[8] = {0x3F,0x7F,0xFF,0x1FF,0x3FF,0x7FF,0xFFF,0x1FFF};
search(val, table, size): first i with val <= table[i]; else return size.
linear2ulaw(short pcm_val): // output already complemented
pcm_val = pcm_val >> 2; // 16 -> 14 bit, arithmetic shift (floor)
if (pcm_val < 0) { pcm_val = -pcm_val; mask = 0x7F; } else mask = 0xFF;
if (pcm_val > CLIP) pcm_val = CLIP;
pcm_val += (BIAS >> 2); // +33
seg = search(pcm_val, seg_uend, 8);
if (seg >= 8) return 0x7F ^ mask; // out of range -> max code
uval = (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); return uval ^ mask;
ulaw2linear(uchar u_val): // returns 16-bit scale directly
u_val = ~u_val;
t = ((u_val & QUANT_MASK) << 3) + BIAS;
t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT;
return (u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS);
linear2alaw(short pcm_val):
pcm_val = pcm_val >> 3; // 16 -> 13 bit
if (pcm_val >= 0) mask = 0xD5; else { mask = 0x55; pcm_val = -pcm_val - 1; }
seg = search(pcm_val, seg_aend, 8);
if (seg >= 8) return 0x7F ^ mask;
aval = seg << SEG_SHIFT;
aval |= (seg < 2) ? ((pcm_val >> 1) & QUANT_MASK) : ((pcm_val >> seg) & QUANT_MASK);
return aval ^ mask;
alaw2linear(uchar a_val):
a_val ^= 0x55;
t = (a_val & QUANT_MASK) << 4; seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT;
seg==0: t += 8; seg==1: t += 0x108; else: t += 0x108; t <<= seg - 1;
return (a_val & SIGN_BIT) ? t : -t;
Notes: the RAT file comment says the u-law biasing "shifts the encoding range from (0 - 8158) to (33 - 8191)".
The sox 12.16 copy of g711.c is an older variant (single seg_end[8] = {0xFF,0x1FF,...,0x7FFF} table); CPython
audioop.c (v3.12.0) defines CLIP 32635 and BIAS 0x84 (a 16-bit-domain form). Do not mix variants; whether
the CPython form matches the 14-bit one was NOT checked: UNVERIFIED.
STL (ITU G.191) algorithm — differences
STL ulaw_compress: absno = (x<0 ? (~x)>>2 : x>>2) + 33; if (absno > 0x1FFF) absno = 0x1FFF; then segment by
counting bits of absno>>6, low = 0xF - ((absno>>segno)&0xF), sign bit 0x80 set when x >= 0. Header comments say
"only 12 MSBits are taken into account" (A-law) and 1's complement for negatives. STL alaw_compress:
ix = (x<0 ? ~x : x) >> 4, loop-normalise, ^ 0x55, sign bit 0x80 when x >= 0. Decoders alaw_expand/ulaw_expand
produce the same 16-bit scale (peak 32256 / 32124 by hand calc).
- Negative input in mu-law: STL uses ones' complement (
~x), Sun uses two's-complement negate after the shift. DERIVED: Sun magnitude = ceil(|x|/4), STL = ceil(|x|/4) - 1, so for negative x the encoders can differ by one quantization step. Example: x = -1 -> Sun 0x7E, STL 0x7F (matches Wikipedia "-1 -> 0x7F" for 14-bit input). Consequently the Sun encoder never emits 0x7F (negative zero); both decode 0x7F as 0. - A-law encoders: Sun (
-p-1after>>3) and STL (~x >> 4) agree for all 16-bit inputs; VERIFIED exhaustively bybrisvox-audiotests (2026-10-08). - Saturation: Sun clips at 8159 (biased 8192 -> seg 8 ->
0x7F ^ mask), STL clips biased value at 0x1FFF; same code (0x80 positive, 0x00 negative) DERIVED. - A-law even-bit inversion: Sun/STL XOR 0x55 (sign handled by masks 0xD5/0x55). Wikipedia: "all resulting even bits (LSB is even) are inverted" so silence is 0xD5 (not 0x80). mu-law: all bits complemented, silence 0xFF.
- Zero-code suppression (T1 networks), ITU-T G.711 text (quoted via search snippet and the wikipedia mirror https://wikipedia.classicistranieri.com/en/articles/g/2E/7/G.711.html): "When using mu-law G.711 in networks where suppression of the all 0 character signal is required, the character signal corresponding to negative input values between decision values numbers 127 and 128 should be 00000010 and the value at the decoder output is -7519." (14-bit scale; DERIVED 16-bit scale -30076, vs. 0x00 = -32124). Neither Sun nor STL implements it. Only the encoder would substitute 0x00 -> 0x02; irrelevant for Twilio/Telnyx (IP, no T1 constraint). Do NOT implement.
Vectors
Legend: SRC = stated in a fetched source; DER = hand-derived from the fetched Sun code (confirm by test).
| Case | Value | Status |
|---|---|---|
| mu enc 0 | 0xFF | SRC (Wikipedia mu-law: "0 encodes as 0xFF"; silence = 0xFF) + DER |
| mu enc -1 (14-bit input, ones' complement ITU table) | 0x7F | SRC; Sun 16-bit code gives 0x7E (DER) |
| mu enc +32767 / -32768 | 0x80 / 0x00 | DER |
| mu dec 0xFF / 0x7F | 0 / 0 | DER |
| mu dec 0x80 / 0x00 | +32124 / -32124 | DER; 14-bit peak 8031 (x4 = 32124) SRC Wikipedia G.711 ("0 to +-8031") |
| mu dec 0xFE / 0x7E | +8 / -8 | DER |
| mu dec 0x02 | -30076 (-7519 in 14-bit) | DER; -7519 SRC (G.711 text above) |
| A enc 0 | 0xD5 | SRC (Wikipedia: silent channel 0xD5) + DER |
| A enc -1 | 0x55 | DER |
| A enc +32767 / -32768 | 0xAA / 0x2A | DER |
| A dec 0xD5 / 0x55 | +8 / -8 | DER (13-bit +-1, x8) |
| A dec 0xAA / 0x2A | +32256 / -32256 | DER; 13-bit peak 4032 = 2^12-2^6 (x8 = 32256) SRC Wikipedia ("+-1 to +-(2^12-2^6)") |
| Round trip: decode(encode(decode(c))) == c for every A-law code (DER); mu-law except 0x7F (which re-encodes as 0xFF). | ||
| Max decode magnitudes: mu 32124, A 32256 (see above). |
Carrier formats
- Twilio Media Streams (websocket-messages page): encoding "always
audio/x-mulaw", sample rate "always8000", payload "Raw mulaw/8000 audio encoded in base64"; payload "should not contain audio file type header bytes". Mono / 20 ms chunking not stated in the fetched text: UNVERIFIED. - Telnyx media streaming: start-event example shows
"encoding": "PCMU", "sample_rate": 8000, "channels": 1; bidirectional codecs listed: PCMU 8k (default), PCMA 8k, G722, OPUS, AMR-WB, L16 16k; chunk size "20 milliseconds to 30 seconds"; mismatched codec is transcoded. Telnyx uses base64 payloads. Default for non-bidirectional: UNVERIFIED. - RTP payload types PCMU=0 / PCMA=8: page fetch failed (404), UNVERIFIED here.
Implementation guidance
- Decode: 256-entry
[i16; 256]tables built at startup (const fn). Encode: 64K-entry u8 tables or arithmetic form (cheap; no inference pool needed). - Use
>>as arithmetic shift on i16 (Rust matches C here); use the 14-bit Sun form above and test against the vectors; add a test pinning the Sun-vs-STL negative-input difference so a future port does not silently change.
Decision (2026-10-08)
brisvox follows the ITU-T G.191 STL behaviour: for negative input, the μ-law magnitude is the
ones' complement ((!x) >> 2) as in STL ulaw_compress, not Sun's negate-after-shift. This
matches the standard's reference software and Wikipedia's "-1 -> 0x7F"; Sun differs by one step
for some negative inputs (-1 -> 0x7E) and never emits 0x7F. A-law and both decoders are identical
in the two. Verified exhaustively by brisvox-audio tests (all 65 536 inputs): the table encoders
equal a const reference encoder and an independent STL-formulation encoder; the hand-derived
vectors above (0xFE -> 8, 0x7E -> -8, 0x02 -> -30076, extremes, silence codes) hold.
Verification status (2026-10-08): the decoders equal STL-formulation expanders for all 256
codes of both laws, and every reconstruction level lies inside its code's quantization
interval (exhaustive tests in crates/brisvox-audio/src/codec.rs). The STL-formulation test
codecs were written from the algorithm summary above, not from the STL source.