【electron6】Web Audio + AudioWorklet PCM 实时采集噪音和模拟调试

连这条博客(【electron6】浏览器实时播放PCM数据)

一、背景与目标

在语音识别或实时通话类项目中,我们通常需要通过 AudioWorkletNode 从麦克风采集音频数据,并将其转换为标准 16bit PCM(线性脉冲编码调制)格式,以便传输或播放。为了调试 Worklet 数据处理流程,本笔记中使用一个本地 PCM 文件 (.ptt) 来模拟 AudioWorkletNode.process() 的输出数据,从而快速验证 PCM ↔ Float32 转换链路是否正确、是否干净无噪音。

二、音频采集基础结构

显式指定 sampleRate = 16000,保证采样率一致性:

复制代码
this.audioCtx = new AudioContext({
  sampleRate: 16000
});
await this.audioCtx.audioWorklet.addModule('./voice.js');

this.randomNoiseNode = new AudioWorkletNode(
  this.audioCtx,
  "voices",
  {
    channelCount: 1,
    processorOptions: {
      recording: this.recording,
      targetSampleRate: 16000,
      frameSize: 320, // 每帧 20ms
    },
    parameterData: {
      customGain: 1.0
    }
  }
);

三、AudioWorkletProcessor 核心实现

Worklet 内部负责实时从输入流读取音频、缓存、打包并发送至主线程:

复制代码
class VoicesProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    this.buffer = [];
    this.recording = options.processorOptions.recording;
    this.frameSize = options.processorOptions.frameSize || 320;
  }

  encodePCM(float32Array) {
    const buffer = new ArrayBuffer(float32Array.length * 2);
    const view = new DataView(buffer);
    let offset = 0;
    for (let i = 0; i < float32Array.length; i++, offset += 2) {
      let s = Math.max(-1, Math.min(1, float32Array[i]));
      view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
    }
    return view;
  }

  process(inputs, outputs) {
    const input = inputs[0][0];
    if (!input) return true;

    // 将每次 process 的 128 采样块累计缓存
    this.buffer.push(...input);

    // 达到一帧长度(320 samples = 20ms@16kHz)时发送
    if (this.buffer.length >= 320) {
      const frame = this.buffer.slice(0, 320);
      this.buffer = this.buffer.slice(320);
      const bytes = this.encodePCM(new Float32Array(frame));
      this.port.postMessage({ type: 'result', data: bytes });
    }

    return this.recording;
  }
}

registerProcessor('voices', VoicesProcessor);

四、encodePCM 函数(Float32 → PCM16)

复制代码
const encodePCM = (float32Array: Float32Array) => {
  const buffer = new ArrayBuffer(float32Array.length * 2);
  const view = new DataView(buffer);
  let offset = 0;
  for (let i = 0; i < float32Array.length; i++, offset += 2) {
    let s = Math.max(-1, Math.min(1, float32Array[i]));
    view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  }
  return view;
};

五、文件模拟 Worklet 数据调试

使用本地 .ptt(其他的.pcm文件也可) PCM 文件模拟 AudioWorkletNode 输出数据:

复制代码
import axios from 'axios';
const onePcm = require('./ceshi.ptt');

const encodePCM = (float32Array: Float32Array) => {
    const buffer = new ArrayBuffer(float32Array.length * 2);
    const view = new DataView(buffer);
    let offset = 0;
    for (let i = 0; i < float32Array.length; i++, offset += 2) {
      let s = Math.max(-1, Math.min(1, float32Array[i]));
      view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
    }
    return view;
}
useEffect(() => {
  axios({
    url: onePcm,
    method: 'get',
    responseType: 'arraybuffer'
  }).then(res => {
    const int16Array = new Int16Array(res.data);
    const float32Array = new Float32Array(int16Array.length);

    // Int16 → Float32 标准化
    for (let i = 0; i < int16Array.length; i++) {
      let s = Math.max(-1, Math.min(1, int16Array[i]));
      float32Array[i] = s < 0 ? int16Array[i] / 0x8000 : int16Array[i] / 0x7FFF;
    }

    // 模拟 Worklet 内 encodePCM 再还原
    const decodeView = encodePCM(float32Array);

    // 使用自定义播放器播放
    let voice = new ProcessPCM();
    voice.playback(decodeView, () => {
      console.log('PCM 播放结束');
    });
  });在这里插入图片描述

}, []);

六、噪音原因与解决总结

流程数据格式说明

七、噪音的本质原因复盘

原因描述

八、最终效果总结

优化点效果

相关推荐
C_心欲无痕8 分钟前
Docker 本地部署 CSR 前端项目完整指南
前端·docker·容器
康一夏1 小时前
React面试题,封装useEffect
前端·javascript·react.js
Full Stack Developme1 小时前
Redis 持久化 备份 还原
前端·chrome
猪猪拆迁队2 小时前
2025年终总结-都在喊前端已死,这一年我的焦虑、挣扎与重组:AI 时代如何摆正自己的位置
前端·后端·ai编程
❆VE❆2 小时前
WebSocket与SSE深度对比:技术差异、场景选型及一些疑惑
前端·javascript·网络·websocket·网络协议·sse
ConardLi2 小时前
SFT、RAG 调优效率翻倍!垂直领域大模型评估实战指南
前端·javascript·后端
rgeshfgreh2 小时前
Java高性能开发:Redis7持久化实战
前端·bootstrap·mybatis
李剑一2 小时前
uni-app使用html5+创建webview,可以控制窗口大小、显隐、与uni通信
前端·trae
Hooray2 小时前
2026年,站在职业生涯十字路口的我该何去何从?
前端·后端
小二·2 小时前
Python Web 开发进阶实战:安全加固实战 —— 基于 OWASP Top 10 的全栈防御体系
前端·python·安全