Using the OpenAL library in Xcode for iOS.

Wed 26 November 2014
By Bram

The last two days I have been porting The Little Plane That Could to iOS. It was already running on OculusRift(Windows), Linux, Android and Mac OSX. When porting it, I struggled with the poor OpenAL implementation that comes with the iOS SDK.

I knew that there was a very low limit of 32 sound sources for iOS. I figured that this limit only applied to sound sources that were in 'AL_PLAYING' state. And sure enough: you can create much more than 32 sources, without getting errors back from the OpenAL implementation.

In my game, I have 48 planes flying around, each with engine sounds. If we forget about gun sounds and explosion sounds for a while, these 48 engine sounds already exceed what the iOS implementation of OpenAL can handle. So this is why I adopted a method of only playing the four closest engine sounds. If an engine sound was further away, I would pause the playing of this sound. This worked well on all platforms, except iOS.

The problem I experienced was the following: alGetError() would start returning a non-sensical value of -1. And the sounds would no longer get started. The return value of -1 for alGetError() violates the OpenAL specification standard though. If you study the header files of the SDK, you see that the only acceptable return values are 0 (AL_NO_ERROR) or:

/**
 * Invalid Name paramater passed to AL call.
 */
#define AL_INVALID_NAME                           0xA001
/**
 * Invalid parameter passed to AL call.
 */
#define AL_INVALID_ENUM                           0xA002
/**
 * Invalid enum parameter value.
 */
#define AL_INVALID_VALUE                          0xA003
/**
 * Illegal call.
 */
#define AL_INVALID_OPERATION                      0xA004
/**
 * No mojo.
 */
#define AL_OUT_OF_MEMORY                          0xA005

After struggling to find the cause of the problem for half a day, I finally solved this issue. It turns out that the 32 source limit applies to BOTH playing and paused sources. Only sources that have not started playing yet (AL_INITIAL) or have been stopped (AL_STOPPED) do not count against the limit. So the fix was relatively easy: sources that are not nearby should not be paused. They should be stopped instead. This does not take away the fact that the alGetError return value is not according to spec. Apple should fix this.