Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

294 строки
8.0KB

  1. //go:build soapy && windows
  2. package soapysdr
  3. import (
  4. "fmt"
  5. "math"
  6. "syscall"
  7. "unsafe"
  8. )
  9. type soapyLib struct {
  10. dll *syscall.DLL
  11. pEnumerate *syscall.Proc
  12. pKwargsListClear *syscall.Proc
  13. pMake *syscall.Proc
  14. pUnmake *syscall.Proc
  15. pSetSampleRate *syscall.Proc
  16. pSetFrequency *syscall.Proc
  17. pSetGain *syscall.Proc
  18. pGetGainRange *syscall.Proc
  19. pSetupStream *syscall.Proc
  20. pCloseStream *syscall.Proc
  21. pGetStreamMTU *syscall.Proc
  22. pActivateStream *syscall.Proc
  23. pDeactivateStream *syscall.Proc
  24. pWriteStream *syscall.Proc
  25. pKwargsSet *syscall.Proc
  26. }
  27. var searchPaths = []string{
  28. "SoapySDR",
  29. `C:\Program Files\PothosSDR\bin\SoapySDR.dll`,
  30. `C:\PothosSDR\bin\SoapySDR.dll`,
  31. }
  32. func loadSoapyLib() (*soapyLib, error) {
  33. var dll *syscall.DLL
  34. var lastErr error
  35. for _, path := range searchPaths {
  36. dll, lastErr = syscall.LoadDLL(path)
  37. if dll != nil {
  38. break
  39. }
  40. }
  41. if dll == nil {
  42. return nil, fmt.Errorf("cannot load SoapySDR.dll: %v (searched: %v)", lastErr, searchPaths)
  43. }
  44. mustProc := func(name string) *syscall.Proc {
  45. p, _ := dll.FindProc(name)
  46. return p
  47. }
  48. return &soapyLib{
  49. dll: dll,
  50. pEnumerate: mustProc("SoapySDRDevice_enumerate"),
  51. pKwargsListClear: mustProc("SoapySDRKwargsList_clear"),
  52. pMake: mustProc("SoapySDRDevice_make"),
  53. pUnmake: mustProc("SoapySDRDevice_unmake"),
  54. pSetSampleRate: mustProc("SoapySDRDevice_setSampleRate"),
  55. pSetFrequency: mustProc("SoapySDRDevice_setFrequency"),
  56. pSetGain: mustProc("SoapySDRDevice_setGain"),
  57. pGetGainRange: mustProc("SoapySDRDevice_getGainRange"),
  58. pSetupStream: mustProc("SoapySDRDevice_setupStream"),
  59. pCloseStream: mustProc("SoapySDRDevice_closeStream"),
  60. pGetStreamMTU: mustProc("SoapySDRDevice_getStreamMTU"),
  61. pActivateStream: mustProc("SoapySDRDevice_activateStream"),
  62. pDeactivateStream: mustProc("SoapySDRDevice_deactivateStream"),
  63. pWriteStream: mustProc("SoapySDRDevice_writeStream"),
  64. pKwargsSet: mustProc("SoapySDRKwargs_set"),
  65. }, nil
  66. }
  67. // --- KWArgs helper (stack-allocated 64-byte struct matching SoapySDRKwargs) ---
  68. // SoapySDRKwargs = { size_t size; char** keys; char** vals; }
  69. // On 64-bit: 8 + 8 + 8 = 24 bytes
  70. type kwargs struct {
  71. size uintptr
  72. keys uintptr
  73. vals uintptr
  74. }
  75. func (lib *soapyLib) kwargsSet(kw *kwargs, key, val string) {
  76. if lib.pKwargsSet == nil {
  77. return
  78. }
  79. cKey, _ := syscall.BytePtrFromString(key)
  80. cVal, _ := syscall.BytePtrFromString(val)
  81. lib.pKwargsSet.Call(uintptr(unsafe.Pointer(kw)), uintptr(unsafe.Pointer(cKey)), uintptr(unsafe.Pointer(cVal)))
  82. }
  83. // --- Enumerate ---
  84. func (lib *soapyLib) enumerate() ([]map[string]string, error) {
  85. if lib.pEnumerate == nil {
  86. return nil, fmt.Errorf("SoapySDRDevice_enumerate not found")
  87. }
  88. var kw kwargs
  89. var length uintptr
  90. ret, _, _ := lib.pEnumerate.Call(uintptr(unsafe.Pointer(&kw)), uintptr(unsafe.Pointer(&length)))
  91. if ret == 0 || length == 0 {
  92. return nil, nil
  93. }
  94. defer func() {
  95. if lib.pKwargsListClear != nil {
  96. lib.pKwargsListClear.Call(ret, length)
  97. }
  98. }()
  99. devices := make([]map[string]string, int(length))
  100. kwSize := unsafe.Sizeof(kwargs{})
  101. for i := 0; i < int(length); i++ {
  102. kw := (*kwargs)(unsafe.Pointer(ret + uintptr(i)*kwSize))
  103. m := make(map[string]string)
  104. for j := 0; j < int(kw.size); j++ {
  105. keyPtr := *(*uintptr)(unsafe.Pointer(kw.keys + uintptr(j)*unsafe.Sizeof(uintptr(0))))
  106. valPtr := *(*uintptr)(unsafe.Pointer(kw.vals + uintptr(j)*unsafe.Sizeof(uintptr(0))))
  107. if keyPtr != 0 && valPtr != 0 {
  108. m[goString(keyPtr)] = goString(valPtr)
  109. }
  110. }
  111. devices[i] = m
  112. }
  113. return devices, nil
  114. }
  115. // --- Device lifecycle ---
  116. func (lib *soapyLib) makeDevice(driver, device string, args map[string]string) (uintptr, error) {
  117. if lib.pMake == nil {
  118. return 0, fmt.Errorf("SoapySDRDevice_make not found")
  119. }
  120. var kw kwargs
  121. if driver != "" {
  122. lib.kwargsSet(&kw, "driver", driver)
  123. }
  124. if device != "" {
  125. lib.kwargsSet(&kw, "device", device)
  126. }
  127. for k, v := range args {
  128. lib.kwargsSet(&kw, k, v)
  129. }
  130. ret, _, _ := lib.pMake.Call(uintptr(unsafe.Pointer(&kw)))
  131. if ret == 0 {
  132. return 0, fmt.Errorf("soapy: failed to open device (driver=%q device=%q)", driver, device)
  133. }
  134. return ret, nil
  135. }
  136. func (lib *soapyLib) unmakeDevice(dev uintptr) {
  137. if lib.pUnmake != nil && dev != 0 {
  138. lib.pUnmake.Call(dev)
  139. }
  140. }
  141. // --- Configuration ---
  142. func (lib *soapyLib) setSampleRate(dev uintptr, dir, ch int, rate float64) error {
  143. if lib.pSetSampleRate == nil {
  144. return fmt.Errorf("setSampleRate not available")
  145. }
  146. bits := math.Float64bits(rate)
  147. rc, _, _ := lib.pSetSampleRate.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits))
  148. if int32(rc) != 0 {
  149. return fmt.Errorf("soapy: setSampleRate(%.0f) failed: %d", rate, int32(rc))
  150. }
  151. return nil
  152. }
  153. func (lib *soapyLib) setFrequency(dev uintptr, dir, ch int, freq float64) error {
  154. if lib.pSetFrequency == nil {
  155. return fmt.Errorf("setFrequency not available")
  156. }
  157. bits := math.Float64bits(freq)
  158. var kw kwargs
  159. rc, _, _ := lib.pSetFrequency.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits), uintptr(unsafe.Pointer(&kw)))
  160. if int32(rc) != 0 {
  161. return fmt.Errorf("soapy: setFrequency(%.0f) failed: %d", freq, int32(rc))
  162. }
  163. return nil
  164. }
  165. func (lib *soapyLib) setGain(dev uintptr, dir, ch int, gain float64) error {
  166. if lib.pSetGain == nil {
  167. return nil
  168. }
  169. bits := math.Float64bits(gain)
  170. lib.pSetGain.Call(dev, uintptr(dir), uintptr(ch), uintptr(bits))
  171. return nil
  172. }
  173. func (lib *soapyLib) getGainRange(dev uintptr, dir, ch int) (float64, float64) {
  174. if lib.pGetGainRange == nil {
  175. return 0, 89
  176. }
  177. // SoapySDRRange is { double minimum; double maximum; double step; } = 24 bytes
  178. var buf [3]float64
  179. lib.pGetGainRange.Call(uintptr(unsafe.Pointer(&buf)), dev, uintptr(dir), uintptr(ch))
  180. return buf[0], buf[1]
  181. }
  182. // --- Stream ---
  183. func (lib *soapyLib) setupStream(dev uintptr, dir int, format string, channels []uint) (uintptr, error) {
  184. if lib.pSetupStream == nil {
  185. return 0, fmt.Errorf("setupStream not available")
  186. }
  187. cFmt, _ := syscall.BytePtrFromString(format)
  188. var chPtr uintptr
  189. if len(channels) > 0 {
  190. chPtr = uintptr(unsafe.Pointer(&channels[0]))
  191. }
  192. var kw kwargs
  193. ret, _, _ := lib.pSetupStream.Call(dev, uintptr(dir), uintptr(unsafe.Pointer(cFmt)),
  194. chPtr, uintptr(len(channels)), uintptr(unsafe.Pointer(&kw)))
  195. if ret == 0 {
  196. return 0, fmt.Errorf("soapy: setupStream failed")
  197. }
  198. return ret, nil
  199. }
  200. func (lib *soapyLib) closeStream(dev, stream uintptr) {
  201. if lib.pCloseStream != nil {
  202. lib.pCloseStream.Call(dev, stream)
  203. }
  204. }
  205. func (lib *soapyLib) getStreamMTU(dev, stream uintptr) int {
  206. if lib.pGetStreamMTU == nil {
  207. return 4096
  208. }
  209. ret, _, _ := lib.pGetStreamMTU.Call(dev, stream)
  210. if ret == 0 {
  211. return 4096
  212. }
  213. return int(ret)
  214. }
  215. func (lib *soapyLib) activateStream(dev, stream uintptr) error {
  216. if lib.pActivateStream == nil {
  217. return fmt.Errorf("activateStream not available")
  218. }
  219. rc, _, _ := lib.pActivateStream.Call(dev, stream, 0, 0, 0)
  220. if int32(rc) != 0 {
  221. return fmt.Errorf("soapy: activateStream failed: %d", int32(rc))
  222. }
  223. return nil
  224. }
  225. func (lib *soapyLib) deactivateStream(dev, stream uintptr) {
  226. if lib.pDeactivateStream != nil {
  227. lib.pDeactivateStream.Call(dev, stream, 0, 0)
  228. }
  229. }
  230. func (lib *soapyLib) writeStream(dev, stream uintptr, buf unsafe.Pointer, numElems int) (int, error) {
  231. if lib.pWriteStream == nil {
  232. return 0, fmt.Errorf("writeStream not available")
  233. }
  234. buffs := [1]uintptr{uintptr(buf)}
  235. var flags int32
  236. rc, _, _ := lib.pWriteStream.Call(dev, stream,
  237. uintptr(unsafe.Pointer(&buffs[0])),
  238. uintptr(numElems),
  239. uintptr(unsafe.Pointer(&flags)),
  240. 0, // timeNs
  241. 100000, // timeoutUs = 100ms
  242. )
  243. n := int32(rc)
  244. if n < 0 {
  245. return 0, fmt.Errorf("soapy: writeStream returned %d", n)
  246. }
  247. return int(n), nil
  248. }
  249. // --- C string helper ---
  250. func goString(p uintptr) string {
  251. if p == 0 {
  252. return ""
  253. }
  254. buf := make([]byte, 0, 256)
  255. for i := uintptr(0); ; i++ {
  256. b := *(*byte)(unsafe.Pointer(p + i))
  257. if b == 0 {
  258. break
  259. }
  260. buf = append(buf, b)
  261. }
  262. return string(buf)
  263. }