feat(audio): 使用 Windowed Sinc 高质量重采样器替代线性插值
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
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:
@@ -14,11 +14,6 @@ import (
|
||||
|
||||
// PlayWav 播放 WAV 文件(阻塞),直到完成或 context 取消
|
||||
func PlayWav(ctx context.Context, r io.ReadCloser) error {
|
||||
otoCtx, err := initContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("音频上下文初始化失败: %w", err)
|
||||
}
|
||||
|
||||
// Read the entire file into memory since wav.NewReader needs ReadAt
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
@@ -38,20 +33,20 @@ func PlayWav(ctx context.Context, r io.ReadCloser) error {
|
||||
|
||||
duration, _ := dec.Duration()
|
||||
sourceRate := int(format.SampleRate)
|
||||
channels := int(format.NumChannels)
|
||||
|
||||
zap.S().Infof("WAV 音频: %d ch, %d Hz, 时长: %v",
|
||||
channels, sourceRate, duration)
|
||||
format.NumChannels, sourceRate, duration)
|
||||
|
||||
// 需要重采样
|
||||
// 需要重采样(使用 Sinc 高质量重采样)
|
||||
var reader io.Reader = dec
|
||||
if needsResampling(sourceRate) {
|
||||
zap.S().Infof("重采样: %d Hz → %d Hz", sourceRate, UniversalSampleRate)
|
||||
resampleReader, err := newResamplingReader(dec, sourceRate, UniversalSampleRate, channels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建重采样器失败: %w", err)
|
||||
}
|
||||
reader = resampleReader
|
||||
zap.S().Infof("Sinc 重采样: %d Hz → %d Hz", sourceRate, UniversalSampleRate)
|
||||
reader = newSincResampler(dec, sourceRate, UniversalSampleRate, int(format.NumChannels))
|
||||
}
|
||||
|
||||
otoCtx, err := initContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("音频上下文初始化失败: %w", err)
|
||||
}
|
||||
|
||||
player := otoCtx.NewPlayer(reader)
|
||||
@@ -82,11 +77,6 @@ func PlayWav(ctx context.Context, r io.ReadCloser) error {
|
||||
|
||||
// PlayMP3 播放 MP3 文件(阻塞),直到完成或 context 取消
|
||||
func PlayMP3(ctx context.Context, r io.ReadCloser) error {
|
||||
otoCtx, err := initContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("音频上下文初始化失败: %w", err)
|
||||
}
|
||||
|
||||
dec, err := mp3.NewDecoder(r)
|
||||
if err != nil {
|
||||
r.Close()
|
||||
@@ -100,17 +90,19 @@ func PlayMP3(ctx context.Context, r io.ReadCloser) error {
|
||||
channels := 2 // MP3 通常是立体声
|
||||
duration := time.Duration(float64(sampleCount)/float64(sampleRate)*1000) * time.Millisecond
|
||||
|
||||
zap.S().Infof("MP3 音频: %d Hz, 时长约: %v", sampleRate, duration)
|
||||
zap.S().Infof("MP3 音频: %d Hz → %d Hz, 时长约: %v",
|
||||
sampleRate, UniversalSampleRate, duration)
|
||||
|
||||
// 需要重采样
|
||||
// 需要重采样(使用 Sinc 高质量重采样)
|
||||
var reader io.Reader = dec
|
||||
if needsResampling(sampleRate) {
|
||||
zap.S().Infof("重采样: %d Hz → %d Hz", sampleRate, UniversalSampleRate)
|
||||
resampleReader, err := newResamplingReader(dec, sampleRate, UniversalSampleRate, channels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建重采样器失败: %w", err)
|
||||
}
|
||||
reader = resampleReader
|
||||
zap.S().Infof("Sinc 重采样: %d Hz → %d Hz", sampleRate, UniversalSampleRate)
|
||||
reader = newSincResampler(dec, sampleRate, UniversalSampleRate, channels)
|
||||
}
|
||||
|
||||
otoCtx, err := initContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("音频上下文初始化失败: %w", err)
|
||||
}
|
||||
|
||||
player := otoCtx.NewPlayer(reader)
|
||||
|
||||
Reference in New Issue
Block a user