作者:徐金生
接上一篇OpenHarmony 分布式相机(上),今天我们来说下如何实现分布式相机。
实现分布式相机其实很简单,正如官方介绍的一样,当被控端相机被连接成功后,可以像使用本地设备一样使用远程相机。
我们先看下效果
视频地址

上一篇已经完整的介绍了如何开发一个本地相机,对于分布式相机我们需要完成以下几个步骤:
前置条件
1、两台带摄像头的设备
2、建议使用相同版本的OH系统,本案例使用OpenHarmony 3.2 beta5
3、连接在同一个网络
开发步骤
1、引入设备管理(@ohos.distributedHardware.deviceManager)
2、通过deviceManager发现周边设备
3、通过pin码完成设备认证
4、获取和展示可信设备
5、在可信设备直接选择切换不同设备的摄像头
6、在主控端查看被控端的摄像头图像
以上描述的功能在应用开发时可以使用一张草图来表示,草图中切换设备->弹窗显示设备列表的过程,草图如下:

代码
RemoteDeviceModel.ts
说明: 远程设备业务处理类,包括获取可信设备列表、获取周边设备列表、监听设备状态(上线、下线、状态变化)、监听设备连接失败、设备授信认证、卸载设备状态监听等
代码如下:
import deviceManager from '@ohos.distributedHardware.deviceManager'
import Logger from './util/Logger'
const TAG: string = 'RemoteDeviceModel'
let subscribeId: number = -1
export class RemoteDeviceModel {
private deviceList: Array<deviceManager.DeviceInfo> = []
private discoverList: Array<deviceManager.DeviceInfo> = []
private callback: () => void
private authCallback: () => void
private deviceManager: deviceManager.DeviceManager
constructor() {
}
public registerDeviceListCallback(bundleName : string, callback) {
if (typeof (this.deviceManager) !== 'undefined') {
this.registerDeviceListCallbackImplement(callback)
return
}
Logger.info(TAG, `deviceManager.createDeviceManager begin`)
try {
deviceManager.createDeviceManager(bundleName, (error, value) => {
if (error) {
Logger.info(TAG, `createDeviceManager failed.`)
return
}
this.deviceManager = value
this.registerDeviceListCallbackImplement(callback)
Logger.info(TAG, `createDeviceManager callback returned, error= ${error},value= ${value}`)
})
} catch (err) {
Logger.error(TAG, `createDeviceManager failed, code is ${err.code}, message is ${err.message}`)
}
Logger.info(TAG, `deviceManager.createDeviceManager end`)
}
private deviceStateChangeActionOffline(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, `deviceStateChange data= ${JSON.stringify(data)}`)
switch (data.action) {
case deviceManager.DeviceStateChangeAction.READY:
this.discoverList = []
this.deviceList.push(data.device)
try {
let list = this.deviceManager.getTrustedDeviceListSync()
if (typeof (list) !== 'undefined' && typeof (list.length) !== 'undefined') {
this.deviceList = list
}
this.callback()
} catch (err) {
Logger.error(TAG, `getTrustedDeviceListSync failed, code is ${err.code}, message is ${err.message}`)
}
break
case deviceManager.DeviceStateChangeAction.OFFLINE:
case deviceManager.DeviceStateChangeAction.CHANGE:
this.deviceStateChangeActionOffline(data.device)
break
default:
break
}
})
this.deviceManager.on('deviceFound', (data) => {
if (data === null) {
return
}
Logger.info(TAG, `deviceFound data= ${JSON.stringify(data)}`)
this.deviceFound(data)
})
this.deviceManager.on('discoverFail', (data) => {
Logger.info(TAG, `discoverFail data= ${JSON.stringify(data)}`)
})
this.deviceManager.on('serviceDie', () => {
Logger.info(TAG, `serviceDie`)
})
this.startDeviceDiscovery()
}
private deviceFound(data) {
for (var i = 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, `started DeviceDiscovery`)
return
}
subscribeId = Math.floor(65536 * Math.random())
let info = {
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 {
this.deviceManager.startDeviceDiscovery(info)
} catch (err) {
Logger.error(TAG, `startDeviceDiscovery failed, code is ${err.code}, message is ${err.message}`)
}
}
public unregisterDeviceListCallback() {
Logger.info(TAG, `stopDeviceDiscovery $subscribeId}`)
this.deviceList = []
this.discoverList = []
try {
this.deviceManager.stopDeviceDiscovery(subscribeId)
} catch (err) {
Logger.error(TAG, `stopDeviceDiscovery failed, code is ${err.code}, message is ${err.message}`)
}
this.deviceManager.off('deviceStateChange')
this.deviceManager.off('deviceFound')
this.deviceManager.off('discoverFail')
this.deviceManager.off('serviceDie')
}
public authenticateDevice(device, extraInfo, callBack) {
Logger.info(TAG, `authenticateDevice ${JSON.stringify(device)}`)
for (let i = 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, `authenticateDevice error: ${JSON.stringify(err)}`)
this.authCallback = null
return
}
Logger.info(TAG, `authenticateDevice succeed: ${JSON.stringify(data)}`)
this.authCallback = callBack
})
} catch (err) {
Logger.error(TAG, `authenticateDevice failed, code is ${err.code}, message is ${err.message}`)
}
}
}
public getDeviceList(): Array<deviceManager.DeviceInfo> {
return this.deviceList
}
public getDiscoverList(): Array<deviceManager.DeviceInfo> {
return this.discoverList
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- getDeviceList() : 获取已认证的设备列表
- getDiscoverList :发现周边设备的列表
DeviceDialog.ets
说明: 通过RemoteDeviceModel.getDiscoverList()和通过RemoteDeviceModel.getDeviceList()获取到所有周边设备列表,用户通过点击"切换设备"按钮弹窗显示所有设备列表信息。
打开设备列表弹窗
说明: 在index.ets页面中,点击“切换设备”按钮即可以开启设备列表弹窗,通过@Watch(‘selectedIndexChange’)监听用户选择的设备标签,在devices中获取到具体的DeviceInfo对象。
代码如下:
重新加载相机
说明: 根据用户选择的设备标签获取到当前用户需要切换的相机设备对象,重新加载相机,重新加载需要释放原有的相机资源,然后重新构建createCameraInput、createPreviewOutput、createSession。可能你注意到这里好像没有执行createPhotoOutput,这是因为在实践过程中发现,添加了一个当前设备所支持的拍照配置到会话管理(CaptureSession.addOutput())时,系统会返回当前拍照配置流不支持,并关闭相机,导致相机预览黑屏,所以这里没有添加。issues:远程相机拍照失败 not found in supported streams
代码如下:
加载过度动画
说明: 在相机切换中会需要释放原相机的资源,在重启新相机,在通过软总线通道同步远程相机的预览数据,这里需要一些时间,根据目前测试,在网络稳定状态下,切换时间3~5s,网络不稳定状态下,切换最长需要13s,当然有时候会出现无法切换成功,这种情况可能是远程设备已经下线,无法再获取到数据。
代码如下:
至此,分布式相机的整体流程就已实现完成。
下一篇我们介绍下分布式相机开发中所遇到的问题。
感谢
如果您能看到最后,还希望您能动动手指点个赞,一个人能走多远关键在于与谁同行,我用跨越山海的一路相伴,希望得到您的点赞。
好奇未来分布式能用在不同网络下吗
分布式不仅仅支持网络,还可以支持蓝牙等进行数据传输,如果需要在不同网络下支持互通,我个人的理解需要创建一个云端服务器进行桥接,使用账号登录进行互信授权。
学习了,感谢回复
分布式相机的代码有链接可以下载吗?
暂时没有,分布式相机与本地相机的区别,在应用层上只是需要更换一个设备ID即可,其他的流程与启动本地相机相同。
您好,能给源码不?
您好,源码有些内容需要剥离暂时没办法直接给您,你可以参看本系列的中、下两个部分,功能性源码都有的,欢迎交流。
大佬,您这个方法能否一并提供下,不懂这个方法里面实现,谢谢
这个方法有具体实现方式不
我这里是简单的处理,我用的是DAYU200设备作为测试工具,目前设备上只有一个摄像头,所以切换相机的时候,默认选择设备上的唯一相机作为显示,只要重启切换的相机预览流就OK。
代码如下: