9

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?

Fabien Snauwaert
  • 4,995
  • 5
  • 52
  • 70
  • Have you had the same issue with other audio files or just that one? I tested your live demo on my iPhone with iOS 8.3 and didn't hear any distortion. – James Simpson Apr 27 '15 at 16:55
  • Yes, it occurs with different files. Here is another higher-level test: https://bilingueanglais.com/tmp/howler/v2-howler-hello.html and Chrome is fine, only Safari has the issue. – Fabien Snauwaert Apr 27 '15 at 16:58
  • 2
    That looks like a low-level Safari problem. Looking at the waveform you recorded, it looks like around 90 samples out of every 512 samples are zero - like, something about their block processing or decoding is having problems. – cwilso Apr 27 '15 at 18:28
  • Edited the question after getting my hands on a phone. The issue is actually there only on iOS 7.1, both Chrome and Safari. – Fabien Snauwaert Apr 29 '15 at 15:34
  • 3
    It's still an issue with iOS 9.2. – Nikolay Tsenkov Dec 27 '15 at 07:08
  • @NikolayTsenkov do you mean iOS 9.2 on an actual device or in the simulator? – Fabien Snauwaert Dec 28 '15 at 10:57
  • 1
    Same problem here (tested on a iPhone 5 (IOS 9.2) and two different iPhone 6 (IOS 9.2 and IOS 9.2.1). Unable to reproduce the problem on a iPhone 5S (IOS 9.2.1). Pattern seems to be that it works first time, and always after reloading. But if I close / reopen Safari, the sound is always distorted the first time, and works after reload. – Patrick Fabrizius Feb 01 '16 at 13:01
  • Woof, I'm still running into this issue and it's 2019. iOS's web audio is basically garbage. – frodo2975 Oct 18 '19 at 05:27

2 Answers2

5

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.

Nikolay Tsenkov
  • 1,128
  • 10
  • 26
5

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
}
Yining Chen
  • 131
  • 2
  • 3
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/12257445) – Teepeemm May 05 '16 at 20:02
  • edited with more detailed explanation & code snippet from original link – Yining Chen May 06 '16 at 19:47
  • Unfortunately this answer didn't work for me in iOS 12. – frodo2975 Nov 13 '19 at 21:36