|
- //go:build soapy && !windows
-
- package soapysdr
-
- import (
- "fmt"
- "math"
- "unsafe"
- )
-
- /*
- #include <dlfcn.h>
- #include <stdlib.h>
- #include <stdint.h>
-
- // 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
- }
|