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.
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"
27 #include <Audioclient.h>
32 // include order is important here
34 #include <mmdeviceapi.h>
35 #include <Functiondiscoverykeys_devpkey.h>
38 #pragma comment(lib, "Rpcrt4.lib")
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) \
49 CLog::LogF(LOGERROR, reason " - HRESULT = {} ErrorMessage = {}", hr, WASAPIErrToStr(hr)); \
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
;
65 static BOOL CALLBACK
DSEnumCallback(LPGUID lpGuid
, LPCTSTR lpcstrDescription
, LPCTSTR lpcstrModule
, LPVOID lpContext
)
68 std::list
<DSDevice
> &enumerator
= *static_cast<std::list
<DSDevice
>*>(lpContext
);
70 dev
.name
= KODI::PLATFORM::WINDOWS::FromW(lpcstrDescription
);
75 enumerator
.push_back(dev
);
80 CAESinkDirectSound::CAESinkDirectSound() :
83 m_encodedFormat (AE_FMT_INVALID
),
92 m_initialized (false),
95 m_channelLayout
.Reset();
98 CAESinkDirectSound::~CAESinkDirectSound()
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
))
122 bool CAESinkDirectSound::Initialize(AEAudioFormat
&format
, std::string
&device
)
127 bool deviceIsBluetooth
= false;
128 LPGUID deviceGUID
= nullptr;
129 RPC_WSTR wszUuid
= nullptr;
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
)
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();
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)
162 if (device
.strDeviceEnumerator
== "BTHENUM")
164 deviceIsBluetooth
= true;
165 CLog::LogF(LOGDEBUG
, "Audio device '{}' is detected as Bluetooth device", deviceFriendlyName
);
171 hr
= DirectSoundCreate(deviceGUID
, m_pDSound
.ReleaseAndGetAddressOf(), nullptr);
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);
183 CLog::LogF(LOGERROR
, "Failed to create the default DirectSound device with error {}.",
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
);
197 CLog::LogF(LOGERROR
, "Failed to create the DirectSound device cooperative level.");
198 CLog::LogF(LOGERROR
, "DSErr: {}", dserr2str(hr
));
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;
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;
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);
259 if (dsbdesc
.dwFlags
& DSBCAPS_LOCHARDWARE
)
261 CLog::LogF(LOGDEBUG
, "Couldn't create secondary buffer ({}). Trying without LOCHARDWARE.",
263 // Try without DSBCAPS_LOCHARDWARE
264 dsbdesc
.dwFlags
&= ~DSBCAPS_LOCHARDWARE
;
265 res
= m_pDSound
->CreateSoundBuffer(&dsbdesc
, m_pBuffer
.ReleaseAndGetAddressOf(), nullptr);
270 CLog::LogF(LOGERROR
, "cannot create secondary buffer ({})", dserr2str(res
));
274 CLog::LogF(LOGDEBUG
, "secondary buffer created");
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
;
290 m_initialized
= true;
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
);
312 void CAESinkDirectSound::Deinitialize()
317 CLog::LogF(LOGDEBUG
, "Cleaning up");
324 m_initialized
= false;
333 unsigned int CAESinkDirectSound::AddPackets(uint8_t **data
, unsigned int frames
, unsigned int offset
)
338 DWORD total
= m_dwFrameSize
* frames
;
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");
348 if (bufferStatus
& DSBSTATUS_BUFFERLOST
)
350 CLog::LogF(LOGDEBUG
, "Buffer allocation was lost. Restoring buffer.");
351 m_pBuffer
->Restore();
354 while (GetSpace() < total
)
361 std::chrono::milliseconds(static_cast<int>(total
* 1000 / m_AvgBytesPerSec
)));
367 void* start
= nullptr, *startWrap
= nullptr;
368 DWORD size
= 0, sizeWrap
= 0;
369 if (m_BufferOffset
>= m_dwBufferLen
) // Wrap-around manually
371 DWORD dwWriteBytes
= std::min((int)m_dwChunkSize
, (int)len
);
372 HRESULT res
= m_pBuffer
->Lock(m_BufferOffset
, dwWriteBytes
, &start
, &size
, &startWrap
, &sizeWrap
, 0);
375 CLog::LogF(LOGERROR
, "Unable to lock buffer at offset {}. HRESULT: {:#08x}", m_BufferOffset
,
381 memcpy(start
, pBuffer
, size
);
386 m_BufferOffset
+= size
;
387 if (startWrap
) // Write-region wraps to beginning of buffer
389 memcpy(startWrap
, pBuffer
, sizeWrap
);
390 m_BufferOffset
= sizeWrap
;
396 m_CacheLen
+= size
+ sizeWrap
; // This data is now in the cache
397 m_pBuffer
->Unlock(start
, size
, startWrap
, sizeWrap
);
402 return (total
- len
) / m_dwFrameSize
; // Frames used
405 void CAESinkDirectSound::Stop()
411 void CAESinkDirectSound::Drain()
413 if (!m_initialized
|| m_isDirtyDS
)
417 HRESULT res
= m_pBuffer
->SetCurrentPosition(0);
421 "SetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
430 void CAESinkDirectSound::GetDelay(AEDelayStatus
& status
)
438 /* Make sure we know how much data is in the cache */
439 if (!UpdateCacheStatus())
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;
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;
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());
488 CLog::LogF(LOGERROR
, "Retrieval of DirectSound endpoint failed.");
492 hr
= pDevice
->OpenPropertyStore(STGM_READ
, pProperty
.GetAddressOf());
495 CLog::LogF(LOGERROR
, "Retrieval of DirectSound endpoint properties failed.");
499 hr
= pProperty
->GetValue(PKEY_Device_FriendlyName
, &varName
);
502 CLog::LogF(LOGERROR
, "Retrieval of DirectSound endpoint device name failed.");
506 std::string strFriendlyName
= KODI::PLATFORM::WINDOWS::FromW(varName
.pwszVal
);
507 PropVariantClear(&varName
);
509 hr
= pProperty
->GetValue(PKEY_AudioEndpoint_GUID
, &varName
);
512 CLog::LogF(LOGERROR
, "Retrieval of DirectSound endpoint GUID failed.");
516 std::string strDevName
= KODI::PLATFORM::WINDOWS::FromW(varName
.pwszVal
);
517 PropVariantClear(&varName
);
519 hr
= pProperty
->GetValue(PKEY_AudioEndpoint_FormFactor
, &varName
);
522 CLog::LogF(LOGERROR
, "Retrieval of DirectSound endpoint form factor 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));
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
);
584 CLog::LogF(LOGERROR
, "Failed to enumerate WASAPI endpoint devices ({}).", WASAPIErrToStr(hr
));
587 ///////////////////////////////////////////////////////////////////////////////
589 void CAESinkDirectSound::CheckPlayStatus()
592 if (m_pBuffer
->GetStatus(&status
) != DS_OK
)
594 CLog::LogF(LOGERROR
, "GetStatus() failed");
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");
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
616 "GetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
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)
648 if (m_BufferTimeouts
> 10)
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
660 else if (m_BufferOffset
> playCursor
)
661 m_CacheLen
= m_BufferOffset
- playCursor
;
663 m_CacheLen
= m_dwBufferLen
- (playCursor
- m_BufferOffset
);
668 unsigned int CAESinkDirectSound::GetSpace()
670 std::unique_lock
<CCriticalSection
> lock(m_runLock
);
671 if (!UpdateCacheStatus())
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
;
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
)
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
];
709 const char *CAESinkDirectSound::dserr2str(int 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()
745 ComPtr
<IMMDeviceEnumerator
> pEnumerator
= nullptr;
746 ComPtr
<IMMDevice
> pDevice
= nullptr;
747 ComPtr
<IPropertyStore
> pProperty
= nullptr;
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
);