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.

393 lignes
9.0KB

  1. package audio
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "io"
  6. "math"
  7. "sync"
  8. "sync/atomic"
  9. "testing"
  10. )
  11. func TestStreamSource_WriteRead(t *testing.T) {
  12. s := NewStreamSource(1024, 44100)
  13. if s.size != 1024 {
  14. t.Fatalf("expected size 1024, got %d", s.size)
  15. }
  16. // Write and read a frame
  17. f := NewFrame(0.5, -0.3)
  18. if !s.WriteFrame(f) {
  19. t.Fatal("write failed")
  20. }
  21. if s.Available() != 1 {
  22. t.Fatalf("expected 1 available, got %d", s.Available())
  23. }
  24. out := s.ReadFrame()
  25. if out.L != 0.5 || out.R != -0.3 {
  26. t.Fatalf("read mismatch: got L=%.2f R=%.2f", out.L, out.R)
  27. }
  28. if s.Available() != 0 {
  29. t.Fatalf("expected 0 available, got %d", s.Available())
  30. }
  31. }
  32. func TestStreamSource_Underrun(t *testing.T) {
  33. s := NewStreamSource(16, 44100)
  34. // Read from empty buffer — should return silence
  35. f := s.ReadFrame()
  36. if f.L != 0 || f.R != 0 {
  37. t.Fatal("expected silence on underrun")
  38. }
  39. if s.Underruns.Load() != 1 {
  40. t.Fatalf("expected 1 underrun, got %d", s.Underruns.Load())
  41. }
  42. }
  43. func TestStreamSource_Overflow(t *testing.T) {
  44. s := NewStreamSource(4, 44100) // size rounds up to 4
  45. // Fill completely
  46. for i := 0; i < 4; i++ {
  47. if !s.WriteFrame(NewFrame(Sample(float64(i)/10), 0)) {
  48. t.Fatalf("write %d failed", i)
  49. }
  50. }
  51. // Next write should overflow
  52. if s.WriteFrame(NewFrame(1, 1)) {
  53. t.Fatal("expected overflow")
  54. }
  55. if s.Overflows.Load() != 1 {
  56. t.Fatalf("expected 1 overflow, got %d", s.Overflows.Load())
  57. }
  58. }
  59. func TestStreamSource_PowerOf2Rounding(t *testing.T) {
  60. tests := []struct{ in, expect int }{
  61. {1, 1}, {2, 2}, {3, 4}, {5, 8}, {100, 128}, {1024, 1024}, {1025, 2048},
  62. }
  63. for _, tt := range tests {
  64. s := NewStreamSource(tt.in, 44100)
  65. if s.size != tt.expect {
  66. t.Fatalf("NewStreamSource(%d): size=%d, expected %d", tt.in, s.size, tt.expect)
  67. }
  68. }
  69. }
  70. func TestStreamSource_FIFO(t *testing.T) {
  71. s := NewStreamSource(64, 44100)
  72. n := 50
  73. for i := 0; i < n; i++ {
  74. s.WriteFrame(NewFrame(Sample(float64(i)), 0))
  75. }
  76. for i := 0; i < n; i++ {
  77. f := s.ReadFrame()
  78. if int(f.L) != i {
  79. t.Fatalf("FIFO order broken at %d: got %d", i, int(f.L))
  80. }
  81. }
  82. }
  83. func TestStreamSource_Wraparound(t *testing.T) {
  84. s := NewStreamSource(8, 44100) // size = 8
  85. // Write and read more than buffer size to test wraparound
  86. for round := 0; round < 10; round++ {
  87. for i := 0; i < 8; i++ {
  88. val := float64(round*8 + i)
  89. if !s.WriteFrame(NewFrame(Sample(val), 0)) {
  90. t.Fatalf("write failed round=%d i=%d", round, i)
  91. }
  92. }
  93. for i := 0; i < 8; i++ {
  94. expected := float64(round*8 + i)
  95. f := s.ReadFrame()
  96. if float64(f.L) != expected {
  97. t.Fatalf("round=%d i=%d: got %f expected %f", round, i, float64(f.L), expected)
  98. }
  99. }
  100. }
  101. stats := s.Stats()
  102. if stats.Underruns != 0 || stats.Overflows != 0 {
  103. t.Fatalf("unexpected errors: underruns=%d overflows=%d", stats.Underruns, stats.Overflows)
  104. }
  105. }
  106. func TestStreamSource_WritePCM(t *testing.T) {
  107. s := NewStreamSource(256, 44100)
  108. // Create 10 stereo frames of S16LE PCM
  109. var buf bytes.Buffer
  110. for i := 0; i < 10; i++ {
  111. l := int16(i * 1000)
  112. r := int16(-i * 1000)
  113. binary.Write(&buf, binary.LittleEndian, l)
  114. binary.Write(&buf, binary.LittleEndian, r)
  115. }
  116. written := s.WritePCM(buf.Bytes())
  117. if written != 10 {
  118. t.Fatalf("expected 10 frames, wrote %d", written)
  119. }
  120. // Verify first frame
  121. f := s.ReadFrame()
  122. if f.L != 0 || f.R != 0 {
  123. t.Fatalf("frame 0: L=%.4f R=%.4f, expected 0", f.L, f.R)
  124. }
  125. // Verify frame 5
  126. for i := 1; i < 5; i++ {
  127. s.ReadFrame()
  128. }
  129. f = s.ReadFrame()
  130. expectedL := 5000.0 / 32768.0
  131. if math.Abs(float64(f.L)-expectedL) > 0.001 {
  132. t.Fatalf("frame 5 L=%.4f, expected %.4f", f.L, expectedL)
  133. }
  134. }
  135. func TestStreamSource_ConcurrentSPSC(t *testing.T) {
  136. s := NewStreamSource(4096, 44100)
  137. frames := 50000
  138. var producerDone atomic.Bool
  139. var wg sync.WaitGroup
  140. wg.Add(2)
  141. // Producer
  142. go func() {
  143. defer wg.Done()
  144. for i := 0; i < frames; i++ {
  145. for !s.WriteFrame(NewFrame(Sample(float64(i+1)), 0)) {
  146. // Buffer full — yield
  147. }
  148. }
  149. producerDone.Store(true)
  150. }()
  151. // Consumer
  152. var lastVal float64
  153. var orderOK = true
  154. var readCount int
  155. go func() {
  156. defer wg.Done()
  157. for {
  158. if s.Available() == 0 {
  159. if producerDone.Load() {
  160. break
  161. }
  162. continue
  163. }
  164. f := s.ReadFrame()
  165. readCount++
  166. v := float64(f.L)
  167. if v > 0 && v < lastVal {
  168. orderOK = false
  169. }
  170. if v > 0 {
  171. lastVal = v
  172. }
  173. }
  174. }()
  175. wg.Wait()
  176. if !orderOK {
  177. t.Fatal("FIFO order broken in concurrent SPSC")
  178. }
  179. if readCount < frames/2 {
  180. t.Fatalf("read too few frames: %d (expected ~%d)", readCount, frames)
  181. }
  182. }
  183. func TestStreamSource_StatsBufferedDuration(t *testing.T) {
  184. rate := 48000
  185. s := NewStreamSource(128, rate)
  186. for i := 0; i < 24; i++ {
  187. s.WriteFrame(NewFrame(0, 0))
  188. }
  189. stats := s.Stats()
  190. if stats.BufferedDurationSeconds <= 0 {
  191. t.Fatalf("expected buffered duration > 0, got %.6f", stats.BufferedDurationSeconds)
  192. }
  193. expected := float64(stats.Available) / float64(rate)
  194. if math.Abs(stats.BufferedDurationSeconds-expected) > 1e-9 {
  195. t.Fatalf("buffered duration %.9f != expected %.9f", stats.BufferedDurationSeconds, expected)
  196. }
  197. }
  198. // --- StreamResampler tests ---
  199. func TestStreamResampler_1to1(t *testing.T) {
  200. s := NewStreamSource(256, 44100)
  201. r := NewStreamResampler(s, 44100) // 1:1
  202. for i := 0; i < 100; i++ {
  203. s.WriteFrame(NewFrame(Sample(float64(i)/100), 0))
  204. }
  205. // At 1:1 ratio, output should track input with a small startup delay.
  206. // Skip first few samples (resampler priming), then verify monotonic increase.
  207. prev := -1.0
  208. for i := 0; i < 90; i++ {
  209. f := r.NextFrame()
  210. v := float64(f.L)
  211. if i > 5 && v < prev-0.001 {
  212. t.Fatalf("sample %d: non-monotonic %.4f < %.4f", i, v, prev)
  213. }
  214. if v > 0 {
  215. prev = v
  216. }
  217. }
  218. // Final value should be close to 0.9 (we wrote 0..0.99)
  219. if prev < 0.5 {
  220. t.Fatalf("final value %.4f too low (expected > 0.5)", prev)
  221. }
  222. }
  223. func TestStreamResampler_Upsample(t *testing.T) {
  224. // 44100 → 228000 (ratio ≈ 0.1934, ~5.17× upsampling)
  225. s := NewStreamSource(4096, 44100)
  226. r := NewStreamResampler(s, 228000)
  227. // Write 1000 frames of a 1kHz sine at 44100 Hz
  228. for i := 0; i < 1000; i++ {
  229. v := math.Sin(2 * math.Pi * 1000 * float64(i) / 44100)
  230. s.WriteFrame(NewFrame(Sample(v), Sample(v)))
  231. }
  232. // Read upsampled output — should be ~5170 samples for 1000 input
  233. // (minus a few for resampler priming)
  234. out := make([]float64, 0, 5200)
  235. for i := 0; i < 5000; i++ {
  236. f := r.NextFrame()
  237. out = append(out, float64(f.L))
  238. }
  239. // Verify the output is a smooth sine, not clicks or zeros
  240. // Check that max amplitude is close to 1.0
  241. maxAmp := 0.0
  242. for _, v := range out[100:] { // skip initial ramp
  243. if math.Abs(v) > maxAmp {
  244. maxAmp = math.Abs(v)
  245. }
  246. }
  247. if maxAmp < 0.8 {
  248. t.Fatalf("max amplitude %.4f too low (expected ~1.0)", maxAmp)
  249. }
  250. // Check smoothness: no sudden jumps > 0.1 between adjacent samples
  251. maxJump := 0.0
  252. for i := 101; i < len(out); i++ {
  253. d := math.Abs(out[i] - out[i-1])
  254. if d > maxJump {
  255. maxJump = d
  256. }
  257. }
  258. // At 228kHz with 1kHz tone: max step ≈ sin(2π*1000/228000) ≈ 0.0276
  259. if maxJump > 0.05 {
  260. t.Fatalf("max inter-sample jump %.4f (expected < 0.05 for smooth sine)", maxJump)
  261. }
  262. }
  263. func TestStreamResampler_Downsample(t *testing.T) {
  264. // 96000 → 44100 (ratio ≈ 2.177, downsampling)
  265. s := NewStreamSource(8192, 96000)
  266. r := NewStreamResampler(s, 44100)
  267. // Write 4000 frames at 96kHz
  268. for i := 0; i < 4000; i++ {
  269. v := math.Sin(2 * math.Pi * 440 * float64(i) / 96000)
  270. s.WriteFrame(NewFrame(Sample(v), 0))
  271. }
  272. // Should get ~1837 output frames (4000 × 44100/96000)
  273. count := 0
  274. for i := 0; i < 1800; i++ {
  275. f := r.NextFrame()
  276. _ = f
  277. count++
  278. }
  279. if count != 1800 {
  280. t.Fatalf("expected 1800 reads, got %d", count)
  281. }
  282. }
  283. func TestStreamResampler_NilSource(t *testing.T) {
  284. r := NewStreamResampler(nil, 228000)
  285. f := r.NextFrame()
  286. if f.L != 0 || f.R != 0 {
  287. t.Fatal("expected silence from nil source")
  288. }
  289. }
  290. // --- IngestReader test ---
  291. func TestIngestReader(t *testing.T) {
  292. s := NewStreamSource(4096, 44100)
  293. // Create PCM data: 100 stereo frames
  294. var buf bytes.Buffer
  295. for i := 0; i < 100; i++ {
  296. l := int16(i * 100)
  297. r := int16(-i * 100)
  298. binary.Write(&buf, binary.LittleEndian, l)
  299. binary.Write(&buf, binary.LittleEndian, r)
  300. }
  301. // IngestReader should read all data and return nil (EOF)
  302. err := IngestReader(bytes.NewReader(buf.Bytes()), s)
  303. if err != nil {
  304. t.Fatalf("IngestReader: %v", err)
  305. }
  306. if s.Available() != 100 {
  307. t.Fatalf("expected 100 frames, got %d", s.Available())
  308. }
  309. // Verify first and last
  310. f := s.ReadFrame()
  311. if f.L != 0 {
  312. t.Fatalf("frame 0 L=%.4f, expected 0", f.L)
  313. }
  314. for i := 1; i < 99; i++ {
  315. s.ReadFrame()
  316. }
  317. f = s.ReadFrame()
  318. expectedL := 9900.0 / 32768.0
  319. if math.Abs(float64(f.L)-expectedL) > 0.01 {
  320. t.Fatalf("frame 99 L=%.4f, expected ~%.4f", f.L, expectedL)
  321. }
  322. }
  323. func TestIngestReader_Error(t *testing.T) {
  324. s := NewStreamSource(256, 44100)
  325. errReader := &errAfterN{n: 10}
  326. err := IngestReader(errReader, s)
  327. if err == nil {
  328. t.Fatal("expected error")
  329. }
  330. }
  331. type errAfterN struct {
  332. n, count int
  333. }
  334. func (r *errAfterN) Read(p []byte) (int, error) {
  335. if r.count >= r.n {
  336. return 0, io.ErrUnexpectedEOF
  337. }
  338. r.count++
  339. // Return 4 bytes (one stereo frame)
  340. if len(p) >= 4 {
  341. p[0], p[1], p[2], p[3] = 0, 0, 0, 0
  342. return 4, nil
  343. }
  344. return 0, nil
  345. }