EasyVQD/pkg/ffmpeg/core.go

198 lines
5.0 KiB
Go
Raw Normal View History

2026-01-15 19:32:33 +08:00
package ffmpeg
import (
"fmt"
"github.com/tcolgate/mp3"
"github.com/youpy/go-wav"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
// 音频转码配置
type TranscodeConfig struct {
InputPath string // 输入文件路径MP3/WAV
OutputPath string // 输出文件路径G711A
SampleRate int // 采样率(默认 8000 HzG711A 标准采样率)
Channels int // 声道数(默认 1单声道
}
// 默认转码配置
func defaultTranscodeConfig(input, output string) TranscodeConfig {
return TranscodeConfig{
InputPath: input,
OutputPath: output,
SampleRate: 8000, // G711A 标准采样率
Channels: 1, // 单声道(电话/语音常用)
}
}
// 校验输入文件类型(仅允许 MP3/WAV
func validateInputFile(inputPath string) error {
// 检查文件是否存在
if _, err := os.Stat(inputPath); os.IsNotExist(err) {
return fmt.Errorf("输入文件不存在: %s", inputPath)
}
// 校验文件扩展名
ext := strings.ToLower(filepath.Ext(inputPath))
if ext != ".mp3" && ext != ".wav" {
return fmt.Errorf("仅支持 MP3/WAV 格式,当前文件扩展名: %s", ext)
}
return nil
}
// MP3/WAV 转 G711APCMA
func TranscodeToG711A(config TranscodeConfig) error {
// 1. 校验输入文件
if err := validateInputFile(config.InputPath); err != nil {
return err
}
// 2. 补全默认配置
if config.SampleRate <= 0 {
config.SampleRate = 8000
}
if config.Channels <= 0 {
config.Channels = 1
}
// 3. 确保输出目录存在
outputDir := filepath.Dir(config.OutputPath)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("创建输出目录失败: %v", err)
}
// 4. 构建 FFmpeg 命令
// 核心参数说明:
// -i: 输入文件
// -ar: 采样率
// -ac: 声道数
// -f: 输出格式alaw 即 G711A
// -y: 覆盖已存在的输出文件
cmdArgs := []string{
"-i", config.InputPath,
"-ar", fmt.Sprintf("%d", config.SampleRate),
"-ac", fmt.Sprintf("%d", config.Channels),
"-f", "alaw", // 指定输出格式为 G711APCMA
"-y", // 覆盖输出文件(无需确认)
config.OutputPath,
}
dir, _ := os.Getwd() // Windows 路径用反斜杠,或双正斜杠
ffmpegPath := ""
switch runtime.GOOS {
case "linux":
ffmpegPath = filepath.Join(dir, "ffmpeg")
case "windows":
ffmpegPath = filepath.Join(dir, "ffmpeg.exe")
}
// 执行 FFmpeg 命令
cmd := exec.Command(ffmpegPath, cmdArgs...)
// 捕获 FFmpeg 输出(便于调试)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("转码失败: %v, FFmpeg 输出: %s", err, string(output))
}
// 校验输出文件是否生成
if _, err := os.Stat(config.OutputPath); os.IsNotExist(err) {
return fmt.Errorf("转码后输出文件未生成: %s", config.OutputPath)
}
fmt.Printf("转码成功!输入: %s → 输出: %s\n", config.InputPath, config.OutputPath)
return nil
}
func TranscodeToG711AFile(inputFile, outputFile string) error {
// 也可自定义配置(比如调整采样率)
/*
customConfig := TranscodeConfig{
InputPath: "./uploads/test.wav",
OutputPath: "./uploads/test_custom.g711a",
SampleRate: 16000, // 自定义采样率
Channels: 1,
}
err = TranscodeToG711A(customConfig)
*/
// 使用默认配置转码
err := TranscodeToG711A(defaultTranscodeConfig(inputFile, outputFile))
if err != nil {
fmt.Printf("转码失败: %v\n", err)
return fmt.Errorf("转码失败: %v\n", err)
}
return nil
}
// GetMP3Duration 获取MP3文件时长
func GetMP3Duration(filePath string) (time.Duration, error) {
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
decoder := mp3.NewDecoder(file)
var frame mp3.Frame
var totalDuration float64
skipped := 0
for {
if err := decoder.Decode(&frame, &skipped); err != nil {
if err == io.EOF {
break
}
return 0, err
}
totalDuration += frame.Duration().Seconds()
}
duration := time.Duration(totalDuration * float64(time.Second))
return duration, nil
}
// GetWAVDurationOptimized 优化的WAV文件时长获取方法
func GetWAVDurationOptimized(filePath string) (time.Duration, error) {
file, err := os.Open(filePath)
if err != nil {
return 0, err
}
defer file.Close()
reader := wav.NewReader(file)
// 使用库提供的Duration方法
duration, err := reader.Duration()
if err != nil {
return 0, err
}
return duration, nil
}
// GetAudioDuration 获取音频文件时长支持MP3和WAV
func GetAudioDuration(filePath string) (time.Duration, string, error) {
// 根据文件扩展名判断文件类型
if len(filePath) > 4 {
ext := filePath[len(filePath)-4:]
switch ext {
case ".mp3":
duration, err := GetMP3Duration(filePath)
return duration, "MP3", err
case ".wav":
duration, err := GetWAVDurationOptimized(filePath)
return duration, "WAV", err
default:
return 0, "", fmt.Errorf("不支持的音频格式: %s", ext)
}
}
// 如果无法从扩展名判断,可以尝试根据文件头部信息判断
return 0, "", fmt.Errorf("无法识别的音频格式")
}