1 ffmpeg工具是什么

FFmpeg即是一款音视频编解码工具,同时也是一组音视频编码开发套件,作为编码开发套件,它为开发者提供了丰富的音视频处理的调用接口。

FFmpeg提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议的流媒体、多种多彩格式转换、多种采样率转换、多种码率转换等;FFmpeg框架提供了多种丰富的插件模块,包含封装与解封装的插件、编码与解码的插件等。ffmpeg官网下载地址:https://johnvansickle.com/ffmpeg/

2 ffmpeg工具类

项目pakage:

golang 使用ffmpeg工具实现音视频转码_封装

2.1 获取编码格式NeedTranscode

golang 使用ffmpeg工具实现音视频转码_封装_02

2.2 获取时长getDuration

2.3 获取码率getCodeRate

golang 使用ffmpeg工具实现音视频转码_bash_03

2.4 获取分辨率getResRate

golang 使用ffmpeg工具实现音视频转码_ide_04

2.5 转码transcode

指定转码格式加参数 -c:a  -c:v  type  a是Audio(音频) v是Video(视频)

ps:

ffmpeg -i input_file.ext -c:a aac output_file.aac
 ffmpeg -i input_file.ext -c:v aac output_file.mp4

golang 使用ffmpeg工具实现音视频转码_bash_05

2.6 源文件ffmpeg_util.go

package ffmpeg_util

import (
	"fmt"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"support/logger"
)

type IFfmpegTranscode interface {
	NeedTranscode(srcPath string) (bool, error)
	Transcode(srcPath, dstPath string) error
	GetDuration(srcPath string) (int, error)
	GetCodeRate(srcPath string) (int, error)
	GetResRate(srcPath string) (string, error)
}

// 默认转码格式为文件名后缀
func transcode(log logger.ILog, srcPath, dstPath string) error {
	cmd := exec.Command(getTranscodeTool(), "-i", srcPath, dstPath)
	res, err := cmd.CombinedOutput()
	log.Debug("ffmpeg exec srcPath: %s, result: %s", srcPath, res)
	return err
}

// 获取音视频时长
func getDuration(log logger.ILog, srcPath string) (int, error) {
	cmd := exec.Command(getBashTool(), "-c")
	arg := fmt.Sprintf(`%s -i %s 2>&1 | grep -m 1 'Duration:' | cut -d ' ' -f 4 | sed s/,// |  awk -F ":" '{ print (($1*3600) + ($2*60) + $3) * 1000 }'`,
		getTranscodeTool(), srcPath)
	cmd.Args = append(cmd.Args, arg)
	res, err := cmd.CombinedOutput()
	log.Debug("ffmpeg exec srcPath: %s, cmd:%s, result: %s", srcPath, arg, res)
	if err != nil {
		return 0, err
	}
	if len(string(res)) == 0 {
		return 0, nil
	}
	// Chrome只支持标准的H.264编码
	durStr := strings.Trim(string(res), "\n")
	return strconv.Atoi(durStr)
}

// 获取码率
func getCodeRate(log logger.ILog, srcPath string) (int, error) {
	cmd := exec.Command(getBashTool(), "-c")
	arg := fmt.Sprintf(`%s -i %s 2>&1 | grep 'Audio:' | grep -m 1 'Hz'| awk -F "," '{print $2}'| awk -F " " '{print $1}'`,
		getTranscodeTool(), srcPath)
	cmd.Args = append(cmd.Args, arg)
	res, err := cmd.CombinedOutput()
	log.Debug("ffmpeg exec srcPath: %s, cmd:%s, result: %s", srcPath, arg, res)
	if err != nil {
		return 0, err
	}
	// Chrome只支持标准的H.264编码
	resStr := strings.Trim(string(res), "\n")
	if len(resStr) == 0 {
		return 0, nil
	}
	return strconv.Atoi(resStr)
}

// 获取分辨率
func getResRate(log logger.ILog, srcPath string) (string, error) {

	cmd := exec.Command(getBashTool(), "-c")
	arg := fmt.Sprintf(`%s -i %s 2>&1 | grep 'Video:' | grep -m 1 'fps' `,
		getTranscodeTool(), srcPath)
	cmd.Args = append(cmd.Args, arg)
	res, err := cmd.CombinedOutput()
	if err != nil {
		return "", err
	}

	log.Debug("ffmpeg exec srcPath: %s, cmd:%s, result: %s", srcPath, arg, res)

	resStr := extractResolution(string(res))
	return resStr, nil
}
func extractResolution(input string) string {
	re := regexp.MustCompile(`\b(\d{3,4}x\d{3,4})\b`)
	matches := re.FindStringSubmatch(input)
	if len(matches) < 2 {
		return ""
	}
	return matches[1]
}

func needTranscode(log logger.ILog, srcPath, streamType, coderType string) (bool, error) {
	cmd := exec.Command(getBashTool(), "-c")
	arg := fmt.Sprintf(`%s -i %s 2>&1 | grep 'Stream' | grep -m 1 '%s:' | sed s/,// | awk -F " " '{ print $4 }'`,
		getTranscodeTool(), srcPath, streamType)
	cmd.Args = append(cmd.Args, arg)
	res, err := cmd.CombinedOutput()

	log.Debug("needTranscode exec srcPath: %s, cmd:%s, result: %s", srcPath, arg, res)

	if err != nil {
		return false, err
	}
	// Chrome支持标准的H.264编码以及MP3编码
	coder := strings.Trim(string(res), "\n")
	if coder == coderType {
		return false, nil
	}
	return true, nil
}

3 系统差异

3.1 program_linux.go

package ffmpeg_util

func getTranscodeTool() string {
	return "ffmpeg"
}

func getBashTool() string {
	return "bash"
}

3.2 program_windows.go

package ffmpeg_util

func getTranscodeTool() string {
	return "./resource/ffmpeg.exe"
}

func getBashTool() string {
	return "D:\\install\\Git\\bin\\bash.exe"
}

4 客户端

4.1 audio_client.go

package ffmpeg_util

import (
	"support/logger"
)

const coderMp3 = "mp3"
const Audio = "Audio"

type audioTranscode struct {
	log logger.ILog
}

func NewAudioTranscode(log logger.ILog) *audioTranscode {
	return &audioTranscode{
		log: log,
	}
}

func (at *audioTranscode) NeedTranscode(srcPath string) (bool, error) {
	return needTranscode(at.log, srcPath, Audio, coderMp3)
}

func (at *audioTranscode) Transcode(srcPath, dstPath string) error {
	return transcode(at.log, srcPath, dstPath)
}
func (at *audioTranscode) GetDuration(srcPath string) (int, error) {
	return getDuration(at.log, srcPath)
}

func (at *audioTranscode) GetCodeRate(srcPath string) (int, error) {
	return getCodeRate(at.log, srcPath)
}
func (at *audioTranscode) GetResRate(srcPath string) (string, error) {
	return "", nil
}

4.2 video_client.go

4.2.1 获取视频封面

golang 使用ffmpeg工具实现音视频转码_bash_06

package ffmpeg_util

import (
	"os/exec"
	"support/logger"
)

const coderH264 = "h264"
const Video = "Video"

type videoTranscode struct {
	log           logger.ILog
	transcodeTool string
	bashTool      string
}

func NewVideoTranscode(log logger.ILog) *videoTranscode {
	return &videoTranscode{
		log:           log,
		transcodeTool: getTranscodeTool(),
		bashTool:      getBashTool(),
	}
}

func (vt *videoTranscode) NeedTranscode(srcPath string) (bool, error) {
	return needTranscode(vt.log, srcPath, Video, coderH264)
}

func (vt *videoTranscode) Transcode(srcPath, dstPath string) error {
	return transcode(vt.log, srcPath, dstPath)
}
func (vt *videoTranscode) GetDuration(srcPath string) (int, error) {
	return getDuration(vt.log, srcPath)
}
func (vt *videoTranscode) GetCodeRate(srcPath string) (int, error) {
	return getCodeRate(vt.log, srcPath)
}
func (vt *videoTranscode) GetResRate(srcPath string) (string, error) {
	return getResRate(vt.log, srcPath)
}

// CaptureCover 视频截取第一帧当封面
func CaptureCover(log logger.ILog, srcPath string, coverPath string) error {
	cmd := exec.Command(getTranscodeTool(), "-i", srcPath)
	cmd.Args = append(cmd.Args, "-y", "-f", "image2")
	cmd.Args = append(cmd.Args, "-frames", "1", coverPath)
	res, err := cmd.CombinedOutput()

	log.Debug("ffmpeg exec srcPath: %s, result: %s", srcPath, res)

	return err
}

5.调用示例

golang 使用ffmpeg工具实现音视频转码_封装_07

var ft ffmpeg_util.IFfmpegTranscode

	// 前面做过fileType校验
	switch record.FileType {
	case audioFileType:
		ft = ffmpeg_util.NewAudioTranscode(log)
	case videoFileType:
		ft = ffmpeg_util.NewVideoTranscode(log)
	}

	// 本地转码
	dstPath := path.Join(videoDir, util.GenObjectId()+filepath.Ext(localPath))

	if err = ft.Transcode(localPath, dstPath); err != nil {
		log.Error("fileId:%s trans failed as:%s", record.Id, err)
		return
	}