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

news2024/11/13 9:25:08

在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框架说着说系统自己画的,叫懒人方案?

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从0开始搭建一个生产级SpringBoot2.0.X项目(十)SpringBoot 集成RabbitMQ

前言 最近有个想法想整理一个内容比较完整springboot项目初始化Demo。 SpringBoot集成RabbitMQ RabbitMQ中的一些角色&#xff1a; publisher&#xff1a;生产者 consumer&#xff1a;消费者 exchange个&#xff1a;交换机&#xff0c;负责消息路由 queue&#xff1a;队列…

比流计算资源效率最高提升 1000 倍,“增量计算”新模式能否颠覆数据分析?

作者 | 关涛 云器科技CTO 数据平台领域发展 20 年&#xff0c;逐渐成为每个企业的基础设施。作为一个进入“普惠期”的领域&#xff0c;当下的架构已经完美了吗&#xff0c;主要问题和挑战是什么&#xff1f;在 2023 年 AI 跃变式爆发的大背景下&#xff0c;数据平台又该如何演…

MySQL性能测试方案设计

在现代互联网系统中&#xff0c;数据库性能直接影响到整体应用的速度和用户体验。而MySQL作为广泛使用的关系型数据库&#xff0c;随着数据量和并发请求的增长&#xff0c;其性能问题也日益突出。今天我们将深入探讨如何设计一套高效的MySQL性能测试方案&#xff0c;帮助你精准…

彻底解决单片机BootLoader升级程序失败问题

文章目录 1、引言2、MicroBoot&#xff1a;优雅的解决升级问题问题1&#xff1a;bootloader 在跳转到app前没有清理干净存在的痕迹问题2&#xff1a; 需要 APP 传递信息给 Bootloader问题3&#xff1a; APP单独运行没有问题&#xff0c;通过Bootloader跳转到APP运行莫名死机问题…

Oracle OCP认证考试考点详解082系列17

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 81. 第81题&#xff1a; 题目 81.Examine these SOL statements which execute successfully Which two statements are true after exec…

【EFK】Linux集群部署Elasticsearch最新版本8.x

【EFK】Linux集群部署Elasticsearch最新版本8.x 摘要环境准备环境信息系统初始化启动先决条件 下载&安装修改elasticsearch.yml控制台启动Linux服务启动访问验证查看集群信息查看es健康状态查看集群节点查询集群状态 生成service token验证service tokenIK分词器下载 摘要 …

基于python的天气数据采集与可视化分析,对20个城市的天气适宜出行度分析

摘要 本项目旨在基于Python对20个城市的天气数据进行采集与可视化分析&#xff0c;以评估天气的适宜出行度。该分析通过四个主要指标进行量化&#xff0c;这些指标分别是天气状况良好率、空气质量优良率、气温适宜率和安全天气率。通过这些指标&#xff0c;我们能够有效地判断…

外贸管理利器7选,助力高效办公

推荐7款外贸管理软件&#xff0c;包括ZohoBooks、ZohoCRM、富通天下等&#xff0c;各具特色&#xff0c;满足外贸企业不同需求&#xff0c;提高管理效率&#xff0c;助力企业全球化竞争。、 一、Zoho Books Zoho Books是一款外贸财务管理软件&#xff0c;不仅为用户提供了一个…

【JWT】Asp.Net Core中JWT刷新Token解决方案

Asp.Net Core中JWT刷新Token解决方案 前言方案一:当我们操作某个需要token作为请求头的接口时,返回的数据错误error.response.status === 401,说明我们的token已经过期了。方案二:实现用户无感知的刷新token值,我们希望当响应返回的数据是401身份过期时,响应阻拦器自动帮我…

当AI遇上时尚:未来的衣橱会由机器人来打理吗?

内容概要 在当今这个快速发展的时代&#xff0c;人工智能与时尚的结合正在逐渐改写我们对衣橱管理的认知。传统的衣橱管理常常面临着空间不足、穿搭单调及库存过多等挑战&#xff0c;许多人在挑选服饰时难以做出决策。然而&#xff0c;随着技术的进步&#xff0c;智能推荐和自…

编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

往期内容 本专栏往期内容&#xff1a; Pinctrl子系统和其主要结构体引入Pinctrl子系统pinctrl_desc结构体进一步介绍Pinctrl子系统中client端设备树相关数据结构介绍和解析inctrl子系统中Pincontroller构造过程驱动分析&#xff1a;imx_pinctrl_soc_info结构体Pinctrl子系统中c…

【MySQL】数据库整合攻略 :表操作技巧与详解

前言&#xff1a;本节内容讲述表的操作&#xff0c; 对表结构的操作。 是对表结构中的字段的增删查改以及表本身的创建以及删除。 ps&#xff1a;本节内容本节内容适合安装了MySQL的友友们进行观看&#xff0c; 实操更有利于记住哦。 目录 创建表 查看表结构 修改表结构 …

CocoaPods安装步骤详解 - 2024

引言 CocoaPods的安装&#xff0c;如果有VPN就一直开启&#xff0c;会让整个流程非常顺畅。 在现代 iOS 开发中&#xff0c;依赖管理变得越来越重要&#xff0c;CocoaPods 成为开发者们首选的依赖管理工具。它不仅可以简化库的安装与更新&#xff0c;还能帮助开发者更高效地管…

二叉树-堆

树的几个重要定义 1.树根子树根亲缘关系 2.节点的度:有几个子树或根有几个孩子 3.叶子节点:没有孩子的终端节点 度为0 4.分支节点:度不为0的节点 5.树叶子分支节点 6.父亲节点/双亲节点 7.子节点 8.树的度:最大节点的度就是树的度 9.树的层:一般从第一层开始数,也有从0层开始数…

内置RTK北斗高精度定位的4G执法记录仪、国网供电服务器记录仪

内置RTK北斗高精度定位的4G执法记录仪、国网供电服务器记录仪BD311R 发布时间: 2024-10-23 11:28:42 一、 产品图片&#xff1a; 二、 产品特性&#xff1a; 4G性能&#xff1a;支持2K超高清图传&#xff0c;数据传输不掉帧&#xff0c;更稳定。 独立北…

浮动路由:实现出口线路的负载均衡冗余备份。

浮动路由 Tip&#xff1a;浮动路由指在多条默认路由基础上加入优先级参数&#xff0c;实现出口线路冗余备份。 ip routing-table //查看路由表命令 路由优先级参数&#xff1a;越小越优 本次实验测试两条默认路由&#xff0c;其中一条默认路由添加优先级参数&#xff0c;设置…

ssm077铁岭河医院医患管理系统+vue(论文+源码)_kaic

毕业设计&#xff08;论文) 题 目&#xff1a; 医院医患管理系统 姓 名&#xff1a; 学 号&#xff1a; 所属学院&#xff1a; 专业班级&#xff1a; 指导&#xff1a; 职 称&#xff1a; 完成日期 2021年 月 摘 要 21世纪的今天&#xf…

关于在VS中使用Qt不同版本报错的问题

最开始需要配置的地方 首先看一下我的Qt有关的环境变量&#xff1a; Path环境变量里&#xff1a; 这里就是把对应Qt编译器环境下的bin目录放进来&#xff1a;比如你使用的是msvc2017_64或者MinGW QMAKESPEC环境变量&#xff1a; 这个就选择Qt对应的编译器目录下的\mkspecs\w…

Redis 权限控制(ACL)|ACL 命令详解、ACL 持久化

官网文档地址&#xff1a;https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/ 使用版本&#xff1a;Redis7.4.1 什么是 ACL&#xff1f; ACL&#xff08;Access Control List&#xff09;&#xff0c;权限控制列表&#xff0c;是 Redis 提供的一种…

任务中心全新升级,新增分享接口文档功能,MeterSphere开源持续测试工具v3.4版本发布

2024年11月5日&#xff0c;MeterSphere开源持续测试工具正式发布v3.4版本。 在这一版本中&#xff0c;系统设置方面&#xff0c;任务中心支持实时查看系统即时任务与系统后台任务&#xff1b;接口测试方面&#xff0c;新增接口文档分享功能、接口场景导入导出功能&#xff0c;…