Reincubate 的 Camo SDK 能够将应用中的实时音视频数据发送到 macOS 和 Windows 上的 Camo Studio。它支持通过 USB 或无线方式工作。USB 针对低延迟和性能进行了优化,尽可能减少应用中的资源占用;无线方式则让你摆脱线缆的束缚。
Camo SDK 可供商业合作伙伴和协作方使用。如需了解更多信息,请通过 enterprise@reincubate.com 联系我们的团队。
目前,该 SDK 仅支持在运行 iOS 12 或更高版本的实体 iOS 或 iPadOS 设备(不包括模拟器和 Mac Catalyst)上运行的 iOS 应用。Camo SDK 并不原生兼容 Objective-C,因此如果你的应用主要使用 Objective-C 编写,可能需要创建一个封装层。
API 参考
如果你想直接上手,可以查看完整的 API 参考,其中记录了 Camo SDK 提供的每一个符号。否则,本文档将带你了解一个示例集成流程。
安装 Camo SDK
收到 Camo SDK 时,你会收到一个名为 CamoProducerKit.xcframework 的文件。这个独立框架包含了你所需的一切,没有任何外部依赖,因此安装非常简单。
- 在 Project Navigator 中选择你的应用项目。
- 在项目的 target 侧边栏中选择你的应用 target。如果没有看到侧边栏,它可能位于页面顶部的下拉菜单中。
- 确保你位于“General”标签页,然后向下滚动到“Frameworks, Libraries, and Embedded Content”标题处。
- 将
CamoProducerKit.xcframework文件拖放到该标题下方的文件列表中。 - 确保该 SDK 设置为“Embed & Sign”。
完成后,你应该会看到如图所示的框架:

示例应用中嵌入的 Camo SDK
与你的应用集成
Camo SDK 实际运行效果
Camo SDK 附带了一个演示应用,展示了如何从设备摄像头和麦克风,以及内置的视频文件中传输数据。
控制 Camo 服务
CamoProducerKit 的核心是 CamoController,它为你的应用提供了一个用于控制 Camo SDK 的集中式接口。
在你的应用中,你应该初始化一个 CamoController 实例。你可以选择在应用启动时执行此操作,也可以留到之后再进行。在通过 start() 显式启动之前,Camo 控制器只会占用极少的资源。不过,它会在初始化时为音视频编码做好准备。
import CamoProducerKit
class MyVideoManager {
let controller = CamoController()
// ...
}
当你想要在应用中启用 Camo 集成,启动 Camo 服务以便建立连接时,可以调用 start()。使用完毕后,可以调用 stop() 将其停止。
controller.start()
// ...
controller.stop()
什么时候应该启动和停止 Camo 服务?
我们在编写 CamoProducerKit 时力求始终占用尽可能少的资源。不过,为了便于与 Camo Studio 建立连接而保持服务运行,仍会占用一定资源。
为了获得最佳用户体验,我们建议始终保持 Camo 服务处于启用状态。或者,你也可以在引导流程中向用户介绍你的 Camo 集成,并让他们自行选择。
如果你需要释放这些资源,停止 Camo 服务会有所帮助。我们不建议在未通知用户的情况下这样做。
这段代码只是一个起点,它目前还无法接受来自 Camo Studio 的新连接。
响应新连接
在你的应用能够接受来自 Camo Studio 的新连接之前,你需要实现 CamoControllerDelegate。通过实现这两个委托方法,你可以在连接状态发生变化时收到通知,并决定是接受还是拒绝连接。
extension MyVideoManager: CamoControllerDelegate {
// Upon receiving a connection request, you can decide whether or not to accept it.
func camoControllerShouldAcceptIncomingConnection(_ controller: CamoController) -> Bool {
// You could return false if you aren't ready to accept connections, such as during onboarding.
return true
}
// Called whenever the connection state changes, such as if the Camo service starts or stops, or if a new connection is made.
func camoController(_ controller: CamoController, stateDidChangeTo state: CamoControllerState?) {
// From here, you can update your UI and other state
print("New state:", state)
}
}
完成后,你现在应该能够打开应用、连接 USB,并在 Camo Studio 中查看你的设备。
无线连接
要使用无线连接,用户必须授予本地网络访问权限。 应在 Info.plist 中将 Bonjour 服务添加为 NSBonjourServices:
_camo._tcp_camopairing._tcp
调用 camoController.start() 后,SDK 就已准备好进行配对,并与 Camo Studio 建立网络连接。
配对
当用户点击 Camo Studio 中的“+”按钮时,配对流程就会开始。Camo Studio 会显示一个二维码,需要在应用端进行扫描。
配对步骤:
- 点击 Camo Studio 中的“+”以显示二维码
- 扫描二维码并将其数据获取为字符串
- 通过调用
camoController.pairStudioWithQRCodeData(codeData) { success in }将该数据传递给 SDK pairStudioWithQRCodeData方法带有一个回调,用于告知你配对是否成功- SDK 会与 Camo Studio 完成配对,并自动建立连接。
如果你需要取消配对(例如用户关闭了二维码扫描器或应用),应调用 camoController.cancelPairing()
配对有 30 秒的超时限制。如果在调用 pairStudioWithQRCodeData 后的 30 秒内未完成配对,pairStudioWithQRCodeData 方法的回调将会以 success 值 false 被调用。
配对的设备
配对完成后,SDK 会将配对数据保存到已配对设备列表中。CamoController 提供了 2 个方法用于操作该列表:
pairedDevices用于获取该列表(如果你想在界面中显示该列表)removePairedDevice用于忽略该设备
下次要与 Camo Studio 建立无线连接时,只需点击 Camo Studio 中的“+”按钮并等待连接建立即可,无需再次扫描二维码。
确定连接状态
CamoController 提供了一个 CamoControllerState 属性,如上文所示,该属性也包含在状态变化委托方法中。该枚举为你的应用提供了有用的信息,例如可在界面中显示的已连接电脑名称。
以下是一个示例,展示如何更新界面以反映连接状态:
func camoController(_ controller: CamoController, stateDidChangeTo state: CamoControllerState?) {
let statusText = { () -> String in
guard case let .running(serviceState) = state else {
return "Camo service not running"
}
switch serviceState {
case .active(let connection): return "Connected to \(connection.name)"
case .paused(let connection): return "Paused, but connected to \(connection.name)"
case .notConnected: return "Camo service running, but no connection"
}
}()
DispatchQueue.main.async {
self.statusTextLabel.text = statusText
}
}
分发音视频数据
为了让客户端拥有尽可能大的灵活性,Camo SDK 不提供也不控制任何采集功能。相反,你需要负责为 CamoController 提供音频和视频数据。这可以通过两个简单的 API 调用来完成:
// upon receiving video from the camera or elsewhere
camoController.enqueueVideo(sampleBuffer: sampleBuffer)
// upon receiving audio from the microphone or elsewhere
camoController.enqueuePCMAudio(data: chunk)
避免不必要的处理
在处理任何音视频数据并将其提供给 Camo SDK 之前,你应确保存在一个处于活动且未暂停状态的连接,以避免不必要的资源消耗。你可以通过检查 CamoController 上的 state 来实现这一点。
发送视频
如果你的视频流程中可以获取到 CMSampleBuffer,将这些数据传递给 Camo SDK 就非常简单。
正因如此,使用基础的视频 AVCaptureSession 进行设置极为简单。以下是一个实际示例。
class CaptureController: AVCaptureVideoDataOutputSampleBufferDelegate {
// ...
func startSession() throws {
guard let camera = AVCaptureDevice.default(for: .video) else { fatalError("No camera found") }
let input = try AVCaptureDeviceInput(device: camera)
captureSession.addInput(input)
let output = AVCaptureVideoDataOutput()
output.alwaysDiscardsLateVideoFrames = true
output.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
captureSession.addOutput(output)
captureSession.startRunning()
}
// ...
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
camoController.enqueueVideo(sampleBuffer: sampleBuffer)
}
}
发送视频帧时,请确保:
- 帧时长为 30 FPS。
- 像素格式为
kCVPixelFormatType_32BGRA。
你可以在API 参考中查看更多详情。
发送音频
发送音频与发送视频类似,但根据你的应用接收音频的方式,可能需要多几个步骤。有关示例实现,请参阅 Camo SDK 附带的演示应用。
发送音频数据时,请确保:
- 采样率为 48 kHz。
- 音频编解码器为 LPCM。
- 声道数为 2。
- 位深度为 32。
- 每个音频数据包的采样数取决于 Studio(macOS 或 Windows),你可以通过 CamoController.audioSamplesRequired 获取该值。
你可以在API 参考中查看更多详情。