1 /*
2  *  Copyright (C) 2010-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #define INITGUID
10 
11 
12 #include "AESinkDirectSound.h"
13 
14 #include "cores/AudioEngine/AESinkFactory.h"
15 #include "cores/AudioEngine/Sinks/windows/AESinkFactoryWin.h"
16 #include "cores/AudioEngine/Utils/AEUtil.h"
17 #include "threads/SingleLock.h"
18 #include "threads/SystemClock.h"
19 #include "utils/StringUtils.h"
20 #include "utils/XTimeUtils.h"
21 #include "utils/log.h"
22 
23 #include "platform/win32/CharsetConverter.h"
24 
25 #include <algorithm>
26 #include <list>
27 
28 #include <Audioclient.h>
29 #include <Mmreg.h>
30 #include <Rpc.h>
31 #include <initguid.h>
32 
33 // include order is important here
34 // clang-format off
35 #include <mmdeviceapi.h>
36 #include <Functiondiscoverykeys_devpkey.h>
37 // clang-format on
38 
39 #pragma comment(lib, "Rpcrt4.lib")
40 
41 extern HWND g_hWnd;
42 
43 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
44 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
45 
46 extern const char *WASAPIErrToStr(HRESULT err);
47 #define EXIT_ON_FAILURE(hr, reason) if(FAILED(hr)) {CLog::LogF(LOGERROR, reason " - HRESULT = %li ErrorMessage = %s", hr, WASAPIErrToStr(hr)); goto failed;}
48 
49 #define DS_SPEAKER_COUNT 8
50 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};
51 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};
52 
53 using namespace Microsoft::WRL;
54 
55 struct DSDevice
56 {
57   std::string name;
58   LPGUID     lpGuid;
59 };
60 
DSEnumCallback(LPGUID lpGuid,LPCTSTR lpcstrDescription,LPCTSTR lpcstrModule,LPVOID lpContext)61 static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext)
62 {
63   DSDevice dev;
64   std::list<DSDevice> &enumerator = *static_cast<std::list<DSDevice>*>(lpContext);
65 
66   dev.name = KODI::PLATFORM::WINDOWS::FromW(lpcstrDescription);
67 
68   dev.lpGuid = lpGuid;
69 
70   if (lpGuid)
71     enumerator.push_back(dev);
72 
73   return TRUE;
74 }
75 
CAESinkDirectSound()76 CAESinkDirectSound::CAESinkDirectSound() :
77   m_pBuffer       (nullptr),
78   m_pDSound       (nullptr),
79   m_encodedFormat (AE_FMT_INVALID),
80   m_AvgBytesPerSec(0    ),
81   m_dwChunkSize   (0    ),
82   m_dwFrameSize   (0    ),
83   m_dwBufferLen   (0    ),
84   m_BufferOffset  (0    ),
85   m_CacheLen      (0    ),
86   m_LastCacheCheck(0    ),
87   m_BufferTimeouts(0    ),
88   m_running       (false),
89   m_initialized   (false),
90   m_isDirtyDS     (false)
91 {
92   m_channelLayout.Reset();
93 }
94 
~CAESinkDirectSound()95 CAESinkDirectSound::~CAESinkDirectSound()
96 {
97   Deinitialize();
98 }
99 
Register()100 void CAESinkDirectSound::Register()
101 {
102   AE::AESinkRegEntry reg;
103   reg.sinkName = "DIRECTSOUND";
104   reg.createFunc = CAESinkDirectSound::Create;
105   reg.enumerateFunc = CAESinkDirectSound::EnumerateDevicesEx;
106   AE::CAESinkFactory::RegisterSink(reg);
107 }
108 
Create(std::string & device,AEAudioFormat & desiredFormat)109 IAESink* CAESinkDirectSound::Create(std::string &device, AEAudioFormat &desiredFormat)
110 {
111   IAESink *sink = new CAESinkDirectSound();
112   if (sink->Initialize(desiredFormat, device))
113     return sink;
114 
115   delete sink;
116   return nullptr;
117 }
118 
Initialize(AEAudioFormat & format,std::string & device)119 bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
120 {
121   if (m_initialized)
122     return false;
123 
124   LPGUID deviceGUID = nullptr;
125   RPC_WSTR wszUuid  = nullptr;
126   HRESULT hr = E_FAIL;
127   std::string strDeviceGUID = device;
128   std::list<DSDevice> DSDeviceList;
129   std::string deviceFriendlyName;
130   DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
131 
132   if(StringUtils::EndsWithNoCase(device, std::string("default")))
133     strDeviceGUID = GetDefaultDevice();
134 
135   for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
136   {
137     if ((*itt).lpGuid)
138     {
139       hr = (UuidToString((*itt).lpGuid, &wszUuid));
140       std::string sztmp = KODI::PLATFORM::WINDOWS::FromW(reinterpret_cast<wchar_t*>(wszUuid));
141       std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
142       if (StringUtils::CompareNoCase(szGUID, strDeviceGUID) == 0)
143       {
144         deviceGUID = (*itt).lpGuid;
145         deviceFriendlyName = (*itt).name.c_str();
146         break;
147       }
148     }
149     if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
150   }
151 
152   hr = DirectSoundCreate(deviceGUID, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
153 
154   if (FAILED(hr))
155   {
156     CLog::LogF(
157         LOGERROR,
158         "Failed to create the DirectSound device %s with error %s, trying the default device.",
159         deviceFriendlyName, dserr2str(hr));
160 
161     hr = DirectSoundCreate(nullptr, m_pDSound.ReleaseAndGetAddressOf(), nullptr);
162     if (FAILED(hr))
163     {
164       CLog::LogF(LOGERROR, "Failed to create the default DirectSound device with error %s.",
165                  dserr2str(hr));
166       return false;
167     }
168   }
169 
170   /* Dodge the null handle on first init by using desktop handle */
171   HWND tmp_hWnd = g_hWnd == nullptr ? GetDesktopWindow() : g_hWnd;
172   CLog::LogF(LOGDEBUG, "Using Window handle: %p", static_cast<void*>(tmp_hWnd));
173 
174   hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
175 
176   if (FAILED(hr))
177   {
178     CLog::LogF(LOGERROR, "Failed to create the DirectSound device cooperative level.");
179     CLog::LogF(LOGERROR, "DSErr: %s", dserr2str(hr));
180     m_pDSound = nullptr;
181     return false;
182   }
183 
184   WAVEFORMATEXTENSIBLE wfxex = {0};
185 
186   // clamp samplerate between 44100 and 192000
187   if (format.m_sampleRate < 44100)
188     format.m_sampleRate = 44100;
189 
190   if (format.m_sampleRate > 192000)
191     format.m_sampleRate = 192000;
192 
193   //fill waveformatex
194   ZeroMemory(&wfxex, sizeof(WAVEFORMATEXTENSIBLE));
195   wfxex.Format.cbSize          = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
196   wfxex.Format.nChannels       = format.m_channelLayout.Count();
197   wfxex.Format.nSamplesPerSec  = format.m_sampleRate;
198   if (format.m_dataFormat == AE_FMT_RAW)
199   {
200     wfxex.dwChannelMask          = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
201     wfxex.Format.wFormatTag      = WAVE_FORMAT_DOLBY_AC3_SPDIF;
202     wfxex.SubFormat              = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
203     wfxex.Format.wBitsPerSample  = 16;
204     wfxex.Format.nChannels       = 2;
205   }
206   else
207   {
208     wfxex.dwChannelMask          = SpeakerMaskFromAEChannels(format.m_channelLayout);
209     wfxex.Format.wFormatTag      = WAVE_FORMAT_EXTENSIBLE;
210     wfxex.SubFormat              = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
211     wfxex.Format.wBitsPerSample  = 32;
212   }
213 
214   wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
215   wfxex.Format.nBlockAlign          = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
216   wfxex.Format.nAvgBytesPerSec      = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
217 
218   m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
219 
220   unsigned int uiFrameCount = (int)(format.m_sampleRate * 0.015); //default to 15ms chunks
221   m_dwFrameSize = wfxex.Format.nBlockAlign;
222   m_dwChunkSize = m_dwFrameSize * uiFrameCount;
223   m_dwBufferLen = m_dwChunkSize * 12; //180ms total buffer
224 
225   // fill in the secondary sound buffer descriptor
226   DSBUFFERDESC dsbdesc;
227   memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
228   dsbdesc.dwSize = sizeof(DSBUFFERDESC);
229   dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
230                   | DSBCAPS_TRUEPLAYPOSITION    /** Vista+ accurate position */
231                   | DSBCAPS_GLOBALFOCUS;         /** Allows background playing */
232 
233   dsbdesc.dwBufferBytes = m_dwBufferLen;
234   dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
235 
236   // now create the stream buffer
237   HRESULT res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
238   if (res != DS_OK)
239   {
240     if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
241     {
242       CLog::LogF(LOGDEBUG, "Couldn't create secondary buffer (%s). Trying without LOCHARDWARE.",
243                  dserr2str(res));
244       // Try without DSBCAPS_LOCHARDWARE
245       dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
246       res = m_pDSound->CreateSoundBuffer(&dsbdesc, m_pBuffer.ReleaseAndGetAddressOf(), nullptr);
247     }
248     if (res != DS_OK)
249     {
250       m_pBuffer = nullptr;
251       CLog::LogF(LOGERROR, "cannot create secondary buffer (%s)", dserr2str(res));
252       return false;
253     }
254   }
255   CLog::LogF(LOGDEBUG, "secondary buffer created");
256 
257   m_pBuffer->Stop();
258 
259   AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
260   format.m_channelLayout = m_channelLayout;
261   m_encodedFormat = format.m_dataFormat;
262   format.m_frames = uiFrameCount;
263   format.m_frameSize =  ((format.m_dataFormat == AE_FMT_RAW) ? (wfxex.Format.wBitsPerSample >> 3) : sizeof(float)) * format.m_channelLayout.Count();
264   format.m_dataFormat = (format.m_dataFormat == AE_FMT_RAW) ? AE_FMT_S16NE : AE_FMT_FLOAT;
265 
266   m_format = format;
267   m_device = device;
268 
269   m_BufferOffset = 0;
270   m_CacheLen = 0;
271   m_LastCacheCheck = XbmcThreads::SystemClockMillis();
272   m_initialized = true;
273   m_isDirtyDS = false;
274 
275   CLog::LogF(LOGDEBUG, "Initializing DirectSound with the following parameters:");
276   CLog::Log(LOGDEBUG, "  Audio Device    : %s", ((std::string)deviceFriendlyName));
277   CLog::Log(LOGDEBUG, "  Sample Rate     : %d", wfxex.Format.nSamplesPerSec);
278   CLog::Log(LOGDEBUG, "  Sample Format   : %s", CAEUtil::DataFormatToStr(format.m_dataFormat));
279   CLog::Log(LOGDEBUG, "  Bits Per Sample : %d", wfxex.Format.wBitsPerSample);
280   CLog::Log(LOGDEBUG, "  Valid Bits/Samp : %d", wfxex.Samples.wValidBitsPerSample);
281   CLog::Log(LOGDEBUG, "  Channel Count   : %d", wfxex.Format.nChannels);
282   CLog::Log(LOGDEBUG, "  Block Align     : %d", wfxex.Format.nBlockAlign);
283   CLog::Log(LOGDEBUG, "  Avg. Bytes Sec  : %d", wfxex.Format.nAvgBytesPerSec);
284   CLog::Log(LOGDEBUG, "  Samples/Block   : %d", wfxex.Samples.wSamplesPerBlock);
285   CLog::Log(LOGDEBUG, "  Format cBSize   : %d", wfxex.Format.cbSize);
286   CLog::Log(LOGDEBUG, "  Channel Layout  : %s", ((std::string)format.m_channelLayout));
287   CLog::Log(LOGDEBUG, "  Channel Mask    : %d", wfxex.dwChannelMask);
288   CLog::Log(LOGDEBUG, "  Frames          : %d", format.m_frames);
289   CLog::Log(LOGDEBUG, "  Frame Size      : %d", format.m_frameSize);
290 
291   return true;
292 }
293 
Deinitialize()294 void CAESinkDirectSound::Deinitialize()
295 {
296   if (!m_initialized)
297     return;
298 
299   CLog::LogF(LOGDEBUG, "Cleaning up");
300 
301   if (m_pBuffer)
302   {
303     m_pBuffer->Stop();
304   }
305 
306   m_initialized = false;
307   m_pBuffer = nullptr;
308   m_pDSound = nullptr;
309   m_BufferOffset = 0;
310   m_CacheLen = 0;
311   m_dwChunkSize = 0;
312   m_dwBufferLen = 0;
313 }
314 
AddPackets(uint8_t ** data,unsigned int frames,unsigned int offset)315 unsigned int CAESinkDirectSound::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
316 {
317   if (!m_initialized)
318     return 0;
319 
320   DWORD total = m_dwFrameSize * frames;
321   DWORD len = total;
322   unsigned char* pBuffer = (unsigned char*)data[0]+offset*m_format.m_frameSize;
323 
324   DWORD bufferStatus = 0;
325   if (m_pBuffer->GetStatus(&bufferStatus) != DS_OK)
326   {
327     CLog::LogF(LOGERROR, "GetStatus() failed");
328     return 0;
329   }
330   if (bufferStatus & DSBSTATUS_BUFFERLOST)
331   {
332     CLog::LogF(LOGDEBUG, "Buffer allocation was lost. Restoring buffer.");
333     m_pBuffer->Restore();
334   }
335 
336   while (GetSpace() < total)
337   {
338     if(m_isDirtyDS)
339       return INT_MAX;
340     else
341     {
342       KODI::TIME::Sleep(total * 1000 / m_AvgBytesPerSec);
343     }
344   }
345 
346   while (len)
347   {
348     void* start = nullptr, *startWrap = nullptr;
349     DWORD size = 0, sizeWrap = 0;
350     if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
351       m_BufferOffset = 0;
352     DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len);
353     HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0);
354     if (DS_OK != res)
355     {
356       CLog::LogF(LOGERROR, "Unable to lock buffer at offset %u. HRESULT: 0x%08x", m_BufferOffset, res);
357       m_isDirtyDS = true;
358       return INT_MAX;
359     }
360 
361     memcpy(start, pBuffer, size);
362 
363     pBuffer += size;
364     len     -= size;
365 
366     m_BufferOffset += size;
367     if (startWrap) // Write-region wraps to beginning of buffer
368     {
369       memcpy(startWrap, pBuffer, sizeWrap);
370       m_BufferOffset = sizeWrap;
371 
372       pBuffer += sizeWrap;
373       len     -= sizeWrap;
374     }
375 
376     m_CacheLen += size + sizeWrap; // This data is now in the cache
377     m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
378   }
379 
380   CheckPlayStatus();
381 
382   return (total - len) / m_dwFrameSize; // Frames used
383 }
384 
Stop()385 void CAESinkDirectSound::Stop()
386 {
387   if (m_pBuffer)
388     m_pBuffer->Stop();
389 }
390 
Drain()391 void CAESinkDirectSound::Drain()
392 {
393   if (!m_initialized || m_isDirtyDS)
394     return;
395 
396   m_pBuffer->Stop();
397   HRESULT res = m_pBuffer->SetCurrentPosition(0);
398   if (DS_OK != res)
399   {
400     CLog::LogF(LOGERROR,
401                "SetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x",
402                res);
403     m_isDirtyDS = true;
404     return;
405   }
406   m_BufferOffset = 0;
407   UpdateCacheStatus();
408 }
409 
GetDelay(AEDelayStatus & status)410 void CAESinkDirectSound::GetDelay(AEDelayStatus& status)
411 {
412   if (!m_initialized)
413   {
414     status.SetDelay(0);
415     return;
416   }
417 
418   /* Make sure we know how much data is in the cache */
419   if (!UpdateCacheStatus())
420     m_isDirtyDS = true;
421 
422   /** returns current cached data duration in seconds */
423   status.SetDelay((double)m_CacheLen / (double)m_AvgBytesPerSec);
424 }
425 
GetCacheTotal()426 double CAESinkDirectSound::GetCacheTotal()
427 {
428   /** returns total cache capacity in seconds */
429   return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
430 }
431 
EnumerateDevicesEx(AEDeviceInfoList & deviceInfoList,bool force)432 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
433 {
434   CAEDeviceInfo        deviceInfo;
435 
436   ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
437   ComPtr<IMMDeviceCollection> pEnumDevices = nullptr;
438 
439   HRESULT                hr;
440 
441   std::string strDD = GetDefaultDevice();
442 
443   /* Windows Vista or later - supporting WASAPI device probing */
444   hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
445   EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator.")
446 
447   UINT uiCount = 0;
448 
449   hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, pEnumDevices.GetAddressOf());
450   EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint enumeration failed.")
451 
452   hr = pEnumDevices->GetCount(&uiCount);
453   EXIT_ON_FAILURE(hr, "Retrieval of audio endpoint count failed.")
454 
455   for (UINT i = 0; i < uiCount; i++)
456   {
457     ComPtr<IMMDevice> pDevice = nullptr;
458     ComPtr<IPropertyStore> pProperty = nullptr;
459     PROPVARIANT varName;
460     PropVariantInit(&varName);
461 
462     deviceInfo.m_channels.Reset();
463     deviceInfo.m_dataFormats.clear();
464     deviceInfo.m_sampleRates.clear();
465 
466     hr = pEnumDevices->Item(i, pDevice.GetAddressOf());
467     if (FAILED(hr))
468     {
469       CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint failed.");
470       goto failed;
471     }
472 
473     hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
474     if (FAILED(hr))
475     {
476       CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint properties failed.");
477       goto failed;
478     }
479 
480     hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
481     if (FAILED(hr))
482     {
483       CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint device name failed.");
484       goto failed;
485     }
486 
487     std::string strFriendlyName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
488     PropVariantClear(&varName);
489 
490     hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
491     if (FAILED(hr))
492     {
493       CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint GUID failed.");
494       goto failed;
495     }
496 
497     std::string strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
498     PropVariantClear(&varName);
499 
500     hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
501     if (FAILED(hr))
502     {
503       CLog::LogF(LOGERROR, "Retrieval of DirectSound endpoint form factor failed.");
504       goto failed;
505     }
506     std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
507     AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
508 
509     PropVariantClear(&varName);
510 
511     /* In shared mode Windows tells us what format the audio must be in. */
512     ComPtr<IAudioClient> pClient;
513     hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast<void**>(pClient.GetAddressOf()));
514     EXIT_ON_FAILURE(hr, "Activate device failed.")
515 
516     //hr = pClient->GetMixFormat(&pwfxex);
517     hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
518     if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
519     {
520       WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
521       deviceInfo.m_channels = layoutsByChCount[std::max(std::min(smpwfxex->nChannels, (WORD) DS_SPEAKER_COUNT), (WORD) 2)];
522       deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
523       if (aeDeviceType != AE_DEVTYPE_PCM)
524       {
525         deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_AC3);
526         // DTS is played with the same infrastructure as AC3
527         deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
528         deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
529         deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
530         deviceInfo.m_streamTypes.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
531         // signal that we can doe AE_FMT_RAW
532         deviceInfo.m_dataFormats.push_back(AE_FMT_RAW);
533       }
534       deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 192000));
535     }
536     else
537     {
538       CLog::LogF(LOGERROR, "Getting DeviceFormat failed (%s)", WASAPIErrToStr(hr));
539     }
540 
541     deviceInfo.m_deviceName       = strDevName;
542     deviceInfo.m_displayName      = strWinDevType.append(strFriendlyName);
543     deviceInfo.m_displayNameExtra = std::string("DIRECTSOUND: ").append(strFriendlyName);
544     deviceInfo.m_deviceType       = aeDeviceType;
545 
546     deviceInfo.m_wantsIECPassthrough = true;
547     deviceInfoList.push_back(deviceInfo);
548 
549     // add the default device with m_deviceName = default
550     if(strDD == strDevName)
551     {
552       deviceInfo.m_deviceName = std::string("default");
553       deviceInfo.m_displayName = std::string("default");
554       deviceInfo.m_displayNameExtra = std::string("");
555       deviceInfo.m_wantsIECPassthrough = true;
556       deviceInfoList.push_back(deviceInfo);
557     }
558   }
559 
560   return;
561 
562 failed:
563 
564   if (FAILED(hr))
565     CLog::LogF(LOGERROR, "Failed to enumerate WASAPI endpoint devices (%s).", WASAPIErrToStr(hr));
566 }
567 
568 ///////////////////////////////////////////////////////////////////////////////
569 
CheckPlayStatus()570 void CAESinkDirectSound::CheckPlayStatus()
571 {
572   DWORD status = 0;
573   if (m_pBuffer->GetStatus(&status) != DS_OK)
574   {
575     CLog::LogF(LOGERROR, "GetStatus() failed");
576     return;
577   }
578 
579   if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
580   {
581     HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
582     CLog::LogF(LOGDEBUG, "Resuming Playback");
583     if (FAILED(hr))
584       CLog::LogF(LOGERROR, "Failed to play the DirectSound buffer: %s", dserr2str(hr));
585   }
586 }
587 
UpdateCacheStatus()588 bool CAESinkDirectSound::UpdateCacheStatus()
589 {
590   CSingleLock lock (m_runLock);
591 
592   DWORD playCursor = 0, writeCursor = 0;
593   HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
594   if (DS_OK != res)
595   {
596     CLog::LogF(LOGERROR,
597                "GetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x",
598                res);
599     m_isDirtyDS = true;
600     return false;
601   }
602 
603   // Check the state of the ring buffer (P->O->W == underrun)
604   // These are the logical situations that can occur
605   // O: CurrentOffset  W: WriteCursor  P: PlayCursor
606   // | | | | | | | | | |
607   // ***O----W----P***** < underrun   P > W && O < W (1)
608   // | | | | | | | | | |
609   // ---P****O----W----- < underrun   O > P && O < W (2)
610   // | | | | | | | | | |
611   // ---W----P****O----- < underrun   P > W && P < O (3)
612   // | | | | | | | | | |
613   // ***W****O----P*****              P > W && P > O (4)
614   // | | | | | | | | | |
615   // ---P****W****O-----              P < W && O > W (5)
616   // | | | | | | | | | |
617   // ***O----P****W*****              P < W && O < P (6)
618 
619   // Check for underruns
620   if ((playCursor > writeCursor && m_BufferOffset < writeCursor) ||    // (1)
621       (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
622       (playCursor > writeCursor && playCursor <  m_BufferOffset))      // (3)
623   {
624     CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:%u, P:%u, O:%u.",
625               writeCursor, playCursor, m_BufferOffset);
626     m_BufferOffset = writeCursor; // Catch up
627     //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
628     m_BufferTimeouts++;
629     if (m_BufferTimeouts > 10)
630     {
631       m_isDirtyDS = true;
632       return false;
633     }
634   }
635   else
636     m_BufferTimeouts = 0;
637 
638   // Calculate available space in the ring buffer
639   if (playCursor == m_BufferOffset && m_BufferOffset ==  writeCursor) // Playback is stopped and we are all at the same place
640     m_CacheLen = 0;
641   else if (m_BufferOffset > playCursor)
642     m_CacheLen = m_BufferOffset - playCursor;
643   else
644     m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
645 
646   return true;
647 }
648 
GetSpace()649 unsigned int CAESinkDirectSound::GetSpace()
650 {
651   CSingleLock lock (m_runLock);
652   if (!UpdateCacheStatus())
653     m_isDirtyDS = true;
654   unsigned int space = m_dwBufferLen - m_CacheLen;
655 
656   // We can never allow the internal buffers to fill up complete
657   // as we get confused between if the buffer is full or empty
658   // so never allow the last chunk to be added
659   if (space > m_dwChunkSize)
660     return space - m_dwChunkSize;
661   else
662     return 0;
663 }
664 
AEChannelsFromSpeakerMask(DWORD speakers)665 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
666 {
667   m_channelLayout.Reset();
668 
669   for (int i = 0; i < DS_SPEAKER_COUNT; i++)
670   {
671     if (speakers & DSChannelOrder[i])
672       m_channelLayout += AEChannelNamesDS[i];
673   }
674 }
675 
SpeakerMaskFromAEChannels(const CAEChannelInfo & channels)676 DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
677 {
678   DWORD mask = 0;
679 
680   for (unsigned int i = 0; i < channels.Count(); i++)
681   {
682     for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
683       if (channels[i] == AEChannelNamesDS[j])
684         mask |= DSChannelOrder[j];
685   }
686 
687   return mask;
688 }
689 
dserr2str(int err)690 const char *CAESinkDirectSound::dserr2str(int err)
691 {
692   switch (err)
693   {
694     case DS_OK: return "DS_OK";
695     case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
696     case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
697     case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
698     case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
699     case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
700     case DSERR_GENERIC: return "DSERR_GENERIC";
701     case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
702     case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
703     case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
704     case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
705     case DSERR_NODRIVER: return "DSERR_NODRIVER";
706     case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
707     case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
708     case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
709     case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
710     case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
711     case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
712     case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
713     case DSERR_BUFFERTOOSMALL: return "DSERR_BUFFERTOOSMALL";
714     case DSERR_DS8_REQUIRED: return "DSERR_DS8_REQUIRED";
715     case DSERR_SENDLOOP: return "DSERR_SENDLOOP";
716     case DSERR_BADSENDBUFFERGUID: return "DSERR_BADSENDBUFFERGUID";
717     case DSERR_OBJECTNOTFOUND: return "DSERR_OBJECTNOTFOUND";
718     case DSERR_FXUNAVAILABLE: return "DSERR_FXUNAVAILABLE";
719     default: return "unknown";
720   }
721 }
722 
GetDefaultDevice()723 std::string CAESinkDirectSound::GetDefaultDevice()
724 {
725   HRESULT hr;
726   ComPtr<IMMDeviceEnumerator> pEnumerator = nullptr;
727   ComPtr<IMMDevice> pDevice = nullptr;
728   ComPtr<IPropertyStore> pProperty = nullptr;
729   PROPVARIANT varName;
730   std::string strDevName = "default";
731 
732   hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(pEnumerator.GetAddressOf()));
733   EXIT_ON_FAILURE(hr, "Could not allocate WASAPI device enumerator")
734 
735   hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, pDevice.GetAddressOf());
736   EXIT_ON_FAILURE(hr, "Retrival of audio endpoint enumeration failed.")
737 
738   hr = pDevice->OpenPropertyStore(STGM_READ, pProperty.GetAddressOf());
739   EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint properties failed.")
740 
741   PropVariantInit(&varName);
742   hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
743   EXIT_ON_FAILURE(hr, "Retrival of DirectSound endpoint form factor failed.")
744 
745   AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
746   PropVariantClear(&varName);
747 
748   hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
749   EXIT_ON_FAILURE(hr, "Retrieval of DirectSound endpoint GUID failed")
750 
751   strDevName = KODI::PLATFORM::WINDOWS::FromW(varName.pwszVal);
752   PropVariantClear(&varName);
753 
754 failed:
755 
756   return strDevName;
757 }
758