ffmpeg库视频硬解码使用流程

news/2025/3/22 3:03:32/

        FFmpeg 的硬解码(Hardware Decoding)通过调用 GPU 或专用硬件的编解码能力实现,能显著降低 CPU 占用率。

一、FFmpeg 支持的硬件解码类型

FFmpeg 原生支持多种硬件加速类型,具体由 AVHWDeviceType 定义,包括:

  • NVIDIA CUDA/NVDEC‌:基于 NVIDIA 显卡的解码‌。
  • Intel Quick Sync Video (QSV)‌:Intel 集成显卡的硬件加速‌。
  • VAAPI‌:适用于 Intel/AMD 硬件的通用视频加速 API‌。
  • VideoToolbox‌:macOS/iOS 平台的硬解码‌。
  • MediaCodec‌:Android 平台的硬解码(需 FFmpeg 编译时启用)‌。
    enum AVHWDeviceType {AV_HWDEVICE_TYPE_NONE,AV_HWDEVICE_TYPE_VDPAU,AV_HWDEVICE_TYPE_CUDA,AV_HWDEVICE_TYPE_VAAPI,AV_HWDEVICE_TYPE_DXVA2,AV_HWDEVICE_TYPE_QSV,AV_HWDEVICE_TYPE_VIDEOTOOLBOX,AV_HWDEVICE_TYPE_D3D11VA,AV_HWDEVICE_TYPE_DRM,AV_HWDEVICE_TYPE_OPENCL,AV_HWDEVICE_TYPE_MEDIACODEC,
    };

av_hwdevice_find_type_by_name:根据名称查找对应的AVHWDeviceType。支持的名称如下所示。

static const char *const hw_type_names[] = {[AV_HWDEVICE_TYPE_CUDA]   = "cuda",[AV_HWDEVICE_TYPE_DRM]    = "drm",[AV_HWDEVICE_TYPE_DXVA2]  = "dxva2",[AV_HWDEVICE_TYPE_D3D11VA] = "d3d11va",[AV_HWDEVICE_TYPE_OPENCL] = "opencl",[AV_HWDEVICE_TYPE_QSV]    = "qsv",[AV_HWDEVICE_TYPE_VAAPI]  = "vaapi",[AV_HWDEVICE_TYPE_VDPAU]  = "vdpau",[AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox",[AV_HWDEVICE_TYPE_MEDIACODEC] = "mediacodec",
};

 与硬解相关的函数:

avcodec_get_hw_config:用于获取编解码器支持的硬件配置AVCodecHWConfig。这里用于获取硬件支持的像素格式。
av_hwdevice_ctx_create:av_hwdevice_ctx_create创建硬件设备相关的上下文信息AVHWDeviceContext和对硬件设备进行初始化。
decoder_ctx->get_format = get_hw_format ,get_hw_format是向AVCodecContext注册的一个函数,用于协商支持的像素格式。
av_hwframe_transfer_data:拷贝数据到一个硬件的surface,或者从一个硬件surface拷贝数据,也就是GPU和CPU之间数据拷贝。这里用于GPU拷贝到CPU。GPU解码后数据格式默认类型是从硬件读取,CUDA可能是AV_PIX_FMT_NV12;而CPU解码后的数据一般是YUV数据,比如AV_PIX_FMT_YUV420P。
av_find_best_stream:查找最佳媒体流(如视频、音频、字幕等)的函数。

enum AVPixelFormat hw_pix_fmt;
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_CUDA;
AVCodec *decoder = NULL;/* open the input file */if (avformat_open_input(&input_ctx, argv[2], NULL, NULL) != 0) {fprintf(stderr, "Cannot open input file '%s'\n", argv[2]);return -1;}if (avformat_find_stream_info(input_ctx, NULL) < 0) {fprintf(stderr, "Cannot find input stream information.\n");return -1;}/* find the video stream information */ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &decoder, 0);if (ret < 0) {fprintf(stderr, "Cannot find a video stream in the input file\n");return -1;}for (i = 0;; i++) {const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);if (!config) {fprintf(stderr, "Decoder %s does not support device type %s.\n",decoder->name, av_hwdevice_get_type_name(type));return -1;}if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&config->device_type == type) {hw_pix_fmt = config->pix_fmt;break;}}

二、FFmpeg 硬件解码器名称及对应编码格式

FFmpeg 支持的硬件解码器名称与编码格式关联紧密,需根据具体硬件平台(如 NVIDIA、Intel、AMD)及接口协议(如 CUDA、VAAPI、QSV)选择适配方案。以下是主流编码格式对应的硬件解码器名称示例:

2.1、视频编码格式与硬件解码器
  1. H.264/AVC

    • h264_cuvid(NVIDIA CUDA加速)
    • h264_qsv(Intel Quick Sync Video)
    • h264_vaapi(跨平台开源接口)
    • h264_amf(AMD Advanced Media Framework)‌
  2. H.265/HEVC

    • hevc_cuvid(NVIDIA)
    • hevc_qsv(Intel)
    • hevc_vaapi(通用接口)
    • hevc_amf(AMD)‌
  3. VP8/VP9

    • vp8_cuvid(NVIDIA)
    • vp9_vaapi(通用接口)
    • vp9_qsv(Intel)‌
  4. AV1

    • av1_qsv(Intel)
    • av1_vaapi(通用接口)‌
2.2、音频编码格式与硬件解码器
  1. AAC

    • 硬件解码依赖平台驱动支持(如 Intel HD Audio),FFmpeg 中通常通过系统接口调用,无独立硬解名称‌。
  2. MP3/Opus

    • 硬解支持较少,多采用软件解码‌。

‌三、硬解码实现流程

1. 初始化硬件设备
  • 获取硬件设备类型
    通过 av_hwdevice_find_type_by_name 或枚举类型确定目标硬解码设备‌。
  • 创建硬件设备上下文
    使用 av_hwdevice_ctx_create 初始化硬件设备上下文(hw_device_ctx)‌。
2. 配置解码器
  • 查找支持硬解码的编解码器
    例如 H.264 硬解需查找 h264_cuvid(NVIDIA)或 h264_mediacodec(Android)等解码器‌。
  • 设置解码器参数
    在 AVCodecContext 中指定 hw_device_ctx,关联硬件设备上下文‌。
3. 解码数据
  • 发送数据包
    调用 avcodec_send_packet 将压缩数据送入解码器。
  • 接收解码帧
    通过 avcodec_receive_frame 获取解码后的帧数据,硬件解码的帧通常存储在 GPU 内存中‌。
4. 处理解码数据
  • 内存映射与格式转换
    若需 CPU 访问解码数据,需使用 av_hwframe_transfer_data 将帧从 GPU 内存复制到 CPU 内存‌。

‌四、代码示例

3.1实现硬件解码(以 ‌NVIDIA CUDA/NVDEC‌ 为例)的完整示例代码。

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>int main(int argc, char *argv[]) {AVFormatContext *fmt_ctx = NULL;AVCodecContext *codec_ctx = NULL;const AVCodec *codec = NULL;AVBufferRef *hw_device_ctx = NULL;AVPacket *pkt = NULL;AVFrame *hw_frame = NULL, *sw_frame = NULL;int video_stream_idx = -1;// 1. 初始化 FFmpegavformat_network_init();// 2. 打开输入文件if (avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL) < 0) {fprintf(stderr, "无法打开输入文件\n");return -1;}// 3. 查找视频流索引if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {fprintf(stderr, "无法获取流信息\n");goto cleanup;}for (int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_idx = i;break;}}if (video_stream_idx == -1) {fprintf(stderr, "未找到视频流\n");goto cleanup;}// 4. 初始化硬件设备 (CUDA)if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0) < 0) {fprintf(stderr, "无法创建 CUDA 硬件设备\n");goto cleanup;}// 5. 配置硬件解码器codec = avcodec_find_decoder_by_name("h264_cuvid"); // NVIDIA 硬解解码器if (!codec) {fprintf(stderr, "未找到支持的硬解解码器\n");goto cleanup;}codec_ctx = avcodec_alloc_context3(codec);avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx); // 关联硬件设备// 6. 打开解码器if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "无法打开硬解解码器\n");goto cleanup;}// 7. 初始化数据包和帧pkt = av_packet_alloc();hw_frame = av_frame_alloc();sw_frame = av_frame_alloc();// 8. 解码循环while (av_read_frame(fmt_ctx, pkt) >= 0) {if (pkt->stream_index == video_stream_idx) {// 发送数据包到解码器if (avcodec_send_packet(codec_ctx, pkt) < 0) {fprintf(stderr, "发送数据包失败\n");continue;}// 接收解码后的帧while (avcodec_receive_frame(codec_ctx, hw_frame) == 0) {// 检查是否为硬件帧if (hw_frame->format == AV_PIX_FMT_CUDA) {// 将 GPU 内存数据复制到 CPU 内存if (av_hwframe_transfer_data(sw_frame, hw_frame, 0) < 0) {fprintf(stderr, "GPU→CPU 内存拷贝失败\n");continue;}// 在此处理 sw_frame(YUV420 数据)// 例如:保存到文件、渲染、转码等printf("解码一帧:宽度=%d, 高度=%d\n", sw_frame->width, sw_frame->height);}av_frame_unref(hw_frame);av_frame_unref(sw_frame);}}av_packet_unref(pkt);}cleanup:// 9. 释放资源av_frame_free(&hw_frame);av_frame_free(&sw_frame);av_packet_free(&pkt);avcodec_free_context(&codec_ctx);av_buffer_unref(&hw_device_ctx);avformat_close_input(&fmt_ctx);avformat_network_deinit();return 0;
}

说明:

AV_PIX_FMT_CUDA等像素格式对比。

格式存储位置典型用途性能优势
AV_PIX_FMT_CUDAGPU 显存硬解码、全流程 GPU 处理零拷贝、低延迟‌
AV_PIX_FMT_NV12CPU 内存软解码、跨设备处理兼容性强,但需拷贝‌
AV_PIX_FMT_RGB24CPU 内存图像显示、算法输入通用性强,但带宽占用高‌

‌五、注意事项

  1. 编译配置
    启用硬解码需在 FFmpeg 编译时添加对应选项(如 --enable-cuda --enable-cuvid --enable-nonfree)‌。
  2. 平台差异
    • Windows:常用 DXVA2 或 NVIDIA CUDA‌6。
    • Android:需启用 --enable-mediacodec 并关联 MediaCodec API‌。
  3. 兼容性回退
    硬解码失败时需切换至软解(如 h264 解码器)‌。
  4. 硬件类型选择
    若需使用其他硬件(如 Intel QSV 或 VAAPI):
    1)解码器名称改为 h264_qsv 或 h264_vaapi。
    2)修改 AV_HWDEVICE_TYPE_CUDA 为 AV_HWDEVICE_TYPE_QSV 或 AV_HWDEVICE_TYPE_VAAPI。
    • 滤镜处理‌:通过 libavfilter 实现缩放、裁剪、水印等操作‌。
    • 封装格式转换‌:使用 avformat_write_header() 和 av_write_frame() 实现转封装(如 MP4 转 TS)‌。

    ‌六、性能优化

    • 减少内存拷贝‌:直接在 GPU 内存中处理数据(如 OpenGL 渲染)‌。
    • 帧格式限制‌:硬解码输出格式通常为 NV12 或 YUV420P,需适配后续处理流程‌。


    http://www.ppmy.cn/news/1581038.html

    相关文章

    Python助力区块链互通——跨链桥接的实现与实践

    Python助力区块链互通——跨链桥接的实现与实践 区块链技术的繁荣发展带来了巨大的生态创新,但也因各链之间的割裂局面限制了它们的潜力。例如,你或许想在以太坊上使用来自比特币的资产,却因两条链不互通而不得不求助于中心化交易所。要打破“链间壁垒”,跨链桥接(Cross-…

    STM32配套程序接线图

    1 工程模板 2 LED闪烁 3LED流水灯 4蜂鸣器 5按键控制LED 6光敏传感器控制蜂鸣器 7OLED显示屏 8对射式红外传感器计次 9旋转编码器计次 10 定时器定时中断 11定时器外部时钟 12PWM驱动LED呼吸灯 13 PWM驱动舵机 14 PWM驱动直流电机 15输入捕获模式测频率 16PWMI模式测频率占空…

    JavaScript语法入门

    目录 1. 变量声明 2. 数据类型 3. 运算符 4. 分支语句 5. 循环语句 6. 字符串 7. 数组 8. 对象 9. 原型与原型链 10. 常用内置对象 JavaScript 是一种轻量级、解释型的编程语言&#xff0c;广泛应用于网页开发中&#xff0c;用于增强网页的交互性。以下是 JavaScript…

    deepseek连续对话与API调用机制

    在调用DeepSeek等大模型进行连续对话时&#xff0c;是否需要每次上传系统提示和对话历史取决于API的设计机制。 一、API调用机制解析 无状态服务原则 DeepSeek的API基于无状态架构设计&#xff0c;每次请求视为独立会话。若需维持对话连续性&#xff0c;必须由客户端主动管理并…

    如何利用爬虫获取1688商品详情API接口:从入门到实战

    一、技术原理分析 API定位方法 使用Chrome开发者工具&#xff08;F12&#xff09;的Network面板筛选XHR/Fetch请求&#xff08;通常返回JSON数据&#xff09;通过关键词搜索&#xff08;如"itemDetail"&#xff09;观察请求参数中的商品ID&#xff08;offerId&…

    Python学习第十九天

    Django-分页 后端分页 Django提供了Paginator类来实现后端分页。Paginator类可以将一个查询集&#xff08;QuerySet&#xff09;分成多个页面&#xff0c;每个页面包含指定数量的对象。 from django.shortcuts import render, redirect, get_object_or_404 from .models impo…

    观察者模式详解:用 Qt 信号与槽机制深入理解

    引言 你是否曾遇到这样的需求&#xff1a;一个对象的状态发生变化后&#xff0c;希望通知其他对象进行相应的更新&#xff1f;比如&#xff1a; 新闻订阅系统&#xff1a;当新闻发布后&#xff0c;所有订阅者都会收到通知。股票行情推送&#xff1a;股价变化时&#xff0c;所…

    实时监控、数据分析!Web-Check构建你的网站健康检测系统实操方案

    文章目录 前言1.关于Web-Check2.功能特点3.安装Docker4.创建并启动Web-Check容器5.本地访问测试6.公网远程访问本地Web-Check7.内网穿透工具安装8.创建远程连接公网地址9.使用固定公网地址远程访问 前言 在数字化运维领域&#xff0c;网站稳定性保障始终是开发者和运维团队的核…