Time for sound. In iOS it looks like sound can be played by AVAudioPlayer, and recorded by AVAudioRecorder. The broader framework is called AVFoundation. However, at a very low level there is also the Core Audio framework, which includes the Audio Toolbox and Audio Unit frameworks.
There are some great sample projects available at Apple’s Audio & Visual Starting Point:
- AddMusic shows how to pick songs from the iTunes library. This also includes setting up an AVAudioSession, which I’m not sure is necessary.
- avTouch shows how to play sounds using AVAudioPlayer.
- SpeakHere shows how to record sounds using AQRecord. On closer inspection this looks extremely daunting.
- AVAudioRecorder is another approach that looks a lot easier, as documented in this Stack Overflow post, and this Techotopia post. However, there is a subtlety when you try to play the recorded sound in an AVAudioPlayer – for some reason using
self.recordingPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.audioRecorder.url error:&error];
does not ever call the delegate routines when the URL is that of the recorded sound (in either the simulator or the device). So instead, I used
NSData* recordedData = [NSData dataWithContentsOfFile:[self.audioRecorder.url path]]; self.recordingPlayer = [[AVAudioPlayer alloc] initWithData:recordedData error:&error];
- which worked fine. Another gotcha is to make sure the AVAudioPlayer object is not disposed of at the end of the method, but before the playing actually happens. My solution was to make it an instance variable (as in the sample code above).
- Audio UI Sounds (SysSound) shows how to play alert sound-effects, but on closer inspection comes with the warning this should not be used for other sound effects, e.g. in games.
- OpenAL is one solution, with sample code called oalTouch. This comes with some C code (MyOpenALSupport.c) which looks handy, if a little daunting. Note that to call this with Automatic Reference Counting (ARC), you need to use
(__bridge CFURLRef)and then delete the line
CFRelease(fileURL);. I forgot this last part and it took a while to realise what was going on. There is also a great basic tutorial by Ben Britten.
I found strange behaviour with AVAudioSession.
- You can provide a category to the session, and I chose
AVAudioSessionCategorySoloAmbient, which is meant to silence sound from other apps. However it seems to also silence OpenAL and other AVAudioPlayer sounds from the same app. Switching to
AVAudioSessionCategoryAmbientsolved this problem for me.
- I had set my category to
AVAudioSessionCategoryAmbientwhile setting up the audio recorder, but changed it to
AVAudioSessionCategoryRecordjust before the
recordcommand; I changed it back again after recording stops. This worked on the simulator, but on the device the call to
recordsimply returned NO with no explanation, and nothing was recorded. I spent most of a day trying to work out why. The answer: use the category
AVAudioSessionCategoryPlayAndRecordwhile setting up the recorder.