feat(audio): 使用 Windowed Sinc 高质量重采样器替代线性插值
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed

统一音频输出采样率为 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>
This commit is contained in:
2026-04-09 01:35:04 +08:00
parent 1feb9f1e75
commit ec168be827
10 changed files with 415 additions and 192 deletions

View File

@@ -9,17 +9,12 @@ import (
"github.com/ebitengine/oto/v3"
"github.com/hajimehoshi/go-mp3"
"go.uber.org/zap"
)
// PlayMP3Loop 循环播放 MP3(非阻塞)
// 返回 player 和清理函数,调用者负责 defer cleanup()
func PlayMP3Loop(r io.ReadCloser) (*oto.Player, func() error, error) {
otoCtx, err := initContext()
if err != nil {
r.Close()
return nil, func() error { return nil }, err
}
// Read the entire MP3 into memory for seeking support
data, err := io.ReadAll(r)
if err != nil {
@@ -36,14 +31,16 @@ func PlayMP3Loop(r io.ReadCloser) (*oto.Player, func() error, error) {
// 获取采样率信息
sampleRate := int(dec.SampleRate())
// 需要重采样
// 需要重采样(使用 Sinc 高质量重采样)
var reader io.Reader = dec
if needsResampling(sampleRate) {
resampleReader, err := newResamplingReader(dec, sampleRate, UniversalSampleRate, 2)
if err != nil {
return nil, func() error { return nil }, err
}
reader = resampleReader
zap.S().Infof("BGM Sinc 重采样: %d Hz → %d Hz", sampleRate, UniversalSampleRate)
reader = newSincResampler(dec, sampleRate, UniversalSampleRate, 2)
}
otoCtx, err := initContext()
if err != nil {
return nil, func() error { return nil }, err
}
player := otoCtx.NewPlayer(reader)