//go:build soapy && windows package soapysdr import ( "fmt" "math" "syscall" "unsafe" ) type soapyLib struct { dll *syscall.DLL pEnumerate *syscall.Proc pKwargsListClear *syscall.Proc pMake *syscall.Proc pUnmake *syscall.Proc pSetSampleRate *syscall.Proc pSetFrequency *syscall.Proc pSetGain *syscall.Proc pGetGainRange *syscall.Proc pSetupStream *syscall.Proc pCloseStream *syscall.Proc pGetStreamMTU *syscall.Proc pActivateStream *syscall.Proc pDeactivateStream *syscall.Proc pWriteStream *syscall.Proc pKwargsSet *syscall.Proc } var searchPaths = []string{ "SoapySDR", `C:\Program Files\PothosSDR\bin\SoapySDR.dll`, `C:\PothosSDR\bin\SoapySDR.dll`, } func loadSoapyLib() (*soapyLib, error) { var dll *syscall.DLL var lastErr error for _, path := range searchPaths { dll, lastErr = syscall.LoadDLL(path) if dll != nil { break } } if dll == nil { return nil, fmt.Errorf("cannot load SoapySDR.dll: %v (searched: %v)", lastErr, searchPaths) } mustProc := func(name string) *syscall.Proc { p, _ := dll.FindProc(name) return p } return &soapyLib{ dll: dll, pEnumerate: mustProc("SoapySDRDevice_enumerate"), pKwargsListClear: mustProc("SoapySDRKwargsList_clear"), pMake: mustProc("SoapySDRDevice_make"), pUnmake: mustProc("SoapySDRDevice_unmake"), pSetSampleRate: mustProc("SoapySDRDevice_setSampleRate"), pSetFrequency: mustProc("SoapySDRDevice_setFrequency"), pSetGain: mustProc("SoapySDRDevice_setGain"), pGetGainRange: mustProc("SoapySDRDevice_getGainRange"), pSetupStream: mustProc("SoapySDRDevice_setupStream"), pCloseStream: mustProc("SoapySDRDevice_closeStream"), pGetStreamMTU: mustProc("SoapySDRDevice_getStreamMTU"), pActivateStream: mustProc("SoapySDRDevice_activateStream"), pDeactivateStream: mustProc("SoapySDRDevice_deactivateStream"), pWriteStream: mustProc("SoapySDRDevice_writeStream"), pKwargsSet: mustProc("SoapySDRKwargs_set"), }, nil } // --- KWArgs helper (stack-allocated 64-byte struct matching SoapySDRKwargs) --- // SoapySDRKwargs = { size_t size; char** keys; char** vals; } // On 64-bit: 8 + 8 + 8 = 24 bytes type kwargs struct { size uintptr keys uintptr vals uintptr } func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) { if lib.pKwargsSet == nil { return } cKey, _ := syscall.BytePtrFromString(key) cVal, _ := syscall.BytePtrFromString(val) lib.pKwargsSet.Call(uintptr(unsafe.Pointer(kw)), uintptr(unsafe.Pointer(cKey)), uintptr(unsafe.Pointer(cVal))) } // --- Enumerate --- func (lib *soapyLib) enumerate() ([]map[string]string, error) { if lib.pEnumerate == nil { return nil, fmt.Errorf("SoapySDRDevice_enumerate not found") } var kw kwargs var length uintptr ret, _, _ := lib.pEnumerate.Call(uintptr(unsafe.Pointer(&kw)), uintptr(unsafe.Pointer(&length))) if ret == 0 || length == 0 { return nil, nil } defer func() { if lib.pKwargsListClear != nil { lib.pKwargsListClear.Call(ret, length) } }() devices := make([]map[string]string, int(length)) kwSize := unsafe.Sizeof(kwargs{}) for i := 0; i < int(length); i++ { kw := (*kwargs)(unsafe.Pointer(ret + uintptr(i)*kwSize)) m := make(map[string]string) for j := 0; j < int(kw.size); j++ { keyPtr := *(*uintptr)(unsafe.Pointer(kw.keys + uintptr(j)*unsafe.Sizeof(uintptr(0)))) valPtr := *(*uintptr)(unsafe.Pointer(kw.vals + uintptr(j)*unsafe.Sizeof(uintptr(0)))) if keyPtr != 0 && valPtr != 0 { m[goString(keyPtr)] = goString(valPtr) } } devices[i] = m } return devices, nil } // --- Device lifecycle --- func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) { if lib.pMake == nil { return 0, fmt.Errorf("SoapySDRDevice_make not found") } var kw kwargs if driver != "" { lib.kwargsSet(&kw, "driver", driver) } if device != "" { lib.kwargsSet(&kw, "device", device) } for k, v := range args { lib.kwargsSet(&kw, k, v) } ret, _, _ := lib.pMake.Call(uintptr(unsafe.Pointer(&kw))) if ret == 0 { return 0, fmt.Errorf("soapy: failed to open device (driver=%q device=%q)", driver, device) } return ret, nil } func (lib *soapyLib) unmakeDevice(dev uintptr) { if lib.pUnmake != nil && dev != 0 { lib.pUnmake.Call(dev) } } // --- Configuration --- func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error { if lib.pSetSampleRate == nil { return fmt.Errorf("setSampleRate not available") } bits := math.Float64bits(rate) rc, _, _ := lib.pSetSampleRate.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits)) if int32(rc) != 0 { return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, int32(rc)) } return nil } func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error { if lib.pSetFrequency == nil { return fmt.Errorf("setFrequency not available") } bits := math.Float64bits(freq) var kw kwargs rc, _, _ := lib.pSetFrequency.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits), uintptr(unsafe.Pointer(&kw))) if int32(rc) != 0 { return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, int32(rc)) } return nil } func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error { if lib.pSetGain == nil { return nil } bits := math.Float64bits(gain) lib.pSetGain.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits)) return nil } func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) { if lib.pGetGainRange == nil { return 0, 89 } // SoapySDRRange is { double minimum; double maximum; double step; } = 24 bytes var buf [3]float64 lib.pGetGainRange.Call(uintptr(unsafe.Pointer(&buf)), dev, uintptr(dir), uintptr(ch)) return buf[0], buf[1] } // --- Stream --- func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) { if lib.pSetupStream == nil { return 0, fmt.Errorf("setupStream not available") } cFmt, _ := syscall.BytePtrFromString(format) var chPtr uintptr if len(channels) > 0 { chPtr = uintptr(unsafe.Pointer(&channels[0])) } var kw kwargs ret, _, _ := lib.pSetupStream.Call(dev, uintptr(dir), uintptr(unsafe.Pointer(cFmt)), chPtr, uintptr(len(channels)), uintptr(unsafe.Pointer(&kw))) if ret == 0 { return 0, fmt.Errorf("soapy: setupStream failed") } return ret, nil } func (lib *soapyLib) closeStream(dev, stream uintptr) { if lib.pCloseStream != nil { lib.pCloseStream.Call(dev, stream) } } func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int { if lib.pGetStreamMTU == nil { return 4096 } ret, _, _ := lib.pGetStreamMTU.Call(dev, stream) if ret == 0 { return 4096 } return int(ret) } func (lib *soapyLib) activateStream(dev, stream uintptr) error { if lib.pActivateStream == nil { return fmt.Errorf("activateStream not available") } rc, _, _ := lib.pActivateStream.Call(dev, stream, 0, 0, 0) if int32(rc) != 0 { return fmt.Errorf("soapy: activateStream failed: %d", int32(rc)) } return nil } func (lib *soapyLib) deactivateStream(dev, stream uintptr) { if lib.pDeactivateStream != nil { lib.pDeactivateStream.Call(dev, stream, 0, 0) } } func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) { if lib.pWriteStream == nil { return 0, fmt.Errorf("writeStream not available") } buffs := [1]uintptr{uintptr(buf)} var flags int32 rc, _, _ := lib.pWriteStream.Call(dev, stream, uintptr(unsafe.Pointer(&buffs[0])), uintptr(numElems), uintptr(unsafe.Pointer(&flags)), 0, // timeNs 100000, // timeoutUs = 100ms ) n := int32(rc) if n < 0 { return 0, fmt.Errorf("soapy: writeStream returned %d", n) } return int(n), nil } // --- C string helper --- func goString(p uintptr) string { if p == 0 { return "" } buf := make([]byte, 0, 256) for i := uintptr(0); ; i++ { b := *(*byte)(unsafe.Pointer(p + i)) if b == 0 { break } buf = append(buf, b) } return string(buf) }