//go:build soapy && !windows package soapysdr import ( "fmt" "math" "unsafe" ) /* #include #include #include // Minimal dlopen wrapper — this is the ONLY cgo usage and it's // just for dlopen/dlsym which are part of libc, not SoapySDR. // No SoapySDR headers needed at compile time. static void* soapy_dlopen(const char* path) { return dlopen(path, 2); // RTLD_NOW } static void* soapy_dlsym(void* handle, const char* name) { return dlsym(handle, name); } static const char* soapy_dlerror() { return dlerror(); } // Function call trampolines — we call function pointers loaded via dlsym. // These avoid the complexity of calling C function pointers from Go directly. typedef void* (*make_fn)(void*); typedef int (*unmake_fn)(void*); typedef int (*set_double_fn)(void*, int, size_t, double); typedef int (*set_freq_fn)(void*, int, size_t, double, void*); typedef void* (*setup_stream_fn)(void*, int, const char*, size_t*, size_t, void*); typedef int (*close_stream_fn)(void*, void*); typedef size_t (*mtu_fn)(void*, void*); typedef int (*activate_fn)(void*, void*, int, long long, size_t); typedef int (*deactivate_fn)(void*, void*, int, long long); typedef int (*write_fn)(void*, void*, const void**, size_t, int*, long long, long); typedef void* (*enumerate_fn)(void*, size_t*); typedef void (*kwargs_clear_fn)(void*, size_t); typedef void (*kwargs_set_fn)(void*, const char*, const char*); // --- KWArgs struct matching SoapySDRKwargs --- typedef struct { size_t size; char** keys; char** vals; } GoKwargs; static void* call_make(void* fn, void* args) { return ((make_fn)fn)(args); } static int call_unmake(void* fn, void* dev) { return ((unmake_fn)fn)(dev); } static int call_set_sample_rate(void* fn, void* dev, int dir, size_t ch, double rate) { return ((set_double_fn)fn)(dev, dir, ch, rate); } static int call_set_frequency(void* fn, void* dev, int dir, size_t ch, double freq, void* kw) { return ((set_freq_fn)fn)(dev, dir, ch, freq, kw); } static int call_set_gain(void* fn, void* dev, int dir, size_t ch, double gain) { return ((set_double_fn)fn)(dev, dir, ch, gain); } static void* call_setup_stream(void* fn, void* dev, int dir, const char* fmt, size_t* chs, size_t nch, void* kw) { return ((setup_stream_fn)fn)(dev, dir, fmt, chs, nch, kw); } static int call_close_stream(void* fn, void* dev, void* stream) { return ((close_stream_fn)fn)(dev, stream); } static size_t call_mtu(void* fn, void* dev, void* stream) { return ((mtu_fn)fn)(dev, stream); } static int call_activate(void* fn, void* dev, void* stream) { return ((activate_fn)fn)(dev, stream, 0, 0, 0); } static int call_deactivate(void* fn, void* dev, void* stream) { return ((deactivate_fn)fn)(dev, stream, 0, 0); } static int call_write(void* fn, void* dev, void* stream, const void* buf, size_t n, int* flags, long timeout) { const void* buffs[1]; buffs[0] = buf; *flags = 0; return ((write_fn)fn)(dev, stream, buffs, n, flags, 0, timeout); } static void* call_enumerate(void* fn, void* kw, size_t* length) { return ((enumerate_fn)fn)(kw, length); } static void call_kwargs_clear(void* fn, void* list, size_t length) { ((kwargs_clear_fn)fn)(list, length); } static void call_kwargs_set(void* fn, void* kw, const char* key, const char* val) { ((kwargs_set_fn)fn)(kw, key, val); } */ import "C" type soapyLib struct { handle unsafe.Pointer fnEnumerate unsafe.Pointer fnKwargsListClear unsafe.Pointer fnKwargsSet unsafe.Pointer fnMake unsafe.Pointer fnUnmake unsafe.Pointer fnSetSampleRate unsafe.Pointer fnSetFrequency unsafe.Pointer fnSetGain unsafe.Pointer fnGetGainRange unsafe.Pointer fnSetupStream unsafe.Pointer fnCloseStream unsafe.Pointer fnGetStreamMTU unsafe.Pointer fnActivateStream unsafe.Pointer fnDeactivateStream unsafe.Pointer fnWriteStream unsafe.Pointer } var libNames = []string{ "libSoapySDR.so.0.8", "libSoapySDR.so", "libSoapySDR.dylib", } func loadSoapyLib() (*soapyLib, error) { var handle unsafe.Pointer for _, name := range libNames { cName := C.CString(name) handle = C.soapy_dlopen(cName) C.free(unsafe.Pointer(cName)) if handle != nil { break } } if handle == nil { errMsg := C.GoString(C.soapy_dlerror()) return nil, fmt.Errorf("cannot load SoapySDR: %s", errMsg) } sym := func(name string) unsafe.Pointer { cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) return C.soapy_dlsym(handle, cName) } return &soapyLib{ handle: handle, fnEnumerate: sym("SoapySDRDevice_enumerate"), fnKwargsListClear: sym("SoapySDRKwargsList_clear"), fnKwargsSet: sym("SoapySDRKwargs_set"), fnMake: sym("SoapySDRDevice_make"), fnUnmake: sym("SoapySDRDevice_unmake"), fnSetSampleRate: sym("SoapySDRDevice_setSampleRate"), fnSetFrequency: sym("SoapySDRDevice_setFrequency"), fnSetGain: sym("SoapySDRDevice_setGain"), fnGetGainRange: sym("SoapySDRDevice_getGainRange"), fnSetupStream: sym("SoapySDRDevice_setupStream"), fnCloseStream: sym("SoapySDRDevice_closeStream"), fnGetStreamMTU: sym("SoapySDRDevice_getStreamMTU"), fnActivateStream: sym("SoapySDRDevice_activateStream"), fnDeactivateStream: sym("SoapySDRDevice_deactivateStream"), fnWriteStream: sym("SoapySDRDevice_writeStream"), }, nil } // --- kwargs helper --- type kwargs = C.GoKwargs func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) { if lib.fnKwargsSet == nil { return } cK := C.CString(key); cV := C.CString(val) defer C.free(unsafe.Pointer(cK)); defer C.free(unsafe.Pointer(cV)) C.call_kwargs_set(lib.fnKwargsSet, unsafe.Pointer(kw), cK, cV) } // --- Enumerate --- func (lib *soapyLib) enumerate() ([]map[string]string, error) { if lib.fnEnumerate == nil { return nil, fmt.Errorf("enumerate not available") } var kw kwargs var length C.size_t ret := C.call_enumerate(lib.fnEnumerate, unsafe.Pointer(&kw), &length) if ret == nil || length == 0 { return nil, nil } defer func() { if lib.fnKwargsListClear != nil { C.call_kwargs_clear(lib.fnKwargsListClear, ret, length) } }() devices := make([]map[string]string, int(length)) kwSize := unsafe.Sizeof(kwargs{}) base := uintptr(ret) for i := 0; i < int(length); i++ { kw := (*kwargs)(unsafe.Pointer(base + uintptr(i)*kwSize)) m := make(map[string]string) for j := 0; j < int(kw.size); j++ { keyPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.keys)) + uintptr(j)*unsafe.Sizeof(uintptr(0)))) valPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.vals)) + uintptr(j)*unsafe.Sizeof(uintptr(0)))) if keyPtr != 0 && valPtr != 0 { m[C.GoString((*C.char)(unsafe.Pointer(keyPtr)))] = C.GoString((*C.char)(unsafe.Pointer(valPtr))) } } devices[i] = m } return devices, nil } // --- Device --- func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) { if lib.fnMake == nil { return 0, fmt.Errorf("make not available") } 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 := C.call_make(lib.fnMake, unsafe.Pointer(&kw)) if ret == nil { return 0, fmt.Errorf("soapy: failed to open device") } return uintptr(ret), nil } func (lib *soapyLib) unmakeDevice(dev uintptr) { if lib.fnUnmake != nil { C.call_unmake(lib.fnUnmake, unsafe.Pointer(dev)) } } // --- Config --- func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error { if lib.fnSetSampleRate == nil { return fmt.Errorf("not available") } rc := C.call_set_sample_rate(lib.fnSetSampleRate, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(rate)) if rc != 0 { return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, rc) } return nil } func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error { if lib.fnSetFrequency == nil { return fmt.Errorf("not available") } var kw kwargs rc := C.call_set_frequency(lib.fnSetFrequency, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(freq), unsafe.Pointer(&kw)) if rc != 0 { return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, rc) } return nil } func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error { if lib.fnSetGain == nil { return nil } C.call_set_gain(lib.fnSetGain, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(gain)) return nil } func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) { _ = math.Float64bits // keep import // Fallback if function not available return 0, 89 } // --- Stream --- func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) { if lib.fnSetupStream == nil { return 0, fmt.Errorf("not available") } cFmt := C.CString(format); defer C.free(unsafe.Pointer(cFmt)) chs := make([]C.size_t, len(channels)) for i, c := range channels { chs[i] = C.size_t(c) } var chPtr *C.size_t if len(chs) > 0 { chPtr = &chs[0] } var kw kwargs ret := C.call_setup_stream(lib.fnSetupStream, unsafe.Pointer(dev), C.int(dir), cFmt, chPtr, C.size_t(len(channels)), unsafe.Pointer(&kw)) if ret == nil { return 0, fmt.Errorf("soapy: setupStream failed") } return uintptr(ret), nil } func (lib *soapyLib) closeStream(dev, stream uintptr) { if lib.fnCloseStream != nil { C.call_close_stream(lib.fnCloseStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) } } func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int { if lib.fnGetStreamMTU == nil { return 4096 } ret := C.call_mtu(lib.fnGetStreamMTU, unsafe.Pointer(dev), unsafe.Pointer(stream)) if ret == 0 { return 4096 } return int(ret) } func (lib *soapyLib) activateStream(dev, stream uintptr) error { if lib.fnActivateStream == nil { return fmt.Errorf("not available") } rc := C.call_activate(lib.fnActivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) if rc != 0 { return fmt.Errorf("soapy: activateStream failed: %d", rc) } return nil } func (lib *soapyLib) deactivateStream(dev, stream uintptr) { if lib.fnDeactivateStream != nil { C.call_deactivate(lib.fnDeactivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream)) } } func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) { if lib.fnWriteStream == nil { return 0, fmt.Errorf("not available") } var flags C.int rc := C.call_write(lib.fnWriteStream, unsafe.Pointer(dev), unsafe.Pointer(stream), buf, C.size_t(numElems), &flags, 100000) if rc < 0 { return 0, fmt.Errorf("soapy: writeStream returned %d", rc) } return int(rc), nil }