iPhone Programming Part 6: Multiple Sounds with OpenAL
So today we want to do the hard stuff I talked about in the last post.
We will do that in three steps.
- Writing an OpenAL sound file loader.
- Writing an OpenAL wrapper class wich is more comfortable than using the OpenAL functions directly.
- Writing the actual code to play some audio files.
1. Writing an OpenAL sound file loader
Actually there is a sample code within the apples developers support files. While Apples iPhone SDK is not under NDA anymore I am able to post the code here:
/* File: MyOpenALSupport.h Abstract: OpenAL-related support functions Version: 1.3 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2008 Apple Inc. All Rights Reserved. */ #import <OpenAL/al.h> #import <OpenAL/alc.h> #import <AudioToolbox/AudioToolbox.h> #import <AudioToolbox/ExtendedAudioFile.h> typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate);
/* File: MyOpenALSupport.c Abstract: OpenAL-related support functions Version: 1.3 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2008 Apple Inc. All Rights Reserved. */ #include "MyOpenALSupport.h" ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq) { static alBufferDataStaticProcPtr proc = NULL; if (proc == NULL) { proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic"); } if (proc) proc(bid, format, data, size, freq); return; } void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) { OSStatus err = noErr; SInt64 theFileLengthInFrames = 0; AudioStreamBasicDescription theFileFormat; UInt32 thePropertySize = sizeof(theFileFormat); ExtAudioFileRef extRef = NULL; void* theData = NULL; AudioStreamBasicDescription theOutputFormat; // Open a file with ExtAudioFileOpen() err = ExtAudioFileOpenURL(inFileURL, &extRef); if(err) { printf("MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; } // Get the audio data format err = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat); if(err) { printf("MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", err); goto Exit; } if (theFileFormat.mChannelsPerFrame > 2) { printf("MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit;} // Set the client format to 16 bit signed integer (native-endian) data // Maintain the channel count and sample rate of the original source format theOutputFormat.mSampleRate = theFileFormat.mSampleRate; theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame; theOutputFormat.mFormatID = kAudioFormatLinearPCM; theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame; theOutputFormat.mFramesPerPacket = 1; theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame; theOutputFormat.mBitsPerChannel = 16; theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; // Set the desired client (output) data format err = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat); if(err) { printf("MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", err); goto Exit; } // Get the total frame count thePropertySize = sizeof(theFileLengthInFrames); err = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); if(err) { printf("MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", err); goto Exit; } // Read all the data into memory UInt32 dataSize = theFileLengthInFrames * theOutputFormat.mBytesPerFrame;; theData = malloc(dataSize); if (theData) { AudioBufferList theDataBuffer; theDataBuffer.mNumberBuffers = 1; theDataBuffer.mBuffers[0].mDataByteSize = dataSize; theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame; theDataBuffer.mBuffers[0].mData = theData; // Read the data into an AudioBufferList err = ExtAudioFileRead(extRef, (UInt32*)&theFileLengthInFrames, &theDataBuffer); if(err == noErr) { // success *outDataSize = (ALsizei)dataSize; *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; *outSampleRate = (ALsizei)theOutputFormat.mSampleRate; } else { // failure free (theData); theData = NULL; // make sure to return NULL printf("MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit; } } Exit: // Dispose the ExtAudioFileRef, it is no longer needed if (extRef) ExtAudioFileDispose(extRef); return theData; }
Writing an OpenAL wrapper class wich is more comfortable than using the OpenAL functions directly.
What you really want is a singelton pattern here, or how apple named this pattern : shared class.
So lets first take a look how you would write a singelton class in objective c. Situations could arise where you want a singleton instance (created and controlled by the class factory method) but also have the ability to create other instances as needed through allocation and initialization. In these cases, you would not override allocWithZone: and the other methods following it as shown in the following code listing :
static MySingeltonClass *sharedSingeltonInstance = nil; + (MySingeltonClass*)sharedManager { @synchronized(self) { if (sharedSingeltonInstance == nil) { [[self alloc] init]; // assignment not done here } } return sharedSingeltonInstance; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedSingeltonInstance == nil) { sharedSingeltonInstance = [super allocWithZone:zone]; return sharedSingeltonInstance; // assignment and return on first allocation } } return nil; //on subsequent allocation attempts return nil } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; //denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; }
Now thats pretty straight forward – so lets create our OpenAL wrapper class. We need some functions to initialize the device, loading and control sound files. Your interface should look like this :
// // MyOpenAL.h // #import <Foundation/Foundation.h> #import <OpenAL/al.h> #import <OpenAL/alc.h> #import <AudioToolbox/AudioToolbox.h> @interface MyOpenAL : NSObject { ALCcontext* mContext; // stores the context (the 'air') ALCdevice* mDevice; // stores the device NSMutableArray * bufferStorageArray; // stores the buffer ids from openAL NSMutableDictionary * soundDictionary; // stores our soundkeys } // if you want to access directly the buffers or our sound dictionary @property (nonatomic, retain) NSMutableArray * bufferStorageArray; @property (nonatomic, retain) NSMutableDictionary * soundDictionary; - (id) init; // init once - (bool) initOpenAL; // no need to make it public, but I post it here to show you which methods we need. initOpenAL will be called within init process once. - (void) playSoundWithKey:(NSString*)soundKey; // play a sound by name - (void) stopSoundWithKey:(NSString*)soundKey; // stop a sound by name - (bool) isPlayingSoundWithKey:(NSString *) soundKey; // check if sound is playing by name - (void) rewindSoundWithKey:(NSString *) soundKey; // rewind a sound by name so its playing again - (bool) loadSoundWithKey:(NSString *)_soundKey File:(NSString *)_file Ext:(NSString *) _ext Loop:(bool)loops; // load a sound and give it a name + (MyOpenAL*)sharedMyOpenAL; // access to our instance @end
So now the easy stuff is done we get to the more complex implementation:
// // MyOpenAL.m // #import "MyOpenAL.h" #import "MyOpenALSupport.h" @implementation MyOpenAL @synthesize bufferStorageArray; @synthesize soundDictionary; static MyOpenAL *sharedMyOpenAL = nil; - (void) shutdownMyOpenAL { @synchronized(self) { if (sharedMyOpenAL != nil) { [self dealloc]; // assignment not done here } } } + (MyOpenAL*)sharedMyOpenAL { @synchronized(self) { if (sharedMyOpenAL == nil) { [[self alloc] init]; // assignment not done here } } return sharedMyOpenAL; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (sharedMyOpenAL == nil) { sharedMyOpenAL = [super allocWithZone:zone]; return sharedMyOpenAL; // assignment and return on first allocation } } return nil; //on subsequent allocation attempts return nil } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; //denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; } // seek in audio file for the property 'size' // return the size in bytes -(UInt32)audioFileSize:(AudioFileID)fileDescriptor { MARK; UInt64 outDataSize = 0; UInt32 thePropSize = sizeof(UInt64); OSStatus result = AudioFileGetProperty(fileDescriptor, kAudioFilePropertyAudioDataByteCount, &thePropSize, &outDataSize); if(result != 0) NSLog(@"cannot find file size"); return (UInt32)outDataSize; } // start up openAL -(bool) initOpenAL { MARK; // Initialization mDevice = alcOpenDevice(NULL); // select the "preferred device" if (mDevice) { // use the device to make a context mContext=alcCreateContext(mDevice,NULL); // set my context to the currently active one alcMakeContextCurrent(mContext); return true; } return false; } -(id) init { MARK; if (self = [super init] ) { if ([self initOpenAL]) { self.bufferStorageArray = [[[NSMutableArray alloc]init]autorelease]; self.soundDictionary = [[[NSMutableDictionary alloc]init]autorelease]; } return self; } [self release]; return nil; } -(void) dealloc { MARK; // delete the sources for (NSNumber * sourceNumber in [soundDictionary allValues]) { NSUInteger sourceID = [sourceNumber unsignedIntegerValue]; alDeleteSources(1, &sourceID); } self.soundDictionary=nil; // delete the buffers for (NSNumber * bufferNumber in bufferStorageArray) { NSUInteger bufferID = [bufferNumber unsignedIntegerValue]; alDeleteBuffers(1, &bufferID); } self.bufferStorageArray=nil; // destroy the context alcDestroyContext(mContext); // close the device alcCloseDevice(mDevice); [super dealloc]; } // the main method: grab the sound ID from the library // and start the source playing - (void)playSoundWithKey:(NSString*)soundKey { MARK; NSNumber * numVal = [soundDictionary objectForKey:soundKey]; if (numVal == nil) return; NSUInteger sourceID = [numVal unsignedIntValue]; alSourcePlay(sourceID); } - (void)stopSoundWithKey:(NSString*)soundKey { MARK; NSNumber * numVal = [soundDictionary objectForKey:soundKey]; if (numVal == nil) return; NSUInteger sourceID = [numVal unsignedIntValue]; alSourceStop(sourceID); } -(void) rewindSoundWithKey:(NSString *) soundKey { MARK; NSNumber * numVal = [soundDictionary objectForKey:soundKey]; if (numVal == nil) return; NSUInteger sourceID = [numVal unsignedIntValue]; alSourceRewind (sourceID); } -(bool) isPlayingSoundWithKey:(NSString *) soundKey { MARK; NSNumber * numVal = [soundDictionary objectForKey:soundKey]; if (numVal == nil) return false; NSUInteger sourceID = [numVal unsignedIntValue]; ALenum state; alGetSourcei(sourceID, AL_SOURCE_STATE, &state); return (state == AL_PLAYING); } -(bool) loadSoundWithKey:(NSString *)_soundKey File:(NSString *)_file Ext:(NSString *) _ext Loop:(bool)loops{ START_TIMER; ALvoid * outData; ALenum error = AL_NO_ERROR; ALenum format; ALsizei size; ALsizei freq; NSBundle * bundle = [NSBundle mainBundle]; // get some audio data from a wave file CFURLRef fileURL = (CFURLRef)[[NSURL fileURLWithPath:[bundle pathForResource:_file ofType:_ext]] retain]; if (!fileURL) { END_TIMER(@"file not found."); return false; } outData = MyGetOpenALAudioData(fileURL, &size, &format, &freq); CFRelease(fileURL); if((error = alGetError()) != AL_NO_ERROR) { printf("error loading sound: %x\n", error); exit(1); } ALog(@"getting a free buffer from openAL."); NSUInteger bufferID; // grab a buffer ID from openAL alGenBuffers(1, &bufferID); ALog(@"loading audio data into openAL buffer."); // load the awaiting data blob into the openAL buffer. alBufferData(bufferID,format,outData,size,freq); // save the buffer so we can release it later [bufferStorageArray addObject:[NSNumber numberWithUnsignedInteger:bufferID]]; ALog(@"getting a free source from openAL."); NSUInteger sourceID; // grab a source ID from openAL alGenSources(1, &sourceID); ALog(@"attatching the buffer to the source and setting up preferences"); // attach the buffer to the source alSourcei(sourceID, AL_BUFFER, bufferID); // set some basic source prefs alSourcef(sourceID, AL_PITCH, 1.0f); alSourcef(sourceID, AL_GAIN, 1.0f); if (loops) alSourcei(sourceID, AL_LOOPING, AL_TRUE); // store this for future use [soundDictionary setObject:[NSNumber numberWithUnsignedInt:sourceID] forKey:_soundKey]; ALog(@"free %i bytes of temporary allocated memory.", size); // clean up the buffer if (outData) { free(outData); outData = NULL; } END_TIMER(@"Audiofile successfully loaded."); return true; } @end
You may noticed that I use some macros like “ALog” and “Mark” – you can find the definitions here.
Writing the actual code to play some audio files.
Notice that you can not load any file format. I successfully used aif and caf files – so maybe you want use them, too.
[[MyOpenAL sharedMyOpenAL] loadSoundWithKey:@"music" File:"backgroundMusic" Ext:"aif" Loop:true]; [[MyOpenAL sharedMyOpenAL] loadSoundWithKey:@"beep" File:"Sound1" Ext:"aif" Loop:false]; [[MyOpenAL sharedMyOpenAL] playSoundWithKey:@"music"]; [[MyOpenAL sharedMyOpenAL] playSoundWithKey:@"beep"];
That’s it. I hope this post was useful although I know its not going deep into the achitechture of openAL.
I like to announce my first Game in Appstore : Bomb Commander
About this entry
You’re currently reading “ iPhone Programming Part 6: Multiple Sounds with OpenAL ,” an entry on coders
- Author:
- hhamm
- Published:
- 3.2.09 / 11am
- Category:
- objective c, openAL, programming, xcode
- Feedback:


71 Comments
Jump to comment form | comments rss [?] | trackback uri [?]