WWDC Quick Look 💓 By SwiftGGTeam
Meet Audio Workgroups

Meet Audio Workgroups

观看原视频

Highlight

2020 年的 macOS 和 iOS 为实时音频线程提供 Audio Workgroups,让内核知道多个线程共享同一个音频截止时间,从而减少低延迟合成和信号处理中的 glitch 风险。

核心内容

做专业音频应用时,延迟不是一个抽象指标。合成器和信号处理代码要在很短的音频周期里产出结果。以前,开发者可以创建自己的 realtime thread(实时线程),把工作拆到多个 CPU 核心上跑。问题在于,调度器只看到一组普通线程,看不到它们正在追赶同一个音频 deadline(截止时间)。

Core Audio 自己管理的 I/O thread 已经知道每个 render cycle(渲染周期)的开始和截止时间。音频服务器会唤醒客户端线程,等待它产出音频,再写入硬件。如果客户端太慢,线程错过截止时间,用户听到的就是 discontinuity 或 glitch(音频中断)。

Audio Workgroups 补上了这一层语义。一个 workgroup 由一个或多个线程组成,master thread(主线程)在每个工作周期告诉内核:工作从何时开始,何时必须结束。这样,性能控制器不只根据“应用用了多少线程”做判断,还能知道这些线程在同一个实时音频任务里协作。

这场 session 的重点很窄:如果你的 app 或 Audio Unit 自己创建实时音频线程,就要把这些线程加入正确的 workgroup。系统框架管理的线程会自动加入;你自己创建的线程,需要显式处理。

详细内容

设备 I/O 并行线程:加入设备的 workgroup

04:00)典型场景是一个 app 同时渲染三条复杂轨道。I/O thread 负责最终混音,两个辅助实时线程并行计算轨道内容。它们和 I/O thread 使用同一个截止时间,但默认不在设备 workgroup 中。

解决方式是先取得设备的 os_workgroup。在 macOS 上,可以通过 HAL API 从 audio device 取得;在各个平台上,也可以从 input/output audio unit、AUHAL 或 AURemoteIO 的属性取得。线程启动后调用 os_workgroup_join,线程结束前调用 os_workgroup_leave

设备 I/O 并行线程工作流
1. 取得设备的 os_workgroup。
2. 在自建 realtime thread 中调用 os_workgroup_join。
3. 让该线程参与与 I/O thread 同 deadline 的音频渲染。
4. 在线程结束前调用 os_workgroup_leave。

关键点:

  • 第 1 步的来源可以是 macOS 的 HAL API,也可以是 input/output audio unit、AUHAL 或 AURemoteIO。
  • 第 2 步只需要在线程中执行一次,作用是把当前实时线程加入设备 workgroup。
  • 第 3 步对应 transcript 里的三条复杂 track 场景:I/O thread 等待辅助线程,再混合最终结果。
  • 第 4 步在线程结束前执行,避免线程生命周期结束后还留在 workgroup 语义中。

异步实时线程:创建自己的工作周期

05:25)另一类辅助线程不跟设备 I/O 同步。它们有自己的节奏和截止时间,例如后台提前计算一段合成结果,再交给 I/O thread 消费。它们不能加入设备 workgroup,因为 deadline 不同。

这时要创建独立 workgroup。session 给出的 API 是 AudioWorkIntervalCreateos_workgroup_interval_startos_workgroup_interval_finish。master thread 在每个周期开始时报告 start time 和 deadline,周期结束时报告 finish。

异步 realtime thread 工作流
1. 用 AudioWorkIntervalCreate 创建自己的 workgroup。
2. 让这些 realtime threads 调用 os_workgroup_join。
3. master thread 在每个工作周期调用 os_workgroup_interval_start。
4. startTime 可以来自 mach_absolute_time,deadline 必须大于 startTime。
5. 每个工作周期结束时调用 os_workgroup_interval_finish。

关键点:

  • AudioWorkIntervalCreate 用来创建和设备 I/O 不同 cadence(节奏)的 workgroup。
  • os_workgroup_join 仍然只需要在线程加入 workgroup 时调用一次。
  • startTime 可以是当前时间;如果你知道周期实际开始时间,也可以传入最近过去的时间点。
  • deadline 必须大于 startTime,两者单位都是 mach_absolute_time
  • session 特别提醒,不要假设 mach_absolute_time 是纳秒;需要用 mach_timebase_info 换算频率。
  • os_workgroup_interval_finish 告诉内核本周期已经完成,让性能控制器得到完整的工作周期信息。

Audio Unit 辅助线程:观察宿主的渲染上下文

07:03)大多数 Audio Unit 不会创建自己的实时线程。少数大型插件会把一次 render request(渲染请求)拆到多个线程上。插件面对的难点是:宿主传进来的线程上下文不固定,可能是设备 I/O thread,也可能是宿主的非实时 worker thread。

为了解决这个问题,Apple 在 2020 年引入 AURenderContextObserver。Audio Unit 实现 render context observer property,系统会在每次 render request 之前调用这个 block,并传入包含 os_workgroup 的 context struct。插件在发现 workgroup 变化时,把自己的辅助实时线程加入新的 workgroup。

Audio Unit render context 工作流
1. Audio Unit 实现 AURenderContextObserver property,并返回一个 block。
2. 系统在每次 render request 前调用这个 block。
3. block 收到的 context struct 包含一个 os_workgroup。
4. 如果 context 的 workgroup 改变,就把辅助 realtime threads 加入新的 workgroup。

关键点:

  • AURenderContextObserver 是 Audio Unit 开发者实现的 render context observer property。
  • context struct 里的 os_workgroup 是当前 render request 对应的 workgroup。
  • workgroup 可能每次渲染都变化,插件不能在初始化时缓存一个永久值。
  • 辅助实时线程要跟随 observer 看到的 workgroup 变化,重新加入当前 render request 的 workgroup。
  • host 不应该主动调用 observer;session 明确说明系统会在 host render 时自动完成这件事。

线程数量:让 workgroup 给出并行上限

09:21)创建并行 worker thread 时,不要只按 CPU core 数量盲目全开。Audio Workgroups 提供 os_workgroup_max_parallel_threads,可以根据已知 workgroup 推荐可用的并行线程数。

推荐并行线程数工作流
1. 等到已知 workgroup 后,调用 os_workgroup_max_parallel_threads。
2. Audio Unit 初始化时可以按 CPU core 数创建 worker threads。
3. realtime render 阶段只使用 API 推荐的线程数量。

关键点:

  • os_workgroup_max_parallel_threads 必须在知道 workgroup 后才能调用。
  • Audio Unit 可能要等到 realtime render 阶段才拿到 workgroup。
  • session 建议初始化时可以按 CPU core 数创建线程,渲染时只启用推荐数量。
  • 这个数量是每个渲染周期真正参与工作的线程数,不等于已创建线程总数。

核心启发

1. 给大型合成器加并行渲染模式

  • 做什么:把多个 oscillator、filter 或 effect chain 拆到辅助实时线程中并行计算。
  • 为什么值得做:session 说明并行线程如果加入设备 workgroup,内核会知道它们和 I/O thread 共享同一个截止时间。
  • 怎么开始:先从 HAL、AUHAL 或 AURemoteIO 取得设备 os_workgroup,在线程启动后调用 os_workgroup_join,再记录启用前后的 overload 或 glitch 次数。

2. 为离线预计算建立独立实时周期

  • 做什么:给提前计算音频片段的 worker 线程建立独立 cadence,避免把不同 deadline 的任务塞进设备 I/O workgroup。
  • 为什么值得做:session 明确区分了 parallel-to-device 和 asynchronous-to-device 两种线程,后者需要自己的 workgroup。
  • 怎么开始:用 AudioWorkIntervalCreate 创建 workgroup,在 master thread 的每个周期调用 os_workgroup_interval_startos_workgroup_interval_finish

3. 让复杂 Audio Unit 适配不同宿主

  • 做什么:插件在不同宿主 app 中运行时,动态跟随宿主当前 render request 对应的 workgroup。
  • 为什么值得做:session 指出 Audio Unit 不知道宿主传入的是设备 I/O thread 还是其他 worker context,只能在 render time 观察。
  • 怎么开始:实现 AURenderContextObserver,比较本次 context 的 os_workgroup 是否变化,再把自己的辅助实时线程切换到新 workgroup。

4. 用推荐线程数控制 CPU 压力

  • 做什么:为实时音频引擎增加“预创建线程、按周期启用”的调度层。
  • 为什么值得做:session 给出 os_workgroup_max_parallel_threads,用于决定当前 workgroup 下应使用多少并行线程。
  • 怎么开始:初始化时创建固定 worker pool,render time 取得 workgroup 后查询推荐数量,只唤醒需要参与当前周期的 worker。

关联 Session

评论

GitHub Issues · utterances