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 }