Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to recover from AUDCLNT_E_DEVICE_INVALIDATED errors in wasapi #237

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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