文档目录

一、从文件读取 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