Files
game-driver/pkg/audio/sinc_resampler_test.go
mapleafgo ec168be827
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
feat(audio): 使用 Windowed Sinc 高质量重采样器替代线性插值
统一音频输出采样率为 44100Hz,使用 go-audio-resampler 库实现
Windowed Sinc + Polyphase FIR 算法(VeryHigh 28-bit 精度),
替代原有的线性插值透传方案。

主要变更:
- 新增 sincResampler:三阶段 Read 循环(填充→处理→Flush)
- 双缓冲区架构避免输出样本丢失,复用内存减少 GC 压力
- WAV/MP3/BGM 播放管线全部接入 Sinc 重采样器
- 移除旧的 linearResampler 和透传模式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 01:35:04 +08:00

217 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package audio
import (
"bytes"
"io"
"math"
"testing"
)
// TestSincResamplerUpsampling 测试上采样 16000Hz → 44100Hz
func TestSincResamplerUpsampling(t *testing.T) {
// VeryHigh 质量 FIR 延迟约 969 输入样本,数据量需远超延迟
inputSamples := make([]int16, 8000)
for i := range inputSamples {
inputSamples[i] = int16(math.Sin(2*math.Pi*440.0*float64(i)/16000.0) * 8000)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 16000, 44100, 2).(*sincResampler)
outputSamples := readAllSamples(t, r)
expectedSamples := int(float64(len(inputSamples)) * 44100.0 / 16000.0)
t.Logf("输入: %d 样本 @ 16000Hz", len(inputSamples))
t.Logf("输出: %d 样本 @ 44100Hz (期望 ~%d)", outputSamples, expectedSamples)
if outputSamples == 0 {
t.Fatal("没有输出数据")
}
// 上采样:输出应多于输入
if outputSamples <= len(inputSamples) {
t.Errorf("上采样失败:输出(%d) 应多于输入(%d)", outputSamples, len(inputSamples))
}
assertWithinTolerance(t, outputSamples, expectedSamples, 0.15)
}
// TestSincResamplerPassthrough 测试采样率相同时直接透传
func TestSincResamplerPassthrough(t *testing.T) {
inputSamples := []int16{100, 200, 300, 400, 500, 600}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 16000, 16000, 2)
if _, ok := r.(*bytes.Buffer); !ok {
t.Error("采样率相同时应该直接透传原始 reader")
}
}
// TestSincResamplerDownsampling 测试下采样 44100Hz → 16000Hz
func TestSincResamplerDownsampling(t *testing.T) {
inputSamples := make([]int16, 8000)
for i := range inputSamples {
inputSamples[i] = int16(math.Sin(2*math.Pi*440.0*float64(i)/44100.0) * 8000)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 44100, 16000, 2).(*sincResampler)
outputSamples := readAllSamples(t, r)
expectedSamples := int(float64(len(inputSamples)) * 16000.0 / 44100.0)
t.Logf("输入: %d 样本 @ 44100Hz", len(inputSamples))
t.Logf("输出: %d 样本 @ 16000Hz (期望 ~%d)", outputSamples, expectedSamples)
if outputSamples == 0 {
t.Fatal("没有输出数据")
}
// 下采样:输出应少于输入
if outputSamples >= len(inputSamples) {
t.Errorf("下采样失败:输出(%d) 应少于输入(%d)", outputSamples, len(inputSamples))
}
assertWithinTolerance(t, outputSamples, expectedSamples, 0.15)
}
// TestSincResamplerFlush 测试小数据量时 Flush 获取尾部残留
func TestSincResamplerFlush(t *testing.T) {
// 小数据集:输入少于 FIR 延迟,输出主要来自 Flush
inputSamples := make([]int16, 500)
for i := range inputSamples {
inputSamples[i] = int16(i * 100)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 16000, 44100, 2).(*sincResampler)
outputSamples := readAllSamples(t, r)
t.Logf("小数据输入: %d 样本, 输出: %d 样本 (来自 Flush)", len(inputSamples), outputSamples)
// 即使输入小于延迟Flush 也应产出数据
if outputSamples == 0 {
t.Fatal("Flush 未产生任何数据")
}
}
// TestSincResamplerShortBuffer 测试 io.Reader 边界行为
func TestSincResamplerShortBuffer(t *testing.T) {
inputSamples := make([]int16, 2000)
for i := range inputSamples {
inputSamples[i] = int16(i)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 16000, 44100, 2).(*sincResampler)
// 1 字节 buffer → ErrShortBuffer
_, err := r.Read(make([]byte, 1))
if err != io.ErrShortBuffer {
t.Errorf("期望 io.ErrShortBuffer得到: %v", err)
}
// 2 字节 buffer → 正常工作
buf := make([]byte, 2)
n, err := r.Read(buf)
if n != 2 || err != nil {
t.Errorf("2 字节 buffer 应正常读取: n=%d, err=%v", n, err)
}
}
// TestSincResamplerStreaming 测试流式多次 Read 的正确性
func TestSincResamplerStreaming(t *testing.T) {
inputSamples := make([]int16, 10000)
for i := range inputSamples {
inputSamples[i] = int16(math.Sin(2*math.Pi*440.0*float64(i)/16000.0) * 8000)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, 16000, 44100, 2).(*sincResampler)
// 小 buffer 模拟流式读取
buf := make([]byte, 128)
totalSamples := 0
readCount := 0
for {
n, err := r.Read(buf)
if n > 0 {
totalSamples += n / 2
readCount++
}
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("读取失败: %v", err)
}
}
expectedSamples := int(float64(len(inputSamples)) * 44100.0 / 16000.0)
t.Logf("流式读取: %d 次, 共 %d 样本 (期望 ~%d)", readCount, totalSamples, expectedSamples)
if readCount < 50 {
t.Errorf("流式读取次数过少: %d", readCount)
}
assertWithinTolerance(t, totalSamples, expectedSamples, 0.15)
}
// TestSincResamplerSineWave 测试已知正弦波信号的重采样
func TestSincResamplerSineWave(t *testing.T) {
const freq = 440.0
const inRate = 16000
inputSamples := make([]int16, inRate/4) // 0.25 秒
for i := range inputSamples {
inputSamples[i] = int16(math.Sin(2*math.Pi*freq*float64(i)/float64(inRate)) * 16000)
}
inputData := encodeInt16LE(inputSamples)
r := newSincResampler(inputData, inRate, 44100, 2).(*sincResampler)
output := readAllSamples(t, r)
expected := int(float64(len(inputSamples)) * 44100.0 / float64(inRate))
t.Logf("440Hz 正弦波: %d → %d 样本 (期望 ~%d)", len(inputSamples), output, expected)
if output == 0 {
t.Fatal("正弦波重采样无输出")
}
assertWithinTolerance(t, output, expected, 0.15)
}
// --- 辅助函数 ---
func encodeInt16LE(samples []int16) *bytes.Buffer {
buf := bytes.NewBuffer(nil)
for _, s := range samples {
buf.Write([]byte{byte(s), byte(s >> 8)})
}
return buf
}
func readAllSamples(t *testing.T, r io.Reader) int {
t.Helper()
outputData := bytes.NewBuffer(nil)
buf := make([]byte, 4096)
for {
n, err := r.Read(buf)
if n > 0 {
outputData.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("读取失败: %v", err)
}
}
return outputData.Len() / 2
}
func assertWithinTolerance(t *testing.T, actual, expected int, tolerance float64) {
t.Helper()
delta := math.Abs(float64(actual - expected))
maxDelta := float64(expected) * tolerance
if delta > maxDelta && delta > 10 {
t.Errorf("超出容忍度: 实际 %d, 期望 %d (差: %.0f, 上限: %.0f)",
actual, expected, delta, maxDelta)
}
}