Also known as: IMBE FEC, IMBE 4400 channel coding, u_0..u_7 vectors
IMBE channel coding is the forward error correction layer that turns the 88 information bits an IMBE vocoder emits every 20 ms into the 144 channel bits carried in each P25 Phase 1 voice subframe.1 It is unequal protection: the perceptually critical bits get a strong Golay(23,12) code, the mid-importance bits a lighter Hamming(15,11) code, and the least-sensitive bits ride bare. This graceful-degradation design (TIA-102.BABA §7.3) is why a weak P25 signal warbles rather than dropping to silence.
How it works
The 88 bits are grouped into eight vectors named u_0 through u_7. The four most significant
vectors carry 12 bits each and are encoded as Golay(23,12,7) codewords — 23 bits able to correct
up to 3 errors. The next three carry 11 bits each as Hamming(15,11,3) codewords — 15 bits
correcting a single error. The last vector, u_7, is 7 bits with no coding at all, because it
holds the least perceptually sensitive spectral detail. The arithmetic closes exactly:
12×4 + 11×3 + 7 = 88 information bits become 23×4 + 15×3 + 7 = 144 channel bits.
| Vector | Info bits | Channel bits | Code |
|---|---|---|---|
| u_0 … u_3 | 12 each | 23 each | Golay(23,12,7) |
| u_4 … u_6 | 11 each | 15 each | Hamming(15,11,3) |
| u_7 | 7 | 7 | none |
GopherTrunk decodes each vector by nearest-codeword search over a precomputed table (4096 Golay
codewords, 2048 Hamming codewords), which for errors inside the correction radius returns the
unique correct data and a corrected-error count. u_0 matters most: its Golay data seeds the
descrambler, so the decoder corrects u_0 first, before it can
trust any of u_1–u_6. The generator itself is a fixed table — each of the 12 Golay data bits
contributes a fixed 11-bit parity pattern, XOR-accumulated:
// 11-bit parity contribution of each Golay(23,12) data bit, MSB-first.
var golayGenerator = [12]uint16{
0x63a, 0x31d, 0x7b4, 0x3da, 0x1ed, 0x6cc,
0x366, 0x1b3, 0x6e3, 0x54b, 0x49f, 0x475,
}
Two different Golay codes in one repo
A subtle trap sits under this page. GopherTrunk carries two unrelated Golay/Hamming
implementations, and they are not interchangeable. The internal/radio/framing package holds
systematic Golay(24,12) and Hamming(15,11) codes used for P25 and DMR link control framing. The
vocoder uses the separate internal/voice/imbe/p25fec.go code, transcribed from mbelib’s ecc.c
in the exact bit order real IMBE transmitters use. Although framing’s Golay generator list equals
golayGenerator shifted by one bit, the two associate generator rows with data bits in opposite
order, so they are different codes in practice: a clean real-air IMBE codeword decodes to the
wrong data under framing’s Golay. That mismatch was the root of GopherTrunk issue #489, verified
against a real P25 voice capture and the mbelib reference decoder. When touching this path, never
reach for the framing package’s codec — the vocoder needs the mbelib-order one, and only that one.
Relevance to SDR
Channel coding is the first thing GopherTrunk’s IMBE receiver runs on each 144-bit subframe, after the interleaver and scrambler layers have been undone. The corrected-error counts it returns feed the frame-repeat logic upstream: an uncorrectable vector marks a bad frame that the synthesizer replays and fades rather than voicing. Each P25 Phase 1 LDU carries nine such subframes, so getting the Golay and Hamming math bit-exact — in the mbelib order, not the framing order — is what lets real off-air voice decode at all.
Sources
-
Multi-Band Excitation — Wikipedia, on the IMBE vocoder family and its role in P25 Phase 1. ↩