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.
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"
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) \
30 CLog::LogF(LOGERROR, reason " - {}", hr, CWIN32Util::FormatHRESULT(hr)); \
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
;
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
53 (hr
= pEnumerator
->GetDefaultAudioEndpoint(eRender
, eConsole
, pDefaultDevice
.GetAddressOf())))
55 if (S_OK
== (hr
= pDefaultDevice
->GetId(&pwszID
)))
58 CoTaskMemFree(pwszID
);
62 CLog::LogF(LOGERROR
, "Unable to retrieve default endpoint identifier ({})",
63 CWIN32Util::FormatHRESULT(hr
));
65 pDefaultDevice
.Reset();
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;
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
;
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
);
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
);
155 CLog::LogF(LOGERROR
, "Unable to retrieve device id ({})", CWIN32Util::FormatHRESULT(hr
));
158 list
.push_back(details
);
165 CLog::Log(LOGERROR
, "Failed to enumerate audio renderer devices.");
169 struct AEWASAPIDeviceWin32
: public IAEWASAPIDevice
171 friend CAESinkFactoryWin
;
173 HRESULT
Activate(IAudioClient
** ppAudioClient
)
175 HRESULT hr
= S_FALSE
;
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();
191 CLog::LogF(LOGERROR
, "unable to activate IAudioClient ({})", CWIN32Util::FormatHRESULT(hr
));
198 int Release() override
204 bool IsUSBDevice() override
207 ComPtr
<IPropertyStore
> pProperty
= nullptr;
209 PropVariantInit(&varName
);
211 HRESULT hr
= m_pDevice
->OpenPropertyStore(STGM_READ
, pProperty
.GetAddressOf());
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
);
227 AEWASAPIDeviceWin32(IMMDevice
* pDevice
)
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
;
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
);
265 HRESULT
CAESinkFactoryWin::ActivateWASAPIDevice(std::string
&device
, IAEWASAPIDevice
**ppDevice
)
267 ComPtr
<IMMDevice
> pDevice
= nullptr;
268 ComPtr
<IMMDeviceEnumerator
> pEnumerator
= nullptr;
269 ComPtr
<IMMDeviceCollection
> pEnumDevices
= nullptr;
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;
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
)
307 PropVariantClear(&varName
);
312 AEWASAPIDeviceWin32
* pAEDevice
= new AEWASAPIDeviceWin32(pDevice
.Get());
313 pAEDevice
->deviceId
= device
;
314 *ppDevice
= pAEDevice
;
321 CLog::LogF(LOGERROR
, "WASAPI initialization failed.");