diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp | 1747 |
1 files changed, 1747 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp b/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp new file mode 100644 index 00000000..91c44f10 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp @@ -0,0 +1,1747 @@ +/* $Id: DrvHostAudioValidationKit.cpp $ */ +/** @file + * Host audio driver - ValidationKit - For dumping and injecting audio data from/to the device emulation. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */ + +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioifs.h> +#include <VBox/vmm/pdmaudioinline.h> + +#include "VBoxDD.h" +#include "AudioHlp.h" +#include "AudioTest.h" +#include "AudioTestService.h" + + +#ifdef DEBUG_andy +/** Enables dumping audio streams to the temporary directory for debugging. */ +# define VBOX_WITH_AUDIO_VALKIT_DUMP_STREAMS +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure for keeping a Validation Kit input/output stream. + */ +typedef struct VALKITAUDIOSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; +#ifdef VBOX_WITH_AUDIO_VALKIT_DUMP_STREAMS + /** Audio file to dump output to. */ + PAUDIOHLPFILE pFile; +#endif +} VALKITAUDIOSTREAM; +/** Pointer to a Validation Kit stream. */ +typedef VALKITAUDIOSTREAM *PVALKITAUDIOSTREAM; + +/** + * Test tone-specific instance data. + */ +typedef struct VALKITTESTTONEDATA +{ + /* Test tone beacon to use. + * Will be re-used for pre/post beacons. */ + AUDIOTESTTONEBEACON Beacon; + union + { + struct + { + /** How many bytes to write. */ + uint64_t cbToWrite; + /** How many bytes already written. */ + uint64_t cbWritten; + } Rec; + struct + { + /** How many bytes to read. */ + uint64_t cbToRead; + /** How many bytes already read. */ + uint64_t cbRead; + } Play; + } u; + /** The test tone instance to use. */ + AUDIOTESTTONE Tone; + /** The test tone parameters to use. */ + AUDIOTESTTONEPARMS Parms; +} VALKITTESTTONEDATA; + +/** + * Structure keeping a single Validation Kit test. + */ +typedef struct VALKITTESTDATA +{ + /** The list node. */ + RTLISTNODE Node; + /** Index in test sequence (0-based). */ + uint32_t idxTest; + /** Current test set entry to process. */ + PAUDIOTESTENTRY pEntry; + /** Current test state. */ + AUDIOTESTSTATE enmState; + /** Current test object to process. */ + AUDIOTESTOBJ Obj; + /** Stream configuration to use for this test. */ + PDMAUDIOSTREAMCFG StreamCfg; + union + { + /** Test tone-specific data. */ + VALKITTESTTONEDATA TestTone; + } t; + /** Time stamp (real, in ms) when test got registered. */ + uint64_t msRegisteredTS; + /** Time stamp (real, in ms) when test started. */ + uint64_t msStartedTS; +} VALKITTESTDATA; +/** Pointer to Validation Kit test data. */ +typedef VALKITTESTDATA *PVALKITTESTDATA; + +/** + * Validation Kit audio driver instance data. + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTVALKITAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** Total number of bytes played since driver construction. */ + uint64_t cbPlayedTotal; + /** Total number of bytes recorded since driver construction. */ + uint64_t cbRecordedTotal; + /** Total number of bytes silence was played in a consequtive block so far. + * Will be reset once audible data is being played (again). */ + uint64_t cbPlayedSilence; + /** Total number of bytes audio (audible or not) was played while no active + * audio test was registered / available. */ + uint64_t cbPlayedNoTest; + /** Temporary path to use. */ + char szPathTemp[RTPATH_MAX]; + /** Output path to use. */ + char szPathOut[RTPATH_MAX]; + /** Current test set being handled. + * At the moment only one test set can be around at a time. */ + AUDIOTESTSET Set; + /** Number of total tests in \a lstTestsRec and \a lstTestsPlay. */ + uint32_t cTestsTotal; + /** Number of tests in \a lstTestsRec. */ + uint32_t cTestsRec; + /** List keeping the recording tests (FIFO). */ + RTLISTANCHOR lstTestsRec; + /** Pointer to current recording test being processed. + * NULL if no current test active. */ + PVALKITTESTDATA pTestCurRec; + /** Number of tests in \a lstTestsPlay. */ + uint32_t cTestsPlay; + /** List keeping the recording tests (FIFO). */ + RTLISTANCHOR lstTestsPlay; + /** Pointer to current playback test being processed. + * NULL if no current test active. */ + PVALKITTESTDATA pTestCurPlay; + /** Critical section for serializing access across threads. */ + RTCRITSECT CritSect; + /** Whether the test set needs to end. + * Needed for packing up (to archive) and termination, as capturing and playback + * can run in asynchronous threads. */ + bool fTestSetEnd; + /** Event semaphore for waiting on the current test set to end. */ + RTSEMEVENT EventSemEnded; + /** The Audio Test Service (ATS) instance. */ + ATSSERVER Srv; + /** Absolute path to the packed up test set archive. + * Keep it simple for now and only support one (open) archive at a time. */ + char szTestSetArchive[RTPATH_MAX]; + /** File handle to the (opened) test set archive for reading. */ + RTFILE hTestSetArchive; + +} DRVHOSTVALKITAUDIO; +/** Pointer to a Validation Kit host audio driver instance. */ +typedef DRVHOSTVALKITAUDIO *PDRVHOSTVALKITAUDIO; + + +/********************************************************************************************************************************* +* Internal test handling code * +*********************************************************************************************************************************/ + +/** + * Unregisters a ValKit test, common code. + * + * @param pThis ValKit audio driver instance. + * @param pTst Test to unregister. + * The pointer will be invalid afterwards. + */ +static void drvHostValKiUnregisterTest(PDRVHOSTVALKITAUDIO pThis, PVALKITTESTDATA pTst) +{ + AssertPtrReturnVoid(pTst); + + RTListNodeRemove(&pTst->Node); + + AudioTestObjClose(pTst->Obj); + pTst->Obj = NULL; + + if (pTst->pEntry) /* Set set entry assign? Mark as done. */ + { + AssertPtrReturnVoid(pTst->pEntry); + pTst->pEntry = NULL; + } + + RTMemFree(pTst); + pTst = NULL; + + Assert(pThis->cTestsTotal); + pThis->cTestsTotal--; + if (pThis->cTestsTotal == 0) + { + if (ASMAtomicReadBool(&pThis->fTestSetEnd)) + { + int rc2 = RTSemEventSignal(pThis->EventSemEnded); + AssertRC(rc2); + } + } +} + +/** + * Unregisters a ValKit recording test. + * + * @param pThis ValKit audio driver instance. + * @param pTst Test to unregister. + * The pointer will be invalid afterwards. + */ +static void drvHostValKiUnregisterRecTest(PDRVHOSTVALKITAUDIO pThis, PVALKITTESTDATA pTst) +{ + Assert(pThis->cTestsRec); + pThis->cTestsRec--; + + drvHostValKiUnregisterTest(pThis, pTst); +} + +/** + * Unregisters a ValKit playback test. + * + * @param pThis ValKit audio driver instance. + * @param pTst Test to unregister. + * The pointer will be invalid afterwards. + */ +static void drvHostValKiUnregisterPlayTest(PDRVHOSTVALKITAUDIO pThis, PVALKITTESTDATA pTst) +{ + Assert(pThis->cTestsPlay); + pThis->cTestsPlay--; + + drvHostValKiUnregisterTest(pThis, pTst); +} + +/** + * Performs some internal cleanup / housekeeping of all registered tests. + * + * @param pThis ValKit audio driver instance. + */ +static void drvHostValKitCleanup(PDRVHOSTVALKITAUDIO pThis) +{ + LogRel(("ValKit: Cleaning up ...\n")); + + if ( pThis->cTestsTotal + && ( !pThis->cbPlayedTotal + && !pThis->cbRecordedTotal) + ) + { + LogRel(("ValKit: Warning: Did not get any audio data to play or record altough tests were configured\n\n")); + LogRel(("ValKit: Hints:\n" + "ValKit: - Audio device emulation configured and enabled for the VM?\n" + "ValKit: - Audio input and/or output enabled for the VM?\n" + "ValKit: - Is the guest able to play / record sound at all?\n" + "ValKit: - Is the guest's audio mixer or input / output sinks muted?\n" + "ValKit: - Audio stack misconfiguration / bug?\n\n")); + } + + if (pThis->cTestsRec) + LogRel(("ValKit: Warning: %RU32 guest recording tests still outstanding:\n", pThis->cTestsRec)); + + PVALKITTESTDATA pTst, pTstNext; + RTListForEachSafe(&pThis->lstTestsRec, pTst, pTstNext, VALKITTESTDATA, Node) + { + if (pTst->enmState != AUDIOTESTSTATE_DONE) + LogRel(("ValKit: \tWarning: Test #%RU32 (recording) not done yet (state is '%s')\n", + pTst->idxTest, AudioTestStateToStr(pTst->enmState))); + + if (pTst->t.TestTone.u.Rec.cbToWrite > pTst->t.TestTone.u.Rec.cbWritten) + { + size_t const cbOutstanding = pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten; + if (cbOutstanding) + LogRel(("ValKit: \tWarning: Recording test #%RU32 has %RU64 bytes (%RU64ms) outstanding (%RU8%% left)\n", + pTst->idxTest, cbOutstanding, PDMAudioPropsBytesToMilli(&pTst->t.TestTone.Parms.Props, (uint32_t)cbOutstanding), + 100 - (pTst->t.TestTone.u.Rec.cbWritten * 100) / RT_MAX(pTst->t.TestTone.u.Rec.cbToWrite, 1))); + } + drvHostValKiUnregisterRecTest(pThis, pTst); + } + + if (pThis->cTestsPlay) + LogRel(("ValKit: Warning: %RU32 guest playback tests still outstanding:\n", pThis->cTestsPlay)); + + RTListForEachSafe(&pThis->lstTestsPlay, pTst, pTstNext, VALKITTESTDATA, Node) + { + if (pTst->enmState != AUDIOTESTSTATE_DONE) + LogRel(("ValKit: \tWarning: Test #%RU32 (playback) not done yet (state is '%s')\n", + pTst->idxTest, AudioTestStateToStr(pTst->enmState))); + + if (pTst->t.TestTone.u.Play.cbToRead > pTst->t.TestTone.u.Play.cbRead) + { + size_t const cbOutstanding = pTst->t.TestTone.u.Play.cbToRead - pTst->t.TestTone.u.Play.cbRead; + if (cbOutstanding) + LogRel(("ValKit: \tWarning: Playback test #%RU32 has %RU64 bytes (%RU64ms) outstanding (%RU8%% left)\n", + pTst->idxTest, cbOutstanding, PDMAudioPropsBytesToMilli(&pTst->t.TestTone.Parms.Props, (uint32_t)cbOutstanding), + 100 - (pTst->t.TestTone.u.Play.cbRead * 100) / RT_MAX(pTst->t.TestTone.u.Play.cbToRead, 1))); + } + drvHostValKiUnregisterPlayTest(pThis, pTst); + } + + Assert(pThis->cTestsRec == 0); + Assert(pThis->cTestsPlay == 0); + + if (pThis->cbPlayedNoTest) + { + LogRel2(("ValKit: Warning: Guest was playing back audio when no playback test is active (%RU64 bytes total)\n", + pThis->cbPlayedNoTest)); + pThis->cbPlayedNoTest = 0; + } +} + + +/********************************************************************************************************************************* +* ATS callback implementations * +*********************************************************************************************************************************/ + +/** @copydoc ATSCALLBACKS::pfnHowdy */ +static DECLCALLBACK(int) drvHostValKitHowdy(void const *pvUser) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + RT_NOREF(pThis); + + LogRel(("ValKit: Client connected\n")); + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnBye */ +static DECLCALLBACK(int) drvHostValKitBye(void const *pvUser) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + RT_NOREF(pThis); + + LogRel(("ValKit: Client disconnected\n")); + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetBegin */ +static DECLCALLBACK(int) drvHostValKitTestSetBegin(void const *pvUser, const char *pszTag) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + LogRel(("ValKit: Beginning test set '%s'\n", pszTag)); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = AudioTestSetCreate(&pThis->Set, pThis->szPathTemp, pszTag); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Beginning test set failed with %Rrc\n", rc)); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetEnd */ +static DECLCALLBACK(int) drvHostValKitTestSetEnd(void const *pvUser, const char *pszTag) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + LogRel(("ValKit: Ending test set '%s'\n", pszTag)); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + const PAUDIOTESTSET pSet = &pThis->Set; + + const char *pszTagSet = AudioTestSetGetTag(pSet); + if (RTStrCmp(pszTagSet, pszTag) != 0) + { + LogRel(("ValKit: Error: Current test does not match test set to end ('%s' vs '%s')\n", pszTagSet, pszTag)); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + + return VERR_NOT_FOUND; /* Return to the caller. */ + } + + LogRel(("ValKit: Test set has %RU32 tests total, %RU32 (still) running, %RU32 failures total so far\n", + AudioTestSetGetTestsTotal(pSet), AudioTestSetGetTestsRunning(pSet), AudioTestSetGetTotalFailures(pSet))); + LogRel(("ValKit: %RU32 tests still registered total (%RU32 play, %RU32 record)\n", + pThis->cTestsTotal, pThis->cTestsPlay, pThis->cTestsRec)); + + if ( AudioTestSetIsRunning(pSet) + || pThis->cTestsTotal) + { + ASMAtomicWriteBool(&pThis->fTestSetEnd, true); + + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Waiting for all tests of set '%s' to end ...\n", pszTag)); + rc = RTSemEventWait(pThis->EventSemEnded, RT_MS_5SEC); + if (RT_FAILURE(rc)) + { + LogRel(("ValKit: Waiting for tests of set '%s' to end failed with %Rrc\n", pszTag, rc)); + + /* The verification on the host will tell us later which tests did run and which didn't (anymore). + * So continue and pack (plus transfer) the test set to the host. */ + if (rc == VERR_TIMEOUT) + rc = VINF_SUCCESS; + } + + int rc2 = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Closing test set '%s' ...\n", pszTag)); + + /* Close the test set first. */ + rc = AudioTestSetClose(pSet); + if (RT_SUCCESS(rc)) + { + /* Before destroying the test environment, pack up the test set so + * that it's ready for transmission. */ + rc = AudioTestSetPack(pSet, pThis->szPathOut, pThis->szTestSetArchive, sizeof(pThis->szTestSetArchive)); + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Packed up to '%s'\n", pThis->szTestSetArchive)); + } + else + LogRel(("ValKit: Packing up test set failed with %Rrc\n", rc)); + + /* Do some internal housekeeping. */ + drvHostValKitCleanup(pThis); + +#ifndef DEBUG_andy + int rc2 = AudioTestSetWipe(pSet); + if (RT_SUCCESS(rc)) + rc = rc2; +#endif + } + else + LogRel(("ValKit: Closing test set failed with %Rrc\n", rc)); + + int rc2 = AudioTestSetDestroy(pSet); + if (RT_FAILURE(rc2)) + { + LogRel(("ValKit: Destroying test set failed with %Rrc\n", rc)); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Ending test set failed with %Rrc\n", rc)); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTonePlay + * + * Creates and registers a new test tone guest recording test. + * This backend will play (inject) input data to the guest. + */ +static DECLCALLBACK(int) drvHostValKitRegisterGuestRecTest(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + PVALKITTESTDATA pTst = (PVALKITTESTDATA)RTMemAllocZ(sizeof(VALKITTESTDATA)); + AssertPtrReturn(pTst, VERR_NO_MEMORY); + + pTst->enmState = AUDIOTESTSTATE_INIT; + + memcpy(&pTst->t.TestTone.Parms, pToneParms, sizeof(AUDIOTESTTONEPARMS)); + + PPDMAUDIOPCMPROPS const pProps = &pTst->t.TestTone.Parms.Props; + + AssertReturn(pTst->t.TestTone.Parms.msDuration, VERR_INVALID_PARAMETER); + AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER); + + AudioTestToneInit(&pTst->t.TestTone.Tone, pProps, pTst->t.TestTone.Parms.dbFreqHz); + + pTst->t.TestTone.u.Rec.cbToWrite = PDMAudioPropsMilliToBytes(pProps, + pTst->t.TestTone.Parms.msDuration); + + /* We inject a pre + post beacon before + after the actual test tone. + * We always start with the pre beacon. */ + AudioTestBeaconInit(&pTst->t.TestTone.Beacon, pToneParms->Hdr.idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_PRE, pProps); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Registering guest recording test #%RU32 (%RU32ms, %RU64 bytes) as test #%RU32\n", + pThis->cTestsRec, pTst->t.TestTone.Parms.msDuration, pTst->t.TestTone.u.Rec.cbToWrite, + pToneParms->Hdr.idxTest)); + + const uint32_t cbBeacon = AudioTestBeaconGetSize(&pTst->t.TestTone.Beacon); + if (cbBeacon) + LogRel2(("ValKit: Test #%RU32: Uses 2 x %RU32 bytes of pre/post beacons\n", + pToneParms->Hdr.idxTest, cbBeacon)); + + RTListAppend(&pThis->lstTestsRec, &pTst->Node); + + pTst->msRegisteredTS = RTTimeMilliTS(); + pTst->idxTest = pToneParms->Hdr.idxTest; /* Use the test ID from the host (so that the beacon IDs match). */ + + pThis->cTestsRec++; + pThis->cTestsTotal++; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnToneRecord + * + * Creates and registers a new test tone guest playback test. + * This backend will record the guest output data. + */ +static DECLCALLBACK(int) drvHostValKitRegisterGuestPlayTest(void const *pvUser, PAUDIOTESTTONEPARMS pToneParms) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + PVALKITTESTDATA pTst = (PVALKITTESTDATA)RTMemAllocZ(sizeof(VALKITTESTDATA)); + AssertPtrReturn(pTst, VERR_NO_MEMORY); + + pTst->enmState = AUDIOTESTSTATE_INIT; + + memcpy(&pTst->t.TestTone.Parms, pToneParms, sizeof(AUDIOTESTTONEPARMS)); + + PPDMAUDIOPCMPROPS const pProps = &pTst->t.TestTone.Parms.Props; + + AssertReturn(pTst->t.TestTone.Parms.msDuration, VERR_INVALID_PARAMETER); + AssertReturn(PDMAudioPropsAreValid(pProps), VERR_INVALID_PARAMETER); + + pTst->t.TestTone.u.Play.cbToRead = PDMAudioPropsMilliToBytes(pProps, + pTst->t.TestTone.Parms.msDuration); + + /* We play a pre + post beacon before + after the actual test tone. + * We always start with the pre beacon. */ + AudioTestBeaconInit(&pTst->t.TestTone.Beacon, pToneParms->Hdr.idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_PRE, pProps); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Registering guest playback test #%RU32 (%RU32ms, %RU64 bytes) as test #%RU32\n", + pThis->cTestsPlay, pTst->t.TestTone.Parms.msDuration, pTst->t.TestTone.u.Play.cbToRead, + pToneParms->Hdr.idxTest)); + + const uint32_t cbBeacon = AudioTestBeaconGetSize(&pTst->t.TestTone.Beacon); + if (cbBeacon) + LogRel2(("ValKit: Test #%RU32: Uses x %RU32 bytes of pre/post beacons\n", + pToneParms->Hdr.idxTest, cbBeacon)); + + RTListAppend(&pThis->lstTestsPlay, &pTst->Node); + + pTst->msRegisteredTS = RTTimeMilliTS(); + pTst->idxTest = pToneParms->Hdr.idxTest; /* Use the test ID from the host (so that the beacon IDs match). */ + + pThis->cTestsTotal++; + pThis->cTestsPlay++; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return VINF_SUCCESS; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendBegin */ +static DECLCALLBACK(int) drvHostValKitTestSetSendBeginCallback(void const *pvUser, const char *pszTag) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (RTFileExists(pThis->szTestSetArchive)) /* Has the archive successfully been created yet? */ + { + rc = RTFileOpen(&pThis->hTestSetArchive, pThis->szTestSetArchive, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + uint64_t uSize; + rc = RTFileQuerySize(pThis->hTestSetArchive, &uSize); + if (RT_SUCCESS(rc)) + LogRel(("ValKit: Sending test set '%s' (%zu bytes)\n", pThis->szTestSetArchive, uSize)); + } + } + else + rc = VERR_FILE_NOT_FOUND; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Beginning to send test set '%s' failed with %Rrc\n", pszTag, rc)); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */ +static DECLCALLBACK(int) drvHostValKitTestSetSendReadCallback(void const *pvUser, + const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (RTFileIsValid(pThis->hTestSetArchive)) + { + rc = RTFileRead(pThis->hTestSetArchive, pvBuf, cbBuf, pcbRead); + } + else + rc = VERR_WRONG_ORDER; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Reading from test set '%s' failed with %Rrc\n", pszTag, rc)); + + return rc; +} + +/** @copydoc ATSCALLBACKS::pfnTestSetSendEnd */ +static DECLCALLBACK(int) drvHostValKitTestSetSendEndCallback(void const *pvUser, const char *pszTag) +{ + PDRVHOSTVALKITAUDIO pThis = (PDRVHOSTVALKITAUDIO)pvUser; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (RTFileIsValid(pThis->hTestSetArchive)) + { + rc = RTFileClose(pThis->hTestSetArchive); + if (RT_SUCCESS(rc)) + pThis->hTestSetArchive = NIL_RTFILE; + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Ending to send test set '%s' failed with %Rrc\n", pszTag, rc)); + + return rc; +} + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO interface implementation * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit"); + pBackendCfg->cbStream = sizeof(VALKITAUDIOSTREAM); + pBackendCfg->fFlags = 0; + pBackendCfg->cMaxStreamsOut = 1; /* Output (Playback). */ + pBackendCfg->cMaxStreamsIn = 1; /* Input (Recording). */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostValKitAudioHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(enmDir); + AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN); + + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + PVALKITAUDIOSTREAM pStreamValKit = (PVALKITAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamValKit, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + RT_NOREF(pThis); + + PDMAudioStrmCfgCopy(&pStreamValKit->Cfg, pCfgAcq); + + int rc2; +#ifdef VBOX_WITH_AUDIO_VALKIT_DUMP_STREAMS + rc2 = AudioHlpFileCreateAndOpenEx(&pStreamValKit->pFile, AUDIOHLPFILETYPE_WAV, NULL /*use temp dir*/, + pThis->pDrvIns->iInstance, AUDIOHLPFILENAME_FLAGS_NONE, AUDIOHLPFILE_FLAGS_NONE, + &pCfgReq->Props, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE, + pCfgReq->enmDir == PDMAUDIODIR_IN ? "ValKitAudioIn" : "ValKitAudioOut"); + if (RT_FAILURE(rc2)) + LogRel(("ValKit: Failed to creating debug file for %s stream '%s' in the temp directory: %Rrc\n", + pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pCfgReq->szName, rc2)); +#endif + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->pTestCurRec == NULL) + { + pThis->pTestCurRec = RTListGetFirst(&pThis->lstTestsRec, VALKITTESTDATA, Node); + if (pThis->pTestCurRec) + LogRel(("ValKit: Next guest recording test in queue is test #%RU32\n", pThis->pTestCurRec->idxTest)); + } + + PVALKITTESTDATA pTst = pThis->pTestCurRec; + + /* If we have a test registered and in the queue coming up next, use + * the beacon size (if any, could be 0) as pre-buffering requirement. */ + if (pTst) + { + const uint32_t cFramesBeacon = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, + AudioTestBeaconGetSize(&pTst->t.TestTone.Beacon)); + if (cFramesBeacon) /* Only assign if not 0, otherwise stay with the default. */ + pCfgAcq->Backend.cFramesPreBuffering = cFramesBeacon; + } + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return rc; +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + bool fImmediate) +{ + RT_NOREF(pInterface, fImmediate); + PVALKITAUDIOSTREAM pStreamValKit = (PVALKITAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamValKit, VERR_INVALID_POINTER); + +#ifdef VBOX_WITH_AUDIO_VALKIT_DUMP_STREAMS + if (pStreamValKit->pFile) + { + AudioHlpFileDestroy(pStreamValKit->pFile); + pStreamValKit->pFile = NULL; + } +#endif + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface, pStream); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + PVALKITAUDIOSTREAM pStreamValKit = (PVALKITAUDIOSTREAM)pStream; + + if (pStreamValKit->Cfg.enmDir == PDMAUDIODIR_OUT) + { + LogRel(("ValKit: Warning: Trying to read from non-input stream '%s' -- report this bug!\n", + pStreamValKit->Cfg.szName)); + return 0; + } + + /* We return UINT32_MAX by default (when no tests are running [anymore] for not being marked + * as "unreliable stream" in the audio mixer. See audioMixerSinkUpdateInput(). */ + uint32_t cbReadable = UINT32_MAX; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->pTestCurRec == NULL) + { + pThis->pTestCurRec = RTListGetFirst(&pThis->lstTestsRec, VALKITTESTDATA, Node); + if (pThis->pTestCurRec) + LogRel(("ValKit: Next guest recording test in queue is test #%RU32\n", pThis->pTestCurRec->idxTest)); + } + + PVALKITTESTDATA pTst = pThis->pTestCurRec; + if (pTst) + { + switch (pTst->enmState) + { + case AUDIOTESTSTATE_INIT: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + cbReadable = AudioTestBeaconGetRemaining(&pTst->t.TestTone.Beacon); + break; + } + + case AUDIOTESTSTATE_RUN: + { + AssertBreakStmt(pTst->t.TestTone.u.Rec.cbToWrite >= pTst->t.TestTone.u.Rec.cbWritten, + rc = VERR_INVALID_STATE); + cbReadable = pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten; + break; + } + + case AUDIOTESTSTATE_DONE: + RT_FALL_THROUGH(); + default: + break; + } + + LogRel2(("ValKit: Test #%RU32: Reporting %RU32 bytes readable (state is '%s')\n", + pTst->idxTest, cbReadable, AudioTestStateToStr(pTst->enmState))); + + if (cbReadable == 0) + LogRel2(("ValKit: Test #%RU32: Warning: Not readable anymore (state is '%s'), returning 0\n", + pTst->idxTest, AudioTestStateToStr(pTst->enmState))); + } + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Reporting readable bytes failed with %Rrc\n", rc)); + + Log3Func(("returns %#x (%RU32)\n", cbReadable, cbReadable)); + return cbReadable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHostValKitAudioHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + RT_NOREF(pStream); + + uint32_t cbWritable = UINT32_MAX; + PVALKITTESTDATA pTst = NULL; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pTst = pThis->pTestCurPlay; + + if (pTst) + { + switch (pTst->enmState) + { + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + cbWritable = AudioTestBeaconGetRemaining(&pTst->t.TestTone.Beacon); + break; + } + + case AUDIOTESTSTATE_RUN: + { + AssertReturn(pTst->t.TestTone.u.Play.cbToRead >= pTst->t.TestTone.u.Play.cbRead, 0); + cbWritable = pTst->t.TestTone.u.Play.cbToRead - pTst->t.TestTone.u.Play.cbRead; + break; + } + + default: + break; + } + + LogRel2(("ValKit: Test #%RU32: Reporting %RU32 bytes writable (state is '%s')\n", + pTst->idxTest, cbWritable, AudioTestStateToStr(pTst->enmState))); + + if (cbWritable == 0) + { + LogRel2(("ValKit: Test #%RU32: Warning: Not writable anymore (state is '%s'), returning UINT32_MAX\n", + pTst->idxTest, AudioTestStateToStr(pTst->enmState))); + cbWritable = UINT32_MAX; + } + } + else + LogRel2(("ValKit: Reporting UINT32_MAX bytes writable (no playback test running)\n")); + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostValKitAudioHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID); + +#if 0 + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + PDMHOSTAUDIOSTREAMSTATE enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; + + if (pStream->pStream->Cfg.enmDir == PDMAUDIODIR_IN) + { + int rc2 = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc2)) + { + enmState = pThis->cTestsRec == 0 + ? PDMHOSTAUDIOSTREAMSTATE_INACTIVE : PDMHOSTAUDIOSTREAMSTATE_OKAY; + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + } + else + enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY; + + return enmState; +#else + RT_NOREF(pInterface, pStream); + return PDMHOSTAUDIOSTREAMSTATE_OKAY; +#endif +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + if (cbBuf == 0) + { + /* Fend off draining calls. */ + *pcbWritten = 0; + return VINF_SUCCESS; + } + + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + PVALKITTESTDATA pTst = NULL; + + int rc2; +#ifdef VBOX_WITH_AUDIO_VALKIT_DUMP_STREAMS + PVALKITAUDIOSTREAM pStrmValKit = (PVALKITAUDIOSTREAM)pStream; + rc2 = AudioHlpFileWrite(pStrmValKit->pFile, pvBuf, cbBuf); + AssertRC(rc2); +#endif + + /* Flag indicating whether the whole block we're going to play is silence or not. */ + bool const fIsAllSilence = PDMAudioPropsIsBufferSilence(&pStream->pStream->Cfg.Props, pvBuf, cbBuf); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + pThis->cbPlayedTotal += cbBuf; /* Do a bit of accounting. */ + + if (pThis->pTestCurPlay == NULL) + { + pThis->pTestCurPlay = RTListGetFirst(&pThis->lstTestsPlay, VALKITTESTDATA, Node); + if (pThis->pTestCurPlay) + LogRel(("ValKit: Next guest playback test in queue is test #%RU32\n", pThis->pTestCurPlay->idxTest)); + } + + pTst = pThis->pTestCurPlay; + + rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + if (pTst == NULL) /* Empty list? */ + { + pThis->cbPlayedNoTest += cbBuf; + + *pcbWritten = cbBuf; + return VINF_SUCCESS; + } + + if (pThis->cbPlayedNoTest) + { + LogRel(("ValKit: Warning: Guest was playing back audio (%RU64 bytes, %RU64ms) when no playback test is active\n", + pThis->cbPlayedNoTest, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, pThis->cbPlayedNoTest))); + pThis->cbPlayedNoTest = 0; + } + + if (fIsAllSilence) + { + pThis->cbPlayedSilence += cbBuf; + } + else /* Audible data */ + { + if (pThis->cbPlayedSilence) + LogRel(("ValKit: Guest was playing back %RU64 bytes (%RU64ms) of silence\n", + pThis->cbPlayedSilence, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, pThis->cbPlayedSilence))); + pThis->cbPlayedSilence = 0; + } + + LogRel3(("ValKit: Test #%RU32: Playing stream '%s' (%RU32 bytes / %RU64ms) -- state is '%s' ...\n", + pTst->idxTest, pStream->pStream->Cfg.szName, + cbBuf, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, cbBuf), + AudioTestStateToStr(pTst->enmState))); + + LogRel4(("ValKit: Playback audio data (%RU32 bytes):\n" + "%.*Rhxd\n", cbBuf, cbBuf, pvBuf)); + + if (pTst->enmState == AUDIOTESTSTATE_INIT) /* Test not started yet? */ + { + AUDIOTESTPARMS Parms; + RT_ZERO(Parms); + Parms.enmDir = PDMAUDIODIR_IN; + Parms.enmType = AUDIOTESTTYPE_TESTTONE_RECORD; + Parms.TestTone = pTst->t.TestTone.Parms; + + rc = AudioTestSetTestBegin(&pThis->Set, "Recording audio data from guest", + &Parms, &pTst->pEntry); + if (RT_SUCCESS(rc)) + rc = AudioTestSetObjCreateAndRegister(&pThis->Set, "host-tone-rec.pcm", &pTst->Obj); + + if (RT_SUCCESS(rc)) + { + pTst->msStartedTS = RTTimeMilliTS(); + LogRel(("ValKit: Test #%RU32: Recording audio data (%RU16Hz, %RU32ms) for host test #%RU32 started (delay is %RU32ms)\n", + pTst->idxTest, (uint16_t)Parms.TestTone.dbFreqHz, Parms.TestTone.msDuration, + Parms.TestTone.Hdr.idxTest, RTTimeMilliTS() - pTst->msRegisteredTS)); + + char szTimeCreated[RTTIME_STR_LEN]; + RTTimeToString(&Parms.TestTone.Hdr.tsCreated, szTimeCreated, sizeof(szTimeCreated)); + LogRel(("ValKit: Test created (caller UTC): %s\n", szTimeCreated)); + + pTst->enmState = AUDIOTESTSTATE_PRE; + } + } + + uint32_t cbWritten = 0; + uint8_t *auBuf = (uint8_t *)pvBuf; + + uint64_t const msStartedTS = RTTimeMilliTS(); + + while (cbWritten < cbBuf) + { + switch (pTst->enmState) + { + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + PAUDIOTESTTONEBEACON pBeacon = &pTst->t.TestTone.Beacon; + + LogRel3(("ValKit: Test #%RU32: %RU32 bytes (%RU64ms) beacon data remaining\n", + pTst->idxTest, + AudioTestBeaconGetRemaining(pBeacon), + PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, AudioTestBeaconGetRemaining(pBeacon)))); + + bool fGoToNextStage = false; + + if ( AudioTestBeaconGetSize(pBeacon) + && !AudioTestBeaconIsComplete(pBeacon)) + { + bool const fStarted = AudioTestBeaconGetRemaining(pBeacon) == AudioTestBeaconGetSize(pBeacon); + + size_t off = 0; /* Points at the data right *after* the found beacon data on return. */ + rc2 = AudioTestBeaconAddConsecutive(pBeacon, auBuf, cbBuf - cbWritten, &off); + if (RT_SUCCESS(rc2)) + { + cbWritten += (uint32_t)off; + auBuf += off; + } + else /* No beacon data found. */ + { + LogRel2(("ValKit: Test #%RU32: Warning: Beacon data for '%s' not found (%Rrc) - Skipping ...\n", + pTst->idxTest, AudioTestBeaconTypeGetName(pBeacon->enmType), rc2)); + cbWritten = cbBuf; /* Skip all. */ + break; + } + + if (fStarted) + LogRel2(("ValKit: Test #%RU32: Detection of %s beacon started (%RU64ms played so far)\n", + pTst->idxTest, AudioTestBeaconTypeGetName(pBeacon->enmType), + PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, pThis->cbPlayedTotal))); + if (AudioTestBeaconIsComplete(pBeacon)) + { + LogRel2(("ValKit: Test #%RU32: Detection of %s beacon ended\n", + pTst->idxTest, AudioTestBeaconTypeGetName(pBeacon->enmType))); + + fGoToNextStage = true; + } + } + else + fGoToNextStage = true; + + if (fGoToNextStage) + { + if (pTst->enmState == AUDIOTESTSTATE_PRE) + pTst->enmState = AUDIOTESTSTATE_RUN; + else if (pTst->enmState == AUDIOTESTSTATE_POST) + pTst->enmState = AUDIOTESTSTATE_DONE; + } + break; + } + + case AUDIOTESTSTATE_RUN: + { + uint32_t const cbRemaining = pTst->t.TestTone.u.Play.cbToRead - pTst->t.TestTone.u.Play.cbRead; + + LogRel3(("ValKit: Test #%RU32: %RU32 bytes (%RU64ms) audio data remaining\n", + pTst->idxTest, cbRemaining, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, cbRemaining))); + + /* Don't read more than we're told to. + * After the actual test tone data there might come a post beacon which also + * needs to be handled in the AUDIOTESTSTATE_POST state then. */ + const uint32_t cbData = RT_MIN(cbBuf - cbWritten, cbRemaining); + + pTst->t.TestTone.u.Play.cbRead += cbData; + + cbWritten += cbData; + auBuf += cbData; + + const bool fComplete = pTst->t.TestTone.u.Play.cbRead >= pTst->t.TestTone.u.Play.cbToRead; + if (fComplete) + { + LogRel(("ValKit: Test #%RU32: Recording audio data ended (took %RU32ms)\n", + pTst->idxTest, RTTimeMilliTS() - pTst->msStartedTS)); + + pTst->enmState = AUDIOTESTSTATE_POST; + + /* Re-use the beacon object, but this time it's the post beacon. */ + AudioTestBeaconInit(&pTst->t.TestTone.Beacon, pTst->idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_POST, + &pTst->t.TestTone.Parms.Props); + } + break; + } + + case AUDIOTESTSTATE_DONE: + { + /* Handled below. */ + break; + } + + default: + AssertFailed(); + break; + } + + if (pTst->enmState == AUDIOTESTSTATE_DONE) + break; + + if (RTTimeMilliTS() - msStartedTS > RT_MS_30SEC) + { + LogRel(("ValKit: Test #%RU32: Error: Playback processing timed out -- please report this bug!\n", pTst->idxTest)); + break; + } + } + + LogRel3(("ValKit: Test #%RU32: Played %RU32/%RU32 bytes\n", pTst->idxTest, cbWritten, cbBuf)); + + rc = AudioTestObjWrite(pTst->Obj, pvBuf, cbWritten); + AssertRC(rc); + + if (pTst->enmState == AUDIOTESTSTATE_DONE) + { + AudioTestSetTestDone(pTst->pEntry); + + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + drvHostValKiUnregisterPlayTest(pThis, pTst); + + pThis->pTestCurPlay = NULL; + pTst = NULL; + + rc2 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + if (RT_FAILURE(rc)) + { + if ( pTst + && pTst->pEntry) + AudioTestSetTestFailed(pTst->pEntry, rc, "Recording audio data failed"); + LogRel(("ValKit: Recording audio data failed with %Rrc\n", rc)); + } + + *pcbWritten = cbWritten; + + return VINF_SUCCESS; /** @todo Return rc here? */ +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHostValKitAudioHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pStream); + + if (cbBuf == 0) + { + /* Fend off draining calls. */ + *pcbRead = 0; + return VINF_SUCCESS; + } + + PDRVHOSTVALKITAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTVALKITAUDIO, IHostAudio); + PVALKITTESTDATA pTst = NULL; + + LogRel3(("ValKit: Capturing stream '%s' (%RU32 bytes / %RU64ms -- %RU64 bytes / %RU64ms total so far) ...\n", + pStream->pStream->Cfg.szName, + cbBuf, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, cbBuf), + pThis->cbRecordedTotal, PDMAudioPropsBytesToMilli(&pStream->pStream->Cfg.Props, pThis->cbRecordedTotal))); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->pTestCurRec == NULL) + { + pThis->pTestCurRec = RTListGetFirst(&pThis->lstTestsRec, VALKITTESTDATA, Node); + if (pThis->pTestCurRec) + LogRel(("ValKit: Next guest recording test in queue is test #%RU32\n", pThis->pTestCurRec->idxTest)); + } + + pTst = pThis->pTestCurRec; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + + LogRel4(("ValKit: Capture audio data (%RU32 bytes):\n" + "%.*Rhxd\n", cbBuf, cbBuf, pvBuf)); + + if (pTst == NULL) /* Empty list? */ + { + LogRel(("ValKit: Warning: Guest is trying to record audio data when no recording test is active\n")); + + /** @todo Not sure yet why this happens after all data has been captured sometimes, + * but the guest side just will record silence and the audio test verification + * will have to deal with (and/or report) it then. */ + PDMAudioPropsClearBuffer(&pStream->pStream->Cfg.Props, pvBuf, cbBuf, + PDMAudioPropsBytesToFrames(&pStream->pStream->Cfg.Props, cbBuf)); + + *pcbRead = cbBuf; /* Just report back stuff as being "recorded" (silence). */ + return VINF_SUCCESS; + } + + uint32_t cbWritten = 0; + + switch (pTst->enmState) + { + case AUDIOTESTSTATE_INIT: /* Test not started yet? */ + { + AUDIOTESTPARMS Parms; + RT_ZERO(Parms); + Parms.enmDir = PDMAUDIODIR_OUT; + Parms.enmType = AUDIOTESTTYPE_TESTTONE_PLAY; + Parms.TestTone = pTst->t.TestTone.Parms; + + rc = AudioTestSetTestBegin(&pThis->Set, "Injecting audio input data to guest", + &Parms, &pTst->pEntry); + if (RT_SUCCESS(rc)) + rc = AudioTestSetObjCreateAndRegister(&pThis->Set, "host-tone-play.pcm", &pTst->Obj); + + if (RT_SUCCESS(rc)) + { + pTst->msStartedTS = RTTimeMilliTS(); + LogRel(("ValKit: Test #%RU32: Injecting audio input data (%RU16Hz, %RU32ms, %RU32 bytes) for host test #%RU32 started (delay is %RU32ms)\n", + pTst->idxTest, (uint16_t)pTst->t.TestTone.Tone.rdFreqHz, + pTst->t.TestTone.Parms.msDuration, pTst->t.TestTone.u.Rec.cbToWrite, + Parms.TestTone.Hdr.idxTest, RTTimeMilliTS() - pTst->msRegisteredTS)); + + char szTimeCreated[RTTIME_STR_LEN]; + RTTimeToString(&Parms.TestTone.Hdr.tsCreated, szTimeCreated, sizeof(szTimeCreated)); + LogRel2(("ValKit: Test created (caller UTC): %s\n", szTimeCreated)); + + pTst->enmState = AUDIOTESTSTATE_PRE; + } + else + break; + + RT_FALL_THROUGH(); + } + + case AUDIOTESTSTATE_PRE: + RT_FALL_THROUGH(); + case AUDIOTESTSTATE_POST: + { + bool fGoToNextStage = false; + + PAUDIOTESTTONEBEACON pBeacon = &pTst->t.TestTone.Beacon; + if ( AudioTestBeaconGetSize(pBeacon) + && !AudioTestBeaconIsComplete(pBeacon)) + { + bool const fStarted = AudioTestBeaconGetRemaining(pBeacon) == AudioTestBeaconGetSize(pBeacon); + + uint32_t const cbBeaconRemaining = AudioTestBeaconGetRemaining(pBeacon); + AssertBreakStmt(cbBeaconRemaining, VERR_WRONG_ORDER); + + /* Limit to exactly one beacon (pre or post). */ + uint32_t const cbToWrite = RT_MIN(cbBuf, cbBeaconRemaining); + + rc = AudioTestBeaconWrite(pBeacon, pvBuf, cbToWrite); + if (RT_SUCCESS(rc)) + cbWritten = cbToWrite; + + if (fStarted) + LogRel2(("ValKit: Test #%RU32: Writing %s beacon begin\n", + pTst->idxTest, AudioTestBeaconTypeGetName(pBeacon->enmType))); + if (AudioTestBeaconIsComplete(pBeacon)) + { + LogRel2(("ValKit: Test #%RU32: Writing %s beacon end\n", + pTst->idxTest, AudioTestBeaconTypeGetName(pBeacon->enmType))); + + fGoToNextStage = true; + } + } + else + fGoToNextStage = true; + + if (fGoToNextStage) + { + if (pTst->enmState == AUDIOTESTSTATE_PRE) + pTst->enmState = AUDIOTESTSTATE_RUN; + else if (pTst->enmState == AUDIOTESTSTATE_POST) + pTst->enmState = AUDIOTESTSTATE_DONE; + } + break; + } + + case AUDIOTESTSTATE_RUN: + { + uint32_t const cbToWrite = RT_MIN(cbBuf, pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten); + if (cbToWrite) + rc = AudioTestToneGenerate(&pTst->t.TestTone.Tone, pvBuf, cbToWrite, &cbWritten); + if ( RT_SUCCESS(rc) + && cbWritten) + { + Assert(cbWritten == cbToWrite); + pTst->t.TestTone.u.Rec.cbWritten += cbWritten; + } + + LogRel3(("ValKit: Test #%RU32: Supplied %RU32 bytes of (capturing) audio data (%RU32 bytes left)\n", + pTst->idxTest, cbWritten, pTst->t.TestTone.u.Rec.cbToWrite - pTst->t.TestTone.u.Rec.cbWritten)); + + const bool fComplete = pTst->t.TestTone.u.Rec.cbWritten >= pTst->t.TestTone.u.Rec.cbToWrite; + if (fComplete) + { + LogRel(("ValKit: Test #%RU32: Recording done (took %RU32ms)\n", + pTst->idxTest, RTTimeMilliTS() - pTst->msStartedTS)); + + pTst->enmState = AUDIOTESTSTATE_POST; + + /* Re-use the beacon object, but this time it's the post beacon. */ + AudioTestBeaconInit(&pTst->t.TestTone.Beacon, pTst->idxTest, AUDIOTESTTONEBEACONTYPE_PLAY_POST, + &pTst->t.TestTone.Parms.Props); + } + break; + } + + case AUDIOTESTSTATE_DONE: + { + /* Handled below. */ + break; + } + + default: + AssertFailed(); + break; + } + + if (RT_SUCCESS(rc)) + rc = AudioTestObjWrite(pTst->Obj, pvBuf, cbWritten); + + if (pTst->enmState == AUDIOTESTSTATE_DONE) + { + AudioTestSetTestDone(pTst->pEntry); + + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + drvHostValKiUnregisterRecTest(pThis, pTst); + + pThis->pTestCurRec = NULL; + pTst = NULL; + + int rc2 = RTCritSectLeave(&pThis->CritSect); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + { + if (pTst->pEntry) + AudioTestSetTestFailed(pTst->pEntry, rc, "Injecting audio input data failed"); + LogRel(("ValKit: Test #%RU32: Failed with %Rrc\n", pTst->idxTest, rc)); + } + + pThis->cbRecordedTotal += cbWritten; /* Do a bit of accounting. */ + + *pcbRead = cbWritten; + + Log3Func(("returns %Rrc *pcbRead=%#x (%#x/%#x), %#x total\n", + rc, cbWritten, pTst ? pTst->t.TestTone.u.Rec.cbWritten : 0, pTst ? pTst->t.TestTone.u.Rec.cbToWrite : 0, + pThis->cbRecordedTotal)); + return VINF_SUCCESS; /** @todo Return rc here? */ +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHostValKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + return NULL; +} + + +/** + * Constructs a VaKit audio driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvHostValKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO); + LogRel(("Audio: Initializing VALKIT driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHostValKitAudioQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHostValKitAudioHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = NULL; + pThis->IHostAudio.pfnGetStatus = drvHostValKitAudioHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHostValKitAudioHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHostValKitAudioHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHostValKitAudioHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHostValKitAudioHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHostValKitAudioHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHostValKitAudioHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHostValKitAudioHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetReadable = drvHostValKitAudioHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamGetWritable = drvHostValKitAudioHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetState = drvHostValKitAudioHA_StreamGetState; + pThis->IHostAudio.pfnStreamPlay = drvHostValKitAudioHA_StreamPlay; + pThis->IHostAudio.pfnStreamCapture = drvHostValKitAudioHA_StreamCapture; + + int rc = RTCritSectInit(&pThis->CritSect); + AssertRCReturn(rc, rc); + rc = RTSemEventCreate(&pThis->EventSemEnded); + AssertRCReturn(rc, rc); + + pThis->cbPlayedTotal = 0; + pThis->cbRecordedTotal = 0; + pThis->cbPlayedSilence = 0; + pThis->cbPlayedNoTest = 0; + + pThis->cTestsTotal = 0; + pThis->fTestSetEnd = false; + + RTListInit(&pThis->lstTestsRec); + pThis->cTestsRec = 0; + RTListInit(&pThis->lstTestsPlay); + pThis->cTestsPlay = 0; + + ATSCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnHowdy = drvHostValKitHowdy; + Callbacks.pfnBye = drvHostValKitBye; + Callbacks.pfnTestSetBegin = drvHostValKitTestSetBegin; + Callbacks.pfnTestSetEnd = drvHostValKitTestSetEnd; + Callbacks.pfnTonePlay = drvHostValKitRegisterGuestRecTest; + Callbacks.pfnToneRecord = drvHostValKitRegisterGuestPlayTest; + Callbacks.pfnTestSetSendBegin = drvHostValKitTestSetSendBeginCallback; + Callbacks.pfnTestSetSendRead = drvHostValKitTestSetSendReadCallback; + Callbacks.pfnTestSetSendEnd = drvHostValKitTestSetSendEndCallback; + Callbacks.pvUser = pThis; + + /** @todo Make this configurable via CFGM. */ + const char *pszBindAddr = "127.0.0.1"; /* Only reachable for localhost for now. */ + uint32_t uBindPort = ATS_TCP_DEF_BIND_PORT_VALKIT; + + LogRel2(("ValKit: Debug logging enabled\n")); + + LogRel(("ValKit: Starting Audio Test Service (ATS) at %s:%RU32...\n", + pszBindAddr, uBindPort)); + + /* Dont' use rc here, as this will be reported back to PDM and will prevent VBox + * from starting -- not critical but warn the user though. */ + int rc2 = AudioTestSvcInit(&pThis->Srv, &Callbacks); + if (RT_SUCCESS(rc2)) + { + RTGETOPTUNION Val; + RT_ZERO(Val); + + Val.u32 = ATSCONNMODE_SERVER; /** @todo No client connection mode needed here (yet). Make this configurable via CFGM. */ + rc2 = AudioTestSvcHandleOption(&pThis->Srv, ATSTCPOPT_CONN_MODE, &Val); + AssertRC(rc2); + + Val.psz = pszBindAddr; + rc2 = AudioTestSvcHandleOption(&pThis->Srv, ATSTCPOPT_BIND_ADDRESS, &Val); + AssertRC(rc2); + + Val.u16 = uBindPort; + rc2 = AudioTestSvcHandleOption(&pThis->Srv, ATSTCPOPT_BIND_PORT, &Val); + AssertRC(rc2); + + rc2 = AudioTestSvcStart(&pThis->Srv); + } + + if (RT_SUCCESS(rc2)) + { + LogRel(("ValKit: Audio Test Service (ATS) running\n")); + + /** @todo Let the following be customizable by CFGM later. */ + rc2 = AudioTestPathCreateTemp(pThis->szPathTemp, sizeof(pThis->szPathTemp), "ValKitAudio"); + if (RT_SUCCESS(rc2)) + { + LogRel(("ValKit: Using temp dir '%s'\n", pThis->szPathTemp)); + rc2 = AudioTestPathGetTemp(pThis->szPathOut, sizeof(pThis->szPathOut)); + if (RT_SUCCESS(rc2)) + LogRel(("ValKit: Using output dir '%s'\n", pThis->szPathOut)); + } + } + + if (RT_FAILURE(rc2)) + LogRel(("ValKit: Error starting Audio Test Service (ATS), rc=%Rrc -- tests *will* fail!\n", rc2)); + + if (RT_FAILURE(rc)) /* This one *is* critical though. */ + LogRel(("ValKit: Initialization failed, rc=%Rrc\n", rc)); + + return rc; +} + +static DECLCALLBACK(void) drvHostValKitAudioDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHOSTVALKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVALKITAUDIO); + + LogRel(("ValKit: Shutting down Audio Test Service (ATS) ...\n")); + + int rc = AudioTestSvcStop(&pThis->Srv); + if (RT_SUCCESS(rc)) + rc = AudioTestSvcDestroy(&pThis->Srv); + + if (RT_SUCCESS(rc)) + { + LogRel(("ValKit: Shutdown of Audio Test Service (ATS) complete\n")); + drvHostValKitCleanup(pThis); + } + else + LogRel(("ValKit: Shutdown of Audio Test Service (ATS) failed, rc=%Rrc\n", rc)); + + /* Try cleaning up a bit. */ + RTDirRemove(pThis->szPathTemp); + RTDirRemove(pThis->szPathOut); + + RTSemEventDestroy(pThis->EventSemEnded); + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + int rc2 = RTCritSectDelete(&pThis->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + if (RT_FAILURE(rc)) + LogRel(("ValKit: Destruction failed, rc=%Rrc\n", rc)); +} + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostValidationKitAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "ValidationKitAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "ValidationKitAudio audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTVALKITAUDIO), + /* pfnConstruct */ + drvHostValKitAudioConstruct, + /* pfnDestruct */ + drvHostValKitAudioDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + |