参考文件doc/examples/encode_video.c,使用x264作为编码器,需要先安装x264,编译方法:
# 先编译FFmpeg
./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-yasm --enable-libx264 --enable-gpl --enable-pthreads --extra-cflags=-I/usr/local/x264/include --extra-ldflags=-L/usr/local/x264/lib
make
make install
# 然后编译example
make examples
编译生成的文件在doc/examples目录下,使用以下命令执行,编码yuv软件内部自动生成,只用设定输出文件名即可
./encode_video test.h264 libx264
参考文件libavcodec/encode.c,分析送帧函数avcodec_send_frame,该函数会送一帧然后尝试启动编码器编码,伪代码如下,仅摘抄了主要流程
avcodec_send_frame(frame)
|-- 如果frame为NULL
| |-- avci->draining = 1 // 排水,即刷新编码器
|-- 否则
| |-- encode_send_frame_internal(frame)
| |-- AVFrame *dst = avci->buffer_frame
| |-- av_frame_ref(dst, src) // src = frame,获取src的引用
|-- ret = encode_receive_packet_internal(&avci->buffer_pkt)
| |-- 如果avci->draining_done为1,返回EOF
| |-- ret = encode_simple_receive_packet(avpkt)
| | |-- while (!avpkt->data)
| | |-- ret = encode_simple_internal
| | | |-- AVFrame *frame = avci->in_frame;
| | | | /* 初始化in_frame->buf[0]为NULL */
| | | |-- 如果frame->buf[0]为NULL,并且avci->draining为0
| | | | |-- av_frame_unref(frame);
| | | | |-- ret = ff_encode_get_frame(avctx, frame);
| | | | | |-- 如果avci->draining等于1,返回EOF
| | | | | |-- 如果avci->buffer_frame->buf[0]为NULL,返回AGAIN
| | | | | | // 转移buffer_frame引用,然后重置buffer_frame,
| | | | | | // 即buffer_frame->buf[0]置为了NULL */
| | | | | |-- av_frame_move_ref(frame, avci->buffer_frame)
| | | | |-- 如果ret失败且不等于EOF,返回ret
| | | |-- got_packet = 0
| | | |-- ret = ff_encode_encode_cb(avpkt,frame, &got_packet)
| | | | |-- ret = codec->cb.encode(avpkt, frame, got_packet)
| | | | |-- 如果ret成功,并且got_packet大于0
| | | | | |-- 如果avpkt->data不为NULL
| | | | | | | // 检查是否使用AVBufferRef进行管理
| | | | | | | // 如果没有则进行相关处理
| | | | | | |-- encode_make_refcounted(avpkt)
| | | | | |-- 否则
| | | | | |-- av_packet_unref(avpkt);
| | | |-- 如果frame不为NULL
| | | | | // 这里因为第7行已经增加了引用计数,因此yuv buf不会被释放
| | | | | // 但是ref会被释放,也就是frame->buf[0]会被释放
| | | | |-- av_frame_unref(frame)
| | | |-- 如果avci->draining为1,并且got_packet为0
| | | | |-- avci->draining_done = 1
| | | |-- 返回ret
| | |-- ret小于0返回ret
| |-- 返回ret
|-- 如果ret失败,并且不为EOF和AGAIN,返回ret
|-- 返回0
启动编码之后,编码的结果会存在buffer_pkt中,然后调用avcodec_receive_packet获取编码的码流,该函数也会启动编码器,伪代码如下
avcodec_receive_packet(avpkt)
|-- 如果buffer_pkt->data不为空
| |-- av_packet_move_ref(avpkt, buffer_pkt)
|-- 否则
| |-- ret = encode_receive_packet_internal(avpkt)
| |-- 如果ret失败,返回ret
|-- 返回0
第一次送帧的流程:
减引用计数的函数,如果减到0则释放其中的buffer,大佬甚至不愿单独写个free函数,直接复用replace,传参也是二重指针,提高阅读难度
像这种复用在ffmpeg中很多,比如encode_simple_internal接口中检查之前有没有送帧实际依靠的是ff_encode_get_frame接口的返回值,该返回值将会影响整个encode_simple_internal接口的返回值,为了可读性这种检查建议显式的写在encode_simple_internal函数中而不是隐含在ff_encode_get_frame返回值中
再比如ff_encode_encode_cb函数中会调用encode_make_refcounted函数来对encode返回的pkt是否使用了ref进行检查,如果使用了ref即avpkt->buf不为NULL,直接返回成功,这个返回值直接影响了是否有122行的数据拷贝,可能影响零拷贝的设计,这种重要的特性建议也是直接写到ff_encode_encode_cb中,而不是依赖encode_make_refcounted函数的检查,encode_make_refcounted只实现单一的申请新的ref然后数据拷贝功能即可
另外很多buffer ref相关的接口有隐含的副作用,比如av_frame_move_ref函数将src转移到dts后会重置src的值
av_frame_unref除了减ref的引用计数之外,还会把ref整个释放掉
这种副作用通过函数名无法得知,只有通过打开函数看实现或者查API reference才知道。
似乎作者的理念似乎趋向于All in one的设计,即一个函数完成尽可能多的功能,不愿意设计单一功能的接口,也不愿意多写一行重复代码,这样其实提高了理解的难度。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。