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

如何彻底销毁AudioContext?解决WebAudio录制上下文泄漏问题

WebAudio录制方案的AudioContext资源泄漏问题

我实现了一个基于WebAudio的录制方案,使用AudioContext进行解码。该方案仅将MediaStreamSourceNode连接至AudioWorklet,后者会将输入缓冲区发送给调用方。

但我遇到了录制结束后上下文泄漏的问题:多次启停录制后,开发者工具会创建新的源上下文,性能分析面板显示多个AudioContext线程仍在执行少量任务,本应被清理。

停止录制的流程如下:

  • 向AudioWorkletProcessor发送Finish消息。
  • AudioWorkletProcessor刷新内部缓冲区,返回Finished消息,并从process()返回false。
  • 调用方收到Finished后,停止MediaStreamTrack,断开节点连接,并调用AudioContext的close()方法。

目前不清楚还能采取哪些措施让浏览器彻底清理这些资源。

调用方代码

const context = new AudioContext({ sampleRate: sampleRate });
const modulePromise = context.audioWorklet.addModule(recorderModule);

const stream = await prewarmedMediaStream.getMediaStream();
const audioTrack = stream.getAudioTracks()[0];

const source = mono(context.createMediaStreamSource(stream));

// Audio worklet nodes run in a separate thread and communicate via message passing.
// This seems to be the only way to convince the browser to give us PCM audio.
await modulePromise;

const recorder = mono(new AudioWorkletNode(context, "PcmRecorder"));

source.connect(recorder);

const recordingStopped = es6WithResolvers<void>();
let recordingEndedError: AsyncError | null;

recorder.port.onmessage = async (event) => {
    const msg = event.data as IncomingMessage;

    if (msg.type == "Audio") listener.onDataAvailable?.(msg.data);

    if (msg.type == "Finished") {
        audioTrack.stop();

        source.disconnect();
        recorder.disconnect();

        if (context.state == "running") {
            await context.close();
        }

        if (recordingEndedError) {
            listener?.onRecordingTerminated?.(recordingEndedError);
        } else {
            try {
                listener?.onRecordingCompleted?.();
            } catch (e) {
            }
        }

        recordingStopped.resolve();
    }
};

recorder.onprocessorerror = console.log;

const stopRecording = (error?: AsyncError | null) => {
    recorder.port.postMessage({ type: "Finish" });
    recordingEndedError = error;
    return recordingStopped.promise;
};

audioTrack.onended = () => {
    stopRecording(new AudioEndedError());
};

await context.resume();

return {
    stopRecording: stopRecording
};

录制节点代码

class PcmRecorder extends AudioWorkletProcessor {
    constructor() {
        super();
        this.buffers = [];
        this.finished = false;
        this.port.onmessage = event => {
            const message = event.data;
            if (message.type == "Finish") this.finish = true;
        };
    }

    process(inputs) {
        const floats = inputs[0][0];
        const samples = Int16Array.from(floats, this.convertSample);

        this.buffers.push(samples);

        const total = this.buffers.reduce((total, arr) => total + arr.length, 0);
        if (total > 1600 || this.finish) {
            const mergedBuffer = new Int16Array(total);
            let writeLocation = 0;
            for (const source of this.buffers) {
                mergedBuffer.set(source, writeLocation);
                writeLocation += source.length;
            }
            
            this.port.postMessage({ type: "Audio", data: new Uint8Array(mergedBuffer.buffer) });
            this.buffers = [];
        }

        if (this.finish) this.port.postMessage({ type: "Finished" });

        return !this.finish;
    }

    convertSample(floatSample) {
        return Math.max(-32768, Math.min(Math.round(floatSample * 32768), 32767));
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 16:34:54