Add WASAPI driver. (#754)

This driver is currently tested and verified to work on:
 - Windows Vista x64 VM
 - Windows 7 x64 VM
 - Windows 10 1909 x64 (VM and Laptop)
 - Windows 10 21296 x64 on a ThinkPad X1 Yoga 1st Gen with 3 different sound cards (Conexant CX20753/4, Scarlett Solo Gen 2, Aureon 7.1 USB)

This driver is capable of reaching very low latency in exclusive mode (~6ms on Scarlett Solo with 48kHz).
This commit is contained in:
Chris Xiong 2021-01-30 01:11:17 +08:00 committed by GitHub
parent d0e90be5b5
commit 712707fe87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 902 additions and 0 deletions

View file

@ -72,6 +72,7 @@ option ( enable-oboe "compile Oboe support (requires OpenSLES and/or AAudio)" of
option ( enable-network "enable network support (requires BSD sockets)" on )
option ( enable-oss "compile OSS support (if it is available)" on )
option ( enable-dsound "compile DirectSound support (if it is available)" on )
option ( enable-wasapi "compile Windows WASAPI support (if it is available)" on )
option ( enable-waveout "compile Windows WaveOut support (if it is available)" on )
option ( enable-winmidi "compile Windows MIDI support (if it is available)" on )
option ( enable-sdl2 "compile SDL2 audio support (if it is available)" on )
@ -229,6 +230,7 @@ endif ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_
# Windows
unset ( WINDOWS_LIBS CACHE )
unset ( DSOUND_SUPPORT CACHE )
unset ( WASAPI_SUPPORT CACHE )
unset ( WAVEOUT_SUPPORT CACHE )
unset ( WINMIDI_SUPPORT CACHE )
unset ( MINGW32 CACHE )
@ -240,6 +242,8 @@ if ( WIN32 )
check_include_file ( io.h HAVE_IO_H )
check_include_file ( dsound.h HAVE_DSOUND_H )
check_include_files ( "windows.h;mmsystem.h" HAVE_MMSYSTEM_H )
check_include_files ( "mmdeviceapi.h;audioclient.h" HAVE_WASAPI_HEADERS )
check_include_file ( objbase.h HAVE_OBJBASE_H )
if ( enable-network )
set ( WINDOWS_LIBS "${WINDOWS_LIBS};ws2_32" )
@ -260,6 +264,11 @@ if ( WIN32 )
set ( WAVEOUT_SUPPORT 1 )
endif ()
if ( enable-wasapi AND HAVE_WASAPI_HEADERS AND HAVE_OBJBASE_H)
set ( WINDOWS_LIBS "${WINDOWS_LIBS};ole32" )
set ( WASAPI_SUPPORT 1 )
endif ()
set ( LIBFLUID_CPPFLAGS "-DFLUIDSYNTH_DLL_EXPORTS" )
set ( FLUID_CPPFLAGS "-DFLUIDSYNTH_NOT_A_DLL" )
if ( MSVC )

View file

@ -79,6 +79,12 @@ else ( SDL2_SUPPORT )
set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} SDL2: no\n" )
endif ( SDL2_SUPPORT )
if ( WASAPI_SUPPORT )
set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WASAPI: yes\n" )
else ( WASAPI_SUPPORT )
set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WASAPI: no\n" )
endif ( WASAPI_SUPPORT )
if ( WAVEOUT_SUPPORT )
set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WaveOut: yes\n" )
else ( WAVEOUT_SUPPORT )

View file

@ -84,6 +84,10 @@ if ( DSOUND_SUPPORT )
set ( fluid_dsound_SOURCES drivers/fluid_dsound.c )
endif ( DSOUND_SUPPORT )
if ( WASAPI_SUPPORT )
set ( fluid_wasapi_SOURCES drivers/fluid_wasapi.c )
endif ( WASAPI_SUPPORT )
if ( WAVEOUT_SUPPORT )
set ( fluid_waveout_SOURCES drivers/fluid_waveout.c )
endif ( WAVEOUT_SUPPORT )
@ -281,6 +285,7 @@ add_library ( libfluidsynth-OBJ OBJECT
${fluid_portaudio_SOURCES}
${fluid_pulse_SOURCES}
${fluid_dsound_SOURCES}
${fluid_wasapi_SOURCES}
${fluid_waveout_SOURCES}
${fluid_winmidi_SOURCES}
${fluid_sdl2_SOURCES}

View file

@ -193,6 +193,9 @@
/* Define to enable DirectSound driver */
#cmakedefine DSOUND_SUPPORT @DSOUND_SUPPORT@
/* Define to enable Windows WASAPI driver */
#cmakedefine WASAPI_SUPPORT @WASAPI_SUPPORT@
/* Define to enable Windows WaveOut driver */
#cmakedefine WAVEOUT_SUPPORT @WAVEOUT_SUPPORT@

View file

@ -120,6 +120,16 @@ static const fluid_audriver_definition_t fluid_audio_drivers[] =
},
#endif
#if WASAPI_SUPPORT
{
"wasapi",
new_fluid_wasapi_audio_driver,
new_fluid_wasapi_audio_driver2,
delete_fluid_wasapi_audio_driver,
fluid_wasapi_audio_driver_settings
},
#endif
#if WAVEOUT_SUPPORT
{
"waveout",

View file

@ -102,6 +102,16 @@ void delete_fluid_dsound_audio_driver(fluid_audio_driver_t *p);
void fluid_dsound_audio_driver_settings(fluid_settings_t *settings);
#endif
#if WASAPI_SUPPORT
fluid_audio_driver_t *new_fluid_wasapi_audio_driver(fluid_settings_t *settings,
fluid_synth_t *synth);
fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings,
fluid_audio_func_t func,
void *data);
void delete_fluid_wasapi_audio_driver(fluid_audio_driver_t *p);
void fluid_wasapi_audio_driver_settings(fluid_settings_t *settings);
#endif
#if WAVEOUT_SUPPORT
fluid_audio_driver_t *new_fluid_waveout_audio_driver(fluid_settings_t *settings,
fluid_synth_t *synth);

859
src/drivers/fluid_wasapi.c Normal file
View file

@ -0,0 +1,859 @@
/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
* Copyright (C) 2021 Chris Xiong
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#include "fluid_synth.h"
#include "fluid_adriver.h"
#include "fluid_settings.h"
#if WASAPI_SUPPORT
#define CINTERFACE
#define COBJMACROS
#include <objbase.h>
#include <windows.h>
#include <initguid.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <audiosessiontypes.h>
#include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
#include <oaidl.h>
#include <ksguid.h>
#include <ksmedia.h>
// these symbols are either never found in headers, or
// only defined but there are no library containing the actual symbol...
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#endif
static const CLSID _CLSID_MMDeviceEnumerator =
{
0xbcde0395, 0xe52f, 0x467c, {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e}
};
static const IID _IID_IMMDeviceEnumerator =
{
0xa95664d2, 0x9614, 0x4f35, {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
};
static const IID _IID_IAudioClient =
{
0x1cb9ad4c, 0xdbfa, 0x4c32, {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
};
static const IID _IID_IAudioRenderClient =
{
0xf294acfc, 0x3146, 0x4483, {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}
};
/*
* WASAPI Driver for fluidsynth
*
* Current limitations:
* - Only one stereo audio output.
* - If audio.sample-format is "16bits", a convertion from float
* without dithering is used.
*
* Available settings:
* - audio.wasapi.exclusive-mode
* 0 = shared mode, 1 = exclusive mode
* - audio.wasapi.device
* Used for device selection. Leave unchanged (or use the value "default")
* to use the default Windows audio device for media playback.
*
* Notes:
* - If exclusive mode is selected, audio.period-size is used as the periodicity
* of the IAudioClient stream, which is the sole factor of audio latency.
* audio.periods still determines the buffer size, but has no direct impact on
* the latency (at least according to Microsoft). The valid range for
* audio.period-size may vary depending on the driver and sample rate.
* - The sample rate and sample format of fluidsynth must be supported by the
* audio device in exclusive mode. Otherwise driver creation will fail.
* - In shared mode, if the sample rate of the synth doesn't match what is
* configured in the 'advanced' tab of the audio device properties dialog,
* Windows will automatically resample the output (obviously). Windows
* Vista doesn't seem to support the resampling method with better quality.
* - Under Windows 10, this driver may report a latency of 0ms in shared mode.
* This is nonsensical and should be disregarded.
*
*/
#define FLUID_WASAPI_MAX_OUTPUTS 1
typedef void(*fluid_wasapi_devenum_callback_t)(IMMDevice *, void *);
static DWORD WINAPI fluid_wasapi_audio_run(void *p);
static int fluid_wasapi_write_processed_channels(void *data, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[]);
static void fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback, void *data);
static void fluid_wasapi_register_callback(IMMDevice *dev, void *data);
static void fluid_wasapi_finddev_callback(IMMDevice *dev, void *data);
static IMMDevice *fluid_wasapi_find_device(IMMDeviceEnumerator *denum, const char *name);
static FLUID_INLINE int16_t round_clip_to_i16(float x);
typedef struct
{
const char *name;
wchar_t *id;
} fluid_wasapi_finddev_data_t;
typedef struct
{
fluid_audio_driver_t driver;
void *user_pointer;
fluid_audio_func_t func;
fluid_audio_channels_callback_t write;
float **drybuf;
UINT32 nframes;
double buffer_duration;
int channels_count;
int float_samples;
HANDLE thread;
DWORD thread_id;
HANDLE quit_ev;
IAudioClient *aucl;
IAudioRenderClient *arcl;
} fluid_wasapi_audio_driver_t;
fluid_audio_driver_t *new_fluid_wasapi_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
{
return new_fluid_wasapi_audio_driver2(settings, (fluid_audio_func_t)fluid_synth_process, synth);
}
fluid_audio_driver_t *new_fluid_wasapi_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data)
{
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,
VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0,
VER_MAJORVERSION, VER_GREATER_EQUAL),
VER_MINORVERSION, VER_GREATER_EQUAL),
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)))
{
FLUID_LOG(FLUID_ERR, "wasapi: this driver requires Windows Vista or newer.");
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)
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
return NULL;
}
FLUID_MEMSET(dev, 0, sizeof(fluid_wasapi_audio_driver_t));
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);
ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
goto cleanup;
}
ret = CoCreateInstance(
&_CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, &_IID_IMMDeviceEnumerator,
(void **)&denum);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot create device enumerator. 0x%x", (unsigned)ret);
goto cleanup;
}
if(fluid_settings_dupstr(settings, "audio.wasapi.device", &dname) == FLUID_OK)
{
mmdev = fluid_wasapi_find_device(denum, dname);
if(mmdev == NULL)
{
FLUID_FREE(dname);
goto cleanup;
}
}
else
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory.");
goto cleanup;
}
FLUID_FREE(dname);
ret = IMMDevice_Activate(mmdev,
&_IID_IAudioClient,
CLSCTX_ALL, NULL,
(void **)&dev->aucl);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot activate audio client. 0x%x", (unsigned)ret);
goto cleanup;
}
share_mode = AUDCLNT_SHAREMODE_SHARED;
if(fluid_settings_getint(settings, "audio.wasapi.exclusive-mode", &i) == FLUID_OK)
{
if(i)
{
share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE;
}
}
if(share_mode == AUDCLNT_SHAREMODE_SHARED)
{
FLUID_LOG(FLUID_DBG, "wasapi: using shared mode.");
periods_reftime = 0;
}
else
{
FLUID_LOG(FLUID_DBG, "wasapi: using exclusive mode.");
}
ret = IAudioClient_IsFormatSupported(dev->aucl, share_mode, (const WAVEFORMATEX *)&wfx, (WAVEFORMATEX **)&rwfx);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: device doesn't support the mode we want. 0x%x", (unsigned)ret);
goto cleanup;
}
else if(ret == S_FALSE)
{
//rwfx is non-null only in this case
FLUID_LOG(FLUID_INFO, "wasapi: requested mode cannot be fully satisfied.");
if(rwfx->Format.nSamplesPerSec != wfx.Format.nSamplesPerSec) // needs resampling
{
flags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
vi.dwMinorVersion = 1;
if(VerifyVersionInfoW(&vi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,
VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0,
VER_MAJORVERSION, VER_GREATER_EQUAL),
VER_MINORVERSION, VER_GREATER_EQUAL),
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL)))
//IAudioClient::Initialize in Vista fails with E_INVALIDARG if this flag is set
{
flags |= AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
}
}
CoTaskMemFree(rwfx);
}
ret = IAudioClient_Initialize(dev->aucl, share_mode, flags,
buffer_duration_reftime, periods_reftime, (WAVEFORMATEX *)&wfx, &GUID_NULL);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to initialize audio client. 0x%x", (unsigned)ret);
if(ret == AUDCLNT_E_INVALID_DEVICE_PERIOD)
{
fluid_long_long_t defp, minp;
FLUID_LOG(FLUID_ERR, "wasapi: the device period size is invalid.");
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);
}
}
goto cleanup;
}
ret = IAudioClient_GetBufferSize(dev->aucl, &dev->nframes);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get audio buffer size. 0x%x", (unsigned)ret);
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;
dev->drybuf = FLUID_ARRAY(float *, audio_channels * 2);
if(dev->drybuf == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory");
goto cleanup;
}
FLUID_MEMSET(dev->drybuf, 0, sizeof(float *) * audio_channels * 2);
for(i = 0; i < audio_channels * 2; ++i)
{
dev->drybuf[i] = FLUID_ARRAY(float, dev->nframes);
if(dev->drybuf[i] == NULL)
{
FLUID_LOG(FLUID_ERR, "wasapi: out of memory");
goto cleanup;
}
}
ret = IAudioClient_GetService(dev->aucl, &_IID_IAudioRenderClient, (void **)&dev->arcl);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get audio render device. 0x%x", (unsigned)ret);
goto cleanup;
}
if(SUCCEEDED(IAudioClient_GetStreamLatency(dev->aucl, &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;
}
ret = IAudioClient_Start(dev->aucl);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to start audio client. 0x%x", (unsigned)ret);
return 0;
}
for(;;)
{
ret = IAudioClient_GetCurrentPadding(dev->aucl, &pos);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer padding. 0x%x", (unsigned)ret);
return 0;
}
len = dev->nframes - pos;
if(len == 0)
{
Sleep(0);
continue;
}
ret = IAudioRenderClient_GetBuffer(dev->arcl, len, &pbuf);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get buffer. 0x%x", (unsigned)ret);
return 0;
}
channels_out[0] = channels_out[1] = (void *)pbuf;
fluid_wasapi_write_processed_channels(dev, len, 2,
channels_out, channels_off, channels_incr);
ret = IAudioRenderClient_ReleaseBuffer(dev->arcl, len, 0);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: failed to release buffer. 0x%x", (unsigned)ret);
return 0;
}
if(WaitForSingleObject(dev->quit_ev, time_to_sleep) == WAIT_OBJECT_0)
{
break;
}
}
return 0;
}
static int fluid_wasapi_write_processed_channels(void *data, int len,
int channels_count,
void *channels_out[], int channels_off[],
int channels_incr[])
{
int i, ch;
int ret;
fluid_wasapi_audio_driver_t *drv = (fluid_wasapi_audio_driver_t *) data;
float *optr[FLUID_WASAPI_MAX_OUTPUTS * 2];
int16_t *ioptr[FLUID_WASAPI_MAX_OUTPUTS * 2];
for(ch = 0; ch < drv->channels_count; ++ch)
{
FLUID_MEMSET(drv->drybuf[ch], 0, len * sizeof(float));
optr[ch] = (float *)channels_out[ch] + channels_off[ch];
ioptr[ch] = (int16_t *)channels_out[ch] + channels_off[ch];
}
ret = drv->func(drv->user_pointer, len, 0, NULL, drv->channels_count, drv->drybuf);
for(ch = 0; ch < drv->channels_count; ++ch)
{
for(i = 0; i < len; ++i)
{
if(drv->float_samples)
{
*optr[ch] = drv->drybuf[ch][i];
optr[ch] += channels_incr[ch];
}
else //This code is taken from fluid_synth.c. No dithering yet.
{
*ioptr[ch] = round_clip_to_i16(drv->drybuf[ch][i] * 32766.0f);
ioptr[ch] += channels_incr[ch];
}
}
}
return ret;
}
static void fluid_wasapi_foreach_device(fluid_wasapi_devenum_callback_t callback, void *data)
{
IMMDeviceEnumerator *denum = NULL;
IMMDeviceCollection *dcoll = NULL;
UINT cnt, i;
HRESULT ret;
ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot initialize COM. 0x%x", (unsigned)ret);
goto cleanup;
}
ret = CoCreateInstance(
&_CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, &_IID_IMMDeviceEnumerator,
(void **)&denum);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot create device enumerator. 0x%x", (unsigned)ret);
goto cleanup;
}
ret = IMMDeviceEnumerator_EnumAudioEndpoints(
denum, eRender,
DEVICE_STATE_ACTIVE, &dcoll);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot enumerate audio devices. 0x%x", (unsigned)ret);
goto cleanup;
}
ret = IMMDeviceCollection_GetCount(dcoll, &cnt);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get device count. 0x%x", (unsigned)ret);
goto cleanup;
}
for(i = 0; i < cnt; ++i)
{
IMMDevice *dev = NULL;
ret = IMMDeviceCollection_Item(dcoll, i, &dev);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get device #%u. 0x%x", i, (unsigned)ret);
continue;
}
callback(dev, data);
IMMDevice_Release(dev);
}
cleanup:
if(dcoll != NULL)
{
IMMDeviceCollection_Release(dcoll);
}
if(denum != NULL)
{
IMMDeviceEnumerator_Release(denum);
}
CoUninitialize();
}
static void fluid_wasapi_register_callback(IMMDevice *dev, void *data)
{
fluid_settings_t *settings = (fluid_settings_t *)data;
IPropertyStore *prop = NULL;
PROPVARIANT var;
int ret;
ret = IMMDevice_OpenPropertyStore(dev, STGM_READ, &prop);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get properties of device. 0x%x", (unsigned)ret);
return;
}
PropVariantInit(&var);
ret = IPropertyStore_GetValue(prop, &PKEY_Device_FriendlyName, &var);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get friendly name of device. 0x%x", (unsigned)ret);
}
else
{
int nsz;
char *name;
nsz = WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, 0, 0, 0, 0);
name = FLUID_ARRAY(char, nsz + 1);
WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, name, nsz, 0, 0);
fluid_settings_add_option(settings, "audio.wasapi.device", name);
FLUID_FREE(name);
}
IPropertyStore_Release(prop);
PropVariantClear(&var);
}
static void fluid_wasapi_finddev_callback(IMMDevice *dev, void *data)
{
fluid_wasapi_finddev_data_t *d = (fluid_wasapi_finddev_data_t *)data;
int nsz;
char *name;
wchar_t *id = NULL;
IPropertyStore *prop = NULL;
PROPVARIANT var;
HRESULT ret;
ret = IMMDevice_OpenPropertyStore(dev, STGM_READ, &prop);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get properties of device. 0x%x", (unsigned)ret);
return;
}
PropVariantInit(&var);
ret = IPropertyStore_GetValue(prop, &PKEY_Device_FriendlyName, &var);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get friendly name of device. 0x%x", (unsigned)ret);
goto cleanup;
}
nsz = WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, 0, 0, 0, 0);
name = FLUID_ARRAY(char, nsz + 1);
WideCharToMultiByte(CP_UTF8, 0, var.pwszVal, -1, name, nsz, 0, 0);
if(!FLUID_STRCASECMP(name, d->name))
{
ret = IMMDevice_GetId(dev, &id);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get id of device. 0x%x", (unsigned)ret);
goto cleanup;
}
nsz = wcslen(id);
d->id = FLUID_ARRAY(wchar_t, nsz + 1);
FLUID_MEMCPY(d->id, id, sizeof(wchar_t) * (nsz + 1));
}
cleanup:
PropVariantClear(&var);
IPropertyStore_Release(prop);
CoTaskMemFree(id);
FLUID_FREE(name);
}
static IMMDevice *fluid_wasapi_find_device(IMMDeviceEnumerator *denum, const char *name)
{
fluid_wasapi_finddev_data_t d;
IMMDevice *dev;
HRESULT ret;
d.name = name;
d.id = NULL;
if(!FLUID_STRCASECMP(name, "default"))
{
ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(
denum,
eRender,
eMultimedia,
&dev);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot get default audio device. 0x%x", (unsigned)ret);
return NULL;
}
else
{
return dev;
}
}
fluid_wasapi_foreach_device(fluid_wasapi_finddev_callback, &d);
if(d.id != NULL)
{
ret = IMMDeviceEnumerator_GetDevice(denum, d.id, &dev);
FLUID_FREE(d.id);
if(FAILED(ret))
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot find device with id. 0x%x", (unsigned)ret);
return NULL;
}
return dev;
}
else
{
FLUID_LOG(FLUID_ERR, "wasapi: cannot find device \"%s\".", name);
return NULL;
}
}
static FLUID_INLINE int16_t
round_clip_to_i16(float x)
{
long i;
if(x >= 0.0f)
{
i = (long)(x + 0.5f);
if(FLUID_UNLIKELY(i > 32767))
{
i = 32767;
}
}
else
{
i = (long)(x - 0.5f);
if(FLUID_UNLIKELY(i < -32768))
{
i = -32768;
}
}
return (int16_t)i;
}
#endif /* WASAPI_SUPPORT */