Files
game-driver/pkg/audio/loop.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

76 lines
1.5 KiB
Go

package audio
import (
"bytes"
"io"
"sync"
"sync/atomic"
"time"
"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) {
// Read the entire MP3 into memory for seeking support
data, err := io.ReadAll(r)
if err != nil {
r.Close()
return nil, func() error { return nil }, err
}
r.Close()
dec, err := mp3.NewDecoder(bytes.NewReader(data))
if err != nil {
return nil, func() error { return nil }, err
}
// 获取采样率信息
sampleRate := int(dec.SampleRate())
// 需要重采样(使用 Sinc 高质量重采样)
var reader io.Reader = dec
if needsResampling(sampleRate) {
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)
playing := atomic.Bool{}
playing.Store(true)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for playing.Load() {
player.Play()
for playing.Load() && player.IsPlaying() {
time.Sleep(10 * time.Millisecond)
}
if playing.Load() {
// 重置解码器位置
_, _ = dec.Seek(0, io.SeekStart)
}
}
}()
cleanup := func() error {
playing.Store(false)
wg.Wait()
player.Close()
return nil
}
return player, cleanup, nil
}