OpenHarmony上实现分布式相机
作者:徐金生
最近陆续看到各社区上有关OpenHarmony媒体相机的使用开发文档,相机对于富设备来说必不可少,日常中我们经常使用相机完成拍照、人脸验证等。
OpenHarmony系统一个重要的能力就是分布式,对于分布式相机我也倍感兴趣,之前看到官方对分布式相机的一些说明,这里简单介绍下。
有兴趣可以查看官方文档:分布式相机部件https:gitee。comopenharmonydistributedhardwaredistributedcamera
分布式框架图
分布式相机框架(DistributedHardware)分为主控端和被控端。假设:设备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组件实现的相机预览区域。底部分为如下三个部分。
具体如下:相机缩略图:显示当前设备媒体库中最新的图片,点击相机缩略图按钮可以查看相关的图片。拍照:点击拍照按钮,将相机当前帧保存到本地媒体库中。切换摄像头:如果一台设备有多个摄像头时,例如相机有前后置摄像头,点击切换后会将当前预览的页面切换到另外一个摄像头的图像。
实现效果
开发环境
如下:系统:OpenHarmony3。2beta4OpenHarmony3。2beta5设备:DAYU200IDE:DevEcoStudio3。0Release,BuildVersion:3。0。0。993,builtonSeptember4,2022SDK:Full3。2。9。2开发模式:Stage开发语言:ets
开发实践
本篇主要在应用层的角度实现分布式相机,实现远程相机与实现本地相机的流程相同,只是使用的相机对象不同,所以我们先完成本地相机的开发,再通过参数修改相机对象来启动远程相机。创建项目
权限声明(1)module。json配置权限
说明:在module模块下添加权限声明,权限的详细说明https:gitee。comopenharmonydocsblobmasterzhcnapplicationdevsecuritypermissionlist。mdrequestPermissions:〔{name:ohos。permission。REQUIREFORM},{name:ohos。permission。MEDIALOCATION},{name:ohos。permission。MODIFYAUDIOSETTINGS},{name:ohos。permission。READMEDIA},{name:ohos。permission。WRITEMEDIA},{name:ohos。permission。GETBUNDLEINFOPRIVILEGED},{name:ohos。permission。CAMERA},{name:ohos。permission。MICROPHONE},{name:ohos。permission。DISTRIBUTEDDATASYNC}〕(2)在index。ets页面的初始化aboutToAppear()申请权限
代码如下:letpermissionList:Arraystring〔ohos。permission。MEDIALOCATION,ohos。permission。READMEDIA,ohos。permission。WRITEMEDIA,ohos。permission。CAMERA,ohos。permission。MICROPHONE,ohos。permission。DISTRIBUTEDDATASYNC〕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:number0for(letresultofdata。authResults){if(result0){resultCount1}}if(resultCountpermissionList。length){this。isPermissionstrue}awaitthis。initCamera()获取缩略图this。mCameraService。getThumbnail(this。functionBackImpl)})}
这里有个获取缩略图的功能,主要是获取媒体库中根据时间排序,获取最新拍照的图片作为当前需要显示的缩略图,实现此方法在后面说CameraService类的时候进行详细介绍。
注意:如果首次启动应用,在授权完成后需要加载相机,则建议授权放在启动页完成,或者在调用相机页面之前添加一个过渡页面,主要用于完成权限申请和启动相机的入口,否则首次完成授权后无法显示相机预览,需要退出应用再重新进入才可以正常预览,这里先简单说明下,文章后续会在问题环节详细介绍。UI布局
说明:UI如前面截图所示,实现整体页面的布局。
页面中主要使用到XComponent组件,用于EGLOpenGLES和媒体数据写入,并显示在XComponent组件。
参看:XComponent详细介绍https:gitee。comopenharmonydocsblobmasterzhcnapplicationdevreferencearkuitstsbasiccomponentsxcomponent。md
onLoad():XComponent插件加载完成时的回调,在插件完成时可以获取ID并初始化相机。
XComponentController:XComponent组件控制器,可以绑定至XComponent组件,通过getXComponentaceId()获取XComponent对应的aceID。
代码如下:StateWatch(selectedIndexChange)selectIndex:number0设备列表Statedevices:ArraydeviceManager。DeviceInfo〔〕设备选择弹窗privatedialogController:CustomDialogControllernewCustomDialogController({builder:DeviceDialog({deviceList:devices,selectIndex:selectIndex,}),autoCancel:true,alignment:DialogAlignment。Center})StatecurPictureWidth:number70StatecurPictureHeight:number70StatecurThumbnailWidth:number70StatecurThumbnailHeight:number70StatecurSwitchAngle:number0StateId:stringStatethumbnail:image。PixelMapundefinedStateresourceUri:stringStateisSwitchDeviceing:booleanfalse是否正在切换相机privateisInitCamera:booleanfalse是否已初始化相机privateisPermissions:booleanfalse是否完成授权privatecomponentController:XComponentControllernewXComponentController()privatemCurDeviceID:stringConstant。LOCALDEVICEID默认本地相机privatemCurCameraIndex:number0默认相机列表中首个相机privatemCameraServiceCameraService。getInstance()build(){Stack({alignContent:Alignment。Center}){Column(){Row({space:20}){Image(r(app。media。iccamerapublicsetting))。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。DEFAULTWIDTH,xxxxaceHeight:Resolution。DEFAULTHEIGHT})this。idthis。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。screenpic))。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。curThumbnailWidth70this。curThumbnailHeight70})}},(){this。curThumbnailWidth60this。curThumbnailHeight60})})}。width(33)。alignItems(HorizontalAlign。Start)Column(){Image(r(app。media。iconpicture))。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。curPictureWidth70this。curPictureHeight70})}},(){this。curPictureWidth60this。curPictureHeight60})})}。width(33)Column(){Image(r(app。media。iconswitch))。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。curSwitchAngle0})}},(){this。curSwitchAngle180})})}。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。loadswitchcamera))。width(400)。height(306)。objectFit(ImageFit。Fill)Text(r(app。string。switchcamera))。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)启动系统相册
说明:用户点击图片缩略图时需要启动图片查看,这里直接打开系统相册,查看相关的图片。
代码如下:awaitglobalThis。cameraAbilityContext。startAbility({parameters:{uri:photodetail},bundleName:com。ohos。photos,abilityName:com。ohos。photos。MainAbility})
相机服务CameraService。ts(1)CameraService单例模式,用于提供操作相机相关的业务
代码如下:privatestaticinstance:CameraServicenullprivateconstructor(){this。mThumbnailGetternewThumbnailGetter()}单例publicstaticgetInstance():CameraService{if(this。instancenull){this。instancenewCameraService()}returnthis。instance}
(2)初始化相机
说明:通过媒体相机提供的API(ohos。multimedia。camera)getCameraManager()获取相机管理对象CameraManager,并注册相机状态变化监听器,实时更新相机状态。
同时通过CameraManagergetSupportedCameras()获取前期支持的相机设备集合,这里的相机设备包括当前设备上安装的相机设备和远程设备上的相机设备。
代码如下:初始化publicasyncinitCamera():Promisenumber{console。info({TAG}initCamera)if(this。mCameraManagernull){this。mCameraManagerawaitcamera。getCameraManager(globalThis。cameraAbilityContext)注册监听相机状态变化this。mCameraManager。on(cameraStatus,(cameraStatusInfo){console。info({TAG}cameraStatus:{JSON。stringify(cameraStatusInfo)})})获取相机列表letcameras:Arraycamera。CameraDeviceawaitthis。mCameraManager。getSupportedCameras()if(cameras){this。mCameraCountcameras。lengthconsole。info({TAG}mCameraCount:{this。mCameraCount})if(this。mCameraCount0){returnthis。mCameraCount}for(leti0;icameras。length;i){console。info({TAG}CameraInfo)consttempCameraId:stringcameras〔i〕。cameraIdconsole。info({TAG}cameraid:{tempCameraId})console。info({TAG}cameraPosition:{cameras〔i〕。cameraPosition})console。info({TAG}cameraType:{cameras〔i〕。cameraType})constconnectionTypecameras〔i〕。connectionTypeconsole。info({TAG}connectionType:{connectionType})CameraPosition0未知未知1后置2前置CameraType0未知类型1广角2超广角3长焦4带景深信息connectionType0内置相机1USB连接相机2远程连接相机判断本地相机还是远程相机if(connectionTypecamera。ConnectionType。CAMERACONNECTIONBUILTIN){本地相机this。displayCameraDevice(Constant。LOCALDEVICEID,cameras〔i〕)}elseif(connectionTypecamera。ConnectionType。CAMERACONNECTIONREMOTE){远程相机相机ID格式:deviceIDCameracameraID例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7Cameralcam001constcameraKey:stringtempCameraId。split(Camera)〔0〕console。info({TAG}cameraKey:{cameraKey})this。displayCameraDevice(cameraKey,cameras〔i〕)}}todotest选择首个相机this。mCurCameraDevicecameras〔0〕console。info({TAG}mCurCameraDevice:{this。mCurCameraDevice。cameraId})}}returnthis。mCameraCount}处理相机设备paramkeyparamcameraDeviceprivatedisplayCameraDevice(key:string,cameraDevice:camera。CameraDevice){console。info({TAG}displayCameraDevice{key})if(this。mCameraMap。has(key)this。mCameraMap。get(key)?。length0){console。info({TAG}displayCameraDevicehasmCameraMap)判断相机列表中是否已经存在此相机letisExist:booleanfalsefor(letitemofthis。mCameraMap。get(key)){if(item。cameraIdcameraDevice。cameraId){isExisttruebreak}}添加列表中没有的相机if(!isExist){console。info({TAG}displayCameraDevicenotexist,push{cameraDevice。cameraId})this。mCameraMap。get(key)。push(cameraDevice)}else{console。info({TAG}displayCameraDevicehasexisted)}}else{letcameras:Arraycamera。CameraDevice〔〕console。info({TAG}displayCameraDevicepush{cameraDevice。cameraId})cameras。push(cameraDevice)this。mCameraMap。set(key,cameras)}}
(3)创建相机输入流
说明:CameraManager。createCameraInput()可以创建相机输出流CameraInput实例,CameraInput是在CaptureSession会话中使用的相机信息,支持打开相机、关闭相机等能力。
代码如下:创建相机输入流paramcameraIndex相机下标paramdeviceId设备IDpublicasynccreateCameraInput(cameraIndex?:number,deviceId?:string){console。info({TAG}createCameraInput)if(this。mCameraManagernull){console。error({TAG}mCameraManagerisnull)return}if(this。mCameraCount0){console。error({TAG}notcameradevice)return}if(this。mCameraInput){this。mCameraInput。release()}if(deviceIdthis。mCameraMap。has(deviceId)){if(cameraIndexthis。mCameraMap。get(deviceId)?。length){this。mCurCameraDevicethis。mCameraMap。get(deviceId)〔cameraIndex〕}else{this。mCurCameraDevicethis。mCameraMap。get(deviceId)〔0〕}}console。info({TAG}mCurCameraDevice:{this。mCurCameraDevice。cameraId})try{this。mCameraInputawaitthis。mCameraManager。createCameraInput(this。mCurCameraDevice)console。info({TAG}mCameraInput:{JSON。stringify(this。mCameraInput)})this。mCameraInput。on(error,this。mCurCameraDevice,(error){console。error({TAG}CameraInputerror:{JSON。stringify(error)})})awaitthis。mCameraInput。open()}catch(err){if(err){console。error({TAG}failedtocreateCameraInput)}}}
(4)相机预览输出流
说明:CameraManager。createPreviewOutput()创建预览输出流对象PreviewOutput,PreviewOutput继承CameraOutput,在CaptureSession会话中使用的输出信息,支持开始输出预览流、停止预览输出流、释放预览输出流等能力。创建相机预览输出流publicasynccreatePreviewOutput(Id:string,callback:PreviewCallBack){console。info({TAG}createPreviewOutput)if(this。mCameraManagernull){console。error({TAG}createPreviewOutputmCameraManagerisnull)return}this。IdIdconsole。info({TAG}Id{Id}})获取当前相机设备支持的输出能力letcameraOutputCapawaitthis。mCameraManager。getSupportedOutputCapability(this。mCurCameraDevice)if(!cameraOutputCap){console。error({TAG}createPreviewOutputgetSupportedOutputCapabilityerror})return}console。info({TAG}createPreviewOutputcameraOutputCap{JSON。stringify(cameraOutputCap)})letpreviewProfilesArraycameraOutputCap。previewProfilesletpreviewProfiles:camera。Profileif(!previewProfilesArraypreviewProfilesArray。length0){console。error({TAG}createPreviewOutputpreviewProfilesArrayerror})previewProfiles{format:1,size:{width:640,height:480}}}else{console。info({TAG}createPreviewOutputpreviewProfilelength{previewProfilesArray。length})previewProfilespreviewProfilesArray〔0〕}console。info({TAG}createPreviewOutputpreviewProfile〔0〕{JSON。stringify(previewProfiles)})try{this。mPreviewOutputawaitthis。mCameraManager。createPreviewOutput(previewProfiles,id)console。info({TAG}createPreviewOutputsuccess)监听预览帧开始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()可以创建拍照输出对象PhotoOutput,PhotoOutput继承CameraOutput在拍照会话中使用的输出信息,支持拍照、判断是否支持镜像拍照、释放资源、监听拍照开始、拍照帧输出捕获、拍照结束等能力。
代码如下:创建拍照输出流publicasynccreatePhotoOutput(functionCallback:FunctionCallBack){console。info({TAG}createPhotoOutput)if(!this。mCameraManager){console。error({TAG}createPhotoOutputmCameraManagerisnull)return}通过宽、高、图片格式、容量创建ImageReceiver实例constreceiver:image。ImageReceiverimage。createImageReceiver(Resolution。DEFAULTWIDTH,Resolution。DEFAULTHEIGHT,image。ImageFormat。JPEG,8)constimageId:stringawaitreceiver。getReceivingxxxxaceId()console。info({TAG}createPhotoOutputimageId:{imageId})letcameraOutputCapawaitthis。mCameraManager。getSupportedOutputCapability(this。mCurCameraDevice)console。info({TAG}createPhotoOutputcameraOutputCap{cameraOutputCap})if(!cameraOutputCap){console。error({TAG}createPhotoOutputgetSupportedOutputCapabilityerror})return}letphotoProfilesArraycameraOutputCap。photoProfilesletphotoProfiles:camera。Profileif(!photoProfilesArrayphotoProfilesArray。length0){使用自定义的配置photoProfiles{format:2000,size:{width:1280,height:960}}}else{console。info({TAG}createPhotoOutputphotoProfilelength{photoProfilesArray。length})photoProfilesphotoProfilesArray〔0〕}console。info({TAG}createPhotoOutputphotoProfile{JSON。stringify(photoProfiles)})try{this。mPhotoOutputawaitthis。mCameraManager。createPhotoOutput(photoProfiles,id)console。info({TAG}createPhotoOutputmPhotoOutputsuccess)保存图片this。mSaveCameraAsset。saveImage(receiver,Resolution。THUMBNAILWIDTH,Resolution。THUMBNAILHEIGHT,this。mThumbnailGetter,functionCallback)}catch(err){console。error({TAG}createPhotoOutputfailedtocreatePhotoOutput{err})}}
this。mSaveCameraAsset。saveImage(),这里将保存拍照的图片进行封装SaveCameraAsset。ts,后面会单独介绍。(6)会话管理
说明:通过CameraManager。createCaptureSession()可以创建相机的会话类,保存相机运行所需要的所有资源CameraInput、CameraOutput,并向相机设备申请完成相机拍照或录像功能。
CaptureSession对象提供了开始配置会话、添加CameraInput到会话、添加CameraOutput到会话、提交配置信息、开始会话、停止会话、释放等能力。
代码如下:publicasynccreateSession(id:string){console。info({TAG}createSession)console。info({TAG}createSessionid{id}})this。ididthis。mCaptureSessionawaitthis。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()可以实现拍照功能。
代码如下:拍照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。QUALITYLEVELHIGH,rotation:camera。ImageRotation。ROTATION0,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主要用于保存拍摄的图片,即是调用拍照操作后,会触发图片接收监听器,在将图片的字节流进行写入本地文件操作。
代码如下:保存相机拍照的资源importimagefromohos。multimedia。imageimportmediaLibraryfromohos。multimedia。mediaLibraryimport{FunctionCallBack}from。。modelCameraServiceimportDateTimeUtilfrom。。utilsDateTimeUtilimportfileIOfromohos。file。fs;importThumbnailGetterfrom。。modelThumbnailGetterletphotoUri:string图片地址constTAG:stringSaveCameraAssetexportdefaultclassSaveCameraAsset{privatelastSaveTime:stringprivatesaveIndex:number0constructor(){}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)constmDateTimeUtilnewDateTimeUtil()constfileKeyObjmediaLibrary。FileKeyconstmediaTypemediaLibrary。MediaType。IMAGEletbuffernewArrayBuffer(4096)constmediamediaLibrary。getMediaLibrary(globalThis。cameraAbilityContext)获取媒体库实例接收图片回调imageReceiver。on(imageArrival,async(){console。info({TAG}saveImageImageArrival)使用当前时间命名constdisplayNamethis。checkName(IMG{mDateTimeUtil。getDate()}{mDateTimeUtil。getTime()})。jpgconsole。info({TAG}displayName{displayName}})imageReceiver。readNextImage((err,imageObj:image。Image){if(imageObjundefined){console。error({TAG}saveImagefailedtogetvalidimageerror{err})return}根据图像的组件类型从图像中获取组件缓存4JPEG类型imageObj。getComponent(image。ComponentType。JPEG,async(errMsg,imgComponent){if(imgComponentundefined){console。error({TAG}getComponentfailedtogetvalidbuffererror{errMsg})return}if(imgComponent。byteBuffer){console。info({TAG}getComponentimgComponent。byteBuffer{imgComponent。byteBuffer})bufferimgComponent。byteBuffer}else{console。info({TAG}getComponentimgComponent。byteBufferisundefined)}awaitimageObj。release()})})letpublicPath:stringawaitmedia。getPublicDirectory(mediaLibrary。DirectoryType。DIRCAMERA)console。info({TAG}saveImagepublicPath{publicPath})创建媒体资源返回提供封装文件属性constdataUri:mediaLibrary。FileAssetawaitmedia。createAsset(mediaType,displayName,publicPath)媒体文件资源创建成功,将拍照的数据写入到媒体资源if(dataUri!undefined){photoUridataUri。uriconsole。info({TAG}saveImagephotoUri:{photoUri})constargsdataUri。id。toString()console。info({TAG}saveImageid:{args})通过ID查找媒体资源constfetchOptions:mediaLibrary。MediaFetchOptions{selections:{fileKeyObj。ID}?,selectionArgs:〔args〕}console。info({TAG}saveImagefetchOptions:{JSON。stringify(fetchOptions)})constfetchFileResultawaitmedia。getFileAssets(fetchOptions)constfileAssetawaitfetchFileResult。getAllObject()获取文件检索结果中的所有文件资if(fileAsset!undefined){fileAsset。forEach((dataInfo){dataInfo。open(Rw)。then((fd){RW是读写方式打开文件获取fdconsole。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(thumbnailundefined){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。lastSaveTimefileName){this。saveIndexreturn{fileName}{this。saveIndex}}this。lastSaveTimefileNamethis。saveIndex0returnfileName}}获取缩略图
说明:主要通过获取当前媒体库中根据时间排序,获取最新的图片并缩放图片大小后返回。
代码如下:获取缩略图paramcallbackpublicgetThumbnail(callback:FunctionCallBack){console。info({TAG}getThumbnail)this。mThumbnailGetter。getThumbnailInfo(Resolution。THUMBNAILWIDTH,Resolution。THUMBNAILHEIGHT)。then((thumbnail){console。info({TAG}getThumbnailthumbnail{thumbnail})callback。thumbnail(thumbnail)})}
(1)ThumbnailGetter。ts
说明:实现获取缩略图的对象。
代码如下:缩略图处理器importmediaLibraryfromohos。multimedia。mediaLibrary;importimagefromohos。multimedia。image;constTAG:stringThumbnailGetterexportdefaultclassThumbnailGetter{publicasyncgetThumbnailInfo(width:number,height:number,uri?:string):Promiseimage。PixelMapundefined{console。info({TAG}getThumbnailInfo)文件关键信息constfileKeyObjmediaLibrary。FileKey获取媒体资源公共路径constmedia:mediaLibrary。MediaLibrarymediaLibrary。getMediaLibrary(globalThis。cameraAbilityContext)letpublicPath:stringawaitmedia。getPublicDirectory(mediaLibrary。DirectoryType。DIRCAMERA)console。info({TAG}publicPath{publicPath})letfetchOptions:mediaLibrary。MediaFetchOptions{selections:{fileKeyObj。RELATIVEPATH}?,检索条件RELATIVEPATH相对公共目录的路径selectionArgs:〔publicPath〕检索条件值}if(uri){fetchOptions。uriuri文件的URI}else{fetchOptions。orderfileKeyObj。DATEADDEDDESC}console。info({TAG}getThumbnailInfofetchOptions:{JSON。stringify(fetchOptions)}})constfetchFileResultawaitmedia。getFileAssets(fetchOptions)文件检索结果集constcountfetchFileResult。getCount()console。info({TAG}count{count})if(count0){returnundefined}获取结果集合中的最后一张图片constlastFileAssetawaitfetchFileResult。getFirstObject()if(lastFileAssetnull){console。error({TAG}getThumbnailInfolastFileAssetisnull)returnundefined}constthumbnailPixelMaplastFileAsset。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。mCameraInputnull}}释放预览输出流publicasyncreleasePreviewOutput(){console。info({TAG}releasePreviewOutput)if(this。mPreviewOutput){awaitthis。mPreviewOutput。release()this。mPreviewOutputnull}}释放拍照输出流publicasyncreleasePhotoOutput(){console。info({TAG}releasePhotoOutput)if(this。mPhotoOutput){awaitthis。mPhotoOutput。release()this。mPhotoOutputnull}}publicasyncreleaseSession(){console。info({TAG}releaseSession)if(this。mCaptureSession){awaitthis。mCaptureSession。stop()console。info({TAG}releaseSessionstop)awaitthis。mCaptureSession。release()console。info({TAG}releaseSessionrelease)this。mCaptureSessionnullconsole。info({TAG}releaseSessionnull)}}
至此,总结下,需要实现相机预览、拍照功能:通过camera媒体api提供的camera。getCameraManager()获取CameraManager相机管理类。通过相机管理类型创建相机预览与拍照需要的输入流(createCameraInput)和输出流(createPreviewOutPut、createPhotoOutput),同时创建相关会话管理(createCaptureSession)将输入流、输出流添加到会话中,并启动会话拍照可以直接使用PhotoOutput。capture执行拍照,并将拍照结果保存到媒体在退出相机应用时,需要注意释放相关的资源。
因为分布式相机的应用开发内容比较长,这篇只说到主控端相机设备预览与拍照功能,下一篇会将结合分布式相关内容完成主控端设备调用远程相机进行预览的功能。