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

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

3天内不再提示

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

OpenHarmony技术社区 来源:OST开源开发者 2023-03-08 14:19 次阅读

接上一篇 OpenHarmony 分布式相机(上),今天我们来说下如何实现分布式相机。

实现分布式相机其实很简单,正如官方介绍的一样,当被控端相机被连接成功后,可以像使用本地设备一样使用远程相机。

我们先看下效果:

066055e4-b9cf-11ed-bfe3-dac502259ad0.jpg

上一篇已经完整的介绍了如何开发一个本地相机,对于分布式相机我们需要完成以下几个步骤。

前置条件:

两台带摄像头的设备

建议使用相同版本的 OH 系统,本案例使用 OpenHarmony 3.2 beta5

连接在同一个网络

开发步骤:

引入设备管理(@ohos.distributedHardware.deviceManager)

通过 deviceManager 发现周边设备

通过 pin 码完成设备认证

获取和展示可信设备

在可信设备直接选择切换不同设备的摄像头

在主控端查看被控端的摄像头图像

以上描述的功能在应用开发时可以使用一张草图来表示,草图中切换设备->弹窗显示设备列表的过程,草图如下:

0679e5a4-b9cf-11ed-bfe3-dac502259ad0.png

代码

①RemoteDeviceModel.ts

说明:远程设备业务处理类,包括获取可信设备列表、获取周边设备列表、监听设备状态(上线、下线、状态变化)、监听设备连接失败、设备授信认证、卸载设备状态监听等。

代码如下:

importdeviceManagerfrom'@ohos.distributedHardware.deviceManager'
importLoggerfrom'./util/Logger'
constTAG:string='RemoteDeviceModel'
letsubscribeId:number=-1
exportclassRemoteDeviceModel{
privatedeviceList:Array=[]
privatediscoverList:Array=[]
privatecallback:()=>void
privateauthCallback:()=>void
privatedeviceManager:deviceManager.DeviceManager
constructor(){
}
publicregisterDeviceListCallback(bundleName:string,callback){
if(typeof(this.deviceManager)!=='undefined'){
this.registerDeviceListCallbackImplement(callback)
return
}
Logger.info(TAG,`deviceManager.createDeviceManagerbegin`)
try{
deviceManager.createDeviceManager(bundleName,(error,value)=>{
if(error){
Logger.info(TAG,`createDeviceManagerfailed.`)
return
}
this.deviceManager=value
this.registerDeviceListCallbackImplement(callback)
Logger.info(TAG,`createDeviceManagercallbackreturned,error=${error},value=${value}`)
})
}catch(err){
Logger.error(TAG,`createDeviceManagerfailed,codeis${err.code},messageis${err.message}`)
}
Logger.info(TAG,`deviceManager.createDeviceManagerend`)
}
privatedeviceStateChangeActionOffline(device){
if(this.deviceList.length<= 0) {
            this.callback()
            return
        }
        for (let j = 0; j < this.deviceList.length; j++) {
            if (this.deviceList[j ].deviceId === device.deviceId) {
                this.deviceList[j] = device
                break
            }
        }
        Logger.info(TAG, `offline, device list= ${JSON.stringify(this.deviceList)}`)
        this.callback()
    }
    private registerDeviceListCallbackImplement(callback) {
        Logger.info(TAG, `registerDeviceListCallback`)
        this.callback = callback
        if (this.deviceManager === undefined) {
            Logger.info(TAG, `deviceManager has not initialized`)
            this.callback()
            return
        }
        Logger.info(TAG, `getTrustedDeviceListSync begin`)
        try {
            let list = this.deviceManager.getTrustedDeviceListSync()
            Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`)
            if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
                this.deviceList = list
            }
        } catch (err) {
            Logger.error(`getTrustedDeviceListSync failed, code is ${err.code}, message is ${err.message}`)
        }
        this.callback()
        Logger.info(TAG, `callback finished`)
        this.deviceManager.on('deviceStateChange', (data) =>{
if(data===null){
return
}
Logger.info(TAG,`deviceStateChangedata=${JSON.stringify(data)}`)
switch(data.action){
casedeviceManager.DeviceStateChangeAction.READY:
this.discoverList=[]
this.deviceList.push(data.device)
try{
letlist=this.deviceManager.getTrustedDeviceListSync()
if(typeof(list)!=='undefined'&&typeof(list.length)!=='undefined'){
this.deviceList=list
}
this.callback()
}catch(err){
Logger.error(TAG,`getTrustedDeviceListSyncfailed,codeis${err.code},messageis${err.message}`)
}
break
casedeviceManager.DeviceStateChangeAction.OFFLINE:
casedeviceManager.DeviceStateChangeAction.CHANGE:
this.deviceStateChangeActionOffline(data.device)
break
default:
break
}
})
this.deviceManager.on('deviceFound',(data)=>{
if(data===null){
return
}
Logger.info(TAG,`deviceFounddata=${JSON.stringify(data)}`)
this.deviceFound(data)
})
this.deviceManager.on('discoverFail',(data)=>{
Logger.info(TAG,`discoverFaildata=${JSON.stringify(data)}`)
})
this.deviceManager.on('serviceDie',()=>{
Logger.info(TAG,`serviceDie`)
})
this.startDeviceDiscovery()
}
privatedeviceFound(data){
for(vari=0;i< this.discoverList.length; i++) {
            if (this.discoverList[i].deviceId === data.device.deviceId) {
                Logger.info(TAG, `device founded ignored`)
                return
            }
        }
        this.discoverList[this.discoverList.length] = data.device
        Logger.info(TAG, `deviceFound self.discoverList= ${this.discoverList}`)
        this.callback()
    }
    private startDeviceDiscovery() {
        if (subscribeId >=0){
Logger.info(TAG,`startedDeviceDiscovery`)
return
}
subscribeId=Math.floor(65536*Math.random())
letinfo={
subscribeId:subscribeId,
mode:deviceManager.DiscoverMode.DISCOVER_MODE_ACTIVE,
medium:deviceManager.ExchangeMedium.COAP,
freq:deviceManager.ExchangeFreq.HIGH,
isSameAccount:false,
isWakeRemote:true,
capability:deviceManager.SubscribeCap.SUBSCRIBE_CAPABILITY_DDMP
}
Logger.info(TAG,`startDeviceDiscovery${subscribeId}`)
try{
// todo 多次启动发现周边设备有什么影响吗?
this.deviceManager.startDeviceDiscovery(info)
}catch(err){
Logger.error(TAG,`startDeviceDiscoveryfailed,codeis${err.code},messageis${err.message}`)
}
}
publicunregisterDeviceListCallback(){
Logger.info(TAG,`stopDeviceDiscovery$subscribeId}`)
this.deviceList=[]
this.discoverList=[]
try{
this.deviceManager.stopDeviceDiscovery(subscribeId)
}catch(err){
Logger.error(TAG,`stopDeviceDiscoveryfailed,codeis${err.code},messageis${err.message}`)
}
this.deviceManager.off('deviceStateChange')
this.deviceManager.off('deviceFound')
this.deviceManager.off('discoverFail')
this.deviceManager.off('serviceDie')
}
publicauthenticateDevice(device,extraInfo,callBack){
Logger.info(TAG,`authenticateDevice${JSON.stringify(device)}`)
for(leti=0;i< this.discoverList.length; i++) {
            if (this.discoverList[i].deviceId !== device.deviceId) {
                continue
            }
            let authParam = {
                'authType': 1,
                'appIcon': '',
                'appThumbnail': '',
                'extraInfo': extraInfo
            }
            try {
                this.deviceManager.authenticateDevice(device, authParam, (err, data) =>{
if(err){
Logger.error(TAG,`authenticateDeviceerror:${JSON.stringify(err)}`)
this.authCallback=null
return
}
Logger.info(TAG,`authenticateDevicesucceed:${JSON.stringify(data)}`)
this.authCallback=callBack
})
}catch(err){
Logger.error(TAG,`authenticateDevicefailed,codeis${err.code},messageis${err.message}`)
}
}
}
/**
*已认证设备列表
*/
publicgetDeviceList():Array{
returnthis.deviceList
}
/**
*发现设备列表
*/
publicgetDiscoverList():Array{
returnthis.discoverList
}
}

getDeviceList() :获取已认证的设备列表;getDiscoverList:发现周边设备的列表。

②DeviceDialog.ets

说明:通过 RemoteDeviceModel.getDiscoverList() 和通过 RemoteDeviceModel.getDeviceList() 获取到所有周边设备列表,用户通过点击"切换设备"按钮弹窗显示所有设备列表信息

importdeviceManagerfrom'@ohos.distributedHardware.deviceManager';
constTAG='DeviceDialog'
//分布式设备选择弹窗
@CustomDialog
exportstructDeviceDialog{
privatecontroller?:CustomDialogController//弹窗控制器
@LinkdeviceList:Array//设备列表
@LinkselectIndex:number//选中的标签
build(){
Column(){
List(){
ForEach(this.deviceList,(item:deviceManager.DeviceInfo,index)=>{
ListItem(){
Row(){
Text(item.deviceName)
.fontSize(22)
.width(350)
.fontColor(Color.Black)
Image(index===this.selectIndex?$r('app.media.checked'):$r('app.media.uncheck'))
.width(35)
.objectFit(ImageFit.Contain)
}
.height(55)
.onClick(()=>{
console.info(`${TAG}selectdevice${item.deviceId}`)
if(index===this.selectIndex){
console.info(`${TAG}devicenotchange`)
}else{
this.selectIndex=index
}
this.controller.close()
})
}
},item=>item.deviceName)
}
.width('100%')
.height(150)
Button(){
Text($r('app.string.cancel'))
.width('100%')
.height(45)
.fontSize(18)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
}.onClick(()=>{
this.controller.close()
})
.backgroundColor('#ed3c13')
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.border({
color:Color.White,
radius:20
})
}
}
③打开设备列表弹窗

说明:在 index.ets 页面中,点击“切换设备”按钮即可以开启设备列表弹窗,通过 @Watch(‘selectedIndexChange’) 监听用户选择的设备标签,在 devices 中获取到具体的 DeviceInfo 对象。

代码如下:

@State@Watch('selectedIndexChange')selectIndex:number=0
//设备列表
@Statedevices:Array=[]
//设备选择弹窗
privatedialogController:CustomDialogController=newCustomDialogController({
builder:DeviceDialog({
deviceList:$devices,
selectIndex:$selectIndex,
}),
autoCancel:true,
alignment:DialogAlignment.Center
})


showDialog(){
console.info(`${TAG}RegisterDeviceListCallbackbegin`)
distributed.registerDeviceListCallback(BUNDLE_NAME,()=>{
console.info(`${TAG}RegisterDeviceListCallbackcallbackentered`)
this.devices=[]
//添加本地设备
this.devices.push({
deviceId:Constant.LOCAL_DEVICE_ID,
deviceName:Constant.LOCAL_DEVICE_NAME,
deviceType:0,
networkId:'',
range:1//发现设备的距离
})
letdiscoverList=distributed.getDiscoverList()
letdeviceList=distributed.getDeviceList()
letdiscoveredDeviceSize=discoverList.length
letdeviceSize=deviceList.length
console.info(`${TAG}discoveredDeviceSize:${discoveredDeviceSize}deviceSize:${deviceSize}`)
letdeviceTemp=discoveredDeviceSize>0?discoverList:deviceList
for(letindex=0;index< deviceTemp.length; index++) {
        this.devices.push(deviceTemp[index])
      }
    })
    this.dialogController.open()
    console.info(`${TAG} RegisterDeviceListCallback end`)
  }


async selectedIndexChange() {
    console.info(`${TAG} select device index ${this.selectIndex}`)
    let discoverList: Array=distributed.getDiscoverList()
if(discoverList.length<= 0) {
      this.mCurDeviceID = this.devices[this.selectIndex].deviceId
      await this.switchDevice()
      this.devices = []
      return
    }
    let selectDeviceName = this.devices[this.selectIndex].deviceName
    let extraInfo = {
      'targetPkgName': BUNDLE_NAME,
      'appName': APP_NAME,
      'appDescription': APP_NAME,
      'business': '0'
    }
    distributed.authenticateDevice(this.devices[this.selectIndex], extraInfo, async () =>{
//获取到相关的设备ID,启动远程应用
for(varindex=0;index< distributed.getDeviceList().length; index++) {
        let deviceName = distributed.getDeviceList()[index].deviceName
        if (deviceName === selectDeviceName) {
          this.mCurDeviceID = distributed.getDeviceList()[index].deviceId
          await this.switchDevice()
        }
      }
    })
    this.devices = []
  }

④重新加载相机

说明:根据用户选择的设备标签获取到当前用户需要切换的相机设备对象,重新加载相机,重新加载需要释放原有的相机资源,然后重新构建 createCameraInput、createPreviewOutput、createSession。 可能你注意到这里好像没有执行 createPhotoOutput,这是因为在实践过程中发现,添加了一个当前设备所支持的拍照配置到会话管理(CaptureSession.addOutput())时,系统会返回当前拍照配置流不支持,并关闭相机,导致相机预览黑屏,所以这里没有添加。

issues:远程相机拍照失败 not found in supported streams

https://gitee.com/openharmony/distributedhardware_distributed_camera/issues/I6E5ZX
mCameraService:这个是相机管理类,代码可以查看上一篇:OpenHarmony 分布式相机(上)中查看。

代码如下:

/**
*切换摄像头
*同一台设备上切换不同摄像头
*/
asyncswitchCamera(){
console.info(`${TAG}switchCamera`)
letcameraList=this.mCameraService.getDeviceCameras(this.mCurDeviceID)
if(cameraList&&cameraList.length>1){
letcameraCount:number=cameraList.length
console.info(`${TAG}cameralist${cameraCount}}`)
if(this.mCurCameraIndex< cameraCount - 1) {
        this.mCurCameraIndex += 1
      } else {
        this.mCurCameraIndex = 0
      }
      await this.reloadCamera()
    } else {
      this.showToast($r('app.string.only_one_camera_hint'))
    }
  }

  /**
   * 重新加载摄像头
   */
  async reloadCamera() {
    // 显示切换loading
    this.isSwitchDeviceing = true
    // 先关闭当前摄像机,再切换新的摄像机
    await this.mCameraService.releaseCamera()
    await this.startPreview()
  }


private async startPreview() {
    console.info(`${TAG} startPreview`)
    await this.mCameraService.createCameraInput(this.mCurCameraIndex, this.mCurDeviceID)
    await this.mCameraService.createPreviewOutput(this.surfaceId, this.previewImpl)
    if (this.mCurDeviceID === Constant.LOCAL_DEVICE_ID) {
      // fixme xjs 如果是远程相机,则不支持拍照,添加拍照输出流会导致相机黑屏
      await this.mCameraService.createPhotoOutput(this.functionBackImpl)
    }
    await this.mCameraService.createSession(this.surfaceId)
  }

⑤加载过度动画

说明:在相机切换中会需要释放原相机的资源,在重启新相机,在通过软总线通道同步远程相机的预览数据。 这里需要一些时间,根据目前测试,在网络稳定状态下,切换时间 3~5s,网络不稳定状态下,切换最长需要 13s,当然有时候会出现无法切换成功,这种情况可能是远程设备已经下线,无法再获取到数据。

代码如下:

@StateisSwitchDeviceing:boolean=false//是否正在切换相机

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(()=>{
})
}
至此,分布式相机的整体流程就已实现完成。下面我们介绍下分布式相机开发中所遇到的问题。

分布式相机问题一览

对于开发过程中所遇到的一些坑,前面多少有简单的提到一些,这里做一次规整,也算是一次回顾。

①首次授权成功无法显示相机预览

解析:我们正常会在 MainAbility.ts 的 onCreate() 函数加载的时候执行申请授权,在 index.ets 页面中,当 XComponent 组件 onLoad() 回调后执行初始化相机操作,代码如下:

MainAbility.ts:

constTAG:string='[DistributedCamera]'
letpermissionList:Array=[
"ohos.permission.MEDIA_LOCATION",
"ohos.permission.READ_MEDIA",
"ohos.permission.WRITE_MEDIA",
"ohos.permission.CAMERA",
"ohos.permission.MICROPHONE",
"ohos.permission.DISTRIBUTED_DATASYNC"
]


exportdefaultclassMainAbilityextendsAbility{
asynconCreate(want,launchParam){
console.info(`${TAG}onCreate`)
globalThis.cameraAbilityContext=this.context
awaitglobalThis.cameraAbilityContext.requestPermissionsFromUser(permissionList)
}
}
index.ets:
//...
//截取部分主要代码

Column(){
XComponent({
id:'componentId',
type:'surface',
controller:this.XComponentController
}).onLoad(async()=>{
console.info(`${TAG}XComponentonLoadiscalled`)
this.XComponentController.setXComponentSurfaceSize({
surfaceWidth:Resolution.DEFAULT_WIDTH,
surfaceHeight:Resolution.DEFAULT_HEIGHT
})
this.surfaceId=this.XComponentController.getXComponentSurfaceId()
console.info(`${TAG}surfaceId:${this.surfaceId}`)
awaitthis.initCamera()
}).height('100%')
.width('100%')
}
.width('100%')
.height('75%')
.margin({
bottom:20
})

//...

应用启动后,调用了 requestPermissionsFromUser() 请求权限后,但未手动授权时,查看相关日志:

0694b794-b9cf-11ed-bfe3-dac502259ad0.png

日志告诉我们,page 的生命周期已启动到 onShow,并且页面布局也完成了加载,XComponent 组件回调 onLoad()。 但是由于还未授权,导致无法初始化相机,此时即便授权成功,也不会再进行初始化,导致相机无法启动,无预览视图。 知道原因后,我们可以有多种方式解决,重点就是在授权完成后,需要再次触发初始化相机,让相机启动才可以正常预览。

我的处理方式:

在 index.ets 页面中处理授权

定义是否已授权的标识,用于判断是否可以初始化相机

定义是否已经初始化相机标识,防止对此初始化

在 page 页面初始化函数 aboutToAppear() 中请求权限,并在权限申请结果中添加初始化相机操作

XComponent 组件回调 onLoad() 初始化相机操作不变

index.ets:

privateisInitCamera:boolean=false//是否已初始化相机
privateisPermissions:boolean=false//是否完成授权

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)
})
}

相机应用未关闭,系统息屏后重新点亮,重新返回相机应用,无预览输出流返回

解析:从现象看,预览画面卡在息屏前的状态,需要退出应用后,重启应用才能正常预览。从日志上看没有查看到具体的原因,只是 camera_host 的数据量日志消失。

猜想:相机在系统息屏后强制关闭,需要重新加载相机才能正常预览,实现方式如下:

在 page 的 onPageShow() 回调函数中重新初始化相机。

在 page 的 onPageHide() 函数中释放相机资源,减少系统资源不必要的消耗。

index.ets:

asynconPageShow(){
console.info(`${TAG}onPageShow`)
awaitthis.initCamera()
}
onPageHide(){
console.info(`${TAG}onPageHide`)
this.isSwitchDeviceing=false
this.isInitCamera=false
this.mCameraService.releaseCamera()
}
结论:实践验证此方法有效解决息屏后点亮返回相机无法预览的问题。

③加载远程相机,在会话管理中添加拍照输出流,无法拍照,预览黑屏

解析:两台设备 pin 码认证通过,连接成功,在主控端选择一台被控端设备时,加载相机,流程与加载本地相机相同。

流程如下:

createCameraInput()
createPreviewOutput()
createPhotoOutput()
createSession()

*createSession.beginConfig()
*createSession.addInput(CameraInput)
*createSession.addOutput(PreviewOutput)
*createSession.addOutput(PhotoOutput)
*createSession.commitConfig()
*createSession.start()

经过排查,发现日志中返回异常 not found in supported streams,详情可以查看关联issues。

https://gitee.com/openharmony/distributedhardware_distributed_camera/issues/I6E5ZX
原因:在创建 PhotoOutput 时需要传递支持的拍照配置信息 Profile,这里的 Profile 可以通过CmeraManager.getSupportedOutputCapability()返回的相机输出能力 CameraOutputCapability 对象获取,但远程相机设备拍照输出能力列表返回空。

但通过查看本地相机拍照输出能力可知 DAYU200 设备支持的 Profile 信息:

photoProfile{"format":2000,"size":{"width":1280,"height":960}}
通过此将 photoProfile 作为远程相机设备构建拍照输出流的入参场景拍照输出流,并把此添加到拍照会话管理中,但是界面出现不支持此相机配置,最终关闭了相机,导致黑屏。 解决方案:根据此问题,目前只能根据场景判断是否需要添加拍照输出流到会话管理,对于本地相机则可以添加拍照输出流,执行拍照业务,远程相机则不添加拍照输出流,这也就不能执行拍照业务,希望社区有解决方案。

④切换不同设备上的相机,相机预览输出流出现异常,无法显示远程相机的画面

解析:此问题存在的原因可能有多种,这里我说下我遇到的情况。 (1)分布式连接被断开,但是因为底层机制,设备之间下线需要在一段时间内才能上报(预计 5 分钟),所以在应用层看到可以连接的远端设备,其实已经下线了,这时当然不能切换到远程相机。 (2)与问题 3 中描述的相同,因为添加了一个无法支持的拍照配置信息导致相机被关闭。

解决方案:

等待线下通知,再重新连接设备,或者等待设备自动完成重连,简单粗暴就是重启设备。

待社区反馈。

⑤相机业务在主线程执行,需要将业务移动到子线程,防止 UI 线程堵塞

解析:如题描述,目前可能存在堵塞 UI 线程的可能,需要将一些耗时的操作移动到子线程,比如预览、拍照保存图片等。 目前正在修改优化,关于 ets 的异步线程 worker 可以查看之前写的一篇关于:OpenHarmony stage worker 多线程。

⑥远程相机预览数据传输存在 500ms 的延迟

解析:在 wifi 环境下,被控端相机将预览数据通过软总线传输到主控端显示,有 500ms 左右的延迟,此问题待排查,具体是那个环境出现的延迟。

⑦no permission for function call

解析:用户动态授予:允许不同设备间的数据(ohos.permission.DISTRIBUTED_DATASYNC) 交换权限后,DeviceManager.startDeviceDiscovery() 启动发现周边设备总会出现异常。

日志中提示:

discoverFaildata={"subscribeId":26386,"reason":-20007,"errInfo":"nopermissionforfunctioncall."}

原因:非系统应用无法使用 DeviceManager,详细可查看:issues。

https://gitee.com/openharmony/distributedhardware_device_manager/issues/I6BYK4
解决方案:系统应用和普通应用是通过签名来区分,那只要通过修改签名 UnsgnedReleasedProfileTemplate.json 文件中的 app-feature 值为 ohos_system_app,即为系统应用。

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

    关注

    59

    文章

    4598

    浏览量

    92825
  • 相机
    +关注

    关注

    4

    文章

    1252

    浏览量

    52390
  • 设备
    +关注

    关注

    2

    文章

    4169

    浏览量

    69149
  • 鸿蒙
    +关注

    关注

    55

    文章

    1556

    浏览量

    42109
  • OpenHarmony
    +关注

    关注

    23

    文章

    3201

    浏览量

    15153

原文标题:鸿蒙分布式相机“踩坑”分享

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

收藏 人收藏

    评论

    相关推荐

    分布式软件系统

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

    分布式系统的优势是什么?

    当讨论分布式系统时,我们面临许多以下这些形容词所描述的 同类型: 分布式的、删络的、并行的、并发的和分散的。分布式处理是一个相对较新的领域,所以还没有‘致的定义。与顺序计算相比、并行的、并发的和
    发表于 03-31 09:01

    HarmonyOS应用开发-分布式设计

    设计理念HarmonyOS 是面向未来全场景智慧生活方式的分布式操作系统。对消费者而言,HarmonyOS 将生活场景中的各类终端进行能力整合,形成“One Super Device”,以实现
    发表于 09-22 17:11

    HarmonyOS鸿蒙操作系统之什么是“基于微内核的全场景分布式操作系统”?

    HarmonyOS鸿蒙操作系统之什么是“基于微内核的全场景分布式操作系统”?即使作为理工科的人咋一眼看上去似乎也不太懂这是什么,就像区块链这个概念刚出来一样,普通人都是一脸懵B(当然现在我对这个也是
    发表于 09-23 17:06

    跟阿斌一起学鸿蒙(4). 分布式Hello Harmony的N种写法

    鸿蒙OS是一个分布式操作系统,而Ability作为它调度的基本单元,那么,一个分布式Hello Harmony可以有几种写法呢?# 分布式Hello Harmony用例## 1. 根据
    发表于 12-10 14:59

    鸿蒙分布式任务调度

    鸿蒙分布式任务调度,实现跨设备FA拉起
    发表于 06-12 17:28

    鸿蒙分布式任务调度——数据传递

    鸿蒙分布式任务调度之数据传递
    发表于 06-12 17:29

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

    文件系统和AI语音识别功能开发了一款分布式语音照相机,这款相机能将拍摄的照片实时共享到同一分布式网络下的不同设备中,这样大家都可以看到拍摄的照片,就不需要一窝蜂的跑到一台手机前,不满意
    发表于 09-10 09:53

    Linux学习过程过的与如何解决

    Linux记录记录Linux学习过程过的与如何解决1解决方法:F10进入BIOS使能
    发表于 11-04 08:44

    HarmonyOS分布式应用框架深入解读

    给原始的应用,既有系统多模统一的交互模块,对应用来说是其实感知不到的,不知道事件来自本端还是另一端。HarmonyOS基本架构:分布式硬件现在的设备包含的硬件、外设非常多,比如相机、麦克风,包括
    发表于 11-22 15:15

    如何高效完成HarmonyOS分布式应用测试?

    作者:liuxun,HarmonyOS测试架构师HarmonyOS是新一代的智能终端操作系统,给开发者提供了设备发现、设备连接、跨设备调用等丰富的分布式API。随着越来越多的开发者投入到
    发表于 12-13 18:07

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

    index.ets:主页面config.json:配置文件4、OpenHarmony3.1的分布式相机简介分布式相机是多个设备的相机同时协同
    发表于 04-11 11:50

    HarmonyOS应用开发-分布式语音摄像头体验

    一、组件说明使用HarmonyOS分布式文件系统和AI语音识别功能开发了一个分布式语音摄像头。使用此相机应用程序,同一分布式网络下的不同设备可以实时看到主设备拍摄的照片。这有效解决了对
    发表于 08-24 15:06

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

    提供更好的服务体验。 图 3 鸿蒙硬件资源池支持各类消费者场景 2.2 开发者场景 对于开发者来说,由于分布式硬件资源池将跨设备硬件调用的复杂度都封装在了系统底层,跨设备硬件复用本地硬件的 API
    发表于 12-06 10:02

    鸿蒙原生应用开发——分布式数据对象

    01、什么是分布式数据对象 在可信组网环境下,多个相互组网认证的设备将各自创建的对象加入同一个 sessionId,使得加入的多个数据对象之间可以同步数据,也就是说,当某一数据对象属性发生
    发表于 12-08 10:01