Index: /trunk/src/VBox/Devices/Audio/AudioTest.cpp
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTest.cpp	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTest.cpp	(revision 89431)
@@ -546,5 +546,5 @@
 {
     PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
-    AssertReturn(pEntry, VERR_NO_MEMORY);
+    AssertPtrReturn(pEntry, VERR_NO_MEMORY);
 
     if (RTStrPrintf2V(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc, args) < 0)
@@ -633,4 +633,15 @@
 {
     return RTPathJoin(pszPathAbs, cbPathAbs, pSet->szPathAbs, pszObjName);
+}
+
+/**
+ * Returns the tag of a test set.
+ *
+ * @returns Test set tag.
+ * @param   pSet                Test set to return tag for.
+ */
+const char *AudioTestSetGetTag(PAUDIOTESTSET pSet)
+{
+    return pSet->szTag;
 }
 
@@ -746,4 +757,24 @@
         if (RT_SUCCESS(rc))
         {
+            PAUDIOTESTOBJMETA pMeta, pMetaNext;
+            RTListForEachSafe(&pObj->lstMeta, pMeta, pMetaNext, AUDIOTESTOBJMETA, Node)
+            {
+                switch (pMeta->enmType)
+                {
+                    case AUDIOTESTOBJMETADATATYPE_STRING:
+                    {
+                        RTStrFree((char *)pMeta->pvMeta);
+                        break;
+                    }
+
+                    default:
+                        AssertFailed();
+                        break;
+                }
+
+                RTListNodeRemove(&pMeta->Node);
+                RTMemFree(pMeta);
+            }
+
             RTListNodeRemove(&pObj->Node);
             RTMemFree(pObj);
@@ -870,4 +901,39 @@
         rc = audioTestManifestWrite(pSet, "obj_name=%s\n", pObj->szName);
         AssertRCReturn(rc, rc);
+
+        switch (pObj->enmType)
+        {
+            case AUDIOTESTOBJTYPE_FILE:
+            {
+                rc = audioTestManifestWrite(pSet, "obj_size=%RU64\n", pObj->File.cbSize);
+                AssertRCReturn(rc, rc);
+                break;
+            }
+
+            default:
+                AssertFailed();
+                break;
+        }
+
+        /*
+         * Write all meta data.
+         */
+        PAUDIOTESTOBJMETA pMeta;
+        RTListForEach(&pObj->lstMeta, pMeta, AUDIOTESTOBJMETA, Node)
+        {
+            switch (pMeta->enmType)
+            {
+                case AUDIOTESTOBJMETADATATYPE_STRING:
+                {
+                    rc = audioTestManifestWrite(pSet, (const char *)pMeta->pvMeta);
+                    AssertRCReturn(rc, rc);
+                    break;
+                }
+
+                default:
+                    AssertFailed();
+                    break;
+            }
+        }
     }
 
@@ -938,4 +1004,6 @@
     AssertPtrReturn(pObj, VERR_NO_MEMORY);
 
+    RTListInit(&pObj->lstMeta);
+
     if (RTStrPrintf2(pObj->szName, sizeof(pObj->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
         AssertFailedReturn(VERR_BUFFER_OVERFLOW);
@@ -996,4 +1064,47 @@
 
 /**
+ * Adds meta data to a test object as a string, va_list version.
+ *
+ * @returns VBox status code.
+ * @param   pObj                Test object to add meta data for.
+ * @param   pszFormat           Format string to add.
+ * @param   va_list             Variable arguments list to use for the format string.
+ */
+static int audioTestSetObjAddMetadataStrV(PAUDIOTESTOBJ pObj, const char *pszFormat, va_list va)
+{
+    PAUDIOTESTOBJMETA pMeta = (PAUDIOTESTOBJMETA)RTMemAlloc(sizeof(AUDIOTESTOBJMETA));
+    AssertPtrReturn(pMeta, VERR_NO_MEMORY);
+
+    pMeta->pvMeta = RTStrAPrintf2V(pszFormat, va);
+    AssertPtrReturn(pMeta->pvMeta, VERR_BUFFER_OVERFLOW);
+    pMeta->cbMeta = RTStrNLen((const char *)pMeta->pvMeta, RTSTR_MAX);
+
+    pMeta->enmType = AUDIOTESTOBJMETADATATYPE_STRING;
+
+    RTListAppend(&pObj->lstMeta, &pMeta->Node);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Adds meta data to a test object as a string.
+ *
+ * @returns VBox status code.
+ * @param   pObj                Test object to add meta data for.
+ * @param   pszFormat           Format string to add.
+ * @param   ...                 Variable arguments for the format string.
+ */
+int AudioTestSetObjAddMetadataStr(PAUDIOTESTOBJ pObj, const char *pszFormat, ...)
+{
+    va_list va;
+
+    va_start(va, pszFormat);
+    int rc = audioTestSetObjAddMetadataStrV(pObj, pszFormat, va);
+    va_end(va);
+
+    return rc;
+}
+
+/**
  * Closes an opened audio test object.
  *
@@ -1013,4 +1124,6 @@
     if (RTFileIsValid(pObj->File.hFile))
     {
+        pObj->File.cbSize = RTFileTell(pObj->File.hFile);
+
         rc = RTFileClose(pObj->File.hFile);
         pObj->File.hFile = NIL_RTFILE;
@@ -1020,4 +1133,13 @@
 }
 
+/**
+ * Begins a new test of a test set.
+ *
+ * @returns VBox status code.
+ * @param   pSet                Test set to begin new test for.
+ * @param   pszDesc             Test description.
+ * @param   pParms              Test parameters to use.
+ * @param   ppEntry             Where to return the new test handle.
+ */
 int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
 {
@@ -1095,4 +1217,12 @@
 }
 
+/**
+ * Marks a running test as failed.
+ *
+ * @returns VBox status code.
+ * @param   pEntry              Test to mark.
+ * @param   rc                  Error code.
+ * @param   pszErr              Error description.
+ */
 int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr)
 {
@@ -1113,4 +1243,10 @@
 }
 
+/**
+ * Marks a running test as successfully done.
+ *
+ * @returns VBox status code.
+ * @param   pEntry              Test to mark.
+ */
 int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
 {
@@ -1143,4 +1279,8 @@
     /** @todo Check and deny if \a pszOutDir is part of the set's path. */
 
+    int rc = RTDirCreateFullPath(pszOutDir, 0755);
+    if (RT_FAILURE(rc))
+        return rc;
+
     char szOutName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 16];
     if (RTStrPrintf2(szOutName, sizeof(szOutName), "%s-%s%s",
@@ -1149,5 +1289,5 @@
 
     char szOutPath[RTPATH_MAX];
-    int rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
+    rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
     AssertRCReturn(rc, rc);
 
@@ -1155,5 +1295,5 @@
     unsigned    cArgs = 0;
 
-    apszArgs[cArgs++] = "AudioTest";
+    apszArgs[cArgs++] = "vkat";
     apszArgs[cArgs++] = "--create";
     apszArgs[cArgs++] = "--gzip";
@@ -1214,5 +1354,5 @@
     unsigned    cArgs = 0;
 
-    apszArgs[cArgs++] = "AudioTest";
+    apszArgs[cArgs++] = "vkat";
     apszArgs[cArgs++] = "--extract";
     apszArgs[cArgs++] = "--gunzip";
Index: /trunk/src/VBox/Devices/Audio/AudioTest.h
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTest.h	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTest.h	(revision 89431)
@@ -170,8 +170,41 @@
 typedef struct AUDIOTESTOBJFILE
 {
-    RTFILE hFile;
+    /** File handle. */
+    RTFILE              hFile;
+    /** Total size (in bytes). */
+    size_t              cbSize;
 } AUDIOTESTOBJFILE;
 /** Pointer to an audio test object file. */
 typedef AUDIOTESTOBJFILE *PAUDIOTESTOBJFILE;
+
+/**
+ * Enumeration for an audio test object meta data type.
+ */
+typedef enum AUDIOTESTOBJMETADATATYPE
+{
+    /** Unknown / invalid, do not use. */
+    AUDIOTESTOBJMETADATATYPE_INVALID = 0,
+    /** Meta data is an UTF-8 string. */
+    AUDIOTESTOBJMETADATATYPE_STRING,
+    /** The usual 32-bit hack. */
+    AUDIOTESTOBJMETADATATYPE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTOBJMETADATATYPE;
+
+/**
+ * Structure for keeping a meta data block.
+ */
+typedef struct AUDIOTESTOBJMETA
+{
+    /** List node. */
+    RTLISTNODE                Node;
+    /** Meta data type. */
+    AUDIOTESTOBJMETADATATYPE  enmType;
+    /** Meta data block. */
+    void                     *pvMeta;
+    /** Size (in bytes) of \a pvMeta. */
+    size_t                    cbMeta;
+} AUDIOTESTOBJMETA;
+/** Pointer to an audio test object file. */
+typedef AUDIOTESTOBJMETA *PAUDIOTESTOBJMETA;
 
 /**
@@ -193,4 +226,6 @@
     /** The object type. */
     AUDIOTESTOBJTYPE     enmType;
+    /** Meta data list. */
+    RTLISTANCHOR         lstMeta;
     /** Union for holding the object type-specific data. */
     union
@@ -323,4 +358,5 @@
 int    AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ *ppObj);
 int    AudioTestSetObjWrite(PAUDIOTESTOBJ pObj, void *pvBuf, size_t cbBuf);
+int    AudioTestSetObjAddMetadataStr(PAUDIOTESTOBJ pObj, const char *pszFormat, ...);
 int    AudioTestSetObjClose(PAUDIOTESTOBJ pObj);
 
@@ -334,4 +370,5 @@
 int    AudioTestSetClose(PAUDIOTESTSET pSet);
 int    AudioTestSetWipe(PAUDIOTESTSET pSet);
+const char *AudioTestSetGetTag(PAUDIOTESTSET pSet);
 bool   AudioTestSetIsPacked(const char *pszPath);
 int    AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName);
Index: /trunk/src/VBox/Devices/Audio/AudioTestService.cpp
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTestService.cpp	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTestService.cpp	(revision 89431)
@@ -474,5 +474,7 @@
     int rc;
     if (pPktHdr->cb == sizeof(ATSPKTHDR))
+    {
         rc = atsReplyAck(pThis, pClient, pPktHdr);
+    }
     else
         rc = atsReplyBadSize(pThis, pClient, pPktHdr, sizeof(ATSPKTHDR));
@@ -514,4 +516,71 @@
         pClient->enmState = ATSCLIENTSTATE_READY;
     }
+
+    return rc;
+}
+
+/**
+ * Verifies and acknowledges a "TSET BEG" request.
+ *
+ * @returns IPRT status code.
+ * @param   pThis               The ATS instance.
+ * @param   pClient             The ATS client structure.
+ * @param   pPktHdr             The test set begin packet.
+ */
+static int atsDoTestSetBegin(PATSSERVER pThis, PATSCLIENTINST pClient, PCATSPKTHDR pPktHdr)
+{
+    if (pPktHdr->cb != sizeof(ATSPKTREQTSETBEG))
+        return atsReplyBadSize(pThis, pClient, pPktHdr, sizeof(ATSPKTREQTSETBEG));
+
+    PATSPKTREQTSETBEG pReq = (PATSPKTREQTSETBEG)pPktHdr;
+
+    int rc = VINF_SUCCESS;
+
+    if (pThis->Callbacks.pfnTestSetBegin)
+    {
+        rc = pThis->Callbacks.pfnTestSetBegin(pThis->Callbacks.pvUser, pReq->szTag);
+        if (RT_FAILURE(rc))
+            return atsReplyRC(pThis, pClient, pPktHdr, rc, "Beginning test set '%s' failed", pReq->szTag);
+    }
+
+    if (RT_SUCCESS(rc))
+    {
+        rc = atsReplyAck(pThis, pClient, pPktHdr);
+    }
+    else
+        rc = atsReplyRC(pThis, pClient, pPktHdr, rc, "Beginning test set failed");
+
+    return rc;
+}
+
+/**
+ * Verifies and acknowledges a "TSET END" request.
+ *
+ * @returns IPRT status code.
+ * @param   pThis               The ATS instance.
+ * @param   pClient             The ATS client structure.
+ * @param   pPktHdr             The test set end packet.
+ */
+static int atsDoTestSetEnd(PATSSERVER pThis, PATSCLIENTINST pClient, PCATSPKTHDR pPktHdr)
+{
+    if (pPktHdr->cb != sizeof(ATSPKTREQTSETEND))
+        return atsReplyBadSize(pThis, pClient, pPktHdr, sizeof(ATSPKTREQTSETEND));
+
+    PATSPKTREQTSETEND pReq = (PATSPKTREQTSETEND)pPktHdr;
+
+    int rc = VINF_SUCCESS;
+
+    if (pThis->Callbacks.pfnTestSetEnd)
+    {
+        rc = pThis->Callbacks.pfnTestSetEnd(pThis->Callbacks.pvUser, pReq->szTag);
+        if (RT_FAILURE(rc))
+            return atsReplyRC(pThis, pClient, pPktHdr, rc, "Ending test set '%s' failed", pReq->szTag);
+    }
+    if (RT_SUCCESS(rc))
+    {
+        rc = atsReplyAck(pThis, pClient, pPktHdr);
+    }
+    else
+        rc = atsReplyRC(pThis, pClient, pPktHdr, rc, "Ending test set failed");
 
     return rc;
@@ -574,4 +643,9 @@
     else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_BYE))
         rc = atsDoBye(pThis, pClient, pPktHdr);
+    /* Test set handling: */
+    else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TESTSET_BEGIN))
+        rc = atsDoTestSetBegin(pThis, pClient, pPktHdr);
+    else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TESTSET_END))
+        rc = atsDoTestSetEnd(pThis, pClient, pPktHdr);
     /* Audio testing: */
     else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TONE_PLAY))
Index: /trunk/src/VBox/Devices/Audio/AudioTestService.h
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTestService.h	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTestService.h	(revision 89431)
@@ -33,4 +33,22 @@
 typedef struct ATSCALLBACKS
 {
+    /**
+     * Begins a test set. Optional.
+     *
+     * @returns VBox status code.
+     * @param   pvUser          User-supplied pointer to context data. Optional.
+     * @param   pszTag          Tag of test set to begin.
+     */
+    DECLR3CALLBACKMEMBER(int, pfnTestSetBegin, (void const *pvUser, const char *pszTag));
+
+    /**
+     * Ends the current test set. Optional.
+     *
+     * @returns VBox status code.
+     * @param   pvUser          User-supplied pointer to context data. Optional.
+     * @param   pszTag          Tag of test set to end.
+     */
+    DECLR3CALLBACKMEMBER(int, pfnTestSetEnd,   (void const *pvUser, const char *pszTag));
+
     /**
      * Plays a test tone.
@@ -79,5 +97,4 @@
 typedef ATSSERVER *PATSSERVER;
 
-
 int AudioTestSvcInit(PATSSERVER pThis, PCATSCALLBACKS pCallbacks);
 int AudioTestSvcDestroy(PATSSERVER pThis);
Index: /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.cpp
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.cpp	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.cpp	(revision 89431)
@@ -305,4 +305,50 @@
 
 /**
+ * Tells the server to begin a new test set.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to issue command for.
+ * @param   pszTag              Tag to use for the test set to begin.
+ */
+int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
+{
+    ATSPKTREQTSETBEG Req;
+
+    int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
+    AssertRCReturn(rc, rc);
+
+    audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
+
+    rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
+    if (RT_SUCCESS(rc))
+        rc = audioTestSvcClientRecvAck(pClient);
+
+    return rc;
+}
+
+/**
+ * Tells the server to end a runing test set.
+ *
+ * @returns VBox status code.
+ * @param   pClient             Client to issue command for.
+ * @param   pszTag              Tag of test set to end.
+ */
+int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
+{
+    ATSPKTREQTSETEND Req;
+
+    int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
+    AssertRCReturn(rc, rc);
+
+    audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
+
+    rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
+    if (RT_SUCCESS(rc))
+        rc = audioTestSvcClientRecvAck(pClient);
+
+    return rc;
+}
+
+/**
  * Tells the server to play a (test) tone.
  *
Index: /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.h
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.h	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTestServiceClient.h	(revision 89431)
@@ -31,4 +31,6 @@
 
 int AudioTestSvcClientConnect(PATSCLIENT pClient, const char *pszAddr, uint32_t uPort);
+int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag);
+int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag);
 int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PPDMAUDIOSTREAMCFG pStreamCfg, PAUDIOTESTTONEPARMS pToneParms);
 int AudioTestSvcClientClose(PATSCLIENT pClient);
Index: /trunk/src/VBox/Devices/Audio/AudioTestServiceProtocol.h
===================================================================
--- /trunk/src/VBox/Devices/Audio/AudioTestServiceProtocol.h	(revision 89430)
+++ /trunk/src/VBox/Devices/Audio/AudioTestServiceProtocol.h	(revision 89431)
@@ -121,4 +121,36 @@
 /* No additional structures for BYE. */
 
+#define ATSPKT_OPCODE_TESTSET_BEGIN     "TSET BEG"
+
+/**
+ * The TSET BEG request structure.
+ */
+typedef struct ATSPKTREQTSETBEG
+{
+    /** Embedded packet header. */
+    ATSPKTHDR          Hdr;
+    /** Audio test set tag to use. */
+    char               szTag[AUDIOTEST_TAG_MAX];
+} ATSPKTREQTSETBEG;
+AssertCompileSizeAlignment(ATSPKTREQTSETBEG, ATSPKT_ALIGNMENT);
+/** Pointer to a TSET BEG reply structure. */
+typedef ATSPKTREQTSETBEG *PATSPKTREQTSETBEG;
+
+#define ATSPKT_OPCODE_TESTSET_END       "TSET END"
+
+/**
+ * The TSET END request structure.
+ */
+typedef struct ATSPKTREQTSETEND
+{
+    /** Embedded packet header. */
+    ATSPKTHDR          Hdr;
+    /** Audio test set tag to use. */
+    char               szTag[AUDIOTEST_TAG_MAX];
+} ATSPKTREQTSETEND;
+AssertCompileSizeAlignment(ATSPKTREQTSETEND, ATSPKT_ALIGNMENT);
+/** Pointer to a TSET STA reply structure. */
+typedef ATSPKTREQTSETEND *PATSPKTREQTSETEND;
+
 #define ATSPKT_OPCODE_TONE_PLAY         "TNPLY   "
 
Index: /trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp
===================================================================
--- /trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp	(revision 89430)
+++ /trunk/src/VBox/ValidationKit/utils/audio/vkat.cpp	(revision 89431)
@@ -179,4 +179,6 @@
     AUDIOTESTMODE           enmMode;
     /** Output path for storing the test environment's final test files. */
+    char                    szTag[AUDIOTEST_TAG_MAX];
+    /** Output path for storing the test environment's final test files. */
     char                    szPathOut[RTPATH_MAX];
     /** Temporary path for this test environment. */
@@ -235,6 +237,8 @@
 *********************************************************************************************************************************/
 static int audioTestCombineParms(PAUDIOTESTPARMS pBaseParms, PAUDIOTESTPARMS pOverrideParms);
-static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms);
+static int audioTestCreateStreamDefaultOut(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PPDMAUDIOPCMPROPS pProps);
 static int audioTestStreamDestroy(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream);
+static int audioTestDevicesEnumerateAndCheck(PAUDIOTESTENV pTstEnv, const char *pszDev, PPDMAUDIOHOSTDEV *ppDev);
+static int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv);
 
 static RTEXITCODE audioTestUsage(PRTSTREAM pStrm);
@@ -393,52 +397,147 @@
 
 /*********************************************************************************************************************************
+*   Test Primitives                                                                                                              *
+*********************************************************************************************************************************/
+
+/**
+ * Plays a test tone on a specific audio test stream.
+ *
+ * @returns VBox status code.
+ * @param   pTstEnv             Test environment to use for running the test.
+ * @param   pStream             Stream to use for playing the tone.
+ * @param   pParms              Tone parameters to use.
+ *
+ * @note    Blocking function.
+ */
+static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
+{
+    AUDIOTESTTONE TstTone;
+    AudioTestToneInit(&TstTone, &pParms->Props, pParms->dbFreqHz);
+
+    const char *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_DEBUG,  "Writing to '%s'\n", pcszPathOut);
+
+    /** @todo Use .WAV here? */
+    PAUDIOTESTOBJ pObj;
+    int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "tone-play.pcm", &pObj);
+    AssertRCReturn(rc, rc);
+
+    if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream->pStream))
+    {
+        uint32_t cbBuf;
+        uint8_t  abBuf[_4K];
+
+        const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Choose a random scheduling (in ms). */
+        const uint32_t cbPerMs       = PDMAudioPropsMilliToBytes(&pParms->Props, cSchedulingMs);
+                size_t cbToWrite     = PDMAudioPropsMilliToBytes(&pParms->Props, pParms->msDuration);
+
+        AudioTestSetObjAddMetadataStr(pObj, "schedule_ms=%RU16", cSchedulingMs);
+
+        while (cbToWrite)
+        {
+            uint32_t cbWritten    = 0;
+            uint32_t cbToGenerate = RT_MIN(cbToWrite, RT_MIN(cbPerMs, sizeof(abBuf)));
+            Assert(cbToGenerate);
+
+            rc = AudioTestToneGenerate(&TstTone, abBuf, cbToGenerate, &cbBuf);
+            if (RT_SUCCESS(rc))
+            {
+                /* Write stuff to disk before trying to play it. Help analysis later. */
+                rc = AudioTestSetObjWrite(pObj, abBuf, cbBuf);
+                if (RT_SUCCESS(rc))
+                    rc = audioTestDriverStackStreamPlay(&pTstEnv->DrvStack, pStream->pStream,
+                                                        abBuf, cbBuf, &cbWritten);
+            }
+
+            if (RT_FAILURE(rc))
+                break;
+
+            RTThreadSleep(cSchedulingMs);
+
+            Assert(cbToWrite >= cbWritten);
+            cbToWrite -= cbWritten;
+        }
+    }
+    else
+        rc = VERR_AUDIO_STREAM_NOT_READY;
+
+    int rc2 = AudioTestSetObjClose(pObj);
+    if (RT_SUCCESS(rc))
+        rc = rc2;
+
+    RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Play tone done\n");
+
+    return rc;
+}
+
+/*********************************************************************************************************************************
 *   ATS Callback Implementations                                                                                                 *
 *********************************************************************************************************************************/
 
-/**
- * Note: Called within server (client serving) thread.
- */
-static DECLCALLBACK(int) audioTestSvcTonePlayCallback(void const *pvUser, PPDMAUDIOSTREAMCFG pStreamCfg, PAUDIOTESTTONEPARMS pToneParms)
+/** @copydoc ATSCALLBACKS::pfnTestSetBegin */
+static DECLCALLBACK(int) audioTestSvcTestSetBeginCallback(void const *pvUser, const char *pszTag)
 {
     PATSCALLBACKCTX pCtx    = (PATSCALLBACKCTX)pvUser;
     PAUDIOTESTENV   pTstEnv = pCtx->pTstEnv;
 
+    RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Beginning test set '%s'\n", pszTag);
+
+    return AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
+}
+
+/** @copydoc ATSCALLBACKS::pfnTestSetEnd */
+static DECLCALLBACK(int) audioTestSvcTestSetEndCallback(void const *pvUser, const char *pszTag)
+{
+    RT_NOREF(pszTag);
+
+    PATSCALLBACKCTX pCtx    = (PATSCALLBACKCTX)pvUser;
+    PAUDIOTESTENV   pTstEnv = pCtx->pTstEnv;
+
+    RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Ending test set '%s'\n", pszTag);
+
+    return audioTestEnvPrologue(pTstEnv);
+}
+
+/** @copydoc ATSCALLBACKS::pfnTonePlay */
+static DECLCALLBACK(int) audioTestSvcTonePlayCallback(void const *pvUser, PPDMAUDIOSTREAMCFG pStreamCfg, PAUDIOTESTTONEPARMS pToneParms)
+{
+    PATSCALLBACKCTX pCtx    = (PATSCALLBACKCTX)pvUser;
+    PAUDIOTESTENV   pTstEnv = pCtx->pTstEnv;
+
     AUDIOTESTTONE TstTone;
     AudioTestToneInitRandom(&TstTone, &pStreamCfg->Props);
 
-    int rc;
-
-    const PPDMAUDIOSTREAM pStream = pTstEnv->aStreams[0].pStream; /** @todo Make this dynamic. */
-
-    if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream))
-    {
-        uint32_t cbBuf;
-        uint8_t  abBuf[_4K];
-
-        const uint64_t tsStartMs     = RTTimeMilliTS();
-        const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Chose a random scheduling (in ms). */
-        const uint32_t cbPerMs       = PDMAudioPropsMilliToBytes(&pStream->Props, cSchedulingMs);
-
-        do
-        {
-            rc = AudioTestToneGenerate(&TstTone, abBuf, RT_MIN(cbPerMs, sizeof(abBuf)), &cbBuf);
+    const PAUDIOTESTSTREAM pTstStream = &pTstEnv->aStreams[0]; /** @todo Make this dynamic. */
+
+    int rc = audioTestCreateStreamDefaultOut(pTstEnv, pTstStream, &pStreamCfg->Props);
+    if (RT_SUCCESS(rc))
+    {
+        AUDIOTESTPARMS TstParms;
+        RT_ZERO(TstParms);
+        TstParms.enmType  = AUDIOTESTTYPE_TESTTONE_PLAY;
+        TstParms.enmDir   = PDMAUDIODIR_OUT;
+        TstParms.TestTone = *pToneParms;
+
+        PAUDIOTESTENTRY pTst;
+        rc = AudioTestSetTestBegin(&pTstEnv->Set, "Playing test tone", &TstParms, &pTst);
+        if (RT_SUCCESS(rc))
+        {
+            rc = audioTestPlayTone(pTstEnv, pTstStream, pToneParms);
             if (RT_SUCCESS(rc))
             {
-                uint32_t cbWritten;
-                rc = audioTestDriverStackStreamPlay(&pTstEnv->DrvStack, pStream, abBuf, cbBuf, &cbWritten);
+                AudioTestSetTestDone(pTst);
             }
-
-            if (RTTimeMilliTS() - tsStartMs >= pToneParms->msDuration)
-                break;
-
-            if (RT_FAILURE(rc))
-                break;
-
-            RTThreadSleep(cSchedulingMs);
-
-        } while (RT_SUCCESS(rc));
+            else
+                AudioTestSetTestFailed(pTst, rc, "Playing tone failed");
+        }
+
+        int rc2 = audioTestStreamDestroy(pTstEnv, pTstStream);
+        if (RT_SUCCESS(rc))
+            rc = rc2;
     }
     else
-        rc = VERR_AUDIO_STREAM_NOT_READY;
+        RTTestFailed(g_hTest, "Error creating output stream, rc=%Rrc\n", rc);
 
     return rc;
@@ -464,10 +563,4 @@
                             const char *pszTcpAddr, uint32_t uTcpPort)
 {
-    PDMAudioHostEnumInit(&pTstEnv->DevEnum);
-
-    int rc = audioTestDriverStackInit(&pTstEnv->DrvStack, pDrvReg, fWithDrvAudio);
-    if (RT_FAILURE(rc))
-        return rc;
-
     RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test mode is '%s'\n", pTstEnv->enmMode == AUDIOTESTMODE_HOST ? "host" : "guest");
     RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Using tag '%s'\n", pszTag);
@@ -475,4 +568,22 @@
     RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Temp directory is '%s'\n", pTstEnv->szPathTemp);
 
+    int rc = VINF_SUCCESS;
+
+    PDMAudioHostEnumInit(&pTstEnv->DevEnum);
+
+    /* Only the guest mode needs initializing the driver stack. */
+    const bool fUseDriverStack = pTstEnv->enmMode == AUDIOTESTMODE_GUEST;
+    if (fUseDriverStack)
+    {
+        rc = audioTestDriverStackInit(&pTstEnv->DrvStack, pDrvReg, fWithDrvAudio);
+        if (RT_FAILURE(rc))
+            return rc;
+
+        PPDMAUDIOHOSTDEV pDev;
+        rc = audioTestDevicesEnumerateAndCheck(pTstEnv, NULL /* pszDevice */, &pDev); /** @todo Implement device checking. */
+        if (RT_FAILURE(rc))
+            return rc;
+    }
+
     char szPathTemp[RTPATH_MAX];
     if (   !strlen(pTstEnv->szPathTemp)
@@ -488,7 +599,4 @@
         rc = RTPathJoin(pTstEnv->szPathOut, sizeof(pTstEnv->szPathOut), szPathTemp, "vkat");
 
-    if (RT_SUCCESS(rc))
-        rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pszTag);
-
     if (RT_FAILURE(rc))
         return rc;
@@ -501,6 +609,8 @@
 
         ATSCALLBACKS Callbacks;
-        Callbacks.pfnTonePlay = audioTestSvcTonePlayCallback;
-        Callbacks.pvUser      = &Ctx;
+        Callbacks.pfnTestSetBegin = audioTestSvcTestSetBeginCallback;
+        Callbacks.pfnTestSetEnd   = audioTestSvcTestSetEndCallback;
+        Callbacks.pfnTonePlay     = audioTestSvcTonePlayCallback;
+        Callbacks.pvUser          = &Ctx;
 
         RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Starting ATS ...\n");
@@ -511,24 +621,12 @@
         if (RT_FAILURE(rc))
         {
-            RTTestFailed(g_hTest, "Initializing ATS failed with %Rrc\n", rc);
+            RTTestFailed(g_hTest, "Starting ATS failed with %Rrc\n", rc);
             return rc;
         }
-
-        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "ATS running\n");
-
-        while (!g_fTerminated) /** @todo Implement signal handling. */
-        {
-            RTThreadSleep(100);
-        }
-
-        RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Shutting down ATS ...\n");
-
-        int rc2 = AudioTestSvcShutdown(&pTstEnv->u.Guest.Srv);
-        if (RT_SUCCESS(rc))
-            rc = rc2;
-    }
-    else
+    }
+    else /* Host mode */
     {
         RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Connecting to ATS at %s:%RU32 ...\n", pszTcpAddr, uTcpPort);
+
         rc = AudioTestSvcClientConnect(&pTstEnv->u.Host.Client, pszTcpAddr, uTcpPort);
         if (RT_FAILURE(rc))
@@ -537,7 +635,11 @@
             return rc;
         }
-    }
-
-    audioTestDriverStackDelete(&pTstEnv->DrvStack);
+
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Connected to ATS\n");
+    }
+
+    if (   RT_FAILURE(rc)
+        && fUseDriverStack)
+        audioTestDriverStackDelete(&pTstEnv->DrvStack);
 
     return rc;
@@ -563,6 +665,32 @@
     }
 
+    audioTestDriverStackDelete(&pTstEnv->DrvStack);
+}
+
+/**
+ * Closes, packs up and destroys a test environment.
+ *
+ * @returns VBox status code.
+ * @param   pTstEnv             Test environment to handle.
+ */
+static int audioTestEnvPrologue(PAUDIOTESTENV pTstEnv)
+{
+    /* Before destroying the test environment, pack up the test set so
+     * that it's ready for transmission. */
+    char szFileOut[RTPATH_MAX];
+    int rc = AudioTestSetPack(&pTstEnv->Set, pTstEnv->szPathOut, szFileOut, sizeof(szFileOut));
+    if (RT_SUCCESS(rc))
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", szFileOut);
+
+    /* Clean up. */
+    AudioTestSetClose(&pTstEnv->Set);
+
+    int rc2 = AudioTestSetWipe(&pTstEnv->Set);
+    if (RT_SUCCESS(rc))
+        rc = rc2;
+
     AudioTestSetDestroy(&pTstEnv->Set);
-    audioTestDriverStackDelete(&pTstEnv->DrvStack);
+
+    return rc;
 }
 
@@ -812,73 +940,4 @@
 
 /**
- * Plays a test tone on a specific audio test stream.
- *
- * @returns VBox status code.
- * @param   pTstEnv             Test environment to use for running the test.
- * @param   pStream             Stream to use for playing the tone.
- * @param   pParms              Tone parameters to use.
- *
- * @note    Blocking function.
- */
-static int audioTestPlayTone(PAUDIOTESTENV pTstEnv, PAUDIOTESTSTREAM pStream, PAUDIOTESTTONEPARMS pParms)
-{
-    AUDIOTESTTONE TstTone;
-    AudioTestToneInit(&TstTone, &pParms->Props, pParms->dbFreqHz);
-
-    const char *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_DEBUG,  "Writing to '%s'\n", pcszPathOut);
-
-    /** @todo Use .WAV here? */
-    PAUDIOTESTOBJ pObj;
-    int rc = AudioTestSetObjCreateAndRegister(&pTstEnv->Set, "tone-play.pcm", &pObj);
-    AssertRCReturn(rc, rc);
-
-    if (audioTestDriverStackStreamIsOkay(&pTstEnv->DrvStack, pStream->pStream))
-    {
-        uint32_t cbBuf;
-        uint8_t  abBuf[_4K];
-
-        const uint64_t tsStartMs     = RTTimeMilliTS();
-        const uint16_t cSchedulingMs = RTRandU32Ex(10, 80); /* Choose a random scheduling (in ms). */
-        const uint32_t cbPerMs       = PDMAudioPropsMilliToBytes(&pParms->Props, cSchedulingMs);
-
-        do
-        {
-            rc = AudioTestToneGenerate(&TstTone, abBuf, RT_MIN(cbPerMs, sizeof(abBuf)), &cbBuf);
-            if (RT_SUCCESS(rc))
-            {
-                /* Write stuff to disk before trying to play it. Help analysis later. */
-                rc = AudioTestSetObjWrite(pObj, abBuf, cbBuf);
-                if (RT_SUCCESS(rc))
-                {
-                    uint32_t cbWritten;
-                    rc = audioTestDriverStackStreamPlay(&pTstEnv->DrvStack, pStream->pStream,
-                                                        abBuf, cbBuf, &cbWritten);
-                }
-            }
-
-            if (RTTimeMilliTS() - tsStartMs >= pParms->msDuration)
-                break;
-
-            if (RT_FAILURE(rc))
-                break;
-
-            RTThreadSleep(cSchedulingMs);
-
-        } while (RT_SUCCESS(rc));
-    }
-    else
-        rc = VERR_AUDIO_STREAM_NOT_READY;
-
-    int rc2 = AudioTestSetObjClose(pObj);
-    if (RT_SUCCESS(rc))
-        rc = rc2;
-
-    return rc;
-}
-
-/**
  * Overrides audio test base parameters with another set.
  *
@@ -933,6 +992,4 @@
     int rc = VINF_SUCCESS;
 
-    PAUDIOTESTSTREAM pStream = &pTstEnv->aStreams[0];
-
     for (uint32_t i = 0; i < pTstParms->cIterations; i++)
     {
@@ -943,14 +1000,10 @@
         if (RT_SUCCESS(rc))
         {
-            rc = audioTestCreateStreamDefaultOut(pTstEnv, pStream, &pTstParms->TestTone.Props);
-            if (RT_SUCCESS(rc))
-            {
-                rc = audioTestPlayTone(pTstEnv, pStream, &pTstParms->TestTone);
-            }
-
-            int rc2 = audioTestStreamDestroy(pTstEnv, pStream);
-            if (RT_SUCCESS(rc))
-                rc = rc2;
-
+            PDMAUDIOSTREAMCFG Cfg;
+            RT_ZERO(Cfg);
+            /** @todo Add more parameters here? */
+            Cfg.Props = pTstParms->Props;
+
+            rc = AudioTestSvcClientTonePlay(&pTstEnv->u.Host.Client, &Cfg, &pTstParms->TestTone);
             if (RT_SUCCESS(rc))
             {
@@ -1145,14 +1198,56 @@
     int rc = VINF_SUCCESS;
 
-    unsigned uSeq = 0;
-    for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
-    {
-        int rc2 = audioTestOne(pTstEnv, &g_aTests[i], uSeq, pOverrideParms);
+    if (pTstEnv->enmMode == AUDIOTESTMODE_GUEST)
+    {
+        RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "ATS running\n");
+
+        while (!g_fTerminated) /** @todo Implement signal handling. */
+        {
+            RTThreadSleep(100);
+        }
+
+        RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Shutting down ATS ...\n");
+
+        int rc2 = AudioTestSvcShutdown(&pTstEnv->u.Guest.Srv);
         if (RT_SUCCESS(rc))
             rc = rc2;
-
-        if (!g_aTests[i].fExcluded)
-            uSeq++;
-    }
+    }
+    else if (pTstEnv->enmMode == AUDIOTESTMODE_HOST)
+    {
+        /* We have one single test set for all executed tests for now. */
+        rc = AudioTestSetCreate(&pTstEnv->Set, pTstEnv->szPathTemp, pTstEnv->szTag);
+        if (RT_SUCCESS(rc))
+        {
+            /* Copy back the (eventually generated) tag to the test environment. */
+            rc = RTStrCopy(pTstEnv->szTag, sizeof(pTstEnv->szTag), AudioTestSetGetTag(&pTstEnv->Set));
+            AssertRC(rc);
+
+            rc = AudioTestSvcClientTestSetBegin(&pTstEnv->u.Host.Client, pTstEnv->szTag);
+            if (RT_SUCCESS(rc))
+            {
+                unsigned uSeq = 0;
+                for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++)
+                {
+                    int rc2 = audioTestOne(pTstEnv, &g_aTests[i], uSeq, pOverrideParms);
+                    if (RT_SUCCESS(rc))
+                        rc = rc2;
+
+                    if (!g_aTests[i].fExcluded)
+                        uSeq++;
+                }
+
+                int rc2 = AudioTestSvcClientTestSetEnd(&pTstEnv->u.Host.Client, pTstEnv->szTag);
+                if (RT_SUCCESS(rc))
+                    rc = rc2;
+            }
+
+            audioTestEnvPrologue(pTstEnv);
+        }
+    }
+    else
+        AssertFailed();
+
+    if (RT_FAILURE(rc))
+        RTTestFailed(g_hTest, "Test worker failed with %Rrc", rc);
 
     return rc;
@@ -1164,9 +1259,12 @@
     switch (pOpt->iShort)
     {
-        case 'd':                 return "Go via DrvAudio instead of directly interfacing with the backend.";
-        case VKAT_TEST_OPT_DEV:   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";
+        case 'd':                    return "Go via DrvAudio instead of directly interfacing with the backend";
+        case VKAT_TEST_OPT_DEV:      return "Use the specified audio device";
+        case VKAT_TEST_OPT_ATS_ADDR: return "ATS address (hostname or IP) to connect to";
+        case VKAT_TEST_OPT_ATS_PORT: return "ATS port to connect to. Defaults to 6052 if not set";
+        case VKAT_TEST_OPT_MODE:     return "Specifies the mode this program runs at";
+        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;
@@ -1336,23 +1434,5 @@
     if (RT_SUCCESS(rc))
     {
-        PPDMAUDIOHOSTDEV pDev;
-        rc = audioTestDevicesEnumerateAndCheck(&TstEnv, pszDevice, &pDev);
-        if (RT_SUCCESS(rc))
-            audioTestWorker(&TstEnv, &TstCust);
-
-        /* Before destroying the test environment, pack up the test set so
-         * that it's ready for transmission. */
-        char szFileOut[RTPATH_MAX];
-        rc = AudioTestSetPack(&TstEnv.Set, TstEnv.szPathOut, szFileOut, sizeof(szFileOut));
-        if (RT_SUCCESS(rc))
-            RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Test set packed up to '%s'\n", szFileOut);
-
-#ifndef DEBUG_andy
-        /* Clean up. */
-        AudioTestSetClose(&TstEnv.Set); /* wipe fails on windows if the manifest file is open*/
-
-        int rc2 = AudioTestSetWipe(&TstEnv.Set);
-        AssertRC(rc2); /* Annoying, but not test-critical. */
-#endif
+        audioTestWorker(&TstEnv, &TstCust);
         audioTestEnvDestroy(&TstEnv);
     }
@@ -1863,9 +1943,5 @@
                     rc = AudioTestSvcInit(&Srv, &Callbacks);
                     if (RT_SUCCESS(rc))
-                    {
                         rc = AudioTestSvcStart(&Srv);
-                        if (RT_SUCCESS(rc))
-                            RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "ATS running\n");
-                    }
                 }
                 else
