phase2

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: GPL-3.0 Imports: 5 Imported by: 0

Documentation

Overview

Package phase2 implements P25 Phase 2 (H-DQPSK TDMA) demodulation and framing. Output is classified bursts with slot/location/DUID metadata. Voice decode, descrambling, and audio output are handled by callers.

Index

Constants

View Source
const (
	// SymbolRate is the H-DQPSK channel symbol rate.
	SymbolRate = 6000.0

	// BurstDibits is the per-burst payload length (180 dibits = 360 bits = 30 ms
	// at SymbolRate 6000 sym/s).
	BurstDibits = 180

	// SuperframeBursts is the number of bursts in one superframe (12 x 30 ms = 360 ms).
	SuperframeBursts = 12

	// SyncDibits is the length of the Phase 2 frame sync pattern.
	SyncDibits = 20

	// SyncBits is SyncDibits * 2.
	SyncBits = 40

	// SyncMagic is the P25 Phase 2 frame sync pattern, 40 bits, MSB-first.
	// Source: op25 frame_sync_magics.h (P25P2_FRAME_SYNC_MAGIC).
	SyncMagic uint64 = 0x575D57F7FF

	// SyncMask masks the relevant 40 bits.
	SyncMask uint64 = 0xFFFFFFFFFF

	// SyncErrorThreshold is the maximum bit-error count for sync detection.
	// op25 uses 4. Matches the 10% threshold typical for marginal SNR.
	SyncErrorThreshold = 4

	// DUIDPositions are the ABSOLUTE dibit indices within a 180-dibit burst
	// that carry the DUID bits. op25 extract_duid reads burstp[10/47/132/169]
	// where burstp = &dibits[10], i.e. absolute 20/57/142/179, on the RAW
	// (pre-descramble) burst. Source: op25 p25p2_duid.cc::extract_duid +
	// p25p2_tdma.cc:698 (burstp = &dibits[10]).
	DUIDPos0 = 20
	DUIDPos1 = 57
	DUIDPos2 = 142
	DUIDPos3 = 179

	// PayloadOffset is where the descrambled payload starts within the
	// 180-dibit burst. op25 calls this "burstp = &dibits[10]".
	PayloadOffset = 10

	// VCW (voice codeword) offsets relative to PayloadOffset.
	// Full burst position = PayloadOffset + offset.
	// Source: op25 p25p2_tdma.cc lines 737-741.
	VCW1Offset = 11  // burst[21]: first 36-dibit voice codeword
	VCW2Offset = 48  // burst[58]: second voice codeword
	VCW3Offset = 96  // burst[106]: third voice codeword (4V only)
	VCW4Offset = 133 // burst[143]: fourth voice codeword (4V only)

	// ESSOffset is the ESS position relative to PayloadOffset.
	// 12 dibits carrying encryption sync signal (algid/keyid/MI fragments).
	// Source: op25 p25p2_tdma.cc line 736.
	ESSOffset = 84 // burst[94]
	ESSDibits = 12
)
View Source
const (
	MACOpSignal   uint8 = 0
	MACOpPTT      uint8 = 1
	MACOpEndPTT   uint8 = 2
	MACOpIdle     uint8 = 3
	MACOpActive   uint8 = 4
	MACOpHangtime uint8 = 6
)

MAC control opcodes (3-bit). Source: op25 process_mac_pdu (p25p2_tdma.cc:171).

View Source
const PayloadDibitsPerBurst = BurstDibits - 10 // 170

PayloadDibitsPerBurst is the number of payload dibits per burst (positions 10..179 = 170 dibits). Used for burst-level payload size calculations.

View Source
const SuperframeDibits = SuperframeBursts * BurstDibits // 2160

SuperframeDibits is the total number of dibits in one superframe XOR mask.

View Source
const VoiceCWDibits = 36

VoiceCWDibits is the number of dibits per voice codeword.

Variables

View Source
var WhichSlot = [SuperframeBursts]int{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0}

WhichSlot maps the 12 superframe burst positions (ISCH "location" field) to slot index (0 or 1). Note position 10 maps to slot 1, not 0 — the pattern is asymmetric. Source: op25 p25p2_tdma.cc.

Functions

func GenerateXORMask

func GenerateXORMask(nac uint16, sysid uint16, wacn uint32) [SuperframeDibits]p25.Dibit

GenerateXORMask computes the 2160-dibit XOR descrambling mask for one superframe, given the system identity parameters. The LFSR produces 4320 bits (2160 dibits = 12 × 180). Indexed at stride 180 (BurstDibits): mask[burstPosition*180 + i] where i=0..169. The first 10 entries per burst slot (ISCH area) exist in the array but are not applied. Source: op25 lfsr.py:mk_xor_bits + p25p2_tdma.cc:set_xormask

func PackAMBE

func PackAMBE(u [4]uint16) [49]uint8

PackAMBE packs the four u[] words into a 49-element bit vector (1 bit per byte, MSB-first within each word) suitable for mbelib's ambe_d[49] input. Layout: u[0] bits 11..0 → d[0..11], u[1] bits 11..0 → d[12..23], u[2] bits 10..0 → d[24..34], u[3] bits 13..0 → d[35..48].

func PackCW

func PackCW(u [4]uint16) [7]byte

PackCW packs the four AMBE+2 parameter words into a 7-byte packed codeword. This is the format used for ADP decryption (XOR keystream is applied to the packed form). Matches op25 p25p2_vf::pack_cw.

func UnpackCW

func UnpackCW(cw [7]byte) [4]uint16

UnpackCW unpacks a 7-byte packed codeword into four AMBE+2 parameter words. Inverse of PackCW. Matches op25 p25p2_vf::unpack_cw.

Types

type ACCHType

type ACCHType int

ACCHType selects the ACCH layout/CRC for a control burst.

const (
	ACCHFacch ACCHType = iota // 2V FACCH / scrambled-or-unscrambled FACCH; CRC-12, 144-bit
	ACCHSacch                 // SACCH; CRC-12, 168-bit
	ACCHLcch                  // LCCH; SACCH RS layout, CRC-16, 180-bit, 23-byte out
)

type Burst

type Burst struct {
	Dibits [BurstDibits]p25.Dibit // descrambled payload after Decoder.processBurst
	Raw    [BurstDibits]p25.Dibit // raw pre-descramble dibits; set by the decoder
	ISCH   ISCHInfo
	DUID   uint8 // raw 8-bit DUID extracted from RAW dibits 20/57/142/179
	Type   BurstType
}

Burst is one Phase 2 burst: 180 dibits plus decoded metadata.

func Descramble

func Descramble(b Burst, mask [SuperframeDibits]p25.Dibit) Burst

Descramble applies the XOR mask to a burst at the given superframe position. Only the 170 payload dibits (burst[10:180]) are XOR'd; the first 10 dibits (ISCH/sync) are preserved. Returns a new burst with descrambled payload.

The mask stride is BurstDibits (180), NOT PayloadDibitsPerBurst (170). Each superframe position occupies a 180-dibit block in the mask array; the first 10 entries of each block (corresponding to ISCH) are skipped. Source: op25 p25p2_tdma.cc handle_packet:

tdma_xormask[sync.tdma_slotid() * BURST_SIZE + i]  (BURST_SIZE=180, i=0..169)

type BurstType

type BurstType int

BurstType is the high-level classification of a burst.

const (
	BurstUnknown BurstType = iota
	Burst4V                // four voice codewords (FEC-detected; DUID id 0)
	Burst2V                // two voice codewords + SACCH/FACCH (FEC-detected; DUID id 6)
	BurstSACCH             // DUID id 3 (scrambled) or 12 (unscrambled) SACCH; no voice
	BurstLCCH              // DUID id 13: unscrambled LCCH (CRC-16); no voice
	BurstFACCH             // DUID id 9 (scrambled) or 15 (unscrambled) FACCH; no voice
)

func Classify

func Classify(b Burst) BurstType

Classify determines a burst's type. Voice detection is FEC-based and authoritative (ClassifyByFEC); the DUID is consulted ONLY when FEC rejects the burst as non-voice, to label it as a control burst. b.DUID must already be set from the RAW burst (see Decoder.processBurst).

func ClassifyByFEC

func ClassifyByFEC(dibits [BurstDibits]p25.Dibit) BurstType

ClassifyByFEC classifies a burst by checking Golay FEC quality at the voice codeword positions. This is far more reliable than DUID-based classification for voice bursts.

Detection uses the SUM of c0 Golay errors across all 4 VCW positions. For voice, each VCW typically has c0=0, so sum ≈ 0-4. For random data, each c0 averages ~2.5 errors (Golay(23,12) is perfect, always ≤3), so sum ≈ 10. The false-positive rate at sum ≤ 4 is negligible (< 10^-6).

4V vs 2V: compare the c0-error sum of VCW1+2 vs VCW3+4. If VCW3+4 contribute ≤ 2 errors, it's 4V (4 voice codewords). Otherwise 2V (2 voice codewords + control data in VCW3+4 positions).

type Decoder

type Decoder struct {

	// Counters (diagnostic only).
	BurstsTotal int
	BurstsValid int // ISCH decoded successfully
	VoiceFrames int // voice frames produced
	// contains filtered or unexported fields
}

Decoder is the Phase 2 receive chain: complex IQ -> classified bursts and decoded voice frames. State persists across Process() calls; feed contiguous IQ samples.

func NewDecoder

func NewDecoder(sampleRate float64) *Decoder

NewDecoder builds a decoder for the given input sample rate (e.g. 25000).

func (*Decoder) Close

func (d *Decoder) Close()

Close releases decoder resources.

func (*Decoder) EVM

func (d *Decoder) EVM() float64

EVM returns the demodulator's RMS Error Vector Magnitude.

func (*Decoder) HasKey

func (d *Decoder) HasKey(algID uint8, keyID uint16) bool

HasKey reports whether a usable decryption key exists for (algID, keyID). Passthrough to the TDMA processor; see TDMAProcessor.HasKey.

func (*Decoder) Process

func (d *Decoder) Process(iq []complex64) ([]Burst, []P2VoiceFrame)

Process consumes IQ and returns classified bursts and decoded voice frames. Voice frames are only produced for 4V/2V bursts when scramble params are set.

The processing pipeline for each burst is:

  1. Assign superframe position from the running counter (or buffer for FEC-guided sync detection if counter not yet initialized).
  2. Descramble payload (positions 10-179) using the XOR mask at the burst's superframe location.
  3. Extract DUID and classify from the descrambled burst.
  4. Feed voice-bearing bursts to the TDMA processor for voice decode.

Superframe offset detection: the ISCH second half (positions 10-19) is in the scrambled region, making the (40,9,16) Hamming decode unreliable for I-ISCH location extraction. Instead, we buffer the first 24 bursts and brute-force all 12 offsets, selecting the one with the most perfect Golay c0 decodes in the voice codeword positions.

func (*Decoder) Reset

func (d *Decoder) Reset()

Reset reinitializes the decoder's demod, framer, and per-slot state so that stale inter-block state does not contaminate a new TDMA session. The scramble mask is preserved (it depends on system identity, not call state). Call when switching from FDMA to TDMA mode or vice versa.

func (*Decoder) ResetStats

func (d *Decoder) ResetStats()

ResetStats clears EVM accumulators without affecting demod state.

func (*Decoder) SetKeyLookup

func (d *Decoder) SetKeyLookup(fn KeyLookupFunc)

SetKeyLookup configures the key-resolution function used for ADP decryption of encrypted Phase 2 voice. Thread-safe.

func (*Decoder) SetScrambleParams

func (d *Decoder) SetScrambleParams(nac uint16, sysid uint16, wacn uint32)

SetScrambleParams configures the TDMA descrambling mask. Must be called before voice bursts can be decoded. Parameters come from the control channel.

type ESSState

type ESSState struct {

	// Decoded encryption parameters. Updated on successful RS decode.
	AlgID uint8
	KeyID uint16
	MI    [9]byte
	Valid bool // true after at least one successful RS decode
	// contains filtered or unexported fields
}

ESSState tracks ESS (Encryption Sync Signal) accumulation for one TDMA slot.

In P25 Phase 2, encryption metadata (AlgID/KeyID/MI) is distributed across a superframe's voice bursts:

  • ESS-B: 4 hexbits per 4V burst × 4 bursts = 16 data hexbits
  • ESS-A: 28 parity hexbits from the 2V burst

After a complete cycle (4 × 4V + 1 × 2V), RS(44,16) decode over GF(2^6) yields the 96-bit encryption metadata: AlgID(8) + KeyID(16) + MI(72).

Source: op25 p25p2_tdma.cc handle_4V2V_ess().

func NewESSState

func NewESSState() *ESSState

NewESSState creates an ESS accumulator in the initial (not-synced) state.

func (*ESSState) BurstPosition

func (e *ESSState) BurstPosition() int

BurstPosition returns the current burst position within the superframe voice cycle: 0-3 for 4V bursts, 4 for 2V, -1 if not synced. This is used by the ADP decryption offset calculation.

func (*ESSState) Encrypted

func (e *ESSState) Encrypted() bool

Encrypted returns true if the decoded AlgID indicates encryption. AlgID 0x80 means "unencrypted" and 0x00 means "not applicable".

func (*ESSState) Feed

func (e *ESSState) Feed(burstType BurstType, dibits [BurstDibits]p25.Dibit)

Feed processes ESS dibits from a descrambled voice burst. burstType must be Burst4V or Burst2V; other types are ignored. The full descrambled 180-dibit burst is required (ESS spans positions 94–178).

For 4V bursts: accumulates 4 ESS-B hexbits from 12 dibits at ESSOffset. For 2V bursts: extracts 28 ESS-A hexbits, then performs RS decode.

func (*ESSState) Reset

func (e *ESSState) Reset()

Reset clears the ESS accumulation state. Call at call boundaries.

type Framer

type Framer struct {
	// contains filtered or unexported fields
}

Framer scans a Phase 2 dibit stream for the SyncMagic pattern and accumulates 180-dibit (30 ms) bursts. After initial sync, it tracks consecutive bursts by counting dibits (like op25's d_in_sync mechanism), collecting both S-ISCH and I-ISCH bursts.

func NewFramer

func NewFramer() *Framer

NewFramer returns a Framer in the "searching" state.

func (*Framer) Feed

func (f *Framer) Feed(dibits []p25.Dibit) []Burst

Feed consumes dibits and returns any completed bursts.

type HDQPSKDemod

type HDQPSKDemod struct {
	// contains filtered or unexported fields
}

HDQPSKDemod converts complex64 IQ at sampleRate Hz into Phase 2 dibits at SymbolRate sym/s using:

  • linear-interpolated symbol-rate strobe
  • Gardner timing error detector with PI loop filter (complex-sample form)
  • decision-directed phase rotator (first-order PLL on the differential phase)
  • Gray-coded pi/4 DQPSK slicer (differentialDecode)

Sample rate must be ≥ 2·SymbolRate. At 25 kSPS the loop runs at 4.167 samp/sym, which Gardner handles cleanly.

func NewHDQPSKDemod

func NewHDQPSKDemod(sampleRate float64) *HDQPSKDemod

NewHDQPSKDemod returns a demod configured for the given input sample rate. Loop gains are tuned for 4–10 samp/sym; the defaults work at 25 kSPS.

func (*HDQPSKDemod) CarrierPhase

func (d *HDQPSKDemod) CarrierPhase() float64

CarrierPhase returns the current accumulated carrier-phase estimate (radians). Diagnostic only.

func (*HDQPSKDemod) EVM

func (d *HDQPSKDemod) EVM() float64

EVM returns the RMS Error Vector Magnitude (phase-domain), normalized by the nominal π/4 phase advance. A perfect signal returns 0; typical values for good P25 Phase 2 signals are 0.05–0.15.

func (*HDQPSKDemod) Process

func (d *HDQPSKDemod) Process(in []complex64) []p25.Dibit

Process consumes complex IQ and returns recovered dibits. State carries across calls — feed contiguous samples from the upstream source.

The returned slice is backed by an internal buffer reused on the next Process call; callers must consume it before calling Process again (framer.Feed does so synchronously within Decoder.Process).

func (*HDQPSKDemod) ResetStats

func (d *HDQPSKDemod) ResetStats()

ResetStats clears the EVM accumulator without affecting timing or carrier state.

type ISCHInfo

type ISCHInfo struct {
	Location int  // 0..11, burst position within superframe; -1 if unknown
	Slot     int  // 0 or 1, derived from WhichSlot[Location]; -1 if Location is -1
	IsSISCH  bool // true for the rare "super-ISCH" sync indicator codeword
	Valid    bool // false if Hamming decode failed
}

ISCHInfo is the decoded Inter-Slot Signalling CHannel field. Identifies which superframe burst and which slot a burst belongs to.

func DecodeISCH

func DecodeISCH(dibits [SyncDibits]p25.Dibit) ISCHInfo

DecodeISCH decodes a 20-dibit (40-bit) ISCH codeword to ISCHInfo. First tries an exact lookup; on miss, finds the nearest codeword by Hamming distance and accepts if ≤7 bits (the code's correction limit).

type KeyLookupFunc

type KeyLookupFunc func(algID uint8, keyID uint16) (key []byte, ok bool)

KeyLookupFunc resolves an encryption key from algorithm ID and key ID. Returns the raw key bytes and true if found, or nil and false if not.

type MACPDU

type MACPDU struct {
	Opcode uint8
	Offset uint8
	Bytes  []byte

	HasIdentity bool
	Talkgroup   uint16
	SourceID    uint32
	ServiceOpts uint8

	HasEncryption bool
	AlgID         uint8
	KeyID         uint16
	MI            [9]byte

	// GPS holds an in-call Harris Talker GPS position (vendor sub-message op=0xAA,
	// MFID=0xA4); GPSOK is true when decoded.
	GPS   p25.GPSPosition
	GPSOK bool
	// contains filtered or unexported fields
}

MACPDU is a decoded Phase 2 MAC PDU from a FACCH/SACCH burst. Opcode is the 3-bit MAC control opcode (0 SIGNAL,1 PTT,2 END_PTT,3 IDLE, 4 ACTIVE,6 HANGTIME). Source: op25 process_mac_pdu (p25p2_tdma.cc:171-221).

func DecodeACCH

func DecodeACCH(dibits [BurstDibits]p25.Dibit, typ ACCHType) (*MACPDU, bool)

DecodeACCH decodes one burst's ACCH into a MAC PDU. typ selects FACCH / SACCH / LCCH (layout + CRC). It runs the FEC pipeline (decodeACCHBytes) then parses call identity per op25's MAC opcode handlers. Returns (nil,false) on FEC/CRC failure. Source: op25 process_mac_pdu (p25p2_tdma.cc:171-221).

type P2VoiceFrame

type P2VoiceFrame struct {
	PCM  []float32 // decoded 8 kHz float32 PCM samples
	Slot int       // 0 or 1 — which TDMA slot this came from
	Errs int       // total FEC errors summed across all voice codewords

	// Total is the number of voice codewords processed in this frame (4 for a
	// 4V burst, 2 for a 2V burst). Uncorrectable is how many of those had a
	// DETECTED uncorrectable c0 (extended Golay(24,12,8) weight->=4 error, via
	// Golay24DetectUncorrectable). Because Golay(23,12) is a perfect code the
	// decoder never self-reports failure; this detection uses the overall-parity
	// bit the (23,12) path discards, so Uncorrectable/Total is a real c0
	// decode-failure signal (Errs only sums CORRECTED bits and is biased low by
	// miscorrected words). Both are 0 for ControlOnly frames. Metric-only:
	// audio emission is unaffected.
	Total         int
	Uncorrectable int

	// ControlOnly is true for frames decoded from a voice-less control burst
	// (SACCH/FACCH/LCCH). PCM is nil for these; they carry MAC identity / alias /
	// encryption and a MACOpcode for the pipeline lifecycle layer.
	ControlOnly bool
	MACOpcode   uint8 // MAC control opcode (MACOp*); only meaningful when ControlOnly

	// Encryption metadata from ESS decode. Updated after each complete
	// superframe cycle (4×4V + 1×2V). AlgID 0x80 = clear, 0x00 = N/A.
	AlgID     uint8
	KeyID     uint16
	MI        [9]byte
	Encrypted bool // true if AlgID indicates active encryption
	Decrypted bool // true if encrypted AND successfully decrypted (PCM is clear)

	// Call identity decoded from the in-call MAC signalling (FACCH on the 2V
	// burst), independent of the Phase 1 control-channel grant. Zero/false when
	// no MAC identity has been seen on this slot yet.
	Talkgroup       uint16
	SourceID        uint32
	ServiceOpts     uint8 // MAC service options byte (emergency/enc/priority bits)
	IdentityFromMAC bool

	// Talker alias decoded from vendor-specific MAC sub-messages (0x91/0x95
	// Motorola, 0xA8 Harris) on the in-call FACCH. Empty until a complete alias
	// is assembled; TalkerAliasTGID is the talkgroup from the Motorola header.
	TalkerAlias     string
	TalkerAliasTGID uint16
	TalkerAliasUnit uint32 // SUID unit ID from the Motorola alias (0 for Harris)

	// In-call GPS decoded from a Harris Talker GPS MAC sub-message (op=0xAA,
	// MFID=0xA4) on the FACCH. GPSOK is false until a position has been seen.
	GPS   p25.GPSPosition
	GPSOK bool
}

P2VoiceFrame holds decoded PCM audio from one Phase 2 TDMA voice burst. A 4V burst produces up to 640 PCM samples (4 × 160); a 2V burst up to 320.

type TDMAProcessor

type TDMAProcessor struct {
	// contains filtered or unexported fields
}

TDMAProcessor manages the two-slot TDMA state machine for P25 Phase 2. It descrambles voice bursts, extracts voice codewords, decodes FEC, and invokes the AMBE+2 vocoder. One TDMAProcessor handles both timeslots.

Usage: call ProcessBurst for each classified burst; returned P2VoiceFrames contain decoded PCM audio ready for playback or recording.

SetScrambleParams / SetXORMask / SetKeyLookup may be called from a different goroutine (e.g. a control-channel decoder propagating WACN/SYSID or key config). A mutex guards shared fields; the lock is uncontended on the hot ProcessBurst path.

func NewTDMAProcessor

func NewTDMAProcessor() *TDMAProcessor

NewTDMAProcessor creates a processor. Call SetScrambleParams once system identity parameters are known (from the control channel).

func (*TDMAProcessor) Close

func (t *TDMAProcessor) Close()

Close releases resources.

func (*TDMAProcessor) HasKey

func (t *TDMAProcessor) HasKey(algID uint8, keyID uint16) bool

HasKey reports whether a usable decryption key exists for (algID, keyID) via the configured key lookup, WITHOUT decrypting. The slot-open gate uses this as a deterministic key-availability signal: the per-frame Decrypted flag only goes true after the ADP cipher is primed (one superframe into the call), which would clip the head of a keyed call joined mid-stream.

func (*TDMAProcessor) ProcessBurst

func (t *TDMAProcessor) ProcessBurst(b Burst) *P2VoiceFrame

ProcessBurst handles one classified, already-descrambled burst. Returns a P2VoiceFrame if the burst is voice-bearing (4V or 2V) and decoding succeeds, or nil otherwise. Descrambling and DUID classification are performed by Decoder.Process before this method is called.

For encrypted calls with a matching key, ADP decryption is applied between FEC decode and vocoder. The cipher is prepared once per superframe cycle on the 2V burst, after that burst's voice codewords have been processed (matching op25 timing: current 2V voice uses the old cipher state, then prepare() sets up the keystream for the next cycle).

func (*TDMAProcessor) ResetSlot

func (t *TDMAProcessor) ResetSlot(slot int)

ResetSlot reinitializes the vocoder and encryption state for the given slot (0 or 1). Call at call boundaries to prevent state leakage between conversations.

func (*TDMAProcessor) SetKeyLookup

func (t *TDMAProcessor) SetKeyLookup(fn KeyLookupFunc)

SetKeyLookup configures the key-resolution function used for ADP decryption. Thread-safe: may be called from any goroutine.

func (*TDMAProcessor) SetScrambleParams

func (t *TDMAProcessor) SetScrambleParams(nac uint16, sysid uint16, wacn uint32)

SetScrambleParams configures the XOR descrambling mask from the system identity. Must be called before voice bursts can be descrambled. Thread-safe: may be called from any goroutine.

func (*TDMAProcessor) SetXORMask

func (t *TDMAProcessor) SetXORMask(mask [SuperframeDibits]p25.Dibit)

SetXORMask sets the XOR mask directly (e.g. from an external source). Thread-safe: may be called from any goroutine.

type VoiceCWResult

type VoiceCWResult struct {
	U               [4]uint16 // u[0..3]: 12+12+11+14 = 49 bits of AMBE+2 parameters
	Errs            int       // total FEC errors (c0 + c1)
	OK              bool      // true if both Golay decodes succeeded
	C0Uncorrectable bool      // c0 extended-Golay detected a weight->=4 (uncorrectable) error
}

VoiceCWResult holds the decoded AMBE+2 parameters from one voice codeword.

func DecodeVoiceCW

func DecodeVoiceCW(dibits []p25.Dibit) VoiceCWResult

DecodeVoiceCW decodes one Phase 2 voice codeword from 36 dibits. Returns the four AMBE+2 parameter words (49 bits total), FEC error count, and whether decoding succeeded.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL