Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

162 строки
3.9KB

  1. package main
  2. import (
  3. "log"
  4. "sort"
  5. "strconv"
  6. "time"
  7. "sdr-visual-suite/internal/config"
  8. "sdr-visual-suite/internal/demod/gpudemod"
  9. "sdr-visual-suite/internal/detector"
  10. "sdr-visual-suite/internal/dsp"
  11. )
  12. func mustParseDuration(raw string, fallback time.Duration) time.Duration {
  13. if raw == "" {
  14. return fallback
  15. }
  16. if d, err := time.ParseDuration(raw); err == nil {
  17. return d
  18. }
  19. return fallback
  20. }
  21. func buildDecoderMap(cfg config.Config) map[string]string {
  22. out := map[string]string{}
  23. if cfg.Decoder.FT8Cmd != "" {
  24. out["FT8"] = cfg.Decoder.FT8Cmd
  25. }
  26. if cfg.Decoder.WSPRCmd != "" {
  27. out["WSPR"] = cfg.Decoder.WSPRCmd
  28. }
  29. if cfg.Decoder.DMRCmd != "" {
  30. out["DMR"] = cfg.Decoder.DMRCmd
  31. }
  32. if cfg.Decoder.DStarCmd != "" {
  33. out["D-STAR"] = cfg.Decoder.DStarCmd
  34. }
  35. if cfg.Decoder.FSKCmd != "" {
  36. out["FSK"] = cfg.Decoder.FSKCmd
  37. }
  38. if cfg.Decoder.PSKCmd != "" {
  39. out["PSK"] = cfg.Decoder.PSKCmd
  40. }
  41. return out
  42. }
  43. func decoderKeys(cfg config.Config) []string {
  44. m := buildDecoderMap(cfg)
  45. keys := make([]string, 0, len(m))
  46. for k := range m {
  47. keys = append(keys, k)
  48. }
  49. sort.Strings(keys)
  50. return keys
  51. }
  52. func (m *extractionManager) reset() {
  53. if m == nil {
  54. return
  55. }
  56. m.mu.Lock()
  57. defer m.mu.Unlock()
  58. if m.runner != nil {
  59. m.runner.Close()
  60. m.runner = nil
  61. }
  62. }
  63. func (m *extractionManager) get(sampleCount int, sampleRate int) *gpudemod.BatchRunner {
  64. if m == nil || sampleCount <= 0 || sampleRate <= 0 || !gpudemod.Available() {
  65. return nil
  66. }
  67. m.mu.Lock()
  68. defer m.mu.Unlock()
  69. if m.runner == nil {
  70. if r, err := gpudemod.NewBatchRunner(sampleCount, sampleRate); err == nil {
  71. m.runner = r
  72. } else {
  73. log.Printf("gpudemod: batch runner init failed: %v", err)
  74. }
  75. return m.runner
  76. }
  77. return m.runner
  78. }
  79. func extractSignalIQ(iq []complex64, sampleRate int, centerHz float64, sigHz float64, bwHz float64) []complex64 {
  80. if len(iq) == 0 || sampleRate <= 0 {
  81. return nil
  82. }
  83. results := extractSignalIQBatch(nil, iq, sampleRate, centerHz, []detector.Signal{{CenterHz: sigHz, BWHz: bwHz}})
  84. if len(results) == 0 {
  85. return nil
  86. }
  87. return results[0]
  88. }
  89. func extractSignalIQBatch(extractMgr *extractionManager, iq []complex64, sampleRate int, centerHz float64, signals []detector.Signal) [][]complex64 {
  90. out := make([][]complex64, len(signals))
  91. if len(iq) == 0 || sampleRate <= 0 || len(signals) == 0 {
  92. return out
  93. }
  94. decimTarget := 200000
  95. if decimTarget <= 0 {
  96. decimTarget = sampleRate
  97. }
  98. runner := extractMgr.get(len(iq), sampleRate)
  99. if runner != nil {
  100. jobs := make([]gpudemod.ExtractJob, len(signals))
  101. for i, sig := range signals {
  102. jobs[i] = gpudemod.ExtractJob{OffsetHz: sig.CenterHz - centerHz, BW: sig.BWHz, OutRate: decimTarget}
  103. }
  104. if gpuOuts, _, err := runner.ShiftFilterDecimateBatch(iq, jobs); err == nil && len(gpuOuts) == len(signals) {
  105. log.Printf("gpudemod: batch extraction used for %d signals", len(signals))
  106. for i := range gpuOuts {
  107. out[i] = gpuOuts[i]
  108. }
  109. return out
  110. } else if err != nil {
  111. log.Printf("gpudemod: batch extraction failed for %d signals: %v", len(signals), err)
  112. }
  113. }
  114. log.Printf("gpudemod: CPU extraction fallback used for %d signals", len(signals))
  115. for i, sig := range signals {
  116. offset := sig.CenterHz - centerHz
  117. shifted := dsp.FreqShift(iq, sampleRate, offset)
  118. cutoff := sig.BWHz / 2
  119. if cutoff < 200 {
  120. cutoff = 200
  121. }
  122. if cutoff > float64(sampleRate)/2-1 {
  123. cutoff = float64(sampleRate)/2 - 1
  124. }
  125. taps := dsp.LowpassFIR(cutoff, sampleRate, 101)
  126. filtered := dsp.ApplyFIR(shifted, taps)
  127. decim := sampleRate / decimTarget
  128. if decim < 1 {
  129. decim = 1
  130. }
  131. out[i] = dsp.Decimate(filtered, decim)
  132. }
  133. return out
  134. }
  135. func parseSince(raw string) (time.Time, error) {
  136. if raw == "" {
  137. return time.Time{}, nil
  138. }
  139. if ms, err := strconv.ParseInt(raw, 10, 64); err == nil {
  140. if ms > 1e12 {
  141. return time.UnixMilli(ms), nil
  142. }
  143. return time.Unix(ms, 0), nil
  144. }
  145. if t, err := time.Parse(time.RFC3339Nano, raw); err == nil {
  146. return t, nil
  147. }
  148. return time.Parse(time.RFC3339, raw)
  149. }