一、概述
解码从数据结构上看,是将 AVPacket
转换为 AVFrame
的过程。如何构建 AVPacket,以及获取到 AVFrame 后续要进行什么操作,严格来说并不是解码的步骤。
本文主要关注使用 FFmpeg
的软解 H.264
。虽然本文并不涉及 VideoToolBox
,但硬解和软解有一些共用的 API,并且软解过程更流畅,所以熟悉软解后再去了解硬解会容易。
二、标准格式视频文件的解码
1、解码过程描述
以下内容来自雷霄骅博客:
视频播放器播放一个互联网上的视频文件,需要经过以下几个步骤:解协议,解封装,解码视音频,视音频同步。如果播放本地文件则不需要解协议,为以下几个步骤:解封装,解码视音频,视音频同步。他们的过程如图所示。
解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如HTTP,RTMP,或是MMS等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。例如,采用RTMP协议传输的数据,经过解协议操作后,输出FLV格式的数据。
解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如MP4,MKV,RMVB,TS,FLV,AVI等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV格式的数据,经过解封装操作后,输出H.264编码的视频码流和AAC编码的音频码流。
解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含AAC,MP3,AC-3等等,视频的压缩编码标准则包含H.264,MPEG2,VC-1等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如YUV420P,RGB等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如PCM数据。
视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡和声卡播放出来。
2、解码过程对应的 FFmpeg API
解码过程对应的 FFmpeg API,可以用伪代码表示:
1 | avformat_open_input() // 解协议。打开流并读取头,从而获取一个格式上下文 AVFormatContext 对象。 |
备注:
- 不使用 av_read_frame 函数,而是采用标准 I/O 的方式读取数据也是可行的,可使用 av_parser_parse2 函数来封装 AVPacket 。
- 本文说的”返回”不一定指函数的返回值,也可能是通过指针参数传入,由函数填充的数据。如果是双重指针,则一般是函数进行内存分配。
3、注意事项
如果解码 mp4 时在调用 avcodec_send_packet 函数失败,确保调用了 avcodec_parameters_to_context 函数。在官方示例 decode_video 的 avcodec_alloc_context3 调用之后也有个注释:
1 | c = avcodec_alloc_context3(codec); |
因为可能存在 1 Packet 多 Frame 的情况,所以一次 avcodec_send_packet 通常配合多次 avcodec_receive_frame调用。官方示例 decode_video:
1 | static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt, |
三、自定义格式视频文件的解码
1. 解码过程描述
因为不是标准格式,所以上文中的一些解码过程在这里是不必要的。以《FFmpeg 与 VideoToolBox(1):准备工作》准备的 temp.data 这样的文件为例:
(1) 非标准的;
(2) 有封装格式又足够简单的,每一帧数据有个长度头(4字节);
(3) 知道文件包含的是 H.264 数据;
(4) SPS、PPS 数据已经在 IDR 帧中。
解协议、解封装是不必要的;SPS、PPS已经在帧数据里面,不用调用 avcodec_parameters_to_context ;采用 av_find_best_stream 去初始化解码器 AVCodec 对象是不可能的; av_read_frame 可用标准 I/O 函数替代。
2. 解码过程对应的 FFmpeg API
解码过程对应的 FFmpeg API,可以用伪代码表示:
1 | fopen() // 打开文件 |