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 #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 #define EXIT_ON_FAILURE(hr, reason, ...) if(FAILED(hr)) {CLog::Log(LOGERROR, reason " - %s", __VA_ARGS__, WASAPIErrToStr(hr)); goto failed;}
47 #define ERRTOSTR(err) case err: return #err
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 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};
53 static enum AEChannel layoutsByChCount[9][9] = {
55 {AE_CH_FC, AE_CH_NULL},
56 {AE_CH_FL, AE_CH_FR, AE_CH_NULL},
57 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_NULL},
58 {AE_CH_FL, AE_CH_FR, AE_CH_BL, AE_CH_BR, AE_CH_NULL},
59 {AE_CH_FL, AE_CH_FR, AE_CH_FC, 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_LFE, AE_CH_NULL},
61 {AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_LFE, AE_CH_NULL},
62 {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}};
70 struct winEndpointsToAEDeviceType
72 std::string winEndpointType;
73 AEDeviceType aeDeviceType;
76 static const winEndpointsToAEDeviceType winEndpoints[EndpointFormFactor_enum_count] =
78 {"Network Device - ", AE_DEVTYPE_PCM},
79 {"Speakers - ", AE_DEVTYPE_PCM},
80 {"LineLevel - ", AE_DEVTYPE_PCM},
81 {"Headphones - ", AE_DEVTYPE_PCM},
82 {"Microphone - ", AE_DEVTYPE_PCM},
83 {"Headset - ", AE_DEVTYPE_PCM},
84 {"Handset - ", AE_DEVTYPE_PCM},
85 {"Digital Passthrough - ", AE_DEVTYPE_IEC958},
86 {"SPDIF - ", AE_DEVTYPE_IEC958},
87 {"HDMI - ", AE_DEVTYPE_HDMI},
88 {"Unknown - ", AE_DEVTYPE_PCM},
91 // implemented in AESinkWASAPI.cpp
92 extern CStdStringA localWideToUtf(LPCWSTR wstr);
94 static BOOL CALLBACK DSEnumCallback(LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext)
97 std::list<DSDevice> &enumerator = *static_cast<std::list<DSDevice>*>(lpContext);
99 int bufSize = MultiByteToWideChar(CP_ACP, 0, lpcstrDescription, -1, NULL, 0);
100 CStdStringW strW (L"", bufSize);
101 if ( bufSize == 0 || MultiByteToWideChar(CP_ACP, 0, lpcstrDescription, -1, strW.GetBuf(bufSize), bufSize) != bufSize )
105 dev.name = localWideToUtf(strW);
110 enumerator.push_back(dev);
115 CAESinkDirectSound::CAESinkDirectSound() :
116 m_initialized (false),
126 m_channelLayout.Reset();
129 CAESinkDirectSound::~CAESinkDirectSound()
134 bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
139 LPGUID deviceGUID = NULL;
140 RPC_CSTR wszUuid = NULL;
142 std::list<DSDevice> DSDeviceList;
143 std::string deviceFriendlyName;
144 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
146 for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); itt++)
150 hr = (UuidToString((*itt).lpGuid, &wszUuid));
151 std::string sztmp = (char*)wszUuid;
152 std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
153 if (strcasecmp(szGUID.c_str(), device.c_str()) == 0)
155 deviceGUID = (*itt).lpGuid;
156 deviceFriendlyName = (*itt).name.c_str();
160 if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
163 hr = DirectSoundCreate(deviceGUID, &m_pDSound, NULL);
167 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device.");
168 CLog::Log(LOGERROR, __FUNCTION__": DSErr: %s", dserr2str(hr));
174 /* Dodge the null handle on first init by using desktop handle */
176 tmp_hWnd = GetDesktopWindow();
180 CLog::Log(LOGDEBUG, __FUNCTION__": Using Window handle: %d", tmp_hWnd);
182 hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
186 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device cooperative level.");
187 CLog::Log(LOGERROR, __FUNCTION__": DSErr: %s", dserr2str(hr));
188 m_pDSound->Release();
192 WAVEFORMATEXTENSIBLE wfxex = {0};
195 ZeroMemory(&wfxex, sizeof(WAVEFORMATEXTENSIBLE));
196 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
197 wfxex.Format.nChannels = format.m_channelLayout.Count();
198 wfxex.Format.nSamplesPerSec = format.m_sampleRate;
199 if (AE_IS_RAW(format.m_dataFormat))
201 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
202 wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
203 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
204 wfxex.Format.wBitsPerSample = 16;
205 wfxex.Format.nChannels = 2;
209 wfxex.dwChannelMask = SpeakerMaskFromAEChannels(format.m_channelLayout);
210 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
211 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
212 wfxex.Format.wBitsPerSample = 32;
215 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
216 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
217 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
219 m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
221 unsigned int uiFrameCount = (int)(format.m_sampleRate * 0.01); //default to 10ms chunks
222 m_dwFrameSize = wfxex.Format.nBlockAlign;
223 m_dwChunkSize = m_dwFrameSize * uiFrameCount;
224 m_dwBufferLen = m_dwChunkSize * 12; //120ms total buffer
226 // fill in the secondary sound buffer descriptor
227 DSBUFFERDESC dsbdesc;
228 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
229 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
230 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
231 | DSBCAPS_GLOBALFOCUS; /** Allows background playing */
233 if (!g_sysinfo.IsVistaOrHigher())
234 dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE; /** Needed for 5.1 on emu101k, fails by design on Vista */
236 dsbdesc.dwBufferBytes = m_dwBufferLen;
237 dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
239 // now create the stream buffer
240 HRESULT res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
243 if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
245 SAFE_RELEASE(m_pBuffer);
246 CLog::Log(LOGDEBUG, __FUNCTION__": Couldn't create secondary buffer (%s). Trying without LOCHARDWARE.", dserr2str(res));
247 // Try without DSBCAPS_LOCHARDWARE
248 dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
249 res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
253 SAFE_RELEASE(m_pBuffer);
254 CLog::Log(LOGERROR, __FUNCTION__": cannot create secondary buffer (%s)", dserr2str(res));
258 CLog::Log(LOGDEBUG, __FUNCTION__": secondary buffer created");
262 AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
263 format.m_channelLayout = m_channelLayout;
264 format.m_frames = uiFrameCount;
265 format.m_frameSamples = format.m_frames * format.m_channelLayout.Count();
266 format.m_frameSize = (AE_IS_RAW(format.m_dataFormat) ? wfxex.Format.wBitsPerSample >> 3 : sizeof(float)) * format.m_channelLayout.Count();
267 format.m_dataFormat = AE_IS_RAW(format.m_dataFormat) ? AE_FMT_S16NE : AE_FMT_FLOAT;
274 m_LastCacheCheck = XbmcThreads::SystemClockMillis();
275 m_initialized = true;
278 CLog::Log(LOGDEBUG, __FUNCTION__": Initializing DirectSound with the following parameters:");
279 CLog::Log(LOGDEBUG, " Audio Device : %s", ((std::string)deviceFriendlyName).c_str());
280 CLog::Log(LOGDEBUG, " Sample Rate : %d", wfxex.Format.nSamplesPerSec);
281 CLog::Log(LOGDEBUG, " Sample Format : %s", CAEUtil::DataFormatToStr(format.m_dataFormat));
282 CLog::Log(LOGDEBUG, " Bits Per Sample : %d", wfxex.Format.wBitsPerSample);
283 CLog::Log(LOGDEBUG, " Valid Bits/Samp : %d", wfxex.Samples.wValidBitsPerSample);
284 CLog::Log(LOGDEBUG, " Channel Count : %d", wfxex.Format.nChannels);
285 CLog::Log(LOGDEBUG, " Block Align : %d", wfxex.Format.nBlockAlign);
286 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : %d", wfxex.Format.nAvgBytesPerSec);
287 CLog::Log(LOGDEBUG, " Samples/Block : %d", wfxex.Samples.wSamplesPerBlock);
288 CLog::Log(LOGDEBUG, " Format cBSize : %d", wfxex.Format.cbSize);
289 CLog::Log(LOGDEBUG, " Channel Layout : %s", ((std::string)format.m_channelLayout).c_str());
290 CLog::Log(LOGDEBUG, " Channel Mask : %d", wfxex.dwChannelMask);
291 CLog::Log(LOGDEBUG, " Frames : %d", format.m_frames);
292 CLog::Log(LOGDEBUG, " Frame Samples : %d", format.m_frameSamples);
293 CLog::Log(LOGDEBUG, " Frame Size : %d", format.m_frameSize);
298 void CAESinkDirectSound::Deinitialize()
303 CLog::Log(LOGDEBUG, __FUNCTION__": Cleaning up");
308 SAFE_RELEASE(m_pBuffer);
313 m_pDSound->Release();
316 m_initialized = false;
325 bool CAESinkDirectSound::IsCompatible(const AEAudioFormat format, const std::string device)
327 if (!m_initialized || m_isDirtyDS)
330 u_int notCompatible = 0;
331 const u_int numTests = 6;
332 std::string strDiffBecause ("");
333 static const char* compatibleParams[numTests] = {":Devices",
338 ":Passthrough Formats"};
340 notCompatible = (notCompatible +!((AE_IS_RAW(format.m_dataFormat) == AE_IS_RAW(m_encodedFormat)) ||
341 (!AE_IS_RAW(format.m_dataFormat) == !AE_IS_RAW(m_encodedFormat)))) << 1;
342 notCompatible = (notCompatible + ((format.m_dataFormat == AE_FMT_EAC3) ||
343 (format.m_dataFormat == AE_FMT_DTSHD ||
344 (format.m_dataFormat == AE_FMT_TRUEHD)))) << 1;
345 notCompatible = (notCompatible + !(format.m_dataFormat == m_format.m_dataFormat)) << 1;
346 notCompatible = (notCompatible + !(format.m_sampleRate == m_format.m_sampleRate)) << 1;
347 notCompatible = (notCompatible + !(format.m_channelLayout.Count() == m_format.m_channelLayout.Count())) << 1;
348 notCompatible = (notCompatible + !(m_device == device));
352 CLog::Log(LOGDEBUG, __FUNCTION__": Formats compatible - reusing existing sink");
356 for (int i = 0; i < numTests ; i++)
358 strDiffBecause += (notCompatible & 0x01) ? (std::string) compatibleParams[i] : "";
359 notCompatible = notCompatible >> 1;
362 CLog::Log(LOGDEBUG, __FUNCTION__": Formats Incompatible due to different %s", strDiffBecause.c_str());
366 unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio)
371 DWORD total = m_dwFrameSize * frames;
373 unsigned char* pBuffer = (unsigned char*)data;
375 DWORD bufferStatus = 0;
376 m_pBuffer->GetStatus(&bufferStatus);
377 if (bufferStatus & DSBSTATUS_BUFFERLOST)
379 CLog::Log(LOGDEBUG, __FUNCTION__ ": Buffer allocation was lost. Restoring buffer.");
380 m_pBuffer->Restore();
383 while (GetSpace() < total)
387 Sleep(total * 1000 / m_AvgBytesPerSec);
392 LPVOID start = NULL, startWrap = NULL;
393 DWORD size = 0, sizeWrap = 0;
394 if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
396 HRESULT res = m_pBuffer->Lock(m_BufferOffset, m_dwChunkSize, &start, &size, &startWrap, &sizeWrap, 0);
399 CLog::Log(LOGERROR, __FUNCTION__ ": Unable to lock buffer at offset %u. HRESULT: 0x%08x", m_BufferOffset, res);
404 memcpy(start, pBuffer, size);
409 m_BufferOffset += size;
410 if (startWrap) // Write-region wraps to beginning of buffer
412 memcpy(startWrap, pBuffer, sizeWrap);
413 m_BufferOffset = sizeWrap;
419 m_CacheLen += size + sizeWrap; // This data is now in the cache
420 m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
425 return (total - len) / m_dwFrameSize; // Frames used
428 void CAESinkDirectSound::Stop()
434 double CAESinkDirectSound::GetDelay()
439 /* Make sure we know how much data is in the cache */
440 if (!UpdateCacheStatus())
443 /** returns current cached data duration in seconds */
444 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
448 double CAESinkDirectSound::GetCacheTime()
453 /* Make sure we know how much data is in the cache */
456 /** returns current cached data duration in seconds */
457 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
461 double CAESinkDirectSound::GetCacheTotal()
463 /** returns total cache capacity in seconds */
464 return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
467 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
469 CAEDeviceInfo deviceInfo;
471 IMMDeviceEnumerator* pEnumerator = NULL;
472 IMMDeviceCollection* pEnumDevices = NULL;
474 WAVEFORMATEX* pwfxex = NULL;
477 /* See if we are on Windows XP */
478 if (!g_sysinfo.IsVistaOrHigher())
480 /* We are on XP - WASAPI not supported - enumerate using DS devices */
481 LPGUID deviceGUID = NULL;
484 std::list<DSDevice> DSDeviceList;
485 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
487 for(std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); itt++)
489 if (UuidToString((*itt).lpGuid, &cszGUID) != RPC_S_OK)
490 continue; /* could not convert GUID to string - skip device */
492 deviceInfo.m_channels.Reset();
493 deviceInfo.m_dataFormats.clear();
494 deviceInfo.m_sampleRates.clear();
496 szGUID = (LPSTR)cszGUID;
498 deviceInfo.m_deviceName = "{" + szGUID + "}";
499 deviceInfo.m_displayName = (*itt).name;
500 deviceInfo.m_displayNameExtra = std::string("DirectSound: ") + (*itt).name;
502 deviceInfo.m_deviceType = AE_DEVTYPE_PCM;
503 deviceInfo.m_channels = layoutsByChCount[2];
505 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
506 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_AC3));
508 deviceInfo.m_sampleRates.push_back((DWORD) 96000);
510 deviceInfoList.push_back(deviceInfo);
513 RpcStringFree(&cszGUID);
517 /* Windows Vista or later - supporting WASAPI device probing */
518 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
519 EXIT_ON_FAILURE(hr, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %li", hr)
523 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pEnumDevices);
524 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint enumeration failed.")
526 hr = pEnumDevices->GetCount(&uiCount);
527 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint count failed.")
529 for (UINT i = 0; i < uiCount; i++)
531 IMMDevice *pDevice = NULL;
532 IPropertyStore *pProperty = NULL;
534 PropVariantInit(&varName);
536 deviceInfo.m_channels.Reset();
537 deviceInfo.m_dataFormats.clear();
538 deviceInfo.m_sampleRates.clear();
540 hr = pEnumDevices->Item(i, &pDevice);
543 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint failed.");
547 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
550 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
551 SAFE_RELEASE(pDevice);
555 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
558 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint device name failed.");
559 SAFE_RELEASE(pDevice);
560 SAFE_RELEASE(pProperty);
564 std::string strFriendlyName = localWideToUtf(varName.pwszVal);
565 PropVariantClear(&varName);
567 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
570 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
571 SAFE_RELEASE(pDevice);
572 SAFE_RELEASE(pProperty);
576 std::string strDevName = localWideToUtf(varName.pwszVal);
577 PropVariantClear(&varName);
579 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
582 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
583 SAFE_RELEASE(pDevice);
584 SAFE_RELEASE(pProperty);
587 std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
588 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
590 PropVariantClear(&varName);
592 /* In shared mode Windows tells us what format the audio must be in. */
593 IAudioClient *pClient;
594 hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pClient);
597 CLog::Log(LOGERROR, __FUNCTION__": Activate device failed (%s)", WASAPIErrToStr(hr));
601 //hr = pClient->GetMixFormat(&pwfxex);
602 hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
603 if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
605 WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
606 deviceInfo.m_channels = layoutsByChCount[std::min(smpwfxex->nChannels, (WORD) 2)];
607 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
608 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_AC3));
609 deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 96000));
613 CLog::Log(LOGERROR, __FUNCTION__": Getting DeviceFormat failed (%s)", WASAPIErrToStr(hr));
617 SAFE_RELEASE(pDevice);
618 SAFE_RELEASE(pProperty);
620 deviceInfo.m_deviceName = strDevName;
621 deviceInfo.m_displayName = strWinDevType.append(strFriendlyName);
622 deviceInfo.m_displayNameExtra = std::string("DirectSound: ").append(strFriendlyName);
623 deviceInfo.m_deviceType = aeDeviceType;
625 deviceInfoList.push_back(deviceInfo);
628 // since AE takes the first device in deviceInfoList as default audio device we need
629 // to sort it in order to use the real default device
630 if(deviceInfoList.size() > 1)
632 std::string strDD = GetDefaultDevice();
633 for (AEDeviceInfoList::iterator itt = deviceInfoList.begin(); itt != deviceInfoList.end(); ++itt)
635 CAEDeviceInfo devInfo = *itt;
636 if(devInfo.m_deviceName == strDD)
638 deviceInfoList.erase(itt);
639 deviceInfoList.insert(deviceInfoList.begin(), devInfo);
649 CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate WASAPI endpoint devices (%s).", WASAPIErrToStr(hr));
651 SAFE_RELEASE(pEnumDevices);
652 SAFE_RELEASE(pEnumerator);
656 ///////////////////////////////////////////////////////////////////////////////
658 void CAESinkDirectSound::CheckPlayStatus()
661 m_pBuffer->GetStatus(&status);
663 if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
665 HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
666 CLog::Log(LOGDEBUG,__FUNCTION__ ": Resuming Playback");
668 CLog::Log(LOGERROR, __FUNCTION__": Failed to play the DirectSound buffer: %s", dserr2str(hr));
672 bool CAESinkDirectSound::UpdateCacheStatus()
674 CSingleLock lock (m_runLock);
675 // TODO: Check to see if we may have cycled around since last time
676 unsigned int time = XbmcThreads::SystemClockMillis();
677 if (time == m_LastCacheCheck)
678 return true; // Don't recalc more frequently than once/ms (that is our max resolution anyway)
680 DWORD playCursor = 0, writeCursor = 0;
681 HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
684 CLog::Log(LOGERROR,__FUNCTION__ ": GetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res);
689 m_LastCacheCheck = time;
690 // Check the state of the ring buffer (P->O->W == underrun)
691 // These are the logical situations that can occur
692 // O: CurrentOffset W: WriteCursor P: PlayCursor
693 // | | | | | | | | | |
694 // ***O----W----P***** < underrun P > W && O < W (1)
695 // | | | | | | | | | |
696 // ---P****O----W----- < underrun O > P && O < W (2)
697 // | | | | | | | | | |
698 // ---W----P****O----- < underrun P > W && P < O (3)
699 // | | | | | | | | | |
700 // ***W****O----P***** P > W && P > O (4)
701 // | | | | | | | | | |
702 // ---P****W****O----- P < W && O > W (5)
703 // | | | | | | | | | |
704 // ***O----P****W***** P < W && O < P (6)
706 // Check for underruns
707 if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
708 (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
709 (playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
711 CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:%u, P:%u, O:%u.", writeCursor, playCursor, m_BufferOffset);
712 m_BufferOffset = writeCursor; // Catch up
713 //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
715 if (m_BufferTimeouts > 10)
721 else m_BufferTimeouts = 0;
723 // Calculate available space in the ring buffer
724 if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
726 else if (m_BufferOffset > playCursor)
727 m_CacheLen = m_BufferOffset - playCursor;
729 m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
734 unsigned int CAESinkDirectSound::GetSpace()
736 CSingleLock lock (m_runLock);
737 if (!UpdateCacheStatus())
739 unsigned int space = m_dwBufferLen - m_CacheLen;
741 // We can never allow the internal buffers to fill up complete
742 // as we get confused between if the buffer is full or empty
743 // so never allow the last chunk to be added
744 if (space > m_dwChunkSize)
745 return space - m_dwChunkSize;
750 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
754 m_channelLayout.Reset();
756 for (int i = 0; i < DS_SPEAKER_COUNT; i++)
758 if (speakers & DSChannelOrder[i])
759 m_channelLayout += AEChannelNames[i];
763 DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
767 for (unsigned int i = 0; i < channels.Count(); i++)
769 for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
770 if (channels[i] == AEChannelNames[j])
771 mask |= DSChannelOrder[j];
777 const char *CAESinkDirectSound::dserr2str(int err)
781 case DS_OK: return "DS_OK";
782 case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
783 case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
784 case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
785 case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
786 case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
787 case DSERR_GENERIC: return "DSERR_GENERIC";
788 case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
789 case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
790 case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
791 case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
792 case DSERR_NODRIVER: return "DSERR_NODRIVER";
793 case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
794 case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
795 case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
796 case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
797 case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
798 case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
799 case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
800 default: return "unknown";
804 const char *CAESinkDirectSound::WASAPIErrToStr(HRESULT err)
808 ERRTOSTR(AUDCLNT_E_NOT_INITIALIZED);
809 ERRTOSTR(AUDCLNT_E_ALREADY_INITIALIZED);
810 ERRTOSTR(AUDCLNT_E_WRONG_ENDPOINT_TYPE);
811 ERRTOSTR(AUDCLNT_E_DEVICE_INVALIDATED);
812 ERRTOSTR(AUDCLNT_E_NOT_STOPPED);
813 ERRTOSTR(AUDCLNT_E_BUFFER_TOO_LARGE);
814 ERRTOSTR(AUDCLNT_E_OUT_OF_ORDER);
815 ERRTOSTR(AUDCLNT_E_UNSUPPORTED_FORMAT);
816 ERRTOSTR(AUDCLNT_E_INVALID_SIZE);
817 ERRTOSTR(AUDCLNT_E_DEVICE_IN_USE);
818 ERRTOSTR(AUDCLNT_E_BUFFER_OPERATION_PENDING);
819 ERRTOSTR(AUDCLNT_E_THREAD_NOT_REGISTERED);
820 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED);
821 ERRTOSTR(AUDCLNT_E_ENDPOINT_CREATE_FAILED);
822 ERRTOSTR(AUDCLNT_E_SERVICE_NOT_RUNNING);
823 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED);
824 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_ONLY);
825 ERRTOSTR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL);
826 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_SET);
827 ERRTOSTR(AUDCLNT_E_INCORRECT_BUFFER_SIZE);
828 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_ERROR);
829 ERRTOSTR(AUDCLNT_E_CPUUSAGE_EXCEEDED);
830 ERRTOSTR(AUDCLNT_E_BUFFER_ERROR);
831 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED);
832 ERRTOSTR(AUDCLNT_E_INVALID_DEVICE_PERIOD);
834 ERRTOSTR(E_INVALIDARG);
835 ERRTOSTR(E_OUTOFMEMORY);
841 std::string CAESinkDirectSound::GetDefaultDevice()
843 IMMDeviceEnumerator* pEnumerator = NULL;
844 IMMDevice* pDevice = NULL;
845 IPropertyStore* pProperty = NULL;
848 std::string strDevName = "default";
850 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
853 CLog::Log(LOGERROR, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %s", WASAPIErrToStr(hr));
857 hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
860 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of audio endpoint enumeration failed.");
864 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
867 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
871 PropVariantInit(&varName);
872 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
875 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
878 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
879 PropVariantClear(&varName);
881 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
884 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
888 strDevName = localWideToUtf(varName.pwszVal);
889 PropVariantClear(&varName);
893 SAFE_RELEASE(pProperty);
894 SAFE_RELEASE(pDevice);
895 SAFE_RELEASE(pEnumerator);