Files

371 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RecorderStudio · 高清录音机
一个面向 **高保真、高清晰度** 的 Windows 录音软件:无损 WAV 母版、WASAPI 独占采集、
线性相位滤波、TPDF 抖动、ITU-R BS.1770 标准响度计量,并带完整的录音体检报告、
后期处理与多格式导出。界面用 PyQt5 手绘,深色专业风格。
自检套件 **97 项检查全部通过**,其中响度算法与 ffmpeg 的 `ebur128` 滤波器做过交叉验证,
WAV 编码用 Python 标准库与 ffmpeg 双向复核。
---
## 目录
1. [30 秒上手](#30-秒上手)
2. [为什么它"清晰"](#为什么它清晰)
3. [界面说明](#界面说明)
4. [参数怎么选](#参数怎么选)
5. [命令行用法](#命令行用法)
6. [录音体检报告](#录音体检报告)
7. [后期处理与导出](#后期处理与导出)
8. [自检与验证证据](#自检与验证证据)
9. [项目结构](#项目结构)
10. [常见问题](#常见问题)
11. [已知限制](#已知限制)
---
## 30 秒上手
```bat
:: 1) 安装依赖(装进项目内的 _vendor,不污染系统 Python)
python install_deps.py --with-pyqt5
:: 2) 检查依赖与设备
python install_deps.py --check
python -m recorder.cli --list-devices
:: 3) 启动
双击 启动录音机.bat :: 或 python -m recorder.gui
```
按下 **空格** 开始录音,再按一次停止;停止后自动弹出体检报告。
> 第一次用,建议先做两件事:
> 1. 点左侧「**检测能力**」,确认所选采样率带 ✓(避免设备不支持导致开流失败);
> 2. 点「**校准本底噪声**」,安静 3 秒,程序会告出房间本底噪声并给出低切/噪声门建议。
---
## 为什么它"清晰"
"清晰度"在数字录音里由几个环节共同决定,这个软件在每一环都做了明确取舍:
| 环节 | 做法 | 为什么重要 |
|---|---|---|
| **采集通道** | WASAPI **独占模式**(可关闭) | 绕开 Windows 混音器,不重采样、不被其它程序改音量;这是 Windows 上最接近原始信号的方式 |
| **内部精度** | 全程 float32 浮点运算 | float32 的 24 位尾数正好无损容纳 24 位 PCM,运算不产生额外量化 |
| **写盘位深** | 16 / 24 / 32 位 PCM / 32 位浮点 | 24 位动态范围 144 dB,远超人耳与话筒本底;24 位用真正的 3 字节打包,不做 16 位截断 |
| **抖动** | 16/24 位可选 **TPDF 抖动**(1 LSB 峰峰值) | 把量化失真从"与信号相关的非线性失真"变成无关宽带白噪。实测误差与信号相关系数从 0.224 降到 0.0008 |
| **低切滤波** | 5000+ 抽头**线性相位 FIR**,并补偿群延迟 | 不引入相位失真;实测直流抑制 -240 dB、1 kHz 通带 +0.0000 dB、样本数严格守恒(不丢头掉尾) |
| **实时链路** | 音频回调**只做内存拷贝** | 滤波/FFT/写盘全在写入线程,从根上避免 xrun(爆音、掉采样)。实测长时间录音 0 xrun、0 溢出块 |
| **计量标准** | 真峰值 4× 过采样(BS.1770 附录 2)+ K 加权响度(EBU R128) | 能看到"采样点之间"的过冲,避免转码后突然过载;响度按国际标准,不是"看着差不多" |
| **崩溃安全** | 每秒回写文件头 | 断电/崩溃后已落盘的数据仍能被播放器正常识别 |
实测的实测数据(自检输出,非估算):
```
K 加权滤波器系数 vs ITU 原文 最大偏差 8.9e-16 (即逐位一致)
响度 vs ffmpeg ebur128 -13.856 LUFS vs -13.8 LUFS(白噪声)
满量程立体声 997 Hz 正弦 0.0007 LUFS(ffmpeg: -0.0)
16/24/32/float32 往返最大误差 4.5e-5 / 1.7e-7 / 6.9e-10 / 0
ffmpeg 独立解码本程序的 24 位文件 最大偏差 4.6e-5
低切:直流 / 20 Hz / 1 kHz -240 dB / -98.6 dB / +0.0000 dB
真峰值捕捉采样点间过冲 采样峰值 0.3536 → 真峰值 0.5050(理论 0.5)
```
---
## 界面说明
```
┌───────────────┬──────────────────────────────────────────────────────┐
│ ① 输入设备 │ ● 00:12.3 [暂停][标记][分段][停止] │
│ 设备 / 检测能力│ 待机 │
│ ② 录音格式 ├──────────────────────────────────────────────────────┤
│ 采样率/位深/声道│ 电平表 RMS 渐变条 + 峰值保持 + 削波锁存 + dB 刻度 │
│ 独占/缓冲/延迟 ├──────────────────────────────────────────────────────┤
│ ③ 信号处理 │ 实时波形(最近 1 秒,min/max 包络) │
│ 低切/增益/抖动 ├──────────────────────────────────────────────────────┤
│ ④ 文件与分段 │ 响度条 瞬时 / 短时 / 整体 + 目标参考线 │
│ 目录/命名/分段 ├──────────────────────────────────────────────────────┤
│ 校准本底噪声 │ 整段总览 + 标记轨道 │
│ ├──────────────────────────────────────────────────────┤
│ │ 文件名 · 磁盘剩余 · 缓冲队列 · 驱动 xrun │
└───────────────┴──────────────────────────────────────────────────────┘
```
**快捷键**
| 键 | 功能 |
|---|---|
| `空格` | 开始 / 停止录音 |
| `P` | 暂停 / 继续(暂停段不写入文件) |
| `M` | 添加标记(写入元数据与标记轨道) |
| `Ctrl+S` | 立即分段(切到新文件继续录) |
| `Ctrl+O` | 打开输出目录 |
| `Ctrl+Q` | 退出 |
**录音中可以实时调整**:软件增益、响度参考线、暂停/标记/分段。
其余参数在录音期间锁定,避免中途改变格式造成文件不一致。
**状态灯含义**
- 「剩余空间」> 2 GB 绿、> 512 MB 黄、再低红(低于 512 MB 时启动也会警告)
- 「缓冲队列」积压 < 8 块绿、< 32 块黄、再多红。持续红灯说明磁盘跟不上,会自动记录溢出块数
- 「驱动 xrun」非 0 表示 PortAudio 层出现溢出,报告里会统计
---
## 参数怎么选
| 参数 | 推荐值 | 理由 |
|---|---|---|
| 采样率 | **48000 Hz** | 设备原生多为 48 kHz,选它可完全避免重采样;语音/音乐/视频后期通吃 |
| 位深 | **24 位 PCM** | 动态范围 144 dB,兼容性极好;要发出去就录 24 位、导出 16 位(带抖动) |
| 声道 | 话筒 **1**;立体声素材 **2** | 单声道话筒录成双声道只是浪费一倍体积 |
| 独占模式 | **开**(WASAPI) | 保真度最高;若打不开设备再关掉换共享模式 |
| 缓冲区 | **自动 / 4096 + 高延迟策略** | 录音不需要低延迟,稳定不出 xrun 才是第一优先级 |
| 低切 | 人声 **80 Hz**,男声 60 Hz,播客 180 Hz | 去掉空调/桌面震动/近讲喷麦的低频堆积 |
| 软件增益 | **0 dB 起** | 优先调系统麦克风音量(作用在更靠前的增益级);软件增益只在不够用时补 |
| 抖动 | **开** | 写 16/24 位时把量化失真变成白噪;写 32 位浮点时自动跳过 |
| 响度参考 | 播客 -16 / 流媒体 -14 LUFS | 只是在响度条上画一条目标线,不影响录音 |
**电平目标:正常说话时峰值落在 -12 ~ -6 dBFS。** 数字削波不可逆,而且比模拟过载难听得多。
---
## 命令行用法
适合自动化、定时任务、无界面环境。
```bat
:: 设备与能力
python -m recorder.cli --list-devices
python -m recorder.cli --probe 3
:: 录 60 秒到指定目录,24 位/48 kHz、80 Hz 低切、顺带导出 16 位 WAV
python -m recorder.cli -d 3 -t 60 -b 24 --lowcut 80 -o D:\rec ^
--name "会议_{datetime}" --report --export wav_16
:: 不定时长,回车停止(Ctrl+C 也可)
python -m recorder.cli -d 3 -b 24 --lowcut 80 -o D:\rec
:: 静音 10 秒自动停止;每 5 分钟自动分段
python -m recorder.cli -t 3600 --stop-after-silence 10 --split-seconds 300
:: 分析已有录音
python -m recorder.cli --analyze D:\rec\会议.wav
:: 后期处理:裁剪静音 + 响度归一化到 -16 LUFS + 混单声道,并导出 FLAC
python -m recorder.cli --process D:\rec\会议.wav --trim ^
--normalize lufs --target -16 --mono --export flac
```
主要参数:`-d 设备` `-r 采样率` `-c 声道` `-b 位深` `--gain dB` `--lowcut Hz`
`--no-exclusive` `--no-dither` `--rf64` `-t 秒` `-o 目录` `--name 模板`
`--split-seconds` `--split-mb` `--stop-after-silence` `--silence-threshold`
`--report` `--export <预设>`;`--gui` 直接启动图形界面。
命名模板变量:`{datetime} {date} {time} {device} {sr} {bits} {ch} {seq}`
---
## 录音体检报告
每次录音结束都会弹出报告,并可生成同名 `.json`(机器可读)与 `.txt`(人读)、
`_waveform.png`(波形预览图,纯 numpy 生成,不依赖 PIL)。
```
RecorderStudio 录音报告
==============================================
开始时间 : 2026-09-12T22:02:15
输入设备 : 麦克风 (F20) [Windows WASAPI]
录制格式 : 48000 Hz / 24-bit PCM / 2 声道
独占模式 : 是
软件增益 : +0.0 dB
低切滤波 : 80 Hz
抖动 : 开启 (TPDF)
总时长 : 00:02.7 总采样帧 : 129840 数据量 : 742.5 KB
音质体检
----------------------------------------------
采样峰值 : -32.60, -32.60 dBFS
真峰值 : -32.48, -32.48 dBTP
RMS 电平 : -68.10, -68.10 dBFS
整体响度 : -55.200 LUFS
动态范围 : 0.0 LU
直流偏移 : [0.0, 0.0]
本底噪声 : -52.3 dBFS
削波样本 : 0 丢弃块/溢出: 0 驱动层 xrun: 0
标记
----------------------------------------------
1.234 s 标记 1 (20260912_220215_麦克风 (F20).wav)
```
说明:
- **真峰值(dBTP)** 比采样峰值更能反映真实过载风险;若超过 -0.1 dBTP 报告会提示降低增益
- **本底噪声** 只在录音里确实存在"安静段"时才给出;全程持续发声时明确显示"无法测定",
而不是编造一个数字
- **动态范围** 是短时响度的 10%~95% 分位差(EBU Tech 3342 的工程近似值)
---
## 后期处理与导出
原则:**原始 WAV 母版永不被覆盖**。所有处理另存为 `*_processed.wav`,
导出时即使目标路径与源文件同名也会自动改名。
**后期处理链**(顺序可自由组合,全部离线、可复现)
1. 去除直流偏移
2. 线性相位低切(40 ~ 180 Hz)
3. 噪声门(带保持/攻放包络,避免"抽气"感)
4. 裁剪首尾静音(只有长于设定时长才裁,且保留呼吸空间)
5. 混合单声道
6. 峰值归一化(-1 dBFS,按真峰值保护)或 **响度归一化**(目标 LUFS,并保证真峰值上限)
7. 淡入 / 淡出
**导出格式**
| 预设 | 说明 |
|---|---|
| WAV 16 / 24 / 32位浮点 | 内置编码器,不需要 ffmpeg |
| FLAC | 无损压缩,体积约减半 |
| MP3 320 kbps / MP3 V0 | 需要 ffmpeg |
| Opus 128 kbps | 语音/播客首选 |
| AAC / M4A 256 kbps | 苹果生态 |
程序会自动在 PATH 与常见安装位置寻找 ffmpeg;找不到时只有 WAV 导出可用,
界面上会明确提示而不是静默失败。
---
## 自检与验证证据
```bat
python -m recorder.selftest :: 全部(含真实硬件录音,约 11 秒)
python -m recorder.selftest --no-hw :: 只跑离线数学/格式测试(无需麦克风)
python dev/gui_check.py --shots :: 界面回归检查(离屏渲染 + 像素校验 + 截图)
python dev/dialog_check.py :: 对话框后台任务链路(QThread + 信号)
python dev/lint_scan.py :: 静态检查(未使用导入 / 裸 except / 过长函数)
```
自检覆盖 6 个方面共 **97 项**(含硬件时):
| 分组 | 内容 |
|---|---|
| ITU-R BS.1770 符合性 | 滤波器系数与标准原文逐位比对;响度标定(满量程立体声正弦 = 0 LUFS、单声道 -3.01 LUFS);与 ffmpeg `ebur128` 交叉验证 |
| 线性相位滤波器 | 直流/通带/阻带响应、系数对称性、群延迟补偿 |
| 真峰值 | 采样点对齐与相移两种情况,验证能捕捉 inter-sample peak |
| WAV 编解码 | 四种载荷往返误差在量化极限内;标准库 `wave` 与 ffmpeg 独立复核;RF64;非法文件与零长度边界 |
| TPDF 抖动 | 误差-信号相关性、方差分解(1/6 抖动 + 1/12 量化 = 1/4 LSB²) |
| 引擎与硬件 | 低切样本数守恒、命名模板、自动分段(1.0 秒分段精确得到 48000 帧)、手动分段、**静音自动停止后音频流确实被关闭**、真实设备录音 xrun 统计 |
界面回归检查(`dev/gui_check.py`)用两种可判定的方式验证渲染,
因为纯截图无法被自动判定:
- **几何**:164 个控件无零尺寸、无越界、无横向裁切;大号计时器与状态栏用字体度量
确认最长文本不被截断
- **像素**:注入已知电平验证 dB→像素映射(-3 dB → 90% 表宽、-30 dB → 49%、-60 dB → 0%),
削波锁存显示红色,示波器/总览图有波形,录音按钮在空闲/录音态颜色正确
---
## 项目结构
```
RecorderStudio/
├── 启动录音机.bat / run.bat 启动器(自动寻找带 PyQt5 的解释器)
├── install_deps.py 依赖安装器(pip + 直接下载 wheel 双通道)
├── requirements.txt
├── recorder/ 主程序包
│ ├── dsp.py DSP:滤波器、电平、真峰值、BS.1770 响度、离线处理链
│ ├── wavfile.py 无损 WAV 读写:16/24/32/float32、TPDF、RF64、崩溃安全头
│ ├── engine.py 录音引擎:设备枚举、独占模式、落盘线程、自动分段
│ ├── post.py 后期处理、导出、元数据、纯 numpy 的 PNG 波形图
│ ├── widgets.py 自绘控件:电平表、示波器、响度条、录音按钮
│ ├── window.py 主窗口、报告/处理/导出对话框
│ ├── gui.py 图形界面入口(High-DPI + 深色主题)
│ ├── cli.py 命令行录音
│ └── selftest.py 自检套件
├── dev/gui_check.py 界面回归检查(开发/验收用)
├── _vendor/ 项目内依赖(sounddevice 及其内置 PortAudio)
└── recordings/ 默认输出目录
```
架构要点:`dsp` / `wavfile` 不依赖 GUI 也不依赖音频后端,可以单独 import 做批量处理;
`engine` 是三线程模型(PortAudio 回调线程 / 写入线程 / 界面线程),
只有写入线程接触磁盘,界面只读快照。因此 CLI 与 GUI 共用同一套录音核心,
不存在"两套行为不一致"的问题。
---
## 常见问题
**打不开设备 / device unavailable**
多半被其它程序独占(浏览器、会议软件、直播工具、DAW)。关掉它们,或在左侧关掉
「WASAPI 独占模式」改用共享模式。
**提示不支持某个采样率**
点「检测能力」看哪些采样率带 ✓。设备原生采样率(通常是 48000)保真度最高;
不要为了"看起来更高"强行选 96 kHz——那只会让驱动做重采样。
**录出来是静音**
1) Windows 设置 → 隐私和安全性 → 麦克风,确认"允许桌面应用访问麦克风"已开;
2) 系统声音设置里确认默认输入设备选对;
3) 用「校准本底噪声」测一下,如果本底是 -inf 说明没有信号进来。
**想录电脑内部声音(内录)**
在设备列表里选 **「立体声混音 / Stereo Mix」** 或 **「主声音捕获驱动程序」**
(通常在 WASAPI / WDM-KS 分组下)。若列表中没有,需要在
「声音设置 → 录制 → 显示禁用的设备」里启用"立体声混音"。
**有爆音 / 掉采样**
看状态栏「缓冲队列」和「驱动 xrun」。把缓冲区改成 4096、延迟策略改成"高",
并关闭其它占用磁盘与 CPU 的程序。报告里的「丢弃块/溢出」与「xrun」会给出确切数字。
**提示超过 4 GB**
RIFF 容器上限就是 4 GB。开启「RF64 大文件容器」用 RF64 单文件继续录,
或设置「按体积分段」自动切成多个文件。
**FFmpeg 找不到**
只影响 FLAC/MP3/Opus/AAC 导出,WAV 全部功能不受影响。安装 ffmpeg 并加入 PATH 即可。
---
## 已知限制
诚实说明,避免误用:
1. **平台**:开发与验证都在 Windows 上完成。`sounddevice` 在 macOS/Linux 也可用,
但本项目的设备排序、独占模式说明、批处理启动器都针对 Windows 优化。
2. **ASIO**:`_vendor` 里带了 ASIO 版 PortAudio DLL,但 sounddevice 默认加载非 ASIO 版本;
需要 ASIO 时要把 `libportaudio64bit-asio.dll` 覆盖为 `libportaudio64bit.dll` 后再启动。
本项目未对 ASIO 路径做测试。
3. **内录**:依赖系统的"立体声混音"设备。PortAudio 19.7(sounddevice 0.5.6 内置)
没有暴露 WASAPI loopback,因此不提供"直接抓系统输出"的开关。
4. **响度计量的实现方式**:K 加权在频域按 |H(f)|² 施加。对"能量"计量而言与
时域滤波等价(自检已与 ffmpeg 交叉验证,误差 < 0.1 LU),
但与逐样本时域实现相比,块边界处存在理论上的细微差异。
5. **动态范围(LRA)** 是 EBU Tech 3342 的工程近似值,不用于合规认证。
6. **暂停是"跳切"**:暂停期间的数据直接丢弃,文件里会有一次不连续;
需要保留时间轴连续性请用分段而不是暂停。
---
## 依赖
| 组件 | 用途 | 许可 |
|---|---|---|
| Python 3.9+ | 运行环境 | PSF |
| numpy | 数值运算 | BSD-3 |
| sounddevice | PortAudio 绑定(内置 PortAudio 二进制) | MIT / PortAudio MIT |
| PyQt5 | 图形界面(仅 GUI 需要) | GPL v3 / 商业双许可 |
| ffmpeg(可选,外部) | FLAC/MP3/Opus/AAC 导出 | LGPL/GPL |
> PyQt5 采用 GPL v3 与商业双许可。若要把本软件用于闭源商业分发,
> 请改用 PySide6(LGPL)或购买 PyQt 商业许可。