1 /*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
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"
11
12 #include "platform/win32/CharsetConverter.h"
13
14 #include <algorithm>
15
16 #include <mmdeviceapi.h>
17 #include <wrl/client.h>
18
19 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
20 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
21 const IID IID_IAudioClient = __uuidof(IAudioClient);
22
23 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
24 DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
25
26 extern const char *WASAPIErrToStr(HRESULT err);
27 #define EXIT_ON_FAILURE(hr, reason) if(FAILED(hr)) {CLog::LogF(LOGERROR, reason " - HRESULT = %li ErrorMessage = %s", hr, WASAPIErrToStr(hr)); goto failed;}
28
29 using namespace Microsoft::WRL;
30
GetRendererDetails()31 std::vector<RendererDetail> CAESinkFactoryWin::GetRendererDetails()
32 {
33 std::vector<RendererDetail> list;
34 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
35 ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
36 ComPtr<IMMDevice> pDefaultDevice = nullptr;
37 LPWSTR pwszID = nullptr;
38 std::wstring wstrDDID;
39 HRESULT hr;
40
41 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
42 EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
43
44 UINT uiCount = 0;
45
46 // get the default audio endpoint
47 if (pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDefaultDevice.GetAddressOf()) == S_OK)
48 {
49 if (pDefaultDevice->GetId(&pwszID) == S_OK)
50 {
51 wstrDDID = pwszID;
52 CoTaskMemFree(pwszID);
53 }
54 pDefaultDevice.Reset();
55 }
56
57 // enumerate over all audio endpoints
58 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
59 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
60
61 hr = pEnumDevices->GetCount(&uiCount);
62 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
63
64 for (UINT i = 0; i < uiCount; i++)
65 {
66 RendererDetail details;
67 ComPtr<IMMDevice> pDevice = nullptr;
68 ComPtr<IPropertyStore> pProperty = nullptr;
69 PROPVARIANT varName;
70 PropVariantInit(&varName);
71
72 hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
73 if (FAILED(hr))
74 {
75 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint failed.");
76 goto failed;
77 }
78
79 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.ReleaseAndGetAddressOf());
80 if (FAILED(hr))
81 {
82 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint properties failed.");
83 goto failed;
84 }
85
86 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
87 if (FAILED(hr))
88 {
89 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint device name failed.");
90 goto failed;
91 }
92
93 details.strDescription = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
94 PropVariantClear(&varName);
95
96 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
97 if (FAILED(hr))
98 {
99 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
100 goto failed;
101 }
102
103 details.strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
104 PropVariantClear(&varName);
105
106 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
107 if (FAILED(hr))
108 {
109 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint form factor failed.");
110 goto failed;
111 }
112 details.strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
113 details.eDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
114
115 PropVariantClear(&varName);
116
117 hr = pProperty->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, &varName);
118 if (FAILED(hr))
119 {
120 CLog::Log(LOGERROR, "Retrieval of WASAPI endpoint speaker layout failed.");
121 goto failed;
122 }
123
124 details.uiChannelMask = std::max(varName.uintVal, (unsigned int)KSAUDIO_SPEAKER_STEREO);
125 PropVariantClear(&varName);
126
127 if (pDevice->GetId(&pwszID) == S_OK)
128 {
129 if (wstrDDID.compare(pwszID) == 0)
130 details.bDefault = true;
131
132 CoTaskMemFree(pwszID);
133 }
134
135 list.push_back(details);
136 }
137
138 return list;
139
140 failed:
141
142 CLog::Log(LOGERROR, "Failed to enumerate audio renderer devices.");
143 return list;
144 }
145
146 struct AEWASAPIDeviceWin32 : public IAEWASAPIDevice
147 {
148 friend CAESinkFactoryWin;
149
Activate(IAudioClient ** ppAudioClient)150 HRESULT AEWASAPIDeviceWin32::Activate(IAudioClient** ppAudioClient)
151 {
152 HRESULT hr = S_FALSE;
153
154 if (!ppAudioClient)
155 return E_POINTER;
156
157 try
158 {
159 ComPtr<IAudioClient> pClient = nullptr;
160 hr = m_pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, reinterpret_cast<void**>(pClient.GetAddressOf()));
161 if (SUCCEEDED(hr) && pClient)
162 {
163 *ppAudioClient = pClient.Detach();
164 return hr;
165 }
166 }
167 catch (...) {}
168 return hr;
169 };
170
Release()171 int AEWASAPIDeviceWin32::Release() override
172 {
173 delete this;
174 return 0;
175 };
176
IsUSBDevice()177 bool AEWASAPIDeviceWin32::IsUSBDevice() override
178 {
179 bool ret = false;
180 ComPtr<IPropertyStore> pProperty = nullptr;
181 PROPVARIANT varName;
182 PropVariantInit(&varName);
183
184 HRESULT hr = m_pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
185 if (!SUCCEEDED(hr))
186 return ret;
187 hr = pProperty->GetValue(PKEY_Device_EnumeratorName, &varName);
188
189 std::string str = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
190 StringUtils::ToUpper(str);
191 ret = (str == "USB");
192 PropVariantClear(&varName);
193 return ret;
194 }
195
196 protected:
AEWASAPIDeviceWin32AEWASAPIDeviceWin32197 AEWASAPIDeviceWin32(IMMDevice* pDevice)
198 : m_pDevice(pDevice)
199 {
200 }
201
202 private:
203 ComPtr<IMMDevice> m_pDevice{ nullptr };
204 };
205
GetDefaultDeviceId()206 std::string CAESinkFactoryWin::GetDefaultDeviceId()
207 {
208 std::string strDeviceId = "";
209 ComPtr<IMMDevice> pDevice = nullptr;
210 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
211 LPWSTR pwszID = NULL;
212 std::wstring wstrDDID;
213
214 HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
215 EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
216
217 // get the default audio endpoint
218 if (pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, pDevice.GetAddressOf()) == S_OK)
219 {
220 ComPtr<IPropertyStore> pProperty = nullptr;
221 PROPVARIANT varName;
222 PropVariantInit(&varName);
223
224 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
225 if (FAILED(hr))
226 {
227 CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint properties failed.");
228 goto failed;
229 }
230
231 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
232 if (FAILED(hr))
233 {
234 CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
235 goto failed;
236 }
237 strDeviceId = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
238 PropVariantClear(&varName);
239
240 }
241
242 failed:
243 return strDeviceId;
244 }
245
ActivateWASAPIDevice(std::string & device,IAEWASAPIDevice ** ppDevice)246 HRESULT CAESinkFactoryWin::ActivateWASAPIDevice(std::string &device, IAEWASAPIDevice **ppDevice)
247 {
248 ComPtr<IMMDevice> pDevice = nullptr;
249 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
250 ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
251
252 if (!ppDevice)
253 return E_POINTER;
254
255 HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
256 EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
257
258 /* Get our device. First try to find the named device. */
259 UINT uiCount = 0;
260
261 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
262 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
263
264 hr = pEnumDevices->GetCount(&uiCount);
265 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
266
267 for (UINT i = 0; i < uiCount; i++)
268 {
269 ComPtr<IPropertyStore> pProperty = nullptr;
270 PROPVARIANT varName;
271
272 hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
273 EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.")
274
275 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
276 EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint properties failed.")
277
278 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
279 if (FAILED(hr))
280 {
281 CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint GUID failed.");
282 goto failed;
283 }
284
285 std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
286
287 if (device == strDevName)
288 i = uiCount;
289 else
290 pDevice.Reset();
291
292 PropVariantClear(&varName);
293 }
294
295 if (pDevice)
296 {
297 AEWASAPIDeviceWin32* pAEDevice = new AEWASAPIDeviceWin32(pDevice.Get());
298 pAEDevice->deviceId = device;
299 *ppDevice = pAEDevice;
300 return S_OK;
301 }
302
303 return E_FAIL;
304
305 failed:
306 CLog::LogF(LOGERROR, "WASAPI initialization failed.");
307 return hr;
308 }
309