Index: trunk/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp
===================================================================
--- trunk/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp	(revision 90721)
+++ trunk/src/VBox/ValidationKit/utils/audio/vkatCmdGeneric.cpp	(revision 90723)
@@ -31,4 +31,5 @@
 #include <iprt/errcore.h>
 #include <iprt/message.h>
+#include <iprt/rand.h>
 #include <iprt/test.h>
 
@@ -366,4 +367,102 @@
 }
 
+/**
+ * Worker for audioTestCmdPlayHandler that plays one test tone.
+ */
+static RTEXITCODE audioTestPlayTestToneOne(PAUDIOTESTTONEPARMS pToneParms,
+                                           PCPDMDRVREG pDrvReg, const char *pszDevId, uint32_t cMsBufferSize,
+                                           uint32_t cMsPreBuffer, uint32_t cMsSchedulingHint,
+                                           uint8_t cChannels, uint8_t cbSample, uint32_t uHz,
+                                           bool fWithDrvAudio, bool fWithMixer)
+{
+    char szTmp[128];
+
+    AUDIOTESTSTREAM TstStream;
+    RT_ZERO(TstStream);
+
+    /*
+     * Construct the driver stack.
+     */
+    RTEXITCODE          rcExit = RTEXITCODE_FAILURE;
+    AUDIOTESTDRVSTACK   DrvStack;
+    int rc = audioTestDriverStackInit(&DrvStack, pDrvReg, fWithDrvAudio);
+    if (RT_SUCCESS(rc))
+    {
+        /*
+         * Set the output device if one is specified.
+         */
+        rc = audioTestDriverStackSetDevice(&DrvStack, PDMAUDIODIR_OUT, pszDevId);
+        if (RT_SUCCESS(rc))
+        {
+            /*
+             * Open a stream for the output.
+             */
+            PDMAUDIOPCMPROPS ReqProps = pToneParms->Props;
+            if (cChannels != 0 && PDMAudioPropsChannels(&ReqProps) != cChannels)
+                PDMAudioPropsSetChannels(&ReqProps, cChannels);
+            if (cbSample != 0)
+                PDMAudioPropsSetSampleSize(&ReqProps, cbSample);
+            if (uHz != 0)
+                ReqProps.uHz = uHz;
+
+            rc = audioTestDriverStackStreamCreateOutput(&DrvStack, &ReqProps, cMsBufferSize,
+                                                        cMsPreBuffer, cMsSchedulingHint, &TstStream.pStream, &TstStream.Cfg);
+            if (RT_SUCCESS(rc))
+            {
+                /*
+                 * Automatically enable the mixer if the wave file and the
+                 * output parameters doesn't match.
+                 */
+                if (   !fWithMixer
+                    && !PDMAudioPropsAreEqual(&pToneParms->Props, &TstStream.pStream->Cfg.Props))
+                {
+                    RTMsgInfo("Enabling the mixer buffer.\n");
+                    fWithMixer = true;
+                }
+
+                /*
+                 * Create a mixer wrapper.  This is just a thin wrapper if fWithMixer
+                 * is false, otherwise it's doing mixing, resampling and recoding.
+                 */
+                rc = AudioTestMixStreamInit(&TstStream.Mix, &DrvStack, TstStream.pStream, fWithMixer ? &pToneParms->Props : NULL, 100 /*ms*/);
+                if (RT_SUCCESS(rc))
+                {
+                    if (g_uVerbosity > 0)
+                        RTMsgInfo("Stream: %s cbBackend=%#RX32%s\n",
+                                  PDMAudioPropsToString(&TstStream.pStream->Cfg.Props, szTmp, sizeof(szTmp)),
+                                  TstStream.pStream->cbBackend, fWithMixer ? " mixed" : "");
+
+                    /*
+                     * Enable the stream and start playing.
+                     */
+                    rc = AudioTestMixStreamEnable(&TstStream.Mix);
+                    if (RT_SUCCESS(rc))
+                    {
+                        rc = audioTestPlayTone(NULL /* pTstEnv */, &TstStream, pToneParms);
+                        if (RT_SUCCESS(rc))
+                            rcExit = RTEXITCODE_SUCCESS;
+                    }
+                    else
+                        rcExit = RTMsgErrorExitFailure("Enabling the output stream failed: %Rrc", rc);
+
+                    /*
+                     * Clean up.
+                     */
+                    AudioTestMixStreamTerm(&TstStream.Mix);
+                }
+                audioTestDriverStackStreamDestroy(&DrvStack, TstStream.pStream);
+            }
+            else
+                rcExit = RTMsgErrorExitFailure("Creating output stream failed: %Rrc", rc);
+        }
+        else
+            rcExit = RTMsgErrorExitFailure("Failed to set output device to '%s': %Rrc", pszDevId, rc);
+        audioTestDriverStackDelete(&DrvStack);
+    }
+    else
+        rcExit = RTMsgErrorExitFailure("Driver stack construction failed: %Rrc", rc);
+    return rcExit;
+}
+
 
 /**
@@ -377,4 +476,5 @@
     { "--frequency",        'f',                          RTGETOPT_REQ_UINT32 },
     { "--sample-size",      'z',                          RTGETOPT_REQ_UINT8 },
+    { "--test-tone",        't',                          RTGETOPT_REQ_NOTHING },
     { "--output-device",    'o',                          RTGETOPT_REQ_STRING  },
     { "--with-drv-audio",   'd',                          RTGETOPT_REQ_NOTHING },
@@ -388,11 +488,12 @@
     switch (pOpt->iShort)
     {
-        case 'b': return "The audio backend to use.";
+        case 'b': return "The audio backend to use";
         case 'c': return "Number of backend output channels";
-        case 'd': return "Go via DrvAudio instead of directly interfacing with the backend.";
+        case 'd': return "Go via DrvAudio instead of directly interfacing with the backend";
         case 'f': return "Output frequency (Hz)";
         case 'z': return "Output sample size (bits)";
-        case 'm': return "Go via the mixer.";
-        case 'o': return "The ID of the output device to use.";
+        case 't': return "Plays a test tone. Can be specified multiple times";
+        case 'm': return "Go via the mixer";
+        case 'o': return "The ID of the output device to use";
         default:  return NULL;
     }
@@ -416,4 +517,5 @@
     bool        fWithDrvAudio       = false;
     bool        fWithMixer          = false;
+    uint32_t    cTestTones          = 0;
     uint8_t     cbSample            = 0;
     uint8_t     cChannels           = 0;
@@ -453,4 +555,8 @@
                 break;
 
+            case 't':
+                cTestTones++;
+                break;
+
             case 'z':
                 cbSample = ValueUnion.u8 / 8;
@@ -459,4 +565,7 @@
             case VINF_GETOPT_NOT_OPTION:
             {
+                if (cTestTones)
+                    return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Playing test tones (-t) cannot be combined with playing files");
+
                 RTEXITCODE rcExit = audioTestPlayOne(ValueUnion.psz, pDrvReg, pszDevId, cMsBufferSize, cMsPreBuffer,
                                                      cMsSchedulingHint, cChannels, cbSample, uHz, fWithDrvAudio, fWithMixer);
@@ -472,4 +581,30 @@
         }
     }
+
+    while (cTestTones--)
+    {
+        AUDIOTESTTONEPARMS ToneParms;
+        RT_ZERO(ToneParms);
+
+        /* Use some sane defaults if no PCM props are set by the user. */
+        PDMAudioPropsInit(&ToneParms.Props,
+                          cbSample ? cbSample : 4, true /* fSigned */, cChannels ? cChannels : 2, uHz ? uHz : 44100);
+
+        ToneParms.dbFreqHz       = AudioTestToneGetRandomFreq();
+        ToneParms.msPrequel      = 0; /** @todo Implement analyzing this first! */
+#ifdef DEBUG_andy
+        ToneParms.msDuration     = RTRandU32Ex(50, 2500);
+#else
+        ToneParms.msDuration     = RTRandU32Ex(0, RT_MS_10SEC); /** @todo Probably a bit too long, but let's see. */
+#endif
+        ToneParms.msSequel       = 0;   /** @todo Implement analyzing this first! */
+        ToneParms.uVolumePercent = 100; /** @todo Implement analyzing this first! */
+
+        RTEXITCODE rcExit = audioTestPlayTestToneOne(&ToneParms, pDrvReg, pszDevId, cMsBufferSize, cMsPreBuffer,
+                                                     cMsSchedulingHint, cChannels, cbSample, uHz, fWithDrvAudio, fWithMixer);
+        if (rcExit != RTEXITCODE_SUCCESS)
+            return rcExit;
+    }
+
     return RTEXITCODE_SUCCESS;
 }
Index: trunk/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp
===================================================================
--- trunk/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp	(revision 90721)
+++ trunk/src/VBox/ValidationKit/utils/audio/vkatCommon.cpp	(revision 90723)
@@ -239,4 +239,5 @@
  * @returns VBox status code.
  * @param   pTstEnv             Test environment to use for running the test.
+ *                              Optional and can be NULL (for simple playback only).
  * @param   pStream             Stream to use for playing the tone.
  * @param   pParms              Tone parameters to use.
@@ -244,19 +245,27 @@
  * @note    Blocking function.
  */
-static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
+int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
 {
     AUDIOTESTTONE TstTone;
     AudioTestToneInit(&TstTone, &pStream->Cfg.Props, pParms->dbFreqHz);
 
-    const char *pcszPathOut = pTstEnv->Set.szPathAbs;
+    char const *pcszPathOut = NULL;
+    if (pTstEnv)
+        pcszPathOut = pTstEnv->Set.szPathAbs;
 
     RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Playing test tone (tone frequency is %RU16Hz, %RU32ms)\n", (uint16_t)pParms->dbFreqHz, pParms->msDuration);
     RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using %RU32ms stream scheduling hint\n", pStream->Cfg.Device.cMsSchedulingHint);
-    RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Writing to '%s'\n", pcszPathOut);
+    if (pcszPathOut)
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Writing to '%s'\n", pcszPathOut);
+
+    int rc;
 
     /** @todo Use .WAV here? */
     AUDIOTESTOBJ Obj;
-    int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-play.pcm", &Obj);
-    AssertRCReturn(rc, rc);
+    if (pTstEnv)
+    {
+        rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "guest-tone-play.pcm", &Obj);
+        AssertRCReturn(rc, rc);
+    }
 
     rc = AudioTestMixStreamEnable(&pStream->Mix);
@@ -271,11 +280,14 @@
         RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Playing %RU32 bytes total\n", cbToPlayTotal);
 
-        AudioTestObjAddMetadataStr(Obj, "stream_to_play_bytes=%RU32\n",      cbToPlayTotal);
-        AudioTestObjAddMetadataStr(Obj, "stream_period_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPeriod);
-        AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesBufferSize);
-        AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPreBuffering);
-        /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and
-         *       has nothing to do with the device emulation scheduling hint. */
-        AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pStream->Cfg.Device.cMsSchedulingHint);
+        if (pTstEnv)
+        {
+            AudioTestObjAddMetadataStr(Obj, "stream_to_play_bytes=%RU32\n",      cbToPlayTotal);
+            AudioTestObjAddMetadataStr(Obj, "stream_period_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPeriod);
+            AudioTestObjAddMetadataStr(Obj, "stream_buffer_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesBufferSize);
+            AudioTestObjAddMetadataStr(Obj, "stream_prebuf_size_frames=%RU32\n", pStream->Cfg.Backend.cFramesPreBuffering);
+            /* Note: This mostly is provided by backend (e.g. PulseAudio / ALSA / ++) and
+             *       has nothing to do with the device emulation scheduling hint. */
+            AudioTestObjAddMetadataStr(Obj, "device_scheduling_hint_ms=%RU32\n", pStream->Cfg.Device.cMsSchedulingHint);
+        }
 
         PAUDIOTESTDRVMIXSTREAM pMix = &pStream->Mix;
@@ -309,6 +321,9 @@
                 if (RT_SUCCESS(rc))
                 {
-                    /* Write stuff to disk before trying to play it. Help analysis later. */
-                    rc = AudioTestObjWrite(Obj, abBuf, cbToPlay);
+                    if (pTstEnv)
+                    {
+                        /* Write stuff to disk before trying to play it. Help analysis later. */
+                        rc = AudioTestObjWrite(Obj, abBuf, cbToPlay);
+                    }
                     if (RT_SUCCESS(rc))
                     {
@@ -342,7 +357,10 @@
         rc = VERR_AUDIO_STREAM_NOT_READY;
 
-    int rc2 = AudioTestObjClose(Obj);
-    if (RT_SUCCESS(rc))
-        rc = rc2;
+    if (pTstEnv)
+    {
+        int rc2 = AudioTestObjClose(Obj);
+        if (RT_SUCCESS(rc))
+            rc = rc2;
+    }
 
     if (RT_FAILURE(rc))
Index: trunk/src/VBox/ValidationKit/utils/audio/vkatInternal.h
===================================================================
--- trunk/src/VBox/ValidationKit/utils/audio/vkatInternal.h	(revision 90721)
+++ trunk/src/VBox/ValidationKit/utils/audio/vkatInternal.h	(revision 90723)
@@ -463,4 +463,8 @@
 int         audioTestWorker(PAUDIOTESTENV pTstEnv);
 
+/** @todo Test tone handling */
+int         audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
+/** @}  */
+
 /** @name Command handlers
  * @{ */
