Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

446 Zeilen
11KB

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