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 是 AudioWorkIntervalCreate、os_workgroup_interval_start 和 os_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_start和os_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
- Port your Mac app to Apple silicon — 讲 Universal Mac、低层代码和插件在 Apple silicon 迁移中的注意事项。
- Explore the new system architecture of Apple silicon Macs — 补充 SoC 架构、统一内存和框架性能收益。
- Identify trends with the Power and Performance API — 展示如何把性能指标和诊断数据接入团队工具。
- Record stereo audio with AVAudioSession — 同属 WWDC 2020 音频开发主题,聚焦移动设备内置麦克风的立体声录音。
评论
GitHub Issues · utterances