You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏语音聊天:如何从主文件访问Audio Worklet Node的输入输出?

实时语音聊天迁移到AudioWorkletNode的解决方案

核心问题

原使用已废弃的createScriptProcessor()实现的实时语音聊天,迁移到AudioWorkletNode后遇到两个关键问题:

  • 无法将AudioWorklet中的麦克风输入数据传递到主线程,通过WebSocket发送给其他玩家
  • 无法将主线程通过WebSocket收到的音频数据传入AudioWorklet,用于播放

解决方案:利用MessagePort实现跨线程通信

AudioWorklet运行在独立的音频线程,无法直接访问主线程的WebSocket或变量,必须通过AudioWorkletNode的port属性(基于MessageChannel)实现双向消息传递,完成数据的跨线程交互。

修改后的Worklet处理器代码(module.js)

class VoiceChatProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.incomingAudioQueue = [];
    // 监听主线程传来的音频数据
    this.port.onmessage = (event) => {
      if (event.data.type === 'AUDIO_DATA') {
        this.incomingAudioQueue.push(event.data.payload);
      }
    };
  }

  process(inputList, outputList) {
    // 1. 把麦克风输入数据发送到主线程
    const inputBuffer = inputList[0];
    if (inputBuffer) {
      const inputData = new Float32Array(inputBuffer[0]);
      this.port.postMessage({
        type: 'MIC_INPUT',
        payload: inputData
      });
    }

    // 2. 播放来自服务器的音频数据
    const outputBuffer = outputList[0];
    let outputData = new Float32Array(outputBuffer[0].length).fill(0);
    if (this.incomingAudioQueue.length > 0) {
      outputData = this.incomingAudioQueue.shift();
    }

    // 将数据写入所有输出声道(兼容立体声)
    for (let channel = 0; channel < outputBuffer.length; channel++) {
      outputBuffer[channel].set(outputData);
    }

    return true;
  }
}

registerProcessor("voice-chat-processor", VoiceChatProcessor);

修改后的主线程代码(main.js)

document.getElementById('btn').onclick = () => createVoiceChatSession();

// 初始化WebSocket连接
const webSocket = new WebSocket('ws://your-server-address');

let voiceChatContext;
let voiceChatProcessor;

// WebSocket接收服务器音频数据
webSocket.onmessage = (event) => {
  // 解码base64为Float32Array(和原逻辑保持一致)
  const decodedAudio = decodeBase64(event.data);
  // 将解码后的数据发送给Worklet
  if (voiceChatProcessor) {
    voiceChatProcessor.port.postMessage({
      type: 'AUDIO_DATA',
      payload: decodedAudio
    });
  }
};

// 解码base64音频数据为Float32Array
function decodeBase64(base64Str) {
  const binary = atob(base64Str);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return new Float32Array(bytes.buffer);
}

// 将麦克风数据编码为base64并通过WebSocket发送
function webSocketSend(audioData) {
  const bytes = new Uint8Array(audioData.buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  const base64 = btoa(binary);
  webSocket.send(base64);
}

async function createVoiceChatSession() {
  voiceChatContext = new AudioContext({ latencyHint: "interactive", sampleRate: 22050 });
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    await voiceChatContext.audioWorklet.addModule('module.js');
    
    const microphone = voiceChatContext.createMediaStreamSource(stream);
    voiceChatProcessor = new AudioWorkletNode(voiceChatContext, 'voice-chat-processor');
    
    // 监听Worklet传来的麦克风输入,发送到WebSocket
    voiceChatProcessor.port.onmessage = (event) => {
      if (event.data.type === 'MIC_INPUT') {
        webSocketSend(event.data.payload);
      }
    };
    
    microphone.connect(voiceChatProcessor).connect(voiceChatContext.destination);
  } catch (err) {
    console.error('语音聊天初始化失败:', err);
  }
}

关键注意事项

  • 线程安全:所有跨线程数据交互必须通过port.postMessage完成,禁止直接访问对方线程的变量或API
  • 数据效率:优先传递Float32Array等TypedArray,比base64编码更高效,减少性能开销
  • 队列缓冲:用队列缓存音频数据,避免因网络延迟或线程调度导致的音频断流、丢包问题
  • 命名规范:避免使用processor这类通用名称,改用更明确的类名(如VoiceChatProcessor),防止命名冲突

内容的提问来源于stack exchange,提问作者BigChungus443

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 03:10:37