RSS

Exploring iPhone Audio Part 7

April 15th, 2008 Posted in Software Development, iPhone

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.m - AudioRecorderAppDelegate.h - AudioRecorderAppDelegate.m

[del.icio.us] [Digg] [Reddit] [Technorati]

RSS feed | Trackback URI

18 Comments »

Comment by Babak
2008-04-21 07:40:56

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

 
Comment by angel matson
2008-04-21 17:06:10

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

Are you missing some code or header file???

regards
angel

 
Comment by Maxime
2008-04-22 09:14:05

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

Comment by pete
2008-04-22 09:24:51

Try adding the AudioToolbox.framework to your project.

 
 
Comment by Maxime
2008-04-22 13:58:16

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

Comment by pete
2008-04-22 14:33:59

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

 
Comment by Babak
2008-04-23 06:40:58

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.

Comment by Maxime
2008-04-23 08:11:20

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.

Comment by Babak
2008-04-27 18:35:32

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

 
 
 
 
Comment by pete
2008-04-24 10:30:56

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

 
Comment by Maxime
2008-04-24 11:55:37

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

Comment by pete
2008-04-24 13:20:34

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.

Comment by Maxime
2008-04-24 15:16:07

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!

 
 
 
Comment by pete
2008-04-24 15:26:18

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.

 
Comment by Michael
2008-04-25 02:41:36

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?

 
Comment by Dave Hill Subscribed to comments via email
2008-04-26 09:18:18

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.

 
Comment by Richard Subscribed to comments via email
2008-05-20 23:40:13

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.

 
Comment by Jinpei Li Subscribed to comments via email
2008-05-26 18:00:24

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?

 
Name (required)
E-mail (required - never shown publicly)
URI
Subscribe to comments via email
Your Comment (smaller size | larger size)
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.