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