WASAPI driver reorganization (#839)

Avoid initializing COM in the caller's thread context.

See also: #833
This commit is contained in:
Pedro López-Cabanillas 2021-04-14 09:35:35 +02:00 committed by GitHub
parent 149e08f181
commit 03fb32c979
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 265 additions and 215 deletions

1
.gitignore vendored
View file

@ -38,3 +38,4 @@ install_manifest.txt
# ProjectFiles
*.pro.user*
*.user

View file

@ -135,6 +135,7 @@ typedef struct
int channels_count;
int float_samples;
HANDLE start_ev;
HANDLE thread;
DWORD thread_id;
HANDLE quit_ev;
@ -142,6 +143,17 @@ typedef struct
IAudioClient *aucl;
IAudioRenderClient *arcl;
double sample_rate;
int periods, period_size;
fluid_long_long_t buffer_duration_reftime;
fluid_long_long_t periods_reftime;
fluid_long_long_t latency_reftime;
int audio_channels;
int sample_size;
char *dname;
int exclusive;
unsigned short sample_format;
} fluid_wasapi_audio_driver_t;
fluid_audio_driver_t *new_fluid_wasapi_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
@ -151,24 +163,9 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver(fluid_settings_t *settings,
fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data)
{
DWORD ret;
HANDLE wait_handles[2];
fluid_wasapi_audio_driver_t *dev = NULL;
double sample_rate;
int periods, period_size;
fluid_long_long_t buffer_duration_reftime;
fluid_long_long_t periods_reftime;
fluid_long_long_t latency_reftime;
int audio_channels;
int sample_size;
int i;
char *dname = NULL;
DWORD flags = 0;
//GUID guid_float = {DEFINE_WAVEFORMATEX_GUID(WAVE_FORMAT_IEEE_FLOAT)};
WAVEFORMATEXTENSIBLE wfx;
WAVEFORMATEXTENSIBLE *rwfx = NULL;
AUDCLNT_SHAREMODE share_mode;
IMMDeviceEnumerator *denum = NULL;
IMMDevice *mmdev = NULL;
HRESULT ret;
OSVERSIONINFOEXW vi = {sizeof(vi), 6, 0, 0, 0, {0}, 0, 0, 0, 0, 0};
if(!VerifyVersionInfoW(&vi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
@ -181,43 +178,6 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
return NULL;
}
/* Retrieve the settings */
fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
fluid_settings_getint(settings, "audio.periods", &periods);
fluid_settings_getint(settings, "audio.period-size", &period_size);
fluid_settings_getint(settings, "synth.audio-channels", &audio_channels);
if(audio_channels > FLUID_WASAPI_MAX_OUTPUTS)
{
FLUID_LOG(FLUID_ERR, "wasapi: channel configuration with more than one stereo pair is not supported.");
return NULL;
}
/* Clear format structure */
ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits"))
{
sample_size = sizeof(int16_t);
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
}
else
{
sample_size = sizeof(float);
wfx.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
}
wfx.Format.nChannels = 2;
wfx.Format.wBitsPerSample = sample_size * 8;
wfx.Format.nBlockAlign = sample_size * wfx.Format.nChannels;
wfx.Format.nSamplesPerSec = (DWORD)sample_rate;
wfx.Format.nAvgBytesPerSec = (DWORD)sample_rate * wfx.Format.nBlockAlign;
//wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
//wfx.Format.cbSize = 22;
//wfx.SubFormat = guid_float;
//wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
//wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
dev = FLUID_NEW(fluid_wasapi_audio_driver_t);
if(dev == NULL)
@ -228,14 +188,188 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
FLUID_MEMSET(dev, 0, sizeof(fluid_wasapi_audio_driver_t));
/* Retrieve the settings */
fluid_settings_getnum(settings, "synth.sample-rate", &dev->sample_rate);
fluid_settings_getint(settings, "audio.periods", &dev->periods);
fluid_settings_getint(settings, "audio.period-size", &dev->period_size);
fluid_settings_getint(settings, "synth.audio-channels", &dev->audio_channels);
fluid_settings_getint(settings, "audio.wasapi.exclusive-mode", &dev->exclusive);
if(dev->audio_channels > FLUID_WASAPI_MAX_OUTPUTS)
{
FLUID_LOG(FLUID_ERR, "wasapi: channel configuration with more than one stereo pair is not supported.");
goto cleanup;
}
if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits"))
{
dev->sample_size = sizeof(int16_t);
dev->sample_format = WAVE_FORMAT_PCM;
}
else
{
dev->sample_size = sizeof(float);
dev->sample_format = WAVE_FORMAT_IEEE_FLOAT;
}
if(fluid_settings_dupstr(settings, "audio.wasapi.device", &dev->dname) != FLUID_OK)
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
goto cleanup;
}
dev->func = func;
dev->user_pointer = data;
dev->buffer_duration = periods * period_size / sample_rate;
dev->channels_count = audio_channels * 2;
dev->float_samples = (wfx.Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT);
buffer_duration_reftime = (fluid_long_long_t)(dev->buffer_duration * 1e7 + .5);
periods_reftime = (fluid_long_long_t)(period_size / sample_rate * 1e7 + .5);
dev->buffer_duration = dev->periods * dev->period_size / dev->sample_rate;
dev->channels_count = dev->audio_channels * 2;
dev->float_samples = (dev->sample_format == WAVE_FORMAT_IEEE_FLOAT);
dev->buffer_duration_reftime = (fluid_long_long_t)(dev->buffer_duration * 1e7 + .5);
dev->periods_reftime = (fluid_long_long_t)(dev->period_size / dev->sample_rate * 1e7 + .5);
dev->quit_ev = CreateEvent(NULL, FALSE, FALSE, NULL);
if(dev->quit_ev == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to create quit event: '%s'", fluid_get_windows_error());
goto cleanup;
}
dev->start_ev = CreateEvent(NULL, FALSE, FALSE, NULL);
if(dev->start_ev == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to create start event: '%s'", fluid_get_windows_error());
goto cleanup;
}
dev->thread = CreateThread(NULL, 0, fluid_wasapi_audio_run, dev, 0, &dev->thread_id);
if(dev->thread == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to create audio thread: '%s'", fluid_get_windows_error());
goto cleanup;
}
/* start event must be first */
wait_handles[0] = dev->start_ev;
wait_handles[1] = dev->thread;
ret = WaitForMultipleObjects(FLUID_N_ELEMENTS(wait_handles), wait_handles, FALSE, 1000);
switch(ret)
{
case WAIT_OBJECT_0:
return &dev->driver;
case WAIT_TIMEOUT:
FLUID_LOG(FLUID_WARN, "wasapi: initialization timeout!");
break;
default:
break;
}
cleanup:
delete_fluid_wasapi_audio_driver(&dev->driver);
return NULL;
}
void delete_fluid_wasapi_audio_driver(fluid_audio_driver_t *p)
{
fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *) p;
int i;
fluid_return_if_fail(dev != NULL);
if(dev->thread != NULL)
{
SetEvent(dev->quit_ev);
if(WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0)
{
FLUID_LOG(FLUID_WARN, "wasapi: couldn't join the audio thread. killing it.");
TerminateThread(dev->thread, 0);
}
CloseHandle(dev->thread);
}
if(dev->quit_ev != NULL)
{
CloseHandle(dev->quit_ev);
}
if(dev->start_ev != NULL)
{
CloseHandle(dev->start_ev);
}
if(dev->drybuf)
{
for(i = 0; i < dev->channels_count; ++i)
{
FLUID_FREE(dev->drybuf[i]);
}
}
FLUID_FREE(dev->dname);
FLUID_FREE(dev->drybuf);
FLUID_FREE(dev);
}
void fluid_wasapi_audio_driver_settings(fluid_settings_t *settings)
{
fluid_settings_register_int(settings, "audio.wasapi.exclusive-mode", 0, 0, 1, FLUID_HINT_TOGGLED);
fluid_settings_register_str(settings, "audio.wasapi.device", "default", 0);
fluid_settings_add_option(settings, "audio.wasapi.device", "default");
fluid_wasapi_foreach_device(fluid_wasapi_register_callback, settings);
}
static DWORD WINAPI fluid_wasapi_audio_run(void *p)
{
fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *)p;
DWORD time_to_sleep = dev->buffer_duration * 1000 / 2;
UINT32 pos;
DWORD len;
void *channels_out[2];
int channels_off[2] = {0, 1};
int channels_incr[2] = {2, 2};
BYTE *pbuf;
HRESULT ret;
IMMDeviceEnumerator *denum = NULL;
IMMDevice *mmdev = NULL;
DWORD flags = 0;
WAVEFORMATEXTENSIBLE wfx;
WAVEFORMATEXTENSIBLE *rwfx = NULL;
AUDCLNT_SHAREMODE share_mode;
OSVERSIONINFOEXW vi = {sizeof(vi), 6, 0, 0, 0, {0}, 0, 0, 0, 0, 0};
int needs_com_uninit = FALSE;
int i;
if(time_to_sleep < 1)
{
time_to_sleep = 1;
}
/* Clear format structure */
ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
wfx.Format.nChannels = 2;
wfx.Format.wBitsPerSample = dev->sample_size * 8;
wfx.Format.nBlockAlign = dev->sample_size * wfx.Format.nChannels;
wfx.Format.nSamplesPerSec = (DWORD) dev->sample_rate;
wfx.Format.nAvgBytesPerSec = (DWORD) dev->sample_rate * wfx.Format.nBlockAlign;
wfx.Format.wFormatTag = dev->sample_format;
//wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
//wfx.Format.cbSize = 22;
//wfx.SubFormat = guid_float;
//wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
//wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
/* initialize COM in a worker thread to avoid a potential double initialization in the callers thread */
ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(ret))
@ -244,6 +378,8 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
needs_com_uninit = TRUE;
ret = CoCreateInstance(
&_CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, &_IID_IMMDeviceEnumerator,
@ -255,24 +391,13 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
if(fluid_settings_dupstr(settings, "audio.wasapi.device", &dname) == FLUID_OK)
{
mmdev = fluid_wasapi_find_device(denum, dname);
mmdev = fluid_wasapi_find_device(denum, dev->dname);
if(mmdev == NULL)
{
FLUID_FREE(dname);
goto cleanup;
}
}
else
if(mmdev == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
goto cleanup;
}
FLUID_FREE(dname);
ret = IMMDevice_Activate(mmdev,
&_IID_IAudioClient,
CLSCTX_ALL, NULL,
@ -284,24 +409,17 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
share_mode = AUDCLNT_SHAREMODE_SHARED;
if(fluid_settings_getint(settings, "audio.wasapi.exclusive-mode", &i) == FLUID_OK)
if(dev->exclusive)
{
if(i)
{
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
}
}
if(share_mode == AUDCLNT_SHAREMODE_SHARED)
{
FLUID_LOG(FLUID_DBG, "wasapi: using shared mode.");
periods_reftime = 0;
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
FLUID_LOG(FLUID_DBG, "wasapi: using exclusive mode.");
}
else
{
FLUID_LOG(FLUID_DBG, "wasapi: using exclusive mode.");
share_mode = AUDCLNT_SHAREMODE_SHARED;
FLUID_LOG(FLUID_DBG, "wasapi: using shared mode.");
dev->periods_reftime = 0;
}
ret = IAudioClient_IsFormatSupported(dev->aucl, share_mode, (const WAVEFORMATEX *)&wfx, (WAVEFORMATEX **)&rwfx);
@ -336,7 +454,7 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
}
ret = IAudioClient_Initialize(dev->aucl, share_mode, flags,
buffer_duration_reftime, periods_reftime, (WAVEFORMATEX *)&wfx, &GUID_NULL);
dev->buffer_duration_reftime, dev->periods_reftime, (WAVEFORMATEX *)&wfx, &GUID_NULL);
if(FAILED(ret))
{
@ -349,9 +467,9 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
if(SUCCEEDED(IAudioClient_GetDevicePeriod(dev->aucl, &defp, &minp)))
{
int defpf = (int)(defp / 1e7 * sample_rate);
int minpf = (int)(minp / 1e7 * sample_rate);
FLUID_LOG(FLUID_ERR, "wasapi: minimum period is %d, default period is %d. selected %d.", minpf, defpf, period_size);
int defpf = (int)(defp / 1e7 * dev->sample_rate);
int minpf = (int)(minp / 1e7 * dev->sample_rate);
FLUID_LOG(FLUID_ERR, "wasapi: minimum period is %d, default period is %d. selected %d.", minpf, defpf, dev->period_size);
}
}
@ -366,10 +484,10 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
FLUID_LOG(FLUID_DBG, "wasapi: requested %d frames of buffers, got %u.", periods * period_size, dev->nframes);
dev->buffer_duration = dev->nframes / sample_rate;
FLUID_LOG(FLUID_DBG, "wasapi: requested %d frames of buffers, got %u.", dev->periods * dev->period_size, dev->nframes);
dev->buffer_duration = dev->nframes / dev->sample_rate;
dev->drybuf = FLUID_ARRAY(float *, audio_channels * 2);
dev->drybuf = FLUID_ARRAY(float *, dev->audio_channels * 2);
if(dev->drybuf == NULL)
{
@ -377,9 +495,9 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
FLUID_MEMSET(dev->drybuf, 0, sizeof(float *) * audio_channels * 2);
FLUID_MEMSET(dev->drybuf, 0, sizeof(float *) * dev->audio_channels * 2);
for(i = 0; i < audio_channels * 2; ++i)
for(i = 0; i < dev->audio_channels * 2; ++i)
{
dev->drybuf[i] = FLUID_ARRAY(float, dev->nframes);
@ -398,121 +516,9 @@ fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
goto cleanup;
}
if(SUCCEEDED(IAudioClient_GetStreamLatency(dev->aucl, &latency_reftime)))
if(SUCCEEDED(IAudioClient_GetStreamLatency(dev->aucl, &dev->latency_reftime)))
{
FLUID_LOG(FLUID_DBG, "wasapi: latency: %fms.", latency_reftime / 1e4);
}
dev->quit_ev = CreateEvent(NULL, FALSE, FALSE, NULL);
if(dev->quit_ev == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to create quit event.");
goto cleanup;
}
dev->thread = CreateThread(NULL, 0, fluid_wasapi_audio_run, (void *)dev, 0, &dev->thread_id);
if(dev->thread == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to create audio thread.");
goto cleanup;
}
IMMDevice_Release(mmdev);
IMMDeviceEnumerator_Release(denum);
return &dev->driver;
cleanup:
if(mmdev != NULL)
{
IMMDevice_Release(mmdev);
}
if(denum != NULL)
{
IMMDeviceEnumerator_Release(denum);
}
delete_fluid_wasapi_audio_driver(&dev->driver);
return NULL;
}
void delete_fluid_wasapi_audio_driver(fluid_audio_driver_t *p)
{
fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *) p;
int i;
fluid_return_if_fail(dev != NULL);
if(dev->thread != NULL)
{
SetEvent(dev->quit_ev);
if(WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0)
{
FLUID_LOG(FLUID_WARN, "wasapi: couldn't join the audio thread. killing it.");
TerminateThread(dev->thread, 0);
}
CloseHandle(dev->thread);
}
if(dev->quit_ev != NULL)
{
CloseHandle(dev->quit_ev);
}
if(dev->aucl != NULL)
{
IAudioClient_Stop(dev->aucl);
IAudioClient_Release(dev->aucl);
}
if(dev->arcl != NULL)
{
IAudioRenderClient_Release(dev->arcl);
}
CoUninitialize();
if(dev->drybuf)
{
for(i = 0; i < dev->channels_count; ++i)
{
FLUID_FREE(dev->drybuf[i]);
}
}
FLUID_FREE(dev->drybuf);
FLUID_FREE(dev);
}
void fluid_wasapi_audio_driver_settings(fluid_settings_t *settings)
{
fluid_settings_register_int(settings, "audio.wasapi.exclusive-mode", 0, 0, 1, FLUID_HINT_TOGGLED);
fluid_settings_register_str(settings, "audio.wasapi.device", "default", 0);
fluid_settings_add_option(settings, "audio.wasapi.device", "default");
fluid_wasapi_foreach_device(fluid_wasapi_register_callback, settings);
}
static DWORD WINAPI fluid_wasapi_audio_run(void *p)
{
fluid_wasapi_audio_driver_t *dev = (fluid_wasapi_audio_driver_t *)p;
DWORD time_to_sleep = dev->buffer_duration * 1000 / 2;
UINT32 pos;
DWORD len;
void *channels_out[2];
int channels_off[2] = {0, 1};
int channels_incr[2] = {2, 2};
BYTE *pbuf;
HRESULT ret;
if(time_to_sleep < 1)
{
time_to_sleep = 1;
FLUID_LOG(FLUID_DBG, "wasapi: latency: %fms.", dev->latency_reftime / 1e4);
}
ret = IAudioClient_Start(dev->aucl);
@ -520,9 +526,12 @@ static DWORD WINAPI fluid_wasapi_audio_run(void *p)
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to start audio client. 0x%x", (unsigned)ret);
return 0;
goto cleanup;
}
/* Signal the success of the driver initialization */
SetEvent(dev->start_ev);
for(;;)
{
ret = IAudioClient_GetCurrentPadding(dev->aucl, &pos);
@ -530,7 +539,7 @@ static DWORD WINAPI fluid_wasapi_audio_run(void *p)
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer padding. 0x%x", (unsigned)ret);
return 0;
goto cleanup;
}
len = dev->nframes - pos;
@ -546,7 +555,7 @@ static DWORD WINAPI fluid_wasapi_audio_run(void *p)
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer. 0x%x", (unsigned)ret);
return 0;
goto cleanup;
}
channels_out[0] = channels_out[1] = (void *)pbuf;
@ -559,7 +568,7 @@ static DWORD WINAPI fluid_wasapi_audio_run(void *p)
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to release buffer. 0x%x", (unsigned)ret);
return 0;
goto cleanup;
}
if(WaitForSingleObject(dev->quit_ev, time_to_sleep) == WAIT_OBJECT_0)
@ -568,6 +577,34 @@ static DWORD WINAPI fluid_wasapi_audio_run(void *p)
}
}
cleanup:
if(dev->aucl != NULL)
{
IAudioClient_Stop(dev->aucl);
IAudioClient_Release(dev->aucl);
}
if(dev->arcl != NULL)
{
IAudioRenderClient_Release(dev->arcl);
}
if(mmdev != NULL)
{
IMMDevice_Release(mmdev);
}
if(denum != NULL)
{
IMMDeviceEnumerator_Release(denum);
}
if(needs_com_uninit)
{
CoUninitialize();
}
return 0;
}
@ -617,13 +654,22 @@ static void fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback
IMMDeviceCollection *dcoll = NULL;
UINT cnt, i;
HRESULT ret;
int com_was_initialized = FALSE;
ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
goto cleanup;
if(ret == RPC_E_CHANGED_MODE)
{
com_was_initialized = TRUE;
FLUID_LOG(FLUID_DBG, "wasapi: COM was already initialized");
}
else
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
return;
}
}
ret = CoCreateInstance(
@ -684,7 +730,10 @@ cleanup:
IMMDeviceEnumerator_Release(denum);
}
CoUninitialize();
if(!com_was_initialized)
{
CoUninitialize();
}
}
static void fluid_wasapi_register_callback(IMMDevice *dev, void *data)