Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

155 řádky
3.5KB

  1. package aoip
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "aoiprxkit"
  7. "github.com/jan/fm-rds-tx/internal/ingest"
  8. )
  9. type stubReceiver struct {
  10. onStart func()
  11. onStop func()
  12. stats aoiprxkit.Stats
  13. }
  14. func (r *stubReceiver) Start(context.Context) error {
  15. if r.onStart != nil {
  16. r.onStart()
  17. }
  18. return nil
  19. }
  20. func (r *stubReceiver) Stop() error {
  21. if r.onStop != nil {
  22. r.onStop()
  23. }
  24. return nil
  25. }
  26. func (r *stubReceiver) Stats() aoiprxkit.Stats {
  27. return r.stats
  28. }
  29. func TestSourceEmitsChunksAndMapsStats(t *testing.T) {
  30. var handler aoiprxkit.FrameHandler
  31. rx := &stubReceiver{
  32. stats: aoiprxkit.Stats{
  33. PacketsGapLoss: 1,
  34. PacketsLateDrop: 2,
  35. JitterReorders: 1,
  36. },
  37. }
  38. src := New("aes67-test", aoiprxkit.Config{
  39. MulticastGroup: "239.10.20.30",
  40. Port: 5004,
  41. PayloadType: 97,
  42. SampleRateHz: 48000,
  43. Channels: 2,
  44. Encoding: "L24",
  45. PacketTime: time.Millisecond,
  46. JitterDepthPackets: 6,
  47. }, WithReceiverFactory(func(_ aoiprxkit.Config, onFrame aoiprxkit.FrameHandler) (ReceiverClient, error) {
  48. handler = onFrame
  49. return rx, nil
  50. }))
  51. if err := src.Start(context.Background()); err != nil {
  52. t.Fatalf("start: %v", err)
  53. }
  54. defer src.Stop()
  55. handler(aoiprxkit.PCMFrame{
  56. SequenceNumber: 100,
  57. SampleRateHz: 48000,
  58. Channels: 2,
  59. Samples: []int32{1, -1, 2, -2},
  60. ReceivedAt: time.Now(),
  61. })
  62. handler(aoiprxkit.PCMFrame{
  63. SequenceNumber: 102,
  64. SampleRateHz: 48000,
  65. Channels: 2,
  66. Samples: []int32{3, -3, 4, -4},
  67. ReceivedAt: time.Now(),
  68. })
  69. chunk1 := readChunk(t, src.Chunks())
  70. if chunk1.Discontinuity {
  71. t.Fatalf("first chunk should not be discontinuity")
  72. }
  73. chunk2 := readChunk(t, src.Chunks())
  74. if !chunk2.Discontinuity {
  75. t.Fatalf("second chunk should be discontinuity on sequence gap")
  76. }
  77. stats := src.Stats()
  78. if stats.State != "running" {
  79. t.Fatalf("state=%q want running", stats.State)
  80. }
  81. if !stats.Connected {
  82. t.Fatalf("connected=false want true")
  83. }
  84. if stats.ChunksIn != 2 {
  85. t.Fatalf("chunksIn=%d want 2", stats.ChunksIn)
  86. }
  87. if stats.SamplesIn != 8 {
  88. t.Fatalf("samplesIn=%d want 8", stats.SamplesIn)
  89. }
  90. if stats.TransportLoss != 1 {
  91. t.Fatalf("transportLoss=%d want 1", stats.TransportLoss)
  92. }
  93. if stats.Reorders != 1 {
  94. t.Fatalf("reorders=%d want 1", stats.Reorders)
  95. }
  96. if stats.Underruns != 2 {
  97. t.Fatalf("underruns=%d want 2", stats.Underruns)
  98. }
  99. if stats.JitterDepth != 6 {
  100. t.Fatalf("jitterDepth=%d want 6", stats.JitterDepth)
  101. }
  102. }
  103. func TestSourceDescriptorSupportsDetailOverride(t *testing.T) {
  104. src := New("aes67-test", aoiprxkit.Config{
  105. MulticastGroup: "239.10.20.30",
  106. Port: 5004,
  107. SampleRateHz: 48000,
  108. Channels: 2,
  109. }, WithDetail("rtp://239.10.20.30:5004 (SAP s=AES67-MAIN)"), WithOrigin(ingest.SourceOrigin{
  110. Kind: "sap-discovery",
  111. StreamName: "AES67-MAIN",
  112. Endpoint: "rtp://239.10.20.30:5004",
  113. }))
  114. desc := src.Descriptor()
  115. if desc.Detail != "rtp://239.10.20.30:5004 (SAP s=AES67-MAIN)" {
  116. t.Fatalf("detail=%q", desc.Detail)
  117. }
  118. if desc.Origin == nil {
  119. t.Fatalf("expected descriptor origin")
  120. }
  121. if desc.Origin.Kind != "sap-discovery" {
  122. t.Fatalf("origin kind=%q", desc.Origin.Kind)
  123. }
  124. if desc.Origin.StreamName != "AES67-MAIN" {
  125. t.Fatalf("origin streamName=%q", desc.Origin.StreamName)
  126. }
  127. }
  128. func readChunk(t *testing.T, ch <-chan ingest.PCMChunk) ingest.PCMChunk {
  129. t.Helper()
  130. select {
  131. case chunk, ok := <-ch:
  132. if !ok {
  133. t.Fatal("chunk channel closed")
  134. }
  135. return chunk
  136. case <-time.After(500 * time.Millisecond):
  137. t.Fatal("timeout waiting for chunk")
  138. return ingest.PCMChunk{}
  139. }
  140. }