|
- //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)
- }
|