最近陸續(xù)看到各社區(qū)上有關 OpenHarmony 媒體相機的使用開發(fā)文檔,相機對于富設備來說必不可少,日常中我們經(jīng)常使用相機完成拍照、人臉驗證等。
OpenHarmony 系統(tǒng)一個重要的能力就是分布式,對于分布式相機我也倍感興趣,之前看到官方對分布式相機的一些說明,這里簡單介紹下。
有興趣可以查看官方文檔:分布式相機部件
https://gitee.com/openharmony/distributedhardware_distributed_camera
分布式框架圖
分布式相機框架(Distributed Hardware)分為主控端和被控端。假設:設備 B 擁有本地相機設備,分布式組網(wǎng)中的設備 A 可以分布式調用設備 B 的相機設備。
這種場景下,設備 A 是主控端,設備 B 是被控端,兩個設備通過軟總線進行交互。
VirtualCameraHAL:作為硬件適配層(HAL)的一部分,負責和分布式相機框架中的主控端交互,將主控端 CameraFramwork 下發(fā)的指令傳輸給分布式相機框架的 SourceMgr 處理。
SourceMgr:通過軟總線將控制信息傳遞給被控端的 CameraClient。
CameraClient:直接通過調用被控端 CameraFramwork 的接口來完成對設備 B 相機的控制。
最后,從設備 B 反饋的預覽圖像數(shù)據(jù)會通過分布式相機框架的 ChannelSink 回傳到設備 A 的 HAL 層,進而反饋給應用。通過這種方式,設備 A 的應用就可以像使用本地設備一樣使用設備 B 的相機。
相關名詞介紹:
主控端(source):控制端,通過調用分布式相機能力,使用被控端的攝像頭進行預覽、拍照、錄像等功能。
被控端(sink):被控制端,通過分布式相機接收主控端的命令,使用本地攝像頭為主控端提供圖像數(shù)據(jù)。
現(xiàn)在我們要實現(xiàn)分布式相機,在主控端調用被控端相機,實現(xiàn)遠程操作相機,開發(fā)此應用的具體需求:
支持本地相機的預覽、拍照、保存相片、相片縮略圖、快速查看相片、切換攝像頭(如果一臺設備上存在多個攝像頭時)。
同一網(wǎng)絡下,支持分布式 pin 碼認證,遠程連接。
自由切換本地相機和遠程相機。
UI 草圖
從草圖上看,我們簡單的明應用 UI 布局的整體內容:
頂部右上角有個"切換設備"的按鈕,點擊彈窗顯示設備列表,可以實現(xiàn)設備認證與設備切換功能。
中間使用 XComponent 組件實現(xiàn)的相機預覽區(qū)域。
底部分為如下三個部分。
具體如下:
相機縮略圖:顯示當前設備媒體庫中最新的圖片,點擊相機縮略圖按鈕可以查看相關的圖片。
拍照:點擊拍照按鈕,將相機當前幀保存到本地媒體庫中。
切換攝像頭:如果一臺設備有多個攝像頭時,例如相機有前后置攝像頭,點擊切換后會將當前預覽的頁面切換到另外一個攝像頭的圖像。
實現(xiàn)效果
開發(fā)環(huán)境
如下:
系統(tǒng):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
開發(fā)模式:Stage
開發(fā)語言:ets
開發(fā)實踐
本篇主要在應用層的角度實現(xiàn)分布式相機,實現(xiàn)遠程相機與實現(xiàn)本地相機的流程相同,只是使用的相機對象不同,所以我們先完成本地相機的開發(fā),再通過參數(shù)修改相機對象來啟動遠程相機。
①創(chuàng)建項目
②權限聲明
(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() 申請權限
代碼如下:
letpermissionList:Array這里有個獲取縮略圖的功能,主要是獲取媒體庫中根據(jù)時間排序,獲取最新拍照的圖片作為當前需要顯示的縮略圖,實現(xiàn)此方法在后面說 CameraService 類的時候進行詳細介紹。 注意:如果首次啟動應用,在授權完成后需要加載相機,則建議授權放在啟動頁完成,或者在調用相機頁面之前添加一個過渡頁面,主要用于完成權限申請和啟動相機的入口,否則首次完成授權后無法顯示相機預覽,需要退出應用再重新進入才可以正常預覽,這里先簡單說明下,文章后續(xù)會在問題環(huán)節(jié)詳細介紹。=[ "ohos.permission.MEDIA_LOCATION", "ohos.permission.READ_MEDIA", "ohos.permission.WRITE_MEDIA", "ohos.permission.CAMERA", "ohos.permission.MICROPHONE", "ohos.permission.DISTRIBUTED_DATASYNC" ] asyncaboutToAppear(){ console.info(`${TAG}aboutToAppear`) globalThis.cameraAbilityContext.requestPermissionsFromUser(permissionList).then(async(data)=>{ console.info(`${TAG}datapermissions:${JSON.stringify(data.permissions)}`) console.info(`${TAG}dataauthResult:${JSON.stringify(data.authResults)}`) //判斷授權是否完成 letresultCount:number=0 for(letresultofdata.authResults){ if(result===0){ resultCount+=1 } } if(resultCount===permissionList.length){ this.isPermissions=true } awaitthis.initCamera() //獲取縮略圖 this.mCameraService.getThumbnail(this.functionBackImpl) }) }
③UI 布局
說明:UI 如前面截圖所示,實現(xiàn)整體頁面的布局。 頁面中主要使用到 XComponent 組件,用于 EGL/OpenGLES 和媒體數(shù)據(jù)寫入,并顯示在 XComponent 組件。
參看:XComponent 詳細介紹
https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-xcomponent.mdonLoad():XComponent 插件加載完成時的回調,在插件完成時可以獲取**ID并初始化相機。
XComponentController:XComponent 組件控制器,可以綁定至 XComponent 組件,通過 getXComponent/**aceId() 獲取 XComponent 對應的/**aceID。
代碼如下:
@State@Watch('selectedIndexChange')selectIndex:number=0 //設備列表 @Statedevices:Array(1)啟動系統(tǒng)相冊=[] //設備選擇彈窗 privatedialogController:CustomDialogController=newCustomDialogController({ builder:DeviceDialog({ deviceList:$devices, selectIndex:$selectIndex, }), autoCancel:true, alignment:DialogAlignment.Center }) @StatecurPictureWidth:number=70 @StatecurPictureHeight:number=70 @StatecurThumbnailWidth:number=70 @StatecurThumbnailHeight:number=70 @StatecurSwitchAngle:number=0 @StateId:string='' @Statethumbnail:image.PixelMap=undefined @StateresourceUri:string='' @StateisSwitchDeviceing:boolean=false//是否正在切換相機 privateisInitCamera:boolean=false//是否已初始化相機 privateisPermissions:boolean=false//是否完成授權 privatecomponentController:XComponentController=newXComponentController() privatemCurDeviceID:string=Constant.LOCAL_DEVICE_ID//默認本地相機 privatemCurCameraIndex:number=0//默認相機列表中首個相機 privatemCameraService=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}clickdistributedauth.`) 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}XComponentonLoadiscalled`) this.componentController.setXComponentxxxxaceSize({ xxxxWidth:Resolution.DEFAULT_WIDTH, xxxxaceHeight:Resolution.DEFAULT_HEIGHT }) this.id=this.componentController.getXComponentxxxxaceId() console.info(`${TAG}id:${this.id}`) awaitthis.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}launchbundlecom.ohos.photos`) awaitglobalThis.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) }
說明:用戶點擊圖片縮略圖時需要啟動圖片查看,這里直接打開系統(tǒng)相冊,查看相關的圖片。
代碼如下:
awaitglobalThis.cameraAbilityContext.startAbility({ parameters:{uri:'photodetail'}, bundleName:'com.ohos.photos', abilityName:'com.ohos.photos.MainAbility' })
④相機服務 CameraService.ts
(1)CameraService 單例模式,用于提供操作相機相關的業(yè)務
代碼如下:
privatestaticinstance:CameraService=null privateconstructor(){ this.mThumbnailGetter=newThumbnailGetter() } /** *單例 */ publicstaticgetInstance():CameraService{ if(this.instance===null){ this.instance=newCameraService() } returnthis.instance }(2)初始化相機 說明:通過媒體相機提供的 API(@ohos.multimedia.camera)getCameraManager() 獲取相機管理對象 CameraManager,并注冊相機狀態(tài)變化監(jiān)聽器,實時更新相機狀態(tài)。
同時通過 CameraManager…getSupportedCameras() 獲取前期支持的相機設備集合,這里的相機設備包括當前設備上安裝的相機設備和遠程設備上的相機設備。
代碼如下:
/** *初始化 */ publicasyncinitCamera():Promise{ console.info(`${TAG}initCamera`) if(this.mCameraManager===null){ this.mCameraManager=awaitcamera.getCameraManager(globalThis.cameraAbilityContext) //注冊監(jiān)聽相機狀態(tài)變化 this.mCameraManager.on('cameraStatus',(cameraStatusInfo)=>{ console.info(`${TAG}cameraStatus:${JSON.stringify(cameraStatusInfo)}`) }) //獲取相機列表 letcameras:Array =awaitthis.mCameraManager.getSupportedCameras() if(cameras){ this.mCameraCount=cameras.length console.info(`${TAG}mCameraCount:${this.mCameraCount}`) if(this.mCameraCount===0){ returnthis.mCameraCount } for(leti=0;i0){ console.info(`${TAG}displayCameraDevicehasmCameraMap`) //判斷相機列表中是否已經(jīng)存在此相機 letisExist:boolean=false for(letitemofthis.mCameraMap.get(key)){ if(item.cameraId===cameraDevice.cameraId){ isExist=true break } } //添加列表中沒有的相機 if(!isExist){ console.info(`${TAG}displayCameraDevicenotexist,push${cameraDevice.cameraId}`) this.mCameraMap.get(key).push(cameraDevice) }else{ console.info(`${TAG}displayCameraDevicehasexisted`) } }else{ letcameras:Array =[] console.info(`${TAG}displayCameraDevicepush${cameraDevice.cameraId}`) cameras.push(cameraDevice) this.mCameraMap.set(key,cameras) } }
(3)創(chuàng)建相機輸入流
說明:CameraManager.createCameraInput() 可以創(chuàng)建相機輸出流 CameraInput 實例,CameraInput 是在 CaptureSession 會話中使用的相機信息,支持打開相機、關閉相機等能力。 代碼如下:
/** *創(chuàng)建相機輸入流 *@paramcameraIndex相機下標 *@paramdeviceId設備ID */ publicasynccreateCameraInput(cameraIndex?:number,deviceId?:string){ console.info(`${TAG}createCameraInput`) if(this.mCameraManager===null){ console.error(`${TAG}mCameraManagerisnull`) 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?{ console.error(`${TAG}CameraInputerror:${JSON.stringify(error)}`) }) awaitthis.mCameraInput.open() }catch(err){ if(err){ console.error(`${TAG}failedtocreateCameraInput`) } } }(4)相機預覽輸出流
說明:CameraManager.createPreviewOutput() 創(chuàng)建預覽輸出流對象 PreviewOutput,PreviewOutput 繼承 CameraOutput,在 CaptureSession 會話中使用的輸出信息,支持開始輸出預覽流、停止預覽輸出流、釋放預覽輸出流等能力。
/** *創(chuàng)建相機預覽輸出流 */ publicasynccreatePreviewOutput(Id:string,callback:PreviewCallBack){ console.info(`${TAG}createPreviewOutput`) if(this.mCameraManager===null){ console.error(`${TAG}createPreviewOutputmCameraManagerisnull`) return } this.Id=Id console.info(`${TAG}Id${Id}}`) //獲取當前相機設備支持的輸出能力 letcameraOutputCap=awaitthis.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice) if(!cameraOutputCap){ console.error(`${TAG}createPreviewOutputgetSupportedOutputCapabilityerror}`) return } console.info(`${TAG}createPreviewOutputcameraOutputCap${JSON.stringify(cameraOutputCap)}`) letpreviewProfilesArray=cameraOutputCap.previewProfiles letpreviewProfiles: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`) ????????????//?監(jiān)聽預覽幀開始 ????????????this.mPreviewOutput.on('frameStart',?()?=>{ console.info(`${TAG}createPreviewOutputcameraframeStart`) callback.onFrameStart() }) this.mPreviewOutput.on('frameEnd',()=>{ console.info(`${TAG}createPreviewOutputcameraframeEnd`) callback.onFrameEnd() }) this.mPreviewOutput.on('error',(error)=>{ console.error(`${TAG}createPreviewOutputerror:${error}`) }) }catch(err){ console.error(`${TAG}failedtocreatePreviewOutput${err}`) } }
(5)拍照輸出流
說明:CameraManager.createPhotoOutput() 可以創(chuàng)建拍照輸出對象 PhotoOutput,PhotoOutput 繼承 CameraOutput 在拍照會話中使用的輸出信息,支持拍照、判斷是否支持鏡像拍照、釋放資源、監(jiān)聽拍照開始、拍照幀輸出捕獲、拍照結束等能力。
代碼如下:
/** *創(chuàng)建拍照輸出流 */ publicasynccreatePhotoOutput(functionCallback:FunctionCallBack){ console.info(`${TAG}createPhotoOutput`) if(!this.mCameraManager){ console.error(`${TAG}createPhotoOutputmCameraManagerisnull`) return } //通過寬、高、圖片格式、容量創(chuàng)建ImageReceiver實例 constreceiver:image.ImageReceiver=image.createImageReceiver(Resolution.DEFAULT_WIDTH,Resolution.DEFAULT_HEIGHT,image.ImageFormat.JPEG,8) constimageId:string=awaitreceiver.getReceivingxxxxaceId() console.info(`${TAG}createPhotoOutputimageId:${imageId}`) letcameraOutputCap=awaitthis.mCameraManager.getSupportedOutputCapability(this.mCurCameraDevice) console.info(`${TAG}createPhotoOutputcameraOutputCap${cameraOutputCap}`) if(!cameraOutputCap){ console.error(`${TAG}createPhotoOutputgetSupportedOutputCapabilityerror}`) return } letphotoProfilesArray=cameraOutputCap.photoProfiles letphotoProfiles: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() 可以創(chuàng)建相機的會話類,保存相機運行所需要的所有資源 CameraInput、CameraOutput,并向相機設備申請完成相機拍照或錄像功能。 CaptureSession 對象提供了開始配置會話、添加 CameraInput 到會話、添加 CameraOutput 到會話、提交配置信息、開始會話、停止會話、釋放等能力。
代碼如下:
publicasynccreateSession(id:string){ console.info(`${TAG}createSession`) console.info(`${TAG}createSessionid${id}}`) this.id=id this.mCaptureSession=awaitthis.mCameraManager.createCaptureSession() console.info(`${TAG}createSessionmCaptureSession${this.mCaptureSession}`) this.mCaptureSession.on('error',(error)=>{ console.error(`${TAG}CaptureSessionerror${JSON.stringify(error)}`) }) try{ awaitthis.mCaptureSession?.beginConfig() awaitthis.mCaptureSession?.addInput(this.mCameraInput) if(this.mPhotoOutput!=null){ console.info(`${TAG}createSessionaddOutputPhotoOutput`) awaitthis.mCaptureSession?.addOutput(this.mPhotoOutput) } awaitthis.mCaptureSession?.addOutput(this.mPreviewOutput) }catch(err){ if(err){ console.error(`${TAG}createSessionbeginConfigfailerr:${JSON.stringify(err)}`) } } try{ awaitthis.mCaptureSession?.commitConfig() }catch(err){ if(err){ console.error(`${TAG}createSessioncommitConfigfailerr:${JSON.stringify(err)}`) } } try{ awaitthis.mCaptureSession?.start() }catch(err){ if(err){ console.error(`${TAG}createSessionstartfailerr:${JSON.stringify(err)}`) } } console.info(`${TAG}createSessionmCaptureSessionstart`) }⑤拍照 說明:通過 PhotoOutput.capture() 可以實現(xiàn)拍照功能。 代碼如下:
/** *拍照 */ publicasynctakePicture(){ console.info(`${TAG}takePicture`) if(!this.mCaptureSession){ console.info(`${TAG}takePicturesessionisrelease`) return } if(!this.mPhotoOutput){ console.info(`${TAG}takePicturemPhotoOutputisnull`) return } try{ constphotoCaptureSetting:camera.PhotoCaptureSetting={ quality:camera.QualityLevel.QUALITY_LEVEL_HIGH, rotation:camera.ImageRotation.ROTATION_0, location:{ latitude:0, longitude:0, altitude:0 }, mirror:false } awaitthis.mPhotoOutput.capture(photoCaptureSetting) }catch(err){ console.error(`${TAG}takePictureerr:${JSON.stringify(err)}`) } }⑥保存圖片 SaveCameraAsset
說明:SaveCameraAsset.ts 主要用于保存拍攝的圖片,即是調用拍照操作后,會觸發(fā)圖片接收監(jiān)聽器,在將圖片的字節(jié)流進行寫入本地文件操作。
代碼如下:
/** *保存相機拍照的資源 */ importimagefrom'@ohos.multimedia.image' importmediaLibraryfrom'@ohos.multimedia.mediaLibrary' import{FunctionCallBack}from'../model/CameraService' importDateTimeUtilfrom'../utils/DateTimeUtil' importfileIOfrom'@ohos.file.fs'; importThumbnailGetterfrom'../model/ThumbnailGetter' letphotoUri:string//圖片地址 constTAG:string='SaveCameraAsset' exportdefaultclassSaveCameraAsset{ privatelastSaveTime:string='' privatesaveIndex:number=0 constructor(){ } publicgetPhotoUri():string{ console.info(`${TAG}getPhotoUri=${photoUri}`) returnphotoUri } /** *保存拍照圖片 *@paramimageReceiver圖像接收對象 *@paramthumbWidth縮略圖寬度 *@paramthumbHeight縮略圖高度 *@paramcallback回調 */ publicsaveImage(imageReceiver:image.ImageReceiver,thumbWidth:number,thumbHeight:number,thumbnailGetter:ThumbnailGetter,callback:FunctionCallBack){ console.info(`${TAG}saveImage`) constmDateTimeUtil=newDateTimeUtil() constfileKeyObj=mediaLibrary.FileKey constmediaType=mediaLibrary.MediaType.IMAGE letbuffer=newArrayBuffer(4096) constmedia=mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext)//獲取媒體庫實例 //接收圖片回調 imageReceiver.on('imageArrival',async()=>{ console.info(`${TAG}saveImageImageArrival`) //使用當前時間命名 constdisplayName=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}saveImagefailedtogetvalidimageerror=${err}`) return } //根據(jù)圖像的組件類型從圖像中獲取組件緩存4-JPEG類型 imageObj.getComponent(image.ComponentType.JPEG,async(errMsg,imgComponent)=>{ if(imgComponent===undefined){ console.error(`${TAG}getComponentfailedtogetvalidbuffererror=${errMsg}`) return } if(imgComponent.byteBuffer){ console.info(`${TAG}getComponentimgComponent.byteBuffer${imgComponent.byteBuffer}`) buffer=imgComponent.byteBuffer }else{ console.info(`${TAG}getComponentimgComponent.byteBufferisundefined`) } awaitimageObj.release() }) }) letpublicPath:string=awaitmedia.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA) console.info(`${TAG}saveImagepublicPath=${publicPath}`) //創(chuàng)建媒體資源返回提供封裝文件屬性 constdataUri:mediaLibrary.FileAsset=awaitmedia.createAsset(mediaType,displayName,publicPath) //媒體文件資源創(chuàng)建成功,將拍照的數(shù)據(jù)寫入到媒體資源 if(dataUri!==undefined){ photoUri=dataUri.uri console.info(`${TAG}saveImagephotoUri:${photoUri}`) constargs=dataUri.id.toString() console.info(`${TAG}saveImageid:${args}`) //通過ID查找媒體資源 constfetchOptions:mediaLibrary.MediaFetchOptions={ selections:`${fileKeyObj.ID}=?`, selectionArgs:[args] } console.info(`${TAG}saveImagefetchOptions:${JSON.stringify(fetchOptions)}`) constfetchFileResult=awaitmedia.getFileAssets(fetchOptions) constfileAsset=awaitfetchFileResult.getAllObject()//獲取文件檢索結果中的所有文件資 if(fileAsset!=undefined){ fileAsset.forEach((dataInfo)=>{ dataInfo.open('Rw').then((fd)=>{//RW是讀寫方式打開文件獲取fd console.info(`${TAG}saveImagedataInfo.opencalled.fd:${fd}`) //將緩存圖片流寫入資源 fileIO.write(fd,buffer).then(()=>{ console.info(`${TAG}saveImagefileIO.writecalled`) dataInfo.close(fd).then(()=>{ console.info(`${TAG}saveImagedataInfo.closecalled`) //獲取資源縮略圖 thumbnailGetter.getThumbnailInfo(thumbWidth,thumbHeight,photoUri).then((thumbnail=>{ if(thumbnail===undefined){ console.error(`${TAG}saveImagegetThumbnailInfoundefined`) callback.onCaptureFailure() }else{ console.info(`${TAG}photoUri:${photoUri}PixelBytesNumber:${thumbnail.getPixelBytesNumber()}`) callback.onCaptureSuccess(thumbnail,photoUri) } })) }).catch(error=>{ console.error(`${TAG}saveImagecloseiserror${JSON.stringify(error)}`) }) }) }) }) }else{ console.error(`${TAG}saveImagefileAsset:isnull`) } }else{ console.error(`${TAG}saveImagephotoUriisnull`) } }) } /** *檢測文件名稱 *@paramfileName文件名稱 *如果同一時間有多張圖片,則使用時間_index命名 */ privatecheckName(fileName:string):string{ if(this.lastSaveTime==fileName){ this.saveIndex++ return`${fileName}_${this.saveIndex}` } this.lastSaveTime=fileName this.saveIndex=0 returnfileName } }
⑦獲取縮略圖
說明:主要通過獲取當前媒體庫中根據(jù)時間排序,獲取最新的圖片并縮放圖片大小后返回。
代碼如下:
/** *獲取縮略圖 *@paramcallback */ publicgetThumbnail(callback:FunctionCallBack){ console.info(`${TAG}getThumbnail`) this.mThumbnailGetter.getThumbnailInfo(Resolution.THUMBNAIL_WIDTH,Resolution.THUMBNAIL_HEIGHT).then((thumbnail)=>{ console.info(`${TAG}getThumbnailthumbnail=${thumbnail}`) callback.thumbnail(thumbnail) }) }(1)ThumbnailGetter.ts 說明:實現(xiàn)獲取縮略圖的對象。 代碼如下:
/** *縮略圖處理器 */ importmediaLibraryfrom'@ohos.multimedia.mediaLibrary'; importimagefrom'@ohos.multimedia.image'; constTAG:string='ThumbnailGetter' exportdefaultclassThumbnailGetter{ publicasyncgetThumbnailInfo(width:number,height:number,uri?:string):Promise⑧釋放資源 說明:在相機設備切換時,如前后置攝像頭切換或者不同設備之間的攝像頭切換時都需要先釋放資源,再重新創(chuàng)建新的相機會話才可以正常運行,釋放的資源包括:釋放相機輸入流、預覽輸出流、拍照輸出流、會話。 代碼如下:{ console.info(`${TAG}getThumbnailInfo`) //文件關鍵信息 constfileKeyObj=mediaLibrary.FileKey //獲取媒體資源公共路徑 constmedia:mediaLibrary.MediaLibrary=mediaLibrary.getMediaLibrary(globalThis.cameraAbilityContext) letpublicPath:string=awaitmedia.getPublicDirectory(mediaLibrary.DirectoryType.DIR_CAMERA) console.info(`${TAG}publicPath=${publicPath}`) letfetchOptions: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}getThumbnailInfofetchOptions:${JSON.stringify(fetchOptions)}}`) constfetchFileResult=awaitmedia.getFileAssets(fetchOptions)//文件檢索結果集 constcount=fetchFileResult.getCount() console.info(`${TAG}count=${count}`) if(count==0){ returnundefined } //獲取結果集合中的最后一張圖片 constlastFileAsset=awaitfetchFileResult.getFirstObject() if(lastFileAsset==null){ console.error(`${TAG}getThumbnailInfolastFileAssetisnull`) returnundefined } constthumbnailPixelMap=lastFileAsset.getThumbnail({ width:width, height:height }) console.info(`${TAG}getThumbnailInfothumbnailPixelMap${JSON.stringify(thumbnailPixelMap)}}`) returnthumbnailPixelMap } }
/** *釋放相機輸入流 */ publicasyncreleaseCameraInput(){ console.info(`${TAG}releaseCameraInput`) if(this.mCameraInput){ try{ awaitthis.mCameraInput.release() }catch(err){ console.error(`${TAG}releaseCameraInput${err}}`) } this.mCameraInput=null } } /** *釋放預覽輸出流 */ publicasyncreleasePreviewOutput(){ console.info(`${TAG}releasePreviewOutput`) if(this.mPreviewOutput){ awaitthis.mPreviewOutput.release() this.mPreviewOutput=null } } /** *釋放拍照輸出流 */ publicasyncreleasePhotoOutput(){ console.info(`${TAG}releasePhotoOutput`) if(this.mPhotoOutput){ awaitthis.mPhotoOutput.release() this.mPhotoOutput=null } } publicasyncreleaseSession(){ console.info(`${TAG}releaseSession`) if(this.mCaptureSession){ awaitthis.mCaptureSession.stop() console.info(`${TAG}releaseSessionstop`) awaitthis.mCaptureSession.release() console.info(`${TAG}releaseSessionrelease`) this.mCaptureSession=null console.info(`${TAG}releaseSessionnull`) } }
至此,總結下,需要實現(xiàn)相機預覽、拍照功能:
通過 camera 媒體 api 提供的 camera.getCameraManager() 獲取 CameraManager 相機管理類。
通過相機管理類型創(chuàng)建相機預覽與拍照需要的輸入流(createCameraInput)和輸出流(createPreviewOutPut、createPhotoOutput),同時創(chuàng)建相關會話管理(createCaptureSession)
將輸入流、輸出流添加到會話中,并啟動會話
拍照可以直接使用 PhotoOutput.capture 執(zhí)行拍照,并將拍照結果保存到媒體
在退出相機應用時,需要注意釋放相關的資源。
因為分布式相機的應用開發(fā)內容比較長,這篇只說到主控端相機設備預覽與拍照功能,下一篇會將結合分布式相關內容完成主控端設備調用遠程相機進行預覽的功能。
審核編輯:湯梓紅
-
攝像頭
+關注
關注
59文章
4836瀏覽量
95599 -
相機
+關注
關注
4文章
1350瀏覽量
53581 -
Module
+關注
關注
0文章
68瀏覽量
12851 -
SDK
+關注
關注
3文章
1035瀏覽量
45900 -
OpenHarmony
+關注
關注
25文章
3713瀏覽量
16254
原文標題:OpenHarmony上實現(xiàn)分布式相機
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術社區(qū)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論