--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#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;
+ 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;
+ }
+}
+
+
+static void
+_close_cb(void *data, const Efl_Event *event EINA_UNUSED)
+{
+ Ecore_Audio_Out_Wasapi_Data *_pd = data;
+
+ if (_pd->event) CloseHandle(_pd->event);
+ if (_pd->render) _pd->render->lpVtbl->Release(_pd->render);
+ if (_pd->client) _pd->client->lpVtbl->Release(_pd->client);
+
+ _pd->event = NULL;
+ _pd->render = NULL;
+ _pd->client = NULL;
+ _pd->play = EINA_FALSE;
+}
+
+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);
+
+ ecore_main_win32_handler_del(_pd->event);
+ 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
+_ecore_audio_out_wasapi_efl_object_destructor(Eo *eo_obj, Ecore_Audio_Out_Wasapi_Data *_pd EINA_UNUSED)
+{
+ 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"