EasyVQD/pkg/ffmpeg/core.go
2026-01-15 19:32:33 +08:00

198 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("无法识别的音频格式")
}