Fugas de memory de API de audio web en plataforms mobilees

Estoy trabajando en una aplicación que utilizará Audio en gran medida y estoy en las etapas de investigación para decidir si utilizar la API de Audio Web en dispositivos que lo puedan admitir. He montado una cama de testing muy simple que carga un file de sprite MP3 (~ 600kB de tamaño), tiene un button de reproducción y pausa y también un button de destrucción, que en teoría debería permitir que GC recupere la memory utilizada por la implementación de la API Audio Web . Sin embargo, después de cargar y destruir ~ 5 veces iOS falla debido a una exception de memory insuficiente.

He perfilado MobileSafari en XCode Instruments y, de hecho, MobileSafari continuamente come memory. Además, el MP3 de 600kb resulta usar ~ 80-90MB de memory cuando se decodifica.

Mi pregunta es: cuando decodificamos datos de audio usando Web Audio API, ¿por qué el uso de la memory es tan grande y también por qué la memory nunca se recupera? Según mi entendimiento, la deencoding es una operación asíncrona para el browser y, por lo tanto, es probable que ocurra en un hilo aparte. ¿Es posible que los browseres separados por hilo nunca liberen la memory utilizada durante la deencoding?

Mi código está debajo, cualquier ayuda / explicación es muy apreciada:

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>Web Audio Playground</title> </head> <body> <button id="load"> Load </button> <button id="play"> Play </button> <button id="pause"> Pause </button> <button id="destroy"> Destroy </button> <script type="application/javascript"> (function () { window.AudioContext = window.AudioContext || window.webkitAudioContext; var loadButton = document.getElementById('load'), playButton = document.getElementById('play'), pauseButton = document.getElementById('pause'), destroyButton = document.getElementById('destroy'), audioContext = new window.AudioContext(), soundBuffer = null, soundSource = null; loadButton.addEventListener('click', function () { var request = new XMLHttpRequest(); request.open('GET', 'live-sprite.mp3', true); request.responseType = 'arraybuffer'; // Decode asynchronously request.onload = function () { audioContext.decodeAudioData(request.response, function (buffer) { soundBuffer = buffer; }); }; request.send(); }); playButton.addEventListener('click', function () { soundSource = audioContext.createBufferSource(); soundSource.buffer = soundBuffer; soundSource.connect(audioContext.destination); soundSource.start(0); }); pauseButton.addEventListener('click', function () { if (soundSource) { soundSource.stop(0); } }); destroyButton.addEventListener('click', function () { if (soundSource) { soundSource.disconnect(0); soundSource = null; soundBuffer = null; alert('destroyed'); } }); })(); </script> </body> </html> 

La memory es grande porque la Web Audio API descodifica su pequeño MP3 en LPCM de 32 bits, lo que le dará algo del order de 10 MB por minuto por canal.

Entonces, un MP3 estéreo de 4 minutos terminaría siendo algo así como 80 MB.

Esta memory no puede recuperarse mientras su aplicación se AudioBuffer al AudioBuffer decodificado. Entonces, siempre que tenga una reference a él (en su caso, soundBuffer ), esa memory no puede liberarse. Si lo fuera, no podría reproducir el audio.

Hice una publicación en el rastreador de problemas SoundJS sobre esto, pero lo reiteraré aquí para cualquiera que busque:

Parece que simplemente desconectar y desreferencer el object AudioBufferSourceNode en iOS Safari no es suficiente; debe borrar manualmente la reference a su búfer, o el búfer en sí mismo se pierde. (Esto implica que el obj de AudioBufferSourceNode se pierde, pero no lo consideramos un límite práctico en nuestro proyecto).

Desafortunadamente para hacer esto, se necesita crear un búfer de memory de 1 muestra de largo, ya que la asignación a nulo causará una exception. La statement también debe estar grabada, como lo hará Chrome / FF cuando se reasigne .buffer en cualquier momento.

La solución que funcionó fue:

 var ctx = new AudioContext(), scratchBuffer = ctx.createBuffer(1, 1, 22050); class WebAudioAdapter extends AudioAdapter { close() { if( this.__src ) { this.__src.onended = null; this.__src.disconnect(0); try { this.__src.buffer = scratchBuffer; } catch(e) {} this.__src = null; } } } 

Espero que esto también los ayude.