Author Archive for jeff

Playing Audio Files using the iPhone SDK

Playing sound is a big part of any rich media application and it's very likely you'll need to do it in your future iPhone apps. Before version 2.2 of the SDK this was somewhat difficult -- you needed to either roll your own player or use AudioQueueServices. To use the latter you have to create a bunch of C-style structures and callbacks to feed the data manually byte by byte. It's a very programming intensive process while all you want to do is just load and play a sound!

To remedy this problem Apple introduced a new Framework: AVFoundation and a new audio player: AVAudioPlayer. It has the simplicity you desire but the features you need in order to play your audio files: play, stop, seek, and pause. You can also choose to register a delegate to get certain events like errors in playback, notification when playback ends, and interruption events for when phone calls occur. Pretty cool stuff.

I've written up an example application which is checked into SVN at http://code.9mmedia.com/svn/public/iphone/audio-example/ but here are the key points:

//Make sure you're building for 2.2 and you are also including AVFoundation.framework
 
#import <AVFoundation/AVFoundation.h>
AVAudioPlayer* player;
...
/* Both these actions are hooked up to buttons in IB */
- (IBAction)startPlayback:(UIButton *)sender {
    if(!player){
        /*
         * Here we grab our path to our resource
         */
        NSString* resourcePath = [[NSBundle mainBundle] resourcePath];
        resourcePath = [resourcePath stringByAppendingString:@"/grabbag.m4a"];
        NSLog(@"Path to play: %@", resourcePath);
        NSError* err;
 
        //Initialize our player pointing to the path to our resource
        player = [[AVAudioPlayer alloc] initWithContentsOfURL:
                            [NSURL fileURLWithPath:resourcePath] error:&err];
 
        if( err ){
            //bail!
            NSLog(@"Failed with reason: %@", [err localizedDescription]);
        }
        else{
            //set our delegate and begin playback
            player.delegate = self;
            [player play];
        }
    }
}
- (IBAction)pausePlayback:(UIButton*)sender {
    NSLog(@"Player paused at time: %f", player.currentTime);
    [player pause];
}

That's all you need to do to play a sound... create a file URL, init, and play! Pausing is done by a simple method call, and you can monitor the location in the song by checking the currentTime property of the object.

Make sure to check out the project to see the delegate methods implemented, and the implementation of the code above! Also make sure to check the iPhone documentation for these classes as well:
  • AVAudioPlayer Documentation (apple.com)
  • AVAudioPlayerDelegate Documentation (apple.com)

23 Comments

iPhone OS, NSThread, and You

Hello 9MM blog readers! This is the first post of many about the iPhone development happenings here. Look for more in the future!

Many new iPhone developers are coming from backgrounds in higher level languages (web, javascript, actionscript, etc), where you don't have to think about things like garbage collection or concurrent processes. You can get along programming for the iPhone without ever touching a thread, but to optimize performance on almost any application it is a must. Threading can be a little tricky so I have put together a simple example to help break the ice.

Before we get down to code, it’s worth defining what a thread does: A thread is a way to break up your code into pieces that can run concurrently.

Threads are most commonly used on the iPhone to maintain UI responsiveness. For example, if you wanted to load a large file into memory while keeping a UI animation smooth, you'd put the loading in its own thread to keep the processor rendering frames. The same concept would apply for a background downloader or a chat application so you can keep socket processes separate from the UI.

So, time for some code. The example is an image loader that loads multiple images into memory while one image is fading in inside a ViewController:

The header:

@interface ImageLoadingExampleViewController : UIViewController {
	NSMutableArray* imagesToLoad; //Strings that contain filenames
	NSMutableArray* loadedImages; //The loaded UIImages
	CALayer* fadeView; //Where we're putting our alpha'd image
	int imagesLoaded;
	int totalImages;
}
-(void)startFade;
 
//selector we're going to use for our thread entry point
-(void)loadImageAndAddToArray:(NSString*)fileName;
 
//callback
-(void)loadComplete;

The .m:

//we'll load each image a handful of times to make the load take longer
NSMutableArray* loadedImages; //declared outside of scope so it can be shared
@implementation ImageLoadingExampleViewController
 
/*This is the thread selector. Images will be opened here*/
-(void)loadImageAndAddToArray:(NSString*)fileName {
 
    /*You need this for all threads you create or you will leak! */
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 
    //Load the image
    UIImage* image = [UIImage imageWithData:data];
    [data release];
 
    [loadedImages addObject:image];
 
    //Tell our callback what we've done
    [self performSelectorOnMainThread:@selector(loadComplete) withObject:fileName waitUntilDone:NO];
 
    //remove our pool and free the memory collected by it
    [pool release];
}
 
- (void)loadComplete:(NSString*)file {
    /* Callback for when the image load is completed.
     * Good for working on the image after it's loaded like
     * putting it in the view, etc.
     */ 
    NSLog(@"loaded %@", fileName);
}
 
- (void)loadView {
    ...
    /* Here we set up the background view to fade in. 
     * It's in the source if you'd like to see it */
 
    loadedImages = [[NSMutableArray alloc] init];
 
    //Start loading images (10 times to slow it down)
    //imagesToLoad is a string array of jpg files
 
    for( NSString* fn in imagesToLoad ){
        int i;
        for( i = 0; i &lt; TIMES_TO_LOAD_IMAGE; i++ ){
            //Start our threads -- this class method creates a new NSThread object to execute
            //the selector of our choice.
            [NSThread detachNewThreadSelector:@selector(loadImageAndAddToArray:) toTarget:self withObject:fn];
        }
    }
}

When using NSThread, there are a few things you should keep in mind:

    • In the application's main thread a NSAutoreleasePool is allocated by default(look at main.c in xcode). When you spawn a selector on its own thread, you need to allocate one or you will be leaking memory that is supposed to go into the pool. If you are getting lots of messages in the console about memory leaks, it's likely because you forgot to do this.

    • Shared variables are very common in multithreaded applications. If you need to reference these outside of a given class you can declare them in a separate header or just outside the class declaration.

    • With shared variables comes responsibility -- if you are operating on a shared resource it becomes a critical section, which means that you need to set up safeguards to make sure that the variable doesn’t get modified at the same time by another process. This can and does happen.

So there you have it. I have also checked the complete project into the public svn at: http://code.9mmedia.com/svn/public/iphone/thread-example/. Check it out if you want to build it and see it in action on your iPhone. In addition to my little tutorial here, it's worthwhile to check out Apple's introduction to threading here:

ADC: Threading Programming Guide

Thanks for reading and happy threading!

Comments