2 * Copyright (C) 2010-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
23 #include "AESinkDirectSound.h"
24 #include "utils/Log.h"
28 #include "threads/SingleLock.h"
29 #include "utils/SystemInfo.h"
30 #include "utils/TimeUtils.h"
31 #include "utils/CharsetConverter.h"
32 #include <Audioclient.h>
34 #include <mmdeviceapi.h>
35 #include <Functiondiscoverykeys_devpkey.h>
37 #include "cores/AudioEngine/Utils/AEUtil.h"
38 #include "utils/StringUtils.h"
39 #pragma comment(lib, "Rpcrt4.lib")
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 );
46 #define EXIT_ON_FAILURE(hr, reason, ...) if(FAILED(hr)) {CLog::Log(LOGERROR, reason " - %s", __VA_ARGS__, WASAPIErrToStr(hr)); goto failed;}
48 #define ERRTOSTR(err) case err: return #err
50 #define DS_SPEAKER_COUNT 8
51 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};
52 static const enum AEChannel AEChannelNames[] = {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};
54 static enum AEChannel layoutsByChCount[9][9] = {
56 {AE_CH_FC, AE_CH_NULL},
57 {AE_CH_FL, AE_CH_FR, AE_CH_NULL},
58 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_NULL},
59 {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
60 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
61 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_LFE, AE_CH_NULL},
62 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_LFE, AE_CH_NULL},
63 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_SL, AE_CH_SR, AE_CH_LFE, AE_CH_NULL}};
71 struct winEndpointsToAEDeviceType
73 std::string winEndpointType;
74 AEDeviceType aeDeviceType;
77 static const winEndpointsToAEDeviceType winEndpoints[EndpointFormFactor_enum_count] =
79 {"Network Device - ", AE_DEVTYPE_PCM},
80 {"Speakers - ", AE_DEVTYPE_PCM},
81 {"LineLevel - ", AE_DEVTYPE_PCM},
82 {"Headphones - ", AE_DEVTYPE_PCM},
83 {"Microphone - ", AE_DEVTYPE_PCM},
84 {"Headset - ", AE_DEVTYPE_PCM},
85 {"Handset - ", AE_DEVTYPE_PCM},
86 {"Digital Passthrough - ", AE_DEVTYPE_IEC958},
87 {"SPDIF - ", AE_DEVTYPE_IEC958},
88 {"HDMI - ", AE_DEVTYPE_HDMI},
89 {"Unknown - ", AE_DEVTYPE_PCM},
92 // implemented in AESinkWASAPI.cpp
93 extern CStdStringA localWideToUtf(LPCWSTR wstr);
95 static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext)
98 std::list<DSDevice> &enumerator = *static_cast<std::list<DSDevice>*>(lpContext);
100 int bufSize = MultiByteToWideChar(CP_ACP, 0, lpcstrDescription, -1, NULL, 0);
101 CStdStringW strW (L"", bufSize);
102 if ( bufSize == 0 || MultiByteToWideChar(CP_ACP, 0, lpcstrDescription, -1, strW.GetBuf(bufSize), bufSize) != bufSize )
106 dev.name = localWideToUtf(strW);
111 enumerator.push_back(dev);
116 CAESinkDirectSound::CAESinkDirectSound() :
119 m_AvgBytesPerSec(0 ),
125 m_LastCacheCheck(0 ),
126 m_BufferTimeouts(0 ),
128 m_initialized (false),
131 m_channelLayout.Reset();
134 CAESinkDirectSound::~CAESinkDirectSound()
139 bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
144 LPGUID deviceGUID = NULL;
145 RPC_CSTR wszUuid = NULL;
147 std::string strDeviceGUID = device;
148 std::list<DSDevice> DSDeviceList;
149 std::string deviceFriendlyName;
150 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
152 if(StringUtils::EndsWith(device, std::string("default")))
153 strDeviceGUID = GetDefaultDevice();
155 for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
159 hr = (UuidToString((*itt).lpGuid, &wszUuid));
160 std::string sztmp = (char*)wszUuid;
161 std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
162 if (strcasecmp(szGUID.c_str(), strDeviceGUID.c_str()) == 0)
164 deviceGUID = (*itt).lpGuid;
165 deviceFriendlyName = (*itt).name.c_str();
169 if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
172 hr = DirectSoundCreate(deviceGUID, &m_pDSound, NULL);
176 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device %s with error %s, trying the default device.", deviceFriendlyName.c_str(), dserr2str(hr));
177 hr = DirectSoundCreate(NULL, &m_pDSound, NULL);
180 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the default DirectSound device with error %s.", dserr2str(hr));
187 /* Dodge the null handle on first init by using desktop handle */
189 tmp_hWnd = GetDesktopWindow();
193 CLog::Log(LOGDEBUG, __FUNCTION__": Using Window handle: %d", tmp_hWnd);
195 hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
199 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device cooperative level.");
200 CLog::Log(LOGERROR, __FUNCTION__": DSErr: %s", dserr2str(hr));
201 m_pDSound->Release();
205 WAVEFORMATEXTENSIBLE wfxex = {0};
208 ZeroMemory(&wfxex, sizeof(WAVEFORMATEXTENSIBLE));
209 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
210 wfxex.Format.nChannels = format.m_channelLayout.Count();
211 wfxex.Format.nSamplesPerSec = format.m_sampleRate;
212 if (AE_IS_RAW(format.m_dataFormat))
214 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
215 wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
216 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
217 wfxex.Format.wBitsPerSample = 16;
218 wfxex.Format.nChannels = 2;
222 wfxex.dwChannelMask = SpeakerMaskFromAEChannels(format.m_channelLayout);
223 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
224 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
225 wfxex.Format.wBitsPerSample = 32;
228 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
229 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
230 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
232 m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
234 unsigned int uiFrameCount = (int)(format.m_sampleRate * 0.01); //default to 10ms chunks
235 m_dwFrameSize = wfxex.Format.nBlockAlign;
236 m_dwChunkSize = m_dwFrameSize * uiFrameCount;
237 m_dwBufferLen = m_dwChunkSize * 12; //120ms total buffer
239 // fill in the secondary sound buffer descriptor
240 DSBUFFERDESC dsbdesc;
241 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
242 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
243 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
244 | DSBCAPS_GLOBALFOCUS; /** Allows background playing */
246 if (!g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionVista))
247 dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE; /** Needed for 5.1 on emu101k, fails by design on Vista */
249 dsbdesc.dwBufferBytes = m_dwBufferLen;
250 dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
252 // now create the stream buffer
253 HRESULT res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
256 if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
258 SAFE_RELEASE(m_pBuffer);
259 CLog::Log(LOGDEBUG, __FUNCTION__": Couldn't create secondary buffer (%s). Trying without LOCHARDWARE.", dserr2str(res));
260 // Try without DSBCAPS_LOCHARDWARE
261 dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
262 res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
266 SAFE_RELEASE(m_pBuffer);
267 CLog::Log(LOGERROR, __FUNCTION__": cannot create secondary buffer (%s)", dserr2str(res));
271 CLog::Log(LOGDEBUG, __FUNCTION__": secondary buffer created");
275 AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
276 format.m_channelLayout = m_channelLayout;
277 format.m_frames = uiFrameCount;
278 format.m_frameSamples = format.m_frames * format.m_channelLayout.Count();
279 format.m_frameSize = (AE_IS_RAW(format.m_dataFormat) ? wfxex.Format.wBitsPerSample >> 3 : sizeof(float)) * format.m_channelLayout.Count();
280 format.m_dataFormat = AE_IS_RAW(format.m_dataFormat) ? AE_FMT_S16NE : AE_FMT_FLOAT;
287 m_LastCacheCheck = XbmcThreads::SystemClockMillis();
288 m_initialized = true;
291 CLog::Log(LOGDEBUG, __FUNCTION__": Initializing DirectSound with the following parameters:");
292 CLog::Log(LOGDEBUG, " Audio Device : %s", ((std::string)deviceFriendlyName).c_str());
293 CLog::Log(LOGDEBUG, " Sample Rate : %d", wfxex.Format.nSamplesPerSec);
294 CLog::Log(LOGDEBUG, " Sample Format : %s", CAEUtil::DataFormatToStr(format.m_dataFormat));
295 CLog::Log(LOGDEBUG, " Bits Per Sample : %d", wfxex.Format.wBitsPerSample);
296 CLog::Log(LOGDEBUG, " Valid Bits/Samp : %d", wfxex.Samples.wValidBitsPerSample);
297 CLog::Log(LOGDEBUG, " Channel Count : %d", wfxex.Format.nChannels);
298 CLog::Log(LOGDEBUG, " Block Align : %d", wfxex.Format.nBlockAlign);
299 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : %d", wfxex.Format.nAvgBytesPerSec);
300 CLog::Log(LOGDEBUG, " Samples/Block : %d", wfxex.Samples.wSamplesPerBlock);
301 CLog::Log(LOGDEBUG, " Format cBSize : %d", wfxex.Format.cbSize);
302 CLog::Log(LOGDEBUG, " Channel Layout : %s", ((std::string)format.m_channelLayout).c_str());
303 CLog::Log(LOGDEBUG, " Channel Mask : %d", wfxex.dwChannelMask);
304 CLog::Log(LOGDEBUG, " Frames : %d", format.m_frames);
305 CLog::Log(LOGDEBUG, " Frame Samples : %d", format.m_frameSamples);
306 CLog::Log(LOGDEBUG, " Frame Size : %d", format.m_frameSize);
311 void CAESinkDirectSound::Deinitialize()
316 CLog::Log(LOGDEBUG, __FUNCTION__": Cleaning up");
321 SAFE_RELEASE(m_pBuffer);
326 m_pDSound->Release();
329 m_initialized = false;
338 bool CAESinkDirectSound::IsCompatible(const AEAudioFormat &format, const std::string &device)
340 if (!m_initialized || m_isDirtyDS)
343 u_int notCompatible = 0;
344 const u_int numTests = 6;
345 std::string strDiffBecause ("");
346 static const char* compatibleParams[numTests] = {":Devices",
351 ":Passthrough Formats"};
353 notCompatible = (notCompatible +!((AE_IS_RAW(format.m_dataFormat) == AE_IS_RAW(m_encodedFormat)) ||
354 (!AE_IS_RAW(format.m_dataFormat) == !AE_IS_RAW(m_encodedFormat)))) << 1;
355 notCompatible = (notCompatible + ((format.m_dataFormat == AE_FMT_EAC3) ||
356 (format.m_dataFormat == AE_FMT_DTSHD ||
357 (format.m_dataFormat == AE_FMT_TRUEHD)))) << 1;
358 notCompatible = (notCompatible + !(format.m_dataFormat == m_format.m_dataFormat)) << 1;
359 notCompatible = (notCompatible + !(format.m_sampleRate == m_format.m_sampleRate)) << 1;
360 notCompatible = (notCompatible + !(format.m_channelLayout.Count() == m_format.m_channelLayout.Count())) << 1;
361 notCompatible = (notCompatible + !(m_device == device));
365 CLog::Log(LOGDEBUG, __FUNCTION__": Formats compatible - reusing existing sink");
369 for (int i = 0; i < numTests ; i++)
371 strDiffBecause += (notCompatible & 0x01) ? (std::string) compatibleParams[i] : "";
372 notCompatible = notCompatible >> 1;
375 CLog::Log(LOGDEBUG, __FUNCTION__": Formats Incompatible due to different %s", strDiffBecause.c_str());
379 unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
384 DWORD total = m_dwFrameSize * frames;
386 unsigned char* pBuffer = (unsigned char*)data;
388 DWORD bufferStatus = 0;
389 m_pBuffer->GetStatus(&bufferStatus);
390 if (bufferStatus & DSBSTATUS_BUFFERLOST)
392 CLog::Log(LOGDEBUG, __FUNCTION__ ": Buffer allocation was lost. Restoring buffer.");
393 m_pBuffer->Restore();
396 while (GetSpace() < total)
403 Sleep(total * 1000 / m_AvgBytesPerSec);
411 LPVOID start = NULL, startWrap = NULL;
412 DWORD size = 0, sizeWrap = 0;
413 if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
415 DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len);
416 HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0);
419 CLog::Log(LOGERROR, __FUNCTION__ ": Unable to lock buffer at offset %u. HRESULT: 0x%08x", m_BufferOffset, res);
424 memcpy(start, pBuffer, size);
429 m_BufferOffset += size;
430 if (startWrap) // Write-region wraps to beginning of buffer
432 memcpy(startWrap, pBuffer, sizeWrap);
433 m_BufferOffset = sizeWrap;
439 m_CacheLen += size + sizeWrap; // This data is now in the cache
440 m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
445 return (total - len) / m_dwFrameSize; // Frames used
448 void CAESinkDirectSound::Stop()
454 void CAESinkDirectSound::Drain()
456 if (!m_initialized || m_isDirtyDS)
460 HRESULT res = m_pBuffer->SetCurrentPosition(0);
463 CLog::Log(LOGERROR,__FUNCTION__ ": SetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res);
471 double CAESinkDirectSound::GetDelay()
476 /* Make sure we know how much data is in the cache */
477 if (!UpdateCacheStatus())
480 /** returns current cached data duration in seconds */
481 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
485 double CAESinkDirectSound::GetCacheTime()
490 /* Make sure we know how much data is in the cache */
493 /** returns current cached data duration in seconds */
494 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
498 double CAESinkDirectSound::GetCacheTotal()
500 /** returns total cache capacity in seconds */
501 return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
504 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
506 CAEDeviceInfo deviceInfo;
508 IMMDeviceEnumerator* pEnumerator = NULL;
509 IMMDeviceCollection* pEnumDevices = NULL;
513 std::string strDD = GetDefaultDevice();
515 /* See if we are on Windows XP */
516 if (!g_sysinfo.IsWindowsVersionAtLeast(CSysInfo::WindowsVersionVista))
518 /* We are on XP - WASAPI not supported - enumerate using DS devices */
519 LPGUID deviceGUID = NULL;
522 std::list<DSDevice> DSDeviceList;
523 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
525 for(std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
527 if (UuidToString((*itt).lpGuid, &cszGUID) != RPC_S_OK)
528 continue; /* could not convert GUID to string - skip device */
530 deviceInfo.m_channels.Reset();
531 deviceInfo.m_dataFormats.clear();
532 deviceInfo.m_sampleRates.clear();
534 szGUID = (LPSTR)cszGUID;
536 deviceInfo.m_deviceName = "{" + szGUID + "}";
537 deviceInfo.m_displayName = (*itt).name;
538 deviceInfo.m_displayNameExtra = std::string("DirectSound: ") + (*itt).name;
540 deviceInfo.m_deviceType = AE_DEVTYPE_PCM;
541 deviceInfo.m_channels = layoutsByChCount[2];
543 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
544 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_AC3));
546 deviceInfo.m_sampleRates.push_back((DWORD) 96000);
548 deviceInfoList.push_back(deviceInfo);
550 // add the default device with m_deviceName = default
551 if(strDD == deviceInfo.m_deviceName)
553 deviceInfo.m_deviceName = std::string("default");
554 deviceInfo.m_displayName = std::string("default");
555 deviceInfo.m_displayNameExtra = std::string("");
556 deviceInfoList.push_back(deviceInfo);
560 RpcStringFree(&cszGUID);
564 /* Windows Vista or later - supporting WASAPI device probing */
565 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
566 EXIT_ON_FAILURE(hr, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %li", hr)
570 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pEnumDevices);
571 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint enumeration failed.")
573 hr = pEnumDevices->GetCount(&uiCount);
574 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint count failed.")
576 for (UINT i = 0; i < uiCount; i++)
578 IMMDevice *pDevice = NULL;
579 IPropertyStore *pProperty = NULL;
581 PropVariantInit(&varName);
583 deviceInfo.m_channels.Reset();
584 deviceInfo.m_dataFormats.clear();
585 deviceInfo.m_sampleRates.clear();
587 hr = pEnumDevices->Item(i, &pDevice);
590 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint failed.");
594 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
597 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
598 SAFE_RELEASE(pDevice);
602 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
605 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint device name failed.");
606 SAFE_RELEASE(pDevice);
607 SAFE_RELEASE(pProperty);
611 std::string strFriendlyName = localWideToUtf(varName.pwszVal);
612 PropVariantClear(&varName);
614 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
617 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
618 SAFE_RELEASE(pDevice);
619 SAFE_RELEASE(pProperty);
623 std::string strDevName = localWideToUtf(varName.pwszVal);
624 PropVariantClear(&varName);
626 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
629 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
630 SAFE_RELEASE(pDevice);
631 SAFE_RELEASE(pProperty);
634 std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
635 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
637 PropVariantClear(&varName);
639 /* In shared mode Windows tells us what format the audio must be in. */
640 IAudioClient *pClient;
641 hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pClient);
644 CLog::Log(LOGERROR, __FUNCTION__": Activate device failed (%s)", WASAPIErrToStr(hr));
648 //hr = pClient->GetMixFormat(&pwfxex);
649 hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
650 if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
652 WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
653 deviceInfo.m_channels = layoutsByChCount[std::max(std::min(smpwfxex->nChannels, (WORD) DS_SPEAKER_COUNT), (WORD) 2)];
654 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
655 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_AC3));
656 deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 192000));
660 CLog::Log(LOGERROR, __FUNCTION__": Getting DeviceFormat failed (%s)", WASAPIErrToStr(hr));
664 SAFE_RELEASE(pDevice);
665 SAFE_RELEASE(pProperty);
667 deviceInfo.m_deviceName = strDevName;
668 deviceInfo.m_displayName = strWinDevType.append(strFriendlyName);
669 deviceInfo.m_displayNameExtra = std::string("DirectSound: ").append(strFriendlyName);
670 deviceInfo.m_deviceType = aeDeviceType;
672 deviceInfoList.push_back(deviceInfo);
674 // add the default device with m_deviceName = default
675 if(strDD == strDevName)
677 deviceInfo.m_deviceName = std::string("default");
678 deviceInfo.m_displayName = std::string("default");
679 deviceInfo.m_displayNameExtra = std::string("");
680 deviceInfoList.push_back(deviceInfo);
689 CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate WASAPI endpoint devices (%s).", WASAPIErrToStr(hr));
691 SAFE_RELEASE(pEnumDevices);
692 SAFE_RELEASE(pEnumerator);
696 ///////////////////////////////////////////////////////////////////////////////
698 void CAESinkDirectSound::CheckPlayStatus()
701 m_pBuffer->GetStatus(&status);
703 if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
705 HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
706 CLog::Log(LOGDEBUG,__FUNCTION__ ": Resuming Playback");
708 CLog::Log(LOGERROR, __FUNCTION__": Failed to play the DirectSound buffer: %s", dserr2str(hr));
712 bool CAESinkDirectSound::UpdateCacheStatus()
714 CSingleLock lock (m_runLock);
716 DWORD playCursor = 0, writeCursor = 0;
717 HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
720 CLog::Log(LOGERROR,__FUNCTION__ ": GetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res);
725 // Check the state of the ring buffer (P->O->W == underrun)
726 // These are the logical situations that can occur
727 // O: CurrentOffset W: WriteCursor P: PlayCursor
728 // | | | | | | | | | |
729 // ***O----W----P***** < underrun P > W && O < W (1)
730 // | | | | | | | | | |
731 // ---P****O----W----- < underrun O > P && O < W (2)
732 // | | | | | | | | | |
733 // ---W----P****O----- < underrun P > W && P < O (3)
734 // | | | | | | | | | |
735 // ***W****O----P***** P > W && P > O (4)
736 // | | | | | | | | | |
737 // ---P****W****O----- P < W && O > W (5)
738 // | | | | | | | | | |
739 // ***O----P****W***** P < W && O < P (6)
741 // Check for underruns
742 if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
743 (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
744 (playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
746 CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:%u, P:%u, O:%u.", writeCursor, playCursor, m_BufferOffset);
747 m_BufferOffset = writeCursor; // Catch up
748 //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
750 if (m_BufferTimeouts > 10)
757 m_BufferTimeouts = 0;
759 // Calculate available space in the ring buffer
760 if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
762 else if (m_BufferOffset > playCursor)
763 m_CacheLen = m_BufferOffset - playCursor;
765 m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
770 unsigned int CAESinkDirectSound::GetSpace()
772 CSingleLock lock (m_runLock);
773 if (!UpdateCacheStatus())
775 unsigned int space = m_dwBufferLen - m_CacheLen;
777 // We can never allow the internal buffers to fill up complete
778 // as we get confused between if the buffer is full or empty
779 // so never allow the last chunk to be added
780 if (space > m_dwChunkSize)
781 return space - m_dwChunkSize;
786 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
788 m_channelLayout.Reset();
790 for (int i = 0; i < DS_SPEAKER_COUNT; i++)
792 if (speakers & DSChannelOrder[i])
793 m_channelLayout += AEChannelNames[i];
797 DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
801 for (unsigned int i = 0; i < channels.Count(); i++)
803 for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
804 if (channels[i] == AEChannelNames[j])
805 mask |= DSChannelOrder[j];
811 const char *CAESinkDirectSound::dserr2str(int err)
815 case DS_OK: return "DS_OK";
816 case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
817 case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
818 case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
819 case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
820 case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
821 case DSERR_GENERIC: return "DSERR_GENERIC";
822 case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
823 case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
824 case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
825 case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
826 case DSERR_NODRIVER: return "DSERR_NODRIVER";
827 case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
828 case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
829 case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
830 case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
831 case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
832 case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
833 case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
834 default: return "unknown";
838 const char *CAESinkDirectSound::WASAPIErrToStr(HRESULT err)
842 ERRTOSTR(AUDCLNT_E_NOT_INITIALIZED);
843 ERRTOSTR(AUDCLNT_E_ALREADY_INITIALIZED);
844 ERRTOSTR(AUDCLNT_E_WRONG_ENDPOINT_TYPE);
845 ERRTOSTR(AUDCLNT_E_DEVICE_INVALIDATED);
846 ERRTOSTR(AUDCLNT_E_NOT_STOPPED);
847 ERRTOSTR(AUDCLNT_E_BUFFER_TOO_LARGE);
848 ERRTOSTR(AUDCLNT_E_OUT_OF_ORDER);
849 ERRTOSTR(AUDCLNT_E_UNSUPPORTED_FORMAT);
850 ERRTOSTR(AUDCLNT_E_INVALID_SIZE);
851 ERRTOSTR(AUDCLNT_E_DEVICE_IN_USE);
852 ERRTOSTR(AUDCLNT_E_BUFFER_OPERATION_PENDING);
853 ERRTOSTR(AUDCLNT_E_THREAD_NOT_REGISTERED);
854 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED);
855 ERRTOSTR(AUDCLNT_E_ENDPOINT_CREATE_FAILED);
856 ERRTOSTR(AUDCLNT_E_SERVICE_NOT_RUNNING);
857 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED);
858 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_ONLY);
859 ERRTOSTR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL);
860 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_SET);
861 ERRTOSTR(AUDCLNT_E_INCORRECT_BUFFER_SIZE);
862 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_ERROR);
863 ERRTOSTR(AUDCLNT_E_CPUUSAGE_EXCEEDED);
864 ERRTOSTR(AUDCLNT_E_BUFFER_ERROR);
865 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED);
866 ERRTOSTR(AUDCLNT_E_INVALID_DEVICE_PERIOD);
868 ERRTOSTR(E_INVALIDARG);
869 ERRTOSTR(E_OUTOFMEMORY);
875 std::string CAESinkDirectSound::GetDefaultDevice()
877 IMMDeviceEnumerator* pEnumerator = NULL;
878 IMMDevice* pDevice = NULL;
879 IPropertyStore* pProperty = NULL;
882 std::string strDevName = "default";
884 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
887 CLog::Log(LOGERROR, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %s", WASAPIErrToStr(hr));
891 hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
894 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of audio endpoint enumeration failed.");
898 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
901 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
905 PropVariantInit(&varName);
906 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
909 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
912 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
913 PropVariantClear(&varName);
915 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
918 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
922 strDevName = localWideToUtf(varName.pwszVal);
923 PropVariantClear(&varName);
927 SAFE_RELEASE(pProperty);
928 SAFE_RELEASE(pDevice);
929 SAFE_RELEASE(pEnumerator);
934 bool CAESinkDirectSound::SoftSuspend()
940 bool CAESinkDirectSound::SoftResume()
942 /* Return false to force re-init by engine */