golang实现高效FFmpeg C绑定功能的插件库go-astiav的使用

Golang实现高效FFmpeg C绑定功能的插件库go-astiav使用指南

介绍

go-astiav是一个为FFmpeg提供C语言绑定的Golang库。它仅兼容FFmpeg n7.0版本。

它的主要目标是:

  • 提供更符合Go语言习惯的API
    • 标准错误模式
    • 类型化的常量和标志
    • 基于结构体的函数
  • 提供与FFmpeg示例对应的Go版本实现
  • 经过全面测试

示例

示例位于项目的examples目录中,尽可能与FFmpeg示例相对应。

功能名称 go-astiav示例 FFmpeg示例
BitStream过滤 查看 X
自定义IO解复用 查看 查看
自定义IO复用 查看 X
解复用/解码 查看 查看
过滤 查看 查看
帧数据操作 查看 X
硬件解码/过滤 查看 查看
硬件编码 查看 查看
复用途 查看 查看
音频重采样 查看 查看
视频缩放 查看 查看
转码 查看 查看

提示:你可以使用testdata目录中的视频样本进行测试

使用模式

注意:为便于阅读,下面的代码没有检查错误,但在实际使用中你应该检查!

首先,所有用例都不同,不可能提供适用于所有情况的模式。这就是为什么FFmpeg的文档或源代码应该是你关于如何使用这个库的最终真相来源。这就是为什么这个库的所有方法都记录了它们使用的C函数的文档链接。

何时调用Alloc()、.Unref()和.Free()

让我们以FormatContext.ReadFrame()模式为例。帧的模式类似。

// 你可以分配packet一次并在下面的for循环中重复使用同一个对象
pkt := astiav.AllocPacket()

// 然而,一旦你完成使用packet,你需要确保释放它
defer pkt.Free()

// 循环
for {
    // 我们将使用一个闭包来方便取消引用packet
    func() {
        // 每次使用同一个packet读取帧
        formatContext.ReadFrame(pkt)

        // 然而,一旦你完成使用.ReadFrame()方法"注入"的内容,
        // 确保取消引用packet
        defer pkt.Unref()

        // 在这里你可以用你的packet做任何你想做的事情
    }()
}

从源代码安装FFmpeg

如果你不知道如何安装ffmpeg,你可以使用以下命令从源代码安装:

$ make install-ffmpeg

ffmpeg将从源代码构建在你工作目录中名为tmp的目录中。

为了让你的Go代码自动获取ffmpeg依赖项,你需要添加以下环境变量:

(不要忘记用你工作目录的绝对路径替换{{ path to your working directory }}

export CGO_LDFLAGS="-L{{ path to your working directory }}/tmp/n7.0/lib/",
export CGO_CFLAGS="-I{{ path to your working directory }}/tmp/n7.0/include/",
export PKG_CONFIG_PATH="{{ path to your working directory }}/tmp/n7.0/lib/pkgconfig",

在Windows上构建

在Windows上构建需要msys2/mingw64 gcc工具链。阅读快速入门指南安装Msys2。

完成后,从安装文件夹运行Mingw64 shell,运行以下命令:

# 更新包
pacman -Syu
# 安装构建要求
pacman -S --noconfirm --needed git diffutils mingw-w64-x86_64-toolchain pkg-config make yasm
# 使用git克隆仓库
git clone https://github.com/asticode/go-astiav
cd go-astiav

然后克隆这个仓库后,按照上面的构建说明操作。

注意: 对于pkg-config,使用choco的pkgconfiglite。 记得正确设置CGOPKG_CONFIG环境变量,指向构建ffmpeg的文件夹。

为什么选择astiav?

在维护goav的最受欢迎分支几年后,我决定从头开始编写我自己的C绑定,以修复我在使用goav时仍然遇到的大多数问题。


更多关于golang实现高效FFmpeg C绑定功能的插件库go-astiav的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现高效FFmpeg C绑定功能的插件库go-astiav的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


go-astiav: 高效的FFmpeg Go绑定库

go-astiav是一个高效的Go语言FFmpeg绑定库,它提供了对FFmpeg功能的直接访问,同时保持了Go语言的简洁性和安全性。下面我将详细介绍如何使用go-astiav以及它的主要功能。

安装与配置

首先需要安装go-astiav和FFmpeg开发库:

go get github.com/asticode/go-astiav

在Linux上安装FFmpeg开发库:

# Ubuntu/Debian
sudo apt-get install libavformat-dev libavcodec-dev libavutil-dev libavdevice-dev libavfilter-dev libswscale-dev libswresample-dev

# CentOS/RHEL
sudo yum install ffmpeg-devel

基本使用示例

1. 打开媒体文件

package main

import (
	"fmt"
	"log"

	"github.com/asticode/go-astiav"
)

func main() {
	// 注册所有编解码器和格式
	astiav.RegisterAll()

	// 打开输入文件
	input, err := astiav.OpenInput("input.mp4")
	if err != nil {
		log.Fatal(err)
	}
	defer input.Close()

	// 打印文件信息
	fmt.Printf("Input format: %s\n", input.Format().Name())
	fmt.Printf("Duration: %v\n", input.Duration())
	fmt.Printf("Bitrate: %d\n", input.BitRate())
}

2. 解码视频流

func decodeVideo() error {
	// 打开输入文件
	input, err := astiav.OpenInput("input.mp4")
	if err != nil {
		return err
	}
	defer input.Close()

	// 查找视频流
	stream := input.FindBestStream(astiav.MediaTypeVideo)
	if stream == nil {
		return fmt.Errorf("no video stream found")
	}

	// 创建解码器上下文
	codec := astiav.FindDecoder(stream.CodecParameters().CodecID())
	if codec == nil {
		return fmt.Errorf("no codec found")
	}

	codecContext := astiav.AllocCodecContext(codec)
	if err := stream.CodecParameters().ToCodecContext(codecContext); err != nil {
		return err
	}
	defer codecContext.Free()

	// 打开解码器
	if err := codecContext.Open(codec); err != nil {
		return err
	}

	// 创建数据包和帧
	pkt := astiav.AllocPacket()
	defer pkt.Free()
	frame := astiav.AllocFrame()
	defer frame.Free()

	// 读取并解码帧
	for {
		if err := input.ReadFrame(pkt); err != nil {
			if err == astiav.ErrEof {
				break
			}
			return err
		}

		if pkt.StreamIndex() != stream.Index() {
			continue
		}

		if err := codecContext.SendPacket(pkt); err != nil {
			return err
		}

		for {
			if err := codecContext.ReceiveFrame(frame); err != nil {
				if err == astiav.ErrEof || err == astiav.ErrEagain {
					break
				}
				return err
			}

			// 处理解码后的帧
			fmt.Printf("Decoded frame: width=%d, height=%d, format=%d\n",
				frame.Width(), frame.Height(), frame.Format())
		}
	}

	return nil
}

3. 编码和保存视频

func encodeVideo() error {
	// 创建输出格式上下文
	output, err := astiav.AllocOutputFormatContext("output.mp4")
	if err != nil {
		return err
	}
	defer output.Free()

	// 添加视频流
	codec := astiav.FindEncoder(astiav.CodecIdH264)
	if codec == nil {
		return fmt.Errorf("codec not found")
	}

	stream := output.NewStream(codec)
	if stream == nil {
		return fmt.Errorf("failed to create stream")
	}

	// 配置编码器参数
	codecContext := stream.CodecContext()
	codecContext.SetWidth(640)
	codecContext.SetHeight(480)
	codecContext.SetPixelFormat(astiav.PixelFormatYuv420p)
	codecContext.SetTimeBase(astiav.NewRational(1, 25))
	codecContext.SetBitRate(400000)

	// 打开编码器
	if err := codecContext.Open(codec); err != nil {
		return err
	}

	// 写入文件头
	if err := output.WriteHeader(); err != nil {
		return err
	}

	// 创建帧和包
	frame := astiav.AllocFrame()
	defer frame.Free()
	frame.SetWidth(640)
	frame.SetHeight(480)
	frame.SetFormat(int(astiav.PixelFormatYuv420p))
	if err := frame.AllocBuffer(32); err != nil {
		return err
	}

	pkt := astiav.AllocPacket()
	defer pkt.Free()

	// 编码并写入帧
	for i := 0; i < 100; i++ {
		// 这里应该填充真实的帧数据
		// 例如: frame.SetData(...)

		if err := codecContext.SendFrame(frame); err != nil {
			return err
		}

		for {
			if err := codecContext.ReceivePacket(pkt); err != nil {
				if err == astiav.ErrEof || err == astiav.ErrEagain {
					break
				}
				return err
			}

			pkt.SetStreamIndex(stream.Index())
			if err := output.WriteFrame(pkt); err != nil {
				return err
			}
		}
	}

	// 写入文件尾
	if err := output.WriteTrailer(); err != nil {
		return err
	}

	return nil
}

高级功能

1. 硬件加速解码

func hwDecode() error {
	// 查找硬件解码器
	hwCodec := astiav.FindDecoderByName("h264_cuvid")
	if hwCodec == nil {
		return fmt.Errorf("hardware decoder not found")
	}

	// 创建硬件解码器上下文
	hwCodecContext := astiav.AllocCodecContext(hwCodec)
	defer hwCodecContext.Free()

	// 配置硬件设备
	hwDevice := astiav.AllocHWDeviceContext(astiav.HWDeviceTypeCuda)
	if hwDevice == nil {
		return fmt.Errorf("failed to create HW device")
	}
	defer hwDevice.Free()

	// 关联硬件设备到解码器
	if err := hwCodecContext.SetHWDevice(hwDevice); err != nil {
		return err
	}

	// 打开解码器
	if err := hwCodecContext.Open(hwCodec); err != nil {
		return err
	}

	// 后续解码流程与软件解码类似...
	return nil
}

2. 滤镜处理

func applyFilter() error {
	// 创建滤镜图
	filterGraph := astiav.AllocFilterGraph()
	defer filterGraph.Free()

	// 创建buffer源
	bufferSrc := filterGraph.AllocFilter("buffer", "in")
	bufferSrcArgs := fmt.Sprintf("video_size=%dx%d:pix_fmt=%d:time_base=%d/%d",
		640, 480, astiav.PixelFormatYuv420p, 1, 25)
	if err := bufferSrc.SetArgs(bufferSrcArgs); err != nil {
		return err
	}

	// 创建buffer接收器
	bufferSink := filterGraph.AllocFilter("buffersink", "out")
	if err := filterGraph.CreateFilter(bufferSink); err != nil {
		return err
	}

	// 创建滤镜
	filters := "scale=320:240,transpose=1" // 缩放并旋转
	if err := filterGraph.Parse(filters, bufferSrc, bufferSink); err != nil {
		return err
	}

	// 初始化滤镜图
	if err := filterGraph.Configure(); err != nil {
		return err
	}

	// 处理帧
	frame := astiav.AllocFrame()
	defer frame.Free()
	// 设置输入帧参数...

	if err := bufferSrc.AddFrame(frame); err != nil {
		return err
	}

	filteredFrame := astiav.AllocFrame()
	defer filteredFrame.Free()
	if err := bufferSink.GetFrame(filteredFrame); err != nil {
		return err
	}

	// 使用处理后的帧...
	return nil
}

性能优化建议

  1. 复用对象:尽可能复用Packet、Frame等对象,避免频繁分配和释放内存
  2. 批量处理:当处理多个文件时,保持编解码器上下文打开状态
  3. 硬件加速:在支持的环境中优先使用硬件编解码
  4. 并行处理:使用Go的goroutine处理多个流或文件
  5. 内存池:对于频繁创建的对象,考虑使用sync.Pool

总结

go-astiav提供了对FFmpeg功能的全面绑定,使Go开发者能够高效地处理多媒体数据。它的主要优势包括:

  1. 直接绑定FFmpeg C API,性能接近原生
  2. 完整的FFmpeg功能覆盖
  3. Go风格的内存管理和错误处理
  4. 良好的文档和示例

对于需要高性能多媒体处理的Go应用,go-astiav是一个值得考虑的选择。

回到顶部