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.
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"
23 #include <Audioclient.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) \
39 CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
44 inline void SafeRelease(T
**ppT
)
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
))
82 bool CAESinkWASAPI::Initialize(AEAudioFormat
&format
, std::string
&device
)
88 bool bdefault
= 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")))
101 hr
= CAESinkFactoryWin::ActivateWASAPIDevice(device
, &m_pDevice
);
102 EXIT_ON_FAILURE(hr
, "Retrieval of WASAPI endpoint failed.")
110 "Could not locate the device named \"{}\" in the list of WASAPI endpoint devices. "
111 " Trying the default 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.");
122 hr
= CAESinkFactoryWin::ActivateWASAPIDevice(defaultId
, &m_pDevice
);
123 EXIT_ON_FAILURE(hr
, "Could not retrieve the default WASAPI audio endpoint.")
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");
137 /* get the buffer size and calculate the frames for AE */
138 m_pAudioClient
->GetBufferSize(&m_uiBufferLen
);
140 format
.m_frames
= m_uiBufferLen
;
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;
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
);
170 CLog::LogF(LOGERROR
, "WASAPI initialization failed.");
171 SafeRelease(&m_pDevice
);
174 CloseHandle(m_needDataEvent
);
181 void CAESinkWASAPI::Deinitialize()
183 if (!m_initialized
&& !m_isDirty
)
190 m_pAudioClient
->Stop(); //stop the audio output
191 m_pAudioClient
->Reset(); //flush buffer and reset audio clock stream position
196 CLog::LogF(LOGDEBUG
, "Invalidated AudioClient - Releasing");
201 CloseHandle(m_needDataEvent
);
203 m_pRenderClient
= nullptr;
204 m_pAudioClient
= nullptr;
205 m_pAudioClock
= nullptr;
206 SafeRelease(&m_pDevice
);
208 m_initialized
= false;
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
)
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 */
243 double CAESinkWASAPI::GetCacheTotal()
248 return m_sinkLatency
;
251 unsigned int CAESinkWASAPI::AddPackets(uint8_t **data
, unsigned int frames
, unsigned int offset
)
261 LARGE_INTEGER timerStart
;
262 LARGE_INTEGER timerStop
;
263 LARGE_INTEGER timerFreq
;
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
)
278 if (!m_running
) //first time called, pre-fill buffer then start audio client
280 hr
= m_pAudioClient
->Reset();
283 CLog::LogF(LOGERROR
, " AudioClient reset failed due to {}", WASAPIErrToStr(hr
));
286 hr
= m_pRenderClient
->GetBuffer(NumFramesRequested
, &buf
);
290 CLog::LogF(LOGERROR
, "GetBuffer failed due to {}", WASAPIErrToStr(hr
));
292 m_isDirty
= true; //flag new device or re-init needed
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
302 CLog::LogF(LOGDEBUG
, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr
));
304 m_isDirty
= true; //flag new device or re-init needed
307 m_sinkFrames
+= NumFramesRequested
;
309 hr
= m_pAudioClient
->Start(); //start the audio driver running
311 CLog::LogF(LOGERROR
, "AudioClient Start Failed");
312 m_running
= true; //signal that we're processing frames
317 /* Get clock time for latency checks */
318 QueryPerformanceFrequency(&timerFreq
);
319 QueryPerformanceCounter(&timerStart
);
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");
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
);
348 hr
= m_pRenderClient
->GetBuffer(NumFramesRequested
, &buf
);
352 CLog::LogF(LOGERROR
, "GetBuffer failed due to {}", WASAPIErrToStr(hr
));
358 memcpy(buf
, m_bufferPtr
== 0 ? buffer
: m_buffer
.data(),
359 NumFramesRequested
* m_format
.m_frameSize
);
362 hr
= m_pRenderClient
->ReleaseBuffer(NumFramesRequested
, flags
); //pass back to audio driver
366 CLog::LogF(LOGDEBUG
, "ReleaseBuffer failed due to {}.", WASAPIErrToStr(hr
));
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
);
382 void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList
&deviceInfoList
, bool force
)
384 CAEDeviceInfo deviceInfo
;
385 CAEChannelInfo deviceChannels
;
389 WAVEFORMATEXTENSIBLE wfxex
= {};
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();
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
);
414 CLog::LogF(LOGERROR
, "Retrieval of WASAPI endpoint failed.");
418 ComPtr
<IAudioClient
> pClient
= nullptr;
419 hr
= pDevice
->Activate(pClient
.GetAddressOf());
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
)
437 "Exclusive mode is not allowed on device \"{}\", check device settings.",
438 details
.strDescription
);
439 SafeRelease(&pDevice
);
442 if (SUCCEEDED(hr
) || details
.eDeviceType
== AE_DEVTYPE_HDMI
)
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
);
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
)
470 "Exclusive mode is not allowed on device \"{}\", check device settings.",
471 details
.strDescription
);
472 SafeRelease(&pDevice
);
475 if (SUCCEEDED(hr
) || details
.eDeviceType
== AE_DEVTYPE_HDMI
)
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
);
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
)
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
);
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
)
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
);
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
)
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
);
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
)
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
);
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
)
585 wfxex
.Samples
.wValidBitsPerSample
= wfxex
.Format
.wBitsPerSample
;
588 hr
= pClient
->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE
, &wfxex
.Format
, NULL
);
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;
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
);
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
);
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
);
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
);
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;
755 CLog::LogF(LOGINFO
, "Format is Supported - will attempt to 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
));
763 else if (format
.m_dataFormat
== AE_FMT_RAW
) //No sense in trying other formats for passthrough.
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;
791 matchNoChannelsOnly
= true;
793 CLog::Log(LOGWARNING
, "AESinkWASAPI: Match only number of audio channels as fallback");
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
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
++)
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 */
835 CLog::Log(LOGDEBUG
, "WASAPI: Trying Format: {}, {}, {}, {}", CAEUtil::DataFormatToStr(testFormats
[j
].subFormatType
),
836 wfxex
.Format
.nSamplesPerSec
,
837 wfxex
.Format
.wBitsPerSample
,
838 wfxex
.Samples
.wValidBitsPerSample
);
841 hr
= m_pAudioClient
->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE
, &wfxex
.Format
, NULL
);
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
))
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
))
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
;
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 */
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
;
896 format
.m_dataFormat
= AE_FMT_S24NE4MSB
;
898 else if (wfxex
.Format
.wBitsPerSample
== 24)
899 format
.m_dataFormat
= AE_FMT_S24NE3
;
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;
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
);
944 CLog::LogF(LOGERROR
, "GetBufferSize Failed : {}", WASAPIErrToStr(hr
));
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());
955 CLog::LogF(LOGERROR
, "Device Activation Failed : {}", WASAPIErrToStr(hr
));
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
);
965 CLog::LogF(LOGERROR
, "Failed to initialize WASAPI in exclusive mode {} - ({}).", HRESULT(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
);
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
);
992 CLog::LogF(LOGERROR
, "GetStreamLatency Failed : {}", WASAPIErrToStr(hr
));
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
);
1004 void CAESinkWASAPI::Drain()
1009 AEDelayStatus status
;
1012 KODI::TIME::Sleep(std::chrono::milliseconds(static_cast<int>(status
.GetDelay() * 500)));
1018 m_pAudioClient
->Stop(); //stop the audio output
1019 m_pAudioClient
->Reset(); //flush buffer and reset audio clock stream position
1024 CLog::LogF(LOGDEBUG
, "Invalidated AudioClient - Releasing");
1030 bool CAESinkWASAPI::IsUSBDevice()
1032 return m_pDevice
&& m_pDevice
->IsUSBDevice();