一、从文件读取 packet 到解码出 frame 的完整流程#
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
// 完整的一个 demux + decode 流程
// 从视频文件中读取每一帧解码为 YUV420P 原始数据
struct VideoDecoder {
AVFormatContext* fmt_ctx = nullptr;
AVCodecContext* dec_ctx = nullptr;
int video_stream_idx = -1;
};
bool open_file(VideoDecoder& dec, const char* filename) {
// ① 打开文件并读取头部信息
if (avformat_open_input(&dec.fmt_ctx, filename, nullptr, nullptr) < 0)
return false;
// ② 读取流信息(找到视频流的索引和编码器参数)
if (avformat_find_stream_info(dec.fmt_ctx, nullptr) < 0)
return false;
// ③ 找到第一个视频流
for (unsigned i = 0; i < dec.fmt_ctx->nb_streams; i++) {
if (dec.fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
dec.video_stream_idx = i;
break;
}
}
if (dec.video_stream_idx < 0) return false;
// ④ 找到对应的解码器并打开
const AVCodec* codec = avcodec_find_decoder(
dec.fmt_ctx->streams[dec.video_stream_idx]->codecpar->codec_id);
if (!codec) return false;
dec.dec_ctx = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(dec.dec_ctx,
dec.fmt_ctx->streams[dec.video_stream_idx]->codecpar);
avcodec_open2(dec.dec_ctx, codec, nullptr);
return true;
}
bool decode_frame(VideoDecoder& dec, AVFrame* frame) {
AVPacket* packet = av_packet_alloc();
while (av_read_frame(dec.fmt_ctx, packet) >= 0) {
if (packet->stream_index == dec.video_stream_idx) {
// ⑤ 把 packet 送入解码器
if (avcodec_send_packet(dec.dec_ctx, packet) == 0) {
// ⑥ 从解码器取出解码后的 frame
if (avcodec_receive_frame(dec.dec_ctx, frame) == 0) {
av_packet_free(&packet);
return true; // 成功解码一帧
}
}
}
av_packet_unref(packet);
}
av_packet_free(&packet);
return false; // 文件读完
}
二、关于 AVFrame 的重要理解#
// 解码后得到的 AVFrame 包含:
// frame->data[0] → Y 平面数据指针
// frame->data[1] → U 平面数据指针
// frame->data[2] → V 平面数据指针(NV12 时 data[1] 是 UV 交错)
//
// frame->linesize[0] → Y 平面的 stride
// frame->linesize[1] → U 平面的 stride
// frame->linesize[2] → V 平面的 stride
//
// frame->pts → 显示时间戳(毫秒单位 × time_base)
// frame->pict_type → AV_PICTURE_TYPE_I / P / B
//
// frame->width, frame->height → 分辨率
// frame->format → 像素格式 AV_PIX_FMT_YUV420P / AV_PIX_FMT_NV12
// ❌ 常见错误:假设 data 是连续的内存块
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t y_val = frame->data[0][y * frame->linesize[0] + x];
}
}
// ✅ linesize 可能不等于 width(有 padding),必须用 linesize