WebKit Audio искажает звук на iOS 6 (iPhone 5) в первый раз после выключения питания

Я боролся с неуловимой ошибкой искажения звука, используя webkitAudioContext в HTML5 под iOS 6. Это может произойти и при других обстоятельствах, но единственный способ получить 100% воспроизведение - это при первом посещении моей страницы после включения и выключения устройства. Похоже, что если вы посетите любую аудио-совместимую страницу до посещения этой, проблема не возникнет.

Искажение происходит только со звуком, сгенерированным webkitAudioContext.decodeAudioData() и затем воспроизведенным через webkitAudioContext.createBufferSource(). Воспроизведение звука webkitAudioContext.createMediaElementSource() не искажается.

Я пропустил какой-то шаг инициализации? Вот весь код и HTML, которые я отправил в Apple в качестве отчета об ошибке (но не получил ответа):

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript">
        var buffer = null;
        var context = null;
        var voice = null;

        function load_music(file) {
            context = new webkitAudioContext();
            voice = context.createBufferSource();
            var request = new XMLHttpRequest();
            request.onload = function() {
                context.decodeAudioData(request.response, function(result) {
                    buffer = result;
                    document.getElementById("start").value = "Start";
                });
            };
            var base = window.location.pathname;
            base = base.substring(0, base.lastIndexOf("/") + 1);
            request.open("GET", base + file, true);
            request.responseType = "arraybuffer";
            request.send(null);
        }

        function start_music() {
            if (!buffer) {
                alert("Not ready yet");
                return;
            }
            voice.buffer = buffer;
            voice.connect(context.destination);
            voice.noteOn(0);

            document.getElementById("compare").style.display = "block";
        }
        </script>       
    </head>

    <body onload="load_music('music.mp3')">
        <p>This is a simple demo page to reproduce a <strong>webkitAudio</strong>
        problem occurring in Safari on iOS 6.1.4. This is a stripped down demo
        of a phenomenon discovered in our HTML5 game under development,
        using different assets.</p>

        <p><u>Steps to reproduce:</u></p>

        <ol>
            <li>Power cycle <strong>iPhone 5 with iOS 6.1.4</strong>.</li>
            <li>Launch Safari immediately, and visit this page.</li>
            <li>Wait for &quot;Loading...&quot; below to change to
                &quot;Start&quot;.</li>
            <li>Tap &quot;Start&quot;.</li>
        </ol>

        <p><u>Issue:</u></p>

        <p>Audio will be excessively distorted and play at wrong pitch. If 
        another audio-enabled web site is visited before this one, or this 
        site is reloaded, the audio will fix. The distortion only happens on 
        the first visit after cold boot.  <strong>To reproduce the bug, it is 
        critical to power cycle before testing.</strong></p>

        <p>This bug has not been observed on any other iOS version (e.g. does
        not occur on iPad Mini or iPod 5 using iOS 6.1.3).</p>

        <input id="start" type="button" value="Loading..." onmousedown="start_music()" />

        <span id="compare" style="display:none;"><p><a href="music.mp3">Direct link</a> to audio file, for
        comparison.</p></span>
    </body>
</html>

Примечание. Основной текст предполагает, что это происходит только в iOS 6.1.4, но я хочу сказать, что в этой ситуации проблема возникает только при включении питания. Я тоже столкнулся с этой проблемой на iPad Mini версии 6.1.3, но не при включении питания.

Редактировать: несколько вещей, которые я пробовал... Отсрочка создания источника буфера не имеет значения. Использование разных транскодеров для создания воспроизводимого файла .mp3 не имеет значения. Воспроизведение одноразовой тишины в качестве первого звука не имеет значения, поскольку искажение продолжается для каждого звука decodeAudioData, пока страница не перезагрузится. Если источники createMediaElementSource и createBufferSource смешаны на одной и той же странице, только звук createBufferSource (с использованием decodeAudioData) будет искажаться. Когда я проверяю request.response.byteLength в случае сбоя и в случае без сбоя, они одинаковы, предполагая, что XMLHttpRequest не возвращает неверные данные, хотя я думаю, что повреждение данных повредит заголовок MP3 и отобразит файл все равно не воспроизводится.

Существует одно заметное различие между состоянием отказа и состоянием отсутствия отказа. Значение context.sampleRate, доступное только для чтения, будет равно 48000 в состоянии сбоя и 44100 в состоянии без сбоя. (Тем не менее, состояние сбоя звучит более низко, чем состояние без сбоя.) Единственное, что мне приходит в голову, — это хак, когда я обновляю страницу с помощью JavaScript, если в браузере обнаруживается 48000, который должен сообщать 44100, но это серьезный userAgent. скрининг и не очень перспективное, что заставляет меня нервничать.


person jeffvav    schedule 26.07.2013    source источник


Ответы (3)


У меня были похожие проблемы, даже на iOS 9.2.

Даже без тега <video> воспроизведение искажается при первом воспроизведении звука на странице после холодной загрузки. После перезагрузки работает нормально.

Начальное значение AudioContext, по-видимому, установлено по умолчанию на 48 кГц, где и происходит искажение (даже с нашим звуком с частотой дискретизации 48 кГц). При правильном воспроизведении частота дискретизации AudioContext составляет 44,1 кГц.

Я нашел обходной путь: можно воссоздать AudioContext после воспроизведения исходного звука. Недавно созданный AudioContext, кажется, имеет правильную частоту дискретизации. Сделать это:

// inside the click/touch handler
var playInitSound = function playInitSound() {
    var source = context.createBufferSource();
    source.buffer = context.createBuffer(1, 1, 48000);
    source.connect(context.destination);
    if (source.start) {
        source.start(0);
    } else {
        source.noteOn(0);
    }
};

playInit();
if (context.sampleRate === 48000) {
    context = new AudioContext();
    playInit();
}
person squidpickles    schedule 28.12.2015
comment
Работает для меня, спасибо! Поместите это в начальный обработчик touchEnd, чтобы также разблокировать веб-аудио iOS. - person Luke Femur; 17.04.2016

Я обнаружил похожую ошибку с видео в формате HTML5 и думаю, что обнаружил корень проблемы.
Я заметил, что если вы воспроизводите видео с использованием тега <video>, оно устанавливает значение context.sampleRate в значение, в котором был закодирован звук видео. Кажется, что в iOS Safari есть один глобальный sampleRate, который он использует для всего. Чтобы увидеть это, попробуйте следующее:

// Play a video with audio encoded at 44100 Hz
video.play();

// This will console log 44100
var ctx = new webkitAudioContext();
console.log(ctx.sampleRate);

// Play a video with audio encoded at 48000 Hz
video2.play();

// This will console log 48000
var ctx = new webkitAudioContext();
console.log(ctx.sampleRate);

Эта глобальная частота дискретизации сохраняется при загрузке страницы и распределяется между вкладками и экземплярами браузера. Таким образом, воспроизведение видео с YouTube на другой вкладке может привести к поломке всего вашего декодированного звука.

Звук искажается, когда он декодируется с одной частотой дискретизации и воспроизводится с другой.

  1. Декодировать звук и сохранить буфер
  2. Сделайте что-нибудь, чтобы изменить частоту дискретизации, например воспроизведите видео или аудио файл.
  3. Буфер воспроизведения (искаженный)

Я не знаю, почему это происходит после холодного запуска. Если бы мне пришлось угадать, Safari не инициализирует эту глобальную частоту дискретизации, пока вы не попытаетесь ее использовать.

Проблема все еще существует на iOS 7, поэтому я не думаю, что в ближайшее время будет исправление. Тем временем мы застряли с хаками, такими как проверка измененной частоты дискретизации.

person jdorn    schedule 07.10.2013
comment
Это все еще проблема на IOS 8. - person John Xiao; 10.11.2014
comment
А также 9.2.1 (последняя версия на момент написания статьи) - person Patrick Fabrizius; 01.02.2016

Пакет npm находится в сети, чтобы исправить это:

https://github.com/Jam3/ios-safe-audio-context

npm install ios-safe-audio-context
person Luke Femur    schedule 18.04.2016