FFmpeg 核心 API 系列:avcodec_find_decoder / avcodec_alloc_context3 / avcodec_open2

🎬 FFmpeg 核心 API 系列:avcodec_find_decoder / avcodec_alloc_context3 / avcodec_open2 全解析

📅 更新时间:2025年10月2日

🏷️ 标签:FFmpeg | 多媒体处理 | 音视频编程 | C/C++ | 流媒体

文章目录


📖 前言

回顾上一篇文章,我们已经能:

  • 打开文件 → 得到 AVFormatContext
  • 分析流信息 → 得到 AVStream(包含编码参数 codecpar

但是!这些信息只是"参数",并不能解码。就像你知道一个文件是H.264编码的,但还需要一个"H.264解码器"才能把压缩数据变成图像。

解码器的作用:将压缩的数据包(AVPacket)→ 解码成原始的帧(AVFrame


🎯 三个核心API详解

API 1️⃣:avcodec_find_decoder - 查找解码器

函数原型

cpp 复制代码
const AVCodec *avcodec_find_decoder(enum AVCodecID id);

参数说明

参数 说明
id 编码ID(从 stream->codecpar->codec_id 获取)

返回值

  • 成功:返回解码器指针(AVCodec*
  • 失败:返回 NULL

作用

根据编码ID找到对应的解码器


用法一:通过 codec_id 查找对应的解码器

cpp 复制代码
AVCodecID cid=stream->codecpar->codec_id;
//根据编码ID查找对应解码器
const AVCodec * decoder=avcodec_find_decoder(cid);

用法二:指定解码器名称查找

注意事项:要判断一下这个解码器是否能解码对应的流

cpp 复制代码
const AVCodec *codec = avcodec_find_decoder_by_name("h264_qsv");
if (!codec) {
    qDebug() << "未找到 h264_qsv 解码器";
} else {
    if (codec->id == stream->codecpar->codec_id) {
        qDebug() << "此解码器可用于该流";
    } else {
        qDebug() << "此解码器和流的编码ID不匹配";
    }
}

练习小Demo

cpp 复制代码
#include "mainwindow.h"
#include<QDebug>
#include <QApplication>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //MainWindow w;
    //w.show();

    AVFormatContext* con=nullptr;
    QString path="E:/BaiduNetdiskDownload/音视频资料/1、C++实战手把手教您用ffmpeg和QT开发播放器实战视频课程/1-02、音视频解封装和解码原理分析_ev.mp4";
    int r=avformat_open_input(&con,path.toUtf8().data(),nullptr,nullptr);

    if(r<0)
    {
        qDebug()<<"avformat_open_input is error";
    }
    else
    {
        qDebug()<<"avformat_open_input is success";
    }

    r=avformat_find_stream_info(con,nullptr);

    if(r<0)
    {
        qDebug()<<"avformat_find_stream_info is error";
    }
    else
    {
        qDebug()<<"avformat_find_stream_info is success";

        for(unsigned i=0;i<con->nb_streams;i++)
        {
            AVStream* stream=con->streams[i];
            if(stream->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
            {
                qDebug()<<"找到视频流";
                AVCodecID cid=stream->codecpar->codec_id;
                //根据编码ID查找对应解码器
                const AVCodec * decoder=avcodec_find_decoder(cid);
                if(!decoder)
                {
                    qDebug()<<"此视频流找不到对应解码器";
                }
                else
                {
                    qDebug()<<"此视频流的编码ID为:"<<cid;
                    qDebug()<<"此视频流成功找到对应解码器";
                }
            }
            else if(stream->codecpar->codec_type==AVMEDIA_TYPE_AUDIO)
            {
                qDebug()<<"找到音频流";
                AVCodecID cid=stream->codecpar->codec_id;
                //根据编码ID查找对应解码器
                const AVCodec * decoder=avcodec_find_decoder(cid);
                if(!decoder)
                {

                    qDebug()<<"此音频流找不到对应解码器";
                }
                else
                {
                    qDebug()<<"此音频流的编码ID为:"<<cid;
                    qDebug()<<"此音频流成功找到对应解码器";
                }
            }
            else
            {
                qDebug()<<"找到其它未知流";
            }
        }
    }

    avformat_close_input(&con);
    return a.exec();
}

输出结果

复制代码
avformat_open_input is success
avformat_find_stream_info is success
找到视频流
此视频流的编码ID为: 27
此视频流成功找到对应解码器
找到音频流
此音频流的编码ID为: 86018
此音频流成功找到对应解码器

API 2️⃣:avcodec_alloc_context3 - 分配解码器上下文

函数原型

cpp 复制代码
AVCodecContext* avcodec_alloc_context3(const AVCodec* codec);

参数说明

参数 说明
codec 解码器指针(可以传 NULL,但通常传第一步找到的解码器)

返回值

  • 成功:返回解码器上下文指针(AVCodecContext*
  • 失败:返回 NULL

作用

为解码器分配一个工作环境(上下文)


示例

cpp 复制代码
// 分配解码器上下文
AVCodecContext* codec_ctx = avcodec_alloc_context3(decoder); // decoder是通过avcodec_find_decoder找到的解码器
if (!codec_ctx) {
    qDebug() << "分配解码器上下文失败!";
}

🔧 辅助API:avcodec_parameters_to_context - 复制参数

为什么需要这一步?

  • AVStream 中的 codecpar 包含了编码参数(分辨率、帧率等)
  • AVCodecContext 刚分配时是空的
  • 需要把参数复制过去

函数原型

cpp 复制代码
int avcodec_parameters_to_context(AVCodecContext* codec_ctx, 
                                   const AVCodecParameters* par);

返回值

  • 0 → 成功
  • < 0 → 失败(通常是负数错误码,比如 AVERROR(EINVAL)

示例

cpp 复制代码
// 将流的参数复制到解码器上下文
int ret = avcodec_parameters_to_context(codec_ctx, video_stream->codecpar);
if (ret < 0) {
    qDebug() << "复制参数失败!";
}

API 3️⃣:avcodec_open2 - 打开解码器

函数原型

cpp 复制代码
int avcodec_open2(AVCodecContext* avctx, 
                  const AVCodec* codec, 
                  AVDictionary** options);

参数说明

参数 说明
avctx 解码器上下文
codec 解码器(传第一步找到的解码器)
options 可选参数字典(通常传 NULL

返回值

  • 0:成功
  • < 0:失败

作用

初始化并打开解码器,打开后才能真正使用


示例

cpp 复制代码
// 打开解码器
int ret = avcodec_open2(codec_ctx, decoder, nullptr);
if (ret < 0) {
    qDebug() << "打开解码器失败!";
}

🔑 关键数据结构

AVCodec(解码器)

cpp 复制代码
const AVCodec* decoder;
decoder->name;        // 解码器名称,如"h264"
decoder->long_name;   // 完整名称,如"H.264 / AVC / MPEG-4 AVC"
decoder->type;        // 类型:AVMEDIA_TYPE_VIDEO 或 AVMEDIA_TYPE_AUDIO
decoder->id;          // 编码ID

AVCodecContext(解码器上下文)

cpp 复制代码
AVCodecContext* codec_ctx;

// 视频相关字段
codec_ctx->width;           // 视频宽度
codec_ctx->height;          // 视频高度
codec_ctx->pix_fmt;         // 像素格式(如AV_PIX_FMT_YUV420P)
codec_ctx->framerate;       // 帧率

// 音频相关字段
codec_ctx->sample_rate;     // 采样率(如44100)
codec_ctx->channel_layout;  // 声道布局
codec_ctx->sample_fmt;      // 采样格式(如AV_SAMPLE_FMT_FLTP)

💻 完整小Demo

目标:打开视频文件,为视频流和音频流找到并打开解码器,打印解码器信息

cpp 复制代码
#include <QCoreApplication>
#include <QDebug>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    AVFormatContext* fmt_ctx = nullptr;
    AVCodecContext* video_codec_ctx = nullptr;
    AVCodecContext* audio_codec_ctx = nullptr;

    // ===== 阶段一:打开文件和分析流信息 =====
    QString path = "E:/BaiduNetdiskDownload/音视频资料/1、C++实战手把手教您用ffmpeg和QT开发播放器实战视频课程/1-02、音视频解封装和解码原理分析_ev.mp4";

    // 1. 打开文件
    if (avformat_open_input(&fmt_ctx, path.toUtf8().data(), nullptr, nullptr) < 0) {
        qDebug() << "打开文件失败!";
        return -1;
    }

    // 2. 分析流信息
    if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
        qDebug() << "分析流信息失败!";
        avformat_close_input(&fmt_ctx);
        return -1;
    }

    // 3. 找到视频流和音频流
    int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    int audio_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);

    qDebug() << "========== 文件信息 ==========";
    qDebug() << "视频流索引:" << video_index;
    qDebug() << "音频流索引:" << audio_index;
    qDebug() << "";

    // ===== 阶段二:查找并打开解码器 =====

    // 处理视频流
    if (video_index >= 0) {
        AVStream* video_stream = fmt_ctx->streams[video_index];

        // 1. 查找解码器
        const AVCodec* video_decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);
        if (!video_decoder) {
            qDebug() << "找不到视频解码器!";
        } else {
            qDebug() << "========== 视频解码器 ==========";
            qDebug() << "解码器名称:" << video_decoder->name;
            qDebug() << "解码器完整名称:" << video_decoder->long_name;

            // 2. 分配解码器上下文
            video_codec_ctx = avcodec_alloc_context3(video_decoder);
            if (!video_codec_ctx) {
                qDebug() << "分配视频解码器上下文失败!";
            } else {
                // 3. 复制参数到上下文
                if (avcodec_parameters_to_context(video_codec_ctx, video_stream->codecpar) < 0) {
                    qDebug() << "复制视频参数失败!";
                } else {
                    // 4. 打开解码器
                    if (avcodec_open2(video_codec_ctx, video_decoder, nullptr) < 0) {
                        qDebug() << "打开视频解码器失败!";
                    } else {
                        qDebug() << "视频解码器打开成功!";
                        qDebug() << "分辨率:" << video_codec_ctx->width << "x" << video_codec_ctx->height;
                        qDebug() << "像素格式:" << video_codec_ctx->pix_fmt;
                        qDebug() << "帧率:" << av_q2d(video_stream->r_frame_rate) << "fps";
                    }
                }
            }
            qDebug() << "";
        }
    }

    // 处理音频流
    if (audio_index >= 0) {
        AVStream* audio_stream = fmt_ctx->streams[audio_index];

        // 1. 查找解码器
        const AVCodec* audio_decoder = avcodec_find_decoder(audio_stream->codecpar->codec_id);
        if (!audio_decoder) {
            qDebug() << "找不到音频解码器!";
        } else {
            qDebug() << "========== 音频解码器 ==========";
            qDebug() << "解码器名称:" << audio_decoder->name;
            qDebug() << "解码器完整名称:" << audio_decoder->long_name;

            // 2. 分配解码器上下文
            audio_codec_ctx = avcodec_alloc_context3(audio_decoder);
            if (!audio_codec_ctx) {
                qDebug() << "分配音频解码器上下文失败!";
            } else {
                // 3. 复制参数到上下文
                if (avcodec_parameters_to_context(audio_codec_ctx, audio_stream->codecpar) < 0) {
                    qDebug() << "复制音频参数失败!";
                } else {
                    // 4. 打开解码器
                    if (avcodec_open2(audio_codec_ctx, audio_decoder, nullptr) < 0) {
                        qDebug() << "打开音频解码器失败!";
                    } else {
                        qDebug() << "音频解码器打开成功!";
                        qDebug() << "采样率:" << audio_codec_ctx->sample_rate << "Hz";
                        qDebug() << "采样格式:" << audio_codec_ctx->sample_fmt;
                    }
                }
            }
            qDebug() << "";
        }
    }

    // ===== 清理资源 =====
    if (video_codec_ctx) {
        avcodec_free_context(&video_codec_ctx);
    }
    if (audio_codec_ctx) {
        avcodec_free_context(&audio_codec_ctx);
    }
    avformat_close_input(&fmt_ctx);

    qDebug() << "程序结束";
    return 0;
}

输出结果

复制代码
========== 文件信息 ==========
视频流索引: 0
音频流索引: 1

========== 视频解码器 ==========
解码器名称: h264
解码器完整名称: H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
视频解码器打开成功!
分辨率: 1280 x 720
像素格式: 0
帧率: 60 fps

========== 音频解码器 ==========
解码器名称: aac
解码器完整名称: AAC (Advanced Audio Coding)
音频解码器打开成功!
采样率: 44100 Hz
采样格式: 8

程序结束

📋 总结

核心流程

复制代码
查找解码器 → 分配上下文 → 复制参数 → 打开解码器 → 开始使用

如果您觉得这篇文章对您有帮助,不妨点赞 + 收藏 + 关注,更多 FFmpeg 系列教程将持续更新 🔥!

相关推荐
Everbrilliant895 小时前
Xcode上编译调试ffmpeg
macos·ffmpeg·xcode·ffmpeg源码编译工具·xcode调试ffmpeg源码·ffmpeg工具环境变量配置
molihuan11 小时前
开源 全平台 哔哩哔哩缓存视频合并 Github地址:https://github.com/molihuan/hlbmerge_flutter
android·flutter·缓存·ffmpeg·开源·github·音视频
爱吃牛肉的大老虎1 天前
FFmpeg和ZLMediaKit 实现本地视频推流
ffmpeg·音视频
liliangcsdn1 天前
基于ollama运行27b gemma3解决ffmpeg命令生成问题
人工智能·ffmpeg
Everbrilliant893 天前
音视频编解码全流程之用Extractor后Decodec
ffmpeg·视频编解码·mediacodec·音视频解码·ffmpeg编解码·decodec·ndkmediacodec
Industio_触觉智能3 天前
瑞芯微RK35XX系列FFmpeg硬件编解码实测,详细性能对比!
ffmpeg·rk3588·rk3568·编解码·rk3562·rk3576
小狮子安度因3 天前
FFmpeg暂停、逐帧和音量
ffmpeg
小狮子安度因3 天前
FFmpeg过滤器实战:水印处理
ffmpeg·myeclipse
小狮子安度因3 天前
FFmpeg过滤器实战:混音
ffmpeg