Acouvero
下载
TECHNICAL DOC · v0.4.0

房间声学,
测量
方案的全栈

Acouvero 是一款运行在 iPhone / iPad 上的房间声学分析与吸音方案生成工具。本文面向对声学工程或客户端实现感兴趣的读者,覆盖核心算法、几何系统、测量方法、推荐系统与工程实现要点。

倍频带
6
房型
4类几何
多阶反射
1–3
§ 01

应用概述

输入:用户手动录入房间尺寸 / 形状 / 装修材质、音箱与听音位位置,或用 AR 卷尺量房(无需 LiDAR)/ LiDAR(RoomPlan)扫描得到房间几何。

输出
  • 早期反射点定位
  • RT60(实测 + 模拟)与 EDT / C50 / C80 / D50(模拟 RIR 派生)全套声学指标
  • 房间模态频率列表
  • 吸音板布置方案(位置、数量、产品 SKU)
  • 3D 可视化 + 反射能量热力图
  • 安装手册级 PDF 导出
  • iPhone 内置麦克风做的实测扫频 / 拍手测量

支持矩形、L 形、U 形与任意简单多边形房型,一阶反射与面积/包含判定按真实多边形计算;2/3 阶反射与房间模态按外接矩形近似(见 §9)。

§ 02

声学模型

2.1 Sabine RT60

经典 Sabine 公式:

SABINE · RT60 $$ RT_{60} = \frac{0.161 \cdot V}{\sum_i S_i \alpha_i} $$
$V$
房间体积(m³),多边形房型走 Shoelace 面积 × 高度,扣除柱 / 梁等障碍物体积。
$\sum_i S_i \alpha_i$
各面表面积乘吸声系数。系数采用 6 段倍频带(125 / 250 / 500 / 1k / 2k / 4k Hz)。
$0.161$
= 24·ln(10)/c,其中 c = 343 m/s(20°C)。

应用层提供 4 个装修材质预设(硬质空房 / 普通硬装 / 软装家具 / 已有处理),每个预设给出按频段的吸声系数曲线;顶部单值 RT60 估算与板数预算用中高频平均吸声系数,6 段 Sabine 曲线用于分频段显示。目标 RT60 也按 6 段曲线给(不是单值),覆盖低频允许更长尾混响的 EBU Tech 3276 / THX 等行业标准。

2.2 图像源法(Image Source Method, ISM)

第一次反射点用镜像法:把声源关于墙面镜像,求“镜像源 → 听音位”的线段与墙面的交点即反射点。

  • 矩形房间:直接对 6 面平面(4 墙 + 地 + 顶)做镜像,闭式解。
  • 非矩形房间PolygonReflectionCalculator):把 RoomPolygon.walls() 拆出的每一段墙做镜像 + 段内参数 s ∈ [0, 1] 校验(地板/天花反射另要求反射点 XY 在多边形内);一阶路径还做 2D 遮挡检测——声源→反射点→听音位连线穿过其它墙段即丢弃。

多阶反射(1–3 阶)由 MultiOrderReflectionCalculator 按 pyroomacoustics 的递归思路实现:

  1. 枚举所有“墙面序列” [s₁, …, sₙ],相邻不同墙
  2. 镜像源迭代:S₀ = source,Sₖ = mirror(Sₖ₋₁, sₖ)
  3. 从 listener 反向 trace:从 Sₙ 拉线到 listener,与 sₙ 的交点是 Pₙ;再从 Sₙ₋₁ 拉到 Pₙ,与 sₙ₋₁ 的交点是 Pₙ₋₁;一路回推
  4. 每个交点必须在墙面有效范围内 + 段内参数 t ∈ (0, 1)
近似 · APPROXIMATION 多阶在非矩形房间用外接矩形近似(凹角附近会有“幽灵反射”),在分析层会过滤掉反射点距任一真实墙段 > 0.3 m 的路径,剩下的合法路径才进入 flutter / slapback 检测。

2.3 路径能量

宽带能量按反射次数 + 距离衰减:

PATH ENERGY $$ E_{rel} = (1 - \bar{\alpha})^N \cdot \frac{d_{direct}^2}{d_{total}^2} $$
  • α 限制在 [0, 0.99],避免 α = 1 时出现 log(0) 或 pow(负, N) = NaN 污染整条路径
  • 距离衰减按球面发散 1/r²
  • 转 dB 用 10·log₁₀(能量域)

分频段能量按 6 段倍频带分别算(每段用 finishProfile.absorptionByBand 的频段系数)。

2.4 RT60 估算(实测)

ImpulseResponseEstimator

  • 匹配滤波:录到的 0.45 s 扫频先做 ESS 反向滤波(Farina 法)压成单脉冲,等效 SNR +32 dB
  • rmsEnvelope:滑窗 RMS 包络
  • Schroeder 反向积分:E_back(t) = ∫ₜ^∞ s²(τ) dτ,做 10·log₁₀ 归一化得 EDC 曲线
  • 斜率线性回归:按 SNR 优先取 T30 [−5, −35 dB],不够回退到 T20 [−5, −25] 或 T10 [−5, −15]
  • RT₆₀ = −60 / slope

倍频带 RT60:在匹配滤波后的样本上按 ISO 倍频带(250 / 500 / 1k / 2k / 4k)中心频率 + Q = √2 做带通滤波,对每段独立跑 Schroeder。

2.5 RIR 模拟

SimulatedRIRGenerator 把多阶反射路径转成离散脉冲事件,写到 8 kHz × 0.5 s PCM 缓冲:

SimulatedRIRGeneratorpseudocode
for path in reflectionPaths:
    t   = path.totalDistance / speedOfSound
    amp = 10^(path.relativeEnergyDB / 20)
    impulse[round(t * sampleRate)] += amp

加上直达声脉冲,再做 Schroeder 反向积分提取 EDT / RT60 / C50 / C80 / D50 五个指标。

Before / After 对比 每条反射路径按其经过的墙累计板数衰减,生成“装板后”模拟 RIR。UI 可以 A/B 切换看 EDT / C50 改善量,再用 AVAudio 卷积干信号实时听处理前后的差异。

2.6 房间模态

矩形房间模态频率:

ROOM MODES $$ f_{n_x, n_y, n_z} = \frac{c}{2} \sqrt{\left(\frac{n_x}{L_x}\right)^2 + \left(\frac{n_y}{L_y}\right)^2 + \left(\frac{n_z}{L_z}\right)^2} $$

按 nₓ, n_y, n_z 中非零的个数分类:1 个 = 轴向(axial),2 个 = 切向(tangential),3 个 = 斜向(oblique)。

枚举范围:每轴 n ≤ 4 且频率 ≤ 300 Hz。非矩形房型用外接矩形近似估算(精确模态需 FEM 求解),在 UI 上明示。

§ 03

几何系统

3.1 房型支持

  • 矩形 — width × length × height
  • L 形 — 在指定角去掉一个 nw × nl 凹槽
  • U 形 — 在指定墙中央去掉一个 nw × nl 凹槽
  • 自定义 — 任意简单多边形(≥ 3 顶点,≤ 24 顶点)

所有非矩形由 RoomPolygon.from(shape:, bboxWidth:, bboxLength:) 转成 CCW 顶点列表。一阶反射、面积/周长与包含判定直接用这套顶点;非法自定义多边形(顶点 < 3 / > 24、自相交、面积近零)会静默降级为外接矩形。

3.2 多边形几何工具

  • 面积 — Shoelace 公式
  • 周长 — 直接累加边长
  • contains(x, y) — Ray-casting + 边界 epsilon 兜底(角点处避免误报)
  • clamped(point) — 矩形房型走 [0, w] × [0, l] 快速路径;非矩形走 polygon.boundingBox 的真实 [minₓ, maxₓ] × [min_y, max_y],然后 polygon.contains 测,不在多边形内时二分拉向 bbox 中心
  • walls() — 把多边形拆成按 inward normal 分类的墙段(自动归到 leftWall / rightWall / frontWall / backWall),方便复用矩形版面板推荐器
  • PolygonValidator.isSimple — 校验非自相交简单多边形

3.3 RoomPlan 扫描

通过 RoomCaptureSession(iOS 16+ API;App 内扫描功能整体门槛为 iOS 17)调起扫描,CapturedRoomMapper 把扫描结果映射为 ScannedRoom(包含 wallSegments + openings + furniture)。坐标系映射:

ARKit (x, y, z) → y 是垂直 App (x, y, z) → z 是垂直 墙端点 (z=0 地面) App.x = ARKit.x App.y = ARKit.z App.z = 0 门窗 / 家具中心  App.x = ARKit.x App.y = ARKit.z App.z = ARKit.y

扫描结果按墙段数判定:恰好 4 段墙直接按矩形处理,其余走 RectangularRoomSimplifier.polygonShapeFromScan 把 wallSegments 链成 polygon 顶点。RoomPlan 端点典型误差 3–8 cm,12 段闭环可累积 5 cm,所以闭环 epsilon 用 12 cm。链不通则降级到外接矩形 + 提示用户重新扫描。

3.4 AR 卷尺量房

无 LiDAR 的机型走 ARTapeMeasureView(RealityKit ARView 世界跟踪):准星对准墙面,两次点击记录起点 / 终点,按 宽 → 长 → 高 分步量房,最后确认生成矩形房间;含相机权限引导与追踪失败提示。

3.5 自由顶点编辑器

FreeVertexEditorView 让用户在 SwiftUI Canvas 上拖动顶点:

  • 长按删除(顶点数 > 3 时,删后仍 ≥ 3)
  • 拖动逐帧做 isSimple 校验,自相交的新位置不生效(顶点原地停住);空白处点按可在最近边插入顶点,另有逐顶点数值输入与「重置为外接矩形」
  • 0.05 m 网格吸附
  • bbox 外预留 max(0.5, bbox × 0.2) 米拖动余量,让用户能把顶点拖出当前 bbox(父视图自动撑大 room.width / length)
§ 04

听音区与多 listener

AcousticConfiguration.listeners 支持 1..N 个听音位(0 听音位时自动合成默认主听位并告警;0 音箱时走 §5.2 的通用模板)。声学分析把每个 listener 都跑一次镜像法,反射点合并到一个数组(primary listener 的点排在前面)。面板推荐器按每面墙的反射点 count 加权(cap = max(6, speakerCount × listenerCount)),多座位共用一面墙自然把该墙推向预算上限。

UI 视觉
  • 3D 视图中 secondary listener 节点用 opacity 0.45 + scale 0.75
  • 顶视图(RoomPlanView)中 primary 反射点全色 r=5,secondary 用 0.45 透明 r=3
  • 反射点列表(RecommendationView)只列 primary 的,副听位用脚注 “+ N 处反射已计入推荐方案”

多阶反射 / RIR / 热力图仍按 primary listener 一次算(per-listener O(speakers × surfaces³) 计算量太重,且 RIR 是单点指标)。

§ 05

推荐系统

PanelPlacementRecommender 输出 [PanelRecommendation],每条包含:位置(左墙 / 右墙 / 顶 / 前墙 / 后墙 / 角)、数量、优先级(必装 / 强推 / 推荐 / 可选)、effectivenessMultiplier、targetReflectionPointId。

5.1 比例分配(最大余数法)

老版本用贪心 add():左墙吃完 → 右墙 0;budget 紧时天花 / 前 / 后墙也会被饿死。新版用 largest-remainder method:

  • 算每面 wall 的 raw desired(反射点数 + 房间深浅 + 用例)
  • floor(raw × budget / total) 作为初始分配
  • raw &gt; 0 的墙保证至少 1 块;仅当预算小于有需求的墙面数时,按优先级从低到高归零
  • 总和 &gt; budget 时按 fractional remainder 从最小开始削
  • 左右墙对称 pass:raw[0] == raw[1] 时强制 alloc[0] == alloc[1]
  • 剩余 leftover 按 frac 最大重分

5.2 用例差异化模板

无音箱场景下走 use-case-specific 模板:

  • 会议室:天花板 35% + 后墙 20% + 两侧墙各 12%
  • 播客:麦克风正前 25% + 两侧各 18% + 顶 20%
  • 录音棚:四面对称压死 + 预算内尽量凑满 4 个墙角低频陷阱(真正绕过预算的强制 4 角只在 §5.3 低频模态密集路径)
  • 通用听音:对称基础铺底 + 提示加音箱

5.3 物理刚需绕过预算

墙角低频陷阱在 80–150 Hz 模态密集时强制 4 根,绕过 totalPanel 限制。理由:墙角声压最大,低频陷阱效率比墙面高 ~1.5×,是物理刚需而不是预算项。

5.4 听音位近端加权

后处理把听音位 1.5 m 半径内的墙面推荐标成 .nearListener,effectivenessMultiplier ×1.15,title / reason 提示用户这是“贴身吸声敏感区”。

§ 06

测量

6.1 ESS 扫频

参考 Farina 法:

  • 起 180 Hz / 终 4 kHz / 0.45 s 对数扫频
  • 反向滤波(makeInverseSweep)= 时间反转 + 幅度补偿(+6 dB/倍频程,随频率上升,抵消 ESS 的粉噪频谱)
  • 录音用 AVAudioRecorder 写无损 Linear PCM(16-bit 单声道 .caf,约 96 dB 动态范围,感知压缩不适合脉冲测量;采样率跟随音频会话——内置麦 44.1 kHz,USB 测量麦按其原生 48 kHz)
  • 读回 → 匹配滤波 → analyzeImpulse
采样率对齐 扫频 WAV、逆扫频与录音全部按音频会话的实际采样率生成(makeInverseSweep(sampleRate:) 已参数化)——匹配滤波在内置麦 44.1 kHz 与 USB 测量麦 48 kHz 下都始终生效。

6.2 拍手脉冲

不放任何声音,用户在 1 秒窗口内大力拍一次手,共录 4.5 s(约 3.5 s 尾音)。直接走 analyzeImpulse(不需要匹配滤波,因为脉冲本身就是窄脉冲)。覆盖 125 Hz 倍频带(扫频从 180 Hz 起,覆盖不到 125)。

6.3 实测 vs 模拟对比

MeasuredVsSimulatedCard 拉项目最近一次实测 RT60,跟当前几何模拟 RT60 并排显示偏差:

< 15%模型与实测吻合
15–30%有差异(可能由未建模家具 / 装修材料导致)
> 30%差异较大,建议核对房间几何 / 材料

6.4 环境噪声 & 校准

AmbientNoiseEstimator 录 3 s 静默(掐头去尾各 100 ms)算房间背景噪声:宽带平均 / 峰值 dBFS + 5 档安静度评级(未标定时 dBFS 仅作相对比较;做过 SPL 标定则显示 dB SPL)。近场延迟校准复用同一 ESS 扫频:手机放在距音箱 30 cm 内播放,取录音起点到冲激峰的延迟(含刻意的 250 ms 预滚),建议测 2–3 次——最近 3 次读数散布 < 5 ms 判高置信、< 20 ms 判中置信。延迟值绑定当前「录音输入 + 播放路由」,任一端变化即自动作废;远场测量显示自动扣除后的传播延迟。

6.5 USB 测量麦与输出路由

测量页的输入选择器列出内置麦与 class-compliant USB 输入(如 miniDSP UMIK-1 / UMIK-2);热插拔监听 routeChangeNotification,外置麦消失自动退回内置麦。录音与扫频采样率跟随会话实际值(UMIK-1 = 48 kHz)。REW .txt 校准文件直接导入(解析 Sens Factor / SERNO,对数频率域线性插值、端点 clamp;房间测量建议用 90° 文件),校正量只叠加到分频段电平显示,不改变 RT60 衰减斜率。

一次性 SPL 标定:把声级校准器(默认 94 dB @ 1 kHz)套在麦克风上,App 采 3 s 电平求 dBFS → SPL 偏移,之后电平读数显示真实 dB SPL。校准(曲线 + 偏移)按输入端口 UID 绑定、持久化到 ~/Library/Application Support/mic_calibration.json;换输入后 UI 橙色提示校准不适用,需重新导入并标定。

播放侧走系统路由选择器(AVRoutePickerView 封装,放在扫频 / 近场校准卡上),session 放开 AirPlay / 蓝牙 A2DP / HFP,可经外置音响播放测试音;无外置输出时强制扬声器。录音输入总是显式钉住用户所选(USB 麦或内置麦),防止带麦蓝牙设备在播放时抢走输入、污染测量。

§ 07

可视化与输出

7.1 3D 视图

Room3DController 用 SceneKit 渲染:墙面 / 地面 / 顶面 / 音箱节点(薄板 cabinet + accent tick + channel pill)/ 听音位(FE glyph + drop stem)/ 障碍物(半透明 SCNBox)/ 推荐面板(按 wall 分布的吸音板 mockup)/ 多阶反射路径线段 / 反射能量热力图叠加。

设计 · TECHNICAL DRAWING 设计上走“技术图风格”(hairline 边线、constant lighting、无 PBR),跟传统照片级 3D 渲染区分。

7.2 反射能量热力图

ReflectionEnergyMapGenerator 把每条多阶反射路径的能量按经过的墙累加到该墙的 uCells × vCells 网格(默认 12×6)。每面墙一张能量图,3D 视图上叠加成能量色层(蓝 → 青 → 绿 → 黄 → 红)。

EnergyHeatmapRenderer 把网格转 UIImage:16 px/格 block 渲染(cell 间不插值,便于辨认网格边界;上墙平滑靠 SceneKit 纹理双线性过滤)+ 自绘 5 段色带(蓝→青→绿→黄→红)+ alpha 按能量在固定 −30…0 dB 标尺上的位置从 0.20 到 0.85 线性映射(−∞ 全透明)。

7.3 RIR 卷积预览

RIRPreviewPlayer 用 AVAudioEngine + AVAudioPlayerNode 把程序合成的干信号(拨弦 / 击掌 / 类元音语音,无内置采样)卷积模拟 RIR 后播放,让用户能听到“如果不装吸音板 / 装了之后”房间听感的差异。

时域卷积 RIRConvolver.convolve(dry:rir:) 直接做 O(N × M)(N = dry samples,M = RIR samples)。归一化按峰值 0.9 防止 clip。

7.4 PDF 安装手册

ReportExporter 用 UIGraphicsPDFRenderer 渲染 A4 PDF(595 × 842 pt):

  • 房间概览 + 3D 快照
  • 6 段 RT60 Sabine 估算 vs 目标条形图(实测 RT60 不进 PDF)
  • 反射点表 + 模态表 + 风险摘要
  • 推荐面板表(购物清单 BOM 在 App 内展示,不进 PDF)
  • Geist 字体 + PingFangSC fallback(CoreText cascadeList,避免 CJK 渲染成 □□□)

7.5 项目持久化

ProjectStore(actor)写 ~/Library/Application Support/Acouvero/projects.json[RoomProject],每条含完整 Room + speakers + listeners + recommendations + panelCountOverrides 等。首次启动自动从旧目录 FindEcho/ 迁移(FindEcho 是产品改名前的内部代号,代码符号仍沿用旧名)。

MeasurementStore(actor)写 ~/Library/Application Support/Acouvero/measurements.json[MeasurementResult],按 roomId 关联到 project。

两者都有元素级 decode 恢复(Throwable<T> 包装)— 单条记录损坏不会让整个列表加载失败。

MicCalibrationStore~/Library/Application Support/mic_calibration.json(根目录,无子目录):REW 校准曲线 + SPL 偏移,按输入端口 UID 绑定,换输入需重新导入 / 标定。

§ 08

工程实现要点

8.1 SwiftUI + @Observable

ViewModel(FindEchoViewModel)用 Swift 5.9 @Observable macro,状态字段直接观测,没有 Combine。AcousticAnalysis 计算结果按 AcousticConfiguration Equatable 比较做缓存:

FindEchoViewModel.analysisswift
var analysis: AcousticAnalysis {
    let cfg = configuration
    if let cached = cachedAnalysis, cachedConfiguration == cfg {
        return cached
    }
    let result = analysisService.analyze(cfg)
    cachedConfiguration = cfg
    cachedAnalysis = result
    return result
}

8.2 Actor 隔离

ProjectStoreMeasurementStoreactor(IO / 异步逻辑天然串行化),RoomPlanService@MainActor 类。UI 调用方用 async / await 拿结果,主线程不会被磁盘 IO 阻塞。

8.3 AVAudio 生命周期

AudioSessionManager 集中管理 session 类别(.playback for RIR preview,.playAndRecord for 测量)。打断保护两条路径都有:测量侧 AudioMeasurementService 内置 InterruptionMonitor 监听 AVAudioSession.interruptionNotification,扫频 / 拍手 / 环境噪音任一路被电话 / Siri 打断即作废结果并报错;RIR 预览侧 RIRPreviewPlayer 同样监听打断,自动停 player node 并清 isPlaying。测量中途音频路由变化也会作废本次测量。

8.4 Task 取消

播放卷积是后台任务,用户连点多个 button 时 pendingPlayTask?.cancel() + Task.isCancelled 双重 check 防止乱序回 MainActor 写错 state。

8.5 测试覆盖

AllShapesAndUseCasesTests 用 Swift Testing 框架跑全场景矩阵:每种房型 × 每种用例(单听音位;音箱按用例取立体声对或单只居中),验证:

  • analysis.reflectionPaths.count >= 1
  • analysis.warnings 包含对非矩形房型的近似说明
  • 推荐板数总计非负(totalPanelCount ≥ 0;无预算相符断言)
  • 自相交自定义多边形安全回退(不 crash,降级为矩形)
  • 顶点 &lt; 3 等边界情况的回退

8.6 平台与本地化

部署目标 iOS 17.0,iPhone + iPad;零第三方依赖(无 SPM / CocoaPods 包),只用系统框架(SwiftUI / SceneKit / ARKit / RoomPlan / AVFoundation / Accelerate)。本地化用 .xcstrings 字符串目录:源语言中文,另有 English / Français / Deutsch / Español / Português (Brasil) 共 6 种,App 内可跟随系统或手动切换。

§ 09

已知限制

  1. 多阶反射在非矩形房间用外接矩形近似 — 凹角附近 2-3 阶 flutter / slapback 风险评估可能偏差。1 阶反射点本身按真实多边形几何。
  2. 房间模态在非矩形房间按外接矩形估算 — 精确模态需 FEM 求解。
  3. iPhone 内置麦克风测量 — 非校准麦克风,频率响应 / 灵敏度不平直;可信度始终标为 .low,作为与几何估算的交叉参考用。接 miniDSP UMIK-1 / UMIK-2 等 USB 测量麦并导入其 REW 校准文件时,分带电平按校准曲线修正;再用 94 dB 参考源做一次 SPL 标定后,环境噪声与测量电平可显示为绝对 dB SPL(未标定时只显示 dBFS)。
  4. 单声源 ISM 假设 — 没考虑音箱箱体衍射、号筒指向性等真实音箱特性。
  5. 不模拟低频陷阱 — 80 Hz 以下低频问题在 Sabine 模型外,需要专门的低频处理。
  6. AR 扫描精度 — RoomPlan 端点典型误差 3-8 cm,12 段闭环可累积 5+ cm。墙厚未建模。
§ 10

参考

  1. Sabine, W. C. (1922). Collected Papers on Acoustics. Harvard University Press.
  2. Allen, J. B., & Berkley, D. A. (1979). Image method for efficiently simulating small-room acoustics. JASA, 65(4), 943–950.
  3. Schroeder, M. R. (1965). New method of measuring reverberation time. JASA, 37(3), 409–412.
  4. Farina, A. (2000). Simultaneous measurement of impulse response and distortion with a swept-sine technique. Audio Engineering Society Convention 108.
  5. Scheibler, R., Bezzam, E., & Dokmanić, I. (2018). Pyroomacoustics: A Python package for audio room simulation and array processing algorithms. ICASSP 2018.
  6. EBU Tech 3276. Listening conditions for the assessment of sound programme material.
  7. ITU-R BS.1116. Methods for the subjective assessment of small impairments in audio systems.
  8. BB93 — Acoustic design of schools (UK).
  9. THX Pro Cinema Certification requirements.
想要更直白的版本?

《为什么你的房间会改写你听到的音乐》

读 Deep Read