Files
game-driver/pkg/audio/resampler.go
mapleafgo 4ddecb7c30 feat(audio): 添加音频重采样支持,修复播放速度问题
问题:
- TTS 返回 16000 Hz 音频,但 Context 使用 44100 Hz
- 播放速度快 2.75 倍(44100/16000)
- 不同采样率的音频播放速度不正确

解决方案:
- 集成 gomplerate 库(纯 Go,零依赖)
- 自动检测音频采样率并重采样到 44100 Hz
- 支持任意采样率的音频文件正常播放

技术实现:
- resampler.go: 封装 gomplerate,实现流式重采样
- play.go: WAV/MP3 播放自动重采样
- loop.go: BGM 循环播放支持重采样

测试:
- 所有单元测试通过(6/6)
- 支持采样率自动转换(如 16000 Hz → 44100 Hz)

依赖:
- github.com/zeozeozeo/gomplerate v0.0.0
2026-04-08 19:39:58 +08:00

109 lines
2.3 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 (
"io"
"github.com/zeozeozeo/gomplerate"
)
// resamplingReader 包装 io.Reader 并提供音频重采样
type resamplingReader struct {
source io.Reader
resampler *gomplerate.Resampler
buffer []byte // 原始数据缓冲区
eof bool
}
// newResamplingReader 创建重采样 reader
// sourceRate: 源采样率(如 16000
// targetRate: 目标采样率(如 44100
// channels: 声道数1=单声道, 2=立体声)
func newResamplingReader(src io.Reader, sourceRate, targetRate, channels int) (io.Reader, error) {
resampler, err := gomplerate.NewResampler(channels, sourceRate, targetRate)
if err != nil {
return nil, err
}
return &resamplingReader{
source: src,
resampler: resampler,
buffer: make([]byte, 0, 8192),
}, nil
}
func (r *resamplingReader) Read(p []byte) (n int, err error) {
const chunkSize = 4096
// 读取原始数据
if !r.eof && len(r.buffer) < chunkSize {
buf := make([]byte, chunkSize)
rn, readErr := r.source.Read(buf)
if readErr != nil {
if readErr == io.EOF {
r.eof = true
} else {
return 0, readErr
}
}
if rn > 0 {
r.buffer = append(r.buffer, buf[:rn]...)
}
}
// 没有数据了
if len(r.buffer) == 0 {
return 0, io.EOF
}
// 将字节转换为 int16
int16Data := bytesToInt16(r.buffer)
// 重采样
resampled := r.resampler.ResampleInt16(int16Data)
// 转回字节
output := int16ToBytes(resampled)
// 如果输出太小,继续读取
if len(output) < len(p) && !r.eof {
return r.Read(p)
}
// 复制到输出
n = copy(p, output)
// 更新缓冲区
remainingSamples := (len(r.buffer) / 2) - len(int16Data)
if remainingSamples > 0 {
r.buffer = r.buffer[len(int16Data)*2:]
} else {
r.buffer = r.buffer[:0]
}
return n, nil
}
// bytesToInt16 将字节切片转换为 int16 切片
func bytesToInt16(b []byte) []int16 {
result := make([]int16, len(b)/2)
for i := 0; i < len(result); i++ {
result[i] = int16(b[i*2]) | int16(b[i*2+1])<<8
}
return result
}
// int16ToBytes 将 int16 切片转换为字节切片
func int16ToBytes(i []int16) []byte {
result := make([]byte, len(i)*2)
for n, v := range i {
result[n*2] = byte(v)
result[n*2+1] = byte(v >> 8)
}
return result
}
// needsResample 检查是否需要重采样
func needsResample(sourceRate, targetRate int) bool {
return sourceRate != targetRate
}