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.

258 satır
9.4KB

  1. package main
  2. import (
  3. "context"
  4. "encoding/json"
  5. "log"
  6. "os"
  7. "runtime/debug"
  8. "strings"
  9. "sync"
  10. "time"
  11. "sdr-wideband-suite/internal/config"
  12. "sdr-wideband-suite/internal/detector"
  13. "sdr-wideband-suite/internal/dsp"
  14. "sdr-wideband-suite/internal/logging"
  15. "sdr-wideband-suite/internal/pipeline"
  16. "sdr-wideband-suite/internal/recorder"
  17. )
  18. func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det *detector.Detector, window []float64, h *hub, eventFile *os.File, eventMu *sync.RWMutex, updates <-chan dspUpdate, gpuState *gpuStatus, rec *recorder.Manager, sigSnap *signalSnapshot, extractMgr *extractionManager, phaseSnap *phaseSnapshot) {
  19. defer func() {
  20. if r := recover(); r != nil {
  21. log.Printf("FATAL: runDSP goroutine panic: %v\n%s", r, debug.Stack())
  22. }
  23. }()
  24. rt := newDSPRuntime(cfg, det, window, gpuState)
  25. ticker := time.NewTicker(cfg.FrameInterval())
  26. defer ticker.Stop()
  27. logTicker := time.NewTicker(5 * time.Second)
  28. defer logTicker.Stop()
  29. enc := json.NewEncoder(eventFile)
  30. dcBlocker := dsp.NewDCBlocker(0.995)
  31. state := &phaseState{}
  32. var frameID uint64
  33. for {
  34. select {
  35. case <-ctx.Done():
  36. return
  37. case <-logTicker.C:
  38. st := srcMgr.Stats()
  39. log.Printf("stats: buf=%d drop=%d reset=%d last=%dms", st.BufferSamples, st.Dropped, st.Resets, st.LastSampleAgoMs)
  40. case upd := <-updates:
  41. rt.applyUpdate(upd, srcMgr, rec, gpuState)
  42. dcBlocker.Reset()
  43. ticker.Reset(rt.cfg.FrameInterval())
  44. case <-ticker.C:
  45. frameID++
  46. art, err := rt.captureSpectrum(srcMgr, rec, dcBlocker, gpuState)
  47. if err != nil {
  48. log.Printf("read IQ: %v", err)
  49. if strings.Contains(err.Error(), "timeout") {
  50. if err := srcMgr.Restart(rt.cfg); err != nil {
  51. log.Printf("restart failed: %v", err)
  52. }
  53. }
  54. continue
  55. }
  56. if !rt.gotSamples {
  57. log.Printf("received IQ samples")
  58. rt.gotSamples = true
  59. }
  60. logging.Debug("trace", "capture_done", "trace", frameID, "allIQ", len(art.allIQ), "detailIQ", len(art.detailIQ))
  61. state.surveillance = rt.buildSurveillanceResult(art)
  62. state.refinement = rt.runRefinement(art, state.surveillance, extractMgr, rec)
  63. finished := state.surveillance.Finished
  64. thresholds := state.surveillance.Thresholds
  65. noiseFloor := state.surveillance.NoiseFloor
  66. var displaySignals []detector.Signal
  67. if len(art.detailIQ) > 0 {
  68. displaySignals = state.refinement.Result.Signals
  69. stableSignals := rt.det.StableSignals()
  70. streamSignals := displaySignals
  71. if len(stableSignals) > 0 {
  72. streamSignals = stableSignals
  73. }
  74. if rec != nil && len(art.allIQ) > 0 {
  75. if art.streamDropped {
  76. rt.streamOverlap = &streamIQOverlap{}
  77. for k := range rt.streamPhaseState {
  78. rt.streamPhaseState[k].phase = 0
  79. }
  80. rec.ResetStreams()
  81. logging.Warn("gap", "iq_dropped", "msg", "buffer bloat caused extraction drop; overlap reset")
  82. }
  83. if rt.cfg.Recorder.DebugLiveAudio {
  84. log.Printf("LIVEAUDIO DSP: detailIQ=%d displaySignals=%d streamSignals=%d stableSignals=%d allIQ=%d", len(art.detailIQ), len(displaySignals), len(streamSignals), len(stableSignals), len(art.allIQ))
  85. }
  86. aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult}
  87. streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, streamSignals, rt.streamPhaseState, rt.streamOverlap, aqCfg)
  88. nonEmpty := 0
  89. minLen := 0
  90. maxLen := 0
  91. for i := range streamSnips {
  92. l := len(streamSnips[i])
  93. if l == 0 {
  94. continue
  95. }
  96. nonEmpty++
  97. if minLen == 0 || l < minLen {
  98. minLen = l
  99. }
  100. if l > maxLen {
  101. maxLen = l
  102. }
  103. }
  104. logging.Debug("trace", "extract_stats", "trace", frameID, "signals", len(streamSignals), "nonempty", nonEmpty, "minLen", minLen, "maxLen", maxLen)
  105. items := make([]recorder.StreamFeedItem, 0, len(streamSignals))
  106. for j, ds := range streamSignals {
  107. className := "<nil>"
  108. if ds.Class != nil {
  109. className = string(ds.Class.ModType)
  110. }
  111. snipLen := 0
  112. if j < len(streamSnips) {
  113. snipLen = len(streamSnips[j])
  114. }
  115. if rt.cfg.Recorder.DebugLiveAudio {
  116. log.Printf("LIVEAUDIO DSP: streamSignal idx=%d id=%d center=%.3fMHz bw=%.0f class=%s snip=%d", j, ds.ID, ds.CenterHz/1e6, ds.BWHz, className, snipLen)
  117. }
  118. if ds.ID == 0 || ds.Class == nil {
  119. continue
  120. }
  121. if j >= len(streamSnips) || len(streamSnips[j]) == 0 {
  122. logging.Warn("gap", "snippet_empty", "signal", ds.ID)
  123. continue
  124. }
  125. snipRate := rt.cfg.SampleRate
  126. if j < len(streamRates) && streamRates[j] > 0 {
  127. snipRate = streamRates[j]
  128. }
  129. items = append(items, recorder.StreamFeedItem{Signal: ds, Snippet: streamSnips[j], SnipRate: snipRate})
  130. }
  131. if rt.cfg.Recorder.DebugLiveAudio {
  132. log.Printf("LIVEAUDIO DSP: feedItems=%d", len(items))
  133. }
  134. if len(items) > 0 {
  135. rec.FeedSnippets(items, frameID)
  136. logging.Debug("trace", "feed", "trace", frameID, "items", len(items), "signals", len(streamSignals), "allIQ", len(art.allIQ))
  137. } else {
  138. logging.Warn("gap", "feed_empty", "signals", len(streamSignals), "trace", frameID)
  139. }
  140. }
  141. rt.maintenance(displaySignals, rec)
  142. } else {
  143. displaySignals = rt.det.StableSignals()
  144. }
  145. if rec != nil && len(displaySignals) > 0 {
  146. runtimeInfo := rec.RuntimeInfoBySignalID()
  147. for i := range displaySignals {
  148. if info, ok := runtimeInfo[displaySignals[i].ID]; ok {
  149. displaySignals[i].DemodName = info.DemodName
  150. displaySignals[i].PlaybackMode = info.PlaybackMode
  151. displaySignals[i].StereoState = info.StereoState
  152. }
  153. }
  154. }
  155. state.arbitration = rt.arbitration
  156. state.presentation = state.surveillance.DisplayLevel
  157. if phaseSnap != nil {
  158. phaseSnap.Set(*state)
  159. }
  160. if sigSnap != nil {
  161. sigSnap.set(displaySignals)
  162. }
  163. eventMu.Lock()
  164. for _, ev := range finished {
  165. _ = enc.Encode(ev)
  166. }
  167. eventMu.Unlock()
  168. if rec != nil && len(finished) > 0 {
  169. evCopy := make([]detector.Event, len(finished))
  170. copy(evCopy, finished)
  171. rec.OnEvents(evCopy)
  172. }
  173. var debugInfo *SpectrumDebug
  174. plan := state.refinement.Input.Plan
  175. windowSummary := buildWindowSummary(plan, state.refinement.Input.Windows, state.surveillance.Candidates, state.refinement.Input.WorkItems, state.refinement.Result.Decisions)
  176. var windowStats *RefinementWindowStats
  177. var monitorSummary []pipeline.MonitorWindowStats
  178. if windowSummary != nil {
  179. windowStats = windowSummary.Refinement
  180. monitorSummary = windowSummary.MonitorWindows
  181. }
  182. hasPlan := plan.TotalCandidates > 0 || plan.Budget > 0 || plan.DroppedBySNR > 0 || plan.DroppedByBudget > 0
  183. hasWindows := windowStats != nil && windowStats.Count > 0
  184. if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan || hasWindows {
  185. scoreDebug := make([]map[string]any, 0, len(displaySignals))
  186. for _, s := range displaySignals {
  187. if s.Class == nil || len(s.Class.Scores) == 0 {
  188. scoreDebug = append(scoreDebug, map[string]any{"center_hz": s.CenterHz, "class": nil})
  189. continue
  190. }
  191. scores := make(map[string]float64, len(s.Class.Scores))
  192. for k, v := range s.Class.Scores {
  193. scores[string(k)] = v
  194. }
  195. scoreDebug = append(scoreDebug, map[string]any{
  196. "center_hz": s.CenterHz,
  197. "mod_type": s.Class.ModType,
  198. "confidence": s.Class.Confidence,
  199. "second_best": s.Class.SecondBest,
  200. "scores": scores,
  201. })
  202. }
  203. debugInfo = &SpectrumDebug{Thresholds: thresholds, NoiseFloor: noiseFloor, Scores: scoreDebug}
  204. candidateSources := buildCandidateSourceSummary(state.surveillance.Candidates)
  205. candidateEvidence := buildCandidateEvidenceSummary(state.surveillance.Candidates)
  206. candidateEvidenceStates := buildCandidateEvidenceStateSummary(state.surveillance.Candidates)
  207. candidateWindows := buildCandidateWindowSummary(state.surveillance.Candidates, plan.MonitorWindows)
  208. if len(candidateSources) > 0 {
  209. debugInfo.CandidateSources = candidateSources
  210. }
  211. if len(candidateEvidence) > 0 {
  212. debugInfo.CandidateEvidence = candidateEvidence
  213. }
  214. if candidateEvidenceStates != nil {
  215. debugInfo.CandidateEvidenceStates = candidateEvidenceStates
  216. }
  217. if len(candidateWindows) > 0 {
  218. debugInfo.CandidateWindows = candidateWindows
  219. }
  220. if len(monitorSummary) > 0 {
  221. debugInfo.MonitorWindowStats = monitorSummary
  222. }
  223. if windowSummary != nil {
  224. debugInfo.WindowSummary = windowSummary
  225. }
  226. if hasPlan {
  227. debugInfo.RefinementPlan = &plan
  228. }
  229. if hasWindows {
  230. debugInfo.Windows = windowStats
  231. }
  232. refinementDebug := &RefinementDebug{}
  233. if hasPlan {
  234. refinementDebug.Plan = &plan
  235. refinementDebug.Request = &state.refinement.Input.Request
  236. refinementDebug.WorkItems = state.refinement.Input.WorkItems
  237. }
  238. if hasWindows {
  239. refinementDebug.Windows = windowStats
  240. }
  241. if len(monitorSummary) > 0 {
  242. refinementDebug.MonitorWindowStats = monitorSummary
  243. }
  244. if windowSummary != nil {
  245. refinementDebug.WindowSummary = windowSummary
  246. }
  247. refinementDebug.Arbitration = buildArbitrationSnapshot(state.refinement, state.arbitration)
  248. debugInfo.Refinement = refinementDebug
  249. }
  250. h.broadcast(SpectrumFrame{Timestamp: art.now.UnixMilli(), CenterHz: rt.cfg.CenterHz, SampleHz: rt.cfg.SampleRate, FFTSize: rt.cfg.FFTSize, Spectrum: art.surveillanceSpectrum, Signals: displaySignals, Debug: debugInfo})
  251. }
  252. }
  253. }