Defective Core Audio (Mac OS X) ALC_ENUMERATION_EXT implementation / My fixed implementation and letter to the community

openal_c_png

(The following is an open letter I posted to the OpenAL community asking for support to convince Apple this problem is indeed a bug in their implementation and to accept my patch.)



The Bug:

There is a bug in the current Mac OS X implementation concerning the ALC_ENUMERATION_EXT.

The current implementation fails to do two things:

1) It fails to return a list of devices as intended by the ALC_ENUMERATION_EXT, even though the implementation claims to support the extension.

2) It fails to support opening of specific devices that would be named in this list.


As my Xmas gift to the community, I have implemented these deficiencies and made my changes publicly available. My wish is that these will be included into the main Mac OS X distribution as I really don't wish to maintain a fork for the long haul.


The current behavior of Apple's OpenAL is to claim the implementation supports the ALC_ENUMERATION_EXT, i.e. 

alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") returns true, 

but the "list" that gets returned using 

alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);

and 

alcGetString(NULL, ALC_DEVICE_SPECIFIER);

only contains a single item, which is the default device. The list will not return any other devices. So for example, Macs come with both a microphone and a line-in for capture devices. But the list will not contain both items. It will only contain the device that is the system default (as selected in System Preferences).


I originally submitted this bug to Apple, but the bug was immediately closed claiming that this is the proper behavior and we shouldn't be able to select non-default devices.

I am usually deferential to these kinds of decisions, but this is absolutely wrong in my opinion. This violates the intent of having the ALC_ENUMERATION_EXT and may ultimately create a poor user experience for users.


The Justification:

First, OpenAL already has ALC_DEFAULT_DEVICE_SPECIFIER and ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER so it is always possible to get the default device without the enumeration extension. This makes Apple's current implementation of the enumeration extension completely redundant and pointless.


Second, as far as user experience goes, this limitation can pose a serious problem for users in specific circumstances. This is my own situation:

I am working on an application that analyzes tones from a sound generator. I basically record a sound and then do some processing on it. I may playback the sound as it comes in so the user can tell what's going on.

The cleanest sound samples will obviously come directly from the line-in attached directly to the sound generator. This requires the sound generator to have a connection that supports line-in.

As a fallback though, I would also like to support microphone input. However, there are considerable differences between using a microphone and line-in as everybody is aware of. Due to noise, it may produce terrible results so the user will want to change this.

My problem is that most people have microphone as the system's default input device (via System Preferences). Since the current Apple OpenAL implementation will not let me list all devices, nor will it let me choose a specific device, I must require the user to go to System Preferences to change the default, and possibly refresh (or worse, restart) my app so I can see the change.


This is very annoying for a user. This is even more annoying if they frequently use other applications that rely on a microphone such as iChat or Skype. So not only must the user change the system default for my app before they run, they must also remember to go back to System Preferences after they run my app and change back the default so iChat/Skype continue to work. But a lot of users won't remember to do this, and when they go into iChat/Skype, they will be puzzled why their microphone seems to be broken. If they finally do realize the problem, they will be angry at my application for making them change the default in the first place. They will not want to run my application because it is too much of a pain to setup and cleanup afterwards.

My application is a very niche application. I have no justification for requiring the user to change their system default device just for my application. The user should be able to pick the device they use the most as the system default, but still be able to run my application and change the devices within my application if they deem necessary without messing up their system defaults.


This is why properly supporting the enumeration extension is important. I understand perfectly about respecting user defaults and I do so. But it is sometimes necessary to allow users to change from the defaults in a per-application (non-system-wide) context as I just described.


Apple need not fear about developers ignoring system defaults. The way the OpenAL implementation and API currently works, it is much easier to respect the defaults than to not. Only the apps that have hit the usability problem I described are going to be inclined to do the extra work involved to incorporate enumeration. There won't be a wave of poorly designed apps suddenly hitting the market just because this extension was properly implemented.



The Fixed Implementation:

I have placed my changes into a Git repository which can be found at:

http://www.assembla.com/spaces/OpenALCoreAudioDeviceEnumeration


Although I only cared about the Capture devices, I implemented both Capture and Output fixes to improve the chances of this being accepted.


I have fixed the following:

- Enumeration lists return all devices, not just the default devices for both capture and output devices,

e.g. list_of_devices = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);


- Fixed specific device queries to return the actual device name associated with the passed in device rather than the default device name,

e.g. device_name = alcGetString(captureDevice, ALC_CAPTURE_DEVICE_SPECIFIER);


- Added support to alcOpenDevice and alcCaptureOpenDevice to allow device names to be passed in instead of NULL as a parameter and the device name is not ignored.


My changes preserve compatibility with the current implementation. In the enumeration lists, I always make sure to place the default device as the first item in the list so any code that unwisely made assumptions about the default device being in the first part of the string will continue to work. I also double-null-terminate individual/specific device queries since that is what the current implementation does even though the 1.1 spec says nothing about this. And of course you still may pass NULL to alc*OpenDevice, and it will behave exactly as before.


Yes, there were a few design decisions. Probably the most significant is the use of Device Names as opposed to Device UID strings. The justification is pretty clear for this though. The current implementation already returns Device Names and not Device UIDs when querying for the device, the default device, or the current broken enumeration list. So for consistency and compatibility, this must continue. But in addition, UIDs aren't very helpful to humans in most cases as the strings are unfriendly (and possibly non-localized).


There is a corner case due to the above design decision if there are multiple devices of the same type (input or output) by the same exact name. I don't know if OS X tries to make sound device names unique like Bonjour (by adding numbers at the end). If not, the code won't be able to distinguish between devices. (In this case though I would suggest device naming really should work like Bonjour. Zeroconf was extremely well thought out.) But even with this corner case, this is still a huge improvement over the current broken implementation.


(By the way, this might also suggest a need for another OpenAL extension. This suggests there is a distinction between a unique identifier and a human-readable localized device description.)


The changed files are:

oalCaptureDevice.cpp
oalCaptureDevice.h
oalDevice.cpp
oalDevice.h
oalImp.cpp
oalImp.h
oalOSX.h


I've tested on an iMac with built-in microphone, built-in line-in, built-in speaker, and an iMic just so I would have a non-Apple and secondary output device to select.


How you can help:

So for the OpenAL community at large, if you agree with me that the current OS X implementation is defective in this regard, I request your support. Please (politely) let Apple know you would like to see my patch integrated into the main distribution. (I've met the OpenAL/Apple engineers in person before and they are kind people and don't wish to offend them.) 

Please file a bug report on the Apple Radar (bugreport.apple.com) and vote for Bug ID: 6469095 (new submission) and Bug ID:6455088 (the original closed one).


Meanwhile, anybody who needs this fix is welcome to use it and embed the modified OpenAL framework directly in their app bundle.


Thanks,
Eric


Copyright © PlayControl Software, LLC / Eric Wing