summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/DrvAudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Audio/DrvAudio.cpp')
-rw-r--r--src/VBox/Devices/Audio/DrvAudio.cpp3676
1 files changed, 3676 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvAudio.cpp b/src/VBox/Devices/Audio/DrvAudio.cpp
new file mode 100644
index 00000000..c11fe1e6
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudio.cpp
@@ -0,0 +1,3676 @@
+/* $Id: DrvAudio.cpp $ */
+/** @file
+ * Intermediate audio driver header.
+ *
+ * @remarks Intermediate audio driver for connecting the audio device emulation
+ * with the host backend.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_GROUP LOG_GROUP_DRV_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/circbuf.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DrvAudio.h"
+#include "AudioMixBuffer.h"
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum);
+#endif
+
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest);
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+
+#ifndef VBOX_AUDIO_TESTCASE
+
+# if 0 /* unused */
+
+static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey,
+ PDMAUDIOFMT enmDefault, bool *pfDefault)
+{
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ char *pszValue = NULL;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue);
+ if (fmt == PDMAUDIOFMT_INVALID)
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ *pfDefault = false;
+ return fmt;
+}
+
+static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey,
+ int iDefault, bool *pfDefault)
+{
+
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return iDefault;
+ }
+
+ uint64_t u64Data = 0;
+ int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return iDefault;
+
+ }
+
+ *pfDefault = false;
+ return u64Data;
+}
+
+static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey,
+ const char *pszDefault, bool *pfDefault)
+{
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return pszDefault;
+ }
+
+ char *pszValue = NULL;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return pszDefault;
+ }
+
+ *pfDefault = false;
+ return pszValue;
+}
+
+# endif /* unused */
+
+#ifdef LOG_ENABLED
+/**
+ * Converts an audio stream status to a string.
+ *
+ * @returns Stringified stream status flags. Must be free'd with RTStrFree().
+ * "NONE" if no flags set.
+ * @param fStatus Stream status flags to convert.
+ */
+static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus)
+{
+#define APPEND_FLAG_TO_STR(_aFlag) \
+ if (fStatus & PDMAUDIOSTREAMSTS_FLAG_##_aFlag) \
+ { \
+ if (pszFlags) \
+ { \
+ rc2 = RTStrAAppend(&pszFlags, " "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+
+ char *pszFlags = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FLAG_TO_STR(INITIALIZED );
+ APPEND_FLAG_TO_STR(ENABLED );
+ APPEND_FLAG_TO_STR(PAUSED );
+ APPEND_FLAG_TO_STR(PENDING_DISABLE);
+ APPEND_FLAG_TO_STR(PENDING_REINIT );
+ } while (0);
+
+ if (!pszFlags)
+ rc2 = RTStrAAppend(&pszFlags, "NONE");
+
+ if ( RT_FAILURE(rc2)
+ && pszFlags)
+ {
+ RTStrFree(pszFlags);
+ pszFlags = NULL;
+ }
+
+#undef APPEND_FLAG_TO_STR
+
+ return pszFlags;
+}
+#endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */
+
+# if 0 /* unused */
+static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts)
+{
+ AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER);
+ /* oaOpts and cOpts are optional. */
+
+ PCFGMNODE pCfgChildHandle = NULL;
+ PCFGMNODE pCfgChildChildHandle = NULL;
+
+ /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
+ * The getter function will return default values.
+ */
+ if (pCfgHandle != NULL)
+ {
+ /* If its audio general setting, need to traverse to one child node.
+ * /Devices/ichac97/0/LUN#0/Config/Audio
+ */
+ if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */
+ {
+ pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
+ if(pCfgChildHandle)
+ pCfgHandle = pCfgChildHandle;
+ }
+ else
+ {
+ /* If its driver specific configuration , then need to traverse two level deep child
+ * child nodes. for eg. in case of DirectSoundConfiguration item
+ * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
+ */
+ pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
+ if (pCfgChildHandle)
+ {
+ pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle);
+ if (pCfgChildChildHandle)
+ pCfgHandle = pCfgChildChildHandle;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < cOpts; i++)
+ {
+ audio_option *pOpt = &paOpts[i];
+ if (!pOpt->valp)
+ {
+ LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name));
+ continue;
+ }
+
+ bool fUseDefault;
+
+ switch (pOpt->tag)
+ {
+ case AUD_OPT_BOOL:
+ case AUD_OPT_INT:
+ {
+ int *intp = (int *)pOpt->valp;
+ *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault);
+
+ break;
+ }
+
+ case AUD_OPT_FMT:
+ {
+ PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp;
+ *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault);
+
+ break;
+ }
+
+ case AUD_OPT_STR:
+ {
+ const char **strp = (const char **)pOpt->valp;
+ *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault);
+
+ break;
+ }
+
+ default:
+ LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag));
+ fUseDefault = false;
+ break;
+ }
+
+ if (!pOpt->overridenp)
+ pOpt->overridenp = &pOpt->overriden;
+
+ *pOpt->overridenp = !fUseDefault;
+ }
+
+ return VINF_SUCCESS;
+}
+# endif /* unused */
+#endif /* !VBOX_AUDIO_TESTCASE */
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
+
+ rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd);
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Controls an audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFlowFunc(("fStatus=%s\n", pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ {
+ /* Is a pending disable outstanding? Then disable first. */
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+
+ if (RT_SUCCESS(rc))
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
+
+ if (RT_SUCCESS(rc))
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ {
+ /*
+ * For playback (output) streams first mark the host stream as pending disable,
+ * so that the rest of the remaining audio data will be played first before
+ * closing the stream.
+ */
+ if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("[%s] Pending disable/pause\n", pStream->szName));
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE;
+ }
+
+ /* Can we close the host stream as well (not in pending disable mode)? */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE))
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ drvAudioStreamResetInternal(pThis, pStream);
+ }
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_SUCCESS(rc))
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_RESUME);
+ if (RT_SUCCESS(rc))
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DROP:
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DROP);
+ if (RT_SUCCESS(rc))
+ {
+ drvAudioStreamDropInternal(pThis, pStream);
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Controls a stream's backend.
+ * If the stream has no backend available, VERR_NOT_FOUND is returned.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ return VINF_SUCCESS;
+
+ LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName));
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_ENABLE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DISABLE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ /* Only pause if the stream is enabled. */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ break;
+
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_PAUSE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ /* Only need to resume if the stream is enabled. */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ break;
+
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_RESUME);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DRAIN);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DROP:
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DROP);
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if ( rc != VERR_NOT_IMPLEMENTED
+ && rc != VERR_NOT_SUPPORTED)
+ LogRel(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName, rc));
+
+ LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Initializes an audio stream with a given host and guest stream configuration.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to initialize.
+ * @param pCfgHost Stream configuration to use for the host side (backend).
+ * @param pCfgGuest Stream configuration to use for the guest side.
+ */
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
+
+ /*
+ * Init host stream.
+ */
+
+ /* Set the host's default audio data layout. */
+ pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+#ifdef DEBUG
+ LogFunc(("[%s] Requested host format:\n", pStream->szName));
+ DrvAudioHlpStreamCfgPrint(pCfgHost);
+#endif
+
+ LogRel2(("Audio: Creating stream '%s'\n", pStream->szName));
+ LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ pCfgGuest->Props.uHz, pCfgGuest->Props.cBytes * 8, pCfgGuest->Props.fSigned ? "S" : "U",
+ pCfgGuest->Props.cChannels, pCfgGuest->Props.cChannels == 1 ? "Channel" : "Channels"));
+ LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ pCfgHost->Props.uHz, pCfgHost->Props.cBytes * 8, pCfgHost->Props.fSigned ? "S" : "U",
+ pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 1 ? "Channel" : "Channels"));
+
+ PDMAUDIOSTREAMCFG CfgHostAcq;
+ int rc = drvAudioStreamCreateInternalBackend(pThis, pStream, pCfgHost, &CfgHostAcq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef DEBUG
+ LogFunc(("[%s] Acquired host format:\n", pStream->szName));
+ DrvAudioHlpStreamCfgPrint(&CfgHostAcq);
+#endif
+
+ LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ CfgHostAcq.Props.uHz, CfgHostAcq.Props.cBytes * 8, CfgHostAcq.Props.fSigned ? "S" : "U",
+ CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 1 ? "Channel" : "Channels"));
+
+ /* Let the user know if the backend changed some of the tweakable values. */
+ if (CfgHostAcq.Backend.cfBufferSize != pCfgHost->Backend.cfBufferSize)
+ LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfBufferSize, &pCfgHost->Props), pCfgHost->Backend.cfBufferSize,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props), CfgHostAcq.Backend.cfBufferSize));
+
+ if (CfgHostAcq.Backend.cfPeriod != pCfgHost->Backend.cfPeriod)
+ LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPeriod, &pCfgHost->Props), pCfgHost->Backend.cfPeriod,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPeriod));
+
+ if (CfgHostAcq.Backend.cfPreBuf != pCfgHost->Backend.cfPreBuf)
+ LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPreBuf, &pCfgHost->Props), pCfgHost->Backend.cfPreBuf,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPreBuf));
+ /*
+ * Configure host buffers.
+ */
+
+ /* Check if the backend did return sane values and correct if necessary.
+ * Should never happen with our own backends, but you never know ... */
+ if (CfgHostAcq.Backend.cfBufferSize < CfgHostAcq.Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), "
+ "setting pre-buffering size to %RU32 frames\n",
+ CfgHostAcq.Backend.cfPreBuf, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
+ CfgHostAcq.Backend.cfPreBuf = CfgHostAcq.Backend.cfBufferSize;
+ }
+
+ if (CfgHostAcq.Backend.cfPeriod > CfgHostAcq.Backend.cfBufferSize)
+ {
+ LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n",
+ CfgHostAcq.Backend.cfPeriod, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
+ CfgHostAcq.Backend.cfPeriod = CfgHostAcq.Backend.cfBufferSize;
+ }
+
+ uint64_t msBufferSize = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props);
+
+ LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msBufferSize, CfgHostAcq.Backend.cfBufferSize));
+
+ /* If no own pre-buffer is set, let the backend choose. */
+ uint64_t msPreBuf = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props);
+ LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msPreBuf, CfgHostAcq.Backend.cfPreBuf));
+
+ /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
+ const uint32_t msPeriod = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props);
+
+ LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msPeriod, CfgHostAcq.Backend.cfPeriod));
+
+ if ( pCfgGuest->Device.uSchedulingHintMs /* Any scheduling hint set? */
+ && pCfgGuest->Device.uSchedulingHintMs > msPeriod) /* This might lead to buffer underflows. */
+ {
+ LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
+ pStream->szName, pCfgGuest->Device.uSchedulingHintMs, msPeriod));
+ }
+
+ /* Destroy any former mixing buffer. */
+ AudioMixBufDestroy(&pStream->Host.MixBuf);
+
+ /* Make sure to (re-)set the host buffer's shift size. */
+ CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cBytes, CfgHostAcq.Props.cChannels);
+
+ rc = AudioMixBufInit(&pStream->Host.MixBuf, pStream->szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cfBufferSize);
+ AssertRC(rc);
+
+ /* Make a copy of the acquired host stream configuration. */
+ rc = DrvAudioHlpStreamCfgCopy(&pStream->Host.Cfg, &CfgHostAcq);
+ AssertRC(rc);
+
+ /*
+ * Init guest stream.
+ */
+
+ if (pCfgGuest->Device.uSchedulingHintMs)
+ LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",
+ pStream->szName, pCfgGuest->Device.uSchedulingHintMs,
+ DrvAudioHlpMilliToBytes(pCfgGuest->Device.uSchedulingHintMs, &pCfgGuest->Props)));
+
+ /* Destroy any former mixing buffer. */
+ AudioMixBufDestroy(&pStream->Guest.MixBuf);
+
+ /* Set the guests's default audio data layout. */
+ pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ /* Make sure to (re-)set the guest buffer's shift size. */
+ pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cBytes, pCfgGuest->Props.cChannels);
+
+ rc = AudioMixBufInit(&pStream->Guest.MixBuf, pStream->szName, &pCfgGuest->Props, CfgHostAcq.Backend.cfBufferSize);
+ AssertRC(rc);
+
+ /* Make a copy of the guest stream configuration. */
+ rc = DrvAudioHlpStreamCfgCopy(&pStream->Guest.Cfg, pCfgGuest);
+ AssertRC(rc);
+
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc));
+
+ if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
+ {
+ /* Host (Parent) -> Guest (Child). */
+ rc = AudioMixBufLinkTo(&pStream->Host.MixBuf, &pStream->Guest.MixBuf);
+ AssertRC(rc);
+ }
+ else
+ {
+ /* Guest (Parent) -> Host (Child). */
+ rc = AudioMixBufLinkTo(&pStream->Guest.MixBuf, &pStream->Host.MixBuf);
+ AssertRC(rc);
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ char szStatName[255];
+
+ if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
+ {
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesCaptured", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured,
+ szStatName, STAMUNIT_COUNT, "Total frames played.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesCaptured", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured,
+ szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesRead", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead,
+ szStatName, STAMUNIT_COUNT, "Total frames read.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesRead", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead,
+ szStatName, STAMUNIT_COUNT, "Total number of reads.");
+ }
+ else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT)
+ {
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesPlayed", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed,
+ szStatName, STAMUNIT_COUNT, "Total frames played.");
+
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesPlayed", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed,
+ szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesWritten", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten,
+ szStatName, STAMUNIT_COUNT, "Total frames written.");
+
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesWritten", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten,
+ szStatName, STAMUNIT_COUNT, "Total number of writes.");
+ }
+ else
+ AssertFailed();
+#endif
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Frees an audio stream and its allocated resources.
+ *
+ * @param pStream Audio stream to free.
+ * After this call the pointer will not be valid anymore.
+ */
+static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream)
+{
+ if (!pStream)
+ return;
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ if (pStream->pvBackend)
+ {
+ Assert(pStream->cbBackend);
+ RTMemFree(pStream->pvBackend);
+ pStream->pvBackend = NULL;
+ }
+
+ RTMemFree(pStream);
+ pStream = NULL;
+}
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Schedules a re-initialization of all current audio streams.
+ * The actual re-initialization will happen at some later point in time.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ */
+static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFunc(("\n"));
+
+ /* Mark all host streams to re-initialize. */
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
+
+# ifdef VBOX_WITH_AUDIO_ENUM
+ /* Re-enumerate all host devices as soon as possible. */
+ pThis->fEnumerateDevices = true;
+# endif
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+/**
+ * Re-initializes an audio stream with its existing host and guest stream configuration.
+ * This might be the case if the backend told us we need to re-initialize because something
+ * on the host side has changed.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to re-initialize.
+ */
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s]\n", pStream->szName));
+
+ /*
+ * Gather current stream status.
+ */
+ bool fIsEnabled = RT_BOOL(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED); /* Stream is enabled? */
+
+ /*
+ * Destroy and re-create stream on backend side.
+ */
+ int rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamCreateInternalBackend(pThis, pStream, &pStream->Host.Cfg, NULL /* pCfgAcq */);
+ /** @todo Validate (re-)acquired configuration with pStream->Host.Cfg? */
+ }
+ }
+
+ /* Drop all old data. */
+ drvAudioStreamDropInternal(pThis, pStream);
+
+ /*
+ * Restore previous stream state.
+ */
+ if (fIsEnabled)
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
+
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+
+ LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Drops all audio data (and associated state) of a stream.
+ *
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to drop data for.
+ */
+static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ RT_NOREF(pThis);
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ AudioMixBufReset(&pStream->Guest.MixBuf);
+ AudioMixBufReset(&pStream->Host.MixBuf);
+
+ pStream->tsLastIteratedNs = 0;
+ pStream->tsLastPlayedCapturedNs = 0;
+ pStream->tsLastReadWrittenNs = 0;
+
+ pStream->fThresholdReached = false;
+}
+
+/**
+ * Resets a given audio stream.
+ *
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to reset.
+ */
+static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ drvAudioStreamDropInternal(pThis, pStream);
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Reset statistics.
+ */
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesCaptured);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesRead);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesCaptured);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesRead);
+ }
+ else if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesPlayed);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesWritten);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesPlayed);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesWritten);
+ }
+ else
+ AssertFailed();
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite}
+ */
+static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
+ pStream->szName, DrvAudioHlpAudDirToStr(pStream->enmDir)));
+
+ AssertMsg(DrvAudioHlpBytesIsAligned(cbBuf, &pStream->Guest.Cfg.Props),
+ ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStream->szName, cbBuf));
+
+ uint32_t cbWrittenTotal = 0;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out);
+#endif
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ AssertPtr(pszStreamSts);
+#endif
+
+ /* Whether to discard the incoming data or not. */
+ bool fToBitBucket = false;
+
+ do
+ {
+ if ( !pThis->Out.fEnabled
+ || !DrvAudioHlpStreamStatusIsReady(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ if (pThis->pHostDrvAudio)
+ {
+ /* If the backend's stream is not writable, all written data goes to /dev/null. */
+ if (!DrvAudioHlpStreamStatusCanWrite(
+ pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
+ {
+ fToBitBucket = true;
+ break;
+ }
+ }
+
+ const uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
+ if (cbFree < cbBuf)
+ LogRel2(("Audio: Lost audio output (%RU64ms, %RU32 free but needs %RU32) due to full host stream buffer '%s'\n",
+ DrvAudioHlpBytesToMilli(cbBuf - cbFree, &pStream->Host.Cfg.Props), cbFree, cbBuf, pStream->szName));
+
+ uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
+ if (!cbToWrite)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* We use the guest side mixing buffer as an intermediate buffer to do some
+ * (first) processing (if needed), so always write the incoming data at offset 0. */
+ uint32_t cfGstWritten = 0;
+ rc = AudioMixBufWriteAt(&pStream->Guest.MixBuf, 0 /* offFrames */, pvBuf, cbToWrite, &cfGstWritten);
+ if ( RT_FAILURE(rc)
+ || !cfGstWritten)
+ {
+ AssertMsgFailed(("[%s] Write failed: cbToWrite=%RU32, cfWritten=%RU32, rc=%Rrc\n",
+ pStream->szName, cbToWrite, cfGstWritten, rc));
+ break;
+ }
+
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Out.Dbg.pFileStreamWrite, pvBuf, cbToWrite, 0 /* fFlags */);
+
+ uint32_t cfGstMixed = 0;
+ if (cfGstWritten)
+ {
+ int rc2 = AudioMixBufMixToParentEx(&pStream->Guest.MixBuf, 0 /* cSrcOffset */, cfGstWritten /* cSrcFrames */,
+ &cfGstMixed /* pcSrcMixed */);
+ if (RT_FAILURE(rc2))
+ {
+ AssertMsgFailed(("[%s] Mixing failed: cbToWrite=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
+ pStream->szName, cbToWrite, cfGstWritten, cfGstMixed, rc2));
+ }
+ else
+ {
+ const uint64_t tsNowNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] Writing %RU32 frames (%RU64ms)\n",
+ pStream->szName, cfGstWritten, DrvAudioHlpFramesToMilli(cfGstWritten, &pStream->Guest.Cfg.Props)));
+
+ Log3Func(("[%s] Last written %RU64ns (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
+ pStream->szName, tsNowNs - pStream->tsLastReadWrittenNs,
+ (tsNowNs - pStream->tsLastReadWrittenNs) / RT_NS_1MS,
+ DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
+ AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
+
+ pStream->tsLastReadWrittenNs = tsNowNs;
+ /* Keep going. */
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ cbWrittenTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfGstWritten);
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfGstWritten);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfGstMixed);
+ Assert(cfGstWritten >= cfGstMixed);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfGstWritten - cfGstMixed);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal);
+
+ STAM_COUNTER_ADD(&pStream->Out.Stats.TotalFramesWritten, cfGstWritten);
+ STAM_COUNTER_INC(&pStream->Out.Stats.TotalTimesWritten);
+#endif
+ }
+
+ Log3Func(("[%s] Dbg: cbBuf=%RU32, cbToWrite=%RU32, cfHstUsed=%RU32, cfHstfLive=%RU32, cfGstWritten=%RU32, "
+ "cfGstMixed=%RU32, cbWrittenTotal=%RU32, rc=%Rrc\n",
+ pStream->szName, cbBuf, cbToWrite, AudioMixBufUsed(&pStream->Host.MixBuf),
+ AudioMixBufLive(&pStream->Host.MixBuf), cfGstWritten, cfGstMixed, cbWrittenTotal, rc));
+
+ } while (0);
+
+#ifdef LOG_ENABLED
+ RTStrFree(pszStreamSts);
+#endif
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fToBitBucket)
+ {
+ Log3Func(("[%s] Backend stream not ready (yet), discarding written data\n", pStream->szName));
+ cbWrittenTotal = cbBuf; /* Report all data as being written to the caller. */
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, UINT32_MAX);
+ AssertPtrReturn(pStream, UINT32_MAX);
+
+ NOREF(pInterface);
+
+ return ++pStream->cRefs;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, UINT32_MAX);
+ AssertPtrReturn(pStream, UINT32_MAX);
+
+ NOREF(pInterface);
+
+ if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */
+ pStream->cRefs--;
+
+ return pStream->cRefs;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = drvAudioStreamIterateInternal(pThis, pStream);
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+
+ return rc;
+}
+
+/**
+ * Does one iteration of an audio stream.
+ * This function gives the backend the chance of iterating / altering data and
+ * does the actual mixing between the guest <-> host mixing buffers.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to iterate.
+ */
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!pThis->pHostDrvAudio)
+ return VINF_SUCCESS;
+
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ int rc;
+
+ /* Is the stream scheduled for re-initialization? Do so now. */
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT)
+ {
+#ifdef VBOX_WITH_AUDIO_ENUM
+ if (pThis->fEnumerateDevices)
+ {
+ /* Re-enumerate all host devices. */
+ drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+
+ pThis->fEnumerateDevices = false;
+ }
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+ /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded
+ * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
+
+ rc = drvAudioStreamReInitInternal(pThis, pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ /* Not enabled or paused? Skip iteration. */
+ if ( !(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ || (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ {
+ return VINF_SUCCESS;
+ }
+
+ /* Whether to try closing a pending to close stream. */
+ bool fTryClosePending = false;
+
+ do
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ /* No audio frames to transfer from guest to host (anymore)?
+ * Then try closing this stream if marked so in the next block. */
+ const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+ fTryClosePending = cfLive == 0;
+ Log3Func(("[%s] fTryClosePending=%RTbool, cfLive=%RU32\n", pStream->szName, fTryClosePending, cfLive));
+ }
+
+ /* Has the host stream marked as pending to disable?
+ * Try disabling the stream then. */
+ if ( pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE
+ && fTryClosePending)
+ {
+ /* Tell the backend to drain the stream, that is, play the remaining (buffered) data
+ * on the backend side. */
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DRAIN);
+ if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining. */
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */
+ {
+ const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStream->pvBackend);
+ Log3Func(("[%s] cxPending=%RU32\n", pStream->szName, cxPending));
+
+ /* Only try close pending if no audio data is pending on the backend-side anymore. */
+ fTryClosePending = cxPending == 0;
+ }
+
+ if (fTryClosePending)
+ {
+ LogFunc(("[%s] Closing pending stream\n", pStream->szName));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ {
+ pStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAG_ENABLED | PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE);
+ drvAudioStreamDropInternal(pThis, pStream);
+ }
+ else
+ LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStream->szName, rc));
+ }
+ }
+ }
+
+ } while (0);
+
+ /* Update timestamps. */
+ pStream->tsLastIteratedNs = RTTimeNanoTS();
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Plays an audio host output stream which has been configured for non-interleaved (layout) data.
+ *
+ * @return IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to play.
+ * @param cfToPlay Number of audio frames to play.
+ * @param pcfPlayed Returns number of audio frames played. Optional.
+ */
+static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfPlayed is optional. */
+
+ if (!cfToPlay)
+ {
+ if (pcfPlayed)
+ *pcfPlayed = 0;
+ return VINF_SUCCESS;
+ }
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_OUT);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfPlayedTotal = 0;
+
+ uint8_t auBuf[256]; /** @todo Get rid of this here. */
+
+ uint32_t cfLeft = cfToPlay;
+ uint32_t cbChunk = sizeof(auBuf);
+
+ while (cfLeft)
+ {
+ uint32_t cfRead = 0;
+ rc = AudioMixBufAcquireReadBlock(&pStream->Host.MixBuf,
+ auBuf, RT_MIN(cbChunk, AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfLeft)),
+ &cfRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfRead);
+ Assert(cbRead <= cbChunk);
+
+ uint32_t cfPlayed = 0;
+ uint32_t cbPlayed = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
+ auBuf, cbRead, &cbPlayed);
+ if ( RT_SUCCESS(rc)
+ && cbPlayed)
+ {
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Out.Dbg.pFilePlayNonInterleaved, auBuf, cbPlayed, 0 /* fFlags */);
+
+ if (cbRead != cbPlayed)
+ LogRel2(("Audio: Host stream '%s' played wrong amount (%RU32 bytes read but played %RU32)\n",
+ pStream->szName, cbRead, cbPlayed));
+
+ cfPlayed = AUDIOMIXBUF_B2F(&pStream->Host.MixBuf, cbPlayed);
+ cfPlayedTotal += cfPlayed;
+
+ Assert(cfLeft >= cfPlayed);
+ cfLeft -= cfPlayed;
+ }
+
+ AudioMixBufReleaseReadBlock(&pStream->Host.MixBuf, cfPlayed);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcfPlayed)
+ *pcfPlayed = cfPlayedTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * Plays an audio host output stream which has been configured for raw audio (layout) data.
+ *
+ * @return IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to play.
+ * @param cfToPlay Number of audio frames to play.
+ * @param pcfPlayed Returns number of audio frames played. Optional.
+ */
+static int drvAudioStreamPlayRaw(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfPlayed is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_OUT);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
+
+ if (!cfToPlay)
+ {
+ if (pcfPlayed)
+ *pcfPlayed = 0;
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfPlayedTotal = 0;
+
+ PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */
+
+ uint32_t cfLeft = cfToPlay;
+ while (cfLeft)
+ {
+ uint32_t cfRead = 0;
+ rc = AudioMixBufPeek(&pStream->Host.MixBuf, cfLeft, aFrameBuf,
+ RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cfRead)
+ {
+ uint32_t cfPlayed;
+
+ /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames
+ * rather on bytes. */
+ Assert(cfRead <= RT_ELEMENTS(aFrameBuf));
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
+ aFrameBuf, cfRead, &cfPlayed);
+ if ( RT_FAILURE(rc)
+ || !cfPlayed)
+ {
+ break;
+ }
+
+ cfPlayedTotal += cfPlayed;
+ Assert(cfPlayedTotal <= cfToPlay);
+
+ Assert(cfLeft >= cfRead);
+ cfLeft -= cfRead;
+ }
+ else
+ {
+ if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */
+ continue;
+
+ break;
+ }
+ }
+ else if (RT_FAILURE(rc))
+ break;
+ }
+
+ Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcfPlayed)
+ *pcfPlayed = cfPlayedTotal;
+
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcFramesPlayed is optional. */
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cfPlayedTotal = 0;
+
+ PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] Start fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ do
+ {
+ if (!pThis->pHostDrvAudio)
+ {
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+ break;
+ }
+
+ if ( !pThis->Out.fEnabled
+ || !DrvAudioHlpStreamStatusIsReady(stsStream))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+#ifdef LOG_ENABLED
+ const uint8_t uLivePercent = (100 * cfLive) / AudioMixBufSize(&pStream->Host.MixBuf);
+#endif
+ const uint64_t tsDeltaPlayedCapturedNs = RTTimeNanoTS() - pStream->tsLastPlayedCapturedNs;
+ const uint32_t cfPassedReal = DrvAudioHlpNanoToFrames(tsDeltaPlayedCapturedNs, &pStream->Host.Cfg.Props);
+
+ const uint32_t cfPeriod = pStream->Host.Cfg.Backend.cfPeriod;
+
+ Log3Func(("[%s] Last played %RU64ns (%RU64ms), filled with %RU64ms (%RU8%%) total (fThresholdReached=%RTbool)\n",
+ pStream->szName, tsDeltaPlayedCapturedNs, tsDeltaPlayedCapturedNs / RT_NS_1MS_64,
+ DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props), uLivePercent, pStream->fThresholdReached));
+
+ if ( pStream->fThresholdReached /* Has the treshold been reached (e.g. are we in playing stage) ... */
+ && cfLive == 0) /* ... and we now have no live samples to process? */
+ {
+ LogRel2(("Audio: Buffer underrun for stream '%s' occurred (%RU64ms passed)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props)));
+
+ if (pStream->Host.Cfg.Backend.cfPreBuf) /* Any pre-buffering configured? */
+ {
+ /* Enter pre-buffering stage again. */
+ pStream->fThresholdReached = false;
+ }
+ }
+
+ bool fDoPlay = pStream->fThresholdReached;
+ bool fJustStarted = false;
+ if (!fDoPlay)
+ {
+ /* Did we reach the backend's playback (pre-buffering) threshold? Can be 0 if no threshold set. */
+ if (cfLive >= pStream->Host.Cfg.Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Stream '%s' buffering complete\n", pStream->szName));
+ Log3Func(("[%s] Dbg: Buffering complete\n", pStream->szName));
+ fDoPlay = true;
+ }
+ /* Some audio files are shorter than the pre-buffering level (e.g. the "click" Explorer sounds on some Windows guests),
+ * so make sure that we also play those by checking if the stream already is pending disable mode, even if we didn't
+ * hit the pre-buffering watermark yet.
+ *
+ * To reproduce, use "Windows Navigation Start.wav" on Windows 7 (2824 samples). */
+ else if ( cfLive
+ && pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
+ {
+ LogRel2(("Audio: Stream '%s' buffering complete (short sound)\n", pStream->szName));
+ Log3Func(("[%s] Dbg: Buffering complete (short)\n", pStream->szName));
+ fDoPlay = true;
+ }
+
+ if (fDoPlay)
+ {
+ pStream->fThresholdReached = true;
+ fJustStarted = true;
+ LogRel2(("Audio: Stream '%s' started playing\n", pStream->szName));
+ Log3Func(("[%s] Dbg: started playing\n", pStream->szName));
+ }
+ else /* Not yet, so still buffering audio data. */
+ LogRel2(("Audio: Stream '%s' is buffering (%RU8%% complete)\n",
+ pStream->szName, (100 * cfLive) / pStream->Host.Cfg.Backend.cfPreBuf));
+ }
+
+ if (fDoPlay)
+ {
+ uint32_t cfWritable = PDMAUDIOPCMPROPS_B2F(&pStream->Host.Cfg.Props,
+ pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream->pvBackend));
+
+ uint32_t cfToPlay = 0;
+ if (fJustStarted)
+ cfToPlay = RT_MIN(cfWritable, cfPeriod);
+
+ if (!cfToPlay)
+ {
+ /* Did we reach/pass (in real time) the device scheduling slot?
+ * Play as much as we can write to the backend then. */
+ if (cfPassedReal >= DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props))
+ cfToPlay = cfWritable;
+ }
+
+ if (cfToPlay > cfLive) /* Don't try to play more than available. */
+ cfToPlay = cfLive;
+#ifdef DEBUG
+ Log3Func(("[%s] Playing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
+ pStream->szName, cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
+ DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
+ AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
+#endif
+ if (cfToPlay)
+ {
+ if (pThis->pHostDrvAudio->pfnStreamPlayBegin)
+ pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
+ {
+ rc = drvAudioStreamPlayNonInterleaved(pThis, pStream, cfToPlay, &cfPlayedTotal);
+ }
+ else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
+ {
+ rc = drvAudioStreamPlayRaw(pThis, pStream, cfToPlay, &cfPlayedTotal);
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (pThis->pHostDrvAudio->pfnStreamPlayEnd)
+ pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ pStream->tsLastPlayedCapturedNs = RTTimeNanoTS();
+ }
+
+ Log3Func(("[%s] Dbg: fJustStarted=%RTbool, cfSched=%RU32 (%RU64ms), cfPassedReal=%RU32 (%RU64ms), "
+ "cfLive=%RU32 (%RU64ms), cfPeriod=%RU32 (%RU64ms), cfWritable=%RU32 (%RU64ms), "
+ "-> cfToPlay=%RU32 (%RU64ms), cfPlayed=%RU32 (%RU64ms)\n",
+ pStream->szName, fJustStarted,
+ DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props),
+ pStream->Guest.Cfg.Device.uSchedulingHintMs,
+ cfPassedReal, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props),
+ cfLive, DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props),
+ cfPeriod, DrvAudioHlpFramesToMilli(cfPeriod, &pStream->Host.Cfg.Props),
+ cfWritable, DrvAudioHlpFramesToMilli(cfWritable, &pStream->Host.Cfg.Props),
+ cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
+ cfPlayedTotal, DrvAudioHlpFramesToMilli(cfPlayedTotal, &pStream->Host.Cfg.Props)));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ AudioMixBufFinish(&pStream->Host.MixBuf, cfPlayedTotal);
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal);
+ STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out);
+
+ STAM_COUNTER_ADD (&pStream->Out.Stats.TotalFramesPlayed, cfPlayedTotal);
+ STAM_COUNTER_INC (&pStream->Out.Stats.TotalTimesPlayed);
+#endif
+ }
+
+ } while (0);
+
+#ifdef LOG_ENABLED
+ uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+ pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] End fStatus=%s, cfLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n",
+ pStream->szName, pszStreamSts, cfLive, cfPlayedTotal, rc));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcFramesPlayed)
+ *pcFramesPlayed = cfPlayedTotal;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Captures non-interleaved input from a host stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Driver instance.
+ * @param pStream Stream to capture from.
+ * @param pcfCaptured Number of (host) audio frames captured. Optional.
+ */
+static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfCaptured is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_IN);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfCapturedTotal = 0;
+
+ AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
+
+ uint8_t auBuf[_1K]; /** @todo Get rid of this. */
+ uint32_t cbBuf = sizeof(auBuf);
+
+ uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (!cbReadable)
+ Log2Func(("[%s] No readable data available\n", pStream->szName));
+
+ uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Guest.MixBuf); /* Parent */
+ if (!cbFree)
+ Log2Func(("[%s] Buffer full\n", pStream->szName));
+
+ if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
+ cbReadable = cbFree;
+
+ while (cbReadable)
+ {
+ uint32_t cbCaptured;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
+ auBuf, RT_MIN(cbReadable, cbBuf), &cbCaptured);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc2);
+
+ break;
+ }
+
+ Assert(cbCaptured <= cbBuf);
+ if (cbCaptured > cbBuf) /* Paranoia. */
+ cbCaptured = cbBuf;
+
+ if (!cbCaptured) /* Nothing captured? Take a shortcut. */
+ break;
+
+ /* We use the host side mixing buffer as an intermediate buffer to do some
+ * (first) processing (if needed), so always write the incoming data at offset 0. */
+ uint32_t cfHstWritten = 0;
+ rc = AudioMixBufWriteAt(&pStream->Host.MixBuf, 0 /* offFrames */, auBuf, cbCaptured, &cfHstWritten);
+ if ( RT_FAILURE(rc)
+ || !cfHstWritten)
+ {
+ AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
+ pStream->szName, cbCaptured, cfHstWritten, rc));
+ break;
+ }
+
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->In.Dbg.pFileCaptureNonInterleaved, auBuf, cbCaptured, 0 /* fFlags */);
+
+ uint32_t cfHstMixed = 0;
+ if (cfHstWritten)
+ {
+ int rc2 = AudioMixBufMixToParentEx(&pStream->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
+ &cfHstMixed /* pcSrcMixed */);
+ Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
+ pStream->szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
+ AssertRC(rc2);
+ }
+
+ Assert(cbReadable >= cbCaptured);
+ cbReadable -= cbCaptured;
+ cfCapturedTotal += cfHstMixed;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cfCapturedTotal)
+ Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
+ }
+ else
+ LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStream->szName, rc));
+
+ if (pcfCaptured)
+ *pcfCaptured = cfCapturedTotal;
+
+ return rc;
+}
+
+/**
+ * Captures raw input from a host stream.
+ * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
+ * no data layout processing done in between.
+ *
+ * Needed for e.g. the VRDP audio backend (in Main).
+ *
+ * @returns IPRT status code.
+ * @param pThis Driver instance.
+ * @param pStream Stream to capture from.
+ * @param pcfCaptured Number of (host) audio frames captured. Optional.
+ */
+static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfCaptured is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_IN);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfCapturedTotal = 0;
+
+ AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
+
+ /* Note: Raw means *audio frames*, not bytes! */
+ uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (!cfReadable)
+ Log2Func(("[%s] No readable data available\n", pStream->szName));
+
+ const uint32_t cfFree = AudioMixBufFree(&pStream->Guest.MixBuf); /* Parent */
+ if (!cfFree)
+ Log2Func(("[%s] Buffer full\n", pStream->szName));
+
+ if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
+ cfReadable = cfFree;
+
+ while (cfReadable)
+ {
+ PPDMAUDIOFRAME paFrames;
+ uint32_t cfWritable;
+ rc = AudioMixBufPeekMutable(&pStream->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
+ if ( RT_FAILURE(rc)
+ || !cfWritable)
+ {
+ break;
+ }
+
+ uint32_t cfCaptured;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
+ paFrames, cfWritable, &cfCaptured);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc2);
+
+ break;
+ }
+
+ Assert(cfCaptured <= cfWritable);
+ if (cfCaptured > cfWritable) /* Paranoia. */
+ cfCaptured = cfWritable;
+
+ Assert(cfReadable >= cfCaptured);
+ cfReadable -= cfCaptured;
+ cfCapturedTotal += cfCaptured;
+ }
+
+ Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
+
+ if (pcfCaptured)
+ *pcfCaptured = cfCapturedTotal;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
+{
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cfCaptured = 0;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ do
+ {
+ if (!pThis->pHostDrvAudio)
+ {
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+ break;
+ }
+
+ if ( !pThis->In.fEnabled
+ || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Do the actual capturing.
+ */
+
+ if (pThis->pHostDrvAudio->pfnStreamCaptureBegin)
+ pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
+ {
+ rc = drvAudioStreamCaptureNonInterleaved(pThis, pStream, &cfCaptured);
+ }
+ else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
+ {
+ rc = drvAudioStreamCaptureRaw(pThis, pStream, &cfCaptured);
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (pThis->pHostDrvAudio->pfnStreamCaptureEnd)
+ pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_SUCCESS(rc))
+ {
+ Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCaptured, rc));
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
+
+ STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesCaptured, cfCaptured);
+#endif
+ }
+ else if (RT_UNLIKELY(RT_FAILURE(rc)))
+ {
+ LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+ }
+
+ } while (0);
+
+ if (pcFramesCaptured)
+ *pcFramesCaptured = cfCaptured;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+
+ return rc;
+}
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Duplicates an audio callback.
+ *
+ * @returns Pointer to duplicated callback, or NULL on failure.
+ * @param pCB Callback to duplicate.
+ */
+static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB)
+{
+ AssertPtrReturn(pCB, NULL);
+
+ PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD));
+ if (!pCBCopy)
+ return NULL;
+
+ if (pCB->pvCtx)
+ {
+ pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx);
+ if (!pCBCopy->pvCtx)
+ {
+ RTMemFree(pCBCopy);
+ return NULL;
+ }
+
+ pCBCopy->cbCtx = pCB->cbCtx;
+ }
+
+ return pCBCopy;
+}
+
+/**
+ * Destroys a given callback.
+ *
+ * @param pCB Callback to destroy.
+ */
+static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB)
+{
+ if (!pCB)
+ return;
+
+ RTListNodeRemove(&pCB->Node);
+ if (pCB->pvCtx)
+ {
+ Assert(pCB->cbCtx);
+ RTMemFree(pCB->pvCtx);
+ }
+ RTMemFree(pCB);
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks}
+ */
+static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
+ AssertReturn(cCallbacks, VERR_INVALID_PARAMETER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ for (size_t i = 0; i < cCallbacks; i++)
+ {
+ PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]);
+ if (!pCB)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ switch (pCB->enmSource)
+ {
+ case PDMAUDIOCBSOURCE_DEVICE:
+ {
+ switch (pCB->Device.enmType)
+ {
+ case PDMAUDIODEVICECBTYPE_DATA_INPUT:
+ RTListAppend(&pThis->In.lstCB, &pCB->Node);
+ break;
+
+ case PDMAUDIODEVICECBTYPE_DATA_OUTPUT:
+ RTListAppend(&pThis->Out.lstCB, &pCB->Node);
+ break;
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+ }
+
+ /** @todo Undo allocations on error. */
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Backend callback implementation.
+ *
+ * Important: No calls back to the backend within this function, as the backend
+ * might hold any locks / critical sections while executing this callback.
+ * Will result in some ugly deadlocks (or at least locking order violations) then.
+ *
+ * @copydoc FNPDMHOSTAUDIOCALLBACK
+ */
+static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns,
+ PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser)
+{
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+ RT_NOREF(pvUser, cbUser);
+ /* pvUser and cbUser are optional. */
+
+ /* Get the upper driver (PDMIAUDIOCONNECTOR). */
+ AssertPtr(pDrvIns->pUpBase);
+ PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pInterface);
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser));
+
+ switch (enmType)
+ {
+ case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED:
+ LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
+ rc = drvAudioScheduleReInitInternal(pThis);
+ break;
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+/**
+ * Enumerates all host audio devices.
+ * This functionality might not be implemented by all backends and will return VERR_NOT_SUPPORTED
+ * if not being supported.
+ *
+ * @returns IPRT status code.
+ * @param pThis Driver instance to be called.
+ * @param fLog Whether to print the enumerated device to the release log or not.
+ * @param pDevEnum Where to store the device enumeration.
+ */
+static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum)
+{
+ int rc;
+
+ /*
+ * If the backend supports it, do a device enumeration.
+ */
+ if (pThis->pHostDrvAudio->pfnGetDevices)
+ {
+ PDMAUDIODEVICEENUM DevEnum;
+ rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
+ if (RT_SUCCESS(rc))
+ {
+ if (fLog)
+ LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName));
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ if (fLog)
+ {
+ char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags);
+
+ LogRel(("Audio: Device '%s':\n", pDev->szName));
+ LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage)));
+ LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "<NONE>"));
+ LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels));
+ LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels));
+
+ if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ }
+
+ if (pDevEnum)
+ rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum);
+
+ DrvAudioHlpDeviceEnumFree(&DevEnum);
+ }
+ else
+ {
+ if (fLog)
+ LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
+ /* Not fatal. */
+ }
+ }
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+
+ if (fLog)
+ LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName));
+ }
+
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+/**
+ * Initializes the host backend and queries its initial configuration.
+ * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned.
+ *
+ * Note: As this routine is called when attaching to the device LUN in the
+ * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED.
+ * Everything else is considered as fatal and must be handled separately in
+ * the device emulation!
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance to be called.
+ * @param pCfgHandle CFGM configuration handle to use for this driver.
+ */
+static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle)
+{
+ /* pCfgHandle is optional. */
+ NOREF(pCfgHandle);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ AssertPtr(pThis->pHostDrvAudio);
+ int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ /*
+ * Get the backend configuration.
+ */
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
+ pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
+
+ LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+
+ LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once\n",
+ pThis->szName,
+ /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */
+ RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, pThis->Out.cStreamsFree)));
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+ if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
+ AssertRC(rc2);
+
+ RT_NOREF(rc2);
+ /* Ignore rc. */
+#endif
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /*
+ * If the backend supports it, offer a callback to this connector.
+ */
+ if (pThis->pHostDrvAudio->pfnSetCallback)
+ {
+ rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback);
+ if (RT_FAILURE(rc2))
+ LogRel(("Audio: Error registering callback for host driver '%s', rc=%Rrc\n", pThis->szName, rc2));
+ /* Not fatal. */
+ }
+#endif
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles state changes for all audio streams.
+ *
+ * @param pDrvIns Pointer to driver instance.
+ * @param enmCmd Stream command to set for all streams.
+ */
+static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFunc(("enmCmd=%s\n", DrvAudioHlpStreamCmdToStr(enmCmd)));
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ if (pThis->pHostDrvAudio)
+ {
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ drvAudioStreamControlInternal(pThis, pStream, enmCmd);
+ }
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Retrieves an audio configuration from the specified CFGM node.
+ *
+ * @return VBox status code.
+ * @param pThis Driver instance to be called.
+ * @param pCfg Where to store the retrieved audio configuration to.
+ * @param pNode Where to get the audio configuration from.
+ */
+static int drvAudioGetCfgFromCFGM(PDRVAUDIO pThis, PDRVAUDIOCFG pCfg, PCFGMNODE pNode)
+{
+ RT_NOREF(pThis);
+
+ /* Debug stuff. */
+ CFGMR3QueryBoolDef(pNode, "DebugEnabled", &pCfg->Dbg.fEnabled, false);
+ int rc2 = CFGMR3QueryString(pNode, "DebugPathOut", pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut));
+ if ( RT_FAILURE(rc2)
+ || !strlen(pCfg->Dbg.szPathOut))
+ {
+ RTStrPrintf(pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+ }
+
+ if (pCfg->Dbg.fEnabled)
+ LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pCfg->Dbg.szPathOut));
+
+ /* Buffering stuff. */
+ CFGMR3QueryU32Def(pNode, "PeriodSizeMs", &pCfg->uPeriodSizeMs, 0);
+ CFGMR3QueryU32Def(pNode, "BufferSizeMs", &pCfg->uBufferSizeMs, 0);
+ CFGMR3QueryU32Def(pNode, "PreBufferSizeMs", &pCfg->uPreBufSizeMs, UINT32_MAX /* No custom value set */);
+
+ LogFunc(("pCfg=%p, uPeriodSizeMs=%RU32, uBufferSizeMs=%RU32, uPreBufSizeMs=%RU32\n",
+ pCfg, pCfg->uPeriodSizeMs, pCfg->uBufferSizeMs, pCfg->uPreBufSizeMs));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Intializes an audio driver instance.
+ *
+ * @returns IPRT status code.
+ * @param pDrvIns Pointer to driver instance.
+ * @param pCfgHandle CFGM handle to use for configuration.
+ */
+static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Configure driver from CFGM.
+ */
+#ifdef DEBUG
+ CFGMR3Dump(pCfgHandle);
+#endif
+
+ pThis->fTerminate = false;
+ pThis->pCFGMNode = pCfgHandle;
+
+ int rc2 = CFGMR3QueryString(pThis->pCFGMNode, "DriverName", pThis->szName, sizeof(pThis->szName));
+ if (RT_FAILURE(rc2))
+ RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled");
+
+ /* By default we don't enable anything if wrongly / not set-up. */
+ CFGMR3QueryBoolDef(pThis->pCFGMNode, "InputEnabled", &pThis->In.fEnabled, false);
+ CFGMR3QueryBoolDef(pThis->pCFGMNode, "OutputEnabled", &pThis->Out.fEnabled, false);
+
+ LogRel2(("Audio: Verbose logging for driver '%s' enabled\n", pThis->szName));
+
+ LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
+ pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
+
+ /*
+ * Load configurations.
+ */
+ rc = drvAudioGetCfgFromCFGM(pThis, &pThis->In.Cfg, pThis->pCFGMNode);
+ if (RT_SUCCESS(rc))
+ rc = drvAudioGetCfgFromCFGM(pThis, &pThis->Out.Cfg, pThis->pCFGMNode);
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
+ */
+static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cbReadTotal = 0;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ do
+ {
+ if ( !pThis->In.fEnabled
+ || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Read from the parent buffer (that is, the guest buffer) which
+ * should have the audio data in the format the guest needs.
+ */
+ uint32_t cfReadTotal = 0;
+
+ const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStream->Guest.MixBuf, cbBuf);
+
+ uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStream->Guest.MixBuf));
+ while (cfToRead)
+ {
+ uint32_t cfRead;
+ rc = AudioMixBufAcquireReadBlock(&pStream->Guest.MixBuf,
+ (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
+ AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfToRead), &cfRead);
+ if (RT_FAILURE(rc))
+ break;
+
+#ifdef VBOX_WITH_STATISTICS
+ const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfRead);
+
+ STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
+
+ STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesRead, cfRead);
+ STAM_COUNTER_INC(&pStream->In.Stats.TotalTimesRead);
+#endif
+ Assert(cfToRead >= cfRead);
+ cfToRead -= cfRead;
+
+ cfReadTotal += cfRead;
+
+ AudioMixBufReleaseReadBlock(&pStream->Guest.MixBuf, cfRead);
+ }
+
+ if (cfReadTotal)
+ {
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->In.Dbg.pFileStreamRead,
+ pvBuf, AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
+
+ AudioMixBufFinish(&pStream->Guest.MixBuf, cfReadTotal);
+ }
+
+ /* If we were not able to read as much data as requested, fill up the returned
+ * data with silence.
+ *
+ * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
+ if (cfReadTotal < cfBuf)
+ {
+ Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
+ DrvAudioHlpFramesToMilli(cfBuf - cfReadTotal, &pStream->Guest.Cfg.Props),
+ DrvAudioHlpFramesToMilli(cfBuf, &pStream->Guest.Cfg.Props)));
+
+ DrvAudioHlpClearBuf(&pStream->Guest.Cfg.Props,
+ (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
+ AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfBuf - cfReadTotal),
+ cfBuf - cfReadTotal);
+
+ cfReadTotal = cfBuf;
+ }
+
+ cbReadTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal);
+
+ pStream->tsLastReadWrittenNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
+
+ } while (0);
+
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest,
+ PPDMAUDIOSTREAM *ppStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
+#ifdef DEBUG
+ DrvAudioHlpStreamCfgPrint(pCfgHost);
+ DrvAudioHlpStreamCfgPrint(pCfgGuest);
+#endif
+
+ PPDMAUDIOSTREAM pStream = NULL;
+
+#define RC_BREAK(x) { rc = x; break; }
+
+ do
+ {
+ if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost)
+ || !DrvAudioHlpStreamCfgIsValid(pCfgGuest))
+ {
+ RC_BREAK(VERR_INVALID_PARAMETER);
+ }
+
+ /* Make sure that both configurations actually intend the same thing. */
+ if (pCfgHost->enmDir != pCfgGuest->enmDir)
+ {
+ AssertMsgFailed(("Stream configuration directions do not match\n"));
+ RC_BREAK(VERR_INVALID_PARAMETER);
+ }
+
+ /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */
+ size_t cbHstStrm;
+ if (pCfgHost->enmDir == PDMAUDIODIR_IN)
+ {
+ if (!pThis->In.cStreamsFree)
+ {
+ LogFlowFunc(("Maximum number of host input streams reached\n"));
+ RC_BREAK(VERR_AUDIO_NO_FREE_INPUT_STREAMS);
+ }
+
+ cbHstStrm = pThis->BackendCfg.cbStreamIn;
+ }
+ else /* Out */
+ {
+ if (!pThis->Out.cStreamsFree)
+ {
+ LogFlowFunc(("Maximum number of host output streams reached\n"));
+ RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS);
+ }
+
+ cbHstStrm = pThis->BackendCfg.cbStreamOut;
+ }
+
+ /*
+ * Allocate and initialize common state.
+ */
+
+ pStream = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM));
+ AssertPtrBreakStmt(pStream, rc = VERR_NO_MEMORY);
+
+ /* Retrieve host driver name for easier identification. */
+ AssertPtr(pThis->pHostDrvAudio);
+ PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase);
+ AssertPtr(pDrvAudioInst);
+ AssertPtr(pDrvAudioInst->pReg);
+
+ Assert(pDrvAudioInst->pReg->szName[0] != '\0');
+ RTStrPrintf(pStream->szName, RT_ELEMENTS(pStream->szName), "[%s] %s",
+ pDrvAudioInst->pReg->szName[0] != '\0' ? pDrvAudioInst->pReg->szName : "Untitled",
+ pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");
+
+ pStream->enmDir = pCfgHost->enmDir;
+
+ /*
+ * Allocate and init backend-specific data.
+ */
+
+ if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */
+ {
+ pStream->pvBackend = RTMemAllocZ(cbHstStrm);
+ AssertPtrBreakStmt(pStream->pvBackend, rc = VERR_NO_MEMORY);
+
+ pStream->cbBackend = cbHstStrm;
+ }
+
+ /*
+ * Try to init the rest.
+ */
+
+ rc = drvAudioStreamInitInternal(pThis, pStream, pCfgHost, pCfgGuest);
+ if (RT_FAILURE(rc))
+ break;
+
+ } while (0);
+
+#undef RC_BREAK
+
+ if (RT_FAILURE(rc))
+ {
+ if (pStream)
+ {
+ int rc2 = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+ }
+ }
+ else
+ {
+ /* Append the stream to our stream list. */
+ RTListAppend(&pThis->lstStreams, &pStream->Node);
+
+ /* Set initial reference counts. */
+ pStream->cRefs = 1;
+
+ if (pCfgHost->enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ {
+ char szFile[RTPATH_MAX + 1];
+
+ int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "CaptureNonInterleaved",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->In.Dbg.pFileCaptureNonInterleaved);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileCaptureNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "StreamRead",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->In.Dbg.pFileStreamRead);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileStreamRead, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+ }
+ }
+
+ if (pThis->In.cStreamsFree)
+ pThis->In.cStreamsFree--;
+ }
+ else /* Out */
+ {
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ {
+ char szFile[RTPATH_MAX + 1];
+
+ int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "PlayNonInterleaved",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->Out.Dbg.pFilePlayNonInterleaved);
+ if (RT_SUCCESS(rc2))
+ rc = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFilePlayNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "StreamWrite",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->Out.Dbg.pFileStreamWrite);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFileStreamWrite, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+ }
+ }
+
+ if (pThis->Out.cStreamsFree)
+ pThis->Out.cStreamsFree--;
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1);
+#endif
+ *ppStream = pStream;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
+ */
+static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ if (fEnable != *pfEnabled)
+ {
+ LogRel(("Audio: %s %s for driver '%s'\n",
+ fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
+
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ {
+ if (pStream->enmDir != enmDir) /* Skip unwanted streams. */
+ continue;
+
+ int rc2 = drvAudioStreamControlInternal(pThis, pStream,
+ fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_FAILURE(rc2))
+ LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n",
+ fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2));
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ /* Keep going. */
+ }
+
+ *pfEnabled = fEnable;
+ }
+
+ int rc3 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc3;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
+ */
+static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, false);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc2))
+ return false;
+
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(false);
+
+ const bool fIsEnabled = *pfEnabled;
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return fIsEnabled;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetConfig)
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetStatus)
+ backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
+ }
+ else
+ backendSts = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return backendSts;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, 0);
+ AssertPtrReturn(pStream, 0);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
+
+ uint32_t cbReadable = 0;
+
+ if ( pThis->pHostDrvAudio
+ && DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ const uint32_t cfReadable = AudioMixBufLive(&pStream->Guest.MixBuf);
+
+ cbReadable = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadable);
+
+ if (!cbReadable)
+ {
+ /*
+ * If nothing is readable, check if the stream on the backend side is ready to be read from.
+ * If it isn't, return the number of bytes readable since the last read from this stream.
+ *
+ * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
+ * situations, but the device emulation needs input data to keep the DMA transfers moving.
+ * Reading the actual data from a stream then will return silence then.
+ */
+ if (!DrvAudioHlpStreamStatusCanRead(
+ pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
+ {
+ cbReadable = DrvAudioHlpNanoToBytes(RTTimeNanoTS() - pStream->tsLastReadWrittenNs,
+ &pStream->Host.Cfg.Props);
+ Log3Func(("[%s] Backend stream not ready, returning silence\n", pStream->szName));
+ }
+ }
+
+ /* Make sure to align the readable size to the guest's frame size. */
+ cbReadable = DrvAudioHlpBytesAlign(cbReadable, &pStream->Guest.Cfg.Props);
+ }
+
+ Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
+ pStream->szName, cbReadable, DrvAudioHlpBytesToMilli(cbReadable, &pStream->Host.Cfg.Props)));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ /* Return bytes instead of audio frames. */
+ return cbReadable;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, 0);
+ AssertPtrReturn(pStream, 0);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"));
+
+ uint32_t cbWritable = 0;
+
+ /* Note: We don't propage the backend stream's status to the outside -- it's the job of this
+ * audio connector to make sense of it. */
+ if (DrvAudioHlpStreamStatusCanWrite(pStream->fStatus))
+ {
+ cbWritable = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
+
+ /* Make sure to align the writable size to the host's frame size. */
+ cbWritable = DrvAudioHlpBytesAlign(cbWritable, &pStream->Host.Cfg.Props);
+ }
+
+ Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
+ pStream->szName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pStream->Host.Cfg.Props)));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return cbWritable;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, false);
+
+ if (!pStream)
+ return PDMAUDIOSTREAMSTS_FLAG_NONE;
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] %s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return stsStream;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
+ */
+static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
+
+ AudioMixBufSetVolume(&pStream->Guest.MixBuf, pVol);
+ AudioMixBufSetVolume(&pStream->Host.MixBuf, pVol);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc);
+
+ LogRel2(("Audio: Destroying stream '%s'\n", pStream->szName));
+
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
+ if (pStream->cRefs > 1)
+ rc = VERR_WRONG_ORDER;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+ pThis->In.cStreamsFree++;
+ }
+ else /* Out */
+ {
+ pThis->Out.cStreamsFree++;
+ }
+
+ RTListNodeRemove(&pStream->Node);
+
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates an audio stream on the backend side.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Audio stream to create the backend side for.
+ * @param pCfgReq Requested audio stream configuration to use for stream creation.
+ * @param pCfgAcq Acquired audio stream configuration returned by the backend.
+ *
+ * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):
+ * - per global extra-data
+ * - per-VM extra-data
+ * - requested configuration (by pCfgReq)
+ * - default value
+ */
+static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ AssertMsg((pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) == 0,
+ ("Stream '%s' already initialized in backend\n", pStream->szName));
+
+ /* Get the right configuration for the stream to be created. */
+ PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;
+
+ /* Fill in the tweakable parameters into the requested host configuration.
+ * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
+
+ char szWhat[64]; /* Log where a value came from. */
+
+ /*
+ * Period size
+ */
+ if (pDrvCfg->uPeriodSizeMs)
+ {
+ pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(pDrvCfg->uPeriodSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+
+ if (!pCfgReq->Backend.cfPeriod) /* Set default period size if nothing explicitly is set. */
+ {
+ pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+
+ LogRel2(("Audio: Using %s period size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props), pCfgReq->Backend.cfPeriod, pStream->szName));
+
+ /*
+ * Buffer size
+ */
+ if (pDrvCfg->uBufferSizeMs)
+ {
+ pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(pDrvCfg->uBufferSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+
+ if (!pCfgReq->Backend.cfBufferSize) /* Set default buffer size if nothing explicitly is set. */
+ {
+ pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(250 /* ms */, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+
+ LogRel2(("Audio: Using %s buffer size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props), pCfgReq->Backend.cfBufferSize, pStream->szName));
+
+ /*
+ * Pre-buffering size
+ */
+ if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
+ {
+ pCfgReq->Backend.cfPreBuf = DrvAudioHlpMilliToFrames(pDrvCfg->uPreBufSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+ else /* No, then either use the default or device-specific settings (if any). */
+ {
+ if (pCfgReq->Backend.cfPreBuf == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
+ {
+ /* For pre-buffering to finish the buffer at least must be full one time. */
+ pCfgReq->Backend.cfPreBuf = pCfgReq->Backend.cfBufferSize;
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+ }
+
+ LogRel2(("Audio: Using %s pre-buffering size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props), pCfgReq->Backend.cfPreBuf, pStream->szName));
+
+ /*
+ * Validate input.
+ */
+ if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPeriod)
+ {
+ LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props),
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props)));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ( pCfgReq->Backend.cfPreBuf != UINT32_MAX /* Custom pre-buffering set? */
+ && pCfgReq->Backend.cfPreBuf)
+ {
+ if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPreBuf)
+ {
+ LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props),
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props)));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ /* Make the acquired host configuration the requested host configuration initially,
+ * in case the backend does not report back an acquired configuration. */
+ int rc = DrvAudioHlpStreamCfgCopy(pCfgAcq, pCfgReq);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
+ pStream->szName));
+ return rc;
+ }
+
+ rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStream->pvBackend, pCfgReq, pCfgAcq);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_SUPPORTED)
+ LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStream->szName));
+ else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
+ LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStream->szName));
+ else
+ LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+ }
+
+ /* Validate acquired configuration. */
+ if (!DrvAudioHlpStreamCfgIsValid(pCfgAcq))
+ {
+ LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pStream->szName));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Let the user know that the backend changed one of the values requested above. */
+ if (pCfgAcq->Backend.cfBufferSize != pCfgReq->Backend.cfBufferSize)
+ LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfBufferSize, &pCfgAcq->Props), pCfgAcq->Backend.cfBufferSize));
+
+ if (pCfgAcq->Backend.cfPeriod != pCfgReq->Backend.cfPeriod)
+ LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPeriod, &pCfgAcq->Props), pCfgAcq->Backend.cfPeriod));
+
+ /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
+ if ( pCfgReq->Backend.cfPreBuf
+ && pCfgAcq->Backend.cfPreBuf != pCfgReq->Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPreBuf, &pCfgAcq->Props), pCfgAcq->Backend.cfPreBuf));
+ }
+ else if (pCfgReq->Backend.cfPreBuf == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
+ {
+ LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStream->szName));
+ pCfgAcq->Backend.cfPreBuf = 0;
+ }
+
+ /* Sanity for detecting buggy backends. */
+ AssertMsgReturn(pCfgAcq->Backend.cfPeriod < pCfgAcq->Backend.cfBufferSize,
+ ("Acquired period size must be smaller than buffer size\n"),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pCfgAcq->Backend.cfPreBuf <= pCfgAcq->Backend.cfBufferSize,
+ ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"),
+ VERR_INVALID_PARAMETER);
+
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the backend to give it the chance to destroy its part of the audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Audio stream destruct backend for.
+ */
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFunc(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+#endif
+
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)
+ {
+ AssertPtr(pStream->pvBackend);
+
+ /* Check if the pointer to the host audio driver is still valid.
+ * It can be NULL if we were called in drvAudioDestruct, for example. */
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+ }
+
+#ifdef LOG_ENABLED
+ RTStrFree(pszStreamSts);
+#endif
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Uninitializes an audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Pointer to audio stream to uninitialize.
+ */
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
+
+ if (pStream->cRefs > 1)
+ return VERR_WRONG_ORDER;
+
+ int rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
+
+ /* Destroy mixing buffers. */
+ AudioMixBufDestroy(&pStream->Guest.MixBuf);
+ AudioMixBufDestroy(&pStream->Host.MixBuf);
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ if (pStream->fStatus != PDMAUDIOSTREAMSTS_FLAG_NONE)
+ {
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+ }
+#endif
+ pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_NONE;
+ }
+
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead);
+#endif
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileCaptureNonInterleaved);
+ pStream->In.Dbg.pFileCaptureNonInterleaved = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileStreamRead);
+ pStream->In.Dbg.pFileStreamRead = NULL;
+ }
+ }
+ else if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten);
+#endif
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFilePlayNonInterleaved);
+ pStream->Out.Dbg.pFilePlayNonInterleaved = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFileStreamWrite);
+ pStream->Out.Dbg.pFileStreamWrite = NULL;
+ }
+ }
+ else
+ AssertFailed();
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Does the actual backend driver attaching and queries the backend's interface.
+ *
+ * @return VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param fFlags Attach flags; see PDMDrvHlpAttach().
+ */
+static int drvAudioDoAttachInternal(PDRVAUDIO pThis, uint32_t fFlags)
+{
+ Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
+
+ /*
+ * Attach driver below and query its connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pThis->pDrvIns, fFlags, &pDownBase);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
+ if (!pThis->pHostDrvAudio)
+ {
+ LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
+ rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("Host audio backend missing or invalid"));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If everything went well, initialize the lower driver.
+ */
+ AssertPtr(pThis->pCFGMNode);
+ rc = drvAudioHostInit(pThis, pThis->pCFGMNode);
+ }
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
+ return rc;
+}
+
+
+/********************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
+
+ return NULL;
+}
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ return;
+
+ /* Just destroy the host stream on the backend side.
+ * The rest will either be destructed by the device emulation or
+ * in drvAudioDestruct(). */
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ {
+ drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ drvAudioStreamDestroyInternalBackend(pThis, pStream);
+ }
+
+ /*
+ * Last call for the driver below us.
+ * Let it know that we reached end of life.
+ */
+ if (pThis->pHostDrvAudio->pfnShutdown)
+ pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);
+
+ pThis->pHostDrvAudio = NULL;
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Constructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
+
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ RTListInit(&pThis->lstStreams);
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ RTListInit(&pThis->In.lstCB);
+ RTListInit(&pThis->Out.lstCB);
+#endif
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
+ /* IAudioConnector. */
+ pThis->IAudioConnector.pfnEnable = drvAudioEnable;
+ pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
+ pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
+ pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
+ pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
+ pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
+ pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
+ pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
+ pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
+ pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
+ pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite;
+ pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
+ pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
+ pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
+ pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
+ pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
+ pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
+ pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks;
+#endif
+
+ int rc = drvAudioInit(pDrvIns, pCfg);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
+ STAMUNIT_COUNT, "Total active audio streams.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
+ STAMUNIT_COUNT, "Total created audio streams.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
+ STAMUNIT_COUNT, "Total frames read by device emulation.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten",
+ STAMUNIT_COUNT, "Total frames written by device emulation ");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn",
+ STAMUNIT_COUNT, "Total input frames mixed.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut",
+ STAMUNIT_COUNT, "Total output frames mixed.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn",
+ STAMUNIT_COUNT, "Total input frames lost.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut",
+ STAMUNIT_COUNT, "Total output frames lost.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut",
+ STAMUNIT_COUNT, "Total frames played by backend.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
+ STAMUNIT_COUNT, "Total frames captured by backend.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
+ STAMUNIT_BYTES, "Total bytes read.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten",
+ STAMUNIT_BYTES, "Total bytes written.");
+
+ PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn",
+ STAMUNIT_NS_PER_CALL, "Profiling of input data processing.");
+ PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut",
+ STAMUNIT_NS_PER_CALL, "Profiling of output data processing.");
+#endif
+ }
+
+ rc = drvAudioDoAttachInternal(pThis, fFlags);
+ if (RT_FAILURE(rc))
+ {
+ /* No lower attached driver (yet)? Not a failure, might get attached later at runtime, just skip. */
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ int rc2;
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ /*
+ * Note: No calls here to the driver below us anymore,
+ * as PDM already has destroyed it.
+ * If you need to call something from the host driver,
+ * do this in drvAudioPowerOff() instead.
+ */
+
+ /* Thus, NULL the pointer to the host audio driver first,
+ * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
+ pThis->pHostDrvAudio = NULL;
+
+ PPDMAUDIOSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pThis->lstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node)
+ {
+ rc2 = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ RTListNodeRemove(&pStream->Node);
+
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThis->lstStreams));
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /*
+ * Destroy callbacks, if any.
+ */
+ PPDMAUDIOCBRECORD pCB, pCBNext;
+ RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
+ drvAudioCallbackDestroy(pCB);
+
+ RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
+ drvAudioCallbackDestroy(pCB);
+#endif
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut);
+#endif
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Suspend notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
+}
+
+/**
+ * Resume notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
+}
+
+/**
+ * Attach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Attach flags.
+ */
+static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ LogFunc(("%s\n", pThis->szName));
+
+ int rc = drvAudioDoAttachInternal(pThis, fFlags);
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Detach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Detach flags.
+ */
+static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ pThis->pHostDrvAudio = NULL;
+
+ LogFunc(("%s\n", pThis->szName));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Audio driver registration record.
+ */
+const PDMDRVREG g_DrvAUDIO =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AUDIO",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio connector driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ UINT32_MAX,
+ /* cbInstance */
+ sizeof(DRVAUDIO),
+ /* pfnConstruct */
+ drvAudioConstruct,
+ /* pfnDestruct */
+ drvAudioDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ drvAudioSuspend,
+ /* pfnResume */
+ drvAudioResume,
+ /* pfnAttach */
+ drvAudioAttach,
+ /* pfnDetach */
+ drvAudioDetach,
+ /* pfnPowerOff */
+ drvAudioPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+