有没有一种方法可以阻止Web Audio API的解码音频数据方法内存泄漏?

时间:2019-01-31 16:01:55

标签: javascript web-audio web-audio-api

问题

当创建使用Web音频API音频缓冲,存在由decodeAudioData方法创建缓冲剂,其驻留在存储器且显然不是通过JavaScript访问。它们似乎在浏览器选项卡的整个生命周期中徘徊,并且永远不会收集垃圾。

问题的可能原因

我知道这些缓冲区与主线程分开,并在另一个线程上设置以进行异步解码。我也知道,API规范说decodeAudioData不应该被允许进行解码相同的输入缓冲了两次,我以为是为什么解码缓冲器和/或缓冲围绕保持编码输入的副本。但是,在内存受限的设备(例如Chromecast)上,这会导致大量内存积聚,并且Chromecast崩溃。

可重复性

在我的示例代码中,我使用Ajax获取mp3,然后将arraybuffer传递给encodeAudioData函数。通常,在该函数中有一个成功回调,可以将解码的AudioBuffer作为参数。但是在我的代码中,我什至没有传递它。因此,在对解码后的缓冲区进行解码之后,我也不做任何事情。我的代码中没有任何地方引用它。它完全留在本机代码中。但是,每次对该函数的调用都会增加内存分配,并且永远不会释放它。例如,在Firefox一下:内存显示那里的标签的使用寿命audiobuffers。对于垃圾回收器而言,非引用应该足以摆脱这些缓冲区。

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

从所有其他我的问题不同目前SO关于decodeAudioData因为我表明,在内存泄漏甚至发生而无需用户存储的任何附图或甚至使用返回的解码的音频缓冲器中。

代码重现

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,但是这并没有经验得到及时足为我的应用程序。 Chromecast在GC清除垃圾之前崩溃。

4 个答案:

答案 0 :(得分:0)

语用解决方法

我已经找到了解决网络音频API audiobuffers切换的问题围绕无限期和崩溃的Chromecast和其他移动平台的方法。 [我还没有在所有浏览器测试这一点 - 您的里程可能会有所不同。 ]]

装载阶段

  1. 在iFrame中使用Web Audio API加载文档。
  2. 加载音频缓冲区,然后尽一切努力播放它们。

结算STAGE

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

RE-装载阶段

  1. 刷新从父页的IFRAME。这将导致所有的audiobuffers的被垃圾收集在NEXT GC ROUND,包括在存储器中的隐藏(非JS)的区域的那些。
  2. 您的IFRAME将不得不重新实例网络音频上下文并加载其缓冲区和创建节点等,就像你做,当你第一次加载它。

注意事项:您必须决定何时使用此清除方法(例如,在加载和播放了如此多的缓冲区之后)。您可以在没有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);
            }

INSIDE WAA 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;

};

更新:

遗憾的是,即使这样做仍然无法可靠地工作,并且在清除了一些新的mp3之后,Chromecast仍然可能崩溃。看“我的本解决方案”别处此页面上。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

我目前的解决方案

我找不到使用Web Audio API并同时播放四个mp3(用于四声部和声)的Chromecast的最终令人满意的解决方案。第二代似乎根本没有足够的资源来容纳音频缓冲区,并同时使用decodeAudioData解码四个mp3文件,而不会留下太多的垃圾并最终导致崩溃。我决定使用基于Web Audio API构建的surikov的webaudiofont,并使用midi文件。在台式机浏览器或具有更多资源的其他设备上,我从来没有遇到过问题,但是我必须使它在Chromecast上运行。我现在使用webaudiofont完全没有问题。

答案 3 :(得分:0)

我面临着同样的问题。最终对我有用的是断开连接并删除所有连接的资源:

    if (this.source) {
      this.source.disconnect()
      delete this.source
    }

    if (this.gain) {
      this.gain.disconnect()
      delete this.gain
    }

    await this.audioContext.close()

    delete this.audioContext
    delete this.audioBuffer

仅关闭audioContext是不够的。似乎引用将继续存在,以防止垃圾收集。