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"
22 #include "platform/win32/WIN32Util.h"
30 // include order is important here
32 #include <mmdeviceapi.h>
33 #include <Functiondiscoverykeys_devpkey.h>
38 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
, WAVE_FORMAT_IEEE_FLOAT
, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
39 DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF
, WAVE_FORMAT_DOLBY_AC3_SPDIF
, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
41 #define EXIT_ON_FAILURE(hr, reason) \
44 CLog::LogF(LOGERROR, reason " - error {}", hr, CWIN32Util::FormatHRESULT(hr)); \
50 constexpr unsigned int DS_SPEAKER_COUNT
= 8U;
51 constexpr unsigned int DSChannelOrder
[] = {
52 SPEAKER_FRONT_LEFT
, SPEAKER_FRONT_RIGHT
, SPEAKER_FRONT_CENTER
, SPEAKER_LOW_FREQUENCY
,
53 SPEAKER_BACK_LEFT
, SPEAKER_BACK_RIGHT
, SPEAKER_SIDE_LEFT
, SPEAKER_SIDE_RIGHT
};
54 constexpr enum AEChannel AEChannelNamesDS
[] = {AE_CH_FL
, AE_CH_FR
, AE_CH_FC
, AE_CH_LFE
, AE_CH_BL
,
55 AE_CH_BR
, AE_CH_SL
, AE_CH_SR
, AE_CH_NULL
};
58 using namespace Microsoft::WRL
;
60 CAESinkDirectSound::CAESinkDirectSound() :
63 m_encodedFormat (AE_FMT_INVALID
),
72 m_initialized (false),
75 m_channelLayout
.Reset();
78 CAESinkDirectSound::~CAESinkDirectSound()
83 void CAESinkDirectSound::Register()
85 AE::AESinkRegEntry reg
;
86 reg
.sinkName
= "DIRECTSOUND";
87 reg
.createFunc
= CAESinkDirectSound::Create
;
88 reg
.enumerateFunc
= CAESinkDirectSound::EnumerateDevicesEx
;
89 AE::CAESinkFactory::RegisterSink(reg
);
92 std::unique_ptr
<IAESink
> CAESinkDirectSound::Create(std::string
& device
,
93 AEAudioFormat
& desiredFormat
)
95 auto sink
= std::make_unique
<CAESinkDirectSound
>();
96 if (sink
->Initialize(desiredFormat
, device
))
102 bool CAESinkDirectSound::Initialize(AEAudioFormat
& format
, std::string
& device
)
107 bool deviceIsBluetooth
= false;
108 GUID deviceGUID
= {};
110 std::string strDeviceGUID
= device
;
111 std::string deviceFriendlyName
= "unknown";
113 if (StringUtils::EndsWithNoCase(device
, "default"))
114 strDeviceGUID
= CAESinkFactoryWin::GetDefaultDeviceId();
116 hr
= CLSIDFromString(KODI::PLATFORM::WINDOWS::ToW(strDeviceGUID
).c_str(), &deviceGUID
);
118 CLog::LogF(LOGERROR
, "Failed to convert the device '{}' to a GUID, with error {}.",
119 strDeviceGUID
, CWIN32Util::FormatHRESULT(hr
));
121 for (const auto& detail
: CAESinkFactoryWin::GetRendererDetails())
123 if (StringUtils::CompareNoCase(detail
.strDeviceId
, strDeviceGUID
) != 0)
126 if (detail
.strDeviceEnumerator
== "BTHENUM")
128 deviceIsBluetooth
= true;
129 CLog::LogF(LOGDEBUG
, "Audio device '{}' is detected as Bluetooth device", deviceFriendlyName
);
132 deviceFriendlyName
= detail
.strDescription
;
137 hr
= DirectSoundCreate(&deviceGUID
, m_pDSound
.ReleaseAndGetAddressOf(), nullptr);
143 "Failed to create the DirectSound device {} with error {}, trying the default device.",
144 deviceFriendlyName
, CWIN32Util::FormatHRESULT(hr
));
146 deviceFriendlyName
= "default";
148 hr
= DirectSoundCreate(nullptr, m_pDSound
.ReleaseAndGetAddressOf(), nullptr);
151 CLog::LogF(LOGERROR
, "Failed to create the default DirectSound device with error {}.",
152 CWIN32Util::FormatHRESULT(hr
));
157 /* Dodge the null handle on first init by using desktop handle */
158 HWND tmp_hWnd
= g_hWnd
== nullptr ? GetDesktopWindow() : g_hWnd
;
159 CLog::LogF(LOGDEBUG
, "Using Window handle: {}", fmt::ptr(tmp_hWnd
));
161 hr
= m_pDSound
->SetCooperativeLevel(tmp_hWnd
, DSSCL_PRIORITY
);
165 CLog::LogF(LOGERROR
, "Failed to create the DirectSound device cooperative level with error {}.",
166 CWIN32Util::FormatHRESULT(hr
));
171 // clamp samplerate between 44100 and 192000
172 if (format
.m_sampleRate
< 44100)
173 format
.m_sampleRate
= 44100;
175 if (format
.m_sampleRate
> 192000)
176 format
.m_sampleRate
= 192000;
179 WAVEFORMATEXTENSIBLE wfxex
= {};
180 wfxex
.Format
.cbSize
= sizeof(WAVEFORMATEXTENSIBLE
)-sizeof(WAVEFORMATEX
);
181 wfxex
.Format
.nChannels
= format
.m_channelLayout
.Count();
182 wfxex
.Format
.nSamplesPerSec
= format
.m_sampleRate
;
183 if (format
.m_dataFormat
== AE_FMT_RAW
)
185 wfxex
.dwChannelMask
= SPEAKER_FRONT_LEFT
| SPEAKER_FRONT_RIGHT
;
186 wfxex
.Format
.wFormatTag
= WAVE_FORMAT_DOLBY_AC3_SPDIF
;
187 wfxex
.SubFormat
= _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF
;
188 wfxex
.Format
.wBitsPerSample
= 16;
189 wfxex
.Format
.nChannels
= 2;
193 wfxex
.dwChannelMask
= SpeakerMaskFromAEChannels(format
.m_channelLayout
);
194 wfxex
.Format
.wFormatTag
= WAVE_FORMAT_EXTENSIBLE
;
195 wfxex
.SubFormat
= _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
;
196 wfxex
.Format
.wBitsPerSample
= 32;
199 wfxex
.Samples
.wValidBitsPerSample
= wfxex
.Format
.wBitsPerSample
;
200 wfxex
.Format
.nBlockAlign
= wfxex
.Format
.nChannels
* (wfxex
.Format
.wBitsPerSample
>> 3);
201 wfxex
.Format
.nAvgBytesPerSec
= wfxex
.Format
.nSamplesPerSec
* wfxex
.Format
.nBlockAlign
;
203 m_AvgBytesPerSec
= wfxex
.Format
.nAvgBytesPerSec
;
205 const unsigned int chunkDurationMs
= deviceIsBluetooth
? 50 : 20;
206 const unsigned int uiFrameCount
= format
.m_sampleRate
* chunkDurationMs
/ 1000U;
207 m_dwFrameSize
= wfxex
.Format
.nBlockAlign
;
208 m_dwChunkSize
= m_dwFrameSize
* uiFrameCount
;
210 // BT: 500ms total buffer (50 * 10); non-BT: 200ms total buffer (20 * 10)
211 m_dwBufferLen
= m_dwChunkSize
* 10;
213 // fill in the secondary sound buffer descriptor
214 DSBUFFERDESC dsbdesc
= {};
215 dsbdesc
.dwSize
= sizeof(DSBUFFERDESC
);
216 dsbdesc
.dwFlags
= DSBCAPS_GETCURRENTPOSITION2
/** Better position accuracy */
217 | DSBCAPS_TRUEPLAYPOSITION
/** Vista+ accurate position */
218 | DSBCAPS_GLOBALFOCUS
; /** Allows background playing */
220 dsbdesc
.dwBufferBytes
= m_dwBufferLen
;
221 dsbdesc
.lpwfxFormat
= (WAVEFORMATEX
*)&wfxex
;
223 // now create the stream buffer
224 HRESULT res
= m_pDSound
->CreateSoundBuffer(&dsbdesc
, m_pBuffer
.ReleaseAndGetAddressOf(), nullptr);
227 if (dsbdesc
.dwFlags
& DSBCAPS_LOCHARDWARE
)
229 CLog::LogF(LOGDEBUG
, "Couldn't create secondary buffer ({}). Trying without LOCHARDWARE.",
230 CWIN32Util::FormatHRESULT(res
));
231 // Try without DSBCAPS_LOCHARDWARE
232 dsbdesc
.dwFlags
&= ~DSBCAPS_LOCHARDWARE
;
233 res
= m_pDSound
->CreateSoundBuffer(&dsbdesc
, m_pBuffer
.ReleaseAndGetAddressOf(), nullptr);
238 CLog::LogF(LOGERROR
, "cannot create secondary buffer ({})", CWIN32Util::FormatHRESULT(res
));
242 CLog::LogF(LOGDEBUG
, "secondary buffer created");
246 AEChannelsFromSpeakerMask(wfxex
.dwChannelMask
);
247 format
.m_channelLayout
= m_channelLayout
;
248 m_encodedFormat
= format
.m_dataFormat
;
249 format
.m_frames
= uiFrameCount
;
250 format
.m_frameSize
= ((format
.m_dataFormat
== AE_FMT_RAW
) ? (wfxex
.Format
.wBitsPerSample
>> 3) : sizeof(float)) * format
.m_channelLayout
.Count();
251 format
.m_dataFormat
= (format
.m_dataFormat
== AE_FMT_RAW
) ? AE_FMT_S16NE
: AE_FMT_FLOAT
;
258 m_initialized
= true;
261 CLog::LogF(LOGDEBUG
, "Initializing DirectSound with the following parameters:");
262 CLog::Log(LOGDEBUG
, " Audio Device : {}", ((std::string
)deviceFriendlyName
));
263 CLog::Log(LOGDEBUG
, " Sample Rate : {}", wfxex
.Format
.nSamplesPerSec
);
264 CLog::Log(LOGDEBUG
, " Sample Format : {}", CAEUtil::DataFormatToStr(format
.m_dataFormat
));
265 CLog::Log(LOGDEBUG
, " Bits Per Sample : {}", wfxex
.Format
.wBitsPerSample
);
266 CLog::Log(LOGDEBUG
, " Valid Bits/Samp : {}", wfxex
.Samples
.wValidBitsPerSample
);
267 CLog::Log(LOGDEBUG
, " Channel Count : {}", wfxex
.Format
.nChannels
);
268 CLog::Log(LOGDEBUG
, " Block Align : {}", wfxex
.Format
.nBlockAlign
);
269 CLog::Log(LOGDEBUG
, " Avg. Bytes Sec : {}", wfxex
.Format
.nAvgBytesPerSec
);
270 CLog::Log(LOGDEBUG
, " Samples/Block : {}", wfxex
.Samples
.wSamplesPerBlock
);
271 CLog::Log(LOGDEBUG
, " Format cBSize : {}", wfxex
.Format
.cbSize
);
272 CLog::Log(LOGDEBUG
, " Channel Layout : {}", ((std::string
)format
.m_channelLayout
));
273 CLog::Log(LOGDEBUG
, " Channel Mask : {}", wfxex
.dwChannelMask
);
274 CLog::Log(LOGDEBUG
, " Frames : {}", format
.m_frames
);
275 CLog::Log(LOGDEBUG
, " Frame Size : {}", format
.m_frameSize
);
280 void CAESinkDirectSound::Deinitialize()
285 CLog::LogF(LOGDEBUG
, "Cleaning up");
292 m_initialized
= false;
301 unsigned int CAESinkDirectSound::AddPackets(uint8_t **data
, unsigned int frames
, unsigned int offset
)
306 DWORD total
= m_dwFrameSize
* frames
;
308 unsigned char* pBuffer
= (unsigned char*)data
[0]+offset
*m_format
.m_frameSize
;
310 DWORD bufferStatus
= 0;
311 if (m_pBuffer
->GetStatus(&bufferStatus
) != DS_OK
)
313 CLog::LogF(LOGERROR
, "GetStatus() failed");
316 if (bufferStatus
& DSBSTATUS_BUFFERLOST
)
318 CLog::LogF(LOGDEBUG
, "Buffer allocation was lost. Restoring buffer.");
319 m_pBuffer
->Restore();
322 while (GetSpace() < total
)
329 std::chrono::milliseconds(static_cast<int>(total
* 1000 / m_AvgBytesPerSec
)));
335 void* start
= nullptr, *startWrap
= nullptr;
336 DWORD size
= 0, sizeWrap
= 0;
337 if (m_BufferOffset
>= m_dwBufferLen
) // Wrap-around manually
339 DWORD dwWriteBytes
= std::min((int)m_dwChunkSize
, (int)len
);
340 HRESULT res
= m_pBuffer
->Lock(m_BufferOffset
, dwWriteBytes
, &start
, &size
, &startWrap
, &sizeWrap
, 0);
343 CLog::LogF(LOGERROR
, "Unable to lock buffer at offset {}. HRESULT: {:#08x}", m_BufferOffset
,
349 memcpy(start
, pBuffer
, size
);
354 m_BufferOffset
+= size
;
355 if (startWrap
) // Write-region wraps to beginning of buffer
357 memcpy(startWrap
, pBuffer
, sizeWrap
);
358 m_BufferOffset
= sizeWrap
;
364 m_CacheLen
+= size
+ sizeWrap
; // This data is now in the cache
365 m_pBuffer
->Unlock(start
, size
, startWrap
, sizeWrap
);
370 return (total
- len
) / m_dwFrameSize
; // Frames used
373 void CAESinkDirectSound::Stop()
379 void CAESinkDirectSound::Drain()
381 if (!m_initialized
|| m_isDirtyDS
)
385 HRESULT res
= m_pBuffer
->SetCurrentPosition(0);
389 "SetCurrentPosition failed. Unable to determine buffer status. HRESULT = {:#08x}",
398 void CAESinkDirectSound::GetDelay(AEDelayStatus
& status
)
406 /* Make sure we know how much data is in the cache */
407 if (!UpdateCacheStatus())
410 /** returns current cached data duration in seconds */
411 status
.SetDelay((double)m_CacheLen
/ (double)m_AvgBytesPerSec
);
414 double CAESinkDirectSound::GetCacheTotal()
416 /** returns total cache capacity in seconds */
417 return (double)m_dwBufferLen
/ (double)m_AvgBytesPerSec
;
420 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList
&deviceInfoList
, bool force
)
422 CAEDeviceInfo deviceInfo
{};
424 for (RendererDetail
& detail
: CAESinkFactoryWin::GetRendererDetails())
426 deviceInfo
.m_channels
.Reset();
427 deviceInfo
.m_dataFormats
.clear();
428 deviceInfo
.m_sampleRates
.clear();
429 deviceInfo
.m_streamTypes
.clear();
431 if (detail
.nChannels
)
432 deviceInfo
.m_channels
=
433 layoutsByChCount
[std::max(std::min(detail
.nChannels
, DS_SPEAKER_COUNT
), 2U)];
434 deviceInfo
.m_dataFormats
.push_back(AEDataFormat(AE_FMT_FLOAT
));
436 if (detail
.eDeviceType
!= AE_DEVTYPE_PCM
)
438 deviceInfo
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_AC3
);
439 // DTS is played with the same infrastructure as AC3
440 deviceInfo
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE
);
441 deviceInfo
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_1024
);
442 deviceInfo
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_2048
);
443 deviceInfo
.m_streamTypes
.push_back(CAEStreamInfo::STREAM_TYPE_DTS_512
);
444 // signal that we can doe AE_FMT_RAW
445 deviceInfo
.m_dataFormats
.push_back(AE_FMT_RAW
);
448 if (detail
.m_samplesPerSec
)
449 deviceInfo
.m_sampleRates
.push_back(std::min(detail
.m_samplesPerSec
, 192000UL));
450 deviceInfo
.m_deviceName
= detail
.strDeviceId
;
451 deviceInfo
.m_displayName
= detail
.strWinDevType
.append(detail
.strDescription
);
452 deviceInfo
.m_displayNameExtra
= std::string("DIRECTSOUND: ").append(detail
.strDescription
);
453 deviceInfo
.m_deviceType
= detail
.eDeviceType
;
454 deviceInfo
.m_wantsIECPassthrough
= true;
455 deviceInfoList
.push_back(deviceInfo
);
457 // add the default device with m_deviceName = default
460 deviceInfo
.m_deviceName
= std::string("default");
461 deviceInfo
.m_displayName
= std::string("default");
462 deviceInfo
.m_displayNameExtra
= std::string("");
463 deviceInfo
.m_wantsIECPassthrough
= true;
464 deviceInfoList
.push_back(deviceInfo
);
469 ///////////////////////////////////////////////////////////////////////////////
471 void CAESinkDirectSound::CheckPlayStatus()
474 if (m_pBuffer
->GetStatus(&status
) != DS_OK
)
476 CLog::LogF(LOGERROR
, "GetStatus() failed");
480 if (!(status
& DSBSTATUS_PLAYING
) &&
481 m_CacheLen
!= 0) // If we have some data, see if we can start playback
483 HRESULT hr
= m_pBuffer
->Play(0, 0, DSBPLAY_LOOPING
);
484 CLog::LogF(LOGDEBUG
, "Resuming Playback");
486 CLog::LogF(LOGERROR
, "Failed to play the DirectSound buffer: {}",
487 CWIN32Util::FormatHRESULT(hr
));
491 bool CAESinkDirectSound::UpdateCacheStatus()
493 std::unique_lock
<CCriticalSection
> lock(m_runLock
);
495 DWORD playCursor
= 0, writeCursor
= 0;
496 HRESULT res
= m_pBuffer
->GetCurrentPosition(&playCursor
, &writeCursor
); // Get the current playback and safe write positions
499 CLog::LogF(LOGERROR
, "GetCurrentPosition failed. Unable to determine buffer status ({})",
500 CWIN32Util::FormatHRESULT(res
));
505 // Check the state of the ring buffer (P->O->W == underrun)
506 // These are the logical situations that can occur
507 // O: CurrentOffset W: WriteCursor P: PlayCursor
508 // | | | | | | | | | |
509 // ***O----W----P***** < underrun P > W && O < W (1)
510 // | | | | | | | | | |
511 // ---P****O----W----- < underrun O > P && O < W (2)
512 // | | | | | | | | | |
513 // ---W----P****O----- < underrun P > W && P < O (3)
514 // | | | | | | | | | |
515 // ***W****O----P***** P > W && P > O (4)
516 // | | | | | | | | | |
517 // ---P****W****O----- P < W && O > W (5)
518 // | | | | | | | | | |
519 // ***O----P****W***** P < W && O < P (6)
521 // Check for underruns
522 if ((playCursor
> writeCursor
&& m_BufferOffset
< writeCursor
) || // (1)
523 (playCursor
< m_BufferOffset
&& m_BufferOffset
< writeCursor
) || // (2)
524 (playCursor
> writeCursor
&& playCursor
< m_BufferOffset
)) // (3)
526 CLog::Log(LOGWARNING
, "CWin32DirectSound::GetSpace - buffer underrun - W:{}, P:{}, O:{}.",
527 writeCursor
, playCursor
, m_BufferOffset
);
528 m_BufferOffset
= writeCursor
; // Catch up
529 //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
531 if (m_BufferTimeouts
> 10)
538 m_BufferTimeouts
= 0;
540 // Calculate available space in the ring buffer
541 if (playCursor
== m_BufferOffset
&& m_BufferOffset
== writeCursor
) // Playback is stopped and we are all at the same place
543 else if (m_BufferOffset
> playCursor
)
544 m_CacheLen
= m_BufferOffset
- playCursor
;
546 m_CacheLen
= m_dwBufferLen
- (playCursor
- m_BufferOffset
);
551 unsigned int CAESinkDirectSound::GetSpace()
553 std::unique_lock
<CCriticalSection
> lock(m_runLock
);
554 if (!UpdateCacheStatus())
556 unsigned int space
= m_dwBufferLen
- m_CacheLen
;
558 // We can never allow the internal buffers to fill up complete
559 // as we get confused between if the buffer is full or empty
560 // so never allow the last chunk to be added
561 if (space
> m_dwChunkSize
)
562 return space
- m_dwChunkSize
;
567 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers
)
569 m_channelLayout
.Reset();
571 for (int i
= 0; i
< DS_SPEAKER_COUNT
; i
++)
573 if (speakers
& DSChannelOrder
[i
])
574 m_channelLayout
+= AEChannelNamesDS
[i
];
578 DWORD
CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo
&channels
)
582 for (unsigned int i
= 0; i
< channels
.Count(); i
++)
584 for (unsigned int j
= 0; j
< DS_SPEAKER_COUNT
; j
++)
585 if (channels
[i
] == AEChannelNamesDS
[j
])
586 mask
|= DSChannelOrder
[j
];