相机是一个系统的基础能力,能够通过调用相机进行拍照,在很多场景下都会使用到相机的调用,如人脸识别门禁,人脸解锁等操作。 本文主要介绍在OpenHarmony应用开发中ArkUI开发框架下相机应用的开发。开发模式:Stage开发模式SDK版本:3。2。2。5开发环境:DevEcoStudio3。0Release3。0。0。993 相机调用成功如下图: 实现步骤声明权限在module。json5中配置权限:reqPermissions:〔{name:ohos。permission。LOCATION,},{name:ohos。permission。CAMERA},{name:ohos。permission。MICROPHONE},{name:ohos。permission。MEDIALOCATION},{name:ohos。permission。WRITEMEDIA},{name:ohos。permission。READMEDIA}〕在MainAbility。ts中调用requestPermissionsFromUser方法申请权限:constPERMISSIONS:Arraystring〔ohos。permission。CAMERA,ohos。permission。MICROPHONE,ohos。permission。MEDIALOCATION,ohos。permission。READMEDIA,ohos。permission。WRITEMEDIA,ohos。permission。GETWIFIINFO,ohos。permission。GETWIFIPEERSMAC,〕globalThis。abilityWantwant;globalThis。contextthis。contextglobalThis。abilityContextthis。context;globalThis。context。requestPermissionsFromUser(PERMISSIONS)。then((message){console。log(JSON。stringify(message))}) 注意:权限需要在页面加载前提前申请,所以需要在调用相机的页面前添加一个过渡的页面。准备工作导包:importcamerafromohos。multimedia。camera;importimagefromohos。multimedia。image;importfileiofromohos。fileio;importmediaLibraryfromohos。multimedia。mediaLibraryconstCameraSize{WIDTH:640,HEIGHT:480} 定义变量:privatemXComponentControllernewXComponentController()privatecameraManager:camera。CameraManagerundefinedprivatecameras:Arraycamera。CameraundefinedprivatecameraId:stringundefinedprivatemReceiver:image。ImageReceiverundefinedprivatecameraInput:camera。CameraInputundefinedprivatepreviewOutput:camera。PreviewOutputundefinedprivatemSurfaceId:stringundefinedprivatephotoOutput:camera。PhotoOutputundefinedprivatecaptureSession:camera。CaptureSessionundefinedprivatemediaUtil:MediaUtilundefinedStatedesStr:stringprivatefileAsset:mediaLibrary。FileAssetprivatesurfaceId:numberStatephotoUriMedia:stringprivatephotoFlag:booleantrueStateimgUrl:stringStateisMediaUrl:booleantrue判断保存路径为是沙箱路径或者媒体路径,默认媒体路径aboutToAppear(){this。mediaTestmediaLibrary。getMediaLibrary(globalThis。context)} 工具方法:asynccreateAndGetUri(mediaType:number){letinfothis。getInfoFromType(mediaType)letdateTimeUtilnewDateTimeUtil()letname{dateTimeUtil。getDate()}{dateTimeUtil。getTime()}letdisplayName{info。prefix}{name}{info。suffix}letpublicPathawaitthis。mediaTest。getPublicDirectory(info。directory)letdataUriawaitthis。mediaTest。createAsset(mediaType,displayName,publicPath)returndataUri}asyncgetFdPath(fileAsset:any){letfdawaitfileAsset。open(Rw)returnfd}getInfoFromType(mediaType:number){letresult{prefix:,suffix:,directory:0}switch(mediaType){casemediaLibrary。MediaType。FILE:result。prefixFILEresult。suffix。txtresult。directorymediaLibrary。DirectoryType。DIRDOCUMENTSbreakcasemediaLibrary。MediaType。IMAGE:result。prefixIMGresult。suffix。jpgresult。directorymediaLibrary。DirectoryType。DIRIMAGEbreakcasemediaLibrary。MediaType。VIDEO:result。prefixVIDresult。suffix。mp4result。directorymediaLibrary。DirectoryType。DIRVIDEObreakcasemediaLibrary。MediaType。AUDIO:result。prefixAUDresult。suffix。wavresult。directorymediaLibrary。DirectoryType。DIRAUDIObreak}returnresult} 工具类:file日期工具exportdefaultclassDateTimeUtil{时分秒getTime(){constDATETIMEnewDate()returnthis。concatTime(DATETIME。getHours(),DATETIME。getMinutes(),DATETIME。getSeconds())}年月日getDate(){constDATETIMEnewDate()returnthis。concatDate(DATETIME。getFullYear(),DATETIME。getMonth()1,DATETIME。getDate())}日期不足两位补充0paramvalue数据值fill(value:number){return(value9?:0)value}年月日格式修饰paramyearparammonthparamdateconcatDate(year:number,month:number,date:number){return{year}{this。fill(month)}{this。fill(date)}}时分秒格式修饰paramhoursparamminutesparamsecondsconcatTime(hours:number,minutes:number,seconds:number){return{this。fill(hours)}{this。fill(minutes)}{this。fill(seconds)}}} 这个工具类主要是用来进行获取时间对相片进行命名的工具类。构建UI组件 页面主要分为2块,左边为相机的XComponent组件,右边为图片显示区域。拍完的照片能够显示在右边。 XComponent组件作用于EGLOpenGLES和媒体数据写入,并显示在XComponent组件。 相关资料:https:developer。harmonyos。comcndocsdocumentationdocreferencestsbasiccomponentsxcomponent0000001333800561 hml代码如下:build(){Flex(){Flex(){Stack(){Flex(){相机显示的组件XComponent({id:componentId,type:surface,controller:this。mXComponentController})。onLoad((){this。mXComponentController。setXComponentSurfaceSize({surfaceWidth:640,surfaceHeight:480})this。surfaceIdthis。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。takephotonormal))。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。isMediaUrlfalse})。stateStyles({normal:{设置默认情况下的显示样式。backgroundColor(Color。Blue)},pressed:{设置手指摁下时的显示样式。backgroundColor(Color。Pink)}})Image(decodeURI(file:this。imgUrl))。width(480)。height(350)显示沙箱图片Button(选择媒体路径存储)。onClick((){this。isMediaUrltrue})。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命令窗口cddataappel2100basecom。chinasoft。photohapsentryfiles进入ls查看全部文件拍照流程初始化相机:这一步需要在拍照前就进行,一般是在XComponent组件的onLoad()中进行的。初始化相机和会话管理asyncinitCamera(surfaceId:number){this。cameraManagerawaitcamera。getCameraManager(globalThis。context)需要在Ability中定义globalThis。cnotallowthis。contextthis。camerasawaitthis。cameraManager。getCameras()this。cameraIdthis。cameras〔1〕。cameraIdawaitthis。photoReceiver()创建图片接收器并进行订阅this。mSurfaceIdawaitthis。mReceiver。getReceivingSurfaceId()this。cameraInputawaitthis。cameraManager。createCameraInput(this。cameraId)this。previewOutputawaitcamera。createPreviewOutput(surfaceId。toString())this。photoOutputawaitcamera。createPhotoOutput(this。mSurfaceId)this。captureSessionawaitcamera。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(zmw1Promisereturnedtoindicatethesessionstartsuccess。);})}创建图片接收器并进行订阅asyncphotoReceiver(){this。mReceiverimage。createImageReceiver(CameraSize。WIDTH,CameraSize。HEIGHT,4,8)letbuffernewArrayBuffer(4096)this。mReceiver。on(imageArrival,(){console。log(zmwserviceimageArrival)this。mReceiver。readNextImage((err,image){if(errimageundefined){return}image。getComponent(4,(errMsg,img){if(errMsgimgundefined){return}if(img。byteBuffer){bufferimg。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。ROTATION0,quality:camera。QualityLevel。QUALITYLEVELLOW,mirror:false}awaitthis。photoOutput。capture(photoSettings)} 调用相机的输出流的capture方法进行拍照操作,会触发图片接收器的监听,进行对字节流的写入操作,保存到沙箱或者媒体。保存图片:分为沙箱路径与媒体路径。保存沙箱路径asyncsavePictureSand(buffer:ArrayBuffer,img:image。Image){letinfothis。mediaUtil。getInfoFromType(mediaLibrary。MediaType。IMAGE)letdateTimeUtilnewDateTimeUtil()letname{dateTimeUtil。getDate()}{dateTimeUtil。getTime()}letdisplayName{info。prefix}{name}{info。suffix}letsandboxDirPathglobalThis。context。filesDir;letpathsandboxDirPathdisplayNamethis。imgUrlpathletfdSandawaitfileio。open(path,0o20o100,0o666);awaitfileio。write(fdSand,buffer)awaitfileio。close(fdSand)。then((){this。desStr});awaitimg。release()}保存媒体路径asyncsavePictureMedia(buffer:ArrayBuffer,img:image。Image){this。fileAssetawaitthis。mediaUtil。createAndGetUri(mediaLibrary。MediaType。IMAGE)this。imgUrlthis。fileAsset。uriletfdawaitthis。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:。 需要扩展研究的是进行相机的摄像操作,以及相机拍照与摄像的切换操作。 参考资料:https:gitee。comopenharmonyappsamplestreemastermediaScanhttps:gitee。comopenharmonyapplicationscamerahttps:gitee。comopenharmonydocsblobOpenHarmony3。2Beta3zhcnapplicationdevmediacamera。md 作者:张明伟