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_encodedFormat (AE_FMT_INVALID),
120 m_AvgBytesPerSec(0 ),
126 m_LastCacheCheck(0 ),
127 m_BufferTimeouts(0 ),
129 m_initialized (false),
132 m_channelLayout.Reset();
135 CAESinkDirectSound::~CAESinkDirectSound()
140 bool CAESinkDirectSound::Initialize(AEAudioFormat &format, std::string &device)
145 LPGUID deviceGUID = NULL;
146 RPC_CSTR wszUuid = NULL;
148 std::string strDeviceGUID = device;
149 std::list<DSDevice> DSDeviceList;
150 std::string deviceFriendlyName;
151 DirectSoundEnumerate(DSEnumCallback, &DSDeviceList);
153 if(StringUtils::EndsWithNoCase(device, std::string("default")))
154 strDeviceGUID = GetDefaultDevice();
156 for (std::list<DSDevice>::iterator itt = DSDeviceList.begin(); itt != DSDeviceList.end(); ++itt)
160 hr = (UuidToString((*itt).lpGuid, &wszUuid));
161 std::string sztmp = (char*)wszUuid;
162 std::string szGUID = "{" + std::string(sztmp.begin(), sztmp.end()) + "}";
163 if (strcasecmp(szGUID.c_str(), strDeviceGUID.c_str()) == 0)
165 deviceGUID = (*itt).lpGuid;
166 deviceFriendlyName = (*itt).name.c_str();
170 if (hr == RPC_S_OK) RpcStringFree(&wszUuid);
173 hr = DirectSoundCreate(deviceGUID, &m_pDSound, NULL);
177 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device %s with error %s, trying the default device.", deviceFriendlyName.c_str(), dserr2str(hr));
178 hr = DirectSoundCreate(NULL, &m_pDSound, NULL);
181 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the default DirectSound device with error %s.", dserr2str(hr));
188 /* Dodge the null handle on first init by using desktop handle */
190 tmp_hWnd = GetDesktopWindow();
194 CLog::Log(LOGDEBUG, __FUNCTION__": Using Window handle: %d", tmp_hWnd);
196 hr = m_pDSound->SetCooperativeLevel(tmp_hWnd, DSSCL_PRIORITY);
200 CLog::Log(LOGERROR, __FUNCTION__": Failed to create the DirectSound device cooperative level.");
201 CLog::Log(LOGERROR, __FUNCTION__": DSErr: %s", dserr2str(hr));
202 m_pDSound->Release();
206 WAVEFORMATEXTENSIBLE wfxex = {0};
209 ZeroMemory(&wfxex, sizeof(WAVEFORMATEXTENSIBLE));
210 wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
211 wfxex.Format.nChannels = format.m_channelLayout.Count();
212 wfxex.Format.nSamplesPerSec = format.m_sampleRate;
213 if (AE_IS_RAW(format.m_dataFormat))
215 wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
216 wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
217 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
218 wfxex.Format.wBitsPerSample = 16;
219 wfxex.Format.nChannels = 2;
223 wfxex.dwChannelMask = SpeakerMaskFromAEChannels(format.m_channelLayout);
224 wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
225 wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
226 wfxex.Format.wBitsPerSample = 32;
229 wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
230 wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
231 wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
233 m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
235 unsigned int uiFrameCount = (int)(format.m_sampleRate * 0.01); //default to 10ms chunks
236 m_dwFrameSize = wfxex.Format.nBlockAlign;
237 m_dwChunkSize = m_dwFrameSize * uiFrameCount;
238 m_dwBufferLen = m_dwChunkSize * 12; //120ms total buffer
240 // fill in the secondary sound buffer descriptor
241 DSBUFFERDESC dsbdesc;
242 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
243 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
244 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
245 | DSBCAPS_GLOBALFOCUS; /** Allows background playing */
247 dsbdesc.dwBufferBytes = m_dwBufferLen;
248 dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
250 // now create the stream buffer
251 HRESULT res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
254 if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE)
256 SAFE_RELEASE(m_pBuffer);
257 CLog::Log(LOGDEBUG, __FUNCTION__": Couldn't create secondary buffer (%s). Trying without LOCHARDWARE.", dserr2str(res));
258 // Try without DSBCAPS_LOCHARDWARE
259 dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
260 res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
264 SAFE_RELEASE(m_pBuffer);
265 CLog::Log(LOGERROR, __FUNCTION__": cannot create secondary buffer (%s)", dserr2str(res));
269 CLog::Log(LOGDEBUG, __FUNCTION__": secondary buffer created");
273 AEChannelsFromSpeakerMask(wfxex.dwChannelMask);
274 format.m_channelLayout = m_channelLayout;
275 m_encodedFormat = format.m_dataFormat;
276 format.m_frames = uiFrameCount;
277 format.m_frameSamples = format.m_frames * format.m_channelLayout.Count();
278 format.m_frameSize = (AE_IS_RAW(format.m_dataFormat) ? wfxex.Format.wBitsPerSample >> 3 : sizeof(float)) * format.m_channelLayout.Count();
279 format.m_dataFormat = AE_IS_RAW(format.m_dataFormat) ? AE_FMT_S16NE : AE_FMT_FLOAT;
286 m_LastCacheCheck = XbmcThreads::SystemClockMillis();
287 m_initialized = true;
290 CLog::Log(LOGDEBUG, __FUNCTION__": Initializing DirectSound with the following parameters:");
291 CLog::Log(LOGDEBUG, " Audio Device : %s", ((std::string)deviceFriendlyName).c_str());
292 CLog::Log(LOGDEBUG, " Sample Rate : %d", wfxex.Format.nSamplesPerSec);
293 CLog::Log(LOGDEBUG, " Sample Format : %s", CAEUtil::DataFormatToStr(format.m_dataFormat));
294 CLog::Log(LOGDEBUG, " Bits Per Sample : %d", wfxex.Format.wBitsPerSample);
295 CLog::Log(LOGDEBUG, " Valid Bits/Samp : %d", wfxex.Samples.wValidBitsPerSample);
296 CLog::Log(LOGDEBUG, " Channel Count : %d", wfxex.Format.nChannels);
297 CLog::Log(LOGDEBUG, " Block Align : %d", wfxex.Format.nBlockAlign);
298 CLog::Log(LOGDEBUG, " Avg. Bytes Sec : %d", wfxex.Format.nAvgBytesPerSec);
299 CLog::Log(LOGDEBUG, " Samples/Block : %d", wfxex.Samples.wSamplesPerBlock);
300 CLog::Log(LOGDEBUG, " Format cBSize : %d", wfxex.Format.cbSize);
301 CLog::Log(LOGDEBUG, " Channel Layout : %s", ((std::string)format.m_channelLayout).c_str());
302 CLog::Log(LOGDEBUG, " Channel Mask : %d", wfxex.dwChannelMask);
303 CLog::Log(LOGDEBUG, " Frames : %d", format.m_frames);
304 CLog::Log(LOGDEBUG, " Frame Samples : %d", format.m_frameSamples);
305 CLog::Log(LOGDEBUG, " Frame Size : %d", format.m_frameSize);
310 void CAESinkDirectSound::Deinitialize()
315 CLog::Log(LOGDEBUG, __FUNCTION__": Cleaning up");
320 SAFE_RELEASE(m_pBuffer);
325 m_pDSound->Release();
328 m_initialized = false;
337 bool CAESinkDirectSound::IsCompatible(const AEAudioFormat &format, const std::string &device)
339 if (!m_initialized || m_isDirtyDS)
342 u_int notCompatible = 0;
343 const u_int numTests = 6;
344 std::string strDiffBecause ("");
345 static const char* compatibleParams[numTests] = {":Devices",
350 ":Passthrough Formats"};
352 notCompatible = (notCompatible +!((AE_IS_RAW(format.m_dataFormat) == AE_IS_RAW(m_encodedFormat)) ||
353 (!AE_IS_RAW(format.m_dataFormat) == !AE_IS_RAW(m_encodedFormat)))) << 1;
354 notCompatible = (notCompatible + ((format.m_dataFormat == AE_FMT_EAC3) ||
355 (format.m_dataFormat == AE_FMT_DTSHD ||
356 (format.m_dataFormat == AE_FMT_TRUEHD)))) << 1;
357 notCompatible = (notCompatible + !(format.m_dataFormat == m_format.m_dataFormat)) << 1;
358 notCompatible = (notCompatible + !(format.m_sampleRate == m_format.m_sampleRate)) << 1;
359 notCompatible = (notCompatible + !(format.m_channelLayout.Count() == m_format.m_channelLayout.Count())) << 1;
360 notCompatible = (notCompatible + !(m_device == device));
364 CLog::Log(LOGDEBUG, __FUNCTION__": Formats compatible - reusing existing sink");
368 for (int i = 0; i < numTests ; i++)
370 strDiffBecause += (notCompatible & 0x01) ? (std::string) compatibleParams[i] : "";
371 notCompatible = notCompatible >> 1;
374 CLog::Log(LOGDEBUG, __FUNCTION__": Formats Incompatible due to different %s", strDiffBecause.c_str());
378 unsigned int CAESinkDirectSound::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
383 DWORD total = m_dwFrameSize * frames;
385 unsigned char* pBuffer = (unsigned char*)data;
387 DWORD bufferStatus = 0;
388 m_pBuffer->GetStatus(&bufferStatus);
389 if (bufferStatus & DSBSTATUS_BUFFERLOST)
391 CLog::Log(LOGDEBUG, __FUNCTION__ ": Buffer allocation was lost. Restoring buffer.");
392 m_pBuffer->Restore();
395 while (GetSpace() < total)
402 Sleep(total * 1000 / m_AvgBytesPerSec);
410 LPVOID start = NULL, startWrap = NULL;
411 DWORD size = 0, sizeWrap = 0;
412 if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
414 DWORD dwWriteBytes = std::min((int)m_dwChunkSize, (int)len);
415 HRESULT res = m_pBuffer->Lock(m_BufferOffset, dwWriteBytes, &start, &size, &startWrap, &sizeWrap, 0);
418 CLog::Log(LOGERROR, __FUNCTION__ ": Unable to lock buffer at offset %u. HRESULT: 0x%08x", m_BufferOffset, res);
423 memcpy(start, pBuffer, size);
428 m_BufferOffset += size;
429 if (startWrap) // Write-region wraps to beginning of buffer
431 memcpy(startWrap, pBuffer, sizeWrap);
432 m_BufferOffset = sizeWrap;
438 m_CacheLen += size + sizeWrap; // This data is now in the cache
439 m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
444 return (total - len) / m_dwFrameSize; // Frames used
447 void CAESinkDirectSound::Stop()
453 void CAESinkDirectSound::Drain()
455 if (!m_initialized || m_isDirtyDS)
459 HRESULT res = m_pBuffer->SetCurrentPosition(0);
462 CLog::Log(LOGERROR,__FUNCTION__ ": SetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res);
470 double CAESinkDirectSound::GetDelay()
475 /* Make sure we know how much data is in the cache */
476 if (!UpdateCacheStatus())
479 /** returns current cached data duration in seconds */
480 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
484 double CAESinkDirectSound::GetCacheTime()
489 /* Make sure we know how much data is in the cache */
492 /** returns current cached data duration in seconds */
493 double delay = (double)m_CacheLen / (double)m_AvgBytesPerSec;
497 double CAESinkDirectSound::GetCacheTotal()
499 /** returns total cache capacity in seconds */
500 return (double)m_dwBufferLen / (double)m_AvgBytesPerSec;
503 void CAESinkDirectSound::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList, bool force)
505 CAEDeviceInfo deviceInfo;
507 IMMDeviceEnumerator* pEnumerator = NULL;
508 IMMDeviceCollection* pEnumDevices = NULL;
512 std::string strDD = GetDefaultDevice();
514 /* Windows Vista or later - supporting WASAPI device probing */
515 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
516 EXIT_ON_FAILURE(hr, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %li", hr)
520 hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pEnumDevices);
521 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint enumeration failed.")
523 hr = pEnumDevices->GetCount(&uiCount);
524 EXIT_ON_FAILURE(hr, __FUNCTION__": Retrieval of audio endpoint count failed.")
526 for (UINT i = 0; i < uiCount; i++)
528 IMMDevice *pDevice = NULL;
529 IPropertyStore *pProperty = NULL;
531 PropVariantInit(&varName);
533 deviceInfo.m_channels.Reset();
534 deviceInfo.m_dataFormats.clear();
535 deviceInfo.m_sampleRates.clear();
537 hr = pEnumDevices->Item(i, &pDevice);
540 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint failed.");
544 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
547 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
548 SAFE_RELEASE(pDevice);
552 hr = pProperty->GetValue(PKEY_Device_FriendlyName, &varName);
555 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint device name failed.");
556 SAFE_RELEASE(pDevice);
557 SAFE_RELEASE(pProperty);
561 std::string strFriendlyName = localWideToUtf(varName.pwszVal);
562 PropVariantClear(&varName);
564 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
567 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
568 SAFE_RELEASE(pDevice);
569 SAFE_RELEASE(pProperty);
573 std::string strDevName = localWideToUtf(varName.pwszVal);
574 PropVariantClear(&varName);
576 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
579 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
580 SAFE_RELEASE(pDevice);
581 SAFE_RELEASE(pProperty);
584 std::string strWinDevType = winEndpoints[(EndpointFormFactor)varName.uiVal].winEndpointType;
585 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
587 PropVariantClear(&varName);
589 /* In shared mode Windows tells us what format the audio must be in. */
590 IAudioClient *pClient;
591 hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pClient);
594 CLog::Log(LOGERROR, __FUNCTION__": Activate device failed (%s)", WASAPIErrToStr(hr));
598 //hr = pClient->GetMixFormat(&pwfxex);
599 hr = pProperty->GetValue(PKEY_AudioEngine_DeviceFormat, &varName);
600 if (SUCCEEDED(hr) && varName.blob.cbSize > 0)
602 WAVEFORMATEX* smpwfxex = (WAVEFORMATEX*)varName.blob.pBlobData;
603 deviceInfo.m_channels = layoutsByChCount[std::max(std::min(smpwfxex->nChannels, (WORD) DS_SPEAKER_COUNT), (WORD) 2)];
604 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_FLOAT));
605 deviceInfo.m_dataFormats.push_back(AEDataFormat(AE_FMT_AC3));
606 deviceInfo.m_sampleRates.push_back(std::min(smpwfxex->nSamplesPerSec, (DWORD) 192000));
610 CLog::Log(LOGERROR, __FUNCTION__": Getting DeviceFormat failed (%s)", WASAPIErrToStr(hr));
614 SAFE_RELEASE(pDevice);
615 SAFE_RELEASE(pProperty);
617 deviceInfo.m_deviceName = strDevName;
618 deviceInfo.m_displayName = strWinDevType.append(strFriendlyName);
619 deviceInfo.m_displayNameExtra = std::string("DirectSound: ").append(strFriendlyName);
620 deviceInfo.m_deviceType = aeDeviceType;
622 deviceInfoList.push_back(deviceInfo);
624 // add the default device with m_deviceName = default
625 if(strDD == strDevName)
627 deviceInfo.m_deviceName = std::string("default");
628 deviceInfo.m_displayName = std::string("default");
629 deviceInfo.m_displayNameExtra = std::string("");
630 deviceInfoList.push_back(deviceInfo);
639 CLog::Log(LOGERROR, __FUNCTION__": Failed to enumerate WASAPI endpoint devices (%s).", WASAPIErrToStr(hr));
641 SAFE_RELEASE(pEnumDevices);
642 SAFE_RELEASE(pEnumerator);
646 ///////////////////////////////////////////////////////////////////////////////
648 void CAESinkDirectSound::CheckPlayStatus()
651 m_pBuffer->GetStatus(&status);
653 if (!(status & DSBSTATUS_PLAYING) && m_CacheLen != 0) // If we have some data, see if we can start playback
655 HRESULT hr = m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
656 CLog::Log(LOGDEBUG,__FUNCTION__ ": Resuming Playback");
658 CLog::Log(LOGERROR, __FUNCTION__": Failed to play the DirectSound buffer: %s", dserr2str(hr));
662 bool CAESinkDirectSound::UpdateCacheStatus()
664 CSingleLock lock (m_runLock);
666 DWORD playCursor = 0, writeCursor = 0;
667 HRESULT res = m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor); // Get the current playback and safe write positions
670 CLog::Log(LOGERROR,__FUNCTION__ ": GetCurrentPosition failed. Unable to determine buffer status. HRESULT = 0x%08x", res);
675 // Check the state of the ring buffer (P->O->W == underrun)
676 // These are the logical situations that can occur
677 // O: CurrentOffset W: WriteCursor P: PlayCursor
678 // | | | | | | | | | |
679 // ***O----W----P***** < underrun P > W && O < W (1)
680 // | | | | | | | | | |
681 // ---P****O----W----- < underrun O > P && O < W (2)
682 // | | | | | | | | | |
683 // ---W----P****O----- < underrun P > W && P < O (3)
684 // | | | | | | | | | |
685 // ***W****O----P***** P > W && P > O (4)
686 // | | | | | | | | | |
687 // ---P****W****O----- P < W && O > W (5)
688 // | | | | | | | | | |
689 // ***O----P****W***** P < W && O < P (6)
691 // Check for underruns
692 if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
693 (playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
694 (playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
696 CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:%u, P:%u, O:%u.", writeCursor, playCursor, m_BufferOffset);
697 m_BufferOffset = writeCursor; // Catch up
698 //m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
700 if (m_BufferTimeouts > 10)
707 m_BufferTimeouts = 0;
709 // Calculate available space in the ring buffer
710 if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
712 else if (m_BufferOffset > playCursor)
713 m_CacheLen = m_BufferOffset - playCursor;
715 m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
720 unsigned int CAESinkDirectSound::GetSpace()
722 CSingleLock lock (m_runLock);
723 if (!UpdateCacheStatus())
725 unsigned int space = m_dwBufferLen - m_CacheLen;
727 // We can never allow the internal buffers to fill up complete
728 // as we get confused between if the buffer is full or empty
729 // so never allow the last chunk to be added
730 if (space > m_dwChunkSize)
731 return space - m_dwChunkSize;
736 void CAESinkDirectSound::AEChannelsFromSpeakerMask(DWORD speakers)
738 m_channelLayout.Reset();
740 for (int i = 0; i < DS_SPEAKER_COUNT; i++)
742 if (speakers & DSChannelOrder[i])
743 m_channelLayout += AEChannelNames[i];
747 DWORD CAESinkDirectSound::SpeakerMaskFromAEChannels(const CAEChannelInfo &channels)
751 for (unsigned int i = 0; i < channels.Count(); i++)
753 for (unsigned int j = 0; j < DS_SPEAKER_COUNT; j++)
754 if (channels[i] == AEChannelNames[j])
755 mask |= DSChannelOrder[j];
761 const char *CAESinkDirectSound::dserr2str(int err)
765 case DS_OK: return "DS_OK";
766 case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
767 case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
768 case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
769 case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
770 case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
771 case DSERR_GENERIC: return "DSERR_GENERIC";
772 case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
773 case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
774 case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
775 case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
776 case DSERR_NODRIVER: return "DSERR_NODRIVER";
777 case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
778 case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
779 case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
780 case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
781 case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
782 case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
783 case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
784 case DSERR_BUFFERTOOSMALL: return "DSERR_BUFFERTOOSMALL";
785 case DSERR_DS8_REQUIRED: return "DSERR_DS8_REQUIRED";
786 case DSERR_SENDLOOP: return "DSERR_SENDLOOP";
787 case DSERR_BADSENDBUFFERGUID: return "DSERR_BADSENDBUFFERGUID";
788 case DSERR_OBJECTNOTFOUND: return "DSERR_OBJECTNOTFOUND";
789 case DSERR_FXUNAVAILABLE: return "DSERR_FXUNAVAILABLE";
790 default: return "unknown";
794 const char *CAESinkDirectSound::WASAPIErrToStr(HRESULT err)
798 ERRTOSTR(AUDCLNT_E_NOT_INITIALIZED);
799 ERRTOSTR(AUDCLNT_E_ALREADY_INITIALIZED);
800 ERRTOSTR(AUDCLNT_E_WRONG_ENDPOINT_TYPE);
801 ERRTOSTR(AUDCLNT_E_DEVICE_INVALIDATED);
802 ERRTOSTR(AUDCLNT_E_NOT_STOPPED);
803 ERRTOSTR(AUDCLNT_E_BUFFER_TOO_LARGE);
804 ERRTOSTR(AUDCLNT_E_OUT_OF_ORDER);
805 ERRTOSTR(AUDCLNT_E_UNSUPPORTED_FORMAT);
806 ERRTOSTR(AUDCLNT_E_INVALID_SIZE);
807 ERRTOSTR(AUDCLNT_E_DEVICE_IN_USE);
808 ERRTOSTR(AUDCLNT_E_BUFFER_OPERATION_PENDING);
809 ERRTOSTR(AUDCLNT_E_THREAD_NOT_REGISTERED);
810 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED);
811 ERRTOSTR(AUDCLNT_E_ENDPOINT_CREATE_FAILED);
812 ERRTOSTR(AUDCLNT_E_SERVICE_NOT_RUNNING);
813 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED);
814 ERRTOSTR(AUDCLNT_E_EXCLUSIVE_MODE_ONLY);
815 ERRTOSTR(AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL);
816 ERRTOSTR(AUDCLNT_E_EVENTHANDLE_NOT_SET);
817 ERRTOSTR(AUDCLNT_E_INCORRECT_BUFFER_SIZE);
818 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_ERROR);
819 ERRTOSTR(AUDCLNT_E_CPUUSAGE_EXCEEDED);
820 ERRTOSTR(AUDCLNT_E_BUFFER_ERROR);
821 ERRTOSTR(AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED);
822 ERRTOSTR(AUDCLNT_E_INVALID_DEVICE_PERIOD);
824 ERRTOSTR(E_INVALIDARG);
825 ERRTOSTR(E_OUTOFMEMORY);
831 std::string CAESinkDirectSound::GetDefaultDevice()
833 IMMDeviceEnumerator* pEnumerator = NULL;
834 IMMDevice* pDevice = NULL;
835 IPropertyStore* pProperty = NULL;
838 std::string strDevName = "default";
840 hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
843 CLog::Log(LOGERROR, __FUNCTION__": Could not allocate WASAPI device enumerator. CoCreateInstance error code: %s", WASAPIErrToStr(hr));
847 hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
850 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of audio endpoint enumeration failed.");
854 hr = pDevice->OpenPropertyStore(STGM_READ, &pProperty);
857 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint properties failed.");
861 PropVariantInit(&varName);
862 hr = pProperty->GetValue(PKEY_AudioEndpoint_FormFactor, &varName);
865 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint form factor failed.");
868 AEDeviceType aeDeviceType = winEndpoints[(EndpointFormFactor)varName.uiVal].aeDeviceType;
869 PropVariantClear(&varName);
871 hr = pProperty->GetValue(PKEY_AudioEndpoint_GUID, &varName);
874 CLog::Log(LOGERROR, __FUNCTION__": Retrieval of DirectSound endpoint GUID failed.");
878 strDevName = localWideToUtf(varName.pwszVal);
879 PropVariantClear(&varName);
883 SAFE_RELEASE(pProperty);
884 SAFE_RELEASE(pDevice);
885 SAFE_RELEASE(pEnumerator);
890 bool CAESinkDirectSound::SoftSuspend()
896 bool CAESinkDirectSound::SoftResume()
898 /* Return false to force re-init by engine */