马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
鸿蒙 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_END
- static 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)
- // 获取预览流profile
- let previewProfiles: Array<camera.Profile> = profiles.previewProfiles
- // 获取录像流profile
- let 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,未完待续~
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |