golang FFmpeg音视频处理库gmf的Go语言绑定插件使用

Golang FFmpeg音视频处理库gmf的Go语言绑定插件使用

Go FFmpeg绑定

gmf是一个Go语言的FFmpeg绑定库,可以用来处理音视频文件。

安装

先决条件

当前master分支支持从1.6开始的所有主要Go版本。

构建/安装FFmpeg

需要构建最新版本的FFmpeg,必须启用--enable-shared选项:

./configure --prefix=/usr/local/ffmpeg --enable-shared
make
make install

添加pkgconfig路径:

export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/ffmpeg/lib/pkgconfig/

确保PKG_CONFIG_PATH包含FFmpeg的pkgconfig文件夹路径:

# 检查方法
pkg-config --libs libavformat

应该会打印出avformat库的有效路径。

现在只需运行:

go get github.com/3d0c/gmf

其他方法

这个包使用pkg-config方式来获取标志、包含和库路径,所以如果你已经安装了FFmpeg,只需确保你的安装包含它们(带有适当pc文件的pkgconfig/文件夹)。

Docker容器

感谢@ergoz,你可以尝试使用docker容器riftbit/ffalpine。

感谢@denismakogon,还有一个值得提及的项目。

使用示例

下面是一个使用gmf库转码视频文件的完整示例:

package main

import (
	"fmt"
	"log"
	"github.com/3d0c/gmf"
)

func main() {
	// 输入文件路径
	inputFile := "input.mp4"
	// 输出文件路径
	outputFile := "output.mp4"

	// 创建输入上下文
	inputCtx, err := gmf.NewInputCtx(inputFile)
	if err != nil {
		log.Fatal(err)
	}
	defer inputCtx.Close()

	// 创建输出上下文
	outputCtx, err := gmf.NewOutputCtx(outputFile)
	if err != nil {
		log.Fatal(err)
	}
	defer outputCtx.Close()

	// 从输入文件获取流信息
	streams, err := inputCtx.GetStreams()
	if err != nil {
		log.Fatal(err)
	}

	// 为每个流创建输出流
	for _, stream := range streams {
		// 复制流参数到输出流
		outputStream, err := outputCtx.AddStreamWithCodeCtx(stream.CodecCtx())
		if err != nil {
			log.Fatal(err)
		}

		// 设置编码器参数
		outputStream.CodecCtx().SetBitRate(stream.CodecCtx().BitRate())
		outputStream.CodecCtx().SetWidth(stream.CodecCtx().Width())
		outputStream.CodecCtx().SetHeight(stream.CodecCtx().Height())
		outputStream.CodecCtx().SetTimeBase(stream.CodecCtx().TimeBase())
		outputStream.CodecCtx().SetGopSize(stream.CodecCtx().GopSize())
		outputStream.CodecCtx().SetPixFmt(stream.CodecCtx().PixFmt())
	}

	// 初始化输出上下文
	if err := outputCtx.WriteHeader(); err != nil {
		log.Fatal(err)
	}

	// 处理输入包的循环
	for {
		// 读取输入包
		pkt, err := inputCtx.GetNextPacket()
		if err != nil {
			if err.Error() == "EOF" {
				break
			}
			log.Fatal(err)
		}

		// 写入输出包
		if err := outputCtx.WritePacket(pkt); err != nil {
			log.Fatal(err)
		}

		// 释放包
		pkt.Free()
	}

	// 写入尾部
	if err := outputCtx.WriteTrailer(); err != nil {
		log.Fatal(err)
	}

	fmt.Println("转码完成")
}

支持与贡献

如果有东西不能正常工作,直接修复它。不要犹豫提交Pull Request。

致谢

我借用了code.google.com/p/gmf上被放弃的项目的名称。原始代码可以在2013年4月3日的初始提交中找到。


更多关于golang FFmpeg音视频处理库gmf的Go语言绑定插件使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang FFmpeg音视频处理库gmf的Go语言绑定插件使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用gmf进行Go语言音视频处理

gmf是FFmpeg的Go语言绑定库,它提供了对FFmpeg功能的直接访问,可以用于音视频的编解码、转码、过滤等操作。下面我将介绍gmf的基本使用方法并提供示例代码。

安装gmf

首先需要安装gmf库:

go get -u github.com/3d0c/gmf

注意:使用前需要确保系统已安装FFmpeg开发库。

基本功能示例

1. 读取视频文件信息

package main

import (
	"fmt"
	"github.com/3d0c/gmf"
	"log"
)

func main() {
	// 打开输入文件
	inputCtx, err := gmf.NewInputCtx("input.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer inputCtx.Free()

	// 打印流信息
	fmt.Printf("File contains %d streams\n", inputCtx.StreamsCnt())
	for i := 0; i < inputCtx.StreamsCnt(); i++ {
		stream := inputCtx.GetStream(i)
		if stream == nil {
			log.Fatal("Unable to get stream")
		}
		fmt.Printf("Stream %d: %s\n", i, stream.CodecCtx().Codec().LongName())
	}
}

2. 视频转码示例

package main

import (
	"log"
	"github.com/3d0c/gmf"
)

func main() {
	inputCtx, err := gmf.NewInputCtx("input.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer inputCtx.Free()

	// 创建输出上下文
	outputCtx, err := gmf.NewOutputCtx("output.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer outputCtx.Free()

	// 查找视频流
	videoStream, err := inputCtx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
	if err != nil {
		log.Fatal(err)
	}

	// 创建输出视频流
	outputVideoStream := outputCtx.NewStream()
	if outputVideoStream == nil {
		log.Fatal("Unable to create stream in output context")
	}

	// 配置输出编解码器
	codec, err := gmf.FindEncoder("libx264")
	if err != nil {
		log.Fatal(err)
	}

	outputVideoCodecCtx := gmf.NewCodecCtx(codec)
	outputVideoCodecCtx.SetWidth(videoStream.CodecCtx().Width())
	outputVideoCodecCtx.SetHeight(videoStream.CodecCtx().Height())
	outputVideoCodeCtx.SetTimeBase(videoStream.TimeBase().AVR())
	outputVideoCodecCtx.SetPixFmt(gmf.AV_PIX_FMT_YUV420P)
	
	if err := outputVideoCodecCtx.Open(nil); err != nil {
		log.Fatal(err)
	}
	outputVideoStream.SetCodecCtx(outputVideoCodecCtx)

	// 写入文件头
	if err := outputCtx.WriteHeader(); err != nil {
		log.Fatal(err)
	}

	// 处理帧
	for packet := range inputCtx.GetNewPackets() {
		if packet.StreamIndex() != videoStream.Index() {
			continue
		}

		frames, err := videoStream.CodecCtx().Decode(packet)
		if err != nil {
			log.Printf("Error decoding packet: %s", err)
			continue
		}

		for _, frame := range frames {
			// 转换帧格式
			swsCtx := gmf.NewSwsCtx(
				videoStream.CodecCtx(),
				outputVideoCodecCtx,
				gmf.SWS_BICUBIC,
			)
			defer swsCtx.Free()

			dstFrame := gmf.NewFrame().
				SetWidth(outputVideoCodecCtx.Width()).
				SetHeight(outputVideoCodecCtx.Height()).
				SetFormat(outputVideoCodecCtx.PixFmt())
			defer dstFrame.Free()

			if err := swsCtx.Scale(frame, dstFrame); err != nil {
				log.Printf("Error scaling frame: %s", err)
				continue
			}

			// 编码帧
			pkts, err := outputVideoCodecCtx.Encode(dstFrame, -1)
			if err != nil {
				log.Printf("Error encoding frame: %s", err)
				continue
			}

			// 写入输出文件
			for _, pkt := range pkts {
				pkt.SetStreamIndex(outputVideoStream.Index())
				pkt.SetPts(outputVideoStream.Pts())
				pkt.SetDts(outputVideoStream.Dts())
				
				if err := outputCtx.WritePacket(pkt); err != nil {
					log.Printf("Error writing packet: %s", err)
				}
				
				outputVideoStream.IncrPts()
				outputVideoStream.IncrDts()
				pkt.Free()
			}
		}
		frame.Free()
	}

	// 写入文件尾
	if err := outputCtx.WriteTrailer(); err != nil {
		log.Fatal(err)
	}
}

3. 提取音频

package main

import (
	"log"
	"github.com/3d0c/gmf"
)

func extractAudio(inputFile, outputFile string) error {
	inputCtx, err := gmf.NewInputCtx(inputFile)
	if err != nil {
		return err
	}
	defer inputCtx.Free()

	outputCtx, err := gmf.NewOutputCtx(outputFile)
	if err != nil {
		return err
	}
	defer outputCtx.Free()

	audioStream, err := inputCtx.GetBestStream(gmf.AVMEDIA_TYPE_AUDIO)
	if err != nil {
		return err
	}

	outputStream := outputCtx.NewStream()
	if outputStream == nil {
		return err
	}

	codec, err := gmf.FindEncoder("aac")
	if err != nil {
		return err
	}

	outputCodecCtx := gmf.NewCodecCtx(codec)
	outputCodecCtx.SetSampleRate(audioStream.CodecCtx().SampleRate())
	outputCodecCtx.SetBitRate(audioStream.CodecCtx().BitRate())
	outputCodecCtx.SetChannels(audioStream.CodecCtx().Channels())
	outputCodecCtx.SetSampleFmt(audioStream.CodecCtx().SampleFmt())
	outputCodecCtx.SetTimeBase(audioStream.TimeBase().AVR())

	if err := outputCodecCtx.Open(nil); err != nil {
		return err
	}
	outputStream.SetCodecCtx(outputCodecCtx)

	if err := outputCtx.WriteHeader(); err != nil {
		return err
	}

	for packet := range inputCtx.GetNewPackets() {
		if packet.StreamIndex() != audioStream.Index() {
			packet.Free()
			continue
		}

		frames, err := audioStream.CodecCtx().Decode(packet)
		if err != nil {
			log.Printf("Error decoding packet: %s", err)
			continue
		}

		for _, frame := range frames {
			pkts, err := outputCodecCtx.Encode(frame, -1)
			if err != nil {
				log.Printf("Error encoding frame: %s", err)
				continue
			}

			for _, pkt := range pkts {
				pkt.SetStreamIndex(outputStream.Index())
				pkt.SetPts(outputStream.Pts())
				pkt.SetDts(outputStream.Dts())
				
				if err := outputCtx.WritePacket(pkt); err != nil {
					log.Printf("Error writing packet: %s", err)
				}
				
				outputStream.IncrPts()
				outputStream.IncrDts()
				pkt.Free()
			}
		}
		frame.Free()
	}

	return outputCtx.WriteTrailer()
}

func main() {
	if err := extractAudio("input.mp4", "output.aac"); err != nil {
		log.Fatal(err)
	}
}

注意事项

  1. gmf是FFmpeg的低级绑定,使用时需要手动管理内存,注意及时调用Free()方法释放资源
  2. 错误处理非常重要,FFmpeg操作可能会因各种原因失败
  3. 性能优化需要考虑缓冲区管理、并行处理等
  4. 不同版本的FFmpeg可能有API差异

gmf提供了对FFmpeg功能的直接访问,虽然使用起来比高级封装库复杂,但可以更灵活地控制音视频处理流程。对于更简单的需求,可以考虑使用goav或ffmpeg-go等更高级的封装库。

回到顶部