视频解码器
视频链接
FFmpeg 介绍
FFmpeg 库
FFmpeg 一共包含 8 个库:
- avcodec: 编解码(最重要的库)*
- avformat: 封装格式处理 *
- avfilter: 滤镜特效处理
- avdevice: 各种设备的输入输出
- avutil: 工具库(大部分库都需要这个库的支持)*
- postproc: 后加工
- swresample: 音频采样数据格式转换
- swscale: 视频像素数据格式转换 *
其中 * 表示本次课程中会涉及到的库
FFmpeg 解码流程
流程图

函数介绍
| 函数名 | 功能描述 |
|---|---|
avformat_open_input |
打开输入文件并创建 AVFormatContext |
avformat_find_stream_info |
获取流信息 |
avcodec_find_decoder |
查找解码器 |
avcodec_alloc_context3 |
分配解码器上下文 |
avcodec_parameters_to_context |
复制编解码器参数 |
avcodec_open2 |
打开解码器 |
av_read_frame |
读取数据包 |
avcodec_send_packet |
发送数据包到解码器 |
avcodec_receive_frame |
接收解码后的帧 |
sws_scale |
像素格式转换 |
fwrite |
写入转换后的帧数据 |
av_packet_unref |
释放数据包 |
avcodec_free_context |
释放解码器上下文 |
avformat_close_input |
关闭输入流 |
FFmpeg 解码的数据结构

AV 表示 Audio Video
AVFormatContext: 用于处理封装格式的上下文,包含视频最外层的信息
AVInputFormat: 输入格式
AVStream: 是一个数组,包含多个流,但是一般就包含视频流和音频流,第 0 个是视频流,第 1 个是音频流
AVCodecContext: 编解码器上下文
AVCodec: 编解码器,指明编码器的类型(h.264之类的)
AVPacket: 压缩编码后的数据包
AVFrame: 解码后的数据包
AVPacket 解码完为 AVFrame

AVFormatContext
用于处理封装格式的上下文,包含视频最外层的信息
- iformat: 输入视频的
AVInputFormat - nb_streams: 输入视频的
AVStream个数 - streams: 输入视频的
AVStream数组 - duration: 输入视频的时长(以微秒为单位)
- bit_rate: 输入视频的码率
AVInputFormat
输入格式
- name: 输入视频的格式名称
- long_name: 输入视频格式的长名称
- extensions: 输入视频格式的扩展名
- id: 输入视频格式的 ID
- 一些封装格式处理的接口函数
AVStream
是一个数组,包含多个流,但是一般就包含视频流和音频流,第 0 个是视频流,第 1 个是音频流
- id: 输入视频流的 ID
- codecpar: 输入视频流的
AVCodecContext - time_base: 输入视频流的时间基
- r_frame_rate: 输入视频流的帧率
time_base 是一个分数,表示时间基,用于将时间戳转换为实际时间。
r_frame_rate 是一个分数,表示帧率,用于计算帧间隔时间。
AVCodecContext
编解码器上下文
- codec:编解码器的
AVCodec - width, height: 图像的宽高
- pix_fmt: 图像的像素格式
- sample_rate: 音频的采样率
- channels: 音频的声道数
- sample_fmt: 音频的采样格式
- codec_type: 编解码器的类型(视频、音频等)
- codec_id: 编解码器的 ID
AVCodec
编解码器,指明编码器的类型(h.264之类的)
- name: 编解码器的名称
- long_name: 编解码器的全称
- id: 编解码器的 ID
- 一些编解码的接口函数
AVPacket
压缩编码后的数据包,理解成装 h264 数据的盒子
- pts: 显示时间戳
- dts: 解码时间戳
- data: 压缩编码的数据
- size: 数据的大小
- stream_index: 所属的 AVStream (音频流还是视频流)
AVFrame
解码后的数据包,理解成装 yuv 数据的盒子
- data: 解码后的图像数据(音频采样数据)
- linesize: 对视频来说是图像中的一行像素的大小;对音频来说是整个音频帧的大小
- width, height: 视频帧的宽和高
- key_frame: 是否是关键帧
- pict_type: 帧类型(I, B, P 帧)
补充小知识
解码后的数据为什么要经过 sws_scale 转换?
解码后 YUV 数据格式的视频像素数据保存在 AVFrame 的 data[0],data[1],data[2],但是这些像素值并不是连续存储的,每行有效像素之后存储的是无效像素。
以亮度 Y 数据为例,data[0] 中一共包含了 linesize[0] * height 个数据。但是出于优化等方面考虑,linesize[0] 可能大于 width 。因此需要使用 sws_scale 进行转换。
转换后去除了无效数据,width 和 linesize[0] 就相等了。

代码运行
雷神给的这些代码,在资源释放上有些问题(资源的释放都集中在最后,如果程序提前终止,那就导致资源没有释放),但这里先不考虑这些。
代码
头文件 lib 和库文件 include 的配置在前面博客说到,可以复制到这个项目中(上一个博客我讲的是放在 usr/local/ffmpeg )
这里代码和雷神视频中的代码略有不同,修改了很多:
|
|
CMakeLists.txt
|
|
手动编译一遍:
|
|
如果成功输出,则说明配置成功
没成功的话要注意看一下源码的路径和播放视频的路径是否正确
我这里源码放在 src/decoder.cpp,播放视频放在video/input.mkv
调试
在左侧工具栏找到运行和调试工具

点击创建 launch.json 文件,选择C++(GDB/LLDB)
在launch.json文件中添加以下内容:
|
|
按下 f5 即可调试
这里可能会遇到两个问题:
-
在源码位置按下
f5后,会提示找不到找不到你链接的库
这里可能原因是没执行你的CMakeLists.txt文件,换成点击左侧工具栏的启动按键就行 -
提示找不到输入文件,可以试试去掉
json中cwd字段的/build:
|
|
后续博客就不讲调试的文件书写了,基本都差不多
练习
获取解码前的 h264 文件
注意这里只获取 MPEG-TS ,AVI 格式的文件,如果是别的文件,无法直接获取
这样重新编码后,就可以获取到解码前的 h264 文件了。
代码分析
- 打开文件
|
|
- 循环从媒体文件中读取一帧数据,并将其存储在
AVPacket结构体中。
|
|
- 这个函数会读取下一个可用的数据包,无论是音频、视频还是其他类型的流
- 判断是否为视频帧
|
|
- 获取数据
|
|
- 关闭文件,释放资源
注意事项
-
输入文件一定要是
ts或avi格式!
因为mp4和flv格式需要解析moov结构,而ts和avi格式可以直接解析h264数据
我就是一开始没注意,导致浪费了很多时间 -
获取完数据记得释放资源和关闭文件
获取解码后的 yuv 文件
- 打开文件
|
|
- 循环从媒体文件中读取一帧数据,并将其存储在
AVPacket结构体中。
|
|
- 判断当前帧是否为视频流
|
|
- 解码一帧视频数据
|
|
- 这个函数会将
AVPacket中的压缩数据发送给解码器进行解码
- 接收解码后的数据
|
|
- 这个函数会从解码器中接收解码后的原始数据,并存储在
AVFrame结构体中 AVERROR(EAGAIN)是FFmpeg库中的一个错误码,表示当前没有足够的数据可供解码,需要等待更多数据到来才能继续解码。这种情况通常发生在数据流尚未准备好或缓冲区为空时AVERROR_EOF表示已经到达数据流的末尾(End of File),没有更多的数据可供解码
- 处理解码后的数据
|
|
前面补充知识有提到,解码后的数据格式的视频像素值不是连续存储,而是按行存储的,会多出一些无效像素,导致像素的 width 和 linesize 不一致,要用 sws_scale 函数进行转换
sws_getContext 函数
- 用途:创建图像转换上下文 (
SwsContext) - 参数说明:
pCodecCtx->width,pCodecCtx->height:原始视频帧的宽度和高度pCodecCtx->pix_fmt:原始视频帧的像素格式AV_PIX_FMT_YUV420P:目标像素格式,此处为 YUV420PSWS_BICUBIC:缩放算法,使用双三次插值
sws_scale 函数
- 用途:执行实际的图像格式转换
- 参数说明:
img_convert_ctx:之前创建的图像转换上下文(const unsigned char *const *)pFrame->data:原始视频帧的数据pFrame->linesize:原始视频帧的每行字节数0:从原始帧的第 0 行开始转换pCodecCtx->height:原始视频帧的高度pFrameYUV->data:目标帧的数据缓冲区,用于存储转换后的 YUV420P 数据pFrameYUV->linesize:目标帧的每行字节数
- 保存 YUV 数据到文件
|
|
- 关闭文件,释放资源
完整代码
代码优化了一些,也把一些要求输出的信息写在里面了
|
|
资源释放问题后面会再出博客写,因为涉及到各种细节