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

166 行
4.1KB

  1. package watermark
  2. import (
  3. "math"
  4. "testing"
  5. )
  6. func TestSTFTRoundTrip(t *testing.T) {
  7. const key = "test-stft-key"
  8. const duration = 150.0 // seconds — need > 136.5s for one full WM cycle
  9. nSamples := int(duration * WMRate)
  10. t.Logf("Generating %d samples @ %d Hz (%.1fs)", nSamples, WMRate, duration)
  11. t.Logf("WM cycle: %d STFT frames, %.1fs", FramesPerWM, float64(SamplesPerWM)/WMRate)
  12. // Generate test signal: broadband noise (the multiplicative watermark
  13. // needs energy in all frequency bins to work — a pure tone only has
  14. // energy in one bin and the watermark has no effect on silent bins)
  15. audio := make([]float64, nSamples)
  16. // Simple LCG pseudo-random for reproducibility
  17. var lcg uint64 = 12345
  18. for i := range audio {
  19. lcg = lcg*6364136223846793005 + 1442695040888963407
  20. audio[i] = 0.3 * (float64(int32(lcg>>33))/float64(1<<31))
  21. }
  22. rmsIn := rmsF64(audio)
  23. t.Logf("Input RMS: %.1f dBFS", 20*math.Log10(rmsIn+1e-12))
  24. // Embed watermark
  25. embedder := NewSTFTEmbedder(key)
  26. watermarked := embedder.ProcessBlock(audio)
  27. rmsOut := rmsF64(watermarked)
  28. t.Logf("Output RMS: %.1f dBFS", 20*math.Log10(rmsOut+1e-12))
  29. t.Logf("RMS change: %.2f dB", 20*math.Log10(rmsOut/rmsIn))
  30. // Detect watermark
  31. detector := NewSTFTDetector()
  32. // Skip embedder OLA startup (first FFTSize samples are pass-through, not watermarked)
  33. corrs, offset := detector.Detect(watermarked[FFTSize:])
  34. t.Logf("Detection offset: %d", offset)
  35. // Check correlations
  36. var nPositive, nNegative int
  37. var sumAbs float64
  38. for _, c := range corrs {
  39. sumAbs += math.Abs(c)
  40. if c > 0 {
  41. nPositive++
  42. } else {
  43. nNegative++
  44. }
  45. }
  46. avgAbs := sumAbs / float64(payloadBits)
  47. t.Logf("Correlations: avg|c|=%.1f, positive=%d, negative=%d", avgAbs, nPositive, nNegative)
  48. if avgAbs < 1.0 {
  49. t.Errorf("avg|c| too low: %.1f (expected >> 1.0)", avgAbs)
  50. }
  51. // Check against known payload
  52. payload := KeyToPayload(key)
  53. codeword := RSEncode(payload)
  54. var expectedBits [payloadBits]int
  55. for i := 0; i < payloadBits; i++ {
  56. expectedBits[i] = int((codeword[i/8] >> uint(7-(i%8))) & 1)
  57. }
  58. nerr := 0
  59. for i := 0; i < payloadBits; i++ {
  60. hard := 0
  61. if corrs[i] < 0 {
  62. hard = 1
  63. }
  64. if hard != expectedBits[i] {
  65. nerr++
  66. t.Logf(" ERR bit %d: corr=%+.2f, expected=%d got=%d", i, corrs[i], expectedBits[i], hard)
  67. }
  68. }
  69. t.Logf("BER: %d/%d (%.1f%%)", nerr, payloadBits, 100*float64(nerr)/float64(payloadBits))
  70. if nerr > 20 {
  71. t.Errorf("BER too high: %d/%d", nerr, payloadBits)
  72. }
  73. // Try RS decode
  74. var recv [rsTotalBytes]byte
  75. for i := 0; i < payloadBits; i++ {
  76. if corrs[i] < 0 {
  77. recv[i/8] |= 1 << uint(7-(i%8))
  78. }
  79. }
  80. // Try with erasures if needed
  81. decoded := false
  82. for nErase := 0; nErase <= rsCheckBytes; nErase++ {
  83. if nErase == 0 {
  84. // Try zero erasures (valid if BER=0)
  85. p, ok := RSDecode(recv, nil)
  86. if ok {
  87. if KeyMatchesPayload(key, p) {
  88. t.Logf("Decoded with 0 erasures: MATCH ✓")
  89. decoded = true
  90. break
  91. }
  92. }
  93. continue
  94. }
  95. // Erase weakest bytes by |correlation|
  96. type bc struct{ idx int; conf float64 }
  97. byteConfs := make([]bc, rsTotalBytes)
  98. for b := 0; b < rsTotalBytes; b++ {
  99. minC := math.Abs(corrs[b*8])
  100. for bit := 1; bit < 8; bit++ {
  101. c := math.Abs(corrs[b*8+bit])
  102. if c < minC {
  103. minC = c
  104. }
  105. }
  106. byteConfs[b] = bc{b, minC}
  107. }
  108. // Sort by confidence (weakest first)
  109. for i := 0; i < len(byteConfs); i++ {
  110. for j := i + 1; j < len(byteConfs); j++ {
  111. if byteConfs[j].conf < byteConfs[i].conf {
  112. byteConfs[i], byteConfs[j] = byteConfs[j], byteConfs[i]
  113. }
  114. }
  115. }
  116. erasePos := make([]int, nErase)
  117. for i := 0; i < nErase; i++ {
  118. erasePos[i] = byteConfs[i].idx
  119. }
  120. // Sort positions
  121. for i := 0; i < len(erasePos); i++ {
  122. for j := i + 1; j < len(erasePos); j++ {
  123. if erasePos[j] < erasePos[i] {
  124. erasePos[i], erasePos[j] = erasePos[j], erasePos[i]
  125. }
  126. }
  127. }
  128. p, ok := RSDecode(recv, erasePos)
  129. if ok {
  130. if KeyMatchesPayload(key, p) {
  131. t.Logf("Decoded with %d erasures: MATCH ✓", nErase)
  132. decoded = true
  133. break
  134. }
  135. }
  136. }
  137. if !decoded {
  138. t.Errorf("RS decode FAILED")
  139. }
  140. }
  141. func rmsF64(s []float64) float64 {
  142. var acc float64
  143. for _, v := range s {
  144. acc += v * v
  145. }
  146. return math.Sqrt(acc / float64(len(s)))
  147. }