summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp')
-rw-r--r--src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp1621
1 files changed, 1621 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp b/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp
new file mode 100644
index 00000000..89dbb4ae
--- /dev/null
+++ b/src/VBox/ValidationKit/utils/audio/vkatDriverStack.cpp
@@ -0,0 +1,1621 @@
+/* $Id: vkatDriverStack.cpp $ */
+/** @file
+ * Validation Kit Audio Test (VKAT) - Driver stack code.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_TEST
+#include <iprt/log.h>
+
+#include <iprt/errcore.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/test.h>
+
+
+/**
+ * Internal driver instance data
+ * @note This must be put here as it's needed before pdmdrv.h is included.
+ */
+typedef struct PDMDRVINSINT
+{
+ /** The stack the drive belongs to. */
+ struct AUDIOTESTDRVSTACK *pStack;
+} PDMDRVINSINT;
+#define PDMDRVINSINT_DECLARED
+
+#include "vkatInternal.h"
+#include "VBoxDD.h"
+
+
+
+/*********************************************************************************************************************************
+* Fake PDM Driver Handling. *
+*********************************************************************************************************************************/
+
+/** @name Driver Fakes/Stubs
+ *
+ * @{ */
+
+VMMR3DECL(PCFGMNODE) audioTestDrvHlp_CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath)
+{
+ RT_NOREF(pNode, pszPath);
+ return NULL;
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString)
+{
+ if (pNode != NULL)
+ {
+ PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
+ if (g_uVerbosity > 2)
+ RTPrintf("debug: CFGMR3QueryString([%s], %s, %p, %#x)\n", pDrvReg->szName, pszName, pszString, cchString);
+
+ if ( ( strcmp(pDrvReg->szName, "PulseAudio") == 0
+ || strcmp(pDrvReg->szName, "HostAudioWas") == 0)
+ && strcmp(pszName, "VmName") == 0)
+ return RTStrCopy(pszString, cchString, "vkat");
+
+ if ( strcmp(pDrvReg->szName, "HostAudioWas") == 0
+ && strcmp(pszName, "VmUuid") == 0)
+ return RTStrCopy(pszString, cchString, "794c9192-d045-4f28-91ed-46253ac9998e");
+ }
+ else if (g_uVerbosity > 2)
+ RTPrintf("debug: CFGMR3QueryString(%p, %s, %p, %#x)\n", pNode, pszName, pszString, cchString);
+
+ return VERR_CFGM_VALUE_NOT_FOUND;
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryStringAlloc(PCFGMNODE pNode, const char *pszName, char **ppszString)
+{
+ char szStr[128];
+ int rc = audioTestDrvHlp_CFGMR3QueryString(pNode, pszName, szStr, sizeof(szStr));
+ if (RT_SUCCESS(rc))
+ *ppszString = RTStrDup(szStr);
+
+ return rc;
+}
+
+
+VMMR3DECL(void) audioTestDrvHlp_MMR3HeapFree(PPDMDRVINS pDrvIns, void *pv)
+{
+ RT_NOREF(pDrvIns);
+
+ /* counterpart to CFGMR3QueryStringAlloc */
+ RTStrFree((char *)pv);
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef)
+{
+ PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
+ if (RT_VALID_PTR(pDrvReg))
+ {
+ const char *pszRet = pszDef;
+ if ( g_pszDrvAudioDebug
+ && strcmp(pDrvReg->szName, "AUDIO") == 0
+ && strcmp(pszName, "DebugPathOut") == 0)
+ pszRet = g_pszDrvAudioDebug;
+
+ int rc = RTStrCopy(pszString, cchString, pszRet);
+
+ if (g_uVerbosity > 2)
+ RTPrintf("debug: CFGMR3QueryStringDef([%s], %s, %p, %#x, %s) -> '%s' + %Rrc\n",
+ pDrvReg->szName, pszName, pszString, cchString, pszDef, pszRet, rc);
+ return rc;
+ }
+
+ if (g_uVerbosity > 2)
+ RTPrintf("debug: CFGMR3QueryStringDef(%p, %s, %p, %#x, %s)\n", pNode, pszName, pszString, cchString, pszDef);
+ return RTStrCopy(pszString, cchString, pszDef);
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryBoolDef(PCFGMNODE pNode, const char *pszName, bool *pf, bool fDef)
+{
+ PCPDMDRVREG pDrvReg = (PCPDMDRVREG)pNode;
+ if (RT_VALID_PTR(pDrvReg))
+ {
+ *pf = fDef;
+ if ( strcmp(pDrvReg->szName, "AUDIO") == 0
+ && strcmp(pszName, "DebugEnabled") == 0)
+ *pf = g_fDrvAudioDebug;
+
+ if (g_uVerbosity > 2)
+ RTPrintf("debug: CFGMR3QueryBoolDef([%s], %s, %p, %RTbool) -> %RTbool\n", pDrvReg->szName, pszName, pf, fDef, *pf);
+ return VINF_SUCCESS;
+ }
+ *pf = fDef;
+ return VINF_SUCCESS;
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryU8(PCFGMNODE pNode, const char *pszName, uint8_t *pu8)
+{
+ RT_NOREF(pNode, pszName, pu8);
+ return VERR_CFGM_VALUE_NOT_FOUND;
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3QueryU32(PCFGMNODE pNode, const char *pszName, uint32_t *pu32)
+{
+ RT_NOREF(pNode, pszName, pu32);
+ return VERR_CFGM_VALUE_NOT_FOUND;
+}
+
+
+VMMR3DECL(int) audioTestDrvHlp_CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode,
+ const char *pszValidValues, const char *pszValidNodes,
+ const char *pszWho, uint32_t uInstance)
+{
+ RT_NOREF(pNode, pszNode, pszValidValues, pszValidNodes, pszWho, uInstance);
+ return VINF_SUCCESS;
+}
+
+/** @} */
+
+/** @name Driver Helper Fakes
+ * @{ */
+
+static DECLCALLBACK(int) audioTestDrvHlp_Attach(PPDMDRVINS pDrvIns, uint32_t fFlags, PPDMIBASE *ppBaseInterface)
+{
+ /* DrvAudio must be allowed to attach the backend driver (paranoid
+ backend drivers may call us to check that nothing is attached). */
+ if (strcmp(pDrvIns->pReg->szName, "AUDIO") == 0)
+ {
+ PAUDIOTESTDRVSTACK pDrvStack = pDrvIns->Internal.s.pStack;
+ AssertReturn(pDrvStack->pDrvBackendIns == NULL, VERR_PDM_DRIVER_ALREADY_ATTACHED);
+
+ if (g_uVerbosity > 1)
+ RTMsgInfo("Attaching backend '%s' to DrvAudio...\n", pDrvStack->pDrvReg->szName);
+ int rc = audioTestDrvConstruct(pDrvStack, pDrvStack->pDrvReg, pDrvIns, &pDrvStack->pDrvBackendIns);
+ if (RT_SUCCESS(rc))
+ {
+ if (ppBaseInterface)
+ *ppBaseInterface = &pDrvStack->pDrvBackendIns->IBase;
+ }
+ else
+ RTMsgError("Failed to attach backend: %Rrc", rc);
+ return rc;
+ }
+ RT_NOREF(fFlags);
+ return VERR_PDM_NO_ATTACHED_DRIVER;
+}
+
+
+static DECLCALLBACK(void) audioTestDrvHlp_STAMRegister(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType, const char *pszName,
+ STAMUNIT enmUnit, const char *pszDesc)
+{
+ RT_NOREF(pDrvIns, pvSample, enmType, pszName, enmUnit, pszDesc);
+}
+
+
+static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterF(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
+ STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
+ const char *pszName, ...)
+{
+ RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName);
+}
+
+
+static DECLCALLBACK(void) audioTestDrvHlp_STAMRegisterV(PPDMDRVINS pDrvIns, void *pvSample, STAMTYPE enmType,
+ STAMVISIBILITY enmVisibility, STAMUNIT enmUnit, const char *pszDesc,
+ const char *pszName, va_list args)
+{
+ RT_NOREF(pDrvIns, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
+}
+
+
+static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregister(PPDMDRVINS pDrvIns, void *pvSample)
+{
+ RT_NOREF(pDrvIns, pvSample);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) audioTestDrvHlp_STAMDeregisterByPrefix(PPDMDRVINS pDrvIns, const char *pszPrefix)
+{
+ RT_NOREF(pDrvIns, pszPrefix);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Get the driver helpers.
+ */
+static const PDMDRVHLPR3 *audioTestFakeGetDrvHlp(void)
+{
+ /*
+ * Note! No initializer for s_DrvHlp (also why it's not a file global).
+ * We do not want to have to update this code every time PDMDRVHLPR3
+ * grows new entries or are otherwise modified. Only when the
+ * entries used by the audio driver changes do we want to change
+ * our code.
+ */
+ static PDMDRVHLPR3 s_DrvHlp;
+ if (s_DrvHlp.u32Version != PDM_DRVHLPR3_VERSION)
+ {
+ s_DrvHlp.u32Version = PDM_DRVHLPR3_VERSION;
+ s_DrvHlp.u32TheEnd = PDM_DRVHLPR3_VERSION;
+ s_DrvHlp.pfnAttach = audioTestDrvHlp_Attach;
+ s_DrvHlp.pfnSTAMRegister = audioTestDrvHlp_STAMRegister;
+ s_DrvHlp.pfnSTAMRegisterF = audioTestDrvHlp_STAMRegisterF;
+ s_DrvHlp.pfnSTAMRegisterV = audioTestDrvHlp_STAMRegisterV;
+ s_DrvHlp.pfnSTAMDeregister = audioTestDrvHlp_STAMDeregister;
+ s_DrvHlp.pfnSTAMDeregisterByPrefix = audioTestDrvHlp_STAMDeregisterByPrefix;
+ s_DrvHlp.pfnCFGMGetChild = audioTestDrvHlp_CFGMR3GetChild;
+ s_DrvHlp.pfnCFGMQueryString = audioTestDrvHlp_CFGMR3QueryString;
+ s_DrvHlp.pfnCFGMQueryStringAlloc = audioTestDrvHlp_CFGMR3QueryStringAlloc;
+ s_DrvHlp.pfnMMHeapFree = audioTestDrvHlp_MMR3HeapFree;
+ s_DrvHlp.pfnCFGMQueryStringDef = audioTestDrvHlp_CFGMR3QueryStringDef;
+ s_DrvHlp.pfnCFGMQueryBoolDef = audioTestDrvHlp_CFGMR3QueryBoolDef;
+ s_DrvHlp.pfnCFGMQueryU8 = audioTestDrvHlp_CFGMR3QueryU8;
+ s_DrvHlp.pfnCFGMQueryU32 = audioTestDrvHlp_CFGMR3QueryU32;
+ s_DrvHlp.pfnCFGMValidateConfig = audioTestDrvHlp_CFGMR3ValidateConfig;
+ }
+ return &s_DrvHlp;
+}
+
+/** @} */
+
+
+/**
+ * Implementation of PDMIBASE::pfnQueryInterface for a fake device above
+ * DrvAudio.
+ */
+static DECLCALLBACK(void *) audioTestFakeDeviceIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
+ RTMsgWarning("audioTestFakeDeviceIBaseQueryInterface: Unknown interface: %s\n", pszIID);
+ return NULL;
+}
+
+/** IBase interface for a fake device above DrvAudio. */
+static PDMIBASE g_AudioTestFakeDeviceIBase = { audioTestFakeDeviceIBaseQueryInterface };
+
+
+static DECLCALLBACK(int) audioTestIHostAudioPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ uintptr_t uUser, void *pvUser)
+{
+ RT_NOREF(pInterface, pStream, uUser, pvUser);
+ RTMsgWarning("audioTestIHostAudioPort_DoOnWorkerThread was called\n");
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
+{
+ RT_NOREF(pInterface, enmDir, pvUser);
+ RTMsgWarning("audioTestIHostAudioPort_NotifyDeviceChanged was called\n");
+}
+
+
+static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ RTMsgWarning("audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch was called\n");
+}
+
+
+static DECLCALLBACK(void) audioTestIHostAudioPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
+{
+ RT_NOREF(pInterface, pStream, fReInit);
+ RTMsgWarning("audioTestIHostAudioPort_StreamNotifyDeviceChanged was called\n");
+}
+
+
+static DECLCALLBACK(void) audioTestIHostAudioPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ RTMsgWarning("audioTestIHostAudioPort_NotifyDevicesChanged was called\n");
+}
+
+
+static PDMIHOSTAUDIOPORT g_AudioTestIHostAudioPort =
+{
+ audioTestIHostAudioPort_DoOnWorkerThread,
+ audioTestIHostAudioPort_NotifyDeviceChanged,
+ audioTestIHostAudioPort_StreamNotifyPreparingDeviceSwitch,
+ audioTestIHostAudioPort_StreamNotifyDeviceChanged,
+ audioTestIHostAudioPort_NotifyDevicesChanged,
+};
+
+
+/**
+ * Implementation of PDMIBASE::pfnQueryInterface for a fake DrvAudio above a
+ * backend.
+ */
+static DECLCALLBACK(void *) audioTestFakeDrvAudioIBaseQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, pInterface);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &g_AudioTestIHostAudioPort);
+ RTMsgWarning("audioTestFakeDrvAudioIBaseQueryInterface: Unknown interface: %s\n", pszIID);
+ return NULL;
+}
+
+
+/** IBase interface for a fake DrvAudio above a lonesome backend. */
+static PDMIBASE g_AudioTestFakeDrvAudioIBase = { audioTestFakeDrvAudioIBaseQueryInterface };
+
+
+
+/**
+ * Constructs a PDM audio driver instance.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The stack this is associated with.
+ * @param pDrvReg PDM driver registration record to use for construction.
+ * @param pParentDrvIns The parent driver (if any).
+ * @param ppDrvIns Where to return the driver instance structure.
+ */
+int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns,
+ PPPDMDRVINS ppDrvIns)
+{
+ /* The destruct function must have valid data to work with. */
+ *ppDrvIns = NULL;
+
+ /*
+ * Check registration structure validation (doesn't need to be too
+ * thorough, PDM check it in detail on every VM startup).
+ */
+ AssertPtrReturn(pDrvReg, VERR_INVALID_POINTER);
+ RTMsgInfo("Initializing backend '%s' ...\n", pDrvReg->szName);
+ AssertPtrReturn(pDrvReg->pfnConstruct, VERR_INVALID_PARAMETER);
+
+ /*
+ * Create the instance data structure.
+ */
+ PPDMDRVINS pDrvIns = (PPDMDRVINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(PDMDRVINS, achInstanceData[pDrvReg->cbInstance]));
+ RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY);
+
+ pDrvIns->u32Version = PDM_DRVINS_VERSION;
+ pDrvIns->iInstance = 0;
+ pDrvIns->pHlpR3 = audioTestFakeGetDrvHlp();
+ pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0];
+ pDrvIns->pReg = pDrvReg;
+ pDrvIns->pCfg = (PCFGMNODE)pDrvReg;
+ pDrvIns->Internal.s.pStack = pDrvStack;
+ pDrvIns->pUpBase = NULL;
+ pDrvIns->pDownBase = NULL;
+ if (pParentDrvIns)
+ {
+ Assert(pParentDrvIns->pDownBase == NULL);
+ pParentDrvIns->pDownBase = &pDrvIns->IBase;
+ pDrvIns->pUpBase = &pParentDrvIns->IBase;
+ }
+ else if (strcmp(pDrvReg->szName, "AUDIO") == 0)
+ pDrvIns->pUpBase = &g_AudioTestFakeDeviceIBase;
+ else
+ pDrvIns->pUpBase = &g_AudioTestFakeDrvAudioIBase;
+
+ /*
+ * Invoke the constructor.
+ */
+ int rc = pDrvReg->pfnConstruct(pDrvIns, pDrvIns->pCfg, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ *ppDrvIns = pDrvIns;
+ return VINF_SUCCESS;
+ }
+
+ if (pDrvReg->pfnDestruct)
+ pDrvReg->pfnDestruct(pDrvIns);
+ RTMemFree(pDrvIns);
+ return rc;
+}
+
+
+/**
+ * Destructs a PDM audio driver instance.
+ *
+ * @param pDrvIns Driver instance to destruct.
+ */
+static void audioTestDrvDestruct(PPDMDRVINS pDrvIns)
+{
+ if (pDrvIns)
+ {
+ Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
+
+ if (pDrvIns->pReg->pfnDestruct)
+ pDrvIns->pReg->pfnDestruct(pDrvIns);
+
+ pDrvIns->u32Version = 0;
+ pDrvIns->pReg = NULL;
+ RTMemFree(pDrvIns);
+ }
+}
+
+
+/**
+ * Sends the PDM driver a power off notification.
+ *
+ * @param pDrvIns Driver instance to notify.
+ */
+static void audioTestDrvNotifyPowerOff(PPDMDRVINS pDrvIns)
+{
+ if (pDrvIns)
+ {
+ Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
+ if (pDrvIns->pReg->pfnPowerOff)
+ pDrvIns->pReg->pfnPowerOff(pDrvIns);
+ }
+}
+
+
+/**
+ * Deletes a driver stack.
+ *
+ * This will power off and destroy the drivers.
+ */
+void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack)
+{
+ /*
+ * Do power off notifications (top to bottom).
+ */
+ audioTestDrvNotifyPowerOff(pDrvStack->pDrvAudioIns);
+ audioTestDrvNotifyPowerOff(pDrvStack->pDrvBackendIns);
+
+ /*
+ * Drivers are destroyed from bottom to top (closest to the device).
+ */
+ audioTestDrvDestruct(pDrvStack->pDrvBackendIns);
+ pDrvStack->pDrvBackendIns = NULL;
+ pDrvStack->pIHostAudio = NULL;
+
+ audioTestDrvDestruct(pDrvStack->pDrvAudioIns);
+ pDrvStack->pDrvAudioIns = NULL;
+ pDrvStack->pIAudioConnector = NULL;
+
+ PDMAudioHostEnumDelete(&pDrvStack->DevEnum);
+}
+
+
+/**
+ * Initializes a driver stack, extended version.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The driver stack to initialize.
+ * @param pDrvReg The backend driver to use.
+ * @param fEnabledIn Whether input is enabled or not on creation time.
+ * @param fEnabledOut Whether output is enabled or not on creation time.
+ * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
+ */
+int audioTestDriverStackInitEx(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio)
+{
+ int rc;
+
+ RT_ZERO(*pDrvStack);
+ pDrvStack->pDrvReg = pDrvReg;
+
+ PDMAudioHostEnumInit(&pDrvStack->DevEnum);
+
+ if (!fWithDrvAudio)
+ rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns);
+ else
+ {
+ rc = audioTestDrvConstruct(pDrvStack, &g_DrvAUDIO, NULL /*pParentDrvIns*/, &pDrvStack->pDrvAudioIns);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pDrvStack->pDrvAudioIns);
+ PPDMIBASE const pIBase = &pDrvStack->pDrvAudioIns->IBase;
+ pDrvStack->pIAudioConnector = (PPDMIAUDIOCONNECTOR)pIBase->pfnQueryInterface(pIBase, PDMIAUDIOCONNECTOR_IID);
+ if (pDrvStack->pIAudioConnector)
+ {
+ /* Both input and output is disabled by default. */
+ if (fEnabledIn)
+ rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_IN, true);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fEnabledOut)
+ rc = pDrvStack->pIAudioConnector->pfnEnable(pDrvStack->pIAudioConnector, PDMAUDIODIR_OUT, true);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "Failed to enabled input and output: %Rrc", rc);
+ audioTestDriverStackDelete(pDrvStack);
+ }
+ }
+ else
+ {
+ RTTestFailed(g_hTest, "Failed to query PDMIAUDIOCONNECTOR");
+ audioTestDriverStackDelete(pDrvStack);
+ rc = VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+ }
+
+ /*
+ * Get the IHostAudio interface and check that the host driver is working.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ PPDMIBASE const pIBase = &pDrvStack->pDrvBackendIns->IBase;
+ pDrvStack->pIHostAudio = (PPDMIHOSTAUDIO)pIBase->pfnQueryInterface(pIBase, PDMIHOSTAUDIO_IID);
+ if (pDrvStack->pIHostAudio)
+ {
+ PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT);
+ if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING)
+ return VINF_SUCCESS;
+
+ RTTestFailed(g_hTest, "Expected backend status RUNNING, got %d instead", enmStatus);
+ }
+ else
+ RTTestFailed(g_hTest, "Failed to query PDMIHOSTAUDIO for '%s'", pDrvReg->szName);
+ audioTestDriverStackDelete(pDrvStack);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Initializes a driver stack.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The driver stack to initialize.
+ * @param pDrvReg The backend driver to use.
+ * @param fEnabledIn Whether input is enabled or not on creation time.
+ * @param fEnabledOut Whether output is enabled or not on creation time.
+ * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
+ */
+int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio)
+{
+ return audioTestDriverStackInitEx(pDrvStack, pDrvReg, true /* fEnabledIn */, true /* fEnabledOut */, fWithDrvAudio);
+}
+
+/**
+ * Initializes a driver stack by probing all backends in the order of appearance
+ * in the backends description table.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The driver stack to initialize.
+ * @param pDrvReg The backend driver to use.
+ * @param fEnabledIn Whether input is enabled or not on creation time.
+ * @param fEnabledOut Whether output is enabled or not on creation time.
+ * @param fWithDrvAudio Whether to include DrvAudio in the stack or not.
+ */
+int audioTestDriverStackProbe(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fEnabledIn, bool fEnabledOut, bool fWithDrvAudio)
+{
+ int rc = VERR_IPE_UNINITIALIZED_STATUS; /* Shut up MSVC. */
+
+ for (size_t i = 0; i < g_cBackends; i++)
+ {
+ pDrvReg = g_aBackends[i].pDrvReg;
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing for backend '%s' ...\n", g_aBackends[i].pszName);
+
+ rc = audioTestDriverStackInitEx(pDrvStack, pDrvReg, fEnabledIn, fEnabledOut, fWithDrvAudio); /** @todo Make in/out configurable, too. */
+ if (RT_SUCCESS(rc))
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' successful\n", g_aBackends[i].pszName);
+ return rc;
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing backend '%s' failed with %Rrc, trying next one\n",
+ g_aBackends[i].pszName, rc);
+ continue;
+ }
+
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Probing all backends failed\n");
+ return rc;
+}
+
+/**
+ * Wrapper around PDMIHOSTAUDIO::pfnSetDevice.
+ */
+int audioTestDriverStackSetDevice(PAUDIOTESTDRVSTACK pDrvStack, PDMAUDIODIR enmDir, const char *pszDevId)
+{
+ int rc;
+ if ( pDrvStack->pIHostAudio
+ && pDrvStack->pIHostAudio->pfnSetDevice)
+ rc = pDrvStack->pIHostAudio->pfnSetDevice(pDrvStack->pIHostAudio, enmDir, pszDevId);
+ else if (!pszDevId || *pszDevId)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_INVALID_FUNCTION;
+ return rc;
+}
+
+
+/**
+ * Common stream creation code.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The audio driver stack to create it via.
+ * @param pCfgReq The requested config.
+ * @param ppStream Where to return the stream pointer on success.
+ * @param pCfgAcq Where to return the actual (well, not necessarily when
+ * using DrvAudio, but probably the same) stream config on
+ * success (not used as input).
+ */
+static int audioTestDriverStackStreamCreate(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16];
+ int rc;
+ *ppStream = NULL;
+
+ if (pDrvStack->pIAudioConnector)
+ {
+ /*
+ * DrvAudio does most of the work here.
+ */
+ rc = pDrvStack->pIAudioConnector->pfnStreamCreate(pDrvStack->pIAudioConnector, 0 /*fFlags*/, pCfgReq, ppStream);
+ if (RT_SUCCESS(rc))
+ {
+ *pCfgAcq = (*ppStream)->Cfg;
+ RTMsgInfo("Created backend stream: %s\n", PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)));
+ return rc;
+ }
+ /* else: Don't set RTTestFailed(...) here, as test boxes (servers) don't have any audio hardware.
+ * Caller has check the rc then. */
+ }
+ else
+ {
+ /*
+ * Get the config so we can see how big the PDMAUDIOBACKENDSTREAM
+ * structure actually is for this backend.
+ */
+ PDMAUDIOBACKENDCFG BackendCfg;
+ rc = pDrvStack->pIHostAudio->pfnGetConfig(pDrvStack->pIHostAudio, &BackendCfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (BackendCfg.cbStream >= sizeof(PDMAUDIOBACKENDSTREAM))
+ {
+ /*
+ * Allocate and initialize the stream.
+ */
+ uint32_t const cbStream = sizeof(AUDIOTESTDRVSTACKSTREAM) - sizeof(PDMAUDIOBACKENDSTREAM) + BackendCfg.cbStream;
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)RTMemAllocZVar(cbStream);
+ if (pStreamAt)
+ {
+ pStreamAt->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
+ pStreamAt->Core.Cfg = *pCfgReq;
+ pStreamAt->Core.cbBackend = cbStream;
+
+ pStreamAt->Backend.uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
+ pStreamAt->Backend.pStream = &pStreamAt->Core;
+
+ /*
+ * Call the backend to create the stream.
+ */
+ rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend,
+ pCfgReq, &pStreamAt->Core.Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_uVerbosity > 1)
+ RTMsgInfo("Created backend stream: %s\n",
+ PDMAudioStrmCfgToString(&pStreamAt->Core.Cfg, szTmp, sizeof(szTmp)));
+
+ /* Return if stream is ready: */
+ if (rc == VINF_SUCCESS)
+ {
+ *ppStream = &pStreamAt->Core;
+ *pCfgAcq = pStreamAt->Core.Cfg;
+ return VINF_SUCCESS;
+ }
+ if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED)
+ {
+ /*
+ * Do async init right here and now.
+ */
+ rc = pDrvStack->pIHostAudio->pfnStreamInitAsync(pDrvStack->pIHostAudio, &pStreamAt->Backend,
+ false /*fDestroyed*/);
+ if (RT_SUCCESS(rc))
+ {
+ *ppStream = &pStreamAt->Core;
+ *pCfgAcq = pStreamAt->Core.Cfg;
+ return VINF_SUCCESS;
+ }
+
+ RTTestFailed(g_hTest, "pfnStreamInitAsync failed: %Rrc\n", rc);
+ }
+ else
+ {
+ RTTestFailed(g_hTest, "pfnStreamCreate returned unexpected info status: %Rrc", rc);
+ rc = VERR_IPE_UNEXPECTED_INFO_STATUS;
+ }
+ pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/);
+ }
+ /* else: Don't set RTTestFailed(...) here, as test boxes (servers) don't have any audio hardware.
+ * Caller has check the rc then. */
+ }
+ else
+ {
+ RTTestFailed(g_hTest, "Out of memory!\n");
+ rc = VERR_NO_MEMORY;
+ }
+ RTMemFree(pStreamAt);
+ }
+ else
+ {
+ RTTestFailed(g_hTest, "cbStream=%#x is too small, min %#zx!\n", BackendCfg.cbStream, sizeof(PDMAUDIOBACKENDSTREAM));
+ rc = VERR_OUT_OF_RANGE;
+ }
+ }
+ else
+ RTTestFailed(g_hTest, "pfnGetConfig failed: %Rrc\n", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Creates an output stream.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The audio driver stack to create it via.
+ * @param pProps The audio properties to use.
+ * @param cMsBufferSize The buffer size in milliseconds.
+ * @param cMsPreBuffer The pre-buffering amount in milliseconds.
+ * @param cMsSchedulingHint The scheduling hint in milliseconds.
+ * @param ppStream Where to return the stream pointer on success.
+ * @param pCfgAcq Where to return the actual (well, not
+ * necessarily when using DrvAudio, but probably
+ * the same) stream config on success (not used as
+ * input).
+ */
+int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
+ uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
+ PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ /*
+ * Calculate the stream config.
+ */
+ PDMAUDIOSTREAMCFG CfgReq;
+ int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
+ AssertRC(rc);
+ CfgReq.enmDir = PDMAUDIODIR_OUT;
+ CfgReq.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
+ ? 10 : cMsSchedulingHint;
+ if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0))
+ CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */
+ else
+ CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps,
+ cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
+ ? 300 : cMsBufferSize);
+ if (cMsPreBuffer == UINT32_MAX)
+ CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudo picks the default */
+ : CfgReq.Backend.cFramesBufferSize * 2 / 3;
+ else
+ CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
+ if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16
+ && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ )
+ {
+ RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
+ CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
+ CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
+ ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
+ }
+
+ static uint32_t s_idxStream = 0;
+ uint32_t const idxStream = s_idxStream++;
+ RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "out-%u", idxStream);
+
+ /*
+ * Call common code to do the actual work.
+ */
+ return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq);
+}
+
+
+/**
+ * Creates an input stream.
+ *
+ * @returns VBox status code.
+ * @param pDrvStack The audio driver stack to create it via.
+ * @param pProps The audio properties to use.
+ * @param cMsBufferSize The buffer size in milliseconds.
+ * @param cMsPreBuffer The pre-buffering amount in milliseconds.
+ * @param cMsSchedulingHint The scheduling hint in milliseconds.
+ * @param ppStream Where to return the stream pointer on success.
+ * @param pCfgAcq Where to return the actual (well, not
+ * necessarily when using DrvAudio, but probably
+ * the same) stream config on success (not used as
+ * input).
+ */
+int audioTestDriverStackStreamCreateInput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
+ uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
+ PPDMAUDIOSTREAM *ppStream, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ /*
+ * Calculate the stream config.
+ */
+ PDMAUDIOSTREAMCFG CfgReq;
+ int rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
+ AssertRC(rc);
+ CfgReq.enmDir = PDMAUDIODIR_IN;
+ CfgReq.enmPath = PDMAUDIOPATH_IN_LINE;
+ CfgReq.Device.cMsSchedulingHint = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
+ ? 10 : cMsSchedulingHint;
+ if (pDrvStack->pIAudioConnector && (cMsBufferSize == UINT32_MAX || cMsBufferSize == 0))
+ CfgReq.Backend.cFramesBufferSize = 0; /* DrvAudio picks the default */
+ else
+ CfgReq.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(pProps,
+ cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
+ ? 300 : cMsBufferSize);
+ if (cMsPreBuffer == UINT32_MAX)
+ CfgReq.Backend.cFramesPreBuffering = pDrvStack->pIAudioConnector ? UINT32_MAX /*DrvAudio picks the default */
+ : CfgReq.Backend.cFramesBufferSize / 2;
+ else
+ CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
+ if ( CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16 /** @todo way to little */
+ && !pDrvStack->pIAudioConnector /*DrvAudio deals with it*/ )
+ {
+ RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
+ CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
+ CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
+ ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
+ }
+
+ static uint32_t s_idxStream = 0;
+ uint32_t const idxStream = s_idxStream++;
+ RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "in-%u", idxStream);
+
+ /*
+ * Call common code to do the actual work.
+ */
+ return audioTestDriverStackStreamCreate(pDrvStack, &CfgReq, ppStream, pCfgAcq);
+}
+
+
+/**
+ * Destroys a stream.
+ *
+ * @param pDrvStack Driver stack the stream to destroy is assigned to.
+ * @param pStream Stream to destroy. Pointer will be NULL (invalid) after successful return.
+ */
+void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ if (!pStream)
+ return;
+
+ if (pDrvStack->pIAudioConnector)
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IAudioConnector) ...\n", pStream->Cfg.szName);
+ int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream, true /*fImmediate*/);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc);
+ }
+ else
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' (IHostAudio) ...\n", pStream->Cfg.szName);
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend, true /*fImmediate*/);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamAt->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
+ pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC;
+
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Destroying stream '%s' done\n", pStream->Cfg.szName);
+
+ RTMemFree(pStreamAt);
+
+ pStreamAt = NULL;
+ pStream = NULL;
+ }
+ else
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc);
+ }
+}
+
+
+/**
+ * Enables a stream.
+ */
+int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ int rc;
+ if (pDrvStack->pIAudioConnector)
+ {
+ rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_ENABLE);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
+ }
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ rc = pDrvStack->pIHostAudio->pfnStreamEnable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamEnable failed: %Rrc", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Disables a stream.
+ */
+int AudioTestDriverStackStreamDisable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ int rc;
+ if (pDrvStack->pIAudioConnector)
+ {
+ rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamControl/DISABLE failed: %Rrc", rc);
+ }
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ rc = pDrvStack->pIHostAudio->pfnStreamDisable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDisable failed: %Rrc", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Drains an output stream.
+ */
+int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync)
+{
+ int rc;
+ if (pDrvStack->pIAudioConnector)
+ {
+ /*
+ * Issue the drain request.
+ */
+ rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DRAIN);
+ if (RT_SUCCESS(rc) && fSync)
+ {
+ /*
+ * This is a synchronous drain, so wait for the driver to change state to inactive.
+ */
+ PDMAUDIOSTREAMSTATE enmState;
+ while ( (enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream))
+ >= PDMAUDIOSTREAMSTATE_ENABLED)
+ {
+ RTThreadSleep(2);
+ rc = pDrvStack->pIAudioConnector->pfnStreamIterate(pDrvStack->pIAudioConnector, pStream);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "pfnStreamIterate/DRAIN failed: %Rrc", rc);
+ break;
+ }
+ }
+ if (enmState != PDMAUDIOSTREAMSTATE_INACTIVE)
+ {
+ RTTestFailed(g_hTest, "Stream state not INACTIVE after draining: %s", PDMAudioStreamStateGetName(enmState));
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+ else if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
+ }
+ else
+ {
+ /*
+ * Issue the drain request.
+ */
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ rc = pDrvStack->pIHostAudio->pfnStreamDrain(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+ if (RT_SUCCESS(rc) && fSync)
+ {
+ RTMSINTERVAL const msTimeout = RT_MS_5MIN; /* 5 minutes should be really enough for draining our stuff. */
+ uint64_t const tsStart = RTTimeMilliTS();
+
+ /*
+ * This is a synchronous drain, so wait for the driver to change state to inactive.
+ */
+ PDMHOSTAUDIOSTREAMSTATE enmHostState;
+ while ( (enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, &pStreamAt->Backend))
+ == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ RTThreadSleep(2);
+ uint32_t cbWritten = UINT32_MAX;
+ rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend,
+ NULL /*pvBuf*/, 0 /*cbBuf*/, &cbWritten);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN failed: %Rrc", rc);
+ break;
+ }
+ if (cbWritten != 0)
+ {
+ RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN did not set cbWritten to zero: %#x", cbWritten);
+ rc = VERR_MISSING;
+ break;
+ }
+
+ /* Fail-safe for audio stacks and/or implementations which mess up draining.
+ *
+ * Note: On some testboxes draining never seems to finish and thus is getting aborted, no clue why.
+ * The test result in the end still could be correct, although the actual draining problem
+ * needs to be investigated further.
+ *
+ * So don't make this (and the stream state check below) an error for now and just warn about it.
+ *
+ ** @todo Investigate draining issues on testboxes.
+ */
+ if (RTTimeMilliTS() - tsStart > msTimeout)
+ {
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS,
+ "Warning: Draining stream took too long (timeout is %RU32ms), giving up", msTimeout);
+ break;
+ }
+ }
+ if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY)
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS,
+ "Warning: Stream state not OKAY after draining: %s", PDMHostAudioStreamStateGetName(enmHostState));
+ }
+ else if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Checks if the stream is okay.
+ * @returns true if okay, false if not.
+ */
+bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ /*
+ * Get the stream status and check if it means is okay or not.
+ */
+ bool fRc = false;
+ if (pDrvStack->pIAudioConnector)
+ {
+ PDMAUDIOSTREAMSTATE enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream);
+ switch (enmState)
+ {
+ case PDMAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMAUDIOSTREAMSTATE_NEED_REINIT:
+ break;
+ case PDMAUDIOSTREAMSTATE_INACTIVE:
+ case PDMAUDIOSTREAMSTATE_ENABLED:
+ case PDMAUDIOSTREAMSTATE_ENABLED_READABLE:
+ case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE:
+ fRc = true;
+ break;
+ /* no default */
+ case PDMAUDIOSTREAMSTATE_INVALID:
+ case PDMAUDIOSTREAMSTATE_END:
+ case PDMAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ }
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ PDMHOSTAUDIOSTREAMSTATE enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio,
+ &pStreamAt->Backend);
+ switch (enmHostState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ fRc = true;
+ break;
+ /* no default */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ }
+ return fRc;
+}
+
+
+/**
+ * Gets the number of bytes it's currently possible to write to the stream.
+ */
+uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ uint32_t cbWritable;
+ if (pDrvStack->pIAudioConnector)
+ cbWritable = pDrvStack->pIAudioConnector->pfnStreamGetWritable(pDrvStack->pIAudioConnector, pStream);
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ cbWritable = pDrvStack->pIHostAudio->pfnStreamGetWritable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+ }
+ return cbWritable;
+}
+
+
+/**
+ * Tries to play the @a cbBuf bytes of samples in @a pvBuf.
+ */
+int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
+ void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
+{
+ int rc;
+ if (pDrvStack->pIAudioConnector)
+ {
+ rc = pDrvStack->pIAudioConnector->pfnStreamPlay(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbPlayed);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc);
+ }
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbPlayed);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamPlay(,,,%#x,) failed: %Rrc", cbBuf, rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Gets the number of bytes it's currently possible to write to the stream.
+ */
+uint32_t audioTestDriverStackStreamGetReadable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+ uint32_t cbReadable;
+ if (pDrvStack->pIAudioConnector)
+ cbReadable = pDrvStack->pIAudioConnector->pfnStreamGetReadable(pDrvStack->pIAudioConnector, pStream);
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ cbReadable = pDrvStack->pIHostAudio->pfnStreamGetReadable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+ }
+ return cbReadable;
+}
+
+
+/**
+ * Tries to capture @a cbBuf bytes of samples in @a pvBuf.
+ */
+int audioTestDriverStackStreamCapture(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured)
+{
+ int rc;
+ if (pDrvStack->pIAudioConnector)
+ {
+ rc = pDrvStack->pIAudioConnector->pfnStreamCapture(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbCaptured);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc);
+ }
+ else
+ {
+ PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+ rc = pDrvStack->pIHostAudio->pfnStreamCapture(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbCaptured);
+ if (RT_FAILURE(rc))
+ RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamCapture(,,,%#x,) failed: %Rrc", cbBuf, rc);
+ }
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Mixed streams *
+*********************************************************************************************************************************/
+
+/**
+ * Initializing mixing for a stream.
+ *
+ * This can be used as a do-nothing wrapper for the stack.
+ *
+ * @returns VBox status code.
+ * @param pMix The mixing state.
+ * @param pStream The stream to mix to/from.
+ * @param pProps The mixer properties. Pass NULL for no mixing, just
+ * wrap the driver stack functionality.
+ * @param cMsBuffer The buffer size.
+ */
+int AudioTestMixStreamInit(PAUDIOTESTDRVMIXSTREAM pMix, PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
+ PCPDMAUDIOPCMPROPS pProps, uint32_t cMsBuffer)
+{
+ RT_ZERO(*pMix);
+
+ AssertReturn(pDrvStack, VERR_INVALID_PARAMETER);
+ AssertReturn(pStream, VERR_INVALID_PARAMETER);
+
+ pMix->pDrvStack = pDrvStack;
+ pMix->pStream = pStream;
+ if (!pProps)
+ {
+ pMix->pProps = &pStream->Cfg.Props;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Okay, we're doing mixing so we need to set up the mixer buffer
+ * and associated states.
+ */
+ pMix->fDoMixing = true;
+ int rc = AudioMixBufInit(&pMix->MixBuf, "mixer", pProps, PDMAudioPropsMilliToFrames(pProps, cMsBuffer));
+ if (RT_SUCCESS(rc))
+ {
+ pMix->pProps = &pMix->MixBuf.Props;
+
+ if (pStream->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pMix->MixBuf.Props);
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pStream->Cfg.Props);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ }
+ else if (pStream->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ rc = AudioMixBufInitWriteState(&pMix->MixBuf, &pMix->WriteState, &pMix->MixBuf.Props);
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixBufInitPeekState(&pMix->MixBuf, &pMix->PeekState, &pStream->Cfg.Props);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ }
+ else
+ {
+ RTTestFailed(g_hTest, "Bogus stream direction!");
+ rc = VERR_INVALID_STATE;
+ }
+ }
+ else
+ RTTestFailed(g_hTest, "AudioMixBufInit failed: %Rrc", rc);
+ RT_ZERO(*pMix);
+ return rc;
+}
+
+
+/**
+ * Terminate mixing (leaves the stream untouched).
+ *
+ * @param pMix The mixing state.
+ */
+void AudioTestMixStreamTerm(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ if (pMix->fDoMixing)
+ {
+ AudioMixBufTerm(&pMix->MixBuf);
+ pMix->pStream = NULL;
+ }
+ RT_ZERO(*pMix);
+}
+
+
+/**
+ * Worker that transports data between the mixer buffer and the drivers.
+ *
+ * @returns VBox status code.
+ * @param pMix The mixer stream setup to do transfers for.
+ */
+static int audioTestMixStreamTransfer(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ uint8_t abBuf[16384];
+ if (pMix->pStream->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ /*
+ * Try fill up the mixer buffer as much as possible.
+ *
+ * Slight fun part is that we have to calculate conversion
+ * ratio and be rather pessimistic about it.
+ */
+ uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->pStream->Cfg.Props, sizeof(abBuf));
+ for (;;)
+ {
+ /*
+ * Figure out how much we can move in this iteration.
+ */
+ uint32_t cDstFrames = AudioMixBufFree(&pMix->MixBuf);
+ if (!cDstFrames)
+ break;
+
+ uint32_t cbReadable = audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream);
+ if (!cbReadable)
+ break;
+
+ uint32_t cbToRead;
+ if (PDMAudioPropsHz(&pMix->pStream->Cfg.Props) == PDMAudioPropsHz(&pMix->MixBuf.Props))
+ cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props, cDstFrames);
+ else
+ cbToRead = PDMAudioPropsFramesToBytes(&pMix->pStream->Cfg.Props,
+ (uint64_t)cDstFrames * PDMAudioPropsHz(&pMix->pStream->Cfg.Props)
+ / PDMAudioPropsHz(&pMix->MixBuf.Props));
+ cbToRead = RT_MIN(cbToRead, RT_MIN(cbReadable, cbBuf));
+ if (!cbToRead)
+ break;
+
+ /*
+ * Get the data.
+ */
+ uint32_t cbCaptured = 0;
+ int rc = audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, abBuf, cbToRead, &cbCaptured);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(cbCaptured == cbToRead);
+ AssertBreak(cbCaptured > 0);
+
+ /*
+ * Feed it to the mixer.
+ */
+ uint32_t cDstFramesWritten = 0;
+ if ((abBuf[0] >> 4) & 1) /* some cheap random */
+ AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured,
+ 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten);
+ else
+ {
+ AudioMixBufSilence(&pMix->MixBuf, &pMix->WriteState, 0 /*offFrame*/, cDstFrames);
+ AudioMixBufBlend(&pMix->MixBuf, &pMix->WriteState, abBuf, cbCaptured,
+ 0 /*offDstFrame*/, cDstFrames, &cDstFramesWritten);
+ }
+ AudioMixBufCommit(&pMix->MixBuf, cDstFramesWritten);
+ }
+ }
+ else
+ {
+ /*
+ * The goal here is to empty the mixer buffer by transfering all
+ * the data to the drivers.
+ */
+ uint32_t const cbBuf = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, sizeof(abBuf));
+ for (;;)
+ {
+ uint32_t cFrames = AudioMixBufUsed(&pMix->MixBuf);
+ if (!cFrames)
+ break;
+
+ uint32_t cbWritable = audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream);
+ if (!cbWritable)
+ break;
+
+ uint32_t cSrcFramesPeeked;
+ uint32_t cbDstPeeked;
+ AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cSrcFramesPeeked,
+ &pMix->PeekState, abBuf, RT_MIN(cbBuf, cbWritable), &cbDstPeeked);
+ AudioMixBufAdvance(&pMix->MixBuf, cSrcFramesPeeked);
+
+ if (!cbDstPeeked)
+ break;
+
+ uint32_t offBuf = 0;
+ while (offBuf < cbDstPeeked)
+ {
+ uint32_t cbPlayed = 0;
+ int rc = audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream,
+ &abBuf[offBuf], cbDstPeeked - offBuf, &cbPlayed);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!cbPlayed)
+ RTThreadSleep(1);
+ offBuf += cbPlayed;
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Same as audioTestDriverStackStreamEnable.
+ */
+int AudioTestMixStreamEnable(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ return audioTestDriverStackStreamEnable(pMix->pDrvStack, pMix->pStream);
+}
+
+
+/**
+ * Same as audioTestDriverStackStreamDrain.
+ */
+int AudioTestMixStreamDrain(PAUDIOTESTDRVMIXSTREAM pMix, bool fSync)
+{
+ /*
+ * If we're mixing, we must first make sure the buffer is empty.
+ */
+ if (pMix->fDoMixing)
+ {
+ audioTestMixStreamTransfer(pMix);
+ while (AudioMixBufUsed(&pMix->MixBuf) > 0)
+ {
+ RTThreadSleep(1);
+ audioTestMixStreamTransfer(pMix);
+ }
+ }
+
+ /*
+ * Then we do the regular work.
+ */
+ return audioTestDriverStackStreamDrain(pMix->pDrvStack, pMix->pStream, fSync);
+}
+
+/**
+ * Same as audioTestDriverStackStreamDisable.
+ */
+int AudioTestMixStreamDisable(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ return AudioTestDriverStackStreamDisable(pMix->pDrvStack, pMix->pStream);
+}
+
+
+/**
+ * Same as audioTestDriverStackStreamIsOkay.
+ */
+bool AudioTestMixStreamIsOkay(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ return audioTestDriverStackStreamIsOkay(pMix->pDrvStack, pMix->pStream);
+}
+
+
+/**
+ * Same as audioTestDriverStackStreamGetWritable
+ */
+uint32_t AudioTestMixStreamGetWritable(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ if (!pMix->fDoMixing)
+ return audioTestDriverStackStreamGetWritable(pMix->pDrvStack, pMix->pStream);
+ uint32_t cbRet = AudioMixBufFreeBytes(&pMix->MixBuf);
+ if (!cbRet)
+ {
+ audioTestMixStreamTransfer(pMix);
+ cbRet = AudioMixBufFreeBytes(&pMix->MixBuf);
+ }
+ return cbRet;
+}
+
+
+
+
+/**
+ * Same as audioTestDriverStackStreamPlay.
+ */
+int AudioTestMixStreamPlay(PAUDIOTESTDRVMIXSTREAM pMix, void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
+{
+ if (!pMix->fDoMixing)
+ return audioTestDriverStackStreamPlay(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbPlayed);
+
+ *pcbPlayed = 0;
+
+ int rc = audioTestMixStreamTransfer(pMix);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props);
+ while (cbBuf >= cbFrame)
+ {
+ uint32_t const cFrames = AudioMixBufFree(&pMix->MixBuf);
+ if (!cFrames)
+ break;
+ uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames);
+ cbToWrite = RT_MIN(cbToWrite, cbBuf);
+ cbToWrite = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToWrite);
+
+ uint32_t cFramesWritten = 0;
+ AudioMixBufWrite(&pMix->MixBuf, &pMix->WriteState, pvBuf, cbToWrite, 0 /*offDstFrame*/, cFrames, &cFramesWritten);
+ Assert(cFramesWritten == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbToWrite));
+ AudioMixBufCommit(&pMix->MixBuf, cFramesWritten);
+
+ *pcbPlayed += cbToWrite;
+ cbBuf -= cbToWrite;
+ pvBuf = (uint8_t const *)pvBuf + cbToWrite;
+
+ rc = audioTestMixStreamTransfer(pMix);
+ if (RT_FAILURE(rc))
+ return *pcbPlayed ? VINF_SUCCESS : rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Same as audioTestDriverStackStreamGetReadable
+ */
+uint32_t AudioTestMixStreamGetReadable(PAUDIOTESTDRVMIXSTREAM pMix)
+{
+ if (!pMix->fDoMixing)
+ return audioTestDriverStackStreamGetReadable(pMix->pDrvStack, pMix->pStream);
+
+ audioTestMixStreamTransfer(pMix);
+ uint32_t cbRet = AudioMixBufUsedBytes(&pMix->MixBuf);
+ return cbRet;
+}
+
+
+
+
+/**
+ * Same as audioTestDriverStackStreamCapture.
+ */
+int AudioTestMixStreamCapture(PAUDIOTESTDRVMIXSTREAM pMix, void *pvBuf, uint32_t cbBuf, uint32_t *pcbCaptured)
+{
+ if (!pMix->fDoMixing)
+ return audioTestDriverStackStreamCapture(pMix->pDrvStack, pMix->pStream, pvBuf, cbBuf, pcbCaptured);
+
+ *pcbCaptured = 0;
+
+ int rc = audioTestMixStreamTransfer(pMix);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pMix->MixBuf.Props);
+ while (cbBuf >= cbFrame)
+ {
+ uint32_t const cFrames = AudioMixBufUsed(&pMix->MixBuf);
+ if (!cFrames)
+ break;
+ uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pMix->MixBuf.Props, cFrames);
+ cbToRead = RT_MIN(cbToRead, cbBuf);
+ cbToRead = PDMAudioPropsFloorBytesToFrame(&pMix->MixBuf.Props, cbToRead);
+
+ uint32_t cFramesPeeked = 0;
+ uint32_t cbPeeked = 0;
+ AudioMixBufPeek(&pMix->MixBuf, 0 /*offSrcFrame*/, cFrames, &cFramesPeeked, &pMix->PeekState, pvBuf, cbToRead, &cbPeeked);
+ Assert(cFramesPeeked == PDMAudioPropsBytesToFrames(&pMix->MixBuf.Props, cbPeeked));
+ AudioMixBufAdvance(&pMix->MixBuf, cFramesPeeked);
+
+ *pcbCaptured += cbToRead;
+ cbBuf -= cbToRead;
+ pvBuf = (uint8_t *)pvBuf + cbToRead;
+
+ rc = audioTestMixStreamTransfer(pMix);
+ if (RT_FAILURE(rc))
+ return *pcbCaptured ? VINF_SUCCESS : rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets the volume of a mixing stream.
+ *
+ * @param pMix Mixing stream to set volume for.
+ * @param uVolumePercent Volume to set (in percent, 0-100).
+ */
+void AudioTestMixStreamSetVolume(PAUDIOTESTDRVMIXSTREAM pMix, uint8_t uVolumePercent)
+{
+ AssertReturnVoid(pMix->fDoMixing);
+
+ uint8_t const uVol = (PDMAUDIO_VOLUME_MAX / 100) * uVolumePercent;
+
+ PDMAUDIOVOLUME Vol;
+ RT_ZERO(Vol);
+ for (size_t i = 0; i < RT_ELEMENTS(Vol.auChannels); i++)
+ Vol.auChannels[i] = uVol;
+ AudioMixBufSetVolume(&pMix->MixBuf, &Vol);
+}
+