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 (eg 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 скрининг и не очень будущее доказательство, что заставляет меня нервничать.

У меня были подобные проблемы, даже на 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(); } 

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

 // 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, поэтому я не думаю, что исправление поступит в ближайшее время. Мы застряли с хаками в среднем времени, например, проверяем изменение частоты дискретизации.

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

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

 npm install ios-safe-audio-context 
Давайте будем гением компьютера.