Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / windows / AESinkFactoryWin32.cpp
blob6bbd53f920d4e2b9b33338de126e9f1dbb571f5e
1 /*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8 #include "AESinkFactoryWin.h"
9 #include "utils/StringUtils.h"
10 #include "utils/log.h"
12 #include "platform/win32/CharsetConverter.h"
13 #include "platform/win32/WIN32Util.h"
15 #include <algorithm>
17 #include <mmdeviceapi.h>
18 #include <wrl/client.h>
20 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
21 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
22 const IID IID_IAudioClient = __uuidof(IAudioClient);
24 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
25 DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
27 #define EXIT_ON_FAILURE(hr, reason) \
28 if (FAILED(hr)) \
29 { \
30 CLog::LogF(LOGERROR, reason " - {}", hr, CWIN32Util::FormatHRESULT(hr)); \
31 goto failed; \
34 using namespace Microsoft::WRL;
36 std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
38 std::vector<RendererDetail> list;
39 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
40 ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
41 ComPtr<IMMDevice> pDefaultDevice = nullptr;
42 LPWSTR pwszID = nullptr;
43 std::wstring wstrDDID;
44 HRESULT hr;
45 UINT uiCount = 0;
47 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
48 reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
49 EXIT_ON_FAILURE(hr, "Could not allocate MMDevice enumerator.")
51 // get the default audio endpoint
52 if (S_OK ==
53 (hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDefaultDevice.GetAddressOf())))
55 if (S_OK == (hr = pDefaultDevice->GetId(&pwszID)))
57 wstrDDID = pwszID;
58 CoTaskMemFree(pwszID);
60 else
62 CLog::LogF(LOGERROR, "Unable to retrieve default endpoint identifier ({})",
63 CWIN32Util::FormatHRESULT(hr));
65 pDefaultDevice.Reset();
67 else
69 CLog::LogF(LOGERROR, "Unable to retrieve default endpoint ({})", CWIN32Util::FormatHRESULT(hr));
72 // enumerate over all audio endpoints
73 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
74 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
76 hr = pEnumDevices->GetCount(&uiCount);
77 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
79 for (UINT i = 0; i < uiCount; i++)
81 RendererDetail details{};
82 ComPtr<IMMDevice> pDevice = nullptr;
83 ComPtr<IPropertyStore> pProperty = nullptr;
84 PROPVARIANT varName;
85 PropVariantInit(&varName);
87 hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
88 EXIT_ON_FAILURE(hr, "Retrieval of endpoint failed.")
90 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.ReleaseAndGetAddressOf());
91 EXIT_ON_FAILURE(hr, "Retrieval of endpoint properties failed.")
93 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
94 EXIT_ON_FAILURE(hr, "Retrieval of endpoint device name failed.")
96 details.strDescription = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
97 PropVariantClear(&varName);
99 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
100 EXIT_ON_FAILURE(hr, "Retrieval of endpoint GUID failed.")
102 details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
103 PropVariantClear(&varName);
105 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
106 EXIT_ON_FAILURE(hr, "Retrieval of endpoint form factor failed.")
108 details.strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
109 details.eDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
110 PropVariantClear(&varName);
112 /* In shared mode Windows tells us what format the audio must be in. */
113 hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
114 if (SUCCEEDED(hr) && varName.blob.cbSize >= sizeof(WAVEFORMATEX))
116 // This may be a WAVEFORMATEXTENSIBLE but the extra data is not needed.
117 WAVEFORMATEX* pwfx = reinterpret_cast<WAVEFORMATEX*>(varName.blob.pBlobData);
118 details.nChannels = pwfx->nChannels;
119 details.m_samplesPerSec = pwfx->nSamplesPerSec;
121 else
123 CLog::LogF(LOGDEBUG, "Getting DeviceFormat failed ({})", CWIN32Util::FormatHRESULT(hr));
125 PropVariantClear(&varName);
127 hr = pProperty->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, &varName);
128 EXIT_ON_FAILURE(hr, "Retrieval of endpoint speaker layout failed.")
130 details.uiChannelMask = std::max(varName.uintVal, (unsigned int)KSAUDIO_SPEAKER_STEREO);
131 PropVariantClear(&varName);
133 hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
134 if (SUCCEEDED(hr) && varName.vt != VT_EMPTY)
136 details.strDeviceEnumerator = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
137 StringUtils::ToUpper(details.strDeviceEnumerator);
139 else
141 CLog::LogF(LOGDEBUG, "Retrieval of endpoint enumerator name failed: {}.",
142 (FAILED(hr)) ? "'GetValue' has failed" : "'varName.pwszVal' is NULL");
144 PropVariantClear(&varName);
146 if (S_OK == (hr = pDevice->GetId(&pwszID)))
148 if (wstrDDID.compare(pwszID) == 0)
149 details.bDefault = true;
151 CoTaskMemFree(pwszID);
153 else
155 CLog::LogF(LOGERROR, "Unable to retrieve device id ({})", CWIN32Util::FormatHRESULT(hr));
158 list.push_back(details);
161 return list;
163 failed:
165 CLog::Log(LOGERROR, "Failed to enumerate audio renderer devices.");
166 return list;
169 struct AEWASAPIDeviceWin32 : public IAEWASAPIDevice
171 friend CAESinkFactoryWin;
173 HRESULT Activate(IAudioClient** ppAudioClient)
175 HRESULT hr = S_FALSE;
177 if (!ppAudioClient)
178 return E_POINTER;
182 ComPtr<IAudioClient> pClient = nullptr;
183 hr = m_pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, reinterpret_cast<void**>(pClient.GetAddressOf()));
184 if (SUCCEEDED(hr) && pClient)
186 *ppAudioClient = pClient.Detach();
187 return hr;
189 else
191 CLog::LogF(LOGERROR, "unable to activate IAudioClient ({})", CWIN32Util::FormatHRESULT(hr));
194 catch (...) {}
195 return hr;
198 int Release() override
200 delete this;
201 return 0;
204 bool IsUSBDevice() override
206 bool ret = false;
207 ComPtr<IPropertyStore> pProperty = nullptr;
208 PROPVARIANT varName;
209 PropVariantInit(&varName);
211 HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
212 if (!SUCCEEDED(hr))
213 return ret;
214 hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
216 if (SUCCEEDED(hr) && varName.vt != VT_EMPTY)
218 std::string str = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
219 StringUtils::ToUpper(str);
220 ret = (str == "USB");
222 PropVariantClear(&varName);
223 return ret;
226 protected:
227 AEWASAPIDeviceWin32(IMMDevice* pDevice)
228 : m_pDevice(pDevice)
232 private:
233 ComPtr<IMMDevice> m_pDevice{ nullptr };
236 std::string CAESinkFactoryWin::GetDefaultDeviceId()
238 std::string strDeviceId;
239 ComPtr<IMMDevice> pDevice;
240 ComPtr<IMMDeviceEnumerator> pEnumerator;
241 ComPtr<IPropertyStore> pProperty;
242 PROPVARIANT varName;
244 HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
245 reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
246 EXIT_ON_FAILURE(hr, "Could not allocate MMDevice device enumerator.")
248 hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDevice.GetAddressOf());
249 EXIT_ON_FAILURE(hr, "Retrieval of default audio endpoint failed.")
251 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
252 EXIT_ON_FAILURE(hr, "Retrieval of endpoint properties failed.")
254 PropVariantInit(&varName);
255 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
256 EXIT_ON_FAILURE(hr, "Retrieval of endpoint GUID failed.")
258 strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
259 PropVariantClear(&varName);
261 failed:
262 return strDeviceId;
265 HRESULT CAESinkFactoryWin::ActivateWASAPIDevice(std::string &device, IAEWASAPIDevice **ppDevice)
267 ComPtr<IMMDevice> pDevice = nullptr;
268 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
269 ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
270 UINT uiCount = 0;
272 if (!ppDevice)
273 return E_POINTER;
275 HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
276 EXIT_ON_FAILURE(hr, "Could not allocate MMDevice enumerator.")
278 /* Get our device. First try to find the named device. */
280 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
281 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
283 hr = pEnumDevices->GetCount(&uiCount);
284 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
286 for (UINT i = 0; i < uiCount; i++)
288 ComPtr<IPropertyStore> pProperty = nullptr;
289 PROPVARIANT varName;
291 hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
292 EXIT_ON_FAILURE(hr, "Retrieval of endpoint failed.")
294 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
295 EXIT_ON_FAILURE(hr, "Retrieval of endpoint properties failed.")
297 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
298 EXIT_ON_FAILURE(hr, "Retrieval of endpoint GUID failed.")
300 std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
302 if (device == strDevName)
303 i = uiCount;
304 else
305 pDevice.Reset();
307 PropVariantClear(&varName);
310 if (pDevice)
312 AEWASAPIDeviceWin32* pAEDevice = new AEWASAPIDeviceWin32(pDevice.Get());
313 pAEDevice->deviceId = device;
314 *ppDevice = pAEDevice;
315 return S_OK;
318 return E_FAIL;
320 failed:
321 CLog::LogF(LOGERROR, "WASAPI initialization failed.");
322 return hr;