FFmpeg 命令:从入门到精通 | FFmpeg 解码流程
FFmpeg 命令:从入门到精通 | FFmpeg 解码流程
本内容参考雷霄骅博士的 FFmpeg 教程。
流程图
FFmpeg 解码的流程图如下所示:
FFmpeg 解码的流程:
- 注册:
使用ffmpeg对应的库,都需要进行注册,可以注册子项也可以注册全部。 - 打开文件:
打开文件,根据文件名信息获取对应的ffmpeg全局上下文。 - 探测流信息:
一定要探测流信息,拿到流编码的编码格式,不探测流信息则其流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无法知晓原始格式,导致错误。 - 查找对应的解码器:
依据流的格式查找解码器,软解码还是硬解码是在此处决定的,但是特别注意是否支持硬件,需要自己查找本地的硬件解码器对应的标识,并查询其是否支持。普遍操作是,枚举支持文件后缀解码的所有解码器进行查找,查找到了就是可以硬解了(此处,不做过多的讨论,对应硬解码后续会有文章进行进一步研究)。(注意:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能打开但是数据是错的) - 打开解码器:
打开获取到的解码器。 - 申请缩放数据格式转换结构体:
此处特别注意,基本上解码的数据都是yuv系列格式,但是我们显示的数据是rgb等相关颜色空间的数据,所以此处转换结构体就是进行转换前到转换后的描述,给后续转换函数提供转码依据,是很关键并且非常常用的结构体。 - 申请缓存区:
申请一个缓存区outBuffer,fill到我们目标帧数据的data上,比如rgb数据,QAVFrame的data上存是有指定格式的数据,且存储有规则,而fill到outBuffer(自己申请的目标格式一帧缓存区),则是我们需要的数据格式存储顺序。
举个例子,解码转换后的数据为rgb888,实际直接用data数据是错误的,但是用outBuffer就是对的,所以此处应该是ffmpeg的fill函数做了一些转换。
进入循环解码: - 获取一帧packet:
拿取封装的一个packet,判断packet数据的类型进行解码拿到存储的编码数据 - 数据转换:
使用转换函数结合转换结构体对编码的数据进行转换,那拿到需要的目标宽度、高度和指定存储格式的原始数据。 - 自行处理:
拿到了原始数据自行处理。不断循环,直到拿取pakcet函数成功,但是无法得到一帧数据,则代表文件解码已经完成。帧率需要自己控制循环,此处只是循环拿取,可加延迟等。 - 释放QAVPacket:
此处要单独列出是因为,其实很多网上和开发者的代码在进入循环解码前进行了av_new_packet,循环中未av_free_packet,造成内存溢出;或者,在进入循环解码前进行了av_new_packet,循环中进行av_free_pakcet,那么一次new对应无数次free,在编码器上是不符合前后一一对应规范的。查看源代码,其实可以发现av_read_frame时,自动进行了av_new_packet(),那么其实对于packet,只需要进行一次av_packet_alloc()即可,解码完后av_free_packet。
执行完后,返回执行“步骤8:获取一帧packet”,一次循环结束。 - 释放转换结构体:
全部解码完成后,安装申请顺序,进行对应资源的释放。 - 关闭解码/编码器:
关闭之前打开的解码/编码器。 - 关闭上下文:
关闭文件上下文后,要对之前申请的变量按照申请的顺序,依次释放。
FFmpeg 解码的函数
- av_register_all():注册所有组件。
- avformat_open_input():打开输入视频文件。
- avformat_find_stream_info():获取视频文件信息。
- avcodec_find_decoder():查找解码器。
- avcodec_open2():打开解码器。
- av_read_frame():从输入文件读取一帧压缩数据。
- avcodec_decode_video2():解码一帧压缩数据。
- avcodec_close():关闭解码器。
- avformat_close_input():关闭输入视频文件。
FFmpeg 解码的数据结构
FFmpeg 解码的数据结构如下所示:
FFmpeg 数据结构:
- AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
- AVInputFormat:每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。
- AVStream:视频文件中每个视频(音频)流对应一个该结构体。
- AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息。
- AVCodec:每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
- AVPacket:存储一帧压缩编码数据。
- AVFrame:存储一帧解码后像素(采样)数据。
FFmpeg 数据结构分析:
AVFormatContext:
- iformat:输入视频的AVInputFormat
- nb_streams :输入视频的AVStream 个数
- streams :输入视频的AVStream []数组
- duration :输入视频的时长(以微秒为单位)
- bit_rate :输入视频的码率
AVInputFormat:
- name:封装格式名称
- long_name:封装格式的长名称
- extensions:封装格式的扩展名
- id:封装格式ID
- 一些封装格式处理的接口函数
AVStream:
- id:序号
- codec:该流对应的AVCodecContext
- time_base:该流的时基
- r_frame_rate:该流的帧率
AVCodecContext:
- codec:编解码器的AVCodec
- width, height:图像的宽高(只针对视频)
- pix_fmt:像素格式(只针对视频)
- sample_rate:采样率(只针对音频)
- channels:声道数(只针对音频)
- sample_fmt:采样格式(只针对音频)
AVCodec:
- name:编解码器名称
- long_name:编解码器长名称
- type:编解码器类型
- id:编解码器ID
- 一些编解码的接口函数
AVPacket:
- pts:显示时间戳
- dts :解码时间戳
- data :压缩编码数据
- size :压缩编码数据大小
- stream_index :所属的AVStream
AVFrame:
- data:解码后的图像像素数据(音频采样数据)。
- linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小。
- width, height:图像的宽高(只针对视频)。
- key_frame:是否为关键帧(只针对视频) 。
- pict_type:帧类型(只针对视频) 。例如I,P,B。
补充小知识
解码后的数据为什么要经过sws_scale()函数处理?
解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素。以亮度Y数据为例,data[0]中一共包含了linesize[0]*height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0] 取值相等。