Rss

alutLoadWAVFile(). Or better yet, don’t.

My last iDevBlogADay entry was about the second edition of the Prags’ iOS development book, so this time, I want to shine some light on my other current writing project, the long-in-coming Core Audio book. Last month, I mentioned that we’d shipped an update with three new chapters. A lot of the focus is and should be on the Audio Units chapters, but I want to talk about OpenAL.

Core AudioIf you go looking for OpenAL examples on the web — like this one or this other one — chances are high that the sample code will include a call to alutLoadWAVFile().

This function, in fact all of alut.h was deprecated in 2005. But people are still using it in tutorials. In iDevBlogADay posts. On iOS. Which never shipped alut.h.

Yes, you can get the source and compile it yourself. There are even fossilized docs for it. But, really, please don’t.

Let’s get to the question of why it was deprecated in the first place. Adam D. Moss of gimp.org, writing back in 2005, please take it away:

OpenAL is an audio renderer and just doesn’t have any business doing file IO and decoding arbitrary file formats, which are well-covered by other specialised libraries.

As sick as everyone probably is of the GL comparisons, OpenAL loading WAV files is like OpenGL loading XPMs. A utility layer on top of AL is a useful thing, but most of the reasons that ever justified GLUT’s existance don’t apply to ALUT or are trivially implementable by the app, with or without
third-party code.

In the book, we didn’t want to rely on something that wasn’t part of iOS or Mac OS X, or on a file format that we otherwise have no use for (we’d much rather you bundle your app’s audio in .caf files). And as it turns out, Core Audio offers a much better way to load audio into your application.

In our example, we load a file into a memory, and then animate its location in the 3D coordinate space to create the illusion of the sound “orbiting” the listener. To keep track of state, we use a struct:



#pragma mark user-data struct
typedef struct MyLoopPlayer {
	AudioStreamBasicDescription	dataFormat;
	UInt16				*sampleBuffer;
	UInt32				bufferSizeBytes;
	ALuint				sources[1];
} MyLoopPlayer;

This struct describes the audio format, has a buffer and size to hold the samples, and has the OpenAL source that we animate.

The function below loads an arbitrary file, LOOP_PATH_STR, into the struct. It’s a long listing; summary follows:


// note: CheckError() is defined earlier in the book. Just tests
// OSStatus==nil, and fails with a useful printf() if not

OSStatus loadLoopIntoBuffer(MyLoopPlayer* player) {
	CFURLRef loopFileURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, 
						LOOP_PATH,
						kCFURLPOSIXPathStyle,
						false);
	
	// describe the client format - AL needs mono
	memset(&player->dataFormat, 0, sizeof(player->dataFormat));
	player->dataFormat.mFormatID = kAudioFormatLinearPCM;
	player->dataFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger |
				kAudioFormatFlagIsPacked;
	player->dataFormat.mSampleRate = 44100.0;
	player->dataFormat.mChannelsPerFrame = 1;
	player->dataFormat.mFramesPerPacket = 1;
	player->dataFormat.mBitsPerChannel = 16;
	player->dataFormat.mBytesPerFrame = 2;
	player->dataFormat.mBytesPerPacket = 2;
	
	ExtAudioFileRef extAudioFile;
	CheckError (ExtAudioFileOpenURL(loopFileURL, &extAudioFile),
				"Couldn't open ExtAudioFile for reading");
	
	// tell extAudioFile about our format
	CheckError(ExtAudioFileSetProperty(extAudioFile,
				kExtAudioFileProperty_ClientDataFormat,
				sizeof (AudioStreamBasicDescription),
				&player->dataFormat),
			   "Couldn't set client format on ExtAudioFile");
	
	// figure out how big a buffer we need
	SInt64 fileLengthFrames;
	UInt32 propSize = sizeof (fileLengthFrames);
	ExtAudioFileGetProperty(extAudioFile,
			kExtAudioFileProperty_FileLengthFrames,
			&propSize,
			&fileLengthFrames);
	
	printf ("plan on reading %lld framesn", fileLengthFrames);
	player->bufferSizeBytes = fileLengthFrames *
		player->dataFormat.mBytesPerFrame;
	
	AudioBufferList *buffers;
	UInt32 ablSize = offsetof(AudioBufferList, mBuffers[0]) +
		(sizeof(AudioBuffer) * 1); // 1 channel
	buffers = malloc (ablSize);
	
	// allocate sample buffer
	player->sampleBuffer =  malloc(sizeof(UInt16) *
		player->bufferSizeBytes); // 4/18/11 - fix 1
	
	buffers->mNumberBuffers = 1;
	buffers->mBuffers[0].mNumberChannels = 1;
	buffers->mBuffers[0].mDataByteSize = player->bufferSizeBytes;
	buffers->mBuffers[0].mData = player->sampleBuffer;
	
	printf ("created AudioBufferListn");
	
	// loop reading into the ABL until buffer is full
	UInt32 totalFramesRead = 0;
	do {
		UInt32 framesRead = fileLengthFrames - totalFramesRead;
		buffers->mBuffers[0].mData = player->sampleBuffer +
			(totalFramesRead * (sizeof(UInt16)));
		CheckError(ExtAudioFileRead(extAudioFile, 
					&framesRead,
					buffers),
				   "ExtAudioFileRead failed");
		totalFramesRead += framesRead;
		printf ("read %d framesn", framesRead);
	} while (totalFramesRead < fileLengthFrames);

	// can free the ABL; still have samples in sampleBuffer
	free(buffers);
	return noErr;
}

The essential technique here is that we are using Extended Audio File Services to read from a source audio file. That gets more important in a minute. For now, we have the following essential steps:

  1. Define a mono PCM format compatible with OpenAL, and set this as the client format for an ExtAudioFile. This is the format that will be delivered to us.
  2. Calculate how big a buffer we need. The property kExtAudioFileProperty_FileLengthFrames gives us a frame count, and in PCM, being constant bitrate, we can calculate the buffer size as channel-count * bytes-per-frame * frame-count.
  3. Create the data buffer, build an AudioBufferList structure around it
  4. Read from the ExtAudioFile, into the AudioBufferList, until we reach end-of-file.

When the function returns, we have data in a format suitable for sending over to OpenAL via the alBufferData call:


alBufferData(*buffers,
		AL_FORMAT_MONO16,
		player.sampleBuffer,
		player.bufferSizeBytes,
		player.dataFormat.mSampleRate);

Now, I mentioned that it was important that we used ExtAudioFile, and here's why: it combines file I/O and audio format conversion into one call. So, whereas alutLoadWAVFile can only work with PCM audio in WAV containers, this code works with anything that Core Audio can open: MP3, AAC, ALAC, etc.

In fact, in the second example in the chapter, we switch from looping a buffer to calling the OpenAL streaming APIs. So if we combine our orbit:

with one of our editor's favorite and appropriately-named songs, loaded from an .m4a, we get this:

So there you have it: don't recompile a neglected and deprecated ALUT library for the sake of alutLoadWAVFile(), when you can use Core Audio on iOS/OSX to open any supported container/codec combination. More powerful, less skeevy... should be an easy choice.

One more thing... people have reported having problems getting the Core Audio sample code from Safari Online Books. I can see it when I'm logged in, but apparently I might be the only one. Until this problem is fixed, I'm making the book's sample code available on my Dropbox: core-audio-examples-04-30-2011.zip. Hope that helps.

Leave a Reply

Your email address will not be published. Required fields are marked *