视频解码流程主要分为**解封装(Demuxing)和解码(Decode)**两个阶段。
一、视频解码流程
1. 初始化 FFmpeg 库
- 注册组件:
调用av_register_all()
注册所有编解码器和封装格式(FFmpeg 4.0+ 已弃用,但仍需初始化网络等模块)。 - 网络初始化(可选):
若处理网络流,需调用avformat_network_init()
。
2. 打开输入文件
- 创建格式上下文:
使用avformat_alloc_context()
分配AVFormatContext
,存储文件格式信息。 - 打开文件:
avformat_open_input(&fmt_ctx, filename, NULL, NULL)
打开文件并填充格式上下文。
3. 获取流信息
- 解析流信息:
avformat_find_stream_info(fmt_ctx, NULL)
获取视频流的编码参数、时长等元数据。
4. 查找视频流
- 遍历流索引:
通过fmt_ctx->nb_streams
遍历,检查stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
确定视频流索引。
5. 初始化解码器
- 查找解码器:
avcodec_find_decoder(stream->codecpar->codec_id)
根据编码格式(如 H.264)查找解码器。 - 创建解码上下文:
avcodec_alloc_context3(codec)
分配AVCodecContext
,并通过avcodec_parameters_to_context()
填充参数。 - 打开解码器:
avcodec_open2(codec_ctx, codec, NULL)
初始化解码器。
6. 解码循环
- 读取压缩数据包:
av_read_frame(fmt_ctx, &packet)
循环读取AVPacket
(压缩数据)。 - 发送数据包:
avcodec_send_packet(codec_ctx, &packet)
将数据包送入解码器。 - 接收解码帧:
avcodec_receive_frame(codec_ctx, frame)
获取解码后的AVFrame
(YUV/RGB 数据)。 - 释放数据包:
每次循环后调用av_packet_unref(&packet)
释放资源。
7. 释放资源
- 关闭解码器:
avcodec_close(codec_ctx)
- 关闭输入文件:
avformat_close_input(&fmt_ctx)
- 释放帧和上下文:
av_frame_free(&frame)
,avcodec_free_context(&codec_ctx)
。
二、核心 API 及数据结构
API/结构体 | 作用 |
---|---|
AVFormatContext |
存储封装格式的全局信息(如流数量、时长)。 |
AVCodecContext |
编解码器上下文,包含宽高、像素格式等参数。 |
AVPacket |
存储压缩编码数据(如 H.264 码流)。 |
AVFrame |
存储解码后的原始数据(YUV/RGB)。 |
avcodec_send_packet() |
将压缩数据发送给解码器。 |
avcodec_receive_frame() |
从解码器获取解码后的帧。 |
sws_scale() |
转换像素格式(如 YUV 转 RGB)。 |
三、完整流程示例
// 初始化
avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL);
avformat_find_stream_info(fmt_ctx, NULL);
// 查找视频流
int video_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
// 初始化解码器
AVCodec *codec = avcodec_find_decoder(fmt_ctx->streams[video_idx]->codecpar->codec_id);
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_idx]->codecpar);
avcodec_open2(codec_ctx, codec, NULL);
// 解码循环
AVFrame *frame = av_frame_alloc();
AVPacket packet;
while (av_read_frame(fmt_ctx, &packet) >= 0) {
if (packet.stream_index == video_idx) {
avcodec_send_packet(codec_ctx, &packet);
while (avcodec_receive_frame(codec_ctx, frame) == 0) {
// 处理帧数据(如渲染或保存)
}
}
av_packet_unref(&packet);
}