专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

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执行拍照,并将拍照结果保存到媒体在退出相机应用时,需要注意释放相关的资源。
  因为分布式相机的应用开发内容比较长,这篇只说到主控端相机设备预览与拍照功能,下一篇会将结合分布式相关内容完成主控端设备调用远程相机进行预览的功能。

供应链显示华为MateX3即将量产,超百万台供货近日有媒体报道称,根据供应链显示华为新一代折叠屏手机MateX3将在2023年初发布,初期量产计划会达到147万部,较2022年倍增。为何华为要在这款MateX3手机上率先押注?首海口长堤路两车相撞,零件碎片散落一地!司机各执一词来源椰网2月14日23时30分许,海口长堤路西往东方向发生一起交通事故,两辆轿车发生碰撞,事故共造成两车受损,无人员受伤。记者在现场看到,肇事车辆发生碰撞后,分别停在道路左侧及中间推送有望!小米10TPro红米K30SU正测试安卓12MIUI14头条创作挑战赛国市场发布了MIUI14系统,第一批已经完全推送了,第二批的更新名单也已经确定!很多关注我的朋友,都应该知道,对于手机的系统更新,我每次发文都标注的很详细,而问最多的快递员配送手机卡,要求当面激活有猫腻吗?咨询快递员配送手机卡,要求当面激活有猫腻吗?有些朋友可能在网上看到了一些关于快递小哥激活会采集信息的文章,所以觉得让快递小哥激活流量卡并不安全,其实,哪有这么多的套路,只要你自己在盘点搭载索尼IMX766传感器的vivo手机,共7款1vivoX70搭载天玑1200旗舰芯片,采用台积电6nm制程工艺,轻松应对游戏视频等场景,大型App轻松运行,告别卡顿,带来高能流畅体验正面是一块6。56英寸的AMOLED柔性直这六款小米手机将很快停止接收MIUI更新?头条创作挑战赛小米最近宣布将终止对小米红米和POCO等六种不同设备的官方支持。这些设备将不再接受MIUI和Android更新的原因是它们已经上市两年了。小米宣布了这一消息,并透露了日本团队用薄片使锂空气电池寿命延长至两倍锂空气电池被称为终极蓄电池,日本物质材料研究机构(NIMS)与软银OHARA联合开发出了可抑制锂空气电池劣化的技术。通过在正极和负极之间插入用固体电解质制成的薄片,保护负极使用的锂苹果魅族接连曝出屏下摄像头设计,网友表示全面屏真要来了?说起魅族,想必数码圈的小伙伴都不会陌生,作为一家知名的国产手机厂商,自诞生之初起就凭借小而美的设计理念,受到了海内外消费者的关注,甚至一度被网友们拿来和iPhone进行比较,因为两陈戌源做的最出格的事是泰山德尔加多当外援?上港随意引援不超额关于陈戌源的问题,他这次出事,估计除了上港队外的球队都高兴,终于不会对上港的时候,有减分加成了。当然他当足协足协本身就是一个奇怪的选择,毕竟这是一个基本上不懂足球的人。其实这次的所深圳马拉松时隔三年开跑,深圳建行如何护航赛事?本文来源时代周报作者赵轶黄芮穆白时隔3年,2022深圳马拉松赛将于2023年2月19日鸣枪开赛,万名参赛者将汇聚鹏城热力开跑。作为本届深圳马拉松首席合作伙伴,中国建设银行深圳市分行2022年云南省青少年田径锦标赛在开远开赛来源人民网云南频道女子1500米决赛。郭斯维摄男子标枪决赛。郭思维摄男子5000米决赛。郭斯维摄男子4x400米接力决赛。郭斯维摄5000米男子决赛。郭斯维摄近日,2022年云南青
憧憬消费复苏,上证指数收复3200点市场观察国内防疫政策调整,以及房地产融资放宽的多重利好背景下,12月5日上证指数收复3200点大关。不过风格剧烈转换之下,创业板逆市下跌。另一方面,离岸人民币兑美元上破6。95,续创9月1香港教父向华强,曾经在香港叱咤风云,他到底是一个怎么样的人呢香港教父向华强,曾经在香港叱咤风云,他到底是一个怎么样的人呢?香港的电影,自从八十年代以来,就已经成为了电影业的一个热门话题,而向华强的名字,更是如雷贯耳。不过,人们所熟悉的向华强吴亦凡被判13年,这8位女明星,恨不得钻进地缝里巅峰时期的吴亦凡简直是娱乐圈的宠儿,也是商业价值最高的明星之一。早在2017年福布斯名人榜上,吴亦凡的名字就已经赫然在列,后续更是不断攀升,风光无限!然而没想的他尽然自毁前程,人设曝王思聪吃回头草,与演员前女友陈米麒复合,靠狗维情长达2年半国民老公王思聪,以女友多出名,再加上背景强硬,即便绯闻满天飞,也有很多漂亮美女争先恐后的当他的女友,可以说是现实版的浪里小白龙。也不知怎么的,他的人设出现了一些翻车的迹象。曝王思聪国家级旅游度假区如何创管评?6月30日,2022全国旅游度假区高质量发展(那拉提)培训班在新疆维吾尔自治区伊犁哈萨克自治州新源县那拉提国家级旅游度假区开班。本期培训班重点围绕世界级旅游度假区发展新趋势国家级旅台青广西有好风光,也有安家逐梦的好机会中新社南宁12月3日电题台青广西有好风光,也有安家逐梦的好机会中新社记者蒋雪林黄艳梅以前从小学课本里读到桂林山水甲天下,我一直想找机会来广西看看。来到广西后,不但看到了美景,还找到俄罗斯莫斯科与圣彼得堡自由行(一)前言,日子不能就这样浪费掉,整理一下以前的游记吧,顺便也是回忆。这篇是2017年7月俄罗斯莫斯科与圣彼得堡的自由行游记。两个不懂俄语的老头老太会有着怎样的故事呢?一,攻略以及初识莫331国道边境线自驾攻略头条创作挑战赛之前菲夜把所有省级大环线都给大家介绍了一遍,从这期开始菲夜将介绍一些经典的自驾路线规划和注意事项,这期就从国道331开始,始于丹东,终于喀纳斯,全程9333公里(一说为什么女厕所经常排长队,男厕所却没人?有一种情况最尴尬俗话说得好,人有三急,最尴尬的不是找不到厕所,而是明明厕所就在眼前却不能用,还得排队,我想这才是最痛苦的事情。根据我多年排队的经验,发现一个问题,那就是男厕所很少出现人满为患的情况泰国特色毒奶服务,游客笑着进去哭着出来快散架了泰国是近些年来旅游业发展的非常好的国家,每年都会吸引大量的来自世界各国的游客前来游玩。(此处已添加小程序,请到今日头条客户端查看)而借助旅游业的发展,如今泰国的经济也得到了极大的提2人1车1个家,设计温馨还时尚!奇瑞电动床版房车,去哪玩都享受出去旅行,我们除了欣赏美景外,就是放松心情,享受生活,外加在沿途采购点心仪的土特产。但要出去旅行,不管是自驾,还是个人跟团走,车里的空间和自己的精力都十分有限,并且休息吃饭时间也紧
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软网