Подтвердить что ты не робот

Утечки памяти веб-аудио API на мобильных платформах

Я работаю над приложением, которое будет использовать Audio довольно сильно, и я нахожусь на этапах исследования, чтобы решить, использовать ли API Web Audio на устройствах, которые могут его поддерживать. Я собрал очень простой тестовый слой, который загружает файл спрайта MP3 (размером ~ 600 КБ), имеет кнопку воспроизведения и паузы, а также кнопку уничтожения, что теоретически позволяет GC восстановить память, используемую при реализации API Web Audio API, Однако после загрузки и уничтожения ~ 5 раз iOS падает из-за исключения из памяти.

Я профилировал MobileSafari в XCode Instruments, и действительно, MobileSafari постоянно ест память. Кроме того, при декодировании 600kb MP3 будет использовать память ~ 80-90 МБ.

Мой вопрос: при декодировании аудиоданных с использованием API веб-аудио, почему использование памяти настолько велико, а также почему память не восстанавливается? Насколько я понимаю, декодирование - это асинхронная операция для браузера и, по-видимому, происходит в отдельном потоке? Возможно ли, что отдельные ветки браузеров никогда не освобождают память, используемую при декодировании?

Мой код ниже, любая помощь/объяснение очень ценится:

<!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>
4b9b3361

Ответ 1

Память велика, потому что API веб-аудио расшифровывает ваш маленький MP3 в 32-разрядный LPCM - который даст вам что-то порядка 10 МБ в минуту на канал.

Итак, 4-минутный стереофонический MP3 в итоге будет чем-то вроде 80 МБ.

Эта память не может быть восстановлена ​​до тех пор, пока ваше приложение держит декодированный AudioBuffer. До тех пор, пока у вас есть ссылка на него (в вашем случае soundBuffer), эта память не может быть выпущена. Если бы это было так, вы не смогли воспроизвести звук.

Ответ 2

Я сделал сообщение в SoundJS issue tracker об этом, но я повторю его здесь для тех, кто смотрит:

Похоже, что просто отключить и разыменовать объект AudioBufferSourceNode на iOS Safari недостаточно; вам нужно вручную очистить ссылку на свой буфер, или сам буфер протекает. (Это означает, что сам AudioBufferSourceNode obj протекает, но мы не рассматривали это как практический предел в нашем проекте.)

К сожалению, для этого нужно создать 1-образный длинный буфер с нуля, поскольку присвоение значения null приведет к исключению. Заявление также должно быть завершено с помощью try-catch, так как Chrome/FF будет выдавать, когда .buffer переназначается в любое время.

Решение:

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;
        }
    }
}

Надеюсь, это тоже поможет!