鸿蒙 next 实现摄像头视频预览编码(一)

2024-08-22 00:20

本文主要是介绍鸿蒙 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 实现摄像头视频预览编码(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu

基于SpringBoot实现文件秒传功能

《基于SpringBoot实现文件秒传功能》在开发Web应用时,文件上传是一个常见需求,然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余,此时可以使用文件秒传技术通过识别重复... 目录前言文件秒传原理代码实现1. 创建项目基础结构2. 创建上传存储代码3. 创建Result类4.

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专