Index: trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp
===================================================================
--- trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp	(revision 89058)
+++ trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp	(revision 89059)
@@ -41,4 +41,5 @@
 #include <iprt/string.h>
 #include <iprt/test.h>
+#include <iprt/formats/riff.h>
 
 #include <package-generated.h>
@@ -46,4 +47,16 @@
 
 #include <VBox/version.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 <VBox/vmm/pdmaudioinline.h>
 #include <VBox/vmm/pdmaudiohostenuminline.h>
@@ -127,4 +140,63 @@
 
 
+/**
+ * Audio driver stack.
+ *
+ * This can be just be backend driver alone or DrvAudio with a backend.
+ * @todo add automatic resampling via mixer so we can test more of the audio
+ *       stack used by the device emulations.
+ */
+typedef struct AUDIOTESTDRVSTACK
+{
+    /** The device registration record for the backend. */
+    PCPDMDRVREG             pDrvReg;
+    /** The backend driver instance. */
+    PPDMDRVINS              pDrvBackendIns;
+    /** The backend's audio interface. */
+    PPDMIHOSTAUDIO          pIHostAudio;
+
+    /** The DrvAudio instance. */
+    PPDMDRVINS              pDrvAudioIns;
+    /** This is NULL if we don't use DrvAudio. */
+    PPDMIAUDIOCONNECTOR     pIAudioConnector;
+} AUDIOTESTDRVSTACK;
+/** Pointer to an audio driver stack. */
+typedef AUDIOTESTDRVSTACK *PAUDIOTESTDRVSTACK;
+
+/**
+ * Backend-only stream structure.
+ */
+typedef struct AUDIOTESTDRVSTACKSTREAM
+{
+    /** The public stream data. */
+    PDMAUDIOSTREAM          Core;
+    /** The acquired config. */
+    PDMAUDIOSTREAMCFG       Cfg;
+    /** The backend data (variable size). */
+    PDMAUDIOBACKENDSTREAM   Backend;
+} AUDIOTESTDRVSTACKSTREAM;
+/** Pointer to a backend-only stream structure. */
+typedef AUDIOTESTDRVSTACKSTREAM *PAUDIOTESTDRVSTACKSTREAM;
+
+/**
+ * An open wave file.
+ */
+typedef struct AUDIOTESTWAVEFILE
+{
+    /** The file handle. */
+    RTFILE              hFile;
+    /** The absolute file offset of the first sample */
+    uint32_t            offSamples;
+    /** Number of bytes of samples. */
+    uint32_t            cbSamples;
+    /** The current read position relative to @a offSamples.  */
+    uint32_t            offCur;
+    /** The PCM properties for the file format.  */
+    PDMAUDIOPCMPROPS    Props;
+} AUDIOTESTWAVEFILE;
+/** Pointer to an open wave file. */
+typedef AUDIOTESTWAVEFILE *PAUDIOTESTWAVEFILE;
+
+
 /*********************************************************************************************************************************
 *   Internal Functions                                                                                                           *
@@ -133,6 +205,7 @@
 static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
 static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
-static DECLCALLBACK(RTEXITCODE) audioTestMain(int argc, char **argv);
-static DECLCALLBACK(RTEXITCODE) audioVerifyMain(int argc, char **argv);
+
+static RTEXITCODE audioTestUsage(PRTSTREAM pStrm);
+static RTEXITCODE audioTestVersion(void);
 
 
@@ -203,26 +276,4 @@
 {
     { "--tag",              VKAT_VERIFY_OPT_TAG,          RTGETOPT_REQ_STRING  }
-};
-
-/**
- * Commands.
- */
-static struct
-{
-    const char     *pszCommand;
-    DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(int argc, char **argv));
-    PCRTGETOPTDEF   paOptions;
-    size_t          cOptions;
-    const char     *pszDesc;
-} const g_aCommands[] =
-{
-    {
-        "test",   audioTestMain,      g_aCmdTestOptions,      RT_ELEMENTS(g_aCmdTestOptions),
-        "Does some kind of testing, I guess..."
-    },
-    {
-        "verify", audioVerifyMain,    g_aCmdVerifyOptions,    RT_ELEMENTS(g_aCmdVerifyOptions),
-        "Verfies something, I guess..."
-    },
 };
 
@@ -274,169 +325,6 @@
 
 /*********************************************************************************************************************************
-*   Implementation                                                                                                               *
+*   Fake PDM driver handling.                                                                                                    *
 *********************************************************************************************************************************/
-
-/**
- * Initializes an audio test environment.
- *
- * @param   pTstEnv             Audio test environment to initialize.
- * @param   pDrvAudio           Audio driver to use.
- * @param   pszPathOut          Output path to use. If NULL, the system's temp directory will be used.
- * @param   pszPathTemp         Temporary path to use. If NULL, the system's temp directory will be used.
- * @param   pszTag              Tag name to use. If NULL, a generated UUID will be used.
- */
-static int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PPDMIHOSTAUDIO pDrvAudio, const char *pszTag)
-{
-    pTstEnv->pDrvAudio = pDrvAudio;
-    PDMAudioHostEnumInit(&pTstEnv->DevEnum);
-
-    int rc = VINF_SUCCESS;
-
-    char szPathTemp[RTPATH_MAX];
-    if (   !strlen(pTstEnv->szPathTemp)
-        || !strlen(pTstEnv->szPathOut))
-        rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
-
-    if (   RT_SUCCESS(rc)
-        && !strlen(pTstEnv->szPathTemp))
-        rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
-
-    if (   RT_SUCCESS(rc)
-        && !strlen(pTstEnv->szPathOut))
-        rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
-
-    if (RT_SUCCESS(rc))
-        rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
-
-    if (RT_SUCCESS(rc))
-    {
-        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
-        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
-        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
-    }
-
-    return rc;
-}
-
-/**
- * Destroys an audio test environment.
- *
- * @param   pTstEnv             Audio test environment to destroy.
- */
-static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
-{
-    if (!pTstEnv)
-        return;
-
-    PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
-
-    for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
-    {
-        int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
-        if (RT_FAILURE(rc2))
-            RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
-    }
-
-    AudioTestSetDestroy(&pTstEnv->Set);
-}
-
-/**
- * Initializes an audio test parameters set.
- *
- * @param   pTstParms           Test parameters set to initialize.
- */
-static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
-{
-    RT_ZERO(*pTstParms);
-}
-
-/**
- * Destroys an audio test parameters set.
- *
- * @param   pTstParms           Test parameters set to destroy.
- */
-static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
-{
-    if (!pTstParms)
-        return;
-
-    return;
-}
-
-/**
- * Shows the logo.
- *
- * @param   pStream             Output stream to show logo on.
- */
-static void audioTestShowLogo(PRTSTREAM pStream)
-{
-    RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) "
-                 VBOX_VERSION_STRING " - r%s\n"
-                 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
-                 "All rights reserved.\n\n", RTBldCfgRevisionStr());
-}
-
-/**
- * Shows tool usage text.
- */
-static void audioTestUsage(PRTSTREAM pStrm)
-{
-    RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
-                 RTPathFilename(RTProcExecutablePath()));
-    RTStrmPrintf(pStrm,
-                 "\n"
-                 "Global Options:\n"
-                 "  -q, --quiet\n"
-                 "    Sets verbosity to zero.\n"
-                 "  -v, --verbose\n"
-                 "    Increase verbosity.\n"
-                 "  -V, --version\n"
-                 "    Displays version.\n"
-                 "  -h, -?, --help\n"
-                 "    Displays help.\n"
-                 );
-
-    for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
-    {
-        RTStrmPrintf(pStrm,
-                     "\n"
-                     "Command '%s':\n"
-                     "    %s\n"
-                     "Options for '%s':\n",
-                     g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
-        PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
-        for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
-        {
-            if (RT_C_IS_PRINT(paOptions[i].iShort))
-                RTStrmPrintf(pStrm, "  -%c, %s\n", g_aCmdTestOptions[i].iShort, g_aCmdTestOptions[i].pszLong);
-            else
-                RTStrmPrintf(pStrm, "  %s\n", g_aCmdTestOptions[i].pszLong);
-
-            const char *pszHelp = NULL;
-            if (paOptions == g_aCmdTestOptions)
-            {
-                switch (g_aCmdTestOptions[i].iShort)
-                {
-                    case 'd':
-                        pszHelp = "Use the specified audio device";
-                        break;
-                    case 'e':
-                        pszHelp = "Exclude the given test id from the list";
-                        break;
-                    case 'a':
-                        pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)";
-                        break;
-                    case 'i':
-                        pszHelp = "Include the given test id in the list";
-                        break;
-                }
-            }
-            /** @todo Add help text for all options. */
-            if (pszHelp)
-                RTStrmPrintf(pStrm, "    %s\n", pszHelp);
-        }
-    }
-
-}
 
 /** @name Driver Helper Fakes / Stubs
@@ -485,18 +373,18 @@
 /** @} */
 
-
 /**
  * Constructs a PDM audio driver instance.
  *
  * @returns VBox status code.
- * @param   pDrvReg     PDM driver registration record to use for construction.
- * @param   ppDrvIns    Where to return the driver instance structure.
- * @param   ppDrvAudio  Where to return the audio driver interface of type IHOSTAUDIO.
- */
-static int audioTestDrvConstruct(PCPDMDRVREG pDrvReg, PPPDMDRVINS ppDrvIns, PPDMIHOSTAUDIO *ppDrvAudio)
+ * @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.
+ */
+static int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns,
+                                 PPPDMDRVINS ppDrvIns)
 {
     /* The destruct function must have valid data to work with. */
     *ppDrvIns   = NULL;
-    *ppDrvAudio = NULL;
 
     /*
@@ -524,12 +412,19 @@
     RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY);
 
-    pDrvIns->u32Version       = PDM_DRVINS_VERSION;
-    pDrvIns->iInstance        = 0;
-    pDrvIns->pHlpR3           = &s_DrvHlp;
-    pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0];
-    pDrvIns->pReg             = pDrvReg;
-    pDrvIns->pCfg             = (PCFGMNODE)pDrvReg;
-    //pDrvIns->pUpBase          = NULL;
-    //pDrvIns->pDownBase        = NULL;
+    pDrvIns->u32Version         = PDM_DRVINS_VERSION;
+    pDrvIns->iInstance          = 0;
+    pDrvIns->pHlpR3             = &s_DrvHlp;
+    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;
+    }
 
     /*
@@ -539,47 +434,762 @@
     if (RT_SUCCESS(rc))
     {
-        PPDMIHOSTAUDIO pDrvAudio = (PPDMIHOSTAUDIO)pDrvIns->IBase.pfnQueryInterface(&pDrvIns->IBase, PDMIHOSTAUDIO_IID);
-        if (pDrvAudio)
+        *ppDrvIns   = pDrvIns;
+        return VINF_SUCCESS;
+    }
+
+    RTTestFailed(g_hTest, "Failed to construct audio driver '%s': %Rrc", pDrvReg->szName, rc);
+    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.
+ */
+static 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;
+}
+
+/**
+ * Initializes a driver stack.
+ * @returns VBox status code.
+ * @param   pDrvStack       The driver stack to initialize.
+ * @param   pDrvReg         The backend driver to use.
+ * @param   fWithDrvAudio   Whether to inlcude DrvAudio in the stack or not.
+ */
+static int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio)
+{
+    int rc;
+    RT_ZERO(*pDrvStack);
+    pDrvStack->pDrvReg = pDrvReg;
+    if (!fWithDrvAudio)
+        rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns);
+    else
+    {
+        rc = VERR_NOT_IMPLEMENTED;
+    }
+
+    /*
+     * Get the IHostAudio interface and check that the host driver is working.
+     */
+    if (RT_SUCCESS(rc))
+    {
+        pDrvStack->pIHostAudio
+            = (PPDMIHOSTAUDIO)pDrvStack->pDrvBackendIns->IBase.pfnQueryInterface(&pDrvStack->pDrvBackendIns->IBase,
+                                                                                 PDMIHOSTAUDIO_IID);
+        if (pDrvStack->pIHostAudio)
         {
-            PDMAUDIOBACKENDSTS enmStatus = pDrvAudio->pfnGetStatus(pDrvAudio, PDMAUDIODIR_OUT);
+            PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT);
             if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING)
-            {
-                *ppDrvAudio = pDrvAudio;
-                *ppDrvIns   = pDrvIns;
                 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;
+}
+
+/**
+ * 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.
+ */
+static int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
+                                                  uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
+                                                  PPDMAUDIOSTREAM *ppStream)
+{
+    *ppStream = NULL;
+
+    int rc;
+    if (pDrvStack->pIAudioConnector)
+    {
+        rc = VERR_NOT_IMPLEMENTED;
     }
     else
-        RTTestFailed(g_hTest, "Failed to construct audio driver '%s': %Rrc", pDrvReg->szName, rc);
-    if (pDrvReg->pfnDestruct)
-        pDrvReg->pfnDestruct(pDrvIns);
-    RTMemFree(pDrvIns);
+    {
+        /*
+         * Calculate the stream config.
+         */
+        PDMAUDIOSTREAMCFG CfgReq;
+        rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
+        AssertRC(rc);
+        CfgReq.enmDir                       = PDMAUDIODIR_OUT;
+        CfgReq.u.enmDst                     = PDMAUDIOPLAYBACKDST_UNKNOWN;
+        CfgReq.enmLayout                    = PDMAUDIOSTREAMLAYOUT_INTERLEAVED;
+        CfgReq.Device.cMsSchedulingHint     = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
+                                            ? 10 : cMsSchedulingHint;
+        CfgReq.Backend.cFramesBufferSize    = PDMAudioPropsMilliToFrames(pProps,
+                                                                         cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
+                                                                         ? 300 : cMsBufferSize);
+        if (cMsPreBuffer == UINT32_MAX)
+            CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize * 2 / 3;
+        else
+            CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
+        if (CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16)
+        {
+            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);
+
+        /*
+         * 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.enmDir     = PDMAUDIODIR_OUT;
+                    pStreamAt->Core.cbBackend  = cbStream;
+                    pStreamAt->Core.Props      = CfgReq.Props;
+                    RTStrPrintf(pStreamAt->Core.szName, sizeof(pStreamAt->Core.szName), "out-%u", idxStream);
+
+                    pStreamAt->Backend.uMagic  = PDMAUDIOBACKENDSTREAM_MAGIC;
+                    pStreamAt->Backend.pStream = &pStreamAt->Core;
+
+                    /*
+                     * Call the backend to create the stream.
+                     */
+                    pStreamAt->Cfg = CfgReq;
+
+                    rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend,
+                                                                 &CfgReq, &pStreamAt->Cfg);
+                    if (RT_SUCCESS(rc))
+                    {
+                        pStreamAt->Core.Props = pStreamAt->Cfg.Props;
+                        if (g_uVerbosity > 1)
+                        {
+                            char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16];
+                            RTMsgInfo("Created backend stream: %s\n",
+                                      PDMAudioStrmCfgToString(&pStreamAt->Cfg, szTmp, sizeof(szTmp)));
+                        }
+
+                        /* Return if stream is ready: */
+                        if (rc == VINF_SUCCESS)
+                        {
+                            *ppStream = &pStreamAt->Core;
+                            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;
+                                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);
+                    }
+                    else
+                        RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc\n", rc);
+                }
+                else
+                {
+                    RTTestFailed(g_hTest, "Out of memory!\n");
+                    rc = VERR_NO_MEMORY;
+                }
+            }
+            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;
 }
 
 /**
- * Destructs a PDM audio driver instance.
+ * Destroys a stream.
+ */
+static void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
+{
+    if (pStream)
+    {
+        if (pDrvStack->pIAudioConnector)
+        {
+            int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream);
+            if (RT_FAILURE(rc))
+                RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc);
+        }
+        else
+        {
+            PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
+            int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend);
+            if (RT_SUCCESS(rc))
+            {
+                pStreamAt->Core.uMagic    = ~PDMAUDIOSTREAM_MAGIC;
+                pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC;
+                RTMemFree(pStreamAt);
+            }
+            else
+                RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc);
+        }
+    }
+}
+
+/**
+ * Enables a stream.
+ */
+static 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->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_ENABLE);
+        if (RT_FAILURE(rc))
+            RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
+    }
+    return rc;
+}
+
+/**
+ * Drains an output stream.
+ */
+static 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 draing: %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->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_DRAIN);
+        if (RT_SUCCESS(rc) && fSync)
+        {
+            /*
+             * 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;
+                }
+            }
+            if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY)
+            {
+                RTTestFailed(g_hTest, "Stream state not OKAY after draing: %s", PDMHostAudioStreamStateGetName(enmHostState));
+                rc = VERR_AUDIO_STREAM_NOT_READY;
+            }
+        }
+        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.
+ */
+static 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.
+ */
+static 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.
+ */
+static 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;
+}
+
+
+/*********************************************************************************************************************************
+*   WAVE File Reader.                                                                                                            *
+*********************************************************************************************************************************/
+/**
+ * Opens a wave-file for reading.
  *
  * @returns VBox status code.
- * @param   pDrvReg             PDM driver registration record to destruct.
- * @param   pDrvIns             Driver instance to destruct.
- */
-static int audioTestDrvDestruct(PCPDMDRVREG pDrvReg, PPDMDRVINS pDrvIns)
-{
-    if (!pDrvIns)
-        return VINF_SUCCESS;
-
-    if (pDrvReg->pfnDestruct)
-        pDrvReg->pfnDestruct(pDrvIns);
-
-    pDrvIns->u32Version = 0;
-    RTMemFree(pDrvIns);
-
-    return VINF_SUCCESS;
-}
+ * @param   pszFile     The file to open.
+ * @param   pWaveFile   The open wave file structure to fill in on success.
+ */
+static int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
+{
+    RT_ZERO(pWaveFile->Props);
+    pWaveFile->hFile = NIL_RTFILE;
+    int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+    if (RT_FAILURE(rc))
+        return rc;
+    uint64_t cbFile = 0;
+    rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
+    if (RT_SUCCESS(rc))
+    {
+        union
+        {
+            uint8_t                 ab[512];
+            struct
+            {
+                RTRIFFHDR           Hdr;
+                RTRIFFWAVEFMTCHUNK  Fmt;
+            } Wave;
+            RTRIFFLIST              List;
+            RTRIFFWAVEDATACHUNK     Data;
+        } uBuf;
+
+        rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
+        if (RT_SUCCESS(rc))
+        {
+            rc = VERR_VFS_UNKNOWN_FORMAT;
+            if (   uBuf.Wave.Hdr.uMagic    == RTRIFFHDR_MAGIC
+                && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
+                && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
+                && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
+            {
+                if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
+                    RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
+                                 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
+                rc = VERR_VFS_BOGUS_FORMAT;
+                if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
+                    RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
+                else if (   uBuf.Wave.Fmt.Data.cBitsPerSample != 8
+                         && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
+                         && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
+                    RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
+                else if (   uBuf.Wave.Fmt.Data.cChannels < 1
+                         || uBuf.Wave.Fmt.Data.cChannels >= 16)
+                    RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
+                else if (   uBuf.Wave.Fmt.Data.uHz < 4096
+                         || uBuf.Wave.Fmt.Data.uHz > 768000)
+                    RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
+                else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
+                    RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
+                               uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
+                else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
+                    RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
+                               uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
+                else
+                {
+                    /*
+                     * Copy out the data we need from the file format structure.
+                     */
+                    PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
+                                      uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
+                    pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
+
+                    /*
+                     * Find the 'data' chunk with the audio samples.
+                     *
+                     * There can be INFO lists both preceeding this and succeeding
+                     * it, containing IART and other things we can ignored.  Thus
+                     * we read a list header here rather than just a chunk header,
+                     * since it doesn't matter if we read 4 bytes extra as
+                     * AudioTestWaveFileRead uses RTFileReadAt anyway.
+                     */
+                    rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
+                    if (RT_SUCCESS(rc))
+                    {
+                        /* HACK ALERT: Skip one INFO list and hope we find a data chunk following it: */
+                        if (   uBuf.List.uMagic    == RTRIFFLIST_MAGIC
+                            && uBuf.List.uListType ==  RTRIFFLIST_TYPE_INFO
+                            && uBuf.List.cbChunk   <= (uint32_t)cbFile - pWaveFile->offSamples - sizeof(RTRIFFCHUNK))
+                        {
+                            pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.List.cbChunk;
+                            rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
+                        }
+
+                        pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
+                        pWaveFile->cbSamples   = (uint32_t)cbFile - pWaveFile->offSamples;
+
+                        rc = VERR_VFS_BOGUS_FORMAT;
+                        if (   uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
+                            && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
+                            && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
+                        {
+                            pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
+                            /*
+                             * We're good!
+                             */
+                            pWaveFile->offCur = 0;
+                            return VINF_SUCCESS;
+                        }
+
+                        RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
+                                   pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
+                                   uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
+                    }
+                    else
+                        RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
+                }
+            }
+            else
+                RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
+                           pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
+                           uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
+                           uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
+        }
+        else
+            RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
+    }
+    else
+        RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
+
+    RTFileClose(pWaveFile->hFile);
+    pWaveFile->hFile = NIL_RTFILE;
+    return rc;
+}
+
+/**
+ * Closes a wave file.
+ */
+static void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
+{
+    RTFileClose(pWaveFile->hFile);
+    pWaveFile->hFile = NIL_RTFILE;
+}
+
+/**
+ * Reads samples from a wave file.
+ *
+ * @returns VBox status code.  See RTVfsFileRead for EOF status handling.
+ * @param   pWaveFile   The file to read from.
+ * @param   pvBuf       Where to put the samples.
+ * @param   cbBuf       How much to read at most.
+ * @param   pcbRead     Where to return the actual number of bytes read,
+ *                      optional.
+ */
+static int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+    int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
+    if (RT_SUCCESS(rc))
+    {
+        if (pcbRead)
+        {
+            pWaveFile->offCur += *pcbRead;
+            if (cbBuf > *pcbRead)
+                rc = VINF_EOF;
+            else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
+                rc = VINF_EOF;
+        }
+        else
+            pWaveFile->offCur += cbBuf;
+    }
+    return rc;
+}
+
+
+/*********************************************************************************************************************************
+*   Implementation of Something                                                                                                  *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes an audio test environment.
+ *
+ * @param   pTstEnv             Audio test environment to initialize.
+ * @param   pDrvAudio           Audio driver to use.
+ * @param   pszPathOut          Output path to use. If NULL, the system's temp directory will be used.
+ * @param   pszPathTemp         Temporary path to use. If NULL, the system's temp directory will be used.
+ * @param   pszTag              Tag name to use. If NULL, a generated UUID will be used.
+ */
+static int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PPDMIHOSTAUDIO pDrvAudio, const char *pszTag)
+{
+    pTstEnv->pDrvAudio = pDrvAudio;
+    PDMAudioHostEnumInit(&pTstEnv->DevEnum);
+
+    int rc = VINF_SUCCESS;
+
+    char szPathTemp[RTPATH_MAX];
+    if (   !strlen(pTstEnv->szPathTemp)
+        || !strlen(pTstEnv->szPathOut))
+        rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
+
+    if (   RT_SUCCESS(rc)
+        && !strlen(pTstEnv->szPathTemp))
+        rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
+
+    if (   RT_SUCCESS(rc)
+        && !strlen(pTstEnv->szPathOut))
+        rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
+
+    if (RT_SUCCESS(rc))
+        rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
+
+    if (RT_SUCCESS(rc))
+    {
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
+    }
+
+    return rc;
+}
+
+/**
+ * Destroys an audio test environment.
+ *
+ * @param   pTstEnv             Audio test environment to destroy.
+ */
+static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
+{
+    if (!pTstEnv)
+        return;
+
+    PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
+
+    for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
+    {
+        int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
+        if (RT_FAILURE(rc2))
+            RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
+    }
+
+    AudioTestSetDestroy(&pTstEnv->Set);
+}
+
+/**
+ * Initializes an audio test parameters set.
+ *
+ * @param   pTstParms           Test parameters set to initialize.
+ */
+static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
+{
+    RT_ZERO(*pTstParms);
+}
+
+/**
+ * Destroys an audio test parameters set.
+ *
+ * @param   pTstParms           Test parameters set to destroy.
+ */
+static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
+{
+    if (!pTstParms)
+        return;
+
+    return;
+}
+
+
+/*********************************************************************************************************************************
+*   Some other stuff, you name it.                                                                                               *
+*********************************************************************************************************************************/
 
 /**
@@ -1009,4 +1619,17 @@
 }
 
+/** Option help for the 'test' command.   */
+static DECLCALLBACK(const char *) audioTestCmdTestHelp(PCRTGETOPTDEF pOpt)
+{
+    switch (pOpt->iShort)
+    {
+        case 'd':   return "Use the specified audio device";
+        case 'e':   return "Exclude the given test id from the list";
+        case 'a':   return "Exclude all tests from the list (useful to enable single tests later with --include)";
+        case 'i':   return "Include the given test id in the list";
+    }
+    return NULL;
+}
+
 /**
  * Main (entry) function for the testing functionality of VKAT.
@@ -1037,8 +1660,4 @@
         switch (rc)
         {
-            case 'h':
-                audioTestUsage(g_pStdOut);
-                return RTEXITCODE_SUCCESS;
-
             case 'a':
                 for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
@@ -1119,4 +1738,9 @@
                 break;
 
+            case 'V':
+                return audioTestVersion();
+            case 'h':
+                return audioTestUsage(g_pStdOut);
+
             default:
                 return RTGetOptPrintError(rc, &ValueUnion);
@@ -1129,11 +1753,12 @@
     RTTestBanner(g_hTest);
 
-    PPDMIHOSTAUDIO pDrvAudio = NULL;
-    PPDMDRVINS     pDrvIns   = NULL;
-    rc = audioTestDrvConstruct(pDrvReg, &pDrvIns, &pDrvAudio);
+    AUDIOTESTDRVSTACK DrvStack;
+    rc = audioTestDriverStackInit(&DrvStack, pDrvReg, false /*fWithDrvAudio*/);
     if (RT_SUCCESS(rc))
     {
         /* For now all tests have the same test environment. */
-        rc = audioTestEnvInit(&TstEnv, pDrvAudio, pszTag);
+        /** @todo bake the DrvStack into the test env make make it more flexible so
+         *        we can also test with/without DrvAudio (need option above). */
+        rc = audioTestEnvInit(&TstEnv, DrvStack.pIHostAudio, pszTag);
         if (RT_SUCCESS(rc))
         {
@@ -1155,6 +1780,6 @@
             audioTestEnvDestroy(&TstEnv);
         }
-    }
-    audioTestDrvDestruct(pDrvReg, pDrvIns);
+        audioTestDriverStackDelete(&DrvStack);
+    }
 
     audioTestParmsDestroy(&TstCust);
@@ -1168,4 +1793,9 @@
     return RTTestSummaryAndDestroy(g_hTest);
 }
+
+
+/*********************************************************************************************************************************
+*   Command: verify                                                                                                              *
+*********************************************************************************************************************************/
 
 /**
@@ -1252,4 +1882,9 @@
                 break;
 
+            case 'V':
+                return audioTestVersion();
+            case 'h':
+                return audioTestUsage(g_pStdOut);
+
             default:
                 return RTGetOptPrintError(rc, &ValueUnion);
@@ -1273,4 +1908,328 @@
      */
     return RTTestSummaryAndDestroy(g_hTest);
+}
+
+
+/*********************************************************************************************************************************
+*   Command: play                                                                                                                *
+*********************************************************************************************************************************/
+/**
+ * Command line parameters for test mode.
+ */
+static const RTGETOPTDEF g_aCmdPlayOptions[] =
+{
+    { "--backend",          'b',                          RTGETOPT_REQ_STRING  },
+};
+
+/** the 'play' command option help. */
+static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
+{
+    switch (pOpt->iShort)
+    {
+        case 'b': return "The audio backend to use.";
+        default:  return NULL;
+    }
+}
+
+/**
+ * Worker for audioTestCmdPlayHandler that plays one file.
+ */
+static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, uint32_t cMsBufferSize,
+                                   uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint)
+{
+    /*
+     * First we must open the file and determin the format.
+     */
+    AUDIOTESTWAVEFILE WaveFile;
+    int rc = AudioTestWaveFileOpen(pszFile, &WaveFile);
+    if (RT_FAILURE(rc))
+        return RTMsgErrorExitFailure("Failed to open '%s': %Rrc", pszFile, rc);
+
+    if (g_uVerbosity > 0)
+    {
+        char szTmp[128];
+        RTMsgInfo("Opened '%s' for playing\n", pszFile);
+        RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
+        RTMsgInfo("Size:   %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
+                  WaveFile.cbSamples, WaveFile.cbSamples,
+                  PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
+                  PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
+    }
+
+    /*
+     * Construct the driver stack.
+     */
+    RTEXITCODE          rcExit = RTEXITCODE_FAILURE;
+    AUDIOTESTDRVSTACK   DrvStack;
+    rc = audioTestDriverStackInit(&DrvStack, pDrvReg, false /*fWithDrvAudio*/);
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Open a stream for the output.
+         */
+        PPDMAUDIOSTREAM pStream = NULL;
+        rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &WaveFile.Props, cMsBufferSize,
+                                                    cMsPreBuffer, cMsSchedulingHint, &pStream);
+        if (RT_SUCCESS(rc))
+        {
+            rc = audioTestDriverStackStreamEnable(&DrvStack, pStream);
+            if (RT_SUCCESS(rc))
+            {
+                uint64_t const nsStarted = RTTimeNanoTS();
+
+                /*
+                 * Transfer data as quickly as we're allowed.
+                 */
+                for (;;)
+                {
+                    /* Read a chunk from the wave file. */
+                    uint8_t  abSamples[16384];
+                    size_t   cbSamples = 0;
+                    rc = AudioTestWaveFileRead(&WaveFile, abSamples, sizeof(abSamples), &cbSamples);
+                    if (RT_SUCCESS(rc) && cbSamples > 0)
+                    {
+                        /* Transfer the data to the audio stream. */
+                        for (uint32_t offSamples = 0; offSamples < cbSamples;)
+                        {
+                            uint32_t const cbCanWrite = audioTestDriverStackStreamGetWritable(&DrvStack, pStream);
+                            if (cbCanWrite > 0)
+                            {
+                                uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
+                                uint32_t       cbPlayed = 0;
+                                rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, &abSamples[offSamples],
+                                                                    cbToPlay, &cbPlayed);
+                                if (RT_SUCCESS(rc))
+                                {
+                                    if (cbPlayed)
+                                        offSamples += cbPlayed;
+                                    else
+                                    {
+                                        rcExit = RTMsgErrorExitFailure("Played zero out of %#x bytes - %#x bytes reported playable!\n",
+                                                                       cbToPlay, cbCanWrite);
+                                        break;
+                                    }
+                                }
+                                else
+                                {
+                                    rcExit = RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
+                                    break;
+                                }
+                            }
+                            else if (audioTestDriverStackStreamIsOkay(&DrvStack, pStream))
+                                RTThreadSleep(RT_MIN(RT_MAX(1, cMsSchedulingHint), 256));
+                            else
+                            {
+                                rcExit = RTMsgErrorExitFailure("Stream is not okay!\n");
+                                break;
+                            }
+                        }
+                    }
+                    else if (RT_SUCCESS(rc) && cbSamples == 0)
+                    {
+                        rcExit = RTEXITCODE_SUCCESS;
+                        break;
+                    }
+                    else
+                    {
+                        rcExit = RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
+                        break;
+                    }
+                }
+
+                /*
+                 * Drain the stream.
+                 */
+                if (rcExit == RTEXITCODE_SUCCESS)
+                {
+                    if (g_uVerbosity > 0)
+                        RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
+                    rc = audioTestDriverStackStreamDrain(&DrvStack, pStream, true /*fSync*/);
+                    if (RT_SUCCESS(rc))
+                    {
+                        if (g_uVerbosity > 0)
+                            RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
+                    }
+                    else
+                        rcExit = RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
+                }
+            }
+            else
+                rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
+            audioTestDriverStackStreamDestroy(&DrvStack, pStream);
+        }
+        else
+            rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
+        audioTestDriverStackDelete(&DrvStack);
+    }
+    else
+        rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
+    AudioTestWaveFileClose(&WaveFile);
+    return rcExit;
+}
+
+/**
+ * The 'play' command handler.
+ *
+ * @returns Program exit code.
+ * @param   argc                Number of argv arguments.
+ * @param   argv                argv arguments.
+ */
+static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(int argc, char **argv)
+{
+    /*
+     * Parse arguments.
+     */
+    /* Option values: */
+    PCPDMDRVREG pDrvReg           = g_aBackends[0].pDrvReg;
+    uint32_t    cMsBufferSize     = UINT32_MAX;
+    uint32_t    cMsPreBuffer      = UINT32_MAX;
+    uint32_t    cMsSchedulingHint = UINT32_MAX;
+
+    RTGETOPTSTATE GetState;
+    int rc = RTGetOptInit(&GetState, argc, argv, g_aCmdPlayOptions, RT_ELEMENTS(g_aCmdPlayOptions),
+                          1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+    AssertRCReturn(rc, RTEXITCODE_INIT);
+
+    RTGETOPTUNION ValueUnion;
+    while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+    {
+        switch (rc)
+        {
+            case 'b':
+                pDrvReg = NULL;
+                for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
+                    if (   strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
+                        || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
+                    {
+                        pDrvReg = g_aBackends[i].pDrvReg;
+                        break;
+                    }
+                if (pDrvReg == NULL)
+                    return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
+                break;
+
+            case VINF_GETOPT_NOT_OPTION:
+            {
+                RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, cMsBufferSize, cMsPreBuffer, cMsSchedulingHint);
+                if (rcExit != RTEXITCODE_SUCCESS)
+                    return rcExit;
+                break;
+            }
+
+            case 'V':
+                return audioTestVersion();
+            case 'h':
+                return audioTestUsage(g_pStdOut);
+
+            default:
+                return RTGetOptPrintError(rc, &ValueUnion);
+        }
+    }
+    return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Commands.
+ */
+static struct
+{
+    /** The command name. */
+    const char     *pszCommand;
+    /** The command handler.   */
+    DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(int argc, char **argv));
+
+    /** Command description.   */
+    const char     *pszDesc;
+    /** Options array.  */
+    PCRTGETOPTDEF   paOptions;
+    /** Number of options in the option array. */
+    size_t          cOptions;
+    /** Gets help for an option. */
+    DECLCALLBACKMEMBER(const char *, pfnOptionHelp,(PCRTGETOPTDEF pOpt));
+} const g_aCommands[] =
+{
+    {
+        "test",     audioTestMain,
+        "Does some kind of testing, I guess...",
+        g_aCmdTestOptions,      RT_ELEMENTS(g_aCmdTestOptions),     audioTestCmdTestHelp
+    },
+    {
+        "verify",   audioVerifyMain,
+        "Verfies something, I guess...",
+        g_aCmdVerifyOptions,    RT_ELEMENTS(g_aCmdVerifyOptions),   NULL,
+    },
+    {
+        "play",     audioTestCmdPlayHandler,
+        "Plays one or more wave files.",
+        g_aCmdPlayOptions,      RT_ELEMENTS(g_aCmdPlayOptions),     audioTestCmdPlayHelp,
+    },
+};
+
+/**
+ * Shows tool usage text.
+ */
+static RTEXITCODE audioTestUsage(PRTSTREAM pStrm)
+{
+    RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
+                 RTPathFilename(RTProcExecutablePath()));
+    RTStrmPrintf(pStrm,
+                 "\n"
+                 "Global Options:\n"
+                 "  -q, --quiet\n"
+                 "    Sets verbosity to zero.\n"
+                 "  -v, --verbose\n"
+                 "    Increase verbosity.\n"
+                 "  -V, --version\n"
+                 "    Displays version.\n"
+                 "  -h, -?, --help\n"
+                 "    Displays help.\n"
+                 );
+
+    for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
+    {
+        RTStrmPrintf(pStrm,
+                     "\n"
+                     "Command '%s':\n"
+                     "    %s\n"
+                     "Options for '%s':\n",
+                     g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
+        PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
+        for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
+        {
+            if (RT_C_IS_PRINT(paOptions[i].iShort))
+                RTStrmPrintf(pStrm, "  -%c, %s\n", g_aCmdTestOptions[i].iShort, g_aCmdTestOptions[i].pszLong);
+            else
+                RTStrmPrintf(pStrm, "  %s\n", g_aCmdTestOptions[i].pszLong);
+
+            const char *pszHelp = NULL;
+            if (g_aCommands[i].pfnOptionHelp)
+                pszHelp = g_aCommands[i].pfnOptionHelp(&paOptions[i]);
+            if (pszHelp)
+                RTStrmPrintf(pStrm, "    %s\n", pszHelp);
+        }
+    }
+    return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Shows tool version.
+ */
+static RTEXITCODE audioTestVersion(void)
+{
+    RTPrintf("v0.0.1\n");
+    return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Shows the logo.
+ *
+ * @param   pStream             Output stream to show logo on.
+ */
+static void audioTestShowLogo(PRTSTREAM pStream)
+{
+    RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) Version " VBOX_VERSION_STRING " - r%s\n"
+                 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
+                 "All rights reserved.\n\n", RTBldCfgRevisionStr());
 }
 
@@ -1306,11 +2265,9 @@
 
             case 'V':
-                RTPrintf("v0.0.1\n");
-                return RTEXITCODE_SUCCESS;
+                return audioTestVersion();
 
             case 'h':
                 audioTestShowLogo(g_pStdOut);
-                audioTestUsage(g_pStdOut);
-                return RTEXITCODE_SUCCESS;
+                return audioTestUsage(g_pStdOut);
 
             case VINF_GETOPT_NOT_OPTION:
