Harmony OS 如何实现 C++ NATIVE YUV420(其他数据格式如BGRA等)自渲染

在HarmonyOS下自渲染视频数据

在本文中,我们将介绍如何在HarmonyOS下自渲染视频数据。我们将实现包括创建本地窗口、设置缓冲区选项、请求缓冲区、处理视频帧数据以及刷新缓冲区等步骤。

环境准备

在开始之前,请确保您已经安装了HarmonyOS的开发环境,并且能够编译和运行C++代码。

注意⚠️

以下方案中用到的API,都不是线程安全的!!!我这里是通过其他手段有保护的,请自行上锁~

创建本地窗口

首先,我们需要根据surfaceId创建一个本地窗口。如何拿这个surfaceId,有两种方案:

  • 一种是原生ArkTs UI开发从XComponent拿;
  • 一种是跨平台框架比如 flutter通过TextureRegistry注册一个Texture拿,

NativeWindowRender 类的构造函数中实现了这一功能:

NativeWindowRender::NativeWindowRender(const RenderConfig &config) : render_config_(config) {
    int ret = OH_NativeWindow_CreateNativeWindowFromSurfaceId(config.surfaceId, &window_);
    if (window_ == nullptr || ret != 0) {
        LOG_ERROR("create native window failed: {}", ret);
        return;
    }
}

在这里,我们使用

OH_NativeWindow_CreateNativeWindowFromSurfaceId

函数从surfaceId创建一个本地窗口。如果创建失败,会记录错误日志。

设置缓冲区选项

创建本地窗口后,我们需要设置一些缓冲区选项,例如缓冲区使用情况、交换间隔、请求超时、颜色范围和变换等可以参考 NativeWindowOperation,我暂时只设置了这些,看看渲染效果和性能

bool NativeWindowRender::UpdateNativeBufferOptionsByVideoFrame(const VideoFrame *frame) {
#if 0
    // get current buffer geometry, if different, reset buffer geometry
    int32_t stride = 0;
    int32_t height = 0;
    // the fucking order of get is h, w, however, the fucking order of set is w, h
    int ret = OH_NativeWindow_NativeWindowHandleOpt(window_, GET_BUFFER_GEOMETRY, &height, &stride);
    if (ret != 0) {
        LOG_ERROR("get buffer geometry failed: {}", ret);
        return false;
    }

    // set buffer geometry if different
    if (stride != frame->yStride || height != frame->height) {
        ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_BUFFER_GEOMETRY, frame->yStride, frame->height);
        if (ret != 0) {
            LOG_ERROR("set buffer geometry failed: {}", ret);
            return false;
        }
    }
#else
    int ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_BUFFER_GEOMETRY, frame->yStride, frame->height);
    if (ret != 0) {
        LOG_ERROR("set buffer geometry failed: {}", ret);
        return false;
    }
#endif

    // set buffer format
    ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_FORMAT, NATIVEBUFFER_PIXEL_FMT_YCBCR_420_P);
    if (ret != 0) {
        LOG_ERROR("set buffer format failed: {}", ret);
        return false;
    }

    // set buffer stride
    ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_STRIDE, 4);
    if (ret != 0) {
        LOG_ERROR("set buffer stride failed: {}", ret);
        return false;
    }

    // set native source type to OH_SURFACE_SOURCE_VIDEO
    ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_SOURCE_TYPE, OH_SURFACE_SOURCE_VIDEO);
    if (ret != 0) {
        LOG_ERROR("set source type failed: {}", ret);
        return false;
    }

    // set app framework type
    ret = OH_NativeWindow_NativeWindowHandleOpt(window_, SET_APP_FRAMEWORK_TYPE, "unknown");
    if (ret != 0) {
        LOG_ERROR("set app framework type failed: {}", ret);
        return false;
    }

    return true;
}

请求缓冲区并处理视频帧数据

在接收到视频帧数据时,我们需要请求缓冲区并将视频帧数据写入缓冲区, 坑点来了,UpdateNativeBufferOptionsByVideoFrame 你必须每次request前都要调用。。。。我反正没找到文档哪里有写,又或者我理解能力有问题,坑了一两个小时,最后lldb debug,发现如果只在构造函数中设置的话,只有第一次request出来的BufferHandle中的width height stride format等参数是符合预期的,后面就不对了,结果就只能画出来第一张图,后面没有任何报错但是就是不出图,所以切记切记, 每次都要调用 (吗?请帮忙指正)

void NativeWindowRender::OnVideoFrameReceived(const void *videoFrame, const VideoFrameConfig &config, bool resize) {
    const VideoFrame *frame = reinterpret_cast<const VideoFrame *>(videoFrame);

    // must call this every time, coz every time you request a buffer, the properties of BufferHandle may change to
    // default value.
    if (!UpdateNativeBufferOptionsByVideoFrame(frame)) {
        return;
    }

    // request buffer from native window
    OHNativeWindowBuffer *buffer = nullptr;
    int fence_fd = -1;
    int ret = OH_NativeWindow_NativeWindowRequestBuffer(window_, &buffer, &fence_fd);
    if (ret != 0 || buffer == nullptr) {
        LOG_ERROR("request buffer failed: {}", ret);
        return;
    }

    // get buffer handle from native buffer
    BufferHandle *handle = OH_NativeWindow_GetBufferHandleFromNative(buffer);

    // mmap buffer handle to write data
    void *data = mmap(handle->virAddr, handle->size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0);
    if (data == MAP_FAILED) {
        LOG_ERROR("mmap buffer failed");
        return;
    }

    // wait for fence fd to be signaled
    uint32_t timeout = 3000;
    if (fence_fd != -1) {
        struct pollfd fds = {.fd = fence_fd, .events = POLLIN};
        do {
            ret = poll(&fds, 1, timeout);
        } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
        close(fence_fd);
    }

    // copy yuv420 data to buffer
    uint8_t *y = (uint8_t *)data;
    uint8_t *u = y + frame->yStride * frame->height;
    uint8_t *v = u + frame->uStride * frame->height / 2;
    memcpy(y, frame->yBuffer, frame->yStride * frame->height);
    memcpy(u, frame->uBuffer, frame->uStride * frame->height / 2);
    memcpy(v, frame->vBuffer, frame->vStride * frame->height / 2);

    // flush buffer
    Region region{.rects = nullptr, .rectNumber = 0};
    int acquire_fence_fd = -1;
    ret = OH_NativeWindow_NativeWindowFlushBuffer(window_, buffer, acquire_fence_fd, region);
    if (ret != 0) {
        LOG_ERROR("flush buffer failed: {}", ret);
    }

    // unmap buffer handle
    ret = munmap(data, handle->size);
    if (ret != 0) {
        LOG_ERROR("munmap buffer failed: {}", ret);
    }
}

在这里,我们首先调用 UpdateNativeBufferOptionsByVideoFrame 函数更新缓冲区选项,然后请求缓冲区并将视频帧数据写入缓冲区。最后,我们刷新缓冲区并解除映射。

结论

通过以上步骤,我们可以在HarmonyOS下实现自渲染视频数据。希望本文对您有所帮助。如果您有任何问题或建议,请随时与我联系。

PS

另外一种方案,是拿到NativeWindow后,用opengl自己画,但是opengl这个鬼你也知道的,一个context要一个线程,我要是十几路,几十路视频渲染,就得几十个线程?这在移动平台谁能忍?共享context的话,我自信我这种new opengler不能保证不出错。。。可以参考官方文档 自定义渲染 (XComponent) 。所以,这种方案交给鸿蒙UI框架说着说系统自己画的,叫懒人方案?

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

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

相关文章

HCIP-HarmonyOS Application Developer V1.0 笔记(五)

弹窗功能 prompt模块来调用系统弹窗API进行弹窗制作。 当前支持3种弹窗API&#xff0c;分别为&#xff1a; 文本弹窗&#xff0c;prompt.showToast&#xff1b;对话框&#xff0c;prompt.showDialog&#xff1b;操作菜单&#xff0c;prompt.showActionMenu。 要使用弹窗功能&…

Linux相关概念和易错知识点(20)(dentry、分区、挂载)

目录 1.dentry &#xff08;1&#xff09;路径缓存的原因 &#xff08;2&#xff09;dentry的结构 ①多叉树结构 ②file和dentry之间的联系 ③路径概念存在的意义 2.分区 &#xff08;1&#xff09;为什么要确认分区 &#xff08;2&#xff09;挂载 ①进入分区 ②被挂…

Redis 缓存击穿

目录 缓存击穿 什么是缓存击穿&#xff1f; 有哪些解决办法&#xff1f; 缓存穿透和缓存击穿有什么区别&#xff1f; 缓存雪崩 什么是缓存雪崩&#xff1f; 有哪些解决办法&#xff1f; 缓存预热如何实现&#xff1f; 缓存雪崩和缓存击穿有什么区别&#xff1f; 如何保…

电信网关配置管理系统 upload_channels.php 文件上传致RCE漏洞复现

0x01 产品简介 中国电信集团有限公司(英文名称“China Telecom”、简称“中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远…

澳鹏通过高质量数据支持 Onfido 优化AI反欺诈功能

“Appen 在 Onfido 的发展中发挥了至关重要的作用&#xff0c;并已成为我们运营的重要组成部分。我们很高兴在 Appen 找到了可靠的合作伙伴。” – Onfido 数据和分析总监 Francois Jehl 简介&#xff1a;利用人工智能和机器学习增强欺诈检测 在当今日益数字化的世界&#xff…

网站架构知识之Ansible模块(day021)

1.Ansible模块 作用:通过ansible模块实现批量管理 2.command模块与shell模块 command模块是ansible默认的模块&#xff0c;适用于执行简单的命令&#xff0c;不支持特殊符号 案列01&#xff0c;批量获取主机名 ansible all -m command -a hostname all表示对主机清单所有组…

应对AI与机器学习的安全与授权管理新挑战,CodeMeter不断创新引领保护方案

人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;技术正在快速发展&#xff0c;逐渐应用到全球各类主流系统、设备及关键应用场景中&#xff0c;尤其是在政府、商业和工业组织不断加深互联的情况下&#xff0c;AI和ML技术的影响日益广泛。虽然AI技术的…

实现uniapp-微信小程序 搜索框+上拉加载+下拉刷新

pages.json 中的配置 { "path": "pages/message", "style": { "navigationBarTitleText": "消息", "enablePullDownRefresh": true, "onReachBottomDistance": 50 } }, <template><view class…

布谷直播源码部署服务器关于数据库配置的详细说明

布谷直播源码搭建部署配置接口数据库 /public/db.php&#xff08;2019年8月后的系统在该路径下配置数据库&#xff0c;老版本继续走下面的操作&#xff09; 在项目代码中执行命令安装依赖库&#xff08;⚠️注意&#xff1a;如果已经有了vendor内的依赖文件的就不用执行了&am…

【C++】STL— stack的常见用法和模拟实现

目录 1、stack的介绍 2、stack的使用 构造一个空栈 stack的简单接口应用 3、stack的模拟实现 4、栈的相关题目 4.1 最小栈 4.1.2思路 4.1.3 实现代码 4.2 栈的压入、弹出序列 4.2.2 思路 4.2.3程序实现 1、stack的介绍 在C中&#xff0c;stack是一种标准模板库&am…

vue大疆建图航拍功能实现

介绍 无人机在规划一块区域的时候&#xff0c;我们需要手动的给予一些参数来影响无人机飞行&#xff0c;对于一块地表&#xff0c;无人机每隔N秒在空中间隔的拍照地表的一块区域&#xff0c;在整个任务执行结束后&#xff0c;拍到的所有区域照片能够完整的表达出一块地表&…

[ DOS 命令基础 2 ] DOS 命令详解-网络相关命令

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

es自动补全(仅供自己参考)

elasticssearch提供了CompletionSuggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询效率&#xff0c;对于文档中字段的类型有一些约束&#xff1a; 查询类型必须是&#xff1a;completion 字段内容是多个补全词条形成的数组 P…

react jsx基本语法,脚手架,父子传参,refs等详解

1&#xff0c;简介 1.1 概念 react是一个渲染html界面的一个js库&#xff0c;类似于vue&#xff0c;但是更加灵活&#xff0c;写法也比较像原生js&#xff0c;之前我们写出一个完成的是分为html&#xff0c;js&#xff0c;css&#xff0c;现在我们使用react库我们把html和js结…

Chrome浏览器如何导出所有书签并导入书签

前言 我平常在开发中&#xff0c;基本是用的谷歌的浏览器&#xff0c;也就是Chrome&#xff0c;因为这个对于开发来说&#xff0c;比较友好。在开发中&#xff0c;包括调试接口&#xff0c;打断点&#xff0c;查看打印日志等&#xff0c;都是非常不错的。另一方面&#xff0c;…

[WSL][桌面][X11]WSL2 Ubuntu22.04 安装Ubuntu桌面并且实现GUI转发(Gnome)

1. WSL安装 这里不再赘述&#xff0c;WSL2支持systemd&#xff0c;如果你发现其没有systemd相关指令&#xff0c;那么你应该看看下面这个 https://blog.csdn.net/noneNull0/article/details/135950369 但是&#xff0c;Ubuntu2204用不了这个脚本&#xff0c;比较蛋疼。 – …

C语言中的 printf( ) 与 scanf( )

时隔多日&#xff0c;小编我又回来咯小编相信之前的博客能够给大家带来不少的收获。在我们之前的文章中&#xff0c;许多代码块的例子都用到了printf( ) 与 scanf( )这两个函数&#xff0c;大家都知道他们需要声明头文件之后才能使用&#xff0c;那这两个函数是什么呢&#xff…

【Homework】【1--4】Learning resources for DQ Robotics in MATLAB

Learning resources for DQ Robotics in MATLAB Lesson 1 代码 % Step 2: Define the real numbers a1 and a2 a1 123; a2 321;% Step 3: Calculate and display a3 a1 a2 a3 a1 a2; disp([a3 (a1 a2) , num2str(a3)])% Step 4: Calculate and display a3 a1 * a2 a3…

前端刺客系列----Vue 3 入门介绍

目录 一.什么是 Vue 3&#xff1f; 二.Vue 3 的主要特性 三,Vue3项目实战 四.总结 在前端开发的世界里&#xff0c;Vue.js 作为一款渐进式的 JavaScript 框架&#xff0c;已成为许多开发者的首选工具。自从 Vue 3 发布以来&#xff0c;它带来了许多重要的改进和新特性&…

Linux入门:环境变量与进程地址空间

一. 环境变量 1. 概念 1️⃣基本概念&#xff1a; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#x…