相機(jī)是一個(gè)系統(tǒng)的基礎(chǔ)能力,能夠通過調(diào)用相機(jī)進(jìn)行拍照,在很多場(chǎng)景下都會(huì)使用到相機(jī)的調(diào)用,如人臉識(shí)別門禁,人臉解鎖等操作。
本文主要介紹在 OpenHarmony 應(yīng)用開發(fā)中 ArkUI 開發(fā)框架下相機(jī)應(yīng)用的開發(fā)。
開發(fā)模式:Stage 開發(fā)模式
SDK 版本:3.2.2.5
開發(fā)環(huán)境:DevEco Studio 3.0 Release 3.0.0.993
實(shí)現(xiàn)步驟
①聲明權(quán)限
在 module.json5 中配置權(quán)限:
"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 中調(diào)用 requestPermissionsFromUser 方法申請(qǐng)權(quán)限:
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)) })
注意:權(quán)限需要在頁(yè)面加載前提前申請(qǐng),所以需要在調(diào)用相機(jī)的頁(yè)面前添加一個(gè)過渡的頁(yè)面。
②準(zhǔn)備工作
導(dǎo)包:
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//判斷保存路徑為是沙箱路徑或者媒體路徑,默認(rèn)媒體路徑 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{ /** *時(shí)分秒 */ getTime(){ constDATETIME=newDate() returnthis.concatTime(DATETIME.getHours(),DATETIME.getMinutes(),DATETIME.getSeconds()) } /** *年月日 */ getDate(){ constDATETIME=newDate() returnthis.concatDate(DATETIME.getFullYear(),DATETIME.getMonth()+1,DATETIME.getDate()) } /** *日期不足兩位補(bǔ)充0 *@paramvalue-數(shù)據(jù)值 */ 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)}` } /** *時(shí)分秒格式修飾 *@paramhours *@paramminutes *@paramseconds */ concatTime(hours:number,minutes:number,seconds:number){ return`${this.fill(hours)}${this.fill(minutes)}${this.fill(seconds)}` } }
這個(gè)工具類主要是用來(lái)進(jìn)行獲取時(shí)間對(duì)相片進(jìn)行命名的工具類。
③構(gòu)建 UI 組件
頁(yè)面主要分為 2 塊,左邊為相機(jī)的 XComponent 組件,右邊為圖片顯示區(qū)域。拍完的照片能夠顯示在右邊。 XComponent 組件作用于 EGL/OpenGLES 和媒體數(shù)據(jù)寫入,并顯示在 XComponent 組件。
相關(guān)資料:
https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-basic-components-xcomponent-0000001333800561
hml 代碼如下:
build(){ Flex(){ Flex(){ Stack(){ Flex(){ //相機(jī)顯示的組件 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) //顯示在相機(jī)上面的組件:拍照和攝像的圖標(biāo),攝像的時(shí)間 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和圖片顯示區(qū)域 Flex({ direction:FlexDirection.Column, justifyContent:FlexAlign.SpaceBetween, alignItems:ItemAlign.Center, }){ Button("選擇沙箱路徑存儲(chǔ)").onClick(()=>{ this.isMediaUrl=false }).stateStyles({ normal:{//設(shè)置默認(rèn)情況下的顯示樣式 .backgroundColor(Color.Blue) }, pressed:{//設(shè)置手指摁下時(shí)的顯示樣式 .backgroundColor(Color.Pink) } }) Image(decodeURI("file://"+this.imgUrl)).width(480).height(350)//顯示沙箱圖片 Button("選擇媒體路徑存儲(chǔ)").onClick(()=>{ this.isMediaUrl=true }).stateStyles({ normal:{//設(shè)置默認(rèn)情況下的顯示樣式 .backgroundColor(Color.Blue) }, pressed:{//設(shè)置手指摁下時(shí)的顯示樣式 .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 實(shí)現(xiàn)了對(duì)存儲(chǔ)路徑的選擇,需要存儲(chǔ)到沙箱路徑還是媒體路徑。
注意:沙箱路徑需要加上"file://",查看對(duì)應(yīng)的存儲(chǔ)路徑步驟:
打開 hdc 命令窗口
cd /data/app/el2/100/base/com.chinasoft.photo/haps/entry/files進(jìn)入
ls 查看全部文件
④拍照流程
初始化相機(jī):這一步需要在拍照前就進(jìn)行,一般是在 XComponent 組件的 onLoad() 中進(jìn)行的。
//初始化相機(jī)和會(huì)話管理 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()//創(chuàng)建圖片接收器并進(jìn)行訂閱 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.'); }) } //創(chuàng)建圖片接收器并進(jìn)行訂閱 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 }) }如下:
根據(jù) camera 的 getCameraManager 方法獲取 CameraManager
通過 CameraManager 獲取所有的相機(jī)數(shù)組,找到可用的相機(jī),并獲取相機(jī)的 cameraid
創(chuàng)建圖片接收器并進(jìn)行訂閱,獲取 receiver 的 surfaceId
通過 CameraManager 的 createCameraInput(cameraid) 創(chuàng)建相機(jī)輸入流
通過 camera 的 createPreviewOutput(sufaceId) 創(chuàng)建相機(jī)預(yù)覽輸出流,這里 sufaceId 為 XComponent 的 id
通過 camera 的 createPhotoOutput(sufaceId) 創(chuàng)建相機(jī)拍照輸出流,這里 sufaceId 為圖片接收器的 surfaceId
會(huì)話管理:創(chuàng)建會(huì)話,并且配置會(huì)話的相機(jī)輸入流,相機(jī)拍照輸出流與相機(jī)預(yù)覽流,提交配置,開始會(huì)話
至此,相機(jī)就能正常的顯示出圖像了。
用拍照方法拍攝照片:
//拍攝照片 asynctakePicture(){ letphotoSettings={ rotation:camera.ImageRotation.ROTATION_0, quality:camera.QualityLevel.QUALITY_LEVEL_LOW, mirror:false } awaitthis.photoOutput.capture(photoSettings) }
調(diào)用相機(jī)的輸出流的 capture 方法進(jìn)行拍照操作,會(huì)觸發(fā)圖片接收器的監(jiān)聽,進(jìn)行對(duì)字節(jié)流的寫入操作,保存到沙箱或者媒體。
保存圖片:分為沙箱路徑與媒體路徑。
//保存沙箱路徑 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() }釋放相機(jī):
//結(jié)束釋放相機(jī)資源 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(()=>{ }) } //釋放會(huì)話 if(this.captureSession){ awaitthis.captureSession.release((err)=>{ if(err){ console.error('zmwFailedtoreleasetheCaptureSessioninstance${err.message}'); return; } }); } }
在完成了相機(jī)的調(diào)用后,需要對(duì)相機(jī)的資源進(jìn)行釋放,否則再次調(diào)用的時(shí)候會(huì)一直被占用而導(dǎo)致黑屏。
總結(jié)
OpenHarmony 對(duì)于相機(jī)的官方使用文檔不太清晰,有許多的坑,需要去趟。
在這個(gè)過程中我遇到的問題:
在相機(jī)的使用時(shí),由于開發(fā)板上的相機(jī)獲取到了兩個(gè),一個(gè)是外接 USB 的相機(jī),一個(gè)應(yīng)該是系統(tǒng)的,在獲取相機(jī)的 id 的時(shí)候需要注意。
在保存相機(jī)拍照的圖片的時(shí)候,保存到沙箱路徑時(shí)顯示不到頁(yè)面上,需要在保存的路徑前加上"file://"。
需要擴(kuò)展研究的是進(jìn)行相機(jī)的攝像操作,以及相機(jī)拍照與攝像的切換操作。
-
相機(jī)
+關(guān)注
關(guān)注
4文章
1337瀏覽量
53442 -
Module
+關(guān)注
關(guān)注
0文章
66瀏覽量
12837 -
SDK
+關(guān)注
關(guān)注
3文章
1020瀏覽量
45699 -
鴻蒙
+關(guān)注
關(guān)注
57文章
2303瀏覽量
42693 -
OpenHarmony
+關(guān)注
關(guān)注
25文章
3641瀏覽量
16066
原文標(biāo)題:鴻蒙上成功調(diào)用相機(jī)!
文章出處:【微信號(hào):gh_834c4b3d87fe,微信公眾號(hào):OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論