6ef9e082a6
- 前端: VAD语音检测(@ricky0123/vad-web) + useVoiceInput双模式(流式WS/REST) - Gateway: VoiceStreamManager代理WS流式STT到voice-service - Voice-service: DashScope REST → Realtime WS → Whisper三级引擎 + ffmpeg转码 - 共享模块: pkg/audio(音频转换) + pkg/dashscope(ASR REST客户端) - 清理: 移除旧plugin-manager和pkg/plugins,完成插件→工具合并 - 文档: 完善gateway-api.md和voice-service.md语音API文档 - 工具: scripts/voice/ 语音转换脚本集 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package audio
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// NormalizeFormat 规范化音频格式字符串。
|
|
func NormalizeFormat(format string) string {
|
|
switch strings.ToLower(format) {
|
|
case "pcm", "wav", "mp3", "mpeg", "ogg", "opus", "flac", "m4a", "mp4", "aac", "webm", "amr":
|
|
return strings.ToLower(format)
|
|
default:
|
|
return format
|
|
}
|
|
}
|
|
|
|
// ConvertToPCM16 将音频数据转换为 16-bit PCM 16000Hz mono。
|
|
// 对于已经是 PCM 的数据直接返回;对于 WAV 跳过 44 字节头部;
|
|
// 其他格式使用 ffmpeg 转码。
|
|
func ConvertToPCM16(data []byte, format string) ([]byte, error) {
|
|
normFormat := NormalizeFormat(format)
|
|
switch normFormat {
|
|
case "pcm":
|
|
return data, nil
|
|
case "wav":
|
|
if len(data) > 44 {
|
|
return data[44:], nil
|
|
}
|
|
return data, nil
|
|
default:
|
|
return transcodeToPCM(data, normFormat)
|
|
}
|
|
}
|
|
|
|
// transcodeToPCM 使用 ffmpeg 将音频数据转码为 PCM 16-bit 16000Hz mono。
|
|
func transcodeToPCM(data []byte, format string) ([]byte, error) {
|
|
inFile, err := os.CreateTemp(os.TempDir(), "cyrene-asr-in-*."+format)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建输入临时文件失败: %w", err)
|
|
}
|
|
inPath := inFile.Name()
|
|
defer os.Remove(inPath)
|
|
if _, err := inFile.Write(data); err != nil {
|
|
inFile.Close()
|
|
return nil, fmt.Errorf("写入输入临时文件失败: %w", err)
|
|
}
|
|
inFile.Close()
|
|
|
|
outFile, err := os.CreateTemp(os.TempDir(), "cyrene-asr-out-*.pcm")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建输出临时文件失败: %w", err)
|
|
}
|
|
outPath := outFile.Name()
|
|
outFile.Close()
|
|
defer os.Remove(outPath)
|
|
|
|
cmd := exec.Command("ffmpeg",
|
|
"-i", inPath,
|
|
"-ar", "16000",
|
|
"-ac", "1",
|
|
"-c:a", "pcm_s16le",
|
|
"-f", "s16le",
|
|
outPath,
|
|
"-y",
|
|
)
|
|
cmd.Stderr = nil
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("音频转码失败 (ffmpeg): %w", err)
|
|
}
|
|
|
|
outData, err := os.ReadFile(outPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取转码结果失败: %w", err)
|
|
}
|
|
|
|
return outData, nil
|
|
}
|
|
|
|
// IsFFmpegAvailable 检查 ffmpeg 是否可执行。
|
|
func IsFFmpegAvailable() bool {
|
|
_, err := exec.LookPath("ffmpeg")
|
|
return err == nil
|
|
}
|