Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.1KB

  1. package aoiprxkit
  2. import (
  3. "fmt"
  4. "net"
  5. "strconv"
  6. "strings"
  7. "time"
  8. )
  9. type SDPInfo struct {
  10. SessionName string
  11. Origin string
  12. MulticastGroup string
  13. Port int
  14. PayloadType uint8
  15. Encoding string
  16. SampleRateHz int
  17. Channels int
  18. PacketTimeMS int
  19. }
  20. // ParseMinimalSDP extracts the multicast address, port and one rtpmap line.
  21. // It is deliberately small and not a full SDP parser.
  22. func ParseMinimalSDP(s string) (SDPInfo, error) {
  23. var out SDPInfo
  24. lines := strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n")
  25. for _, raw := range lines {
  26. line := strings.TrimSpace(raw)
  27. switch {
  28. case strings.HasPrefix(line, "s="):
  29. out.SessionName = strings.TrimPrefix(line, "s=")
  30. case strings.HasPrefix(line, "o="):
  31. out.Origin = strings.TrimPrefix(line, "o=")
  32. case strings.HasPrefix(line, "c=IN IP4 "):
  33. rest := strings.TrimPrefix(line, "c=IN IP4 ")
  34. host := strings.Split(rest, "/")[0]
  35. if net.ParseIP(host) == nil {
  36. return out, fmt.Errorf("invalid multicast host in c=: %q", host)
  37. }
  38. out.MulticastGroup = host
  39. case strings.HasPrefix(line, "m=audio "):
  40. fields := strings.Fields(line)
  41. if len(fields) < 4 {
  42. return out, fmt.Errorf("invalid m=audio line")
  43. }
  44. port, err := strconv.Atoi(fields[1])
  45. if err != nil {
  46. return out, fmt.Errorf("invalid audio port: %w", err)
  47. }
  48. pt, err := strconv.Atoi(fields[3])
  49. if err != nil {
  50. return out, fmt.Errorf("invalid payload type: %w", err)
  51. }
  52. out.Port = port
  53. out.PayloadType = uint8(pt)
  54. case strings.HasPrefix(line, "a=rtpmap:"):
  55. rest := strings.TrimPrefix(line, "a=rtpmap:")
  56. parts := strings.Fields(rest)
  57. if len(parts) != 2 {
  58. return out, fmt.Errorf("invalid rtpmap line")
  59. }
  60. pt, err := strconv.Atoi(parts[0])
  61. if err != nil {
  62. return out, fmt.Errorf("invalid rtpmap payload type: %w", err)
  63. }
  64. codecParts := strings.Split(parts[1], "/")
  65. if len(codecParts) < 2 {
  66. return out, fmt.Errorf("invalid rtpmap codec tuple")
  67. }
  68. sr, err := strconv.Atoi(codecParts[1])
  69. if err != nil {
  70. return out, fmt.Errorf("invalid rtpmap sample rate: %w", err)
  71. }
  72. ch := 1
  73. if len(codecParts) >= 3 {
  74. ch, err = strconv.Atoi(codecParts[2])
  75. if err != nil {
  76. return out, fmt.Errorf("invalid rtpmap channel count: %w", err)
  77. }
  78. }
  79. out.PayloadType = uint8(pt)
  80. out.Encoding = codecParts[0]
  81. out.SampleRateHz = sr
  82. out.Channels = ch
  83. case strings.HasPrefix(line, "a=ptime:"):
  84. ms, err := strconv.Atoi(strings.TrimPrefix(line, "a=ptime:"))
  85. if err == nil {
  86. out.PacketTimeMS = ms
  87. }
  88. }
  89. }
  90. if out.MulticastGroup == "" || out.Port == 0 || out.Encoding == "" || out.SampleRateHz == 0 {
  91. return out, fmt.Errorf("incomplete SDP: %+v", out)
  92. }
  93. return out, nil
  94. }
  95. func ConfigFromSDP(base Config, info SDPInfo) (Config, error) {
  96. cfg := base
  97. cfg.MulticastGroup = info.MulticastGroup
  98. cfg.Port = info.Port
  99. cfg.PayloadType = info.PayloadType
  100. cfg.SampleRateHz = info.SampleRateHz
  101. cfg.Channels = info.Channels
  102. cfg.Encoding = info.Encoding
  103. if info.PacketTimeMS > 0 {
  104. cfg.PacketTime = time.Duration(info.PacketTimeMS) * time.Millisecond
  105. }
  106. return cfg, cfg.Validate()
  107. }