Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
310 views
in Technique[技术] by (71.8m points)

html - WebKit Audio distorts on iOS 6 (iPhone 5) first time after power cycling

I've been struggling with an elusive audio distortion bug using webkitAudioContext in HTML5 under iOS 6. It can happen in other circumstances, but the only way I can get 100% repro is on the first visit to my page after power cycling the device. It seems like if you visit any audio-capable page prior to visiting this one, the problem will not occur.

The distortion only happens to audio generated by webkitAudioContext.decodeAudioData() and then played through webkitAudioContext.createBufferSource(). Audio playback of webkitAudioContext.createMediaElementSource() will not distort.

Am I missing some initialisation step? Here's the code and HTML in its entirety that I submitted to Apple as a bug report (but have received no reply):

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

Note: The body text suggests this only occurs on iOS 6.1.4, but I mean to say that the problem only occurs upon power cycling in this situation. I've experienced the problem on the iPad Mini under 6.1.3, too, but not upon power cycling.

Edit: a few things I've tried... Deferring the creation of the buffer source makes no difference. Using different transcoders to generate the .mp3 file it plays makes no difference. Playing throwaway silence as the first sound makes no difference as the distortion continues for every decodeAudioData sound until the page reloads. If createMediaElementSource and createBufferSource sources are mixed in the same page, only the createBufferSource audio (using decodeAudioData) will distort. When I check the request.response.byteLength in the failure case and the non-failure case, they are the same, suggesting the XMLHttpRequest is not returning incorrect data, though I would think corruption of the data would damage the MP3 header and render the file unplayable anyway.

There is one observable difference between the failure condition and the non-failure condition. The read-only value context.sampleRate will be 48000 in the failure state and 44100 in the non-failure state. (Yet the failure state sounds lower pitch than the non-failure state.) The only thing that occurs to me is a hack wherein I refresh the page via JavaScript if 48000 is detected on a browser that should be reporting 44100, but that's serious userAgent screening and not very future proof, which makes me nervous.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I have been having similar problems, even on iOS 9.2.

Even without a <video> tag, playback is distorted when first playing audio on the page after cold boot. After a reload, it works fine.

The initial AudioContext seems to default to 48 kHz, which is where distortion is happening (even with our audio at 48 kHz sample rate). When playback is working properly, the AudioContext has a sample rate of 44.1 kHz.

I did find a workaround: it is possible to re-create the AudioContext after playing an initial sound. The newly-created AudioContext seems to have the correct sample rate. To do this:

// 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();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...