[XAudio2] avoid leak + fix voice creation for closest match
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkXAudio.cpp
blobadd7388a70a78fec95afa1e6d0bc0d5632ad95df
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 // ForegroundOnlyMedia/BackgroundCapableMedia replaced in Windows 10 by Movie/Media
506 const AUDIO_STREAM_CATEGORY streamCategory{
507 CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)
508 ? AudioCategory_Media
509 : AudioCategory_ForegroundOnlyMedia};
511 if (!bdefault)
513 hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
514 wfxex.Format.nSamplesPerSec, 0, device.c_str(), nullptr,
515 streamCategory);
518 if (!pMasterVoice)
520 if (!bdefault)
522 CLog::LogF(LOGINFO,
523 "could not locate the device named \"{}\" in the list of Xaudio endpoint devices. "
524 "Trying the default device...",
525 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 hr = m_xAudio2->CreateMasteringVoice(&pMasterVoice, wfxex.Format.nChannels,
531 wfxex.Format.nSamplesPerSec, 0, nullptr, nullptr,
532 streamCategory);
533 if (FAILED(hr) || !pMasterVoice)
535 CLog::LogF(LOGINFO, "Could not retrieve the default XAudio audio endpoint ({}).",
536 WASAPIErrToStr(hr));
537 return false;
541 m_masterVoice = pMasterVoice;
543 int closestMatch = 0;
544 unsigned int requestedChannels = 0;
545 unsigned int noOfCh = 0;
547 hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
548 if (SUCCEEDED(hr))
550 CLog::LogF(LOGINFO, "Format is Supported - will attempt to Initialize");
551 goto initialize;
554 if (format.m_dataFormat == AE_FMT_RAW) //No sense in trying other formats for passthrough.
555 return false;
557 if (CServiceBroker::GetLogging().CanLogComponent(LOGAUDIO))
558 CLog::LogFC(LOGDEBUG, LOGAUDIO,
559 "CreateSourceVoice failed ({}) - trying to find a compatible format",
560 WASAPIErrToStr(hr));
562 requestedChannels = wfxex.Format.nChannels;
564 /* The requested format is not supported by the device. Find something that works */
565 for (int layout = -1; layout <= (int)ARRAYSIZE(layoutsList); layout++)
567 // if requested layout is not supported, try standard layouts with at least
568 // the number of channels as requested
569 // as the last resort try stereo
570 if (layout == ARRAYSIZE(layoutsList))
572 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
573 wfxex.Format.nChannels = 2;
575 else if (layout >= 0)
577 wfxex.dwChannelMask = CAESinkFactoryWin::ChLayoutToChMask(layoutsList[layout], &noOfCh);
578 wfxex.Format.nChannels = noOfCh;
579 if (noOfCh < requestedChannels)
580 continue;
583 for (int j = 0; j < sizeof(testFormats)/sizeof(sampleFormat); j++)
585 closestMatch = -1;
587 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
588 wfxex.SubFormat = testFormats[j].subFormat;
589 wfxex.Format.wBitsPerSample = testFormats[j].bitsPerSample;
590 wfxex.Samples.wValidBitsPerSample = testFormats[j].validBitsPerSample;
591 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
593 for (int i = 0 ; i < WASAPISampleRateCount; i++)
595 if (WASAPISampleRates[j] < XAUDIO2_MIN_SAMPLE_RATE ||
596 WASAPISampleRates[j] > XAUDIO2_MAX_SAMPLE_RATE)
597 continue;
599 SafeDestroyVoice(&m_sourceVoice);
600 SafeDestroyVoice(&m_masterVoice);
602 wfxex.Format.nSamplesPerSec = WASAPISampleRates[i];
603 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
605 hr = m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
606 wfxex.Format.nSamplesPerSec, 0, device.c_str(),
607 nullptr, streamCategory);
608 if (SUCCEEDED(hr))
610 hr = m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback);
611 if (SUCCEEDED(hr))
613 /* If the current sample rate matches the source then stop looking and use it */
614 if ((WASAPISampleRates[i] == format.m_sampleRate) && (testFormats[j].subFormatType <= format.m_dataFormat))
615 goto initialize;
616 /* If this rate is closer to the source then the previous one, save it */
617 else if (closestMatch < 0 || abs((int)WASAPISampleRates[i] - (int)format.m_sampleRate) < abs((int)WASAPISampleRates[closestMatch] - (int)format.m_sampleRate))
618 closestMatch = i;
622 if (FAILED(hr))
623 CLog::LogF(LOGERROR, "creating voices failed ({})", WASAPIErrToStr(hr));
626 if (closestMatch >= 0)
628 // Closest match may be different from the last successful sample rate tested
629 SafeDestroyVoice(&m_sourceVoice);
630 SafeDestroyVoice(&m_masterVoice);
632 wfxex.Format.nSamplesPerSec = WASAPISampleRates[closestMatch];
633 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
635 if (SUCCEEDED(m_xAudio2->CreateMasteringVoice(&m_masterVoice, wfxex.Format.nChannels,
636 wfxex.Format.nSamplesPerSec, 0,
637 device.c_str(), nullptr, streamCategory)) &&
638 SUCCEEDED(m_xAudio2->CreateSourceVoice(&m_sourceVoice, &wfxex.Format, 0,
639 XAUDIO2_DEFAULT_FREQ_RATIO, &m_voiceCallback)))
640 goto initialize;
645 CLog::LogF(LOGERROR, "unable to locate a supported output format for the device. Check the "
646 "speaker settings in the control panel.");
648 /* We couldn't find anything supported. This should never happen */
649 /* unless the user set the wrong speaker setting in the control panel */
650 return false;
652 initialize:
654 CAEChannelInfo channelLayout;
655 CAESinkFactoryWin::AEChannelsFromSpeakerMask(channelLayout, wfxex.dwChannelMask);
656 format.m_channelLayout = channelLayout;
658 /* Set up returned sink format for engine */
659 if (format.m_dataFormat != AE_FMT_RAW)
661 if (wfxex.Format.wBitsPerSample == 32)
663 if (wfxex.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
664 format.m_dataFormat = AE_FMT_FLOAT;
665 else if (wfxex.Samples.wValidBitsPerSample == 32)
666 format.m_dataFormat = AE_FMT_S32NE;
667 else
668 format.m_dataFormat = AE_FMT_S24NE4MSB;
670 else if (wfxex.Format.wBitsPerSample == 24)
671 format.m_dataFormat = AE_FMT_S24NE3;
672 else
673 format.m_dataFormat = AE_FMT_S16NE;
676 format.m_sampleRate = wfxex.Format.nSamplesPerSec; //PCM: Sample rate. RAW: Link speed
677 format.m_frameSize = (wfxex.Format.wBitsPerSample >> 3) * wfxex.Format.nChannels;
679 if (format.m_dataFormat == AE_FMT_RAW)
680 format.m_dataFormat = AE_FMT_S16NE;
682 hr = m_sourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
683 if (FAILED(hr))
685 CLog::LogF(LOGERROR, "Voice start failed : {}", WASAPIErrToStr(hr));
686 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
687 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
688 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
689 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
690 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
691 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
692 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
693 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
694 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
695 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
696 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
697 return false;
700 XAUDIO2_PERFORMANCE_DATA perfData;
701 m_xAudio2->GetPerformanceData(&perfData);
702 if (!perfData.TotalSourceVoiceCount)
704 CLog::LogF(LOGERROR, "GetPerformanceData Failed : {}", WASAPIErrToStr(hr));
705 return false;
708 format.m_frames = static_cast<int>(format.m_sampleRate * 0.02); // 20 ms chunks
710 m_format = format;
712 CLog::LogF(LOGINFO, "XAudio Sink Initialized using: {}, {}, {}",
713 CAEUtil::DataFormatToStr(format.m_dataFormat), wfxex.Format.nSamplesPerSec,
714 wfxex.Format.nChannels);
716 m_sourceVoice->Stop();
718 CLog::LogF(LOGDEBUG, "Initializing XAudio with the following parameters:");
719 CLog::Log(LOGDEBUG, " Audio Device : {}", KODI::PLATFORM::WINDOWS::FromW(device));
720 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
721 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
722 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
723 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
724 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
725 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
726 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
727 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
728 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
729 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
730 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
731 CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames);
732 CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize);
734 return true;
737 void CAESinkXAudio::Drain()
739 if(!m_sourceVoice)
740 return;
742 if (m_running)
746 // Contrary to MS doc, the voice must play a buffer with end of stream flag for the voice
747 // SamplesPlayed counter to be reset.
748 // Per MS doc, Discontinuity() may not take effect until after the entire buffer queue is
749 // consumed, which wouldn't invoke the callback or reset the voice stats.
750 // Solution: submit a 1 sample buffer with end of stream flag and wait for StreamEnd callback
751 // The StreamEnd event is manual reset so that it cannot be missed even if raised before this
752 // code starts waiting for it
754 AddEndOfStreamPacket();
756 constexpr uint32_t waitSafety = 100; // extra ms wait in case of scheduler issue
757 DWORD waitRc =
758 WaitForSingleObject(m_voiceCallback.m_StreamEndEvent,
759 m_framesInBuffers * 1000 / m_format.m_sampleRate + waitSafety);
761 if (waitRc != WAIT_OBJECT_0)
763 if (waitRc == WAIT_FAILED)
765 //! @todo use FormatMessage for a human readable error message
766 CLog::LogF(LOGERROR,
767 "error WAIT_FAILED while waiting for queued buffers to drain. GetLastError:{}",
768 GetLastError());
770 else
772 CLog::LogF(LOGERROR, "error {} while waiting for queued buffers to drain.", waitRc);
776 m_sourceVoice->Stop();
777 ResetEvent(m_voiceCallback.m_StreamEndEvent);
779 m_sinkFrames = 0;
780 m_framesInBuffers = 0;
782 catch (...)
784 CLog::LogF(LOGERROR, "invalidated source voice - Releasing");
787 m_running = false;
790 bool CAESinkXAudio::AddEndOfStreamPacket()
792 constexpr unsigned int frames{1};
794 XAUDIO2_BUFFER xbuffer = BuildXAudio2Buffer(nullptr, frames, 0);
795 xbuffer.Flags = XAUDIO2_END_OF_STREAM;
797 HRESULT hr = m_sourceVoice->SubmitSourceBuffer(&xbuffer);
799 if (hr != S_OK)
801 CLog::LogF(LOGERROR, "SubmitSourceBuffer failed due to {:X}", hr);
802 delete xbuffer.pContext;
803 return false;
806 m_sinkFrames += frames;
807 m_framesInBuffers += frames;
808 return true;
811 XAUDIO2_BUFFER CAESinkXAudio::BuildXAudio2Buffer(uint8_t** data,
812 unsigned int frames,
813 unsigned int offset)
815 const unsigned int dataLength{frames * m_format.m_frameSize};
817 struct buffer_ctx* ctx = new buffer_ctx;
818 ctx->data = new uint8_t[dataLength];
819 ctx->frames = frames;
820 ctx->sink = this;
822 if (data)
823 memcpy(ctx->data, data[0] + offset * m_format.m_frameSize, dataLength);
824 else
825 CAEUtil::GenerateSilence(m_format.m_dataFormat, m_format.m_frameSize, ctx->data, frames);
827 XAUDIO2_BUFFER xbuffer{};
828 xbuffer.AudioBytes = dataLength;
829 xbuffer.pAudioData = ctx->data;
830 xbuffer.pContext = ctx;
832 return xbuffer;