[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkDirectSound.cpp
blob43216796878e59cdc8fdcf64f76039065fde9afd
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 #define INITGUID
12 #include "AESinkDirectSound.h"
14 #include "cores/AudioEngine/AESinkFactory.h"
15 #include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
16 #include "cores/AudioEngine/Utils/AEUtil.h"
17 #include "utils/StringUtils.h"
18 #include "utils/XTimeUtils.h"
19 #include "utils/log.h"
21 #include "platform/win32/CharsetConverter.h"
23 #include <algorithm>
24 #include <list>
25 #include <mutex>
27 #include <Audioclient.h>
28 #include <Mmreg.h>
29 #include <Rpc.h>
30 #include <initguid.h>
32 // include order is important here
33 // clang-format off
34 #include <mmdeviceapi.h>
35 #include <Functiondiscoverykeys_devpkey.h>
36 // clang-format on
38 #pragma comment(lib, "Rpcrt4.lib")
40 extern HWND g_hWnd;
42 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
43 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
45 extern const char *WASAPIErrToStr(HRESULT err);
46 #define EXIT_ON_FAILURE(hr, reason) \
47 if (FAILED(hr)) \
48 { \
49 CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
50 goto failed; \
53 #define DS_SPEAKER_COUNT 8
54 static const unsigned int DSChannelOrder[] = {SPEAKER_FRONT_LEFT, SPEAKER_FRONT_RIGHT, SPEAKER_FRONT_CENTER, SPEAKER_LOW_FREQUENCY, SPEAKER_BACK_LEFT, SPEAKER_BACK_RIGHT, SPEAKER_SIDE_LEFT, SPEAKER_SIDE_RIGHT};
55 static const enum AEChannel AEChannelNamesDS[] = {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR, AE_CH_NULL};
57 using namespace Microsoft::WRL;
59 struct DSDevice
61 std::string name;
62 LPGUID lpGuid;
65 static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext)
67 DSDevice dev;
68 std::list<DSDevice> &enumerator = *static_cast<std::list<DSDevice>*>(lpContext);
70 dev.name = KODI::PLATFORM::WINDOWS::FromW(lpcstrDescription);
72 dev.lpGuid = lpGuid;
74 if (lpGuid)
75 enumerator.push_back(dev);
77 return TRUE;
80 CAESinkDirectSound::CAESinkDirectSound() :
81 m_pBuffer (nullptr),
82 m_pDSound (nullptr),
83 m_encodedFormat (AE_FMT_INVALID),
84 m_AvgBytesPerSec(0 ),
85 m_dwChunkSize (0 ),
86 m_dwFrameSize (0 ),
87 m_dwBufferLen (0 ),
88 m_BufferOffset (0 ),
89 m_CacheLen (0 ),
90 m_BufferTimeouts(0 ),
91 m_running (false),
92 m_initialized (false),
93 m_isDirtyDS (false)
95 m_channelLayout.Reset();
98 CAESinkDirectSound::~CAESinkDirectSound()
100 Deinitialize();
103 void CAESinkDirectSound::Register()
105 AE::AESinkRegEntry reg;
106 reg.sinkName = "DIRECTSOUND";
107 reg.createFunc = CAESinkDirectSound::Create;
108 reg.enumerateFunc = CAESinkDirectSound::EnumerateDevicesEx;
109 AE::CAESinkFactory::RegisterSink(reg);
112 std::unique_ptr<IAESink> CAESinkDirectSound::Create(std::string& device,
113 AEAudioFormat& desiredFormat)
115 auto sink = std::make_unique<CAESinkDirectSound>();
116 if (sink->Initialize(desiredFormat, device))
117 return sink;
119 return {};
122 bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
124 if (m_initialized)
125 return false;
127 bool deviceIsBluetooth = false;
128 LPGUID deviceGUID = nullptr;
129 RPC_WSTR wszUuid = nullptr;
130 HRESULT hr = E_FAIL;
131 std::string strDeviceGUID = device;
132 std::list<DSDevice> DSDeviceList;
133 std::string deviceFriendlyName;
134 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
136 if(StringUtils::EndsWithNoCase(device, std::string("default")))
137 strDeviceGUID = GetDefaultDevice();
139 for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
141 if ((*itt).lpGuid)
143 hr = (UuidToString((*itt).lpGuid, &wszUuid));
144 std::string sztmp = KODI::PLATFORM::WINDOWS::FromW(reinterpret_cast<wchar_t*>(wszUuid));
145 std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
146 if (StringUtils::CompareNoCase(szGUID, strDeviceGUID) == 0)
148 deviceGUID = (*itt).lpGuid;
149 deviceFriendlyName = (*itt).name.c_str();
150 break;
153 if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
156 // Detect a Bluetooth device
157 for (const auto& device : CAESinkFactoryWin::GetRendererDetails())
159 if (StringUtils::CompareNoCase(device.strDeviceId, strDeviceGUID) != 0)
160 continue;
162 if (device.strDeviceEnumerator == "BTHENUM")
164 deviceIsBluetooth = true;
165 CLog::LogF(LOGDEBUG, "Audio device '{}' is detected as Bluetooth device", deviceFriendlyName);
168 break;
171 hr = DirectSoundCreate(deviceGUID, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
173 if (FAILED(hr))
175 CLog::LogF(
176 LOGERROR,
177 "Failed to create the DirectSound device {} with error {}, trying the default device.",
178 deviceFriendlyName, dserr2str(hr));
180 hr = DirectSoundCreate(nullptr, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
181 if (FAILED(hr))
183 CLog::LogF(LOGERROR, "Failed to create the default DirectSound device with error {}.",
184 dserr2str(hr));
185 return false;
189 /* Dodge the null handle on first init by using desktop handle */
190 HWND tmp_hWnd = g_hWnd == nullptr ? GetDesktopWindow() : g_hWnd;
191 CLog::LogF(LOGDEBUG, "Using Window handle: {}", fmt::ptr(tmp_hWnd));
193 hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
195 if (FAILED(hr))
197 CLog::LogF(LOGERROR, "Failed to create the DirectSound device cooperative level.");
198 CLog::LogF(LOGERROR, "DSErr: {}", dserr2str(hr));
199 m_pDSound = nullptr;
200 return false;
203 // clamp samplerate between 44100 and 192000
204 if (format.m_sampleRate < 44100)
205 format.m_sampleRate = 44100;
207 if (format.m_sampleRate > 192000)
208 format.m_sampleRate = 192000;
210 // fill waveformatex
211 WAVEFORMATEXTENSIBLE wfxex = {};
212 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
213 wfxex.Format.nChannels = format.m_channelLayout.Count();
214 wfxex.Format.nSamplesPerSec = format.m_sampleRate;
215 if (format.m_dataFormat == AE_FMT_RAW)
217 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
218 wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
219 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
220 wfxex.Format.wBitsPerSample = 16;
221 wfxex.Format.nChannels = 2;
223 else
225 wfxex.dwChannelMask = SpeakerMaskFromAEChannels(format.m_channelLayout);
226 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
227 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
228 wfxex.Format.wBitsPerSample = 32;
231 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
232 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
233 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
235 m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
237 const unsigned int chunkDurationMs = deviceIsBluetooth ? 50 : 20;
238 const unsigned int uiFrameCount = format.m_sampleRate * chunkDurationMs / 1000U;
239 m_dwFrameSize = wfxex.Format.nBlockAlign;
240 m_dwChunkSize = m_dwFrameSize * uiFrameCount;
242 // BT: 500ms total buffer (50 * 10); non-BT: 200ms total buffer (20 * 10)
243 m_dwBufferLen = m_dwChunkSize * 10;
245 // fill in the secondary sound buffer descriptor
246 DSBUFFERDESC dsbdesc = {};
247 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
248 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
249 | DSBCAPS_TRUEPLAYPOSITION /** Vista+ accurate position */
250 | DSBCAPS_GLOBALFOCUS; /** Allows background playing */
252 dsbdesc.dwBufferBytes = m_dwBufferLen;
253 dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
255 // now create the stream buffer
256 HRESULT res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
257 if (res != DS_OK)
259 if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
261 CLog::LogF(LOGDEBUG, "Couldn't create secondary buffer ({}). Trying without LOCHARDWARE.",
262 dserr2str(res));
263 // Try without DSBCAPS_LOCHARDWARE
264 dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
265 res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
267 if (res != DS_OK)
269 m_pBuffer = nullptr;
270 CLog::LogF(LOGERROR, "cannot create secondary buffer ({})", dserr2str(res));
271 return false;
274 CLog::LogF(LOGDEBUG, "secondary buffer created");
276 m_pBuffer->Stop();
278 AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
279 format.m_channelLayout = m_channelLayout;
280 m_encodedFormat = format.m_dataFormat;
281 format.m_frames = uiFrameCount;
282 format.m_frameSize = ((format.m_dataFormat == AE_FMT_RAW) ? (wfxex.Format.wBitsPerSample >> 3) : sizeof(float)) * format.m_channelLayout.Count();
283 format.m_dataFormat = (format.m_dataFormat == AE_FMT_RAW) ? AE_FMT_S16NE : AE_FMT_FLOAT;
285 m_format = format;
286 m_device = device;
288 m_BufferOffset = 0;
289 m_CacheLen = 0;
290 m_initialized = true;
291 m_isDirtyDS = false;
293 CLog::LogF(LOGDEBUG, "Initializing DirectSound with the following parameters:");
294 CLog::Log(LOGDEBUG, " Audio Device : {}", ((std::string)deviceFriendlyName));
295 CLog::Log(LOGDEBUG, " Sample Rate : {}", wfxex.Format.nSamplesPerSec);
296 CLog::Log(LOGDEBUG, " Sample Format : {}", CAEUtil::DataFormatToStr(format.m_dataFormat));
297 CLog::Log(LOGDEBUG, " Bits Per Sample : {}", wfxex.Format.wBitsPerSample);
298 CLog::Log(LOGDEBUG, " Valid Bits/Samp : {}", wfxex.Samples.wValidBitsPerSample);
299 CLog::Log(LOGDEBUG, " Channel Count : {}", wfxex.Format.nChannels);
300 CLog::Log(LOGDEBUG, " Block Align : {}", wfxex.Format.nBlockAlign);
301 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : {}", wfxex.Format.nAvgBytesPerSec);
302 CLog::Log(LOGDEBUG, " Samples/Block : {}", wfxex.Samples.wSamplesPerBlock);
303 CLog::Log(LOGDEBUG, " Format cBSize : {}", wfxex.Format.cbSize);
304 CLog::Log(LOGDEBUG, " Channel Layout : {}", ((std::string)format.m_channelLayout));
305 CLog::Log(LOGDEBUG, " Channel Mask : {}", wfxex.dwChannelMask);
306 CLog::Log(LOGDEBUG, " Frames : {}", format.m_frames);
307 CLog::Log(LOGDEBUG, " Frame Size : {}", format.m_frameSize);
309 return true;
312 void CAESinkDirectSound::Deinitialize()
314 if (!m_initialized)
315 return;
317 CLog::LogF(LOGDEBUG, "Cleaning up");
319 if (m_pBuffer)
321 m_pBuffer->Stop();
324 m_initialized = false;
325 m_pBuffer = nullptr;
326 m_pDSound = nullptr;
327 m_BufferOffset = 0;
328 m_CacheLen = 0;
329 m_dwChunkSize = 0;
330 m_dwBufferLen = 0;
333 unsigned int CAESinkDirectSound::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
335 if (!m_initialized)
336 return 0;
338 DWORD total = m_dwFrameSize * frames;
339 DWORD len = total;
340 unsigned char* pBuffer = (unsigned char*)data[0]+offset*m_format.m_frameSize;
342 DWORD bufferStatus = 0;
343 if (m_pBuffer->GetStatus(&bufferStatus) != DS_OK)
345 CLog::LogF(LOGERROR, "GetStatus() failed");
346 return 0;
348 if (bufferStatus & DSBSTATUS_BUFFERLOST)
350 CLog::LogF(LOGDEBUG, "Buffer allocation was lost. Restoring buffer.");
351 m_pBuffer->Restore();
354 while (GetSpace() < total)
356 if(m_isDirtyDS)
357 return INT_MAX;
358 else
360 KODI::TIME::Sleep(
361 std::chrono::milliseconds(static_cast<int>(total * 1000 / m_AvgBytesPerSec)));
365 while (len)
367 void* start = nullptr, *startWrap = nullptr;
368 DWORD size = 0, sizeWrap = 0;
369 if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
370 m_BufferOffset = 0;
371 DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len);
372 HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0);
373 if (DS_OK != res)
375 CLog::LogF(LOGERROR, "Unable to lock buffer at offset {}. HRESULT: {:#08x}", m_BufferOffset,
376 res);
377 m_isDirtyDS = true;
378 return INT_MAX;
381 memcpy(start, pBuffer, size);
383 pBuffer += size;
384 len -= size;
386 m_BufferOffset += size;
387 if (startWrap) // Write-region wraps to beginning of buffer
389 memcpy(startWrap, pBuffer, sizeWrap);
390 m_BufferOffset = sizeWrap;
392 pBuffer += sizeWrap;
393 len -= sizeWrap;
396 m_CacheLen += size + sizeWrap; // This data is now in the cache
397 m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
400 CheckPlayStatus();
402 return (total - len) / m_dwFrameSize; // Frames used
405 void CAESinkDirectSound::Stop()
407 if (m_pBuffer)
408 m_pBuffer->Stop();
411 void CAESinkDirectSound::Drain()
413 if (!m_initialized || m_isDirtyDS)
414 return;
416 m_pBuffer->Stop();
417 HRESULT res = m_pBuffer->SetCurrentPosition(0);
418 if (DS_OK != res)
420 CLog::LogF(LOGERROR,
421 "SetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
422 res);
423 m_isDirtyDS = true;
424 return;
426 m_BufferOffset = 0;
427 UpdateCacheStatus();
430 void CAESinkDirectSound::GetDelay(AEDelayStatus& status)
432 if (!m_initialized)
434 status.SetDelay(0);
435 return;
438 /* Make sure we know how much data is in the cache */
439 if (!UpdateCacheStatus())
440 m_isDirtyDS = true;
442 /** returns current cached data duration in seconds */
443 status.SetDelay((double)m_CacheLen / (double)m_AvgBytesPerSec);
446 double CAESinkDirectSound::GetCacheTotal()
448 /** returns total cache capacity in seconds */
449 return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
452 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
454 CAEDeviceInfo deviceInfo;
456 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
457 ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
458 UINT uiCount = 0;
460 HRESULT hr;
462 std::string strDD = GetDefaultDevice();
464 /* Windows Vista or later - supporting WASAPI device probing */
465 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
466 EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
468 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
469 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
471 hr = pEnumDevices->GetCount(&uiCount);
472 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
474 for (UINT i = 0; i < uiCount; i++)
476 ComPtr<IMMDevice> pDevice = nullptr;
477 ComPtr<IPropertyStore> pProperty = nullptr;
478 PROPVARIANT varName;
479 PropVariantInit(&varName);
481 deviceInfo.m_channels.Reset();
482 deviceInfo.m_dataFormats.clear();
483 deviceInfo.m_sampleRates.clear();
485 hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
486 if (FAILED(hr))
488 CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint failed.");
489 goto failed;
492 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
493 if (FAILED(hr))
495 CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint properties failed.");
496 goto failed;
499 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
500 if (FAILED(hr))
502 CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint device name failed.");
503 goto failed;
506 std::string strFriendlyName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
507 PropVariantClear(&varName);
509 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
510 if (FAILED(hr))
512 CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint GUID failed.");
513 goto failed;
516 std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
517 PropVariantClear(&varName);
519 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
520 if (FAILED(hr))
522 CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint form factor failed.");
523 goto failed;
525 std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
526 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
528 PropVariantClear(&varName);
530 /* In shared mode Windows tells us what format the audio must be in. */
531 ComPtr<IAudioClient> pClient;
532 hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast<void**>(pClient.GetAddressOf()));
533 EXIT_ON_FAILURE(hr, "Activate device failed.")
535 //hr = pClient->GetMixFormat(&pwfxex);
536 hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
537 if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
539 WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
540 deviceInfo.m_channels = layoutsByChCount[std::max(std::min(smpwfxex->nChannels, (WORD) DS_SPEAKER_COUNT), (WORD) 2)];
541 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
542 if (aeDeviceType != AE_DEVTYPE_PCM)
544 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
545 // DTS is played with the same infrastructure as AC3
546 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
547 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
548 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
549 deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
550 // signal that we can doe AE_FMT_RAW
551 deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
553 deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 192000));
555 else
557 CLog::LogF(LOGERROR, "Getting DeviceFormat failed ({})", WASAPIErrToStr(hr));
560 deviceInfo.m_deviceName = strDevName;
561 deviceInfo.m_displayName = strWinDevType.append(strFriendlyName);
562 deviceInfo.m_displayNameExtra = std::string("DIRECTSOUND: ").append(strFriendlyName);
563 deviceInfo.m_deviceType = aeDeviceType;
565 deviceInfo.m_wantsIECPassthrough = true;
566 deviceInfoList.push_back(deviceInfo);
568 // add the default device with m_deviceName = default
569 if(strDD == strDevName)
571 deviceInfo.m_deviceName = std::string("default");
572 deviceInfo.m_displayName = std::string("default");
573 deviceInfo.m_displayNameExtra = std::string("");
574 deviceInfo.m_wantsIECPassthrough = true;
575 deviceInfoList.push_back(deviceInfo);
579 return;
581 failed:
583 if (FAILED(hr))
584 CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr));
587 ///////////////////////////////////////////////////////////////////////////////
589 void CAESinkDirectSound::CheckPlayStatus()
591 DWORD status = 0;
592 if (m_pBuffer->GetStatus(&status) != DS_OK)
594 CLog::LogF(LOGERROR, "GetStatus() failed");
595 return;
598 if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
600 HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
601 CLog::LogF(LOGDEBUG, "Resuming Playback");
602 if (FAILED(hr))
603 CLog::LogF(LOGERROR, "Failed to play the DirectSound buffer: {}", dserr2str(hr));
607 bool CAESinkDirectSound::UpdateCacheStatus()
609 std::unique_lock<CCriticalSection> lock(m_runLock);
611 DWORD playCursor = 0, writeCursor = 0;
612 HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
613 if (DS_OK != res)
615 CLog::LogF(LOGERROR,
616 "GetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
617 res);
618 m_isDirtyDS = true;
619 return false;
622 // Check the state of the ring buffer (P->O->W == underrun)
623 // These are the logical situations that can occur
624 // O: CurrentOffset W: WriteCursor P: PlayCursor
625 // | | | | | | | | | |
626 // ***O----W----P***** < underrun P > W && O < W (1)
627 // | | | | | | | | | |
628 // ---P****O----W----- < underrun O > P && O < W (2)
629 // | | | | | | | | | |
630 // ---W----P****O----- < underrun P > W && P < O (3)
631 // | | | | | | | | | |
632 // ***W****O----P***** P > W && P > O (4)
633 // | | | | | | | | | |
634 // ---P****W****O----- P < W && O > W (5)
635 // | | | | | | | | | |
636 // ***O----P****W***** P < W && O < P (6)
638 // Check for underruns
639 if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
640 (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
641 (playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
643 CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:{}, P:{}, O:{}.",
644 writeCursor, playCursor, m_BufferOffset);
645 m_BufferOffset = writeCursor; // Catch up
646 //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
647 m_BufferTimeouts++;
648 if (m_BufferTimeouts > 10)
650 m_isDirtyDS = true;
651 return false;
654 else
655 m_BufferTimeouts = 0;
657 // Calculate available space in the ring buffer
658 if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
659 m_CacheLen = 0;
660 else if (m_BufferOffset > playCursor)
661 m_CacheLen = m_BufferOffset - playCursor;
662 else
663 m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
665 return true;
668 unsigned int CAESinkDirectSound::GetSpace()
670 std::unique_lock<CCriticalSection> lock(m_runLock);
671 if (!UpdateCacheStatus())
672 m_isDirtyDS = true;
673 unsigned int space = m_dwBufferLen - m_CacheLen;
675 // We can never allow the internal buffers to fill up complete
676 // as we get confused between if the buffer is full or empty
677 // so never allow the last chunk to be added
678 if (space > m_dwChunkSize)
679 return space - m_dwChunkSize;
680 else
681 return 0;
684 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
686 m_channelLayout.Reset();
688 for (int i = 0; i < DS_SPEAKER_COUNT; i++)
690 if (speakers & DSChannelOrder[i])
691 m_channelLayout += AEChannelNamesDS[i];
695 DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
697 DWORD mask = 0;
699 for (unsigned int i = 0; i < channels.Count(); i++)
701 for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
702 if (channels[i] == AEChannelNamesDS[j])
703 mask |= DSChannelOrder[j];
706 return mask;
709 const char *CAESinkDirectSound::dserr2str(int err)
711 switch (err)
713 case DS_OK: return "DS_OK";
714 case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
715 case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
716 case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
717 case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
718 case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
719 case DSERR_GENERIC: return "DSERR_GENERIC";
720 case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
721 case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
722 case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
723 case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
724 case DSERR_NODRIVER: return "DSERR_NODRIVER";
725 case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
726 case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
727 case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
728 case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
729 case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
730 case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
731 case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
732 case DSERR_BUFFERTOOSMALL: return "DSERR_BUFFERTOOSMALL";
733 case DSERR_DS8_REQUIRED: return "DSERR_DS8_REQUIRED";
734 case DSERR_SENDLOOP: return "DSERR_SENDLOOP";
735 case DSERR_BADSENDBUFFERGUID: return "DSERR_BADSENDBUFFERGUID";
736 case DSERR_OBJECTNOTFOUND: return "DSERR_OBJECTNOTFOUND";
737 case DSERR_FXUNAVAILABLE: return "DSERR_FXUNAVAILABLE";
738 default: return "unknown";
742 std::string CAESinkDirectSound::GetDefaultDevice()
744 HRESULT hr;
745 ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
746 ComPtr<IMMDevice> pDevice = nullptr;
747 ComPtr<IPropertyStore> pProperty = nullptr;
748 PROPVARIANT varName;
749 std::string strDevName = "default";
750 AEDeviceType aeDeviceType;
752 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
753 EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator")
755 hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, pDevice.GetAddressOf());
756 EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
758 hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
759 EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint properties failed.")
761 PropVariantInit(&varName);
762 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
763 EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint form factor failed.")
765 aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
766 PropVariantClear(&varName);
768 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
769 EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint GUID failed")
771 strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
772 PropVariantClear(&varName);
774 failed:
776 return strDevName;