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

如何在AudioWorkletProcessor.process函数中使用await或等待Promise?

如何在AudioWorkletProcessor.process函数中正确处理异步任务?

问题背景

需要在AudioWorkletProcessor的process函数中执行异步任务(比如机器学习模型推理),但尝试以下三种方案均失败:

  • 直接在process里用await:触发SyntaxError: Unexpected reserved word
  • 把process声明为async函数:页面卡顿,无法处理麦克风输入
  • 用then回调加循环等待:CPU占用飙升,异步函数根本没执行

核心原因

AudioWorklet的process函数运行在实时音频线程中,要求必须同步执行、快速返回,不能有任何阻塞操作(包括异步等待)。一旦process阻塞,会导致音频卡顿、丢帧,甚至浏览器直接冻结。

正确解决方案:异步任务离线处理+消息队列

把异步任务移到非实时线程处理,通过消息队列传递音频数据和处理结果,具体步骤如下:

  1. 在AudioWorkletProcessor中维护数据队列与结果缓存

    • 每次process调用时,收集需要处理的音频数据,加入待处理队列
    • 缓存异步任务返回的结果,在下一次process调用时应用到音频流中
  2. 通过port通信触发异步任务

    • 从process里把音频数据通过port.postMessage发送到主线程(或其他Worker)处理
    • 主线程完成异步任务后,再通过port.postMessage把结果发回Processor
  3. 在后续process调用中应用结果

    • Processor收到结果后缓存,等下一次process执行时,用缓存的结果处理当前音频帧

修改后的代码示例

<!doctype html>
<html>
    <head>
        <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
    </head>
    <div class="p-3 mb-2 bg-slate-100 w-1/3 rounded text-sm">
        <input id="volume-meter" class="w-full bg-white"
            type="range" min="0" max="100" value="0" step="1" disabled>
    </div>
    <button id="button-start" disabled>START</button>
    <script>
        const audioContext = new AudioContext();
        
        let process_code = `
        const SMOOTHING_FACTOR = 0.8;
        const FRAME_PER_SECOND = 60;
        const FRAME_INTERVAL = 1 / FRAME_PER_SECOND;
        
        class VolumeMeter extends AudioWorkletProcessor {
        constructor() {
          super();
          this._lastUpdate = currentTime;
          this._volume = 0;
          this._pendingTasks = []; // 待处理的音频数据队列
          this._cachedResult = null; // 缓存异步任务结果

          // 监听主线程发回的异步结果
          this.port.onmessage = (event) => {
            if (event.data.type === 'async-result') {
              this._cachedResult = event.data.value;
            }
          };
        }
        calculateRMS(inputChannelData) {
          let sum = 0;
          for (let i = 0; i < inputChannelData.length; i++) {
            sum += inputChannelData[i] * inputChannelData[i];
          }
          let rms = Math.sqrt(sum / inputChannelData.length);
          this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR);
        }
        process(inputs, outputs) {
          const inputChannelData = inputs[0][0];
          if (currentTime - this._lastUpdate > FRAME_INTERVAL) {
            this.calculateRMS(inputChannelData);
            this.port.postMessage({type: 'volume-data', value: this._volume});
            this._lastUpdate = currentTime;
          }

          // 1. 收集音频数据,发送到主线程执行异步任务
          if (this._pendingTasks.length === 0) {
            // 克隆音频数据(避免内存共享问题)
            const dataClone = new Float32Array(inputChannelData);
            this._pendingTasks.push(dataClone);
            this.port.postMessage({type: 'process-data', data: dataClone});
          }

          // 2. 如果有缓存的异步结果,应用到输出(这里只是示例,根据实际需求修改)
          if (this._cachedResult) {
            // 比如把结果打印到控制台,或者修改输出音频
            console.log('异步任务结果:', this._cachedResult);
            this._cachedResult = null; // 重置缓存,避免重复使用
            this._pendingTasks.shift(); // 移除已处理的任务
          }

          return true;
        }
        }
        registerProcessor("volume-meter", VolumeMeter);
        `;
        
        
        const startAudio = async (context, meterElement) => {
          let blob = new Blob([process_code], {type: 'application/javascript'});
          await context.audioWorklet.addModule(URL.createObjectURL(blob));
          const mediaStream = await navigator.mediaDevices.getUserMedia({audio: true});
          const micNode = context.createMediaStreamSource(mediaStream);
          const volumeMeterNode = new AudioWorkletNode(context, 'volume-meter');   
          
          // 主线程处理异步任务
          volumeMeterNode.port.onmessage = (event) => {
            if (event.data.type === 'volume-data') {
              meterElement.value = event.data.value * 500;
            } else if (event.data.type === 'process-data') {
              // 模拟异步任务(比如ML模型推理)
              const asyncTask = async (data) => {
                // 这里替换成实际的异步操作
                await new Promise(resolve => setTimeout(resolve, 10));
                return data.reduce((a,b) => a+b, 0) / data.length; // 计算平均值作为示例结果
              };
              
              asyncTask(event.data.data).then(result => {
                volumeMeterNode.port.postMessage({type: 'async-result', value: result});
              });
            }
          };

          micNode.connect(volumeMeterNode).connect(context.destination);
        };
        
        window.addEventListener('load', async () => {
          const buttonEl = document.getElementById('button-start');
          const meterEl = document.getElementById('volume-meter');
          buttonEl.disabled = false;
          meterEl.disabled = false;
          buttonEl.addEventListener('click', async () => {
            await startAudio(audioContext, meterEl);
            audioContext.resume();
            buttonEl.disabled = true;
            buttonEl.textContent = 'Playing...';
          }, false);
        });
    </script>
</html>

关键注意点

  • 音频数据传递时要注意内存管理,避免内存泄漏,建议使用Float32Array克隆数据
  • 异步任务的处理速度要尽量跟上音频帧的生成速度,避免队列积压导致延迟
  • 如果异步任务耗时过长,可以考虑批量处理音频数据,减少通信次数

内容的提问来源于stack exchange,提问作者JK-Hu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 16:25:21