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