18 KiB
RecorderStudio · 高清录音机
一个面向 高保真、高清晰度 的 Windows 录音软件:无损 WAV 母版、WASAPI 独占采集、 线性相位滤波、TPDF 抖动、ITU-R BS.1770 标准响度计量,并带完整的录音体检报告、 后期处理与多格式导出。界面用 PyQt5 手绘,深色专业风格。
自检套件 97 项检查全部通过,其中响度算法与 ffmpeg 的 ebur128 滤波器做过交叉验证,
WAV 编码用 Python 标准库与 ffmpeg 双向复核。
目录
30 秒上手
:: 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
按下 空格 开始录音,再按一次停止;停止后自动弹出体检报告。
第一次用,建议先做两件事:
- 点左侧「检测能力」,确认所选采样率带 ✓(避免设备不支持导致开流失败);
- 点「校准本底噪声」,安静 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。 数字削波不可逆,而且比模拟过载难听得多。
命令行用法
适合自动化、定时任务、无界面环境。
:: 设备与能力
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,
导出时即使目标路径与源文件同名也会自动改名。
后期处理链(顺序可自由组合,全部离线、可复现)
- 去除直流偏移
- 线性相位低切(40 ~ 180 Hz)
- 噪声门(带保持/攻放包络,避免"抽气"感)
- 裁剪首尾静音(只有长于设定时长才裁,且保留呼吸空间)
- 混合单声道
- 峰值归一化(-1 dBFS,按真峰值保护)或 响度归一化(目标 LUFS,并保证真峰值上限)
- 淡入 / 淡出
导出格式
| 预设 | 说明 |
|---|---|
| WAV 16 / 24 / 32位浮点 | 内置编码器,不需要 ffmpeg |
| FLAC | 无损压缩,体积约减半 |
| MP3 320 kbps / MP3 V0 | 需要 ffmpeg |
| Opus 128 kbps | 语音/播客首选 |
| AAC / M4A 256 kbps | 苹果生态 |
程序会自动在 PATH 与常见安装位置寻找 ffmpeg;找不到时只有 WAV 导出可用, 界面上会明确提示而不是静默失败。
自检与验证证据
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——那只会让驱动做重采样。
录出来是静音
- Windows 设置 → 隐私和安全性 → 麦克风,确认"允许桌面应用访问麦克风"已开;
- 系统声音设置里确认默认输入设备选对;
- 用「校准本底噪声」测一下,如果本底是 -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 即可。
已知限制
诚实说明,避免误用:
- 平台:开发与验证都在 Windows 上完成。
sounddevice在 macOS/Linux 也可用, 但本项目的设备排序、独占模式说明、批处理启动器都针对 Windows 优化。 - ASIO:
_vendor里带了 ASIO 版 PortAudio DLL,但 sounddevice 默认加载非 ASIO 版本; 需要 ASIO 时要把libportaudio64bit-asio.dll覆盖为libportaudio64bit.dll后再启动。 本项目未对 ASIO 路径做测试。 - 内录:依赖系统的"立体声混音"设备。PortAudio 19.7(sounddevice 0.5.6 内置) 没有暴露 WASAPI loopback,因此不提供"直接抓系统输出"的开关。
- 响度计量的实现方式:K 加权在频域按 |H(f)|² 施加。对"能量"计量而言与 时域滤波等价(自检已与 ffmpeg 交叉验证,误差 < 0.1 LU), 但与逐样本时域实现相比,块边界处存在理论上的细微差异。
- 动态范围(LRA) 是 EBU Tech 3342 的工程近似值,不用于合规认证。
- 暂停是"跳切":暂停期间的数据直接丢弃,文件里会有一次不连续; 需要保留时间轴连续性请用分段而不是暂停。
依赖
| 组件 | 用途 | 许可 |
|---|---|---|
| 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 商业许可。