Przeglądaj źródła

Add live demod endpoint for recent IQ

master
Jan Svabenik 4 dni temu
rodzic
commit
40f62f8579
3 zmienionych plików z 93 dodań i 14 usunięć
  1. +19
    -0
      cmd/sdrd/main.go
  2. +56
    -0
      internal/recorder/demod_live.go
  3. +18
    -14
      internal/recorder/wavwriter.go

+ 19
- 0
cmd/sdrd/main.go Wyświetl plik

@@ -525,6 +525,25 @@ func main() {
http.ServeFile(w, r, filepath.Join(base, "meta.json"))
})

http.HandleFunc("/api/demod", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
q := r.URL.Query()
freq, _ := strconv.ParseFloat(q.Get("freq"), 64)
bw, _ := strconv.ParseFloat(q.Get("bw"), 64)
sec, _ := strconv.Atoi(q.Get("sec"))
mode := q.Get("mode")
data, _, err := recMgr.DemodLive(freq, bw, mode, sec)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "audio/wav")
_, _ = w.Write(data)
})

http.Handle("/", http.FileServer(http.Dir(cfg.WebRoot)))

server := &http.Server{Addr: cfg.WebAddr}


+ 56
- 0
internal/recorder/demod_live.go Wyświetl plik

@@ -0,0 +1,56 @@
package recorder

import (
"bytes"
"errors"
"time"

"sdr-visual-suite/internal/demod"
"sdr-visual-suite/internal/dsp"
)

// DemodLive demodulates a recent window and returns WAV bytes.
func (m *Manager) DemodLive(centerHz float64, bw float64, mode string, seconds int) ([]byte, int, error) {
if m == nil || m.ring == nil {
return nil, 0, errors.New("recorder not ready")
}
if seconds <= 0 {
seconds = 2
}
end := time.Now()
start := end.Add(-time.Duration(seconds) * time.Second)
segment := m.ring.Slice(start, end)
if len(segment) == 0 {
return nil, 0, errors.New("no iq in ring")
}
name := mode
if name == "" {
name = "NFM"
}
d := demod.Get(name)
if d == nil {
return nil, 0, errors.New("demodulator not found")
}
offset := centerHz - m.centerHz
shifted := dsp.FreqShift(segment, m.sampleRate, offset)
if bw <= 0 {
bw = 12000
}
cutoff := bw / 2
if cutoff < 200 {
cutoff = 200
}
taps := dsp.LowpassFIR(cutoff, m.sampleRate, 101)
filtered := dsp.ApplyFIR(shifted, taps)
decim := m.sampleRate / (d.OutputSampleRate() * 4)
if decim < 1 {
decim = 1
}
dec := dsp.Decimate(filtered, decim)
audio := d.Demod(dec, m.sampleRate/decim)
buf := &bytes.Buffer{}
if err := writeWAVTo(buf, audio, d.OutputSampleRate(), d.Channels()); err != nil {
return nil, 0, err
}
return buf.Bytes(), d.OutputSampleRate(), nil
}

+ 18
- 14
internal/recorder/wavwriter.go Wyświetl plik

@@ -2,6 +2,7 @@ package recorder

import (
"encoding/binary"
"io"
"os"
)

@@ -11,59 +12,62 @@ func writeWAV(path string, samples []float32, sampleRate int, channels int) erro
return err
}
defer f.Close()
return writeWAVTo(f, samples, sampleRate, channels)
}

func writeWAVTo(w io.Writer, samples []float32, sampleRate int, channels int) error {
if channels <= 0 {
channels = 1
}
// 16-bit PCM
dataSize := uint32(len(samples) * 2)
// RIFF header
if _, err := f.Write([]byte("RIFF")); err != nil {
if _, err := w.Write([]byte("RIFF")); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint32(36)+dataSize); err != nil {
if err := binary.Write(w, binary.LittleEndian, uint32(36)+dataSize); err != nil {
return err
}
if _, err := f.Write([]byte("WAVE")); err != nil {
if _, err := w.Write([]byte("WAVE")); err != nil {
return err
}
// fmt chunk
if _, err := f.Write([]byte("fmt ")); err != nil {
if _, err := w.Write([]byte("fmt ")); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint32(16)); err != nil {
if err := binary.Write(w, binary.LittleEndian, uint32(16)); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint16(1)); err != nil { // PCM
if err := binary.Write(w, binary.LittleEndian, uint16(1)); err != nil { // PCM
return err
}
if err := binary.Write(f, binary.LittleEndian, uint16(channels)); err != nil {
if err := binary.Write(w, binary.LittleEndian, uint16(channels)); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint32(sampleRate)); err != nil {
if err := binary.Write(w, binary.LittleEndian, uint32(sampleRate)); err != nil {
return err
}
byteRate := uint32(sampleRate * channels * 2)
if err := binary.Write(f, binary.LittleEndian, byteRate); err != nil {
if err := binary.Write(w, binary.LittleEndian, byteRate); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint16(channels*2)); err != nil {
if err := binary.Write(w, binary.LittleEndian, uint16(channels*2)); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, uint16(16)); err != nil { // bits
if err := binary.Write(w, binary.LittleEndian, uint16(16)); err != nil { // bits
return err
}
// data chunk
if _, err := f.Write([]byte("data")); err != nil {
if _, err := w.Write([]byte("data")); err != nil {
return err
}
if err := binary.Write(f, binary.LittleEndian, dataSize); err != nil {
if err := binary.Write(w, binary.LittleEndian, dataSize); err != nil {
return err
}
// samples
for _, s := range samples {
v := int16(clip(s * 32767))
if err := binary.Write(f, binary.LittleEndian, v); err != nil {
if err := binary.Write(w, binary.LittleEndian, v); err != nil {
return err
}
}


Ładowanie…
Anuluj
Zapisz