有没有办法阻止Web Audio API decodeAudioData方法内存泄漏?

问题描述 投票:3回答:3

问题

使用Web Audio API创建音频缓冲区时,可以使用decodeAudioData方法创建的缓冲区,这些缓冲区驻留在内存中,显然无法通过JavaScript访问。它们似乎在浏览器选项卡的整个生命周期中都存在,并且永远不会被垃圾收集。

问题的可能原因

我知道这些缓冲区与主线程分离,并设置在另一个线程上进行异步解码。我也知道API规范说不应该允许decodeAudioData对相同的输入缓冲区进行两次解码,我假设是为什么要保留解码缓冲区和/或编码输入缓冲区的副本。但是,在Chromecast等内存有限的设备上,这会导致大量内存累积,Chromecast崩溃。

再生性

在我的示例代码中,我使用Ajax获取mp3,然后将arraybuffer传递给decodeAudioData函数。通常在该函数内有一个onsuccess回调,它可以将解码的AudioBuffer作为参数。但是在我的代码中,我甚至没有通过它。因此我在解码后也没有对解码缓冲区做任何事情。在我的代码中没有引用它。它完全留在本机代码中。但是,每次调用此函数都会增加内存分配,并且永远不会释放。例如,在Firefox中:内存显示了Tab的生命周期中的音频缓存。非引用应该足以让垃圾收集器摆脱这些缓冲区。

我的主要问题是,是否有任何对这些解码音频缓冲区的引用,比如在audiocontext对象中,或者我可以尝试从内存中删除它们的其他地方?或者还有其他方法可以使这些存储和无法访问的缓冲区消失吗?

我的问题与目前关于decodeAudioData的所有其他问题不同,因为我表明即使没有用户存储任何引用甚至使用返回的解码音频缓冲区,也会发生内存泄漏。

Code To Reproduce

function loadBuffer() {
    // create an audio context
    var context = new (window.AudioContext || window.webkitAudioContext)();

    // fetch mp3 as an arraybuffer async
    var url = "beep.mp3";
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.responseType = "arraybuffer";

    request.onload = function () {

        context.decodeAudioData(
                request.response,
                function () {// not even passing buffer into this function as a parameter
                    console.log("just got tiny beep file and did nothing with it, and yet there are audio buffers in memory that never seem to be released or gc'd");
                },
                function (error) {
                    console.error('decodeAudioData error', error);
                }
        );
    };

    request.onerror = function () {
        console.log('error loading mp3');
    }
    request.send();
}

预测一些可能的反应。

  1. 我必须使用Web Audio API,因为我正在使用Chromecast上的四个音频文件播放四部分和声,并且html音频元素不支持Chromecast上的多个同时播放。
  2. 可能你提到的任何JS库[例如Howler.js,Tone.js,Amplitude.js等]建立在Web Audio API之上,因此他们都会分享这个内存泄漏问题。
  3. 我知道WAA的实现依赖于每个浏览器。我目前主要担心的是Chromecast,但我尝试过的每个浏览器都存在这个问题。
  4. 因此,我认为这是规范相关的问题,其中规范要求非重复编码规则,因此实现者在浏览器级线程上保留缓冲区的副本,以便它们可以针对新的xhr输入进行检查。如果规范编写者碰巧读到我的问题,那么用户是否可以选择此行为,如果他们希望选择退出,以防止移动和精简内存平台上的内部缓冲存储?
  5. 我无法在任何JS对象中找到对这些缓冲区的任何引用。
  6. 我知道我可以使用audio_context.close(),然后希望对audio_context所拥有的所有资源进行垃圾收集,然后希望我可以用一个新的重新复制audio_context,但这对我的应用程序来说还不够经验。 。在GC取出垃圾箱之前,Chromecast崩溃了。
javascript web-audio web-audio-api
3个回答
0
投票

当您将每个音频标签路由到Web Audio图表(通过使用MediaElementAudioSourceNode)时,您是否可以在Chromecast上使用多个音频标签?


0
投票

My present solution

我无法使用Web Audio API找到最终令人满意的Chromecast解决方案,同时播放四个mp3 - 用于四部分和声。第二代似乎没有足够的资源来保存音频缓冲器,同时使用decodeAudioData解码四个mp3文件,而不会留下太多的垃圾并最终崩溃。我决定使用基于Web Audio API构建的surikov的webaudiofont,并使用midi文件。我从未在桌面浏览器或其他具有更多资源的设备上遇到问题,但我必须让它在Chromecast上运行。我现在使用webaudiofont没有任何问题。


0
投票

Pragmatic Workaround

我找到了一种方法来解决Web Audio API音频播放器无限期地处理并崩溃Chromecast和其他移动平台的问题。 [[我没有在所有浏览器上测试过这个 - 你的里程可能会有所不同。 ]]

加载阶段

  1. 使用iFrame中的Web Audio API加载文档。
  2. 加载音频缓冲区并执行任何操作来播放它们。

清除舞台

  1. 在您引用的所有播放节点上调用sourceNode.stop。
  2. 调用source.disconnect();在所有源节点上。
  3. 调用gainNode.disconnect();在所有增益节点上,这些源节点与之相关联(以及您可能正在使用的具有断开连接方法的任何其他类型的WAA节点)
  4. 将所有引用的gainNodes和sourceNodes设置为null;
  5. 将已解码的所有缓冲区和xhr提取的编码音频缓冲区均为空;
  6. KEY:在WAA页面内调用audio_context.close();然后设置audio_context = null; (这可以使用contentWindow从iFrame的父级完成)。
  7. 注意:其中一些归零步骤可能不是绝对必要的,但这种方法对我有用。

重新加载阶段

  1. 从父页面重新加载iframe。这将导致所有音频播放器在NEXT GC ROUND上被垃圾收集,包括隐藏(非JS)内存区域中的那些。
  2. 您的iframe必须重新实现Web音频上下文并加载其缓冲区并创建节点等,就像您第一次加载它时一样。

注意:您必须决定何时使用此清除方法(例如,在加载和播放了这么多缓冲区之后)。您可以在没有iframe的情况下执行此操作,但您可能需要重新加载页面一次或两次以启动垃圾收集。对于那些需要在Chromecast或其他移动设备等内存瘦平台上加载大量Web Audio API音频缓冲区的用户而言,这是一种实用的解决方法。

来自父母

  function hack_memory_management() {
                var frame_player = document.getElementById("castFrame");
                //sample is the object which holds an audio_context
               frame_player.contentWindow.sample.clearBuffers();
                 setTimeout(function () {
                    frame_player.contentWindow.location.reload();
                }, 1000);
            }

内部是IFRAME

CrossfadeSample.prototype.clearBuffers = function () {
    console.log("CLEARING ALL BUFFERS -IT'S UP TO GC NOW'");
    // I have four of each thing because I am doing four part harmony

    // these are the decoded audiobuffers used to be passed to the source nodes
    this.soprano = null;
    this.alto = null;
    this.tenor = null;
    this.bass = null;
    if (this.ctl1) {

        //these are the control handles which hold a source node and gain node 
        var offName = 'stop';
        this.ctl1.source[offName](0);
        this.ctl2.source[offName](0);
        this.ctl3.source[offName](0);
        this.ctl4.source[offName](0);

        // MAX GARGABE COLLECTION PARANOIA

        //disconnect all source nodes
        this.ctl1.source.disconnect();
        this.ctl2.source.disconnect();
        this.ctl3.source.disconnect();
        this.ctl4.source.disconnect();

        //disconnect all gain nodes
        this.ctl1.gainNode.disconnect();
        this.ctl2.gainNode.disconnect();
        this.ctl3.gainNode.disconnect();
        this.ctl4.gainNode.disconnect();

        // null out all source and gain nodes
        this.ctl1.source = null;
        this.ctl2.source = null;
        this.ctl3.source = null;
        this.ctl4.source = null;

        this.ctl1.gainNode = null;
        this.ctl2.gainNode = null;
        this.ctl3.gainNode = null;
        this.ctl4.gainNode = null;
    }

    // null out the controls
    this.ctl1 = null;
    this.ctl2 = null;
    this.ctl3 = null;
    this.ctl4 = null;

    // close the audio context
    if (this.audio_context) {
        this.audio_context.close();
    }
    // null the audio context
    this.audio_context = null;

};

更新:

遗憾的是,即使这不能可靠地工作,Chromecast仍然会因为一些清晰的新mp3而崩溃。请参阅本页其他地方的“我现有的解决方案”。

© www.soinside.com 2019 - 2024. All rights reserved.