All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
为诊断 TTS 音频播放停滞问题,添加手动测试代码验证 Resampler 是否能正常读取音频数据: - 在 speaker.Play() 前尝试从 Resampler 读取 10 个样本 - 打印读取状态和样本数据,验证数据流是否正常 - 重新创建 Resampler 确保测试不影响正常播放 此调试代码用于确认问题是在 Resampler/WAV 解码层,还是在 speaker/mixer 层 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package audio
|
||
|
||
import (
|
||
"context"
|
||
"io"
|
||
"time"
|
||
|
||
"github.com/gopxl/beep/v2"
|
||
"github.com/gopxl/beep/v2/mp3"
|
||
"github.com/gopxl/beep/v2/speaker"
|
||
"github.com/gopxl/beep/v2/wav"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
var DefaultSampleRate = beep.SampleRate(44100)
|
||
|
||
func init() {
|
||
err := speaker.Init(DefaultSampleRate, DefaultSampleRate.N(time.Second/10))
|
||
if err != nil {
|
||
panic("扬声器初始化异常: " + err.Error())
|
||
}
|
||
zap.S().Infoln("扬声器初始化完成")
|
||
}
|
||
|
||
func PlayWav(c context.Context, r io.Reader) {
|
||
zap.S().Debugln("开始 WAV 解码")
|
||
streamer, format, err := wav.Decode(r)
|
||
if err != nil {
|
||
zap.S().Errorln("WAV解码失败: ", err)
|
||
return
|
||
}
|
||
defer streamer.Close()
|
||
|
||
// 获取音频长度信息
|
||
totalSamples := streamer.Len()
|
||
zap.S().Debugf("WAV解码成功,采样率: %d, 总样本数: %d, 预计时长: %.2f秒",
|
||
format.SampleRate, totalSamples, float64(totalSamples)/float64(format.SampleRate))
|
||
|
||
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
||
|
||
ctrl := &beep.Ctrl{Streamer: s}
|
||
|
||
// 测试 Streamer 是否可以正常读取数据
|
||
testSamples := make([][2]float64, 10)
|
||
n, ok := s.Stream(testSamples)
|
||
zap.S().Debugf("测试读取 Resampler: 读取 %d 样本, ok=%v, 数据=%v", n, ok, testSamples[:n])
|
||
|
||
// 重置 streamer
|
||
s = beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
||
ctrl.Streamer = s
|
||
|
||
done := make(chan struct{})
|
||
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
||
zap.S().Debugln("音频播放完成")
|
||
close(done)
|
||
})))
|
||
|
||
zap.S().Debugln("等待音频播放完成...")
|
||
ticker := time.NewTicker(1 * time.Second)
|
||
defer ticker.Stop()
|
||
|
||
lastPos := 0
|
||
for {
|
||
select {
|
||
case <-done:
|
||
zap.S().Infoln("音频播放正常结束")
|
||
return
|
||
case <-c.Done():
|
||
zap.S().Debugf("音频播放被 context 取消: %v", c.Err())
|
||
speaker.Lock()
|
||
ctrl.Streamer = nil
|
||
speaker.Unlock()
|
||
return
|
||
case <-ticker.C:
|
||
// 获取当前播放位置
|
||
pos := streamer.Position()
|
||
if pos != lastPos {
|
||
progress := float64(pos) / float64(totalSamples) * 100
|
||
currentTime := float64(pos) / float64(format.SampleRate)
|
||
zap.S().Debugf("播放进度: %d/%d (%.1f%%), %.2f秒", pos, totalSamples, progress, currentTime)
|
||
lastPos = pos
|
||
} else {
|
||
zap.S().Debugf("播放停滞在位置: %d/%d, Streamer状态: %v",
|
||
pos, totalSamples, ctrl.Streamer != nil)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func PlayMP3(c context.Context, r io.ReadCloser) {
|
||
streamer, format, err := mp3.Decode(r)
|
||
if err != nil {
|
||
zap.S().Errorln("MP3解码失败: ", err)
|
||
return
|
||
}
|
||
defer streamer.Close()
|
||
|
||
s := beep.Resample(4, format.SampleRate, DefaultSampleRate, streamer)
|
||
|
||
ctrl := &beep.Ctrl{Streamer: s}
|
||
done := make(chan struct{})
|
||
speaker.Play(beep.Seq(ctrl, beep.Callback(func() {
|
||
close(done)
|
||
})))
|
||
|
||
for {
|
||
select {
|
||
case <-done:
|
||
return
|
||
case <-c.Done():
|
||
speaker.Lock()
|
||
ctrl.Streamer = nil
|
||
speaker.Unlock()
|
||
return
|
||
}
|
||
}
|
||
}
|