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

打印 上一主题 下一主题

主题 991|帖子 991|积分 2973

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
鸿蒙 next 即将发布,让我们先喊3遍 遥遥领先~ 遥遥领先~ 遥遥领先~
作为一门新的系统,本人也是刚入门学习中,如果对于一些理解有问题的,欢迎纵然指出哈
起首这里要讲一下,在鸿蒙 next 中,要实现摄像头预览&编码有两种方式。第一种,通过摄像头的预览流&录制流来实现,此中预览很简朴,直接使用 xcomponent 即可,对于编码,则可以通过创建编码器获取到的 surfaceid 传递给录制流即可。第二种是通过 nativeimage 类似于 android 的 surfacetexture 然后将纹理通过 opengl 绘制到预览 surface 和编码 surface 上去,这边文章重要将第一种简朴的方式,步骤大抵如下:
第一步,创建 xcomponaent,代码如下:
  1. XComponent({
  2.           id: '',
  3.           type: XComponentType.SURFACE,
  4.           libraryname: '',
  5.           controller: this.XcomponentController
  6.         })
  7.           .onLoad(() => {
  8.             this.XcomponentController.setXComponentSurfaceSize({
  9.               surfaceWidth: this.cameraWidth, surfaceHeight: this.cameraHeight
  10.             })
  11.             this.XcomponentSurfaceId = this.XcomponentController.getXComponentSurfaceId()
  12.           })
复制代码
创建 xcomponeant 的关键是获取 surfaceid,这个背面会用来传给摄像头预览流用的。
第二步,获取编码器的 surfaceid,由于目前鸿蒙没有为编码器这块提供 arkts 接口,所以需要用到 napi 作为中间桥接,通过 arkts 来调用 c++ 代码,大抵代码如下:
arkts 部分:
  1. import recorder from 'librecorder.so'
  2. recorder.initNative()
复制代码
librecorder.so 为工程中 c++ 的部分,具体可以参考项目模板中关于 c++ 的示例
napi 部分:
  1. #include "RecorderNative.h"
  2. #include <bits/alltypes.h>
  3. #undef LOG_DOMAIN
  4. #undef LOG_TAG
  5. #define LOG_DOMAIN 0xFF00
  6. #define LOG_TAG "recorder"
  7. struct AsyncCallbackInfo {
  8.     napi_env env;
  9.     napi_async_work asyncWork;
  10.     napi_deferred deferred;
  11.     int32_t resultCode = 0;
  12.     std::string surfaceId = "";
  13.     SampleInfo sampleInfo;
  14. };
  15. void DealCallBack(napi_env env, void *data)
  16. {
  17.     AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
  18.     napi_value code;
  19.     napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
  20.     napi_value surfaceId;
  21.     napi_create_string_utf8(env, asyncCallbackInfo->surfaceId.data(), NAPI_AUTO_LENGTH, &surfaceId);
  22.     napi_value obj;
  23.     napi_create_object(env, &obj);
  24.     napi_set_named_property(env, obj, "code", code);
  25.     napi_set_named_property(env, obj, "surfaceId", surfaceId);
  26.     napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
  27.     napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
  28.     delete asyncCallbackInfo;
  29. }
  30. void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code)
  31. {
  32.     asyncCallbackInfo->resultCode = code;
  33. }
  34. void SurfaceIdCallBack(AsyncCallbackInfo *asyncCallbackInfo, std::string surfaceId)
  35. {
  36.     asyncCallbackInfo->surfaceId = surfaceId;
  37. }
  38. void NativeInit(napi_env env, void *data)
  39. {
  40.     AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
  41.     int32_t ret = Recorder::GetInstance().Init(asyncCallbackInfo->sampleInfo);
  42.     if (ret != AVCODEC_SAMPLE_ERR_OK) {
  43.         SetCallBackResult(asyncCallbackInfo, -1);
  44.     }
  45.     uint64_t id = 0;
  46.     ret = OH_NativeWindow_GetSurfaceId(asyncCallbackInfo->sampleInfo.window, &id);
  47.     if (ret != AVCODEC_SAMPLE_ERR_OK) {
  48.         SetCallBackResult(asyncCallbackInfo, -1);
  49.     }
  50.     asyncCallbackInfo->surfaceId = std::to_string(id);
  51.     SurfaceIdCallBack(asyncCallbackInfo, asyncCallbackInfo->surfaceId);
  52. }
  53. napi_value RecorderNative::Init(napi_env env, napi_callback_info info)
  54. {
  55.     SampleInfo sampleInfo;
  56.     napi_value promise;
  57.     napi_deferred deferred;
  58.     napi_create_promise(env, &deferred, &promise);
  59.     AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
  60.     asyncCallbackInfo->env = env;
  61.     asyncCallbackInfo->asyncWork = nullptr;
  62.     asyncCallbackInfo->deferred = deferred;
  63.     asyncCallbackInfo->resultCode = -1;
  64.     asyncCallbackInfo->sampleInfo = sampleInfo;
  65.    
  66.     napi_value resourceName;
  67.     napi_create_string_latin1(env, "recorder", NAPI_AUTO_LENGTH, &resourceName);
  68.     napi_create_async_work(
  69.         env, nullptr, resourceName, [](napi_env env, void *data) { NativeInit(env, data); },
  70.         [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void *)asyncCallbackInfo,
  71.         &asyncCallbackInfo->asyncWork);
  72.     napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
  73.     return promise;
  74. }
  75. EXTERN_C_START
  76. static napi_value Init(napi_env env, napi_value exports)
  77. {
  78.     napi_property_descriptor classProp[] = {
  79.         {"initNative", nullptr, RecorderNative::Init, nullptr, nullptr, nullptr, napi_default, nullptr}
  80.     };
  81.     return exports;
  82. }
  83. EXTERN_C_END
  84. static napi_module RecorderModule = {
  85.     .nm_version = 1,
  86.     .nm_flags = 0,
  87.     .nm_filename = nullptr,
  88.     .nm_register_func = Init,
  89.     .nm_modname = "recorder",
  90.     .nm_priv = ((void *)0),
  91.     .reserved = {0},
  92. };
  93. extern "C" __attribute__((constructor)) void RegisterRecorderModule(void) { napi_module_register(&RecorderModule); }
复制代码
鸿蒙这边的 napi 其实是参考的 nodejs 的,语法基本同等,这里的大抵逻辑就是调用 Recorder::GetInstance().Init() 获取编码器的 surfaceid 然后通过 ts 的 promise 传递给前端
c++ 编码器部分:
  1. int32_t Recorder::Init(SampleInfo &sampleInfo)
  2. {
  3.     std::lock_guard<std::mutex> lock(mutex_);
  4.     sampleInfo_ = sampleInfo;
  5.     videoEncoder_ = std::make_unique<VideoEncoder>();
  6.     muxer_ = std::make_unique<Muxer>();
  7.    
  8.     videoEncoder_->Create(sampleInfo_.videoCodecMime);
  9.     ret = muxer_->Create(sampleInfo_.outputFd);
  10.     encContext_ = new CodecUserData;
  11.     videoEncoder_->Config(sampleInfo_, encContext_);
  12.     muxer_->Config(sampleInfo_);
  13.     sampleInfo.window = sampleInfo_.window;
  14.     releaseThread_ = nullptr;
  15.     return AVCODEC_SAMPLE_ERR_OK;
  16. }
复制代码
此中核心的在于 videoEncoder_->Config(),这一步会将 nativewindow 赋值给 sampleInfo 结构体,然后就可以获取到nativewindow 的 surfaceid了
代码如下:
  1. int32_t VideoEncoder::Config(SampleInfo &sampleInfo, CodecUserData *codecUserData)
  2. {
  3.     Configure(sampleInfo);
  4.     OH_VideoEncoder_GetSurface(encoder_, &sampleInfo.window);
  5.     SetCallback(codecUserData);
  6.     OH_VideoEncoder_Prepare(encoder_);
  7.     return AVCODEC_SAMPLE_ERR_OK;
  8. }
复制代码
到此为止,xcomponents 的 surfaceid 和编码器的 surtfaceid 都获取到了,接着就是在 arkts 层创建摄像头,并设置预览&编码输出了,这块比较简朴,照着文档来就行,代码如下:
  1. let cameraManager = camera.getCameraManager(globalThis.context)
  2. let camerasDevices: Array<camera.CameraDevice> = getCameraDevices(cameraManager)
  3. let profiles: camera.CameraOutputCapability = cameraManager.getSupportedOutputCapability(camerasDevices[0],
  4.       camera.SceneMode.NORMAL_VIDEO)
  5. // 获取预览流profile
  6.     let previewProfiles: Array<camera.Profile> = profiles.previewProfiles
  7. // 获取录像流profile
  8.     let videoProfiles: Array<camera.VideoProfile> = profiles.videoProfiles
  9. // Xcomponent预览流
  10.     let XComponentPreviewProfile: camera.Profile = previewProfiles[0]
  11. // 创建 编码器 输出对象
  12.     encoderVideoOutput = cameraManager.createVideoOutput(videoProfile, encoderSurfaceId)
  13. // 创建 预览流 输出对象
  14.     XcomponentPreviewOutput = cameraManager.createPreviewOutput(XComponentPreviewProfile, this.XcomponentSurfaceId)
  15. // 创建cameraInput对象
  16.     cameraInput = cameraManager.createCameraInput(camerasDevices[0])
  17. // 打开相机
  18.     await cameraInput.open()
  19. // 会话流程
  20.     videoSession = cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession
  21. // 开始配置会话
  22.     videoSession.beginConfig()
  23. // 把CameraInput加入到会话
  24.     videoSession.addInput(cameraInput)
  25. // 把 Xcomponent 预览流加入到会话
  26.     videoSession.addOutput(XcomponentPreviewOutput)
  27. // 把编码器录像流加入到会话
  28.     videoSession.addOutput(encoderVideoOutput)
  29. // 提交配置信息
  30.     await videoSession.commitConfig()
  31. // 会话开始
  32.     await videoSession.start()
复制代码
至此,关于预览&编码的大抵流程就是如许了,整体流程其实还是很简朴的,核心就是获取两个 surfaceid,然后传入到摄像头录制&预览流中即可。这里就大抵讲一下思路,相信做安卓或者前端的同学都能看明白。不过这种模式的一个缺点在于无法做一些深层次的操纵,比方水印、美白、瘦脸等,优点在于代码量比较少。第二篇要将的是关于如何通过 opengl 来绘制预览 & 编码 surface,未完待续~
 
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表