【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 播放结束');
    });
  });在这里插入图片描述

}, []);

六、噪音原因与解决总结

流程数据格式说明

七、噪音的本质原因复盘

原因描述

八、最终效果总结

优化点效果

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
大蓝头4 天前
安装包UI美化之路-nsNiuniuSkin全新UI调试与预览方法
ui·electron·安装包·截图控件·截屏控件
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄4 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端