Wideband autonomous SDR analysis engine forked from sdr-visual-suite
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

637 satır
14KB

  1. package rds
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. // Decoder performs RDS baseband decode with Costas loop carrier recovery
  7. // and Mueller & Muller symbol timing synchronization.
  8. type Decoder struct {
  9. ps [8]rune
  10. rt [64]rune
  11. lastPI uint16
  12. // Costas loop state (persistent across calls)
  13. costasPhase float64
  14. costasFreq float64
  15. // Symbol sync state
  16. syncMu float64
  17. syncPrev complex64
  18. syncPrevDecision complex64
  19. syncHasPrev bool
  20. lastSampleRate int
  21. // Differential decode state across Decode() calls
  22. lastHardBit int
  23. hasLastHardBit bool
  24. // Diagnostic counters
  25. TotalDecodes int
  26. BlockAHits int
  27. GroupsFound int
  28. LastDiag string
  29. }
  30. type Result struct {
  31. PI uint16 `json:"pi"`
  32. PS string `json:"ps"`
  33. RT string `json:"rt"`
  34. }
  35. type scanDiag struct {
  36. pol string
  37. blockHits int
  38. offAHits int
  39. offBHits int
  40. offCHits int
  41. offCpHits int
  42. offDHits int
  43. abSeq int
  44. abcSeq int
  45. groups int
  46. piHint uint16
  47. piHintCount int
  48. fecBlockFix int
  49. grpFecFix int
  50. blockAmbig int
  51. seqAmbig int
  52. }
  53. func (s scanDiag) score() int {
  54. return s.groups*100000 + s.abcSeq*1000 + s.abSeq*100 + s.blockHits
  55. }
  56. func bestDiag(a, b scanDiag) scanDiag {
  57. if b.score() > a.score() {
  58. return b
  59. }
  60. if b.score() == a.score() {
  61. if b.piHintCount > a.piHintCount {
  62. return b
  63. }
  64. if b.fecBlockFix > a.fecBlockFix {
  65. return b
  66. }
  67. }
  68. return a
  69. }
  70. // Decode takes complex baseband samples at ~20kHz and extracts RDS data.
  71. func (d *Decoder) Decode(samples []complex64, sampleRate int) Result {
  72. if len(samples) < 104 || sampleRate <= 0 {
  73. return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
  74. }
  75. d.TotalDecodes++
  76. if d.lastSampleRate != 0 && d.lastSampleRate != sampleRate {
  77. d.costasPhase = 0
  78. d.costasFreq = 0
  79. d.syncMu = 0
  80. d.syncPrev = 0
  81. d.syncPrevDecision = 0
  82. d.syncHasPrev = false
  83. d.lastHardBit = 0
  84. d.hasLastHardBit = false
  85. }
  86. d.lastSampleRate = sampleRate
  87. sps := float64(sampleRate) / 1187.5 // samples per symbol
  88. // === Mueller & Muller symbol timing recovery (persistent across calls) ===
  89. mu := d.syncMu
  90. if mu < 0 || mu >= sps || math.IsNaN(mu) || math.IsInf(mu, 0) {
  91. mu = sps / 2
  92. }
  93. symbols := make([]complex64, 0, len(samples)/int(sps)+1)
  94. prev := d.syncPrev
  95. prevDecision := d.syncPrevDecision
  96. havePrev := d.syncHasPrev
  97. for mu < float64(len(samples)-1) {
  98. idx := int(mu)
  99. frac := mu - float64(idx)
  100. if idx+1 >= len(samples) {
  101. break
  102. }
  103. samp := complex64(complex(
  104. float64(real(samples[idx]))*(1-frac)+float64(real(samples[idx+1]))*frac,
  105. float64(imag(samples[idx]))*(1-frac)+float64(imag(samples[idx+1]))*frac,
  106. ))
  107. var decision complex64
  108. if real(samp) >= 0 {
  109. decision = 1
  110. } else {
  111. decision = -1
  112. }
  113. if havePrev {
  114. errR := float64(real(decision)-real(prevDecision))*float64(real(prev)) -
  115. float64(real(samp)-real(prev))*float64(real(prevDecision))
  116. mu += sps + 0.01*errR
  117. } else {
  118. mu += sps
  119. havePrev = true
  120. }
  121. prevDecision = decision
  122. prev = samp
  123. symbols = append(symbols, samp)
  124. }
  125. residualMu := mu - float64(len(samples))
  126. for residualMu < 0 {
  127. residualMu += sps
  128. }
  129. for residualMu >= sps {
  130. residualMu -= sps
  131. }
  132. d.syncMu = residualMu
  133. d.syncPrev = prev
  134. d.syncPrevDecision = prevDecision
  135. d.syncHasPrev = havePrev
  136. if len(symbols) < 26*4 {
  137. d.LastDiag = fmt.Sprintf("too few symbols: %d sps=%.1f mu=%.3f", len(symbols), sps, d.syncMu)
  138. return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
  139. }
  140. // === Costas loop for fine frequency/phase synchronization (persistent across calls) ===
  141. alpha := 0.132
  142. beta := alpha * alpha / 4.0
  143. phase := d.costasPhase
  144. freq := d.costasFreq
  145. synced := make([]complex64, len(symbols))
  146. for i, s := range symbols {
  147. // Multiply by exp(-j*phase) to de-rotate.
  148. cosP := float32(math.Cos(phase))
  149. sinP := float32(math.Sin(phase))
  150. synced[i] = complex(
  151. real(s)*cosP+imag(s)*sinP,
  152. imag(s)*cosP-real(s)*sinP,
  153. )
  154. // BPSK phase error: sign(I) * Q.
  155. var err float64
  156. if real(synced[i]) >= 0 {
  157. err = float64(imag(synced[i]))
  158. } else {
  159. err = -float64(imag(synced[i]))
  160. }
  161. freq += beta * err
  162. phase += freq + alpha*err
  163. for phase > math.Pi {
  164. phase -= 2 * math.Pi
  165. }
  166. for phase < -math.Pi {
  167. phase += 2 * math.Pi
  168. }
  169. }
  170. d.costasPhase = phase
  171. d.costasFreq = freq
  172. // Measure signal quality: average |I| and |Q| after Costas.
  173. var sumI, sumQ float64
  174. for _, s := range synced {
  175. ri := math.Abs(float64(real(s)))
  176. rq := math.Abs(float64(imag(s)))
  177. sumI += ri
  178. sumQ += rq
  179. }
  180. avgI := sumI / float64(len(synced))
  181. avgQ := sumQ / float64(len(synced))
  182. // === BPSK demodulation ===
  183. hardBits := make([]int, len(synced))
  184. for i, s := range synced {
  185. if real(s) >= 0 {
  186. hardBits[i] = 1
  187. } else {
  188. hardBits[i] = 0
  189. }
  190. }
  191. // === Differential decoding ===
  192. // Preserve the differential transition across Decode() calls. On the very
  193. // first call we keep the historical behavior (N hard bits -> N-1 diff bits);
  194. // once carry state exists we prepend the cross-chunk transition bit.
  195. var bits []int
  196. if len(hardBits) > 0 {
  197. if d.hasLastHardBit {
  198. bits = make([]int, len(hardBits))
  199. bits[0] = hardBits[0] ^ d.lastHardBit
  200. for i := 1; i < len(hardBits); i++ {
  201. bits[i] = hardBits[i] ^ hardBits[i-1]
  202. }
  203. } else if len(hardBits) >= 2 {
  204. bits = make([]int, len(hardBits)-1)
  205. for i := 1; i < len(hardBits); i++ {
  206. bits[i-1] = hardBits[i] ^ hardBits[i-1]
  207. }
  208. }
  209. d.lastHardBit = hardBits[len(hardBits)-1]
  210. d.hasLastHardBit = true
  211. }
  212. invBits := make([]int, len(bits))
  213. for i, b := range bits {
  214. invBits[i] = 1 - b
  215. }
  216. // === Diagnostics before/after 1-bit FEC ===
  217. rawBest := bestDiag(analyzeStream(bits, false, "dir"), analyzeStream(invBits, false, "inv"))
  218. fecBest := bestDiag(analyzeStream(bits, true, "dir"), analyzeStream(invBits, true, "inv"))
  219. d.BlockAHits += rawBest.offAHits
  220. // === Block sync + CRC decode with conservative 1-bit FEC ===
  221. groupsFound := d.tryDecode(bits, true)
  222. usedPol := "dir"
  223. if groupsFound == 0 {
  224. groupsFound = d.tryDecode(invBits, true)
  225. if groupsFound > 0 {
  226. usedPol = "inv"
  227. } else {
  228. usedPol = "none"
  229. }
  230. }
  231. if groupsFound > 0 {
  232. d.GroupsFound++
  233. }
  234. d.LastDiag = fmt.Sprintf(
  235. "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",
  236. len(symbols), sps, d.syncMu, d.costasFreq, avgI, avgQ, d.hasLastHardBit,
  237. rawBest.pol, rawBest.blockHits, rawBest.offAHits, rawBest.offBHits, rawBest.offCHits, rawBest.offCpHits, rawBest.offDHits, rawBest.abSeq, rawBest.abcSeq, rawBest.groups, rawBest.piHint, rawBest.piHintCount,
  238. 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,
  239. usedPol, groupsFound, d.GroupsFound,
  240. )
  241. return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
  242. }
  243. func analyzeStream(bits []int, useFEC bool, pol string) scanDiag {
  244. diag := scanDiag{pol: pol}
  245. if len(bits) < 26 {
  246. return diag
  247. }
  248. piCounts := make(map[uint16]int)
  249. for i := 0; i+26 <= len(bits); i++ {
  250. blk, ok, corrected, ambiguous := decodeBlockAny(bits[i:i+26], useFEC)
  251. if ambiguous {
  252. diag.blockAmbig++
  253. }
  254. if !ok {
  255. continue
  256. }
  257. diag.blockHits++
  258. if corrected {
  259. diag.fecBlockFix++
  260. }
  261. switch blk.offset {
  262. case offA:
  263. diag.offAHits++
  264. if blk.data != 0 {
  265. piCounts[blk.data]++
  266. }
  267. case offB:
  268. diag.offBHits++
  269. case offC:
  270. diag.offCHits++
  271. case offCp:
  272. diag.offCpHits++
  273. case offD:
  274. diag.offDHits++
  275. }
  276. }
  277. for pi, n := range piCounts {
  278. if n > diag.piHintCount {
  279. diag.piHint = pi
  280. diag.piHintCount = n
  281. }
  282. }
  283. const groupBits = 26 * 4
  284. for i := 0; i+groupBits <= len(bits); i++ {
  285. fixes := 0
  286. _, okA, fixedA, ambA := decodeBlockExpected(bits[i:i+26], []uint16{offA}, useFEC)
  287. if ambA {
  288. diag.seqAmbig++
  289. }
  290. if !okA {
  291. continue
  292. }
  293. if fixedA {
  294. fixes++
  295. }
  296. _, okB, fixedB, ambB := decodeBlockExpected(bits[i+26:i+52], []uint16{offB}, useFEC)
  297. if ambB {
  298. diag.seqAmbig++
  299. }
  300. if !okB {
  301. continue
  302. }
  303. diag.abSeq++
  304. if fixedB {
  305. fixes++
  306. }
  307. _, okC, fixedC, ambC := decodeBlockExpected(bits[i+52:i+78], []uint16{offC, offCp}, useFEC)
  308. if ambC {
  309. diag.seqAmbig++
  310. }
  311. if !okC {
  312. continue
  313. }
  314. diag.abcSeq++
  315. if fixedC {
  316. fixes++
  317. }
  318. _, okD, fixedD, ambD := decodeBlockExpected(bits[i+78:i+104], []uint16{offD}, useFEC)
  319. if ambD {
  320. diag.seqAmbig++
  321. }
  322. if !okD {
  323. continue
  324. }
  325. if fixedD {
  326. fixes++
  327. }
  328. diag.groups++
  329. diag.grpFecFix += fixes
  330. }
  331. return diag
  332. }
  333. func (d *Decoder) tryDecode(bits []int, useFEC bool) int {
  334. const (
  335. groupBits = 26 * 4
  336. flywheelJitter = 3
  337. )
  338. groups := 0
  339. for i := 0; i+groupBits <= len(bits); {
  340. grp, _, ok := decodeGroupAt(bits, i, useFEC)
  341. if !ok {
  342. i++
  343. continue
  344. }
  345. groups++
  346. d.applyGroup(grp)
  347. // Flywheel: once a valid group was found, prefer the next expected 104-bit
  348. // boundary and search only in a tiny jitter window around it before falling
  349. // back to full bitwise scanning.
  350. nextExpected := i + groupBits
  351. locked := true
  352. for locked && nextExpected+groupBits <= len(bits) {
  353. if nextGrp, _, ok := decodeGroupAt(bits, nextExpected, useFEC); ok {
  354. d.applyGroup(nextGrp)
  355. groups++
  356. nextExpected += groupBits
  357. continue
  358. }
  359. matched := false
  360. for delta := 1; delta <= flywheelJitter && !matched; delta++ {
  361. left := nextExpected - delta
  362. if left >= 0 {
  363. if nextGrp, _, ok := decodeGroupAt(bits, left, useFEC); ok {
  364. d.applyGroup(nextGrp)
  365. groups++
  366. nextExpected = left + groupBits
  367. matched = true
  368. break
  369. }
  370. }
  371. right := nextExpected + delta
  372. if right+groupBits <= len(bits) {
  373. if nextGrp, _, ok := decodeGroupAt(bits, right, useFEC); ok {
  374. d.applyGroup(nextGrp)
  375. groups++
  376. nextExpected = right + groupBits
  377. matched = true
  378. break
  379. }
  380. }
  381. }
  382. if !matched {
  383. locked = false
  384. }
  385. }
  386. resume := nextExpected - flywheelJitter
  387. if resume <= i {
  388. resume = i + 1
  389. }
  390. i = resume
  391. }
  392. return groups
  393. }
  394. func decodeGroupAt(bits []int, start int, useFEC bool) ([4]block, int, bool) {
  395. const groupBits = 26 * 4
  396. var grp [4]block
  397. if start < 0 || start+groupBits > len(bits) {
  398. return grp, 0, false
  399. }
  400. fixes := 0
  401. bA, okA, fixedA, _ := decodeBlockExpected(bits[start:start+26], []uint16{offA}, useFEC)
  402. if !okA {
  403. return grp, 0, false
  404. }
  405. if fixedA {
  406. fixes++
  407. }
  408. bB, okB, fixedB, _ := decodeBlockExpected(bits[start+26:start+52], []uint16{offB}, useFEC)
  409. if !okB {
  410. return grp, 0, false
  411. }
  412. if fixedB {
  413. fixes++
  414. }
  415. bC, okC, fixedC, _ := decodeBlockExpected(bits[start+52:start+78], []uint16{offC, offCp}, useFEC)
  416. if !okC {
  417. return grp, 0, false
  418. }
  419. if fixedC {
  420. fixes++
  421. }
  422. bD, okD, fixedD, _ := decodeBlockExpected(bits[start+78:start+104], []uint16{offD}, useFEC)
  423. if !okD {
  424. return grp, 0, false
  425. }
  426. if fixedD {
  427. fixes++
  428. }
  429. grp[0] = bA
  430. grp[1] = bB
  431. grp[2] = bC
  432. grp[3] = bD
  433. return grp, fixes, true
  434. }
  435. func (d *Decoder) applyGroup(grp [4]block) {
  436. bA, bB, bC, bD := grp[0], grp[1], grp[2], grp[3]
  437. pi := bA.data
  438. if pi != 0 {
  439. d.lastPI = pi
  440. }
  441. groupType := (bB.data >> 12) & 0xF
  442. versionA := ((bB.data >> 11) & 0x1) == 0
  443. if groupType == 0 {
  444. addr := bB.data & 0x3
  445. if versionA {
  446. chars := []byte{byte(bD.data >> 8), byte(bD.data & 0xFF)}
  447. idx := int(addr) * 2
  448. if idx+1 < len(d.ps) {
  449. d.ps[idx] = sanitizeRune(chars[0])
  450. d.ps[idx+1] = sanitizeRune(chars[1])
  451. }
  452. }
  453. }
  454. if groupType == 2 && versionA {
  455. addr := bB.data & 0xF
  456. chars := []byte{byte(bC.data >> 8), byte(bC.data & 0xFF), byte(bD.data >> 8), byte(bD.data & 0xFF)}
  457. idx := int(addr) * 4
  458. for j := 0; j < 4 && idx+j < len(d.rt); j++ {
  459. d.rt[idx+j] = sanitizeRune(chars[j])
  460. }
  461. }
  462. }
  463. type block struct {
  464. data uint16
  465. offset uint16
  466. }
  467. const (
  468. offA uint16 = 0x0FC
  469. offB uint16 = 0x198
  470. offC uint16 = 0x168
  471. offCp uint16 = 0x350
  472. offD uint16 = 0x1B4
  473. )
  474. var allOffsets = []uint16{offA, offB, offC, offCp, offD}
  475. func decodeBlock(bits []int) (block, bool) {
  476. if len(bits) != 26 {
  477. return block{}, false
  478. }
  479. return decodeRawBlock(bitsToRaw(bits))
  480. }
  481. func bitsToRaw(bits []int) uint32 {
  482. var raw uint32
  483. for _, b := range bits {
  484. raw = (raw << 1) | uint32(b&1)
  485. }
  486. return raw
  487. }
  488. func decodeRawBlock(raw uint32) (block, bool) {
  489. data := uint16(raw >> 10)
  490. synd := crcSyndrome(raw)
  491. switch synd {
  492. case offA, offB, offC, offCp, offD:
  493. return block{data: data, offset: synd}, true
  494. default:
  495. return block{}, false
  496. }
  497. }
  498. func decodeBlockAny(bits []int, useFEC bool) (block, bool, bool, bool) {
  499. return decodeBlockExpected(bits, allOffsets, useFEC)
  500. }
  501. func decodeBlockExpected(bits []int, allowed []uint16, useFEC bool) (block, bool, bool, bool) {
  502. if len(bits) != 26 {
  503. return block{}, false, false, false
  504. }
  505. if blk, ok := decodeBlock(bits); ok && offsetAllowed(blk.offset, allowed) {
  506. return blk, true, false, false
  507. }
  508. if !useFEC {
  509. return block{}, false, false, false
  510. }
  511. raw := bitsToRaw(bits)
  512. var candidate block
  513. candidateCount := 0
  514. for i := 0; i < 26; i++ {
  515. flipped := raw ^ (uint32(1) << uint(25-i))
  516. blk, ok := decodeRawBlock(flipped)
  517. if !ok || !offsetAllowed(blk.offset, allowed) {
  518. continue
  519. }
  520. candidate = blk
  521. candidateCount++
  522. if candidateCount > 1 {
  523. return block{}, false, false, true
  524. }
  525. }
  526. if candidateCount == 1 {
  527. return candidate, true, true, false
  528. }
  529. return block{}, false, false, false
  530. }
  531. func offsetAllowed(offset uint16, allowed []uint16) bool {
  532. for _, want := range allowed {
  533. if offset == want {
  534. return true
  535. }
  536. }
  537. return false
  538. }
  539. func crcSyndrome(raw uint32) uint16 {
  540. poly := uint32(0x1B9)
  541. reg := raw
  542. for i := 25; i >= 10; i-- {
  543. if (reg>>uint(i))&1 == 1 {
  544. reg ^= poly << uint(i-10)
  545. }
  546. }
  547. return uint16(reg & 0x3FF)
  548. }
  549. func sanitizeRune(b byte) rune {
  550. if b < 32 || b > 126 {
  551. return ' '
  552. }
  553. return rune(b)
  554. }
  555. func (d *Decoder) psString() string {
  556. out := make([]rune, 0, len(d.ps))
  557. for _, r := range d.ps {
  558. if r == 0 {
  559. r = ' '
  560. }
  561. out = append(out, r)
  562. }
  563. return trimRight(out)
  564. }
  565. func (d *Decoder) rtString() string {
  566. out := make([]rune, 0, len(d.rt))
  567. for _, r := range d.rt {
  568. if r == 0 {
  569. r = ' '
  570. }
  571. out = append(out, r)
  572. }
  573. return trimRight(out)
  574. }
  575. func trimRight(in []rune) string {
  576. end := len(in)
  577. for end > 0 && in[end-1] == ' ' {
  578. end--
  579. }
  580. return string(in[:end])
  581. }