audio-recorder-worklet-processor
v0.0.9
Published
基于 AudioWorklet 的浏览器端实时录音库,提供音频采集、降噪、格式转换等原子能力。
Readme
audio-recorder-worklet-processor
基于 AudioWorklet 的浏览器端实时录音库,提供音频采集、降噪、格式转换等原子能力。
不做音频数据存储,仅提供原子能力。业务层可自行缓存数据,再调用
encodePCM/encodeWAV转换格式。 内置多种降噪算法,可根据业务需求选择。
浏览器支持
| Chrome | Firefox | Edge | Opera | Safari | |--------|---------|------|-------|--------| | 66+ | 76+ | 79+ | 53+ | 14.1+ |
音频处理流程
┌──────────┐ ┌──────────────┐ ┌────────────┐ ┌───────────────┐ ┌────────────┐
│ 麦克风 │───▶│ RNNoise? │───▶│ Biquad? │───▶│ MainWorklet │───▶│ Destination│
│ │ │ (AI降噪-A) │ │ (带通滤波) │ │ (采集+音量) │ │ (扬声器) │
└──────────┘ └──────────────┘ └────────────┘ └───────┬───────┘ └────────────┘
│ postMessage
▼
┌──────────────┐
│ 主线程回调 │
│ DeepFilter? │── 48kHz降噪 → 重采样回目标采样率
│ │
│ onDataProcess│── { vol, buffer }
└──────────────┘降噪方案(互斥,三选一)
| 方案 | 原理 | 延迟 | 效果 | 备注 | |------|------|------|------|------| | RNNoise | AudioWorklet 线程内 RNN 推理 | 极低 | 中 | 需引入 WASM + Worklet 文件 | | DeepFilter | 主线程 DeepFilterNet3 WASM 推理 | ~10-20ms | 高 | 需联网加载模型,内部 48kHz 自动重采样 | | Vonage | 替换麦克风流轨道 | 低 | 中 | 第三方 SDK |
另有 Biquad 带通滤波器(传统 DSP),可与上述方案叠加使用。
安装
npm install audio-recorder-worklet-processor引入
ES Module:
import Recorder from "audio-recorder-worklet-processor";
const recorder = new Recorder();CDN / Script 标签:
<script src="../dist/index.js"></script>
<script>
const recorder = new Recorder();
</script>API
init(config?)
初始化录音环境,创建 AudioContext 和各音频节点(不获取麦克风、不连接链路)。
await recorder.init(config);interface IConfig {
/** 实时音频回调,返回音量(0-1)和 Float32 原始数据 */
onDataProcess?: (data: { vol: number; buffer: Float32Array }) => void;
/** 音频采集参数 */
processOptions?: IProcessOptions;
/** 频谱分析参数(可用于可视化) */
analyserOptions?: IAnalyserOptions;
/** Biquad 带通滤波器参数 */
noiseReductionOptions?: INoiseReductionOptions;
/** 是否使用 Vonage AI 降噪,默认 false */
useVonage?: boolean;
/** RNNoise 降噪参数 */
rnnoiseOptions?: IRnnoiseOptions;
/** DeepFilter 降噪参数(内部 48kHz 处理后自动重采样回 sampleRate) */
deepFilterOptions?: IDeepFilterOptions;
}| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| processSize | number | 4096 | 每次回调的采样数 |
| numChannels | 1 \| 2 | 1 | 声道数 |
| sampleBits | 16 \| 8 | 16 | 采样位深 |
| sampleRate | number | 16000 | 采样率,常用 16000 / 48000 |
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| open | boolean | false | 是否开启 AnalyserNode |
| fftSize | number | 512 | FFT 精度,范围 32-32768,越大越精确 |
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| open | boolean | false | 是否启用 |
| frequency | number | 1300 | 中心频率 Hz(人声 300-3400Hz) |
| Q | number | 1 | 品质因数,决定带宽 = frequency/Q |
| 参数 | 类型 | 说明 |
|------|------|------|
| useRnnoise | boolean | 是否启用 |
| rnnoiseWasmPath | string | rnnoise.wasm 文件路径 |
| rnnoiseSimdWasmPath | string | rnnoise_simd.wasm 文件路径(SIMD 加速) |
| rnnoiseWorkletPath | string | rnnoise.worklet.js 文件路径 |
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| useDeepFilter | boolean | false | 是否启用 |
| cdnUrl | string | https://cdn.laptrinhai.id.vn/deepfilternet3 | WASM/模型资源 CDN 地址 |
| attenuationLimit | number | 50 | 降噪强度 dB,0-100,越大越强 |
| postFilterBeta | number | 0.02 | 后滤波强度,0-0.05,0=禁用 |
注意: 首次
init()需联网加载 WASM + 模型(约 2-4MB),耗时数秒,建议应用启动时预初始化。启用后自动关闭浏览器原生降噪/回声消除/自动增益。内部 48kHz 处理后自动重采样回你配置的sampleRate。
start()
开始录音,获取麦克风并建立音频链路。
await recorder.start();stop()
停止录音。会将缓冲区中不足 processSize 的剩余数据再回调一次 onDataProcess,不丢数据。
await recorder.stop();destroy()
销毁录音器,彻底释放所有资源。调用后需重新 init() 才能使用。
await recorder.destroy();getAnalyserData()
获取实时频谱数据(需在 init 时开启 analyserOptions.open)。
const data: Uint8Array = recorder.getAnalyserData();encodePCM(bytes, sampleBits?, littleEdian?)
Float32 原始数据 → PCM DataView。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| bytes | Float32Array | — | onDataProcess 返回的原始数据 |
| sampleBits | 16 \| 8 | init 配置 | 采样位深 |
| littleEdian | boolean | 自动检测 | 字节序 |
const pcmData = recorder.encodePCM(bufferArr);encodeWAV(buffer, sampleRate?, numChannels?, sampleBits?, littleEdian?)
PCM DataView → WAV DataView。
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| buffer | DataView | — | encodePCM 返回的数据 |
| sampleRate | number | init 配置 | 采样率 |
| numChannels | 1 \| 2 | init 配置 | 声道数 |
| sampleBits | 16 \| 8 | init 配置 | 采样位深 |
| littleEdian | boolean | 自动检测 | 字节序 |
const wavData = recorder.encodeWAV(pcmData);dataViewToBase64(dataView)
DataView → base64 字符串。
const base64 = recorder.dataViewToBase64(wavData);示例
基础录音 + 频谱
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Recorder Demo</title>
<script src="../dist/index.js"></script>
</head>
<body>
<button onclick="init()">init</button>
<button onclick="start()">start</button>
<button onclick="stop()">stop</button>
</body>
<script>
const recorder = new Recorder();
const bufferArr = [];
const init = () => {
recorder.init({
analyserOptions: { open: true },
onDataProcess: (data) => {
bufferArr.push(...data.buffer);
},
});
};
const start = async () => {
await recorder.start();
};
const stop = async () => {
await recorder.stop();
const pcmData = recorder.encodePCM(bufferArr);
const wavData = recorder.encodeWAV(pcmData);
const wavBlob = new Blob([wavData], { type: "audio/wav" });
const oA = document.createElement("a");
oA.href = URL.createObjectURL(wavBlob);
oA.download = "recorder.wav";
oA.click();
};
</script>
</html>
</html>DeepFilter AI 降噪
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>DeepFilter Demo</title>
<script src="../dist/index.js"></script>
</head>
<body>
<button onclick="init()">init</button>
<button onclick="start()">start</button>
<button onclick="stop()">stop</button>
</body>
<script>
const recorder = new Recorder();
const bufferArr = [];
const init = async () => {
await recorder.init({
deepFilterOptions: {
useDeepFilter: true,
attenuationLimit: 50, // 降噪强度 0-100
postFilterBeta: 0.02, // 后滤波 0-0.05
},
processOptions: {
numChannels: 1,
sampleBits: 16,
sampleRate: 16000, // 可自由配置,DeepFilter 内部 48kHz 处理后自动重采样回来
},
onDataProcess: (data) => {
bufferArr.push(...data.buffer);
},
});
console.log("DeepFilter ready");
};
const start = async () => {
await recorder.start();
};
const stop = async () => {
await recorder.stop();
const pcmData = recorder.encodePCM(bufferArr);
const wavData = recorder.encodeWAV(pcmData);
const wavBlob = new Blob([wavData], { type: "audio/wav" });
const oA = document.createElement("a");
oA.href = URL.createObjectURL(wavBlob);
oA.download = "denoised.wav";
oA.click();
};
</script>
</html>
</html>生命周期
init() ──▶ start() ──▶ stop() ──▶ start() ──▶ ...
│ │ │
│ └─ onDataProcess 循环回调
│
└──────────────────────────▶ destroy() ──彻底释放init()可重复调用(会关闭旧 context),但录音中不允许stop()后状态回到ready,可再次start()destroy()后需重新init()才能使用
