相机是一个系统的基础能力,能够通过调用相机进行拍照,在很多场景下都会使用到相机的调用,如人脸识别门禁,人脸解锁等操作。
本文主要介绍在 OpenHarmony 应用开发中 ArkUI 开发框架下相机应用的开发。
开发模式:Stage 开发模式
SDK 版本:3.2.2.5
开发环境:DevEco Studio 3.0 Release 3.0.0.993
实现步骤
①声明权限
在 module.json5 中配置权限:
"reqPermissions":[{ "name":"ohos.permission.LOCATION", }, { "name":"ohos.permission.CAMERA" }, { "name":"ohos.permission.MICROPHONE" }, { "name":"ohos.permission.MEDIA_LOCATION" }, { "name":"ohos.permission.WRITE_MEDIA" }, { "name":"ohos.permission.READ_MEDIA" }]
在 MainAbility.ts 中调用 requestPermissionsFromUser 方法申请权限:
constPERMISSIONS:Array=[ 'ohos.permission.CAMERA', 'ohos.permission.MICROPHONE', 'ohos.permission.MEDIA_LOCATION', 'ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA', 'ohos.permission.GET_WIFI_INFO', 'ohos.permission.GET_WIFI_PEERS_MAC', ] globalThis.abilityWant=want; globalThis.context=this.context globalThis.abilityContext=this.context; globalThis.context.requestPermissionsFromUser(PERMISSIONS).then((message)=>{ console.log(JSON.stringify(message)) })
注意:权限需要在页面加载前提前申请,所以需要在调用相机的页面前添加一个过渡的页面。
②准备工作
导包:
importcamerafrom'@ohos.multimedia.camera'; importimagefrom'@ohos.multimedia.image'; importfileiofrom'@ohos.fileio'; importmediaLibraryfrom'@ohos.multimedia.mediaLibrary' constCameraSize={ WIDTH:640, HEIGHT:480 }定义变量:
privatemXComponentController=newXComponentController() privatecameraManager:camera.CameraManager=undefined privatecameras:Array工具方法:=undefined privatecameraId:string=undefined privatemReceiver:image.ImageReceiver=undefined privatecameraInput:camera.CameraInput=undefined privatepreviewOutput:camera.PreviewOutput=undefined privatemSurfaceId:string=undefined privatephotoOutput:camera.PhotoOutput=undefined privatecaptureSession:camera.CaptureSession=undefined privatemediaUtil:MediaUtil=undefined @StatedesStr:string="" privatefileAsset:mediaLibrary.FileAsset privatesurfaceId:number @StatephotoUriMedia:string="" privatephotoFlag:boolean=true @StateimgUrl:string="" @StateisMediaUrl:boolean=true//判断保存路径为是沙箱路径或者媒体路径,默认媒体路径 aboutToAppear(){ this.mediaTest=mediaLibrary.getMediaLibrary(globalThis.context) }
asynccreateAndGetUri(mediaType:number){ letinfo=this.getInfoFromType(mediaType) letdateTimeUtil=newDateTimeUtil() letname=`${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}` letdisplayName=`${info.prefix}${name}${info.suffix}` letpublicPath=awaitthis.mediaTest.getPublicDirectory(info.directory) letdataUri=awaitthis.mediaTest.createAsset(mediaType,displayName,publicPath) returndataUri } asyncgetFdPath(fileAsset:any){ letfd=awaitfileAsset.open('Rw') returnfd } getInfoFromType(mediaType:number){ letresult={ prefix:'',suffix:'',directory:0 } switch(mediaType){ casemediaLibrary.MediaType.FILE: result.prefix='FILE_' result.suffix='.txt' result.directory=mediaLibrary.DirectoryType.DIR_DOCUMENTS break casemediaLibrary.MediaType.IMAGE: result.prefix='IMG_' result.suffix='.jpg' result.directory=mediaLibrary.DirectoryType.DIR_IMAGE break casemediaLibrary.MediaType.VIDEO: result.prefix='VID_' result.suffix='.mp4' result.directory=mediaLibrary.DirectoryType.DIR_VIDEO break casemediaLibrary.MediaType.AUDIO: result.prefix='AUD_' result.suffix='.wav' result.directory=mediaLibrary.DirectoryType.DIR_AUDIO break } returnresult }工具类:
/** *@file日期工具 */ exportdefaultclassDateTimeUtil{ /** *时分秒 */ getTime(){ constDATETIME=newDate() returnthis.concatTime(DATETIME.getHours(),DATETIME.getMinutes(),DATETIME.getSeconds()) } /** *年月日 */ getDate(){ constDATETIME=newDate() returnthis.concatDate(DATETIME.getFullYear(),DATETIME.getMonth()+1,DATETIME.getDate()) } /** *日期不足两位补充0 *@paramvalue-数据值 */ fill(value:number){ return(value>9?'':'0')+value } /** *年月日格式修饰 *@paramyear *@parammonth *@paramdate */ concatDate(year:number,month:number,date:number){ return`${year}${this.fill(month)}${this.fill(date)}` } /** *时分秒格式修饰 *@paramhours *@paramminutes *@paramseconds */ concatTime(hours:number,minutes:number,seconds:number){ return`${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}` } }
这个工具类主要是用来进行获取时间对相片进行命名的工具类。
③构建 UI 组件
页面主要分为 2 块,左边为相机的 XComponent 组件,右边为图片显示区域。拍完的照片能够显示在右边。 XComponent 组件作用于 EGL/OpenGLES 和媒体数据写入,并显示在 XComponent 组件。
相关资料:
https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-xcomponent-0000001333800561
hml 代码如下:
build(){ Flex(){ Flex(){ Stack(){ Flex(){ //相机显示的组件 XComponent({ id:'componentId', type:'surface', controller:this.mXComponentController }).onLoad(()=>{ this.mXComponentController.setXComponentSurfaceSize({surfaceWidth:640,surfaceHeight:480}) this.surfaceId=this.mXComponentController.getXComponentSurfaceId() this.initCamera(this.surfaceId) }) }.width(800).height(800) //显示在相机上面的组件:拍照和摄像的图标,摄像的时间 Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.End,alignItems:ItemAlign.Center}){ if(this.photoFlag){//拍照 Image($r("app.media.take_photo_normal")).width(50).height(50).onClick(()=>{ this.desStr="拍照完成" this.takePicture() }) } Text(this.desStr).fontColor("red").height(30).fontSize(20) }.width(480).height(480) }.border({width:1,style:BorderStyle.Solid,color:"#000000"}) //右边的控制button和图片显示区域 Flex({ direction:FlexDirection.Column, justifyContent:FlexAlign.SpaceBetween, alignItems:ItemAlign.Center, }){ Button("选择沙箱路径存储").onClick(()=>{ this.isMediaUrl=false }).stateStyles({ normal:{//设置默认情况下的显示样式 .backgroundColor(Color.Blue) }, pressed:{//设置手指摁下时的显示样式 .backgroundColor(Color.Pink) } }) Image(decodeURI("file://"+this.imgUrl)).width(480).height(350)//显示沙箱图片 Button("选择媒体路径存储").onClick(()=>{ this.isMediaUrl=true }).stateStyles({ normal:{//设置默认情况下的显示样式 .backgroundColor(Color.Blue) }, pressed:{//设置手指摁下时的显示样式 .backgroundColor(Color.Pink) } }) Image(decodeURI(this.imgUrl)).width(480).height(350)//显示媒体图片 }.width(480).height("100%").border({width:1,style:BorderStyle.Solid,color:"#000000"}) }.border({width:1,style:BorderStyle.Solid,color:"red"}) .width("100%").height("100%") } .height('100%').width("100%") }UI 实现了对存储路径的选择,需要存储到沙箱路径还是媒体路径。
注意:沙箱路径需要加上"file://",查看对应的存储路径步骤:
打开 hdc 命令窗口
cd /data/app/el2/100/base/com.chinasoft.photo/haps/entry/files进入
ls 查看全部文件
④拍照流程
初始化相机:这一步需要在拍照前就进行,一般是在 XComponent 组件的 onLoad() 中进行的。
//初始化相机和会话管理 asyncinitCamera(surfaceId:number){ this.cameraManager=awaitcamera.getCameraManager(globalThis.context)//需要在Ability中定义globalThis.context=this.context this.cameras=awaitthis.cameraManager.getCameras() this.cameraId=this.cameras[1].cameraId awaitthis.photoReceiver()//创建图片接收器并进行订阅 this.mSurfaceId=awaitthis.mReceiver.getReceivingSurfaceId() this.cameraInput=awaitthis.cameraManager.createCameraInput(this.cameraId) this.previewOutput=awaitcamera.createPreviewOutput(surfaceId.toString()) this.photoOutput=awaitcamera.createPhotoOutput(this.mSurfaceId) this.captureSession=awaitcamera.createCaptureSession(globalThis.context) awaitthis.captureSession.beginConfig() awaitthis.captureSession.addInput(this.cameraInput) awaitthis.captureSession.addOutput(this.previewOutput) awaitthis.captureSession.addOutput(this.photoOutput) awaitthis.captureSession.commitConfig() awaitthis.captureSession.start().then(()=>{ console.log('zmw1--Promisereturnedtoindicatethesessionstartsuccess.'); }) } //创建图片接收器并进行订阅 asyncphotoReceiver(){ this.mReceiver=image.createImageReceiver(CameraSize.WIDTH,CameraSize.HEIGHT,4,8) letbuffer=newArrayBuffer(4096) this.mReceiver.on('imageArrival',()=>{ console.log("zmw-service-imageArrival") this.mReceiver.readNextImage((err,image)=>{ if(err||image===undefined){ return } image.getComponent(4,(errMsg,img)=>{ if(errMsg||img===undefined){ return } if(img.byteBuffer){ buffer=img.byteBuffer } if(this.isMediaUrl){ this.savePictureMedia(buffer,image) }else{ this.savePictureSand(buffer,image) } }) }) returnbuffer }) }如下:
根据 camera 的 getCameraManager 方法获取 CameraManager
通过 CameraManager 获取所有的相机数组,找到可用的相机,并获取相机的 cameraid
创建图片接收器并进行订阅,获取 receiver 的 surfaceId
通过 CameraManager 的 createCameraInput(cameraid) 创建相机输入流
通过 camera 的 createPreviewOutput(sufaceId) 创建相机预览输出流,这里 sufaceId 为 XComponent 的 id
通过 camera 的 createPhotoOutput(sufaceId) 创建相机拍照输出流,这里 sufaceId 为图片接收器的 surfaceId
会话管理:创建会话,并且配置会话的相机输入流,相机拍照输出流与相机预览流,提交配置,开始会话
至此,相机就能正常的显示出图像了。
用拍照方法拍摄照片:
//拍摄照片 asynctakePicture(){ letphotoSettings={ rotation:camera.ImageRotation.ROTATION_0, quality:camera.QualityLevel.QUALITY_LEVEL_LOW, mirror:false } awaitthis.photoOutput.capture(photoSettings) }
调用相机的输出流的 capture 方法进行拍照操作,会触发图片接收器的监听,进行对字节流的写入操作,保存到沙箱或者媒体。
保存图片:分为沙箱路径与媒体路径。
//保存沙箱路径 asyncsavePictureSand(buffer:ArrayBuffer,img:image.Image){ letinfo=this.mediaUtil.getInfoFromType(mediaLibrary.MediaType.IMAGE) letdateTimeUtil=newDateTimeUtil() letname=`${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}` letdisplayName=`${info.prefix}${name}${info.suffix}` letsandboxDirPath=globalThis.context.filesDir; letpath=sandboxDirPath+'/'+displayName this.imgUrl=path letfdSand=awaitfileio.open(path,0o2|0o100,0o666); awaitfileio.write(fdSand,buffer) awaitfileio.close(fdSand).then(()=>{ this.desStr="" }); awaitimg.release() } //保存媒体路径 asyncsavePictureMedia(buffer:ArrayBuffer,img:image.Image){ this.fileAsset=awaitthis.mediaUtil.createAndGetUri(mediaLibrary.MediaType.IMAGE) this.imgUrl=this.fileAsset.uri letfd=awaitthis.mediaUtil.getFdPath(this.fileAsset) awaitfileio.write(fd,buffer) awaitthis.fileAsset.close(fd).then(()=>{ this.desStr="" }) awaitimg.release() }释放相机:
//结束释放相机资源 asyncreleaseCamera(){ if(this.captureSession){ awaitthis.captureSession.stop().then(()=>{ }) } if(this.cameraInput){ awaitthis.cameraInput.release().then(()=>{ }) } if(this.previewOutput){ awaitthis.previewOutput.release().then(()=>{ }) } if(this.photoOutput){ awaitthis.photoOutput.release().then(()=>{ }) } //释放会话 if(this.captureSession){ awaitthis.captureSession.release((err)=>{ if(err){ console.error('zmwFailedtoreleasetheCaptureSessioninstance${err.message}'); return; } }); } }
在完成了相机的调用后,需要对相机的资源进行释放,否则再次调用的时候会一直被占用而导致黑屏。
总结
OpenHarmony 对于相机的官方使用文档不太清晰,有许多的坑,需要去趟。
在这个过程中我遇到的问题:
在相机的使用时,由于开发板上的相机获取到了两个,一个是外接 USB 的相机,一个应该是系统的,在获取相机的 id 的时候需要注意。
在保存相机拍照的图片的时候,保存到沙箱路径时显示不到页面上,需要在保存的路径前加上"file://"。
需要扩展研究的是进行相机的摄像操作,以及相机拍照与摄像的切换操作。
-
相机
+关注
关注
4文章
1350浏览量
53576 -
Module
+关注
关注
0文章
68浏览量
12851 -
SDK
+关注
关注
3文章
1033浏览量
45881 -
鸿蒙
+关注
关注
57文章
2335浏览量
42794 -
OpenHarmony
+关注
关注
25文章
3710浏览量
16243
原文标题:鸿蒙上成功调用相机!
文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论