golang使用ffmpeg的那些坑(整理后)

  1. 1、静态编译问题
  2. 2、找到合适的ffmpeg包
  3. 3、项目性能调优
  4. 问题汇总

因业务需求需要用到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

×

喜欢就点赞,疼爱就打赏