Fabien Snauwaert
Fabien Snauwaert

Reputation: 5651

Distorted audio in iOS 7.1 with WebAudio API

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:

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?

Upvotes: 9

Views: 3264

Answers (2)

Yining Chen
Yining Chen

Reputation: 141

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
}

Upvotes: 5

Nikolay Tsenkov
Nikolay Tsenkov

Reputation: 1128

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.

Upvotes: 5

Related Questions