2017-12-13 10:04:49 -08:00
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2017-12-13 10:05:26 -08:00
# ifdef _WIN32
2017-12-13 10:04:49 -08:00
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <Eo.h>
# include "ecore_audio_private.h"
# define INITGUID
# include <initguid.h>
# include <functiondiscoverykeys.h>
# include <audioclient.h>
# include <audiopolicy.h>
# include <endpointvolume.h>
# include <mmdeviceapi.h>
# include <mmreg.h>
# include <wtypes.h>
# include <rpc.h>
# include <rpcdce.h>
# include <propkey.h>
# define MY_CLASS ECORE_AUDIO_OUT_WASAPI_CLASS
# define MY_CLASS_NAME "Ecore_Audio_Out_Wasapi"
# define REFTIMES_PER_SEC 10000000
# define REFTIMES_PER_MILLISEC 10000
static int client_connect_count = 0 ;
DEFINE_PROPERTYKEY ( PKEY_Device_FriendlyName , 0xb725f130 , 0x47ef , 0x101a , 0xa5 , 0xf1 , 0x02 , 0x60 , 0x8c , 0x9e , 0xeb , 0xac , 10 ) ;
struct _Ecore_Audio_Wasapi_Class {
Ecore_Job * state_job ;
Eina_List * outputs ;
} ;
typedef struct _Ecore_Audio_Out_Wasapi_Device Ecore_Audio_Out_Wasapi_Device ;
typedef struct _Ecore_Audio_Out_Wasapi_Data Ecore_Audio_Out_Wasapi_Data ;
struct _Ecore_Audio_Out_Wasapi_Device
{
IMMDevice * pDevice ;
IMMDeviceEnumerator * pDeviceEnumerator ;
} ;
struct _Ecore_Audio_Out_Wasapi_Data
{
Eo * in ;
Eo * out ;
IAudioClient * client ;
IAudioRenderClient * render ;
IAudioStreamVolume * volume ;
WAVEFORMATEXTENSIBLE * wave_format ;
2018-01-12 21:44:07 -08:00
Ecore_Win32_Handler * handler ;
2017-12-13 10:04:49 -08:00
HANDLE event ;
UINT32 NumBufferFrames ;
Eina_Bool spec_format ;
Eina_Bool play ;
} ;
Ecore_Audio_Out_Wasapi_Device * device = NULL ;
EOLIAN static void
_ecore_audio_out_wasapi_ecore_audio_volume_set ( Eo * eo_obj EINA_UNUSED , Ecore_Audio_Out_Wasapi_Data * _pd , double volume )
{
HRESULT hr ;
UINT32 count ;
const float volumes = volume ;
hr = _pd - > client - > lpVtbl - > GetService ( _pd - > client ,
& IID_IAudioStreamVolume ,
( void * * ) & ( _pd - > volume ) ) ;
if ( hr ! = S_OK )
{
ERR ( " GetService does not have access to the IID_IAudioStreamVolume interface. " ) ;
return ;
}
hr = _pd - > volume - > lpVtbl - > GetChannelCount ( _pd - > volume , & count ) ;
if ( hr ! = S_OK )
{
ERR ( " The GetChannelCount method can not to gets a count of the channels. " ) ;
return ;
}
hr = _pd - > volume - > lpVtbl - > SetAllVolumes ( _pd - > volume , count , & volumes ) ;
if ( hr ! = S_OK )
{
ERR ( " The SetAllVolumes method can not to sets the individual volume levels for all the channels in the audio stream. " ) ;
return ;
}
}
2018-01-12 21:44:07 -08:00
static void
_clear ( Ecore_Audio_Out_Wasapi_Data * _pd )
{
if ( _pd - > event ) CloseHandle ( _pd - > event ) ;
if ( _pd - > handler ) ecore_main_win32_handler_del ( _pd - > handler ) ;
if ( _pd - > render ) _pd - > render - > lpVtbl - > Release ( _pd - > render ) ;
if ( _pd - > client ) _pd - > client - > lpVtbl - > Release ( _pd - > client ) ;
_pd - > event = NULL ;
_pd - > handler = NULL ;
_pd - > render = NULL ;
_pd - > client = NULL ;
_pd - > play = EINA_FALSE ;
}
2017-12-13 10:04:49 -08:00
static void
_close_cb ( void * data , const Efl_Event * event EINA_UNUSED )
{
2018-01-12 21:44:07 -08:00
_clear ( data ) ;
2017-12-13 10:04:49 -08:00
}
static void
_samplerate_changed_cb ( void * data , const Efl_Event * event EINA_UNUSED )
{
HRESULT hr ;
IAudioClient * client ;
IAudioClockAdjustment * adjustment ;
Ecore_Audio_Out_Wasapi_Data * _pd ;
double sr ;
double speed ;
unsigned int samplerate ;
_pd = data ;
client = _pd - > client ;
speed = ecore_audio_obj_in_speed_get ( _pd - > in ) ;
samplerate = ecore_audio_obj_in_samplerate_get ( _pd - > in ) ;
samplerate = samplerate * speed ;
hr = client - > lpVtbl - > GetService ( client ,
& IID_IAudioClockAdjustment ,
( void * * ) & ( adjustment ) ) ;
if ( hr ! = S_OK )
{
ERR ( " GetService does not have access to the IID_IAudioClockAdjustments interface. " ) ;
return ;
}
sr = ( REFERENCE_TIME ) ( _pd - > NumBufferFrames / samplerate ) ;
adjustment - > lpVtbl - > SetSampleRate ( adjustment , sr ) ;
}
static Eina_Bool
_write_cb ( void * data , Ecore_Win32_Handler * wh EINA_UNUSED )
{
Ecore_Audio_Out_Wasapi_Data * _pd ;
HRESULT hr ;
BYTE * pData ;
UINT32 numAvailableFrames ;
UINT32 numPaddingFrames ;
int nBlockAlign ;
ssize_t bread ;
_pd = data ;
pData = NULL ;
numPaddingFrames = 0 ;
bread = 0 ;
nBlockAlign = _pd - > wave_format - > Format . nBlockAlign ;
hr = ( _pd - > client - > lpVtbl - > GetBufferSize ( _pd - > client , & ( _pd - > NumBufferFrames ) ) ) ;
if ( hr ! = S_OK )
{
ERR ( " The GetBufferSize does not can retrieves the size (maximum capacity) of the endpoint buffer. " ) ;
return ECORE_CALLBACK_CANCEL ;
}
if ( ! _pd - > render )
{
hr = _pd - > client - > lpVtbl - > GetService ( _pd - > client ,
& IID_IAudioRenderClient ,
( void * * ) & ( _pd - > render ) ) ;
if ( hr ! = S_OK )
{
ERR ( " GetService does not have access to the IID_IAudioRenderClient interface. " ) ;
return ECORE_CALLBACK_CANCEL ;
}
}
numPaddingFrames = 0 ;
hr = _pd - > client - > lpVtbl - > GetCurrentPadding ( _pd - > client , & ( numPaddingFrames ) ) ;
if ( hr = = S_OK )
{
numAvailableFrames = _pd - > NumBufferFrames - numPaddingFrames ;
if ( numAvailableFrames = = 0 ) return ECORE_CALLBACK_RENEW ;
hr = _pd - > render - > lpVtbl - > GetBuffer ( _pd - > render , numAvailableFrames , & pData ) ;
if ( hr = = S_OK )
{
bread = ecore_audio_obj_in_read ( _pd - > in , pData , nBlockAlign * numAvailableFrames ) ;
if ( bread > 0 )
{
if ( bread < ( nBlockAlign * numAvailableFrames ) )
memset ( ( char * ) pData + bread , 0 , ( nBlockAlign * numAvailableFrames ) - bread ) ;
hr = _pd - > render - > lpVtbl - > ReleaseBuffer ( _pd - > render , ( UINT32 ) ( bread / nBlockAlign ) , 0 ) ;
if ( hr = = S_OK )
{
if ( ( bread % nBlockAlign ) = = 0 ) return ECORE_CALLBACK_RENEW ;
}
else
{
ERR ( " ReleaseBuffer method cannot releases the buffer space acquired in the previous call to the IAudioRenderClient::GetBuffer method. " ) ;
}
}
}
else
{
ERR ( " GetBuffer method cannot retrieves a pointer to the next available space in the rendering endpoint buffer into which the caller can write a data packet. " ) ;
}
}
else
{
ERR ( " GetCurrentPadding method cannot retrieves the number of frames of padding in the endpoint buffer. " ) ;
}
if ( _pd - > client ) _pd - > client - > lpVtbl - > Stop ( _pd - > client ) ;
2018-01-12 21:44:07 -08:00
if ( _pd - > handler ) ecore_main_win32_handler_del ( _pd - > handler ) ;
_pd - > handler = NULL ;
2017-12-13 10:04:49 -08:00
efl_event_callback_call ( _pd - > out , ECORE_AUDIO_OUT_WASAPI_EVENT_STOP , NULL ) ;
_pd - > play = EINA_FALSE ;
return ECORE_CALLBACK_CANCEL ;
}
static Eina_Bool
wave_format_selection ( Ecore_Audio_Out_Wasapi_Data * _pd , Eo * in )
{
HRESULT hr ;
IAudioClient * client ;
WAVEFORMATEXTENSIBLE * buff ;
WAVEFORMATEXTENSIBLE * correct_wave_format ;
double speed ;
unsigned int channels ;
unsigned int samplerate ;
client = _pd - > client ;
buff = NULL ;
correct_wave_format = NULL ;
speed = ecore_audio_obj_in_speed_get ( in ) ;
channels = ecore_audio_obj_in_channels_get ( in ) ;
samplerate = ecore_audio_obj_in_samplerate_get ( in ) ;
samplerate = samplerate * speed ;
if ( _pd - > spec_format )
{
CoTaskMemFree ( _pd - > wave_format ) ;
_pd - > wave_format = NULL ;
_pd - > spec_format = EINA_FALSE ;
}
hr = client - > lpVtbl - > GetMixFormat ( client , ( WAVEFORMATEX * * ) & ( buff ) ) ;
if ( hr ! = S_OK )
{
return EINA_FALSE ;
}
buff - > Format . wFormatTag = WAVE_FORMAT_EXTENSIBLE ;
buff - > Format . nChannels = channels ;
buff - > Format . nSamplesPerSec = samplerate ;
buff - > Format . nBlockAlign = ( buff - > Format . nChannels * buff - > Format . wBitsPerSample ) / 8 ;
buff - > Format . nAvgBytesPerSec = buff - > Format . nSamplesPerSec * buff - > Format . nBlockAlign ;
buff - > Format . cbSize = sizeof ( WAVEFORMATEXTENSIBLE ) - sizeof ( WAVEFORMATEX ) ;
buff - > dwChannelMask = 0 ;
switch ( channels )
{
case 1 :
buff - > dwChannelMask = KSAUDIO_SPEAKER_MONO ;
break ;
case 2 :
buff - > dwChannelMask = KSAUDIO_SPEAKER_STEREO ;
break ;
case 4 :
buff - > dwChannelMask = KSAUDIO_SPEAKER_QUAD ;
break ;
case 6 :
buff - > dwChannelMask = KSAUDIO_SPEAKER_5POINT1 ;
break ;
case 8 :
buff - > dwChannelMask = KSAUDIO_SPEAKER_7POINT1 ;
break ;
default :
buff - > dwChannelMask = 0 ;
break ;
}
buff - > SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ;
hr = client - > lpVtbl - > IsFormatSupported ( client ,
AUDCLNT_SHAREMODE_SHARED , ( WAVEFORMATEX * ) buff ,
( WAVEFORMATEX * * ) & correct_wave_format ) ;
switch ( hr )
{
case S_OK :
_pd - > wave_format = buff ;
return EINA_TRUE ;
case S_FALSE :
ERR ( " Succeeded with a closest match to the specified format. " ) ;
_pd - > spec_format = EINA_TRUE ;
_pd - > wave_format = correct_wave_format ;
return EINA_TRUE ;
case E_POINTER :
ERR ( " code: E_POINTER " ) ;
return EINA_FALSE ;
case E_INVALIDARG :
ERR ( " code: E_INVALIDARG " ) ;
return EINA_FALSE ;
case AUDCLNT_E_DEVICE_INVALIDATED :
ERR ( " code: AUDCLNT_E_DEVICE_INVALIDATED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_SERVICE_NOT_RUNNING :
ERR ( " code: AUDCLNT_E_SERVICE_NOT_RUNNING " ) ;
return EINA_FALSE ;
default :
ERR ( " IsFormatSupported - return code is unknown " ) ;
return EINA_FALSE ;
}
return EINA_TRUE ;
}
static Eina_Bool
_client_initialize ( Ecore_Audio_Out_Wasapi_Data * _pd )
{
HRESULT hr ;
IAudioClient * client ;
REFERENCE_TIME hnsRequestedDuration ;
WAVEFORMATEXTENSIBLE * correct_wave_format ;
DWORD flags ;
int nbf ;
int sps ;
client = _pd - > client ;
hnsRequestedDuration = REFTIMES_PER_SEC ;
correct_wave_format = _pd - > wave_format ;
flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_RATEADJUST | AUDCLNT_STREAMFLAGS_NOPERSIST ;
hr = client - > lpVtbl - > GetDevicePeriod ( client , NULL , & hnsRequestedDuration ) ;
hr = client - > lpVtbl - > Initialize ( client ,
AUDCLNT_SHAREMODE_SHARED ,
flags ,
hnsRequestedDuration ,
hnsRequestedDuration ,
( WAVEFORMATEX * ) correct_wave_format ,
NULL ) ;
switch ( hr )
{
case S_OK :
break ;
case AUDCLNT_E_ALREADY_INITIALIZED :
ERR ( " code: AUDCLNT_E_ALREADY_INITIALIZED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_WRONG_ENDPOINT_TYPE :
ERR ( " code: AUDCLNT_E_WRONG_ENDPOINT_TYPE " ) ;
return EINA_FALSE ;
case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED :
ERR ( " code: AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED " ) ;
hr = client - > lpVtbl - > GetBufferSize ( client , & ( _pd - > NumBufferFrames ) ) ;
if ( hr ! = S_OK )
{
ERR ( " The GetBufferSize does not can retrieves the size (maximum capacity) of the endpoint buffer. " ) ;
return EINA_FALSE ;
}
nbf = _pd - > NumBufferFrames ;
sps = correct_wave_format - > Format . nSamplesPerSec ;
//*Calculate the aligned buffer size in 100-nansecond units (hns).(https://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx)*//
hnsRequestedDuration = ( REFERENCE_TIME ) ( ( ( REFTIMES_PER_SEC * nbf ) / sps ) + 0.5 ) ;
if ( client )
client - > lpVtbl - > Release ( client ) ;
hr = device - > pDevice - > lpVtbl - > Activate ( device - > pDevice ,
& IID_IAudioClient ,
CLSCTX_ALL ,
NULL ,
( void * * ) & ( client ) ) ;
if ( hr ! = S_OK )
{
ERR ( " The Activate method cannot create a COM object with the specified interface. " ) ;
return EINA_FALSE ;
}
hr = client - > lpVtbl - > GetMixFormat ( client , ( WAVEFORMATEX * * ) & ( correct_wave_format ) ) ;
if ( hr ! = S_OK )
{
ERR ( " The GetMixFormat cannot retrieves the stream format that the audio engine uses for its internal processing of shared-mode streams. " ) ;
return EINA_FALSE ;
}
hr = client - > lpVtbl - > Initialize ( client ,
AUDCLNT_SHAREMODE_SHARED ,
flags ,
hnsRequestedDuration ,
hnsRequestedDuration ,
( WAVEFORMATEX * ) correct_wave_format ,
NULL ) ;
if ( hr ! = S_OK ) return EINA_FALSE ;
return EINA_TRUE ;
case AUDCLNT_E_BUFFER_SIZE_ERROR :
ERR ( " code: AUDCLNT_E_BUFFER_SIZE_ERROR " ) ;
return EINA_FALSE ;
case AUDCLNT_E_CPUUSAGE_EXCEEDED :
ERR ( " code: AUDCLNT_E_CPUUSAGE_EXCEEDED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_DEVICE_INVALIDATED :
ERR ( " code: AUDCLNT_E_DEVICE_INVALIDATED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_DEVICE_IN_USE :
ERR ( " code: AUDCLNT_E_DEVICE_IN_USE " ) ;
return EINA_FALSE ;
case AUDCLNT_E_ENDPOINT_CREATE_FAILED :
ERR ( " code: AUDCLNT_E_ENDPOINT_CREATE_FAILED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_INVALID_DEVICE_PERIOD :
ERR ( " code: AUDCLNT_E_INVALID_DEVICE_PERIOD " ) ;
return EINA_FALSE ;
case AUDCLNT_E_UNSUPPORTED_FORMAT :
ERR ( " code: AUDCLNT_E_UNSUPPORTED_FORMAT " ) ;
return EINA_FALSE ;
case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED :
ERR ( " code: AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED " ) ;
return EINA_FALSE ;
case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL :
ERR ( " code: AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL " ) ;
return EINA_FALSE ;
case AUDCLNT_E_SERVICE_NOT_RUNNING :
ERR ( " code: AUDCLNT_E_SERVICE_NOT_RUNNING " ) ;
return EINA_FALSE ;
case E_POINTER :
ERR ( " code: E_POINTER " ) ;
return EINA_FALSE ;
case E_INVALIDARG :
ERR ( " code: E_INVALIDARG " ) ;
return EINA_FALSE ;
case E_OUTOFMEMORY :
ERR ( " code: E_OUTOFMEMORY " ) ;
return EINA_FALSE ;
default :
ERR ( " code: " ) ;
return EINA_FALSE ;
}
if ( ! _pd - > event )
{
_pd - > event = CreateEvent ( NULL , 0 , 0 , NULL ) ;
hr = client - > lpVtbl - > SetEventHandle ( client , _pd - > event ) ;
if ( hr ! = S_OK ) return EINA_FALSE ;
}
return EINA_TRUE ;
}
static Eina_Bool
_input_attach_internal ( Eo * eo_obj EINA_UNUSED , Eo * in , Ecore_Audio_Out_Wasapi_Data * _pd )
{
HRESULT hr ;
if ( ! device | | ! device - > pDevice ) return EINA_FALSE ;
if ( ! _pd - > client )
{
hr = device - > pDevice - > lpVtbl - > Activate ( device - > pDevice ,
& IID_IAudioClient ,
CLSCTX_ALL ,
NULL ,
( void * * ) & ( _pd - > client ) ) ;
if ( hr ! = S_OK )
{
ERR ( " The Activate method cannot create a COM object with the specified interface. " ) ;
return EINA_FALSE ;
}
}
if ( ! wave_format_selection ( _pd , in ) )
return EINA_FALSE ;
if ( ! _client_initialize ( _pd ) )
return EINA_FALSE ;
_pd - > in = in ;
return EINA_TRUE ;
}
EOLIAN static Eina_Bool
_ecore_audio_out_wasapi_ecore_audio_out_input_attach ( Eo * eo_obj , Ecore_Audio_Out_Wasapi_Data * _pd , Eo * in )
{
Eina_Bool ret = ecore_audio_obj_out_input_attach ( efl_super ( eo_obj , MY_CLASS ) , in ) ;
if ( ! ret ) return EINA_FALSE ;
if ( ! device ) return EINA_FALSE ;
if ( _pd - > play ) return EINA_TRUE ;
if ( ! _input_attach_internal ( eo_obj , in , _pd ) ) return EINA_FALSE ;
ecore_main_win32_handler_add ( _pd - > event , _write_cb , _pd ) ;
efl_event_callback_add ( in , ECORE_AUDIO_IN_EVENT_IN_SAMPLERATE_CHANGED , _samplerate_changed_cb , eo_obj ) ;
efl_event_callback_add ( eo_obj , ECORE_AUDIO_OUT_WASAPI_EVENT_STOP , _close_cb , _pd ) ;
_pd - > play = EINA_TRUE ;
_pd - > out = eo_obj ;
_pd - > client - > lpVtbl - > Start ( _pd - > client ) ;
return EINA_TRUE ;
}
EOLIAN static Eina_Bool
_ecore_audio_out_wasapi_ecore_audio_out_input_detach ( Eo * eo_obj , Ecore_Audio_Out_Wasapi_Data * _pd EINA_UNUSED , Eo * in )
{
Eina_Bool ret = ecore_audio_obj_out_input_detach ( efl_super ( eo_obj , MY_CLASS ) , in ) ;
if ( ! ret ) return EINA_FALSE ;
efl_event_callback_call ( _pd - > out , ECORE_AUDIO_OUT_WASAPI_EVENT_STOP , NULL ) ;
return EINA_TRUE ;
}
EOLIAN static Eo *
_ecore_audio_out_wasapi_efl_object_constructor ( Eo * eo_obj , Ecore_Audio_Out_Wasapi_Data * _pd EINA_UNUSED )
{
HRESULT hr ;
if ( ! device )
{
device = calloc ( 1 , sizeof ( Ecore_Audio_Out_Wasapi_Device ) ) ;
if ( ! device ) return NULL ;
}
if ( device - > pDeviceEnumerator & & device - > pDevice )
{
client_connect_count + + ;
eo_obj = efl_constructor ( efl_super ( eo_obj , MY_CLASS ) ) ;
Ecore_Audio_Output * out_obj = efl_data_scope_get ( eo_obj , ECORE_AUDIO_OUT_CLASS ) ;
out_obj - > need_writer = EINA_FALSE ;
return eo_obj ;
}
hr = CoInitialize ( NULL ) ;
if ( hr = = S_OK | | hr = = S_FALSE )
{
if ( device - > pDeviceEnumerator )
device - > pDeviceEnumerator - > lpVtbl - > Release ( device - > pDeviceEnumerator ) ;
if ( device - > pDevice )
device - > pDevice - > lpVtbl - > Release ( device - > pDevice ) ;
hr = CoCreateInstance ( & CLSID_MMDeviceEnumerator ,
NULL ,
CLSCTX_ALL ,
& IID_IMMDeviceEnumerator ,
( void * * ) & ( device - > pDeviceEnumerator ) ) ;
if ( hr = = S_OK )
{
hr = device - > pDeviceEnumerator - > lpVtbl - > GetDefaultAudioEndpoint ( device - > pDeviceEnumerator ,
eRender ,
eMultimedia ,
& ( device - > pDevice ) ) ;
if ( hr = = S_OK )
{
client_connect_count + + ;
eo_obj = efl_constructor ( efl_super ( eo_obj , MY_CLASS ) ) ;
Ecore_Audio_Output * out_obj = efl_data_scope_get ( eo_obj , ECORE_AUDIO_OUT_CLASS ) ;
out_obj - > need_writer = EINA_FALSE ;
return eo_obj ;
}
device - > pDeviceEnumerator - > lpVtbl - > Release ( device - > pDeviceEnumerator ) ;
}
}
CoUninitialize ( ) ;
free ( device ) ;
device = NULL ;
return NULL ;
}
EOLIAN static void
2018-01-12 21:44:07 -08:00
_ecore_audio_out_wasapi_efl_object_destructor ( Eo * eo_obj , Ecore_Audio_Out_Wasapi_Data * _pd )
2017-12-13 10:04:49 -08:00
{
2018-01-12 21:44:07 -08:00
_clear ( _pd ) ;
2017-12-13 10:04:49 -08:00
client_connect_count - - ;
efl_destructor ( efl_super ( eo_obj , MY_CLASS ) ) ;
if ( ! client_connect_count )
{
if ( device - > pDevice )
device - > pDevice - > lpVtbl - > Release ( device - > pDevice ) ;
if ( device - > pDeviceEnumerator )
device - > pDeviceEnumerator - > lpVtbl - > Release ( device - > pDeviceEnumerator ) ;
free ( device ) ;
device = NULL ;
CoUninitialize ( ) ;
}
}
# include "ecore_audio_out_wasapi.eo.c"
2017-12-13 10:05:26 -08:00
# endif /*_WIN32*/