[openal] Device removing, moving, and invalidation

Chris Robinson chris.kcat at gmail.com
Sun Mar 20 20:08:33 EDT 2016


Hello.

I'm looking to get some feedback and advice for an extension to handle 
the aforementioned device events. The playback device being removed, 
playback being moved to a different device than the one that was 
originally opened, and the playback device being invalidated.

Currently, OpenAL has the ALC_EXT_disconnect extension that defines the 
behavior, and a query, for a device being removed/disconnected. I 
remember discussion had touched upon the idea of automatically switching 
to another device before being marked as disconnected, but at the time 
it was decided to let it be implementation-defined about how to handle 
devices being lost, with the disconnect state simply being the point 
when playback/capture was unrecoverable.

Moving devices in a more general capacity has some conceptual 
difficulties that I'd like to tackle, but I'm looking for ideas. The 
problems as I see them are:

1) The device specifier can change. It's one thing if OpenAL reports a 
device as simply "PulseAudio" or something, in which case it can more 
freely move between other PulseAudio devices and keep the same name. 
However, if it reports the real name, like "Built-in Audio Analog 
Surround 5.1", it couldn't then decide to switch to "GK107 HDMI Audio 
Controller Digital Stereo (HDMI)" without some issues. In particular, 
the pointer returned by previous string queries for the device's 
ALC_ALL_DEVICES_SPECIFIER may no longer be valid if the name string was 
allocated dynamically, or the supposedly const string would change even 
if the pointer remained the same. So there needs to be some rules for 
when (or if?) the device specifier can change that won't break existing 
apps. Would querying a device's ALC_ALL_DEVICES_SPECIFIER be a safe 
place to change the reported name, invalidating old pointers, similar to 
changing the enumerated list? I'm not sure.

2) The playback format may change. Not as big of a deal since the app 
doesn't deal with the channel configuration or bit-depth in the first 
place, but it can have an effect on the sample rate, refresh rate, and 
HRTF status, which I think the app should be able to assume will be 
relatively constant between resets (either via alcCreateContext or 
alcResetDeviceSOFT).

3) Some moves may be reactive, and others proactive. For instance, if 
the app opens the default AL device with alcOpenDevice(NULL), and the 
user then plugs in another system device that takes over as (or 
otherwise changes) the default, the app could receive a message that the 
default device was changed and could decide to move the AL device over 
to it on their own time. In another example, a user could use an 
external app to force OpenAL output to play on a different system 
device, in which case the AL device is moved and the app merely gets a 
notification after the fact. Should requested moves for default devices 
automatically happen, or should the app receive some kind of change 
request for ones that aren't forced?

In the same vein, on some systems it's possible for the system device to 
be invalidated such that OpenAL can't keep sending audio to it as-is, 
and needs to reset it to continue. For example I have a report that 
switching a video device to fullscreen on Windows causes an HDMI audio 
device to either temporarily unplug or reconfigure. OpenAL Soft sees 
this error and reports that device was disconnected because it's not 
accepting audio anymore. Currently, the disconnect extension specifies 
that once a device is disconnected, it can't become connected again and 
the app needs to close it down and open a device again. Transparently 
resetting the device, before marking it disconnected, could be difficult 
since it could be changing app-visible attributes.


My thinking is we could add a few new functions and queries.

ALCboolean alcReopenDevice(ALCdevice *device, const ALCchar *name);

This would allow moving a pre-existing device to another named device. 
The ALCdevice handle itself remains valid, as does all contexts and 
other AL objects and state. It's simply moving output over to the new 
device, and may cause the device specifier, playback format/metrics, etc 
to change. This way also allows OpenAL to cleanly fail if it can't be 
moved (e.g. a hardware implementation may allow moving between outputs 
on the same device, but moving may fail when switching between devices 
or drivers that don't share state).

For trying to reconnect a lost device, we could have

ALCboolean alcResetDevice(ALCdevice *device, const ALCint *attribs);

OpenAL Soft basically has this already (alcResetDeviceSOFT), but this 
additional behavior would allow a disconnected device to attempt to 
reconnect to the output it was using. It may also specify attributes 
like you'd specify for context creation, updating things like the 
playback frequency, max source sends, etc.

In addition, there could be integer queries that return a queue of 
events that have happened, either on a specific device or the audio 
system in general. So calling alcGetIntegerv with

ALC_STATUS_UPDATE_SIZE

would return the number of status updates queued, which can be anywhere 
between 0 and whatever. With

ALC_STATUS_UPDATE

it would pop off and return the given number of queued items, which you 
can then respond to. So for example

ALCint count;
alcGetIntegerv(device, ALC_STATUS_UPDATE_SIZE, 1, &count);
for(int i = 0;i < count;i++)
{
     ALCint status;
     alcGetIntegerv(device, ALC_STATUS_UPDATE, 1, &status);
     if(status == ALC_DISCONNECTED)
     {
         // Disconnected (ALC_CONNECTED would be false too), try to
         // reconnect
         if(!alcResetDevice(device, NULL))
         {
             // Reconnect failed, try to switch to the default
             alcReopenDevice(device, NULL);
         }
     }
     else if(status == ALC_OUTPUT_MOVED)
     {
         // We've been moved to a different output.
         const ALCchar *newname = alcGetString(status,
             ALC_ALL_DEVICES_SPECIFIER
         );
         ...
     }
}

or alternatively, you can get them all at once:

ALCint status[count];
alcGetIntegerv(device, ALC_STATUS_UPDATE, count, status);

But this idea could be expanded upon in the future, like to add status 
updates for device suspension/resume (which don't exactly disconnect the 
device, but do stop updates for a time).


So I'm curious about your thoughts. Do you see any problems with these 
ideas, do you have alternative ideas, or ways to improve them? Any and 
all feedback is appreciated. Thanks!


More information about the openal mailing list