前言
AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的编解码层,主要包括三大数据结构:AVStream
,AVCodecContex
,AVCodec
。
一、FFmpeg 解码流程
得到输入文件 -> 解封格式 -> 得到编码的数据包 -> 解码数据包 -> 得到解码后的数据帧 -> 处理数据帧 -> 编码 -> 得到编码后的数据包 -> 封装格式 -> 输出文件
涉及到下面的 API 函数:
- 注册所有容器格式和 CODEC:
av_register_all()
; - 打开文件:
av_open_input_file()
; - 从文件中提取流信息:
av_find_stream_info()
; - 穷举所有的流,查找其中种类为
CODEC_TYPE_VIDEO
; - 查找对应的解码器:
avcodec_find_decoder()
; - 打开编解码器:
avcodec_open()
; - 为解码帧分配内存:
avcodec_alloc_frame()
; - 不停地从码流中提取出帧数据:
av_read_frame()
; - 判断帧的类型,对于视频帧调用:
avcodec_decode_video()
; - 解码完后,释放解码器:
avcodec_close()
; - 关闭输入文件:
av_close_input_file()
;
程序流程图如下图所示:
二、FFmpeg 转码流程
- 大流程可以划分为输入、输出、转码、播放四大块;
- 其中转码涉及比较多的处理环节,从图中可以看出,转码功能在整个功能图中占比很大。转码的核心功能在解码和编码两个部分,但在一个可用的示例程序中,编码解码与输入输出是难以分割的。
- 解复用器为解码器提供输入,解码器会输出原始帧,对原始帧可进行各种复杂的滤镜处理,滤镜处理后的帧经编码器生成编码帧,多路流的编码帧经复用器输出到输出文件。
三、编解码 API 详解
- 解码使用
avcodec_send_packet()
和avcodec_receive_frame()
两个函数。 - 编码使用
avcodec_send_frame()
和avcodec_receive_packet()
两个函数。
1、解码 API 使用详解
关于 avcodec_send_packet()
与 avcodec_receive_frame()
的使用说明:
- ①、按 dts 递增的顺序向解码器送入编码帧 packet,解码器按 pts 递增的顺序输出原始帧 frame,实际上解码器不关注输入 packet 的 dts(错值都没关系),它只管依次处理收到的 packet,按需缓冲和解码;
- ②、
avcodec_receive_frame()
输出 frame 时,会根据各种因素设置好frame->best_effort_timestamp
(文档明确说明),实测frame->pts
也会被设置(通常直接拷贝自对应的packet.pts
,文档未明确说明)用户应确保avcodec_send_packet()
发送的 packet 具有正确的 pts,编码帧 packet 与原始帧 frame 间的对应关系通过 pts 确定; - ③、
avcodec_receive_frame()
输 出 frame 时 ,frame->pkt_dts
拷贝自当前avcodec_send_packet()
发送的 packet 中的 dts, 如果当前 packet 为 NULL(flush packet),解码器进入 flush 模式,当前及剩余的frame->pkt_dts
值总为AV_NOPTS_VALUE
。因为解码器中有缓存帧,当前输出的 frame 并不是由当前输入的 packet 解码得到的,所以这个frame->pkt_dts
没什么实际意义,可以不必关注; - ④、
avcodec_send_packet()
发送第一个 NULL 会返回成功,后续的 NULL 会返回AVERROR_EOF
; - ⑤、
avcodec_send_packet()
多次发送 NULL 并不会导致解码器中缓存的帧丢失,使用avcodec_flush_buffers()
可以立即丢掉解码器中缓存帧。因此播放完毕时应avcodec_send_packet(NULL)
来取完缓存的帧,而 SEEK 操作或切换流时应调用avcodec_flush_buffers()
来直接丢弃缓存帧; - ⑥、解码器通常的冲洗方法:调用一次
avcodec_send_packet(NULL)
(返回成功),然后不停调用avcodec_receive_frame()
直到其返回AVERROR_EOF
,取出所有缓存帧,avcodec_receive_frame()
返回AVERROR_EOF
这一次是没有有效数据的,仅仅获取到一个结束标志;
2、编码 API 使用详解
关于 avcodec_send_frame()
与 avcodec_receive_packet()
的使用说明:
- ①、按 pts 递增的顺序向编码器送入原始帧 frame, 编码器按 dts 递增的顺序输出编码帧 packet,实际上编码器关注输入 frame 的 pts 不关注其 dts,它只管依次处理收到的 frame,按需缓冲和编码;
- ②、
avcodec_receive_packet()
输出 packet 时,会设置 packet.dts,从 0 开始,每次输出的 packet 的 dts 加 1,这是视频层的 dts,用户写输出前应将其转换为容器层的 dts; - ③、
avcodec_receive_packet()
输出 packet 时,packet.pts
拷贝自对应的frame.pts
,这是视频层的 pts,用户写输出前应将其转换为容器层的 pts; - ④、
avcodec_send_frame()
发送 NULL frame 时, 编码器进入 flush 模式; - ⑤、
avcodec_send_frame()
发送第一个 NULL 会返回成功 ,后续的 NULL 会返回AVERROR_EOF
; - ⑥、
avcodec_send_frame()
多次发送 NULL 并不会导致编码器中缓存的帧丢失,使用avcodec_flush_buffers()
可以立即丢掉编码器中缓存帧。因此编码完毕时应使用avcodec_send_frame(NULL)
来取完缓存的帧,而 SEEK 操作或切换流时应调用avcodec_flush_buffers()
来直接丢弃缓存帧; - ⑦、编码器通常的冲洗方法:调用一次
avcodec_send_frame(NULL)
(返回成功),然后不停调用avcodec_receive_packet()
直到其返回AVERROR_EOF
,取出所有缓存帧,avcodec_receive_packet()
返回AVERROR_EOF
这一次是没有有效数据的,仅仅获取到一个结束标志; - ⑧、对音频来说,如果
AV_CODEC_CAP_VARIABLE_FRAME_SIZE
(在AVCodecContext.codec.capabilities
变量中,只读)标志有效,表示编码器支持可变尺寸音频帧,送入编码器的音频帧可以包含任意数量的采样点。如果此标志无效,则每一个音频帧的采样点数目(frame->nb_samples
)必须等于编码器设定的音频帧尺寸(avctx->frame_size
),最后一帧除外,最后一帧音频帧采样点数可以小于avctx->frame_size
;
AVFormatContext编解码层:理论与实战(二)https://developer.aliyun.com/article/1473960