因业务需求需要用到golang调用ffmpeg,期间遇到各种曲折离奇的事情,例如服务运行时崩溃,流解析出错等等,还好最终拨开云雾见日出
1、静态编译问题
(1)常规使用
go1.11.2版本之前
go build -ldflags -extldflags=-static
go1.11.2
可以直接在声明C或C++代码区域添加-static
//#cgo CFLAGS: -D_REENTRANT
//#cgo LDFLAGS:-static
//#cgo pkg-config: libavutil
import "C"
(2)go调用ffmpeg
正常情况以上办法就可以编译生成静态可执行文件,但是go调用ffmpeg时就不行了,因为要用到glibc只能进行动态链接,解决办法是修改ffmpeg编译后生成的pkgconfig中的相关*.pc文件。
-Wl,-Bstatic:ffmpeg接口强制静态链接, -Wl,-Bdynamic -lc :glibc强制动态链接 ,将libav-开头的.pc文件改过就好。
Conflicts:
Libs: -L${libdir} -Wl,-Bstatic -lavutil -lm -Wl,-Bdynamic -lc
注:本人使用过程中发现要先进行动态编译后再进行以上操作才可,原因未明。
2、找到合适的ffmpeg包
本人在原先go-libav包的基础上做了很多改动,不知这个包为什么有那么多star,结果确连example都跑不起来,最后无奈进行了些许改动,例如超时函数的实现,音频数据的处理等。修改后的包看glibav,暂无时间写example,因项目紧任务重,未来得及进行重新架构,本人的项目主要功能是处理各种视频stream生成jpg和wav数据然后发送到kafka,已经上线使用。
3、项目性能调优
在实际应用中发现服务性能与完全用C编写性能差距还是很大的,最后使用多个goroutine勉强达到业务需求。以下列举一些性能调优的常用命令:
1、goTool之pprof的使用
//生成cpu profile
go test -run=^$ -bench=. -cpuprofile=profile.out
//生成memery profile
go test -run=^$ -bench=. -memprofile=profile.out
//使用原生pprof启动Web UI
pprof -http=:8080 profile.out
2、代码优化--goreporter
goreporter -p [projectRelativePath] -r [reportPath] -e [exceptPackagesName] -f [json / html / text] {-t templatePathIfHtml}
问题汇总
获取输入流帧率
videoFPS 即输入流的帧率,下面是在原代码例子上改的,可以参考原示例对照,https://github.com/imkira/go-libav
func (si *SIConf) openInputVideoStream(ctx *avContext) error {
var err error
// find first video stream
if ctx.decStream = videoStream(ctx.decFmt); ctx.decStream == nil {
logger.Error("Could not find a video stream. Aborting...")
return fmt.Errorf("Could not find a video stream")
}
codecCtx := ctx.decStream.CodecContext()
codecPar := ctx.decStream.CodecParameters()
codec := avcodec.FindDecoderByID(codecCtx.CodecID())
rat := ctx.decFmt.GuessFrameRate(ctx.decStream, nil)
si.videoFPS = float32(rat.Numerator()) / float32(rat.Denominator())
if codec == nil {
logger.Error("Could not find decoder:", codecCtx.CodecID())
}
if ctx.decCodec, err = avcodec.NewContextWithCodec(codec); err != nil {
logger.Error("Failed to create codec context:", err)
}
if err := codecCtx.CopyTo(ctx.decCodec, codecPar); err != nil {
logger.Error("Failed to copy codec context:", err)
}
if err := ctx.decCodec.SetInt64Option("refcounted_frames", 1); err != nil {
logger.Error("Failed to copy codec context:", err)
}
//设置多线程
options := avutil.NewDictionary()
defer options.Free()
if err := options.Set("threads", "auto"); err != nil {
logger.Error("Failed to set input options:", err)
}
if err := ctx.decCodec.OpenWithCodec(codec, options); err != nil {
logger.Error("Failed to open codec:", err)
}
//set time base
ctx.decCodec.SetTimeBase(ctx.decStream.TimeBase())
// we need a v log.Println(ctx.decCodec.Width(), ctx.decCodec.Height(), ctx.decCodec.PixelFormat(), ctx.decCodec.TimeBase())ideo filter to push the decoded frames to
ctx.srcFilter = addFilter(ctx, "buffer", "in")
if err = ctx.srcFilter.SetImageSizeOption("video_size", ctx.decCodec.Width(), ctx.decCodec.Height()); err != nil {
logger.Error("Failed to set filter option:", err)
}
if err = ctx.srcFilter.SetPixelFormatOption("pix_fmt", ctx.decCodec.PixelFormat()); err != nil {
logger.Error("Failed to set filter option:", err)
}
if err = ctx.srcFilter.SetRationalOption("time_base", ctx.decStream.TimeBase()); err != nil {
logger.Error("Failed to set filter option:", err)
}
//log.Println(ctx.decCodec.Width(), ctx.decCodec.Height(), ctx.decCodec.PixelFormat(), ctx.decStream.TimeBase())
if err = ctx.srcFilter.Init(); err != nil {
logger.Error("Failed to initialize buffer filter:", err)
}
return nil
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2291184112@qq.com