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

175 строки
4.8KB

  1. package factory
  2. import (
  3. "fmt"
  4. "io"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. "aoiprxkit"
  11. "github.com/jan/fm-rds-tx/internal/config"
  12. "github.com/jan/fm-rds-tx/internal/ingest"
  13. "github.com/jan/fm-rds-tx/internal/ingest/adapters/aoip"
  14. "github.com/jan/fm-rds-tx/internal/ingest/adapters/httpraw"
  15. "github.com/jan/fm-rds-tx/internal/ingest/adapters/icecast"
  16. "github.com/jan/fm-rds-tx/internal/ingest/adapters/srt"
  17. "github.com/jan/fm-rds-tx/internal/ingest/adapters/stdinpcm"
  18. )
  19. type Deps struct {
  20. Stdin io.Reader
  21. HTTP *http.Client
  22. SRTOpener aoiprxkit.SRTConnOpener
  23. AES67ReceiverFactory aoip.ReceiverFactory
  24. }
  25. type AudioIngress interface {
  26. WritePCM16(data []byte) (int, error)
  27. }
  28. func BuildSource(cfg config.Config, deps Deps) (ingest.Source, AudioIngress, error) {
  29. switch normalizeIngestKind(cfg.Ingest.Kind) {
  30. case "", "none":
  31. return nil, nil, nil
  32. case "stdin", "stdin-pcm":
  33. reader := deps.Stdin
  34. if reader == nil {
  35. reader = os.Stdin
  36. }
  37. src := stdinpcm.New("stdin-main", reader, cfg.Ingest.Stdin.SampleRateHz, cfg.Ingest.Stdin.Channels, 1024)
  38. return src, nil, nil
  39. case "http-raw":
  40. src := httpraw.New("http-raw-main", cfg.Ingest.HTTPRaw.SampleRateHz, cfg.Ingest.HTTPRaw.Channels)
  41. return src, src, nil
  42. case "icecast":
  43. src := icecast.New(
  44. "icecast-main",
  45. cfg.Ingest.Icecast.URL,
  46. deps.HTTP,
  47. icecast.ReconnectConfig{
  48. Enabled: cfg.Ingest.Reconnect.Enabled,
  49. InitialBackoffMs: cfg.Ingest.Reconnect.InitialBackoffMs,
  50. MaxBackoffMs: cfg.Ingest.Reconnect.MaxBackoffMs,
  51. },
  52. icecast.WithDecoderPreference(cfg.Ingest.Icecast.Decoder),
  53. )
  54. return src, nil, nil
  55. case "srt":
  56. srtCfg := aoiprxkit.SRTConfig{
  57. URL: cfg.Ingest.SRT.URL,
  58. Mode: cfg.Ingest.SRT.Mode,
  59. SampleRateHz: cfg.Ingest.SRT.SampleRateHz,
  60. Channels: cfg.Ingest.SRT.Channels,
  61. }
  62. opts := []srt.Option{}
  63. if deps.SRTOpener != nil {
  64. opts = append(opts, srt.WithConnOpener(deps.SRTOpener))
  65. }
  66. src := srt.New("srt-main", srtCfg, opts...)
  67. return src, nil, nil
  68. case "aes67", "aoip", "aoip-rtp":
  69. aoipCfg, err := buildAES67Config(cfg)
  70. if err != nil {
  71. return nil, nil, err
  72. }
  73. opts := []aoip.Option{}
  74. if deps.AES67ReceiverFactory != nil {
  75. opts = append(opts, aoip.WithReceiverFactory(deps.AES67ReceiverFactory))
  76. }
  77. src := aoip.New("aes67-main", aoipCfg, opts...)
  78. return src, nil, nil
  79. default:
  80. return nil, nil, fmt.Errorf("unsupported ingest kind: %s", cfg.Ingest.Kind)
  81. }
  82. }
  83. func SampleRateForKind(cfg config.Config) int {
  84. switch normalizeIngestKind(cfg.Ingest.Kind) {
  85. case "stdin", "stdin-pcm":
  86. if cfg.Ingest.Stdin.SampleRateHz > 0 {
  87. return cfg.Ingest.Stdin.SampleRateHz
  88. }
  89. case "http-raw":
  90. if cfg.Ingest.HTTPRaw.SampleRateHz > 0 {
  91. return cfg.Ingest.HTTPRaw.SampleRateHz
  92. }
  93. case "icecast":
  94. return 44100
  95. case "srt":
  96. if cfg.Ingest.SRT.SampleRateHz > 0 {
  97. return cfg.Ingest.SRT.SampleRateHz
  98. }
  99. case "aes67", "aoip", "aoip-rtp":
  100. if cfg.Ingest.AES67.SampleRateHz > 0 {
  101. return cfg.Ingest.AES67.SampleRateHz
  102. }
  103. }
  104. return 44100
  105. }
  106. func normalizeIngestKind(kind string) string {
  107. return strings.ToLower(strings.TrimSpace(kind))
  108. }
  109. func buildAES67Config(cfg config.Config) (aoiprxkit.Config, error) {
  110. base := aoiprxkit.DefaultConfig()
  111. ing := cfg.Ingest.AES67
  112. if strings.TrimSpace(ing.InterfaceName) != "" {
  113. base.InterfaceName = strings.TrimSpace(ing.InterfaceName)
  114. }
  115. if ing.PayloadType >= 0 {
  116. base.PayloadType = uint8(ing.PayloadType)
  117. }
  118. if ing.SampleRateHz > 0 {
  119. base.SampleRateHz = ing.SampleRateHz
  120. }
  121. if ing.Channels > 0 {
  122. base.Channels = ing.Channels
  123. }
  124. if strings.TrimSpace(ing.Encoding) != "" {
  125. base.Encoding = strings.ToUpper(strings.TrimSpace(ing.Encoding))
  126. }
  127. if ing.PacketTimeMs > 0 {
  128. base.PacketTime = time.Duration(ing.PacketTimeMs) * time.Millisecond
  129. }
  130. if ing.JitterDepthPackets > 0 {
  131. base.JitterDepthPackets = ing.JitterDepthPackets
  132. }
  133. if ing.ReadBufferBytes > 0 {
  134. base.ReadBufferBytes = ing.ReadBufferBytes
  135. }
  136. sdpText := strings.TrimSpace(ing.SDP)
  137. if sdpText == "" && strings.TrimSpace(ing.SDPPath) != "" {
  138. data, err := os.ReadFile(filepath.Clean(ing.SDPPath))
  139. if err != nil {
  140. return aoiprxkit.Config{}, fmt.Errorf("read ingest.aes67.sdpPath: %w", err)
  141. }
  142. sdpText = string(data)
  143. }
  144. if sdpText != "" {
  145. info, err := aoiprxkit.ParseMinimalSDP(sdpText)
  146. if err != nil {
  147. return aoiprxkit.Config{}, fmt.Errorf("parse ingest.aes67 SDP: %w", err)
  148. }
  149. parsed, err := aoiprxkit.ConfigFromSDP(base, info)
  150. if err != nil {
  151. return aoiprxkit.Config{}, fmt.Errorf("map ingest.aes67 SDP: %w", err)
  152. }
  153. return parsed, nil
  154. }
  155. if strings.TrimSpace(ing.MulticastGroup) != "" {
  156. base.MulticastGroup = strings.TrimSpace(ing.MulticastGroup)
  157. }
  158. if ing.Port > 0 {
  159. base.Port = ing.Port
  160. }
  161. if err := base.Validate(); err != nil {
  162. return aoiprxkit.Config{}, err
  163. }
  164. return base, nil
  165. }