I'm trying to eliminate startup lag when playing a (very short -- less than 2 seconds) audio file via AVAudioPlayer on the iPhone.
First, the code:
NSString *audioFile = [NSString stringWithFormat:@"%@/%@.caf", [[NSBundle mainBundle] resourcePath], @"audiofile"];
NSData *audioData = [NSData dataWithContentsOfMappedFile:audioFile];
NSError *err;
AVAudioPlayer *audioPlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithData:audioData error:&err];
audioPlayer.delegate = self;
[audioPlayer play];
I also implement the audioPlayerDidFinishPlaying method to release the AVAudioPlayer once I'm done.
The first time I play the audio the lag is palpable -- at least 2 seconds. However, after that the sound plays immediately. I suspect that the culprit, then, is the [NSData dataWithContentsOfMappedFile] taking a long time reading from the flash initially, but then being fast on later reads. I'm not sure how to test that, though.
Is that the case? If so, should I just pre-cache the NSData objects and be aggressive about clearing them in low memory conditions?
I wrote a simple wrapper for AVAudioPlayer that provides a prepareToPlay method that actually works:
https://github.com/nicklockwood/SoundManager
It basically just scans your app for sound files, picks one and plays it at zero volume. That initialises the audio hardware and allows the next sound to play instantly.
I've taken an alternative approach that works for me. I've seen this technique mentioned elsewhere (tho I don't recall at the moment where that was).
In short, preflight the sound system by loading and playing a short, "blank" sound before you do anything else. The code looks like this for a short mp3 file I preload in my view controller's
viewDidLoad
method that's of .1 second duration:You can create your own blank mp3 if you want, or do a google search on "blank mp3s" to find and download one already constructed by somebody else.
I don't know for sure, but I suspect the NSData object is being lazy and loading the contents of the file on demand. You can try "cheating" by calling
[audioData getBytes:someBuffer length:1];
at some early point to get it to load that file before it's needed.Here's a simple Swift extension to AVAudioPlayer that uses the play-and-stop at 0 volume idea presented in previous answers. PrepareToPlay() unfortunately at least for me did not do the trick.
If your audio is less than 30 seconds long in length and is in linear PCM or IMA4 format, and is packaged as a .caf, .wav, or .aiff you can use system sounds:
Import the AudioToolbox Framework
In your .h file create this variable:
In your .m file implement it in your init method:
In your IBAction method you call the sound with this:
This works for me, plays the sound pretty damn close to when the button is pressed. Hope this helps you.
Other workaround is to create a short & silent audio and play it on the first time player is initiated.
If you dont want to create a new sound file yourself, you may download a short & silent audio file here : https://www.dropbox.com/s/3u45x9v72ic70tk/silent.m4a?dl=0