iOS Programming Recipe 23: Audio Recording and Playback

One of the coolest things about the iPhone is it’s ability to handle audio. Since it was born out of an iPod, the iPhone handles audio quite well. In this recipe we’ll explore audio recording and playback. We’ll be creating a simple app that records a sound file and plays it back. We’re basically making a voice memo app that only saves one memo and plays it back.

Assumptions

  • None other than the very basics! how to navigate Xcode: Get Started

Setting Up the Project

To start with, create a new single view controller and title it something rediculously long like “Recipe23AudioRecordAndPlayback”. Just kidding, call it whatever you like, but that’s what I named mine and that’s where you’ll find it on Github.

Linking Frameworks

When the project opens up select the main project from the project navigator and scroll down in the main content area until you reach a section titled “Linked Frameworks and Libraries”. Then press the little “+” button in the bottom left corner of that section.

SelectFrameworks

When the Dialogue pops up choose “CoreAudio.framework” and “AVFoundation.framework” by holding command and selecting and then press “Add”.

Congrats, your frameworks are all linked up! Now lets design the interface a bit.

Designing the Interface

Open up the storyboard, add three buttons, and title them “Record”,”Stop”, and “Play”.

Hey look at that! We’re already done with the interface. Kinda boring, but sometimes less is more. If you want, change the background to spice it up a bit.

Now You should have a view designed as follows:

CompletedView

Making Connections

Switch to the split view editor and control drag from each button to the ViewController.h file. Make them all actions and give them titles of “audioRecord”,”audioStop”, and “audioPlay”.

MakeConnection

ChooseAction

Populating the Interface File

Ok Now we’re ready to fill in all the other junk needed to make this simple app work!

First we’ll need to import our frameworks so we can use them.


1
2
3
#import <UIKit/UIKit.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AVFoundation/AVFoundation.h>

Then We’ll add some properties:


1
2
@property (strong, nonatomic) AVAudioRecorder *audioRecorder;
@property (strong, nonatomic) AVAudioPlayer *audioPlayer;

Your full interface file should now look like the following:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) AVAudioRecorder *audioRecorder;
@property (strong, nonatomic) AVAudioRecorder *audioPlayer;


- (IBAction)audioRecord:(id)sender;
- (IBAction)audioStop:(id)sender;
- (IBAction)audioPlay:(id)sender;


@end

Now we’re done with the interface file so let’s update the implementation file

Populating the Implementation file

The ViewDidLoad Method

Ok First we’ll add some setup code to the viewDidLoad method:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad
{

    //Audio Recording Setup

    NSURL *audioFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingString:@"audioRecording.m4a"]];

    NSDictionary *audioSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                               [NSNumber numberWithFloat:44100],AVSampleRateKey,
                               [NSNumber numberWithInt: kAudioFormatAppleLossless],AVFormatIDKey,
                               [NSNumber numberWithInt: 1],AVNumberOfChannelsKey,
                               [NSNumber numberWithInt:AVAudioQualityMedium],AVEncoderAudioQualityKey,nil];

    self.audioRecorder = [[AVAudioRecorder alloc]
                      initWithURL:audioFileURL
                      settings:audioSettings
                      error:nil];

    [super viewDidLoad];
}

What we’re doing here is first we set the url to a file named audioRecording.m4a in the temp directory. Now we can choose to use a file path to a specific folder in the file system, but I didn’t feel it was worth getting into for this recipe.

Next we create a dictionary for all of the settings required for recording audio :

  • AVSampleRateKey – The sample rate, CD’s are usually sampled at 44100, so this float number should never usually change.
  • AVFormatIDKey – This is the format for the audio, we chose apple lossless, but there are a few to choose from like mpeg 4 and many other common formats.
  • AVNumberOfChannelsKey – This sets the channels we record, since we’re recording mono there was no need for stereo so we chose 1. A mono recording. For stereo choose 2.
  • AVEncoderAudioQualityKey – Here again you have many to choose from (min,max,high,low) I chose medium for a good balance of quality and file size.

The Action Methods

Now that the view did load method is done we can implement our button actions. As mentioned previously we have Record, Stop, and Play:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (IBAction)audioRecord:(id)sender
{
    [self.audioRecorder record];
}

- (IBAction)audioStop:(id)sender
{
    [self.audioPlayer stop];
    [self.audioRecorder stop];

    NSURL *audioFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingString:@"audioRecording.m4a"]];
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:nil];
}

- (IBAction)audioPlay:(id)sender
{
    [self.audioPlayer play];
}

The first method is easy! All we do is call the class method “record” on our audioRecord property. This starts recording. The auidoPlay action is also very easy, it calls the “play” method on our audioPlayer.

The audioStop method is a little more tricky, although not very much. First we tell both the audioRecorder and audioPlayer to stop.
Then we update the audioPlayer file with the latest recording.

Now you should be able to run it and everything should work just fine.

Going Further

Ok, The recording works and all, but wouldn’t be nice to have some visual feedback? Lets add a progress bar this so we can see how far we have gone into our playback.

To do this first add a progress bar to your interface from the storyboard. While you’re add it add a label at the top:

progressBarView

Now, We’ll want our default state for the progress bar to be 0. Select the progress bar and from the attributes inspector change the progress to 0

progessValue

Now control click the progress bar and drag it into the ViewController.h file and make an an outlet with a name “audioProgress”

add a new NSTimer property to the ViewController.h file as well. Your interface file should now look like the following:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <UIKit/UIKit.h>
#import <CoreAudio/CoreAudioTypes.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) AVAudioRecorder *audioRecorder;
@property (strong, nonatomic) AVAudioPlayer *audioPlayer;
@property (strong, nonatomic) NSTimer *timer;


- (IBAction)audioRecord:(id)sender;
- (IBAction)audioStop:(id)sender;
- (IBAction)audioPlay:(id)sender;

@property (strong, nonatomic) IBOutlet UIProgressView *audioProgress;

@end

Ok Now we’re ready to update the implentation file!

Switch to the ViewController.m file and update the audioplay action as follows:


1
2
3
4
5
6
7
8
9
10
11
- (IBAction)audioPlay:(id)sender
{
    [self.audioPlayer play];

    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.25
                                                     target:self
                                                   selector:@selector(updateProgress)
                                                   userInfo:nil
                                                repeats:YES];
    [self.audioProgress setHidden:NO];
}

Then update the audioRecord action like so:


1
2
3
4
5
6
7
- (IBAction)audioRecord:(id)sender
{
    [self.timer invalidate];
    [self.audioProgress setHidden:YES];

    [self.audioRecorder record];
}

Then add a new method “updateProgress”:


1
2
3
4
5
6
7
8
- (void)updateProgress
{
    float timeLeft = self.audioPlayer.currentTime/self.audioPlayer.duration;

    // upate the UIProgress

    self.audioProgress.progress= timeLeft;
}

Ok, so first we’re creating a timer that will call our new method “updateProgress” every .25 seconds. Then we make sure the audioProgress bar is not hidden. The new method devides the current time by the total time and sets the current progress to that float value (a number between 0.0 and 1.0).

In the audioRecord action we invalidate the time so it doesn’t cause our recording to clip. Then we hide the audioProgress bar since it doesn’t make much sense when recording a track of unknown length.

That’s it! Build it and run it!

FinalProduct

About Joe Hoffman

I am An Electrical Engineer that spends a lot of my off time doing web development and other programming. Currently I am trying to expand my knowledge with iOS and I find that writing tutorials helps me learn more thoroughly as well as provide some useful info to others.

Comments

  1. Hello Joe!

    First of all, thanks for your tutorial here.
    I’m trying to do that same thing and I’m using an identical code, although when I try to play the recorded sound, AVAudioPlayer responds with a OSStatusError 1685348671, which basically means that either the file or the format is corrupt or not playable. If I try to bring the file to my computer, not even Chrome or other players can play.
    When opening the file and checking format and data, everything seems to be ok, but you can see a lack of metadata within the file. (Excuse the ASCII instead of HEX): “ftypM4A M4A mp42isom f òÄ” – Where right after ” f òÄ!” is where the music data begins.

    Any idea? Have you experience that?
    Thanks in advance for your help.

    Jose L

    • Joe Hoffman says:

      Hi Jose,

      Try downloading the source from our Github account and see if you still have issues.

      -Joe

      • Hi Joe, first time that I’m posting here. Thank you for all your efforts by writing these terrific tutorials.

        Your sample recipe with the audio only works in the simulator. Not on devices. At least – I encountered the same thing as Jose.

        You might need to initialize your audio session in the viewDidLoad, like so:

        AVAudioSession *session = [AVAudioSession sharedInstance];
        [session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];

        Everything should work fine on devices after that.

        • Joe Hoffman says:

          Thanks Cliff!

          I find it odd it would work in the simulator and not on the device…..

          Either way, Thanks for posting a solution to this! I’ll definitely investigate this a little further.

          Thanks Again!
          Joe

        • I experienced the same thing – worked in simulator but not on device. Cliff’s suggestion with the session fixed it and it now works on device as well. Thanks to you both, good stuff!

  2. what if i want it to keep recording and playing at the same time, user set delay time in mili seconds and add frequency to play with it variable frequency

  3. Yeah Thanks in advance :)

  4. can we record audio in background?

  5. Hi,
    Is it possible to record on one thread and play one the other thread continuously.

    Thanks

    • Joe Hoffman says:

      Hi Niki,

      I don’t think AVAudio interferes with your main thread, so it shouldn’t be necessary to get into multithreading. To play audio and record audio simultaneously you will need to create an audio session with the correct category:

      [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];

      Then start playing shortly after you start recording.

  6. Hello Joe,
    thanks for the tutorial!
    What if I want to save whatever I record?
    And by saving I mean that whenever I exit my app or turn off my mobile or whatever, then accessing my app again, I can find all the audios I recorded before.

    thanks in advance.

    • Joe Hoffman says:

      Hi Hind,

      There are several ways to do this. The first way I can think of is simply change the file name of the recording every time a recording stops by modifying the audiorecorder file. You could then save every new audio file in a plist or something like that. Alternatively, you could also query the temp directory for the contents of it. In this case “documentsPath” would just be the temp directory.

      NSError * error;
      NSArray * directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsPath error:&error];
  7. Matthew D says:

    I have a similar question…is there a way to send to email in the way “Voice Memos” does it?

    Thank you

    • Joe Hoffman says:

      Hi Matthew,

      For this, I think you would want to use UIActivityViewController or MFMailComposeViewController. The difference is, the UIActivityViewController will popup options like you would expect when pushing the share button in most apps. The MFMaiLComposeViewController will take you straight to email. Here’s a link on audio using the UIActivityViewController, this may be slightly different depending on your audio format.

  8. Krithika says:

    In the same project, how do you implement voice as input for play , pause etc?

  9. Thanks for the great tutorial! i followed all your steps one by one but the audio player is empty. It actually never recorded.
    In the first line of audioStop method, I print out NSLog(@”is recording = %hhd”, self.audioRecorder.isRecording); and it gives me a 0.
    at the end of the audioStop method, I print the duration NSLog(@”duration = %f”, self.audioPlayer.duration); and it is also 0.
    It’s running on an iPod touch with a working mircrophone (tested it in another app) – and the permissions to use the microphone is given to my app.
    Any idea why it is not recording?

    • I actually got it working after reading some questions on stack overflow.
      All I did was adding this line to the audioRecord method:
      [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:NULL];

  10. Hi. Great tutorial. :)

    Can you elaborate more and perhaps modify from your example codes how to save the recorded files using document directory or plist (whichever is easier) and display all the recorded audios in a list format. Hope you can help.

    Thanks in advance!

Speak Your Mind

*

css.php
Privacy Policy