文章转移:https://cloud.tencent.com/developer/article/2389370
1 编码流程
FFmpeg是一个开源的多媒体框架,底层可对接实现多种编解码器,下面参考文件doc/examples/encode_video.c
分析编码一帧的流程
1.1 整体流程
统一的编码流程如下图所示
FFmpeg使用的是引用计数的思想,对于一块buffer,刚申请时引用计数为1,每有一个模块进行使用,引用计数加1,使用完毕后引用计数减1,当减为0时释放buffer。
此流程中需要关注buffer的分配,对于编码器来说,输入buffer是yuv,也就是上图中的frame,输出buffer是码流包,也就是上图中的pkt,下面对这两个buffer进行分析
- frame:这个结构体是由
av_frame_alloc
分配的,但这里并没有分配yuv的内存,yuv内存是av_frame_get_buffer
分配的,可见这里输入buffer完全是来自外部的,不需要编码器来管理,编码器只需要根据所给的yuv地址来进行编码就行了 - pkt:这个结构体是由
av_packet_alloc
分配的,也没有分配码流包的内存,可见这里pkt仅仅是一个引用,pkt直接传到了avcodec_receive_packet
接口进行编码,完成之后将pkt中码流的内容写到文件,最后调用av_packet_unref
接口减引用计数,因此这里pkt是编码器内部分配的,分配完成之后会减pkt的引用计数加1,然后输出到外部,外部使用完毕之后再减引用计数来释放buffer
编码一帧的相关代码如下:
static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
FILE *outfile)
{
int ret;
/* send the frame to the encoder */
if (frame)
printf("Send frame %3"PRId64"\n", frame->pts);
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame for encoding\n");
exit(1);
}
while (ret >= 0) {
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0) {
fprintf(stderr, "Error during encoding\n");
exit(1);
}
printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);
fwrite(pkt->data, 1, pkt->size, outfile);
av_packet_unref(pkt);
}
}
其中avcodec_receive_packet
返回EAGAIN表示送下一帧,返回EOF表示编码器内部已经没有码流。
1.2 内部流程
此处分析编码一帧的内部流程,首先看FFmpeg内部编码器的上下文,其中有三个重要结构体
typedef struct AVCodecInternal {
...
/**
* The input frame is stored here for encoders implementing the simple
* encode API.
*
* Not allocated in other cases.
*/
AVFrame *in_frame;
/**
* Temporary buffers for newly received or not yet output packets/frames.
*/
AVPacket *buffer_pkt;
AVFrame *buffer_frame;
...
} AVCodecInternal;
下面结合送帧和收流的接口进行介绍
- avcodec_send_frame: 送帧接口,将yuv的帧信息赋值到
buffer_frame
,然后触发一帧编码,将编码出的码流赋值到buffer_pkt
- avcodec_receive_packet: 收流接口,检查上下文中是否有已经编码好的码流
buffer_pkt
,如果有则将其返回,如果没有再触发一帧编码,将编码好的码流返回
可见send和rec