0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

OpenHarmony上实现分布式相机

OpenHarmony技术社区 来源:OST开源开发者 2023-02-20 10:41 次阅读

最近陆续看到各社区上有关 OpenHarmony 媒体相机的使用开发文档,相机对于富设备来说必不可少,日常中我们经常使用相机完成拍照、人脸验证等。

OpenHarmony 系统一个重要的能力就是分布式,对于分布式相机我也倍感兴趣,之前看到官方对分布式相机的一些说明,这里简单介绍下。

有兴趣可以查看官方文档:分布式相机部件

https://gitee.com/openharmony/distributedhardware_distributed_camera

分布式框架图

eeb726ac-b069-11ed-bfe3-dac502259ad0.png

分布式相机框架(Distributed Hardware)分为主控端和被控端。假设:设备 B 拥有本地相机设备,分布式组网中的设备 A 可以分布式调用设备 B 的相机设备。

这种场景下,设备 A 是主控端,设备 B 是被控端,两个设备通过软总线进行交互。

VirtualCameraHAL:作为硬件适配层(HAL)的一部分,负责和分布式相机框架中的主控端交互,将主控端 CameraFramwork 下发的指令传输给分布式相机框架的 SourceMgr 处理。

SourceMgr:通过软总线将控制信息传递给被控端的 CameraClient。

CameraClient:直接通过调用被控端 CameraFramwork 的接口来完成对设备 B 相机的控制。

最后,从设备 B 反馈的预览图像数据会通过分布式相机框架的 ChannelSink 回传到设备 A 的 HAL 层,进而反馈给应用。通过这种方式,设备 A 的应用就可以像使用本地设备一样使用设备 B 的相机。

相关名词介绍:

主控端(source):控制端,通过调用分布式相机能力,使用被控端的摄像头进行预览、拍照、录像等功能。

被控端(sink):被控制端,通过分布式相机接收主控端的命令,使用本地摄像头为主控端提供图像数据。

现在我们要实现分布式相机,在主控端调用被控端相机,实现远程操作相机,开发此应用的具体需求:

支持本地相机的预览、拍照、保存相片、相片缩略图、快速查看相片、切换摄像头(如果一台设备上存在多个摄像头时)。

同一网络下,支持分布式 pin 码认证,远程连接。

自由切换本地相机和远程相机。

UI 草图

eed7ec5c-b069-11ed-bfe3-dac502259ad0.png

从草图上看,我们简单的明应用 UI 布局的整体内容:

顶部右上角有个"切换设备"的按钮,点击弹窗显示设备列表,可以实现设备认证与设备切换功能。

中间使用 XComponent 组件实现的相机预览区域。

底部分为如下三个部分。

具体如下:

相机缩略图:显示当前设备媒体库中最新的图片,点击相机缩略图按钮可以查看相关的图片。

拍照:点击拍照按钮,将相机当前帧保存到本地媒体库中。

切换摄像头:如果一台设备有多个摄像头时,例如相机有前后置摄像头,点击切换后会将当前预览的页面切换到另外一个摄像头的图像。

实现效果

eef80c8a-b069-11ed-bfe3-dac502259ad0.jpg

ef1765b2-b069-11ed-bfe3-dac502259ad0.jpg

开发环境

如下:

系统: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

开发模式:Stage

开发语言:ets

开发实践

本篇主要在应用层的角度实现分布式相机,实现远程相机与实现本地相机的流程相同,只是使用的相机对象不同,所以我们先完成本地相机的开发,再通过参数修改相机对象来启动远程相机。

①创建项目

ef388f4e-b069-11ed-bfe3-dac502259ad0.png

②权限声明

(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=[
"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)
})
}
这里有个获取缩略图的功能,主要是获取媒体库中根据时间排序,获取最新拍照的图片作为当前需要显示的缩略图,实现此方法在后面说 CameraService 类的时候进行详细介绍。 注意:如果首次启动应用,在授权完成后需要加载相机,则建议授权放在启动页完成,或者在调用相机页面之前添加一个过渡页面,主要用于完成权限申请和启动相机的入口,否则首次完成授权后无法显示相机预览,需要退出应用再重新进入才可以正常预览,这里先简单说明下,文章后续会在问题环节详细介绍。

③UI 布局

说明:UI 如前面截图所示,实现整体页面的布局。 页面中主要使用到 XComponent 组件,用于 EGL/OpenGLES 和媒体数据写入,并显示在 XComponent 组件。

参看:XComponent 详细介绍

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-xcomponent.md
onLoad():XComponent 插件加载完成时的回调,在插件完成时可以获取**ID并初始化相机。

XComponentController:XComponent 组件控制器,可以绑定至 XComponent 组件,通过 getXComponent/**aceId() 获取 XComponent 对应的/**aceID。

代码如下:

@State@Watch('selectedIndexChange')selectIndex:number=0
//设备列表
@Statedevices:Array=[]
//设备选择弹窗
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)
}
(1)启动系统相册

说明:用户点击图片缩略图时需要启动图片查看,这里直接打开系统相册,查看相关的图片。

代码如下:

awaitglobalThis.cameraAbilityContext.startAbility({
parameters:{uri:'photodetail'},
bundleName:'com.ohos.photos',
abilityName:'com.ohos.photos.MainAbility'
})

④相机服务 CameraService.ts

(1)CameraService 单例模式,用于提供操作相机相关的业务

代码如下:

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,并注册相机状态变化监听器,实时更新相机状态。

同时通过 CameraManager…getSupportedCameras() 获取前期支持的相机设备集合,这里的相机设备包括当前设备上安装的相机设备和远程设备上的相机设备。

代码如下:

/**
*初始化
*/
publicasyncinitCamera():Promise{
console.info(`${TAG}initCamera`)
if(this.mCameraManager===null){
this.mCameraManager=awaitcamera.getCameraManager(globalThis.cameraAbilityContext)
//注册监听相机状态变化
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;i< cameras.length; i++) {
                    console.info(`${TAG} --------------Camera Info-------------`)
                    const tempCameraId: string = cameras[i].cameraId
                    console.info(`${TAG} camera_id: ${tempCameraId}`)
                    console.info(`${TAG} cameraPosition: ${cameras[i].cameraPosition}`)
                    console.info(`${TAG} cameraType: ${cameras[i].cameraType}`)
                    const connectionType = cameras[i].connectionType
                    console.info(`${TAG} connectionType: ${connectionType}`)
                    // CameraPosition 0-未知未知 1-后置 2-前置
                    // CameraType 0-未知类型 1-广角 2-超广角 3长焦 4-带景深信息
                    // connectionType 0-内置相机 1-USB连接相机 2-远程连接相机
                    // 判断本地相机还是远程相机
                    if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_BUILT_IN) {
                        // 本地相机
                        this.displayCameraDevice(Constant.LOCAL_DEVICE_ID, cameras[i])
                    } else if (connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {
                        // 远程相机 相机ID格式 :deviceID__Camera_cameraID 例如:3c8e510a1d0807ea51c2e893029a30816ed940bf848754749f427724e846fab7__Camera_lcam001
                        const cameraKey: string = tempCameraId.split('__Camera_')[0]
                        console.info(`${TAG} cameraKey: ${cameraKey}`)
                        this.displayCameraDevice(cameraKey, cameras[i])
                    }
                }
                // todo test 选择首个相机
                this.mCurCameraDevice = cameras[0]
                console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)
            }
        }
        return this.mCameraCount
    }


    /**
     * 处理相机设备
     * @param key
     * @param cameraDevice
     */
    private displayCameraDevice(key: string, cameraDevice: camera.CameraDevice) {
        console.info(`${TAG} displayCameraDevice ${key}`)
        if (this.mCameraMap.has(key) && this.mCameraMap.get(key)?.length >0){
console.info(`${TAG}displayCameraDevicehasmCameraMap`)
//判断相机列表中是否已经存在此相机
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)创建相机输入流

说明:CameraManager.createCameraInput() 可以创建相机输出流 CameraInput 实例,CameraInput 是在 CaptureSession 会话中使用的相机信息,支持打开相机、关闭相机等能力。 代码如下:

/**
*创建相机输入流
*@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 < this.mCameraMap.get(deviceId)?.length) {
                this.mCurCameraDevice = this.mCameraMap.get(deviceId)[cameraIndex]
            } else {
                this.mCurCameraDevice = this.mCameraMap.get(deviceId)[0]
            }
        }
        console.info(`${TAG} mCurCameraDevice: ${this.mCurCameraDevice.cameraId}`)
        try {
            this.mCameraInput = await this.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.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`)
            // 监听预览帧开始
            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.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() 可以创建相机的会话类,保存相机运行所需要的所有资源 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() 可以实现拍照功能。 代码如下:
/**
*拍照
*/
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 主要用于保存拍摄的图片,即是调用拍照操作后,会触发图片接收监听器,在将图片的字节流进行写入本地文件操作。

代码如下:

/**
*保存相机拍照的资源
*/
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
}
//根据图像的组件类型从图像中获取组件缓存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}`)
//创建媒体资源返回提供封装文件属性
constdataUri:mediaLibrary.FileAsset=awaitmedia.createAsset(mediaType,displayName,publicPath)
//媒体文件资源创建成功,将拍照的数据写入到媒体资源
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
}
}

⑦获取缩略图

说明:主要通过获取当前媒体库中根据时间排序,获取最新的图片并缩放图片大小后返回。

代码如下:

/**
*获取缩略图
*@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 说明:实现获取缩略图的对象。 代码如下:
/**
*缩略图处理器
*/
importmediaLibraryfrom'@ohos.multimedia.mediaLibrary';
importimagefrom'@ohos.multimedia.image';
constTAG:string='ThumbnailGetter'
exportdefaultclassThumbnailGetter{
publicasyncgetThumbnailInfo(width:number,height:number,uri?:string):Promise{
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`)
}
}

至此,总结下,需要实现相机预览、拍照功能:

通过 camera 媒体 api 提供的 camera.getCameraManager() 获取 CameraManager 相机管理类。

通过相机管理类型创建相机预览与拍照需要的输入流(createCameraInput)和输出流(createPreviewOutPut、createPhotoOutput),同时创建相关会话管理(createCaptureSession)

将输入流、输出流添加到会话中,并启动会话

拍照可以直接使用 PhotoOutput.capture 执行拍照,并将拍照结果保存到媒体

在退出相机应用时,需要注意释放相关的资源。

因为分布式相机的应用开发内容比较长,这篇只说到主控端相机设备预览与拍照功能,下一篇会将结合分布式相关内容完成主控端设备调用远程相机进行预览的功能。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 摄像头
    +关注

    关注

    59

    文章

    4612

    浏览量

    92911
  • 相机
    +关注

    关注

    4

    文章

    1254

    浏览量

    52469
  • Module
    +关注

    关注

    0

    文章

    58

    浏览量

    12810
  • SDK
    SDK
    +关注

    关注

    3

    文章

    966

    浏览量

    44722
  • OpenHarmony
    +关注

    关注

    23

    文章

    3322

    浏览量

    15161

原文标题:OpenHarmony上实现分布式相机

文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    分布式软件系统

    分布式软件系统分布式软件系统(Distributed Software Systems)是支持分布式处理的软件系统,是在由通信网络互联的多处理机体系结构执行任务的系统。它包括
    发表于 07-22 14:53

    OpenHarmony 2.2 Beta2 版本发布,具备典型的分布式能力和媒体类产品开发能力

    OpenHarmony 具备了典型的分布式能力和媒体类产品开发能力。即日起,全球开发者可通过 Gitee 和镜像站点下载完整代码(https://gitee.com/openharmony)6 月 1 日
    发表于 08-09 15:15

    HarmonyOS教程一分布式语音照相机

    之前,我们先对分布式语音照相机进行展示,让您快速了解到本篇Codelab所实现的功能。图1-1 分布式语音照相机效果图1-2
    发表于 09-10 09:53

    OpenHarmony分布式软总线流程分析

    OpenHarmony分布式软总线流程分析,大神总结,大家可以下载去学习了~.~
    发表于 11-19 15:56

    基于润和DAYU200开发套件的OpenHarmony分布式音乐播放器

    RJ45以太网口,可满足NVR、工业网关等多网口产品需求。分布式音乐播放器这里给大家分享一个样例,分布式音乐播放器,实现了基本的音乐播放、暂停、一曲、下一曲功能,并使用
    发表于 03-14 09:07

    OpenHarmony标准设备应用开发(三)——分布式数据管理

    程序,并在此基础,知道了如何在 OpenHarmony 中做到音乐播放,显示动画,转场动画等相关进阶技能,以及如何通过分布式数据管理在多台设备之间实现数据的同步更新。在后续
    发表于 04-07 18:48

    OpenHarmony3.1分布式技术资料合集

    客户端(ScreenClient):屏幕图像显示代理客户端,用于在设备显示其他设备投射过来的屏幕图像数据。3、OpenHarmony3.1的分布式手写板1.介绍基于TS扩展的声明
    发表于 04-11 11:50

    OpenHarmony 3.1 Beta版本关键特性解析——分布式DeviceProfile

    (以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点)成翔OpenAtom OpenHarmony(以下简称“OpenHarmony”)作为分布式操作系统,让
    发表于 04-25 11:14

    【学习打卡】OpenHarmony分布式数据管理介绍

    中,精心设计的架构为数据库和其他数据平台提供了一个模型,在该模型上将部署特定技术以适应各个应用程序。分布式数据管理作为OpenHarmony系统的模块之一,它建立在分布式软总线的基础
    发表于 07-15 15:49

    【学习打卡】OpenHarmony分布式任务调度

    之前我们分享过分布式软总线和分布式数据管理,今天主要说一下OpenHarmony分布式任务调度,分布式任务调度是建立在
    发表于 07-18 17:06

    【开发样例】OpenHarmony分布式购物车

    OpenHarmony分布式购物车一、简介1.样例效果分布式购物车demo 模拟的是我们购物时参加满减活动,进行拼单的场景;实现两人拼单时,其他一人添加商品到购物车,另外一人购物车列表
    发表于 07-29 14:17

    OpenHarmony 分布式硬件关键技术

    的视频会议;在影音娱乐场景下,能够轻松地把手机音视频放到电视和音箱播放,还可以让家里的灯光自动跟随电影和音乐进行变化,实现非常震撼的家庭影院的效果。 期待越来越多的开发者参与OpenHarmony的生态中来,共同研究和探讨
    发表于 08-24 17:25

    分布式系统硬件资源池原理和接入实践

    /distributed_hardware_components_cfg.json 三个接口的 so 实现后,编译打包到系统库路径下,同时配置到分布式硬件部件配置文件中,设备组网上线后,可以看到分布式
    发表于 12-06 10:02

    OpenHarmony技术论坛:分布式相机分布式图库功能

    OpenHarmony Tech Day·技术日》 技术论坛 新增分布式相机分布式图库功能 相比OpenHarmony 3.0版本,
    的头像 发表于 04-25 15:06 1524次阅读
    <b class='flag-5'>OpenHarmony</b>技术论坛:<b class='flag-5'>分布式</b><b class='flag-5'>相机</b>和<b class='flag-5'>分布式</b>图库功能

    鸿蒙分布式相机“踩坑”分享

    接上一篇 OpenHarmony 分布式相机(上),今天我们来说下如何实现分布式相机
    的头像 发表于 03-08 14:19 1292次阅读