Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

318 lignes
6.3KB

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