音频分析器最难的部分,不是画一条会动的曲线,而是让每一条曲线在正确的时间描述同一条信号,同时不让音频回调变成 UI 调度器,也不让屏幕暂时变慢后积累越来越长的队列。
SignalMetric 的一个核心决定贯穿了整个实现:Monitor、Spectrum、Timeline 和 Scope 应当读取同一份分析帧,而不是四个各自追赶的独立工具。
左右滑动查看完整图表
图表暂时无法生成,下方仍可查看源代码。
查看图表源代码
flowchart LR
A[实时麦克风输入] --> D[音频采集]
B[导入文件或本地录音] --> C[AVAudioPlayerNode]
C --> E[主混音器 Tap]
D --> F[2,048 采样窗口]
E --> F
F --> G[串行 ScopeAnalyzer]
G --> H[最新 ScopeFrame]
H --> I[ViewModel 以 30 FPS 发布]
I --> J[Monitor]
I --> K[Spectrum]
I --> L[Timeline]
I --> M[Scope]
D --> N[受限录音写入器]
图本身很常见,真正决定仪表是否可信的是边界的细节。
所有来源最终进入同一分析契约
SignalMetric 有四种来源:无需权限的 Demo、Live Mic、导入音频和显式本地录音。它们进入 AVAudioEngine 的位置并不相同:
- 麦克风会在 input node 安装 tap。
- 导入文件和完成的本地录音经由
AVAudioPlayerNode播放,并在 main mixer tap 分析实际播放路径。 - 录音复用麦克风 input tap,同时把已接收的 buffer 交给文件写入器。
- Demo 不请求权限,直接生成确定性的
ScopeFrame。
关键不在于所有来源都使用同一套播放控制,而在于真实音频最终都会满足同一契约:Float32 单声道窗口、采样率、路由指纹和声道数。
多声道输入会折叠为明确的单声道分析信号。这使频谱和响度都围绕同一边界,但也意味着它不是多声道交付仪表。UI 会显示声道上下文,不会隐瞒折叠操作。路由或采样率变化时,分析器会重置状态并重新配置频段;把旧的整合响度或 FFT 范围带入新输入,会得到一张看似连续、实际不再一致的仪表盘。
让 render thread 只做有边界的工作
AVAudioEngine tap 靠近音频渲染路径。它不应该等待视图更新、发起网络请求、追加无限历史,或分配一块随会话长度增长的缓冲区。
SignalMetric 收集固定的 2,048 采样窗口,并预分配两个 sample slot:一个可以正在串行分析,另一个可以等待处理。窗口完整后,采集路径拷贝到可用 slot 并调度一次分析;若已经有一个待处理窗口,就不再往后追加。宁可跳过一次调度机会,也不制造延迟债务。
左右滑动查看完整图表
图表暂时无法生成,下方仍可查看源代码。
查看图表源代码
sequenceDiagram
participant Tap as Audio tap
participant Capture as 固定采集槽
participant Analyzer as 串行分析器
participant Store as 最新帧锁
participant UI as 主线程 30 FPS
Tap->>Capture: 填充 2,048 采样窗口
alt 没有等待帧
Capture->>Analyzer: 调度一个窗口
Analyzer->>Analyzer: FFT、电平、响度、波形
Analyzer->>Store: 替换最新 ScopeFrame
else 已有等待帧
Capture-->>Capture: 不增长积压
end
UI->>Store: 读取最新完成帧
Store-->>UI: 一份一致的快照
UI->>UI: 渲染四个工作区
UI 由主线程以 30 FPS 读取最近完成的帧,音频回调从不直接调用 SwiftUI。这样显示节奏保持稳定,而音频回调仍可随设备路由和 I/O buffer 改变。
最新帧通过锁整体替换。所以 Monitor 的读数不会属于一个 FFT 窗口,而 Scope 的波形却属于另一个。界面可能落后一帧,但它内部是一致的。
一帧里有哪些证据
ScopeFrame 是信号处理和呈现之间的边界,包含:
- 原始 RMS dBFS、平滑显示电平、即时 Sample Peak、峰值保持和 4x True Peak estimate;
- K-weighted Momentary、Short-Term、Integrated loudness 与 LRA;
- 24 个显示频段、64 个对数 dBFS 频谱点、峰值保持和频谱统计;
- 强分量、Centroid、Bandwidth、85% roll-off、Flatness;
- 触发后的 256 bin 波形包络;
- DC offset、过零率、声道数、削波状态、主频和节拍置信度;
- 采样率、来源指纹、时间戳与窗口时长。
格式化、颜色和页面规则不属于这一层。分析器只拥有信号证据;ViewModel 才负责单位文本、当前工作区、无障碍摘要、响应偏好、会话重置和有界的 30 秒历史。这样 UI 重构或格式变化不会悄悄改变测量结果。
FFT 只是工作的一部分
分析器用 Accelerate/vDSP 在 Hanning 窗之后执行 2,048 点 DFT。Hanning 窗可减少泄漏,但一个显眼的 bin 也不能直接被解释为中心频率上存在完美正弦波。
强分量展示的流程是:
- 找到高于绝对和相对阈值的局部极大值。
- 抑制同一可分辨 Hanning 主瓣附近的候选,避免一个分量变成多个旁瓣卡片。
- 用对数幅度抛物线插值细化频率。
- 对展示电平补偿 Hanning coherent gain。
- 仅当至少两个峰支持同一候选基频时,才添加谐波标签。
- 以更低的展示频率匹配并平滑分量,避免排序闪烁。
因此 Spectrum 可以让频率证据易读,但不会声称六条正弦波重建了整个音频。原始频率分辨率仍按 sampleRate / 2048 公开。
电平和响度也遵循同样的克制。RMS 与 Sample Peak 处在实用数字范围内;True Peak 明确写作 estimate。响度使用持久 K-weighting 和按时长加权的窗口:400 ms Momentary、3 s Short-Term、每 100 ms 跳进的 400 ms Integrated block,以及绝对和相对门限。它使用 BS.1770-style 单声道术语,不宣称认证节目交付。
历史也必须有上限
好的 Timeline 应该连续,但仍必须有固定内存预算。SignalMetric 以每秒 8 帧发布频谱历史,30 秒最多 240 列;它不是从会话开头开始无限保留每一帧。
Scope 也遵循同样规则。它不是隐藏的全分辨率录音,而是在上升过零触发点后,把当前窗口缩减为固定 256 bin 的 min/max/average 包络。窄瞬态仍会以每 bin 的 min/max 被保留,显示成本也始终可预测。
左右滑动查看完整图表
图表暂时无法生成,下方仍可查看源代码。
查看图表源代码
flowchart TD
A[原始来源帧] --> B[固定 2,048 采样分析窗口]
B --> C[64 个对数频段]
C --> D[每秒 8 帧历史发布]
D --> E[240 列滚动 Timeline]
B --> F[触发后缩减]
F --> G[256 bin 波形包络]
E --> H[恒定会话内存]
G --> H
这不只是性能优化。无限历史会让内存和渲染成本随时间漂移,让长会话和短会话变成两种不同的工具;一台仪表不该在运行一小时后改变自己的性格。
录音是第二项实时责任
录音把磁盘 I/O 加入了时间敏感的输入路径。若把原始 AVAudioPCMBuffer 直接交给慢速写入器,存储压力就会反过来影响采集。SignalMetric 会先复制每个已接受的 buffer,再在独立串行队列上写入。
待写队列最多 64 个 buffer。存储跟不上时,录音会停止,而不是让内存无限增长。用户停止、发生中断、路由改变或应用进入后台时,引擎先拒绝新 buffer,随后等待已经接受的写入完成,最后才关闭本地 M4A;主线程不会为此等待。
左右滑动查看完整图表
图表暂时无法生成,下方仍可查看源代码。
查看图表源代码
stateDiagram-v2
[*] --> LiveAnalysis
LiveAnalysis --> Recording: 点击 Record
Recording --> Finalizing: 停止 / 中断 / 后台
Finalizing --> LocalFile: 已接收写入排空并关闭 M4A
Finalizing --> Failed: 空文件或写入失败
LocalFile --> FileAnalysis: 打开本地录音
FileAnalysis --> LiveAnalysis: 切换来源
正常 Live Mic 仍然是内存分析;只有用户点下 Record 才会创建文件,文件也只在用户选择分享目的地时离开应用。
边界本身就是设计的一部分
SignalMetric 不走几条看似方便的捷径:
- 不从音频回调调用 SwiftUI。
- 不让队列或历史随会话时长增长。
- 不上传麦克风或导入音频做处理。
- 不把未校准麦克风读数描述为 SPL。
- 不把 4x 采样间估算描述为认证结果。
- 节拍和音高置信度不足时,不强行给出确定数值。
结果不是四个彼此无关的动画,而是一条来源、一个分析边界,以及四种更准确的提问方式。SignalMetric 产品指南从使用者角度介绍了这四个工作区。