The Shortcomings of OpenAL

OpenAL is great. The proceeding sentence is sarcasm. This post talks about why openAL is not in fact great, and why you don't actually want to touch it without good reason. The motivation for this is the following: as part of the new Camlorn_audio, in order to obtain features that are pretty much considered standard these days, I am preparing to generate 100+ C++ classes with a script, the equivalent in C helper functions, or the equivalent in C helper macros. I am basically writing Twisted, the C++ version;there is a concept of deferred execution. Everything must be on a background thread. Everything is very unhappy. This unhappiness is OpenAL.

Anyhow, the following headings cover the major issues. All of these issues can be solved on top of OpenAL. Given time, I could manage to work around them, but probably not satisfactorily in the long-term. Taken individually, each issue doesn't seem so bad.

Take these issues and their fixes together, however. Deal with all of them at once. A bug in one causes a bug in other part of the code, perhaps even unrelated, to experience the behavior. Imprecisions add up, as does complexity. Setting a property can become a harrowing task, involving signalling a couple other subcomponents and recomputing a bunch of very magical stuff, all while worrying about locks.

The Lie of Thread Safety

Is OpenAL threadsafe? If you say yes, you're wrong. Not completely wrong, mind you. The answer is "maybe, it depends".

Is an OpenAL operation threadsafe? If you said yes, you're right. Operations in OpenAL are atomic.

here's what I'm getting at. If your code never errors, never uses more than one thread, or never checks for errors, you're good. The first and last statement in that list are different.

In the first case-your code never errors-you are always fine. Your code is threadsafe. Congradulations on leaving the real world. Can you tell me how to join you? I'd appreciate it.

In the second, you can stop worrying about this issue. You aren't using threads; OpenAL can be the most thread-unsafe monster imaginable.

But, the third case is a problem. All operations in OpenAL are atomic. No operation in OpenAL, save for initialization, returns information on error status. Checking for errors is an operation. Your most perfect code may error, because this is how OpenAL communicates that it is out of memory. Furthermore, any library that needs to sit between you and OpenAL must protect you from errors in OpenAL. Consequently, as soon as it uses more than one thread or gets called from more than one thread, it starts breaking. The problem with a global library lock is the huge performance hit; operations such as loading sounds must also lock. This lead directly to the huge number of fine-grained locks throughout Camlorn_audio, and bugs related to it still remain.

The error stays set until someone checks it, and so it becomes quite possible for a thread to see errors from another thread (not to mention the number of bugs related to errors happening in destructors, which then get caught by the next call, but that's another issue).

You can't not use threads, save in the most trivial apps. Streaming must be done via background threads or via an explicit ticking function that gets called in the main one. The first case is what a library or reusable audio engine needs to do to be convenient. You can do the second, but disk or network I/O can easily destroy performance.

OpenAL Does not Have Callbacks

How do you get a callback? Query OpenAL's state on some sort of resolution, and see if the condition is met. This immediately puts streaming into the hard to implement category: you now must implement this query as well as your streaming. Camlorn_audio "guesses" when it thinks OpenAL will be close to running out, and then asks for the buffers that can be given back to be given back for reuse.

One thing that keeps coming up, and which cannot be done with any reliability, is the ability to tell when a sound stops. As far as I can figure, the minimum resolution that can be reasonably done without tying up a core or duplicating a bunch of state and computations that OpenAL does internally is about 5 MS, and even if the work is done the effectiveness depends on the clock's resolution. Increase this resolution and watch the battery meter go down. 5 MS is good for most applications, but not all-you can't, for example, queue up notes in a song based off it. Tell when a sound is about to stop is a more tractable problem, but this may happen far enough in advance that one ends up with "phantom" sounds that were queued for events that don't come to pass-taking one more step, for example, because you died.

Both of the above examples of why callbacks are useful are actually a special case of a more general problem: when sample N is played, tell the user of the library. This could be done on top of OpenAL, but requires calculating how long until it is likely that sample N will be played. This is further complicated by the following: sounds can be paused (current playback position stops advancing), stopped (current position goes to the beginning), or pitch bent (current position begins advancing slower or faster, at any time). The last is clearly a problem: if we don't check again until some estimate, the sound may be pitch bent upwards and play faster. Thus, estimates must be recomputed. Note that dopler counts as pitch bend to the best of my knowledge.

These callback related problems keep adding up.

OpenAL Errors are Unhelpful

There are 5 or 6 OpenAL errors. No additional context is given as to what caused it. They are assigned to more than 5 or 6 possible conditions, and extensions do not add new ones. I think I do not need to say more.

OpenAL is the Only Current Complete Option

I could list more things about OpenAL that are a pain: the rigidity of the architecture, the fact that Creative has apparently abandoned it, the fact that the API is geared for hardware manufacturers in a lot of ways, etc. I will not. Instead, I will close with this: in terms of not paying multiple hundreds or thousands of dollars, OpenAL is currently the only complete option for HRTF. Microsoft killed DirectX's audio hardware accelleration, and I am aware of no other free or open source option that can do HRTF; this means that 3d audio is only really available with surround sound or surround sound emulation hahrdware.

So, it's bad, but it's the only thing we have without pulling a bunch of stuff together. I am currently investingating pulling a bunch of stuff together, but may be forced to continue on it. If this is the case, all I can say is-hello, thousands of lines of code generated from templates, and forcing things to all happen on a background thread.