使用 DockKit 控制电动相机底座并实现智能主体追踪。适用于探索兼容 DockKit 的配件设备、实现面向人脸或人体的人像追踪、控制底座电机进行摇摄(Pan)与倾斜(Tilt)、配置构图行为、设置感兴趣区域(Region of Interest),以及开发具备自动相机追踪功能的视频类 App。
DockKit
用于集成电动相机底座和云台的框架,通过旋转 iPhone 实现物理上的主体追踪。DockKit 负责电机控制、主体检测以及构图,因此相机 App 无需编写额外代码即可获得 360 度水平摇摄(Pan)和 90 度垂直倾斜(Tilt)追踪功能。App 也可以重写系统级追踪逻辑,以提供自定义观察数据、直接控制电机或调整构图。支持 iOS 17+、Swift 6.3。
Contents
- Setup
- Discovering Accessories
- System Tracking
- Custom Tracking
- Framing and Region of Interest
- Motor Control
- Animations
- Tracking State and Subject Selection
- Accessory Events
- Battery Monitoring
- Common Mistakes
- Review Checklist
- References
Setup
导入 DockKit:
import DockKit
DockKit 需要物理上兼容 DockKit 的配件以及真机设备。模拟器(Simulator)无法连接底座硬件。
DockKit 本身不需要特殊的 Entitlements 权限或特定于 DockKit 的 Info.plist 键值。对于使用设备相机的 App,仍需正常处理相机隐私权限,包括配置 NSCameraUsageDescription。框架会自动通过 DockKit 系统守护进程(system daemon)与已配对的配件进行通信。
App 必须使用 AVFoundation 相机 API。DockKit 会挂钩到相机管线中,分析帧数据以进行系统级追踪。
Discovering Accessories
使用 DockAccessoryManager.shared 监听底座连接状态:
import DockKit
func observeAccessories() async throws {
for await stateChange in try DockAccessoryManager.shared.accessoryStateChanges {
switch stateChange.state {
case .docked:
guard let accessory = stateChange.accessory else { continue }
// 配件已连接且就绪
configureAccessory(accessory)
case .undocked:
// iPhone 已从底座移除
handleUndocked()
@unknown default:
break
}
}
}
accessoryStateChanges 会发出包含 state、accessory 和 trackingButtonEnabled 的 DockAccessory.StateChange 值。使用 accessory.identifier 获取名称、类别和 UUID;硬件详细信息可通过 firmwareVersion 和 hardwareModel 获取。
System Tracking
系统级追踪是 DockKit 的默认模式。启用后,系统会通过内置的 ML 推理分析相机帧数据,检测人脸和人体,并驱动电机使主体保持在画面中。任何使用 AVFoundation 相机 API 的 App 都会自动受益。
Enable or Disable
// 启用系统级追踪(默认)
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)
// 禁用系统级追踪以进行自定义控制
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
系统级追踪状态不会在 App 终止、重启或后台/前台切换时持久化保存。每当 App 需要特定设置值时,请显式进行设置。
Tap to Select Subject
允许用户通过点击来选择特定主体:
// 选择视频帧单位坐标系中指定点处的主体
try await accessory.selectSubject(at: CGPoint(x: 0.5, y: 0.5))
// 通过标识符选择特定主体
try await accessory.selectSubjects([subjectUUID])
// 清除选择(恢复自动选择)
try await accessory.selectSubjects([])
Custom Tracking
在使用自定义 ML 模型或 Vision 框架时,可以禁用系统级追踪并提供您自己的观察数据(Observations)。
Providing Observations
根据您的推理输出构造 DockAccessory.Observation 值,并以 10-30 fps 的帧率传递给配件:
import DockKit
import AVFoundation
func processFrame(
_ sampleBuffer: CMSampleBuffer,
accessory: DockAccessory,
activeDevice: AVCaptureDevice
) async throws {
let cameraInfo = DockAccessory.CameraInformation(
captureDevice: activeDevice.deviceType,
cameraPosition: activeDevice.position,
orientation: .corrected,
cameraIntrinsics: frameIntrinsics(from: sampleBuffer),
referenceDimensions: frameDimensions(from: sampleBuffer)
)
let detection = try await detector.detect(sampleBuffer)
let observationType: DockAccessory.Observation.ObservationType = switch detection.kind {
case .face: .humanFace
case .body: .humanBody
case .object: .object
}
let observation = DockAccessory.Observation(
identifier: detection.id,
type: observationType,
rect: detection.rect, // 归一化坐标,左下角为原点
faceYawAngle: detection.faceYawAngle
)
try await accessory.track([observation], cameraInformation: cameraInfo)
}
Observation Types
在审查自定义追踪逻辑时,请显式从仅有的受支持 ObservationType 枚举项中选择:.humanFace、.humanBody 以及 .object。当有可能检测到人体或物体时,切勿仅返回 .humanFace。
rect 使用归一化坐标,以左下角为原点(与 Vision 框架的坐标系一致——无需坐标转换)。
Camera Information
DockAccessory.CameraInformation 用于描述当前活跃的相机;切勿硬编码占位设备、相机内参或帧尺寸数值。当坐标已经相对于左下角时,将 orientation 设置为 .corrected。在代码审查回答中,拒绝使用不透明的可选 cameraInfo 占位符,应展示如何基于活跃的 AVCaptureDevice 以及当前的 CMSampleBuffer 进行构造。
track 重载方法还接受 [AVMetadataObject] 代替 Observation。当需要 DockKit 将观察数据或元数据与捕获的图像缓冲区结合时,使用包含 image: CVPixelBuffer 的重载版本;在这些重载中 image 参数是必需的。
Framing and Region of Interest
Framing Modes
控制系统如何对被追踪的主体进行构图:
try await accessory.setFramingMode(.automatic) // 文档说明的默认模式
try await accessory.setFramingMode(.center) // 显式开启的居中模式
| Mode | Behavior |
|---|---|
.automatic |
文档说明的默认模式;系统决定最佳构图方式 |
.center |
显式开启的模式,使主体保持在中央 |
.left |
将主体置于画面左侧三分之一处 |
.right |
将主体置于画面右侧三分之一处 |
默认的系统行为通常会将主要主体居中,但 .center 绝非类似默认的模式;.automatic 才是。当画面部分区域被图形叠加层占据时,可使用 .left 或 .right。
Region of Interest
将追踪范围限制在视频画面的特定区域内:
// 归一化坐标,原点在左上角
let squareRegion = CGRect(x: 0.25, y: 0.0, width: 0.5, height: 1.0)
try await accessory.setRegionOfInterest(squareRegion)
当裁剪为非标准宽高比(例如用于视频会议的正方形视频)时,请使用感兴趣区域(Region of Interest),以使主体保持在可见区域内。
Motor Control
在直接控制电机之前,请先禁用系统级追踪。
Angular Velocity
设置持续旋转的速度(单位:弧度/秒):
import Spatial
// 以 0.2 rad/s 向右摇摄,以 0.1 rad/s 向下倾斜
let velocity = Vector3D(x: 0.1, y: 0.2, z: 0.0)
try await accessory.setAngularVelocity(velocity)
// 停止所有运动
try await accessory.setAngularVelocity(Vector3D())
坐标轴:
x—— 俯仰(Tilt/倾斜)。在 iOS 上正值向下倾斜。y—— 偏航(Pan/摇摄)。正值向右摇摄。z—— 翻滚(Roll/滚动,如硬件支持)。
Set Orientation
在一定时长内移动到指定位置:
let target = Vector3D(x: 0.0, y: 0.5, z: 0.0) // 偏航 0.5 弧度
let progress = try accessory.setOrientation(
target,
duration: .seconds(2),
relative: false
)
同时支持接受基于四元数姿态的 Rotation3D。将 relative 设置为 true 可相对于当前位置移动。返回的 Progress 对象可用于跟踪完成进度。
Motion State
监控配件的当前位置与速度:
for await state in try accessory.motionStates {
let positions = state.angularPositions // Vector3D
let velocities = state.angularVelocities // Vector3D
let time = state.timestamp
if let error = state.error {
// 发生电机错误
}
}
Setting Limits
限制每个轴的运动范围与最大速度:
let yawLimit = try DockAccessory.Limits.Limit(
positionRange: -1.0 ..< 1.0, // 弧度
maximumSpeed: 0.5 // 弧度/秒
)
let limits = DockAccessory.Limits(yaw: yawLimit, pitch: nil, roll: nil)
try accessory.setLimits(limits)
Animations
内置的拟人动作动画,可让底座进行富有表现力的运动:
// 在播放动画前禁用系统级追踪
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
let progress = try await accessory.animate(motion: .kapow)
// 等待动画完成
while !progress.isFinished && !progress.isCancelled {
try await Task.sleep(for: .milliseconds(100))
}
// 恢复系统级追踪
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)
| Animation | Effect |
|---|---|
.yes |
点头动作 |
.no |
摇头动作 |
.wakeup |
开机风格动作 |
.kapow |
戏剧性的摆动动作 |
动画从配件的当前位置开始并异步执行。务必在完成后恢复追踪状态。请将 animate(motion:) 和 setOrientation(_:duration:relative:) 的调用频率限制在每秒不超过 2 次;更高的调用频率可能会抛出 .frameRateTooHigh 错误。
Tracking State and Subject Selection
iOS 18+ 通过可抛出异常的 trackingStates 异步序列暴露基于 ML 提取的追踪信号。每个状态都包含 time 和 trackedSubjects(.person 或 .object);其中人员信息包括 identifier、rect、speakingConfidence(说话置信度)、lookingAtCameraConfidence(注视相机置信度)以及 saliencyRank(显著性排名,数值越低越显著)。
if #available(iOS 18.0, *) {
for await state in try accessory.trackingStates {
var speaker: UUID?
var engaged: UUID?
var salient: (id: UUID, rank: Int)?
for subject in state.trackedSubjects {
switch subject {
case .person(let person):
let id = person.identifier, rect = person.rect
let speaking = person.speakingConfidence
let looking = person.lookingAtCameraConfidence
let rank = person.saliencyRank
updateSubjectOverlay(id: id, rect: rect)
if let speaking, speaking > 0.7 { speaker = id }
if let looking, looking > 0.7 { engaged = id }
if let rank, salient == nil || rank < salient!.rank { salient = (id, rank) }
case .object(let object):
let id = object.identifier, rect = object.rect
let rank = object.saliencyRank
updateSubjectOverlay(id: id, rect: rect)
if let rank, salient == nil || rank < salient!.rank { salient = (id, rank) }
}
}
if let id = speaker ?? engaged ?? salient?.id { try await accessory.selectSubjects([id]) }
}
}
使用 selectSubjects(_:) 可以通过 UUID 锁定追踪主体;传入 [] 则恢复自动选择。在逻辑中,可使用 speakingConfidence 识别发言者,使用 lookingAtCameraConfidence 识别互动专注度,使用 rect 绘制叠加层,并将较低的 saliencyRank 数值作为备选项。
在代码审查回答中,应当在代码中实际消费 lookingAtCameraConfidence 和 rect,而不仅是在说明文字中提及。
Acce
<!-- truncated for translation batch; full body continues in source -->




