Distorted audio in iOS 7.1 with WebAudio API

2019-04-06 16:54发布

问题:

On iOS 7.1, I keep getting a buzzing / noisy / distorted sound when playing back audio using the Web Audio API. It sounds distorted like this, in place of normal like this.

The same files are fine when using HTML5 audio. It all works fine on desktop (Firefox, Chrome, Safari.)

EDIT:

  • The audio is distorted in the iOS Simulator versions iOS 7.1, 8.1, 8.2. The buzzing sound often starts before I even playback anything.
  • The audio is distorted on a physical iPhone running iOS 7.1, in both Chrome and Safari.
  • The audio is fine on a physical iPhone running iOS 8.1, in both Chrome and Safari.

i.e.: the buzzing audio is on iOS 7.1. only.


Howler.js is not the issue. The problem is still there using pure JS like so:

var context;
var sound;
var extension = '.' + ( new Audio().canPlayType( 'audio/ogg' ) !== '' ? 'ogg' : 'mp3');


/** Test for WebAudio API support **/
try {
    // still needed for Safari
    window.AudioContext = window.AudioContext || window.webkitAudioContext;

    // create an AudioContext
    context = new AudioContext();
} catch(e) {
    // API not supported
    throw new Error( 'Web Audio API not supported.' );
}

function loadSound( url ) {
    var request = new XMLHttpRequest();
    request.open( 'GET', url, true );
    request.responseType = 'arraybuffer';

    request.onload = function() {
        // request.response is encoded... so decode it now
        context.decodeAudioData( request.response, function( buffer ) {
        sound = buffer;
        }, function( err ) {
            throw new Error( err );
        });
    }

    request.send();
}

function playSound(buffer) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.start(0);
}

loadSound( '/tests/Assets/Audio/En-us-hello' + extension );


$(document).ready(function(){ 

    $( '#clickme' ).click( function( event ) {
        playSound(sound);
    });


}); /* END .ready() */

A live version of this code is available here: Web Audio API - Hello world


Google did not bring up any result about such a distorted sound issue on iOS 7.1.

Has anyone else run into it? Should I file a bug report to Apple?

回答1:

I believe the issue is caused due to resetting the audioContext.sampleRate prop, which seem to happen after the browser/OS plays something recorded in a different sampling rate.

I've devised the following workaround, which basically silently plays a short wav file recorded in the sampling rate that the device currently does playback on:

"use strict";

var getData = function( context, filePath, callback ) {
    var source = context.createBufferSource(),
        request = new XMLHttpRequest();

    request.open( "GET", filePath, true );

    request.responseType = "arraybuffer";

    request.onload = function() {
        var audioData = request.response;

        context.decodeAudioData(
            audioData,
            function( buffer ) {
                source.buffer = buffer;

                callback( source );
            },
            function( e ) {
                console.log( "Error with decoding audio data" + e.err );
            }
        );
    };

    request.send();
};

module.exports = function() {
    var AudioContext = window.AudioContext || window.webkitAudioContext,
        context = new AudioContext();

    getData(
        context,
        "path/to/short/file.wav",
        function( bufferSource ) {
            var gain = context.createGain();
            gain.gain.value = 0;
            bufferSource.connect( gain );
            gain.connect( context.destination );
            bufferSource.start( 0 );
        }
    );
};

Obviously, if some of the devices have different sampling rates, you would need to detect and use a specific file for every rate.



回答2:

it looks like iOS6+ Safari defaults to a sample rate of 48000. If you type this into the developer console when you first open mobile safari, you'll get 48000:

var ctx = new window.webkitAudioContext();
console.log(ctx.sampleRate);

Further Reference: https://forums.developer.apple.com/thread/20677

Then if you close the initial context on load: ctx.close(), the next created context will use the sample rate most other browsers use (44100) and sound will play without distortion.

Credit to this for pointing me in the right direction (and in case the above no longer works in the future): https://github.com/Jam3/ios-safe-audio-context/blob/master/index.js

function as of post date:

function createAudioContext (desiredSampleRate) {
  var AudioCtor = window.AudioContext || window.webkitAudioContext

  desiredSampleRate = typeof desiredSampleRate === 'number'
    ? desiredSampleRate
    : 44100
  var context = new AudioCtor()

  // Check if hack is necessary. Only occurs in iOS6+ devices
  // and only when you first boot the iPhone, or play a audio/video
  // with a different sample rate
  if (/(iPhone|iPad)/i.test(navigator.userAgent) &&
      context.sampleRate !== desiredSampleRate) {
    var buffer = context.createBuffer(1, 1, desiredSampleRate)
    var dummy = context.createBufferSource()
    dummy.buffer = buffer
    dummy.connect(context.destination)
    dummy.start(0)
    dummy.disconnect()

    context.close() // dispose old context
    context = new AudioCtor()
  }

  return context
}