应用概述
输入:用户输入房间尺寸 / 形状 / 装修材质、音箱与听音位位置,或通过 LiDAR(RoomPlan)扫描得到的房间几何。
- 早期反射点定位
- RT60 / EDT / C50 / C80 / D50 全套声学指标(实测 + 模拟)
- 房间模态频率列表
- 吸音板布置方案(位置、数量、产品 SKU)
- 3D 可视化 + 反射能量热力图
- 安装手册级 PDF 导出
- iPhone 内置麦克风做的实测扫频 / 拍手测量
支持矩形、L 形、U 形与任意简单多边形房型,每种几何下声学计算都按真实多边形而非外接矩形。
声学模型
2.1 Sabine RT60
经典 Sabine 公式:
- $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)。
应用层提供 5 个装修材质预设(空房 / 普通硬装 / 软装家具 / 已有处理 / 录音棚),每个预设给出按频段的吸声系数曲线。目标 RT60 也按 6 段曲线给(不是单值),覆盖低频允许更长尾混响的 EBU Tech 3276 / THX 等行业标准。
2.2 图像源法(Image Source Method, ISM)
第一次反射点用镜像法:把声源关于墙面镜像,求“镜像源 → 听音位”的线段与墙面的交点即反射点。
- 矩形房间:直接对 6 面平面(4 墙 + 地 + 顶)做镜像,闭式解。
- 非矩形房间(
PolygonReflectionCalculator):把RoomPolygon.walls()拆出的每一段墙做镜像 + 段内参数 s ∈ [0, 1] 校验 + 反射点 XY 必须在多边形内。
多阶反射(1–3 阶)由 MultiOrderReflectionCalculator 按 pyroomacoustics 的递归思路实现:
- 枚举所有“墙面序列” [s₁, …, sₙ],相邻不同墙
- 镜像源迭代:S₀ = source,Sₖ = mirror(Sₖ₋₁, sₖ)
- 从 listener 反向 trace:从 Sₙ 拉线到 listener,与 sₙ 的交点是 Pₙ;再从 Sₙ₋₁ 拉到 Pₙ,与 sₙ₋₁ 的交点是 Pₙ₋₁;一路回推
- 每个交点必须在墙面有效范围内 + 段内参数 t ∈ (0, 1)
2.3 路径能量
宽带能量按反射次数 + 距离衰减:
- α 限制在 [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 +40 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 缓冲:
for path in reflectionPaths:
t = path.totalDistance / speedOfSound
amp = 10^(path.relativeEnergyDB / 20)
impulse[round(t * sampleRate)] += amp
加上直达声脉冲,再做 Schroeder 反向积分提取 EDT / RT60 / C50 / C80 / D50 五个指标。
2.6 房间模态
矩形房间模态频率:
按 nₓ, n_y, n_z 中非零的个数分类:1 个 = 轴向(axial),2 个 = 切向(tangential),3 个 = 斜向(oblique)。
非矩形房型用外接矩形近似估算(精确模态需 FEM 求解),在 UI 上明示。
几何系统
3.1 房型支持
- 矩形 — width × length × height
- L 形 — 在指定角去掉一个 nw × nl 凹槽
- U 形 — 在指定墙中央去掉一个 nw × nl 凹槽
- 自定义 — 任意简单多边形(≥ 3 顶点,≤ 24 顶点)
所有非矩形由 RoomPolygon.from(shape:, bboxWidth:, bboxLength:) 转成 CCW 顶点列表。下游声学计算都直接用这套顶点而非外接矩形。
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 扫描
iOS 17+ 上调 RoomCaptureSession,CapturedRoomMapper 把扫描结果映射为 ScannedRoom(包含 wallSegments + openings + furniture)。坐标系映射:
扫到的非矩形房间走 RectangularRoomSimplifier.polygonShapeFromScan 把 wallSegments 链成 polygon 顶点。RoomPlan 端点典型误差 3–8 cm,12 段闭环可累积 5 cm,所以闭环 epsilon 用 12 cm。链不通则降级到外接矩形 + 提示用户重新扫描。
3.4 自由顶点编辑器
FreeVertexEditorView 让用户在 SwiftUI Canvas 上拖动顶点:
- 长按删除(顶点数 ≥ 3 时)
- 拖动用
dragSnapshot记录上一合法状态,新位置自相交时静默回滚 - 0.05 m 网格吸附
- bbox 外预留 max(0.5, bbox × 0.2) 米拖动余量,让用户能把顶点拖出当前 bbox(父视图自动撑大 room.width / length)
听音区与多 listener
AcousticConfiguration.listeners 支持 1..N 个听音位。声学分析把每个 listener 都跑一次镜像法,反射点合并到一个数组(primary listener 的点排在前面)。面板推荐器按每面墙的反射点 count 加权(cap = max(6, speakerCount × listenerCount)),多座位共用一面墙自然把该墙推向预算上限。
- 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 是单点指标)。
推荐系统
PanelPlacementRecommender 输出 [PanelRecommendation],每条包含:位置(左墙 / 右墙 / 顶 / 前墙 / 后墙 / 角)、数量、优先级(必装 / 强推 / 推荐 / 可选)、effectivenessMultiplier、targetReflectionPointId。
5.1 比例分配(最大余数法)
老版本用贪心 add():左墙吃完 → 右墙 0;budget 紧时天花 / 前 / 后墙也会被饿死。新版用 largest-remainder method:
- 算每面 wall 的 raw desired(反射点数 + 房间深浅 + 用例)
- floor(raw × budget / total) 作为初始分配
- raw > 0 的墙保证至少 1 块(不让有需求的墙被完全饿死)
- 总和 > 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 个墙角低频陷阱
- 通用听音:对称基础铺底 + 提示加音箱
5.3 物理刚需绕过预算
墙角低频陷阱在 80–150 Hz 模态密集时强制 4 根,绕过 totalPanel 限制。理由:墙角声压最大,低频陷阱效率比墙面高 ~1.5×,是物理刚需而不是预算项。
5.4 听音位近端加权
后处理把听音位 1.5 m 半径内的墙面推荐标成 .nearListener,effectivenessMultiplier ×1.15,title / reason 提示用户这是“贴身吸声敏感区”。
测量
6.1 ESS 扫频
参考 Farina 法:
- 起 180 Hz / 终 4 kHz / 0.45 s 对数扫频
- 反向滤波(
makeInverseSweep)= 时间反转 + 1/freq 幅度补偿 - 录音用 AVAudioRecorder 写 AAC m4a(44.1 kHz 单声道)
- 读回 → 匹配滤波 → analyzeImpulse
6.2 拍手脉冲
不放任何声音,用户大力拍一次手,录 3 s 尾音。直接走 analyzeImpulse(不需要匹配滤波,因为脉冲本身就是窄脉冲)。覆盖 125 Hz 倍频带(扫频从 180 Hz 起,覆盖不到 125)。
6.3 实测 vs 模拟对比
MeasuredVsSimulatedCard 拉项目最近一次实测 RT60,跟当前几何模拟 RT60 并排显示偏差:
6.4 环境噪声 & 校准
AmbientNoiseEstimator 跑一段静默录音算房间背景噪声 dBFS / 噪声谱,用于评估测量信噪比是否够。DelayCalibrationService 跑近场播放 + 录音确定 iPhone 自身播放-录制延迟(typical 80–150 ms)。
可视化与输出
7.1 3D 视图
Room3DController 用 SceneKit 渲染:墙面 / 地面 / 顶面 / 音箱节点(薄板 cabinet + accent tick + channel pill)/ 听音位(FE glyph + drop stem)/ 障碍物(半透明 SCNBox)/ 推荐面板(按 wall 分布的吸音板 mockup)/ 多阶反射路径线段 / 反射能量热力图叠加。
7.2 反射能量热力图
ReflectionEnergyMapGenerator 把每条多阶反射路径的能量按经过的墙累加到该墙的 uCells × vCells 网格(默认 12×6)。每面墙一张能量图,3D 视图上叠加成色温层(蓝 → 红)。
EnergyHeatmapRenderer 把网格转 UIImage:Bicubic 上采样 + Magma colormap + alpha 透明度按能量绝对值。
7.3 RIR 卷积预览
RIRPreviewPlayer 用 AVAudioEngine + AVAudioPlayerNode 把任意干信号(语音 / 鼓 / 钢琴 sample)卷积模拟 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 实测 vs 目标条形图
- 反射点表 + 模态表 + 风险摘要
- 推荐面板表 + 购物清单 BOM
- Geist 字体 + PingFangSC fallback(CoreText cascadeList,避免 CJK 渲染成 □□□)
7.5 项目持久化
ProjectStore(actor)写 ~/Library/Application Support/AcouVero/projects.json:[RoomProject],每条含完整 Room + speakers + listeners + recommendations + panelCountOverrides 等。
MeasurementStore(actor)写 ~/Library/Application Support/AcouVero/measurements.json:[MeasurementResult],按 roomId 关联到 project。
两者都有元素级 decode 恢复(Throwable<T> 包装)— 单条记录损坏不会让整个列表加载失败。
工程实现要点
8.1 SwiftUI + @Observable
ViewModel(AcouVeroViewModel)用 Swift 5.9 @Observable macro,状态字段直接观测,没有 Combine。AcousticAnalysis 计算结果按 AcousticConfiguration Equatable 比较做缓存:
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 隔离
ProjectStore、MeasurementStore、RoomPlanService 都是 actor,IO / 异步逻辑天然串行化。UI 调用方用 async / await 拿结果,主线程不会被磁盘 IO 阻塞。
8.3 AVAudio 生命周期
AudioSessionManager 集中管理 session 类别(.playback for RIR preview,.playAndRecord for 测量)。AVAudioSession.interruptionNotification 监听电话 / Siri 打断,断开时自动 stop engine 并清 isPlaying 状态。
8.4 Task 取消
播放卷积是后台任务,用户连点多个 button 时 pendingPlayTask?.cancel() + Task.isCancelled 双重 check 防止乱序回 MainActor 写错 state。
8.5 测试覆盖
AllShapesAndUseCasesTests 用 Swift Testing 框架跑全场景矩阵:每种房型 × 每种用例 × 各种 listener / speaker 组合,验证:
analysis.reflectionPaths.count >= 1analysis.warnings包含对非矩形房型的近似说明- 推荐方案总数与 budget 匹配
- 比例分配器在各种 budget 下保证 sum ≤ budget + 对称性
- 多边形闭合 / 顶点 < 3 等边界
已知限制
- 多阶反射在非矩形房间用外接矩形近似 — 凹角附近 2-3 阶 flutter / slapback 风险评估可能偏差。1 阶反射点本身按真实多边形几何。
- 房间模态在非矩形房间按外接矩形估算 — 精确模态需 FEM 求解。
- iPhone 内置麦克风测量 — 非校准麦克风,频率响应 / 灵敏度不平直;可信度始终标为
.low,作为与几何估算的交叉参考用。 - 单声源 ISM 假设 — 没考虑音箱箱体衍射、号筒指向性等真实音箱特性。
- 不模拟低频陷阱 — 80 Hz 以下低频问题在 Sabine 模型外,需要专门的低频处理。
- AR 扫描精度 — RoomPlan 端点典型误差 3-8 cm,12 段闭环可累积 5+ cm。墙厚未建模。
参考
- Sabine, W. C. (1922). Collected Papers on Acoustics. Harvard University Press.
- Allen, J. B., & Berkley, D. A. (1979). Image method for efficiently simulating small-room acoustics. JASA, 65(4), 943–950.
- Schroeder, M. R. (1965). New method of measuring reverberation time. JASA, 37(3), 409–412.
- Farina, A. (2000). Simultaneous measurement of impulse response and distortion with a swept-sine technique. Audio Engineering Society Convention 108.
- Scheibler, R., Bezzam, E., & Dokmanić, I. (2018). Pyroomacoustics: A Python package for audio room simulation and array processing algorithms. ICASSP 2018.
- EBU Tech 3276. Listening conditions for the assessment of sound programme material.
- ITU-R BS.1116. Methods for the subjective assessment of small impairments in audio systems.
- BB93 — Acoustic design of schools (UK).
- THX Pro Cinema Certification requirements.