Преглед изворни кода

RDS: stabilize live decode + conservative recovery

master
Jan Svabenik пре 16 часа
родитељ
комит
0d9a9e096e
4 измењених фајлова са 441 додато и 94 уклоњено
  1. +2
    -0
      .gitignore
  2. +22
    -14
      cmd/sdrd/dsp_loop.go
  3. +417
    -80
      internal/rds/rds.go
  4. BIN
      sdr-visual-suite.rar

+ 2
- 0
.gitignore Прегледај датотеку

@@ -24,6 +24,8 @@ web/old/
web/openai/ web/openai/
web/*-web.zip web/*-web.zip
sdr-visual-suite.zip sdr-visual-suite.zip
*.zip
*.rar


# downloaded decoder binaries # downloaded decoder binaries
tools/downloads/ tools/downloads/


+ 22
- 14
cmd/sdrd/dsp_loop.go Прегледај датотеку

@@ -85,18 +85,18 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
cfg = upd.cfg cfg = upd.cfg
if rec != nil { if rec != nil {
rec.Update(cfg.SampleRate, cfg.FFTSize, recorder.Policy{ rec.Update(cfg.SampleRate, cfg.FFTSize, recorder.Policy{
Enabled: cfg.Recorder.Enabled,
MinSNRDb: cfg.Recorder.MinSNRDb,
MinDuration: mustParseDuration(cfg.Recorder.MinDuration, 1*time.Second),
MaxDuration: mustParseDuration(cfg.Recorder.MaxDuration, 300*time.Second),
PrerollMs: cfg.Recorder.PrerollMs,
RecordIQ: cfg.Recorder.RecordIQ,
RecordAudio: cfg.Recorder.RecordAudio,
AutoDemod: cfg.Recorder.AutoDemod,
AutoDecode: cfg.Recorder.AutoDecode,
MaxDiskMB: cfg.Recorder.MaxDiskMB,
OutputDir: cfg.Recorder.OutputDir,
ClassFilter: cfg.Recorder.ClassFilter,
Enabled: cfg.Recorder.Enabled,
MinSNRDb: cfg.Recorder.MinSNRDb,
MinDuration: mustParseDuration(cfg.Recorder.MinDuration, 1*time.Second),
MaxDuration: mustParseDuration(cfg.Recorder.MaxDuration, 300*time.Second),
PrerollMs: cfg.Recorder.PrerollMs,
RecordIQ: cfg.Recorder.RecordIQ,
RecordAudio: cfg.Recorder.RecordAudio,
AutoDemod: cfg.Recorder.AutoDemod,
AutoDecode: cfg.Recorder.AutoDecode,
MaxDiskMB: cfg.Recorder.MaxDiskMB,
OutputDir: cfg.Recorder.OutputDir,
ClassFilter: cfg.Recorder.ClassFilter,
RingSeconds: cfg.Recorder.RingSeconds, RingSeconds: cfg.Recorder.RingSeconds,
DeemphasisUs: cfg.Recorder.DeemphasisUs, DeemphasisUs: cfg.Recorder.DeemphasisUs,
ExtractionTaps: cfg.Recorder.ExtractionTaps, ExtractionTaps: cfg.Recorder.ExtractionTaps,
@@ -242,7 +242,11 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
} }
// RDS decode for WFM — async, uses ring buffer for continuous IQ // RDS decode for WFM — async, uses ring buffer for continuous IQ
if (cls.ModType == classifier.ClassWFM || cls.ModType == classifier.ClassWFMStereo) && rec != nil { if (cls.ModType == classifier.ClassWFM || cls.ModType == classifier.ClassWFMStereo) && rec != nil {
key := int64(math.Round(signals[i].CenterHz / 500000))
keyHz := pll.ExactHz
if keyHz == 0 {
keyHz = signals[i].CenterHz
}
key := int64(math.Round(keyHz / 25000.0))
st := rdsMap[key] st := rdsMap[key]
if st == nil { if st == nil {
st = &rdsState{} st = &rdsState{}
@@ -321,7 +325,11 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *
if len(rdsMap) > 0 { if len(rdsMap) > 0 {
activeIDs := make(map[int64]bool, len(signals)) activeIDs := make(map[int64]bool, len(signals))
for _, s := range signals { for _, s := range signals {
activeIDs[int64(math.Round(s.CenterHz / 500000))] = true
keyHz := s.CenterHz
if s.PLL != nil && s.PLL.ExactHz != 0 {
keyHz = s.PLL.ExactHz
}
activeIDs[int64(math.Round(keyHz/25000.0))] = true
} }
for id := range rdsMap { for id := range rdsMap {
if !activeIDs[id] { if !activeIDs[id] {


+ 417
- 80
internal/rds/rds.go Прегледај датотеку

@@ -15,12 +15,19 @@ type Decoder struct {
costasPhase float64 costasPhase float64
costasFreq float64 costasFreq float64
// Symbol sync state // Symbol sync state
syncMu float64
syncMu float64
syncPrev complex64
syncPrevDecision complex64
syncHasPrev bool
lastSampleRate int
// Differential decode state across Decode() calls
lastHardBit int
hasLastHardBit bool
// Diagnostic counters // Diagnostic counters
TotalDecodes int
BlockAHits int
GroupsFound int
LastDiag string
TotalDecodes int
BlockAHits int
GroupsFound int
LastDiag string
} }


type Result struct { type Result struct {
@@ -29,6 +36,44 @@ type Result struct {
RT string `json:"rt"` RT string `json:"rt"`
} }


type scanDiag struct {
pol string
blockHits int
offAHits int
offBHits int
offCHits int
offCpHits int
offDHits int
abSeq int
abcSeq int
groups int
piHint uint16
piHintCount int
fecBlockFix int
grpFecFix int
blockAmbig int
seqAmbig int
}

func (s scanDiag) score() int {
return s.groups*100000 + s.abcSeq*1000 + s.abSeq*100 + s.blockHits
}

func bestDiag(a, b scanDiag) scanDiag {
if b.score() > a.score() {
return b
}
if b.score() == a.score() {
if b.piHintCount > a.piHintCount {
return b
}
if b.fecBlockFix > a.fecBlockFix {
return b
}
}
return a
}

// Decode takes complex baseband samples at ~20kHz and extracts RDS data. // Decode takes complex baseband samples at ~20kHz and extracts RDS data.
func (d *Decoder) Decode(samples []complex64, sampleRate int) Result { func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
if len(samples) < 104 || sampleRate <= 0 { if len(samples) < 104 || sampleRate <= 0 {
@@ -36,13 +81,29 @@ func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
} }
d.TotalDecodes++ d.TotalDecodes++


if d.lastSampleRate != 0 && d.lastSampleRate != sampleRate {
d.costasPhase = 0
d.costasFreq = 0
d.syncMu = 0
d.syncPrev = 0
d.syncPrevDecision = 0
d.syncHasPrev = false
d.lastHardBit = 0
d.hasLastHardBit = false
}
d.lastSampleRate = sampleRate

sps := float64(sampleRate) / 1187.5 // samples per symbol sps := float64(sampleRate) / 1187.5 // samples per symbol


// === Mueller & Muller symbol timing recovery ===
// Reset state each call — accumulated samples have phase gaps between frames
mu := sps / 2
// === Mueller & Muller symbol timing recovery (persistent across calls) ===
mu := d.syncMu
if mu < 0 || mu >= sps || math.IsNaN(mu) || math.IsInf(mu, 0) {
mu = sps / 2
}
symbols := make([]complex64, 0, len(samples)/int(sps)+1) symbols := make([]complex64, 0, len(samples)/int(sps)+1)
var prev, prevDecision complex64
prev := d.syncPrev
prevDecision := d.syncPrevDecision
havePrev := d.syncHasPrev
for mu < float64(len(samples)-1) { for mu < float64(len(samples)-1) {
idx := int(mu) idx := int(mu)
frac := mu - float64(idx) frac := mu - float64(idx)
@@ -55,49 +116,59 @@ func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
)) ))


var decision complex64 var decision complex64
if real(samp) > 0 {
if real(samp) >= 0 {
decision = 1 decision = 1
} else { } else {
decision = -1 decision = -1
} }


if len(symbols) >= 2 {
if havePrev {
errR := float64(real(decision)-real(prevDecision))*float64(real(prev)) - errR := float64(real(decision)-real(prevDecision))*float64(real(prev)) -
float64(real(samp)-real(prev))*float64(real(prevDecision)) float64(real(samp)-real(prev))*float64(real(prevDecision))
mu += sps + 0.01*errR mu += sps + 0.01*errR
} else { } else {
mu += sps mu += sps
havePrev = true
} }


prevDecision = decision prevDecision = decision
prev = samp prev = samp
symbols = append(symbols, samp) symbols = append(symbols, samp)
} }
residualMu := mu - float64(len(samples))
for residualMu < 0 {
residualMu += sps
}
for residualMu >= sps {
residualMu -= sps
}
d.syncMu = residualMu
d.syncPrev = prev
d.syncPrevDecision = prevDecision
d.syncHasPrev = havePrev


if len(symbols) < 26*4 { if len(symbols) < 26*4 {
d.LastDiag = fmt.Sprintf("too few symbols: %d", len(symbols))
d.LastDiag = fmt.Sprintf("too few symbols: %d sps=%.1f mu=%.3f", len(symbols), sps, d.syncMu)
return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()} return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
} }


// === Costas loop for fine frequency/phase synchronization ===
// Reset each call — phase gaps between accumulated frames break continuity
// === Costas loop for fine frequency/phase synchronization (persistent across calls) ===
alpha := 0.132 alpha := 0.132
beta := alpha * alpha / 4.0 beta := alpha * alpha / 4.0
phase := 0.0
freq := 0.0
phase := d.costasPhase
freq := d.costasFreq
synced := make([]complex64, len(symbols)) synced := make([]complex64, len(symbols))
for i, s := range symbols { for i, s := range symbols {
// Multiply by exp(-j*phase) to de-rotate
// Multiply by exp(-j*phase) to de-rotate.
cosP := float32(math.Cos(phase)) cosP := float32(math.Cos(phase))
sinP := float32(math.Sin(phase)) sinP := float32(math.Sin(phase))
synced[i] = complex( synced[i] = complex(
real(s)*cosP+imag(s)*sinP, real(s)*cosP+imag(s)*sinP,
imag(s)*cosP-real(s)*sinP, imag(s)*cosP-real(s)*sinP,
) )
// BPSK phase error: sign(I) * Q
// BPSK phase error: sign(I) * Q.
var err float64 var err float64
if real(synced[i]) > 0 {
if real(synced[i]) >= 0 {
err = float64(imag(synced[i])) err = float64(imag(synced[i]))
} else { } else {
err = -float64(imag(synced[i])) err = -float64(imag(synced[i]))
@@ -111,16 +182,14 @@ func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
phase += 2 * math.Pi phase += 2 * math.Pi
} }
} }
// state not persisted — samples have gaps
d.costasPhase = phase
d.costasFreq = freq


// Measure signal quality: average |I| and |Q| after Costas
// Measure signal quality: average |I| and |Q| after Costas.
var sumI, sumQ float64 var sumI, sumQ float64
for _, s := range synced { for _, s := range synced {
ri := float64(real(s))
rq := float64(imag(s))
if ri < 0 { ri = -ri }
if rq < 0 { rq = -rq }
ri := math.Abs(float64(real(s)))
rq := math.Abs(float64(imag(s)))
sumI += ri sumI += ri
sumQ += rq sumQ += rq
} }
@@ -130,7 +199,7 @@ func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
// === BPSK demodulation === // === BPSK demodulation ===
hardBits := make([]int, len(synced)) hardBits := make([]int, len(synced))
for i, s := range synced { for i, s := range synced {
if real(s) > 0 {
if real(s) >= 0 {
hardBits[i] = 1 hardBits[i] = 1
} else { } else {
hardBits[i] = 0 hardBits[i] = 0
@@ -138,85 +207,298 @@ func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
} }


// === Differential decoding === // === Differential decoding ===
bits := make([]int, len(hardBits)-1)
for i := 1; i < len(hardBits); i++ {
bits[i-1] = hardBits[i] ^ hardBits[i-1]
}

// === Block sync + CRC decode (try both polarities) ===
// Count block A CRC hits for diagnostics
blockAHits := 0
for i := 0; i+26 <= len(bits); i++ {
if _, ok := decodeBlock(bits[i : i+26]); ok {
blockAHits++
// Preserve the differential transition across Decode() calls. On the very
// first call we keep the historical behavior (N hard bits -> N-1 diff bits);
// once carry state exists we prepend the cross-chunk transition bit.
var bits []int
if len(hardBits) > 0 {
if d.hasLastHardBit {
bits = make([]int, len(hardBits))
bits[0] = hardBits[0] ^ d.lastHardBit
for i := 1; i < len(hardBits); i++ {
bits[i] = hardBits[i] ^ hardBits[i-1]
}
} else if len(hardBits) >= 2 {
bits = make([]int, len(hardBits)-1)
for i := 1; i < len(hardBits); i++ {
bits[i-1] = hardBits[i] ^ hardBits[i-1]
}
} }
d.lastHardBit = hardBits[len(hardBits)-1]
d.hasLastHardBit = true
} }
d.BlockAHits += blockAHits

found1 := d.tryDecode(bits)
invBits := make([]int, len(bits)) invBits := make([]int, len(bits))
for i, b := range bits { for i, b := range bits {
invBits[i] = 1 - b invBits[i] = 1 - b
} }
found2 := d.tryDecode(invBits)
if found1 || found2 {

// === Diagnostics before/after 1-bit FEC ===
rawBest := bestDiag(analyzeStream(bits, false, "dir"), analyzeStream(invBits, false, "inv"))
fecBest := bestDiag(analyzeStream(bits, true, "dir"), analyzeStream(invBits, true, "inv"))
d.BlockAHits += rawBest.offAHits

// === Block sync + CRC decode with conservative 1-bit FEC ===
groupsFound := d.tryDecode(bits, true)
usedPol := "dir"
if groupsFound == 0 {
groupsFound = d.tryDecode(invBits, true)
if groupsFound > 0 {
usedPol = "inv"
} else {
usedPol = "none"
}
}
if groupsFound > 0 {
d.GroupsFound++ d.GroupsFound++
} }


d.LastDiag = fmt.Sprintf("syms=%d sps=%.1f costasFreq=%.4f avgI=%.4f avgQ=%.4f blockAHits=%d groups=%d",
len(symbols), sps, freq, avgI, avgQ, blockAHits, d.GroupsFound)
d.LastDiag = fmt.Sprintf(
"syms=%d sps=%.1f mu=%.3f costasFreq=%.4f avgI=%.4f avgQ=%.4f diffCarry=%t raw[%s blk=%d A/B/C/Cp/D=%d/%d/%d/%d/%d AB=%d ABC=%d grp=%d pi=%04Xx%d] fec[%s blk=%d A/B/C/Cp/D=%d/%d/%d/%d/%d AB=%d ABC=%d grp=%d pi=%04Xx%d fixBlk=%d fixGrp=%d ambBlk=%d ambSeq=%d] use=%s found=%d okCalls=%d",
len(symbols), sps, d.syncMu, d.costasFreq, avgI, avgQ, d.hasLastHardBit,
rawBest.pol, rawBest.blockHits, rawBest.offAHits, rawBest.offBHits, rawBest.offCHits, rawBest.offCpHits, rawBest.offDHits, rawBest.abSeq, rawBest.abcSeq, rawBest.groups, rawBest.piHint, rawBest.piHintCount,
fecBest.pol, fecBest.blockHits, fecBest.offAHits, fecBest.offBHits, fecBest.offCHits, fecBest.offCpHits, fecBest.offDHits, fecBest.abSeq, fecBest.abcSeq, fecBest.groups, fecBest.piHint, fecBest.piHintCount, fecBest.fecBlockFix, fecBest.grpFecFix, fecBest.blockAmbig, fecBest.seqAmbig,
usedPol, groupsFound, d.GroupsFound,
)


return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()} return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
} }


func (d *Decoder) tryDecode(bits []int) bool {
found := false
for i := 0; i+26*4 <= len(bits); i++ {
bA, okA := decodeBlock(bits[i : i+26])
if !okA || bA.offset != offA {
func analyzeStream(bits []int, useFEC bool, pol string) scanDiag {
diag := scanDiag{pol: pol}
if len(bits) < 26 {
return diag
}

piCounts := make(map[uint16]int)
for i := 0; i+26 <= len(bits); i++ {
blk, ok, corrected, ambiguous := decodeBlockAny(bits[i:i+26], useFEC)
if ambiguous {
diag.blockAmbig++
}
if !ok {
continue continue
} }
bB, okB := decodeBlock(bits[i+26 : i+52])
if !okB || bB.offset != offB {
diag.blockHits++
if corrected {
diag.fecBlockFix++
}
switch blk.offset {
case offA:
diag.offAHits++
if blk.data != 0 {
piCounts[blk.data]++
}
case offB:
diag.offBHits++
case offC:
diag.offCHits++
case offCp:
diag.offCpHits++
case offD:
diag.offDHits++
}
}
for pi, n := range piCounts {
if n > diag.piHintCount {
diag.piHint = pi
diag.piHintCount = n
}
}

const groupBits = 26 * 4
for i := 0; i+groupBits <= len(bits); i++ {
fixes := 0

_, okA, fixedA, ambA := decodeBlockExpected(bits[i:i+26], []uint16{offA}, useFEC)
if ambA {
diag.seqAmbig++
}
if !okA {
continue continue
} }
bC, okC := decodeBlock(bits[i+52 : i+78])
if !okC || (bC.offset != offC && bC.offset != offCp) {
if fixedA {
fixes++
}

_, okB, fixedB, ambB := decodeBlockExpected(bits[i+26:i+52], []uint16{offB}, useFEC)
if ambB {
diag.seqAmbig++
}
if !okB {
continue continue
} }
bD, okD := decodeBlock(bits[i+78 : i+104])
if !okD || bD.offset != offD {
diag.abSeq++
if fixedB {
fixes++
}

_, okC, fixedC, ambC := decodeBlockExpected(bits[i+52:i+78], []uint16{offC, offCp}, useFEC)
if ambC {
diag.seqAmbig++
}
if !okC {
continue
}
diag.abcSeq++
if fixedC {
fixes++
}

_, okD, fixedD, ambD := decodeBlockExpected(bits[i+78:i+104], []uint16{offD}, useFEC)
if ambD {
diag.seqAmbig++
}
if !okD {
continue
}
if fixedD {
fixes++
}

diag.groups++
diag.grpFecFix += fixes
}

return diag
}

func (d *Decoder) tryDecode(bits []int, useFEC bool) int {
const (
groupBits = 26 * 4
flywheelJitter = 3
)
groups := 0
for i := 0; i+groupBits <= len(bits); {
grp, _, ok := decodeGroupAt(bits, i, useFEC)
if !ok {
i++
continue continue
} }
found = true
pi := bA.data
if pi != 0 {
d.lastPI = pi
}
groupType := (bB.data >> 12) & 0xF
versionA := ((bB.data >> 11) & 0x1) == 0
if groupType == 0 {
addr := bB.data & 0x3
if versionA {
chars := []byte{byte(bD.data >> 8), byte(bD.data & 0xFF)}
idx := int(addr) * 2
if idx+1 < len(d.ps) {
d.ps[idx] = sanitizeRune(chars[0])
d.ps[idx+1] = sanitizeRune(chars[1])

groups++
d.applyGroup(grp)

// Flywheel: once a valid group was found, prefer the next expected 104-bit
// boundary and search only in a tiny jitter window around it before falling
// back to full bitwise scanning.
nextExpected := i + groupBits
locked := true
for locked && nextExpected+groupBits <= len(bits) {
if nextGrp, _, ok := decodeGroupAt(bits, nextExpected, useFEC); ok {
d.applyGroup(nextGrp)
groups++
nextExpected += groupBits
continue
}

matched := false
for delta := 1; delta <= flywheelJitter && !matched; delta++ {
left := nextExpected - delta
if left >= 0 {
if nextGrp, _, ok := decodeGroupAt(bits, left, useFEC); ok {
d.applyGroup(nextGrp)
groups++
nextExpected = left + groupBits
matched = true
break
}
}
right := nextExpected + delta
if right+groupBits <= len(bits) {
if nextGrp, _, ok := decodeGroupAt(bits, right, useFEC); ok {
d.applyGroup(nextGrp)
groups++
nextExpected = right + groupBits
matched = true
break
}
} }
} }

if !matched {
locked = false
}
} }
if groupType == 2 && versionA {
addr := bB.data & 0xF
chars := []byte{byte(bC.data >> 8), byte(bC.data & 0xFF), byte(bD.data >> 8), byte(bD.data & 0xFF)}
idx := int(addr) * 4
for j := 0; j < 4 && idx+j < len(d.rt); j++ {
d.rt[idx+j] = sanitizeRune(chars[j])

resume := nextExpected - flywheelJitter
if resume <= i {
resume = i + 1
}
i = resume
}
return groups
}

func decodeGroupAt(bits []int, start int, useFEC bool) ([4]block, int, bool) {
const groupBits = 26 * 4
var grp [4]block
if start < 0 || start+groupBits > len(bits) {
return grp, 0, false
}
fixes := 0

bA, okA, fixedA, _ := decodeBlockExpected(bits[start:start+26], []uint16{offA}, useFEC)
if !okA {
return grp, 0, false
}
if fixedA {
fixes++
}
bB, okB, fixedB, _ := decodeBlockExpected(bits[start+26:start+52], []uint16{offB}, useFEC)
if !okB {
return grp, 0, false
}
if fixedB {
fixes++
}
bC, okC, fixedC, _ := decodeBlockExpected(bits[start+52:start+78], []uint16{offC, offCp}, useFEC)
if !okC {
return grp, 0, false
}
if fixedC {
fixes++
}
bD, okD, fixedD, _ := decodeBlockExpected(bits[start+78:start+104], []uint16{offD}, useFEC)
if !okD {
return grp, 0, false
}
if fixedD {
fixes++
}
grp[0] = bA
grp[1] = bB
grp[2] = bC
grp[3] = bD
return grp, fixes, true
}

func (d *Decoder) applyGroup(grp [4]block) {
bA, bB, bC, bD := grp[0], grp[1], grp[2], grp[3]
pi := bA.data
if pi != 0 {
d.lastPI = pi
}
groupType := (bB.data >> 12) & 0xF
versionA := ((bB.data >> 11) & 0x1) == 0
if groupType == 0 {
addr := bB.data & 0x3
if versionA {
chars := []byte{byte(bD.data >> 8), byte(bD.data & 0xFF)}
idx := int(addr) * 2
if idx+1 < len(d.ps) {
d.ps[idx] = sanitizeRune(chars[0])
d.ps[idx+1] = sanitizeRune(chars[1])
} }
} }
i += 103
} }
return found
if groupType == 2 && versionA {
addr := bB.data & 0xF
chars := []byte{byte(bC.data >> 8), byte(bC.data & 0xFF), byte(bD.data >> 8), byte(bD.data & 0xFF)}
idx := int(addr) * 4
for j := 0; j < 4 && idx+j < len(d.rt); j++ {
d.rt[idx+j] = sanitizeRune(chars[j])
}
}
} }


type block struct { type block struct {
@@ -232,14 +514,24 @@ const (
offD uint16 = 0x1B4 offD uint16 = 0x1B4
) )


var allOffsets = []uint16{offA, offB, offC, offCp, offD}

func decodeBlock(bits []int) (block, bool) { func decodeBlock(bits []int) (block, bool) {
if len(bits) != 26 { if len(bits) != 26 {
return block{}, false return block{}, false
} }
return decodeRawBlock(bitsToRaw(bits))
}

func bitsToRaw(bits []int) uint32 {
var raw uint32 var raw uint32
for _, b := range bits { for _, b := range bits {
raw = (raw << 1) | uint32(b&1) raw = (raw << 1) | uint32(b&1)
} }
return raw
}

func decodeRawBlock(raw uint32) (block, bool) {
data := uint16(raw >> 10) data := uint16(raw >> 10)
synd := crcSyndrome(raw) synd := crcSyndrome(raw)
switch synd { switch synd {
@@ -250,6 +542,51 @@ func decodeBlock(bits []int) (block, bool) {
} }
} }


func decodeBlockAny(bits []int, useFEC bool) (block, bool, bool, bool) {
return decodeBlockExpected(bits, allOffsets, useFEC)
}

func decodeBlockExpected(bits []int, allowed []uint16, useFEC bool) (block, bool, bool, bool) {
if len(bits) != 26 {
return block{}, false, false, false
}
if blk, ok := decodeBlock(bits); ok && offsetAllowed(blk.offset, allowed) {
return blk, true, false, false
}
if !useFEC {
return block{}, false, false, false
}

raw := bitsToRaw(bits)
var candidate block
candidateCount := 0
for i := 0; i < 26; i++ {
flipped := raw ^ (uint32(1) << uint(25-i))
blk, ok := decodeRawBlock(flipped)
if !ok || !offsetAllowed(blk.offset, allowed) {
continue
}
candidate = blk
candidateCount++
if candidateCount > 1 {
return block{}, false, false, true
}
}
if candidateCount == 1 {
return candidate, true, true, false
}
return block{}, false, false, false
}

func offsetAllowed(offset uint16, allowed []uint16) bool {
for _, want := range allowed {
if offset == want {
return true
}
}
return false
}

func crcSyndrome(raw uint32) uint16 { func crcSyndrome(raw uint32) uint16 {
poly := uint32(0x1B9) poly := uint32(0x1B9)
reg := raw reg := raw


BIN
sdr-visual-suite.rar Прегледај датотеку


Loading…
Откажи
Сачувај