通过调试ffmpeg对HEVC码流(格式为es流,就是rawvideo)的解码过程,分析ffmpeg的HEVC解码过程和实现方法。
首先要说的是调试所用的工程的config为:./configure –disable-asm –disable-pthreads –enable-debug –disable-optimizations进行configure。
禁用了pthread,所以涉及到pthread的函数都不采用。
配置和编译的详细过程见文章《Linux环境下,用eclipse对ffmpeg源代码进行调试》。
输入为HEVC的纯码流,输出为YUV文件。
开始调试,进入main函数后的函数调用关系如图fig.A所示:
fig.A中大多数的步骤都是ffmpeg中各种codec通用的,不针对HEVC。
1.注册变量和函数,通过一系列的函数顺序实现,包括:avcodec_register_all(),avfilter_register_all,av_register_all等等。
2.ffmpeg_parse_options():深入分析输入参数。对输入输出文件做分析。
为什么说深入呢?因为这个函数做了很多工作:将用户的命令行分解,打开输入的码流文件,并对码流文件进行试解码(调用函数try_decode_frame)以分析输入码流的特征。所以运行完这个函数,ffmpeg已经可以确定输入文件的文件类型,codec等等信息了。
3.transcode():字面意思是转码,就是将输入文件转成输出文件的格式,对视频来说就是一个解码到YUV图像,再进行编码的过程,在本文中,输出为YUV,就不用再进行编码了。
3.1:transcode_init():初始化,输入输出文件的各种配置,比如色彩空间,文件格式等等。
3.2:transcode_step()到process_init(): 开始转码,对本文来说就是解码。
3.2.1:get_input_packet(),av_read_frame(),read_from_packet_buffer()函数,完成的任务就是从输入码流中读入一个packet,将信息存放到结构体AVPacket(AVPacket的分析见《ffmpeg重要结构体之AVPacket》)。packet的含义我个人理解就是能解出一帧图像的码流单元。
举个例子,对于HEVC,在某码流开始时,前6个nal的类型依次为:VPS,SPS,PPS,SEI,slice,slice.两个slice的nal组成一帧,则第一个packet就由这6个nal组成。
3.2.2 :从process_input_packet()一直到avctx->codec->decode(),avctx->codec->decode()指向hevc_decode_frame(),正式进入到HEVC解码部分。
fig.B从函数hevc_decode_frame()开始的函数调用关系。hevc_decode_frame()调用decode_nal_units(),逐个解析nalu。
1.ff_hevc_split_packet():这个函数将AVPacket分解为一个个的nalu,并将每个nalu的信息存放的结构体HEVCContext的成员HEVCPacket pkt(见《ffmpeg重要结构体之HEVCContext和HEVCFrame》)中,举个例子,一个packet中有10个nalu,ff_hevc_split_packet()会将这10个nalu的类型和长度都解析出来,并存好。
2.decode_nal_unit():此函数解码一个nalu的码流,根据nalu的类型调用不同的函数进行处理。
2.1:码流解析:当nalu的类型为VPS,PPS,SPS,或者SEI时,decode_nal_unit()调用相对应的解析函数进行处理。
这个步骤相对简单,解析函数比如ff_hevc_decode_nal_sps(),基本上就是按照HEVC的标准,将码流元素逐个读入,并存放的过程。
2.2slice解码:当nalu的类型为slice的时候,会先调用hls_slice_header()函数,将slice header 的信息读入,然后便是HEVC的slice解码。
hevc_frame_start()和函数ff_hevc_slice_rpl()涉及到DPB(decoded picture buffer)管理和rpl(reference picture list)解析即应用,以后会展开讨论。
而hls_decode_entry()就是逐个解码slice中的CTU(coding tree unit)了,在其他文章里会继续讨论。