Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

124 行
3.1KB

  1. package srt
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "testing"
  7. "time"
  8. "aoiprxkit"
  9. "github.com/jan/fm-rds-tx/internal/ingest"
  10. )
  11. type readCloser struct{ io.Reader }
  12. func (r readCloser) Close() error { return nil }
  13. func TestSourceEmitsChunksFromSRTFrames(t *testing.T) {
  14. var stream bytes.Buffer
  15. if err := aoiprxkit.WritePCM32Packet(&stream, 2, 48000, 2, 10, 100, []int32{1, 2, 3, 4}); err != nil {
  16. t.Fatalf("write packet 1: %v", err)
  17. }
  18. if err := aoiprxkit.WritePCM32Packet(&stream, 2, 48000, 2, 12, 200, []int32{5, 6, 7, 8}); err != nil {
  19. t.Fatalf("write packet 2: %v", err)
  20. }
  21. src := New("srt-test", aoiprxkit.SRTConfig{
  22. URL: "srt://127.0.0.1:9000?mode=listener",
  23. Mode: "listener",
  24. SampleRateHz: 48000,
  25. Channels: 2,
  26. }, WithConnOpener(func(ctx context.Context, cfg aoiprxkit.SRTConfig) (io.ReadCloser, error) {
  27. _ = ctx
  28. _ = cfg
  29. return readCloser{Reader: bytes.NewReader(stream.Bytes())}, nil
  30. }))
  31. desc := src.Descriptor()
  32. if desc.Origin == nil {
  33. t.Fatalf("expected descriptor origin")
  34. }
  35. if desc.Origin.Kind != "url" {
  36. t.Fatalf("origin kind=%q want url", desc.Origin.Kind)
  37. }
  38. if desc.Origin.Endpoint != "srt://127.0.0.1:9000" {
  39. t.Fatalf("origin endpoint=%q", desc.Origin.Endpoint)
  40. }
  41. if desc.Origin.Mode != "listener" {
  42. t.Fatalf("origin mode=%q want listener", desc.Origin.Mode)
  43. }
  44. if err := src.Start(context.Background()); err != nil {
  45. t.Fatalf("start: %v", err)
  46. }
  47. defer src.Stop()
  48. chunk1 := readChunk(t, src.Chunks())
  49. if chunk1.SourceID != "srt-test" {
  50. t.Fatalf("source id=%q want srt-test", chunk1.SourceID)
  51. }
  52. if chunk1.Channels != 2 || chunk1.SampleRateHz != 48000 {
  53. t.Fatalf("shape=%d/%d", chunk1.Channels, chunk1.SampleRateHz)
  54. }
  55. if chunk1.Discontinuity {
  56. t.Fatalf("first chunk should not be discontinuity")
  57. }
  58. assertSamples(t, chunk1.Samples, []int32{1, 2, 3, 4})
  59. chunk2 := readChunk(t, src.Chunks())
  60. if !chunk2.Discontinuity {
  61. t.Fatalf("second chunk should be marked discontinuity on seq gap")
  62. }
  63. assertSamples(t, chunk2.Samples, []int32{5, 6, 7, 8})
  64. stats := src.Stats()
  65. if stats.State != "running" {
  66. t.Fatalf("state=%q want running", stats.State)
  67. }
  68. if !stats.Connected {
  69. t.Fatalf("connected=false want true")
  70. }
  71. if stats.ChunksIn != 2 {
  72. t.Fatalf("chunksIn=%d want 2", stats.ChunksIn)
  73. }
  74. if stats.SamplesIn != 8 {
  75. t.Fatalf("samplesIn=%d want 8", stats.SamplesIn)
  76. }
  77. if stats.TransportLoss != 1 {
  78. t.Fatalf("transportLoss=%d want 1", stats.TransportLoss)
  79. }
  80. if stats.Discontinuities < 1 {
  81. t.Fatalf("discontinuities=%d want >=1", stats.Discontinuities)
  82. }
  83. if stats.LastChunkAt.IsZero() {
  84. t.Fatalf("lastChunkAt should be set")
  85. }
  86. }
  87. func readChunk(t *testing.T, ch <-chan ingest.PCMChunk) ingest.PCMChunk {
  88. t.Helper()
  89. select {
  90. case chunk, ok := <-ch:
  91. if !ok {
  92. t.Fatal("chunk channel closed")
  93. }
  94. return chunk
  95. case <-time.After(500 * time.Millisecond):
  96. t.Fatal("timeout waiting for chunk")
  97. return ingest.PCMChunk{}
  98. }
  99. }
  100. func assertSamples(t *testing.T, got, want []int32) {
  101. t.Helper()
  102. if len(got) != len(want) {
  103. t.Fatalf("sample len=%d want %d", len(got), len(want))
  104. }
  105. for i := range want {
  106. if got[i] != want[i] {
  107. t.Fatalf("sample[%d]=%d want %d", i, got[i], want[i])
  108. }
  109. }
  110. }