应用概述
输入:用户手动录入房间尺寸 / 形状 / 装修材质、音箱与听音位位置,或用 AR 卷尺量房(无需 LiDAR)/ LiDAR(RoomPlan)扫描得到房间几何。
- 早期反射点定位
- RT60(实测 + 模拟)与 EDT / C50 / C80 / D50(模拟 RIR 派生)全套声学指标
- 房间模态频率列表
- 吸音板布置方案(位置、数量、产品 SKU)
- 3D 可视化 + 反射能量热力图
- 安装手册级 PDF 导出
- iPhone 内置麦克风做的实测扫频 / 拍手测量
支持矩形、L 形、U 形与任意简单多边形房型,一阶反射与面积/包含判定按真实多边形计算;2/3 阶反射与房间模态按外接矩形近似(见 §9)。
声学模型
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)。
应用层提供 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 的递归思路实现:
- 枚举所有“墙面序列” [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 +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 缓冲:
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)。
枚举范围:每轴 n ≤ 4 且频率 ≤ 300 Hz。非矩形房型用外接矩形近似估算(精确模态需 FEM 求解),在 UI 上明示。
几何系统
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)。坐标系映射:
扫描结果按墙段数判定:恰好 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)
听音区与多 listener
AcousticConfiguration.listeners 支持 1..N 个听音位(0 听音位时自动合成默认主听位并告警;0 音箱时走 §5.2 的通用模板)。声学分析把每个 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 个墙角低频陷阱(真正绕过预算的强制 4 角只在 §5.3 低频模态密集路径)
- 通用听音:对称基础铺底 + 提示加音箱
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)= 时间反转 + 幅度补偿(+6 dB/倍频程,随频率上升,抵消 ESS 的粉噪频谱) - 录音用 AVAudioRecorder 写无损 Linear PCM(16-bit 单声道 .caf,约 96 dB 动态范围,感知压缩不适合脉冲测量;采样率跟随音频会话——内置麦 44.1 kHz,USB 测量麦按其原生 48 kHz)
- 读回 → 匹配滤波 → analyzeImpulse
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 并排显示偏差:
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 麦或内置麦),防止带麦蓝牙设备在播放时抢走输入、污染测量。
可视化与输出
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: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 绑定,换输入需重新导入 / 标定。
工程实现要点
8.1 SwiftUI + @Observable
ViewModel(FindEchoViewModel)用 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 是 actor(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 >= 1analysis.warnings包含对非矩形房型的近似说明- 推荐板数总计非负(totalPanelCount ≥ 0;无预算相符断言)
- 自相交自定义多边形安全回退(不 crash,降级为矩形)
- 顶点 < 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 内可跟随系统或手动切换。
已知限制
- 多阶反射在非矩形房间用外接矩形近似 — 凹角附近 2-3 阶 flutter / slapback 风险评估可能偏差。1 阶反射点本身按真实多边形几何。
- 房间模态在非矩形房间按外接矩形估算 — 精确模态需 FEM 求解。
- iPhone 内置麦克风测量 — 非校准麦克风,频率响应 / 灵敏度不平直;可信度始终标为
.low,作为与几何估算的交叉参考用。接 miniDSP UMIK-1 / UMIK-2 等 USB 测量麦并导入其 REW 校准文件时,分带电平按校准曲线修正;再用 94 dB 参考源做一次 SPL 标定后,环境噪声与测量电平可显示为绝对 dB SPL(未标定时只显示 dBFS)。 - 单声源 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.