process_input_packet解码封装—ffmpeg.c源码分析

作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音

process_input_packet() 的主要功能是 解码第二个参数 const AVPacket *pkt,如果有解码数据出来,就发送给输入流关联的所有入口滤镜

函数的定义如下:

/* pkt = NULL means EOF (needed to flush decoder buffers) */
static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof)

参数解析如下:

1,InputStream *ist,要处理的这个 AVPacket 是属于哪个输入流的。

2,const AVPacket *pkt,要送给解码器的 AVPacket 。如果为 NULL,就会 flush 解码器,把解码器的数据都刷出来。

3,int no_eofno_eof 是用来控制要不要往输入流绑定的入口滤镜发送 eof。当 pkt 为 NULL 的时候这个字段才会生效。

pkt 为 NULL , no_eof 为 1 的时候,process_input_packet() 内部会 flush 解码器,把剩余的 AVFrame 都刷出来,但是不会对入口滤镜进行 eof 操作,因为文件会循环,还需要复用之前的滤镜容器。如下:

1-1

1-2

no_eof 设置为 1 只有在文件循环的时候才用到。


process_input_packet() 函数有以下重点。

第一个重点ist->ptsist->dtsist->next_ptsist->next_dts,这 4 个字段的计算。

ist->pts 代表当前流解码到什么时刻了,它不是直接取最后一个的 AVPacketpts,而是取的上一个 AVPacketpts 加上 这个 AVPacketduration。如下:

1-3

ist->pts 一开始是赋值为 0 ,然后在 while (ist->decoding_needed) {...} 循环里面,ist->pts 被赋值为 ist->next_pts,如下:

1-4

next_pts 就是用上一个 AVPacketpts 加上 duration 计算出来的,如下:

1-5


ist->dts 代表当前流解码的 dts 时刻了,可以理解为当前解码的 dts,这个字段有两种计算方式。

1,ist->dts 未进入解码循环之前,是直接取的 AVPacketdts ,如下:

1-5-2

2,如果解码器有数据出来,ist->dts 就会用上一个 AVPacketdts 加上 duration 计算出来的,如下:

ist->dts 的第一次赋值有点复杂,是用 帧率 + B 帧数量计算出来的,如下:

1-6

然后在 while (ist->decoding_needed) {...} 循环里面,ist->dts 被赋值为 ist->next_dts,如下:

1-4

next_dts 就是用上一个 AVPacketdts 加上 duration 计算出来的,如下:

1-7

ist->next_ptsist->next_dts ,自然就是下一个的时间。

具体这 4 个字段的计算方式特别复杂,但是好像只有打印日志的时候用了一下。


process_input_packet() 函数的第二个重点,就是 while (ist->decoding_needed){...} 解码循环。

由于往解码器发送一个 AVPacket,解码器可能会吐出来多个 AVFrame,所以需要一个循环来接受所有的 AVFrame

while 循环里面有 4 个局部变量,如下:

while (ist->decoding_needed) {
    int64_t duration_dts = 0;
    int64_t duration_pts = 0;
    int got_output = 0;
    int decode_failed = 0;
    ...省略代码...
}

1,int64_t duration_dts,主要给视频流使用的,默认就是 pkt->duration ,如果 pkt->duration 是 空,就用帧率来预估,如下:

1-8

2,int64_t duration_pts,虽然是 decode_video() 函数修改的这个字段,但是实际上也是 pkt->duration,只是换了一下单位。

3,int got_output,代表是否从 解码器 读取到 AVFrame,也是 decode_video() 函数修改的这个字段,如果 decode_video() 能从解码器读到 AVFrame ,就会把这个 got_output 设置为 1。

4,int decode_failed,解码器是否返回失败。

实际上,while 循环里面的解码过程都是调子函数 decode_audio()decode_video() 来实现的,推荐阅读《decode_audio解码音频帧》《decode_video解码视频帧


while (ist->decoding_needed){...} 解码循环里面还有一个重点,就是 repeating 变量,这个变量代表在循环里面是不是第二次读取解码器。

第二次读取解码器,是不需要发送 AVPacket 的,可以直接传 NULL。

1-9

这是解码函数 decode() 的封装规则。

2-1

我们在使用 avcode_send_packet() 函数的时候,如果想 flush 解码器,你可以传 pkt 等于 null,或者 pkt->size 等于 0 都可以的,都可以 flush 解码器。

但是对于 decode() 函数,他做了一下区别,必须 pkt->size 等于 0 才是 flush 解码器, pkt 等于 null 就不会调 avcodec_send_packet() ,所以就不会 flush 解码器,只会不断从解码器读数据,直到没数据可读。


process_input_packet() 函数的第三个重点是它的解码结束逻辑,如下:

/* after flushing, send an EOF on all the filter inputs attached to the stream */
/* except when looping we need to flush but not to send an EOF */
if (!pkt && ist->decoding_needed && eof_reached && !no_eof) {
    int ret = send_filter_eof(ist);
    if (ret < 0) {
        av_log(NULL, AV_LOG_FATAL, "Error marking filters as finished\n");
        exit_program(1);
    }
}

如果解码器已经解码结束了,decode_audio() 或者 decode_video() 函数返回 AVERROR_EOF 就代表解码结束,要想解码结束,必须发一个 pkt->size 等于 0 进去。

解码结束就会调 send_filter_eof(ist) 来关闭入口滤镜,因为一个输入流可能关联多个入口滤镜,所以每个入口滤镜都会被关闭。

不过如果有文件循环,就不需要关闭入口滤镜。文件循环下 no_eof 会是 1,

关于结束处理的整体逻辑,推荐阅读《FFmpeg转换器转码结束分析


还剩一些 stream_copy 的代码,相对较简单,不进行讲解。

2-2


最后,process_input_packet() 的返回值是 !eof_reached,代表是否解码结束。

return !eof_reached


感谢 NETINT(镕铭微电子) 赞助《FFmpeg原理》免费版一书的服务器费用,下面是 VPU 产品介绍

版权所属 xianwanzhiyin.net 罗上文 2024 all right reserved,powered by Gitbook该文件修订时间: 2023-05-17 11:39:55

results matching ""

    No results matching ""