Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 line
11KB

  1. //go:build soapy && !windows
  2. package soapysdr
  3. import (
  4. "fmt"
  5. "math"
  6. "unsafe"
  7. )
  8. /*
  9. #include <dlfcn.h>
  10. #include <stdlib.h>
  11. #include <stdint.h>
  12. // Minimal dlopen wrapper — this is the ONLY cgo usage and it's
  13. // just for dlopen/dlsym which are part of libc, not SoapySDR.
  14. // No SoapySDR headers needed at compile time.
  15. static void* soapy_dlopen(const char* path) {
  16. return dlopen(path, 2); // RTLD_NOW
  17. }
  18. static void* soapy_dlsym(void* handle, const char* name) {
  19. return dlsym(handle, name);
  20. }
  21. static const char* soapy_dlerror() {
  22. return dlerror();
  23. }
  24. // Function call trampolines — we call function pointers loaded via dlsym.
  25. // These avoid the complexity of calling C function pointers from Go directly.
  26. typedef void* (*make_fn)(void*);
  27. typedef int (*unmake_fn)(void*);
  28. typedef int (*set_double_fn)(void*, int, size_t, double);
  29. typedef int (*set_freq_fn)(void*, int, size_t, double, void*);
  30. typedef void* (*setup_stream_fn)(void*, int, const char*, size_t*, size_t, void*);
  31. typedef int (*close_stream_fn)(void*, void*);
  32. typedef size_t (*mtu_fn)(void*, void*);
  33. typedef int (*activate_fn)(void*, void*, int, long long, size_t);
  34. typedef int (*deactivate_fn)(void*, void*, int, long long);
  35. typedef int (*write_fn)(void*, void*, const void**, size_t, int*, long long, long);
  36. typedef void* (*enumerate_fn)(void*, size_t*);
  37. typedef void (*kwargs_clear_fn)(void*, size_t);
  38. typedef void (*kwargs_set_fn)(void*, const char*, const char*);
  39. // --- KWArgs struct matching SoapySDRKwargs ---
  40. typedef struct {
  41. size_t size;
  42. char** keys;
  43. char** vals;
  44. } GoKwargs;
  45. static void* call_make(void* fn, void* args) {
  46. return ((make_fn)fn)(args);
  47. }
  48. static int call_unmake(void* fn, void* dev) {
  49. return ((unmake_fn)fn)(dev);
  50. }
  51. static int call_set_sample_rate(void* fn, void* dev, int dir, size_t ch, double rate) {
  52. return ((set_double_fn)fn)(dev, dir, ch, rate);
  53. }
  54. static int call_set_frequency(void* fn, void* dev, int dir, size_t ch, double freq, void* kw) {
  55. return ((set_freq_fn)fn)(dev, dir, ch, freq, kw);
  56. }
  57. static int call_set_gain(void* fn, void* dev, int dir, size_t ch, double gain) {
  58. return ((set_double_fn)fn)(dev, dir, ch, gain);
  59. }
  60. static void* call_setup_stream(void* fn, void* dev, int dir, const char* fmt, size_t* chs, size_t nch, void* kw) {
  61. return ((setup_stream_fn)fn)(dev, dir, fmt, chs, nch, kw);
  62. }
  63. static int call_close_stream(void* fn, void* dev, void* stream) {
  64. return ((close_stream_fn)fn)(dev, stream);
  65. }
  66. static size_t call_mtu(void* fn, void* dev, void* stream) {
  67. return ((mtu_fn)fn)(dev, stream);
  68. }
  69. static int call_activate(void* fn, void* dev, void* stream) {
  70. return ((activate_fn)fn)(dev, stream, 0, 0, 0);
  71. }
  72. static int call_deactivate(void* fn, void* dev, void* stream) {
  73. return ((deactivate_fn)fn)(dev, stream, 0, 0);
  74. }
  75. static int call_write(void* fn, void* dev, void* stream, const void* buf, size_t n, int* flags, long timeout) {
  76. const void* buffs[1];
  77. buffs[0] = buf;
  78. *flags = 0;
  79. return ((write_fn)fn)(dev, stream, buffs, n, flags, 0, timeout);
  80. }
  81. static void* call_enumerate(void* fn, void* kw, size_t* length) {
  82. return ((enumerate_fn)fn)(kw, length);
  83. }
  84. static void call_kwargs_clear(void* fn, void* list, size_t length) {
  85. ((kwargs_clear_fn)fn)(list, length);
  86. }
  87. static void call_kwargs_set(void* fn, void* kw, const char* key, const char* val) {
  88. ((kwargs_set_fn)fn)(kw, key, val);
  89. }
  90. */
  91. import "C"
  92. type soapyLib struct {
  93. handle unsafe.Pointer
  94. fnEnumerate unsafe.Pointer
  95. fnKwargsListClear unsafe.Pointer
  96. fnKwargsSet unsafe.Pointer
  97. fnMake unsafe.Pointer
  98. fnUnmake unsafe.Pointer
  99. fnSetSampleRate unsafe.Pointer
  100. fnSetFrequency unsafe.Pointer
  101. fnSetGain unsafe.Pointer
  102. fnGetGainRange unsafe.Pointer
  103. fnSetupStream unsafe.Pointer
  104. fnCloseStream unsafe.Pointer
  105. fnGetStreamMTU unsafe.Pointer
  106. fnActivateStream unsafe.Pointer
  107. fnDeactivateStream unsafe.Pointer
  108. fnWriteStream unsafe.Pointer
  109. }
  110. var libNames = []string{
  111. "libSoapySDR.so.0.8",
  112. "libSoapySDR.so",
  113. "libSoapySDR.dylib",
  114. }
  115. func loadSoapyLib() (*soapyLib, error) {
  116. var handle unsafe.Pointer
  117. for _, name := range libNames {
  118. cName := C.CString(name)
  119. handle = C.soapy_dlopen(cName)
  120. C.free(unsafe.Pointer(cName))
  121. if handle != nil {
  122. break
  123. }
  124. }
  125. if handle == nil {
  126. errMsg := C.GoString(C.soapy_dlerror())
  127. return nil, fmt.Errorf("cannot load SoapySDR: %s", errMsg)
  128. }
  129. sym := func(name string) unsafe.Pointer {
  130. cName := C.CString(name)
  131. defer C.free(unsafe.Pointer(cName))
  132. return C.soapy_dlsym(handle, cName)
  133. }
  134. return &soapyLib{
  135. handle: handle,
  136. fnEnumerate: sym("SoapySDRDevice_enumerate"),
  137. fnKwargsListClear: sym("SoapySDRKwargsList_clear"),
  138. fnKwargsSet: sym("SoapySDRKwargs_set"),
  139. fnMake: sym("SoapySDRDevice_make"),
  140. fnUnmake: sym("SoapySDRDevice_unmake"),
  141. fnSetSampleRate: sym("SoapySDRDevice_setSampleRate"),
  142. fnSetFrequency: sym("SoapySDRDevice_setFrequency"),
  143. fnSetGain: sym("SoapySDRDevice_setGain"),
  144. fnGetGainRange: sym("SoapySDRDevice_getGainRange"),
  145. fnSetupStream: sym("SoapySDRDevice_setupStream"),
  146. fnCloseStream: sym("SoapySDRDevice_closeStream"),
  147. fnGetStreamMTU: sym("SoapySDRDevice_getStreamMTU"),
  148. fnActivateStream: sym("SoapySDRDevice_activateStream"),
  149. fnDeactivateStream: sym("SoapySDRDevice_deactivateStream"),
  150. fnWriteStream: sym("SoapySDRDevice_writeStream"),
  151. }, nil
  152. }
  153. // --- kwargs helper ---
  154. type kwargs = C.GoKwargs
  155. func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) {
  156. if lib.fnKwargsSet == nil { return }
  157. cK := C.CString(key); cV := C.CString(val)
  158. defer C.free(unsafe.Pointer(cK)); defer C.free(unsafe.Pointer(cV))
  159. C.call_kwargs_set(lib.fnKwargsSet, unsafe.Pointer(kw), cK, cV)
  160. }
  161. // --- Enumerate ---
  162. func (lib *soapyLib) enumerate() ([]map[string]string, error) {
  163. if lib.fnEnumerate == nil { return nil, fmt.Errorf("enumerate not available") }
  164. var kw kwargs
  165. var length C.size_t
  166. ret := C.call_enumerate(lib.fnEnumerate, unsafe.Pointer(&kw), &length)
  167. if ret == nil || length == 0 { return nil, nil }
  168. defer func() {
  169. if lib.fnKwargsListClear != nil {
  170. C.call_kwargs_clear(lib.fnKwargsListClear, ret, length)
  171. }
  172. }()
  173. devices := make([]map[string]string, int(length))
  174. kwSize := unsafe.Sizeof(kwargs{})
  175. base := uintptr(ret)
  176. for i := 0; i < int(length); i++ {
  177. kw := (*kwargs)(unsafe.Pointer(base + uintptr(i)*kwSize))
  178. m := make(map[string]string)
  179. for j := 0; j < int(kw.size); j++ {
  180. keyPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.keys)) + uintptr(j)*unsafe.Sizeof(uintptr(0))))
  181. valPtr := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(kw.vals)) + uintptr(j)*unsafe.Sizeof(uintptr(0))))
  182. if keyPtr != 0 && valPtr != 0 {
  183. m[C.GoString((*C.char)(unsafe.Pointer(keyPtr)))] = C.GoString((*C.char)(unsafe.Pointer(valPtr)))
  184. }
  185. }
  186. devices[i] = m
  187. }
  188. return devices, nil
  189. }
  190. // --- Device ---
  191. func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) {
  192. if lib.fnMake == nil { return 0, fmt.Errorf("make not available") }
  193. var kw kwargs
  194. if driver != "" { lib.kwargsSet(&kw, "driver", driver) }
  195. if device != "" { lib.kwargsSet(&kw, "device", device) }
  196. for k, v := range args { lib.kwargsSet(&kw, k, v) }
  197. ret := C.call_make(lib.fnMake, unsafe.Pointer(&kw))
  198. if ret == nil { return 0, fmt.Errorf("soapy: failed to open device") }
  199. return uintptr(ret), nil
  200. }
  201. func (lib *soapyLib) unmakeDevice(dev uintptr) {
  202. if lib.fnUnmake != nil { C.call_unmake(lib.fnUnmake, unsafe.Pointer(dev)) }
  203. }
  204. // --- Config ---
  205. func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error {
  206. if lib.fnSetSampleRate == nil { return fmt.Errorf("not available") }
  207. rc := C.call_set_sample_rate(lib.fnSetSampleRate, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(rate))
  208. if rc != 0 { return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, rc) }
  209. return nil
  210. }
  211. func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error {
  212. if lib.fnSetFrequency == nil { return fmt.Errorf("not available") }
  213. var kw kwargs
  214. rc := C.call_set_frequency(lib.fnSetFrequency, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(freq), unsafe.Pointer(&kw))
  215. if rc != 0 { return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, rc) }
  216. return nil
  217. }
  218. func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error {
  219. if lib.fnSetGain == nil { return nil }
  220. C.call_set_gain(lib.fnSetGain, unsafe.Pointer(dev), C.int(dir), C.size_t(ch), C.double(gain))
  221. return nil
  222. }
  223. func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) {
  224. _ = math.Float64bits // keep import
  225. // Fallback if function not available
  226. return 0, 89
  227. }
  228. // --- Stream ---
  229. func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) {
  230. if lib.fnSetupStream == nil { return 0, fmt.Errorf("not available") }
  231. cFmt := C.CString(format); defer C.free(unsafe.Pointer(cFmt))
  232. chs := make([]C.size_t, len(channels))
  233. for i, c := range channels { chs[i] = C.size_t(c) }
  234. var chPtr *C.size_t
  235. if len(chs) > 0 { chPtr = &chs[0] }
  236. var kw kwargs
  237. ret := C.call_setup_stream(lib.fnSetupStream, unsafe.Pointer(dev), C.int(dir), cFmt, chPtr, C.size_t(len(channels)), unsafe.Pointer(&kw))
  238. if ret == nil { return 0, fmt.Errorf("soapy: setupStream failed") }
  239. return uintptr(ret), nil
  240. }
  241. func (lib *soapyLib) closeStream(dev, stream uintptr) {
  242. if lib.fnCloseStream != nil {
  243. C.call_close_stream(lib.fnCloseStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
  244. }
  245. }
  246. func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int {
  247. if lib.fnGetStreamMTU == nil { return 4096 }
  248. ret := C.call_mtu(lib.fnGetStreamMTU, unsafe.Pointer(dev), unsafe.Pointer(stream))
  249. if ret == 0 { return 4096 }
  250. return int(ret)
  251. }
  252. func (lib *soapyLib) activateStream(dev, stream uintptr) error {
  253. if lib.fnActivateStream == nil { return fmt.Errorf("not available") }
  254. rc := C.call_activate(lib.fnActivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
  255. if rc != 0 { return fmt.Errorf("soapy: activateStream failed: %d", rc) }
  256. return nil
  257. }
  258. func (lib *soapyLib) deactivateStream(dev, stream uintptr) {
  259. if lib.fnDeactivateStream != nil {
  260. C.call_deactivate(lib.fnDeactivateStream, unsafe.Pointer(dev), unsafe.Pointer(stream))
  261. }
  262. }
  263. func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) {
  264. if lib.fnWriteStream == nil { return 0, fmt.Errorf("not available") }
  265. var flags C.int
  266. rc := C.call_write(lib.fnWriteStream, unsafe.Pointer(dev), unsafe.Pointer(stream), buf, C.size_t(numElems), &flags, 100000)
  267. if rc < 0 { return 0, fmt.Errorf("soapy: writeStream returned %d", rc) }
  268. return int(rc), nil
  269. }