VirtualBox

Changeset 89059 in vbox for trunk


Ignore:
Timestamp:
May 16, 2021 4:01:19 AM (4 years ago)
Author:
vboxsync
Message:

ValKit/Audio: Added a 'play' command for playing wave files so that it's easier to tell whether something works or if our sound generator is just generating weird sounds. Introduced a audio driver stack wrapper for allowing raw backend interfacing and interfacing via DrvAudio (todo) for testing more of the driver stack. bugref:10008

Location:
trunk
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp

    r89058 r89059  
    4141#include <iprt/string.h>
    4242#include <iprt/test.h>
     43#include <iprt/formats/riff.h>
    4344
    4445#include <package-generated.h>
     
    4647
    4748#include <VBox/version.h>
     49
     50/**
     51 * Internal driver instance data
     52 * @note This must be put here as it's needed before pdmdrv.h is included.
     53 */
     54typedef struct PDMDRVINSINT
     55{
     56    /** The stack the drive belongs to. */
     57    struct AUDIOTESTDRVSTACK *pStack;
     58} PDMDRVINSINT;
     59#define PDMDRVINSINT_DECLARED
     60
    4861#include <VBox/vmm/pdmaudioinline.h>
    4962#include <VBox/vmm/pdmaudiohostenuminline.h>
     
    127140
    128141
     142/**
     143 * Audio driver stack.
     144 *
     145 * This can be just be backend driver alone or DrvAudio with a backend.
     146 * @todo add automatic resampling via mixer so we can test more of the audio
     147 *       stack used by the device emulations.
     148 */
     149typedef struct AUDIOTESTDRVSTACK
     150{
     151    /** The device registration record for the backend. */
     152    PCPDMDRVREG             pDrvReg;
     153    /** The backend driver instance. */
     154    PPDMDRVINS              pDrvBackendIns;
     155    /** The backend's audio interface. */
     156    PPDMIHOSTAUDIO          pIHostAudio;
     157
     158    /** The DrvAudio instance. */
     159    PPDMDRVINS              pDrvAudioIns;
     160    /** This is NULL if we don't use DrvAudio. */
     161    PPDMIAUDIOCONNECTOR     pIAudioConnector;
     162} AUDIOTESTDRVSTACK;
     163/** Pointer to an audio driver stack. */
     164typedef AUDIOTESTDRVSTACK *PAUDIOTESTDRVSTACK;
     165
     166/**
     167 * Backend-only stream structure.
     168 */
     169typedef struct AUDIOTESTDRVSTACKSTREAM
     170{
     171    /** The public stream data. */
     172    PDMAUDIOSTREAM          Core;
     173    /** The acquired config. */
     174    PDMAUDIOSTREAMCFG       Cfg;
     175    /** The backend data (variable size). */
     176    PDMAUDIOBACKENDSTREAM   Backend;
     177} AUDIOTESTDRVSTACKSTREAM;
     178/** Pointer to a backend-only stream structure. */
     179typedef AUDIOTESTDRVSTACKSTREAM *PAUDIOTESTDRVSTACKSTREAM;
     180
     181/**
     182 * An open wave file.
     183 */
     184typedef struct AUDIOTESTWAVEFILE
     185{
     186    /** The file handle. */
     187    RTFILE              hFile;
     188    /** The absolute file offset of the first sample */
     189    uint32_t            offSamples;
     190    /** Number of bytes of samples. */
     191    uint32_t            cbSamples;
     192    /** The current read position relative to @a offSamples.  */
     193    uint32_t            offCur;
     194    /** The PCM properties for the file format.  */
     195    PDMAUDIOPCMPROPS    Props;
     196} AUDIOTESTWAVEFILE;
     197/** Pointer to an open wave file. */
     198typedef AUDIOTESTWAVEFILE *PAUDIOTESTWAVEFILE;
     199
     200
    129201/*********************************************************************************************************************************
    130202*   Internal Functions                                                                                                           *
     
    133205static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
    134206static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
    135 static DECLCALLBACK(RTEXITCODE) audioTestMain(int argc, char **argv);
    136 static DECLCALLBACK(RTEXITCODE) audioVerifyMain(int argc, char **argv);
     207
     208static RTEXITCODE audioTestUsage(PRTSTREAM pStrm);
     209static RTEXITCODE audioTestVersion(void);
    137210
    138211
     
    203276{
    204277    { "--tag",              VKAT_VERIFY_OPT_TAG,          RTGETOPT_REQ_STRING  }
    205 };
    206 
    207 /**
    208  * Commands.
    209  */
    210 static struct
    211 {
    212     const char     *pszCommand;
    213     DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(int argc, char **argv));
    214     PCRTGETOPTDEF   paOptions;
    215     size_t          cOptions;
    216     const char     *pszDesc;
    217 } const g_aCommands[] =
    218 {
    219     {
    220         "test",   audioTestMain,      g_aCmdTestOptions,      RT_ELEMENTS(g_aCmdTestOptions),
    221         "Does some kind of testing, I guess..."
    222     },
    223     {
    224         "verify", audioVerifyMain,    g_aCmdVerifyOptions,    RT_ELEMENTS(g_aCmdVerifyOptions),
    225         "Verfies something, I guess..."
    226     },
    227278};
    228279
     
    274325
    275326/*********************************************************************************************************************************
    276 *   Implementation                                                                                                               *
     327*   Fake PDM driver handling.                                                                                                    *
    277328*********************************************************************************************************************************/
    278 
    279 /**
    280  * Initializes an audio test environment.
    281  *
    282  * @param   pTstEnv             Audio test environment to initialize.
    283  * @param   pDrvAudio           Audio driver to use.
    284  * @param   pszPathOut          Output path to use. If NULL, the system's temp directory will be used.
    285  * @param   pszPathTemp         Temporary path to use. If NULL, the system's temp directory will be used.
    286  * @param   pszTag              Tag name to use. If NULL, a generated UUID will be used.
    287  */
    288 static int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PPDMIHOSTAUDIO pDrvAudio, const char *pszTag)
    289 {
    290     pTstEnv->pDrvAudio = pDrvAudio;
    291     PDMAudioHostEnumInit(&pTstEnv->DevEnum);
    292 
    293     int rc = VINF_SUCCESS;
    294 
    295     char szPathTemp[RTPATH_MAX];
    296     if (   !strlen(pTstEnv->szPathTemp)
    297         || !strlen(pTstEnv->szPathOut))
    298         rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
    299 
    300     if (   RT_SUCCESS(rc)
    301         && !strlen(pTstEnv->szPathTemp))
    302         rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
    303 
    304     if (   RT_SUCCESS(rc)
    305         && !strlen(pTstEnv->szPathOut))
    306         rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
    307 
    308     if (RT_SUCCESS(rc))
    309         rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
    310 
    311     if (RT_SUCCESS(rc))
    312     {
    313         RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
    314         RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
    315         RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
    316     }
    317 
    318     return rc;
    319 }
    320 
    321 /**
    322  * Destroys an audio test environment.
    323  *
    324  * @param   pTstEnv             Audio test environment to destroy.
    325  */
    326 static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
    327 {
    328     if (!pTstEnv)
    329         return;
    330 
    331     PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
    332 
    333     for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
    334     {
    335         int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
    336         if (RT_FAILURE(rc2))
    337             RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
    338     }
    339 
    340     AudioTestSetDestroy(&pTstEnv->Set);
    341 }
    342 
    343 /**
    344  * Initializes an audio test parameters set.
    345  *
    346  * @param   pTstParms           Test parameters set to initialize.
    347  */
    348 static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
    349 {
    350     RT_ZERO(*pTstParms);
    351 }
    352 
    353 /**
    354  * Destroys an audio test parameters set.
    355  *
    356  * @param   pTstParms           Test parameters set to destroy.
    357  */
    358 static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
    359 {
    360     if (!pTstParms)
    361         return;
    362 
    363     return;
    364 }
    365 
    366 /**
    367  * Shows the logo.
    368  *
    369  * @param   pStream             Output stream to show logo on.
    370  */
    371 static void audioTestShowLogo(PRTSTREAM pStream)
    372 {
    373     RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) "
    374                  VBOX_VERSION_STRING " - r%s\n"
    375                  "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
    376                  "All rights reserved.\n\n", RTBldCfgRevisionStr());
    377 }
    378 
    379 /**
    380  * Shows tool usage text.
    381  */
    382 static void audioTestUsage(PRTSTREAM pStrm)
    383 {
    384     RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
    385                  RTPathFilename(RTProcExecutablePath()));
    386     RTStrmPrintf(pStrm,
    387                  "\n"
    388                  "Global Options:\n"
    389                  "  -q, --quiet\n"
    390                  "    Sets verbosity to zero.\n"
    391                  "  -v, --verbose\n"
    392                  "    Increase verbosity.\n"
    393                  "  -V, --version\n"
    394                  "    Displays version.\n"
    395                  "  -h, -?, --help\n"
    396                  "    Displays help.\n"
    397                  );
    398 
    399     for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
    400     {
    401         RTStrmPrintf(pStrm,
    402                      "\n"
    403                      "Command '%s':\n"
    404                      "    %s\n"
    405                      "Options for '%s':\n",
    406                      g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
    407         PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
    408         for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
    409         {
    410             if (RT_C_IS_PRINT(paOptions[i].iShort))
    411                 RTStrmPrintf(pStrm, "  -%c, %s\n", g_aCmdTestOptions[i].iShort, g_aCmdTestOptions[i].pszLong);
    412             else
    413                 RTStrmPrintf(pStrm, "  %s\n", g_aCmdTestOptions[i].pszLong);
    414 
    415             const char *pszHelp = NULL;
    416             if (paOptions == g_aCmdTestOptions)
    417             {
    418                 switch (g_aCmdTestOptions[i].iShort)
    419                 {
    420                     case 'd':
    421                         pszHelp = "Use the specified audio device";
    422                         break;
    423                     case 'e':
    424                         pszHelp = "Exclude the given test id from the list";
    425                         break;
    426                     case 'a':
    427                         pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)";
    428                         break;
    429                     case 'i':
    430                         pszHelp = "Include the given test id in the list";
    431                         break;
    432                 }
    433             }
    434             /** @todo Add help text for all options. */
    435             if (pszHelp)
    436                 RTStrmPrintf(pStrm, "    %s\n", pszHelp);
    437         }
    438     }
    439 
    440 }
    441329
    442330/** @name Driver Helper Fakes / Stubs
     
    485373/** @} */
    486374
    487 
    488375/**
    489376 * Constructs a PDM audio driver instance.
    490377 *
    491378 * @returns VBox status code.
    492  * @param   pDrvReg     PDM driver registration record to use for construction.
    493  * @param   ppDrvIns    Where to return the driver instance structure.
    494  * @param   ppDrvAudio  Where to return the audio driver interface of type IHOSTAUDIO.
    495  */
    496 static int audioTestDrvConstruct(PCPDMDRVREG pDrvReg, PPPDMDRVINS ppDrvIns, PPDMIHOSTAUDIO *ppDrvAudio)
     379 * @param   pDrvStack       The stack this is associated with.
     380 * @param   pDrvReg         PDM driver registration record to use for construction.
     381 * @param   pParentDrvIns   The parent driver (if any).
     382 * @param   ppDrvIns        Where to return the driver instance structure.
     383 */
     384static int audioTestDrvConstruct(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, PPDMDRVINS pParentDrvIns,
     385                                 PPPDMDRVINS ppDrvIns)
    497386{
    498387    /* The destruct function must have valid data to work with. */
    499388    *ppDrvIns   = NULL;
    500     *ppDrvAudio = NULL;
    501389
    502390    /*
     
    524412    RTTEST_CHECK_RET(g_hTest, pDrvIns, VERR_NO_MEMORY);
    525413
    526     pDrvIns->u32Version       = PDM_DRVINS_VERSION;
    527     pDrvIns->iInstance        = 0;
    528     pDrvIns->pHlpR3           = &s_DrvHlp;
    529     pDrvIns->pvInstanceDataR3 = &pDrvIns->achInstanceData[0];
    530     pDrvIns->pReg             = pDrvReg;
    531     pDrvIns->pCfg             = (PCFGMNODE)pDrvReg;
    532     //pDrvIns->pUpBase          = NULL;
    533     //pDrvIns->pDownBase        = NULL;
     414    pDrvIns->u32Version         = PDM_DRVINS_VERSION;
     415    pDrvIns->iInstance          = 0;
     416    pDrvIns->pHlpR3             = &s_DrvHlp;
     417    pDrvIns->pvInstanceDataR3   = &pDrvIns->achInstanceData[0];
     418    pDrvIns->pReg               = pDrvReg;
     419    pDrvIns->pCfg               = (PCFGMNODE)pDrvReg;
     420    pDrvIns->Internal.s.pStack  = pDrvStack;
     421    pDrvIns->pUpBase            = NULL;
     422    pDrvIns->pDownBase          = NULL;
     423    if (pParentDrvIns)
     424    {
     425        Assert(pParentDrvIns->pDownBase == NULL);
     426        pParentDrvIns->pDownBase = &pDrvIns->IBase;
     427        pDrvIns->pUpBase         = &pParentDrvIns->IBase;
     428    }
    534429
    535430    /*
     
    539434    if (RT_SUCCESS(rc))
    540435    {
    541         PPDMIHOSTAUDIO pDrvAudio = (PPDMIHOSTAUDIO)pDrvIns->IBase.pfnQueryInterface(&pDrvIns->IBase, PDMIHOSTAUDIO_IID);
    542         if (pDrvAudio)
     436        *ppDrvIns   = pDrvIns;
     437        return VINF_SUCCESS;
     438    }
     439
     440    RTTestFailed(g_hTest, "Failed to construct audio driver '%s': %Rrc", pDrvReg->szName, rc);
     441    if (pDrvReg->pfnDestruct)
     442        pDrvReg->pfnDestruct(pDrvIns);
     443    RTMemFree(pDrvIns);
     444    return rc;
     445}
     446
     447/**
     448 * Destructs a PDM audio driver instance.
     449 *
     450 * @param   pDrvIns             Driver instance to destruct.
     451 */
     452static void audioTestDrvDestruct(PPDMDRVINS pDrvIns)
     453{
     454    if (pDrvIns)
     455    {
     456        Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
     457
     458        if (pDrvIns->pReg->pfnDestruct)
     459            pDrvIns->pReg->pfnDestruct(pDrvIns);
     460
     461        pDrvIns->u32Version = 0;
     462        pDrvIns->pReg = NULL;
     463        RTMemFree(pDrvIns);
     464    }
     465}
     466
     467/**
     468 * Sends the PDM driver a power off notification.
     469 *
     470 * @param   pDrvIns             Driver instance to notify.
     471 */
     472static void audioTestDrvNotifyPowerOff(PPDMDRVINS pDrvIns)
     473{
     474    if (pDrvIns)
     475    {
     476        Assert(pDrvIns->u32Version == PDM_DRVINS_VERSION);
     477        if (pDrvIns->pReg->pfnPowerOff)
     478            pDrvIns->pReg->pfnPowerOff(pDrvIns);
     479    }
     480}
     481
     482/**
     483 * Deletes a driver stack.
     484 *
     485 * This will power off and destroy the drivers.
     486 */
     487static void audioTestDriverStackDelete(PAUDIOTESTDRVSTACK pDrvStack)
     488{
     489    /*
     490     * Do power off notifications (top to bottom).
     491     */
     492    audioTestDrvNotifyPowerOff(pDrvStack->pDrvAudioIns);
     493    audioTestDrvNotifyPowerOff(pDrvStack->pDrvBackendIns);
     494
     495    /*
     496     * Drivers are destroyed from bottom to top (closest to the device).
     497     */
     498    audioTestDrvDestruct(pDrvStack->pDrvBackendIns);
     499    pDrvStack->pDrvBackendIns   = NULL;
     500    pDrvStack->pIHostAudio      = NULL;
     501
     502    audioTestDrvDestruct(pDrvStack->pDrvAudioIns);
     503    pDrvStack->pDrvAudioIns     = NULL;
     504    pDrvStack->pIAudioConnector = NULL;
     505}
     506
     507/**
     508 * Initializes a driver stack.
     509 * @returns VBox status code.
     510 * @param   pDrvStack       The driver stack to initialize.
     511 * @param   pDrvReg         The backend driver to use.
     512 * @param   fWithDrvAudio   Whether to inlcude DrvAudio in the stack or not.
     513 */
     514static int audioTestDriverStackInit(PAUDIOTESTDRVSTACK pDrvStack, PCPDMDRVREG pDrvReg, bool fWithDrvAudio)
     515{
     516    int rc;
     517    RT_ZERO(*pDrvStack);
     518    pDrvStack->pDrvReg = pDrvReg;
     519    if (!fWithDrvAudio)
     520        rc = audioTestDrvConstruct(pDrvStack, pDrvReg, NULL /*pParentDrvIns*/, &pDrvStack->pDrvBackendIns);
     521    else
     522    {
     523        rc = VERR_NOT_IMPLEMENTED;
     524    }
     525
     526    /*
     527     * Get the IHostAudio interface and check that the host driver is working.
     528     */
     529    if (RT_SUCCESS(rc))
     530    {
     531        pDrvStack->pIHostAudio
     532            = (PPDMIHOSTAUDIO)pDrvStack->pDrvBackendIns->IBase.pfnQueryInterface(&pDrvStack->pDrvBackendIns->IBase,
     533                                                                                 PDMIHOSTAUDIO_IID);
     534        if (pDrvStack->pIHostAudio)
    543535        {
    544             PDMAUDIOBACKENDSTS enmStatus = pDrvAudio->pfnGetStatus(pDrvAudio, PDMAUDIODIR_OUT);
     536            PDMAUDIOBACKENDSTS enmStatus = pDrvStack->pIHostAudio->pfnGetStatus(pDrvStack->pIHostAudio, PDMAUDIODIR_OUT);
    545537            if (enmStatus == PDMAUDIOBACKENDSTS_RUNNING)
    546             {
    547                 *ppDrvAudio = pDrvAudio;
    548                 *ppDrvIns   = pDrvIns;
    549538                return VINF_SUCCESS;
    550             }
     539
    551540            RTTestFailed(g_hTest, "Expected backend status RUNNING, got %d instead", enmStatus);
    552541        }
    553542        else
    554543            RTTestFailed(g_hTest, "Failed to query PDMIHOSTAUDIO for '%s'", pDrvReg->szName);
     544        audioTestDriverStackDelete(pDrvStack);
     545    }
     546
     547    return rc;
     548}
     549
     550/**
     551 * Creates an output stream.
     552 *
     553 * @returns VBox status code.
     554 * @param   pDrvStack           The audio driver stack to create it via.
     555 * @param   pProps              The audio properties to use.
     556 * @param   cMsBufferSize       The buffer size in milliseconds.
     557 * @param   cMsPreBuffer        The pre-buffering amount in milliseconds.
     558 * @param   cMsSchedulingHint   The scheduling hint in milliseconds.
     559 * @param   ppStream            Where to return the stream pointer on success.
     560 */
     561static int audioTestDriverStackStreamCreateOutput(PAUDIOTESTDRVSTACK pDrvStack, PCPDMAUDIOPCMPROPS pProps,
     562                                                  uint32_t cMsBufferSize, uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
     563                                                  PPDMAUDIOSTREAM *ppStream)
     564{
     565    *ppStream = NULL;
     566
     567    int rc;
     568    if (pDrvStack->pIAudioConnector)
     569    {
     570        rc = VERR_NOT_IMPLEMENTED;
    555571    }
    556572    else
    557         RTTestFailed(g_hTest, "Failed to construct audio driver '%s': %Rrc", pDrvReg->szName, rc);
    558     if (pDrvReg->pfnDestruct)
    559         pDrvReg->pfnDestruct(pDrvIns);
    560     RTMemFree(pDrvIns);
     573    {
     574        /*
     575         * Calculate the stream config.
     576         */
     577        PDMAUDIOSTREAMCFG CfgReq;
     578        rc = PDMAudioStrmCfgInitWithProps(&CfgReq, pProps);
     579        AssertRC(rc);
     580        CfgReq.enmDir                       = PDMAUDIODIR_OUT;
     581        CfgReq.u.enmDst                     = PDMAUDIOPLAYBACKDST_UNKNOWN;
     582        CfgReq.enmLayout                    = PDMAUDIOSTREAMLAYOUT_INTERLEAVED;
     583        CfgReq.Device.cMsSchedulingHint     = cMsSchedulingHint == UINT32_MAX || cMsSchedulingHint == 0
     584                                            ? 10 : cMsSchedulingHint;
     585        CfgReq.Backend.cFramesBufferSize    = PDMAudioPropsMilliToFrames(pProps,
     586                                                                         cMsBufferSize == UINT32_MAX || cMsBufferSize == 0
     587                                                                         ? 300 : cMsBufferSize);
     588        if (cMsPreBuffer == UINT32_MAX)
     589            CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize * 2 / 3;
     590        else
     591            CfgReq.Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(pProps, cMsPreBuffer);
     592        if (CfgReq.Backend.cFramesPreBuffering >= CfgReq.Backend.cFramesBufferSize + 16)
     593        {
     594            RTMsgWarning("Cannot pre-buffer %#x frames with only %#x frames of buffer!",
     595                         CfgReq.Backend.cFramesPreBuffering, CfgReq.Backend.cFramesBufferSize);
     596            CfgReq.Backend.cFramesPreBuffering = CfgReq.Backend.cFramesBufferSize > 16
     597                                               ? CfgReq.Backend.cFramesBufferSize - 16 : 0;
     598        }
     599
     600        static uint32_t s_idxStream = 0;
     601        uint32_t const idxStream = s_idxStream++;
     602        RTStrPrintf(CfgReq.szName, sizeof(CfgReq.szName), "out-%u", idxStream);
     603
     604        /*
     605         * Get the config so we can see how big the PDMAUDIOBACKENDSTREAM
     606         * structure actually is for this backend.
     607         */
     608        PDMAUDIOBACKENDCFG BackendCfg;
     609        rc = pDrvStack->pIHostAudio->pfnGetConfig(pDrvStack->pIHostAudio, &BackendCfg);
     610        if (RT_SUCCESS(rc))
     611        {
     612            if (BackendCfg.cbStream >= sizeof(PDMAUDIOBACKENDSTREAM))
     613            {
     614                /*
     615                 * Allocate and initialize the stream.
     616                 */
     617                uint32_t const cbStream = sizeof(AUDIOTESTDRVSTACKSTREAM) - sizeof(PDMAUDIOBACKENDSTREAM) + BackendCfg.cbStream;
     618                PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)RTMemAllocZVar(cbStream);
     619                if (pStreamAt)
     620                {
     621                    pStreamAt->Core.uMagic     = PDMAUDIOSTREAM_MAGIC;
     622                    pStreamAt->Core.enmDir     = PDMAUDIODIR_OUT;
     623                    pStreamAt->Core.cbBackend  = cbStream;
     624                    pStreamAt->Core.Props      = CfgReq.Props;
     625                    RTStrPrintf(pStreamAt->Core.szName, sizeof(pStreamAt->Core.szName), "out-%u", idxStream);
     626
     627                    pStreamAt->Backend.uMagic  = PDMAUDIOBACKENDSTREAM_MAGIC;
     628                    pStreamAt->Backend.pStream = &pStreamAt->Core;
     629
     630                    /*
     631                     * Call the backend to create the stream.
     632                     */
     633                    pStreamAt->Cfg = CfgReq;
     634
     635                    rc = pDrvStack->pIHostAudio->pfnStreamCreate(pDrvStack->pIHostAudio, &pStreamAt->Backend,
     636                                                                 &CfgReq, &pStreamAt->Cfg);
     637                    if (RT_SUCCESS(rc))
     638                    {
     639                        pStreamAt->Core.Props = pStreamAt->Cfg.Props;
     640                        if (g_uVerbosity > 1)
     641                        {
     642                            char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX + 16];
     643                            RTMsgInfo("Created backend stream: %s\n",
     644                                      PDMAudioStrmCfgToString(&pStreamAt->Cfg, szTmp, sizeof(szTmp)));
     645                        }
     646
     647                        /* Return if stream is ready: */
     648                        if (rc == VINF_SUCCESS)
     649                        {
     650                            *ppStream = &pStreamAt->Core;
     651                            return VINF_SUCCESS;
     652                        }
     653                        if (rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED)
     654                        {
     655                            /*
     656                             * Do async init right here and now.
     657                             */
     658                            rc = pDrvStack->pIHostAudio->pfnStreamInitAsync(pDrvStack->pIHostAudio, &pStreamAt->Backend,
     659                                                                            false /*fDestroyed*/);
     660                            if (RT_SUCCESS(rc))
     661                            {
     662                                *ppStream = &pStreamAt->Core;
     663                                return VINF_SUCCESS;
     664                            }
     665
     666                            RTTestFailed(g_hTest, "pfnStreamInitAsync failed: %Rrc\n", rc);
     667                        }
     668                        else
     669                        {
     670                            RTTestFailed(g_hTest, "pfnStreamCreate returned unexpected info status: %Rrc", rc);
     671                            rc = VERR_IPE_UNEXPECTED_INFO_STATUS;
     672                        }
     673                        pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend);
     674                    }
     675                    else
     676                        RTTestFailed(g_hTest, "pfnStreamCreate failed: %Rrc\n", rc);
     677                }
     678                else
     679                {
     680                    RTTestFailed(g_hTest, "Out of memory!\n");
     681                    rc = VERR_NO_MEMORY;
     682                }
     683            }
     684            else
     685            {
     686                RTTestFailed(g_hTest, "cbStream=%#x is too small, min %#zx!\n", BackendCfg.cbStream, sizeof(PDMAUDIOBACKENDSTREAM));
     687                rc = VERR_OUT_OF_RANGE;
     688            }
     689        }
     690        else
     691            RTTestFailed(g_hTest, "pfnGetConfig failed: %Rrc\n", rc);
     692    }
    561693    return rc;
    562694}
    563695
    564696/**
    565  * Destructs a PDM audio driver instance.
     697 * Destroys a stream.
     698 */
     699static void audioTestDriverStackStreamDestroy(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
     700{
     701    if (pStream)
     702    {
     703        if (pDrvStack->pIAudioConnector)
     704        {
     705            int rc = pDrvStack->pIAudioConnector->pfnStreamDestroy(pDrvStack->pIAudioConnector, pStream);
     706            if (RT_FAILURE(rc))
     707                RTTestFailed(g_hTest, "pfnStreamDestroy failed: %Rrc", rc);
     708        }
     709        else
     710        {
     711            PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     712            int rc = pDrvStack->pIHostAudio->pfnStreamDestroy(pDrvStack->pIHostAudio, &pStreamAt->Backend);
     713            if (RT_SUCCESS(rc))
     714            {
     715                pStreamAt->Core.uMagic    = ~PDMAUDIOSTREAM_MAGIC;
     716                pStreamAt->Backend.uMagic = ~PDMAUDIOBACKENDSTREAM_MAGIC;
     717                RTMemFree(pStreamAt);
     718            }
     719            else
     720                RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamDestroy failed: %Rrc", rc);
     721        }
     722    }
     723}
     724
     725/**
     726 * Enables a stream.
     727 */
     728static int audioTestDriverStackStreamEnable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
     729{
     730    int rc;
     731    if (pDrvStack->pIAudioConnector)
     732    {
     733        rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_ENABLE);
     734        if (RT_FAILURE(rc))
     735            RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
     736    }
     737    else
     738    {
     739        PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     740        rc = pDrvStack->pIHostAudio->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_ENABLE);
     741        if (RT_FAILURE(rc))
     742            RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
     743    }
     744    return rc;
     745}
     746
     747/**
     748 * Drains an output stream.
     749 */
     750static int audioTestDriverStackStreamDrain(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream, bool fSync)
     751{
     752    int rc;
     753    if (pDrvStack->pIAudioConnector)
     754    {
     755        /*
     756         * Issue the drain request.
     757         */
     758        rc = pDrvStack->pIAudioConnector->pfnStreamControl(pDrvStack->pIAudioConnector, pStream, PDMAUDIOSTREAMCMD_DRAIN);
     759        if (RT_SUCCESS(rc) && fSync)
     760        {
     761            /*
     762             * This is a synchronous drain, so wait for the driver to change state to inactive.
     763             */
     764            PDMAUDIOSTREAMSTATE enmState;
     765            while (   (enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream))
     766                   >= PDMAUDIOSTREAMSTATE_ENABLED)
     767            {
     768                RTThreadSleep(2);
     769                rc = pDrvStack->pIAudioConnector->pfnStreamIterate(pDrvStack->pIAudioConnector, pStream);
     770                if (RT_FAILURE(rc))
     771                {
     772                    RTTestFailed(g_hTest, "pfnStreamIterate/DRAIN failed: %Rrc", rc);
     773                    break;
     774                }
     775            }
     776            if (enmState != PDMAUDIOSTREAMSTATE_INACTIVE)
     777            {
     778                RTTestFailed(g_hTest, "Stream state not INACTIVE after draing: %s", PDMAudioStreamStateGetName(enmState));
     779                rc = VERR_AUDIO_STREAM_NOT_READY;
     780            }
     781        }
     782        else if (RT_FAILURE(rc))
     783            RTTestFailed(g_hTest, "pfnStreamControl/ENABLE failed: %Rrc", rc);
     784    }
     785    else
     786    {
     787        /*
     788         * Issue the drain request.
     789         */
     790        PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     791        rc = pDrvStack->pIHostAudio->pfnStreamControl(pDrvStack->pIHostAudio, &pStreamAt->Backend, PDMAUDIOSTREAMCMD_DRAIN);
     792        if (RT_SUCCESS(rc) && fSync)
     793        {
     794            /*
     795             * This is a synchronous drain, so wait for the driver to change state to inactive.
     796             */
     797            PDMHOSTAUDIOSTREAMSTATE enmHostState;
     798            while (   (enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio, &pStreamAt->Backend))
     799                   == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
     800            {
     801                RTThreadSleep(2);
     802                uint32_t cbWritten = UINT32_MAX;
     803                rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend,
     804                                                           NULL /*pvBuf*/, 0 /*cbBuf*/, &cbWritten);
     805                if (RT_FAILURE(rc))
     806                {
     807                    RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN failed: %Rrc", rc);
     808                    break;
     809                }
     810                if (cbWritten != 0)
     811                {
     812                    RTTestFailed(g_hTest, "pfnStreamPlay/DRAIN did not set cbWritten to zero: %#x", cbWritten);
     813                    rc = VERR_MISSING;
     814                    break;
     815                }
     816            }
     817            if (enmHostState != PDMHOSTAUDIOSTREAMSTATE_OKAY)
     818            {
     819                RTTestFailed(g_hTest, "Stream state not OKAY after draing: %s", PDMHostAudioStreamStateGetName(enmHostState));
     820                rc = VERR_AUDIO_STREAM_NOT_READY;
     821            }
     822        }
     823        else if (RT_FAILURE(rc))
     824            RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamControl/ENABLE failed: %Rrc", rc);
     825    }
     826    return rc;
     827}
     828
     829/**
     830 * Checks if the stream is okay.
     831 * @returns true if okay, false if not.
     832 */
     833static bool audioTestDriverStackStreamIsOkay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
     834{
     835    /*
     836     * Get the stream status and check if it means is okay or not.
     837     */
     838    bool fRc = false;
     839    if (pDrvStack->pIAudioConnector)
     840    {
     841        PDMAUDIOSTREAMSTATE enmState = pDrvStack->pIAudioConnector->pfnStreamGetState(pDrvStack->pIAudioConnector, pStream);
     842        switch (enmState)
     843        {
     844            case PDMAUDIOSTREAMSTATE_NOT_WORKING:
     845            case PDMAUDIOSTREAMSTATE_NEED_REINIT:
     846                break;
     847            case PDMAUDIOSTREAMSTATE_INACTIVE:
     848            case PDMAUDIOSTREAMSTATE_ENABLED:
     849            case PDMAUDIOSTREAMSTATE_ENABLED_READABLE:
     850            case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE:
     851                fRc = true;
     852                break;
     853            /* no default */
     854            case PDMAUDIOSTREAMSTATE_INVALID:
     855            case PDMAUDIOSTREAMSTATE_END:
     856            case PDMAUDIOSTREAMSTATE_32BIT_HACK:
     857                break;
     858        }
     859    }
     860    else
     861    {
     862        PAUDIOTESTDRVSTACKSTREAM pStreamAt    = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     863        PDMHOSTAUDIOSTREAMSTATE  enmHostState = pDrvStack->pIHostAudio->pfnStreamGetState(pDrvStack->pIHostAudio,
     864                                                                                          &pStreamAt->Backend);
     865        switch (enmHostState)
     866        {
     867            case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
     868            case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
     869                break;
     870            case PDMHOSTAUDIOSTREAMSTATE_OKAY:
     871            case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
     872            case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
     873                fRc = true;
     874                break;
     875            /* no default */
     876            case PDMHOSTAUDIOSTREAMSTATE_INVALID:
     877            case PDMHOSTAUDIOSTREAMSTATE_END:
     878            case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
     879                break;
     880        }
     881    }
     882    return fRc;
     883}
     884
     885/**
     886 * Gets the number of bytes it's currently possible to write to the stream.
     887 */
     888static uint32_t audioTestDriverStackStreamGetWritable(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream)
     889{
     890    uint32_t cbWritable;
     891    if (pDrvStack->pIAudioConnector)
     892        cbWritable = pDrvStack->pIAudioConnector->pfnStreamGetWritable(pDrvStack->pIAudioConnector, pStream);
     893    else
     894    {
     895        PAUDIOTESTDRVSTACKSTREAM pStreamAt    = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     896        cbWritable = pDrvStack->pIHostAudio->pfnStreamGetWritable(pDrvStack->pIHostAudio, &pStreamAt->Backend);
     897    }
     898    return cbWritable;
     899}
     900
     901/**
     902 * Tries to play the @a cbBuf bytes of samples in @a pvBuf.
     903 */
     904static int audioTestDriverStackStreamPlay(PAUDIOTESTDRVSTACK pDrvStack, PPDMAUDIOSTREAM pStream,
     905                                          void const *pvBuf, uint32_t cbBuf, uint32_t *pcbPlayed)
     906{
     907    int rc;
     908    if (pDrvStack->pIAudioConnector)
     909    {
     910        rc = pDrvStack->pIAudioConnector->pfnStreamPlay(pDrvStack->pIAudioConnector, pStream, pvBuf, cbBuf, pcbPlayed);
     911        if (RT_FAILURE(rc))
     912            RTTestFailed(g_hTest, "pfnStreamPlay(,,,%#x) failed: %Rrc", cbBuf, rc);
     913    }
     914    else
     915    {
     916        PAUDIOTESTDRVSTACKSTREAM pStreamAt = (PAUDIOTESTDRVSTACKSTREAM)pStream;
     917        rc = pDrvStack->pIHostAudio->pfnStreamPlay(pDrvStack->pIHostAudio, &pStreamAt->Backend, pvBuf, cbBuf, pcbPlayed);
     918        if (RT_FAILURE(rc))
     919            RTTestFailed(g_hTest, "PDMIHOSTAUDIO::pfnStreamPlay(,,,%#x) failed: %Rrc", cbBuf, rc);
     920    }
     921    return rc;
     922}
     923
     924
     925/*********************************************************************************************************************************
     926*   WAVE File Reader.                                                                                                            *
     927*********************************************************************************************************************************/
     928/**
     929 * Opens a wave-file for reading.
    566930 *
    567931 * @returns VBox status code.
    568  * @param   pDrvReg             PDM driver registration record to destruct.
    569  * @param   pDrvIns             Driver instance to destruct.
    570  */
    571 static int audioTestDrvDestruct(PCPDMDRVREG pDrvReg, PPDMDRVINS pDrvIns)
    572 {
    573     if (!pDrvIns)
    574         return VINF_SUCCESS;
    575 
    576     if (pDrvReg->pfnDestruct)
    577         pDrvReg->pfnDestruct(pDrvIns);
    578 
    579     pDrvIns->u32Version = 0;
    580     RTMemFree(pDrvIns);
    581 
    582     return VINF_SUCCESS;
    583 }
     932 * @param   pszFile     The file to open.
     933 * @param   pWaveFile   The open wave file structure to fill in on success.
     934 */
     935static int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile)
     936{
     937    RT_ZERO(pWaveFile->Props);
     938    pWaveFile->hFile = NIL_RTFILE;
     939    int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
     940    if (RT_FAILURE(rc))
     941        return rc;
     942    uint64_t cbFile = 0;
     943    rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
     944    if (RT_SUCCESS(rc))
     945    {
     946        union
     947        {
     948            uint8_t                 ab[512];
     949            struct
     950            {
     951                RTRIFFHDR           Hdr;
     952                RTRIFFWAVEFMTCHUNK  Fmt;
     953            } Wave;
     954            RTRIFFLIST              List;
     955            RTRIFFWAVEDATACHUNK     Data;
     956        } uBuf;
     957
     958        rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
     959        if (RT_SUCCESS(rc))
     960        {
     961            rc = VERR_VFS_UNKNOWN_FORMAT;
     962            if (   uBuf.Wave.Hdr.uMagic    == RTRIFFHDR_MAGIC
     963                && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
     964                && uBuf.Wave.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
     965                && uBuf.Wave.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.Fmt.Data))
     966            {
     967                if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
     968                    RTMsgWarning("%s: File size mismatch: %#x, actual %#RX64 (ignored)",
     969                                 pszFile, uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
     970                rc = VERR_VFS_BOGUS_FORMAT;
     971                if (uBuf.Wave.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM)
     972                    RTMsgError("%s: Unsupported uFormatTag value: %u (expected 1)", pszFile, uBuf.Wave.Fmt.Data.uFormatTag);
     973                else if (   uBuf.Wave.Fmt.Data.cBitsPerSample != 8
     974                         && uBuf.Wave.Fmt.Data.cBitsPerSample != 16
     975                         && uBuf.Wave.Fmt.Data.cBitsPerSample != 32)
     976                    RTMsgError("%s: Unsupported cBitsPerSample value: %u", pszFile, uBuf.Wave.Fmt.Data.cBitsPerSample);
     977                else if (   uBuf.Wave.Fmt.Data.cChannels < 1
     978                         || uBuf.Wave.Fmt.Data.cChannels >= 16)
     979                    RTMsgError("%s: Unsupported cChannels value: %u (expected 1..15)", pszFile, uBuf.Wave.Fmt.Data.cChannels);
     980                else if (   uBuf.Wave.Fmt.Data.uHz < 4096
     981                         || uBuf.Wave.Fmt.Data.uHz > 768000)
     982                    RTMsgError("%s: Unsupported uHz value: %u (expected 4096..768000)", pszFile, uBuf.Wave.Fmt.Data.uHz);
     983                else if (uBuf.Wave.Fmt.Data.cbFrame != uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8)
     984                    RTMsgError("%s: Invalid cbFrame value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbFrame,
     985                               uBuf.Wave.Fmt.Data.cChannels * uBuf.Wave.Fmt.Data.cBitsPerSample / 8);
     986                else if (uBuf.Wave.Fmt.Data.cbRate != uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz)
     987                    RTMsgError("%s: Invalid cbRate value: %u (expected %u)", pszFile, uBuf.Wave.Fmt.Data.cbRate,
     988                               uBuf.Wave.Fmt.Data.cbFrame * uBuf.Wave.Fmt.Data.uHz);
     989                else
     990                {
     991                    /*
     992                     * Copy out the data we need from the file format structure.
     993                     */
     994                    PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
     995                                      uBuf.Wave.Fmt.Data.cChannels, uBuf.Wave.Fmt.Data.uHz);
     996                    pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.Fmt.Chunk.cbChunk;
     997
     998                    /*
     999                     * Find the 'data' chunk with the audio samples.
     1000                     *
     1001                     * There can be INFO lists both preceeding this and succeeding
     1002                     * it, containing IART and other things we can ignored.  Thus
     1003                     * we read a list header here rather than just a chunk header,
     1004                     * since it doesn't matter if we read 4 bytes extra as
     1005                     * AudioTestWaveFileRead uses RTFileReadAt anyway.
     1006                     */
     1007                    rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
     1008                    if (RT_SUCCESS(rc))
     1009                    {
     1010                        /* HACK ALERT: Skip one INFO list and hope we find a data chunk following it: */
     1011                        if (   uBuf.List.uMagic    == RTRIFFLIST_MAGIC
     1012                            && uBuf.List.uListType ==  RTRIFFLIST_TYPE_INFO
     1013                            && uBuf.List.cbChunk   <= (uint32_t)cbFile - pWaveFile->offSamples - sizeof(RTRIFFCHUNK))
     1014                        {
     1015                            pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.List.cbChunk;
     1016                            rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
     1017                        }
     1018
     1019                        pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
     1020                        pWaveFile->cbSamples   = (uint32_t)cbFile - pWaveFile->offSamples;
     1021
     1022                        rc = VERR_VFS_BOGUS_FORMAT;
     1023                        if (   uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
     1024                            && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
     1025                            && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
     1026                        {
     1027                            pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
     1028                            /*
     1029                             * We're good!
     1030                             */
     1031                            pWaveFile->offCur = 0;
     1032                            return VINF_SUCCESS;
     1033                        }
     1034
     1035                        RTMsgError("%s: Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
     1036                                   pszFile, uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
     1037                                   uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
     1038                    }
     1039                    else
     1040                        RTMsgError("%s: Failed to read data header: %Rrc", pszFile, rc);
     1041                }
     1042            }
     1043            else
     1044                RTMsgError("%s: Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
     1045                           pszFile, uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
     1046                           uBuf.Wave.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
     1047                           uBuf.Wave.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.Fmt.Data));
     1048        }
     1049        else
     1050            RTMsgError("%s: Failed to read file header: %Rrc", pszFile, rc);
     1051    }
     1052    else
     1053        RTMsgError("%s: Failed to query file size: %Rrc", pszFile, rc);
     1054
     1055    RTFileClose(pWaveFile->hFile);
     1056    pWaveFile->hFile = NIL_RTFILE;
     1057    return rc;
     1058}
     1059
     1060/**
     1061 * Closes a wave file.
     1062 */
     1063static void AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
     1064{
     1065    RTFileClose(pWaveFile->hFile);
     1066    pWaveFile->hFile = NIL_RTFILE;
     1067}
     1068
     1069/**
     1070 * Reads samples from a wave file.
     1071 *
     1072 * @returns VBox status code.  See RTVfsFileRead for EOF status handling.
     1073 * @param   pWaveFile   The file to read from.
     1074 * @param   pvBuf       Where to put the samples.
     1075 * @param   cbBuf       How much to read at most.
     1076 * @param   pcbRead     Where to return the actual number of bytes read,
     1077 *                      optional.
     1078 */
     1079static int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
     1080{
     1081    int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
     1082    if (RT_SUCCESS(rc))
     1083    {
     1084        if (pcbRead)
     1085        {
     1086            pWaveFile->offCur += *pcbRead;
     1087            if (cbBuf > *pcbRead)
     1088                rc = VINF_EOF;
     1089            else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
     1090                rc = VINF_EOF;
     1091        }
     1092        else
     1093            pWaveFile->offCur += cbBuf;
     1094    }
     1095    return rc;
     1096}
     1097
     1098
     1099/*********************************************************************************************************************************
     1100*   Implementation of Something                                                                                                  *
     1101*********************************************************************************************************************************/
     1102
     1103/**
     1104 * Initializes an audio test environment.
     1105 *
     1106 * @param   pTstEnv             Audio test environment to initialize.
     1107 * @param   pDrvAudio           Audio driver to use.
     1108 * @param   pszPathOut          Output path to use. If NULL, the system's temp directory will be used.
     1109 * @param   pszPathTemp         Temporary path to use. If NULL, the system's temp directory will be used.
     1110 * @param   pszTag              Tag name to use. If NULL, a generated UUID will be used.
     1111 */
     1112static int audioTestEnvInit(PAUDIOTESTENV pTstEnv, PPDMIHOSTAUDIO pDrvAudio, const char *pszTag)
     1113{
     1114    pTstEnv->pDrvAudio = pDrvAudio;
     1115    PDMAudioHostEnumInit(&pTstEnv->DevEnum);
     1116
     1117    int rc = VINF_SUCCESS;
     1118
     1119    char szPathTemp[RTPATH_MAX];
     1120    if (   !strlen(pTstEnv->szPathTemp)
     1121        || !strlen(pTstEnv->szPathOut))
     1122        rc = RTPathTemp(szPathTemp, sizeof(szPathTemp));
     1123
     1124    if (   RT_SUCCESS(rc)
     1125        && !strlen(pTstEnv->szPathTemp))
     1126        rc = RTPathJoin(pTstEnv->szPathTemp, sizeof(pTstEnv->szPathTemp), szPathTemp, "vkat-temp");
     1127
     1128    if (   RT_SUCCESS(rc)
     1129        && !strlen(pTstEnv->szPathOut))
     1130        rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
     1131
     1132    if (RT_SUCCESS(rc))
     1133        rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
     1134
     1135    if (RT_SUCCESS(rc))
     1136    {
     1137        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
     1138        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Output directory is '%s'\n", pTstEnv->szPathOut);
     1139        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
     1140    }
     1141
     1142    return rc;
     1143}
     1144
     1145/**
     1146 * Destroys an audio test environment.
     1147 *
     1148 * @param   pTstEnv             Audio test environment to destroy.
     1149 */
     1150static void audioTestEnvDestroy(PAUDIOTESTENV pTstEnv)
     1151{
     1152    if (!pTstEnv)
     1153        return;
     1154
     1155    PDMAudioHostEnumDelete(&pTstEnv->DevEnum);
     1156
     1157    for (unsigned i = 0; i < RT_ELEMENTS(pTstEnv->aStreams); i++)
     1158    {
     1159        int rc2 = audioTestStreamDestroy(pTstEnv, &pTstEnv->aStreams[i]);
     1160        if (RT_FAILURE(rc2))
     1161            RTTestFailed(g_hTest, "Stream destruction for stream #%u failed with %Rrc\n", i, rc2);
     1162    }
     1163
     1164    AudioTestSetDestroy(&pTstEnv->Set);
     1165}
     1166
     1167/**
     1168 * Initializes an audio test parameters set.
     1169 *
     1170 * @param   pTstParms           Test parameters set to initialize.
     1171 */
     1172static void audioTestParmsInit(PAUDIOTESTPARMS pTstParms)
     1173{
     1174    RT_ZERO(*pTstParms);
     1175}
     1176
     1177/**
     1178 * Destroys an audio test parameters set.
     1179 *
     1180 * @param   pTstParms           Test parameters set to destroy.
     1181 */
     1182static void audioTestParmsDestroy(PAUDIOTESTPARMS pTstParms)
     1183{
     1184    if (!pTstParms)
     1185        return;
     1186
     1187    return;
     1188}
     1189
     1190
     1191/*********************************************************************************************************************************
     1192*   Some other stuff, you name it.                                                                                               *
     1193*********************************************************************************************************************************/
    5841194
    5851195/**
     
    10091619}
    10101620
     1621/** Option help for the 'test' command.   */
     1622static DECLCALLBACK(const char *) audioTestCmdTestHelp(PCRTGETOPTDEF pOpt)
     1623{
     1624    switch (pOpt->iShort)
     1625    {
     1626        case 'd':   return "Use the specified audio device";
     1627        case 'e':   return "Exclude the given test id from the list";
     1628        case 'a':   return "Exclude all tests from the list (useful to enable single tests later with --include)";
     1629        case 'i':   return "Include the given test id in the list";
     1630    }
     1631    return NULL;
     1632}
     1633
    10111634/**
    10121635 * Main (entry) function for the testing functionality of VKAT.
     
    10371660        switch (rc)
    10381661        {
    1039             case 'h':
    1040                 audioTestUsage(g_pStdOut);
    1041                 return RTEXITCODE_SUCCESS;
    1042 
    10431662            case 'a':
    10441663                for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
     
    11191738                break;
    11201739
     1740            case 'V':
     1741                return audioTestVersion();
     1742            case 'h':
     1743                return audioTestUsage(g_pStdOut);
     1744
    11211745            default:
    11221746                return RTGetOptPrintError(rc, &ValueUnion);
     
    11291753    RTTestBanner(g_hTest);
    11301754
    1131     PPDMIHOSTAUDIO pDrvAudio = NULL;
    1132     PPDMDRVINS     pDrvIns   = NULL;
    1133     rc = audioTestDrvConstruct(pDrvReg, &pDrvIns, &pDrvAudio);
     1755    AUDIOTESTDRVSTACK DrvStack;
     1756    rc = audioTestDriverStackInit(&DrvStack, pDrvReg, false /*fWithDrvAudio*/);
    11341757    if (RT_SUCCESS(rc))
    11351758    {
    11361759        /* For now all tests have the same test environment. */
    1137         rc = audioTestEnvInit(&TstEnv, pDrvAudio, pszTag);
     1760        /** @todo bake the DrvStack into the test env make make it more flexible so
     1761         *        we can also test with/without DrvAudio (need option above). */
     1762        rc = audioTestEnvInit(&TstEnv, DrvStack.pIHostAudio, pszTag);
    11381763        if (RT_SUCCESS(rc))
    11391764        {
     
    11551780            audioTestEnvDestroy(&TstEnv);
    11561781        }
    1157     }
    1158     audioTestDrvDestruct(pDrvReg, pDrvIns);
     1782        audioTestDriverStackDelete(&DrvStack);
     1783    }
    11591784
    11601785    audioTestParmsDestroy(&TstCust);
     
    11681793    return RTTestSummaryAndDestroy(g_hTest);
    11691794}
     1795
     1796
     1797/*********************************************************************************************************************************
     1798*   Command: verify                                                                                                              *
     1799*********************************************************************************************************************************/
    11701800
    11711801/**
     
    12521882                break;
    12531883
     1884            case 'V':
     1885                return audioTestVersion();
     1886            case 'h':
     1887                return audioTestUsage(g_pStdOut);
     1888
    12541889            default:
    12551890                return RTGetOptPrintError(rc, &ValueUnion);
     
    12731908     */
    12741909    return RTTestSummaryAndDestroy(g_hTest);
     1910}
     1911
     1912
     1913/*********************************************************************************************************************************
     1914*   Command: play                                                                                                                *
     1915*********************************************************************************************************************************/
     1916/**
     1917 * Command line parameters for test mode.
     1918 */
     1919static const RTGETOPTDEF g_aCmdPlayOptions[] =
     1920{
     1921    { "--backend",          'b',                          RTGETOPT_REQ_STRING  },
     1922};
     1923
     1924/** the 'play' command option help. */
     1925static DECLCALLBACK(const char *) audioTestCmdPlayHelp(PCRTGETOPTDEF pOpt)
     1926{
     1927    switch (pOpt->iShort)
     1928    {
     1929        case 'b': return "The audio backend to use.";
     1930        default:  return NULL;
     1931    }
     1932}
     1933
     1934/**
     1935 * Worker for audioTestCmdPlayHandler that plays one file.
     1936 */
     1937static RTEXITCODE audioTestPlayOne(const char *pszFile, PCPDMDRVREG pDrvReg, uint32_t cMsBufferSize,
     1938                                   uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint)
     1939{
     1940    /*
     1941     * First we must open the file and determin the format.
     1942     */
     1943    AUDIOTESTWAVEFILE WaveFile;
     1944    int rc = AudioTestWaveFileOpen(pszFile, &WaveFile);
     1945    if (RT_FAILURE(rc))
     1946        return RTMsgErrorExitFailure("Failed to open '%s': %Rrc", pszFile, rc);
     1947
     1948    if (g_uVerbosity > 0)
     1949    {
     1950        char szTmp[128];
     1951        RTMsgInfo("Opened '%s' for playing\n", pszFile);
     1952        RTMsgInfo("Format: %s\n", PDMAudioPropsToString(&WaveFile.Props, szTmp, sizeof(szTmp)));
     1953        RTMsgInfo("Size:   %'RU32 bytes / %#RX32 / %'RU32 frames / %'RU64 ns\n",
     1954                  WaveFile.cbSamples, WaveFile.cbSamples,
     1955                  PDMAudioPropsBytesToFrames(&WaveFile.Props, WaveFile.cbSamples),
     1956                  PDMAudioPropsBytesToNano(&WaveFile.Props, WaveFile.cbSamples));
     1957    }
     1958
     1959    /*
     1960     * Construct the driver stack.
     1961     */
     1962    RTEXITCODE          rcExit = RTEXITCODE_FAILURE;
     1963    AUDIOTESTDRVSTACK   DrvStack;
     1964    rc = audioTestDriverStackInit(&DrvStack, pDrvReg, false /*fWithDrvAudio*/);
     1965    if (RT_SUCCESS(rc))
     1966    {
     1967        /*
     1968         * Open a stream for the output.
     1969         */
     1970        PPDMAUDIOSTREAM pStream = NULL;
     1971        rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &WaveFile.Props, cMsBufferSize,
     1972                                                    cMsPreBuffer, cMsSchedulingHint, &pStream);
     1973        if (RT_SUCCESS(rc))
     1974        {
     1975            rc = audioTestDriverStackStreamEnable(&DrvStack, pStream);
     1976            if (RT_SUCCESS(rc))
     1977            {
     1978                uint64_t const nsStarted = RTTimeNanoTS();
     1979
     1980                /*
     1981                 * Transfer data as quickly as we're allowed.
     1982                 */
     1983                for (;;)
     1984                {
     1985                    /* Read a chunk from the wave file. */
     1986                    uint8_t  abSamples[16384];
     1987                    size_t   cbSamples = 0;
     1988                    rc = AudioTestWaveFileRead(&WaveFile, abSamples, sizeof(abSamples), &cbSamples);
     1989                    if (RT_SUCCESS(rc) && cbSamples > 0)
     1990                    {
     1991                        /* Transfer the data to the audio stream. */
     1992                        for (uint32_t offSamples = 0; offSamples < cbSamples;)
     1993                        {
     1994                            uint32_t const cbCanWrite = audioTestDriverStackStreamGetWritable(&DrvStack, pStream);
     1995                            if (cbCanWrite > 0)
     1996                            {
     1997                                uint32_t const cbToPlay = RT_MIN(cbCanWrite, (uint32_t)cbSamples - offSamples);
     1998                                uint32_t       cbPlayed = 0;
     1999                                rc = audioTestDriverStackStreamPlay(&DrvStack, pStream, &abSamples[offSamples],
     2000                                                                    cbToPlay, &cbPlayed);
     2001                                if (RT_SUCCESS(rc))
     2002                                {
     2003                                    if (cbPlayed)
     2004                                        offSamples += cbPlayed;
     2005                                    else
     2006                                    {
     2007                                        rcExit = RTMsgErrorExitFailure("Played zero out of %#x bytes - %#x bytes reported playable!\n",
     2008                                                                       cbToPlay, cbCanWrite);
     2009                                        break;
     2010                                    }
     2011                                }
     2012                                else
     2013                                {
     2014                                    rcExit = RTMsgErrorExitFailure("Failed to play %#x bytes: %Rrc\n", cbToPlay, rc);
     2015                                    break;
     2016                                }
     2017                            }
     2018                            else if (audioTestDriverStackStreamIsOkay(&DrvStack, pStream))
     2019                                RTThreadSleep(RT_MIN(RT_MAX(1, cMsSchedulingHint), 256));
     2020                            else
     2021                            {
     2022                                rcExit = RTMsgErrorExitFailure("Stream is not okay!\n");
     2023                                break;
     2024                            }
     2025                        }
     2026                    }
     2027                    else if (RT_SUCCESS(rc) && cbSamples == 0)
     2028                    {
     2029                        rcExit = RTEXITCODE_SUCCESS;
     2030                        break;
     2031                    }
     2032                    else
     2033                    {
     2034                        rcExit = RTMsgErrorExitFailure("Error reading wav file '%s': %Rrc", pszFile, rc);
     2035                        break;
     2036                    }
     2037                }
     2038
     2039                /*
     2040                 * Drain the stream.
     2041                 */
     2042                if (rcExit == RTEXITCODE_SUCCESS)
     2043                {
     2044                    if (g_uVerbosity > 0)
     2045                        RTMsgInfo("%'RU64 ns: Draining...\n", RTTimeNanoTS() - nsStarted);
     2046                    rc = audioTestDriverStackStreamDrain(&DrvStack, pStream, true /*fSync*/);
     2047                    if (RT_SUCCESS(rc))
     2048                    {
     2049                        if (g_uVerbosity > 0)
     2050                            RTMsgInfo("%'RU64 ns: Done\n", RTTimeNanoTS() - nsStarted);
     2051                    }
     2052                    else
     2053                        rcExit = RTMsgErrorExitFailure("Draining failed: %Rrc", rc);
     2054                }
     2055            }
     2056            else
     2057                rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
     2058            audioTestDriverStackStreamDestroy(&DrvStack, pStream);
     2059        }
     2060        else
     2061            rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
     2062        audioTestDriverStackDelete(&DrvStack);
     2063    }
     2064    else
     2065        rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
     2066    AudioTestWaveFileClose(&WaveFile);
     2067    return rcExit;
     2068}
     2069
     2070/**
     2071 * The 'play' command handler.
     2072 *
     2073 * @returns Program exit code.
     2074 * @param   argc                Number of argv arguments.
     2075 * @param   argv                argv arguments.
     2076 */
     2077static DECLCALLBACK(RTEXITCODE) audioTestCmdPlayHandler(int argc, char **argv)
     2078{
     2079    /*
     2080     * Parse arguments.
     2081     */
     2082    /* Option values: */
     2083    PCPDMDRVREG pDrvReg           = g_aBackends[0].pDrvReg;
     2084    uint32_t    cMsBufferSize     = UINT32_MAX;
     2085    uint32_t    cMsPreBuffer      = UINT32_MAX;
     2086    uint32_t    cMsSchedulingHint = UINT32_MAX;
     2087
     2088    RTGETOPTSTATE GetState;
     2089    int rc = RTGetOptInit(&GetState, argc, argv, g_aCmdPlayOptions, RT_ELEMENTS(g_aCmdPlayOptions),
     2090                          1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
     2091    AssertRCReturn(rc, RTEXITCODE_INIT);
     2092
     2093    RTGETOPTUNION ValueUnion;
     2094    while ((rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
     2095    {
     2096        switch (rc)
     2097        {
     2098            case 'b':
     2099                pDrvReg = NULL;
     2100                for (uintptr_t i = 0; i < RT_ELEMENTS(g_aBackends); i++)
     2101                    if (   strcmp(ValueUnion.psz, g_aBackends[i].pszName) == 0
     2102                        || strcmp(ValueUnion.psz, g_aBackends[i].pDrvReg->szName) == 0)
     2103                    {
     2104                        pDrvReg = g_aBackends[i].pDrvReg;
     2105                        break;
     2106                    }
     2107                if (pDrvReg == NULL)
     2108                    return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown backend: '%s'", ValueUnion.psz);
     2109                break;
     2110
     2111            case VINF_GETOPT_NOT_OPTION:
     2112            {
     2113                RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, cMsBufferSize, cMsPreBuffer, cMsSchedulingHint);
     2114                if (rcExit != RTEXITCODE_SUCCESS)
     2115                    return rcExit;
     2116                break;
     2117            }
     2118
     2119            case 'V':
     2120                return audioTestVersion();
     2121            case 'h':
     2122                return audioTestUsage(g_pStdOut);
     2123
     2124            default:
     2125                return RTGetOptPrintError(rc, &ValueUnion);
     2126        }
     2127    }
     2128    return RTEXITCODE_SUCCESS;
     2129}
     2130
     2131
     2132/**
     2133 * Commands.
     2134 */
     2135static struct
     2136{
     2137    /** The command name. */
     2138    const char     *pszCommand;
     2139    /** The command handler.   */
     2140    DECLCALLBACKMEMBER(RTEXITCODE, pfnHandler,(int argc, char **argv));
     2141
     2142    /** Command description.   */
     2143    const char     *pszDesc;
     2144    /** Options array.  */
     2145    PCRTGETOPTDEF   paOptions;
     2146    /** Number of options in the option array. */
     2147    size_t          cOptions;
     2148    /** Gets help for an option. */
     2149    DECLCALLBACKMEMBER(const char *, pfnOptionHelp,(PCRTGETOPTDEF pOpt));
     2150} const g_aCommands[] =
     2151{
     2152    {
     2153        "test",     audioTestMain,
     2154        "Does some kind of testing, I guess...",
     2155        g_aCmdTestOptions,      RT_ELEMENTS(g_aCmdTestOptions),     audioTestCmdTestHelp
     2156    },
     2157    {
     2158        "verify",   audioVerifyMain,
     2159        "Verfies something, I guess...",
     2160        g_aCmdVerifyOptions,    RT_ELEMENTS(g_aCmdVerifyOptions),   NULL,
     2161    },
     2162    {
     2163        "play",     audioTestCmdPlayHandler,
     2164        "Plays one or more wave files.",
     2165        g_aCmdPlayOptions,      RT_ELEMENTS(g_aCmdPlayOptions),     audioTestCmdPlayHelp,
     2166    },
     2167};
     2168
     2169/**
     2170 * Shows tool usage text.
     2171 */
     2172static RTEXITCODE audioTestUsage(PRTSTREAM pStrm)
     2173{
     2174    RTStrmPrintf(pStrm, "usage: %s [global options] <command> [command-options]\n",
     2175                 RTPathFilename(RTProcExecutablePath()));
     2176    RTStrmPrintf(pStrm,
     2177                 "\n"
     2178                 "Global Options:\n"
     2179                 "  -q, --quiet\n"
     2180                 "    Sets verbosity to zero.\n"
     2181                 "  -v, --verbose\n"
     2182                 "    Increase verbosity.\n"
     2183                 "  -V, --version\n"
     2184                 "    Displays version.\n"
     2185                 "  -h, -?, --help\n"
     2186                 "    Displays help.\n"
     2187                 );
     2188
     2189    for (uintptr_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++)
     2190    {
     2191        RTStrmPrintf(pStrm,
     2192                     "\n"
     2193                     "Command '%s':\n"
     2194                     "    %s\n"
     2195                     "Options for '%s':\n",
     2196                     g_aCommands[iCmd].pszCommand, g_aCommands[iCmd].pszDesc, g_aCommands[iCmd].pszCommand);
     2197        PCRTGETOPTDEF const paOptions = g_aCommands[iCmd].paOptions;
     2198        for (unsigned i = 0; i < g_aCommands[iCmd].cOptions; i++)
     2199        {
     2200            if (RT_C_IS_PRINT(paOptions[i].iShort))
     2201                RTStrmPrintf(pStrm, "  -%c, %s\n", g_aCmdTestOptions[i].iShort, g_aCmdTestOptions[i].pszLong);
     2202            else
     2203                RTStrmPrintf(pStrm, "  %s\n", g_aCmdTestOptions[i].pszLong);
     2204
     2205            const char *pszHelp = NULL;
     2206            if (g_aCommands[i].pfnOptionHelp)
     2207                pszHelp = g_aCommands[i].pfnOptionHelp(&paOptions[i]);
     2208            if (pszHelp)
     2209                RTStrmPrintf(pStrm, "    %s\n", pszHelp);
     2210        }
     2211    }
     2212    return RTEXITCODE_SUCCESS;
     2213}
     2214
     2215/**
     2216 * Shows tool version.
     2217 */
     2218static RTEXITCODE audioTestVersion(void)
     2219{
     2220    RTPrintf("v0.0.1\n");
     2221    return RTEXITCODE_SUCCESS;
     2222}
     2223
     2224/**
     2225 * Shows the logo.
     2226 *
     2227 * @param   pStream             Output stream to show logo on.
     2228 */
     2229static void audioTestShowLogo(PRTSTREAM pStream)
     2230{
     2231    RTStrmPrintf(pStream, VBOX_PRODUCT " VKAT (Validation Kit Audio Test) Version " VBOX_VERSION_STRING " - r%s\n"
     2232                 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
     2233                 "All rights reserved.\n\n", RTBldCfgRevisionStr());
    12752234}
    12762235
     
    13062265
    13072266            case 'V':
    1308                 RTPrintf("v0.0.1\n");
    1309                 return RTEXITCODE_SUCCESS;
     2267                return audioTestVersion();
    13102268
    13112269            case 'h':
    13122270                audioTestShowLogo(g_pStdOut);
    1313                 audioTestUsage(g_pStdOut);
    1314                 return RTEXITCODE_SUCCESS;
     2271                return audioTestUsage(g_pStdOut);
    13152272
    13162273            case VINF_GETOPT_NOT_OPTION:
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette