diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Audio/DrvHostAudioDSound.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Audio/DrvHostAudioDSound.cpp')
-rw-r--r-- | src/VBox/Devices/Audio/DrvHostAudioDSound.cpp | 2881 |
1 files changed, 2881 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp b/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp new file mode 100644 index 00000000..c24e39bc --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp @@ -0,0 +1,2881 @@ +/* $Id: DrvHostAudioDSound.cpp $ */ +/** @file + * Host audio driver - DirectSound (Windows). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#define INITGUID +#include <VBox/log.h> +#include <iprt/win/windows.h> +#include <dsound.h> +#include <mmdeviceapi.h> +#include <functiondiscoverykeys_devpkey.h> +#include <iprt/win/mmreg.h> /* WAVEFORMATEXTENSIBLE */ + +#include <iprt/alloc.h> +#include <iprt/system.h> +#include <iprt/uuid.h> +#include <iprt/utf16.h> + +#include <VBox/vmm/pdmaudioinline.h> +#include <VBox/vmm/pdmaudiohostenuminline.h> + +#include "VBoxDD.h" + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT +# include <new> /* For bad_alloc. */ +# include "DrvHostAudioDSoundMMNotifClient.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Optional release logging, which a user can turn on with the + * 'VBoxManage debugvm' command. + * Debug logging still uses the common Log* macros from VBox. + * Messages which always should go to the release log use LogRel. + * + * @deprecated Use LogRelMax, LogRel2 and LogRel3 directly. + */ +/** General code behavior. */ +#define DSLOG(a) do { LogRel2(a); } while(0) +/** Something which produce a lot of logging during playback/recording. */ +#define DSLOGF(a) do { LogRel3(a); } while(0) +/** Important messages like errors. Limited in the default release log to avoid log flood. */ +#define DSLOGREL(a) \ + do { \ + static int8_t s_cLogged = 0; \ + if (s_cLogged < 8) { \ + ++s_cLogged; \ + LogRel(a); \ + } else DSLOG(a); \ + } while (0) + +/** Maximum number of attempts to restore the sound buffer before giving up. */ +#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3 +#if 0 /** @todo r=bird: What are these for? Nobody is using them... */ +/** Default input latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50 +/** Default output latency (in ms). */ +#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/* Dynamically load dsound.dll. */ +typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext); +typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW; +typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter); +typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8; + +#define VBOX_DSOUND_MAX_EVENTS 3 + +typedef enum DSOUNDEVENT +{ + DSOUNDEVENT_NOTIFY = 0, + DSOUNDEVENT_INPUT, + DSOUNDEVENT_OUTPUT, +} DSOUNDEVENT; + +typedef struct DSOUNDHOSTCFG +{ + RTUUID uuidPlay; + LPCGUID pGuidPlay; + RTUUID uuidCapture; + LPCGUID pGuidCapture; +} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG; + +typedef struct DSOUNDSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** Entry in DRVHOSTDSOUND::HeadStreams. */ + RTLISTNODE ListEntry; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** Buffer alignment. */ + uint8_t uAlign; + /** Whether this stream is in an enable state on the DirectSound side. */ + bool fEnabled; + bool afPadding[2]; + /** Size (in bytes) of the DirectSound buffer. */ + DWORD cbBufSize; + union + { + struct + { + /** The actual DirectSound Buffer (DSB) used for the capturing. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB; + /** Current read offset (in bytes) within the DSB. */ + DWORD offReadPos; + /** Number of buffer overruns happened. Used for logging. */ + uint8_t cOverruns; + } In; + struct + { + /** The actual DirectSound Buffer (DSB) used for playback. + * This is a secondary buffer and is used as a streaming buffer. */ + LPDIRECTSOUNDBUFFER8 pDSB; + /** Current write offset (in bytes) within the DSB. + * @note This is needed as the current write position as kept by direct sound + * will move ahead if we're too late. */ + DWORD offWritePos; + /** Offset of last play cursor within the DSB when checked for pending. */ + DWORD offPlayCursorLastPending; + /** Offset of last play cursor within the DSB when last played. */ + DWORD offPlayCursorLastPlayed; + /** Total amount (in bytes) written to our internal ring buffer. */ + uint64_t cbWritten; + /** Total amount (in bytes) played (to the DirectSound buffer). */ + uint64_t cbTransferred; + /** Flag indicating whether playback was just (re)started. */ + bool fFirstTransfer; + /** Flag indicating whether this stream is in draining mode, e.g. no new + * data is being written to it but DirectSound still needs to be able to + * play its remaining (buffered) data. */ + bool fDrain; + /** How much (in bytes) the last transfer from the internal buffer + * to the DirectSound buffer was. */ + uint32_t cbLastTransferred; + /** The RTTimeMilliTS() deadline for the draining of this stream. */ + uint64_t msDrainDeadline; + } Out; + }; + /** Timestamp (in ms) of the last transfer from the internal buffer to/from the + * DirectSound buffer. */ + uint64_t msLastTransfer; + /** The stream's critical section for synchronizing access. */ + RTCRITSECT CritSect; + /** Used for formatting the current DSound status. */ + char szStatus[127]; + /** Fixed zero terminator. */ + char const chStateZero; +} DSOUNDSTREAM, *PDSOUNDSTREAM; + +/** + * DirectSound-specific device entry. + */ +typedef struct DSOUNDDEV +{ + PDMAUDIOHOSTDEV Core; + /** The GUID if handy. */ + GUID Guid; + /** The GUID as a string (empty if default). */ + char szGuid[RTUUID_STR_LENGTH]; +} DSOUNDDEV; +/** Pointer to a DirectSound device entry. */ +typedef DSOUNDDEV *PDSOUNDDEV; + +/** + * Structure for holding a device enumeration context. + */ +typedef struct DSOUNDENUMCBCTX +{ + /** Enumeration flags. */ + uint32_t fFlags; + /** Pointer to device list to populate. */ + PPDMAUDIOHOSTENUM pDevEnm; +} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX; + +typedef struct DRVHOSTDSOUND +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Our audio host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Critical section to serialize access. */ + RTCRITSECT CritSect; + /** DirectSound configuration options. */ + DSOUNDHOSTCFG Cfg; + /** List of devices of last enumeration. */ + PDMAUDIOHOSTENUM DeviceEnum; + /** Whether this backend supports any audio input. + * @todo r=bird: This is not actually used for anything. */ + bool fEnabledIn; + /** Whether this backend supports any audio output. + * @todo r=bird: This is not actually used for anything. */ + bool fEnabledOut; + /** The Direct Sound playback interface. */ + LPDIRECTSOUND8 pDS; + /** The Direct Sound capturing interface. */ + LPDIRECTSOUNDCAPTURE8 pDSC; + /** List of streams (DSOUNDSTREAM). + * Requires CritSect ownership. */ + RTLISTANCHOR HeadStreams; + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + DrvHostAudioDSoundMMNotifClient *m_pNotificationClient; +#endif +} DRVHOSTDSOUND, *PDRVHOSTDSOUND; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB); +static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset); + +static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDMAUDIOHOSTENUM pDevEnm, uint32_t fEnum); + +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis); + + +#if defined(LOG_ENABLED) || defined(RTLOG_REL_ENABLED) +/** + * Gets the stream status as a string for logging purposes. + * + * @returns Status string (pStreamDS->szStatus). + * @param pStreamDS The stream to get the status for. + */ +static const char *drvHostDSoundStreamStatusString(PDSOUNDSTREAM pStreamDS) +{ + /* + * Out internal stream status first. + */ + size_t off; + if (pStreamDS->fEnabled) + { + memcpy(pStreamDS->szStatus, RT_STR_TUPLE("ENABLED ")); + off = sizeof("ENABLED ") - 1; + } + else + { + memcpy(pStreamDS->szStatus, RT_STR_TUPLE("DISABLED")); + off = sizeof("DISABLED") - 1; + } + + /* + * Direction specific stuff, returning with a status DWORD and string mappings for it. + */ + typedef struct DRVHOSTDSOUNDSFLAGS2STR + { + const char *pszMnemonic; + uint32_t cchMnemonic; + uint32_t fFlag; + } DRVHOSTDSOUNDSFLAGS2STR; + static const DRVHOSTDSOUNDSFLAGS2STR s_aCaptureFlags[] = + { + { RT_STR_TUPLE(" CAPTURING"), DSCBSTATUS_CAPTURING }, + { RT_STR_TUPLE(" LOOPING"), DSCBSTATUS_LOOPING }, + }; + static const DRVHOSTDSOUNDSFLAGS2STR s_aPlaybackFlags[] = + { + { RT_STR_TUPLE(" PLAYING"), DSBSTATUS_PLAYING }, + { RT_STR_TUPLE(" BUFFERLOST"), DSBSTATUS_BUFFERLOST }, + { RT_STR_TUPLE(" LOOPING"), DSBSTATUS_LOOPING }, + { RT_STR_TUPLE(" LOCHARDWARE"), DSBSTATUS_LOCHARDWARE }, + { RT_STR_TUPLE(" LOCSOFTWARE"), DSBSTATUS_LOCSOFTWARE }, + { RT_STR_TUPLE(" TERMINATED"), DSBSTATUS_TERMINATED }, + }; + DRVHOSTDSOUNDSFLAGS2STR const *paMappings = NULL; + size_t cMappings = 0; + DWORD fStatus = 0; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = pStreamDS->In.pDSCB->GetStatus(&fStatus); + if (SUCCEEDED(hrc)) + { + paMappings = s_aCaptureFlags; + cMappings = RT_ELEMENTS(s_aCaptureFlags); + } + else + RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSCB"); + } + else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + if (pStreamDS->Out.fDrain) + { + memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" DRAINING")); + off += sizeof(" DRAINING") - 1; + } + + if (pStreamDS->Out.fFirstTransfer) + { + memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" NOXFER")); + off += sizeof(" NOXFER") - 1; + } + + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = pStreamDS->Out.pDSB->GetStatus(&fStatus); + if (SUCCEEDED(hrc)) + { + paMappings = s_aPlaybackFlags; + cMappings = RT_ELEMENTS(s_aPlaybackFlags); + } + else + RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSB"); + } + else + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "BAD-DIR"); + + /* Format flags. */ + if (paMappings) + { + if (fStatus == 0) + RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " 0"); + else + { + for (size_t i = 0; i < cMappings; i++) + if (fStatus & paMappings[i].fFlag) + { + memcpy(&pStreamDS->szStatus[off], paMappings[i].pszMnemonic, paMappings[i].cchMnemonic); + off += paMappings[i].cchMnemonic; + + fStatus &= ~paMappings[i].fFlag; + if (!fStatus) + break; + } + if (fStatus != 0) + off += RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " %#x", fStatus); + } + } + + /* + * Finally, terminate the string. By postponing it this long, it won't be + * a big deal if two threads go thru here at the same time as long as the + * status is the same. + */ + Assert(off < sizeof(pStreamDS->szStatus)); + pStreamDS->szStatus[off] = '\0'; + + return pStreamDS->szStatus; +} +#endif /* LOG_ENABLED || RTLOG_REL_ENABLED */ + + +static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize) +{ + AssertReturn(offEnd <= cSize, 0); + AssertReturn(offBegin <= cSize, 0); + + return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd; +} + + +static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID) +{ + if (pGUID) + { + LPOLESTR lpOLEStr; + HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr); + if (SUCCEEDED(hr)) + { + char *pszGUID; + int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID); + CoTaskMemFree(lpOLEStr); + + return RT_SUCCESS(rc) ? pszGUID : NULL; + } + } + + return RTStrDup("{Default device}"); +} + + +static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Restore(pDSB); + if (FAILED(hr)) + DSLOG(("DSound: Restoring playback buffer\n")); + else + DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr)); + + return hr; +} + + +static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + RT_NOREF(pThis); + HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr)); + return hr; +} + + +static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + AssertReturn(dwBytes, VERR_INVALID_PARAMETER); + + HRESULT hr = E_FAIL; + AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0); + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + PVOID pv1, pv2; + DWORD cb1, cb2; + hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags); + if (SUCCEEDED(hr)) + { + if ( (!pv1 || !(cb1 & pStreamDS->uAlign)) + && (!pv2 || !(cb2 & pStreamDS->uAlign))) + { + if (ppv1) + *ppv1 = pv1; + if (ppv2) + *ppv2 = pv2; + if (pcb1) + *pcb1 = cb1; + if (pcb2) + *pcb2 = cb2; + return S_OK; + } + DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n", + *pcb1, *pcb2, pStreamDS->uAlign)); + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + if (hr != DSERR_BUFFERLOST) + break; + + LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + } + + DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes)); + return hr; +} + + +static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB, + PVOID pv1, PVOID pv2, + DWORD cb1, DWORD cb2) +{ + HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2); + if (FAILED(hr)) + DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr)); + return hr; +} + + +static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS, + DWORD dwOffset, DWORD dwBytes, + PVOID *ppv1, PVOID *ppv2, + DWORD *pcb1, DWORD *pcb2, + DWORD dwFlags) +{ + PVOID pv1 = NULL; + PVOID pv2 = NULL; + DWORD cb1 = 0; + DWORD cb2 = 0; + + HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes, + &pv1, &cb1, &pv2, &cb2, dwFlags); + if (FAILED(hr)) + { + DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr)); + return hr; + } + + if ( (pv1 && (cb1 & pStreamDS->uAlign)) + || (pv2 && (cb2 & pStreamDS->uAlign))) + { + DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n", + cb1, cb2, pStreamDS->uAlign)); + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); + return E_FAIL; + } + + *ppv1 = pv1; + *ppv2 = pv2; + *pcb1 = cb1; + *pcb2 = cb2; + + return S_OK; +} + + +/* + * DirectSound playback + */ + +/** + * Creates a DirectSound playback instance. + * + * @return HRESULT + * @param pGUID GUID of device to create the playback interface for. NULL + * for the default device. + * @param ppDS Where to return the interface to the created instance. + */ +static HRESULT drvHostDSoundCreateDSPlaybackInstance(LPCGUID pGUID, LPDIRECTSOUND8 *ppDS) +{ + LogFlowFuncEnter(); + + LPDIRECTSOUND8 pDS = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, IID_IDirectSound8, (void **)&pDS); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSound8_Initialize(pDS, pGUID); + if (SUCCEEDED(hrc)) + { + HWND hWnd = GetDesktopWindow(); + hrc = IDirectSound8_SetCooperativeLevel(pDS, hWnd, DSSCL_PRIORITY); + if (SUCCEEDED(hrc)) + { + *ppDS = pDS; + LogFlowFunc(("LEAVE S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Setting cooperative level for (hWnd=%p) failed: %Rhrc\n", hWnd, hrc)); + } + else if (hrc == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */ + LogRelMax(64, ("DSound: DirectSound playback is currently unavailable\n")); + else + LogRelMax(64, ("DSound: DirectSound playback initialization failed: %Rhrc\n", hrc)); + + IDirectSound8_Release(pDS); + } + else + LogRelMax(64, ("DSound: Creating playback instance failed: %Rhrc\n", hrc)); + + LogFlowFunc(("LEAVE %Rhrc\n", hrc)); + return hrc; +} + + +#if 0 /* not used */ +static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus) +{ + AssertPtrReturn(pThis, E_POINTER); + AssertPtrReturn(pDSB, E_POINTER); + + AssertPtrNull(pdwStatus); + + DWORD dwStatus = 0; + HRESULT hr = E_FAIL; + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus); + if ( hr == DSERR_BUFFERLOST + || ( SUCCEEDED(hr) + && (dwStatus & DSBSTATUS_BUFFERLOST))) + { + LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pDSB); + } + else + break; + } + + if (SUCCEEDED(hr)) + { + if (pdwStatus) + *pdwStatus = dwStatus; + } + else + DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr)); + + return hr; +} +#endif + + +/* + * DirectSoundCapture + */ + +#if 0 /* unused */ +static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg) +{ + AssertPtrReturn(pThis, NULL); + AssertPtrReturn(pCfg, NULL); + + int rc = VINF_SUCCESS; + + LPCGUID pGUID = pThis->Cfg.pGuidCapture; + if (!pGUID) + { + PDSOUNDDEV pDev = NULL; + switch (pCfg->enmPath) + { + case PDMAUDIOPATH_IN_LINE: + /* + * At the moment we're only supporting line-in in the HDA emulation, + * and line-in + mic-in in the AC'97 emulation both are expected + * to use the host's mic-in as well. + * + * So the fall through here is intentional for now. + */ + case PDMAUDIOPATH_IN_MIC: + pDev = (PDSOUNDDEV)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->DeviceEnum, PDMAUDIODIR_IN); + break; + + default: + AssertFailedStmt(rc = VERR_NOT_SUPPORTED); + break; + } + + if ( RT_SUCCESS(rc) + && pDev) + { + DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n", + PDMAudioPathGetName(pCfg->enmPath), pDev->Core.szName)); + pGUID = &pDev->Guid; + } + if (RT_FAILURE(rc)) + { + LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc)); + return NULL; + } + } + + /* This always has to be in the release log. */ + char *pszGUID = dsoundGUIDToUtf8StrA(pGUID); + LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n", + PDMAudioPathGetName(pCfg->enmPath), pszGUID ? pszGUID: "{?}")); + RTStrFree(pszGUID); + + return pGUID; +} +#endif + + +/** + * Creates a DirectSound capture instance. + * + * @returns HRESULT + * @param pGUID GUID of device to create the capture interface for. NULL + * for default. + * @param ppDSC Where to return the interface to the created instance. + */ +static HRESULT drvHostDSoundCreateDSCaptureInstance(LPCGUID pGUID, LPDIRECTSOUNDCAPTURE8 *ppDSC) +{ + LogFlowFuncEnter(); + + LPDIRECTSOUNDCAPTURE8 pDSC = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, IID_IDirectSoundCapture8, (void **)&pDSC); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSoundCapture_Initialize(pDSC, pGUID); + if (SUCCEEDED(hrc)) + { + *ppDSC = pDSC; + LogFlowFunc(("LEAVE S_OK\n")); + return S_OK; + } + if (hrc == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */ + LogRelMax(64, ("DSound: Capture device currently is unavailable\n")); + else + LogRelMax(64, ("DSound: Initializing capturing device failed: %Rhrc\n", hrc)); + IDirectSoundCapture_Release(pDSC); + } + else + LogRelMax(64, ("DSound: Creating capture instance failed: %Rhrc\n", hrc)); + + LogFlowFunc(("LEAVE %Rhrc\n", hrc)); + return hrc; +} + + +/** + * Updates this host driver's internal status, according to the global, overall input/output + * state and all connected (native) audio streams. + * + * @todo r=bird: This is a 'ing waste of 'ing time! We're doing this everytime + * an 'ing stream is created and we doesn't 'ing use the information here + * for any darn thing! Given the reported slowness of enumeration and + * issues with the 'ing code the only appropriate response is: + * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARG!!!!!!! + * + * @param pThis Host audio driver instance. + */ +static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis) +{ +#if 0 /** @todo r=bird: This isn't doing *ANYTHING* useful. So, I've just disabled it. */ + AssertPtrReturnVoid(pThis); + LogFlowFuncEnter(); + + PDMAudioHostEnumDelete(&pThis->DeviceEnum); + int rc = dsoundDevicesEnumerate(pThis, &pThis->DeviceEnum); + if (RT_SUCCESS(rc)) + { +#if 0 + if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut) + || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn)) + { + /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to + * let the connector know that something has changed within the host backend. */ + } +#endif + pThis->fEnabledIn = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_IN) != 0; + pThis->fEnabledOut = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_OUT) != 0; + } + + LogFlowFuncLeaveRC(rc); +#else + RT_NOREF(pThis); +#endif +} + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostDSoundHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pInterface, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DirectSound"); + pBackendCfg->cbStream = sizeof(DSOUNDSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; + + return VINF_SUCCESS; +} + + +/** + * Callback for the playback device enumeration. + * + * @return TRUE if continuing enumeration, FALSE if not. + * @param pGUID Pointer to GUID of enumerated device. Can be NULL. + * @param pwszDescription Pointer to (friendly) description of enumerated device. + * @param pwszModule Pointer to module name of enumerated device. + * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. + * + * @note Carbon copy of drvHostDSoundEnumOldStyleCaptureCallback with OUT direction. + */ +static BOOL CALLBACK drvHostDSoundEnumOldStylePlaybackCallback(LPGUID pGUID, LPCWSTR pwszDescription, + LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX)lpContext; + AssertPtrReturn(pEnumCtx, FALSE); + + PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm; + AssertPtrReturn(pDevEnm, FALSE); + + AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */ + AssertPtrReturn(pwszDescription, FALSE); + RT_NOREF(pwszModule); /* Do not care about pwszModule. */ + + int rc; + size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = PDMAUDIODIR_OUT; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + + if (pGUID == NULL) + pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + + rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + if (!pGUID) + pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + else + { + memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid)); + rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + } + pDev->Core.pszId = &pDev->szGuid[0]; + + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + + /* Note: Querying the actual device information will be done at some + * later point in time outside this enumeration callback to prevent + * DSound hangs. */ + return TRUE; + } + PDMAudioHostDevFree(&pDev->Core); + } + else + rc = VERR_NO_MEMORY; + + LogRel(("DSound: Error enumeration playback device '%ls': rc=%Rrc\n", pwszDescription, rc)); + return FALSE; /* Abort enumeration. */ +} + + +/** + * Callback for the capture device enumeration. + * + * @return TRUE if continuing enumeration, FALSE if not. + * @param pGUID Pointer to GUID of enumerated device. Can be NULL. + * @param pwszDescription Pointer to (friendly) description of enumerated device. + * @param pwszModule Pointer to module name of enumerated device. + * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information. + * + * @note Carbon copy of drvHostDSoundEnumOldStylePlaybackCallback with IN direction. + */ +static BOOL CALLBACK drvHostDSoundEnumOldStyleCaptureCallback(LPGUID pGUID, LPCWSTR pwszDescription, + LPCWSTR pwszModule, PVOID lpContext) +{ + PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX )lpContext; + AssertPtrReturn(pEnumCtx, FALSE); + + PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm; + AssertPtrReturn(pDevEnm, FALSE); + + AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */ + AssertPtrReturn(pwszDescription, FALSE); + RT_NOREF(pwszModule); /* Do not care about pwszModule. */ + + int rc; + size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = PDMAUDIODIR_IN; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + + rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + { + if (!pGUID) + pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN; + else + { + memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid)); + rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + } + pDev->Core.pszId = &pDev->szGuid[0]; + + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + + /* Note: Querying the actual device information will be done at some + * later point in time outside this enumeration callback to prevent + * DSound hangs. */ + return TRUE; + } + PDMAudioHostDevFree(&pDev->Core); + } + else + rc = VERR_NO_MEMORY; + + LogRel(("DSound: Error enumeration capture device '%ls', rc=%Rrc\n", pwszDescription, rc)); + return FALSE; /* Abort enumeration. */ +} + + +/** + * Queries information for a given (DirectSound) device. + * + * @returns VBox status code. + * @param pDev Audio device to query information for. + */ +static int drvHostDSoundEnumOldStyleQueryDeviceInfo(PDSOUNDDEV pDev) +{ + AssertPtr(pDev); + int rc; + + if (pDev->Core.enmUsage == PDMAUDIODIR_OUT) + { + LPDIRECTSOUND8 pDS; + HRESULT hr = drvHostDSoundCreateDSPlaybackInstance(&pDev->Guid, &pDS); + if (SUCCEEDED(hr)) + { + DSCAPS DSCaps; + RT_ZERO(DSCaps); + DSCaps.dwSize = sizeof(DSCAPS); + hr = IDirectSound_GetCaps(pDS, &DSCaps); + if (SUCCEEDED(hr)) + { + pDev->Core.cMaxOutputChannels = DSCaps.dwFlags & DSCAPS_PRIMARYSTEREO ? 2 : 1; + + DWORD dwSpeakerCfg; + hr = IDirectSound_GetSpeakerConfig(pDS, &dwSpeakerCfg); + if (SUCCEEDED(hr)) + { + unsigned uSpeakerCount = 0; + switch (DSSPEAKER_CONFIG(dwSpeakerCfg)) + { + case DSSPEAKER_MONO: uSpeakerCount = 1; break; + case DSSPEAKER_HEADPHONE: uSpeakerCount = 2; break; + case DSSPEAKER_STEREO: uSpeakerCount = 2; break; + case DSSPEAKER_QUAD: uSpeakerCount = 4; break; + case DSSPEAKER_SURROUND: uSpeakerCount = 4; break; + case DSSPEAKER_5POINT1: uSpeakerCount = 6; break; + case DSSPEAKER_5POINT1_SURROUND: uSpeakerCount = 6; break; + case DSSPEAKER_7POINT1: uSpeakerCount = 8; break; + case DSSPEAKER_7POINT1_SURROUND: uSpeakerCount = 8; break; + default: break; + } + + if (uSpeakerCount) /* Do we need to update the channel count? */ + pDev->Core.cMaxOutputChannels = uSpeakerCount; + + rc = VINF_SUCCESS; + } + else + { + LogRel(("DSound: Error retrieving playback device speaker config, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + } + else + { + LogRel(("DSound: Error retrieving playback device capabilities, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + + IDirectSound8_Release(pDS); + } + else + rc = VERR_GENERAL_FAILURE; + } + else if (pDev->Core.enmUsage == PDMAUDIODIR_IN) + { + LPDIRECTSOUNDCAPTURE8 pDSC; + HRESULT hr = drvHostDSoundCreateDSCaptureInstance(&pDev->Guid, &pDSC); + if (SUCCEEDED(hr)) + { + DSCCAPS DSCCaps; + RT_ZERO(DSCCaps); + DSCCaps.dwSize = sizeof(DSCCAPS); + hr = IDirectSoundCapture_GetCaps(pDSC, &DSCCaps); + if (SUCCEEDED(hr)) + { + pDev->Core.cMaxInputChannels = DSCCaps.dwChannels; + rc = VINF_SUCCESS; + } + else + { + LogRel(("DSound: Error retrieving capture device capabilities, hr=%Rhrc\n", hr)); + rc = VERR_ACCESS_DENIED; /** @todo Fudge! */ + } + + IDirectSoundCapture_Release(pDSC); + } + else + rc = VERR_GENERAL_FAILURE; + } + else + AssertFailedStmt(rc = VERR_NOT_SUPPORTED); + + return rc; +} + + +/** + * Queries information for @a pDevice and adds an entry to the enumeration. + * + * @returns VBox status code. + * @param pDevEnm The enumeration to add the device to. + * @param pDevice The device. + * @param enmType The type of device. + * @param fDefault Whether it's the default device. + */ +static int drvHostDSoundEnumNewStyleAdd(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pDevice, EDataFlow enmType, bool fDefault) +{ + int rc = VINF_SUCCESS; /* ignore most errors */ + + /* + * Gather the necessary properties. + */ + IPropertyStore *pProperties = NULL; + HRESULT hrc = pDevice->OpenPropertyStore(STGM_READ, &pProperties); + if (SUCCEEDED(hrc)) + { + /* Get the friendly name. */ + PROPVARIANT VarName; + PropVariantInit(&VarName); + hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName); + if (SUCCEEDED(hrc)) + { + /* Get the DirectSound GUID. */ + PROPVARIANT VarGUID; + PropVariantInit(&VarGUID); + hrc = pProperties->GetValue(PKEY_AudioEndpoint_GUID, &VarGUID); + if (SUCCEEDED(hrc)) + { + /* Get the device format. */ + PROPVARIANT VarFormat; + PropVariantInit(&VarFormat); + hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat); + if (SUCCEEDED(hrc)) + { + WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData; + AssertPtr(pFormat); + + /* + * Create a enumeration entry for it. + */ + size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1; + PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0); + if (pDev) + { + pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN; + pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN; + if (fDefault) + pDev->Core.fFlags |= enmType == eRender + ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN; + if (enmType == eRender) + pDev->Core.cMaxOutputChannels = pFormat->nChannels; + else + pDev->Core.cMaxInputChannels = pFormat->nChannels; + + //if (fDefault) + rc = RTUuidFromUtf16((PRTUUID)&pDev->Guid, VarGUID.pwszVal); + if (RT_SUCCESS(rc)) + { + rc = RTUuidToStr((PCRTUUID)&pDev->Guid, pDev->szGuid, sizeof(pDev->szGuid)); + AssertRC(rc); + pDev->Core.pszId = &pDev->szGuid[0]; + + rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL); + if (RT_SUCCESS(rc)) + PDMAudioHostEnumAppend(pDevEnm, &pDev->Core); + else + PDMAudioHostDevFree(&pDev->Core); + } + else + { + LogFunc(("RTUuidFromUtf16(%ls): %Rrc\n", VarGUID.pwszVal, rc)); + PDMAudioHostDevFree(&pDev->Core); + } + } + else + rc = VERR_NO_MEMORY; + PropVariantClear(&VarFormat); + } + else + LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc)); + PropVariantClear(&VarGUID); + } + else + LogFunc(("Failed to get PKEY_AudioEndpoint_GUID: %Rhrc\n", hrc)); + PropVariantClear(&VarName); + } + else + LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc)); + pProperties->Release(); + } + else + LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc)); + + if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc)) + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Does a (Re-)enumeration of the host's playback + capturing devices. + * + * @return VBox status code. + * @param pDevEnm Where to store the enumerated devices. + */ +static int drvHostDSoundEnumerateDevices(PPDMAUDIOHOSTENUM pDevEnm) +{ + DSLOG(("DSound: Enumerating devices ...\n")); + + /* + * Use the Vista+ API. + */ + IMMDeviceEnumerator *pEnumerator; + HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), (void **)&pEnumerator); + if (SUCCEEDED(hrc)) + { + int rc = VINF_SUCCESS; + for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++) + { + EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture; + + /* Get the default device first. */ + IMMDevice *pDefaultDevice = NULL; + hrc = pEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pDefaultDevice); + if (SUCCEEDED(hrc)) + rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDefaultDevice, enmType, true); + else + pDefaultDevice = NULL; + + /* Enumerate the devices. */ + IMMDeviceCollection *pCollection = NULL; + hrc = pEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection); + if (SUCCEEDED(hrc) && pCollection != NULL) + { + UINT cDevices = 0; + hrc = pCollection->GetCount(&cDevices); + if (SUCCEEDED(hrc)) + { + for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++) + { + IMMDevice *pDevice = NULL; + hrc = pCollection->Item(idxDevice, &pDevice); + if (SUCCEEDED(hrc) && pDevice) + { + if (pDevice != pDefaultDevice) + rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDevice, enmType, false); + pDevice->Release(); + } + } + } + pCollection->Release(); + } + else + LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc)); + + if (pDefaultDevice) + pDefaultDevice->Release(); + } + pEnumerator->Release(); + if (pDevEnm->cDevices > 0 || RT_FAILURE(rc)) + { + DSLOG(("DSound: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc)); + return rc; + } + } + + /* + * Fall back to dsound. + */ + /* Resolve symbols once. */ + static PFNDIRECTSOUNDENUMERATEW volatile s_pfnDirectSoundEnumerateW = NULL; + static PFNDIRECTSOUNDCAPTUREENUMERATEW volatile s_pfnDirectSoundCaptureEnumerateW = NULL; + + PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = s_pfnDirectSoundEnumerateW; + PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = s_pfnDirectSoundCaptureEnumerateW; + if (!pfnDirectSoundEnumerateW || !pfnDirectSoundCaptureEnumerateW) + { + RTLDRMOD hModDSound = NIL_RTLDRMOD; + int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hModDSound); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hModDSound, "DirectSoundEnumerateW", (void **)&pfnDirectSoundEnumerateW); + if (RT_SUCCESS(rc)) + s_pfnDirectSoundEnumerateW = pfnDirectSoundEnumerateW; + else + LogRel(("DSound: Failed to get dsound.dll export DirectSoundEnumerateW: %Rrc\n", rc)); + + rc = RTLdrGetSymbol(hModDSound, "DirectSoundCaptureEnumerateW", (void **)&pfnDirectSoundCaptureEnumerateW); + if (RT_SUCCESS(rc)) + s_pfnDirectSoundCaptureEnumerateW = pfnDirectSoundCaptureEnumerateW; + else + LogRel(("DSound: Failed to get dsound.dll export DirectSoundCaptureEnumerateW: %Rrc\n", rc)); + RTLdrClose(hModDSound); + } + else + LogRel(("DSound: Unable to load dsound.dll for enumerating devices: %Rrc\n", rc)); + if (!pfnDirectSoundEnumerateW && !pfnDirectSoundCaptureEnumerateW) + return rc; + } + + /* Common callback context for both playback and capture enumerations: */ + DSOUNDENUMCBCTX EnumCtx; + EnumCtx.fFlags = 0; + EnumCtx.pDevEnm = pDevEnm; + + /* Enumerate playback devices. */ + if (pfnDirectSoundEnumerateW) + { + DSLOG(("DSound: Enumerating playback devices ...\n")); + HRESULT hr = pfnDirectSoundEnumerateW(&drvHostDSoundEnumOldStylePlaybackCallback, &EnumCtx); + if (FAILED(hr)) + LogRel(("DSound: Error enumerating host playback devices: %Rhrc\n", hr)); + } + + /* Enumerate capture devices. */ + if (pfnDirectSoundCaptureEnumerateW) + { + DSLOG(("DSound: Enumerating capture devices ...\n")); + HRESULT hr = pfnDirectSoundCaptureEnumerateW(&drvHostDSoundEnumOldStyleCaptureCallback, &EnumCtx); + if (FAILED(hr)) + LogRel(("DSound: Error enumerating host capture devices: %Rhrc\n", hr)); + } + + /* + * Query Information for all enumerated devices. + * Note! This is problematic to do from the enumeration callbacks. + */ + PDSOUNDDEV pDev; + RTListForEach(&pDevEnm->LstDevices, pDev, DSOUNDDEV, Core.ListEntry) + { + drvHostDSoundEnumOldStyleQueryDeviceInfo(pDev); /* ignore rc */ + } + + DSLOG(("DSound: Enumerating devices done\n")); + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHostDSoundHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDMAudioHostEnumInit(pDeviceEnum); + int rc = drvHostDSoundEnumerateDevices(pDeviceEnum); + if (RT_FAILURE(rc)) + PDMAudioHostEnumDelete(pDeviceEnum); + + LogFlowFunc(("Returning %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct. + * + * @param pCfg The PDM audio stream config to convert from. + * @param pFmt The windows structure to initialize. + */ +static void dsoundWaveFmtFromCfg(PCPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEXTENSIBLE pFmt) +{ + RT_ZERO(*pFmt); + pFmt->Format.wFormatTag = WAVE_FORMAT_PCM; + pFmt->Format.nChannels = PDMAudioPropsChannels(&pCfg->Props); + pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props); + pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(&pCfg->Props); + pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(&pCfg->Props); + pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(&pCfg->Props, PDMAudioPropsHz(&pCfg->Props)); + pFmt->Format.cbSize = 0; /* No extra data specified. */ + + /* + * We need to use the extensible structure if there are more than two channels + * or if the channels have non-standard assignments. + */ + if ( pFmt->Format.nChannels > 2 + || ( pFmt->Format.nChannels == 1 + ? pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_MONO + : pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT + || pCfg->Props.aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT)) + { + pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format); + pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props); + pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pFmt->dwChannelMask = 0; + unsigned const cSrcChannels = pFmt->Format.nChannels; + for (unsigned i = 0; i < cSrcChannels; i++) + if ( pCfg->Props.aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD + && pCfg->Props.aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD) + pFmt->dwChannelMask |= RT_BIT_32(pCfg->Props.aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD); + else + pFmt->Format.nChannels -= 1; + } +} + + +/** + * Resets the state of a DirectSound stream, clearing the buffer content. + * + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to reset state for. + */ +static void drvHostDSoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + RT_NOREF(pThis); + LogFunc(("Resetting %s\n", pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback")); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* + * Input streams. + */ + LogFunc(("Resetting capture stream '%s'\n", pStreamDS->Cfg.szName)); + + /* Reset the state: */ + pStreamDS->msLastTransfer = 0; +/** @todo r=bird: We set the read position to zero here, but shouldn't we query it + * from the buffer instead given that there isn't any interface for repositioning + * to the start of the buffer as with playback buffers? */ + pStreamDS->In.offReadPos = 0; + pStreamDS->In.cOverruns = 0; + + /* Clear the buffer content: */ + AssertPtr(pStreamDS->In.pDSCB); + if (pStreamDS->In.pDSCB) + { + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + HRESULT hrc = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, 0, pStreamDS->cbBufSize, + &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + if (SUCCEEDED(hrc)) + { + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + hrc = IDirectSoundCaptureBuffer8_Unlock(pStreamDS->In.pDSCB, pv1, cb1, pv2, cb2); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Unlocking capture buffer '%s' after reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMaxFunc(64, ("DSound: Locking capture buffer '%s' for reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + } + else + { + /* + * Output streams. + */ + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + LogFunc(("Resetting playback stream '%s'\n", pStreamDS->Cfg.szName)); + + /* If draining was enagaged, make sure dsound has stopped playing: */ + if (pStreamDS->Out.fDrain && pStreamDS->Out.pDSB) + pStreamDS->Out.pDSB->Stop(); + + /* Reset the internal state: */ + pStreamDS->msLastTransfer = 0; + pStreamDS->Out.fFirstTransfer = true; + pStreamDS->Out.fDrain = false; + pStreamDS->Out.cbLastTransferred = 0; + pStreamDS->Out.cbTransferred = 0; + pStreamDS->Out.cbWritten = 0; + pStreamDS->Out.offWritePos = 0; + pStreamDS->Out.offPlayCursorLastPending = 0; + pStreamDS->Out.offPlayCursorLastPlayed = 0; + + /* Reset the buffer content and repositioning the buffer to the start of the buffer. */ + AssertPtr(pStreamDS->Out.pDSB); + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Failed to set buffer position for '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + if (hrc == DSERR_BUFFERLOST) + { + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/); + } + if (SUCCEEDED(hrc)) + { + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1)); + if (pv2 && cb2) + PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2)); + + hrc = IDirectSoundBuffer8_Unlock(pStreamDS->Out.pDSB, pv1, cb1, pv2, cb2); + if (FAILED(hrc)) + LogRelMaxFunc(64, ("DSound: Unlocking playback buffer '%s' after reset failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMaxFunc(64, ("DSound: Locking playback buffer '%s' for reset failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + } +} + + +/** + * Worker for drvHostDSoundHA_StreamCreate that creates caputre stream. + * + * @returns Windows COM status code. + * @param pThis The DSound instance data. + * @param pStreamDS The stream instance data. + * @param pCfgReq The requested stream config (input). + * @param pCfgAcq Where to return the actual stream config. This is a + * copy of @a *pCfgReq when called. + * @param pWaveFmtExt On input the requested stream format. Updated to the + * actual stream format on successful return. + */ +static HRESULT drvHostDSoundStreamCreateCapture(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt) +{ + Assert(pStreamDS->In.pDSCB == NULL); + HRESULT hrc; + + /* + * Create, initialize and set up a IDirectSoundCapture instance the first time + * we go thru here. + */ + /** @todo bird: Or should we rather just throw this away after we've gotten the + * capture buffer? Old code would just leak it... */ + if (pThis->pDSC == NULL) + { + hrc = drvHostDSoundCreateDSCaptureInstance(pThis->Cfg.pGuidCapture, &pThis->pDSC); + if (FAILED(hrc)) + return hrc; /* The worker has complained to the release log already. */ + } + + /* + * Create the capture buffer. + */ + DSCBUFFERDESC BufferDesc = + { + /*.dwSize = */ sizeof(BufferDesc), + /*.dwFlags = */ 0, + /*.dwBufferBytes =*/ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), + /*.dwReserved = */ 0, + /*.lpwfxFormat = */ &pWaveFmtExt->Format, + /*.dwFXCount = */ 0, + /*.lpDSCFXDesc = */ NULL + }; + + LogRel2(("DSound: Requested capture buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes))); + + LPDIRECTSOUNDCAPTUREBUFFER pLegacyDSCB = NULL; + hrc = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &BufferDesc, &pLegacyDSCB, NULL); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Creating capture buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* Get the IDirectSoundCaptureBuffer8 version of the interface. */ + hrc = IDirectSoundCaptureBuffer_QueryInterface(pLegacyDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB); + IDirectSoundCaptureBuffer_Release(pLegacyDSCB); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Querying IID_IDirectSoundCaptureBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* + * Query the actual stream configuration. + */ +#if 0 /** @todo r=bird: WTF was this for? */ + DWORD offByteReadPos = 0; + hrc = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos); + if (FAILED(hrc)) + { + offByteReadPos = 0; + DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr)); + } +#endif + RT_ZERO(*pWaveFmtExt); + hrc = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL); + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */ + + DSCBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0 }; + hrc = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &BufferCaps); + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired capture buffer capabilities for '%s':\n" + "DSound: dwFlags = %#RX32\n" + "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n" + "DSound: dwReserved = %#RX32\n", + pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), BufferCaps.dwReserved )); + + /* Update buffer related stuff: */ + pStreamDS->In.offReadPos = 0; /** @todo shouldn't we use offBytReadPos here to "read at the initial capture position"? */ + pStreamDS->cbBufSize = BufferCaps.dwBufferBytes; + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes); + +#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */ + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); +#endif + LogFlow(("returns S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Getting capture buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + } + else + LogRelMax(64, ("DSound: Getting capture format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + LogFlowFunc(("returns %Rhrc\n", hrc)); + return hrc; +} + + +/** + * Worker for drvHostDSoundHA_StreamCreate that creates playback stream. + * + * @returns Windows COM status code. + * @param pThis The DSound instance data. + * @param pStreamDS The stream instance data. + * @param pCfgReq The requested stream config (input). + * @param pCfgAcq Where to return the actual stream config. This is a + * copy of @a *pCfgReq when called. + * @param pWaveFmtExt On input the requested stream format. + * Updated to the actual stream format on successful + * return. + */ +static HRESULT drvHostDSoundStreamCreatePlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq, + PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt) +{ + Assert(pStreamDS->Out.pDSB == NULL); + HRESULT hrc; + + /* + * Create, initialize and set up a DirectSound8 instance the first time + * we go thru here. + */ + /** @todo bird: Or should we rather just throw this away after we've gotten the + * sound buffer? Old code would just leak it... */ + if (pThis->pDS == NULL) + { + hrc = drvHostDSoundCreateDSPlaybackInstance(pThis->Cfg.pGuidPlay, &pThis->pDS); + if (FAILED(hrc)) + return hrc; /* The worker has complained to the release log already. */ + } + + /* + * As we reuse our (secondary) buffer for playing out data as it comes in, + * we're using this buffer as a so-called streaming buffer. + * + * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx + * + * However, as we do not want to use memory on the sound device directly + * (as most modern audio hardware on the host doesn't have this anyway), + * we're *not* going to use DSBCAPS_STATIC for that. + * + * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill + * of copying own buffer data to our secondary's Direct Sound buffer. + */ + DSBUFFERDESC BufferDesc = + { + /*.dwSize = */ sizeof(BufferDesc), + /*.dwFlags = */ DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE, + /*.dwBufferBytes = */ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), + /*.dwReserved = */ 0, + /*.lpwfxFormat = */ &pWaveFmtExt->Format, + /*.guid3DAlgorithm = {0, 0, 0, {0,0,0,0, 0,0,0,0}} */ + }; + LogRel2(("DSound: Requested playback buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes))); + + LPDIRECTSOUNDBUFFER pLegacyDSB = NULL; + hrc = IDirectSound8_CreateSoundBuffer(pThis->pDS, &BufferDesc, &pLegacyDSB, NULL); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Creating playback sound buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* Get the IDirectSoundBuffer8 version of the interface. */ + hrc = IDirectSoundBuffer_QueryInterface(pLegacyDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB); + IDirectSoundBuffer_Release(pLegacyDSB); + if (FAILED(hrc)) + { + LogRelMax(64, ("DSound: Querying IID_IDirectSoundBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + return hrc; + } + + /* + * Query the actual stream parameters, they may differ from what we requested. + */ + RT_ZERO(*pWaveFmtExt); + hrc = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL); + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */ + + DSBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0, 0 }; + hrc = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &BufferCaps); + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired playback buffer capabilities for '%s':\n" + "DSound: dwFlags = %#RX32\n" + "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n" + "DSound: dwUnlockTransferRate = %RU32 KB/s\n" + "DSound: dwPlayCpuOverhead = %RU32%%\n", + pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes, + PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), + BufferCaps.dwUnlockTransferRate, BufferCaps.dwPlayCpuOverhead)); + + /* Update buffer related stuff: */ + pStreamDS->cbBufSize = BufferCaps.dwBufferBytes; + pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes); + pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 4; /* total fiction */ + pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + +#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */ + if (bc.dwBufferBytes & pStreamDS->uAlign) + DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n", + bc.dwBufferBytes, pStreamDS->uAlign + 1)); +#endif + LogFlow(("returns S_OK\n")); + return S_OK; + } + LogRelMax(64, ("DSound: Getting playback buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + } + else + LogRelMax(64, ("DSound: Getting playback format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc)); + + IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); + pStreamDS->Out.pDSB = NULL; + LogFlowFunc(("returns %Rhrc\n", hrc)); + return hrc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq)); + + const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType); + LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName)); + RTListInit(&pStreamDS->ListEntry); /* paranoia */ + + /* For whatever reason: */ + dsoundUpdateStatusInternal(pThis); + + /* + * DSound has different COM interfaces for working with input and output + * streams, so we'll quickly part ways here after some common format + * specification setup and logging. + */ +#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED) + char szTmp[64]; +#endif + LogRel2(("DSound: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType, + PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp)))); + + WAVEFORMATEXTENSIBLE WaveFmtExt; + dsoundWaveFmtFromCfg(pCfgReq, &WaveFmtExt); + LogRel2(("DSound: Requested %s format for '%s':\n" + "DSound: wFormatTag = %RU16\n" + "DSound: nChannels = %RU16\n" + "DSound: nSamplesPerSec = %RU32\n" + "DSound: nAvgBytesPerSec = %RU32\n" + "DSound: nBlockAlign = %RU16\n" + "DSound: wBitsPerSample = %RU16\n" + "DSound: cbSize = %RU16\n", + pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels, + WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign, + WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize)); + if (WaveFmtExt.Format.cbSize != 0) + LogRel2(("DSound: dwChannelMask = %#RX32\n" + "DSound: wValidBitsPerSample = %RU16\n", + WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample)); + + HRESULT hrc; + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt); + else + hrc = drvHostDSoundStreamCreatePlayback(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt); + int rc; + if (SUCCEEDED(hrc)) + { + LogRel2(("DSound: Acquired %s format for '%s':\n" + "DSound: wFormatTag = %RU16\n" + "DSound: nChannels = %RU16\n" + "DSound: nSamplesPerSec = %RU32\n" + "DSound: nAvgBytesPerSec = %RU32\n" + "DSound: nBlockAlign = %RU16\n" + "DSound: wBitsPerSample = %RU16\n" + "DSound: cbSize = %RU16\n", + pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels, + WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign, + WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize)); + if (WaveFmtExt.Format.cbSize != 0) + { + LogRel2(("DSound: dwChannelMask = %#RX32\n" + "DSound: wValidBitsPerSample = %RU16\n", + WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample)); + + /* Update the channel count and map here. */ + PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels); + uint8_t idCh = 0; + for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++) + if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit)) + { + pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit; + idCh++; + } + Assert(idCh == WaveFmtExt.Format.nChannels); + } + + /* + * Copy the acquired config and reset the stream (clears the buffer). + */ + PDMAudioStrmCfgCopy(&pStreamDS->Cfg, pCfgAcq); + drvHostDSoundStreamReset(pThis, pStreamDS); + + RTCritSectEnter(&pThis->CritSect); + RTListAppend(&pThis->HeadStreams, &pStreamDS->ListEntry); + RTCritSectLeave(&pThis->CritSect); + + rc = VINF_SUCCESS; + } + else + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + RT_NOREF(fImmediate); + + RTCritSectEnter(&pThis->CritSect); + RTListNodeRemove(&pStreamDS->ListEntry); + RTCritSectLeave(&pThis->CritSect); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* + * Input. + */ + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (FAILED(hrc)) + LogFunc(("IDirectSoundCaptureBuffer_Stop failed: %Rhrc\n", hrc)); + + drvHostDSoundStreamReset(pThis, pStreamDS); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + } + } + else + { + /* + * Output. + */ + if (pStreamDS->Out.pDSB) + { + drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/); + + IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB); + pStreamDS->Out.pDSB = NULL; + } + } + + if (RTCritSectIsInitialized(&pStreamDS->CritSect)) + RTCritSectDelete(&pStreamDS->CritSect); + + return VINF_SUCCESS; +} + + +/** + * Worker for drvHostDSoundHA_StreamEnable and drvHostDSoundHA_StreamResume. + * + * This will try re-open the capture device if we're having trouble starting it. + * + * @returns VBox status code. + * @param pThis The DSound host audio driver instance data. + * @param pStreamDS The stream instance data. + */ +static int drvHostDSoundStreamCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + /* + * Check the stream status first. + */ + int rc = VERR_AUDIO_STREAM_NOT_READY; + if (pStreamDS->In.pDSCB) + { + DWORD fStatus = 0; + HRESULT hrc = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &fStatus); + if (SUCCEEDED(hrc)) + { + /* + * Try start capturing if it's not already doing so. + */ + if (!(fStatus & DSCBSTATUS_CAPTURING)) + { + LogRel2(("DSound: Starting capture on '%s' ... \n", pStreamDS->Cfg.szName)); + hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else + { + /* + * Failed to start, try re-create the capture buffer. + */ + LogRelMax(64, ("DSound: Starting to capture on '%s' failed: %Rhrc - will try re-open it ...\n", + pStreamDS->Cfg.szName, hrc)); + + IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB); + pStreamDS->In.pDSCB = NULL; + + PDMAUDIOSTREAMCFG CfgReq = pStreamDS->Cfg; + PDMAUDIOSTREAMCFG CfgAcq = pStreamDS->Cfg; + WAVEFORMATEXTENSIBLE WaveFmtExt; + dsoundWaveFmtFromCfg(&pStreamDS->Cfg, &WaveFmtExt); + hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, &CfgReq, &CfgAcq, &WaveFmtExt); + if (SUCCEEDED(hrc)) + { + PDMAudioStrmCfgCopy(&pStreamDS->Cfg, &CfgAcq); + + /* + * Try starting capture again. + */ + LogRel2(("DSound: Starting capture on re-opened '%s' ... \n", pStreamDS->Cfg.szName)); + hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else + LogRelMax(64, ("DSound: Starting to capture on re-opened '%s' failed: %Rhrc\n", + pStreamDS->Cfg.szName, hrc)); + } + else + LogRelMax(64, ("DSound: Re-opening '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + } + else + { + LogRel2(("DSound: Already capturing (%#x)\n", fStatus)); + AssertFailed(); + } + } + else + LogRelMax(64, ("DSound: Retrieving capture status for '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * We always reset the buffer before enabling the stream (normally never necessary). + */ + drvHostDSoundStreamReset(pThis, pStreamDS); + pStreamDS->fEnabled = true; + + /* + * Input streams will start capturing, while output streams will only start + * playing once we get some audio data to play. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS); + else + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * Worker for drvHostDSoundHA_StreamDestroy, drvHostDSoundHA_StreamDisable and + * drvHostDSoundHA_StreamPause. + * + * @returns VBox status code. + * @param pThis The DSound host audio driver instance data. + * @param pStreamDS The stream instance data. + * @param fReset Whether to reset the buffer and state. + */ +static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset) +{ + if (!pStreamDS->Out.pDSB) + return VINF_SUCCESS; + + LogRel2(("DSound: Stopping playback of '%s'...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + { + LogFunc(("IDirectSoundBuffer8_Stop -> %Rhrc; will attempt restoring the stream...\n", hrc)); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + LogRelMax(64, ("DSound: %s playback of '%s' failed: %Rhrc\n", fReset ? "Stopping" : "Pausing", + pStreamDS->Cfg.szName, hrc)); + } + LogRel2(("DSound: Stopped playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + + if (fReset) + drvHostDSoundStreamReset(pThis, pStreamDS); + return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_AUDIO_STREAM_NOT_READY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Change the state. + */ + pStreamDS->fEnabled = false; + + /* + * Stop the stream and maybe reset the buffer. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (SUCCEEDED(hrc)) + LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName)); + else + { + LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + /* Don't report errors up to the caller, as it might just be a capture device change. */ + } + + /* This isn't strictly speaking necessary since StreamEnable does it too... */ + drvHostDSoundStreamReset(pThis, pStreamDS); + } + } + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (pStreamDS->Out.pDSB) + { + rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/); + if (RT_SUCCESS(rc)) + LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName)); + } + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + * + * @note Basically the same as drvHostDSoundHA_StreamDisable, just w/o the + * buffer resetting and fEnabled change. + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Stop the stream and maybe reset the buffer. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + { + if (pStreamDS->In.pDSCB) + { + HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB); + if (SUCCEEDED(hrc)) + LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName)); + else + { + LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + /* Don't report errors up to the caller, as it might just be a capture device change. */ + } + } + } + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (pStreamDS->Out.pDSB) + { + /* Don't stop draining buffers, we won't be resuming them right. + They'll stop by themselves anyway. */ + if (pStreamDS->Out.fDrain) + LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName)); + else + { + rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, false /*fReset*/); + if (RT_SUCCESS(rc)) + LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName)); + } + } + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * Worker for drvHostDSoundHA_StreamResume and drvHostDSoundHA_StreamPlay that + * starts playing the DirectSound Buffer. + * + * @returns VBox status code. + * @param pThis Host audio driver instance. + * @param pStreamDS Stream to start playing. + */ +static int directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS) +{ + if (!pStreamDS->Out.pDSB) + return VERR_AUDIO_STREAM_NOT_READY; + + LogRel2(("DSound: Starting playback of '%s' ...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + return VINF_SUCCESS; + + for (unsigned i = 0; hrc == DSERR_BUFFERLOST && i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n")); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + + hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING); + if (SUCCEEDED(hrc)) + return VINF_SUCCESS; + } + + LogRelMax(64, ("DSound: Failed to start playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + return VERR_AUDIO_STREAM_NOT_READY; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * Input streams will start capturing, while output streams will only start + * playing if we're past the pre-buffering state. + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN) + rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS); + else + { + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); + if (!pStreamDS->Out.fFirstTransfer) + rc = directSoundPlayStart(pThis, pStreamDS); + } + + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertReturn(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1, + pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * We've started the buffer in looping mode, try switch to non-looping... + */ + int rc = VINF_SUCCESS; + if (pStreamDS->Out.pDSB && !pStreamDS->Out.fDrain) + { + LogRel2(("DSound: Switching playback stream '%s' to drain mode...\n", pStreamDS->Cfg.szName)); + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (SUCCEEDED(hrc)) + { + hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, 0); + if (SUCCEEDED(hrc)) + { + uint64_t const msNow = RTTimeMilliTS(); + pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props, pStreamDS->cbBufSize) + msNow; + pStreamDS->Out.fDrain = true; + } + else + LogRelMax(64, ("DSound: Failed to restart '%s' in drain mode: %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + else + { + Log2Func(("drain: IDirectSoundBuffer8_Stop failed: %Rhrc\n", hrc)); + directSoundPlayRestore(pThis, pStreamDS->Out.pDSB); + + HRESULT hrc2 = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (SUCCEEDED(hrc2)) + LogFunc(("Successfully stopped the stream after restoring it. (hrc=%Rhrc)\n", hrc)); + else + { + LogRelMax(64, ("DSound: Failed to stop playback stream '%s' for putting into drain mode: %Rhrc (initial), %Rhrc (after restore)\n", + pStreamDS->Cfg.szName, hrc, hrc2)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + } + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS))); + return rc; +} + + +/** + * Retrieves the number of free bytes available for writing to a DirectSound output stream. + * + * @return VBox status code. VERR_NOT_AVAILABLE if unable to determine or the + * buffer was not recoverable. + * @param pThis Host audio driver instance. + * @param pStreamDS DirectSound output stream to retrieve number for. + * @param pdwFree Where to return the free amount on success. + * @param poffPlayCursor Where to return the play cursor offset. + */ +static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree, DWORD *poffPlayCursor) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER); + AssertPtrReturn(pdwFree, VERR_INVALID_POINTER); + AssertPtr(poffPlayCursor); + + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */ + + LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB; + AssertPtrReturn(pDSB, VERR_INVALID_POINTER); + + HRESULT hr = S_OK; + + /* Get the current play position which is used for calculating the free space in the buffer. */ + for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++) + { + DWORD offPlayCursor = 0; + DWORD offWriteCursor = 0; + hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor); + if (SUCCEEDED(hr)) + { + int32_t cbDiff = offWriteCursor - offPlayCursor; + if (cbDiff < 0) + cbDiff += pStreamDS->cbBufSize; + + int32_t cbFree = offPlayCursor - pStreamDS->Out.offWritePos; + if (cbFree < 0) + cbFree += pStreamDS->cbBufSize; + + if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff) + { + /** @todo count/log these. */ + pStreamDS->Out.offWritePos = offWriteCursor; + cbFree = pStreamDS->cbBufSize - cbDiff; + } + + /* When starting to use a DirectSound buffer, offPlayCursor and offWriteCursor + * both point at position 0, so we won't be able to detect how many bytes + * are writable that way. + * + * So use our per-stream written indicator to see if we just started a stream. */ + if (pStreamDS->Out.cbWritten == 0) + cbFree = pStreamDS->cbBufSize; + + LogRel4(("DSound: offPlayCursor=%RU32, offWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n", + offPlayCursor, offWriteCursor, pStreamDS->Out.offWritePos, cbFree)); + + *pdwFree = cbFree; + *poffPlayCursor = offPlayCursor; + return VINF_SUCCESS; + } + + if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */ + break; + + LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n")); + + directSoundPlayRestore(pThis, pDSB); + } + + if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */ + DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr)); + + LogFunc(("Failed with %Rhrc\n", hr)); + + *poffPlayCursor = pStreamDS->cbBufSize; + return VERR_NOT_AVAILABLE; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostDSoundHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + if ( pStreamDS->Cfg.enmDir != PDMAUDIODIR_OUT + || !pStreamDS->Out.fDrain) + { + LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; + } + LogFlowFunc(("returns DRAINING for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + return PDMHOSTAUDIOSTREAMSTATE_DRAINING; +} + +#if 0 /* This isn't working as the write cursor is more a function of time than what we do. + Previously we only reported the pre-buffering status anyway, so no harm. */ +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT) + { + /* This is a similar calculation as for StreamGetReadable, only for an output buffer. */ + AssertPtr(pStreamDS->In.pDSCB); + DWORD offPlayCursor = 0; + DWORD offWriteCursor = 0; + HRESULT hrc = IDirectSoundBuffer8_GetCurrentPosition(pStreamDS->Out.pDSB, &offPlayCursor, &offWriteCursor); + if (SUCCEEDED(hrc)) + { + uint32_t cbPending = dsoundRingDistance(offWriteCursor, offPlayCursor, pStreamDS->cbBufSize); + Log3Func(("cbPending=%RU32\n", cbPending)); + return cbPending; + } + AssertMsgFailed(("hrc=%Rhrc\n", hrc)); + } + /* else: For input streams we never have any pending data. */ + + return 0; +} +#endif + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + + DWORD cbFree = 0; + DWORD offIgn = 0; + int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbFree, &offIgn); + AssertRCReturn(rc, 0); + + return cbFree; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + + if (pStreamDS->fEnabled) + AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2); + else + { + Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + +/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here? + * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */ + + /* + * Transfer loop. + */ + uint32_t cbWritten = 0; + while (cbBuf > 0) + { + /* + * Figure out how much we can possibly write. + */ + DWORD offPlayCursor = 0; + DWORD cbWritable = 0; + int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor); + AssertRCReturn(rc, rc); + if (cbWritable < pStreamDS->Cfg.Props.cbFrame) + break; + + uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf); + Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos, + cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Lock that amount of buffer. + */ + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite, + &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite)); + + /* + * Copy over the data. + */ + memcpy(pv1, pvBuf, cb1); + pvBuf = (uint8_t *)pvBuf + cb1; + cbBuf -= cb1; + cbWritten += cb1; + + if (pv2) + { + memcpy(pv2, pvBuf, cb2); + pvBuf = (uint8_t *)pvBuf + cb2; + cbBuf -= cb2; + cbWritten += cb2; + } + + /* + * Unlock and update the write position. + */ + directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */ + pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize; + + /* + * If this was the first chunk, kick off playing. + */ + if (!pStreamDS->Out.fFirstTransfer) + { /* likely */ } + else + { + *pcbWritten = cbWritten; + rc = directSoundPlayStart(pThis, pStreamDS); + AssertRCReturn(rc, rc); + pStreamDS->Out.fFirstTransfer = false; + } + } + + /* + * Done. + */ + *pcbWritten = cbWritten; + + pStreamDS->Out.cbTransferred += cbWritten; + if (cbWritten) + { + uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev); + pStreamDS->Out.cbLastTransferred = cbWritten; + pStreamDS->msLastTransfer = RTTimeMilliTS(); + LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n", + cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0, + drvHostDSoundStreamStatusString(pStreamDS) )); + } + else if ( pStreamDS->Out.fDrain + && RTTimeMilliTS() >= pStreamDS->Out.msDrainDeadline) + { + LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS))); + if (pStreamDS->Out.pDSB) + { + HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB); + if (FAILED(hrc)) + LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc)); + } + pStreamDS->Out.fDrain = false; + pStreamDS->fEnabled = false; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN); + + if (pStreamDS->fEnabled) + { + /* This is the same calculation as for StreamGetPending. */ + AssertPtr(pStreamDS->In.pDSCB); + DWORD offCaptureCursor = 0; + DWORD offReadCursor = 0; + HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor); + if (SUCCEEDED(hrc)) + { + uint32_t cbPending = dsoundRingDistance(offCaptureCursor, offReadCursor, pStreamDS->cbBufSize); + Log3Func(("cbPending=%RU32\n", cbPending)); + return cbPending; + } + AssertMsgFailed(("hrc=%Rhrc\n", hrc)); + } + + return 0; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface); + PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream; + AssertPtrReturn(pStreamDS, 0); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + +#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */ + if (pStreamDS->fEnabled) + AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2); + else + { + Log2Func(("Stream disabled, skipping\n")); + return VINF_SUCCESS; + } +#endif + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) )); + + /* + * Read loop. + */ + uint32_t cbRead = 0; + while (cbBuf > 0) + { + /* + * Figure out how much we can read. + */ + DWORD offCaptureCursor = 0; + DWORD offReadCursor = 0; + HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos)); + + uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize); + + if (cbReadable >= pStreamDS->Cfg.Props.cbFrame) + { /* likely */ } + else + { + if (cbRead > 0) + { /* likely */ } + else if (pStreamDS->In.cOverruns < 32) + { + pStreamDS->In.cOverruns++; + DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n", + pStreamDS->cbBufSize, pStreamDS->In.cOverruns)); + } + break; + } + + uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf); + Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor, + pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS))); + + /* + * Lock that amount of buffer. + */ + PVOID pv1 = NULL; + DWORD cb1 = 0; + PVOID pv2 = NULL; + DWORD cb2 = 0; + hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/); + AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */ + AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead)); + + /* + * Copy over the data. + */ + memcpy(pvBuf, pv1, cb1); + pvBuf = (uint8_t *)pvBuf + cb1; + cbBuf -= cb1; + cbRead += cb1; + + if (pv2) + { + memcpy(pvBuf, pv2, cb2); + pvBuf = (uint8_t *)pvBuf + cb2; + cbBuf -= cb2; + cbRead += cb2; + } + + /* + * Unlock and update the write position. + */ + directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */ + pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize; + } + + /* + * Done. + */ + *pcbRead = cbRead; + if (cbRead) + { + uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev); + pStreamDS->msLastTransfer = RTTimeMilliTS(); + LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n", + cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0, + drvHostDSoundStreamStatusString(pStreamDS) )); + } + + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* PDMDRVINS::IBase Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG Interface * +*********************************************************************************************************************************/ + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct} + */ +static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns) +{ + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + LogFlowFuncEnter(); + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + if (pThis->m_pNotificationClient) + { + pThis->m_pNotificationClient->Unregister(); + pThis->m_pNotificationClient->Release(); + + pThis->m_pNotificationClient = NULL; + } +#endif + + PDMAudioHostEnumDelete(&pThis->DeviceEnum); + + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + + LogFlowFuncLeave(); +} + + +static LPCGUID dsoundConfigQueryGUID(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid) +{ + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + LPCGUID pGuid = NULL; + + char *pszGuid = NULL; + int rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, pszName, &pszGuid); + if (RT_SUCCESS(rc)) + { + rc = RTUuidFromStr(pUuid, pszGuid); + if (RT_SUCCESS(rc)) + pGuid = (LPCGUID)pUuid; + else + DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc)); + + RTStrFree(pszGuid); + } + + return pGuid; +} + + +static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg) +{ + pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pThis->pDrvIns, pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay); + pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pThis->pDrvIns, pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture); + + DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n", + &pThis->Cfg.uuidPlay, + &pThis->Cfg.uuidCapture)); +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a DirectSound Audio driver instance.} + */ +static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND); + RT_NOREF(fFlags); + LogRel(("Audio: Initializing DirectSound audio driver\n")); + + /* + * Init basic data members and interfaces. + */ + RTListInit(&pThis->HeadStreams); + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = NULL; + pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHostDSoundHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHostDSoundHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHostDSoundHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHostDSoundHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHostDSoundHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetState = drvHostDSoundHA_StreamGetState; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay; + pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture; + + /* + * Init the static parts. + */ + PDMAudioHostEnumInit(&pThis->DeviceEnum); + + pThis->fEnabledIn = false; + pThis->fEnabledOut = false; + + /* + * Verify that IDirectSound is available. + */ + LPDIRECTSOUND pDirectSound = NULL; + HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound); + if (SUCCEEDED(hrc)) + IDirectSound_Release(pDirectSound); + else + { + LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + +#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT + /* + * Set up WASAPI device change notifications (Vista+). + */ + if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) + { + /* Get the notification interface (from DrvAudio). */ +# ifdef VBOX_WITH_AUDIO_CALLBACKS + PPDMIHOSTAUDIOPORT pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + Assert(pIHostAudioPort); +# else + PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL; +# endif +#ifdef RT_EXCEPTIONS_ENABLED + try +#endif + { + pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIHostAudioPort, + pThis->Cfg.pGuidCapture == NULL, + pThis->Cfg.pGuidPlay == NULL); + } +#ifdef RT_EXCEPTIONS_ENABLED + catch (std::bad_alloc &) + { + return VERR_NO_MEMORY; + } +#else + AssertReturn(pThis->m_pNotificationClient, VERR_NO_MEMORY); +#endif + + hrc = pThis->m_pNotificationClient->Initialize(); + if (SUCCEEDED(hrc)) + { + hrc = pThis->m_pNotificationClient->Register(); + if (SUCCEEDED(hrc)) + LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion())); + else + { + LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + { + LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc)); + return VERR_AUDIO_BACKEND_INIT_FAILED; + } + } + else + LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion())); +#endif + + /* + * Initialize configuration values and critical section. + */ + dsoundConfigInit(pThis, pCfg); + return RTCritSectInit(&pThis->CritSect); +} + + +/** + * PDM driver registration. + */ +const PDMDRVREG g_DrvHostDSound = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "DSoundAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "DirectSound Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTDSOUND), + /* pfnConstruct */ + drvHostDSoundConstruct, + /* pfnDestruct */ + drvHostDSoundDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; |