Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

501 wiersze
15KB

  1. package main
  2. import (
  3. "math"
  4. "strings"
  5. "sync"
  6. "sync/atomic"
  7. "time"
  8. "sdr-wideband-suite/internal/classifier"
  9. "sdr-wideband-suite/internal/config"
  10. "sdr-wideband-suite/internal/demod"
  11. "sdr-wideband-suite/internal/detector"
  12. "sdr-wideband-suite/internal/dsp"
  13. fftutil "sdr-wideband-suite/internal/fft"
  14. "sdr-wideband-suite/internal/fft/gpufft"
  15. "sdr-wideband-suite/internal/pipeline"
  16. "sdr-wideband-suite/internal/rds"
  17. "sdr-wideband-suite/internal/recorder"
  18. )
  19. type rdsState struct {
  20. dec rds.Decoder
  21. result rds.Result
  22. lastDecode time.Time
  23. busy int32
  24. mu sync.Mutex
  25. }
  26. type dspRuntime struct {
  27. cfg config.Config
  28. det *detector.Detector
  29. window []float64
  30. plan *fftutil.CmplxPlan
  31. dcEnabled bool
  32. iqEnabled bool
  33. useGPU bool
  34. gpuEngine *gpufft.Engine
  35. rdsMap map[int64]*rdsState
  36. streamPhaseState map[int64]*streamExtractState
  37. streamOverlap *streamIQOverlap
  38. decisionQueues *decisionQueues
  39. queueStats decisionQueueStats
  40. gotSamples bool
  41. }
  42. type spectrumArtifacts struct {
  43. allIQ []complex64
  44. iq []complex64
  45. spectrum []float64
  46. finished []detector.Event
  47. detected []detector.Signal
  48. thresholds []float64
  49. noiseFloor float64
  50. now time.Time
  51. }
  52. func newDSPRuntime(cfg config.Config, det *detector.Detector, window []float64, gpuState *gpuStatus) *dspRuntime {
  53. rt := &dspRuntime{
  54. cfg: cfg,
  55. det: det,
  56. window: window,
  57. plan: fftutil.NewCmplxPlan(cfg.FFTSize),
  58. dcEnabled: cfg.DCBlock,
  59. iqEnabled: cfg.IQBalance,
  60. useGPU: cfg.UseGPUFFT,
  61. rdsMap: map[int64]*rdsState{},
  62. streamPhaseState: map[int64]*streamExtractState{},
  63. streamOverlap: &streamIQOverlap{},
  64. }
  65. if rt.useGPU && gpuState != nil {
  66. snap := gpuState.snapshot()
  67. if snap.Available {
  68. if eng, err := gpufft.New(cfg.FFTSize); err == nil {
  69. rt.gpuEngine = eng
  70. gpuState.set(true, nil)
  71. } else {
  72. gpuState.set(false, err)
  73. rt.useGPU = false
  74. }
  75. }
  76. }
  77. return rt
  78. }
  79. func (rt *dspRuntime) applyUpdate(upd dspUpdate, srcMgr *sourceManager, rec *recorder.Manager, gpuState *gpuStatus) {
  80. prevFFT := rt.cfg.FFTSize
  81. prevUseGPU := rt.useGPU
  82. rt.cfg = upd.cfg
  83. if rec != nil {
  84. rec.Update(rt.cfg.SampleRate, rt.cfg.FFTSize, recorder.Policy{
  85. Enabled: rt.cfg.Recorder.Enabled,
  86. MinSNRDb: rt.cfg.Recorder.MinSNRDb,
  87. MinDuration: mustParseDuration(rt.cfg.Recorder.MinDuration, 1*time.Second),
  88. MaxDuration: mustParseDuration(rt.cfg.Recorder.MaxDuration, 300*time.Second),
  89. PrerollMs: rt.cfg.Recorder.PrerollMs,
  90. RecordIQ: rt.cfg.Recorder.RecordIQ,
  91. RecordAudio: rt.cfg.Recorder.RecordAudio,
  92. AutoDemod: rt.cfg.Recorder.AutoDemod,
  93. AutoDecode: rt.cfg.Recorder.AutoDecode,
  94. MaxDiskMB: rt.cfg.Recorder.MaxDiskMB,
  95. OutputDir: rt.cfg.Recorder.OutputDir,
  96. ClassFilter: rt.cfg.Recorder.ClassFilter,
  97. RingSeconds: rt.cfg.Recorder.RingSeconds,
  98. DeemphasisUs: rt.cfg.Recorder.DeemphasisUs,
  99. ExtractionTaps: rt.cfg.Recorder.ExtractionTaps,
  100. ExtractionBwMult: rt.cfg.Recorder.ExtractionBwMult,
  101. }, rt.cfg.CenterHz, buildDecoderMap(rt.cfg))
  102. }
  103. if upd.det != nil {
  104. rt.det = upd.det
  105. }
  106. if upd.window != nil {
  107. rt.window = upd.window
  108. rt.plan = fftutil.NewCmplxPlan(rt.cfg.FFTSize)
  109. }
  110. rt.dcEnabled = upd.dcBlock
  111. rt.iqEnabled = upd.iqBalance
  112. if rt.cfg.FFTSize != prevFFT || rt.cfg.UseGPUFFT != prevUseGPU {
  113. srcMgr.Flush()
  114. rt.gotSamples = false
  115. if rt.gpuEngine != nil {
  116. rt.gpuEngine.Close()
  117. rt.gpuEngine = nil
  118. }
  119. rt.useGPU = rt.cfg.UseGPUFFT
  120. if rt.useGPU && gpuState != nil {
  121. snap := gpuState.snapshot()
  122. if snap.Available {
  123. if eng, err := gpufft.New(rt.cfg.FFTSize); err == nil {
  124. rt.gpuEngine = eng
  125. gpuState.set(true, nil)
  126. } else {
  127. gpuState.set(false, err)
  128. rt.useGPU = false
  129. }
  130. } else {
  131. gpuState.set(false, nil)
  132. rt.useGPU = false
  133. }
  134. } else if gpuState != nil {
  135. gpuState.set(false, nil)
  136. }
  137. }
  138. }
  139. func (rt *dspRuntime) captureSpectrum(srcMgr *sourceManager, rec *recorder.Manager, dcBlocker *dsp.DCBlocker, gpuState *gpuStatus) (*spectrumArtifacts, error) {
  140. available := rt.cfg.FFTSize
  141. st := srcMgr.Stats()
  142. if st.BufferSamples > rt.cfg.FFTSize {
  143. available = (st.BufferSamples / rt.cfg.FFTSize) * rt.cfg.FFTSize
  144. if available < rt.cfg.FFTSize {
  145. available = rt.cfg.FFTSize
  146. }
  147. }
  148. allIQ, err := srcMgr.ReadIQ(available)
  149. if err != nil {
  150. return nil, err
  151. }
  152. if rec != nil {
  153. rec.Ingest(time.Now(), allIQ)
  154. }
  155. iq := allIQ
  156. if len(allIQ) > rt.cfg.FFTSize {
  157. iq = allIQ[len(allIQ)-rt.cfg.FFTSize:]
  158. }
  159. if rt.dcEnabled {
  160. dcBlocker.Apply(iq)
  161. }
  162. if rt.iqEnabled {
  163. dsp.IQBalance(iq)
  164. }
  165. var spectrum []float64
  166. if rt.useGPU && rt.gpuEngine != nil {
  167. gpuBuf := make([]complex64, len(iq))
  168. if len(rt.window) == len(iq) {
  169. for i := 0; i < len(iq); i++ {
  170. v := iq[i]
  171. w := float32(rt.window[i])
  172. gpuBuf[i] = complex(real(v)*w, imag(v)*w)
  173. }
  174. } else {
  175. copy(gpuBuf, iq)
  176. }
  177. out, err := rt.gpuEngine.Exec(gpuBuf)
  178. if err != nil {
  179. if gpuState != nil {
  180. gpuState.set(false, err)
  181. }
  182. rt.useGPU = false
  183. spectrum = fftutil.SpectrumWithPlan(gpuBuf, nil, rt.plan)
  184. } else {
  185. spectrum = fftutil.SpectrumFromFFT(out)
  186. }
  187. } else {
  188. spectrum = fftutil.SpectrumWithPlan(iq, rt.window, rt.plan)
  189. }
  190. for i := range spectrum {
  191. if math.IsNaN(spectrum[i]) || math.IsInf(spectrum[i], 0) {
  192. spectrum[i] = -200
  193. }
  194. }
  195. now := time.Now()
  196. finished, detected := rt.det.Process(now, spectrum, rt.cfg.CenterHz)
  197. return &spectrumArtifacts{
  198. allIQ: allIQ,
  199. iq: iq,
  200. spectrum: spectrum,
  201. finished: finished,
  202. detected: detected,
  203. thresholds: rt.det.LastThresholds(),
  204. noiseFloor: rt.det.LastNoiseFloor(),
  205. now: now,
  206. }, nil
  207. }
  208. func (rt *dspRuntime) buildSurveillanceResult(art *spectrumArtifacts) pipeline.SurveillanceResult {
  209. if art == nil {
  210. return pipeline.SurveillanceResult{}
  211. }
  212. policy := pipeline.PolicyFromConfig(rt.cfg)
  213. candidates := pipeline.CandidatesFromSignals(art.detected, "surveillance-detector")
  214. scheduled := pipeline.ScheduleCandidates(candidates, policy)
  215. level := pipeline.AnalysisLevel{
  216. Name: "surveillance",
  217. SampleRate: rt.cfg.SampleRate,
  218. FFTSize: rt.cfg.Surveillance.AnalysisFFTSize,
  219. CenterHz: rt.cfg.CenterHz,
  220. SpanHz: float64(rt.cfg.SampleRate),
  221. Source: "baseband",
  222. }
  223. lowRate := rt.cfg.SampleRate / 2
  224. lowFFT := rt.cfg.Surveillance.AnalysisFFTSize / 2
  225. if lowRate < 200000 {
  226. lowRate = rt.cfg.SampleRate
  227. }
  228. if lowFFT < 256 {
  229. lowFFT = rt.cfg.Surveillance.AnalysisFFTSize
  230. }
  231. lowLevel := pipeline.AnalysisLevel{
  232. Name: "surveillance-lowres",
  233. SampleRate: lowRate,
  234. FFTSize: lowFFT,
  235. CenterHz: rt.cfg.CenterHz,
  236. SpanHz: float64(lowRate),
  237. Source: "downsampled",
  238. }
  239. displayLevel := pipeline.AnalysisLevel{
  240. Name: "presentation",
  241. SampleRate: rt.cfg.SampleRate,
  242. FFTSize: rt.cfg.Surveillance.DisplayBins,
  243. CenterHz: rt.cfg.CenterHz,
  244. SpanHz: float64(rt.cfg.SampleRate),
  245. Source: "display",
  246. }
  247. levels := []pipeline.AnalysisLevel{level}
  248. if lowLevel.SampleRate != level.SampleRate || lowLevel.FFTSize != level.FFTSize {
  249. levels = append(levels, lowLevel)
  250. }
  251. return pipeline.SurveillanceResult{
  252. Level: level,
  253. Levels: levels,
  254. DisplayLevel: displayLevel,
  255. Candidates: candidates,
  256. Scheduled: scheduled,
  257. Finished: art.finished,
  258. Signals: art.detected,
  259. NoiseFloor: art.noiseFloor,
  260. Thresholds: art.thresholds,
  261. }
  262. }
  263. func (rt *dspRuntime) buildRefinementInput(surv pipeline.SurveillanceResult) pipeline.RefinementInput {
  264. policy := pipeline.PolicyFromConfig(rt.cfg)
  265. plan := pipeline.BuildRefinementPlan(surv.Candidates, policy)
  266. scheduled := append([]pipeline.ScheduledCandidate(nil), surv.Scheduled...)
  267. if len(scheduled) == 0 && len(plan.Selected) > 0 {
  268. scheduled = append([]pipeline.ScheduledCandidate(nil), plan.Selected...)
  269. }
  270. windows := make([]pipeline.RefinementWindow, 0, len(scheduled))
  271. for _, sc := range scheduled {
  272. span := sc.Candidate.BandwidthHz
  273. windowSource := "candidate"
  274. if policy.RefinementAutoSpan && (span <= 0 || span < 2000 || span > 400000) {
  275. autoSpan, autoSource := pipeline.AutoSpanForHint(sc.Candidate.Hint)
  276. if autoSpan > 0 {
  277. span = autoSpan
  278. windowSource = autoSource
  279. }
  280. }
  281. if policy.RefinementMinSpanHz > 0 && span < policy.RefinementMinSpanHz {
  282. span = policy.RefinementMinSpanHz
  283. }
  284. if policy.RefinementMaxSpanHz > 0 && span > policy.RefinementMaxSpanHz {
  285. span = policy.RefinementMaxSpanHz
  286. }
  287. if span <= 0 {
  288. span = 12000
  289. }
  290. windows = append(windows, pipeline.RefinementWindow{
  291. CenterHz: sc.Candidate.CenterHz,
  292. SpanHz: span,
  293. Source: windowSource,
  294. })
  295. }
  296. level := pipeline.AnalysisLevel{
  297. Name: "refinement",
  298. SampleRate: rt.cfg.SampleRate,
  299. FFTSize: rt.cfg.FFTSize,
  300. CenterHz: rt.cfg.CenterHz,
  301. SpanHz: float64(rt.cfg.SampleRate),
  302. Source: "refinement-window",
  303. }
  304. input := pipeline.RefinementInput{
  305. Level: level,
  306. Candidates: append([]pipeline.Candidate(nil), surv.Candidates...),
  307. Scheduled: scheduled,
  308. Plan: plan,
  309. Windows: windows,
  310. SampleRate: rt.cfg.SampleRate,
  311. FFTSize: rt.cfg.FFTSize,
  312. CenterHz: rt.cfg.CenterHz,
  313. Source: "surveillance-detector",
  314. }
  315. if !policy.RefinementEnabled {
  316. input.Scheduled = nil
  317. }
  318. return input
  319. }
  320. func (rt *dspRuntime) refineSignals(art *spectrumArtifacts, input pipeline.RefinementInput, extractMgr *extractionManager, rec *recorder.Manager) pipeline.RefinementResult {
  321. if art == nil || len(art.iq) == 0 || len(input.Scheduled) == 0 {
  322. return pipeline.RefinementResult{}
  323. }
  324. policy := pipeline.PolicyFromConfig(rt.cfg)
  325. selectedCandidates := make([]pipeline.Candidate, 0, len(input.Scheduled))
  326. selectedSignals := make([]detector.Signal, 0, len(input.Scheduled))
  327. for _, sc := range input.Scheduled {
  328. selectedCandidates = append(selectedCandidates, sc.Candidate)
  329. selectedSignals = append(selectedSignals, detector.Signal{
  330. ID: sc.Candidate.ID,
  331. FirstBin: sc.Candidate.FirstBin,
  332. LastBin: sc.Candidate.LastBin,
  333. CenterHz: sc.Candidate.CenterHz,
  334. BWHz: sc.Candidate.BandwidthHz,
  335. PeakDb: sc.Candidate.PeakDb,
  336. SNRDb: sc.Candidate.SNRDb,
  337. NoiseDb: sc.Candidate.NoiseDb,
  338. })
  339. }
  340. sampleRate := input.SampleRate
  341. fftSize := input.FFTSize
  342. centerHz := input.CenterHz
  343. if sampleRate <= 0 {
  344. sampleRate = rt.cfg.SampleRate
  345. }
  346. if fftSize <= 0 {
  347. fftSize = rt.cfg.FFTSize
  348. }
  349. if centerHz == 0 {
  350. centerHz = rt.cfg.CenterHz
  351. }
  352. snips, snipRates := extractSignalIQBatch(extractMgr, art.iq, sampleRate, centerHz, selectedSignals)
  353. refined := pipeline.RefineCandidates(selectedCandidates, input.Windows, art.spectrum, sampleRate, fftSize, snips, snipRates, classifier.ClassifierMode(rt.cfg.ClassifierMode))
  354. signals := make([]detector.Signal, 0, len(refined))
  355. decisions := make([]pipeline.SignalDecision, 0, len(refined))
  356. for i, ref := range refined {
  357. sig := ref.Signal
  358. signals = append(signals, sig)
  359. cls := sig.Class
  360. snipRate := ref.SnippetRate
  361. decision := pipeline.DecideSignalAction(policy, ref.Candidate, cls)
  362. decisions = append(decisions, decision)
  363. if cls != nil {
  364. pll := classifier.PLLResult{}
  365. if i < len(snips) && snips[i] != nil && len(snips[i]) > 256 {
  366. pll = classifier.EstimateExactFrequency(snips[i], snipRate, signals[i].CenterHz, cls.ModType)
  367. cls.PLL = &pll
  368. signals[i].PLL = &pll
  369. if cls.ModType == classifier.ClassWFM && pll.Stereo {
  370. cls.ModType = classifier.ClassWFMStereo
  371. }
  372. }
  373. if (cls.ModType == classifier.ClassWFM || cls.ModType == classifier.ClassWFMStereo) && rec != nil {
  374. rt.updateRDS(art.now, rec, &signals[i], cls)
  375. }
  376. }
  377. }
  378. maxRecord := rt.cfg.Resources.MaxRecordingStreams
  379. maxDecode := rt.cfg.Resources.MaxDecodeJobs
  380. hold := time.Duration(rt.cfg.Resources.DecisionHoldMs) * time.Millisecond
  381. queueStats := rt.decisionQueues.Apply(decisions, maxRecord, maxDecode, hold, art.now, policy)
  382. rt.queueStats = queueStats
  383. summary := summarizeDecisions(decisions)
  384. if rec != nil {
  385. if summary.RecordEnabled > 0 {
  386. rt.cfg.Recorder.Enabled = true
  387. }
  388. if summary.DecodeEnabled > 0 {
  389. rt.cfg.Recorder.AutoDecode = true
  390. }
  391. }
  392. rt.det.UpdateClasses(signals)
  393. return pipeline.RefinementResult{Level: input.Level, Signals: signals, Decisions: decisions, Candidates: selectedCandidates}
  394. }
  395. func (rt *dspRuntime) updateRDS(now time.Time, rec *recorder.Manager, sig *detector.Signal, cls *classifier.Classification) {
  396. if sig == nil || cls == nil {
  397. return
  398. }
  399. keyHz := sig.CenterHz
  400. if sig.PLL != nil && sig.PLL.ExactHz != 0 {
  401. keyHz = sig.PLL.ExactHz
  402. }
  403. key := int64(math.Round(keyHz / 25000.0))
  404. st := rt.rdsMap[key]
  405. if st == nil {
  406. st = &rdsState{}
  407. rt.rdsMap[key] = st
  408. }
  409. if now.Sub(st.lastDecode) >= 4*time.Second && atomic.LoadInt32(&st.busy) == 0 {
  410. st.lastDecode = now
  411. atomic.StoreInt32(&st.busy, 1)
  412. go func(st *rdsState, sigHz float64) {
  413. defer atomic.StoreInt32(&st.busy, 0)
  414. ringIQ, ringSR, ringCenter := rec.SliceRecent(4.0)
  415. if len(ringIQ) < ringSR || ringSR <= 0 {
  416. return
  417. }
  418. offset := sigHz - ringCenter
  419. shifted := dsp.FreqShift(ringIQ, ringSR, offset)
  420. decim1 := ringSR / 1000000
  421. if decim1 < 1 {
  422. decim1 = 1
  423. }
  424. lp1 := dsp.LowpassFIR(float64(ringSR/decim1)/2.0*0.8, ringSR, 51)
  425. f1 := dsp.ApplyFIR(shifted, lp1)
  426. d1 := dsp.Decimate(f1, decim1)
  427. rate1 := ringSR / decim1
  428. decim2 := rate1 / 250000
  429. if decim2 < 1 {
  430. decim2 = 1
  431. }
  432. lp2 := dsp.LowpassFIR(float64(rate1/decim2)/2.0*0.8, rate1, 101)
  433. f2 := dsp.ApplyFIR(d1, lp2)
  434. decimated := dsp.Decimate(f2, decim2)
  435. actualRate := rate1 / decim2
  436. rdsBase := demod.RDSBasebandComplex(decimated, actualRate)
  437. if len(rdsBase.Samples) == 0 {
  438. return
  439. }
  440. st.mu.Lock()
  441. result := st.dec.Decode(rdsBase.Samples, rdsBase.SampleRate)
  442. if result.PS != "" {
  443. st.result = result
  444. }
  445. st.mu.Unlock()
  446. }(st, sig.CenterHz)
  447. }
  448. st.mu.Lock()
  449. ps := st.result.PS
  450. st.mu.Unlock()
  451. if ps != "" && sig.PLL != nil {
  452. sig.PLL.RDSStation = strings.TrimSpace(ps)
  453. cls.PLL = sig.PLL
  454. }
  455. }
  456. func (rt *dspRuntime) maintenance(displaySignals []detector.Signal, rec *recorder.Manager) {
  457. if len(rt.rdsMap) > 0 {
  458. activeIDs := make(map[int64]bool, len(displaySignals))
  459. for _, s := range displaySignals {
  460. keyHz := s.CenterHz
  461. if s.PLL != nil && s.PLL.ExactHz != 0 {
  462. keyHz = s.PLL.ExactHz
  463. }
  464. activeIDs[int64(math.Round(keyHz/25000.0))] = true
  465. }
  466. for id := range rt.rdsMap {
  467. if !activeIDs[id] {
  468. delete(rt.rdsMap, id)
  469. }
  470. }
  471. }
  472. if len(rt.streamPhaseState) > 0 {
  473. sigIDs := make(map[int64]bool, len(displaySignals))
  474. for _, s := range displaySignals {
  475. sigIDs[s.ID] = true
  476. }
  477. for id := range rt.streamPhaseState {
  478. if !sigIDs[id] {
  479. delete(rt.streamPhaseState, id)
  480. }
  481. }
  482. }
  483. if rec != nil && len(displaySignals) > 0 {
  484. aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult}
  485. _ = aqCfg
  486. }
  487. }