下图转自掘金社区:
音视频 FFmpeg解码详解
视频解码知识
纯净的视频解码流程
- 压缩编码数据->像素数据。
- 例如解码H.264,就是“H.264码流->YUV”。
一般的视频解码流程
- 视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。封装格式中通常还包含音频码流等内容。
- 对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。
- 例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”。
FFmpeg库简介
FFmpeg一共包含8个库:
- avcodec:编解码(最重要的库)。
- avformat:封装格式处理。
- avfilter:滤镜特效处理。
- avdevice:各种设备的输入输出。
- avutil:工具库(大部分库都需要这个库的支持)。
- postproc:后加工。
- swresample:音频采样数据格式转换。
- swscale:视频像素数据格式转换。
解码流程如下:
FFmpeg解码函数简介
- av_register_all():注册所有组件。
- avformat_open_input():打开输入视频文件。
- avformat_find_stream_info():获取视频文件信息。
- avcodec_find_decoder():查找解码器。
- avcodec_open2():打开解码器。
- av_read_frame():从输入文件读取一帧压缩数据。
- avcodec_decode_video2():解码一帧压缩数据。
avcodec_decode_video2()主要做了以下几个方面的工作:
(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。
(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。其中第二部是关键的一步,它调用了AVCodec的decode()方法完成了解码。AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数。
- avcodec_close():关闭解码器。
- avformat_close_input():关闭输入视频文件。
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。
- AVFrame存放从AVPacket中解码出来的原始数据,其必须通过
av_frame_alloc
来创建,通过av_frame_free
来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,
在调用av_frame_alloc
的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同- 一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。
- 另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。代码如下:
AVFrame* pFrameYUV;
pFrameYUV = av_frame_alloc();
// 手动为 pFrameYUV分配数据缓存空间
int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->widht,pCodecCtx->width);
uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
// 将分配的数据缓存空间和AVFrame关联起来
avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height)
首先计算需要缓存空间大小,调用av_malloc
分配缓存空间,最后调用avpicture_fill
将分配的缓存空间和AVFrame关联起来。
调用av_frame_free
来释放AVFrame,该函数不止释放AVFrame本身的空间,还会释放掉包含在其内的其他对象动态申请的空间,例如上面的缓存空间。
- av_malloc和av_free,FFmpeg并没有提供垃圾回收机制,所有的内存管理都要手动进行。
av_malloc
只是在申请内存空间的时候会考虑到内存对齐(2字节,4字节对齐),
其申请的空间要调用av_free
释放。