本文通过ffmpeg编程的例子来讲述如何封装cgo库
更多内容访问我的博客
前言
继上一篇 ffmpeg音视频C编程入门, 使用高性能的C语言进行音视频的处理,比较执行效率比较高,但是业务需求,快捷开发需要使用更方便的语言,比如 golang,本文介绍如何将 将视频转成GIF 的C语言方法封装成 golang 方法以便调用。(不明白的同学请点击上面链接多了解)
认识cgo的封装技巧
最简单的 cgo 封装例子看这篇 cgo快速入门
我这里讲几个注意事项
extern C
开始编程
第一步,处理例子中已经写好的 gen_gif 方法,修改 gen_gif.h 文件
#ifdef __cplusplus
extern "C" {
#endif
int gen_gif(const int gifSeconds, const int rotate, void* data, int data_size, void* outBuf, int outBufLen, int *outSize);
#ifdef __cplusplus
}
#endif
在处理 ffmpeg 的 av_log 日志的回调方法
/* log.c */
/*
mpeg 的日志库用法
#include <libavutil/log.h>
// 设置日志级别
av_log_set_level(AV_LOG_DEBUG)
// 打印日志
av_log(NULL, AV_LOG_INFO,"...%s\n",op)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/log.h>
// 定义输出日志的函数,留白给使用者实现
extern void Ffmpeglog(int , char*);
static void log_callback(void *avcl, int level, const char *fmt, va_list vl)
{
(void) avcl;
char log[1024] = {0};
// int vsnprintf(buffer,bufsize ,fmt, argptr) , va_list 是可变参数的指针,相关方法有: va_start(), type va_atg(va_list, type), va_end()
int n = vsnprintf(log, 1024, fmt, vl);
if (n > 0 && log[n - 1] == '\n')
log[n - 1] = 0;
if (strlen(log) == 0)
return;
Ffmpeglog(level, log);
}
void set_log_callback()
{
// 给 av 解码器注册日志回调函数
av_log_set_callback(log_callback);
}
第二步,编写 go 文件
package c
/*
#include <stdlib.h>
#include "gen_gif.h"
#include "log.h"
#cgo LDFLAGS: -lavcodec -lavformat -lswscale -lavutil -lavfilter -lm
*/
import "C"
import (
"errors"
"fmt"
"unsafe"
)
func init() {
C.set_log_callback()
}
var logger func(s string) = nil
func SetFfmpegLogger(f func(s string)) {
logger = f
}
//export Ffmpeglog
func Ffmpeglog(l C.int, t *C.char) {
if l <= 32 {
if logger == nil {
fmt.Printf("ffmpeg log:%s\n", C.GoString(t))
} else {
logger(fmt.Sprintf("ffmpeg log:%s\n", C.GoString(t)))
}
}
}
func GenGif(second, rotate int, input []byte) (err error, output []byte) {
buf := make([]byte, 1<<20)
var outsz C.int
ret := C.gen_gif(C.int(second), C.int(rotate), unsafe.Pointer(&input[0]), C.int(len(input)), unsafe.Pointer(&buf[0]), C.int(len(buf)), &outsz)
if ret != 0 {
return errors.New(fmt.Sprintf("error, ret=%v", ret)), nil
}
output = make([]byte, outsz)
copy(output, buf[:outsz])
return nil, output
}
libav*#cgo LDFLAGSlog.cimport "C"
测试代码
github.com/lightfish-zhang/mpegUtil/c
GenGif()
package main
import (
"fmt"
"io/ioutil"
"os"
mpegUtil "github.com/lightfish-zhang/mpegUtil/c"
)
func main() {
if len(os.Args) < 3 {
fmt.Printf("Usage: %s <input file> <output file>\n", os.Args[0])
os.Exit(1)
}
inFile, err := os.Open(os.Args[1])
if err != nil {
fmt.Printf("open file fail, path=%v, err=%v", os.Args[1], err)
os.Exit(1)
}
outFile, err := os.Create(os.Args[2])
if err != nil {
fmt.Printf("create file fail, path=%v, err=%v", os.Args[2], err)
os.Exit(1)
}
input, err := ioutil.ReadAll(inFile)
if err != nil {
fmt.Printf("read file fail, err=%v", err)
os.Exit(1)
}
err, output := mpegUtil.GenGif(5, 90, input)
if err != nil {
fmt.Printf("generate gif fail, err=%v", err)
os.Exit(1)
}
_, err = outFile.Write(output)
if err != nil {
fmt.Printf("write file fail, err=%v", err)
os.Exit(1)
}
}
我在本地执行,对某一个 mp4 文件进行转码 GIF,如
./genGif test.mp4 test.gif
运行成功,在我的电脑上可以看到完美的 GIF 图片!
动态链接或者静态链接
ffmpeglibav*.so
有没有便利的方法进行编译或部署呢?
打包动态链接库
LD_LIBRARY_PATHlibc.so
ldd genGif*.solibav*libm.solibz.so
LD_LIBRARY_PATH*.so
静态链接编译
这个方法也需要机器的 linux 最好保持一致,ffmpeg 依赖的 libc.so 的函数好像会随着 linux 版本不同而有所差异。
准备好各种依赖库的静态库,参照如下命令
- 编译C例子
g++ -o gen_gif -I./ -I./ffmpeg/include -I/usr/local/include main.o gen_gif.a ./ffmpeg/lib/libavdevice.a ./ffmpeg/lib/libavfilter.a ./ffmpeg/lib/libavformat.a ./ffmpeg/lib/libavcodec.a ./ffmpeg/lib/libavutil.a ./ffmpeg/lib/libswreample.a ./ffmpeg/lib/libswcale.a ./lib/liblzma.a ./lib/libm.a ./lib/libz.a ./lib/libbz2.a -lpthread
#cgo
/*
#cgo CFLAGS: -I./ffmpeg/include -I/usr/local/include
#cgo LDFLAGS: -L./lib -L./ffmpeg/lib
*/