[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkXAudio.cpp
blob792c8ffc6a501c1e82fd2d820ae3ec4437410a4a
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 "AESinkXAudio.h"
11 #include "ServiceBroker.h"
12 #include "cores/AudioEngine/AESinkFactory.h"
13 #include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
14 #include "cores/AudioEngine/Utils/AEDeviceInfo.h"
15 #include "cores/AudioEngine/Utils/AEUtil.h"
16 #include "utils/SystemInfo.h"
17 #include "utils/log.h"
19 #include "platform/win32/CharsetConverter.h"
21 #include <algorithm>
22 #include <stdint.h>
24 #include <ksmedia.h>
26 using namespace Microsoft::WRL;
28 namespace
30 constexpr int XAUDIO_BUFFERS_IN_QUEUE = 2;
32 HRESULT KXAudio2Create(IXAudio2** ppXAudio2,
33 UINT32 Flags X2DEFAULT(0),
34 XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR))
36 typedef HRESULT(__stdcall * XAudio2CreateInfoFunc)(_Outptr_ IXAudio2**, UINT32,
37 XAUDIO2_PROCESSOR);
38 static HMODULE dll = NULL;
39 static XAudio2CreateInfoFunc XAudio2CreateFn = nullptr;
41 if (dll == NULL)
43 dll = LoadLibraryEx(L"xaudio2_9redist.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
45 if (dll == NULL)
47 dll = LoadLibraryEx(L"xaudio2_9.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
49 if (dll == NULL)
51 // Windows 8 compatibility
52 dll = LoadLibraryEx(L"xaudio2_8.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
54 if (dll == NULL)
55 return HRESULT_FROM_WIN32(GetLastError());
59 XAudio2CreateFn = (XAudio2CreateInfoFunc)(void*)GetProcAddress(dll, "XAudio2Create");
60 if (!XAudio2CreateFn)
62 return HRESULT_FROM_WIN32(GetLastError());
66 if (XAudio2CreateFn)
67 return (*XAudio2CreateFn)(ppXAudio2, Flags, XAudio2Processor);
68 else
69 return E_FAIL;
72 } // namespace
74 extern const char* WASAPIErrToStr(HRESULT err);
76 template <class TVoice>
77 inline void SafeDestroyVoice(TVoice **ppVoice)
79 if (*ppVoice)
81 (*ppVoice)->DestroyVoice();
82 *ppVoice = nullptr;
86 CAESinkXAudio::CAESinkXAudio()
88 HRESULT hr = KXAudio2Create(m_xAudio2.ReleaseAndGetAddressOf(), 0);
89 if (FAILED(hr))
91 CLog::LogF(LOGERROR, "XAudio initialization failed, error {:X}.", hr);
93 #ifdef _DEBUG
94 else
96 XAUDIO2_DEBUG_CONFIGURATION config = {};
97 config.BreakMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_API_CALLS | XAUDIO2_LOG_STREAMING;
98 config.TraceMask = XAUDIO2_LOG_ERRORS | XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_API_CALLS | XAUDIO2_LOG_STREAMING;
99 config.LogTiming = true;
100 config.LogFunctionName = true;
101 m_xAudio2->SetDebugConfiguration(&config, 0);
103 #endif // _DEBUG
105 // Get performance counter frequency for latency calculations
106 QueryPerformanceFrequency(&m_timerFreq);
109 CAESinkXAudio::~CAESinkXAudio()
111 if (m_xAudio2)
112 m_xAudio2.Reset();
115 void CAESinkXAudio::Register()
117 AE::AESinkRegEntry reg;
118 reg.sinkName = "XAUDIO";
119 reg.createFunc = CAESinkXAudio::Create;
120 reg.enumerateFunc = CAESinkXAudio::EnumerateDevicesEx;
121 AE::CAESinkFactory::RegisterSink(reg);
124 std::unique_ptr<IAESink> CAESinkXAudio::Create(std::string& device, AEAudioFormat& desiredFormat)
126 auto sink = std::make_unique<CAESinkXAudio>();
128 if (!sink->m_xAudio2)
130 CLog::LogF(LOGERROR, "XAudio2 not loaded.");
131 return {};
134 if (sink->Initialize(desiredFormat, device))
135 return sink;
137 return {};
140 bool CAESinkXAudio::Initialize(AEAudioFormat &format, std::string &device)
142 if (m_initialized)
143 return false;
145 /* Save requested format */
146 AEDataFormat reqFormat = format.m_dataFormat;
148 if (!InitializeInternal(device, format))
150 CLog::LogF(LOGINFO, "could not Initialize voices with format {}",
151 CAEUtil::DataFormatToStr(reqFormat));
152 CLog::LogF(LOGERROR, "XAudio initialization failed");
153 return false;
156 m_initialized = true;
157 m_isDirty = false;
159 return true;
162 void CAESinkXAudio::Deinitialize()
164 if (!m_initialized && !m_isDirty)
165 return;
167 if (m_running)
171 m_sourceVoice->Stop();
172 m_sourceVoice->FlushSourceBuffers();
174 // Stop and FlushSourceBuffers are async, wait for queued buffers to be released by XAudio2.
175 // callbacks don't seem to be called otherwise, with memory leakage.
176 XAUDIO2_VOICE_STATE state{};
179 if (WAIT_OBJECT_0 != WaitForSingleObject(m_voiceCallback.mBufferEnd.get(), 500))
181 CLog::LogF(LOGERROR, "timeout waiting for buffer flush - possible buffer memory leak");
182 break;
184 m_sourceVoice->GetState(&state, 0);
185 } while (state.BuffersQueued > 0);
187 m_sinkFrames = 0;
188 m_framesInBuffers = 0;
190 catch (...)
192 CLog::LogF(LOGERROR, "invalidated source voice - Releasing");
195 m_running = false;
197 SafeDestroyVoice(&m_sourceVoice);
198 SafeDestroyVoice(&m_masterVoice);
200 m_initialized = false;
203 void CAESinkXAudio::GetDelay(AEDelayStatus& status)
205 if (!m_initialized)
207 status.SetDelay(0.0);
208 return;
211 XAUDIO2_VOICE_STATE state;
212 m_sourceVoice->GetState(&state, 0);
214 status.SetDelay(static_cast<double>(m_sinkFrames - state.SamplesPlayed) / m_format.m_sampleRate);
215 return;
218 double CAESinkXAudio::GetCacheTotal()
220 if (!m_initialized)
221 return 0.0;
223 return static_cast<double>(XAUDIO_BUFFERS_IN_QUEUE * m_format.m_frames) / m_format.m_sampleRate;
226 double CAESinkXAudio::GetLatency()
228 if (!m_initialized || !m_xAudio2)
229 return 0.0;
231 XAUDIO2_PERFORMANCE_DATA perfData;
232 m_xAudio2->GetPerformanceData(&perfData);
234 return static_cast<double>(perfData.CurrentLatencyInSamples) / m_format.m_sampleRate;
237 unsigned int CAESinkXAudio::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
239 if (!m_initialized)
240 return 0;
242 HRESULT hr = S_OK;
244 LARGE_INTEGER timerStart;
245 LARGE_INTEGER timerStop;
247 XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(data, frames, offset);
249 if (!m_running) //first time called, pre-fill buffer then start voice
251 m_sourceVoice->Stop();
252 hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
253 if (FAILED(hr))
255 CLog::LogF(LOGERROR, "voice submit buffer failed due to {}", WASAPIErrToStr(hr));
256 delete xbuffer.pContext;
257 return 0;
259 hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
260 if (FAILED(hr))
262 CLog::LogF(LOGERROR, "voice start failed due to {}", WASAPIErrToStr(hr));
263 m_isDirty = true; //flag new device or re-init needed
264 delete xbuffer.pContext;
265 return INT_MAX;
267 m_sinkFrames += frames;
268 m_framesInBuffers += frames;
269 m_running = true; //signal that we're processing frames
270 return frames;
273 /* Get clock time for latency checks */
274 QueryPerformanceCounter(&timerStart);
276 /* Wait for Audio Driver to tell us it's got a buffer available */
277 while (m_format.m_frames * XAUDIO_BUFFERS_IN_QUEUE <= m_framesInBuffers.load())
279 DWORD eventAudioCallback;
280 eventAudioCallback = WaitForSingleObjectEx(m_voiceCallback.mBufferEnd.get(), 1100, TRUE);
281 if (eventAudioCallback != WAIT_OBJECT_0)
283 CLog::LogF(LOGERROR, "voice buffer timed out");
284 delete xbuffer.pContext;
285 return INT_MAX;
289 if (!m_running)
290 return 0;
292 QueryPerformanceCounter(&timerStop);
293 const LONGLONG timerDiff = timerStop.QuadPart - timerStart.QuadPart;
294 const double timerElapsed = static_cast<double>(timerDiff) * 1000.0 / m_timerFreq.QuadPart;
295 m_avgTimeWaiting += (timerElapsed - m_avgTimeWaiting) * 0.5;
297 if (m_avgTimeWaiting < 3.0)
299 CLog::LogF(LOGDEBUG, "Possible AQ Loss: Avg. Time Waiting for Audio Driver callback : {}msec",
300 (int)m_avgTimeWaiting);
303 hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
304 if (FAILED(hr))
306 CLog::LogF(LOGERROR, "submiting buffer failed due to {}", WASAPIErrToStr(hr));
307 delete xbuffer.pContext;
308 return INT_MAX;
311 m_sinkFrames += frames;
312 m_framesInBuffers += frames;
314 return frames;
317 void CAESinkXAudio::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
319 HRESULT hr = S_OK;
320 CAEDeviceInfo deviceInfo;
321 CAEChannelInfo deviceChannels;
322 WAVEFORMATEXTENSIBLE wfxex = {};
323 UINT32 eflags = 0; // XAUDIO2_DEBUG_ENGINE;
324 IXAudio2MasteringVoice* mMasterVoice = nullptr;
325 IXAudio2SourceVoice* mSourceVoice = nullptr;
326 Microsoft::WRL::ComPtr<IXAudio2> xaudio2;
328 // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
329 const AUDIO_STREAM_CATEGORY streamCategory{
330 CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
331 ? AudioCategory_Media
332 : AudioCategory_ForegroundOnlyMedia};
334 hr = KXAudio2Create(xaudio2.ReleaseAndGetAddressOf(), eflags);
335 if (FAILED(hr))
337 CLog::LogF(LOGERROR, "failed to activate XAudio for capability testing ({})",
338 WASAPIErrToStr(hr));
339 return;
342 for (RendererDetail& details : CAESinkFactoryWin::GetRendererDetailsWinRT())
344 deviceInfo.m_channels.Reset();
345 deviceInfo.m_dataFormats.clear();
346 deviceInfo.m_sampleRates.clear();
347 deviceChannels.Reset();
349 for (unsigned int c = 0; c < WASAPI_SPEAKER_COUNT; c++)
351 if (details.uiChannelMask & WASAPIChannelOrder[c])
352 deviceChannels += AEChannelNames[c];
355 const std::wstring deviceId = KODI::PLATFORM::WINDOWS::ToW(details.strDeviceId);
357 /* Test format for PCM format iteration */
358 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
359 wfxex.Format.nSamplesPerSec = 48000;
360 wfxex.Format.nChannels = 2;
361 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
362 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
363 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
365 hr = xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels,
366 wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(), nullptr,
367 streamCategory);
369 if (FAILED(hr))
371 CLog::LogF(LOGERROR, "failed to create mastering voice (:X)", hr);
372 return;
375 for (int p = AE_FMT_FLOAT; p > AE_FMT_INVALID; p--)
377 if (p < AE_FMT_FLOAT)
378 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
379 wfxex.Format.wBitsPerSample = CAEUtil::DataFormatToBits((AEDataFormat)p);
380 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
381 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
382 if (p == AE_FMT_S24NE4MSB)
384 wfxex.Samples.wValidBitsPerSample = 24;
386 else if (p <= AE_FMT_S24NE4 && p >= AE_FMT_S24BE4)
388 // not supported
389 continue;
391 else
393 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
396 SafeDestroyVoice(&mSourceVoice);
397 hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
399 if (SUCCEEDED(hr))
400 deviceInfo.m_dataFormats.push_back((AEDataFormat)p);
403 /* Test format for sample rate iteration */
404 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
405 wfxex.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
406 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
407 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
408 wfxex.Format.wBitsPerSample = 16;
409 wfxex.Samples.wValidBitsPerSample = 16;
410 wfxex.Format.nChannels = 2;
411 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
412 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
414 for (int j = 0; j < WASAPISampleRateCount; j++)
416 if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE ||
417 WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE)
418 continue;
420 SafeDestroyVoice(&mSourceVoice);
421 SafeDestroyVoice(&mMasterVoice);
423 wfxex.Format.nSamplesPerSec = WASAPISampleRates[j];
424 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
426 if (SUCCEEDED(xaudio2->CreateMasteringVoice(&mMasterVoice, wfxex.Format.nChannels,
427 wfxex.Format.nSamplesPerSec, 0, deviceId.c_str(),
428 nullptr, streamCategory)))
430 hr = xaudio2->CreateSourceVoice(&mSourceVoice, &wfxex.Format);
432 if (SUCCEEDED(hr))
433 deviceInfo.m_sampleRates.push_back(WASAPISampleRates[j]);
437 SafeDestroyVoice(&mSourceVoice);
438 SafeDestroyVoice(&mMasterVoice);
440 deviceInfo.m_deviceName = details.strDeviceId;
441 deviceInfo.m_displayName = details.strWinDevType.append(details.strDescription);
442 deviceInfo.m_displayNameExtra = std::string("XAudio: ").append(details.strDescription);
443 deviceInfo.m_deviceType = details.eDeviceType;
444 deviceInfo.m_channels = deviceChannels;
446 /* Store the device info */
447 deviceInfo.m_wantsIECPassthrough = true;
448 deviceInfo.m_onlyPCM = true;
450 if (!deviceInfo.m_streamTypes.empty())
451 deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
453 deviceInfoList.push_back(deviceInfo);
455 if (details.bDefault)
457 deviceInfo.m_deviceName = std::string("default");
458 deviceInfo.m_displayName = std::string("default");
459 deviceInfo.m_displayNameExtra = std::string("");
460 deviceInfo.m_wantsIECPassthrough = true;
461 deviceInfo.m_onlyPCM = true;
462 deviceInfoList.push_back(deviceInfo);
467 bool CAESinkXAudio::InitializeInternal(std::string deviceId, AEAudioFormat &format)
469 const std::wstring device = KODI::PLATFORM::WINDOWS::ToW(deviceId);
470 WAVEFORMATEXTENSIBLE wfxex = {};
472 if ( format.m_dataFormat <= AE_FMT_FLOAT
473 || format.m_dataFormat == AE_FMT_RAW)
474 CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
475 else
477 // planar formats are currently not supported by this sink
478 format.m_dataFormat = AE_FMT_FLOAT;
479 CAESinkFactoryWin::BuildWaveFormatExtensible(format, wfxex);
482 /* Test for incomplete format and provide defaults */
483 if (format.m_sampleRate == 0 ||
484 format.m_channelLayout == CAEChannelInfo(nullptr) ||
485 format.m_dataFormat <= AE_FMT_INVALID ||
486 format.m_dataFormat >= AE_FMT_MAX ||
487 format.m_channelLayout.Count() == 0)
489 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
490 wfxex.Format.nChannels = 2;
491 wfxex.Format.nSamplesPerSec = 44100L;
492 wfxex.Format.wBitsPerSample = 16;
493 wfxex.Format.nBlockAlign = 4;
494 wfxex.Samples.wValidBitsPerSample = 16;
495 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
496 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nBlockAlign * wfxex.Format.nSamplesPerSec;
497 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
498 wfxex.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
501 const bool bdefault = deviceId.find("default") != std::string::npos;
503 HRESULT hr;
504 IXAudio2MasteringVoice* pMasterVoice = nullptr;
505 const wchar_t* pDevice = device.c_str();
506 // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
507 const AUDIO_STREAM_CATEGORY streamCategory{
508 CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
509 ? AudioCategory_Media
510 : AudioCategory_ForegroundOnlyMedia};
512 if (!bdefault)
514 hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
515 wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
516 streamCategory);
519 if (!pMasterVoice)
521 if (!bdefault)
523 CLog::LogF(LOGINFO,
524 "could not locate the device named \"{}\" in the list of Xaudio endpoint devices. "
525 "Trying the default device...",
526 KODI::PLATFORM::WINDOWS::FromW(device));
528 // smartphone issue: providing device ID (even default ID) causes E_NOINTERFACE result
529 // workaround: device = nullptr will initialize default audio endpoint
530 pDevice = nullptr;
531 hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
532 wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
533 streamCategory);
534 if (FAILED(hr) || !pMasterVoice)
536 CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).",
537 WASAPIErrToStr(hr));
538 return false;
542 m_masterVoice = pMasterVoice;
544 int closestMatch = 0;
545 unsigned int requestedChannels = 0;
546 unsigned int noOfCh = 0;
548 hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
549 if (SUCCEEDED(hr))
551 CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize");
552 goto initialize;
555 if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
556 return false;
558 if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
559 CLog::LogFC(LOGDEBUG, LOGAUDIO,
560 "CreateSourceVoice failed ({}) - trying to find a compatible format",
561 WASAPIErrToStr(hr));
563 requestedChannels = wfxex.Format.nChannels;
565 /* The requested format is not supported by the device. Find something that works */
566 for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
568 // if requested layout is not supported, try standard layouts with at least
569 // the number of channels as requested
570 // as the last resort try stereo
571 if (layout == ARRAYSIZE(layoutsList))
573 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
574 wfxex.Format.nChannels = 2;
576 else if (layout >= 0)
578 wfxex.dwChannelMask = CAESinkFactoryWin::ChLayoutToChMask(layoutsList[layout], &noOfCh);
579 wfxex.Format.nChannels = noOfCh;
580 if (noOfCh < requestedChannels)
581 continue;
584 for (int j = 0; j < sizeof(testFormats)/sizeof(sampleFormat); j++)
586 closestMatch = -1;
588 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
589 wfxex.SubFormat = testFormats[j].subFormat;
590 wfxex.Format.wBitsPerSample = testFormats[j].bitsPerSample;
591 wfxex.Samples.wValidBitsPerSample = testFormats[j].validBitsPerSample;
592 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
594 for (int i = 0 ; i < WASAPISampleRateCount; i++)
596 if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE ||
597 WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE)
598 continue;
600 SafeDestroyVoice(&m_sourceVoice);
601 SafeDestroyVoice(&m_masterVoice);
603 wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
604 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
606 hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
607 wfxex.Format.nSamplesPerSec, 0, pDevice, nullptr,
608 streamCategory);
609 if (SUCCEEDED(hr))
611 hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
612 if (SUCCEEDED(hr))
614 /* If the current sample rate matches the source then stop looking and use it */
615 if ((WASAPISampleRates[i] == format.m_sampleRate) && (testFormats[j].subFormatType <= format.m_dataFormat))
616 goto initialize;
617 /* If this rate is closer to the source then the previous one, save it */
618 else if (closestMatch < 0 || abs((int)WASAPISampleRates[i] - (int)format.m_sampleRate) < abs((int)WASAPISampleRates[closestMatch] - (int)format.m_sampleRate))
619 closestMatch = i;
623 if (FAILED(hr))
624 CLog::LogF(LOGERROR, "creating voices failed ({})", WASAPIErrToStr(hr));
627 if (closestMatch >= 0)
629 // Closest match may be different from the last successful sample rate tested
630 SafeDestroyVoice(&m_sourceVoice);
631 SafeDestroyVoice(&m_masterVoice);
633 wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
634 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
636 if (SUCCEEDED(m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
637 wfxex.Format.nSamplesPerSec, 0, pDevice,
638 nullptr, streamCategory)) &&
639 SUCCEEDED(m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0,
640 XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback)))
641 goto initialize;
646 CLog::LogF(LOGERROR, "unable to locate a supported output format for the device. Check the "
647 "speaker settings in the control panel.");
649 /* We couldn't find anything supported. This should never happen */
650 /* unless the user set the wrong speaker setting in the control panel */
651 return false;
653 initialize:
655 CAEChannelInfo channelLayout;
656 CAESinkFactoryWin::AEChannelsFromSpeakerMask(channelLayout, wfxex.dwChannelMask);
657 format.m_channelLayout = channelLayout;
659 /* Set up returned sink format for engine */
660 if (format.m_dataFormat != AE_FMT_RAW)
662 if (wfxex.Format.wBitsPerSample == 32)
664 if (wfxex.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
665 format.m_dataFormat = AE_FMT_FLOAT;
666 else if (wfxex.Samples.wValidBitsPerSample == 32)
667 format.m_dataFormat = AE_FMT_S32NE;
668 else
669 format.m_dataFormat = AE_FMT_S24NE4MSB;
671 else if (wfxex.Format.wBitsPerSample == 24)
672 format.m_dataFormat = AE_FMT_S24NE3;
673 else
674 format.m_dataFormat = AE_FMT_S16NE;
677 format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
678 format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
680 if (format.m_dataFormat == AE_FMT_RAW)
681 format.m_dataFormat = AE_FMT_S16NE;
683 hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
684 if (FAILED(hr))
686 CLog::LogF(LOGERROR, "Voice start failed : {}", WASAPIErrToStr(hr));
687 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
688 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
689 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
690 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
691 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
692 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
693 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
694 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
695 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
696 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
697 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
698 return false;
701 XAUDIO2_PERFORMANCE_DATA perfData;
702 m_xAudio2->GetPerformanceData(&perfData);
703 if (!perfData.TotalSourceVoiceCount)
705 CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", WASAPIErrToStr(hr));
706 return false;
709 format.m_frames = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks
711 m_format = format;
713 CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}",
714 CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
715 wfxex.Format.nChannels);
717 m_sourceVoice->Stop();
719 CLog::LogF(LOGDEBUG, "Initializing XAudio with the following parameters:");
720 CLog::Log(LOGDEBUG, " Audio Device : {}", KODI::PLATFORM::WINDOWS::FromW(device));
721 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
722 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
723 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
724 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
725 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
726 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
727 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
728 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
729 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
730 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
731 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
732 CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames);
733 CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize);
735 return true;
738 void CAESinkXAudio::Drain()
740 if(!m_sourceVoice)
741 return;
743 if (m_running)
747 // Contrary to MS doc, the voice must play a buffer with end of stream flag for the voice
748 // SamplesPlayed counter to be reset.
749 // Per MS doc, Discontinuity() may not take effect until after the entire buffer queue is
750 // consumed, which wouldn't invoke the callback or reset the voice stats.
751 // Solution: submit a 1 sample buffer with end of stream flag and wait for StreamEnd callback
752 // The StreamEnd event is manual reset so that it cannot be missed even if raised before this
753 // code starts waiting for it
755 AddEndOfStreamPacket();
757 constexpr uint32_t waitSafety = 100; // extra ms wait in case of scheduler issue
758 DWORD waitRc =
759 WaitForSingleObject(m_voiceCallback.m_StreamEndEvent,
760 m_framesInBuffers * 1000 / m_format.m_sampleRate + waitSafety);
762 if (waitRc != WAIT_OBJECT_0)
764 if (waitRc == WAIT_FAILED)
766 //! @todo use FormatMessage for a human readable error message
767 CLog::LogF(LOGERROR,
768 "error WAIT_FAILED while waiting for queued buffers to drain. GetLastError:{}",
769 GetLastError());
771 else
773 CLog::LogF(LOGERROR, "error {} while waiting for queued buffers to drain.", waitRc);
777 m_sourceVoice->Stop();
778 ResetEvent(m_voiceCallback.m_StreamEndEvent);
780 m_sinkFrames = 0;
781 m_framesInBuffers = 0;
783 catch (...)
785 CLog::LogF(LOGERROR, "invalidated source voice - Releasing");
788 m_running = false;
791 bool CAESinkXAudio::AddEndOfStreamPacket()
793 constexpr unsigned int frames{1};
795 XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(nullptr, frames, 0);
796 xbuffer.Flags = XAUDIO2_END_OF_STREAM;
798 HRESULT hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
800 if (hr != S_OK)
802 CLog::LogF(LOGERROR, "SubmitSourceBuffer failed due to {:X}", hr);
803 delete xbuffer.pContext;
804 return false;
807 m_sinkFrames += frames;
808 m_framesInBuffers += frames;
809 return true;
812 XAUDIO2_BUFFER CAESinkXAudio::BuildXAudio2Buffer(uint8_t** data,
813 unsigned int frames,
814 unsigned int offset)
816 const unsigned int dataLength{frames * m_format.m_frameSize};
818 struct buffer_ctx* ctx = new buffer_ctx;
819 ctx->data = new uint8_t[dataLength];
820 ctx->frames = frames;
821 ctx->sink = this;
823 if (data)
824 memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLength);
825 else
826 CAEUtil::GenerateSilence(m_format.m_dataFormat, m_format.m_frameSize, ctx->data, frames);
828 XAUDIO2_BUFFER xbuffer{};
829 xbuffer.AudioBytes = dataLength;
830 xbuffer.pAudioData = ctx->data;
831 xbuffer.pContext = ctx;
833 return xbuffer;