/* RetroArch - A frontend for libretro. * Copyright (C) 2011-2017 - Daniel De Matteis * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include "mmdevice_common.h" #include "mmdevice_common_inline.h" #include "../../verbosity.h" static const char* mmdevice_data_flow_name(unsigned data_flow) { switch (data_flow) { case 0: return "eRender"; case 1: return "eCapture"; case 2: return "eAll"; default: break; } return ""; } const char *mmdevice_hresult_name(int hr) { switch (hr) { /* Standard error codes */ case E_INVALIDARG: return "E_INVALIDARG"; case E_NOINTERFACE: return "E_NOINTERFACE"; case E_OUTOFMEMORY: return "E_OUTOFMEMORY"; case E_POINTER: return "E_POINTER"; /* Standard success codes */ case S_FALSE: return "S_FALSE"; case S_OK: return "S_OK"; /* AUDCLNT error codes */ case AUDCLNT_E_ALREADY_INITIALIZED: return "AUDCLNT_E_ALREADY_INITIALIZED"; case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: return "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; case AUDCLNT_E_BUFFER_ERROR: return "AUDCLNT_E_BUFFER_ERROR"; case AUDCLNT_E_BUFFER_OPERATION_PENDING: return "AUDCLNT_E_BUFFER_OPERATION_PENDING"; case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED: return "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; case AUDCLNT_E_BUFFER_SIZE_ERROR: return "AUDCLNT_E_BUFFER_SIZE_ERROR"; case AUDCLNT_E_CPUUSAGE_EXCEEDED: return "AUDCLNT_E_CPUUSAGE_EXCEEDED"; case AUDCLNT_E_DEVICE_IN_USE: return "AUDCLNT_E_DEVICE_IN_USE"; case AUDCLNT_E_DEVICE_INVALIDATED: return "AUDCLNT_E_DEVICE_INVALIDATED"; case AUDCLNT_E_ENDPOINT_CREATE_FAILED: return "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: return "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; case AUDCLNT_E_INVALID_DEVICE_PERIOD: return "AUDCLNT_E_INVALID_DEVICE_PERIOD"; case AUDCLNT_E_INVALID_SIZE: return "AUDCLNT_E_INVALID_SIZE"; case AUDCLNT_E_NOT_INITIALIZED: return "AUDCLNT_E_NOT_INITIALIZED"; case AUDCLNT_E_OUT_OF_ORDER: return "AUDCLNT_E_OUT_OF_ORDER"; case AUDCLNT_E_SERVICE_NOT_RUNNING: return "AUDCLNT_E_SERVICE_NOT_RUNNING"; case AUDCLNT_E_UNSUPPORTED_FORMAT: return "AUDCLNT_E_UNSUPPORTED_FORMAT"; case AUDCLNT_E_WRONG_ENDPOINT_TYPE: return "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; /* AUDCLNT success codes */ case AUDCLNT_S_BUFFER_EMPTY: return "AUDCLNT_S_BUFFER_EMPTY"; /* Something else; probably from an API that we started using * after mic support was implemented */ default: break; } return ""; } size_t mmdevice_samplerate(void *data) { HRESULT hr; PWAVEFORMATEX devfmt_props; PROPVARIANT prop_var; IMMDevice *device = (IMMDevice*)data; IPropertyStore *prop_store = NULL; DWORD result = 0; if (!device) return 0; hr = _IMMDevice_OpenPropertyStore(device, STGM_READ, &prop_store); if (FAILED(hr)) return 0; PropVariantInit(&prop_var); hr = _IPropertyStore_GetValue(prop_store, PKEY_AudioEngine_DeviceFormat, &prop_var); if (SUCCEEDED(hr)) { devfmt_props = (PWAVEFORMATEX)prop_var.blob.pBlobData; result = devfmt_props->nSamplesPerSec; } PropVariantClear(&prop_var); if (prop_store) { #ifdef __cplusplus prop_store->Release(); #else prop_store->lpVtbl->Release(prop_store); #endif prop_store = NULL; } return (size_t)result; } char *mmdevice_name(void *data) { HRESULT hr; PROPVARIANT prop_var; IMMDevice *device = (IMMDevice*)data; IPropertyStore *prop_store = NULL; char* result = NULL; if (!device) return NULL; hr = _IMMDevice_OpenPropertyStore(device, STGM_READ, &prop_store); if (FAILED(hr)) return NULL; PropVariantInit(&prop_var); hr = _IPropertyStore_GetValue(prop_store, PKEY_Device_FriendlyName, &prop_var); if (SUCCEEDED(hr)) result = utf16_to_utf8_string_alloc(prop_var.pwszVal); PropVariantClear(&prop_var); if (prop_store) { #ifdef __cplusplus prop_store->Release(); #else prop_store->lpVtbl->Release(prop_store); #endif prop_store = NULL; } return result; } void *mmdevice_handle(int id, unsigned data_flow) { HRESULT hr; IMMDeviceEnumerator *enumerator = NULL; IMMDevice *device = NULL; IMMDeviceCollection *collection = NULL; const char *data_flow_name = mmdevice_data_flow_name(data_flow); #ifdef __cplusplus hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator); #else hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **)&enumerator); #endif if (FAILED(hr)) return NULL; hr = _IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, data_flow, DEVICE_STATE_ACTIVE, &collection); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to enumerate audio endpoints: %s.\n", mmdevice_hresult_name(hr)); goto error; } hr = _IMMDeviceCollection_Item(collection, id, &device); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to get IMMDevice #%d: %s.\n", id, mmdevice_hresult_name(hr)); goto error; } return device; error: if (collection) #ifdef __cplusplus collection->Release(); #else collection->lpVtbl->Release(collection); #endif if (enumerator) #ifdef __cplusplus enumerator->Release(); #else enumerator->lpVtbl->Release(enumerator); #endif collection = NULL; enumerator = NULL; return NULL; } void *mmdevice_init_device(const char *id, unsigned data_flow) { HRESULT hr; UINT32 dev_count, i; IMMDeviceEnumerator *enumerator = NULL; IMMDevice *device = NULL; IMMDeviceCollection *collection = NULL; const char *data_flow_name = mmdevice_data_flow_name(data_flow); if (id) RARCH_DBG("[MMDevice] Initializing %s device \"%s\"...\n", data_flow_name, id); else RARCH_DBG("[MMDevice] Initializing default %s device...\n", data_flow_name); #ifdef __cplusplus hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator); #else hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **)&enumerator); #endif if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to create device enumerator: %s.\n", mmdevice_hresult_name(hr)); goto error; } if (id) { /* If a specific device was requested... */ int32_t idx_found = -1; struct string_list *list = (struct string_list*)mmdevice_list_new(NULL, data_flow); if (!list) { RARCH_ERR("[MMDevice] Failed to allocate %s device list.\n", data_flow_name); goto error; } if (list->elems) { size_t d; /* If any devices were found... */ for (d = 0; d < list->size; d++) { if (string_is_equal(id, list->elems[d].data)) { RARCH_DBG("[MMDevice] Found device #%d: \"%s\".\n", d, list->elems[d].data); idx_found = d; break; } } /* Index was not found yet based on name string, * just assume id is a one-character number index. */ if (idx_found == -1 && isdigit(id[0])) { idx_found = strtoul(id, NULL, 0); RARCH_LOG("[MMDevice] Fallback, %s device index is a single number index instead: %u.\n", data_flow_name, idx_found); } } string_list_free(list); if (idx_found == -1) idx_found = 0; hr = _IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, data_flow, DEVICE_STATE_ACTIVE, &collection); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to enumerate audio endpoints: %s.\n", mmdevice_hresult_name(hr)); goto error; } hr = _IMMDeviceCollection_GetCount(collection, &dev_count); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to count IMMDevices: %s.\n", mmdevice_hresult_name(hr)); goto error; } for (i = 0; i < dev_count; ++i) { hr = _IMMDeviceCollection_Item(collection, i, &device); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to get IMMDevice #%d: %s.\n", i, mmdevice_hresult_name(hr)); goto error; } if (i == (UINT32)idx_found) break; if (device) #ifdef __cplusplus device->Release(); #else device->lpVtbl->Release(device); #endif device = NULL; } } else { hr = _IMMDeviceEnumerator_GetDefaultAudioEndpoint( enumerator, data_flow, eConsole, &device); if (FAILED(hr)) { RARCH_ERR("[MMDevice] Failed to get default audio endpoint: %s.\n", mmdevice_hresult_name(hr)); goto error; } } if (!device) goto error; if (collection) #ifdef __cplusplus collection->Release(); #else collection->lpVtbl->Release(collection); #endif if (enumerator) #ifdef __cplusplus enumerator->Release(); #else enumerator->lpVtbl->Release(enumerator); #endif collection = NULL; enumerator = NULL; return device; error: if (collection) #ifdef __cplusplus collection->Release(); #else collection->lpVtbl->Release(collection); #endif if (enumerator) #ifdef __cplusplus enumerator->Release(); #else enumerator->lpVtbl->Release(enumerator); #endif collection = NULL; enumerator = NULL; if (id) RARCH_WARN("[MMDevice] Failed to initialize %s device \"%s\".\n", data_flow_name, id); else RARCH_ERR("[MMDevice] Failed to initialize default %s device.\n", data_flow_name); return NULL; } void *mmdevice_list_new(const void *u, unsigned data_flow) { HRESULT hr; UINT i; union string_list_elem_attr attr; IMMDeviceEnumerator *enumerator = NULL; IMMDeviceCollection *collection = NULL; UINT dev_count = 0; IMMDevice *device = NULL; LPWSTR dev_id_wstr = NULL; bool br = false; char *dev_id_str = NULL; char *dev_name_str = NULL; struct string_list *sl = string_list_new(); if (!sl) return NULL; attr.i = 0; #ifdef __cplusplus hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator); #else hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void **)&enumerator); #endif if (FAILED(hr)) goto error; hr = _IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, data_flow, DEVICE_STATE_ACTIVE, &collection); if (FAILED(hr)) goto error; hr = _IMMDeviceCollection_GetCount(collection, &dev_count); if (FAILED(hr)) goto error; for (i = 0; i < dev_count; ++i) { hr = _IMMDeviceCollection_Item(collection, i, &device); if (FAILED(hr)) goto error; hr = _IMMDevice_GetId(device, &dev_id_wstr); if (FAILED(hr)) goto error; if (!(dev_id_str = utf16_to_utf8_string_alloc(dev_id_wstr))) goto error; if (!(dev_name_str = mmdevice_name(device))) goto error; br = string_list_append(sl, dev_name_str, attr); if (!br) goto error; if (dev_id_str) sl->elems[sl->size-1].userdata = dev_id_str; if (dev_id_wstr) CoTaskMemFree(dev_id_wstr); if (dev_name_str) free(dev_name_str); dev_name_str = NULL; dev_id_wstr = NULL; if (device) { #ifdef __cplusplus device->Release(); #else device->lpVtbl->Release(device); #endif device = NULL; } } if (collection) { #ifdef __cplusplus collection->Release(); #else collection->lpVtbl->Release(collection); #endif collection = NULL; } if (enumerator) { #ifdef __cplusplus enumerator->Release(); #else enumerator->lpVtbl->Release(enumerator); #endif enumerator = NULL; } return sl; error: if (dev_id_str) free(dev_id_str); if (dev_name_str) free(dev_name_str); dev_id_str = NULL; dev_name_str = NULL; if (dev_id_wstr) CoTaskMemFree(dev_id_wstr); dev_id_wstr = NULL; if (device) { #ifdef __cplusplus device->Release(); #else device->lpVtbl->Release(device); #endif device = NULL; } if (collection) { #ifdef __cplusplus collection->Release(); #else collection->lpVtbl->Release(collection); #endif collection = NULL; } if (enumerator) { #ifdef __cplusplus enumerator->Release(); #else enumerator->lpVtbl->Release(enumerator); #endif enumerator = NULL; } if (sl) string_list_free(sl); return NULL; }