鸿蒙 next 实现摄像头视频预览编码(一)
鸿蒙 next 即将发布,让我们先喊3遍 遥遥领先~ 遥遥领先~ 遥遥领先~
作为一门新的系统,本人也是刚入门学习中,如果对于一些理解有问题的,欢迎即使指出哈
首先这里要讲一下,在鸿蒙 next 中,要实现摄像头预览&编码有两种方式。第一种,通过摄像头的预览流&录制流来实现,其中预览很简单,直接使用 xcomponent 即可,对于编码,则可以通过创建编码器获取到的 surfaceid 传递给录制流即可。第二种是通过 nativeimage 类似于 android 的 surfacetexture 然后将纹理通过 opengl 绘制到预览 surface 和编码 surface 上去,这边文章主要将第一种简单的方式,步骤大致如下:
第一步,创建 xcomponaent,代码如下:
XComponent({id: '',type: XComponentType.SURFACE,libraryname: '',controller: this.XcomponentController}).onLoad(() => {this.XcomponentController.setXComponentSurfaceSize({surfaceWidth: this.cameraWidth, surfaceHeight: this.cameraHeight})this.XcomponentSurfaceId = this.XcomponentController.getXComponentSurfaceId()})
创建 xcomponeant 的关键是获取 surfaceid,这个后面会用来传给摄像头预览流用的。
第二步,获取编码器的 surfaceid,由于目前鸿蒙没有为编码器这块提供 arkts 接口,所以需要用到 napi 作为中间桥接,通过 arkts 来调用 c++ 代码,大致代码如下:
arkts 部分:
import recorder from 'librecorder.so'
recorder.initNative()
librecorder.so 为工程中 c++ 的部分,具体可以参考项目模板中关于 c++ 的示例
napi 部分:
#include "RecorderNative.h"
#include <bits/alltypes.h>#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xFF00
#define LOG_TAG "recorder"struct AsyncCallbackInfo {napi_env env;napi_async_work asyncWork;napi_deferred deferred;int32_t resultCode = 0;std::string surfaceId = "";SampleInfo sampleInfo;
};void DealCallBack(napi_env env, void *data)
{AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);napi_value code;napi_create_int32(env, asyncCallbackInfo->resultCode, &code);napi_value surfaceId;napi_create_string_utf8(env, asyncCallbackInfo->surfaceId.data(), NAPI_AUTO_LENGTH, &surfaceId);napi_value obj;napi_create_object(env, &obj);napi_set_named_property(env, obj, "code", code);napi_set_named_property(env, obj, "surfaceId", surfaceId);napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);napi_delete_async_work(env, asyncCallbackInfo->asyncWork);delete asyncCallbackInfo;
}void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code)
{asyncCallbackInfo->resultCode = code;
}void SurfaceIdCallBack(AsyncCallbackInfo *asyncCallbackInfo, std::string surfaceId)
{asyncCallbackInfo->surfaceId = surfaceId;
}void NativeInit(napi_env env, void *data)
{AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);int32_t ret = Recorder::GetInstance().Init(asyncCallbackInfo->sampleInfo);if (ret != AVCODEC_SAMPLE_ERR_OK) {SetCallBackResult(asyncCallbackInfo, -1);}uint64_t id = 0;ret = OH_NativeWindow_GetSurfaceId(asyncCallbackInfo->sampleInfo.window, &id);if (ret != AVCODEC_SAMPLE_ERR_OK) {SetCallBackResult(asyncCallbackInfo, -1);}asyncCallbackInfo->surfaceId = std::to_string(id);SurfaceIdCallBack(asyncCallbackInfo, asyncCallbackInfo->surfaceId);
}napi_value RecorderNative::Init(napi_env env, napi_callback_info info)
{SampleInfo sampleInfo;napi_value promise;napi_deferred deferred;napi_create_promise(env, &deferred, &promise);AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();asyncCallbackInfo->env = env;asyncCallbackInfo->asyncWork = nullptr;asyncCallbackInfo->deferred = deferred;asyncCallbackInfo->resultCode = -1;asyncCallbackInfo->sampleInfo = sampleInfo;napi_value resourceName;napi_create_string_latin1(env, "recorder", NAPI_AUTO_LENGTH, &resourceName);napi_create_async_work(env, nullptr, resourceName, [](napi_env env, void *data) { NativeInit(env, data); },[](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,&asyncCallbackInfo->asyncWork);napi_queue_async_work(env, asyncCallbackInfo->asyncWork);return promise;
}EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{napi_property_descriptor classProp[] = {{"initNative", nullptr, RecorderNative::Init, nullptr, nullptr, nullptr, napi_default, nullptr}};return exports;
}
EXTERN_C_ENDstatic napi_module RecorderModule = {.nm_version = 1,.nm_flags = 0,.nm_filename = nullptr,.nm_register_func = Init,.nm_modname = "recorder",.nm_priv = ((void *)0),.reserved = {0},
};extern "C" __attribute__((constructor)) void RegisterRecorderModule(void) { napi_module_register(&RecorderModule); }
鸿蒙这边的 napi 其实是参考的 nodejs 的,语法基本一致,这里的大致逻辑就是调用 Recorder::GetInstance().Init() 获取编码器的 surfaceid 然后通过 ts 的 promise 传递给前端
c++ 编码器部分:
int32_t Recorder::Init(SampleInfo &sampleInfo)
{std::lock_guard<std::mutex> lock(mutex_);sampleInfo_ = sampleInfo;videoEncoder_ = std::make_unique<VideoEncoder>();muxer_ = std::make_unique<Muxer>();videoEncoder_->Create(sampleInfo_.videoCodecMime);ret = muxer_->Create(sampleInfo_.outputFd);encContext_ = new CodecUserData;videoEncoder_->Config(sampleInfo_, encContext_);muxer_->Config(sampleInfo_);sampleInfo.window = sampleInfo_.window;releaseThread_ = nullptr;return AVCODEC_SAMPLE_ERR_OK;
}
其中核心的在于 videoEncoder_->Config(),这一步会将 nativewindow 赋值给 sampleInfo 结构体,然后就可以获取到nativewindow 的 surfaceid了
代码如下:
int32_t VideoEncoder::Config(SampleInfo &sampleInfo, CodecUserData *codecUserData)
{Configure(sampleInfo);OH_VideoEncoder_GetSurface(encoder_, &sampleInfo.window);SetCallback(codecUserData);OH_VideoEncoder_Prepare(encoder_);return AVCODEC_SAMPLE_ERR_OK;
}
到此为止,xcomponents 的 surfaceid 和编码器的 surtfaceid 都获取到了,接着就是在 arkts 层创建摄像头,并设置预览&编码输出了,这块比较简单,照着文档来就行,代码如下:
let cameraManager = camera.getCameraManager(globalThis.context)
let camerasDevices: Array<camera.CameraDevice> = getCameraDevices(cameraManager)
let profiles: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(camerasDevices[0],camera.SceneMode.NORMAL_VIDEO)// 获取预览流profilelet previewProfiles: Array<camera.Profile> = profiles.previewProfiles// 获取录像流profilelet videoProfiles: Array<camera.VideoProfile> = profiles.videoProfiles// Xcomponent预览流let XComponentPreviewProfile: camera.Profile = previewProfiles[0]// 创建 编码器 输出对象encoderVideoOutput = cameraManager.createVideoOutput(videoProfile, encoderSurfaceId)// 创建 预览流 输出对象XcomponentPreviewOutput = cameraManager.createPreviewOutput(XComponentPreviewProfile, this.XcomponentSurfaceId)// 创建cameraInput对象cameraInput = cameraManager.createCameraInput(camerasDevices[0])// 打开相机await cameraInput.open()// 会话流程videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession// 开始配置会话videoSession.beginConfig()// 把CameraInput加入到会话videoSession.addInput(cameraInput)// 把 Xcomponent 预览流加入到会话videoSession.addOutput(XcomponentPreviewOutput)// 把编码器录像流加入到会话videoSession.addOutput(encoderVideoOutput)// 提交配置信息await videoSession.commitConfig()// 会话开始await videoSession.start()
至此,关于预览&编码的大致流程就是这样了,整体流程其实还是很简单的,核心就是获取两个 surfaceid,然后传入到摄像头录制&预览流中即可。这里就大致讲一下思路,相信做安卓或者前端的同学都能看明白。不过这种模式的一个缺点在于无法做一些深层次的操作,例如水印、美白、瘦脸等,优点在于代码量比较少。第二篇要将的是关于如何通过 opengl 来绘制预览 & 编码 surface,未完待续~
相关文章:
鸿蒙 next 实现摄像头视频预览编码(一)
鸿蒙 next 即将发布,让我们先喊3遍 遥遥领先~ 遥遥领先~ 遥遥领先~ 作为一门新的系统,本人也是刚入门学习中,如果对于一些理解有问题的,欢迎即使指出哈 首先这里要讲一下,在鸿蒙 next 中,要实现摄像头预览…...
YOLO-V3
一、概述 最大的改进就是网络结构,使其更适合小目标检测特征做的更细致,融入多持续特征图信息来预测不同规格物体先验框更丰富了,3种scale,每种3个规格,一共9种softmax改进,预测多标签任务 先验框…...
golang提案,内置 Go 错误检查函数
先来狠狠吐个槽 要吐槽 Go1 的 error ,那咱得先整明白大家为啥都猛喷它的错误处理做得不咋地。在 Go 语言里头,error 本质上其实就是个 Error 的接口: type error interface {Error() string }实际的应用场景如下: func main()…...
零售业务产品系统应用架构设计(三)
智慧物业依据《住房和城乡建设部等部门关于推动物业服务企业加快发展线上线下生活服务的意见建房〔2020〕99号》,推动物业管理公司广泛运用5G、互联网、物联网、云计算、大数据、区块链和人工智能等技术,建设智慧物业管理服务平台,对接城市信息模型(CIM)和城市运行管理服务…...
【GD32】从零开始学GD32单片机 | PMU电源管理单元+深度睡眠和待机例程(GD32F470ZGT6)
1. 简介 PMU电源管理单元通俗讲就是用来管理MCU的电源域的,它主要有两个功能——电压监测和低功耗管理。在GD32中一共有3个电源域——VDD/VDDA域、1.2V域和备份域。 VDD/VDDA域主要供PMU控制器、ADC、DAC等外设使用;1.2V域就是大部分外设都会使用的电源域…...
公司员工电脑桌面太乱如何解决?桌面管理软件一招解决!
“工欲善其事,必先利其器。” 在数字化管理的时代背景下,选择合适的桌面管理软件就如同为企业网络管理装上了一双慧眼。 员工的电脑桌面往往因为长时间的使用而变得杂乱无章,这不仅影响了工作效率,还可能给企业信息安全带来隐患。…...
leetcode:2119. 反转两次的数字(python3解法)
难度:简单 反转 一个整数意味着倒置它的所有位。 例如,反转 2021 得到 1202 。反转 12300 得到 321 ,不保留前导零 。 给你一个整数 num ,反转 num 得到 reversed1 ,接着反转 reversed1 得到 reversed2 。如果 reverse…...
5.vue中axios封装工程化
vue工程化中axios封装 视频演示地址:https://www.bilibili.com/video/BV121egeQEHg/?vd_source0f4eae2845bd3b24b877e4586ffda69a 通常我们封装需要封装request.js基础的发送请求工具类,再根据业务封装service类,service类是具体业务的接口…...
实验六:动态数码管实验
实验结果图,从右到左0-7,从左到右7-0,来回滚动。 硬件接线图: 具体看图,不说了,前面讲过,自己查资料就可以,资料得慢慢查,熟练就好了,不浪费时间和版面了 main.c代码 #include<reg52.h>typedef unsigned int u16; typedef unsigned char u8;#define SMG P0 …...
《Cloud Native Data Center Networking》(云原生数据中心网络设计)读书笔记 -- 05网络虚拟化
本章帮助网络工程师或架构师回答如下问题: 什么是网络虚拟化?网络虚拟化有哪些用途?网络虚拟化领域内有哪些不同的技术方向?网络虚拟化的控制面有哪些选择?当使用 VXLAN 时如何进行桥接和路由? 什么是网络虚拟化? 网络虚拟化可以让网络…...
奥威BI数据可视化展示:如何充分发挥数据价值
奥威BI数据可视化展示:如何充分发挥数据价值 在大数据时代,数据已成为企业最宝贵的资产之一。然而,仅仅拥有海量数据并不足以带来竞争优势,关键在于如何有效地挖掘、分析和展示这些数据,从而转化为有价值的洞察和决策…...
jenkins工具配置
上一篇(https://blog.csdn.net/abc666_666/article/details/141207741)文章我们介绍了基于docker安装jenkins的过程,本文将介绍如何配置jenkins的相关全局工具如maven、 jdk以及git等 配置的页面如下: 打开后的页面如下ÿ…...
VAuditDemo文件漏洞
目录 VAuditDemo文件漏洞 一、首页文件包含漏洞 包含图片马 利用伪协议phar:// 构造shell.inc被压缩为shell.zip,然后更改shell.zip 为 shell.jpg上传 二、任意文件读取漏洞 avatar.php updateAvatar.php logCheck.php 任意文件读取漏洞利用 VAuditDemo文件…...
[Meachines] [Medium] poison LFI+日志投毒+VNC权限提升
信息收集 IP AddressOpening Ports10.10.10.84TCP:22,80 $ nmap -p- 10.10.10.84 --min-rate 1000 -sC -sV 22/tcp open ssh OpenSSH 7.2 (FreeBSD 20161230; protocol 2.0) | ssh-hostkey: | 2048 e3:3b:7d:3c:8f:4b:8c:f9:cd:7f:d2:3a:ce:2d:ff:bb (RSA) | 256 …...
EtherCAT运动控制器上位机开发之Python+Qt(三):PDO配置与SDO读写
ZMC408CE控制器硬件介绍 ZMC408CE是正运动推出的一款多轴高性能EtherCAT总线运动控制器,具有EtherCAT、EtherNET、RS232、CAN和U盘等通讯接口,ZMC系列运动控制器可应用于各种需要脱机或联机运行的场合。 ZMC408CE支持8轴运动控制,最多可扩展…...
MyBatis源码系列1(使用JDBC查询数据)
使用原生jdbc进行查询数据步骤. 1、加载驱动2、获取数据库连接3、创建Statement对象4、占位符赋值5、执行脚本6、解析结果7、关闭资源 代码示例 public class T0 {public static void main(String[] args) throws Exception {String sql "SELECT id,name FROM goods WH…...
【微服务】Nacos配置中心和客户端数据同步模式
一、Nacos概述 Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它提供了一组简单易用的特性集,帮助用户快速实现动态服务发现、服务配置、服务元数据及流量管理。 二、数据同步模式 1. 实时同步 Push模式:在服务端的配置信…...
WebRTC音视频开发读书笔记(六)
数据通道不仅可以发送文本消息, 还可以发送图片、二进制文件,将其类型binaryType属性设置成arraybuffer类型即可. 九\、文件传输 1、文件传输流程 (1)使用表单file打开本地文件 (2)使用FileReader读取文件的二进制数据 &#…...
高级列表组件ReList
高级列表组件ReList 组件实现基于 Vue3 Element Plus Typescript,同时引用 vueUse lodash-es tailwindCss (不影响功能,可忽略) 主要基于JSX风格实现高度动态的列表渲染组件,可以通过信息配置Metas配置控制信息项展示,同时支持…...
Vxe UI vue vxe-table 实现表格数据分组功能,根据字段数据分组
Vxe UI vue vxe-table 实现表格数据分组功能,根据字段数据分组 实现数据分组功能 基于树结构功能就可以直接实现数据分组功能,代码如下: <template><div><vxe-button status"primary" click"listToGroup()&…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
