问题: - 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
109 lines
2.3 KiB
Go
109 lines
2.3 KiB
Go
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
|
||
}
|