Exploring iPhone Audio Part 7

Old Fashioned Microphone
In the last article of this series we started looking at iPhone audio playback. This time we are going to see how to actually play an audio file that our applicaiton has recorded.








The startPlayback method sets everything up for audio playback and starts the playback process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)startPlayback
{
    playState.currentPacket = 0;
 
    [self setupAudioFormat:&playState.dataFormat];
 
    OSStatus status;
    status = AudioFileOpenURL(fileURL, fsRdPerm, kAudioFileAIFFType, &playState.audioFile);
    if(status == 0)
    {
        status = AudioQueueNewOutput(
                &playState.dataFormat,
                AudioOutputCallback,
                &playState,
                CFRunLoopGetCurrent(),
                kCFRunLoopCommonModes,
                0,
                &playState.queue);
 
        if(status == 0)
        {
            playState.playing = true;
            for(int i = 0; i < NUM_BUFFERS && playState.playing; i++)
            {
                if(playState.playing)
                {
                    AudioQueueAllocateBuffer(playState.queue, 16000, &playState.buffers[i]);
                    AudioOutputCallback(&playState, playState.queue, playState.buffers[i]);
                }
            }
 
            if(playState.playing)
            {
                status = AudioQueueStart(playState.queue, NULL);
                if(status == 0)
                {
                    labelStatus.text = @"Playing";
                }
            }
        }        
    }
 
    if(status != 0)
    {
        labelStatus.text = @"Play failed";
    }
}

We set up for playing audio by setting the currentPacket variable of the PlayState struct to 0 and using the setupAudioFormat method to initialize the dataFormat. Next we use the AudioFileOpenURL function to open the audio file we are playing. In the case of this example we are always playing and recording the same file.

If the file is opened successfully we call the AudioQueueNewOutput function to open a new audio output queue.

AudioQueueNewOuput arguments:

  1. A pointer to the dataFormat variable of our PlayState struct.
  2. The audio callback function that will be described later in the article.
  3. A pointer to our PlayState struct.
  4. The CFRunLoopGetCurrent function causes the callback to be called from the main application thread.
  5. The run loop mode to use.
  6. No flags are set for our call.
  7. A pointer to our audio queue that will be initialized by this call.

Upon successfully opening the output queue we allocate each of the 3 audio output buffers we will use to play audio and we pass each of them to our output callback function. The output callback function will populate the buffers and enqueue them for playback.

Once the three audio buffers are loaded with audio data to be played we call the AudioQueueStart function to start the playback process. As each buffer is played the AudioOutputCallback function will be called with the buffer.

The stopPlayback method stops audio playback and frees up any resources allocated for the playback process.

1
2
3
4
5
6
7
8
9
10
11
12
- (void)stopPlayback
{
    playState.playing = false;
 
    for(int i = 0; i < NUM_BUFFERS; i++)
    {
        AudioQueueFreeBuffer(playState.queue, playState.buffers[i]);
    }
 
    AudioQueueDispose(playState.queue, true);
    AudioFileClose(playState.audioFile);
}

The AudioOutputCallback function is called for each audio buffer that has been played. This function is responsible for loading and queuing additional audio buffers for playback. It will also stop the playback process when the complete audio file has been played.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void AudioOutputCallback(
        void* inUserData,
        AudioQueueRef outAQ,
        AudioQueueBufferRef outBuffer)
{
    PlayState* playState = (PlayState*)inUserData;	
    if(!playState->playing)
    {
        printf("Not playing, returning\n");
        return;
    }
 
    printf("Queuing buffer %d for playback\n", playState->currentPacket);
 
    AudioStreamPacketDescription* packetDescs;
 
    UInt32 bytesRead;
    UInt32 numPackets = 8000;
    OSStatus status;
    status = AudioFileReadPackets(
            playState->audioFile,
            false,
            &bytesRead,
            packetDescs,
            playState->currentPacket,
            &numPackets,
            outBuffer->mAudioData);
 
    if(numPackets)
    {
        outBuffer->mAudioDataByteSize = bytesRead;
        status = AudioQueueEnqueueBuffer(
                playState->queue,
                outBuffer,
                0,
                packetDescs);
 
        playState->currentPacket += numPackets;
    }
    else
    {
        if(playState->playing)
        {
            AudioQueueStop(playState->queue, false);
            AudioFileClose(playState->audioFile);
            playState->playing = false;
        }
    }
 
}

Our PlayState structure is passed into the callback as the inUserData void* parameter. If the playing flag of our struct is set to false we simply return from the callback. The outBuffer parameter is the buffer that has just finished playing.

We use the AudioFileReadPackets function to read the next hunk of data into the just completed outBuffer buffer. We only work with three audio buffers at any given time just like when we are recording.

AudioFileReadPackets arguments:

  1. The audio file we opened.
  2. Flag specifying whether or not to cache data.
  3. The number of bytes read from the file.
  4. A list of packet descriptions.
  5. The number of audio packets read from the file.
  6. The audio buffer’s data variable.

With the audio data read into our buffer we now set the mAudioDataByteSize variable of our buffer to equal the number of bytes read from the file. Next we enqueue the packet to be played.

If we fail to read any packets from the file we stop the play process with the AudioQueueStop function and close the file. Passing false as the second parameter to AudioQueueStop will cause the remaining buffers that are enqueued to finish playing. If we passed true then playing would stop immediately.

That’s it for the baics of audio recording and play back using the iPhone SDK. I am posting the source files for you reference.

NOTE: On beta 3 of the SDK the AudioQueueStart call is failing when trying to start recording. This was working fine in beta 2. I’ll post an update when I’ve found a solution to this issue. If anyone finds a solution please post a comment.

I’m not sure if I will continue this series of articles into more advanced audio topics or if I’ll start a new “Advanced” series of articles.

Source Files:
main.mAudioRecorderAppDelegate.hAudioRecorderAppDelegate.m

Share and Enjoy:
  • StumbleUpon
  • Digg
  • Reddit
  • del.icio.us
  • Suggest to Techmeme via Twitter
  • Technorati
  • Slashdot
  • HackerNews
  • Twitter
  • Facebook
  • Print
You can leave a response, or trackback from your own site.

37 Responses to “Exploring iPhone Audio Part 7”

  1. Babak says:

    Have you had any updates on the Audio not working any longer?

  2. angel matson says:

    When I compile your sample code I get an undeclared “fsRdPerm” from AudioRecorderAppDelegate.m

    Are you missing some code or header file???

    regards
    angel

  3. Maxime says:

    Hi!
    Thanks for your tutorial.
    Like the previous 2 comments here, the audio doesn’t seem to work. The label always says that the device is Idle, and I have to comment out the start recording function because of the “fsRdPerm” undeclared error. Any ideas?
    Thanks!
    Maxime

  4. pete says:

    Try adding the AudioToolbox.framework to your project.

  5. Maxime says:

    Hi Pete, thanks for the quick reply!
    I made sure to include the AudioToolbox framework.
    Now when we press the record button, I get “Record Failed”.
    I was able to downgrade the SDK to Beta 2, but not on the iPhone (when downgrading to Beta 2, it says it has expired and greets meet with the infamous pink screen). Hence, would the error come from using Beta 3 on the iPhone itself? Because you only mention you have noticed it doesn’t work with SDK Beta 3.
    Let me know!
    Thanks,
    Maxime

  6. pete says:

    It worked in beta 2 and stopped working after I updated to beta 3…

  7. Babak says:

    what do you mean on the iPhone, you are actually testing on the iPhone? I am still on the developer waiting list so I’m stuck on the simulator.

  8. Maxime says:

    Yes we have a developer license, otherwise there is no way to test sound recording in the simulator. Unfortunately it doesn’t anymore, and we couldn’t find a mean to restore beta 2 OS on the iPhone itself.

  9. pete says:

    I just tried the AudioRecorder app on Beta 4 of the SDK and it is working again!

  10. Maxime says:

    Hi Pete, this is good news. We tried on our side but after we managed to get both SDK and OS to beta 4 we consistently get on all demos and even your sound recording application the following crash error: EXC_BAD_ACCESS. A few people mention some signing certificate issue, but we believe that it is not the problem for us. Any idea ?

    Thanks,
    Maxime

  11. pete says:

    You’ll want to download new copies of the sample applications from the apple web site. They have been updated for the latest version of the SDK.

    My audio recorder works on the simulator. I also get the bad access error on my device. BUT, my development device is an iPod touch which doesn’t have a mic so who know if it will even run on an iPod.

  12. Maxime says:

    Interesting that it works in the simulator when it doesn’t have a mic !
    I thought you were testing it on the iPhone itself.
    What do you mean by working on the simulator? Can you actually record and playback the recording and hear your voice?
    I only get the “Record Failed” error in the simulator.
    I still haven’t figured the EXC_BAD_ACCESS error on the iPhone, but it finally looks like it is tied to the certificate issue since I get the same errors with the new demos.
    What a game, seriously!

  13. pete says:

    I’m able to record and play back audio on the simulator. It just uses the computer’s sound card.

    The apple samples work for me both in the simulator and on the iPod Touch. I had to download the new versions though.

  14. Michael says:

    Hi!

    How do I play compressed audio formats?
    I’ve just found some demo code @apple dev site, but this code freezes always the GUI and cannot return after playback…

    Any idea?

  15. Dave Hill says:

    I’m trying to play some mp3 audio clips from a packed binary file, and I’m having a hell of a time figuring out what approach is appropriate. I was looking at the streaming stuff, but before that I tried just saving my binary chunks out as files, and then playing them using something like your code above. Everything executes, but no sound is played, and no errors occur.

    I’m very frustrated. I just don’t understand why playing audio has to be so damned complicated.

  16. Babak says:

    how did you get through? I’ve been on the waiting list for months

  17. Richard says:

    Can any one comment on whether or not this works with the sdk v5? I run it but the UI never gets created even though NSLog’s added to the beginning and end of applicationDidFinishLaunching are getting fired.

  18. Jinpei Li says:

    Thank you for the example. I test these files on iPhone SDK 5, get UIButtonType error. I change it to other UIButton types and has EXC_BAD_ACCESS error on the iPhone Simulator. Does anybody figure it out?

  19. EXcell says:

    Well, I’m using sdk beta 8. I tried to modify this example to be able just to play ANY long sound on iPhone, as long as I dont have any mic I have no chance to use original example provided by Pete. Anyways, I try to play “caf” files, but it gives me just lots of noise instead of sound. Wav files shows me “Playback failed” statement, MP3s just crash iPhoneSimulator. Any ideas how to organize playback on iPhone will be really appreciated.

  20. n00b says:

    error:’AudioOutputCallback’ undeclared

    *kills self*

  21. ente says:

    Use 0×01 instead of fsRdPerm.

  22. Chris says:

    IIs the full project somewhere? I see the source files (main.m, AudioRecorderAppDelegate.h,AudioRecorderAppDelegate.m), but it still seems like something is missing when I tried it today.

    This is a GREAT help, but it be even better with a gzip/zip/dmg/whatever of the entire project. :-)

  23. Hasnat says:

    thanks
    works fine for me
    I just had to rewrite code for creating UIButtons as UIButtonTypeNavigation is not supported anymore
    use this instead for buttons
    // Create Record button
    buttonRecord = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    [buttonRecord setTitle:@"Record" forState:UIControlStateNormal];
    [buttonRecord addTarget:self action:@selector(recordPressed:)
    forControlEvents:UIControlEventTouchUpInside];
    //buttonRecord.center = CGPointMake(window.center.x, 200);
    buttonRecord.frame = CGRectMake(6.0, 120.0, 140, 35);
    buttonRecord.backgroundColor = [UIColor clearColor];
    // Create Play button
    buttonPlay = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    [buttonPlay setTitle:@"Play" forState:UIControlStateNormal];
    [buttonPlay addTarget:self action:@selector(playPressed:)
    forControlEvents:UIControlEventTouchUpInside];
    //buttonPlay.center = CGPointMake(window.center.x, 150);
    buttonPlay.frame = CGRectMake(161.0, 120.0, 140, 35);
    buttonPlay.backgroundColor = [UIColor clearColor];

  24. shubham says:

    hi, can you please share a working code for the same for 3.1 sdk?
    a full zip folder of the working code will be highly appreciated.

    also is tehre anyway we can save the sound in .amr format???

  25. Kosso says:

    Hi,

    Thanks for this top on the button codes. ;)

    I’ve managed to get the app to build now with no warnings or errors.

    I changed the printfs to NSLog outputs and also had to change fsRdPerm to 0×01 in AudioFileOpenURL.

    The app installs fine, but on launching it gets to the ‘init’ (where added an NSLog debug message) and then it just closes with no more debug info. :/ hmmm…

    I’m building against SDK 3.1.2

    Any idea what I might be doing wrong?

    I did add the AudioToolbox framework to a new ‘Window-based application’ project too.

    Any pointers would be most appreciated. Thank you. ;)

  26. Hasnat says:

    those unable to make this class to work,
    i’ev uploaded xcode project on my blog

    http://fkn1337.com/iphone-audio-recording-app-xcodepro/

    thanks

  27. Niketa says:

    i want to record live stream by copying the bytes or any other way which is feasible. can any one help to solve this.

    Thanks

  28. marc says:

    Hello,

    Your link isn’t working. I can’t even get a simle audio file to be played. The AudioQueueStart is giving problems.

    Marc

  29. Jeevan says:

    Great one, Thanks for sharing…. What Exactly I’m looking for…

    I directly jump to the last page to get the source codes and tried to implement in my program and faced 13 errors related to AudioToolBox Framework errors because I didn’t add them to my Framework… for those who’s facing trouble… make sure you have the “AudioToolbox” framework added to your project framework!…

  30. For the fsRdPerm constant you can specify one of this:

    kAudioFileReadPermission: File is read-only.
    kAudioFileWritePermission: File is write-only.
    kAudioFileReadWritePermission: File has read-write permission.

    Available in iPhone OS 2.0 and later.
    Declared in AudioFile.h.

  31. Mike says:

    Hi,

    I wanted to use your code for an iPhone application I’m writing. Are you distributing this code under a particular license?

  32. pete says:

    You’re free to use it however you want.

  33. Pi says:

    ok, This project is very easy to get running.

    you need to start with a completely blank slate. So create a window-based application, then
    1. remove both .xib files
    2. empty out ‘ main nib file base name’ ( or even kill the entire line )
    3. replace the main and application delegate header & body files with the three files listed on this posting
    4. read through the comments. You need to do the three fixes
    (1) change the code for the button generation
    (2) set the read write permission on the file ( that’s the undeclared variable )
    (3) typecast a couple of variables to (int) get rid of the remaining couple of warnings

    and you should be good to go

    this is a fabulous tutorial! Thank you so much for doing this! I wish Apple documentation was lucid like this…

    PS there is a fairly active community on irc.freenode.net#coreaudio

  34. anon says:

    thanks for summarizing the fixes. I didn’t need to do step 1 or 2, but it turned out it was important to name the project ‘AudioRecorder’.

  35. Dru Kepple says:

    This was a helpful series, thanks for this. Thanks to other commenters who have posted about fixes/updates, too.

    I have one question. Is there a way to tell when audio playback as stopped as a result of the playback having reached the end of the file? It would seem that the first if statement in AudioOutputCallback does this, and/or checking for the lack of numPackets at the end of AudioOutputCallback.

    What I was trying to do, however, was update the label with some “idle” text, and doing that in AudioOutputCallback is A) not feasible since it’s a C function and setting the label text involves objective-c, and B) just not the right place to do it, conceptually; that function is all about queueing audio packets for playback, and shouldn’t really be used for UI changes.

    I’m probably just missing something, as my C/Obj-C skills haven’t fully matured yet. Is there even a way to call Obj-C from a C function? My initial attempts at that seemed to fail.

    Thanks.

  36. Dhatchina says:

    I want to detect the mic input by using your code . how to do that?. And how to find the intensity of sound from mic inputs. could you help me please….

  37. Dhatchina says:

    I want to use your code to detect the mic . how to do that , can you help me…

Leave a Reply

*