ffmpeg使用vaapi解码后的视频如何基于x11或EGL实现0-copy渲染?

技术背景

对于ffmpeg硬解码后渲染常见的做法是解码后通过av_hwframe_transfer_data方法将数据从GPU拷贝到CPU,然后做一些转换处理用opengl渲染,必然涉及到譬如类似glTexImage2D的函数将数据上传到GPU。而这样2次copy就会导致CPU的使用率变高,且GPU没有被充分利用。

基于此,我们可以使用下面的0-copy技术可以将VAAPI解码后的数据直接通过GPU渲染,1080P@60FPS的视频CPU使用率可以降低到5%以内。

知识背景

一、ffmpeg 硬件解码

首先假设读者已经了解或具备使用 ffmpeg 进行硬件解码的相关知识。如果不了解的话建议先学习ffmpeg官方示例:https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/hw_decode.c

二、vaapi 解码流程

//以下代码位于:/usr/include/va/va.h

 * dpy = vaGetDisplayDRM(fd);
 * vaInitialize(dpy, ...);
 *
 * // Create surfaces required for decoding and subsequence encoding
 * vaCreateSurfaces(dpy, VA_RT_FORMAT_YUV420, width, height, &surfaces[0], ...);
 *
 * // Set up a queue for the surfaces shared between decode and encode threads
 * surface_queue = queue_create();
 *
 * // Create decode_thread
 * pthread_create(&decode_thread, NULL, decode, ...);
 *
 * // Create encode_thread
 * pthread_create(&encode_thread, NULL, encode, ...);
 *
 * // Decode thread function
 * decode() {
 *   // Find the decode entrypoint for H.264
 *   vaQueryConfigEntrypoints(dpy, h264_profile, entrypoints, ...);
 *
 *   // Create a config for H.264 decode
 *   vaCreateConfig(dpy, h264_profile, VAEntrypointVLD, ...);
 *
 *   // Create a context for decode
 *   vaCreateContext(dpy, config, width, height, VA_PROGRESSIVE, surfaces,
 *     num_surfaces, &decode_context);
 *
 *   // Decode frames in the bitstream
 *   for (;;) {
 *     // Parse one frame and decode
 *     vaBeginPicture(dpy, decode_context, surfaces[surface_index]);
 *     vaRenderPicture(dpy, decode_context, buf, ...);
 *     vaEndPicture(dpy, decode_context);
 *     // Poll the decoding status and enqueue the surface in display order after
 *     // decoding is complete
 *     vaQuerySurfaceStatus();
 *     enqueue(surface_queue, surface_index);
 *   }
 * }

如上,vaapi硬解码的基本流程为:

  1. 创建vadisplay.
  2. 查询profile和Entrypoint。
  3. 根据profile创建config和context。
  4. 通过vaBeginPicture、vaRenderPicture、vaEndPicture完成解码(真正的硬解码是发生在vaEndPicture的调用上)。

三、vaapi硬解码中 VADisplay 和 VASurfaceID 的关系

VADisplay:它是一个代表和管理整个VAAPI会话的上下文对象。VADisplay负责与硬件加速器的通信,创建会话,查询硬件加速器的功能,并且是进行各种VAAPI调用的起点。在X Window系统中,VADisplay通常与一个X11 Display连接关联,因为它需要与视频输出设备进行交互,但它也可以在不直接使用X11的情况下使用,比如通过DRM直接与GPU交互。

VASurfaceID: 这是一个标识符,代表特定的解码后的视频帧或者用于编码、处理的图像表面。VASurfaceID实质上是内存中存放图像数据的缓冲区,这些缓冲区是硬件加速的,并且对应于VAAPI内部的图像资源。

彼此之间的关系:

  • VADisplay用于创建和管理VASurfaceID。在解码视频流的过程中,首先需要通过VADisplay来创建一系列的VASurfaceID,这些VASurface将用于存储解码出来的视频帧。
  • 解码视频时,解码器会将视频帧解码到由VASurfaceID代表的表面上。这些表面被管理在VADisplay的上下文中,以便能够让解码器知道将解码数据放置在哪里。
  • 在视频渲染或播放阶段,VADisplay能够利用关联的输出系统(比如X11或者Wayland)来显示VASurfaceID所代表的视频帧。

总的来说,VADisplay是管理和执行解码会话的接口,而VASurfaceID则是在这一过程中实际存储解码数据的缓冲区的标识。两者配合使用,实现了硬件加速视频的解码和显示过程。

完整流程图

在这里插入图片描述

核心点释义

VA-X11 方式渲染

如上图所示,黑色线条为ffmpeg常规解码流程。红色线框加入了为实现0-copy渲染所需要的必要步骤。蓝色线框为利用libva直接渲染到x11窗口。橙色线框为借助egl和opengl渲染。主要增加了两个核心点。第一是在打开解码器前的配置,第二是解码后如何从avframe得到vasurfaceID并零复制渲染。

在①②③处,首先使用Xopendisplay得到默认显示设备指针,其次并创建出要显示的目标窗口。此处其实也并不一定要直接用Xlib库创建窗口,作为不依赖其他图形库的Demo程序,用X11窗口是比较合适的,当然也可以用qt创建QWidget,用其句柄作为之后的渲染目标。然后用vaGetDisPlay得到VADisPlay对象。VADisPlay对象比较重要,使用vaapi硬解码,ffmpeg要求我们必须将申请的vadisplay指针赋值给AVCodecContext中的hw_device_ctx的hwctx的display,否则在后面是无法得到有效的vasurfaceID。这也能理解,作为承载vaapi解码上下文的重要对象,参考“知识背景”中的第二节[vaapi 解码流程],vaBeginPicture、vaEndPicture等函数都需要依赖这个vadisplay,而这些函数其实被实现在ffmpeg解码函数中的。对于老版本的fffmpeg,vaEndPicture等函数是实现在 avcodec_decode_video2() 中。得到VADisplay后就是用vaInitialize对vaapi进行初始化了。

上面的流程图是新版本(4.x)ffmpeg的vaapi硬解初始化配置方法。对于老版的ffmpeg就比较复杂了。需要使用vaapi_context,用户自己实现createconfig、createcontext,得到configID和contextid,并将vaapi_context赋值给AVCodecContext的hwaccel_context(下面代码132行)。可以看ffmpeg源码中关于新旧版本context(old_context)的实现区别:

以下代码位置:<https://github.com/FFmpeg/FFmpeg/blob/release/4.4/libavcodec/vaapi_decode.c>
    int ff_vaapi_decode_init(AVCodecContext *avctx)
    {
        VAAPIDecodeContext *ctx = avctx->internal->hwaccel_priv_data;
        VAStatus vas;
        int err;

        ctx->va_config  = VA_INVALID_ID;
        ctx->va_context = VA_INVALID_ID;

    #if FF_API_STRUCT_VAAPI_CONTEXT
        if (avctx->hwaccel_context) {
            av_log(avctx, AV_LOG_WARNING, "Using deprecated struct "
                   "vaapi_context in decode.\n");

            ctx->have_old_context = 1;
            ctx->old_context = avctx->hwaccel_context;

            // Really we only want the VAAPI device context, but this
            // allocates a whole generic device context because we don't
            // have any other way to determine how big it should be.
            ctx->device_ref =
                av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_VAAPI);
            if (!ctx->device_ref) {
                err = AVERROR(ENOMEM);
                goto fail;
            }
            ctx->device = (AVHWDeviceContext*)ctx->device_ref->data;
            ctx->hwctx  = ctx->device->hwctx;
            ctx->hwctx->display = ctx->old_context->display;
            // The old VAAPI decode setup assumed this quirk was always
            // present, so set it here to avoid the behaviour changing.
            ctx->hwctx->driver_quirks =
                AV_VAAPI_DRIVER_QUIRK_RENDER_PARAM_BUFFERS;
        }
    #endif

    #if FF_API_STRUCT_VAAPI_CONTEXT
        if (ctx->have_old_context) {
            ctx->va_config  = ctx->old_context->config_id;
            ctx->va_context = ctx->old_context->context_id;
            av_log(avctx, AV_LOG_DEBUG, "Using user-supplied decoder "
                   "context: %#x/%#x.\n", ctx->va_config, ctx->va_context);
        } 
    #endif

旧版ffmpeg具体实现可见:[https://gitpub.sietium.com/tools/toolkits/ffvademo/-/tree/bridge]。差异化对比后就可见新版本的ffmpeg已经帮我们做了很多背后的事情。

进入正式的解码循环并解完一帧后,关键点就是我们就可以从AVFrame的data[3]中得到vasurfaceID。这点我们可以从ffmpeg源码中得到验证,ffmpeg内部map这个VAImage时也是同样的操作。

以下代码位置:https://github.com/FFmpeg/FFmpeg/blob/release/4.4/libavutil/hwcontext_vaapi.c
在这里插入图片描述

得到VASurfaceID后,就可以使用VAPutSurface函数将指定的视频帧请求渲染到之前申请的x11窗口句柄上了。

EGL 方式渲染

如果我们想使用opengl进行渲染,稍微麻烦一点。需要借助EGL帮我们得到共享的纹理数据。

如上面流程图中的橙色线框 ⑥ ⑦处,首先我们需要在解码循环前初始化EGL环境和opengl环境。 这块是标准的初始化过程,也不会和VADisPlay、VASurfaceID建立任何的关系。唯一有点关系的是在初始化egl时因为我们要显示到一个可见的窗口中,所以使用eglCreateWindowSurface函数时需要一个X11 window(在流程图的①处我们已经通过XCreateWindow创建了一个),并将其指针作为egl的渲染目标窗口。

重点来到⑧ ⑨ ⑩ ⑪ 。不变的是我们依旧从AVFrame的data[3]中得到VASurfaceID,变化的是我们需要利用libva提供的vaExportSurfaceHandle函数,结合VASurfaceID 导出当前VASurfaceID所对应的VADRMPRIMESurfaceDescriptor结构体。有了这个结构体后我们可以从里面获取到解码数据的比如平面信息(NV12?YUV420P?)和DRM Prime的文件描述符、宽高、像素格式等。有了这些数据我们就可以用其填充eglCreateImageKHR所需要的attributes,要注意attributes 必须与 VADRMPRIMESurfaceDescriptor 提供的信息匹配。创建出EGLImage之后,我们就可以用glEGLImageTargetTexture2DOES将其绑定到我们的opengl(es)纹理之上。之后就可以进行正常的或其他的opengl渲染动作。

至此,vaapi解码出的视频数据已经被0-copy的方式所渲染出来,核心动作全部在GPU上进行,CPU的占用率只占5%以下。

在这里插入图片描述
关注公众号 QTShared 获取源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/430805.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:外描边设置)

设置组件外描边样式。 说明&#xff1a; 从API Version 11开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 outline outline(value: OutlineOptions) 统一外描边样式设置接口。 卡片能力&#xff1a; 从API version 11开始&#xff0c;该…

【题解】—— LeetCode一周小结9

【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结8 26.二叉搜索树的范围和 题目链接&#xff1a;938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输…

Stable Diffusion——Animate Diff一键AI图像转视频

前言 AnimateDiff 是一个实用框架&#xff0c;可以对文本生成图像模型进行动画处理&#xff0c;无需进行特定模型调整&#xff0c;即可为大多数现有的个性化文本转图像模型提供动画化能力。而Animatediff 已更新至 2.0 版本和3.0两个版本&#xff0c;相较于 1.0 版本&#xff…

MySQL 多表查询 连接查询 自连接

介绍 自连接查询&#xff0c;可以是内连接查询&#xff0c;也可以是外连接查询&#xff0c;一句话自己连接自己&#xff0c;一个表当作两个表进行连接。 语法 SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件两个表A说明是同一张表&#xff0c;但是别名不同 案例…

【ue5】滑铲系统蓝图笔记

大致逻辑如下&#xff1a; 一、导入动画 滑铲蹲待机蹲行走 导入到文件夹中 可以右键设置颜色&#xff0c;便于区分。 二、调整动画 1.启动根运动 启动根运动后&#xff0c;人物才可以位移&#xff0c;不然只能在原地。 打开动画序列&#xff0c;勾选启用根运动Enabled…

好书推荐丨细说PyTorch深度学习:理论、算法、模型与编程实现

文章目录 写在前面深度学习推荐图书内容简介作者简介 推荐理由粉丝福利写在最后 写在前面 本期博主给大家推荐一本深度学习的全新正版书籍&#xff0c;感兴趣的小伙伴快来看看吧~ 深度学习 深度学习是机器学习的一个分支&#xff0c;它模仿人脑神经网络的工作原理进行复杂的…

网络协议栈--应用层--HTTPS协议

目录 一、HTTPS协议原理1.1 HTTPS协议是什么&#xff1f;1.2 概念准备1.2.1 什么是“加密”&#xff1f;1.2.2 为什么要加密&#xff1f;1.2.3 常见的加密方式1.2.3.1 对称加密1.2.3.2 非对称加密 1.2.4 数据摘要&&数据指纹1.2.5 数字签名1.2.6 理解链-承上启下 1.3 HT…

掘根宝典之C语言字符串输入函数(gets(),fgets(),get_s())

字符串输入前的注意事项 如果想把一个字符串读入程序&#xff0c;首先必须预留该字符串的空间&#xff0c;然后用输入函数获取该字符串 这意味着必须要为字符串分配足够的空间。 不要指望计算机在读取字符串时顺便计算它的长度&#xff0c;然后再分配空间(计算机不会这样做&a…

软件实例,佳易王账单账本记账汇总统计管理系统软件教程

软件实例&#xff0c;佳易王账单账本记账汇总统计管理系统软件教程 一、前言 以下软件程序教程 以 佳易王账单记账汇总统计管理系统软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 账单可以记录 1、收入明细 2、支出明细 3、客户…

信息安全是什么

信息安全&#xff0c;也称为信息安全或数据安全&#xff0c;是防止未经授权的访问、更改、中断和破坏信息。 信息安全本身包括的范围很大&#xff0c;大到国家军事政治等机密安全&#xff0c;小范围的当然还包括如防范商业企业机密泄露&#xff0c;防范青少年对不良信息的浏览…

Excel中筛选合并单元格后,只显示第一行怎么办?

Excel中筛选合并单元格后,只显示第一行怎么办? 参考链接:https://baijiahao.baidu.com/s?id=1736773058549439034&wfr=spider&for=pc 我们日常的Excel数据在展示的时候为了数据的清晰和美观往往部分相同的单元格进行合并,但是合并之后在筛选时会发现结果会显示异…

Vanna-ai -基于RAG的TextToSql实现方案

官方连接&#xff1a;Vanna.AI - Personalized AI SQL Agent 1.背景 基于大模型的TextToSql的关键为给大模型提供正确有效的数据库信息及问题&#xff0c;以提升大模型生成sql的正确率。database_info question形成prompt&#xff0c;但是实际中通常会遇到一个问题&#xff…

【前端】Vite打包页面简单部署到GitHub上

创建仓库---->上传代码---->设置 注意点已经打上箭头,代码我传到的是test分支 vite打包的配置如图&#xff0c;base是仓库名称&#xff0c;docs是build后生成的打包目录。 上传到GitHub就自动部署了 访问就是第一张图里的一串地址&#xff0c;这种方式比较方便吧

Claude 3 模型列表

claude-3-opus-20240229 这个模型就好

Midjourney入门:AI绘画真的能替代人类的丹青妙笔吗?

名人说&#xff1a;一花独放不是春&#xff0c;百花齐放花满园。——《增广贤文》 作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、简要介绍1、Midjourney2、使用方法 二、绘画1、动物类2、风景类3、动漫类4、艺…

CNAN知识图谱辅助推荐系统

CNAN知识图谱辅助推荐系统 文章介绍了一个基于KG的推荐系统模型&#xff0c;代码也已开源&#xff0c;可以看出主要follow了KGNN-LS 。算法流程大致如下&#xff1a; 1. 算法介绍 算法除去attention机制外&#xff0c;主要的思想在于&#xff1a;user由交互过的item来表示、i…

VMvare安装17安装centos8教程

阿里镜像站&#xff1a;https://mirrors.aliyun.com/centos centos-8-isos-x86_64安装包下载_开源镜像站-阿里云 https://mirrors.aliyun.com/centos/8/isos/x86_64/CentOS-8.5.2111-x86_64-dvd1.iso 将上面的链接复制到迅雷进行高速下载 vmvare安装配置教程安装教程 CentO…

Vue3 五天速成

文章目录 day 11. 创建vue3工程3. 响应式数据4. 计算属性 day 25. watch 监视6. watchEffect7. 标签的ref属性 day 38. 回顾TS中的接口_泛型_自定义类型9. props的使用10. 生命周期11. 自定义Hooks12. 路由 基本切换效果13. 路由 两个注意点14. 路由 路由器的工作模式15. 路由 …

【C++】类和对象终篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 友元2.1 友元函数2.2 友元类 3. 内部类4. 匿名对象5. 拷贝对象时的一些编译器优化6. 再次理解类和对象 1. 前言 在上一篇博客中提到了类和对象中的构造函数与stat…