Skip to content

Commit

Permalink
Try to recover from AUDCLNT_E_DEVICE_INVALIDATED errors in wasapi bac…
Browse files Browse the repository at this point in the history
…kend.

Instead of disconnecting the device and passing the error to the aplication, try to recover from the error in the mixing loop.

The error is triggered on some configurations when switching to fullscreen mode, or with some usb headsets. And it can take a few seconds to recover.
It also occurs when changing the sound card default sample rate in the windows configuration dialog, or other properties.
  • Loading branch information
xbx committed Oct 30, 2018
1 parent f1ced2e commit dd44357
Showing 1 changed file with 111 additions and 36 deletions.
147 changes: 111 additions & 36 deletions Alc/backends/wasapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ typedef struct ALCwasapiPlayback {
} ALCwasapiPlayback;

static int ALCwasapiPlayback_mixerProc(void *arg);
static HRESULT ALCwasapiPlayback_resetProxy_forceFlags(ALCwasapiPlayback *self, ALuint forceRequestFlags);

static void ALCwasapiPlayback_Construct(ALCwasapiPlayback *self, ALCdevice *device);
static void ALCwasapiPlayback_Destruct(ALCwasapiPlayback *self);
Expand Down Expand Up @@ -617,6 +618,50 @@ static void ALCwasapiPlayback_Destruct(ALCwasapiPlayback *self)
ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
}

static HRESULT ALCwasapiPlayback_TryRecover(ALCwasapiPlayback* self)
{
ALuint recovery_attempts;
unsigned long recovery_sleep;
HRESULT hr;

WARN("AUDCLNT_E_DEVICE_INVALIDATED in mixer proc.\n");

/* attempt recovery as per https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
* (only works when device is reconfigured, not when it is disconnected.)
*/
if (self->render)
IAudioRenderClient_Release(self->render);
self->render = NULL;
ATOMIC_STORE(&self->Padding, 0, almemory_order_relaxed);

for (recovery_attempts = 0, recovery_sleep = 1; recovery_attempts < 6; recovery_attempts++, recovery_sleep *= 2) // try for 6.3 seconds before reporting disconnection
{
TRACE("Attempting playback recovery...\n");
al_nssleep(recovery_sleep * 100000000); // *100ms
if (ATOMIC_LOAD(&self->killNow, almemory_order_relaxed))
return NOERROR; // return to ALCwasapiPlayback_mixerProc() and exit the thread.
hr = ALCwasapiPlayback_resetProxy_forceFlags(self, DEVICE_CHANNELS_REQUEST|DEVICE_FREQUENCY_REQUEST);
if (SUCCEEDED(hr)) {
hr = IAudioClient_Start(self->client);
if (FAILED(hr)) {
ERR("Failed to restart audio client: 0x%08lx\n", hr);
}
else {
ResetEvent(self->NotifyEvent);
void *ptr;
hr = IAudioClient_GetService(self->client, &IID_IAudioRenderClient, &ptr);
if (SUCCEEDED(hr)) {
self->render = ptr;
TRACE("playback recovery succedeed.\n");
break;
}
}
}
else if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT)
break; // no need to try again, the format has changed
}
return hr;
}

FORCE_ALIGN static int ALCwasapiPlayback_mixerProc(void *arg)
{
Expand Down Expand Up @@ -645,7 +690,16 @@ FORCE_ALIGN static int ALCwasapiPlayback_mixerProc(void *arg)
while(!ATOMIC_LOAD(&self->killNow, almemory_order_relaxed))
{
hr = IAudioClient_GetCurrentPadding(self->client, &written);
if(FAILED(hr))
if(hr == AUDCLNT_E_DEVICE_INVALIDATED)
{
hr = ALCwasapiPlayback_TryRecover(self);
if (SUCCEEDED(hr)) {
update_size = device->UpdateSize;
buffer_len = update_size * device->NumUpdates;
continue;
}
}
if (FAILED(hr))
{
ERR("Failed to get padding: 0x%08lx\n", hr);
V0(device->Backend,lock)();
Expand All @@ -667,6 +721,15 @@ FORCE_ALIGN static int ALCwasapiPlayback_mixerProc(void *arg)
len -= len%update_size;

hr = IAudioRenderClient_GetBuffer(self->render, len, &buffer);
if (hr == AUDCLNT_E_DEVICE_INVALIDATED)
{
hr = ALCwasapiPlayback_TryRecover(self);
if (SUCCEEDED(hr)) {
update_size = device->UpdateSize;
buffer_len = update_size * device->NumUpdates;
continue;
}
}
if(SUCCEEDED(hr))
{
ALCwasapiPlayback_lock(self);
Expand Down Expand Up @@ -875,7 +938,33 @@ static ALCboolean ALCwasapiPlayback_reset(ALCwasapiPlayback *self)
return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE;
}

static ALCboolean GetFmtChans(const WAVEFORMATEXTENSIBLE* outputType, enum DevFmtChannels* fmtChans)
{
if (outputType->Format.nChannels == 1 && outputType->dwChannelMask == MONO)
*fmtChans = DevFmtMono;
else if (outputType->Format.nChannels == 2 && outputType->dwChannelMask == STEREO)
*fmtChans = DevFmtStereo;
else if (outputType->Format.nChannels == 4 && outputType->dwChannelMask == QUAD)
*fmtChans = DevFmtQuad;
else if (outputType->Format.nChannels == 6 && outputType->dwChannelMask == X5DOT1)
*fmtChans = DevFmtX51;
else if (outputType->Format.nChannels == 6 && outputType->dwChannelMask == X5DOT1REAR)
*fmtChans = DevFmtX51Rear;
else if (outputType->Format.nChannels == 7 && outputType->dwChannelMask == X6DOT1)
*fmtChans = DevFmtX61;
else if (outputType->Format.nChannels == 8 && (outputType->dwChannelMask == X7DOT1 || outputType->dwChannelMask == X7DOT1_WIDE))
*fmtChans = DevFmtX71;
else
return ALC_FALSE;
return ALC_TRUE;
}

static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self)
{
return ALCwasapiPlayback_resetProxy_forceFlags(self, 0);
}

static HRESULT ALCwasapiPlayback_resetProxy_forceFlags(ALCwasapiPlayback *self, ALuint forceRequestFlags)
{
ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
EndpointFormFactor formfactor = UnknownFormFactor;
Expand Down Expand Up @@ -916,25 +1005,11 @@ static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self)
buf_time = ScaleCeil(device->UpdateSize*device->NumUpdates, REFTIME_PER_SEC,
device->Frequency);

if(!(device->Flags&DEVICE_FREQUENCY_REQUEST))
if(!(device->Flags&DEVICE_FREQUENCY_REQUEST) && !(forceRequestFlags&DEVICE_FREQUENCY_REQUEST))
device->Frequency = OutputType.Format.nSamplesPerSec;
if(!(device->Flags&DEVICE_CHANNELS_REQUEST))
if(!(device->Flags&DEVICE_CHANNELS_REQUEST) && !(forceRequestFlags&DEVICE_CHANNELS_REQUEST))
{
if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
device->FmtChans = DevFmtMono;
else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
device->FmtChans = DevFmtStereo;
else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
device->FmtChans = DevFmtQuad;
else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
device->FmtChans = DevFmtX51;
else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
device->FmtChans = DevFmtX51Rear;
else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
device->FmtChans = DevFmtX61;
else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
device->FmtChans = DevFmtX71;
else
if (!GetFmtChans(&OutputType, &device->FmtChans))
ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
}

Expand Down Expand Up @@ -1023,7 +1098,7 @@ static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self)
return hr;
}

if(wfx != NULL)
if(wfx != NULL) // Succeeded with a closest match to the specified format.
{
if(!MakeExtensible(&OutputType, wfx))
{
Expand All @@ -1033,22 +1108,20 @@ static HRESULT ALCwasapiPlayback_resetProxy(ALCwasapiPlayback *self)
CoTaskMemFree(wfx);
wfx = NULL;

if ((device->Flags&DEVICE_FREQUENCY_REQUEST) || (forceRequestFlags&DEVICE_FREQUENCY_REQUEST))
{
if (device->Frequency != OutputType.Format.nSamplesPerSec)
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
if ((device->Flags&DEVICE_CHANNELS_REQUEST) || (forceRequestFlags&DEVICE_CHANNELS_REQUEST))
{
enum DevFmtChannels FmtChans;
if (!GetFmtChans(&OutputType, &FmtChans) || FmtChans != device->FmtChans)
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}

device->Frequency = OutputType.Format.nSamplesPerSec;
if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO)
device->FmtChans = DevFmtMono;
else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO)
device->FmtChans = DevFmtStereo;
else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD)
device->FmtChans = DevFmtQuad;
else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1)
device->FmtChans = DevFmtX51;
else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1REAR)
device->FmtChans = DevFmtX51Rear;
else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1)
device->FmtChans = DevFmtX61;
else if(OutputType.Format.nChannels == 8 && (OutputType.dwChannelMask == X7DOT1 || OutputType.dwChannelMask == X7DOT1_WIDE))
device->FmtChans = DevFmtX71;
else
if (!GetFmtChans(&OutputType, &device->FmtChans))
{
ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask);
device->FmtChans = DevFmtStereo;
Expand Down Expand Up @@ -1194,9 +1267,11 @@ static void ALCwasapiPlayback_stopProxy(ALCwasapiPlayback *self)
ATOMIC_STORE_SEQ(&self->killNow, 1);
althrd_join(self->thread, &res);

IAudioRenderClient_Release(self->render);
if (self->render)
IAudioRenderClient_Release(self->render);
self->render = NULL;
IAudioClient_Stop(self->client);
if (self->client)
IAudioClient_Stop(self->client);
}


Expand Down

0 comments on commit dd44357

Please sign in to comment.