Acouvero
下载
TECHNICAL DOC · v0.3.1

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

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

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

应用概述

输入:用户输入房间尺寸 / 形状 / 装修材质、音箱与听音位位置,或通过 LiDAR(RoomPlan)扫描得到的房间几何。

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

支持矩形、L 形、U 形与任意简单多边形房型,每种几何下声学计算都按真实多边形而非外接矩形。

§ 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)。

应用层提供 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 的递归思路实现:

  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 +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 缓冲:

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)。

非矩形房型用外接矩形近似估算(精确模态需 FEM 求解),在 UI 上明示。

§ 03

几何系统

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+ 上调 RoomCaptureSessionCapturedRoomMapper 把扫描结果映射为 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

扫到的非矩形房间走 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)
§ 04

听音区与多 listener

AcousticConfiguration.listeners 支持 1..N 个听音位。声学分析把每个 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 > 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 提示用户这是“贴身吸声敏感区”。

§ 06

测量

6.1 ESS 扫频

参考 Farina 法:

  • 起 180 Hz / 终 4 kHz / 0.45 s 对数扫频
  • 反向滤波(makeInverseSweep)= 时间反转 + 1/freq 幅度补偿
  • 录音用 AVAudioRecorder 写 AAC m4a(44.1 kHz 单声道)
  • 读回 → 匹配滤波 → analyzeImpulse
采样率防御 若设备实际录音采样率 ≠ 44.1 kHz(USB-C 外接音频接口 / 某些 Bluetooth 配置),匹配滤波被跳过,UI 显式提示用户结果可信度低,建议断开外接设备后重测。

6.2 拍手脉冲

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

6.3 实测 vs 模拟对比

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

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

6.4 环境噪声 & 校准

AmbientNoiseEstimator 跑一段静默录音算房间背景噪声 dBFS / 噪声谱,用于评估测量信噪比是否够。DelayCalibrationService 跑近场播放 + 录音确定 iPhone 自身播放-录制延迟(typical 80–150 ms)。

§ 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: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> 包装)— 单条记录损坏不会让整个列表加载失败。

§ 08

工程实现要点

8.1 SwiftUI + @Observable

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

AcouVeroViewModel.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 隔离

ProjectStoreMeasurementStoreRoomPlanService 都是 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 >= 1
  • analysis.warnings 包含对非矩形房型的近似说明
  • 推荐方案总数与 budget 匹配
  • 比例分配器在各种 budget 下保证 sum ≤ budget + 对称性
  • 多边形闭合 / 顶点 &lt; 3 等边界
§ 09

已知限制

  1. 多阶反射在非矩形房间用外接矩形近似 — 凹角附近 2-3 阶 flutter / slapback 风险评估可能偏差。1 阶反射点本身按真实多边形几何。
  2. 房间模态在非矩形房间按外接矩形估算 — 精确模态需 FEM 求解。
  3. iPhone 内置麦克风测量 — 非校准麦克风,频率响应 / 灵敏度不平直;可信度始终标为 .low,作为与几何估算的交叉参考用。
  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