Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkWASAPI.cpp
blobcb9f8d3bb0c1ea06f848c1aa895bea0fed81b2ee
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 "platform/win32/WIN32Util.h"
22 #include <algorithm>
23 #include <stdint.h>
25 #include <Audioclient.h>
26 #include <Mmreg.h>
28 #ifdef TARGET_WINDOWS_DESKTOP
29 # pragma comment(lib, "Avrt.lib")
30 #endif // TARGET_WINDOWS_DESKTOP
32 const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
33 const IID IID_IAudioClock = __uuidof(IAudioClock);
34 DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
35 DEFINE_PROPERTYKEY(PKEY_Device_EnumeratorName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 24);
37 #define EXIT_ON_FAILURE(hr, reason) \
38 if (FAILED(hr)) \
39 { \
40 CLog::LogF(LOGERROR, reason " - error {}", hr, CWIN32Util::FormatHRESULT(hr)); \
41 goto failed; \
44 template<class T>
45 inline void SafeRelease(T **ppT)
47 if (*ppT)
49 (*ppT)->Release();
50 *ppT = nullptr;
54 using namespace Microsoft::WRL;
56 CAESinkWASAPI::CAESinkWASAPI()
58 m_channelLayout.Reset();
61 CAESinkWASAPI::~CAESinkWASAPI()
65 void CAESinkWASAPI::Register()
67 AE::AESinkRegEntry reg;
68 reg.sinkName = "WASAPI";
69 reg.createFunc = CAESinkWASAPI::Create;
70 reg.enumerateFunc = CAESinkWASAPI::EnumerateDevicesEx;
71 AE::CAESinkFactory::RegisterSink(reg);
74 std::unique_ptr<IAESink> CAESinkWASAPI::Create(std::string& device, AEAudioFormat& desiredFormat)
76 auto sink = std::make_unique<CAESinkWASAPI>();
77 if (sink->Initialize(desiredFormat, device))
78 return sink;
80 return {};
83 bool CAESinkWASAPI::Initialize(AEAudioFormat &format, std::string &device)
85 if (m_initialized)
86 return false;
88 m_device = device;
89 bool bdefault = false;
90 HRESULT hr = S_FALSE;
92 /* Save requested format */
93 /* Clear returned format */
94 sinkReqFormat = format.m_dataFormat;
95 sinkRetFormat = AE_FMT_INVALID;
97 if(StringUtils::EndsWithNoCase(device, std::string("default")))
98 bdefault = true;
100 if(!bdefault)
102 hr = CAESinkFactoryWin::ActivateWASAPIDevice(device, &m_pDevice);
103 EXIT_ON_FAILURE(hr, "Retrieval of WASAPI endpoint failed.")
106 if (!m_pDevice)
108 if(!bdefault)
110 CLog::LogF(LOGINFO,
111 "Could not locate the device named \"{}\" in the list of WASAPI endpoint devices. "
112 " Trying the default device...",
113 device);
116 std::string defaultId = CAESinkFactoryWin::GetDefaultDeviceId();
117 if (defaultId.empty())
119 CLog::LogF(LOGINFO, "Could not locate the default device id in the list of WASAPI endpoint devices.");
120 goto failed;
123 hr = CAESinkFactoryWin::ActivateWASAPIDevice(defaultId, &m_pDevice);
124 EXIT_ON_FAILURE(hr, "Could not retrieve the default WASAPI audio endpoint.")
126 device = defaultId;
129 hr = m_pDevice->Activate(m_pAudioClient.ReleaseAndGetAddressOf());
130 EXIT_ON_FAILURE(hr, "Activating the WASAPI endpoint device failed.")
132 if (!InitializeExclusive(format))
134 CLog::LogF(LOGINFO, "Could not Initialize Exclusive with that format");
135 goto failed;
138 /* get the buffer size and calculate the frames for AE */
139 m_pAudioClient->GetBufferSize(&m_uiBufferLen);
141 format.m_frames = m_uiBufferLen;
142 m_format = format;
143 sinkRetFormat = format.m_dataFormat;
145 hr = m_pAudioClient->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(m_pRenderClient.ReleaseAndGetAddressOf()));
146 EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI render client interface.")
148 hr = m_pAudioClient->GetService(IID_IAudioClock, reinterpret_cast<void**>(m_pAudioClock.ReleaseAndGetAddressOf()));
149 EXIT_ON_FAILURE(hr, "Could not initialize the WASAPI audio clock interface.")
151 hr = m_pAudioClock->GetFrequency(&m_clockFreq);
152 EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetFrequency failed.")
154 m_needDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
155 hr = m_pAudioClient->SetEventHandle(m_needDataEvent);
156 EXIT_ON_FAILURE(hr, "Could not set the WASAPI event handler.");
158 m_initialized = true;
159 m_isDirty = false;
161 // allow feeding less samples than buffer size
162 // if the device is opened exclusive and event driven, provided samples must match buffersize
163 // ActiveAE tries to align provided samples with buffer size but cannot guarantee (e.g. transcoding)
164 // this can be avoided by dropping the event mode which has not much benefit; SoftAE polls anyway
165 m_buffer.resize(format.m_frames * format.m_frameSize);
166 m_bufferPtr = 0;
168 return true;
170 failed:
171 CLog::LogF(LOGERROR, "WASAPI initialization failed.");
172 SafeRelease(&m_pDevice);
173 if(m_needDataEvent)
175 CloseHandle(m_needDataEvent);
176 m_needDataEvent = 0;
179 return false;
182 void CAESinkWASAPI::Deinitialize()
184 if (!m_initialized && !m_isDirty)
185 return;
187 if (m_running)
191 m_pAudioClient->Stop(); //stop the audio output
192 m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
193 m_sinkFrames = 0;
195 catch (...)
197 CLog::LogF(LOGDEBUG, "Invalidated AudioClient - Releasing");
200 m_running = false;
202 CloseHandle(m_needDataEvent);
204 m_pRenderClient = nullptr;
205 m_pAudioClient = nullptr;
206 m_pAudioClock = nullptr;
207 SafeRelease(&m_pDevice);
209 m_initialized = false;
211 m_bufferPtr = 0;
215 * @brief rescale uint64_t without overflowing on large values
217 static uint64_t rescale_u64(uint64_t val, uint64_t num, uint64_t den)
219 return ((val / den) * num) + (((val % den) * num) / den);
223 void CAESinkWASAPI::GetDelay(AEDelayStatus& status)
225 HRESULT hr;
226 uint64_t pos, tick;
227 int retries = 0;
229 if (!m_initialized)
230 goto failed;
232 do {
233 hr = m_pAudioClock->GetPosition(&pos, &tick);
234 } while (hr != S_OK && ++retries < 100);
235 EXIT_ON_FAILURE(hr, "Retrieval of IAudioClock::GetPosition failed.")
237 status.delay = (double)(m_sinkFrames + m_bufferPtr) / m_format.m_sampleRate - (double)pos / m_clockFreq;
238 status.tick = rescale_u64(tick, CurrentHostFrequency(), 10000000); /* convert from 100ns back to qpc ticks */
239 return;
240 failed:
241 status.SetDelay(0);
244 double CAESinkWASAPI::GetCacheTotal()
246 if (!m_initialized)
247 return 0.0;
249 return m_sinkLatency;
252 unsigned int CAESinkWASAPI::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
254 if (!m_initialized)
255 return 0;
257 HRESULT hr;
258 BYTE* buf;
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 {}", CWIN32Util::FormatHRESULT(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 {}", CWIN32Util::FormatHRESULT(hr));
291 #endif
292 m_isDirty = true; //flag new device or re-init needed
293 return INT_MAX;
296 hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested,
297 AUDCLNT_BUFFERFLAGS_SILENT); //pass back to audio driver
298 if (FAILED(hr))
300 #ifdef _DEBUG
301 CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", CWIN32Util::FormatHRESULT(hr));
302 #endif
303 m_isDirty = true; //flag new device or re-init needed
304 return INT_MAX;
306 m_sinkFrames += NumFramesRequested;
308 hr = m_pAudioClient->Start(); //start the audio driver running
309 if (FAILED(hr))
310 CLog::LogF(LOGERROR, "AudioClient Start Failed");
311 m_running = true; //signal that we're processing frames
312 return 0U;
315 #ifndef _DEBUG
316 /* Get clock time for latency checks */
317 QueryPerformanceFrequency(&timerFreq);
318 QueryPerformanceCounter(&timerStart);
319 #endif
321 /* Wait for Audio Driver to tell us it's got a buffer available */
322 DWORD eventAudioCallback;
323 eventAudioCallback = WaitForSingleObject(m_needDataEvent, 1100);
325 if(eventAudioCallback != WAIT_OBJECT_0 || !&buf)
327 CLog::LogF(LOGERROR, "Endpoint Buffer timed out");
328 return INT_MAX;
331 if (!m_running)
332 return 0;
334 #ifndef _DEBUG
335 QueryPerformanceCounter(&timerStop);
336 LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
337 double timerElapsed = (double) timerDiff * 1000.0 / (double) timerFreq.QuadPart;
338 m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
340 if (m_avgTimeWaiting < 3.0)
342 CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
343 (int)m_avgTimeWaiting);
345 #endif
347 hr = m_pRenderClient->GetBuffer(NumFramesRequested, &buf);
348 if (FAILED(hr))
350 #ifdef _DEBUG
351 CLog::LogF(LOGERROR, "GetBuffer failed due to {}", CWIN32Util::FormatHRESULT(hr));
352 #endif
353 return INT_MAX;
356 // fill buffer
357 memcpy(buf, m_bufferPtr == 0 ? buffer : m_buffer.data(),
358 NumFramesRequested * m_format.m_frameSize);
359 m_bufferPtr = 0;
361 hr = m_pRenderClient->ReleaseBuffer(NumFramesRequested, 0); //pass back to audio driver
362 if (FAILED(hr))
364 #ifdef _DEBUG
365 CLog::LogF(LOGDEBUG, "ReleaseBuffer failed due to {}.", CWIN32Util::FormatHRESULT(hr));
366 #endif
367 return INT_MAX;
369 m_sinkFrames += NumFramesRequested;
371 if (FramesToCopy != frames)
373 m_bufferPtr = frames-FramesToCopy;
374 memcpy(m_buffer.data(), buffer + FramesToCopy * m_format.m_frameSize,
375 m_bufferPtr * m_format.m_frameSize);
378 return frames;
381 void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
383 CAEDeviceInfo deviceInfo;
384 CAEChannelInfo deviceChannels;
385 bool add192 = false;
386 bool add48 = false;
388 WAVEFORMATEXTENSIBLE wfxex = {};
389 HRESULT hr;
391 const bool onlyPT = (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::WindowsDeviceFamily::Xbox);
393 for(RendererDetail& details : CAESinkFactoryWin::GetRendererDetails())
395 deviceInfo.m_channels.Reset();
396 deviceInfo.m_dataFormats.clear();
397 deviceInfo.m_sampleRates.clear();
398 deviceInfo.m_streamTypes.clear();
399 deviceChannels.Reset();
400 add192 = false;
401 add48 = false;
403 for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
405 if (details.uiChannelMask & WASAPIChannelOrder[c])
406 deviceChannels += AEChannelNames[c];
409 IAEWASAPIDevice* pDevice;
410 hr = CAESinkFactoryWin::ActivateWASAPIDevice(details.strDeviceId, &pDevice);
411 if (FAILED(hr))
413 CLog::LogF(LOGERROR, "Retrieval of WASAPI endpoint failed.");
414 goto failed;
417 ComPtr<IAudioClient> pClient = nullptr;
418 hr = pDevice->Activate(pClient.GetAddressOf());
419 if (SUCCEEDED(hr))
421 /* Test format DTS-HD-HR */
422 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
423 wfxex.Format.nSamplesPerSec = 192000;
424 wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
425 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
426 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
427 wfxex.Format.wBitsPerSample = 16;
428 wfxex.Samples.wValidBitsPerSample = 16;
429 wfxex.Format.nChannels = 2;
430 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
431 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
432 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
433 if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
435 CLog::LogF(LOGINFO,
436 "Exclusive mode is not allowed on device \"{}\", check device settings.",
437 details.strDescription);
438 SafeRelease(&pDevice);
439 continue;
441 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
443 if(FAILED(hr))
445 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
446 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD),
447 details.strDescription);
450 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
451 add192 = true;
454 /* Test format DTS-HD */
455 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
456 wfxex.Format.nSamplesPerSec = 192000;
457 wfxex.dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND;
458 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
459 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS_HD;
460 wfxex.Format.wBitsPerSample = 16;
461 wfxex.Samples.wValidBitsPerSample = 16;
462 wfxex.Format.nChannels = 8;
463 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
464 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
465 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
466 if (hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
468 CLog::LogF(LOGINFO,
469 "Exclusive mode is not allowed on device \"{}\", check device settings.",
470 details.strDescription);
471 SafeRelease(&pDevice);
472 continue;
474 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
476 if(FAILED(hr))
478 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
479 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_DTSHD_MA),
480 details.strDescription);
483 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
484 add192 = true;
487 /* Test format Dolby TrueHD */
488 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_MLP;
489 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
490 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
492 if(FAILED(hr))
494 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
495 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_TRUEHD),
496 details.strDescription);
499 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_TRUEHD);
500 add192 = true;
503 /* Test format Dolby EAC3 */
504 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
505 wfxex.Format.nChannels = 2;
506 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
507 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
508 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
509 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
511 if(FAILED(hr))
513 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
514 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_EAC3),
515 details.strDescription);
518 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_EAC3);
519 add192 = true;
522 /* Test format DTS */
523 wfxex.Format.nSamplesPerSec = 48000;
524 wfxex.dwChannelMask = KSAUDIO_SPEAKER_5POINT1;
525 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DTS;
526 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
527 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
528 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
529 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
531 if(FAILED(hr))
533 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
534 "STREAM_TYPE_DTS", details.strDescription);
537 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
538 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
539 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
540 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
541 add48 = true;
544 /* Test format Dolby AC3 */
545 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL;
546 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
547 if (SUCCEEDED(hr) || details.eDeviceType == AE_DEVTYPE_HDMI)
549 if(FAILED(hr))
551 CLog::LogF(LOGINFO, "stream type \"{}\" on device \"{}\" seems to be not supported.",
552 CAEUtil::StreamTypeToStr(CAEStreamInfo::STREAM_TYPE_AC3),
553 details.strDescription);
556 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
557 add48 = true;
560 /* Test format for PCM format iteration */
561 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
562 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
563 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
564 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
566 for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
568 if (p < AE_FMT_FLOAT)
569 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
570 wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat) p);
571 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
572 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
573 if (p == AE_FMT_S24NE4MSB)
575 wfxex.Samples.wValidBitsPerSample = 24;
577 else if (p <= AE_FMT_S24NE4 && p >= AE_FMT_S24BE4)
579 // not supported
580 continue;
582 else
584 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
587 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
588 if (SUCCEEDED(hr))
589 deviceInfo.m_dataFormats.push_back((AEDataFormat) p);
592 /* Test format for sample rate iteration */
593 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
594 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
595 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
596 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
598 // 16 bits is most widely supported and likely to have the widest range of sample rates
599 if (deviceInfo.m_dataFormats.empty() ||
600 std::find(deviceInfo.m_dataFormats.cbegin(), deviceInfo.m_dataFormats.cend(),
601 AE_FMT_S16NE) != deviceInfo.m_dataFormats.cend())
603 wfxex.Format.wBitsPerSample = 16;
604 wfxex.Samples.wValidBitsPerSample = 16;
606 else
608 const AEDataFormat fmt = deviceInfo.m_dataFormats.front();
609 wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits(fmt);
610 wfxex.Samples.wValidBitsPerSample =
611 (fmt == AE_FMT_S24NE4MSB ? 24 : wfxex.Format.wBitsPerSample);
614 wfxex.Format.nChannels = 2;
615 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
616 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
618 for (int j = 0; j < WASAPISampleRateCount; j++)
620 wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
621 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
622 hr = pClient->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, &wfxex.Format, NULL);
623 if (SUCCEEDED(hr))
624 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
625 else if (wfxex.Format.nSamplesPerSec == 192000 && add192)
627 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
628 CLog::LogF(LOGINFO, "sample rate 192khz on device \"{}\" seems to be not supported.",
629 details.strDescription);
631 else if (wfxex.Format.nSamplesPerSec == 48000 && add48)
633 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
634 CLog::LogF(LOGINFO, "sample rate 48khz on device \"{}\" seems to be not supported.",
635 details.strDescription);
638 pClient = nullptr;
640 else
642 CLog::LogF(LOGDEBUG, "Failed to activate device for passthrough capability testing.");
645 deviceInfo.m_deviceName = details.strDeviceId;
646 deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
647 deviceInfo.m_displayNameExtra = std::string("WASAPI: ").append(details.strDescription);
648 deviceInfo.m_deviceType = details.eDeviceType;
649 deviceInfo.m_channels = deviceChannels;
651 /* Store the device info */
652 deviceInfo.m_wantsIECPassthrough = true;
653 deviceInfo.m_onlyPassthrough = onlyPT;
655 if (!deviceInfo.m_streamTypes.empty())
656 deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
658 deviceInfoList.push_back(deviceInfo);
660 if (details.bDefault)
662 deviceInfo.m_deviceName = std::string("default");
663 deviceInfo.m_displayName = std::string("default");
664 deviceInfo.m_displayNameExtra = std::string("");
665 deviceInfo.m_wantsIECPassthrough = true;
666 deviceInfo.m_onlyPassthrough = onlyPT;
667 deviceInfoList.push_back(deviceInfo);
670 SafeRelease(&pDevice);
672 return;
674 failed:
676 if (FAILED(hr))
677 CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).",
678 CWIN32Util::FormatHRESULT(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 ({})", CWIN32Util::FormatHRESULT(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 ({})", CWIN32Util::FormatHRESULT(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, {}", CWIN32Util::FormatHRESULT(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 : {}", CWIN32Util::FormatHRESULT(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 : {}", CWIN32Util::FormatHRESULT(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 - {}.",
966 CWIN32Util::FormatHRESULT(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 : {}", CWIN32Util::FormatHRESULT(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();