最近陆续看到各社区上有关 OpenHarmony 媒体相机的使用开发文档,相机对于富设备来说必不可少,日常中我们经常使用相机完成拍照、人脸验证等。
OpenHarmony 系统一个重要的能力就是分布式,对于分布式相机我也倍感兴趣,之前看到官方对分布式相机的一些说明,这里简单介绍下。
有兴趣可以查看官方文档:分布式相机部件
https://gitee.com/openharmony/distributedhardware_distributed_camera
分布式框架图

分布式相机框架(Distributed Hardware)分为主控端和被控端。假设:设备 B 拥有本地相机设备,分布式组网中的设备 A 可以分布式调用设备 B 的相机设备。
这种场景下,设备 A 是主控端,设备 B 是被控端,两个设备通过软总线进行交互。
VirtualCameraHAL:作为硬件适配层(HAL)的一部分,负责和分布式相机框架中的主控端交互,将主控端 CameraFramwork 下发的指令传输给分布式相机框架的 SourceMgr 处理。
SourceMgr:通过软总线将控制信息传递给被控端的 CameraClient。
CameraClient:直接通过调用被控端 CameraFramwork 的接口来完成对设备 B 相机的控制。
最后,从设备 B 反馈的预览图像数据会通过分布式相机框架的 ChannelSink 回传到设备 A 的 HAL 层,进而反馈给应用。通过这种方式,设备 A 的应用就可以像使用本地设备一样使用设备 B 的相机。
相关名词介绍:
主控端(source):控制端,通过调用分布式相机能力,使用被控端的摄像头进行预览、拍照、录像等功能。
被控端(sink):被控制端,通过分布式相机接收主控端的命令,使用本地摄像头为主控端提供图像数据。
现在我们要实现分布式相机,在主控端调用被控端相机,实现远程操作相机,开发此应用的具体需求:
支持本地相机的预览、拍照、保存相片、相片缩略图、快速查看相片、切换摄像头(如果一台设备上存在多个摄像头时)。
自由切换本地相机和远程相机。
UI 草图

从草图上看,我们简单的明应用 UI 布局的整体内容:
顶部右上角有个"切换设备"的按钮,点击弹窗显示设备列表,可以实现设备认证与设备切换功能。
中间使用 XComponent 组件实现的相机预览区域。
底部分为如下三个部分。
具体如下:
相机缩略图:显示当前设备媒体库中最新的图片,点击相机缩略图按钮可以查看相关的图片。
拍照:点击拍照按钮,将相机当前帧保存到本地媒体库中。
切换摄像头:如果一台设备有多个摄像头时,例如相机有前后置摄像头,点击切换后会将当前预览的页面切换到另外一个摄像头的图像。
实现效果


开发环境
如下:
系统: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
开发实践
本篇主要在应用层的角度实现分布式相机,实现远程相机与实现本地相机的流程相同,只是使用的相机对象不同,所以我们先完成本地相机的开发,再通过参数修改相机对象来启动远程相机。
①创建项目

②权限声明
(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这里有个获取缩略图的功能,主要是获取媒体库中根据时间排序,获取最新拍照的图片作为当前需要显示的缩略图,实现此方法在后面说 CameraService 类的时候进行详细介绍。 注意:如果首次启动应用,在授权完成后需要加载相机,则建议授权放在启动页完成,或者在调用相机页面之前添加一个过渡页面,主要用于完成权限申请和启动相机的入口,否则首次完成授权后无法显示相机预览,需要退出应用再重新进入才可以正常预览,这里先简单说明下,文章后续会在问题环节详细介绍。=[ "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 如前面截图所示,实现整体页面的布局。 页面中主要使用到 XComponent 组件,用于 EGL/OpenGLES 和媒体数据写入,并显示在 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=[]
//设备选择弹窗
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 执行拍照,并将拍照结果保存到媒体
在退出相机应用时,需要注意释放相关的资源。
因为分布式相机的应用开发内容比较长,这篇只说到主控端相机设备预览与拍照功能,下一篇会将结合分布式相关内容完成主控端设备调用远程相机进行预览的功能。
审核编辑:汤梓红
-
摄像头
+关注
关注
61文章
5058浏览量
102418 -
相机
+关注
关注
5文章
1535浏览量
55407 -
Module
+关注
关注
0文章
74浏览量
13478 -
SDK
+关注
关注
3文章
1093浏览量
51178 -
OpenHarmony
+关注
关注
31文章
3926浏览量
20702
原文标题:OpenHarmony上实现分布式相机
文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录

OpenHarmony上实现分布式相机
评论