371 lines
18 KiB
Markdown
371 lines
18 KiB
Markdown
# 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 商业许可。
|