[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkWASAPI.cpp
blob0c0e1bc7e7f3623652ec707cef6f9434f5c031f8
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 */
9 #include "AESinkWASAPI.h"
11 #include "cores/AudioEngine/AESinkFactory.h"
12 #include "cores/AudioEngine/Utils/AEDeviceInfo.h"
13 #include "cores/AudioEngine/Utils/AEUtil.h"
14 #include "utils/StringUtils.h"
15 #include "utils/SystemInfo.h"
16 #include "utils/TimeUtils.h"
17 #include "utils/XTimeUtils.h"
18 #include "utils/log.h"
20 #include <algorithm>
21 #include <stdint.h>
23 #include <Audioclient.h>
24 #include <Mmreg.h>
26 #ifdef TARGET_WINDOWS_DESKTOP
27 # pragma comment(lib, "Avrt.lib")
28 #endif // TARGET_WINDOWS_DESKTOP
30 const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
31 const IID IID_IAudioClock = __uuidof(IAudioClock);
32 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
33 DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
35 extern const char *WASAPIErrToStr(HRESULT err);
36 #define EXIT_ON_FAILURE(hr, reason) \
37 if (FAILED(hr)) \
38 { \
39 CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
40 goto failed; \
43 template<class T>
44 inline void SafeRelease(T **ppT)
46 if (*ppT)
48 (*ppT)->Release();
49 *ppT = nullptr;
53 using namespace Microsoft::WRL;
55 CAESinkWASAPI::CAESinkWASAPI()
57 m_channelLayout.Reset();
60 CAESinkWASAPI::~CAESinkWASAPI()
64 void CAESinkWASAPI::Register()
66 AE::AESinkRegEntry reg;
67 reg.sinkName = "WASAPI";
68 reg.createFunc = CAESinkWASAPI::Create;
69 reg.enumerateFunc = CAESinkWASAPI::EnumerateDevicesEx;
70 AE::CAESinkFactory::RegisterSink(reg);
73 std::unique_ptr<IAESink> CAESinkWASAPI::Create(std::string& device, AEAudioFormat& desiredFormat)
75 auto sink = std::make_unique<CAESinkWASAPI>();
76 if (sink->Initialize(desiredFormat, device))
77 return sink;
79 return {};
82 bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device)
84 if (m_initialized)
85 return false;
87 m_device = device;
88 bool bdefault = false;
89 HRESULT hr = S_FALSE;
91 /* Save requested format */
92 /* Clear returned format */
93 sinkReqFormat = format.m_dataFormat;
94 sinkRetFormat = AE_FMT_INVALID;
96 if(StringUtils::EndsWithNoCase(device, std::string("default")))
97 bdefault = true;
99 if(!bdefault)
101 hr = CAESinkFactoryWin::ActivateWASAPIDevice(device, &m_pDevice);
102 EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.")
105 if (!m_pDevice)
107 if(!bdefault)
109 CLog::LogF(LOGINFO,
110 "Could not locate the device named \"{}\" in the list of WASAPI endpoint devices. "
111 " Trying the default device...",
112 device);
115 std::string defaultId = CAESinkFactoryWin::GetDefaultDeviceId();
116 if (defaultId.empty())
118 CLog::LogF(LOGINFO, "Could not locate the default device id in the list of WASAPI endpoint devices.");
119 goto failed;
122 hr = CAESinkFactoryWin::ActivateWASAPIDevice(defaultId, &m_pDevice);
123 EXIT_ON_FAILURE(hr, "Could not retrieve the default WASAPI audio endpoint.")
125 device = defaultId;
128 hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf());
129 EXIT_ON_FAILURE(hr, "Activating the WASAPI endpoint device failed.")
131 if (!InitializeExclusive(format))
133 CLog::LogF(LOGINFO, "Could not Initialize Exclusive with that format");
134 goto failed;
137 /* get the buffer size and calculate the frames for AE */
138 m_pAudioClient->GetBufferSize(&m_uiBufferLen);
140 format.m_frames = m_uiBufferLen;
141 m_format = format;
142 sinkRetFormat = format.m_dataFormat;
144 hr = m_pAudioClient->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(m_pRenderClient.ReleaseAndGetAddressOf()));
145 EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI render client interface.")
147 hr = m_pAudioClient->GetService(IID_IAudioClock, reinterpret_cast<void**>(m_pAudioClock.ReleaseAndGetAddressOf()));
148 EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI audio clock interface.")
150 hr = m_pAudioClock->GetFrequency(&m_clockFreq);
151 EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetFrequency failed.")
153 m_needDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
154 hr = m_pAudioClient->SetEventHandle(m_needDataEvent);
155 EXIT_ON_FAILURE(hr, "Could not set the WASAPI event handler.");
157 m_initialized = true;
158 m_isDirty = false;
160 // allow feeding less samples than buffer size
161 // if the device is opened exclusive and event driven, provided samples must match buffersize
162 // ActiveAE tries to align provided samples with buffer size but cannot guarantee (e.g. transcoding)
163 // this can be avoided by dropping the event mode which has not much benefit; SoftAE polls anyway
164 m_buffer.resize(format.m_frames * format.m_frameSize);
165 m_bufferPtr = 0;
167 return true;
169 failed:
170 CLog::LogF(LOGERROR, "WASAPI initialization failed.");
171 SafeRelease(&m_pDevice);
172 if(m_needDataEvent)
174 CloseHandle(m_needDataEvent);
175 m_needDataEvent = 0;
178 return false;
181 void CAESinkWASAPI::Deinitialize()
183 if (!m_initialized && !m_isDirty)
184 return;
186 if (m_running)
190 m_pAudioClient->Stop(); //stop the audio output
191 m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
192 m_sinkFrames = 0;
194 catch (...)
196 CLog::LogF(LOGDEBUG, "Invalidated AudioClient - Releasing");
199 m_running = false;
201 CloseHandle(m_needDataEvent);
203 m_pRenderClient = nullptr;
204 m_pAudioClient = nullptr;
205 m_pAudioClock = nullptr;
206 SafeRelease(&m_pDevice);
208 m_initialized = false;
210 m_bufferPtr = 0;
214 * @brief rescale uint64_t without overflowing on large values
216 static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den)
218 return ((val / den) * num) + (((val % den) * num) / den);
222 void CAESinkWASAPI::GetDelay(AEDelayStatus& status)
224 HRESULT hr;
225 uint64_t pos, tick;
226 int retries = 0;
228 if (!m_initialized)
229 goto failed;
231 do {
232 hr = m_pAudioClock->GetPosition(&pos, &tick);
233 } while (hr != S_OK && ++retries < 100);
234 EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetPosition failed.")
236 status.delay = (double)(m_sinkFrames + m_bufferPtr) / m_format.m_sampleRate - (double)pos / m_clockFreq;
237 status.tick = rescale_u64(tick, CurrentHostFrequency(), 10000000); /* convert from 100ns back to qpc ticks */
238 return;
239 failed:
240 status.SetDelay(0);
243 double CAESinkWASAPI::GetCacheTotal()
245 if (!m_initialized)
246 return 0.0;
248 return m_sinkLatency;
251 unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
253 if (!m_initialized)
254 return 0;
256 HRESULT hr;
257 BYTE *buf;
258 DWORD flags = 0;
260 #ifndef _DEBUG
261 LARGE_INTEGER timerStart;
262 LARGE_INTEGER timerStop;
263 LARGE_INTEGER timerFreq;
264 #endif
266 unsigned int NumFramesRequested = m_format.m_frames;
267 unsigned int FramesToCopy = std::min(m_format.m_frames - m_bufferPtr, frames);
268 uint8_t *buffer = data[0]+offset*m_format.m_frameSize;
269 if (m_bufferPtr != 0 || frames != m_format.m_frames)
271 memcpy(m_buffer.data() + m_bufferPtr * m_format.m_frameSize, buffer,
272 FramesToCopy * m_format.m_frameSize);
273 m_bufferPtr += FramesToCopy;
274 if (m_bufferPtr != m_format.m_frames)
275 return frames;
278 if (!m_running) //first time called, pre-fill buffer then start audio client
280 hr = m_pAudioClient->Reset();
281 if (FAILED(hr))
283 CLog::LogF(LOGERROR, " AudioClient reset failed due to {}", WASAPIErrToStr(hr));
284 return 0;
286 hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf);
287 if (FAILED(hr))
289 #ifdef _DEBUG
290 CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr));
291 #endif
292 m_isDirty = true; //flag new device or re-init needed
293 return INT_MAX;
296 memset(buf, 0, NumFramesRequested * m_format.m_frameSize); //fill buffer with silence
298 hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
299 if (FAILED(hr))
301 #ifdef _DEBUG
302 CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr));
303 #endif
304 m_isDirty = true; //flag new device or re-init needed
305 return INT_MAX;
307 m_sinkFrames += NumFramesRequested;
309 hr = m_pAudioClient->Start(); //start the audio driver running
310 if (FAILED(hr))
311 CLog::LogF(LOGERROR, "AudioClient Start Failed");
312 m_running = true; //signal that we're processing frames
313 return 0U;
316 #ifndef _DEBUG
317 /* Get clock time for latency checks */
318 QueryPerformanceFrequency(&timerFreq);
319 QueryPerformanceCounter(&timerStart);
320 #endif
322 /* Wait for Audio Driver to tell us it's got a buffer available */
323 DWORD eventAudioCallback;
324 eventAudioCallback = WaitForSingleObject(m_needDataEvent, 1100);
326 if(eventAudioCallback != WAIT_OBJECT_0 || !&buf)
328 CLog::LogF(LOGERROR, "Endpoint Buffer timed out");
329 return INT_MAX;
332 if (!m_running)
333 return 0;
335 #ifndef _DEBUG
336 QueryPerformanceCounter(&timerStop);
337 LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
338 double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart;
339 m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
341 if (m_avgTimeWaiting < 3.0)
343 CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
344 (int)m_avgTimeWaiting);
346 #endif
348 hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf);
349 if (FAILED(hr))
351 #ifdef _DEBUG
352 CLog::LogF(LOGERROR, "GetBuffer failed due to {}", WASAPIErrToStr(hr));
353 #endif
354 return INT_MAX;
357 // fill buffer
358 memcpy(buf, m_bufferPtr == 0 ? buffer : m_buffer.data(),
359 NumFramesRequested * m_format.m_frameSize);
360 m_bufferPtr = 0;
362 hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, flags); //pass back to audio driver
363 if (FAILED(hr))
365 #ifdef _DEBUG
366 CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr));
367 #endif
368 return INT_MAX;
370 m_sinkFrames += NumFramesRequested;
372 if (FramesToCopy != frames)
374 m_bufferPtr = frames-FramesToCopy;
375 memcpy(m_buffer.data(), buffer + FramesToCopy * m_format.m_frameSize,
376 m_bufferPtr * m_format.m_frameSize);
379 return frames;
382 void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
384 CAEDeviceInfo deviceInfo;
385 CAEChannelInfo deviceChannels;
386 bool add192 = false;
387 bool add48 = false;
389 WAVEFORMATEXTENSIBLE wfxex = {};
390 HRESULT hr;
392 const bool onlyPT = (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Xbox);
394 for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails())
396 deviceInfo.m_channels.Reset();
397 deviceInfo.m_dataFormats.clear();
398 deviceInfo.m_sampleRates.clear();
399 deviceInfo.m_streamTypes.clear();
400 deviceChannels.Reset();
401 add192 = false;
402 add48 = false;
404 for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
406 if (details.uiChannelMask & WASAPIChannelOrder[c])
407 deviceChannels += AEChannelNames[c];
410 IAEWASAPIDevice* pDevice;
411 hr = CAESinkFactoryWin::ActivateWASAPIDevice(details.strDeviceId, &pDevice);
412 if (FAILED(hr))
414 CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint failed.");
415 goto failed;
418 ComPtr<IAudioClient> pClient = nullptr;
419 hr = pDevice->Activate(pClient.GetAddressOf());
420 if (SUCCEEDED(hr))
422 /* Test format DTS-HD-HR */
423 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
424 wfxex.Format.nSamplesPerSec = 192000;
425 wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
426 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
427 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
428 wfxex.Format.wBitsPerSample = 16;
429 wfxex.Samples.wValidBitsPerSample = 16;
430 wfxex.Format.nChannels = 2;
431 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
432 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
433 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
434 if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
436 CLog::LogF(LOGINFO,
437 "Exclusive mode is not allowed on device \"{}\", check device settings.",
438 details.strDescription);
439 SafeRelease(&pDevice);
440 continue;
442 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
444 if(FAILED(hr))
446 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
447 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD),
448 details.strDescription);
451 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
452 add192 = true;
455 /* Test format DTS-HD */
456 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
457 wfxex.Format.nSamplesPerSec = 192000;
458 wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
459 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
460 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
461 wfxex.Format.wBitsPerSample = 16;
462 wfxex.Samples.wValidBitsPerSample = 16;
463 wfxex.Format.nChannels = 8;
464 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
465 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
466 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
467 if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
469 CLog::LogF(LOGINFO,
470 "Exclusive mode is not allowed on device \"{}\", check device settings.",
471 details.strDescription);
472 SafeRelease(&pDevice);
473 continue;
475 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
477 if(FAILED(hr))
479 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
480 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA),
481 details.strDescription);
484 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
485 add192 = true;
488 /* Test format Dolby TrueHD */
489 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
490 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
491 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
493 if(FAILED(hr))
495 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
496 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD),
497 details.strDescription);
500 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
501 add192 = true;
504 /* Test format Dolby EAC3 */
505 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
506 wfxex.Format.nChannels = 2;
507 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
508 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
509 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
510 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
512 if(FAILED(hr))
514 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
515 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3),
516 details.strDescription);
519 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
520 add192 = true;
523 /* Test format DTS */
524 wfxex.Format.nSamplesPerSec = 48000;
525 wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
526 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
527 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
528 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
529 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
530 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
532 if(FAILED(hr))
534 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
535 "STREAM_TYPE_DTS", details.strDescription);
538 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
539 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
540 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
541 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
542 add48 = true;
545 /* Test format Dolby AC3 */
546 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
547 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
548 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
550 if(FAILED(hr))
552 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
553 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3),
554 details.strDescription);
557 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
558 add48 = true;
561 /* Test format for PCM format iteration */
562 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
563 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
564 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
565 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
567 for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
569 if (p < AE_FMT_FLOAT)
570 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
571 wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat) p);
572 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
573 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
574 if (p == AE_FMT_S24NE4MSB)
576 wfxex.Samples.wValidBitsPerSample = 24;
578 else if (p <= AE_FMT_S24NE4 && p >= AE_FMT_S24BE4)
580 // not supported
581 continue;
583 else
585 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
588 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
589 if (SUCCEEDED(hr))
590 deviceInfo.m_dataFormats.push_back((AEDataFormat) p);
593 /* Test format for sample rate iteration */
594 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
595 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
596 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
597 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
599 // 16 bits is most widely supported and likely to have the widest range of sample rates
600 if (deviceInfo.m_dataFormats.empty() ||
601 std::find(deviceInfo.m_dataFormats.cbegin(), deviceInfo.m_dataFormats.cend(),
602 AE_FMT_S16NE) != deviceInfo.m_dataFormats.cend())
604 wfxex.Format.wBitsPerSample = 16;
605 wfxex.Samples.wValidBitsPerSample = 16;
607 else
609 const AEDataFormat fmt = deviceInfo.m_dataFormats.front();
610 wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits(fmt);
611 wfxex.Samples.wValidBitsPerSample =
612 (fmt == AE_FMT_S24NE4MSB ? 24 : wfxex.Format.wBitsPerSample);
615 wfxex.Format.nChannels = 2;
616 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
617 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
619 for (int j = 0; j < WASAPISampleRateCount; j++)
621 wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
622 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
623 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
624 if (SUCCEEDED(hr))
625 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
626 else if (wfxex.Format.nSamplesPerSec == 192000 && add192)
628 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
629 CLog::LogF(LOGINFO, "sample rate 192khz on device \"{}\" seems to be not supported.",
630 details.strDescription);
632 else if (wfxex.Format.nSamplesPerSec == 48000 && add48)
634 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
635 CLog::LogF(LOGINFO, "sample rate 48khz on device \"{}\" seems to be not supported.",
636 details.strDescription);
639 pClient = nullptr;
641 else
643 CLog::LogF(LOGDEBUG, "Failed to activate device for passthrough capability testing.");
646 deviceInfo.m_deviceName = details.strDeviceId;
647 deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
648 deviceInfo.m_displayNameExtra = std::string("WASAPI: ").append(details.strDescription);
649 deviceInfo.m_deviceType = details.eDeviceType;
650 deviceInfo.m_channels = deviceChannels;
652 /* Store the device info */
653 deviceInfo.m_wantsIECPassthrough = true;
654 deviceInfo.m_onlyPassthrough = onlyPT;
656 if (!deviceInfo.m_streamTypes.empty())
657 deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
659 deviceInfoList.push_back(deviceInfo);
661 if (details.bDefault)
663 deviceInfo.m_deviceName = std::string("default");
664 deviceInfo.m_displayName = std::string("default");
665 deviceInfo.m_displayNameExtra = std::string("");
666 deviceInfo.m_wantsIECPassthrough = true;
667 deviceInfo.m_onlyPassthrough = onlyPT;
668 deviceInfoList.push_back(deviceInfo);
671 SafeRelease(&pDevice);
673 return;
675 failed:
677 if (FAILED(hr))
678 CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr));
681 //Private utility functions////////////////////////////////////////////////////
683 void CAESinkWASAPI::BuildWaveFormatExtensibleIEC61397(AEAudioFormat &format, WAVEFORMATEXTENSIBLE_IEC61937 &wfxex)
685 /* Fill the common structure */
686 CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex.FormatExt);
688 /* Code below kept for future use - preferred for later Windows versions */
689 /* but can cause problems on older Windows versions and drivers */
691 wfxex.FormatExt.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE_IEC61937)-sizeof(WAVEFORMATEX);
692 wfxex.dwEncodedChannelCount = format.m_channelLayout.Count();
693 wfxex.dwEncodedSamplesPerSec = bool(format.m_dataFormat == AE_FMT_TRUEHD ||
694 format.m_dataFormat == AE_FMT_DTSHD ||
695 format.m_dataFormat == AE_FMT_EAC3) ? 96000L : 48000L;
696 wfxex.dwAverageBytesPerSec = 0; //Ignored */
699 bool CAESinkWASAPI::InitializeExclusive(AEAudioFormat &format)
701 WAVEFORMATEXTENSIBLE_IEC61937 wfxex_iec61937;
702 WAVEFORMATEXTENSIBLE &wfxex = wfxex_iec61937.FormatExt;
704 if (format.m_dataFormat <= AE_FMT_FLOAT)
705 CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
706 else if (format.m_dataFormat == AE_FMT_RAW)
707 BuildWaveFormatExtensibleIEC61397(format, wfxex_iec61937);
708 else
710 // planar formats are currently not supported by this sink
711 format.m_dataFormat = AE_FMT_FLOAT;
712 CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
715 // Prevents NULL speaker mask. To do: debug exact cause.
716 // When this happens requested AE format is AE_FMT_FLOAT + channel layout
717 // RAW, RAW, RAW... (6 channels). Only happens at end of playback PT
718 // stream, force to defaults does not affect functionality or user
719 // experience. Only avoids crash.
720 if (!wfxex.dwChannelMask && format.m_dataFormat <= AE_FMT_FLOAT)
722 CLog::LogF(LOGWARNING, "NULL Channel Mask detected. Default values are enforced.");
723 format.m_sampleRate = 0; // force defaults in following code
726 /* Test for incomplete format and provide defaults */
727 if (format.m_sampleRate == 0 ||
728 format.m_channelLayout == CAEChannelInfo(nullptr) ||
729 format.m_dataFormat <= AE_FMT_INVALID ||
730 format.m_dataFormat >= AE_FMT_MAX ||
731 format.m_channelLayout.Count() == 0)
733 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
734 wfxex.Format.nChannels = 2;
735 wfxex.Format.nSamplesPerSec = 44100L;
736 wfxex.Format.wBitsPerSample = 16;
737 wfxex.Format.nBlockAlign = 4;
738 wfxex.Samples.wValidBitsPerSample = 16;
739 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
740 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
741 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
742 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
745 HRESULT hr = m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
747 int closestMatch = 0;
748 unsigned int requestedChannels = 0;
749 unsigned int noOfCh = 0;
750 uint64_t desired_map = 0;
751 bool matchNoChannelsOnly = false;
753 if (SUCCEEDED(hr))
755 CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize");
756 goto initialize;
758 else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT) //It failed for a reason unrelated to an unsupported format.
760 CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr));
761 return false;
763 else if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
764 return false;
766 CLog::LogF(LOGWARNING,
767 "format {} not supported by the device - trying to find a compatible format",
768 CAEUtil::DataFormatToStr(format.m_dataFormat));
770 requestedChannels = wfxex.Format.nChannels;
771 desired_map = CAESinkFactoryWin::SpeakerMaskFromAEChannels(format.m_channelLayout);
773 /* The requested format is not supported by the device. Find something that works */
774 CLog::LogF(LOGWARNING, "Input channels are [{}] - Trying to find a matching output layout",
775 std::string(format.m_channelLayout));
777 for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
779 // if requested layout is not supported, try standard layouts which contain
780 // at least the same channels as the input source
781 // as the last resort try stereo
782 if (layout == ARRAYSIZE(layoutsList))
784 if (matchNoChannelsOnly)
786 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
787 wfxex.Format.nChannels = 2;
789 else
791 matchNoChannelsOnly = true;
792 layout = -1;
793 CLog::Log(LOGWARNING, "AESinkWASAPI: Match only number of audio channels as fallback");
794 continue;
797 else if (layout >= 0)
799 wfxex.dwChannelMask = CAESinkFactoryWin::ChLayoutToChMask(layoutsList[layout], &noOfCh);
800 wfxex.Format.nChannels = noOfCh;
801 int res = desired_map & wfxex.dwChannelMask;
802 if (matchNoChannelsOnly)
804 if (noOfCh < requestedChannels)
805 continue; // number of channels doesn't match requested channels
807 else
809 if (res != desired_map)
810 continue; // output channel layout doesn't match input channels
813 CAEChannelInfo foundChannels;
814 CAESinkFactoryWin::AEChannelsFromSpeakerMask(foundChannels, wfxex.dwChannelMask);
815 CLog::Log(LOGDEBUG, "AESinkWASAPI: Trying matching channel layout [{}]",
816 std::string(foundChannels));
818 for (int j = 0; j < sizeof(testFormats)/sizeof(sampleFormat); j++)
820 closestMatch = -1;
822 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
823 wfxex.SubFormat = testFormats[j].subFormat;
824 wfxex.Format.wBitsPerSample = testFormats[j].bitsPerSample;
825 wfxex.Samples.wValidBitsPerSample = testFormats[j].validBitsPerSample;
826 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
828 for (int i = 0 ; i < WASAPISampleRateCount; i++)
830 wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
831 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
833 /* Trace format match iteration loop via log */
834 #if 0
835 CLog::Log(LOGDEBUG, "WASAPI: Trying Format: {}, {}, {}, {}", CAEUtil::DataFormatToStr(testFormats[j].subFormatType),
836 wfxex.Format.nSamplesPerSec,
837 wfxex.Format.wBitsPerSample,
838 wfxex.Samples.wValidBitsPerSample);
839 #endif
841 hr = m_pAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
843 if (SUCCEEDED(hr))
845 /* If the current sample rate matches the source then stop looking and use it */
846 if ((WASAPISampleRates[i] == format.m_sampleRate) && (testFormats[j].subFormatType <= format.m_dataFormat))
847 goto initialize;
848 /* If this rate is closer to the source then the previous one, save it */
849 else if (closestMatch < 0 || abs((int)WASAPISampleRates[i] - (int)format.m_sampleRate) < abs((int)WASAPISampleRates[closestMatch] - (int)format.m_sampleRate))
850 closestMatch = i;
852 else if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
853 CLog::LogF(LOGERROR, "IsFormatSupported failed ({})", WASAPIErrToStr(hr));
856 if (closestMatch >= 0)
858 wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
859 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
860 goto initialize;
863 CLog::Log(LOGDEBUG, "AESinkWASAPI: Format [{}] not supported by driver",
864 std::string(foundChannels));
867 CLog::LogF(LOGERROR, "Unable to locate a supported output format for the device. "
868 "Check the speaker settings in the control panel.");
870 /* We couldn't find anything supported. This should never happen */
871 /* unless the user set the wrong speaker setting in the control panel */
872 return false;
874 initialize:
876 CAESinkFactoryWin::AEChannelsFromSpeakerMask(m_channelLayout, wfxex.dwChannelMask);
877 format.m_channelLayout = m_channelLayout;
879 /* When the stream is raw, the values in the format structure are set to the link */
880 /* parameters, so store the encoded stream values here for the IsCompatible function */
881 m_encodedChannels = wfxex.Format.nChannels;
882 m_encodedSampleRate = (format.m_dataFormat == AE_FMT_RAW) ? format.m_streamInfo.m_sampleRate : format.m_sampleRate;
883 wfxex_iec61937.dwEncodedChannelCount = wfxex.Format.nChannels;
884 wfxex_iec61937.dwEncodedSamplesPerSec = m_encodedSampleRate;
886 /* Set up returned sink format for engine */
887 if (format.m_dataFormat != AE_FMT_RAW)
889 if (wfxex.Format.wBitsPerSample == 32)
891 if (wfxex.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
892 format.m_dataFormat = AE_FMT_FLOAT;
893 else if (wfxex.Samples.wValidBitsPerSample == 32)
894 format.m_dataFormat = AE_FMT_S32NE;
895 else
896 format.m_dataFormat = AE_FMT_S24NE4MSB;
898 else if (wfxex.Format.wBitsPerSample == 24)
899 format.m_dataFormat = AE_FMT_S24NE3;
900 else
901 format.m_dataFormat = AE_FMT_S16NE;
904 format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
905 format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
907 ComPtr<IAudioClient2> audioClient2;
908 if (SUCCEEDED(m_pAudioClient.As(&audioClient2)))
910 AudioClientProperties props = {};
911 props.cbSize = sizeof(props);
912 // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
913 props.eCategory = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
914 ? AudioCategory_Media
915 : AudioCategory_ForegroundOnlyMedia;
917 if (FAILED(hr = audioClient2->SetClientProperties(&props)))
918 CLog::LogF(LOGERROR, "unable to set audio category, {}", WASAPIErrToStr(hr));
921 REFERENCE_TIME audioSinkBufferDurationMsec, hnsLatency;
923 audioSinkBufferDurationMsec = (REFERENCE_TIME)500000;
924 if (IsUSBDevice())
926 CLog::LogF(LOGDEBUG, "detected USB device, increasing buffer size");
927 audioSinkBufferDurationMsec = (REFERENCE_TIME)1000000;
929 audioSinkBufferDurationMsec = (REFERENCE_TIME)((audioSinkBufferDurationMsec / format.m_frameSize) * format.m_frameSize); //even number of frames
931 if (format.m_dataFormat == AE_FMT_RAW)
932 format.m_dataFormat = AE_FMT_S16NE;
934 hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
935 audioSinkBufferDurationMsec, audioSinkBufferDurationMsec, &wfxex.Format, NULL);
937 if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
939 /* WASAPI requires aligned buffer */
940 /* Get the next aligned frame */
941 hr = m_pAudioClient->GetBufferSize(&m_uiBufferLen);
942 if (FAILED(hr))
944 CLog::LogF(LOGERROR, "GetBufferSize Failed : {}", WASAPIErrToStr(hr));
945 return false;
948 audioSinkBufferDurationMsec = (REFERENCE_TIME) ((10000.0 * 1000 / wfxex.Format.nSamplesPerSec * m_uiBufferLen) + 0.5);
950 /* Release the previous allocations */
951 /* Create a new audio client */
952 hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf());
953 if (FAILED(hr))
955 CLog::LogF(LOGERROR, "Device Activation Failed : {}", WASAPIErrToStr(hr));
956 return false;
959 /* Open the stream and associate it with an audio session */
960 hr = m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
961 audioSinkBufferDurationMsec, audioSinkBufferDurationMsec, &wfxex.Format, NULL);
963 if (FAILED(hr))
965 CLog::LogF(LOGERROR, "Failed to initialize WASAPI in exclusive mode {} - ({}).", HRESULT(hr),
966 WASAPIErrToStr(hr));
967 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
968 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
969 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
970 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
971 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
972 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
973 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
974 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
975 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
976 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
977 CLog::Log(LOGDEBUG, " Enc. Channels : {}", wfxex_iec61937.dwEncodedChannelCount);
978 CLog::Log(LOGDEBUG, " Enc. Samples/Sec: {}", wfxex_iec61937.dwEncodedSamplesPerSec);
979 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
980 CLog::Log(LOGDEBUG, " Periodicty : {}", audioSinkBufferDurationMsec);
981 return false;
984 /* Latency of WASAPI buffers in event-driven mode is equal to the returned value */
985 /* of GetStreamLatency converted from 100ns intervals to seconds then multiplied */
986 /* by two as there are two equally-sized buffers and playback starts when the */
987 /* second buffer is filled. Multiplying the returned 100ns intervals by 0.0000002 */
988 /* is handles both the unit conversion and twin buffers. */
989 hr = m_pAudioClient->GetStreamLatency(&hnsLatency);
990 if (FAILED(hr))
992 CLog::LogF(LOGERROR, "GetStreamLatency Failed : {}", WASAPIErrToStr(hr));
993 return false;
996 m_sinkLatency = hnsLatency * 0.0000002;
998 CLog::LogF(LOGINFO, "WASAPI Exclusive Mode Sink Initialized using: {}, {}, {}",
999 CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
1000 wfxex.Format.nChannels);
1001 return true;
1004 void CAESinkWASAPI::Drain()
1006 if(!m_pAudioClient)
1007 return;
1009 AEDelayStatus status;
1010 GetDelay(status);
1012 KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status.GetDelay() * 500)));
1014 if (m_running)
1018 m_pAudioClient->Stop(); //stop the audio output
1019 m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
1020 m_sinkFrames = 0;
1022 catch (...)
1024 CLog::LogF(LOGDEBUG, "Invalidated AudioClient - Releasing");
1027 m_running = false;
1030 bool CAESinkWASAPI::IsUSBDevice()
1032 return m_pDevice && m_pDevice->IsUSBDevice();