Wideband autonomous SDR analysis engine forked from sdr-visual-suite
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

225 lines
8.2KB

  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/pipeline"
  15. "sdr-wideband-suite/internal/recorder"
  16. )
  17. 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) {
  18. defer func() {
  19. if r := recover(); r != nil {
  20. log.Printf("FATAL: runDSP goroutine panic: %v\n%s", r, debug.Stack())
  21. }
  22. }()
  23. rt := newDSPRuntime(cfg, det, window, gpuState)
  24. ticker := time.NewTicker(cfg.FrameInterval())
  25. defer ticker.Stop()
  26. logTicker := time.NewTicker(5 * time.Second)
  27. defer logTicker.Stop()
  28. enc := json.NewEncoder(eventFile)
  29. dcBlocker := dsp.NewDCBlocker(0.995)
  30. state := &phaseState{}
  31. for {
  32. select {
  33. case <-ctx.Done():
  34. return
  35. case <-logTicker.C:
  36. st := srcMgr.Stats()
  37. log.Printf("stats: buf=%d drop=%d reset=%d last=%dms", st.BufferSamples, st.Dropped, st.Resets, st.LastSampleAgoMs)
  38. case upd := <-updates:
  39. rt.applyUpdate(upd, srcMgr, rec, gpuState)
  40. dcBlocker.Reset()
  41. ticker.Reset(rt.cfg.FrameInterval())
  42. case <-ticker.C:
  43. art, err := rt.captureSpectrum(srcMgr, rec, dcBlocker, gpuState)
  44. if err != nil {
  45. log.Printf("read IQ: %v", err)
  46. if strings.Contains(err.Error(), "timeout") {
  47. if err := srcMgr.Restart(rt.cfg); err != nil {
  48. log.Printf("restart failed: %v", err)
  49. }
  50. }
  51. continue
  52. }
  53. if !rt.gotSamples {
  54. log.Printf("received IQ samples")
  55. rt.gotSamples = true
  56. }
  57. state.surveillance = rt.buildSurveillanceResult(art)
  58. state.refinement = rt.runRefinement(art, state.surveillance, extractMgr, rec)
  59. finished := state.surveillance.Finished
  60. thresholds := state.surveillance.Thresholds
  61. noiseFloor := state.surveillance.NoiseFloor
  62. var displaySignals []detector.Signal
  63. if len(art.detailIQ) > 0 {
  64. displaySignals = state.refinement.Result.Signals
  65. stableSignals := rt.det.StableSignals()
  66. streamSignals := displaySignals
  67. if len(stableSignals) > 0 {
  68. streamSignals = stableSignals
  69. }
  70. if rec != nil && len(art.allIQ) > 0 {
  71. if rt.cfg.Recorder.DebugLiveAudio {
  72. 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))
  73. }
  74. aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult}
  75. streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, streamSignals, rt.streamPhaseState, rt.streamOverlap, aqCfg)
  76. items := make([]recorder.StreamFeedItem, 0, len(streamSignals))
  77. for j, ds := range streamSignals {
  78. className := "<nil>"
  79. if ds.Class != nil {
  80. className = string(ds.Class.ModType)
  81. }
  82. snipLen := 0
  83. if j < len(streamSnips) {
  84. snipLen = len(streamSnips[j])
  85. }
  86. if rt.cfg.Recorder.DebugLiveAudio {
  87. 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)
  88. }
  89. if ds.ID == 0 || ds.Class == nil {
  90. continue
  91. }
  92. if j >= len(streamSnips) || len(streamSnips[j]) == 0 {
  93. continue
  94. }
  95. snipRate := rt.cfg.SampleRate
  96. if j < len(streamRates) && streamRates[j] > 0 {
  97. snipRate = streamRates[j]
  98. }
  99. items = append(items, recorder.StreamFeedItem{Signal: ds, Snippet: streamSnips[j], SnipRate: snipRate})
  100. }
  101. if rt.cfg.Recorder.DebugLiveAudio {
  102. log.Printf("LIVEAUDIO DSP: feedItems=%d", len(items))
  103. }
  104. if len(items) > 0 {
  105. rec.FeedSnippets(items)
  106. }
  107. }
  108. rt.maintenance(displaySignals, rec)
  109. } else {
  110. displaySignals = rt.det.StableSignals()
  111. }
  112. if rec != nil && len(displaySignals) > 0 {
  113. runtimeInfo := rec.RuntimeInfoBySignalID()
  114. for i := range displaySignals {
  115. if info, ok := runtimeInfo[displaySignals[i].ID]; ok {
  116. displaySignals[i].DemodName = info.DemodName
  117. displaySignals[i].PlaybackMode = info.PlaybackMode
  118. displaySignals[i].StereoState = info.StereoState
  119. }
  120. }
  121. }
  122. state.arbitration = rt.arbitration
  123. state.presentation = state.surveillance.DisplayLevel
  124. if phaseSnap != nil {
  125. phaseSnap.Set(*state)
  126. }
  127. if sigSnap != nil {
  128. sigSnap.set(displaySignals)
  129. }
  130. eventMu.Lock()
  131. for _, ev := range finished {
  132. _ = enc.Encode(ev)
  133. }
  134. eventMu.Unlock()
  135. if rec != nil && len(finished) > 0 {
  136. evCopy := make([]detector.Event, len(finished))
  137. copy(evCopy, finished)
  138. rec.OnEvents(evCopy)
  139. }
  140. var debugInfo *SpectrumDebug
  141. plan := state.refinement.Input.Plan
  142. windowSummary := buildWindowSummary(plan, state.refinement.Input.Windows, state.surveillance.Candidates, state.refinement.Input.WorkItems, state.refinement.Result.Decisions)
  143. var windowStats *RefinementWindowStats
  144. var monitorSummary []pipeline.MonitorWindowStats
  145. if windowSummary != nil {
  146. windowStats = windowSummary.Refinement
  147. monitorSummary = windowSummary.MonitorWindows
  148. }
  149. hasPlan := plan.TotalCandidates > 0 || plan.Budget > 0 || plan.DroppedBySNR > 0 || plan.DroppedByBudget > 0
  150. hasWindows := windowStats != nil && windowStats.Count > 0
  151. if len(thresholds) > 0 || len(displaySignals) > 0 || noiseFloor != 0 || hasPlan || hasWindows {
  152. scoreDebug := make([]map[string]any, 0, len(displaySignals))
  153. for _, s := range displaySignals {
  154. if s.Class == nil || len(s.Class.Scores) == 0 {
  155. scoreDebug = append(scoreDebug, map[string]any{"center_hz": s.CenterHz, "class": nil})
  156. continue
  157. }
  158. scores := make(map[string]float64, len(s.Class.Scores))
  159. for k, v := range s.Class.Scores {
  160. scores[string(k)] = v
  161. }
  162. scoreDebug = append(scoreDebug, map[string]any{
  163. "center_hz": s.CenterHz,
  164. "mod_type": s.Class.ModType,
  165. "confidence": s.Class.Confidence,
  166. "second_best": s.Class.SecondBest,
  167. "scores": scores,
  168. })
  169. }
  170. debugInfo = &SpectrumDebug{Thresholds: thresholds, NoiseFloor: noiseFloor, Scores: scoreDebug}
  171. candidateSources := buildCandidateSourceSummary(state.surveillance.Candidates)
  172. candidateEvidence := buildCandidateEvidenceSummary(state.surveillance.Candidates)
  173. candidateEvidenceStates := buildCandidateEvidenceStateSummary(state.surveillance.Candidates)
  174. candidateWindows := buildCandidateWindowSummary(state.surveillance.Candidates, plan.MonitorWindows)
  175. if len(candidateSources) > 0 {
  176. debugInfo.CandidateSources = candidateSources
  177. }
  178. if len(candidateEvidence) > 0 {
  179. debugInfo.CandidateEvidence = candidateEvidence
  180. }
  181. if candidateEvidenceStates != nil {
  182. debugInfo.CandidateEvidenceStates = candidateEvidenceStates
  183. }
  184. if len(candidateWindows) > 0 {
  185. debugInfo.CandidateWindows = candidateWindows
  186. }
  187. if len(monitorSummary) > 0 {
  188. debugInfo.MonitorWindowStats = monitorSummary
  189. }
  190. if windowSummary != nil {
  191. debugInfo.WindowSummary = windowSummary
  192. }
  193. if hasPlan {
  194. debugInfo.RefinementPlan = &plan
  195. }
  196. if hasWindows {
  197. debugInfo.Windows = windowStats
  198. }
  199. refinementDebug := &RefinementDebug{}
  200. if hasPlan {
  201. refinementDebug.Plan = &plan
  202. refinementDebug.Request = &state.refinement.Input.Request
  203. refinementDebug.WorkItems = state.refinement.Input.WorkItems
  204. }
  205. if hasWindows {
  206. refinementDebug.Windows = windowStats
  207. }
  208. if len(monitorSummary) > 0 {
  209. refinementDebug.MonitorWindowStats = monitorSummary
  210. }
  211. if windowSummary != nil {
  212. refinementDebug.WindowSummary = windowSummary
  213. }
  214. refinementDebug.Arbitration = buildArbitrationSnapshot(state.refinement, state.arbitration)
  215. debugInfo.Refinement = refinementDebug
  216. }
  217. 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})
  218. }
  219. }
  220. }