Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

338 Zeilen
6.8KB

  1. package aoip
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "sync"
  7. "sync/atomic"
  8. "time"
  9. "aoiprxkit"
  10. "github.com/jan/fm-rds-tx/internal/ingest"
  11. )
  12. type ReceiverClient interface {
  13. Start(ctx context.Context) error
  14. Stop() error
  15. Stats() aoiprxkit.Stats
  16. }
  17. type ReceiverFactory func(cfg aoiprxkit.Config, onFrame aoiprxkit.FrameHandler) (ReceiverClient, error)
  18. type Option func(*Source)
  19. func WithReceiverFactory(factory ReceiverFactory) Option {
  20. return func(s *Source) {
  21. if factory != nil {
  22. s.factory = factory
  23. }
  24. }
  25. }
  26. func WithDetail(detail string) Option {
  27. return func(s *Source) {
  28. s.detail = detail
  29. }
  30. }
  31. func WithOrigin(origin ingest.SourceOrigin) Option {
  32. return func(s *Source) {
  33. clone := origin
  34. s.origin = &clone
  35. }
  36. }
  37. type Source struct {
  38. id string
  39. cfg aoiprxkit.Config
  40. factory ReceiverFactory
  41. detail string
  42. origin *ingest.SourceOrigin
  43. chunks chan ingest.PCMChunk
  44. errs chan error
  45. cancel context.CancelFunc
  46. wg sync.WaitGroup
  47. mu sync.Mutex
  48. rx ReceiverClient
  49. started atomic.Bool
  50. closeOnce sync.Once
  51. state atomic.Value // string
  52. connected atomic.Bool
  53. chunksIn atomic.Uint64
  54. samplesIn atomic.Uint64
  55. overflows atomic.Uint64
  56. discontinuities atomic.Uint64
  57. transportLoss atomic.Uint64
  58. reorders atomic.Uint64
  59. lastChunkAtUnix atomic.Int64
  60. lastError atomic.Value // string
  61. nextSeq atomic.Uint64
  62. seqMu sync.Mutex
  63. lastFrame uint16
  64. lastHasVal bool
  65. }
  66. func New(id string, cfg aoiprxkit.Config, opts ...Option) *Source {
  67. if id == "" {
  68. id = "aes67-main"
  69. }
  70. if cfg.MulticastGroup == "" {
  71. cfg = aoiprxkit.DefaultConfig()
  72. }
  73. s := &Source{
  74. id: id,
  75. cfg: cfg,
  76. factory: newReceiverAdapter,
  77. chunks: make(chan ingest.PCMChunk, 64),
  78. errs: make(chan error, 8),
  79. }
  80. for _, opt := range opts {
  81. if opt != nil {
  82. opt(s)
  83. }
  84. }
  85. s.state.Store("idle")
  86. s.lastError.Store("")
  87. return s
  88. }
  89. func (s *Source) Descriptor() ingest.SourceDescriptor {
  90. detail := s.detail
  91. if detail == "" {
  92. detail = fmt.Sprintf("rtp://%s:%d", s.cfg.MulticastGroup, s.cfg.Port)
  93. }
  94. origin := s.origin
  95. if origin == nil {
  96. origin = &ingest.SourceOrigin{
  97. Kind: "manual",
  98. }
  99. }
  100. if origin.Endpoint == "" {
  101. copyOrigin := *origin
  102. copyOrigin.Endpoint = fmt.Sprintf("rtp://%s:%d", s.cfg.MulticastGroup, s.cfg.Port)
  103. origin = &copyOrigin
  104. }
  105. return ingest.SourceDescriptor{
  106. ID: s.id,
  107. Kind: "aes67",
  108. Family: "aoip",
  109. Transport: "rtp",
  110. Codec: "l24",
  111. Channels: s.cfg.Channels,
  112. SampleRateHz: s.cfg.SampleRateHz,
  113. Detail: detail,
  114. Origin: origin,
  115. }
  116. }
  117. func (s *Source) Start(ctx context.Context) error {
  118. if !s.started.CompareAndSwap(false, true) {
  119. return nil
  120. }
  121. rx, err := s.factory(s.cfg, s.handleFrame)
  122. if err != nil {
  123. s.started.Store(false)
  124. s.connected.Store(false)
  125. s.state.Store("failed")
  126. s.setError(err)
  127. return err
  128. }
  129. runCtx, cancel := context.WithCancel(ctx)
  130. s.cancel = cancel
  131. s.mu.Lock()
  132. s.rx = rx
  133. s.mu.Unlock()
  134. s.lastError.Store("")
  135. s.connected.Store(false)
  136. s.state.Store("connecting")
  137. if err := rx.Start(runCtx); err != nil {
  138. s.started.Store(false)
  139. s.connected.Store(false)
  140. s.state.Store("failed")
  141. s.setError(err)
  142. return err
  143. }
  144. s.connected.Store(true)
  145. s.state.Store("running")
  146. s.wg.Add(1)
  147. go func() {
  148. defer s.wg.Done()
  149. <-runCtx.Done()
  150. _ = s.stopReceiver()
  151. s.connected.Store(false)
  152. s.closeChannels()
  153. }()
  154. return nil
  155. }
  156. func (s *Source) Stop() error {
  157. if !s.started.CompareAndSwap(true, false) {
  158. return nil
  159. }
  160. if s.cancel != nil {
  161. s.cancel()
  162. }
  163. if err := s.stopReceiver(); err != nil {
  164. s.setError(err)
  165. s.state.Store("failed")
  166. }
  167. s.wg.Wait()
  168. s.connected.Store(false)
  169. state, _ := s.state.Load().(string)
  170. if state != "failed" {
  171. s.state.Store("stopped")
  172. }
  173. return nil
  174. }
  175. func (s *Source) Chunks() <-chan ingest.PCMChunk { return s.chunks }
  176. func (s *Source) Errors() <-chan error { return s.errs }
  177. func (s *Source) Stats() ingest.SourceStats {
  178. state, _ := s.state.Load().(string)
  179. last := s.lastChunkAtUnix.Load()
  180. errStr, _ := s.lastError.Load().(string)
  181. var lastChunkAt time.Time
  182. if last > 0 {
  183. lastChunkAt = time.Unix(0, last)
  184. }
  185. var rxStats aoiprxkit.Stats
  186. s.mu.Lock()
  187. rx := s.rx
  188. s.mu.Unlock()
  189. if rx != nil {
  190. rxStats = rx.Stats()
  191. }
  192. transportLoss := s.transportLoss.Load()
  193. if rxStats.PacketsGapLoss > transportLoss {
  194. transportLoss = rxStats.PacketsGapLoss
  195. }
  196. reorders := s.reorders.Load()
  197. if rxStats.JitterReorders > reorders {
  198. reorders = rxStats.JitterReorders
  199. }
  200. return ingest.SourceStats{
  201. State: state,
  202. Connected: s.connected.Load(),
  203. LastChunkAt: lastChunkAt,
  204. ChunksIn: s.chunksIn.Load(),
  205. SamplesIn: s.samplesIn.Load(),
  206. Overflows: s.overflows.Load(),
  207. Underruns: rxStats.PacketsLateDrop,
  208. Discontinuities: s.discontinuities.Load() + rxStats.PacketsLateDrop,
  209. TransportLoss: transportLoss,
  210. Reorders: reorders,
  211. JitterDepth: s.cfg.JitterDepthPackets,
  212. LastError: errStr,
  213. }
  214. }
  215. func (s *Source) handleFrame(frame aoiprxkit.PCMFrame) {
  216. if !s.started.Load() {
  217. return
  218. }
  219. discontinuity := false
  220. s.seqMu.Lock()
  221. if s.lastHasVal {
  222. expected := s.lastFrame + 1
  223. if frame.SequenceNumber != expected {
  224. discontinuity = true
  225. delta := int16(frame.SequenceNumber - expected)
  226. if delta > 0 {
  227. s.transportLoss.Add(uint64(delta))
  228. } else {
  229. s.reorders.Add(1)
  230. }
  231. }
  232. }
  233. s.lastFrame = frame.SequenceNumber
  234. s.lastHasVal = true
  235. s.seqMu.Unlock()
  236. chunk := ingest.PCMChunk{
  237. Samples: append([]int32(nil), frame.Samples...),
  238. Channels: frame.Channels,
  239. SampleRateHz: frame.SampleRateHz,
  240. Sequence: s.nextSeq.Add(1) - 1,
  241. Timestamp: frame.ReceivedAt,
  242. SourceID: s.id,
  243. Discontinuity: discontinuity,
  244. }
  245. s.chunksIn.Add(1)
  246. s.samplesIn.Add(uint64(len(chunk.Samples)))
  247. s.lastChunkAtUnix.Store(time.Now().UnixNano())
  248. if discontinuity {
  249. s.discontinuities.Add(1)
  250. }
  251. select {
  252. case s.chunks <- chunk:
  253. default:
  254. s.overflows.Add(1)
  255. s.discontinuities.Add(1)
  256. s.setError(io.ErrShortBuffer)
  257. s.emitError(fmt.Errorf("aes67 chunk buffer overflow"))
  258. }
  259. }
  260. func (s *Source) stopReceiver() error {
  261. s.mu.Lock()
  262. rx := s.rx
  263. s.rx = nil
  264. s.mu.Unlock()
  265. if rx == nil {
  266. return nil
  267. }
  268. return rx.Stop()
  269. }
  270. func (s *Source) closeChannels() {
  271. s.closeOnce.Do(func() {
  272. close(s.chunks)
  273. close(s.errs)
  274. })
  275. }
  276. func (s *Source) setError(err error) {
  277. if err == nil {
  278. return
  279. }
  280. s.lastError.Store(err.Error())
  281. s.emitError(err)
  282. }
  283. func (s *Source) emitError(err error) {
  284. if err == nil {
  285. return
  286. }
  287. select {
  288. case s.errs <- err:
  289. default:
  290. }
  291. }
  292. type receiverAdapter struct {
  293. *aoiprxkit.Receiver
  294. }
  295. func newReceiverAdapter(cfg aoiprxkit.Config, onFrame aoiprxkit.FrameHandler) (ReceiverClient, error) {
  296. rx, err := aoiprxkit.NewReceiver(cfg, onFrame)
  297. if err != nil {
  298. return nil, err
  299. }
  300. return &receiverAdapter{Receiver: rx}, nil
  301. }