范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

OpenHarmony上实现分布式相机

  作者:徐金生
  最近陆续看到各社区上有关 OpenHarmony 媒体相机的使用开发文档,相机对于富设备来说必不可少,日常中我们经常使用相机完成拍照、人脸验证等。
  OpenHarmony 系统一个重要的能力就是分布式,对于分布式相机我也倍感兴趣,之前看到官方对分布式相机的一些说明,这里简单介绍下。
  有兴趣可以查看官方文档:分布式相机部件 https://gitee.com/openharmony/distributedhardware_distributed_camera
  分布式框架图
  分布式相机框架(Distributed Hardware)分为主控端和被控端。假设:设备 B 拥有本地相机设备,分布式组网中的设备 A 可以分布式调用设备 B 的相机设备。
  这种场景下,设备 A 是主控端,设备 B 是被控端,两个设备通过软总线进行交互。 VirtualCameraHAL: 作为硬件适配层(HAL)的一部分,负责和分布式相机框架中的主控端交互,将主控端 CameraFramwork 下发的指令传输给分布式相机框架的 SourceMgr 处理。 SourceMgr: 通过软总线将控制信息传递给被控端的 CameraClient。 CameraClient: 直接通过调用被控端 CameraFramwork 的接口来完成对设备 B 相机的控制。
  最后,从设备 B 反馈的预览图像数据会通过分布式相机框架的 ChannelSink 回传到设备 A 的 HAL 层,进而反馈给应用。通过这种方式,设备 A 的应用就可以像使用本地设备一样使用设备 B 的相机。 相关名词介绍:主控端(source): 控制端,通过调用分布式相机能力,使用被控端的摄像头进行预览、拍照、录像等功能。 被控端(sink): 被控制端,通过分布式相机接收主控端的命令,使用本地摄像头为主控端提供图像数据。
  现在我们要实现分布式相机,在主控端调用被控端相机,实现远程操作相机,开发此应用的具体需求: 支持本地相机的预览、拍照、保存相片、相片缩略图、快速查看相片、切换摄像头(如果一台设备上存在多个摄像头时)。 同一网络下,支持分布式 pin 码认证,远程连接。 自由切换本地相机和远程相机。
  UI 草图
  从草图上看,我们简单的明应用 UI 布局的整体内容: 顶部右上角有个"切换设备"的按钮,点击弹窗显示设备列表,可以实现设备认证与设备切换功能。 中间使用 XComponent 组件实现的相机预览区域。 底部分为如下三个部分。
  具体如下: 相机缩略图:显示当前设备媒体库中最新的图片,点击相机缩略图按钮可以查看相关的图片。 拍照:点击拍照按钮,将相机当前帧保存到本地媒体库中。 切换摄像头:如果一台设备有多个摄像头时,例如相机有前后置摄像头,点击切换后会将当前预览的页面切换到另外一个摄像头的图像。
  实现效果
  开发环境
  如下: 系统: OpenHarmony 3.2 beta4/OpenHarmony 3.2 beta5 设备: DAYU200 IDE: DevEco Studio 3.0 Release ,Build Version: 3.0.0.993, built on September 4, 2022 SDK: Full_3.2.9.2 开发模式: Stage 开发语言: ets
  开发实践
  本篇主要在应用层的角度实现分布式相机,实现远程相机与实现本地相机的流程相同,只是使用的相机对象不同,所以我们先完成本地相机的开发,再通过参数修改相机对象来启动远程相机。 ①创建项目
  ②权限声明(1)module.json 配置权限
  说明: 在 module 模块下添加权限声明,权限的详细说明 https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/permission-list.md"requestPermissions": [   {     "name": "ohos.permission.REQUIRE_FORM"   },   {     "name": "ohos.permission.MEDIA_LOCATION"   },   {     "name": "ohos.permission.MODIFY_AUDIO_SETTINGS"   },   {     "name": "ohos.permission.READ_MEDIA"   },   {     "name": "ohos.permission.WRITE_MEDIA"   },   {     "name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"   },   {     "name": "ohos.permission.CAMERA"   },   {     "name": "ohos.permission.MICROPHONE"   },   {     "name": "ohos.permission.DISTRIBUTED_DATASYNC"   } ](2)在 index.ets 页面的初始化 aboutToAppear() 申请权限
  代码如下: let permissionList: Array = [   "ohos.permission.MEDIA_LOCATION",   "ohos.permission.READ_MEDIA",   "ohos.permission.WRITE_MEDIA",   "ohos.permission.CAMERA",   "ohos.permission.MICROPHONE",   "ohos.permission.DISTRIBUTED_DATASYNC" ]    async aboutToAppear() {     console.info(`${TAG} aboutToAppear`)     globalThis.cameraAbilityContext.requestPermissionsFromUser(permissionList).then(async (data) => {       console.info(`${TAG} data permissions: ${JSON.stringify(data.permissions)}`)       console.info(`${TAG} data authResult: ${JSON.stringify(data.authResults)}`)       // 判断授权是否完成       let resultCount: number = 0       for (let result of data.authResults) {         if (result === 0) {           resultCount += 1         }       }       if (resultCount === permissionList.length) {         this.isPermissions = true       }       await this.initCamera()       // 获取缩略图       this.mCameraService.getThumbnail(this.functionBackImpl)     })   }
  这里有个获取缩略图的功能,主要是获取媒体库中根据时间排序,获取最新拍照的图片作为当前需要显示的缩略图,实现此方法在后面说 CameraService 类的时候进行详细介绍。
  注意:如果首次启动应用,在授权完成后需要加载相机,则建议授权放在启动页完成,或者在调用相机页面之前添加一个过渡页面,主要用于完成权限申请和启动相机的入口,否则首次完成授权后无法显示相机预览,需要退出应用再重新进入才可以正常预览,这里先简单说明下,文章后续会在问题环节详细介绍。 ③UI 布局
  说明:UI 如前面截图所示,实现整体页面的布局。
  页面中主要使用到 XComponent 组件,用于 EGL/OpenGLES 和媒体数据写入,并显示在 XComponent 组件。
  参看:XComponent 详细介绍 https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-xcomponent.md
  onLoad(): XComponent 插件加载完成时的回调,在插件完成时可以获取**ID并初始化相机。
  XComponentController: XComponent 组件控制器,可以绑定至 XComponent 组件,通过 getXComponent/**aceId() 获取 XComponent 对应的/**aceID。
  代码如下: @State @Watch("selectedIndexChange") selectIndex: number = 0   // 设备列表   @State devices: Array = []   // 设备选择弹窗   private dialogController: CustomDialogController = new CustomDialogController({     builder: DeviceDialog({       deviceList: $devices,       selectIndex: $selectIndex,     }),     autoCancel: true,     alignment: DialogAlignment.Center   })   @State curPictureWidth: number = 70   @State curPictureHeight: number = 70   @State curThumbnailWidth: number = 70   @State curThumbnailHeight: number = 70   @State curSwitchAngle: number = 0   @State Id: string = ""   @State thumbnail: image.PixelMap = undefined   @State resourceUri: string = ""   @State isSwitchDeviceing: boolean = false // 是否正在切换相机   private isInitCamera: boolean = false // 是否已初始化相机   private isPermissions: boolean = false // 是否完成授权   private componentController: XComponentController = new XComponentController()   private mCurDeviceID: string = Constant.LOCAL_DEVICE_ID // 默认本地相机   private mCurCameraIndex: number = 0 //  默认相机列表中首个相机   private mCameraService = CameraService.getInstance()    build() {     Stack({ alignContent: Alignment.Center }) {       Column() {         Row({ space: 20 }) {           Image($r("app.media.ic_camera_public_setting"))             .width(40)             .height(40)             .margin({               right: 20             })             .objectFit(ImageFit.Contain)             .onClick(() => {               console.info(`${TAG} click distributed auth.`)               this.showDialog()             })         }         .width("100%")         .height("5%")         .margin({           top: 20,           bottom: 20         })         .alignItems(VerticalAlign.Center)         .justifyContent(FlexAlign.End)          Column() {           XComponent({             id: "componentId",             type: "xxxxace",             controller: this.componentController           }).onLoad(async () => {             console.info(`${TAG} XComponent onLoad is called`)             this.componentController.setXComponentxxxxaceSize({               xxxxWidth: Resolution.DEFAULT_WIDTH,               xxxxaceHeight: Resolution.DEFAULT_HEIGHT             })             this.id = this.componentController.getXComponentxxxxaceId()             console.info(`${TAG} id: ${this.id}`)             await this.initCamera()           }).height("100%")             .width("100%")         }         .width("100%")         .height("75%")         .margin({           bottom: 20         })          Row() {           Column() {             Image(this.thumbnail != undefined ? this.thumbnail : $r("app.media.screen_pic"))               .width(this.curThumbnailWidth)               .height(this.curThumbnailHeight)               .objectFit(ImageFit.Cover)               .onClick(async () => {                 console.info(`${TAG} launch bundle com.ohos.photos`)                 await globalThis.cameraAbilityContext.startAbility({                   parameters: { uri: "photodetail" },                   bundleName: "com.ohos.photos",                   abilityName: "com.ohos.photos.MainAbility"                 })                 animateTo({                   duration: 200,                   curve: Curve.EaseInOut,                   delay: 0,                   iterations: 1,                   playMode: PlayMode.Reverse,                   onFinish: () => {                     animateTo({                       duration: 100,                       curve: Curve.EaseInOut,                       delay: 0,                       iterations: 1,                       playMode: PlayMode.Reverse                     }, () => {                       this.curThumbnailWidth = 70                       this.curThumbnailHeight = 70                     })                   }                 }, () => {                   this.curThumbnailWidth = 60                   this.curThumbnailHeight = 60                 })               })           }           .width("33%")           .alignItems(HorizontalAlign.Start)            Column() {             Image($r("app.media.icon_picture"))               .width(this.curPictureWidth)               .height(this.curPictureHeight)               .objectFit(ImageFit.Cover)               .alignRules({                 center: {                   align: VerticalAlign.Center,                   anchor: "center"                 }               })               .onClick(() => {                 this.takePicture()                 animateTo({                   duration: 200,                   curve: Curve.EaseInOut,                   delay: 0,                   iterations: 1,                   playMode: PlayMode.Reverse,                   onFinish: () => {                     animateTo({                       duration: 100,                       curve: Curve.EaseInOut,                       delay: 0,                       iterations: 1,                       playMode: PlayMode.Reverse                     }, () => {                       this.curPictureWidth = 70                       this.curPictureHeight = 70                     })                   }                 }, () => {                   this.curPictureWidth = 60                   this.curPictureHeight = 60                 })               })           }           .width("33%")            Column() {             Image($r("app.media.icon_switch"))               .width(50)               .height(50)               .objectFit(ImageFit.Cover)               .rotate({                 x: 0,                 y: 1,                 z: 0,                 angle: this.curSwitchAngle               })               .onClick(() => {                 this.switchCamera()                 animateTo({                   duration: 500,                   curve: Curve.EaseInOut,                   delay: 0,                   iterations: 1,                   playMode: PlayMode.Reverse,                   onFinish: () => {                     animateTo({                       duration: 500,                       curve: Curve.EaseInOut,                       delay: 0,                       iterations: 1,                       playMode: PlayMode.Reverse                     }, () => {                       this.curSwitchAngle = 0                     })                   }                 }, () => {                   this.curSwitchAngle = 180                 })               })           }           .width("33%")           .alignItems(HorizontalAlign.End)          }         .width("100%")         .height("10%")         .justifyContent(FlexAlign.SpaceBetween)         .alignItems(VerticalAlign.Center)         .padding({           left: 40,           right: 40         })       }       .height("100%")       .width("100%")       .padding(10)        if (this.isSwitchDeviceing) {         Column() {           Image($r("app.media.load_switch_camera"))             .width(400)             .height(306)             .objectFit(ImageFit.Fill)           Text($r("app.string.switch_camera"))             .width("100%")             .height(50)             .fontSize(16)             .fontColor(Color.White)             .align(Alignment.Center)         }         .width("100%")         .height("100%")         .backgroundColor(Color.Black)         .justifyContent(FlexAlign.Center)         .alignItems(HorizontalAlign.Center)         .onClick(() => {          })       }     }     .height("100%")     .backgroundColor(Color.Black)   }
  (1)启动系统相册
  说明:用户点击图片缩略图时需要启动图片查看,这里直接打开系统相册,查看相关的图片。
  代码如下: await globalThis.cameraAbilityContext.startAbility({                   parameters: { uri: "photodetail" },                   bundleName: "com.ohos.photos",                   abilityName: "com.ohos.photos.MainAbility"                 })
  ④相机服务 CameraService.ts (1)CameraService 单例模式,用于提供操作相机相关的业务
  代码如下: private static instance: CameraService = null       private constructor() {         this.mThumbnailGetter = new ThumbnailGetter()     }     /**      * 单例      */     public static getInstance(): CameraService {         if (this.instance === null) {             this.instance = new CameraService()         }         return this.instance     }
  (2)初始化相机
  说明:通过媒体相机提供的 API(@ohos.multimedia.camera)getCameraManager() 获取相机管理对象 CameraManager,并注册相机状态变化监听器,实时更新相机状态。
  同时通过 CameraManager…getSupportedCameras() 获取前期支持的相机设备集合,这里的相机设备包括当前设备上安装的相机设备和远程设备上的相机设备。
  代码如下: /**      * 初始化      */     public async initCamera(): Promise {         console.info(`${TAG} initCamera`)         if (this.mCameraManager === null) {             this.mCameraManager = await camera.getCameraManager(globalThis.cameraAbilityContext)             // 注册监听相机状态变化             this.mCameraManager.on("cameraStatus", (cameraStatusInfo) => {                 console.info(`${TAG} camera Status: ${JSON.stringify(cameraStatusInfo)}`)             })             // 获取相机列表             let cameras: Array = await this.mCameraManager.getSupportedCameras()             if (cameras) {                 this.mCameraCount = cameras.length                 console.info(`${TAG} mCameraCount: ${this.mCameraCount}`)                 if (this.mCameraCount === 0) {                     return this.mCameraCount                 }                 for (let i = 0; i < cameras.length; i++) {                     console.info(`${TAG} --------------Camera Info-------------`)                     const tempCameraId: string = cameras[i].cameraId                     console.info(`${TAG} camera_id: ${tempCameraId}`)                     console.info(`${TAG} cameraPosition: ${cameras[i].cameraPosition}`)                     console.info(`${TAG} cameraType: ${cameras[i].cameraType}`)                     const connectionType = cameras[i].connectionType                     console.info(`${TAG} connectionType: ${connectionType}`)                     // CameraPosition 0-未知未知 1-后置 2-前置                     // CameraType 0-未知类型 1-广角 2-超广角 3长焦 4-带景深信息                     // connectionType 0-内置相机 1-USB连接相机 2-远程连接相机                     // 判断本地相机还是远程相机                     if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) {                         // 本地相机                         this.displayCameraDevice(Constant.LOCAL_DEVICE_ID, cameras[i])                     } else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {                         // 远程相机 相机ID格式 :deviceID__Camera_cameraID 例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7__Camera_lcam001                         const cameraKey: string = tempCameraId.split("__Camera_")[0]                         console.info(`${TAG} cameraKey: ${cameraKey}`)                         this.displayCameraDevice(cameraKey, cameras[i])                     }                 }                 // todo test 选择首个相机                 this.mCurCameraDevice = cameras[0]                 console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)             }         }         return this.mCameraCount     }       /**      * 处理相机设备      * @param key      * @param cameraDevice      */     private displayCameraDevice(key: string, cameraDevice: camera.CameraDevice) {         console.info(`${TAG} displayCameraDevice ${key}`)         if (this.mCameraMap.has(key) && this.mCameraMap.get(key)?.length > 0) {             console.info(`${TAG} displayCameraDevice has mCameraMap`)             // 判断相机列表中是否已经存在此相机             let isExist: boolean = false             for (let item of this.mCameraMap.get(key)) {                 if (item.cameraId === cameraDevice.cameraId) {                     isExist = true                     break                 }             }             // 添加列表中没有的相机             if (!isExist) {                 console.info(`${TAG} displayCameraDevice not exist , push ${cameraDevice.cameraId}`)                 this.mCameraMap.get(key).push(cameraDevice)             } else {                 console.info(`${TAG} displayCameraDevice has existed`)             }         } else {             let cameras: Array = []             console.info(`${TAG} displayCameraDevice push ${cameraDevice.cameraId}`)             cameras.push(cameraDevice)             this.mCameraMap.set(key, cameras)         }     }
  (3)创建相机输入流
  说明:CameraManager.createCameraInput() 可以创建相机输出流 CameraInput 实例,CameraInput 是在 CaptureSession 会话中使用的相机信息,支持打开相机、关闭相机等能力。
  代码如下: /**      * 创建相机输入流      * @param cameraIndex 相机下标      * @param deviceId 设备ID      */     public async createCameraInput(cameraIndex?: number, deviceId?: string) {         console.info(`${TAG} createCameraInput`)         if (this.mCameraManager === null) {             console.error(`${TAG} mCameraManager is null`)             return         }         if (this.mCameraCount <= 0) {             console.error(`${TAG} not camera device`)             return         }         if (this.mCameraInput) {             this.mCameraInput.release()         }         if (deviceId && this.mCameraMap.has(deviceId)) {             if (cameraIndex < this.mCameraMap.get(deviceId)?.length) {                 this.mCurCameraDevice = this.mCameraMap.get(deviceId)[cameraIndex]             } else {                 this.mCurCameraDevice = this.mCameraMap.get(deviceId)[0]             }         }         console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)         try {             this.mCameraInput = await this.mCameraManager.createCameraInput(this.mCurCameraDevice)             console.info(`${TAG} mCameraInput: ${JSON.stringify(this.mCameraInput)}`)             this.mCameraInput.on("error", this.mCurCameraDevice, (error) => {                 console.error(`${TAG} CameraInput error: ${JSON.stringify(error)}`)             })             await this.mCameraInput.open()         } catch (err) {             if (err) {                 console.error(`${TAG} failed to createCameraInput`)             }         }     }
  (4)相机预览输出流
  说明:CameraManager.createPreviewOutput() 创建预览输出流对象 PreviewOutput,PreviewOutput 继承 CameraOutput,在 CaptureSession 会话中使用的输出信息,支持开始输出预览流、停止预览输出流、释放预览输出流等能力。 /**      * 创建相机预览输出流      */     public async createPreviewOutput(Id: string, callback : PreviewCallBack) {         console.info(`${TAG} createPreviewOutput`)         if (this.mCameraManager === null) {             console.error(`${TAG} createPreviewOutput mCameraManager is null`)             return         }         this.Id = Id         console.info(`${TAG} Id ${Id}}`)         // 获取当前相机设备支持的输出能力         let cameraOutputCap = await this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)         if (!cameraOutputCap) {             console.error(`${TAG} createPreviewOutput getSupportedOutputCapability error}`)             return         }         console.info(`${TAG} createPreviewOutput cameraOutputCap ${JSON.stringify(cameraOutputCap)}`)         let previewProfilesArray = cameraOutputCap.previewProfiles         let previewProfiles: camera.Profile         if (!previewProfilesArray || previewProfilesArray.length <= 0) {             console.error(`${TAG} createPreviewOutput previewProfilesArray error}`)             previewProfiles = {                 format: 1,                 size: {                     width: 640,                     height: 480                 }             }         } else {             console.info(`${TAG} createPreviewOutput previewProfile length ${previewProfilesArray.length}`)             previewProfiles = previewProfilesArray[0]         }         console.info(`${TAG} createPreviewOutput previewProfile[0] ${JSON.stringify(previewProfiles)}`)         try {             this.mPreviewOutput = await this.mCameraManager.createPreviewOutput(previewProfiles, id )             console.info(`${TAG} createPreviewOutput success`)             // 监听预览帧开始             this.mPreviewOutput.on("frameStart", () => {                 console.info(`${TAG} createPreviewOutput camera frame Start`)                 callback.onFrameStart()             })             this.mPreviewOutput.on("frameEnd", () => {                 console.info(`${TAG} createPreviewOutput camera frame End`)                 callback.onFrameEnd()             })             this.mPreviewOutput.on("error", (error) => {                 console.error(`${TAG} createPreviewOutput error: ${error}`)             })         } catch (err) {             console.error(`${TAG} failed to createPreviewOutput ${err}`)         }     }
  (5)拍照输出流
  说明:CameraManager.createPhotoOutput() 可以创建拍照输出对象 PhotoOutput,PhotoOutput 继承 CameraOutput 在拍照会话中使用的输出信息,支持拍照、判断是否支持镜像拍照、释放资源、监听拍照开始、拍照帧输出捕获、拍照结束等能力。
  代码如下: /**      * 创建拍照输出流      */     public async createPhotoOutput(functionCallback: FunctionCallBack) {         console.info(`${TAG} createPhotoOutput`)         if (!this.mCameraManager) {             console.error(`${TAG} createPhotoOutput mCameraManager is null`)             return         }         // 通过宽、高、图片格式、容量创建ImageReceiver实例         const receiver: image.ImageReceiver = image.createImageReceiver(Resolution.DEFAULT_WIDTH, Resolution.DEFAULT_HEIGHT, image.ImageFormat.JPEG, 8)         const imageId: string = await receiver.getReceivingxxxxaceId()         console.info(`${TAG} createPhotoOutput imageId: ${imageId}`)         let cameraOutputCap = await this.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice)         console.info(`${TAG} createPhotoOutput cameraOutputCap ${cameraOutputCap}`)         if (!cameraOutputCap) {             console.error(`${TAG} createPhotoOutput getSupportedOutputCapability error}`)             return         }         let photoProfilesArray = cameraOutputCap.photoProfiles         let photoProfiles: camera.Profile         if (!photoProfilesArray || photoProfilesArray.length <= 0) {             // 使用自定义的配置             photoProfiles = {                 format: 2000,                 size: {                     width: 1280,                     height: 960                 }             }         } else {             console.info(`${TAG} createPhotoOutput photoProfile length ${photoProfilesArray.length}`)             photoProfiles = photoProfilesArray[0]         }         console.info(`${TAG} createPhotoOutput photoProfile ${JSON.stringify(photoProfiles)}`)         try {             this.mPhotoOutput = await this.mCameraManager.createPhotoOutput(photoProfiles, id)             console.info(`${TAG} createPhotoOutput mPhotoOutput success`)             // 保存图片             this.mSaveCameraAsset.saveImage(receiver, Resolution.THUMBNAIL_WIDTH, Resolution.THUMBNAIL_HEIGHT, this.mThumbnailGetter, functionCallback)         } catch (err) {             console.error(`${TAG} createPhotoOutput failed to createPhotoOutput ${err}`)         }     }
  this.mSaveCameraAsset.saveImage(),这里将保存拍照的图片进行封装—SaveCameraAsset.ts,后面会单独介绍。 (6)会话管理
  说明:通过 CameraManager.createCaptureSession() 可以创建相机的会话类,保存相机运行所需要的所有资源 CameraInput、CameraOutput,并向相机设备申请完成相机拍照或录像功能。
  CaptureSession 对象提供了开始配置会话、添加 CameraInput 到会话、添加 CameraOutput 到会话、提交配置信息、开始会话、停止会话、释放等能力。
  代码如下: public async createSession(id: string) {         console.info(`${TAG} createSession`)         console.info(`${TAG} createSession id ${id}}`)         this.id= id          this.mCaptureSession = await this.mCameraManager.createCaptureSession()         console.info(`${TAG} createSession mCaptureSession ${this.mCaptureSession}`)          this.mCaptureSession.on("error", (error) => {             console.error(`${TAG} CaptureSession error ${JSON.stringify(error)}`)         })         try {             await this.mCaptureSession?.beginConfig()             await this.mCaptureSession?.addInput(this.mCameraInput)             if (this.mPhotoOutput != null) {                 console.info(`${TAG} createSession addOutput PhotoOutput`)                 await this.mCaptureSession?.addOutput(this.mPhotoOutput)             }             await this.mCaptureSession?.addOutput(this.mPreviewOutput)         } catch (err) {             if (err) {                 console.error(`${TAG} createSession beginConfig fail err:${JSON.stringify(err)}`)             }         }         try {             await this.mCaptureSession?.commitConfig()         } catch (err) {             if (err) {                 console.error(`${TAG} createSession commitConfig fail err:${JSON.stringify(err)}`)             }         }         try {             await this.mCaptureSession?.start()         } catch (err) {             if (err) {                 console.error(`${TAG} createSession start fail err:${JSON.stringify(err)}`)             }         }         console.info(`${TAG} createSession mCaptureSession start`)     }
  ⑤拍照
  说明:通过 PhotoOutput.capture() 可以实现拍照功能。
  代码如下: /**      * 拍照      */     public async takePicture() {         console.info(`${TAG} takePicture`)         if (!this.mCaptureSession) {             console.info(`${TAG} takePicture session is release`)             return         }         if (!this.mPhotoOutput) {             console.info(`${TAG} takePicture mPhotoOutput is null`)             return         }         try {             const photoCaptureSetting: camera.PhotoCaptureSetting = {                 quality: camera.QualityLevel.QUALITY_LEVEL_HIGH,                 rotation: camera.ImageRotation.ROTATION_0,                 location: {                     latitude: 0,                     longitude: 0,                     altitude: 0                 },                 mirror: false             }             await this.mPhotoOutput.capture(photoCaptureSetting)         } catch (err) {             console.error(`${TAG} takePicture err:${JSON.stringify(err)}`)         }     }
  ⑥保存图片 SaveCameraAsset
  说明:SaveCameraAsset.ts 主要用于保存拍摄的图片,即是调用拍照操作后,会触发图片接收监听器,在将图片的字节流进行写入本地文件操作。
  代码如下: /**  * 保存相机拍照的资源  */ import image from "@ohos.multimedia.image" import mediaLibrary from "@ohos.multimedia.mediaLibrary" import { FunctionCallBack } from "../model/CameraService" import DateTimeUtil from "../utils/DateTimeUtil" import fileIO from "@ohos.file.fs"; import ThumbnailGetter from "../model/ThumbnailGetter" let photoUri: string // 图片地址 const TAG: string = "SaveCameraAsset" export default class SaveCameraAsset {     private lastSaveTime: string = ""     private saveIndex: number = 0     constructor() {     }     public getPhotoUri(): string {         console.info(`${TAG} getPhotoUri = ${photoUri}`)         return photoUri     }     /**      *  保存拍照图片      * @param imageReceiver 图像接收对象      * @param thumbWidth 缩略图宽度      * @param thumbHeight 缩略图高度      * @param callback 回调      */     public saveImage(imageReceiver: image.ImageReceiver, thumbWidth: number, thumbHeight: number, thumbnailGetter :ThumbnailGetter, callback: FunctionCallBack) {         console.info(`${TAG} saveImage`)         const mDateTimeUtil = new DateTimeUtil()         const fileKeyObj = mediaLibrary.FileKey         const mediaType = mediaLibrary.MediaType.IMAGE         let buffer = new ArrayBuffer(4096)         const media = mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext) // 获取媒体库实例         // 接收图片回调         imageReceiver.on("imageArrival", async () => {             console.info(`${TAG} saveImage ImageArrival`)             // 使用当前时间命名             const displayName = this.checkName(`IMG_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + ".jpg"             console.info(`${TAG} displayName = ${displayName}}`)             imageReceiver.readNextImage((err, imageObj: image.Image) => {                 if (imageObj === undefined) {                     console.error(`${TAG} saveImage failed to get valid image error = ${err}`)                     return                 }                 // 根据图像的组件类型从图像中获取组件缓存 4-JPEG类型                 imageObj.getComponent(image.ComponentType.JPEG, async (errMsg, imgComponent) => {                     if (imgComponent === undefined) {                         console.error(`${TAG} getComponent failed to get valid buffer error = ${errMsg}`)                         return                     }                     if (imgComponent.byteBuffer) {                         console.info(`${TAG} getComponent imgComponent.byteBuffer ${imgComponent.byteBuffer}`)                         buffer = imgComponent.byteBuffer                     } else {                         console.info(`${TAG} getComponent imgComponent.byteBuffer is undefined`)                     }                     await imageObj.release()                 })             })             let publicPath:string = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)             console.info(`${TAG} saveImage publicPath = ${publicPath}`)             //  创建媒体资源 返回提供封装文件属性             const dataUri : mediaLibrary.FileAsset = await media.createAsset(mediaType, displayName, publicPath)             // 媒体文件资源创建成功,将拍照的数据写入到媒体资源             if (dataUri !== undefined) {                 photoUri = dataUri.uri                 console.info(`${TAG} saveImage photoUri: ${photoUri}`)                 const args = dataUri.id.toString()                 console.info(`${TAG} saveImage id: ${args}`)                 //  通过ID查找媒体资源                 const fetchOptions:mediaLibrary.MediaFetchOptions = {                     selections : `${fileKeyObj.ID} = ?`,                     selectionArgs : [args]                 }                 console.info(`${TAG} saveImage fetchOptions: ${JSON.stringify(fetchOptions)}`)                 const fetchFileResult = await media.getFileAssets(fetchOptions)                 const fileAsset = await fetchFileResult.getAllObject() // 获取文件检索结果中的所有文件资                 if (fileAsset != undefined) {                     fileAsset.forEach((dataInfo) => {                         dataInfo.open("Rw").then((fd) => { // RW是读写方式打开文件 获取fd                             console.info(`${TAG} saveImage dataInfo.open called. fd: ${fd}`)                             // 将缓存图片流写入资源                             fileIO.write(fd, buffer).then(() => {                                 console.info(`${TAG} saveImage fileIO.write called`)                                 dataInfo.close(fd).then(() => {                                     console.info(`${TAG} saveImage dataInfo.close called`)                                     // 获取资源缩略图                                     thumbnailGetter.getThumbnailInfo(thumbWidth, thumbHeight, photoUri).then((thumbnail => {                                         if (thumbnail === undefined) {                                             console.error(`${TAG} saveImage getThumbnailInfo undefined`)                                             callback.onCaptureFailure()                                         } else {                                             console.info(`${TAG} photoUri: ${photoUri} PixelBytesNumber: ${thumbnail.getPixelBytesNumber()}`)                                             callback.onCaptureSuccess(thumbnail, photoUri)                                         }                                     }))                                 }).catch(error => {                                     console.error(`${TAG} saveImage close is error ${JSON.stringify(error)}`)                                 })                             })                         })                     })                 } else {                     console.error(`${TAG} saveImage fileAsset: is null`)                 }             } else {                 console.error(`${TAG} saveImage photoUri is null`)             }         })     }     /**      * 检测文件名称      * @param fileName 文件名称      * 如果同一时间有多张图片,则使用时间_index命名      */     private checkName(fileName: string): string {         if (this.lastSaveTime == fileName) {             this.saveIndex++             return `${fileName}_${this.saveIndex}`         }         this.lastSaveTime = fileName         this.saveIndex = 0         return fileName     } }⑦获取缩略图
  说明:主要通过获取当前媒体库中根据时间排序,获取最新的图片并缩放图片大小后返回。
  代码如下: /**      * 获取缩略图      * @param callback      */     public getThumbnail(callback: FunctionCallBack) {         console.info(`${TAG} getThumbnail`)         this.mThumbnailGetter.getThumbnailInfo(Resolution.THUMBNAIL_WIDTH, Resolution.THUMBNAIL_HEIGHT).then((thumbnail) => {             console.info(`${TAG} getThumbnail thumbnail = ${thumbnail}`)             callback.thumbnail(thumbnail)         })     }
  (1)ThumbnailGetter.ts
  说明: 实现获取缩略图的对象。
  代码如下: /**  * 缩略图处理器  */ import mediaLibrary from "@ohos.multimedia.mediaLibrary"; import image from "@ohos.multimedia.image"; const TAG: string = "ThumbnailGetter" export default class ThumbnailGetter {     public async getThumbnailInfo(width: number, height: number, uri?: string): Promise {         console.info(`${TAG} getThumbnailInfo`)         // 文件关键信息         const fileKeyObj = mediaLibrary.FileKey         // 获取媒体资源公共路径         const media: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext)         let publicPath: string = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA)         console.info(`${TAG} publicPath = ${publicPath}`)         let fetchOptions: mediaLibrary.MediaFetchOptions = {             selections: `${fileKeyObj.RELATIVE_PATH}=?`, // 检索条件 RELATIVE_PATH-相对公共目录的路径             selectionArgs: [publicPath] // 检索条件值         }         if (uri) {             fetchOptions.uri = uri // 文件的URI         } else {             fetchOptions.order = fileKeyObj.DATE_ADDED + " DESC"         }         console.info(`${TAG} getThumbnailInfo fetchOptions :  ${JSON.stringify(fetchOptions)}}`)         const fetchFileResult = await media.getFileAssets(fetchOptions) // 文件检索结果集         const count = fetchFileResult.getCount()         console.info(`${TAG} count = ${count}`)         if (count == 0) {             return undefined         }         // 获取结果集合中的最后一张图片         const lastFileAsset = await fetchFileResult.getFirstObject()         if (lastFileAsset == null) {             console.error(`${TAG} getThumbnailInfo lastFileAsset is null`)             return undefined         }         const thumbnailPixelMap = lastFileAsset.getThumbnail({             width: width,             height: height         })         console.info(`${TAG} getThumbnailInfo thumbnailPixelMap ${JSON.stringify(thumbnailPixelMap)}}`)         return thumbnailPixelMap     } }
  ⑧释放资源 ​
  说明:在相机设备切换时,如前后置摄像头切换或者不同设备之间的摄像头切换时都需要先释放资源,再重新创建新的相机会话才可以正常运行,释放的资源包括:释放相机输入流、预览输出流、拍照输出流、会话。
  代码如下: /**      * 释放相机输入流      */     public async releaseCameraInput() {         console.info(`${TAG} releaseCameraInput`)         if (this.mCameraInput) {             try {                 await this.mCameraInput.release()             } catch (err) {                 console.error(`${TAG} releaseCameraInput ${err}}`)             }             this.mCameraInput = null         }     }    /**      * 释放预览输出流      */     public async releasePreviewOutput() {         console.info(`${TAG} releasePreviewOutput`)         if (this.mPreviewOutput) {             await this.mPreviewOutput.release()             this.mPreviewOutput = null         }     }   /**      * 释放拍照输出流      */     public async releasePhotoOutput() {         console.info(`${TAG} releasePhotoOutput`)         if (this.mPhotoOutput) {             await this.mPhotoOutput.release()             this.mPhotoOutput = null         }     }       public async releaseSession() {         console.info(`${TAG} releaseSession`)         if (this.mCaptureSession) {             await this.mCaptureSession.stop()             console.info(`${TAG} releaseSession stop`)             await this.mCaptureSession.release()             console.info(`${TAG} releaseSession release`)             this.mCaptureSession = null             console.info(`${TAG} releaseSession null`)         }     }
  至此,总结下,需要实现相机预览、拍照功能: 通过 camera 媒体 api 提供的 camera.getCameraManager() 获取 CameraManager 相机管理类。 通过相机管理类型创建相机预览与拍照需要的输入流(createCameraInput)和输出流(createPreviewOutPut、createPhotoOutput),同时创建相关会话管理(createCaptureSession) 将输入流、输出流添加到会话中,并启动会话 拍照可以直接使用 PhotoOutput.capture 执行拍照,并将拍照结果保存到媒体 在退出相机应用时,需要注意释放相关的资源。
  因为分布式相机的应用开发内容比较长,这篇只说到主控端相机设备预览与拍照功能,下一篇会将结合分布式相关内容完成主控端设备调用远程相机进行预览的功能。

女排林莉亮相新工作,月薪7000,考试第一当大学老师中国女排有许多明星球员,她们能力出众,帮助女排多次拿到世界大赛的奖牌。退役之后,同样是完成了华丽转型,像惠若琪魏秋月等人,就十分的成功。她们长相漂亮,所以也是娱乐圈的宠儿,经常可以预防不孕,先做好这件事每年的9月26日是世界避孕日,其愿景为建立一个没有意外妊娠的世界。避孕,是女性人生当中的重要话题。有些年轻女性以为无痛人流不影响再孕,不把人流当回事,甚至每次都把人流作为自己犯错后公立和私立幼儿园的差距,小学三年级后看得很清楚,希望你没选错现在的孩子大概在两三岁,就要进入到幼儿园。家长因为工作忙,没有时间照看,孩子们也需要融入集体生活,学会开始认识这个世界。所以选择一个好的幼儿园,成为了家长们比较关心的事情。关于幼儿怀孕后,总是烧心,怎么办?有没有孕妈和我一样,整个孕期经常会有烧心的感觉,有人会问,什么是烧心。所谓烧心,是隔离食道和胃的一圈肌肉松弛下来,经过初步消化的食物会混杂着胃酸从胃部返回食道,胃酸刺激着敏感的食道史上首个行星防御航天器今晨高速撞向近地小行星迪莫弗斯美国宇航局(NASA)表示,有史以来第一个行星防御航天器将于美国东部时间9月26日1914(北京时间27日0014)以每小时1。5万英里的高速撞向一颗小行星迪莫弗斯。望远镜将从远处透视地下80米!Nature祝融号火星车全球首个雷达探测结果出炉来源中国科学报文中国科学报记者冯丽妃9月26日,中国祝融号火星车在乌托邦平原实施的全球首个雷达探测结果出炉,发表于自然杂志,引人注目。中国是全球首个在地外天体上开展巡视雷达探测的国刚刚,人类完成有史以来第一个行星防御试验北京时间714左右,人类主动用飞行器撞击了行星。当年刺秦王的荆轲要是知道以后还会有飞行器主动撞上行星,一定也会和孤注一掷的飞行器颇有共鸣。同样预见到了自己的有来无回,同样执行的是自太空跨尺度能量如何传输?我国学者有最新发现宇宙中存在着多种不同尺度的物理行为,从由电子回旋运动和离子回旋运动表征的微观尺度,一直延伸到与行星大小相当的宏观尺度,跨越超过8个数量级。这些不同尺度的物理过程如何耦合?能量如何在科学家预测2023年或将爆发超强太阳风暴,人类生存几率多大?地球真的安全吗?世界末日也许并不是危言耸听,而是地球有幸躲过了数次惊天灾难。而2023年将会爆发百年一遇的太阳危机,届时地球还会如此幸运吗?对于人类来说,这会是一场灭顶之灾吗?喜欢三十年!载人航天工程飞行任务大事记一览1992年9月21日,这是一个载入中国航天史册的日子,这是一个值得被永远铭记的日子。这一天,中央正式决策实施载人航天工程,并确定了我国载人航天三步走发展战略,代号为921工程。三十行星防御,美国先走出第一步,纵观全球,只有中美有这个实力近日,美国国家航天局成功操控一颗人造卫星对一颗名为迪莫弗斯的小行星进行动能撞击,使其运转轨道发生偏离。这是人类首次对地外天体的运转轨道进行偏转干涉,此次试验的成功,证明了人类已经初
杨振宁改回中国籍是要养老?不要想得太简单,他的贡献超乎你想象文猫行图网络杨振宁改回中国籍是要养老?不要想得太简单,杨老的贡献超乎你想象!有人说,能奋斗,有价值的时候他选择了美国,没有剩余价值了,还回来干啥?给你那么多荣誉,你也好意思接受?你长期喝电热水壶烧的水,对身体有影响吗?安全使用,牢记3点科技改变生活的同时,有时也会有出差错的时候,家用电器是家家户户不能脱离的必需品,但在近些年,网络常常报道一些事件某牌炒锅的涂层致使买家中毒电饭煲在使用期间自燃导致烧伤还有长期使用电被日媒封为国民情妇的她,到底有多美?这身材,让人脸红啊说起日本性感女神,怎么能少了被日媒封为国民情妇的桥本爱实。凭借完美身材和优雅气质,一直活跃于日本写真和影视界。1984年出生的桥本爱实其实小时候很内向,不敢在人前说话。小学六年级在郭子瑜再爆料,称李易峰抠门,聊天内容涉及陈伟霆和马苏头条创作挑战赛刚刚前两天,李易峰个人账号以及工作室账号相应封禁,这起风波也算是有了一个剧终,不过关于这起事件的话题却远远没有结束。就在李易峰出事的当天,一位自称李易峰前女友的网友郭公司法定代表人免职后,请求公司办理工商变更登记应予支持(2022)最高法民再94号裁判观点法定代表人是对外代表公司意志的机关之一,登记的法定代表人依法具有公示效力,但就公司内部而言,公司和法定代表人之间为委托法律关系,法定代表人行使代王者荣耀赏金联赛什么时候关闭赏金联赛还有吗本篇将为大家介绍王者荣耀赏金联赛什么时候关闭,很多玩王者荣耀的小伙伴们应该都了解到了赏金联赛即将关闭的消息,那么具体的关闭时间是什么时候呢,下面小编就带大家一起来了解一下。王者荣耀还在害怕裴擒虎?用这4位英雄,让他不敢入侵野区过两天就是KPL首届夏季赛的总决赛,总冠军也将会在3号晚上诞生,天美为此特意返场了一款KPL皮肤裴擒虎的天狼狩猎者。这个返场消息也传到老村长那里去了,老村长听后大怒,想想他的伴生衣LOLLOL英雄联盟S12全球总决赛奖杯设计公布LCK那边酸了北京时间9月1日,前不久拳头官方公布了新的世界冠军奖杯的设计细节,可以说这次冠军奖杯是下了血本了,这次不是真正的捧起奖杯而是抬起奖杯了,因为这座奖杯重达40斤,一个人想要捧起来还是自由之刃火祭系统自由之刃游戏中的火祭,对于玩家的等级提升和战力提升至关重要。其实就是对19阶以上装备进行回收,从而获得特殊极品材料,目前只有19阶以上的装备可以火祭,极地装备是不能火祭的。并且玩家王者荣耀9。1更新,电玩小子回归碎片商店,天狼狩猎者限时返场前言9月1日,王者荣耀再次迎来更新,此次更新,有6个英雄遭到了调整,其中狄仁杰干将莫邪宫本武藏都被削弱了,不过赵云扁鹊和刘邦都得到了加强。而除了英雄的调整之外,此次也更新了很多活动白色月牙欠TIAN两个FMVP没毛病吧?春季赛决赛一抢炸弹人葬送春季赛冠军,如果那时候tes夺冠,大概率是天fmvp。刚刚结束的夏季赛,又被气到!上次输jdg我觉得输一次不是坏事,可以总结教训避免膨胀。败者组赢了edg,