通过 Headphone Motion 访问 AirPods 的头部跟踪数据

Core Motion

Core Motion 用以处理加速度计(Accelerometer)、陀螺仪(Gyroscope)、计步器(Pedometer),以及其他环境相关事件。在我们的应用程序中,可以使用这些数据作为用户交互、健身跟踪等活动的输入。

框架的服务可提供对原始值、处理值,两种运动数据的访问。原始值反映了来自硬件的未修改数据,而处理值消除了可能对数据使用产生不利影响的偏差。例如,处理后的加速度值仅反映用户引起的加速度,而不反映重力引起的加速度。

框架的某些服务即使在具有所需硬件的设备上也可能不可用。例如,许多 Core Motion 服务可供 visionOS 应用程序使用,但这些服务不适用于其在 iPad 或 iPhone 应用程序上。在尝试使用任何与运动相关的服务之前,需要检查这些服务的可用性。

iOS 应用程序必须在其 Info.plist 文件中包含其所需数据类型的使用描述,否则尝试访问相应的服务时,应用程序会崩溃。 要访问运动和健身数据,请包含 NSMotionUsageDescription;要访问跌倒检测服务,请包含 NSFallDetectionUsageDescription。

本文将围绕 Core Motion 框架下的 CMHeadphoneMotionManager,讲解和实现访问 AirPods (3rd generation)、AirPods Pro (all generations)、 AirPods Max 的头部跟踪数据。

使用描述与授权

创建 HeadphoneMotion 项目,并在 Info.plist 文件中新增 Privacy - Motion Usage Description,并添加文字描述:

稍后,在第一次使用 Core Motion 相关 API 时,将会有对应的提示。我们可以通过 authorizationStatus() API 获取返回监听耳机运动的授权状态:

swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    // ...
    open class func authorizationStatus() -> CMAuthorizationStatus
}

若用户在安装后首次未授权该权限,需要引导用户至设置->对应应用->开启"运动与健身"。可以使用 isDeviceMotionAvailable 属性判断当前设备是否支持和是否有权限获取头部跟踪数据:

swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    // ...
    open var isDeviceMotionAvailable: Bool { get }
}

获取头部跟踪数据

CMHeadphoneMotionManager 提供了两种头部跟踪数据获取方式:

  1. 使用CMHeadphoneMotionManager 的 startDeviceMotionUpdates()方法,开始设备运动数据的更新,将在稍后修改 CMHeadphoneMotionManager 的 deviceMotion 属性:
swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    //...
    open var deviceMotion: CMDeviceMotion? { get }
    open func startDeviceMotionUpdates()
}
  1. 使用CMHeadphoneMotionManager 的 startDeviceMotionUpdates(to:withHandler:) 方法,指定队列并流式接收数据更新回调:
swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    //...
    open func startDeviceMotionUpdates(to queue: OperationQueue, withHandler handler: @escaping CMHeadphoneMotionManager.DeviceMotionHandler)
}
  1. CMHeadphoneMotionManager的 isDeviceMotionActive 属性标识设备是否处于活动状态,在更新头部跟踪数据数据期间将返回 true:
swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    //...
    open var isDeviceMotionActive: Bool { get }
}
  1. 相应的,CMHeadphoneMotionManager 的 stopDeviceMotionUpdates() 方法停止数据接收的能力:
swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    //...
    open func stopDeviceMotionUpdates()
}
  1. CMHeadphoneMotionManager 提供了代理方法 CMHeadphoneMotionManagerDelegate,回调首次设置及后续的连接和断开耳机的事件:
swift 复制代码
open class CMHeadphoneMotionManager : NSObject {
    //...
    weak open var delegate: CMHeadphoneMotionManagerDelegate?
}
​
public protocol CMHeadphoneMotionManagerDelegate : NSObjectProtocol {
    // 连接耳机时调用
    optional func headphoneMotionManagerDidConnect(_ manager: CMHeadphoneMotionManager)
    // 断开耳机时调用
    optional func headphoneMotionManagerDidDisconnect(_ manager: CMHeadphoneMotionManager)
}

以上述方式二为例,我们可以使用以下代码进行数据获取,:

swift 复制代码
import CoreMotion
​
class CoreMotionViewController: UIViewController {
    
    let manager = CMHeadphoneMotionManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        guard manager.isDeviceMotionAvailable else {
            print("Device Motion is not Available.")
            return
        }
        manager.delegate = self
        manager.startDeviceMotionUpdates(to: OperationQueue.main) { [weak self] deviceMotion, error in
            guard let self, error == nil else {
                print("Start device motion updates failed.")
                return
            }
            self.printData(from: deviceMotion)
        }
    }
  
     deinit {
        manager.stopDeviceMotionUpdates()
    }
}
​
extension CoreMotionViewController: CMHeadphoneMotionManagerDelegate {
    
    func headphoneMotionManagerDidConnect(_ manager: CMHeadphoneMotionManager) {
        print("Headphone motion manager did connect")
    }
        
    func headphoneMotionManagerDidDisconnect(_ manager: CMHeadphoneMotionManager) {
        print("Headphone motion manager did dis connect")
    }
    
    private func printData(from deviceMotion: CMDeviceMotion?) {
        // 将在下部分进行解析
    }
}

解析头部跟踪数据

我们将通过 CMDeviceMotion 来解析头部跟踪数据。CMDeviceMotion 是设备姿态、旋转速率和加速度的封装测量。

swift 复制代码
class CMDeviceMotion : CMLogItem

要解释姿态数据,我们需要知道设备坐标轴的方向,下图显示了 Airpods 的正 x 轴、正 y 轴和正 z 轴:

CMDeviceMotion 的声明如下,我们依次来看:

swift 复制代码
@available(iOS 4.0, *)
open class CMDeviceMotion : CMLogItem {
    // 返回设备的姿态。
    open var attitude: CMAttitude { get }
    // 对于带有陀螺仪的设备,返回设备的旋转速率。
    open var rotationRate: CMRotationRate { get }
    // 返回设备参考系下的重力矢量。
    open var gravity: CMAcceleration { get }
    // 返回用户给予设备的加速度。
    open var userAcceleration: CMAcceleration { get }
    // 对于带有磁力计的设备,返回相对于设备的磁场矢量。
    @available(iOS 5.0, *)
    open var magneticField: CMCalibratedMagneticField { get }
    // 返回相对于 CMAttitude 参考系的航向角度,范围为 [0,360) 度。
    @available(iOS 11.0, *)
    open var heading: Double { get }
    // 返回用于计算设备运动数据的传感器的位置。
    open var sensorLocation: CMDeviceMotion.SensorLocation { get }
}
  1. attitude 是设备相对于已知参考系的方向。roll、pitch、yaw 属性获得弧度为单位的欧拉角:

可以通过数学计算,将其简单转换为度数为单位:

swift 复制代码
let rollValue  = (180 / Double.pi) * deviceMotion.attitude.roll
let pitchValue = (180 / Double.pi) * deviceMotion.attitude.pitch
let yawValue   = (180 / Double.pi) * deviceMotion.attitude.yaw

以上图为例,若我们向下低头 45 度,则计算得到的 pitchValue 为 -45,若我们向上抬头 45 度,则计算得到的 pitchValue 为 45,以此类推。

在 CMAttitude 中,我们除了使用 roll、pitch、yaw 属性获得弧度为单位的欧拉角表示,还可以使用 rotationMatrix 获得其旋转矩阵表示、可以使用 quaternion 获得其四元数表示。这里不做详细展开。

  1. rotationRate 是设备的旋转速率,CMRotationRate 结构中包含指定设备绕三个轴的旋转速率的数据 x、y、z。其单位为弧度/秒。
  2. gravity,返回以设备参考系表示的重力矢量,包含 x、y、z 三个方向。设备的总加速度等于重力加上用户施加到设备的加速度。单位是 m/s²,或是 N/kg。
  3. userAcceleration 是用户给予设备的加速度,包含 x、y、z 三个方向。单位是 m/s²,或是 N/kg。
  4. magneticField 返回相对于设备的磁场矢量。 其属性 field 是包含 3 轴校准磁场数据的结构。accuracy 是指示磁场估计准确性的枚举常量值。
  5. heading 是相对于当前参考系的航向角,以度为单位。该属性只在 VisionOS 系统上生效。
  6. sensorLocation 定义设备的传感器位置。返回枚举类型默认传感器位置、传感器位于左侧耳机中、传感器位于右侧耳机中。

总结

CMHeadphoneMotionManager 是 Apple 在 iOS 14 及以后的版本中提供的一个 API,它允许应用程序检测和响应耳机的运动和姿态。这个 API 可以检测到耳机的倾斜、旋转和移动等动作,并将这些信息传递给应用程序。对于增强现实和虚拟现实应用程序、运动和健身应用程序等,为开发人员提供了一种新的用户交互的方式,带来用户带来更丰富、更个性化的体验。

笔者的 GitHub Headphone Motion 项目基于上述描述,通过 CoreMotion 和 SceneKit 实现了头部跟踪数据的获取与可视化,以及实现使用头部跟踪数据进行视频流滑动的 Demo:

头部跟踪数据的获取与可视化 使用头部跟踪数据进行视频流滑动

其他参考内容

相关推荐
美狐美颜SDK开放平台11 小时前
准备开发直播APP?视频美颜SDK接入前的十项检查清单
android·ios·音视频·直播美颜sdk·视频美颜sdk
承渊政道1 天前
iOS远程控制电脑用什么好?iPhoneiPad远程控制Windows电脑最佳方案2026横评
windows·ios·远程控制·1024程序员节·iphone ipad
茶底世界之下1 天前
彩色建筑适合转黑白吗?用 Awaking 检查颜色消失后的层次
ios
黑科技iOS上架1 天前
App Store 26年10月新应用审核放缓大量应用不进审
ios·审核
OxYGC1 天前
手机为什么能“自动跳过“开屏广告?李跳跳、GKD 与 iOS 代理软件原理盘点
android·ios·开屏广告·广告屏蔽
终端安全笔记1 天前
一份配置管十项 AI 功能:它关的是入口,不是数据
网络安全·ios·移动设备管理
2501_915106322 天前
跨平台抓包工具怎么选?TraceEagle抓包鹰 的四种跨平台能力
网络协议·计算机网络·网络安全·ios·adb·https·udp
黑科技iOS上架2 天前
iOS应用上架:提前规避审核被拒风险
ios·审核
茶底世界之下2 天前
方图一定要裁照片吗?用 Awaking 比较裁切与留白
ios
00后程序员张2 天前
无需 Mac 的 iOS App 上架工具,开心上架Appuploader 负责哪些环节
android·ios·小程序·https·uni-app·iphone·webview