【发布时间】:2019-06-25 03:35:20
【问题描述】:
问题
使用 Web Audio API 创建音频缓冲区时,decodeAudioData 方法会创建一些缓冲区,这些缓冲区驻留在内存中,显然无法通过 JavaScript 访问。它们似乎在浏览器选项卡的整个生命周期中都存在,并且从不收集垃圾。
问题的可能原因
我知道这些缓冲区是从主线程中分离出来的,并设置在另一个线程上进行异步解码。我也知道 API 规范说 decodeAudioData 不应该被允许对同一个输入缓冲区进行两次解码,我认为这就是为什么要保留解码缓冲区和/或编码输入缓冲区的副本的原因。但是,在 Chromecast 等内存有限的设备上,这会导致大量内存累积并导致 Chromecast 崩溃。
再现性
在我的示例代码中,我使用 Ajax 获取 mp3,然后将 arraybuffer 传递给 decodeAudioData 函数。通常在该函数中有一个 onsuccess 回调,它可以将解码的 AudioBuffer 作为参数。但是在我的代码中,我什至没有将其传递进去。因此,在解码后我也不对解码后的缓冲区做任何事情。我的代码中的任何地方都没有引用它。它完全留在本机代码中。但是,每次调用此函数都会增加内存分配并且它永远不会被释放。例如,在 Firefox 中,about:memory 会在选项卡的整个生命周期内显示音频缓冲区。非引用应该足以让垃圾收集器摆脱这些缓冲区。
然后我的主要问题是,是否有对这些解码音频缓冲区的引用,比如在 audiocontext 对象中,或者我可以尝试从内存中删除它们的其他地方?或者有没有其他方法可以使这些存储的和无法访问的缓冲区消失?
我的问题与目前关于 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();
}
预测一些可能的反应。
- 我必须使用 Web Audio API,因为我在 Chromecast 上播放四个音频文件的四个部分和声,并且 html 音频元素不支持在 Chromecast 上同时播放多个。
- 可能是您可以引用的任何 JS 库 [例如Howler.js、Tone.js、Amplitude.js 等] 是建立在 Web Audio API 之上的,因此它们都会共享这个内存泄漏问题。
- 我知道 WAA 的实现取决于每个浏览器。目前我主要关心的是 Chromecast,但我尝试过的每个浏览器都存在这个问题。
- 因此,我认为这是与规范相关的问题,规范需要非重复编码规则,因此实施者将缓冲区的副本保留在浏览器级线程上,以便他们可以根据新的 xhr 输入检查它们。如果规范作者碰巧读到了我的问题,是否有一种方法可以让用户选择这种行为,如果他们愿意,可以选择退出它以防止移动和薄内存平台上的内部缓冲存储?
- 我无法在任何 JS 对象中找到对这些缓冲区的任何引用。
- 我知道我可以使用 audio_context.close() 然后希望对 audio_context 持有的所有资源进行垃圾收集,然后希望我可以用一个新的资源重新实例化 audio_context,但是从经验上讲这还不够及时对于我的申请。 Chromecast 在 GC 取出垃圾之前崩溃。
【问题讨论】:
-
看起来几年前有一个类似的问题也没有得到解答...stackoverflow.com/q/45896791/215552
-
是的,我见过很多类似的问题。但是,他们的所有情况都包括使用解码缓冲区。我的问题表明,这些 audibuffers 是在用户没有以任何方式引用它们的情况下存储的。
-
其实参考我上面的(6)。我在几个小时前调用了 audio_context.close() 并打开了选项卡,当我检查 about:memory 时,音频缓冲区仍然存在。所以它们似乎超出了普通垃圾收集器的范围。即使在我单击 Firefox 的最小化内存使用按钮后,它们仍然存在。只有关闭选项卡,然后运行 Minimize Memory Usage 才能删除它们。
-
根据我的经验,浏览器不会在内部保留解码后的音频,但它们也不会在您期望的时候进行垃圾收集。你试过用开发工具强制垃圾回收吗?
-
是的,您可以根据需要多次运行垃圾回收,并且它不会删除由 decodeAudioData 创建的音频缓冲区。 decodeAudioData 的规范说您的输入缓冲区将被中性或与主线程分离,并制作它的副本,然后传递给执行解码的线程。这一切都是在本机浏览器代码中完成的,而不是在 JS 空间中完成的,所以没有对它的引用,JS 垃圾收集也看不到这一点。浏览器自己的 GC 机制会保留这些副本,直到选项卡完全关闭。