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

}, []);

六、噪音原因与解决总结

流程数据格式说明

七、噪音的本质原因复盘

原因描述

八、最终效果总结

优化点效果

相关推荐
顾安r8 小时前
11.8 脚本网页 星际逃生
c语言·前端·javascript·flask
Hello.Reader8 小时前
Data Sink定义、参数与可落地示例
java·前端·网络
im_AMBER9 小时前
React 17
前端·javascript·笔记·学习·react.js·前端框架
一雨方知深秋9 小时前
2.fs模块对计算机硬盘进行读写操作(Promise进行封装)
javascript·node.js·promise·v8·cpython
谷歌开发者10 小时前
Web 开发指向标 | Chrome 开发者工具学习资源 (六)
前端·chrome·学习
一晌小贪欢10 小时前
【Html模板】电商运营可视化大屏模板 Excel存储 + 一键导出(已上线-可预览)
前端·数据分析·html·excel·数据看板·电商大屏·大屏看板
发现你走远了10 小时前
连接模拟器网页进行h5的调试(使用Chrome远程调试(推荐)) 保姆级图文
前端·chrome
街尾杂货店&11 小时前
css - 实现三角形 div 容器,用css画一个三角形(提供示例源码)简单粗暴几行代码搞定!
前端·css
顺凡11 小时前
删一个却少俩:Antd Tag 多节点同时消失的原因
前端·javascript·面试
小白路过11 小时前
CSS transform矩阵变换全面解析
前端·css·矩阵