summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Audio
parentInitial commit. (diff)
downloadvirtualbox-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 'src/VBox/Devices/Audio')
-rw-r--r--src/VBox/Devices/Audio/AudioHlp.cpp656
-rw-r--r--src/VBox/Devices/Audio/AudioHlp.h136
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h315
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer.cpp2124
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer.h252
-rw-r--r--src/VBox/Devices/Audio/AudioMixer.cpp2732
-rw-r--r--src/VBox/Devices/Audio/AudioMixer.h350
-rw-r--r--src/VBox/Devices/Audio/AudioTest.cpp3580
-rw-r--r--src/VBox/Devices/Audio/AudioTest.h491
-rw-r--r--src/VBox/Devices/Audio/AudioTestService.cpp1322
-rw-r--r--src/VBox/Devices/Audio/AudioTestService.h217
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceClient.cpp608
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceClient.h83
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceInternal.h276
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceProtocol.cpp37
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceProtocol.h268
-rw-r--r--src/VBox/Devices/Audio/AudioTestServiceTcp.cpp967
-rw-r--r--src/VBox/Devices/Audio/DevHda.cpp5469
-rw-r--r--src/VBox/Devices/Audio/DevHda.h918
-rw-r--r--src/VBox/Devices/Audio/DevHdaCodec.cpp2893
-rw-r--r--src/VBox/Devices/Audio/DevHdaCodec.h909
-rw-r--r--src/VBox/Devices/Audio/DevHdaStream.cpp2540
-rw-r--r--src/VBox/Devices/Audio/DevHdaStream.h330
-rw-r--r--src/VBox/Devices/Audio/DevIchAc97.cpp4829
-rw-r--r--src/VBox/Devices/Audio/DevSB16.cpp3159
-rw-r--r--src/VBox/Devices/Audio/DrvAudio.cpp5045
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp1611
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp364
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h72
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h99
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp2925
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm150
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioDSound.cpp2881
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp237
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h87
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioDebug.cpp409
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioNull.cpp328
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioOss.cpp1027
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp2432
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp375
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h41
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h105
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioValidationKit.cpp1747
-rw-r--r--src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp3376
-rw-r--r--src/VBox/Devices/Audio/Makefile.kup0
-rw-r--r--src/VBox/Devices/Audio/testcase/Makefile.kmk83
-rw-r--r--src/VBox/Devices/Audio/testcase/tstAudioClient3.cpp112
-rw-r--r--src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp916
-rw-r--r--src/VBox/Devices/Audio/testcase/tstAudioTestService.cpp178
49 files changed, 60061 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/AudioHlp.cpp b/src/VBox/Devices/Audio/AudioHlp.cpp
new file mode 100644
index 00000000..b3f54167
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioHlp.cpp
@@ -0,0 +1,656 @@
+/* $Id: AudioHlp.cpp $ */
+/** @file
+ * Audio helper routines.
+ *
+ * These are used with both drivers and devices.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/formats/riff.h>
+
+#define LOG_GROUP LOG_GROUP_DRV_AUDIO
+#include <VBox/log.h>
+
+#include <VBox/err.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "AudioHlp.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct AUDIOWAVEFILEHDR
+{
+ RTRIFFHDR Hdr;
+ RTRIFFWAVEFMTEXTCHUNK FmtExt;
+ RTRIFFCHUNK Data;
+} AUDIOWAVEFILEHDR;
+
+
+#if 0 /* unused, no header prototypes */
+
+/**
+ * Retrieves the matching PDMAUDIOFMT for the given bits + signing flag.
+ *
+ * @return Matching PDMAUDIOFMT value.
+ * @retval PDMAUDIOFMT_INVALID if unsupported @a cBits value.
+ *
+ * @param cBits The number of bits in the audio format.
+ * @param fSigned Whether the audio format is signed @c true or not.
+ */
+PDMAUDIOFMT DrvAudioAudFmtBitsToFormat(uint8_t cBits, bool fSigned)
+{
+ if (fSigned)
+ {
+ switch (cBits)
+ {
+ case 8: return PDMAUDIOFMT_S8;
+ case 16: return PDMAUDIOFMT_S16;
+ case 32: return PDMAUDIOFMT_S32;
+ default: AssertMsgFailedReturn(("Bogus audio bits %RU8\n", cBits), PDMAUDIOFMT_INVALID);
+ }
+ }
+ else
+ {
+ switch (cBits)
+ {
+ case 8: return PDMAUDIOFMT_U8;
+ case 16: return PDMAUDIOFMT_U16;
+ case 32: return PDMAUDIOFMT_U32;
+ default: AssertMsgFailedReturn(("Bogus audio bits %RU8\n", cBits), PDMAUDIOFMT_INVALID);
+ }
+ }
+}
+
+/**
+ * Returns an unique file name for this given audio connector instance.
+ *
+ * @return Allocated file name. Must be free'd using RTStrFree().
+ * @param uInstance Driver / device instance.
+ * @param pszPath Path name of the file to delete. The path must exist.
+ * @param pszSuffix File name suffix to use.
+ */
+char *DrvAudioDbgGetFileNameA(uint8_t uInstance, const char *pszPath, const char *pszSuffix)
+{
+ char szFileName[64];
+ RTStrPrintf(szFileName, sizeof(szFileName), "drvAudio%RU8-%s", uInstance, pszSuffix);
+
+ char szFilePath[RTPATH_MAX];
+ int rc2 = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath);
+ AssertRC(rc2);
+ rc2 = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName);
+ AssertRC(rc2);
+
+ return RTStrDup(szFilePath);
+}
+
+#endif /* unused */
+
+/**
+ * Checks whether a given stream configuration is valid or not.
+ *
+ * @note See notes on AudioHlpPcmPropsAreValid().
+ *
+ * Returns @c true if configuration is valid, @c false if not.
+ * @param pCfg Stream configuration to check.
+ */
+bool AudioHlpStreamCfgIsValid(PCPDMAUDIOSTREAMCFG pCfg)
+{
+ /* Ugly! HDA attach code calls us with uninitialized (all zero) config. */
+ if (PDMAudioPropsHz(&pCfg->Props) != 0)
+ {
+ if (PDMAudioStrmCfgIsValid(pCfg))
+ {
+ if ( pCfg->enmDir == PDMAUDIODIR_IN
+ || pCfg->enmDir == PDMAUDIODIR_OUT)
+ return AudioHlpPcmPropsAreValidAndSupported(&pCfg->Props);
+ }
+ }
+ return false;
+}
+
+/**
+ * Calculates the audio bit rate of the given bits per sample, the Hz and the number
+ * of audio channels.
+ *
+ * Divide the result by 8 to get the byte rate.
+ *
+ * @returns Bitrate.
+ * @param cBits Number of bits per sample.
+ * @param uHz Hz (Hertz) rate.
+ * @param cChannels Number of audio channels.
+ */
+uint32_t AudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels)
+{
+ return cBits * uHz * cChannels;
+}
+
+
+/**
+ * Checks whether given PCM properties are valid *and* supported by the audio stack or not.
+ *
+ * @returns @c true if the properties are valid and supported, @c false if not.
+ * @param pProps The PCM properties to check.
+ *
+ * @note Use PDMAudioPropsAreValid() to just check the validation bits.
+ */
+bool AudioHlpPcmPropsAreValidAndSupported(PCPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, false);
+
+ if (!PDMAudioPropsAreValid(pProps))
+ return false;
+
+ /* Properties seem valid, now check if we actually support those. */
+ switch (PDMAudioPropsSampleSize(pProps))
+ {
+ case 1: /* 8 bit */
+ /* Signed / unsigned. */
+ break;
+ case 2: /* 16 bit */
+ /* Signed / unsigned. */
+ break;
+ /** @todo Do we need support for 24 bit samples? */
+ case 4: /* 32 bit */
+ /* Signed / unsigned. */
+ break;
+ case 8: /* 64-bit raw */
+ if ( !PDMAudioPropsIsSigned(pProps)
+ || !pProps->fRaw)
+ return false;
+ break;
+ default:
+ return false;
+ }
+
+ if (!pProps->fSwapEndian) /** @todo Handling Big Endian audio data is not supported yet. */
+ return true;
+ return false;
+}
+
+
+/*********************************************************************************************************************************
+* Audio File Helpers *
+*********************************************************************************************************************************/
+
+/**
+ * Constructs an unique file name, based on the given path and the audio file type.
+ *
+ * @returns VBox status code.
+ * @param pszDst Where to store the constructed file name.
+ * @param cbDst Size of the destination buffer (bytes; incl terminator).
+ * @param pszPath Base path to use. If NULL or empty, the user's
+ * temporary directory will be used.
+ * @param pszNameFmt A name for better identifying the file.
+ * @param va Arguments for @a pszNameFmt.
+ * @param uInstance Device / driver instance which is using this file.
+ * @param enmType Audio file type to construct file name for.
+ * @param fFlags File naming flags, AUDIOHLPFILENAME_FLAGS_XXX.
+ * @param chTweak Retry tweak character.
+ */
+static int audioHlpConstructPathWorker(char *pszDst, size_t cbDst, const char *pszPath, const char *pszNameFmt, va_list va,
+ uint32_t uInstance, AUDIOHLPFILETYPE enmType, uint32_t fFlags, char chTweak)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszNameFmt, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~AUDIOHLPFILENAME_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /* Validate the type and translate it into a suffix. */
+ const char *pszSuffix = NULL;
+ switch (enmType)
+ {
+ case AUDIOHLPFILETYPE_RAW: pszSuffix = ".pcm"; break;
+ case AUDIOHLPFILETYPE_WAV: pszSuffix = ".wav"; break;
+ case AUDIOHLPFILETYPE_INVALID:
+ case AUDIOHLPFILETYPE_32BIT_HACK:
+ break; /* no default */
+ }
+ AssertMsgReturn(pszSuffix, ("enmType=%d\n", enmType), VERR_INVALID_PARAMETER);
+
+ /*
+ * The directory. Make sure it exists and ends with a path separator.
+ */
+ int rc;
+ if (!pszPath || !*pszPath)
+ rc = RTPathTemp(pszDst, cbDst);
+ else
+ {
+ AssertPtrReturn(pszDst, VERR_INVALID_POINTER);
+ rc = RTStrCopy(pszDst, cbDst, pszPath);
+ }
+ AssertRCReturn(rc, rc);
+
+ if (!RTDirExists(pszDst))
+ {
+ rc = RTDirCreateFullPath(pszDst, RTFS_UNIX_IRWXU);
+ AssertRCReturn(rc, rc);
+ }
+
+ size_t offDst = RTPathEnsureTrailingSeparator(pszDst, cbDst);
+ AssertReturn(offDst > 0, VERR_BUFFER_OVERFLOW);
+ Assert(offDst < cbDst);
+
+ /*
+ * The filename.
+ */
+ /* Start with a ISO timestamp w/ colons replaced by dashes if requested. */
+ if (fFlags & AUDIOHLPFILENAME_FLAGS_TS)
+ {
+ RTTIMESPEC NowTimeSpec;
+ RTTIME NowUtc;
+ AssertReturn(RTTimeToString(RTTimeExplode(&NowUtc, RTTimeNow(&NowTimeSpec)), &pszDst[offDst], cbDst - offDst),
+ VERR_BUFFER_OVERFLOW);
+
+ /* Change the two colons in the time part to dashes. */
+ char *pchColon = &pszDst[offDst];
+ while ((pchColon = strchr(pchColon, ':')) != NULL)
+ *pchColon++ = '-';
+
+ offDst += strlen(&pszDst[offDst]);
+ Assert(pszDst[offDst - 1] == 'Z');
+
+ /* Append a dash to separate the timestamp from the name. */
+ AssertReturn(offDst + 2 <= cbDst, VERR_BUFFER_OVERFLOW);
+ pszDst[offDst++] = '-';
+ pszDst[offDst] = '\0';
+ }
+
+ /* Append the filename, instance, retry-tweak and suffix. */
+ va_list vaCopy;
+ va_copy(vaCopy, va);
+ ssize_t cchTail;
+ if (chTweak == '\0')
+ cchTail = RTStrPrintf2(&pszDst[offDst], cbDst - offDst, "%N-%u%s", pszNameFmt, &vaCopy, uInstance, pszSuffix);
+ else
+ cchTail = RTStrPrintf2(&pszDst[offDst], cbDst - offDst, "%N-%u%c%s", pszNameFmt, &vaCopy, uInstance, chTweak, pszSuffix);
+ va_end(vaCopy);
+ AssertReturn(cchTail > 0, VERR_BUFFER_OVERFLOW);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for AudioHlpFileCreateF and AudioHlpFileCreateAndOpenEx that allocates
+ * and initializes a AUDIOHLPFILE instance.
+ */
+static int audioHlpFileCreateWorker(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType, const char *pszPath)
+{
+ AssertReturn(!(fFlags & ~AUDIOHLPFILE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+
+ size_t const cbPath = strlen(pszPath) + 1;
+ PAUDIOHLPFILE pFile = (PAUDIOHLPFILE)RTMemAllocVar(RT_UOFFSETOF_DYN(AUDIOHLPFILE, szName[cbPath]));
+ AssertPtrReturn(pFile, VERR_NO_MEMORY);
+
+ pFile->enmType = enmType;
+ pFile->fFlags = fFlags;
+ pFile->cbWaveData = 0;
+ pFile->hFile = NIL_RTFILE;
+ memcpy(pFile->szName, pszPath, cbPath);
+
+ *ppFile = pFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates an instance of AUDIOHLPFILE with the given filename and type.
+ *
+ * @note This does <b>NOT</b> create the file, see AudioHlpFileOpen for that.
+ *
+ * @returns VBox status code.
+ * @param ppFile Where to return the pointer to the audio debug file
+ * instance on success.
+ * @param fFlags AUDIOHLPFILE_FLAGS_XXX.
+ * @param enmType The audio file type to produce.
+ * @param pszPath The directory path. The temporary directory will be
+ * used if NULL or empty.
+ * @param fFilename AUDIOHLPFILENAME_FLAGS_XXX.
+ * @param uInstance The instance number (will be appended to the filename
+ * with a dash inbetween).
+ * @param pszNameFmt The filename format string.
+ * @param ... Arguments to the filename format string.
+ */
+int AudioHlpFileCreateF(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType,
+ const char *pszPath, uint32_t fFilename, uint32_t uInstance, const char *pszNameFmt, ...)
+{
+ *ppFile = NULL;
+
+ /*
+ * Construct the filename first.
+ */
+ char szPath[RTPATH_MAX];
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = audioHlpConstructPathWorker(szPath, sizeof(szPath), pszPath, pszNameFmt, va, uInstance, enmType, fFilename, '\0');
+ va_end(va);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Allocate and initializes a debug file instance with that filename path.
+ */
+ return audioHlpFileCreateWorker(ppFile, fFlags, enmType, szPath);
+}
+
+
+/**
+ * Destroys a formerly created audio file.
+ *
+ * @param pFile Audio file (object) to destroy.
+ */
+void AudioHlpFileDestroy(PAUDIOHLPFILE pFile)
+{
+ if (pFile)
+ {
+ AudioHlpFileClose(pFile);
+ RTMemFree(pFile);
+ }
+}
+
+
+/**
+ * Opens or creates an audio file.
+ *
+ * @returns VBox status code.
+ * @param pFile Pointer to audio file handle to use.
+ * @param fOpen Open flags.
+ * Use AUDIOHLPFILE_DEFAULT_OPEN_FLAGS for the default open flags.
+ * @param pProps PCM properties to use.
+ */
+int AudioHlpFileOpen(PAUDIOHLPFILE pFile, uint64_t fOpen, PCPDMAUDIOPCMPROPS pProps)
+{
+ int rc;
+
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+ /** @todo Validate fOpen flags. */
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsAreValid(pProps));
+
+ /*
+ * Raw files just needs to be opened.
+ */
+ if (pFile->enmType == AUDIOHLPFILETYPE_RAW)
+ rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen);
+ /*
+ * Wave files needs a header to be constructed and we need to take note of where
+ * there are sizes to update later when closing the file.
+ */
+ else if (pFile->enmType == AUDIOHLPFILETYPE_WAV)
+ {
+ /* Construct the header. */
+ AUDIOWAVEFILEHDR FileHdr;
+ FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
+ FileHdr.Hdr.cbFile = 0; /* need to update this later */
+ FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
+ FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
+ FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
+ FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
+ FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
+ FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
+ FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
+ FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
+ FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
+ FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ FileHdr.FmtExt.Data.fChannelMask = 0;
+ for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
+ {
+ PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
+ AssertLogRelMsgReturn(idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD && idCh < PDMAUDIOCHANNELID_END_STANDARD,
+ ("Invalid channel ID %d for channel #%u", idCh, idxCh), VERR_INVALID_PARAMETER);
+ AssertLogRelMsgReturn(!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)),
+ ("Channel #%u repeats channel ID %d", idxCh, idCh), VERR_INVALID_PARAMETER);
+ FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
+ }
+
+ RTUUID UuidTmp;
+ rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
+ AssertRCReturn(rc, rc);
+ FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
+
+ FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
+ FileHdr.Data.cbChunk = 0; /* need to update this later */
+
+ /* Open the file and write out the header. */
+ rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(pFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(pFile->hFile);
+ pFile->hFile = NIL_RTFILE;
+ }
+ }
+ }
+ else
+ AssertFailedStmt(rc = VERR_INTERNAL_ERROR_3);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->cbWaveData = 0;
+ LogRel2(("Audio: Opened file '%s'\n", pFile->szName));
+ }
+ else
+ LogRel(("Audio: Failed opening file '%s': %Rrc\n", pFile->szName, rc));
+ return rc;
+}
+
+
+/**
+ * Creates a debug file structure and opens a file for it, extended version.
+ *
+ * @returns VBox status code.
+ * @param ppFile Where to return the debug file instance on success.
+ * @param enmType The file type.
+ * @param pszDir The directory to open the file in.
+ * @param iInstance The device/driver instance.
+ * @param fFilename AUDIOHLPFILENAME_FLAGS_XXX.
+ * @param fCreate AUDIOHLPFILE_FLAGS_XXX.
+ * @param pProps PCM audio properties for the file.
+ * @param fOpen RTFILE_O_XXX or AUDIOHLPFILE_DEFAULT_OPEN_FLAGS.
+ * @param pszNameFmt The base filename.
+ * @param ... Filename format arguments.
+ */
+int AudioHlpFileCreateAndOpenEx(PAUDIOHLPFILE *ppFile, AUDIOHLPFILETYPE enmType, const char *pszDir,
+ uint32_t iInstance, uint32_t fFilename, uint32_t fCreate,
+ PCPDMAUDIOPCMPROPS pProps, uint64_t fOpen, const char *pszNameFmt, ...)
+{
+ *ppFile = NULL;
+
+ for (uint32_t iTry = 0; ; iTry++)
+ {
+ /* Format the path to the filename. */
+ char szFile[RTPATH_MAX];
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = audioHlpConstructPathWorker(szFile, sizeof(szFile), pszDir, pszNameFmt, va, iInstance, enmType, fFilename,
+ iTry == 0 ? '\0' : iTry + 'a');
+ va_end(va);
+ AssertRCReturn(rc, rc);
+
+ /* Create an debug audio file instance with the filename path. */
+ PAUDIOHLPFILE pFile = NULL;
+ rc = audioHlpFileCreateWorker(&pFile, fCreate, enmType, szFile);
+ AssertRCReturn(rc, rc);
+
+ /* Try open it. */
+ rc = AudioHlpFileOpen(pFile, fOpen, pProps);
+ if (RT_SUCCESS(rc))
+ {
+ *ppFile = pFile;
+ return rc;
+ }
+ AudioHlpFileDestroy(pFile);
+
+ AssertReturn(iTry < 16, rc);
+ }
+}
+
+
+/**
+ * Creates a debug wav-file structure and opens a file for it, default flags.
+ *
+ * @returns VBox status code.
+ * @param ppFile Where to return the debug file instance on success.
+ * @param pszDir The directory to open the file in.
+ * @param pszName The base filename.
+ * @param iInstance The device/driver instance.
+ * @param pProps PCM audio properties for the file.
+ */
+int AudioHlpFileCreateAndOpen(PAUDIOHLPFILE *ppFile, const char *pszDir, const char *pszName,
+ uint32_t iInstance, PCPDMAUDIOPCMPROPS pProps)
+{
+ return AudioHlpFileCreateAndOpenEx(ppFile, AUDIOHLPFILETYPE_WAV, pszDir, iInstance,
+ AUDIOHLPFILENAME_FLAGS_NONE, AUDIOHLPFILE_FLAGS_NONE,
+ pProps, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS, "%s", pszName);
+}
+
+
+/**
+ * Closes an audio file.
+ *
+ * @returns VBox status code.
+ * @param pFile Audio file handle to close.
+ */
+int AudioHlpFileClose(PAUDIOHLPFILE pFile)
+{
+ if (!pFile || pFile->hFile == NIL_RTFILE)
+ return VINF_SUCCESS;
+
+ /*
+ * Wave files needs to update the data size and file size in the header.
+ */
+ if (pFile->enmType == AUDIOHLPFILETYPE_WAV)
+ {
+ uint32_t const cbFile = sizeof(AUDIOWAVEFILEHDR) - sizeof(RTRIFFCHUNK) + (uint32_t)pFile->cbWaveData;
+ uint32_t const cbData = (uint32_t)pFile->cbWaveData;
+
+ int rc2;
+ rc2 = RTFileWriteAt(pFile->hFile, RT_UOFFSETOF(AUDIOWAVEFILEHDR, Hdr.cbFile), &cbFile, sizeof(cbFile), NULL);
+ AssertRC(rc2);
+ rc2 = RTFileWriteAt(pFile->hFile, RT_UOFFSETOF(AUDIOWAVEFILEHDR, Data.cbChunk), &cbData, sizeof(cbData), NULL);
+ AssertRC(rc2);
+ }
+
+ /*
+ * Do the closing.
+ */
+ int rc = RTFileClose(pFile->hFile);
+ if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE)
+ pFile->hFile = NIL_RTFILE;
+
+ if (RT_SUCCESS(rc))
+ LogRel2(("Audio: Closed file '%s' (%'RU64 bytes PCM data)\n", pFile->szName, pFile->cbWaveData));
+ else
+ LogRel(("Audio: Failed closing file '%s': %Rrc\n", pFile->szName, rc));
+
+ /*
+ * Delete empty file if requested.
+ */
+ if ( !(pFile->fFlags & AUDIOHLPFILE_FLAGS_KEEP_IF_EMPTY)
+ && pFile->cbWaveData == 0
+ && RT_SUCCESS(rc))
+ AudioHlpFileDelete(pFile);
+
+ return rc;
+}
+
+
+/**
+ * Deletes an audio file.
+ *
+ * @returns VBox status code.
+ * @param pFile Audio file to delete.
+ */
+int AudioHlpFileDelete(PAUDIOHLPFILE pFile)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+
+ int rc = RTFileDelete(pFile->szName);
+ if (RT_SUCCESS(rc))
+ LogRel2(("Audio: Deleted file '%s'\n", pFile->szName));
+ else if (rc == VERR_FILE_NOT_FOUND) /* Don't bitch if the file is not around anymore. */
+ rc = VINF_SUCCESS;
+
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Failed deleting file '%s', rc=%Rrc\n", pFile->szName, rc));
+
+ return rc;
+}
+
+
+/**
+ * Returns whether the given audio file is open and in use or not.
+ *
+ * @returns True if open, false if not.
+ * @param pFile Audio file to check open status for.
+ */
+bool AudioHlpFileIsOpen(PAUDIOHLPFILE pFile)
+{
+ if (!pFile || pFile->hFile == NIL_RTFILE)
+ return false;
+
+ return RTFileIsValid(pFile->hFile);
+}
+
+
+/**
+ * Write PCM data to a wave (.WAV) file.
+ *
+ * @returns VBox status code.
+ * @param pFile Audio file to write PCM data to.
+ * @param pvBuf Audio data to write.
+ * @param cbBuf Size (in bytes) of audio data to write.
+ */
+int AudioHlpFileWrite(PAUDIOHLPFILE pFile, const void *pvBuf, size_t cbBuf)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ int rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL);
+ if (RT_SUCCESS(rc))
+ pFile->cbWaveData += cbBuf;
+
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Audio/AudioHlp.h b/src/VBox/Devices/Audio/AudioHlp.h
new file mode 100644
index 00000000..c592a778
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioHlp.h
@@ -0,0 +1,136 @@
+/* $Id: AudioHlp.h $ */
+/** @file
+ * Audio helper routines.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioHlp_h
+#define VBOX_INCLUDED_SRC_Audio_AudioHlp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <limits.h>
+
+#include <iprt/circbuf.h>
+#include <iprt/critsect.h>
+#include <iprt/file.h>
+#include <iprt/path.h>
+
+#include <VBox/vmm/pdmaudioifs.h>
+
+/** @name Audio calculation helper methods.
+ * @{ */
+uint32_t AudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels);
+/** @} */
+
+/** @name Audio PCM properties helper methods.
+ * @{ */
+bool AudioHlpPcmPropsAreValidAndSupported(PCPDMAUDIOPCMPROPS pProps);
+/** @} */
+
+/** @name Audio configuration helper methods.
+ * @{ */
+bool AudioHlpStreamCfgIsValid(PCPDMAUDIOSTREAMCFG pCfg);
+/** @} */
+
+
+/** @name AUDIOHLPFILE_FLAGS_XXX
+ * @{ */
+/** No flags defined. */
+#define AUDIOHLPFILE_FLAGS_NONE UINT32_C(0)
+/** Keep the audio file even if it contains no audio data. */
+#define AUDIOHLPFILE_FLAGS_KEEP_IF_EMPTY RT_BIT_32(0)
+/** Audio file flag validation mask. */
+#define AUDIOHLPFILE_FLAGS_VALID_MASK UINT32_C(0x1)
+/** @} */
+
+/** Audio file default open flags. */
+#define AUDIOHLPFILE_DEFAULT_OPEN_FLAGS (RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE)
+
+/**
+ * Audio file types.
+ */
+typedef enum AUDIOHLPFILETYPE
+{
+ /** The customary invalid zero value. */
+ AUDIOHLPFILETYPE_INVALID = 0,
+ /** Raw (PCM) file. */
+ AUDIOHLPFILETYPE_RAW,
+ /** Wave (.WAV) file. */
+ AUDIOHLPFILETYPE_WAV,
+ /** Hack to blow the type up to 32-bit. */
+ AUDIOHLPFILETYPE_32BIT_HACK = 0x7fffffff
+} AUDIOHLPFILETYPE;
+
+/** @name AUDIOHLPFILENAME_FLAGS_XXX
+ * @{ */
+/** No flags defined. */
+#define AUDIOHLPFILENAME_FLAGS_NONE UINT32_C(0)
+/** Adds an ISO timestamp to the file name. */
+#define AUDIOHLPFILENAME_FLAGS_TS RT_BIT_32(0)
+/** Valid flag mask. */
+#define AUDIOHLPFILENAME_FLAGS_VALID_MASK AUDIOHLPFILENAME_FLAGS_TS
+/** @} */
+
+/**
+ * Audio file handle.
+ */
+typedef struct AUDIOHLPFILE
+{
+ /** Type of the audio file. */
+ AUDIOHLPFILETYPE enmType;
+ /** Audio file flags, AUDIOHLPFILE_FLAGS_XXX. */
+ uint32_t fFlags;
+ /** Amount of wave data written. */
+ uint64_t cbWaveData;
+ /** Actual file handle. */
+ RTFILE hFile;
+ /** File name and path. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} AUDIOHLPFILE;
+/** Pointer to an audio file handle. */
+typedef AUDIOHLPFILE *PAUDIOHLPFILE;
+
+/** @name Audio file methods.
+ * @{ */
+int AudioHlpFileCreateAndOpen(PAUDIOHLPFILE *ppFile, const char *pszDir, const char *pszName,
+ uint32_t iInstance, PCPDMAUDIOPCMPROPS pProps);
+int AudioHlpFileCreateAndOpenEx(PAUDIOHLPFILE *ppFile, AUDIOHLPFILETYPE enmType, const char *pszDir,
+ uint32_t iInstance, uint32_t fFilename, uint32_t fCreate,
+ PCPDMAUDIOPCMPROPS pProps, uint64_t fOpen, const char *pszName, ...);
+int AudioHlpFileCreateF(PAUDIOHLPFILE *ppFile, uint32_t fFlags, AUDIOHLPFILETYPE enmType,
+ const char *pszPath, uint32_t fFilename, uint32_t uInstance, const char *pszFileFmt, ...);
+
+void AudioHlpFileDestroy(PAUDIOHLPFILE pFile);
+int AudioHlpFileOpen(PAUDIOHLPFILE pFile, uint64_t fOpen, PCPDMAUDIOPCMPROPS pProps);
+int AudioHlpFileClose(PAUDIOHLPFILE pFile);
+int AudioHlpFileDelete(PAUDIOHLPFILE pFile);
+bool AudioHlpFileIsOpen(PAUDIOHLPFILE pFile);
+int AudioHlpFileWrite(PAUDIOHLPFILE pFile, const void *pvBuf, size_t cbBuf);
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioHlp_h */
+
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h b/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h
new file mode 100644
index 00000000..3913449b
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixBuffer-Convert.cpp.h
@@ -0,0 +1,315 @@
+/* $Id: AudioMixBuffer-Convert.cpp.h $ */
+/** @file
+ * Audio mixing buffer - Format conversion template.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+
+/* used to be: #define AUDMIXBUF_CONVERT(a_Name, a_Type, a_Min, a_Max, a_fSigned, a_cShift) */
+
+/* Clips a specific output value to a single sample value. */
+DECLINLINE(int32_t) RT_CONCAT(audioMixBufSampleFrom,a_Name)(a_Type aVal)
+{
+ /* left shifting of signed values is not defined, therefore the intermediate uint64_t cast */
+ if (a_fSigned)
+ return (int32_t) (((uint32_t) ((int32_t) aVal )) << (32 - a_cShift));
+ return (int32_t) (((uint32_t) ((int32_t) aVal - ((a_Max >> 1) + 1))) << (32 - a_cShift));
+}
+
+/* Clips a single sample value to a specific output value. */
+DECLINLINE(a_Type) RT_CONCAT(audioMixBufSampleTo,a_Name)(int32_t iVal)
+{
+ if (a_fSigned)
+ return (a_Type) (iVal >> (32 - a_cShift));
+ return (a_Type) ((iVal >> (32 - a_cShift)) + ((a_Max >> 1) + 1));
+}
+
+/* Encoders for peek: */
+
+/* Generic */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncodeGeneric,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames,
+ PAUDIOMIXBUFPEEKSTATE pState)
+{
+ RT_NOREF_PV(pState);
+ uintptr_t const cSrcChannels = pState->cSrcChannels;
+ uintptr_t const cDstChannels = pState->cDstChannels;
+ a_Type *pDst = (a_Type *)pvDst;
+ while (cFrames-- > 0)
+ {
+ uintptr_t idxDst = cDstChannels;
+ while (idxDst-- > 0)
+ {
+ intptr_t idxSrc = pState->aidxChannelMap[idxDst];
+ if (idxSrc >= 0)
+ pDst[idxDst] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[idxSrc]);
+ else if (idxSrc != -2)
+ pDst[idxDst] = (a_fSigned) ? 0 : (a_Max >> 1);
+ else
+ pDst[idxDst] = 0;
+ }
+ pDst += cDstChannels;
+ pi32Src += cSrcChannels;
+ }
+}
+
+/* 2ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode2ChTo2Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames,
+ PAUDIOMIXBUFPEEKSTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type *pDst = (a_Type *)pvDst;
+ while (cFrames-- > 0)
+ {
+ pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]);
+ pDst[1] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[1]);
+ AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n",
+ &pi32Src[0], pi32Src[0], pi32Src[1], (int32_t)pDst[0], (int32_t)pDst[1]));
+ pDst += 2;
+ pi32Src += 2;
+ }
+}
+
+/* 2ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode2ChTo1Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames,
+ PAUDIOMIXBUFPEEKSTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type *pDst = (a_Type *)pvDst;
+ while (cFrames-- > 0)
+ {
+ pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(audioMixBufBlendSampleRet(pi32Src[0], pi32Src[1]));
+ pDst += 1;
+ pi32Src += 2;
+ }
+}
+
+/* 1ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode1ChTo2Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames,
+ PAUDIOMIXBUFPEEKSTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type *pDst = (a_Type *)pvDst;
+ while (cFrames-- > 0)
+ {
+ pDst[0] = pDst[1] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]);
+ pDst += 2;
+ pi32Src += 1;
+ }
+}
+/* 1ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufEncode1ChTo1Ch,a_Name)(void *pvDst, int32_t const *pi32Src, uint32_t cFrames,
+ PAUDIOMIXBUFPEEKSTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type *pDst = (a_Type *)pvDst;
+ while (cFrames-- > 0)
+ {
+ pDst[0] = RT_CONCAT(audioMixBufSampleTo,a_Name)(pi32Src[0]);
+ pDst += 1;
+ pi32Src += 1;
+ }
+}
+
+/* Decoders for write: */
+
+/* Generic */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecodeGeneric,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames,
+ PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ uintptr_t const cSrcChannels = pState->cSrcChannels;
+ uintptr_t const cDstChannels = pState->cDstChannels;
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ uintptr_t idxDst = cDstChannels;
+ while (idxDst-- > 0)
+ {
+ intptr_t idxSrc = pState->aidxChannelMap[idxDst];
+ if (idxSrc >= 0)
+ pi32Dst[idxDst] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[idxSrc]);
+ else if (idxSrc != -2)
+ pi32Dst[idxDst] = (a_fSigned) ? 0 : (a_Max >> 1);
+ else
+ pi32Dst[idxDst] = 0;
+ }
+ pi32Dst += cDstChannels;
+ pSrc += cSrcChannels;
+ }
+}
+
+/* 2ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode2ChTo2Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames,
+ PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]);
+ pi32Dst[1] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1]);
+ AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n",
+ &pSrc[0], (int32_t)pSrc[0], (int32_t)pSrc[1], pi32Dst[0], pi32Dst[1]));
+ pi32Dst += 2;
+ pSrc += 2;
+ }
+}
+
+/* 2ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode2ChTo1Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames,
+ PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ pi32Dst[0] = audioMixBufBlendSampleRet(RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]),
+ RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1]));
+ pi32Dst += 1;
+ pSrc += 2;
+ }
+}
+
+/* 1ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode1ChTo2Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames,
+ PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ pi32Dst[1] = pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]);
+ pi32Dst += 2;
+ pSrc += 1;
+ }
+}
+
+/* 1ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT(audioMixBufDecode1ChTo1Ch,a_Name)(int32_t *pi32Dst, void const *pvSrc, uint32_t cFrames,
+ PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ pi32Dst[0] = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]);
+ pi32Dst += 1;
+ pSrc += 1;
+ }
+}
+
+/* Decoders for blending: */
+
+/* Generic */
+static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecodeGeneric,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc,
+ uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ uintptr_t const cSrcChannels = pState->cSrcChannels;
+ uintptr_t const cDstChannels = pState->cDstChannels;
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ uintptr_t idxDst = cDstChannels;
+ while (idxDst-- > 0)
+ {
+ intptr_t idxSrc = pState->aidxChannelMap[idxDst];
+ if (idxSrc >= 0)
+ audioMixBufBlendSample(&pi32Dst[idxDst], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[idxSrc]));
+ }
+ pi32Dst += cDstChannels;
+ pSrc += cSrcChannels;
+ }
+}
+
+/* 2ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode2ChTo2Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc,
+ uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ audioMixBufBlendSample(&pi32Dst[0], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]));
+ audioMixBufBlendSample(&pi32Dst[1], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1]));
+ AUDMIXBUF_MACRO_LOG(("%p: %RI32 / %RI32 => %RI32 / %RI32\n",
+ &pSrc[0], (int32_t)pSrc[0], (int32_t)pSrc[1], pi32Dst[0], pi32Dst[1]));
+ pi32Dst += 2;
+ pSrc += 2;
+ }
+}
+
+/* 2ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode2ChTo1Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc,
+ uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ audioMixBufBlendSample(&pi32Dst[0], audioMixBufBlendSampleRet(RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]),
+ RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[1])));
+ pi32Dst += 1;
+ pSrc += 2;
+ }
+}
+
+/* 1ch -> 2ch */
+static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode1ChTo2Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc,
+ uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ int32_t const i32Src = RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]);
+ audioMixBufBlendSample(&pi32Dst[0], i32Src);
+ audioMixBufBlendSample(&pi32Dst[1], i32Src);
+ pi32Dst += 2;
+ pSrc += 1;
+ }
+}
+
+/* 1ch -> 1ch */
+static DECLCALLBACK(void) RT_CONCAT3(audioMixBufDecode1ChTo1Ch,a_Name,Blend)(int32_t *pi32Dst, void const *pvSrc,
+ uint32_t cFrames, PAUDIOMIXBUFWRITESTATE pState)
+{
+ RT_NOREF_PV(pState);
+ a_Type const *pSrc = (a_Type const *)pvSrc;
+ while (cFrames-- > 0)
+ {
+ audioMixBufBlendSample(&pi32Dst[0], RT_CONCAT(audioMixBufSampleFrom,a_Name)(pSrc[0]));
+ pi32Dst += 1;
+ pSrc += 1;
+ }
+}
+
+
+#undef a_Name
+#undef a_Type
+#undef a_Min
+#undef a_Max
+#undef a_fSigned
+#undef a_cShift
+
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.cpp b/src/VBox/Devices/Audio/AudioMixBuffer.cpp
new file mode 100644
index 00000000..7ce89088
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixBuffer.cpp
@@ -0,0 +1,2124 @@
+/* $Id: AudioMixBuffer.cpp $ */
+/** @file
+ * Audio mixing buffer for converting reading/writing audio data.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+/** @page pg_audio_mixing_buffers Audio Mixer Buffer
+ *
+ * @section sec_audio_mixing_buffers_volume Soft Volume Control
+ *
+ * The external code supplies an 8-bit volume (attenuation) value in the
+ * 0 .. 255 range. This represents 0 to -96dB attenuation where an input
+ * value of 0 corresponds to -96dB and 255 corresponds to 0dB (unchanged).
+ *
+ * Each step thus corresponds to 96 / 256 or 0.375dB. Every 6dB (16 steps)
+ * represents doubling the sample value.
+ *
+ * For internal use, the volume control needs to be converted to a 16-bit
+ * (sort of) exponential value between 1 and 65536. This is used with fixed
+ * point arithmetic such that 65536 means 1.0 and 1 means 1/65536.
+ *
+ * For actual volume calculation, 33.31 fixed point is used. Maximum (or
+ * unattenuated) volume is represented as 0x40000000; conveniently, this
+ * value fits into a uint32_t.
+ *
+ * To enable fast processing, the maximum volume must be a power of two
+ * and must not have a sign when converted to int32_t. While 0x80000000
+ * violates these constraints, 0x40000000 does not.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_MIXER_BUFFER
+#if defined(VBOX_AUDIO_MIX_BUFFER_TESTCASE) && !defined(RT_STRICT)
+# define RT_STRICT /* Run the testcase with assertions because the main functions doesn't return on invalid input. */
+#endif
+#include <VBox/log.h>
+
+#if 0
+/*
+ * AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA enables dumping the raw PCM data
+ * to a file on the host. Be sure to adjust AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH
+ * to your needs before using this!
+ */
+# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+# ifdef RT_OS_WINDOWS
+# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "c:\\temp\\"
+# else
+# define AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "/tmp/"
+# endif
+/* Warning: Enabling this will generate *huge* logs! */
+//# define AUDIOMIXBUF_DEBUG_MACROS
+#endif
+
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+# include <iprt/file.h>
+#endif
+#include <iprt/mem.h>
+#include <iprt/string.h> /* For RT_BZERO. */
+
+#ifdef VBOX_AUDIO_TESTCASE
+# define LOG_ENABLED
+# include <iprt/stream.h>
+#endif
+#include <iprt/errcore.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "AudioMixBuffer.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef VBOX_AUDIO_TESTCASE
+# ifdef DEBUG
+# define AUDMIXBUF_LOG(x) LogFlowFunc(x)
+# define AUDMIXBUF_LOG_ENABLED
+# else
+# define AUDMIXBUF_LOG(x) do {} while (0)
+# endif
+#else /* VBOX_AUDIO_TESTCASE */
+# define AUDMIXBUF_LOG(x) RTPrintf x
+# define AUDMIXBUF_LOG_ENABLED
+#endif
+
+
+/** Bit shift for fixed point conversion.
+ * @sa @ref sec_audio_mixing_buffers_volume */
+#define AUDIOMIXBUF_VOL_SHIFT 30
+
+/** Internal representation of 0dB volume (1.0 in fixed point).
+ * @sa @ref sec_audio_mixing_buffers_volume */
+#define AUDIOMIXBUF_VOL_0DB (1 << AUDIOMIXBUF_VOL_SHIFT)
+AssertCompile(AUDIOMIXBUF_VOL_0DB <= 0x40000000); /* Must always hold. */
+AssertCompile(AUDIOMIXBUF_VOL_0DB == 0x40000000); /* For now -- when only attenuation is used. */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Logarithmic/exponential volume conversion table.
+ * @sa @ref sec_audio_mixing_buffers_volume
+ */
+static uint32_t const s_aVolumeConv[256] =
+{
+ 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */
+ 1, 2, 2, 2, 2, 2, 2, 2, /* 15 */
+ 2, 2, 2, 2, 2, 3, 3, 3, /* 23 */
+ 3, 3, 3, 3, 4, 4, 4, 4, /* 31 */
+ 4, 4, 5, 5, 5, 5, 5, 6, /* 39 */
+ 6, 6, 6, 7, 7, 7, 8, 8, /* 47 */
+ 8, 9, 9, 10, 10, 10, 11, 11, /* 55 */
+ 12, 12, 13, 13, 14, 15, 15, 16, /* 63 */
+ 17, 17, 18, 19, 20, 21, 22, 23, /* 71 */
+ 24, 25, 26, 27, 28, 29, 31, 32, /* 79 */
+ 33, 35, 36, 38, 40, 41, 43, 45, /* 87 */
+ 47, 49, 52, 54, 56, 59, 61, 64, /* 95 */
+ 67, 70, 73, 76, 79, 83, 87, 91, /* 103 */
+ 95, 99, 103, 108, 112, 117, 123, 128, /* 111 */
+ 134, 140, 146, 152, 159, 166, 173, 181, /* 119 */
+ 189, 197, 206, 215, 225, 235, 245, 256, /* 127 */
+ 267, 279, 292, 304, 318, 332, 347, 362, /* 135 */
+ 378, 395, 412, 431, 450, 470, 490, 512, /* 143 */
+ 535, 558, 583, 609, 636, 664, 693, 724, /* 151 */
+ 756, 790, 825, 861, 899, 939, 981, 1024, /* 159 */
+ 1069, 1117, 1166, 1218, 1272, 1328, 1387, 1448, /* 167 */
+ 1512, 1579, 1649, 1722, 1798, 1878, 1961, 2048, /* 175 */
+ 2139, 2233, 2332, 2435, 2543, 2656, 2774, 2896, /* 183 */
+ 3025, 3158, 3298, 3444, 3597, 3756, 3922, 4096, /* 191 */
+ 4277, 4467, 4664, 4871, 5087, 5312, 5547, 5793, /* 199 */
+ 6049, 6317, 6597, 6889, 7194, 7512, 7845, 8192, /* 207 */
+ 8555, 8933, 9329, 9742, 10173, 10624, 11094, 11585, /* 215 */
+ 12098, 12634, 13193, 13777, 14387, 15024, 15689, 16384, /* 223 */
+ 17109, 17867, 18658, 19484, 20347, 21247, 22188, 23170, /* 231 */
+ 24196, 25268, 26386, 27554, 28774, 30048, 31379, 32768, /* 239 */
+ 34219, 35734, 37316, 38968, 40693, 42495, 44376, 46341, /* 247 */
+ 48393, 50535, 52773, 55109, 57549, 60097, 62757, 65536, /* 255 */
+};
+
+
+
+#ifdef VBOX_STRICT
+# ifdef UNUSED
+
+/**
+ * Prints a single mixing buffer.
+ * Internal helper function for debugging. Do not use directly.
+ *
+ * @returns VBox status code.
+ * @param pMixBuf Mixing buffer to print.
+ * @param pszFunc Function name to log this for.
+ * @param uIdtLvl Indention level to use.
+ */
+static void audioMixBufDbgPrintSingle(PAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl)
+{
+ Log(("%s: %*s %s: offRead=%RU32, offWrite=%RU32 -> %RU32/%RU32\n",
+ pszFunc, uIdtLvl * 4, "",
+ pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cUsed, pMixBuf->cFrames));
+}
+
+static void audioMixBufDbgPrintInternal(PAUDIOMIXBUF pMixBuf, const char *pszFunc)
+{
+ audioMixBufDbgPrintSingle(pMixBuf, pszFunc, 0 /* iIdtLevel */);
+}
+
+/**
+ * Validates a single mixing buffer.
+ *
+ * @return @true if the buffer state is valid or @false if not.
+ * @param pMixBuf Mixing buffer to validate.
+ */
+static bool audioMixBufDbgValidate(PAUDIOMIXBUF pMixBuf)
+{
+ //const uint32_t offReadEnd = (pMixBuf->offRead + pMixBuf->cUsed) % pMixBuf->cFrames;
+ //const uint32_t offWriteEnd = (pMixBuf->offWrite + (pMixBuf->cFrames - pMixBuf->cUsed)) % pMixBuf->cFrames;
+
+ bool fValid = true;
+
+ AssertStmt(pMixBuf->offRead <= pMixBuf->cFrames, fValid = false);
+ AssertStmt(pMixBuf->offWrite <= pMixBuf->cFrames, fValid = false);
+ AssertStmt(pMixBuf->cUsed <= pMixBuf->cFrames, fValid = false);
+
+ if (pMixBuf->offWrite > pMixBuf->offRead)
+ {
+ if (pMixBuf->offWrite - pMixBuf->offRead != pMixBuf->cUsed)
+ fValid = false;
+ }
+ else if (pMixBuf->offWrite < pMixBuf->offRead)
+ {
+ if (pMixBuf->offWrite + pMixBuf->cFrames - pMixBuf->offRead != pMixBuf->cUsed)
+ fValid = false;
+ }
+
+ if (!fValid)
+ {
+ audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__);
+ AssertFailed();
+ }
+
+ return fValid;
+}
+
+# endif /* UNUSED */
+#endif /* VBOX_STRICT */
+
+
+/**
+ * Merges @a i32Src into the value stored at @a pi32Dst.
+ *
+ * @param pi32Dst The value to merge @a i32Src into.
+ * @param i32Src The new value to add.
+ */
+DECL_FORCE_INLINE(void) audioMixBufBlendSample(int32_t *pi32Dst, int32_t i32Src)
+{
+ if (i32Src)
+ {
+ int64_t const i32Dst = *pi32Dst;
+ if (!i32Dst)
+ *pi32Dst = i32Src;
+ else
+ *pi32Dst = (int32_t)(((int64_t)i32Dst + i32Src) / 2);
+ }
+}
+
+
+/**
+ * Variant of audioMixBufBlendSample that returns the result rather than storing it.
+ *
+ * This is used for stereo -> mono.
+ */
+DECL_FORCE_INLINE(int32_t) audioMixBufBlendSampleRet(int32_t i32Sample1, int32_t i32Sample2)
+{
+ if (!i32Sample1)
+ return i32Sample2;
+ if (!i32Sample2)
+ return i32Sample1;
+ return (int32_t)(((int64_t)i32Sample1 + i32Sample2) / 2);
+}
+
+
+/**
+ * Blends (merges) the source buffer into the destination buffer.
+ *
+ * We're taking a very simple approach here, working sample by sample:
+ * - if one is silent, use the other one.
+ * - otherwise sum and divide by two.
+ *
+ * @param pi32Dst The destination stream buffer (input and output).
+ * @param pi32Src The source stream buffer.
+ * @param cFrames Number of frames to process.
+ * @param cChannels Number of channels.
+ */
+static void audioMixBufBlendBuffer(int32_t *pi32Dst, int32_t const *pi32Src, uint32_t cFrames, uint8_t cChannels)
+{
+ switch (cChannels)
+ {
+ case 2:
+ while (cFrames-- > 0)
+ {
+ audioMixBufBlendSample(&pi32Dst[0], pi32Src[0]);
+ audioMixBufBlendSample(&pi32Dst[1], pi32Src[1]);
+ pi32Dst += 2;
+ pi32Src += 2;
+ }
+ break;
+
+ default:
+ cFrames *= cChannels;
+ RT_FALL_THROUGH();
+ case 1:
+ while (cFrames-- > 0)
+ {
+ audioMixBufBlendSample(pi32Dst, pi32Src[0]);
+ pi32Dst++;
+ pi32Src++;
+ }
+ break;
+ }
+}
+
+
+#ifdef AUDIOMIXBUF_DEBUG_MACROS
+# define AUDMIXBUF_MACRO_LOG(x) AUDMIXBUF_LOG(x)
+#elif defined(VBOX_AUDIO_TESTCASE_VERBOSE) /* Warning: VBOX_AUDIO_TESTCASE_VERBOSE will generate huge logs! */
+# define AUDMIXBUF_MACRO_LOG(x) RTPrintf x
+#else
+# define AUDMIXBUF_MACRO_LOG(x) do {} while (0)
+#endif
+
+/*
+ * Instantiate format conversion (in and out of the mixer buffer.)
+ */
+/** @todo Currently does not handle any endianness conversion yet! */
+
+/* audioMixBufConvXXXS8: 8-bit, signed. */
+#define a_Name S8
+#define a_Type int8_t
+#define a_Min INT8_MIN
+#define a_Max INT8_MAX
+#define a_fSigned 1
+#define a_cShift 8
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXU8: 8-bit, unsigned. */
+#define a_Name U8
+#define a_Type uint8_t
+#define a_Min 0
+#define a_Max UINT8_MAX
+#define a_fSigned 0
+#define a_cShift 8
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXS16: 16-bit, signed. */
+#define a_Name S16
+#define a_Type int16_t
+#define a_Min INT16_MIN
+#define a_Max INT16_MAX
+#define a_fSigned 1
+#define a_cShift 16
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXU16: 16-bit, unsigned. */
+#define a_Name U16
+#define a_Type uint16_t
+#define a_Min 0
+#define a_Max UINT16_MAX
+#define a_fSigned 0
+#define a_cShift 16
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXS32: 32-bit, signed. */
+#define a_Name S32
+#define a_Type int32_t
+#define a_Min INT32_MIN
+#define a_Max INT32_MAX
+#define a_fSigned 1
+#define a_cShift 32
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXU32: 32-bit, unsigned. */
+#define a_Name U32
+#define a_Type uint32_t
+#define a_Min 0
+#define a_Max UINT32_MAX
+#define a_fSigned 0
+#define a_cShift 32
+#include "AudioMixBuffer-Convert.cpp.h"
+
+/* audioMixBufConvXXXRaw: 32-bit stored as 64-bit, signed. */
+#define a_Name Raw
+#define a_Type int64_t
+#define a_Min INT64_MIN
+#define a_Max INT64_MAX
+#define a_fSigned 1
+#define a_cShift 32 /* Yes, 32! */
+#include "AudioMixBuffer-Convert.cpp.h"
+
+#undef AUDMIXBUF_CONVERT
+#undef AUDMIXBUF_MACRO_LOG
+
+
+/*
+ * Resampling core.
+ */
+/** @todo Separate down- and up-sampling, borrow filter code from RDP. */
+#define COPY_LAST_FRAME_1CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ } while (0)
+#define COPY_LAST_FRAME_2CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ } while (0)
+#define COPY_LAST_FRAME_3CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ } while (0)
+#define COPY_LAST_FRAME_4CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ } while (0)
+#define COPY_LAST_FRAME_5CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ } while (0)
+#define COPY_LAST_FRAME_6CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ } while (0)
+#define COPY_LAST_FRAME_7CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ } while (0)
+#define COPY_LAST_FRAME_8CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ (a_pi32Dst)[7] = (a_pi32Src)[7]; \
+ } while (0)
+#define COPY_LAST_FRAME_9CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ (a_pi32Dst)[7] = (a_pi32Src)[7]; \
+ (a_pi32Dst)[8] = (a_pi32Src)[8]; \
+ } while (0)
+#define COPY_LAST_FRAME_10CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ (a_pi32Dst)[7] = (a_pi32Src)[7]; \
+ (a_pi32Dst)[8] = (a_pi32Src)[8]; \
+ (a_pi32Dst)[9] = (a_pi32Src)[9]; \
+ } while (0)
+#define COPY_LAST_FRAME_11CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ (a_pi32Dst)[7] = (a_pi32Src)[7]; \
+ (a_pi32Dst)[8] = (a_pi32Src)[8]; \
+ (a_pi32Dst)[9] = (a_pi32Src)[9]; \
+ (a_pi32Dst)[10] = (a_pi32Src)[10]; \
+ } while (0)
+#define COPY_LAST_FRAME_12CH(a_pi32Dst, a_pi32Src, a_cChannels) do { \
+ (a_pi32Dst)[0] = (a_pi32Src)[0]; \
+ (a_pi32Dst)[1] = (a_pi32Src)[1]; \
+ (a_pi32Dst)[2] = (a_pi32Src)[2]; \
+ (a_pi32Dst)[3] = (a_pi32Src)[3]; \
+ (a_pi32Dst)[4] = (a_pi32Src)[4]; \
+ (a_pi32Dst)[5] = (a_pi32Src)[5]; \
+ (a_pi32Dst)[6] = (a_pi32Src)[6]; \
+ (a_pi32Dst)[7] = (a_pi32Src)[7]; \
+ (a_pi32Dst)[8] = (a_pi32Src)[8]; \
+ (a_pi32Dst)[9] = (a_pi32Src)[9]; \
+ (a_pi32Dst)[10] = (a_pi32Src)[10]; \
+ (a_pi32Dst)[11] = (a_pi32Src)[11]; \
+ } while (0)
+
+#define INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_iCh) \
+ (a_pi32Dst)[a_iCh] = ((a_pi32Last)[a_iCh] * a_i64FactorLast + (a_pi32Src)[a_iCh] * a_i64FactorCur) >> 32
+#define INTERPOLATE_1CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ } while (0)
+#define INTERPOLATE_2CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ } while (0)
+#define INTERPOLATE_3CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ } while (0)
+#define INTERPOLATE_4CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ } while (0)
+#define INTERPOLATE_5CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ } while (0)
+#define INTERPOLATE_6CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ } while (0)
+#define INTERPOLATE_7CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ } while (0)
+#define INTERPOLATE_8CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \
+ } while (0)
+#define INTERPOLATE_9CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \
+ } while (0)
+#define INTERPOLATE_10CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \
+ } while (0)
+#define INTERPOLATE_11CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \
+ } while (0)
+#define INTERPOLATE_12CH(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, a_cChannels) do { \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 0); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 1); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 2); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 3); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 4); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 5); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 6); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 7); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 8); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 9); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 10); \
+ INTERPOLATE_ONE(a_pi32Dst, a_pi32Src, a_pi32Last, a_i64FactorCur, a_i64FactorLast, 11); \
+ } while (0)
+
+#define AUDIOMIXBUF_RESAMPLE(a_cChannels, a_Suffix) \
+ /** @returns Number of destination frames written. */ \
+ static DECLCALLBACK(uint32_t) \
+ audioMixBufResample##a_cChannels##Ch##a_Suffix(int32_t *pi32Dst, uint32_t cDstFrames, \
+ int32_t const *pi32Src, uint32_t cSrcFrames, uint32_t *pcSrcFramesRead, \
+ PAUDIOSTREAMRATE pRate) \
+ { \
+ Log5(("Src: %RU32 L %RU32; Dst: %RU32 L%RU32; uDstInc=%#RX64\n", \
+ pRate->offSrc, cSrcFrames, RT_HI_U32(pRate->offDst), cDstFrames, pRate->uDstInc)); \
+ int32_t * const pi32DstStart = pi32Dst; \
+ int32_t const * const pi32SrcStart = pi32Src; \
+ \
+ int32_t ai32LastFrame[a_cChannels]; \
+ COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, pRate->SrcLast.ai32Samples, a_cChannels); \
+ \
+ while (cDstFrames > 0 && cSrcFrames > 0) \
+ { \
+ int32_t const cSrcNeeded = RT_HI_U32(pRate->offDst) - pRate->offSrc + 1; \
+ if (cSrcNeeded > 0) \
+ { \
+ if ((uint32_t)cSrcNeeded + 1 < cSrcFrames) \
+ { \
+ pRate->offSrc += (uint32_t)cSrcNeeded; \
+ cSrcFrames -= (uint32_t)cSrcNeeded; \
+ pi32Src += (uint32_t)cSrcNeeded * a_cChannels; \
+ COPY_LAST_FRAME_##a_cChannels##CH(ai32LastFrame, &pi32Src[-a_cChannels], a_cChannels); \
+ } \
+ else \
+ { \
+ pi32Src += cSrcFrames * a_cChannels; \
+ pRate->offSrc += cSrcFrames; \
+ COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, &pi32Src[-a_cChannels], a_cChannels); \
+ *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \
+ return (pi32Dst - pi32DstStart) / a_cChannels; \
+ } \
+ } \
+ \
+ /* Interpolate. */ \
+ int64_t const offFactorCur = pRate->offDst & UINT32_MAX; \
+ int64_t const offFactorLast = (int64_t)_4G - offFactorCur; \
+ INTERPOLATE_##a_cChannels##CH(pi32Dst, pi32Src, ai32LastFrame, offFactorCur, offFactorLast, a_cChannels); \
+ \
+ /* Advance. */ \
+ pRate->offDst += pRate->uDstInc; \
+ pi32Dst += a_cChannels; \
+ cDstFrames -= 1; \
+ } \
+ \
+ COPY_LAST_FRAME_##a_cChannels##CH(pRate->SrcLast.ai32Samples, ai32LastFrame, a_cChannels); \
+ *pcSrcFramesRead = (pi32Src - pi32SrcStart) / a_cChannels; \
+ return (pi32Dst - pi32DstStart) / a_cChannels; \
+ }
+
+AUDIOMIXBUF_RESAMPLE(1,Generic)
+AUDIOMIXBUF_RESAMPLE(2,Generic)
+AUDIOMIXBUF_RESAMPLE(3,Generic)
+AUDIOMIXBUF_RESAMPLE(4,Generic)
+AUDIOMIXBUF_RESAMPLE(5,Generic)
+AUDIOMIXBUF_RESAMPLE(6,Generic)
+AUDIOMIXBUF_RESAMPLE(7,Generic)
+AUDIOMIXBUF_RESAMPLE(8,Generic)
+AUDIOMIXBUF_RESAMPLE(9,Generic)
+AUDIOMIXBUF_RESAMPLE(10,Generic)
+AUDIOMIXBUF_RESAMPLE(11,Generic)
+AUDIOMIXBUF_RESAMPLE(12,Generic)
+
+
+/**
+ * Resets the resampling state unconditionally.
+ *
+ * @param pRate The state to reset.
+ */
+static void audioMixBufRateResetAlways(PAUDIOSTREAMRATE pRate)
+{
+ pRate->offDst = 0;
+ pRate->offSrc = 0;
+ for (uintptr_t i = 0; i < RT_ELEMENTS(pRate->SrcLast.ai32Samples); i++)
+ pRate->SrcLast.ai32Samples[0] = 0;
+}
+
+
+/**
+ * Resets the resampling state.
+ *
+ * @param pRate The state to reset.
+ */
+DECLINLINE(void) audioMixBufRateReset(PAUDIOSTREAMRATE pRate)
+{
+ if (pRate->offDst == 0)
+ { /* likely */ }
+ else
+ {
+ Assert(!pRate->fNoConversionNeeded);
+ audioMixBufRateResetAlways(pRate);
+ }
+}
+
+
+/**
+ * Initializes the frame rate converter state.
+ *
+ * @returns VBox status code.
+ * @param pRate The state to initialize.
+ * @param uSrcHz The source frame rate.
+ * @param uDstHz The destination frame rate.
+ * @param cChannels The number of channels in a frame.
+ */
+DECLINLINE(int) audioMixBufRateInit(PAUDIOSTREAMRATE pRate, uint32_t uSrcHz, uint32_t uDstHz, uint8_t cChannels)
+{
+ /*
+ * Do we need to set up frequency conversion?
+ *
+ * Some examples to get an idea of what uDstInc holds:
+ * 44100 to 44100 -> (44100<<32) / 44100 = 0x01'00000000 (4294967296)
+ * 22050 to 44100 -> (22050<<32) / 44100 = 0x00'80000000 (2147483648)
+ * 44100 to 22050 -> (44100<<32) / 22050 = 0x02'00000000 (8589934592)
+ * 44100 to 48000 -> (44100<<32) / 48000 = 0x00'EB333333 (3946001203.2)
+ * 48000 to 44100 -> (48000<<32) / 44100 = 0x01'16A3B35F (4674794335.7823129251700680272109)
+ */
+ audioMixBufRateResetAlways(pRate);
+ if (uSrcHz == uDstHz)
+ {
+ pRate->fNoConversionNeeded = true;
+ pRate->uDstInc = RT_BIT_64(32);
+ pRate->pfnResample = NULL;
+ }
+ else
+ {
+ pRate->fNoConversionNeeded = false;
+ pRate->uDstInc = ((uint64_t)uSrcHz << 32) / uDstHz;
+ AssertReturn(uSrcHz != 0, VERR_INVALID_PARAMETER);
+ switch (cChannels)
+ {
+ case 1: pRate->pfnResample = audioMixBufResample1ChGeneric; break;
+ case 2: pRate->pfnResample = audioMixBufResample2ChGeneric; break;
+ case 3: pRate->pfnResample = audioMixBufResample3ChGeneric; break;
+ case 4: pRate->pfnResample = audioMixBufResample4ChGeneric; break;
+ case 5: pRate->pfnResample = audioMixBufResample5ChGeneric; break;
+ case 6: pRate->pfnResample = audioMixBufResample6ChGeneric; break;
+ case 7: pRate->pfnResample = audioMixBufResample7ChGeneric; break;
+ case 8: pRate->pfnResample = audioMixBufResample8ChGeneric; break;
+ case 9: pRate->pfnResample = audioMixBufResample9ChGeneric; break;
+ case 10: pRate->pfnResample = audioMixBufResample10ChGeneric; break;
+ case 11: pRate->pfnResample = audioMixBufResample11ChGeneric; break;
+ case 12: pRate->pfnResample = audioMixBufResample12ChGeneric; break;
+ default:
+ AssertMsgFailedReturn(("resampling %u changes is not implemented yet\n", cChannels), VERR_OUT_OF_RANGE);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes a mixing buffer.
+ *
+ * @returns VBox status code.
+ * @param pMixBuf Mixing buffer to initialize.
+ * @param pszName Name of mixing buffer for easier identification. Optional.
+ * @param pProps PCM audio properties to use for the mixing buffer.
+ * @param cFrames Maximum number of audio frames the mixing buffer can hold.
+ */
+int AudioMixBufInit(PAUDIOMIXBUF pMixBuf, const char *pszName, PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsAreValid(pProps));
+
+ /*
+ * Initialize all members, setting the volume to max (0dB).
+ */
+ pMixBuf->cFrames = 0;
+ pMixBuf->pi32Samples = NULL;
+ pMixBuf->cChannels = 0;
+ pMixBuf->cbFrame = 0;
+ pMixBuf->offRead = 0;
+ pMixBuf->offWrite = 0;
+ pMixBuf->cUsed = 0;
+ pMixBuf->Props = *pProps;
+ pMixBuf->Volume.fMuted = false;
+ pMixBuf->Volume.fAllMax = true;
+ for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++)
+ pMixBuf->Volume.auChannels[i] = AUDIOMIXBUF_VOL_0DB;
+
+ int rc;
+ uint8_t const cChannels = PDMAudioPropsChannels(pProps);
+ if (cChannels >= 1 && cChannels <= PDMAUDIO_MAX_CHANNELS)
+ {
+ pMixBuf->pszName = RTStrDup(pszName);
+ if (pMixBuf->pszName)
+ {
+ pMixBuf->pi32Samples = (int32_t *)RTMemAllocZ(cFrames * cChannels * sizeof(pMixBuf->pi32Samples[0]));
+ if (pMixBuf->pi32Samples)
+ {
+ pMixBuf->cFrames = cFrames;
+ pMixBuf->cChannels = cChannels;
+ pMixBuf->cbFrame = cChannels * sizeof(pMixBuf->pi32Samples[0]);
+ pMixBuf->uMagic = AUDIOMIXBUF_MAGIC;
+#ifdef AUDMIXBUF_LOG_ENABLED
+ char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
+ AUDMIXBUF_LOG(("%s: %s - cFrames=%#x (%d)\n",
+ pMixBuf->pszName, PDMAudioPropsToString(pProps, szTmp, sizeof(szTmp)), cFrames, cFrames));
+#endif
+ return VINF_SUCCESS;
+ }
+ RTStrFree(pMixBuf->pszName);
+ pMixBuf->pszName = NULL;
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ {
+ LogRelMaxFunc(64, ("cChannels=%d pszName=%s\n", cChannels, pszName));
+ rc = VERR_OUT_OF_RANGE;
+ }
+ pMixBuf->uMagic = AUDIOMIXBUF_MAGIC_DEAD;
+ return rc;
+}
+
+/**
+ * Terminates (uninitializes) a mixing buffer.
+ *
+ * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be
+ * quietly ignored. As will NULL.
+ */
+void AudioMixBufTerm(PAUDIOMIXBUF pMixBuf)
+{
+ if (!pMixBuf)
+ return;
+
+ /* Ignore calls for an uninitialized (zeroed) or already destroyed instance. Happens a lot. */
+ if ( pMixBuf->uMagic == 0
+ || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD)
+ {
+ Assert(!pMixBuf->pszName);
+ Assert(!pMixBuf->pi32Samples);
+ Assert(!pMixBuf->cFrames);
+ return;
+ }
+
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ pMixBuf->uMagic = ~AUDIOMIXBUF_MAGIC;
+
+ if (pMixBuf->pszName)
+ {
+ AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName));
+
+ RTStrFree(pMixBuf->pszName);
+ pMixBuf->pszName = NULL;
+ }
+
+ if (pMixBuf->pi32Samples)
+ {
+ Assert(pMixBuf->cFrames);
+ RTMemFree(pMixBuf->pi32Samples);
+ pMixBuf->pi32Samples = NULL;
+ }
+
+ pMixBuf->cFrames = 0;
+ pMixBuf->cChannels = 0;
+}
+
+
+/**
+ * Drops all the frames in the given mixing buffer
+ *
+ * This will reset the read and write offsets to zero.
+ *
+ * @param pMixBuf The mixing buffer. Uninitialized mixer buffers will be
+ * quietly ignored.
+ */
+void AudioMixBufDrop(PAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturnVoid(pMixBuf);
+
+ /* Ignore uninitialized (zeroed) mixer sink buffers (happens with AC'97 during VM construction). */
+ if ( pMixBuf->uMagic == 0
+ || pMixBuf->uMagic == AUDIOMIXBUF_MAGIC_DEAD)
+ return;
+
+ AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName));
+
+ pMixBuf->offRead = 0;
+ pMixBuf->offWrite = 0;
+ pMixBuf->cUsed = 0;
+}
+
+
+/**
+ * Gets the maximum number of audio frames this buffer can hold.
+ *
+ * @returns Number of frames.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufSize(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return pMixBuf->cFrames;
+}
+
+
+/**
+ * Gets the maximum number of bytes this buffer can hold.
+ *
+ * @returns Number of bytes.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufSizeBytes(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ AssertReturn(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC, 0);
+ return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames);
+}
+
+
+/**
+ * Worker for AudioMixBufUsed and AudioMixBufUsedBytes.
+ */
+DECLINLINE(uint32_t) audioMixBufUsedInternal(PCAUDIOMIXBUF pMixBuf)
+{
+ uint32_t const cFrames = pMixBuf->cFrames;
+ uint32_t cUsed = pMixBuf->cUsed;
+ AssertStmt(cUsed <= cFrames, cUsed = cFrames);
+ return cUsed;
+}
+
+
+/**
+ * Get the number of used (readable) frames in the buffer.
+ *
+ * @returns Number of frames.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufUsed(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return audioMixBufUsedInternal(pMixBuf);
+}
+
+
+/**
+ * Get the number of (readable) bytes in the buffer.
+ *
+ * @returns Number of bytes.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufUsedBytes(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufUsedInternal(pMixBuf));
+}
+
+
+/**
+ * Worker for AudioMixBufFree and AudioMixBufFreeBytes.
+ */
+DECLINLINE(uint32_t) audioMixBufFreeInternal(PCAUDIOMIXBUF pMixBuf)
+{
+ uint32_t const cFrames = pMixBuf->cFrames;
+ uint32_t cUsed = pMixBuf->cUsed;
+ AssertStmt(cUsed <= cFrames, cUsed = cFrames);
+ uint32_t const cFramesFree = cFrames - cUsed;
+
+ AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames));
+ return cFramesFree;
+}
+
+
+/**
+ * Gets the free buffer space in frames.
+ *
+ * @return Number of frames.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufFree(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return audioMixBufFreeInternal(pMixBuf);
+}
+
+
+/**
+ * Gets the free buffer space in bytes.
+ *
+ * @return Number of bytes.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufFreeBytes(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return AUDIOMIXBUF_F2B(pMixBuf, audioMixBufFreeInternal(pMixBuf));
+}
+
+
+/**
+ * Checks if the buffer is empty.
+ *
+ * @retval true if empty buffer.
+ * @retval false if not empty and there are frames to be processed.
+ * @param pMixBuf The mixing buffer.
+ */
+bool AudioMixBufIsEmpty(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, true);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return pMixBuf->cUsed == 0;
+}
+
+
+/**
+ * Get the current read position.
+ *
+ * This is for the testcase.
+ *
+ * @returns Frame number.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufReadPos(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return pMixBuf->offRead;
+}
+
+
+/**
+ * Gets the current write position.
+ *
+ * This is for the testcase.
+ *
+ * @returns Frame number.
+ * @param pMixBuf The mixing buffer.
+ */
+uint32_t AudioMixBufWritePos(PCAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ return pMixBuf->offWrite;
+}
+
+
+/**
+ * Creates a mapping between desination channels and source source channels.
+ *
+ * @param paidxChannelMap Where to store the mapping. Indexed by
+ * destination channel. Entry is either source
+ * channel index or -1 for zero and -2 for silence.
+ * @param pSrcProps The source properties.
+ * @param pDstProps The desination properties.
+ */
+static void audioMixBufInitChannelMap(int8_t paidxChannelMap[PDMAUDIO_MAX_CHANNELS],
+ PCPDMAUDIOPCMPROPS pSrcProps, PCPDMAUDIOPCMPROPS pDstProps)
+{
+ uintptr_t const cDstChannels = PDMAudioPropsChannels(pDstProps);
+ uintptr_t const cSrcChannels = PDMAudioPropsChannels(pSrcProps);
+ uintptr_t idxDst;
+ for (idxDst = 0; idxDst < cDstChannels; idxDst++)
+ {
+ uint8_t const idDstCh = pDstProps->aidChannels[idxDst];
+ if (idDstCh >= PDMAUDIOCHANNELID_FRONT_LEFT && idDstCh < PDMAUDIOCHANNELID_END)
+ {
+ uintptr_t idxSrc;
+ for (idxSrc = 0; idxSrc < cSrcChannels; idxSrc++)
+ if (idDstCh == pSrcProps->aidChannels[idxSrc])
+ {
+ paidxChannelMap[idxDst] = idxSrc;
+ break;
+ }
+ if (idxSrc >= cSrcChannels)
+ {
+ /** @todo deal with mono. */
+ paidxChannelMap[idxDst] = -2;
+ }
+ }
+ else if (idDstCh == PDMAUDIOCHANNELID_UNKNOWN)
+ {
+ /** @todo What to do here? Pick unused source channels in order? */
+ paidxChannelMap[idxDst] = -2;
+ }
+ else
+ {
+ AssertMsg(idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE || idDstCh == PDMAUDIOCHANNELID_UNUSED_ZERO,
+ ("idxDst=%u idDstCh=%u\n", idxDst, idDstCh));
+ paidxChannelMap[idxDst] = idDstCh == PDMAUDIOCHANNELID_UNUSED_SILENCE ? -2 : -1;
+ }
+ }
+
+ /* Set the remainder to -1 just to be sure their are safe. */
+ for (; idxDst < PDMAUDIO_MAX_CHANNELS; idxDst++)
+ paidxChannelMap[idxDst] = -1;
+}
+
+
+/**
+ * Initializes the peek state, setting up encoder and (if necessary) resampling.
+ *
+ * @returns VBox status code.
+ */
+int AudioMixBufInitPeekState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFPEEKSTATE pState, PCPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtr(pMixBuf);
+ AssertPtr(pState);
+ AssertPtr(pProps);
+
+ /*
+ * Pick the encoding function first.
+ */
+ uint8_t const cbSample = PDMAudioPropsSampleSize(pProps);
+ uint8_t const cSrcCh = PDMAudioPropsChannels(&pMixBuf->Props);
+ uint8_t const cDstCh = PDMAudioPropsChannels(pProps);
+ pState->cSrcChannels = cSrcCh;
+ pState->cDstChannels = cDstCh;
+ pState->cbDstFrame = PDMAudioPropsFrameSize(pProps);
+ audioMixBufInitChannelMap(pState->aidxChannelMap, &pMixBuf->Props, pProps);
+ AssertReturn(cDstCh > 0 && cDstCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE);
+ AssertReturn(cSrcCh > 0 && cSrcCh <= PDMAUDIO_MAX_CHANNELS, VERR_OUT_OF_RANGE);
+
+ if (PDMAudioPropsIsSigned(pProps))
+ {
+ /* Assign generic encoder first. */
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncodeGenericS8; break;
+ case 2: pState->pfnEncode = audioMixBufEncodeGenericS16; break;
+ case 4: pState->pfnEncode = audioMixBufEncodeGenericS32; break;
+ case 8:
+ AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT);
+ pState->pfnEncode = audioMixBufEncodeGenericRaw;
+ break;
+ default:
+ AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE);
+ }
+
+ /* Any specializations available? */
+ switch (cDstCh)
+ {
+ case 1:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChS8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChS16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChS32; break;
+ case 8: pState->pfnEncode = audioMixBufEncode1ChTo1ChRaw; break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChS8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChS16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChS32; break;
+ case 8: pState->pfnEncode = audioMixBufEncode2ChTo1ChRaw; break;
+ }
+ break;
+
+ case 2:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChS8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChS16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChS32; break;
+ case 8: pState->pfnEncode = audioMixBufEncode1ChTo2ChRaw; break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChS8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChS16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChS32; break;
+ case 8: pState->pfnEncode = audioMixBufEncode2ChTo2ChRaw; break;
+ }
+ break;
+ }
+ }
+ else
+ {
+ /* Assign generic encoder first. */
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncodeGenericU8; break;
+ case 2: pState->pfnEncode = audioMixBufEncodeGenericU16; break;
+ case 4: pState->pfnEncode = audioMixBufEncodeGenericU32; break;
+ default:
+ AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE);
+ }
+
+ /* Any specializations available? */
+ switch (cDstCh)
+ {
+ case 1:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode1ChTo1ChU8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode1ChTo1ChU16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode1ChTo1ChU32; break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode2ChTo1ChU8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode2ChTo1ChU16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode2ChTo1ChU32; break;
+ }
+ break;
+
+ case 2:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode1ChTo2ChU8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode1ChTo2ChU16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode1ChTo2ChU32; break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1: pState->pfnEncode = audioMixBufEncode2ChTo2ChU8; break;
+ case 2: pState->pfnEncode = audioMixBufEncode2ChTo2ChU16; break;
+ case 4: pState->pfnEncode = audioMixBufEncode2ChTo2ChU32; break;
+ }
+ break;
+ }
+ }
+
+ int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(&pMixBuf->Props), PDMAudioPropsHz(pProps), cSrcCh);
+ AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(&pMixBuf->Props),
+ PDMAudioPropsHz(pProps), pState->Rate.uDstInc));
+ return rc;
+}
+
+
+/**
+ * Initializes the write/blend state, setting up decoders and (if necessary)
+ * resampling.
+ *
+ * @returns VBox status code.
+ */
+int AudioMixBufInitWriteState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, PCPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtr(pMixBuf);
+ AssertPtr(pState);
+ AssertPtr(pProps);
+
+ /*
+ * Pick the encoding function first.
+ */
+ uint8_t const cbSample = PDMAudioPropsSampleSize(pProps);
+ uint8_t const cSrcCh = PDMAudioPropsChannels(pProps);
+ uint8_t const cDstCh = PDMAudioPropsChannels(&pMixBuf->Props);
+ pState->cSrcChannels = cSrcCh;
+ pState->cDstChannels = cDstCh;
+ pState->cbSrcFrame = PDMAudioPropsFrameSize(pProps);
+ audioMixBufInitChannelMap(pState->aidxChannelMap, pProps, &pMixBuf->Props);
+
+ if (PDMAudioPropsIsSigned(pProps))
+ {
+ /* Assign generic decoders first. */
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecodeGenericS8;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericS8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecodeGenericS16;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericS16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecodeGenericS32;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericS32Blend;
+ break;
+ case 8:
+ AssertReturn(pProps->fRaw, VERR_DISK_INVALID_FORMAT);
+ pState->pfnDecode = audioMixBufDecodeGenericRaw;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericRawBlend;
+ break;
+ default:
+ AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE);
+ }
+
+ /* Any specializations available? */
+ switch (cDstCh)
+ {
+ case 1:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChS8;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChS16;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChS32;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChS32Blend;
+ break;
+ case 8:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChRaw;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChRawBlend;
+ break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChS8;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChS16;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChS32;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChS32Blend;
+ break;
+ case 8:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChRaw;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChRawBlend;
+ break;
+ }
+ break;
+
+ case 2:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChS8;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChS16;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChS32;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChS32Blend;
+ break;
+ case 8:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChRaw;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChRawBlend;
+ break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChS8;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChS16;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChS32;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChS32Blend;
+ break;
+ case 8:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChRaw;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChRawBlend;
+ break;
+ }
+ break;
+ }
+ }
+ else
+ {
+ /* Assign generic decoders first. */
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecodeGenericU8;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericU8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecodeGenericU16;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericU16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecodeGenericU32;
+ pState->pfnDecodeBlend = audioMixBufDecodeGenericU32Blend;
+ break;
+ default:
+ AssertMsgFailedReturn(("%u bytes\n", cbSample), VERR_OUT_OF_RANGE);
+ }
+
+ /* Any specializations available? */
+ switch (cDstCh)
+ {
+ case 1:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChU8;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChU16;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode1ChTo1ChU32;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo1ChU32Blend;
+ break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChU8;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChU16;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode2ChTo1ChU32;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo1ChU32Blend;
+ break;
+ }
+ break;
+
+ case 2:
+ if (cSrcCh == 1)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChU8;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChU16;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode1ChTo2ChU32;
+ pState->pfnDecodeBlend = audioMixBufDecode1ChTo2ChU32Blend;
+ break;
+ }
+ else if (cSrcCh == 2)
+ switch (cbSample)
+ {
+ case 1:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChU8;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU8Blend;
+ break;
+ case 2:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChU16;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU16Blend;
+ break;
+ case 4:
+ pState->pfnDecode = audioMixBufDecode2ChTo2ChU32;
+ pState->pfnDecodeBlend = audioMixBufDecode2ChTo2ChU32Blend;
+ break;
+ }
+ break;
+ }
+ }
+
+ int rc = audioMixBufRateInit(&pState->Rate, PDMAudioPropsHz(pProps), PDMAudioPropsHz(&pMixBuf->Props), cDstCh);
+ AUDMIXBUF_LOG(("%s: %RU32 Hz to %RU32 Hz => uDstInc=0x%'RX64\n", pMixBuf->pszName, PDMAudioPropsHz(pProps),
+ PDMAudioPropsHz(&pMixBuf->Props), pState->Rate.uDstInc));
+ return rc;
+}
+
+
+/**
+ * Worker for AudioMixBufPeek that handles the rate conversion case.
+ */
+DECL_NO_INLINE(static, void)
+audioMixBufPeekResampling(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked,
+ PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked)
+{
+ *pcSrcFramesPeeked = 0;
+ *pcbDstPeeked = 0;
+ while (cMaxSrcFrames > 0 && cbDst >= pState->cbDstFrame)
+ {
+ /* Rate conversion into temporary buffer. */
+ int32_t ai32DstRate[1024];
+ uint32_t cSrcFrames = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames);
+ uint32_t cDstMaxFrames = RT_MIN(RT_ELEMENTS(ai32DstRate) / pState->cSrcChannels, cbDst / pState->cbDstFrame);
+ uint32_t const cDstFrames = pState->Rate.pfnResample(ai32DstRate, cDstMaxFrames,
+ &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels],
+ cSrcFrames, &cSrcFrames, &pState->Rate);
+ *pcSrcFramesPeeked += cSrcFrames;
+ cMaxSrcFrames -= cSrcFrames;
+ offSrcFrame = (offSrcFrame + cSrcFrames) % pMixBuf->cFrames;
+
+ /* Encode the converted frames. */
+ uint32_t const cbDstEncoded = cDstFrames * pState->cbDstFrame;
+ pState->pfnEncode(pvDst, ai32DstRate, cDstFrames, pState);
+ *pcbDstPeeked += cbDstEncoded;
+ cbDst -= cbDstEncoded;
+ pvDst = (uint8_t *)pvDst + cbDstEncoded;
+ }
+}
+
+
+/**
+ * Copies data out of the mixing buffer, converting it if needed, but leaves the
+ * read offset untouched.
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param offSrcFrame The offset to start reading at relative to
+ * current read position (offRead). The caller has
+ * made sure there is at least this number of
+ * frames available in the buffer before calling.
+ * @param cMaxSrcFrames Maximum number of frames to read.
+ * @param pcSrcFramesPeeked Where to return the actual number of frames read
+ * from the mixing buffer.
+ * @param pState Output configuration & conversion state.
+ * @param pvDst The destination buffer.
+ * @param cbDst The size of the destination buffer in bytes.
+ * @param pcbDstPeeked Where to put the actual number of bytes
+ * returned.
+ */
+void AudioMixBufPeek(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked,
+ PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked)
+{
+ /*
+ * Check inputs.
+ */
+ AssertPtr(pMixBuf);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ AssertPtr(pState);
+ AssertPtr(pState->pfnEncode);
+ Assert(pState->cSrcChannels == PDMAudioPropsChannels(&pMixBuf->Props));
+ Assert(cMaxSrcFrames > 0);
+ Assert(cMaxSrcFrames <= pMixBuf->cFrames);
+ Assert(offSrcFrame <= pMixBuf->cFrames);
+ Assert(offSrcFrame + cMaxSrcFrames <= pMixBuf->cUsed);
+ AssertPtr(pcSrcFramesPeeked);
+ AssertPtr(pvDst);
+ Assert(cbDst >= pState->cbDstFrame);
+ AssertPtr(pcbDstPeeked);
+
+ /*
+ * Make start frame absolute.
+ */
+ offSrcFrame = (pMixBuf->offRead + offSrcFrame) % pMixBuf->cFrames;
+
+ /*
+ * Hopefully no sample rate conversion is necessary...
+ */
+ if (pState->Rate.fNoConversionNeeded)
+ {
+ /* Figure out how much we should convert. */
+ cMaxSrcFrames = RT_MIN(cMaxSrcFrames, cbDst / pState->cbDstFrame);
+ *pcSrcFramesPeeked = cMaxSrcFrames;
+ *pcbDstPeeked = cMaxSrcFrames * pState->cbDstFrame;
+
+ /* First chunk. */
+ uint32_t const cSrcFrames1 = RT_MIN(pMixBuf->cFrames - offSrcFrame, cMaxSrcFrames);
+ pState->pfnEncode(pvDst, &pMixBuf->pi32Samples[offSrcFrame * pMixBuf->cChannels], cSrcFrames1, pState);
+
+ /* Another chunk from the start of the mixing buffer? */
+ if (cMaxSrcFrames > cSrcFrames1)
+ pState->pfnEncode((uint8_t *)pvDst + cSrcFrames1 * pState->cbDstFrame,
+ &pMixBuf->pi32Samples[0], cMaxSrcFrames - cSrcFrames1, pState);
+
+ //Log9Func(("*pcbDstPeeked=%#x\n%32.*Rhxd\n", *pcbDstPeeked, *pcbDstPeeked, pvDst));
+ }
+ else
+ audioMixBufPeekResampling(pMixBuf, offSrcFrame, cMaxSrcFrames, pcSrcFramesPeeked, pState, pvDst, cbDst, pcbDstPeeked);
+}
+
+
+/**
+ * Worker for AudioMixBufWrite that handles the rate conversion case.
+ */
+DECL_NO_INLINE(static, void)
+audioMixBufWriteResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten)
+{
+ *pcDstFramesWritten = 0;
+ while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame)
+ {
+ /* Decode into temporary buffer. */
+ int32_t ai32Decoded[1024];
+ uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame);
+ pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState);
+ cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame;
+ pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame;
+
+ /* Rate convert that into the mixer. */
+ uint32_t iFrameDecoded = 0;
+ while (iFrameDecoded < cFramesDecoded)
+ {
+ uint32_t cDstMaxFramesNow = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames);
+ uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded;
+ uint32_t const cDstFrames = pState->Rate.pfnResample(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels],
+ cDstMaxFramesNow,
+ &ai32Decoded[iFrameDecoded * pState->cDstChannels],
+ cSrcFrames, &cSrcFrames, &pState->Rate);
+
+ iFrameDecoded += cSrcFrames;
+ *pcDstFramesWritten += cDstFrames;
+ offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames;
+ }
+ }
+
+ /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */
+}
+
+
+/**
+ * Writes @a cbSrcBuf bytes to the mixer buffer starting at @a offDstFrame,
+ * converting it as needed, leaving the write offset untouched.
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param pState Source configuration & conversion state.
+ * @param pvSrcBuf The source frames.
+ * @param cbSrcBuf Number of bytes of source frames. This will be
+ * convered in full.
+ * @param offDstFrame Mixing buffer offset relative to the write
+ * position.
+ * @param cDstMaxFrames Max number of frames to write.
+ * @param pcDstFramesWritten Where to return the number of frames actually
+ * written.
+ *
+ * @note Does not advance the write position, please call AudioMixBufCommit()
+ * to do that.
+ */
+void AudioMixBufWrite(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesWritten)
+{
+ /*
+ * Check inputs.
+ */
+ AssertPtr(pMixBuf);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ AssertPtr(pState);
+ AssertPtr(pState->pfnDecode);
+ AssertPtr(pState->pfnDecodeBlend);
+ Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props));
+ Assert(cDstMaxFrames > 0);
+ Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed);
+ Assert(offDstFrame <= pMixBuf->cFrames);
+ AssertPtr(pvSrcBuf);
+ Assert(!(cbSrcBuf % pState->cbSrcFrame));
+ AssertPtr(pcDstFramesWritten);
+
+ /*
+ * Make start frame absolute.
+ */
+ offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames;
+
+ /*
+ * Hopefully no sample rate conversion is necessary...
+ */
+ if (pState->Rate.fNoConversionNeeded)
+ {
+ /* Figure out how much we should convert. */
+ Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame);
+ cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame);
+ *pcDstFramesWritten = cDstMaxFrames;
+
+ //Log10Func(("cbSrc=%#x\n%32.*Rhxd\n", pState->cbSrcFrame * cDstMaxFrames, pState->cbSrcFrame * cDstMaxFrames, pvSrcBuf));
+
+ /* First chunk. */
+ uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames);
+ pState->pfnDecode(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState);
+ //Log8Func(("offDstFrame=%#x cDstFrames1=%#x\n%32.*Rhxd\n", offDstFrame, cDstFrames1,
+ // cDstFrames1 * pMixBuf->cbFrame, &pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels]));
+
+ /* Another chunk from the start of the mixing buffer? */
+ if (cDstMaxFrames > cDstFrames1)
+ {
+ pState->pfnDecode(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame,
+ cDstMaxFrames - cDstFrames1, pState);
+ //Log8Func(("cDstFrames2=%#x\n%32.*Rhxd\n", cDstMaxFrames - cDstFrames1,
+ // (cDstMaxFrames - cDstFrames1) * pMixBuf->cbFrame, &pMixBuf->pi32Samples[0]));
+ }
+ }
+ else
+ audioMixBufWriteResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesWritten);
+}
+
+
+/**
+ * Worker for AudioMixBufBlend that handles the rate conversion case.
+ */
+DECL_NO_INLINE(static, void)
+audioMixBufBlendResampling(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended)
+{
+ *pcDstFramesBlended = 0;
+ while (cDstMaxFrames > 0 && cbSrcBuf >= pState->cbSrcFrame)
+ {
+ /* Decode into temporary buffer. This then has the destination channel count. */
+ int32_t ai32Decoded[1024];
+ uint32_t cFramesDecoded = RT_MIN(RT_ELEMENTS(ai32Decoded) / pState->cDstChannels, cbSrcBuf / pState->cbSrcFrame);
+ pState->pfnDecode(ai32Decoded, pvSrcBuf, cFramesDecoded, pState);
+ cbSrcBuf -= cFramesDecoded * pState->cbSrcFrame;
+ pvSrcBuf = (uint8_t const *)pvSrcBuf + cFramesDecoded * pState->cbSrcFrame;
+
+ /* Rate convert that into another temporary buffer and then blend that into the mixer. */
+ uint32_t iFrameDecoded = 0;
+ while (iFrameDecoded < cFramesDecoded)
+ {
+ int32_t ai32Rate[1024];
+ uint32_t cDstMaxFramesNow = RT_MIN(RT_ELEMENTS(ai32Rate) / pState->cDstChannels, cDstMaxFrames);
+ uint32_t cSrcFrames = cFramesDecoded - iFrameDecoded;
+ uint32_t const cDstFrames = pState->Rate.pfnResample(&ai32Rate[0], cDstMaxFramesNow,
+ &ai32Decoded[iFrameDecoded * pState->cDstChannels],
+ cSrcFrames, &cSrcFrames, &pState->Rate);
+
+ /* First chunk.*/
+ uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstFrames);
+ audioMixBufBlendBuffer(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels],
+ ai32Rate, cDstFrames1, pState->cDstChannels);
+
+ /* Another chunk from the start of the mixing buffer? */
+ if (cDstFrames > cDstFrames1)
+ audioMixBufBlendBuffer(&pMixBuf->pi32Samples[0], &ai32Rate[cDstFrames1 * pState->cDstChannels],
+ cDstFrames - cDstFrames1, pState->cDstChannels);
+
+ /* Advance */
+ iFrameDecoded += cSrcFrames;
+ *pcDstFramesBlended += cDstFrames;
+ offDstFrame = (offDstFrame + cDstFrames) % pMixBuf->cFrames;
+ }
+ }
+
+ /** @todo How to squeeze odd frames out of 22050 => 44100 conversion? */
+}
+
+
+/**
+ * @todo not sure if 'blend' is the appropriate term here, but you know what
+ * we mean.
+ */
+void AudioMixBufBlend(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cDstMaxFrames, uint32_t *pcDstFramesBlended)
+{
+ /*
+ * Check inputs.
+ */
+ AssertPtr(pMixBuf);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ AssertPtr(pState);
+ AssertPtr(pState->pfnDecode);
+ AssertPtr(pState->pfnDecodeBlend);
+ Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props));
+ Assert(cDstMaxFrames > 0);
+ Assert(cDstMaxFrames <= pMixBuf->cFrames - pMixBuf->cUsed);
+ Assert(offDstFrame <= pMixBuf->cFrames);
+ AssertPtr(pvSrcBuf);
+ Assert(!(cbSrcBuf % pState->cbSrcFrame));
+ AssertPtr(pcDstFramesBlended);
+
+ /*
+ * Make start frame absolute.
+ */
+ offDstFrame = (pMixBuf->offWrite + offDstFrame) % pMixBuf->cFrames;
+
+ /*
+ * Hopefully no sample rate conversion is necessary...
+ */
+ if (pState->Rate.fNoConversionNeeded)
+ {
+ /* Figure out how much we should convert. */
+ Assert(cDstMaxFrames >= cbSrcBuf / pState->cbSrcFrame);
+ cDstMaxFrames = RT_MIN(cDstMaxFrames, cbSrcBuf / pState->cbSrcFrame);
+ *pcDstFramesBlended = cDstMaxFrames;
+
+ /* First chunk. */
+ uint32_t const cDstFrames1 = RT_MIN(pMixBuf->cFrames - offDstFrame, cDstMaxFrames);
+ pState->pfnDecodeBlend(&pMixBuf->pi32Samples[offDstFrame * pMixBuf->cChannels], pvSrcBuf, cDstFrames1, pState);
+
+ /* Another chunk from the start of the mixing buffer? */
+ if (cDstMaxFrames > cDstFrames1)
+ pState->pfnDecodeBlend(&pMixBuf->pi32Samples[0], (uint8_t *)pvSrcBuf + cDstFrames1 * pState->cbSrcFrame,
+ cDstMaxFrames - cDstFrames1, pState);
+ }
+ else
+ audioMixBufBlendResampling(pMixBuf, pState, pvSrcBuf, cbSrcBuf, offDstFrame, cDstMaxFrames, pcDstFramesBlended);
+}
+
+
+/**
+ * Writes @a cFrames of silence at @a offFrame relative to current write pos.
+ *
+ * This will also adjust the resampling state.
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param pState The write state.
+ * @param offFrame Where to start writing silence relative to the current
+ * write position.
+ * @param cFrames Number of frames of silence.
+ * @sa AudioMixBufWrite
+ *
+ * @note Does not advance the write position, please call AudioMixBufCommit()
+ * to do that.
+ */
+void AudioMixBufSilence(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t offFrame, uint32_t cFrames)
+{
+ /*
+ * Check inputs.
+ */
+ AssertPtr(pMixBuf);
+ Assert(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+ AssertPtr(pState);
+ AssertPtr(pState->pfnDecode);
+ AssertPtr(pState->pfnDecodeBlend);
+ Assert(pState->cDstChannels == PDMAudioPropsChannels(&pMixBuf->Props));
+ Assert(cFrames > 0);
+#ifdef VBOX_STRICT
+ uint32_t const cMixBufFree = pMixBuf->cFrames - pMixBuf->cUsed;
+#endif
+ Assert(cFrames <= cMixBufFree);
+ Assert(offFrame < cMixBufFree);
+ Assert(offFrame + cFrames <= cMixBufFree);
+
+ /*
+ * Make start frame absolute.
+ */
+ offFrame = (pMixBuf->offWrite + offFrame) % pMixBuf->cFrames;
+
+ /*
+ * First chunk.
+ */
+ uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFrame, cFrames);
+ RT_BZERO(&pMixBuf->pi32Samples[offFrame * pMixBuf->cChannels], cFramesChunk1 * pMixBuf->cbFrame);
+
+ /*
+ * Second chunk, if needed.
+ */
+ if (cFrames > cFramesChunk1)
+ {
+ cFrames -= cFramesChunk1;
+ AssertStmt(cFrames <= pMixBuf->cFrames, cFrames = pMixBuf->cFrames);
+ RT_BZERO(&pMixBuf->pi32Samples[0], cFrames * pMixBuf->cbFrame);
+ }
+
+ /*
+ * Reset the resampling state.
+ */
+ audioMixBufRateReset(&pState->Rate);
+}
+
+
+/**
+ * Records a blending gap (silence) of @a cFrames.
+ *
+ * This is used to adjust or reset the resampling state so we start from a
+ * silence state the next time we need to blend or write using @a pState.
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param pState The write state.
+ * @param cFrames Number of frames of silence.
+ * @sa AudioMixBufSilence
+ */
+void AudioMixBufBlendGap(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t cFrames)
+{
+ /*
+ * For now we'll just reset the resampling state regardless of how many
+ * frames of silence there is.
+ */
+ audioMixBufRateReset(&pState->Rate);
+ RT_NOREF(pMixBuf, cFrames);
+}
+
+
+/**
+ * Advances the read position of the buffer.
+ *
+ * For use after done peeking with AudioMixBufPeek().
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param cFrames Number of frames to advance.
+ * @sa AudioMixBufCommit
+ */
+void AudioMixBufAdvance(PAUDIOMIXBUF pMixBuf, uint32_t cFrames)
+{
+ AssertPtrReturnVoid(pMixBuf);
+ AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+
+ AssertStmt(cFrames <= pMixBuf->cUsed, cFrames = pMixBuf->cUsed);
+ pMixBuf->cUsed -= cFrames;
+ pMixBuf->offRead = (pMixBuf->offRead + cFrames) % pMixBuf->cFrames;
+ LogFlowFunc(("%s: Advanced %u frames: offRead=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offRead, pMixBuf->cUsed));
+}
+
+
+/**
+ * Worker for audioMixAdjustVolume that adjust one contiguous chunk.
+ */
+static void audioMixAdjustVolumeWorker(PAUDIOMIXBUF pMixBuf, uint32_t off, uint32_t cFrames)
+{
+ int32_t *pi32Samples = &pMixBuf->pi32Samples[off * pMixBuf->cChannels];
+ switch (pMixBuf->cChannels)
+ {
+ case 1:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ while (cFrames-- > 0)
+ {
+ *pi32Samples = (int32_t)(ASMMult2xS32RetS64(*pi32Samples, uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples++;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1];
+ while (cFrames-- > 0)
+ {
+ pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples += 2;
+ }
+ break;
+ }
+
+ case 3:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1];
+ uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2];
+ while (cFrames-- > 0)
+ {
+ pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples += 3;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1];
+ uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2];
+ uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3];
+ while (cFrames-- > 0)
+ {
+ pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples += 4;
+ }
+ break;
+ }
+
+ case 5:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1];
+ uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2];
+ uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3];
+ uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4];
+ while (cFrames-- > 0)
+ {
+ pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples += 5;
+ }
+ break;
+ }
+
+ case 6:
+ {
+ uint32_t const uFactorCh0 = pMixBuf->Volume.auChannels[0];
+ uint32_t const uFactorCh1 = pMixBuf->Volume.auChannels[1];
+ uint32_t const uFactorCh2 = pMixBuf->Volume.auChannels[2];
+ uint32_t const uFactorCh3 = pMixBuf->Volume.auChannels[3];
+ uint32_t const uFactorCh4 = pMixBuf->Volume.auChannels[4];
+ uint32_t const uFactorCh5 = pMixBuf->Volume.auChannels[5];
+ while (cFrames-- > 0)
+ {
+ pi32Samples[0] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[0], uFactorCh0) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[1] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[1], uFactorCh1) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[2] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[2], uFactorCh2) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[3] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[3], uFactorCh3) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[4] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[4], uFactorCh4) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples[5] = (int32_t)(ASMMult2xS32RetS64(pi32Samples[5], uFactorCh5) >> AUDIOMIXBUF_VOL_SHIFT);
+ pi32Samples += 6;
+ }
+ break;
+ }
+
+ default:
+ while (cFrames-- > 0)
+ for (uint32_t iCh = 0; iCh < pMixBuf->cChannels; iCh++, pi32Samples++)
+ *pi32Samples = ASMMult2xS32RetS64(*pi32Samples, pMixBuf->Volume.auChannels[iCh]) >> AUDIOMIXBUF_VOL_SHIFT;
+ break;
+ }
+}
+
+
+/**
+ * Does volume adjustments for the given stretch of the buffer.
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param offFirst Where to start (validated).
+ * @param cFrames How many frames (validated).
+ */
+static void audioMixAdjustVolume(PAUDIOMIXBUF pMixBuf, uint32_t offFirst, uint32_t cFrames)
+{
+ /* Caller has already validated these, so we don't need to repeat that in non-strict builds. */
+ Assert(offFirst < pMixBuf->cFrames);
+ Assert(cFrames <= pMixBuf->cFrames);
+
+ /*
+ * Muted?
+ */
+ if (pMixBuf->Volume.fMuted)
+ {
+ /* first chunk */
+ uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames);
+ RT_BZERO(&pMixBuf->pi32Samples[offFirst * pMixBuf->cChannels], pMixBuf->cbFrame * cFramesChunk1);
+
+ /* second chunk */
+ if (cFramesChunk1 < cFrames)
+ RT_BZERO(&pMixBuf->pi32Samples[0], pMixBuf->cbFrame * (cFrames - cFramesChunk1));
+ }
+ /*
+ * Less than max volume?
+ */
+ else if (!pMixBuf->Volume.fAllMax)
+ {
+ /* first chunk */
+ uint32_t const cFramesChunk1 = RT_MIN(pMixBuf->cFrames - offFirst, cFrames);
+ audioMixAdjustVolumeWorker(pMixBuf, offFirst, cFramesChunk1);
+
+ /* second chunk */
+ if (cFramesChunk1 < cFrames)
+ audioMixAdjustVolumeWorker(pMixBuf, 0, cFrames - cFramesChunk1);
+ }
+}
+
+
+/**
+ * Adjust for volume settings and advances the write position of the buffer.
+ *
+ * For use after done peeking with AudioMixBufWrite(), AudioMixBufSilence(),
+ * AudioMixBufBlend() and AudioMixBufBlendGap().
+ *
+ * @param pMixBuf The mixing buffer.
+ * @param cFrames Number of frames to advance.
+ * @sa AudioMixBufAdvance, AudioMixBufSetVolume
+ */
+void AudioMixBufCommit(PAUDIOMIXBUF pMixBuf, uint32_t cFrames)
+{
+ AssertPtrReturnVoid(pMixBuf);
+ AssertReturnVoid(pMixBuf->uMagic == AUDIOMIXBUF_MAGIC);
+
+ AssertStmt(cFrames <= pMixBuf->cFrames - pMixBuf->cUsed, cFrames = pMixBuf->cFrames - pMixBuf->cUsed);
+
+ audioMixAdjustVolume(pMixBuf, pMixBuf->offWrite, cFrames);
+
+ pMixBuf->cUsed += cFrames;
+ pMixBuf->offWrite = (pMixBuf->offWrite + cFrames) % pMixBuf->cFrames;
+ LogFlowFunc(("%s: Advanced %u frames: offWrite=%u cUsed=%u\n", pMixBuf->pszName, cFrames, pMixBuf->offWrite, pMixBuf->cUsed));
+}
+
+
+/**
+ * Sets the volume.
+ *
+ * The volume adjustments are applied by AudioMixBufCommit().
+ *
+ * @param pMixBuf Mixing buffer to set volume for.
+ * @param pVol Pointer to volume structure to set.
+ */
+void AudioMixBufSetVolume(PAUDIOMIXBUF pMixBuf, PCPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturnVoid(pMixBuf);
+ AssertPtrReturnVoid(pVol);
+
+ LogFlowFunc(("%s: fMuted=%RTbool auChannels=%.*Rhxs\n",
+ pMixBuf->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels));
+
+ /*
+ * Convert PDM audio volume to the internal format.
+ */
+ if (!pVol->fMuted)
+ {
+ pMixBuf->Volume.fMuted = false;
+
+ AssertCompileSize(pVol->auChannels[0], sizeof(uint8_t));
+ for (uintptr_t i = 0; i < pMixBuf->cChannels; i++)
+ pMixBuf->Volume.auChannels[i] = s_aVolumeConv[pVol->auChannels[i]] * (AUDIOMIXBUF_VOL_0DB >> 16);
+
+ pMixBuf->Volume.fAllMax = true;
+ for (uintptr_t i = 0; i < pMixBuf->cChannels; i++)
+ if (pMixBuf->Volume.auChannels[i] != AUDIOMIXBUF_VOL_0DB)
+ {
+ pMixBuf->Volume.fAllMax = false;
+ break;
+ }
+ }
+ else
+ {
+ pMixBuf->Volume.fMuted = true;
+ pMixBuf->Volume.fAllMax = false;
+ for (uintptr_t i = 0; i < RT_ELEMENTS(pMixBuf->Volume.auChannels); i++)
+ pMixBuf->Volume.auChannels[i] = 0;
+ }
+}
+
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.h b/src/VBox/Devices/Audio/AudioMixBuffer.h
new file mode 100644
index 00000000..a175c5a7
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixBuffer.h
@@ -0,0 +1,252 @@
+/* $Id: AudioMixBuffer.h $ */
+/** @file
+ * Audio Mixing bufer convert audio samples to/from different rates / formats.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h
+#define VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+/** @defgroup grp_pdm_ifs_audio_mixing_buffers Audio Mixing Buffers
+ * @ingroup grp_pdm_ifs_audio_mixing
+ *
+ * @note This is currently placed under PDM Audio Interface as that seemed like
+ * the best place for it.
+ *
+ * @{
+ */
+
+
+/**
+ * Rate processing information of a source & destination audio stream.
+ *
+ * This is needed because both streams can differ regarding their rates and
+ * therefore need to be treated accordingly.
+ */
+typedef struct AUDIOSTREAMRATE
+{
+ /** Current (absolute) offset in the output (destination) stream.
+ * @todo r=bird: Please reveal which unit these members are given in. */
+ uint64_t offDst;
+ /** Increment for moving offDst for the destination stream.
+ * This is needed because the source <-> destination rate might be different. */
+ uint64_t uDstInc;
+ /** Current (absolute) offset in the input stream. */
+ uint32_t offSrc;
+ /** Set if no conversion is necessary. */
+ bool fNoConversionNeeded;
+ bool afPadding[3];
+
+ /** Last processed frame of the input stream.
+ * Needed for interpolation. */
+ union
+ {
+ int32_t ai32Samples[PDMAUDIO_MAX_CHANNELS];
+ } SrcLast;
+
+ /**
+ * Resampling function.
+ * @returns Number of destination frames written.
+ */
+ DECLR3CALLBACKMEMBER(uint32_t, pfnResample, (int32_t *pi32Dst, uint32_t cDstFrames,
+ int32_t const *pi32Src, uint32_t cSrcFrames, uint32_t *pcSrcFramesRead,
+ struct AUDIOSTREAMRATE *pRate));
+
+} AUDIOSTREAMRATE;
+/** Pointer to rate processing information of a stream. */
+typedef AUDIOSTREAMRATE *PAUDIOSTREAMRATE;
+
+/**
+ * Mixing buffer volume parameters.
+ *
+ * The volume values are in fixed point style and must be converted to/from
+ * before using with e.g. PDMAUDIOVOLUME.
+ */
+typedef struct AUDMIXBUFVOL
+{
+ /** Set to @c true if this stream is muted, @c false if not. */
+ bool fMuted;
+ /** Set if all (relevant) channels are at max. */
+ bool fAllMax;
+ /** The per-channels values. */
+ uint32_t auChannels[PDMAUDIO_MAX_CHANNELS];
+} AUDMIXBUFVOL;
+/** Pointer to mixing buffer volument parameters. */
+typedef AUDMIXBUFVOL *PAUDMIXBUFVOL;
+
+
+/** Pointer to audio mixing buffer. */
+typedef struct AUDIOMIXBUF *PAUDIOMIXBUF;
+/** Pointer to a const audio mixing buffer. */
+typedef struct AUDIOMIXBUF const *PCAUDIOMIXBUF;
+
+
+/**
+ * State & config for AudioMixBufPeek created by AudioMixBufInitPeekState.
+ */
+typedef struct AUDIOMIXBUFPEEKSTATE
+{
+ /** Encodes @a cFrames from @a paSrc to @a pvDst. */
+ DECLR3CALLBACKMEMBER(void, pfnEncode,(void *pvDst, int32_t const *paSrc, uint32_t cFrames, struct AUDIOMIXBUFPEEKSTATE *pState));
+ /** Sample rate conversion state (only used when needed). */
+ AUDIOSTREAMRATE Rate;
+ /** Source (mixer) channels. */
+ uint8_t cSrcChannels;
+ /** Destination channels. */
+ uint8_t cDstChannels;
+ /** Destination frame size. */
+ uint8_t cbDstFrame;
+ /** The destination frame layout described as indexes into the source frame.
+ * This ASSUMES that all channels uses the same sample size, so one sample per
+ * channel if you like.
+ * Negative values are special: -1 for zero, -2 for silence.
+ * @note Blending stereo into mono is not really expressible here. */
+ int8_t aidxChannelMap[PDMAUDIO_MAX_CHANNELS];
+} AUDIOMIXBUFPEEKSTATE;
+/** Pointer to peek state & config. */
+typedef AUDIOMIXBUFPEEKSTATE *PAUDIOMIXBUFPEEKSTATE;
+
+
+/**
+ * State & config for AudioMixBufWrite, AudioMixBufSilence, AudioMixBufBlend and
+ * AudioMixBufBlendGap, created by AudioMixBufInitWriteState.
+ */
+typedef struct AUDIOMIXBUFWRITESTATE
+{
+ /** Encodes @a cFrames from @a pvSrc to @a paDst. */
+ DECLR3CALLBACKMEMBER(void, pfnDecode,(int32_t *paDst, const void *pvSrc, uint32_t cFrames, struct AUDIOMIXBUFWRITESTATE *pState));
+ /** Encodes @a cFrames from @a pvSrc blending into @a paDst. */
+ DECLR3CALLBACKMEMBER(void, pfnDecodeBlend,(int32_t *paDst, const void *pvSrc, uint32_t cFrames, struct AUDIOMIXBUFWRITESTATE *pState));
+ /** Sample rate conversion state (only used when needed). */
+ AUDIOSTREAMRATE Rate;
+ /** Destination (mixer) channels. */
+ uint8_t cDstChannels;
+ /** Source hannels. */
+ uint8_t cSrcChannels;
+ /** Source frame size. */
+ uint8_t cbSrcFrame;
+ /** The destination frame layout described as indexes into the source frame.
+ * This ASSUMES that all channels uses the same sample size, so one sample per
+ * channel if you like.
+ * Negative values are special: -1 for zero, -2 for silence.
+ * @note Blending stereo into mono is not really expressible here. */
+ int8_t aidxChannelMap[PDMAUDIO_MAX_CHANNELS];
+} AUDIOMIXBUFWRITESTATE;
+/** Pointer to write state & config. */
+typedef AUDIOMIXBUFWRITESTATE *PAUDIOMIXBUFWRITESTATE;
+
+
+/**
+ * Audio mixing buffer.
+ */
+typedef struct AUDIOMIXBUF
+{
+ /** Magic value (AUDIOMIXBUF_MAGIC). */
+ uint32_t uMagic;
+ /** Size of the frame buffer (in audio frames). */
+ uint32_t cFrames;
+ /** The frame buffer.
+ * This is a two dimensional array consisting of cFrames rows and
+ * cChannels columns. */
+ int32_t *pi32Samples;
+ /** The number of channels. */
+ uint8_t cChannels;
+ /** The frame size (row size if you like). */
+ uint8_t cbFrame;
+ uint8_t abPadding[2];
+ /** The current read position (in frames). */
+ uint32_t offRead;
+ /** The current write position (in frames). */
+ uint32_t offWrite;
+ /** How much audio frames are currently being used in this buffer.
+ * @note This also is known as the distance in ring buffer terms. */
+ uint32_t cUsed;
+ /** Audio properties for the buffer content - for frequency and channel count.
+ * (This is the guest side PCM properties.) */
+ PDMAUDIOPCMPROPS Props;
+ /** Internal representation of current volume used for mixing. */
+ AUDMIXBUFVOL Volume;
+ /** Name of the buffer. */
+ char *pszName;
+} AUDIOMIXBUF;
+
+/** Magic value for AUDIOMIXBUF (Antonio Lucio Vivaldi). */
+#define AUDIOMIXBUF_MAGIC UINT32_C(0x16780304)
+/** Dead mixer buffer magic. */
+#define AUDIOMIXBUF_MAGIC_DEAD UINT32_C(0x17410728)
+
+/** Converts (audio) frames to bytes. */
+#define AUDIOMIXBUF_F2B(a_pMixBuf, a_cFrames) PDMAUDIOPCMPROPS_F2B(&(a_pMixBuf)->Props, a_cFrames)
+/** Converts bytes to (audio) frames.
+ * @note Does *not* take the conversion ratio into account. */
+#define AUDIOMIXBUF_B2F(a_pMixBuf, a_cb) PDMAUDIOPCMPROPS_B2F(&(a_pMixBuf)->Props, a_cb)
+
+
+int AudioMixBufInit(PAUDIOMIXBUF pMixBuf, const char *pszName, PCPDMAUDIOPCMPROPS pProps, uint32_t cFrames);
+void AudioMixBufTerm(PAUDIOMIXBUF pMixBuf);
+void AudioMixBufDrop(PAUDIOMIXBUF pMixBuf);
+void AudioMixBufSetVolume(PAUDIOMIXBUF pMixBuf, PCPDMAUDIOVOLUME pVol);
+
+/** @name Mixer buffer getters
+ * @{ */
+uint32_t AudioMixBufSize(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufSizeBytes(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufUsed(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufUsedBytes(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufFree(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufFreeBytes(PCAUDIOMIXBUF pMixBuf);
+bool AudioMixBufIsEmpty(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufReadPos(PCAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufWritePos(PCAUDIOMIXBUF pMixBuf);
+/** @} */
+
+/** @name Mixer buffer reading
+ * @{ */
+int AudioMixBufInitPeekState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFPEEKSTATE pState, PCPDMAUDIOPCMPROPS pDstProps);
+void AudioMixBufPeek(PCAUDIOMIXBUF pMixBuf, uint32_t offSrcFrame, uint32_t cMaxSrcFrames, uint32_t *pcSrcFramesPeeked,
+ PAUDIOMIXBUFPEEKSTATE pState, void *pvDst, uint32_t cbDst, uint32_t *pcbDstPeeked);
+void AudioMixBufAdvance(PAUDIOMIXBUF pMixBuf, uint32_t cFrames);
+/** @} */
+
+/** @name Mixer buffer writing
+ * @{ */
+int AudioMixBufInitWriteState(PCAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, PCPDMAUDIOPCMPROPS pSrcProps);
+void AudioMixBufWrite(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cMaxDstFrames, uint32_t *pcDstFramesWritten);
+void AudioMixBufSilence(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t offFrame, uint32_t cFrames);
+void AudioMixBufBlend(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, const void *pvSrcBuf, uint32_t cbSrcBuf,
+ uint32_t offDstFrame, uint32_t cMaxDstFrames, uint32_t *pcDstFramesBlended);
+void AudioMixBufBlendGap(PAUDIOMIXBUF pMixBuf, PAUDIOMIXBUFWRITESTATE pState, uint32_t cFrames);
+void AudioMixBufCommit(PAUDIOMIXBUF pMixBuf, uint32_t cFrames);
+/** @} */
+
+/** @} */
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixBuffer_h */
+
diff --git a/src/VBox/Devices/Audio/AudioMixer.cpp b/src/VBox/Devices/Audio/AudioMixer.cpp
new file mode 100644
index 00000000..90d86063
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixer.cpp
@@ -0,0 +1,2732 @@
+/* $Id: AudioMixer.cpp $ */
+/** @file
+ * Audio mixing routines for multiplexing audio sources in device emulations.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+/** @page pg_audio_mixer Audio Mixer
+ *
+ * @section sec_audio_mixer_overview Overview
+ *
+ * This mixer acts as a layer between the audio connector interface and the
+ * actual device emulation, providing mechanisms for audio input sinks (sometime
+ * referred to as audio sources) and audio output sinks.
+ *
+ * Think of this mixer as kind of a higher level interface for the audio device
+ * to use in steado of PDMIAUDIOCONNECTOR, where it works with sinks rather than
+ * individual PDMAUDIOSTREAM instances.
+ *
+ * How and which audio streams are connected to the sinks depends on how the
+ * audio mixer has been set up by the device. Though, generally, each driver
+ * chain (LUN) has a mixer stream for each sink.
+ *
+ * An output sink can connect multiple output streams together, whereas an input
+ * sink (source) does this with input streams. Each of these mixer stream will
+ * in turn point to actual PDMAUDIOSTREAM instances.
+ *
+ * A mixing sink employs an own audio mixing buffer in a standard format (32-bit
+ * signed) with the virtual device's rate and channel configuration. The mixer
+ * streams will convert to/from this as they write and read from it.
+ *
+ *
+ * @section sec_audio_mixer_playback Playback
+ *
+ * For output sinks there can be one or more mixing stream attached.
+ *
+ * The backends are the consumers here and if they don't get samples when then
+ * need them we'll be having cracles, distortion and/or bits of silence in the
+ * actual output. The guest runs independently at it's on speed (see @ref
+ * sec_pdm_audio_timing for more details) and we're just inbetween trying to
+ * shuffle the data along as best as we can. If one or more of the backends
+ * for some reason isn't able to process data at a nominal speed (as defined by
+ * the others), we'll try detect this, mark it as bad and disregard it when
+ * calculating how much we can write to the backends in a buffer update call.
+ *
+ * This is called synchronous multiplexing.
+ *
+ *
+ * @section sec_audio_mixer_recording Recording
+ *
+ * For input sinks (sources) we blend the samples of all mixing streams
+ * together, however ignoring silent ones to avoid too much of a hit on the
+ * volume level. It is otherwise very similar to playback, only the direction
+ * is different and we don't multicast but blend.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_MIXER
+#include <VBox/log.h>
+#include "AudioMixer.h"
+#include "AudioMixBuffer.h"
+#include "AudioHlp.h"
+
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#ifdef VBOX_WITH_DTRACE
+# include "dtrace/VBoxDD.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
+
+static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink, PPDMDEVINS pDevIns);
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster);
+static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+static void audioMixerSinkResetInternal(PAUDMIXSINK pSink);
+
+static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd);
+static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream, PPDMDEVINS pDevIns, bool fImmediate);
+static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream);
+
+
+/** size of output buffer for dbgAudioMixerSinkStatusToStr. */
+#define AUDIOMIXERSINK_STATUS_STR_MAX sizeof("RUNNING DRAINING DRAINED_DMA DRAINED_MIXBUF DIRTY 0x12345678")
+
+/**
+ * Converts a mixer sink status to a string.
+ *
+ * @returns pszDst
+ * @param fStatus The mixer sink status.
+ * @param pszDst The output buffer. Must be at least
+ * AUDIOMIXERSINK_STATUS_STR_MAX in length.
+ */
+static const char *dbgAudioMixerSinkStatusToStr(uint32_t fStatus, char pszDst[AUDIOMIXERSINK_STATUS_STR_MAX])
+{
+ if (!fStatus)
+ return strcpy(pszDst, "NONE");
+ static const struct
+ {
+ const char *pszMnemonic;
+ uint32_t cchMnemonic;
+ uint32_t fStatus;
+ } s_aFlags[] =
+ {
+ { RT_STR_TUPLE("RUNNING "), AUDMIXSINK_STS_RUNNING },
+ { RT_STR_TUPLE("DRAINING "), AUDMIXSINK_STS_DRAINING },
+ { RT_STR_TUPLE("DRAINED_DMA "), AUDMIXSINK_STS_DRAINED_DMA },
+ { RT_STR_TUPLE("DRAINED_MIXBUF "), AUDMIXSINK_STS_DRAINED_MIXBUF },
+ { RT_STR_TUPLE("DIRTY "), AUDMIXSINK_STS_DIRTY },
+ };
+ char *psz = pszDst;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
+ if (fStatus & s_aFlags[i].fStatus)
+ {
+ memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemonic);
+ psz += s_aFlags[i].cchMnemonic;
+ fStatus &= ~s_aFlags[i].fStatus;
+ if (!fStatus)
+ {
+ psz[-1] = '\0';
+ return pszDst;
+ }
+ }
+ RTStrPrintf(psz, AUDIOMIXERSINK_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus);
+ return pszDst;
+}
+
+
+/**
+ * Creates an audio mixer.
+ *
+ * @returns VBox status code.
+ * @param pszName Name of the audio mixer.
+ * @param fFlags Creation flags - AUDMIXER_FLAGS_XXX.
+ * @param ppMixer Pointer which returns the created mixer object.
+ */
+int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t const cchName = strlen(pszName);
+ AssertReturn(cchName > 0 && cchName < 128, VERR_INVALID_NAME);
+ AssertReturn (!(fFlags & ~AUDMIXER_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(ppMixer, VERR_INVALID_POINTER);
+
+ int rc;
+ PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZVar(sizeof(AUDIOMIXER) + cchName + 1);
+ if (pMixer)
+ {
+ rc = RTCritSectInit(&pMixer->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pMixer->pszName = (const char *)memcpy(pMixer + 1, pszName, cchName + 1);
+
+ pMixer->cSinks = 0;
+ RTListInit(&pMixer->lstSinks);
+
+ pMixer->fFlags = fFlags;
+ pMixer->uMagic = AUDIOMIXER_MAGIC;
+
+ if (pMixer->fFlags & AUDMIXER_FLAGS_DEBUG)
+ LogRel(("Audio Mixer: Debug mode enabled\n"));
+
+ /* Set master volume to the max. */
+ PDMAudioVolumeInitMax(&pMixer->VolMaster);
+
+ LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName));
+ *ppMixer = pMixer;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pMixer);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Destroys an audio mixer.
+ *
+ * @param pMixer Audio mixer to destroy. NULL is ignored.
+ * @param pDevIns The device instance the statistics are associated with.
+ */
+void AudioMixerDestroy(PAUDIOMIXER pMixer, PPDMDEVINS pDevIns)
+{
+ if (!pMixer)
+ return;
+ AssertPtrReturnVoid(pMixer);
+ AssertReturnVoid(pMixer->uMagic == AUDIOMIXER_MAGIC);
+
+ int rc2 = RTCritSectEnter(&pMixer->CritSect);
+ AssertRCReturnVoid(rc2);
+ Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
+
+ LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
+ pMixer->uMagic = AUDIOMIXER_MAGIC_DEAD;
+
+ PAUDMIXSINK pSink, pSinkNext;
+ RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node)
+ {
+ audioMixerRemoveSinkInternal(pMixer, pSink);
+ audioMixerSinkDestroyInternal(pSink, pDevIns);
+ }
+ Assert(pMixer->cSinks == 0);
+
+ rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ RTCritSectDelete(&pMixer->CritSect);
+ RTMemFree(pMixer);
+}
+
+
+/**
+ * Helper function for the internal debugger to print the mixer's current
+ * state, along with the attached sinks.
+ *
+ * @param pMixer Mixer to print debug output for.
+ * @param pHlp Debug info helper to use.
+ * @param pszArgs Optional arguments. Not being used at the moment.
+ */
+void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ AssertReturnVoid(pMixer->uMagic == AUDIOMIXER_MAGIC);
+
+ int rc = RTCritSectEnter(&pMixer->CritSect);
+ AssertRCReturnVoid(rc);
+
+ /* Determin max sink name length for pretty formatting: */
+ size_t cchMaxName = strlen(pMixer->pszName);
+ PAUDMIXSINK pSink;
+ RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
+ {
+ size_t const cchMixer = strlen(pSink->pszName);
+ cchMaxName = RT_MAX(cchMixer, cchMaxName);
+ }
+
+ /* Do the displaying. */
+ pHlp->pfnPrintf(pHlp, "[Master] %*s: fMuted=%#RTbool auChannels=%.*Rhxs\n", cchMaxName, pMixer->pszName,
+ pMixer->VolMaster.fMuted, sizeof(pMixer->VolMaster.auChannels), pMixer->VolMaster.auChannels);
+ unsigned iSink = 0;
+ RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
+ {
+ pHlp->pfnPrintf(pHlp, "[Sink %u] %*s: fMuted=%#RTbool auChannels=%.*Rhxs\n", iSink, cchMaxName, pSink->pszName,
+ pSink->Volume.fMuted, sizeof(pSink->Volume.auChannels), pSink->Volume.auChannels);
+ ++iSink;
+ }
+
+ RTCritSectLeave(&pMixer->CritSect);
+}
+
+
+/**
+ * Sets the mixer's master volume.
+ *
+ * @returns VBox status code.
+ * @param pMixer Mixer to set master volume for.
+ * @param pVol Volume to set.
+ */
+int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PCPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+ AssertReturn(pMixer->uMagic == AUDIOMIXER_MAGIC, VERR_INVALID_MAGIC);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pMixer->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Make a copy.
+ */
+ LogFlowFunc(("[%s] fMuted=%RTbool auChannels=%.*Rhxs => fMuted=%RTbool auChannels=%.*Rhxs\n", pMixer->pszName,
+ pMixer->VolMaster.fMuted, sizeof(pMixer->VolMaster.auChannels), pMixer->VolMaster.auChannels,
+ pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels ));
+ memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME));
+
+ /*
+ * Propagate new master volume to all sinks.
+ */
+ PAUDMIXSINK pSink;
+ RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
+ {
+ int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster);
+ AssertRC(rc2);
+ }
+
+ RTCritSectLeave(&pMixer->CritSect);
+ return rc;
+}
+
+
+/**
+ * Removes an audio sink from the given audio mixer, internal version.
+ *
+ * Used by AudioMixerDestroy and AudioMixerSinkDestroy.
+ *
+ * Caller must hold the mixer lock.
+ *
+ * @returns VBox status code.
+ * @param pMixer Mixer to remove sink from.
+ * @param pSink Sink to remove.
+ */
+static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
+{
+ LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n", pMixer->pszName, pSink->pszName, pMixer->cSinks));
+ Assert(RTCritSectIsOwner(&pMixer->CritSect));
+ AssertMsgReturn(pSink->pParent == pMixer,
+ ("%s: Is not part of mixer '%s'\n", pSink->pszName, pMixer->pszName), VERR_INTERNAL_ERROR_4);
+
+ /* Remove sink from mixer. */
+ RTListNodeRemove(&pSink->Node);
+
+ Assert(pMixer->cSinks);
+ pMixer->cSinks--;
+
+ /* Set mixer to NULL so that we know we're not part of any mixer anymore. */
+ pSink->pParent = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Mixer Sink implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Creates an audio sink and attaches it to the given mixer.
+ *
+ * @returns VBox status code.
+ * @param pMixer Mixer to attach created sink to.
+ * @param pszName Name of the sink to create.
+ * @param enmDir Direction of the sink to create.
+ * @param pDevIns The device instance to register statistics under.
+ * @param ppSink Pointer which returns the created sink on success.
+ */
+int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, PDMAUDIODIR enmDir, PPDMDEVINS pDevIns, PAUDMIXSINK *ppSink)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t const cchName = strlen(pszName);
+ AssertReturn(cchName > 0 && cchName < 64, VERR_INVALID_NAME);
+ AssertPtrNullReturn(ppSink, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pMixer->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /** @todo limit the number of sinks? */
+
+ /*
+ * Allocate the data and initialize the critsect.
+ */
+ PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZVar(sizeof(AUDMIXSINK) + cchName + 1);
+ if (pSink)
+ {
+ rc = RTCritSectInit(&pSink->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize it.
+ */
+ pSink->uMagic = AUDMIXSINK_MAGIC;
+ pSink->pParent = NULL;
+ pSink->enmDir = enmDir;
+ pSink->pszName = (const char *)memcpy(pSink + 1, pszName, cchName + 1);
+ RTListInit(&pSink->lstStreams);
+
+ /* Set initial volume to max. */
+ PDMAudioVolumeInitMax(&pSink->Volume);
+
+ /* Ditto for the combined volume. */
+ PDMAudioVolumeInitMax(&pSink->VolumeCombined);
+
+ /* AIO */
+ AssertPtr(pDevIns);
+ pSink->AIO.pDevIns = pDevIns;
+ pSink->AIO.hThread = NIL_RTTHREAD;
+ pSink->AIO.hEvent = NIL_RTSEMEVENT;
+ pSink->AIO.fStarted = false;
+ pSink->AIO.fShutdown = false;
+ pSink->AIO.cUpdateJobs = 0;
+
+ /*
+ * Add it to the mixer.
+ */
+ RTListAppend(&pMixer->lstSinks, &pSink->Node);
+ pMixer->cSinks++;
+ pSink->pParent = pMixer;
+
+ RTCritSectLeave(&pMixer->CritSect);
+
+ /*
+ * Register stats and return.
+ */
+ char szPrefix[128];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "MixerSink-%s/", pSink->pszName);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pSink->MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Sink mixer buffer size in frames.", "%sMixBufSize", szPrefix);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pSink->MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Sink mixer buffer fill size in frames.", "%sMixBufUsed", szPrefix);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pSink->cStreams, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Number of streams attached to the sink.", "%sStreams", szPrefix);
+
+ if (ppSink)
+ *ppSink = pSink;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pSink);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTCritSectLeave(&pMixer->CritSect);
+ if (ppSink)
+ *ppSink = NULL;
+ return rc;
+}
+
+
+/**
+ * Starts playback/capturing on the mixer sink.
+ *
+ * @returns VBox status code. Generally always VINF_SUCCESS unless the input
+ * is invalid. Individual driver errors are suppressed and ignored.
+ * @param pSink Mixer sink to control.
+ */
+int AudioMixerSinkStart(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+ char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
+ LogFunc(("Starting '%s'. Old status: %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+
+ AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN || pSink->enmDir == PDMAUDIODIR_OUT,
+ RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Make sure the sink and its streams are all stopped.
+ */
+ if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
+ Assert(pSink->fStatus == AUDMIXSINK_STS_NONE);
+ else
+ {
+ LogFunc(("%s: This sink is still running!! Stop it before starting it again.\n", pSink->pszName));
+
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ /** @todo PDMAUDIOSTREAMCMD_STOP_NOW */
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ }
+ audioMixerSinkResetInternal(pSink);
+ }
+
+ /*
+ * Send the command to the streams.
+ */
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE);
+ }
+
+ /*
+ * Update the sink status.
+ */
+ pSink->fStatus = AUDMIXSINK_STS_RUNNING;
+
+ LogRel2(("Audio Mixer: Started sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+
+ RTCritSectLeave(&pSink->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Helper for AudioMixerSinkDrainAndStop that calculates the max length a drain
+ * operation should take.
+ *
+ * @returns The drain deadline (relative to RTTimeNanoTS).
+ * @param pSink The sink.
+ * @param cbDmaLeftToDrain The number of bytes in the DMA buffer left to
+ * transfer into the mixbuf.
+ */
+static uint64_t audioMixerSinkDrainDeadline(PAUDMIXSINK pSink, uint32_t cbDmaLeftToDrain)
+{
+ /*
+ * Calculate the max backend buffer size in mixbuf frames.
+ * (This is somewhat similar to audioMixerSinkUpdateOutputCalcFramesToRead.)
+ */
+ uint32_t cFramesStreamMax = 0;
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ /*LogFunc(("Stream '%s': %#x (%u frames)\n", pMixStream->pszName, pMixStream->fStatus, pMixStream->cFramesBackendBuffer));*/
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE)
+ {
+ uint32_t cFrames = pMixStream->cFramesBackendBuffer;
+ if (PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props))
+ { /* likely */ }
+ else
+ cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props);
+ if (cFrames > cFramesStreamMax)
+ {
+ Log4Func(("%s: cFramesStreamMax %u -> %u; %s\n", pSink->pszName, cFramesStreamMax, cFrames, pMixStream->pszName));
+ cFramesStreamMax = cFrames;
+ }
+ }
+ }
+
+ /*
+ * Combine that with the pending DMA and mixbuf content, then convert
+ * to nanoseconds and apply a fudge factor to get a generous deadline.
+ */
+ uint32_t const cFramesDmaAndMixBuf = PDMAudioPropsBytesToFrames(&pSink->MixBuf.Props, cbDmaLeftToDrain)
+ + AudioMixBufUsed(&pSink->MixBuf);
+ uint64_t const cNsToDrainMax = PDMAudioPropsFramesToNano(&pSink->MixBuf.Props, cFramesDmaAndMixBuf + cFramesStreamMax);
+ uint64_t const nsDeadline = cNsToDrainMax * 2;
+ LogFlowFunc(("%s: cFramesStreamMax=%#x cFramesDmaAndMixBuf=%#x -> cNsToDrainMax=%RU64 -> %RU64\n",
+ pSink->pszName, cFramesStreamMax, cFramesDmaAndMixBuf, cNsToDrainMax, nsDeadline));
+ return nsDeadline;
+}
+
+
+/**
+ * Kicks off the draining and stopping playback/capture on the mixer sink.
+ *
+ * For input streams this causes an immediate stop, as draining only makes sense
+ * to output stream in the VBox device context.
+ *
+ * @returns VBox status code. Generally always VINF_SUCCESS unless the input
+ * is invalid. Individual driver errors are suppressed and ignored.
+ * @param pSink Mixer sink to control.
+ * @param cbComming The number of bytes still left in the device's DMA
+ * buffers that the update job has yet to transfer. This
+ * is ignored for input streams.
+ */
+int AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+ char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
+ LogFunc(("Draining '%s' with %#x bytes left. Old status: %s\n",
+ pSink->pszName, cbComming, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus) ));
+
+ AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN || pSink->enmDir == PDMAUDIODIR_OUT,
+ RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3);
+
+ if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ {
+ /*
+ * Output streams will be drained then stopped (all by the AIO thread).
+ *
+ * For streams we define that they shouldn't not be written to after we start draining,
+ * so we have to hold back sending the command to them till we've processed all the
+ * cbComming remaining bytes in the DMA buffer.
+ */
+ if (pSink->enmDir == PDMAUDIODIR_OUT)
+ {
+ if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+ {
+ Assert(!(pSink->fStatus & (AUDMIXSINK_STS_DRAINED_DMA | AUDMIXSINK_STS_DRAINED_MIXBUF)));
+
+ /* Update the status and draining member. */
+ pSink->cbDmaLeftToDrain = cbComming;
+ pSink->nsDrainDeadline = audioMixerSinkDrainDeadline(pSink, cbComming);
+ if (pSink->nsDrainDeadline > 0)
+ {
+ pSink->nsDrainStarted = RTTimeNanoTS();
+ pSink->nsDrainDeadline += pSink->nsDrainStarted;
+ pSink->fStatus |= AUDMIXSINK_STS_DRAINING;
+
+ /* Kick the AIO thread so it can keep pushing data till we're out of this
+ status. (The device's DMA timer won't kick it any more, so we must.) */
+ AudioMixerSinkSignalUpdateJob(pSink);
+ }
+ else
+ {
+ LogFunc(("%s: No active streams, doing an immediate stop.\n", pSink->pszName));
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ }
+ audioMixerSinkResetInternal(pSink);
+ }
+ }
+ else
+ AssertMsgFailed(("Already draining '%s': %s\n",
+ pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+ }
+ /*
+ * Input sinks are stopped immediately.
+ *
+ * It's the guest giving order here and we can't force it to accept data that's
+ * already in the buffer pipeline or anything. So, there can be no draining here.
+ */
+ else
+ {
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ }
+ audioMixerSinkResetInternal(pSink);
+ }
+ }
+ else
+ LogFunc(("%s: Not running\n", pSink->pszName));
+
+ LogRel2(("Audio Mixer: Started draining sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+ RTCritSectLeave(&pSink->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroys and frees a mixer sink.
+ *
+ * Worker for AudioMixerSinkDestroy(), AudioMixerCreateSink() and
+ * AudioMixerDestroy().
+ *
+ * @param pSink Mixer sink to destroy.
+ * @param pDevIns The device instance statistics are registered with.
+ */
+static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink, PPDMDEVINS pDevIns)
+{
+ AssertPtrReturnVoid(pSink);
+
+ LogFunc(("%s\n", pSink->pszName));
+
+ /*
+ * Invalidate the sink instance.
+ */
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ pSink->uMagic = AUDMIXSINK_MAGIC_DEAD;
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Destroy all streams.
+ */
+ PAUDMIXSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
+ {
+ audioMixerSinkRemoveStreamInternal(pSink, pStream);
+ audioMixerStreamDestroyInternal(pStream, pDevIns, true /*fImmediate*/);
+ }
+
+ rc = RTCritSectLeave(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Destroy debug file and statistics.
+ */
+ if (!pSink->Dbg.pFile)
+ { /* likely */ }
+ else
+ {
+ AudioHlpFileDestroy(pSink->Dbg.pFile);
+ pSink->Dbg.pFile = NULL;
+ }
+
+ char szPrefix[128];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "MixerSink-%s/", pSink->pszName);
+ PDMDevHlpSTAMDeregisterByPrefix(pDevIns, szPrefix);
+
+ /*
+ * Shutdown the AIO thread if started:
+ */
+ ASMAtomicWriteBool(&pSink->AIO.fShutdown, true);
+ if (pSink->AIO.hEvent != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventSignal(pSink->AIO.hEvent);
+ AssertRC(rc2);
+ }
+ if (pSink->AIO.hThread != NIL_RTTHREAD)
+ {
+ LogFlowFunc(("Waiting for AIO thread for %s...\n", pSink->pszName));
+ int rc2 = RTThreadWait(pSink->AIO.hThread, RT_MS_30SEC, NULL);
+ AssertRC(rc2);
+ pSink->AIO.hThread = NIL_RTTHREAD;
+ }
+ if (pSink->AIO.hEvent != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventDestroy(pSink->AIO.hEvent);
+ AssertRC(rc2);
+ pSink->AIO.hEvent = NIL_RTSEMEVENT;
+ }
+
+ /*
+ * Mixing buffer, critsect and the structure itself.
+ */
+ AudioMixBufTerm(&pSink->MixBuf);
+ RTCritSectDelete(&pSink->CritSect);
+ RTMemFree(pSink);
+}
+
+
+/**
+ * Destroys a mixer sink and removes it from the attached mixer (if any).
+ *
+ * @param pSink Mixer sink to destroy. NULL is ignored.
+ * @param pDevIns The device instance that statistics are registered with.
+ */
+void AudioMixerSinkDestroy(PAUDMIXSINK pSink, PPDMDEVINS pDevIns)
+{
+ if (!pSink)
+ return;
+ AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC);
+
+ /*
+ * Serializing paranoia.
+ */
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+ RTCritSectLeave(&pSink->CritSect);
+
+ /*
+ * Unlink from parent.
+ */
+ PAUDIOMIXER pMixer = pSink->pParent;
+ if ( RT_VALID_PTR(pMixer)
+ && pMixer->uMagic == AUDIOMIXER_MAGIC)
+ {
+ RTCritSectEnter(&pMixer->CritSect);
+ audioMixerRemoveSinkInternal(pMixer, pSink);
+ RTCritSectLeave(&pMixer->CritSect);
+ }
+ else if (pMixer)
+ AssertFailed();
+
+ /*
+ * Actually destroy it.
+ */
+ audioMixerSinkDestroyInternal(pSink, pDevIns);
+}
+
+
+/**
+ * Get the number of bytes that can be read from the sink.
+ *
+ * @returns Number of bytes.
+ * @param pSink The mixer sink.
+ *
+ * @note Only applicable to input sinks, will assert and return zero for
+ * other sink directions.
+ */
+uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, 0);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, 0);
+ AssertMsgReturn(pSink->enmDir == PDMAUDIODIR_IN, ("%s: Can't read from a non-input sink\n", pSink->pszName), 0);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, 0);
+
+ uint32_t cbReadable = 0;
+ if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ cbReadable = AudioMixBufUsedBytes(&pSink->MixBuf);
+
+ RTCritSectLeave(&pSink->CritSect);
+ Log3Func(("[%s] cbReadable=%#x\n", pSink->pszName, cbReadable));
+ return cbReadable;
+}
+
+
+/**
+ * Get the number of bytes that can be written to be sink.
+ *
+ * @returns Number of bytes.
+ * @param pSink The mixer sink.
+ *
+ * @note Only applicable to output sinks, will assert and return zero for
+ * other sink directions.
+ */
+uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, 0);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, 0);
+ AssertMsgReturn(pSink->enmDir == PDMAUDIODIR_OUT, ("%s: Can't write to a non-output sink\n", pSink->pszName), 0);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, 0);
+
+ uint32_t cbWritable = 0;
+ if ((pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_DRAINING)) == AUDMIXSINK_STS_RUNNING)
+ cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf);
+
+ RTCritSectLeave(&pSink->CritSect);
+ Log3Func(("[%s] cbWritable=%#x (%RU64ms)\n", pSink->pszName, cbWritable,
+ PDMAudioPropsBytesToMilli(&pSink->PCMProps, cbWritable) ));
+ return cbWritable;
+}
+
+
+/**
+ * Get the sink's mixing direction.
+ *
+ * @returns Mixing direction.
+ * @param pSink The mixer sink.
+ */
+PDMAUDIODIR AudioMixerSinkGetDir(PCAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, PDMAUDIODIR_INVALID);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, PDMAUDIODIR_INVALID);
+
+ /* The sink direction cannot be changed after creation, so no need for locking here. */
+ return pSink->enmDir;
+}
+
+
+/**
+ * Get the sink status.
+ *
+ * @returns AUDMIXSINK_STS_XXX
+ * @param pSink The mixer sink.
+ */
+uint32_t AudioMixerSinkGetStatus(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, AUDMIXSINK_STS_NONE);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, AUDMIXSINK_STS_NONE);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, AUDMIXSINK_STS_NONE);
+
+ uint32_t const fStsSink = pSink->fStatus;
+
+ RTCritSectLeave(&pSink->CritSect);
+ return fStsSink;
+}
+
+
+/**
+ * Checks if the sink is active not.
+ *
+ * @note The pending disable state also counts as active.
+ *
+ * @retval true if active.
+ * @retval false if not active.
+ * @param pSink The mixer sink. NULL is okay (returns false).
+ */
+bool AudioMixerSinkIsActive(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return false;
+ AssertPtr(pSink);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, false);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, false);
+
+ bool const fIsActive = RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_RUNNING);
+
+ RTCritSectLeave(&pSink->CritSect);
+ Log3Func(("[%s] returns %RTbool\n", pSink->pszName, fIsActive));
+ return fIsActive;
+}
+
+
+/**
+ * Resets the sink's state.
+ *
+ * @param pSink The sink to reset.
+ * @note Must own sink lock.
+ */
+static void audioMixerSinkResetInternal(PAUDMIXSINK pSink)
+{
+ Assert(RTCritSectIsOwner(&pSink->CritSect));
+ LogFunc(("[%s]\n", pSink->pszName));
+
+ /* Drop mixing buffer content. */
+ AudioMixBufDrop(&pSink->MixBuf);
+
+ /* Reset status. */
+ pSink->fStatus = AUDMIXSINK_STS_NONE;
+ pSink->tsLastUpdatedMs = 0;
+}
+
+
+/**
+ * Resets a sink. This will immediately stop all processing.
+ *
+ * @param pSink Sink to reset.
+ */
+void AudioMixerSinkReset(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+ AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+
+ LogFlowFunc(("[%s]\n", pSink->pszName));
+
+ /*
+ * Stop any stream that's enabled before resetting the state.
+ */
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ if (pStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ }
+
+ /*
+ * Reset the state.
+ */
+ audioMixerSinkResetInternal(pSink);
+
+ RTCritSectLeave(&pSink->CritSect);
+}
+
+
+/**
+ * Sets the audio format of a mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink The sink to set audio format for.
+ * @param pProps The properties of the new audio format (guest side).
+ * @param cMsSchedulingHint Scheduling hint for mixer buffer sizing.
+ */
+int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PCPDMAUDIOPCMPROPS pProps, uint32_t cMsSchedulingHint)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, VERR_INVALID_MAGIC);
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+ AssertReturn(AudioHlpPcmPropsAreValidAndSupported(pProps), VERR_INVALID_PARAMETER);
+
+ /*
+ * Calculate the mixer buffer size so we can force a recreation if it changes.
+ *
+ * This used to be fixed at 100ms, however that's usually too generous and can
+ * in theory be too small. Generally, we size the buffer at 3 DMA periods as
+ * that seems reasonable. Now, since the we don't quite trust the scheduling
+ * hint we're getting, make sure we're got a minimum of 30ms buffer space, but
+ * no more than 500ms.
+ */
+ if (cMsSchedulingHint <= 10)
+ cMsSchedulingHint = 30;
+ else
+ {
+ cMsSchedulingHint *= 3;
+ if (cMsSchedulingHint > 500)
+ cMsSchedulingHint = 500;
+ }
+ uint32_t const cBufferFrames = PDMAudioPropsMilliToFrames(pProps, cMsSchedulingHint);
+ /** @todo configuration override on the buffer size? */
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do nothing unless the format actually changed.
+ * The buffer size must not match exactly, within +/- 2% is okay.
+ */
+ uint32_t cOldBufferFrames;
+ if ( !PDMAudioPropsAreEqual(&pSink->PCMProps, pProps)
+ || ( cBufferFrames != (cOldBufferFrames = AudioMixBufSize(&pSink->MixBuf))
+ && (uint32_t)RT_ABS((int32_t)(cBufferFrames - cOldBufferFrames)) > cBufferFrames / 50) )
+ {
+#ifdef LOG_ENABLED
+ char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
+#endif
+ if (PDMAudioPropsHz(&pSink->PCMProps) != 0)
+ LogFlowFunc(("[%s] Old format: %s; buffer: %u frames\n", pSink->pszName,
+ PDMAudioPropsToString(&pSink->PCMProps, szTmp, sizeof(szTmp)), AudioMixBufSize(&pSink->MixBuf) ));
+ pSink->PCMProps = *pProps;
+ LogFlowFunc(("[%s] New format: %s; buffer: %u frames\n", pSink->pszName,
+ PDMAudioPropsToString(&pSink->PCMProps, szTmp, sizeof(szTmp)), cBufferFrames ));
+
+ /*
+ * Also update the sink's mixing buffer format.
+ */
+ AudioMixBufTerm(&pSink->MixBuf);
+
+ rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps, cBufferFrames);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Input sinks must init their (mostly dummy) peek state.
+ */
+ if (pSink->enmDir == PDMAUDIODIR_IN)
+ rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pSink->In.State, &pSink->PCMProps);
+ else
+ rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pSink->Out.State, &pSink->PCMProps);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Re-initialize the peek/write states as the frequency, channel count
+ * and other things may have changed now.
+ */
+ PAUDMIXSTREAM pMixStream;
+ if (pSink->enmDir == PDMAUDIODIR_IN)
+ {
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pMixStream->pStream->Cfg.Props);
+ /** @todo remember this. */
+ AssertLogRelRC(rc2);
+ }
+ }
+ else
+ {
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pMixStream->pStream->Cfg.Props);
+ /** @todo remember this. */
+ AssertLogRelRC(rc2);
+ }
+ }
+
+ /*
+ * Debug.
+ */
+ if (!(pSink->pParent->fFlags & AUDMIXER_FLAGS_DEBUG))
+ { /* likely */ }
+ else
+ {
+ AudioHlpFileClose(pSink->Dbg.pFile);
+
+ char szName[64];
+ RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName);
+ AudioHlpFileCreateAndOpen(&pSink->Dbg.pFile, NULL /*pszDir - use temp dir*/, szName,
+ 0 /*iInstance*/, &pSink->PCMProps);
+ }
+ }
+ else
+ LogFunc(("%s failed: %Rrc\n",
+ pSink->enmDir == PDMAUDIODIR_IN ? "AudioMixBufInitPeekState" : "AudioMixBufInitWriteState", rc));
+ }
+ else
+ LogFunc(("AudioMixBufInit failed: %Rrc\n", rc));
+ }
+
+ RTCritSectLeave(&pSink->CritSect);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Updates the combined volume (sink + mixer) of a mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink to update volume for (valid).
+ * @param pVolMaster The master (mixer) volume (valid).
+ */
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster)
+{
+ AssertPtr(pSink);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertPtr(pVolMaster);
+ LogFlowFunc(("[%s] Master fMuted=%RTbool auChannels=%.*Rhxs\n",
+ pSink->pszName, pVolMaster->fMuted, sizeof(pVolMaster->auChannels), pVolMaster->auChannels));
+
+ PDMAudioVolumeCombine(&pSink->VolumeCombined, &pSink->Volume, pVolMaster);
+
+ LogFlowFunc(("[%s] fMuted=%RTbool auChannels=%.*Rhxs -> fMuted=%RTbool auChannels=%.*Rhxs\n", pSink->pszName,
+ pSink->Volume.fMuted, sizeof(pSink->Volume.auChannels), pSink->Volume.auChannels,
+ pSink->VolumeCombined.fMuted, sizeof(pSink->VolumeCombined.auChannels), pSink->VolumeCombined.auChannels ));
+
+ AudioMixBufSetVolume(&pSink->MixBuf, &pSink->VolumeCombined);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the volume a mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink The sink to set volume for.
+ * @param pVol New volume settings.
+ */
+int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertReturn(pSink->uMagic == AUDMIXSINK_MAGIC, VERR_INVALID_MAGIC);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+ memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME));
+
+ LogRel2(("Audio Mixer: Setting volume of sink '%s' to fMuted=%RTbool auChannels=%.*Rhxs\n",
+ pSink->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels));
+
+ Assert(pSink->pParent);
+ if (pSink->pParent)
+ rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster);
+
+ RTCritSectLeave(&pSink->CritSect);
+
+ return rc;
+}
+
+
+/**
+ * Helper for audioMixerSinkUpdateInput that determins now many frames it can
+ * transfer from the drivers and into the sink's mixer buffer.
+ *
+ * This also updates the mixer stream status, which may involve stream re-inits.
+ *
+ * @returns Number of frames.
+ * @param pSink The sink.
+ * @param pcReadableStreams Where to return the number of readable streams.
+ */
+static uint32_t audioMixerSinkUpdateInputCalcFramesToTransfer(PAUDMIXSINK pSink, uint32_t *pcReadableStreams)
+{
+ uint32_t cFramesToRead = AudioMixBufFree(&pSink->MixBuf);
+ uint32_t cReadableStreams = 0;
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = audioMixerStreamUpdateStatus(pMixStream);
+ AssertRC(rc2);
+
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ)
+ {
+ PPDMIAUDIOCONNECTOR const pIConnector = pMixStream->pConn;
+ PPDMAUDIOSTREAM const pStream = pMixStream->pStream;
+ pIConnector->pfnStreamIterate(pIConnector, pStream);
+
+ uint32_t const cbReadable = pIConnector->pfnStreamGetReadable(pIConnector, pStream);
+ uint32_t cFrames = PDMAudioPropsBytesToFrames(&pStream->Cfg.Props, cbReadable);
+ pMixStream->cFramesLastAvail = cFrames;
+ if (PDMAudioPropsHz(&pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props))
+ { /* likely */ }
+ else
+ {
+ cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pStream->Cfg.Props);
+ cFrames = cFrames > 2 ? cFrames - 2 : 0; /* rounding safety fudge */
+ }
+ if (cFramesToRead > cFrames && !pMixStream->fUnreliable)
+ {
+ Log4Func(("%s: cFramesToRead %u -> %u; %s (%u bytes readable)\n",
+ pSink->pszName, cFramesToRead, cFrames, pMixStream->pszName, cbReadable));
+ cFramesToRead = cFrames;
+ }
+ cReadableStreams++;
+ }
+ }
+
+ *pcReadableStreams = cReadableStreams;
+ return cFramesToRead;
+}
+
+
+/**
+ * Updates an input mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink Mixer sink to update.
+ * @param cbDmaBuf The number of bytes in the DMA buffer. For detecting
+ * underruns. Zero if we don't know.
+ * @param cbDmaPeriod The minimum number of bytes required for reliable DMA
+ * operation. Zero if we don't know.
+ */
+static int audioMixerSinkUpdateInput(PAUDMIXSINK pSink, uint32_t cbDmaBuf, uint32_t cbDmaPeriod)
+{
+ PAUDMIXSTREAM pMixStream;
+ Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_MIXBUF)); /* (can't drain input sink) */
+
+ /*
+ * Iterate, update status and check each mixing sink stream for how much
+ * we can transfer.
+ *
+ * We're currently using the minimum size of all streams, however this
+ * isn't a smart approach as it means one disfunctional stream can block
+ * working ones. So, if we end up with zero frames and a full mixer
+ * buffer we'll disregard the stream that accept the smallest amount and
+ * try again.
+ */
+ uint32_t cReadableStreams = 0;
+ uint32_t cFramesToXfer = audioMixerSinkUpdateInputCalcFramesToTransfer(pSink, &cReadableStreams);
+ if ( cFramesToXfer != 0
+ || cReadableStreams <= 1
+ || cbDmaPeriod == 0 /* Insufficient info to decide. The update function will call us again, at least for HDA. */
+ || cbDmaBuf + PDMAudioPropsFramesToBytes(&pSink->PCMProps, AudioMixBufUsed(&pSink->MixBuf)) >= cbDmaPeriod)
+ Log3Func(("%s: cFreeFrames=%#x cFramesToXfer=%#x cReadableStreams=%#x\n", pSink->pszName,
+ AudioMixBufFree(&pSink->MixBuf), cFramesToXfer, cReadableStreams));
+ else
+ {
+ Log3Func(("%s: MixBuf is underrunning but one or more streams only provides zero frames. Try disregarding those...\n", pSink->pszName));
+ uint32_t cReliableStreams = 0;
+ uint32_t cMarkedUnreliable = 0;
+ PAUDMIXSTREAM pMixStreamMin = NULL;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ)
+ {
+ if (!pMixStream->fUnreliable)
+ {
+ if (pMixStream->cFramesLastAvail == 0)
+ {
+ cMarkedUnreliable++;
+ pMixStream->fUnreliable = true;
+ Log3Func(("%s: Marked '%s' as unreliable.\n", pSink->pszName, pMixStream->pszName));
+ pMixStreamMin = pMixStream;
+ }
+ else
+ {
+ if (!pMixStreamMin || pMixStream->cFramesLastAvail < pMixStreamMin->cFramesLastAvail)
+ pMixStreamMin = pMixStream;
+ cReliableStreams++;
+ }
+ }
+ }
+ }
+
+ if (cMarkedUnreliable == 0 && cReliableStreams > 1 && pMixStreamMin != NULL)
+ {
+ cReliableStreams--;
+ cMarkedUnreliable++;
+ pMixStreamMin->fUnreliable = true;
+ Log3Func(("%s: Marked '%s' as unreliable (%u frames).\n",
+ pSink->pszName, pMixStreamMin->pszName, pMixStreamMin->cFramesLastAvail));
+ }
+
+ if (cMarkedUnreliable > 0)
+ {
+ cReadableStreams = 0;
+ cFramesToXfer = audioMixerSinkUpdateInputCalcFramesToTransfer(pSink, &cReadableStreams);
+ }
+
+ Log3Func(("%s: cFreeFrames=%#x cFramesToXfer=%#x cReadableStreams=%#x cMarkedUnreliable=%#x cReliableStreams=%#x\n",
+ pSink->pszName, AudioMixBufFree(&pSink->MixBuf), cFramesToXfer,
+ cReadableStreams, cMarkedUnreliable, cReliableStreams));
+ }
+
+ if (cReadableStreams > 0)
+ {
+ if (cFramesToXfer > 0)
+ {
+/*#define ELECTRIC_INPUT_BUFFER*/ /* if buffer code is misbehaving, enable this to catch overflows. */
+#ifndef ELECTRIC_INPUT_BUFFER
+ union
+ {
+ uint8_t ab[8192];
+ uint64_t au64[8192 / sizeof(uint64_t)]; /* Use uint64_t to ensure good alignment. */
+ } Buf;
+ void * const pvBuf = &Buf;
+ uint32_t const cbBuf = sizeof(Buf);
+#else
+ uint32_t const cbBuf = 0x2000 - 16;
+ void * const pvBuf = RTMemEfAlloc(cbBuf, RTMEM_TAG, RT_SRC_POS);
+#endif
+
+ /*
+ * For each of the enabled streams, read cFramesToXfer frames worth
+ * of samples from them and merge that into the mixing buffer.
+ */
+ bool fAssign = true;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_READ)
+ {
+ PPDMIAUDIOCONNECTOR const pIConnector = pMixStream->pConn;
+ PPDMAUDIOSTREAM const pStream = pMixStream->pStream;
+
+ /* Calculate how many bytes we should read from this stream. */
+ bool const fResampleSrc = PDMAudioPropsHz(&pStream->Cfg.Props) != PDMAudioPropsHz(&pSink->MixBuf.Props);
+ uint32_t const cbSrcToXfer = !fResampleSrc
+ ? PDMAudioPropsFramesToBytes(&pStream->Cfg.Props, cFramesToXfer)
+ : PDMAudioPropsFramesToBytes(&pStream->Cfg.Props, /** @todo check rounding errors here... */
+ cFramesToXfer * PDMAudioPropsHz(&pSink->MixBuf.Props)
+ / PDMAudioPropsHz(&pStream->Cfg.Props));
+
+ /* Do the reading. */
+ uint32_t offSrc = 0;
+ uint32_t offDstFrame = 0;
+ do
+ {
+ /*
+ * Read a chunk from the backend.
+ */
+ uint32_t const cbSrcToRead = RT_MIN(cbBuf, cbSrcToXfer - offSrc);
+ uint32_t cbSrcRead = 0;
+ if (cbSrcToRead > 0)
+ {
+ int rc2 = pIConnector->pfnStreamCapture(pIConnector, pStream, pvBuf, cbSrcToRead, &cbSrcRead);
+ Log3Func(("%s: %#x L %#x => %#x bytes; rc2=%Rrc %s\n",
+ pSink->pszName, offSrc, cbSrcToRead, cbSrcRead, rc2, pMixStream->pszName));
+
+ if (RT_SUCCESS(rc2))
+ AssertLogRelMsg(cbSrcRead == cbSrcToRead || pMixStream->fUnreliable,
+ ("cbSrcRead=%#x cbSrcToRead=%#x - (sink '%s')\n",
+ cbSrcRead, cbSrcToRead, pSink->pszName));
+ else if (rc2 == VERR_AUDIO_STREAM_NOT_READY)
+ {
+ LogRel2(("Audio Mixer: '%s' (sink '%s'): Stream not ready - skipping.\n",
+ pMixStream->pszName, pSink->pszName)); /* must've changed status, stop processing */
+ break;
+ }
+ else
+ {
+ Assert(rc2 != VERR_BUFFER_OVERFLOW);
+ LogRel2(("Audio Mixer: Reading from mixer stream '%s' (sink '%s') failed, rc=%Rrc\n",
+ pMixStream->pszName, pSink->pszName, rc2));
+ break;
+ }
+ offSrc += cbSrcRead;
+ }
+ else
+ Assert(fResampleSrc); /** @todo test this case */
+
+ /*
+ * Assign or blend it into the mixer buffer.
+ */
+ uint32_t cFramesDstTransferred = 0;
+ if (fAssign)
+ {
+ /** @todo could complicate this by detecting silence here too and stay in
+ * assign mode till we get a stream with non-silence... */
+ AudioMixBufWrite(&pSink->MixBuf, &pMixStream->WriteState, pvBuf, cbSrcRead,
+ offDstFrame, cFramesToXfer - offDstFrame, &cFramesDstTransferred);
+ }
+ /* We don't need to blend silence buffers. For simplicity, always blend
+ when we're resampling (for rounding). */
+ else if (fResampleSrc || !PDMAudioPropsIsBufferSilence(&pStream->Cfg.Props, pvBuf, cbSrcRead))
+ {
+ AudioMixBufBlend(&pSink->MixBuf, &pMixStream->WriteState, pvBuf, cbSrcRead,
+ offDstFrame, cFramesToXfer - offDstFrame, &cFramesDstTransferred);
+ }
+ else
+ {
+ cFramesDstTransferred = PDMAudioPropsBytesToFrames(&pStream->Cfg.Props, cbSrcRead);
+ AudioMixBufBlendGap(&pSink->MixBuf, &pMixStream->WriteState, cFramesDstTransferred);
+ }
+ AssertBreak(cFramesDstTransferred > 0);
+
+ /* Advance. */
+ offDstFrame += cFramesDstTransferred;
+ } while (offDstFrame < cFramesToXfer);
+
+ /*
+ * In case the first stream is misbehaving, make sure we written the entire area.
+ */
+ if (offDstFrame >= cFramesToXfer)
+ { /* likely */ }
+ else if (fAssign)
+ AudioMixBufSilence(&pSink->MixBuf, &pMixStream->WriteState, offDstFrame, cFramesToXfer - offDstFrame);
+ else
+ AudioMixBufBlendGap(&pSink->MixBuf, &pMixStream->WriteState, cFramesToXfer - offDstFrame);
+ fAssign = false;
+ }
+ }
+
+ /*
+ * Commit the buffer area we've written and blended into.
+ */
+ AudioMixBufCommit(&pSink->MixBuf, cFramesToXfer);
+
+#ifdef ELECTRIC_INPUT_BUFFER
+ RTMemEfFree(pvBuf, RT_SRC_POS);
+#endif
+ }
+
+ /*
+ * Set the dirty flag for what it's worth.
+ */
+ pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
+ }
+ else
+ {
+ /*
+ * No readable stream. Clear the dirty flag if empty (pointless flag).
+ */
+ if (!AudioMixBufUsed(&pSink->MixBuf))
+ pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
+ }
+
+ /* Update last updated timestamp. */
+ pSink->tsLastUpdatedMs = RTTimeMilliTS();
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Helper for audioMixerSinkUpdateOutput that determins now many frames it
+ * can transfer from the sink's mixer buffer and to the drivers.
+ *
+ * This also updates the mixer stream status, which may involve stream re-inits.
+ *
+ * @returns Number of frames.
+ * @param pSink The sink.
+ * @param pcWritableStreams Where to return the number of writable streams.
+ */
+static uint32_t audioMixerSinkUpdateOutputCalcFramesToRead(PAUDMIXSINK pSink, uint32_t *pcWritableStreams)
+{
+ uint32_t cFramesToRead = AudioMixBufUsed(&pSink->MixBuf); /* (to read from the mixing buffer) */
+ uint32_t cWritableStreams = 0;
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+#if 0 /** @todo this conceptually makes sense, but may mess up the pending-disable logic ... */
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)
+ pConn->pfnStreamIterate(pConn, pStream);
+#endif
+
+ int rc2 = audioMixerStreamUpdateStatus(pMixStream);
+ AssertRC(rc2);
+
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE)
+ {
+ uint32_t const cbWritable = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream);
+ uint32_t cFrames = PDMAudioPropsBytesToFrames(&pMixStream->pStream->Cfg.Props, cbWritable);
+ pMixStream->cFramesLastAvail = cFrames;
+ if (PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props) == PDMAudioPropsHz(&pSink->MixBuf.Props))
+ { /* likely */ }
+ else
+ {
+ cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pMixStream->pStream->Cfg.Props);
+ cFrames = cFrames > 2 ? cFrames - 2 : 0; /* rounding safety fudge */
+ }
+ if (cFramesToRead > cFrames && !pMixStream->fUnreliable)
+ {
+ Log4Func(("%s: cFramesToRead %u -> %u; %s (%u bytes writable)\n",
+ pSink->pszName, cFramesToRead, cFrames, pMixStream->pszName, cbWritable));
+ cFramesToRead = cFrames;
+ }
+ cWritableStreams++;
+ }
+ }
+
+ *pcWritableStreams = cWritableStreams;
+ return cFramesToRead;
+}
+
+
+/**
+ * Updates an output mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink Mixer sink to update.
+ */
+static int audioMixerSinkUpdateOutput(PAUDMIXSINK pSink)
+{
+ PAUDMIXSTREAM pMixStream;
+ Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_MIXBUF) || AudioMixBufUsed(&pSink->MixBuf) == 0);
+
+ /*
+ * Update each mixing sink stream's status and check how much we can
+ * write into them.
+ *
+ * We're currently using the minimum size of all streams, however this
+ * isn't a smart approach as it means one disfunctional stream can block
+ * working ones. So, if we end up with zero frames and a full mixer
+ * buffer we'll disregard the stream that accept the smallest amount and
+ * try again.
+ */
+ uint32_t cWritableStreams = 0;
+ uint32_t cFramesToRead = audioMixerSinkUpdateOutputCalcFramesToRead(pSink, &cWritableStreams);
+ if ( cFramesToRead != 0
+ || cWritableStreams <= 1
+ || AudioMixBufFree(&pSink->MixBuf) > 2)
+ Log3Func(("%s: cLiveFrames=%#x cFramesToRead=%#x cWritableStreams=%#x\n", pSink->pszName,
+ AudioMixBufUsed(&pSink->MixBuf), cFramesToRead, cWritableStreams));
+ else
+ {
+ Log3Func(("%s: MixBuf is full but one or more streams only want zero frames. Try disregarding those...\n", pSink->pszName));
+ uint32_t cReliableStreams = 0;
+ uint32_t cMarkedUnreliable = 0;
+ PAUDMIXSTREAM pMixStreamMin = NULL;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE)
+ {
+ if (!pMixStream->fUnreliable)
+ {
+ if (pMixStream->cFramesLastAvail == 0)
+ {
+ cMarkedUnreliable++;
+ pMixStream->fUnreliable = true;
+ Log3Func(("%s: Marked '%s' as unreliable.\n", pSink->pszName, pMixStream->pszName));
+ pMixStreamMin = pMixStream;
+ }
+ else
+ {
+ if (!pMixStreamMin || pMixStream->cFramesLastAvail < pMixStreamMin->cFramesLastAvail)
+ pMixStreamMin = pMixStream;
+ cReliableStreams++;
+ }
+ }
+ }
+ }
+
+ if (cMarkedUnreliable == 0 && cReliableStreams > 1 && pMixStreamMin != NULL)
+ {
+ cReliableStreams--;
+ cMarkedUnreliable++;
+ pMixStreamMin->fUnreliable = true;
+ Log3Func(("%s: Marked '%s' as unreliable (%u frames).\n",
+ pSink->pszName, pMixStreamMin->pszName, pMixStreamMin->cFramesLastAvail));
+ }
+
+ if (cMarkedUnreliable > 0)
+ {
+ cWritableStreams = 0;
+ cFramesToRead = audioMixerSinkUpdateOutputCalcFramesToRead(pSink, &cWritableStreams);
+ }
+
+ Log3Func(("%s: cLiveFrames=%#x cFramesToRead=%#x cWritableStreams=%#x cMarkedUnreliable=%#x cReliableStreams=%#x\n",
+ pSink->pszName, AudioMixBufUsed(&pSink->MixBuf), cFramesToRead,
+ cWritableStreams, cMarkedUnreliable, cReliableStreams));
+ }
+
+ if (cWritableStreams > 0)
+ {
+ if (cFramesToRead > 0)
+ {
+ /*
+ * For each of the enabled streams, convert cFramesToRead frames from
+ * the mixing buffer and write that to the downstream driver.
+ */
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE)
+ {
+ uint32_t offSrcFrame = 0;
+ do
+ {
+ /* Convert a chunk from the mixer buffer. */
+/*#define ELECTRIC_PEEK_BUFFER*/ /* if buffer code is misbehaving, enable this to catch overflows. */
+#ifndef ELECTRIC_PEEK_BUFFER
+ union
+ {
+ uint8_t ab[8192];
+ uint64_t au64[8192 / sizeof(uint64_t)]; /* Use uint64_t to ensure good alignment. */
+ } Buf;
+ void * const pvBuf = &Buf;
+ uint32_t const cbBuf = sizeof(Buf);
+#else
+ uint32_t const cbBuf = 0x2000 - 16;
+ void * const pvBuf = RTMemEfAlloc(cbBuf, RTMEM_TAG, RT_SRC_POS);
+#endif
+ uint32_t cbDstPeeked = cbBuf;
+ uint32_t cSrcFramesPeeked = cFramesToRead - offSrcFrame;
+ AudioMixBufPeek(&pSink->MixBuf, offSrcFrame, cSrcFramesPeeked, &cSrcFramesPeeked,
+ &pMixStream->PeekState, pvBuf, cbBuf, &cbDstPeeked);
+ offSrcFrame += cSrcFramesPeeked;
+
+ /* Write it to the backend. Since've checked that there is buffer
+ space available, this should always write the whole buffer unless
+ it's an unreliable stream. */
+ uint32_t cbDstWritten = 0;
+ int rc2 = pMixStream->pConn->pfnStreamPlay(pMixStream->pConn, pMixStream->pStream,
+ pvBuf, cbDstPeeked, &cbDstWritten);
+ Log3Func(("%s: %#x L %#x => %#x bytes; wrote %#x rc2=%Rrc %s\n", pSink->pszName, offSrcFrame,
+ cSrcFramesPeeked - cSrcFramesPeeked, cbDstPeeked, cbDstWritten, rc2, pMixStream->pszName));
+#ifdef ELECTRIC_PEEK_BUFFER
+ RTMemEfFree(pvBuf, RT_SRC_POS);
+#endif
+ if (RT_SUCCESS(rc2))
+ AssertLogRelMsg(cbDstWritten == cbDstPeeked || pMixStream->fUnreliable,
+ ("cbDstWritten=%#x cbDstPeeked=%#x - (sink '%s')\n",
+ cbDstWritten, cbDstPeeked, pSink->pszName));
+ else if (rc2 == VERR_AUDIO_STREAM_NOT_READY)
+ {
+ LogRel2(("Audio Mixer: '%s' (sink '%s'): Stream not ready - skipping.\n",
+ pMixStream->pszName, pSink->pszName));
+ break; /* must've changed status, stop processing */
+ }
+ else
+ {
+ Assert(rc2 != VERR_BUFFER_OVERFLOW);
+ LogRel2(("Audio Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n",
+ pMixStream->pszName, pSink->pszName, rc2));
+ break;
+ }
+ } while (offSrcFrame < cFramesToRead);
+ }
+ }
+
+ AudioMixBufAdvance(&pSink->MixBuf, cFramesToRead);
+ }
+
+ /*
+ * Update the dirty flag for what it's worth.
+ */
+ if (AudioMixBufUsed(&pSink->MixBuf) > 0)
+ pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
+ else
+ pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
+ }
+ else
+ {
+ /*
+ * If no writable streams, just drop the mixer buffer content.
+ */
+ AudioMixBufDrop(&pSink->MixBuf);
+ pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
+ }
+
+ /*
+ * Iterate buffers.
+ */
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)
+ pMixStream->pConn->pfnStreamIterate(pMixStream->pConn, pMixStream->pStream);
+ }
+
+ /* Update last updated timestamp. */
+ uint64_t const nsNow = RTTimeNanoTS();
+ pSink->tsLastUpdatedMs = nsNow / RT_NS_1MS;
+
+ /*
+ * Deal with pending disable.
+ * We reset the sink when all streams have been disabled.
+ */
+ if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+ { /* likely, till we get to the end */ }
+ else if (nsNow <= pSink->nsDrainDeadline)
+ {
+ /* Have we drained the mixbuf now? If so, update status and send drain
+ command to streams. (As mentioned elsewhere we don't want to confuse
+ driver code by sending drain command while there is still data to write.) */
+ Assert((pSink->fStatus & AUDMIXSINK_STS_DIRTY) == (AudioMixBufUsed(&pSink->MixBuf) > 0 ? AUDMIXSINK_STS_DIRTY : 0));
+ if ((pSink->fStatus & (AUDMIXSINK_STS_DRAINED_MIXBUF | AUDMIXSINK_STS_DIRTY)) == 0)
+ {
+ LogFunc(("Sink '%s': Setting AUDMIXSINK_STS_DRAINED_MIXBUF and sending drain command to streams (after %RU64 ns).\n",
+ pSink->pszName, nsNow - pSink->nsDrainStarted));
+ pSink->fStatus |= AUDMIXSINK_STS_DRAINED_MIXBUF;
+
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DRAIN);
+ }
+ }
+
+ /* Check if all streams has stopped, and if so we stop the sink. */
+ uint32_t const cStreams = pSink->cStreams;
+ uint32_t cStreamsDisabled = pSink->cStreams;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)
+ {
+ PDMAUDIOSTREAMSTATE const enmState = pMixStream->pConn->pfnStreamGetState(pMixStream->pConn, pMixStream->pStream);
+ if (enmState >= PDMAUDIOSTREAMSTATE_ENABLED)
+ cStreamsDisabled--;
+ }
+ }
+
+ if (cStreamsDisabled != cStreams)
+ Log3Func(("Sink '%s': %u out of %u streams disabled (after %RU64 ns).\n",
+ pSink->pszName, cStreamsDisabled, cStreams, nsNow - pSink->nsDrainStarted));
+ else
+ {
+ LogFunc(("Sink '%s': All %u streams disabled. Drain done after %RU64 ns.\n",
+ pSink->pszName, cStreamsDisabled, nsNow - pSink->nsDrainStarted));
+ audioMixerSinkResetInternal(pSink); /* clears the status */
+ }
+ }
+ else
+ {
+ /* Draining timed out. Just do an instant stop. */
+ LogFunc(("Sink '%s': pending disable timed out after %RU64 ns!\n", pSink->pszName, nsNow - pSink->nsDrainStarted));
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ }
+ audioMixerSinkResetInternal(pSink); /* clears the status */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Updates (invalidates) a mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pSink Mixer sink to update.
+ * @param cbDmaUsed The DMA buffer fill for input stream, ignored for
+ * output sinks.
+ * @param cbDmaPeriod The DMA period in bytes for input stream, ignored
+ * for output sinks.
+ */
+int AudioMixerSinkUpdate(PAUDMIXSINK pSink, uint32_t cbDmaUsed, uint32_t cbDmaPeriod)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+#ifdef LOG_ENABLED
+ char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] fStatus=%s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+
+ /* Only process running sinks. */
+ if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ {
+ /* Do separate processing for input and output sinks. */
+ if (pSink->enmDir == PDMAUDIODIR_OUT)
+ rc = audioMixerSinkUpdateOutput(pSink);
+ else if (pSink->enmDir == PDMAUDIODIR_IN)
+ rc = audioMixerSinkUpdateInput(pSink, cbDmaUsed, cbDmaPeriod);
+ else
+ AssertFailedStmt(rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ rc = VINF_SUCCESS; /* disabled */
+
+ RTCritSectLeave(&pSink->CritSect);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTHREAD, Audio Mixer Sink asynchronous I/O thread}
+ */
+static DECLCALLBACK(int) audioMixerSinkAsyncIoThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PAUDMIXSINK pSink = (PAUDMIXSINK)pvUser;
+ AssertPtr(pSink);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ RT_NOREF(hThreadSelf);
+
+ /*
+ * The run loop.
+ */
+ LogFlowFunc(("%s: Entering run loop...\n", pSink->pszName));
+ while (!pSink->AIO.fShutdown)
+ {
+ RTMSINTERVAL cMsSleep = RT_INDEFINITE_WAIT;
+
+ RTCritSectEnter(&pSink->CritSect);
+ if (pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_DRAINING))
+ {
+ /*
+ * Before doing jobs, always update input sinks.
+ */
+ if (pSink->enmDir == PDMAUDIODIR_IN)
+ audioMixerSinkUpdateInput(pSink, 0 /*cbDmaUsed*/, 0 /*cbDmaPeriod*/);
+
+ /*
+ * Do the device specific updating.
+ */
+ uintptr_t const cUpdateJobs = RT_MIN(pSink->AIO.cUpdateJobs, RT_ELEMENTS(pSink->AIO.aUpdateJobs));
+ for (uintptr_t iJob = 0; iJob < cUpdateJobs; iJob++)
+ pSink->AIO.aUpdateJobs[iJob].pfnUpdate(pSink->AIO.pDevIns, pSink, pSink->AIO.aUpdateJobs[iJob].pvUser);
+
+ /*
+ * Update output sinks after the updating.
+ */
+ if (pSink->enmDir == PDMAUDIODIR_OUT)
+ audioMixerSinkUpdateOutput(pSink);
+
+ /*
+ * If we're in draining mode, we use the smallest typical interval of the
+ * jobs for the next wait as we're unlikly to be woken up again by any
+ * DMA timer as it has normally stopped running at this point.
+ */
+ if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+ { /* likely */ }
+ else
+ {
+ /** @todo Also do some kind of timeout here and do a forced stream disable w/o
+ * any draining if we exceed it. */
+ cMsSleep = pSink->AIO.cMsMinTypicalInterval;
+ }
+
+ }
+ RTCritSectLeave(&pSink->CritSect);
+
+ /*
+ * Now block till we're signalled or
+ */
+ if (!pSink->AIO.fShutdown)
+ {
+ int rc = RTSemEventWait(pSink->AIO.hEvent, cMsSleep);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%s: RTSemEventWait -> %Rrc\n", pSink->pszName, rc), rc);
+ }
+ }
+
+ LogFlowFunc(("%s: returnining normally.\n", pSink->pszName));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds an AIO update job to the sink.
+ *
+ * @returns VBox status code.
+ * @retval VERR_ALREADY_EXISTS if already registered job with same @a pvUser
+ * and @a pfnUpdate.
+ *
+ * @param pSink The mixer sink to remove the AIO job from.
+ * @param pfnUpdate The update callback for the job.
+ * @param pvUser The user parameter to pass to @a pfnUpdate. This should
+ * identify the job unique together with @a pfnUpdate.
+ * @param cMsTypicalInterval A typical interval between jobs in milliseconds.
+ * This is used when draining.
+ */
+int AudioMixerSinkAddUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser, uint32_t cMsTypicalInterval)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Check that the job hasn't already been added.
+ */
+ uintptr_t const iEnd = pSink->AIO.cUpdateJobs;
+ for (uintptr_t i = 0; i < iEnd; i++)
+ AssertReturnStmt( pvUser != pSink->AIO.aUpdateJobs[i].pvUser
+ || pfnUpdate != pSink->AIO.aUpdateJobs[i].pfnUpdate,
+ RTCritSectLeave(&pSink->CritSect),
+ VERR_ALREADY_EXISTS);
+
+ AssertReturnStmt(iEnd < RT_ELEMENTS(pSink->AIO.aUpdateJobs),
+ RTCritSectLeave(&pSink->CritSect),
+ VERR_ALREADY_EXISTS);
+
+ /*
+ * Create the thread if not already running or if it stopped.
+ */
+/** @todo move this to the sink "enable" code */
+ if (pSink->AIO.hThread != NIL_RTTHREAD)
+ {
+ int rcThread = VINF_SUCCESS;
+ rc = RTThreadWait(pSink->AIO.hThread, 0, &rcThread);
+ if (RT_FAILURE_NP(rc))
+ { /* likely */ }
+ else
+ {
+ LogRel(("Audio: AIO thread for '%s' died? rcThread=%Rrc\n", pSink->pszName, rcThread));
+ pSink->AIO.hThread = NIL_RTTHREAD;
+ }
+ }
+ if (pSink->AIO.hThread == NIL_RTTHREAD)
+ {
+ LogFlowFunc(("%s: Starting AIO thread...\n", pSink->pszName));
+ if (pSink->AIO.hEvent == NIL_RTSEMEVENT)
+ {
+ rc = RTSemEventCreate(&pSink->AIO.hEvent);
+ AssertRCReturnStmt(rc, RTCritSectLeave(&pSink->CritSect), rc);
+ }
+ static uint32_t volatile s_idxThread = 0;
+ uint32_t idxThread = ASMAtomicIncU32(&s_idxThread);
+ rc = RTThreadCreateF(&pSink->AIO.hThread, audioMixerSinkAsyncIoThread, pSink, 0 /*cbStack*/, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "MixAIO-%u", idxThread);
+ AssertRCReturnStmt(rc, RTCritSectLeave(&pSink->CritSect), rc);
+ }
+
+ /*
+ * Finally, actually add the job.
+ */
+ pSink->AIO.aUpdateJobs[iEnd].pfnUpdate = pfnUpdate;
+ pSink->AIO.aUpdateJobs[iEnd].pvUser = pvUser;
+ pSink->AIO.aUpdateJobs[iEnd].cMsTypicalInterval = cMsTypicalInterval;
+ pSink->AIO.cUpdateJobs = (uint8_t)(iEnd + 1);
+ if (cMsTypicalInterval < pSink->AIO.cMsMinTypicalInterval)
+ pSink->AIO.cMsMinTypicalInterval = cMsTypicalInterval;
+ LogFlowFunc(("%s: [#%zu]: Added pfnUpdate=%p pvUser=%p typically every %u ms (min %u ms)\n",
+ pSink->pszName, iEnd, pfnUpdate, pvUser, cMsTypicalInterval, pSink->AIO.cMsMinTypicalInterval));
+
+ RTCritSectLeave(&pSink->CritSect);
+ return VINF_SUCCESS;
+
+}
+
+
+/**
+ * Removes an update job previously registered via AudioMixerSinkAddUpdateJob().
+ *
+ * @returns VBox status code.
+ * @retval VERR_NOT_FOUND if not found.
+ *
+ * @param pSink The mixer sink to remove the AIO job from.
+ * @param pfnUpdate The update callback of the job.
+ * @param pvUser The user parameter identifying the job.
+ */
+int AudioMixerSinkRemoveUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = VERR_NOT_FOUND;
+ for (uintptr_t iJob = 0; iJob < pSink->AIO.cUpdateJobs; iJob++)
+ if ( pvUser == pSink->AIO.aUpdateJobs[iJob].pvUser
+ && pfnUpdate == pSink->AIO.aUpdateJobs[iJob].pfnUpdate)
+ {
+ pSink->AIO.cUpdateJobs--;
+ if (iJob != pSink->AIO.cUpdateJobs)
+ memmove(&pSink->AIO.aUpdateJobs[iJob], &pSink->AIO.aUpdateJobs[iJob + 1],
+ (pSink->AIO.cUpdateJobs - iJob) * sizeof(pSink->AIO.aUpdateJobs[0]));
+ LogFlowFunc(("%s: [#%zu]: Removed pfnUpdate=%p pvUser=%p => cUpdateJobs=%u\n",
+ pSink->pszName, iJob, pfnUpdate, pvUser, pSink->AIO.cUpdateJobs));
+ rc = VINF_SUCCESS;
+ break;
+ }
+ AssertRC(rc);
+
+ /* Recalc the minimum sleep interval (do it always). */
+ pSink->AIO.cMsMinTypicalInterval = RT_MS_1SEC / 2;
+ for (uintptr_t iJob = 0; iJob < pSink->AIO.cUpdateJobs; iJob++)
+ if (pSink->AIO.aUpdateJobs[iJob].cMsTypicalInterval < pSink->AIO.cMsMinTypicalInterval)
+ pSink->AIO.cMsMinTypicalInterval = pSink->AIO.aUpdateJobs[iJob].cMsTypicalInterval;
+
+
+ RTCritSectLeave(&pSink->CritSect);
+ return rc;
+}
+
+
+/**
+ * Writes data to a mixer output sink.
+ *
+ * @param pSink The sink to write data to.
+ * @param pvBuf Buffer containing the audio data to write.
+ * @param cbBuf How many bytes to write.
+ * @param pcbWritten Number of bytes written.
+ *
+ * @todo merge with caller.
+ */
+static void audioMixerSinkWrite(PAUDMIXSINK pSink, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ uint32_t cFrames = AudioMixBufFree(&pSink->MixBuf);
+ uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFrames);
+ cbToWrite = RT_MIN(cbToWrite, cbBuf);
+ AudioMixBufWrite(&pSink->MixBuf, &pSink->Out.State, pvBuf, cbToWrite, 0 /*offDstFrame*/, cFrames, &cFrames);
+ Assert(cbToWrite == PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFrames));
+ AudioMixBufCommit(&pSink->MixBuf, cFrames);
+ *pcbWritten = cbToWrite;
+
+ /* Update the sink's last written time stamp. */
+ pSink->tsLastReadWrittenNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] cbBuf=%#x -> cbWritten=%#x\n", pSink->pszName, cbBuf, cbToWrite));
+}
+
+
+/**
+ * Transfer data from the device's DMA buffer and into the sink.
+ *
+ * The caller is already holding the mixer sink's critical section, either by
+ * way of being the AIO thread doing update jobs or by explicit locking calls.
+ *
+ * @returns The new stream offset.
+ * @param pSink The mixer sink to transfer samples to.
+ * @param pCircBuf The internal DMA buffer to move samples from.
+ * @param offStream The stream current offset (logging, dtrace, return).
+ * @param idStream Device specific audio stream identifier (logging, dtrace).
+ * @param pDbgFile Debug file, NULL if disabled.
+ */
+uint64_t AudioMixerSinkTransferFromCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream,
+ uint32_t idStream, PAUDIOHLPFILE pDbgFile)
+{
+ /*
+ * Sanity.
+ */
+ AssertReturn(pSink, offStream);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertReturn(pCircBuf, offStream);
+ Assert(RTCritSectIsOwner(&pSink->CritSect));
+ Assert(pSink->enmDir == PDMAUDIODIR_OUT);
+ RT_NOREF(idStream);
+
+ /*
+ * Figure how much that we can push down.
+ */
+ uint32_t const cbSinkWritable = AudioMixerSinkGetWritable(pSink);
+ uint32_t const cbCircBufReadable = (uint32_t)RTCircBufUsed(pCircBuf);
+ uint32_t cbToTransfer = RT_MIN(cbCircBufReadable, cbSinkWritable);
+ /* Make sure that we always align the number of bytes when reading to the stream's PCM properties. */
+ uint32_t const cbToTransfer2 = cbToTransfer = PDMAudioPropsFloorBytesToFrame(&pSink->PCMProps, cbToTransfer);
+
+ Log3Func(("idStream=%u: cbSinkWritable=%#RX32 cbCircBufReadable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n",
+ idStream, cbSinkWritable, cbCircBufReadable, cbToTransfer, offStream));
+ AssertMsg(!(pSink->fStatus & AUDMIXSINK_STS_DRAINING) || cbCircBufReadable == pSink->cbDmaLeftToDrain,
+ ("cbCircBufReadable=%#x cbDmaLeftToDrain=%#x\n", cbCircBufReadable, pSink->cbDmaLeftToDrain));
+
+ /*
+ * Do the pushing.
+ */
+ while (cbToTransfer > 0)
+ {
+ void /*const*/ *pvSrcBuf;
+ size_t cbSrcBuf;
+ RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvSrcBuf, &cbSrcBuf);
+
+ uint32_t cbWritten = 0;
+ audioMixerSinkWrite(pSink, pvSrcBuf, (uint32_t)cbSrcBuf, &cbWritten);
+ Assert(cbWritten <= cbSrcBuf);
+
+ Log2Func(("idStream=%u: %#RX32/%#zx bytes read @%#RX64\n", idStream, cbWritten, cbSrcBuf, offStream));
+#ifdef VBOX_WITH_DTRACE
+ VBOXDD_AUDIO_MIXER_SINK_AIO_OUT(idStream, cbWritten, offStream);
+#endif
+ offStream += cbWritten;
+
+ if (!pDbgFile)
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pDbgFile, pvSrcBuf, cbSrcBuf);
+
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbWritten);
+
+ /* advance */
+ cbToTransfer -= cbWritten;
+ }
+
+ /*
+ * Advance drain status.
+ */
+ if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+ { /* likely for most of the playback time ... */ }
+ else if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_DMA))
+ {
+ if (cbToTransfer2 >= pSink->cbDmaLeftToDrain)
+ {
+ Assert(cbToTransfer2 == pSink->cbDmaLeftToDrain);
+ Log3Func(("idStream=%u/'%s': Setting AUDMIXSINK_STS_DRAINED_DMA.\n", idStream, pSink->pszName));
+ pSink->cbDmaLeftToDrain = 0;
+ pSink->fStatus |= AUDMIXSINK_STS_DRAINED_DMA;
+ }
+ else
+ {
+ pSink->cbDmaLeftToDrain -= cbToTransfer2;
+ Log3Func(("idStream=%u/'%s': still %#x bytes left in the DMA buffer\n",
+ idStream, pSink->pszName, pSink->cbDmaLeftToDrain));
+ }
+ }
+ else
+ Assert(cbToTransfer2 == 0);
+
+ return offStream;
+}
+
+
+/**
+ * Transfer data to the device's DMA buffer from the sink.
+ *
+ * The caller is already holding the mixer sink's critical section, either by
+ * way of being the AIO thread doing update jobs or by explicit locking calls.
+ *
+ * @returns The new stream offset.
+ * @param pSink The mixer sink to transfer samples from.
+ * @param pCircBuf The internal DMA buffer to move samples to.
+ * @param offStream The stream current offset (logging, dtrace, return).
+ * @param idStream Device specific audio stream identifier (logging, dtrace).
+ * @param pDbgFile Debug file, NULL if disabled.
+ */
+uint64_t AudioMixerSinkTransferToCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream,
+ uint32_t idStream, PAUDIOHLPFILE pDbgFile)
+{
+ /*
+ * Sanity.
+ */
+ AssertReturn(pSink, offStream);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertReturn(pCircBuf, offStream);
+ Assert(RTCritSectIsOwner(&pSink->CritSect));
+
+ /*
+ * Figure out how much we can transfer.
+ */
+ const uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink);
+ const uint32_t cbCircBufWritable = (uint32_t)RTCircBufFree(pCircBuf);
+ uint32_t cbToTransfer = RT_MIN(cbCircBufWritable, cbSinkReadable);
+ uint32_t cFramesToTransfer = PDMAudioPropsBytesToFrames(&pSink->PCMProps, cbToTransfer);
+ cbToTransfer = PDMAudioPropsFramesToBytes(&pSink->PCMProps, cFramesToTransfer);
+
+ Log3Func(("idStream=%u: cbSinkReadable=%#RX32 cbCircBufWritable=%#RX32 -> cbToTransfer=%#RX32 (%RU32 frames) @%#RX64\n",
+ idStream, cbSinkReadable, cbCircBufWritable, cbToTransfer, cFramesToTransfer, offStream));
+ RT_NOREF(idStream);
+
+ /** @todo should we throttle (read less) this if we're far ahead? */
+
+ /*
+ * Copy loop.
+ */
+ while (cbToTransfer > 0)
+ {
+/** @todo We should be able to read straight into the circular buffer here
+ * as it should have a frame aligned size. */
+
+ /* Read a chunk of data. */
+ uint8_t abBuf[4096];
+ uint32_t cbRead = 0;
+ uint32_t cFramesRead = 0;
+ AudioMixBufPeek(&pSink->MixBuf, 0, cFramesToTransfer, &cFramesRead,
+ &pSink->In.State, abBuf, RT_MIN(cbToTransfer, sizeof(abBuf)), &cbRead);
+ AssertBreak(cFramesRead > 0);
+ Assert(cbRead > 0);
+
+ cFramesToTransfer -= cFramesRead;
+ AudioMixBufAdvance(&pSink->MixBuf, cFramesRead);
+
+ /* Write it to the internal DMA buffer. */
+ uint32_t off = 0;
+ while (off < cbRead)
+ {
+ void *pvDstBuf;
+ size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbRead - off, &pvDstBuf, &cbDstBuf);
+
+ memcpy(pvDstBuf, &abBuf[off], cbDstBuf);
+
+#ifdef VBOX_WITH_DTRACE
+ VBOXDD_AUDIO_MIXER_SINK_AIO_IN(idStream, (uint32_t)cbDstBuf, offStream);
+#endif
+ offStream += cbDstBuf;
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf);
+
+ off += (uint32_t)cbDstBuf;
+ }
+ Assert(off == cbRead);
+
+ /* Write to debug file? */
+ if (RT_LIKELY(!pDbgFile))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pDbgFile, abBuf, cbRead);
+
+ /* Advance. */
+ Assert(cbRead <= cbToTransfer);
+ cbToTransfer -= cbRead;
+ }
+
+ return offStream;
+}
+
+
+/**
+ * Signals the AIO thread to perform updates.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink which AIO thread needs to do chores.
+ */
+int AudioMixerSinkSignalUpdateJob(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ return RTSemEventSignal(pSink->AIO.hEvent);
+}
+
+
+/**
+ * Checks if the caller is the owner of the mixer sink's critical section.
+ *
+ * @returns \c true if the caller is the lock owner, \c false if not.
+ * @param pSink The mixer sink to check.
+ */
+bool AudioMixerSinkLockIsOwner(PAUDMIXSINK pSink)
+{
+ return RTCritSectIsOwner(&pSink->CritSect);
+}
+
+
+/**
+ * Locks the mixer sink for purposes of serializing with the AIO thread.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink to lock.
+ */
+int AudioMixerSinkLock(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ return RTCritSectEnter(&pSink->CritSect);
+}
+
+
+/**
+ * Try to lock the mixer sink for purposes of serializing with the AIO thread.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink to lock.
+ */
+int AudioMixerSinkTryLock(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ return RTCritSectTryEnter(&pSink->CritSect);
+}
+
+
+/**
+ * Unlocks the sink.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink to unlock.
+ */
+int AudioMixerSinkUnlock(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ return RTCritSectLeave(&pSink->CritSect);
+}
+
+
+/**
+ * Creates an audio mixer stream.
+ *
+ * @returns VBox status code.
+ * @param pSink Sink to use for creating the stream.
+ * @param pConn Audio connector interface to use.
+ * @param pCfg Audio stream configuration to use. This may be modified
+ * in some unspecified way (see
+ * PDMIAUDIOCONNECTOR::pfnStreamCreate).
+ * @param pDevIns The device instance to register statistics with.
+ * @param ppStream Pointer which receives the newly created audio stream.
+ */
+int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConn, PCPDMAUDIOSTREAMCFG pCfg,
+ PPDMDEVINS pDevIns, PAUDMIXSTREAM *ppStream)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertPtrReturn(pConn, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(ppStream, VERR_INVALID_POINTER);
+ Assert(pSink->AIO.pDevIns == pDevIns); RT_NOREF(pDevIns); /* we'll probably be adding more statistics */
+ AssertReturn(pCfg->enmDir == pSink->enmDir, VERR_MISMATCH);
+
+ /*
+ * Check status and get the host driver config.
+ */
+ if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_DUPLEX) == PDMAUDIOBACKENDSTS_NOT_ATTACHED)
+ return VERR_AUDIO_BACKEND_NOT_ATTACHED;
+
+ PDMAUDIOBACKENDCFG BackendCfg;
+ int rc = pConn->pfnGetConfig(pConn, &BackendCfg);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Allocate the instance.
+ */
+ PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM));
+ AssertReturn(pMixStream, VERR_NO_MEMORY);
+
+ /* Assign the backend's name to the mixer stream's name for easier identification in the (release) log. */
+ pMixStream->pszName = RTStrAPrintf2("[%s] %s", pCfg->szName, BackendCfg.szName);
+ pMixStream->pszStatPrefix = RTStrAPrintf2("MixerSink-%s/%s/", pSink->pszName, BackendCfg.szName);
+ if (pMixStream->pszName && pMixStream->pszStatPrefix)
+ {
+ rc = RTCritSectInit(&pMixStream->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Lock the sink so we can safely get it's properties and call
+ * down into the audio driver to create that end of the stream.
+ */
+ rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("[%s] (enmDir=%ld, %u bits, %RU8 channels, %RU32Hz)\n", pSink->pszName, pCfg->enmDir,
+ PDMAudioPropsSampleBits(&pCfg->Props), PDMAudioPropsChannels(&pCfg->Props), pCfg->Props.uHz));
+
+ /*
+ * Initialize the host-side configuration for the stream to be created,
+ * this is the sink format & direction with the src/dir, layout, name
+ * and device specific config copied from the guest side config (pCfg).
+ * We disregard any Backend settings here.
+ *
+ * (Note! pfnStreamCreate used to get both CfgHost and pCfg (aka pCfgGuest)
+ * passed in, but that became unnecessary with DrvAudio stoppping
+ * mixing. The mixing is done here and we bridge guest & host configs.)
+ */
+ AssertMsg(AudioHlpPcmPropsAreValidAndSupported(&pSink->PCMProps),
+ ("%s: Does not (yet) have a (valid and supported) format set when it must\n", pSink->pszName));
+
+ PDMAUDIOSTREAMCFG CfgHost;
+ rc = PDMAudioStrmCfgInitWithProps(&CfgHost, &pSink->PCMProps);
+ AssertRC(rc); /* cannot fail */
+ CfgHost.enmDir = pSink->enmDir;
+ CfgHost.enmPath = pCfg->enmPath;
+ CfgHost.Device = pCfg->Device;
+ RTStrCopy(CfgHost.szName, sizeof(CfgHost.szName), pCfg->szName);
+
+ /*
+ * Create the stream.
+ *
+ * Output streams are not using any mixing buffers in DrvAudio. This will
+ * become the norm after we move the input mixing here and convert DevSB16
+ * to use this mixer code too.
+ */
+ PPDMAUDIOSTREAM pStream;
+ rc = pConn->pfnStreamCreate(pConn, 0 /*fFlags*/, &CfgHost, &pStream);
+ if (RT_SUCCESS(rc))
+ {
+ pMixStream->cFramesBackendBuffer = pStream->Cfg.Backend.cFramesBufferSize;
+
+ /* Set up the mixing buffer conversion state. */
+ if (pSink->enmDir == PDMAUDIODIR_IN)
+ rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pStream->Cfg.Props);
+ else
+ rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pStream->Cfg.Props);
+ if (RT_SUCCESS(rc))
+ {
+ /* Save the audio stream pointer to this mixing stream. */
+ pMixStream->pStream = pStream;
+
+ /* Increase the stream's reference count to let others know
+ * we're relying on it to be around now. */
+ pConn->pfnStreamRetain(pConn, pStream);
+ pMixStream->pConn = pConn;
+ pMixStream->uMagic = AUDMIXSTREAM_MAGIC;
+
+ RTCritSectLeave(&pSink->CritSect);
+
+ if (ppStream)
+ *ppStream = pMixStream;
+ return VINF_SUCCESS;
+ }
+
+ rc = pConn->pfnStreamDestroy(pConn, pStream, true /*fImmediate*/);
+ }
+
+ /*
+ * Failed. Tear down the stream.
+ */
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+ }
+ RTCritSectDelete(&pMixStream->CritSect);
+ }
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ RTStrFree(pMixStream->pszStatPrefix);
+ pMixStream->pszStatPrefix = NULL;
+ RTStrFree(pMixStream->pszName);
+ pMixStream->pszName = NULL;
+ RTMemFree(pMixStream);
+ return rc;
+}
+
+
+/**
+ * Adds an audio stream to a specific audio sink.
+ *
+ * @returns VBox status code.
+ * @param pSink Sink to add audio stream to.
+ * @param pStream Stream to add.
+ */
+int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ LogFlowFuncEnter();
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ Assert(pStream->uMagic == AUDMIXSTREAM_MAGIC);
+ AssertPtrReturn(pStream->pConn, VERR_AUDIO_STREAM_NOT_READY);
+ AssertReturn(pStream->pSink == NULL, VERR_ALREADY_EXISTS);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturn(rc, rc);
+
+ AssertLogRelMsgReturnStmt(pSink->cStreams < UINT8_MAX, ("too many streams!\n"), RTCritSectLeave(&pSink->CritSect),
+ VERR_TOO_MANY_OPEN_FILES);
+
+ /*
+ * If the sink is running and not in pending disable mode, make sure that
+ * the added stream also is enabled. Ignore any failure to enable it.
+ */
+ if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ && !(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+ {
+ audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE);
+ }
+
+ /* Save pointer to sink the stream is attached to. */
+ pStream->pSink = pSink;
+
+ /* Append stream to sink's list. */
+ RTListAppend(&pSink->lstStreams, &pStream->Node);
+ pSink->cStreams++;
+
+ LogFlowFunc(("[%s] cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc));
+ RTCritSectLeave(&pSink->CritSect);
+ return rc;
+}
+
+
+/**
+ * Removes a mixer stream from a mixer sink, internal version.
+ *
+ * @returns VBox status code.
+ * @param pSink The mixer sink (valid).
+ * @param pStream The stream to remove (valid).
+ *
+ * @note Caller must own the sink lock.
+ */
+static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertPtr(pSink);
+ AssertPtr(pStream);
+ AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n",
+ pStream->pszName, pSink->pszName), VERR_NOT_FOUND);
+ Assert(RTCritSectIsOwner(&pSink->CritSect));
+ LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n", pSink->pszName, pStream->pStream->Cfg.szName, pSink->cStreams));
+
+ /*
+ * Remove stream from sink, update the count and set the pSink member to NULL.
+ */
+ RTListNodeRemove(&pStream->Node);
+
+ Assert(pSink->cStreams > 0);
+ pSink->cStreams--;
+
+ pStream->pSink = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Removes a mixer stream from a mixer sink.
+ *
+ * @param pSink The mixer sink.
+ * @param pStream The stream to remove.
+ */
+void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertPtrReturnVoid(pSink);
+ AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC);
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(pStream->uMagic == AUDMIXSTREAM_MAGIC);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+
+ audioMixerSinkRemoveStreamInternal(pSink, pStream);
+
+ RTCritSectLeave(&pSink->CritSect);
+}
+
+
+/**
+ * Removes all streams from a given sink.
+ *
+ * @param pSink The mixer sink. NULL is ignored.
+ */
+void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+ AssertReturnVoid(pSink->uMagic == AUDMIXSINK_MAGIC);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ AssertRCReturnVoid(rc);
+
+ LogFunc(("%s\n", pSink->pszName));
+
+ PAUDMIXSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
+ {
+ audioMixerSinkRemoveStreamInternal(pSink, pStream);
+ }
+ AssertStmt(pSink->cStreams == 0, pSink->cStreams = 0);
+
+ RTCritSectLeave(&pSink->CritSect);
+}
+
+
+
+/*********************************************************************************************************************************
+ * Mixer Stream implementation.
+ ********************************************************************************************************************************/
+
+/**
+ * Controls a mixer stream, internal version.
+ *
+ * @returns VBox status code (generally ignored).
+ * @param pMixStream Mixer stream to control.
+ * @param enmCmd Mixer stream command to use.
+ */
+static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd)
+{
+ Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
+ AssertPtrReturn(pMixStream->pConn, VERR_AUDIO_STREAM_NOT_READY);
+ AssertPtrReturn(pMixStream->pStream, VERR_AUDIO_STREAM_NOT_READY);
+
+ int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd);
+
+ LogFlowFunc(("[%s] enmCmd=%d, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc));
+
+ return rc;
+}
+
+/**
+ * Updates a mixer stream's internal status.
+ *
+ * This may perform a stream re-init if the driver requests it, in which case
+ * this may take a little while longer than usual...
+ *
+ * @returns VBox status code.
+ * @param pMixStream Mixer stream to to update internal status for.
+ */
+static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream)
+{
+ Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
+
+ /*
+ * Reset the mixer status to start with.
+ */
+ pMixStream->fStatus = AUDMIXSTREAM_STATUS_NONE;
+
+ PPDMIAUDIOCONNECTOR const pConn = pMixStream->pConn;
+ if (pConn) /* Audio connector available? */
+ {
+ PPDMAUDIOSTREAM const pStream = pMixStream->pStream;
+
+ /*
+ * Get the stream status.
+ * Do re-init if needed and fetch the status again afterwards.
+ */
+ PDMAUDIOSTREAMSTATE enmState = pConn->pfnStreamGetState(pConn, pStream);
+ if (enmState != PDMAUDIOSTREAMSTATE_NEED_REINIT)
+ { /* likely */ }
+ else
+ {
+ LogFunc(("[%s] needs re-init...\n", pMixStream->pszName));
+ int rc = pConn->pfnStreamReInit(pConn, pStream);
+ enmState = pConn->pfnStreamGetState(pConn, pStream);
+ LogFunc(("[%s] re-init returns %Rrc and %s.\n", pMixStream->pszName, rc, PDMAudioStreamStateGetName(enmState)));
+
+ PAUDMIXSINK const pSink = pMixStream->pSink;
+ AssertPtr(pSink);
+ if (pSink->enmDir == PDMAUDIODIR_OUT)
+ {
+ rc = AudioMixBufInitPeekState(&pSink->MixBuf, &pMixStream->PeekState, &pStream->Cfg.Props);
+ /** @todo we need to remember this, don't we? */
+ AssertLogRelRCReturn(rc, VINF_SUCCESS);
+ }
+ else
+ {
+ rc = AudioMixBufInitWriteState(&pSink->MixBuf, &pMixStream->WriteState, &pStream->Cfg.Props);
+ /** @todo we need to remember this, don't we? */
+ AssertLogRelRCReturn(rc, VINF_SUCCESS);
+ }
+ }
+
+ /*
+ * Translate the status to mixer speak.
+ */
+ AssertMsg(enmState > PDMAUDIOSTREAMSTATE_INVALID && enmState < PDMAUDIOSTREAMSTATE_END, ("%d\n", enmState));
+ switch (enmState)
+ {
+ case PDMAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMAUDIOSTREAMSTATE_NEED_REINIT:
+ case PDMAUDIOSTREAMSTATE_INACTIVE:
+ pMixStream->fStatus = AUDMIXSTREAM_STATUS_NONE;
+ break;
+ case PDMAUDIOSTREAMSTATE_ENABLED:
+ pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED;
+ break;
+ case PDMAUDIOSTREAMSTATE_ENABLED_READABLE:
+ Assert(pMixStream->pSink->enmDir == PDMAUDIODIR_IN);
+ pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED | AUDMIXSTREAM_STATUS_CAN_READ;
+ break;
+ case PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE:
+ Assert(pMixStream->pSink->enmDir == PDMAUDIODIR_OUT);
+ pMixStream->fStatus = AUDMIXSTREAM_STATUS_ENABLED | AUDMIXSTREAM_STATUS_CAN_WRITE;
+ break;
+ /* no default */
+ case PDMAUDIOSTREAMSTATE_INVALID:
+ case PDMAUDIOSTREAMSTATE_END:
+ case PDMAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ }
+
+ LogFlowFunc(("[%s] -> 0x%x\n", pMixStream->pszName, pMixStream->fStatus));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroys & frees a mixer stream, internal version.
+ *
+ * Worker for audioMixerSinkDestroyInternal and AudioMixerStreamDestroy.
+ *
+ * @param pMixStream Mixer stream to destroy.
+ * @param pDevIns The device instance the statistics are registered with.
+ * @param fImmediate How to handle still draining streams, whether to let
+ * them complete (@c false) or destroy them immediately (@c
+ * true).
+ */
+static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream, PPDMDEVINS pDevIns, bool fImmediate)
+{
+ AssertPtr(pMixStream);
+ LogFunc(("%s\n", pMixStream->pszName));
+ Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
+
+ /*
+ * Invalidate it.
+ */
+ pMixStream->uMagic = AUDMIXSTREAM_MAGIC_DEAD;
+
+ /*
+ * Destroy the driver stream (if any).
+ */
+ if (pMixStream->pConn) /* Stream has a connector interface present? */
+ {
+ if (pMixStream->pStream)
+ {
+ pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream);
+ pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream, fImmediate);
+
+ pMixStream->pStream = NULL;
+ }
+
+ pMixStream->pConn = NULL;
+ }
+
+ /*
+ * Stats. Doing it by prefix is soo much faster than individually, btw.
+ */
+ if (pMixStream->pszStatPrefix)
+ {
+ PDMDevHlpSTAMDeregisterByPrefix(pDevIns, pMixStream->pszStatPrefix);
+ RTStrFree(pMixStream->pszStatPrefix);
+ pMixStream->pszStatPrefix = NULL;
+ }
+
+ /*
+ * Delete the critsect and free the memory.
+ */
+ int rc2 = RTCritSectDelete(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ RTStrFree(pMixStream->pszName);
+ pMixStream->pszName = NULL;
+
+ RTMemFree(pMixStream);
+}
+
+
+/**
+ * Destroys a mixer stream.
+ *
+ * @param pMixStream Mixer stream to destroy.
+ * @param pDevIns The device instance statistics are registered with.
+ * @param fImmediate How to handle still draining streams, whether to let
+ * them complete (@c false) or destroy them immediately (@c
+ * true).
+ */
+void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream, PPDMDEVINS pDevIns, bool fImmediate)
+{
+ if (!pMixStream)
+ return;
+ AssertReturnVoid(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
+ LogFunc(("%s\n", pMixStream->pszName));
+
+ /*
+ * Serializing paranoia.
+ */
+ int rc = RTCritSectEnter(&pMixStream->CritSect);
+ AssertRCReturnVoid(rc);
+ RTCritSectLeave(&pMixStream->CritSect);
+
+ /*
+ * Unlink from sink if associated with one.
+ */
+ PAUDMIXSINK pSink = pMixStream->pSink;
+ if ( RT_VALID_PTR(pSink)
+ && pSink->uMagic == AUDMIXSINK_MAGIC)
+ {
+ RTCritSectEnter(&pSink->CritSect);
+ audioMixerSinkRemoveStreamInternal(pMixStream->pSink, pMixStream);
+ RTCritSectLeave(&pSink->CritSect);
+ }
+ else if (pSink)
+ AssertFailed();
+
+ /*
+ * Do the actual stream destruction.
+ */
+ audioMixerStreamDestroyInternal(pMixStream, pDevIns, fImmediate);
+ LogFlowFunc(("returns\n"));
+}
+
diff --git a/src/VBox/Devices/Audio/AudioMixer.h b/src/VBox/Devices/Audio/AudioMixer.h
new file mode 100644
index 00000000..cb701dac
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixer.h
@@ -0,0 +1,350 @@
+/* $Id: AudioMixer.h $ */
+/** @file
+ * VBox audio - Mixing routines.
+ *
+ * The mixing routines are mainly used by the various audio device emulations
+ * to achieve proper multiplexing from/to attached devices LUNs.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioMixer_h
+#define VBOX_INCLUDED_SRC_Audio_AudioMixer_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/critsect.h>
+
+#include <VBox/vmm/pdmaudioifs.h>
+#include "AudioMixBuffer.h"
+#include "AudioHlp.h"
+
+
+/** @defgroup grp_pdm_ifs_audio_mixing Audio Mixing
+ * @ingroup grp_pdm_ifs_audio
+ *
+ * @note This is currently placed under PDM Audio Interface as that seemed like
+ * the best place for it.
+ *
+ * @{
+ */
+
+/** Pointer to an audio mixer sink. */
+typedef struct AUDMIXSINK *PAUDMIXSINK;
+/** Pointer to a const audio mixer sink. */
+typedef struct AUDMIXSINK const *PCAUDMIXSINK;
+
+
+/**
+ * Audio mixer instance.
+ */
+typedef struct AUDIOMIXER
+{
+ /** Magic value (AUDIOMIXER_MAGIC). */
+ uintptr_t uMagic;
+ /** The mixer's name (allocated after this structure). */
+ char const *pszName;
+ /** The master volume of this mixer. */
+ PDMAUDIOVOLUME VolMaster;
+ /** List of audio mixer sinks (AUDMIXSINK). */
+ RTLISTANCHOR lstSinks;
+ /** Number of used audio sinks. */
+ uint8_t cSinks;
+ /** Mixer flags. See AUDMIXER_FLAGS_XXX. */
+ uint32_t fFlags;
+ /** The mixer's critical section. */
+ RTCRITSECT CritSect;
+} AUDIOMIXER;
+/** Pointer to an audio mixer instance. */
+typedef AUDIOMIXER *PAUDIOMIXER;
+
+/** Value for AUDIOMIXER::uMagic. (Attilio Joseph "Teo" Macero) */
+#define AUDIOMIXER_MAGIC UINT32_C(0x19251030)
+/** Value for AUDIOMIXER::uMagic after destruction. */
+#define AUDIOMIXER_MAGIC_DEAD UINT32_C(0x20080219)
+
+/** @name AUDMIXER_FLAGS_XXX - For AudioMixerCreate().
+ * @{ */
+/** No mixer flags specified. */
+#define AUDMIXER_FLAGS_NONE 0
+/** Debug mode enabled.
+ * This writes .WAV file to the host, usually to the temporary directory. */
+#define AUDMIXER_FLAGS_DEBUG RT_BIT(0)
+/** Validation mask. */
+#define AUDMIXER_FLAGS_VALID_MASK UINT32_C(0x00000001)
+/** @} */
+
+
+/**
+ * Audio mixer stream.
+ */
+typedef struct AUDMIXSTREAM
+{
+ /** List entry on AUDMIXSINK::lstStreams. */
+ RTLISTNODE Node;
+ /** Magic value (AUDMIXSTREAM_MAGIC). */
+ uint32_t uMagic;
+ /** The backend buffer size in frames (for draining deadline calc). */
+ uint32_t cFramesBackendBuffer;
+ /** Stream status of type AUDMIXSTREAM_STATUS_. */
+ uint32_t fStatus;
+ /** Number of writable/readable frames the last time we checked. */
+ uint32_t cFramesLastAvail;
+ /** Set if the stream has been found unreliable wrt. consuming/producing
+ * samples, and that we shouldn't consider it when deciding how much to move
+ * from the mixer buffer and to the drivers. */
+ bool fUnreliable;
+ /** Name of this stream. */
+ char *pszName;
+ /** The statistics prefix. */
+ char *pszStatPrefix;
+ /** Sink this stream is attached to. */
+ PAUDMIXSINK pSink;
+ /** Pointer to audio connector being used. */
+ PPDMIAUDIOCONNECTOR pConn;
+ /** Pointer to PDM audio stream this mixer stream handles. */
+ PPDMAUDIOSTREAM pStream;
+ union
+ {
+ /** Output: Mixing buffer peeking state & config. */
+ AUDIOMIXBUFPEEKSTATE PeekState;
+ /** Input: Mixing buffer writing state & config. */
+ AUDIOMIXBUFWRITESTATE WriteState;
+ };
+ /** Last read (recording) / written (playback) timestamp (in ns). */
+ uint64_t tsLastReadWrittenNs;
+ /** The streams's critical section. */
+ RTCRITSECT CritSect;
+} AUDMIXSTREAM;
+/** Pointer to an audio mixer stream. */
+typedef AUDMIXSTREAM *PAUDMIXSTREAM;
+
+/** Value for AUDMIXSTREAM::uMagic. (Jan Erik Kongshaug) */
+#define AUDMIXSTREAM_MAGIC UINT32_C(0x19440704)
+/** Value for AUDMIXSTREAM::uMagic after destruction. */
+#define AUDMIXSTREAM_MAGIC_DEAD UINT32_C(0x20191105)
+
+
+/** @name AUDMIXSTREAM_STATUS_XXX - mixer stream status.
+ * (This is a destilled version of PDMAUDIOSTREAM_STS_XXX.)
+ * @{ */
+/** No status set. */
+#define AUDMIXSTREAM_STATUS_NONE UINT32_C(0)
+/** The mixing stream is enabled (active). */
+#define AUDMIXSTREAM_STATUS_ENABLED RT_BIT_32(0)
+/** The mixing stream can be read from.
+ * Always set together with AUDMIXSTREAM_STATUS_ENABLED. */
+#define AUDMIXSTREAM_STATUS_CAN_READ RT_BIT_32(1)
+/** The mixing stream can be written to.
+ * Always set together with AUDMIXSTREAM_STATUS_ENABLED. */
+#define AUDMIXSTREAM_STATUS_CAN_WRITE RT_BIT_32(2)
+/** @} */
+
+
+/** Callback for an asynchronous I/O update job. */
+typedef DECLCALLBACKTYPE(void, FNAUDMIXSINKUPDATE,(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser));
+/** Pointer to a callback for an asynchronous I/O update job. */
+typedef FNAUDMIXSINKUPDATE *PFNAUDMIXSINKUPDATE;
+
+/**
+ * Audio mixer sink.
+ */
+typedef struct AUDMIXSINK
+{
+ /** List entry on AUDIOMIXER::lstSinks. */
+ RTLISTNODE Node;
+ /** Magic value (AUDMIXSINK_MAGIC). */
+ uint32_t uMagic;
+ /** The sink direction (either PDMAUDIODIR_IN or PDMAUDIODIR_OUT). */
+ PDMAUDIODIR enmDir;
+ /** Pointer to mixer object this sink is bound to. */
+ PAUDIOMIXER pParent;
+ /** Name of this sink (allocated after this structure). */
+ char const *pszName;
+ /** The sink's PCM format (i.e. the guest device side). */
+ PDMAUDIOPCMPROPS PCMProps;
+ /** Sink status bits - AUDMIXSINK_STS_XXX. */
+ uint32_t fStatus;
+ /** Number of bytes to be transferred from the device DMA buffer before the
+ * streams will be put into draining mode. */
+ uint32_t cbDmaLeftToDrain;
+ /** The deadline for draining if it's pending. */
+ uint64_t nsDrainDeadline;
+ /** When the draining startet (for logging). */
+ uint64_t nsDrainStarted;
+ /** Number of streams assigned. */
+ uint8_t cStreams;
+ /** List of assigned streams (AUDMIXSTREAM).
+ * @note All streams have the same PCM properties, so the mixer does not do
+ * any conversion. bird: That is *NOT* true any more, the mixer has
+ * encoders/decoder states for each stream (well, input is still a todo).
+ *
+ * @todo Use something faster -- vector maybe? bird: It won't be faster. You
+ * will have a vector of stream pointers (because you cannot have a vector
+ * of full AUDMIXSTREAM structures since they'll move when the vector is
+ * reallocated and we need pointers to them to give out to devices), which
+ * is the same cost as going via Node.pNext/pPrev. */
+ RTLISTANCHOR lstStreams;
+ /** The volume of this sink. The volume always will
+ * be combined with the mixer's master volume. */
+ PDMAUDIOVOLUME Volume;
+ /** The volume of this sink, combined with the last set master volume. */
+ PDMAUDIOVOLUME VolumeCombined;
+ /** Timestamp since last update (in ms). */
+ uint64_t tsLastUpdatedMs;
+ /** Last read (recording) / written (playback) timestamp (in ns). */
+ uint64_t tsLastReadWrittenNs;
+ /** Union for input/output specifics. */
+ union
+ {
+ struct
+ {
+ /** The sink's peek state. */
+ AUDIOMIXBUFPEEKSTATE State;
+ } In;
+ struct
+ {
+ /** The sink's write state. */
+ AUDIOMIXBUFWRITESTATE State;
+ } Out;
+ };
+ struct
+ {
+ PAUDIOHLPFILE pFile;
+ } Dbg;
+ /** This sink's mixing buffer. */
+ AUDIOMIXBUF MixBuf;
+ /** Asynchronous I/O thread related stuff. */
+ struct
+ {
+ /** The thread handle, NIL_RTTHREAD if not active. */
+ RTTHREAD hThread;
+ /** Event for letting the thread know there is some data to process. */
+ RTSEMEVENT hEvent;
+ /** The device instance (same for all update jobs). */
+ PPDMDEVINS pDevIns;
+ /** Started indicator. */
+ volatile bool fStarted;
+ /** Shutdown indicator. */
+ volatile bool fShutdown;
+ /** Number of update jobs this sink has (usually zero or one). */
+ uint8_t cUpdateJobs;
+ /** The minimum typical interval for all jobs. */
+ uint32_t cMsMinTypicalInterval;
+ /** Update jobs for this sink. */
+ struct
+ {
+ /** User specific argument. */
+ void *pvUser;
+ /** The callback. */
+ PFNAUDMIXSINKUPDATE pfnUpdate;
+ /** Typical interval in milliseconds. */
+ uint32_t cMsTypicalInterval;
+ } aUpdateJobs[8];
+ } AIO;
+ /** The sink's critical section. */
+ RTCRITSECT CritSect;
+} AUDMIXSINK;
+
+/** Value for AUDMIXSINK::uMagic. (Sir George Martin) */
+#define AUDMIXSINK_MAGIC UINT32_C(0x19260103)
+/** Value for AUDMIXSINK::uMagic after destruction. */
+#define AUDMIXSINK_MAGIC_DEAD UINT32_C(0x20160308)
+
+
+/** @name AUDMIXSINK_STS_XXX - Sink status bits.
+ * @{ */
+/** No status specified. */
+#define AUDMIXSINK_STS_NONE 0
+/** The sink is active and running. */
+#define AUDMIXSINK_STS_RUNNING RT_BIT(0)
+/** Draining the buffers and pending stop - output only. */
+#define AUDMIXSINK_STS_DRAINING RT_BIT(1)
+/** Drained the DMA buffer. */
+#define AUDMIXSINK_STS_DRAINED_DMA RT_BIT(2)
+/** Drained the mixer buffer, only waiting for streams (drivers) now. */
+#define AUDMIXSINK_STS_DRAINED_MIXBUF RT_BIT(3)
+/** Dirty flag.
+ * - For output sinks this means that there is data in the sink which has not
+ * been played yet.
+ * - For input sinks this means that there is data in the sink which has been
+ * recorded but not transferred to the destination yet.
+ * @todo This isn't used for *anything* at the moment. Remove? */
+#define AUDMIXSINK_STS_DIRTY RT_BIT(4)
+/** @} */
+
+
+/** @name Audio mixer methods
+ * @{ */
+int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer);
+void AudioMixerDestroy(PAUDIOMIXER pMixer, PPDMDEVINS pDevIns);
+void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs);
+int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PCPDMAUDIOVOLUME pVol);
+int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, PDMAUDIODIR enmDir, PPDMDEVINS pDevIns, PAUDMIXSINK *ppSink);
+/** @} */
+
+/** @name Audio mixer sink methods
+ * @{ */
+int AudioMixerSinkStart(PAUDMIXSINK pSink);
+int AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming);
+void AudioMixerSinkDestroy(PAUDMIXSINK pSink, PPDMDEVINS pDevIns);
+uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink);
+uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink);
+PDMAUDIODIR AudioMixerSinkGetDir(PCAUDMIXSINK pSink);
+uint32_t AudioMixerSinkGetStatus(PAUDMIXSINK pSink);
+bool AudioMixerSinkIsActive(PAUDMIXSINK pSink);
+void AudioMixerSinkReset(PAUDMIXSINK pSink);
+int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PCPDMAUDIOPCMPROPS pPCMProps, uint32_t cMsSchedulingHint);
+int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVol);
+int AudioMixerSinkUpdate(PAUDMIXSINK pSink, uint32_t cbDmaUsed, uint32_t cbDmaPeriod);
+
+int AudioMixerSinkAddUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser, uint32_t cMsTypicalInterval);
+int AudioMixerSinkRemoveUpdateJob(PAUDMIXSINK pSink, PFNAUDMIXSINKUPDATE pfnUpdate, void *pvUser);
+int AudioMixerSinkSignalUpdateJob(PAUDMIXSINK pSink);
+uint64_t AudioMixerSinkTransferFromCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream,
+ uint32_t idStream, PAUDIOHLPFILE pDbgFile);
+uint64_t AudioMixerSinkTransferToCircBuf(PAUDMIXSINK pSink, PRTCIRCBUF pCircBuf, uint64_t offStream,
+ uint32_t idStream, PAUDIOHLPFILE pDbgFile);
+bool AudioMixerSinkLockIsOwner(PAUDMIXSINK pSink);
+int AudioMixerSinkLock(PAUDMIXSINK pSink);
+int AudioMixerSinkTryLock(PAUDMIXSINK pSink);
+int AudioMixerSinkUnlock(PAUDMIXSINK pSink);
+
+int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PCPDMAUDIOSTREAMCFG pCfg,
+ PPDMDEVINS pDevIns, PAUDMIXSTREAM *ppStream);
+int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink);
+/** @} */
+
+/** @name Audio mixer stream methods
+ * @{ */
+void AudioMixerStreamDestroy(PAUDMIXSTREAM pStream, PPDMDEVINS pDevIns, bool fImmediate);
+/** @} */
+
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixer_h */
+
diff --git a/src/VBox/Devices/Audio/AudioTest.cpp b/src/VBox/Devices/Audio/AudioTest.cpp
new file mode 100644
index 00000000..a4115ec8
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTest.cpp
@@ -0,0 +1,3580 @@
+/* $Id: AudioTest.cpp $ */
+/** @file
+ * Audio testing routines.
+ *
+ * Common code which is being used by the ValidationKit and the
+ * debug / ValdikationKit audio driver(s).
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <package-generated.h>
+#include "product-generated.h"
+
+#include <iprt/buildconfig.h>
+#include <iprt/cdefs.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/formats/riff.h>
+#include <iprt/inifile.h>
+#include <iprt/list.h>
+#include <iprt/message.h> /** @todo Get rid of this once we have own log hooks. */
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+#define _USE_MATH_DEFINES
+#include <math.h> /* sin, M_PI */
+
+#define LOG_GROUP LOG_GROUP_AUDIO_TEST
+#include <VBox/log.h>
+
+#include <VBox/version.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "AudioTest.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+/** The test manifest file name. */
+#define AUDIOTEST_MANIFEST_FILE_STR "vkat_manifest.ini"
+/** The current test manifest version. */
+#define AUDIOTEST_MANIFEST_VER 1
+/** Audio test archive default suffix.
+ * According to IPRT terminology this always contains the dot. */
+#define AUDIOTEST_ARCHIVE_SUFF_STR ".tar.gz"
+
+/** Test manifest header name. */
+#define AUDIOTEST_SEC_HDR_STR "header"
+/** Maximum section name length (in UTF-8 characters). */
+#define AUDIOTEST_MAX_SEC_LEN 128
+/** Maximum object name length (in UTF-8 characters). */
+#define AUDIOTEST_MAX_OBJ_LEN 128
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Enumeration for an audio test object type.
+ */
+typedef enum AUDIOTESTOBJTYPE
+{
+ /** Unknown / invalid, do not use. */
+ AUDIOTESTOBJTYPE_UNKNOWN = 0,
+ /** The test object is a file. */
+ AUDIOTESTOBJTYPE_FILE,
+ /** The usual 32-bit hack. */
+ AUDIOTESTOBJTYPE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTOBJTYPE;
+
+/**
+ * Structure for keeping an audio test object file.
+ */
+typedef struct AUDIOTESTOBJFILE
+{
+ /** 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;
+
+/**
+ * Structure for keeping a single audio test object.
+ *
+ * A test object is data which is needed in order to perform and verify one or
+ * more audio test case(s).
+ */
+typedef struct AUDIOTESTOBJINT
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Pointer to test set this handle is bound to. */
+ PAUDIOTESTSET pSet;
+ /** As we only support .INI-style files for now, this only has the object's section name in it. */
+ /** @todo Make this more generic (union, ++). */
+ char szSec[AUDIOTEST_MAX_SEC_LEN];
+ /** The UUID of the object.
+ * Used to identify an object within a test set. */
+ RTUUID Uuid;
+ /** Number of references to this test object. */
+ uint32_t cRefs;
+ /** Name of the test object.
+ * Must not contain a path and has to be able to serialize to disk. */
+ char szName[256];
+ /** The test type. */
+ AUDIOTESTTYPE enmTestType;
+ /** The object type. */
+ AUDIOTESTOBJTYPE enmType;
+ /** Meta data list. */
+ RTLISTANCHOR lstMeta;
+ /** Union for holding the object type-specific data. */
+ union
+ {
+ AUDIOTESTOBJFILE File;
+ };
+} AUDIOTESTOBJINT;
+/** Pointer to an audio test object. */
+typedef AUDIOTESTOBJINT *PAUDIOTESTOBJINT;
+
+/**
+ * Structure for keeping an audio test verification job.
+ */
+typedef struct AUDIOTESTVERIFYJOB
+{
+ /** Pointer to set A. */
+ PAUDIOTESTSET pSetA;
+ /** Pointer to set B. */
+ PAUDIOTESTSET pSetB;
+ /** Pointer to the error description to use. */
+ PAUDIOTESTERRORDESC pErr;
+ /** Zero-based index of current test being verified. */
+ uint32_t idxTest;
+ /** The verification options to use. */
+ AUDIOTESTVERIFYOPTS Opts;
+ /** PCM properties to use for verification. */
+ PDMAUDIOPCMPROPS PCMProps;
+} AUDIOTESTVERIFYJOB;
+/** Pointer to an audio test verification job. */
+typedef AUDIOTESTVERIFYJOB *PAUDIOTESTVERIFYJOB;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Well-known frequency selection test tones. */
+static const double s_aAudioTestToneFreqsHz[] =
+{
+ 349.2282 /*F4*/,
+ 440.0000 /*A4*/,
+ 523.2511 /*C5*/,
+ 698.4565 /*F5*/,
+ 880.0000 /*A5*/,
+ 1046.502 /*C6*/,
+ 1174.659 /*D6*/,
+ 1396.913 /*F6*/,
+ 1760.0000 /*A6*/
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int audioTestObjClose(PAUDIOTESTOBJINT pObj);
+static void audioTestObjFinalize(PAUDIOTESTOBJINT pObj);
+static void audioTestObjInit(PAUDIOTESTOBJINT pObj);
+static bool audioTestObjIsOpen(PAUDIOTESTOBJINT pObj);
+
+
+/**
+ * Initializes a test tone with a specific frequency (in Hz).
+ *
+ * @returns Used tone frequency (in Hz).
+ * @param pTone Pointer to test tone to initialize.
+ * @param pProps PCM properties to use for the test tone.
+ * @param dbFreq Frequency (in Hz) to initialize tone with.
+ * When set to 0.0, a random frequency will be chosen.
+ */
+double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq)
+{
+ if (dbFreq == 0.0)
+ dbFreq = AudioTestToneGetRandomFreq();
+
+ pTone->rdFreqHz = dbFreq;
+ pTone->rdFixed = 2.0 * M_PI * pTone->rdFreqHz / PDMAudioPropsHz(pProps);
+ pTone->uSample = 0;
+
+ memcpy(&pTone->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
+
+ pTone->enmType = AUDIOTESTTONETYPE_SINE; /* Only type implemented so far. */
+
+ return dbFreq;
+}
+
+/**
+ * Initializes a test tone by picking a random but well-known frequency (in Hz).
+ *
+ * @returns Randomly picked tone frequency (in Hz).
+ * @param pTone Pointer to test tone to initialize.
+ * @param pProps PCM properties to use for the test tone.
+ */
+double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps)
+{
+ return AudioTestToneInit(pTone, pProps,
+ /* Pick a frequency from our selection, so that every time a recording starts
+ * we'll hopfully generate a different note. */
+ 0.0);
+}
+
+/**
+ * Writes (and iterates) a given test tone to an output buffer.
+ *
+ * @returns VBox status code.
+ * @param pTone Pointer to test tone to write.
+ * @param pvBuf Pointer to output buffer to write test tone to.
+ * @param cbBuf Size (in bytes) of output buffer.
+ * @param pcbWritten How many bytes were written on success.
+ */
+int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ /*
+ * Clear the buffer first so we don't need to think about additional channels.
+ */
+ uint32_t cFrames = PDMAudioPropsBytesToFrames(&pTone->Props, cbBuf);
+
+ /* Input cbBuf not necessarily is aligned to the frames, so re-calculate it. */
+ const uint32_t cbToWrite = PDMAudioPropsFramesToBytes(&pTone->Props, cFrames);
+
+ PDMAudioPropsClearBuffer(&pTone->Props, pvBuf, cbBuf, cFrames);
+
+ /*
+ * Generate the select sin wave in the first channel:
+ */
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pTone->Props);
+ double const rdFixed = pTone->rdFixed;
+ uint64_t iSrcFrame = pTone->uSample;
+ switch (PDMAudioPropsSampleSize(&pTone->Props))
+ {
+ case 1:
+ /* untested */
+ if (PDMAudioPropsIsSigned(&pTone->Props))
+ {
+ int8_t *piSample = (int8_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *piSample = (int8_t)(126 /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame));
+ iSrcFrame++;
+ piSample += cbFrame;
+ }
+ }
+ else
+ {
+ /* untested */
+ uint8_t *pbSample = (uint8_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *pbSample = (uint8_t)(126 /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame) + 0x80);
+ iSrcFrame++;
+ pbSample += cbFrame;
+ }
+ }
+ break;
+
+ case 2:
+ if (PDMAudioPropsIsSigned(&pTone->Props))
+ {
+ int16_t *piSample = (int16_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *piSample = (int16_t)(32760 /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame));
+ iSrcFrame++;
+ piSample = (int16_t *)((uint8_t *)piSample + cbFrame);
+ }
+ }
+ else
+ {
+ /* untested */
+ uint16_t *puSample = (uint16_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *puSample = (uint16_t)(32760 /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame) + 0x8000);
+ iSrcFrame++;
+ puSample = (uint16_t *)((uint8_t *)puSample + cbFrame);
+ }
+ }
+ break;
+
+ case 4:
+ /* untested */
+ if (PDMAudioPropsIsSigned(&pTone->Props))
+ {
+ int32_t *piSample = (int32_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *piSample = (int32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame));
+ iSrcFrame++;
+ piSample = (int32_t *)((uint8_t *)piSample + cbFrame);
+ }
+ }
+ else
+ {
+ uint32_t *puSample = (uint32_t *)pvBuf;
+ while (cFrames-- > 0)
+ {
+ *puSample = (uint32_t)((32760 << 16) /*Amplitude*/ * sin(rdFixed * (double)iSrcFrame) + UINT32_C(0x80000000));
+ iSrcFrame++;
+ puSample = (uint32_t *)((uint8_t *)puSample + cbFrame);
+ }
+ }
+ break;
+
+ default:
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ }
+
+ pTone->uSample = iSrcFrame;
+
+ if (pcbWritten)
+ *pcbWritten = cbToWrite;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns a random test tone frequency.
+ */
+double AudioTestToneGetRandomFreq(void)
+{
+ return s_aAudioTestToneFreqsHz[RTRandU32Ex(0, RT_ELEMENTS(s_aAudioTestToneFreqsHz) - 1)];
+}
+
+/**
+ * Finds the next audible *or* silent audio sample and returns its offset.
+ *
+ * @returns Offset (in bytes) of the next found sample, or \a cbMax if not found / invalid parameters.
+ * @param hFile File handle of file to search in.
+ * @param fFindSilence Whether to search for a silent sample or not (i.e. audible).
+ * What a silent sample is depends on \a pToneParms PCM parameters.
+ * @param uOff Absolute offset (in bytes) to start searching from.
+ * @param cbMax Maximum amount of bytes to process.
+ * @param pToneParms Tone parameters to use.
+ * @param cbWindow Search window size (in bytes).
+ */
+static uint64_t audioTestToneFileFind(RTFILE hFile, bool fFindSilence, uint64_t uOff, uint64_t cbMax,
+ PAUDIOTESTTONEPARMS pToneParms, size_t cbWindow)
+{
+ int rc = RTFileSeek(hFile, uOff, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, UINT64_MAX);
+
+ uint64_t offFound = 0;
+ uint8_t abBuf[_64K];
+
+ size_t const cbFrame = PDMAudioPropsFrameSize(&pToneParms->Props);
+ AssertReturn(cbFrame, UINT64_MAX);
+
+ AssertReturn(PDMAudioPropsIsSizeAligned(&pToneParms->Props, (uint32_t)cbWindow), UINT64_MAX);
+
+ size_t cbRead;
+ for (;;)
+ {
+ rc = RTFileRead(hFile, &abBuf, RT_MIN(cbWindow, sizeof(abBuf)), &cbRead);
+ if ( RT_FAILURE(rc)
+ || !cbRead)
+ break;
+
+ AssertReturn(PDMAudioPropsIsSizeAligned(&pToneParms->Props, (uint32_t)cbRead), UINT64_MAX);
+ AssertReturn(cbRead % cbFrame == 0, UINT64_MAX);
+
+ /** @todo Do we need to have a sliding window here? */
+
+ for (size_t i = 0; i < cbRead; i += cbWindow) /** @todo Slow as heck, but works for now. */
+ {
+ bool const fIsSilence = PDMAudioPropsIsBufferSilence(&pToneParms->Props, (const uint8_t *)abBuf + i, cbWindow);
+ if (fIsSilence != fFindSilence)
+ {
+ AssertReturn(PDMAudioPropsIsSizeAligned(&pToneParms->Props, offFound), 0);
+ return offFound;
+ }
+ offFound += cbWindow;
+ }
+ }
+
+ return cbMax;
+}
+
+/**
+ * Generates a tag.
+ *
+ * @returns VBox status code.
+ * @param pszTag The output buffer.
+ * @param cbTag The size of the output buffer.
+ * AUDIOTEST_TAG_MAX is a good size.
+ */
+int AudioTestGenTag(char *pszTag, size_t cbTag)
+{
+ RTUUID UUID;
+ int rc = RTUuidCreate(&UUID);
+ AssertRCReturn(rc, rc);
+ rc = RTUuidToStr(&UUID, pszTag, cbTag);
+ AssertRCReturn(rc, rc);
+ return rc;
+}
+
+/**
+ * Return the tag to use in the given buffer, generating one if needed.
+ *
+ * @returns VBox status code.
+ * @param pszTag The output buffer.
+ * @param cbTag The size of the output buffer.
+ * AUDIOTEST_TAG_MAX is a good size.
+ * @param pszTagUser User specified tag, optional.
+ */
+static int audioTestCopyOrGenTag(char *pszTag, size_t cbTag, const char *pszTagUser)
+{
+ if (pszTagUser && *pszTagUser)
+ return RTStrCopy(pszTag, cbTag, pszTagUser);
+ return AudioTestGenTag(pszTag, cbTag);
+}
+
+
+/**
+ * Creates a new path (directory) for a specific audio test set tag.
+ *
+ * @returns VBox status code.
+ * @param pszPath On input, specifies the absolute base path where to create the test set path.
+ * On output this specifies the absolute path created.
+ * @param cbPath Size (in bytes) of \a pszPath.
+ * @param pszTag Tag to use for path creation.
+ *
+ * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
+ * on each call.
+ */
+int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszTag)
+{
+ char szTag[AUDIOTEST_TAG_MAX];
+ int rc = audioTestCopyOrGenTag(szTag, sizeof(szTag), pszTag);
+ AssertRCReturn(rc, rc);
+
+ char szName[RT_ELEMENTS(AUDIOTEST_PATH_PREFIX_STR) + AUDIOTEST_TAG_MAX + 4];
+ if (RTStrPrintf2(szName, sizeof(szName), "%s-%s", AUDIOTEST_PATH_PREFIX_STR, szTag) < 0)
+ AssertFailedReturn(VERR_BUFFER_OVERFLOW);
+
+ rc = RTPathAppend(pszPath, cbPath, szName);
+ AssertRCReturn(rc, rc);
+
+#ifndef DEBUG /* Makes debugging easier to have a deterministic directory. */
+ char szTime[64];
+ RTTIMESPEC time;
+ if (!RTTimeSpecToString(RTTimeNow(&time), szTime, sizeof(szTime)))
+ return VERR_BUFFER_UNDERFLOW;
+
+ /* Colons aren't allowed in windows filenames, so change to dashes. */
+ char *pszColon;
+ while ((pszColon = strchr(szTime, ':')) != NULL)
+ *pszColon = '-';
+
+ rc = RTPathAppend(pszPath, cbPath, szTime);
+ AssertRCReturn(rc, rc);
+#endif
+
+ return RTDirCreateFullPath(pszPath, RTFS_UNIX_IRWXU);
+}
+
+DECLINLINE(int) audioTestManifestWriteData(PAUDIOTESTSET pSet, const void *pvData, size_t cbData)
+{
+ /** @todo Use RTIniFileWrite once its implemented. */
+ return RTFileWrite(pSet->f.hFile, pvData, cbData, NULL);
+}
+
+/**
+ * Writes string data to a test set manifest.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to write manifest for.
+ * @param pszFormat Format string to write.
+ * @param args Variable arguments for \a pszFormat.
+ */
+static int audioTestManifestWriteV(PAUDIOTESTSET pSet, const char *pszFormat, va_list args)
+{
+ /** @todo r=bird: Use RTStrmOpen + RTStrmPrintf instead of this slow
+ * do-it-all-yourself stuff. */
+ char *psz = NULL;
+ if (RTStrAPrintfV(&psz, pszFormat, args) == -1)
+ return VERR_NO_MEMORY;
+ AssertPtrReturn(psz, VERR_NO_MEMORY);
+
+ int rc = audioTestManifestWriteData(pSet, psz, strlen(psz));
+ AssertRC(rc);
+
+ RTStrFree(psz);
+
+ return rc;
+}
+
+/**
+ * Writes a string to a test set manifest.
+ * Convenience function.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to write manifest for.
+ * @param pszFormat Format string to write.
+ * @param ... Variable arguments for \a pszFormat. Optional.
+ */
+static int audioTestManifestWrite(PAUDIOTESTSET pSet, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+
+ int rc = audioTestManifestWriteV(pSet, pszFormat, va);
+ AssertRC(rc);
+
+ va_end(va);
+
+ return rc;
+}
+
+/**
+ * Returns the current read/write offset (in bytes) of the opened manifest file.
+ *
+ * @returns Current read/write offset (in bytes).
+ * @param pSet Set to return offset for.
+ * Must have an opened manifest file.
+ */
+DECLINLINE(uint64_t) audioTestManifestGetOffsetAbs(PAUDIOTESTSET pSet)
+{
+ AssertReturn(RTFileIsValid(pSet->f.hFile), 0);
+ return RTFileTell(pSet->f.hFile);
+}
+
+/**
+ * Writes a section header to a test set manifest.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to write manifest for.
+ * @param pszSection Format string of section to write.
+ * @param ... Variable arguments for \a pszSection. Optional.
+ */
+static int audioTestManifestWriteSectionHdr(PAUDIOTESTSET pSet, const char *pszSection, ...)
+{
+ va_list va;
+ va_start(va, pszSection);
+
+ /** @todo Keep it as simple as possible for now. Improve this later. */
+ int rc = audioTestManifestWrite(pSet, "[%N]\n", pszSection, &va);
+
+ va_end(va);
+
+ return rc;
+}
+
+/**
+ * Initializes an audio test set, internal function.
+ *
+ * @param pSet Test set to initialize.
+ */
+static void audioTestSetInitInternal(PAUDIOTESTSET pSet)
+{
+ pSet->f.hFile = NIL_RTFILE;
+
+ RTListInit(&pSet->lstObj);
+ pSet->cObj = 0;
+
+ RTListInit(&pSet->lstTest);
+ pSet->cTests = 0;
+ pSet->cTestsRunning = 0;
+ pSet->offTestCount = 0;
+ pSet->pTestCur = NULL;
+ pSet->cObj = 0;
+ pSet->offObjCount = 0;
+ pSet->cTotalFailures = 0;
+}
+
+/**
+ * Returns whether a test set's manifest file is open (and thus ready) or not.
+ *
+ * @returns \c true if open (and ready), or \c false if not.
+ * @retval VERR_
+ * @param pSet Test set to return open status for.
+ */
+static bool audioTestManifestIsOpen(PAUDIOTESTSET pSet)
+{
+ if ( pSet->enmMode == AUDIOTESTSETMODE_TEST
+ && pSet->f.hFile != NIL_RTFILE)
+ return true;
+ else if ( pSet->enmMode == AUDIOTESTSETMODE_VERIFY
+ && pSet->f.hIniFile != NIL_RTINIFILE)
+ return true;
+
+ return false;
+}
+
+/**
+ * Initializes an audio test error description.
+ *
+ * @param pErr Test error description to initialize.
+ */
+static void audioTestErrorDescInit(PAUDIOTESTERRORDESC pErr)
+{
+ RTListInit(&pErr->List);
+ pErr->cErrors = 0;
+}
+
+/**
+ * Destroys an audio test error description.
+ *
+ * @param pErr Test error description to destroy.
+ */
+void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr)
+{
+ if (!pErr)
+ return;
+
+ PAUDIOTESTERRORENTRY pErrEntry, pErrEntryNext;
+ RTListForEachSafe(&pErr->List, pErrEntry, pErrEntryNext, AUDIOTESTERRORENTRY, Node)
+ {
+ RTListNodeRemove(&pErrEntry->Node);
+
+ RTMemFree(pErrEntry);
+ }
+
+ pErr->cErrors = 0;
+}
+
+/**
+ * Returns the the number of errors of an audio test error description.
+ *
+ * @returns Error count.
+ * @param pErr Test error description to return error count for.
+ */
+uint32_t AudioTestErrorDescCount(PCAUDIOTESTERRORDESC pErr)
+{
+ return pErr->cErrors;
+}
+
+/**
+ * Returns if an audio test error description contains any errors or not.
+ *
+ * @returns \c true if it contains errors, or \c false if not.
+ * @param pErr Test error description to return error status for.
+ */
+bool AudioTestErrorDescFailed(PCAUDIOTESTERRORDESC pErr)
+{
+ if (pErr->cErrors)
+ {
+ Assert(!RTListIsEmpty(&pErr->List));
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Adds a single error entry to an audio test error description, va_list version.
+ *
+ * @returns VBox status code.
+ * @param pErr Test error description to add entry for.
+ * @param idxTest Index of failing test (zero-based).
+ * @param rc Result code of entry to add.
+ * @param pszFormat Error description format string to add.
+ * @param va Optional format arguments of \a pszDesc to add.
+ */
+static int audioTestErrorDescAddV(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, int rc, const char *pszFormat, va_list va)
+{
+ PAUDIOTESTERRORENTRY pEntry = (PAUDIOTESTERRORENTRY)RTMemAlloc(sizeof(AUDIOTESTERRORENTRY));
+ AssertPtrReturn(pEntry, VERR_NO_MEMORY);
+
+ char *pszDescTmp;
+ if (RTStrAPrintfV(&pszDescTmp, pszFormat, va) < 0)
+ AssertFailedReturn(VERR_NO_MEMORY);
+
+ const ssize_t cch = RTStrPrintf2(pEntry->szDesc, sizeof(pEntry->szDesc), "Test #%RU32 %s: %s",
+ idxTest, RT_FAILURE(rc) ? "failed" : "info", pszDescTmp);
+ RTStrFree(pszDescTmp);
+ AssertReturn(cch > 0, VERR_BUFFER_OVERFLOW);
+
+ pEntry->rc = rc;
+
+ RTListAppend(&pErr->List, &pEntry->Node);
+
+ if (RT_FAILURE(rc))
+ pErr->cErrors++;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Adds a single error entry to an audio test error description.
+ *
+ * @returns VBox status code.
+ * @param pErr Test error description to add entry for.
+ * @param idxTest Index of failing test (zero-based).
+ * @param pszFormat Error description format string to add.
+ * @param ... Optional format arguments of \a pszDesc to add.
+ */
+static int audioTestErrorDescAddError(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+
+ int rc = audioTestErrorDescAddV(pErr, idxTest, VERR_GENERAL_FAILURE /** @todo Fudge! */, pszFormat, va);
+
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Adds a single info entry to an audio test error description, va_list version.
+ *
+ * @returns VBox status code.
+ * @param pErr Test error description to add entry for.
+ * @param idxTest Index of failing test (zero-based).
+ * @param pszFormat Error description format string to add.
+ * @param ... Optional format arguments of \a pszDesc to add.
+ */
+static int audioTestErrorDescAddInfo(PAUDIOTESTERRORDESC pErr, uint32_t idxTest, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+
+ int rc = audioTestErrorDescAddV(pErr, idxTest, VINF_SUCCESS, pszFormat, va);
+
+ va_end(va);
+ return rc;
+}
+
+#if 0
+static int audioTestErrorDescAddRc(PAUDIOTESTERRORDESC pErr, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+
+ int rc2 = audioTestErrorDescAddV(pErr, rc, pszFormat, va);
+
+ va_end(va);
+ return rc2;
+}
+#endif
+
+/**
+ * Retrieves the temporary directory.
+ *
+ * @returns VBox status code.
+ * @param pszPath Where to return the absolute path of the created directory on success.
+ * @param cbPath Size (in bytes) of \a pszPath.
+ */
+int AudioTestPathGetTemp(char *pszPath, size_t cbPath)
+{
+ int rc = RTEnvGetEx(RTENV_DEFAULT, "TESTBOX_PATH_SCRATCH", pszPath, cbPath, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = RTPathTemp(pszPath, cbPath);
+ AssertRCReturn(rc, rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Creates a new temporary directory with a specific (test) tag.
+ *
+ * @returns VBox status code.
+ * @param pszPath Where to return the absolute path of the created directory on success.
+ * @param cbPath Size (in bytes) of \a pszPath.
+ * @param pszTag Tag name to use for directory creation.
+ *
+ * @note Can be used multiple times with the same tag; a sub directory with an ISO time string will be used
+ * on each call.
+ */
+int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszTag)
+{
+ AssertReturn(pszTag && strlen(pszTag) <= AUDIOTEST_TAG_MAX, VERR_INVALID_PARAMETER);
+
+ char szTemp[RTPATH_MAX];
+ int rc = AudioTestPathGetTemp(szTemp, sizeof(szTemp));
+ AssertRCReturn(rc, rc);
+
+ rc = AudioTestPathCreate(szTemp, sizeof(szTemp), pszTag);
+ AssertRCReturn(rc, rc);
+
+ return RTStrCopy(pszPath, cbPath, szTemp);
+}
+
+/**
+ * Gets a value as string.
+ *
+ * @returns VBox status code.
+ * @param pObj Object handle to get value for.
+ * @param pszKey Key to get value from.
+ * @param pszVal Where to return the value on success.
+ * @param cbVal Size (in bytes) of \a pszVal.
+ */
+static int audioTestObjGetStr(PAUDIOTESTOBJINT pObj, const char *pszKey, char *pszVal, size_t cbVal)
+{
+ /** @todo For now we only support .INI-style files. */
+ AssertPtrReturn(pObj->pSet, VERR_WRONG_ORDER);
+ return RTIniFileQueryValue(pObj->pSet->f.hIniFile, pObj->szSec, pszKey, pszVal, cbVal, NULL);
+}
+
+/**
+ * Gets a value as boolean.
+ *
+ * @returns VBox status code.
+ * @param pObj Object handle to get value for.
+ * @param pszKey Key to get value from.
+ * @param pbVal Where to return the value on success.
+ */
+static int audioTestObjGetBool(PAUDIOTESTOBJINT pObj, const char *pszKey, bool *pbVal)
+{
+ char szVal[_1K];
+ int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc))
+ *pbVal = (RTStrICmp(szVal, "true") == 0)
+ || (RTStrICmp(szVal, "1") == 0) ? true : false;
+
+ return rc;
+}
+
+/**
+ * Gets a value as uint8_t.
+ *
+ * @returns VBox status code.
+ * @param pObj Object handle to get value for.
+ * @param pszKey Key to get value from.
+ * @param puVal Where to return the value on success.
+ */
+static int audioTestObjGetUInt8(PAUDIOTESTOBJINT pObj, const char *pszKey, uint8_t *puVal)
+{
+ char szVal[_1K];
+ int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc))
+ *puVal = RTStrToUInt8(szVal);
+
+ return rc;
+}
+
+/**
+ * Gets a value as uint32_t.
+ *
+ * @returns VBox status code.
+ * @param pObj Object handle to get value for.
+ * @param pszKey Key to get value from.
+ * @param puVal Where to return the value on success.
+ */
+static int audioTestObjGetUInt32(PAUDIOTESTOBJINT pObj, const char *pszKey, uint32_t *puVal)
+{
+ char szVal[_1K];
+ int rc = audioTestObjGetStr(pObj, pszKey, szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc))
+ *puVal = RTStrToUInt32(szVal);
+
+ return rc;
+}
+
+/**
+ * Returns the absolute path of a given audio test set object.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set the object contains.
+ * @param pszPathAbs Where to return the absolute path on success.
+ * @param cbPathAbs Size (in bytes) of \a pszPathAbs.
+ * @param pszObjName Name of the object to create absolute path for.
+ */
+DECLINLINE(int) audioTestSetGetObjPath(PAUDIOTESTSET pSet, char *pszPathAbs, size_t cbPathAbs, const char *pszObjName)
+{
+ 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;
+}
+
+/**
+ * Returns the total number of registered tests.
+ *
+ * @returns Total number of registered tests.
+ * @param pSet Test set to return value for.
+ */
+uint32_t AudioTestSetGetTestsTotal(PAUDIOTESTSET pSet)
+{
+ return pSet->cTests;
+}
+
+/**
+ * Returns the total number of (still) running tests.
+ *
+ * @returns Total number of (still) running tests.
+ * @param pSet Test set to return value for.
+ */
+uint32_t AudioTestSetGetTestsRunning(PAUDIOTESTSET pSet)
+{
+ return pSet->cTestsRunning;
+}
+
+/**
+ * Returns the total number of test failures occurred.
+ *
+ * @returns Total number of test failures occurred.
+ * @param pSet Test set to return value for.
+ */
+uint32_t AudioTestSetGetTotalFailures(PAUDIOTESTSET pSet)
+{
+ return pSet->cTotalFailures;
+}
+
+/**
+ * Creates a new audio test set.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to create.
+ * @param pszPath Where to store the set set data. If NULL, the
+ * temporary directory will be used.
+ * @param pszTag Tag name to use for this test set.
+ */
+int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag)
+{
+ audioTestSetInitInternal(pSet);
+
+ int rc = audioTestCopyOrGenTag(pSet->szTag, sizeof(pSet->szTag), pszTag);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Test set directory.
+ */
+ if (pszPath)
+ {
+ rc = RTPathAbs(pszPath, pSet->szPathAbs, sizeof(pSet->szPathAbs));
+ AssertRCReturn(rc, rc);
+
+ rc = AudioTestPathCreate(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
+ }
+ else
+ rc = AudioTestPathCreateTemp(pSet->szPathAbs, sizeof(pSet->szPathAbs), pSet->szTag);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the manifest file.
+ */
+ char szTmp[RTPATH_MAX];
+ rc = RTPathJoin(szTmp, sizeof(szTmp), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
+ AssertRCReturn(rc, rc);
+
+ rc = RTFileOpen(&pSet->f.hFile, szTmp, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWriteSectionHdr(pSet, "header");
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "magic=vkat_ini\n"); /* VKAT Manifest, .INI-style. */
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "ver=%d\n", AUDIOTEST_MANIFEST_VER);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tag=%s\n", pSet->szTag);
+ AssertRCReturn(rc, rc);
+
+ AssertCompile(sizeof(szTmp) > RTTIME_STR_LEN);
+ RTTIMESPEC Now;
+ rc = audioTestManifestWrite(pSet, "date_created=%s\n", RTTimeSpecToString(RTTimeNow(&Now), szTmp, sizeof(szTmp)));
+ AssertRCReturn(rc, rc);
+
+ RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
+ rc = audioTestManifestWrite(pSet, "os_product=%s\n", szTmp);
+ AssertRCReturn(rc, rc);
+
+ RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
+ rc = audioTestManifestWrite(pSet, "os_rel=%s\n", szTmp);
+ AssertRCReturn(rc, rc);
+
+ RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); /* do NOT return on failure. */
+ rc = audioTestManifestWrite(pSet, "os_ver=%s\n", szTmp);
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "vbox_ver=%s r%u %s (%s %s)\n",
+ VBOX_VERSION_STRING, RTBldCfgRevision(), RTBldCfgTargetDotArch(), __DATE__, __TIME__);
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "test_count=");
+ AssertRCReturn(rc, rc);
+ pSet->offTestCount = audioTestManifestGetOffsetAbs(pSet);
+ rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "obj_count=");
+ AssertRCReturn(rc, rc);
+ pSet->offObjCount = audioTestManifestGetOffsetAbs(pSet);
+ rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
+ AssertRCReturn(rc, rc);
+
+ pSet->enmMode = AUDIOTESTSETMODE_TEST;
+
+ return rc;
+}
+
+/**
+ * Destroys a test set.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to destroy.
+ */
+int AudioTestSetDestroy(PAUDIOTESTSET pSet)
+{
+ if (!pSet)
+ return VINF_SUCCESS;
+
+ /* No more validation (no / still running tests) here -- just pack all stuff we got so far
+ * and let the verification routine deal with it later. */
+
+ int rc = AudioTestSetClose(pSet);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PAUDIOTESTOBJINT pObj, pObjNext;
+ RTListForEachSafe(&pSet->lstObj, pObj, pObjNext, AUDIOTESTOBJINT, Node)
+ {
+ rc = audioTestObjClose(pObj);
+ 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);
+
+ Assert(pSet->cObj);
+ pSet->cObj--;
+ }
+ else
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSet->cObj == 0);
+
+ PAUDIOTESTENTRY pEntry, pEntryNext;
+ RTListForEachSafe(&pSet->lstTest, pEntry, pEntryNext, AUDIOTESTENTRY, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+
+ Assert(pSet->cTests);
+ pSet->cTests--;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSet->cTests == 0);
+
+ return rc;
+}
+
+/**
+ * Opens an existing audio test set.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to open.
+ * @param pszPath Absolute path of the test set to open.
+ */
+int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath)
+{
+ audioTestSetInitInternal(pSet);
+
+ char szManifest[RTPATH_MAX];
+ int rc = RTPathJoin(szManifest, sizeof(szManifest), pszPath, AUDIOTEST_MANIFEST_FILE_STR);
+ AssertRCReturn(rc, rc);
+
+ RTVFSFILE hVfsFile;
+ rc = RTVfsFileOpenNormal(szManifest, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, &hVfsFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = RTIniFileCreateFromVfsFile(&pSet->f.hIniFile, hVfsFile, RTINIFILE_F_READONLY);
+ RTVfsFileRelease(hVfsFile);
+ AssertRCReturn(rc, rc);
+
+ rc = RTStrCopy(pSet->szPathAbs, sizeof(pSet->szPathAbs), pszPath);
+ AssertRCReturn(rc, rc);
+
+ pSet->enmMode = AUDIOTESTSETMODE_VERIFY;
+
+ return rc;
+}
+
+/**
+ * Closes an opened audio test set.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to close.
+ */
+int AudioTestSetClose(PAUDIOTESTSET pSet)
+{
+ AssertPtrReturn(pSet, VERR_INVALID_POINTER);
+
+ if (!audioTestManifestIsOpen(pSet))
+ return VINF_SUCCESS;
+
+ int rc;
+
+ if (pSet->enmMode == AUDIOTESTSETMODE_TEST)
+ {
+ /* Update number of bound test objects. */
+ PAUDIOTESTENTRY pTest;
+ uint32_t cTests = 0;
+ RTListForEach(&pSet->lstTest, pTest, AUDIOTESTENTRY, Node)
+ {
+ rc = RTFileSeek(pSet->f.hFile, pTest->offObjCount, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "%04RU32", pTest->cObj);
+ AssertRCReturn(rc, rc);
+ cTests++; /* Sanity checking. */
+ }
+
+ AssertMsgReturn(pSet->cTests == cTests, ("Test count and list don't match"), VERR_INTERNAL_ERROR);
+
+ /*
+ * Update number of total objects.
+ */
+ rc = RTFileSeek(pSet->f.hFile, pSet->offObjCount, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cObj);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Update number of total tests.
+ */
+ rc = RTFileSeek(pSet->f.hFile, pSet->offTestCount, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "%04RU32", pSet->cTests);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Serialize all registered test objects.
+ */
+ rc = RTFileSeek(pSet->f.hFile, 0, RTFILE_SEEK_END, NULL);
+ AssertRCReturn(rc, rc);
+
+ PAUDIOTESTOBJINT pObj;
+ uint32_t cObj = 0;
+ RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJINT, Node)
+ {
+ /* First, close the object.
+ * This also does some needed finalization. */
+ rc = AudioTestObjClose(pObj);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "\n");
+ AssertRCReturn(rc, rc);
+ char szUuid[AUDIOTEST_MAX_SEC_LEN];
+ rc = RTUuidToStr(&pObj->Uuid, szUuid, sizeof(szUuid));
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWriteSectionHdr(pSet, "obj_%s", szUuid);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "obj_type=%RU32\n", pObj->enmType);
+ AssertRCReturn(rc, rc);
+ 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;
+ }
+ }
+
+ cObj++; /* Sanity checking. */
+ }
+
+ AssertMsgReturn(pSet->cObj == cObj, ("Object count and list don't match"), VERR_INTERNAL_ERROR);
+
+ int rc2 = RTFileClose(pSet->f.hFile);
+ if (RT_SUCCESS(rc2))
+ pSet->f.hFile = NIL_RTFILE;
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ else if (pSet->enmMode == AUDIOTESTSETMODE_VERIFY)
+ {
+ RTIniFileRelease(pSet->f.hIniFile);
+ pSet->f.hIniFile = NIL_RTINIFILE;
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
+
+ return rc;
+}
+
+/**
+ * Physically wipes all related test set files off the disk.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to wipe.
+ */
+int AudioTestSetWipe(PAUDIOTESTSET pSet)
+{
+ AssertPtrReturn(pSet, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ char szFilePath[RTPATH_MAX];
+
+ PAUDIOTESTOBJINT pObj;
+ RTListForEach(&pSet->lstObj, pObj, AUDIOTESTOBJINT, Node)
+ {
+ int rc2 = audioTestObjClose(pObj);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = audioTestSetGetObjPath(pSet, szFilePath, sizeof(szFilePath), pObj->szName);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTFileDelete(szFilePath);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathJoin(szFilePath, sizeof(szFilePath), pSet->szPathAbs, AUDIOTEST_MANIFEST_FILE_STR);
+ if (RT_SUCCESS(rc))
+ rc = RTFileDelete(szFilePath);
+ }
+
+ /* Remove the (hopefully now empty) directory. Otherwise let this fail. */
+ if (RT_SUCCESS(rc))
+ rc = RTDirRemove(pSet->szPathAbs);
+
+ return rc;
+}
+
+/**
+ * Creates and registers a new audio test object to the current running test.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to create and register new object for.
+ * @param pszName Name of new object to create.
+ * @param pObj Where to return the pointer to the newly created object on success.
+ */
+int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ pObj)
+{
+ AssertReturn(pSet->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
+
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ PAUDIOTESTOBJINT pThis = (PAUDIOTESTOBJINT)RTMemAlloc(sizeof(AUDIOTESTOBJINT));
+ AssertPtrReturn(pThis, VERR_NO_MEMORY);
+
+ audioTestObjInit(pThis);
+
+ if (RTStrPrintf2(pThis->szName, sizeof(pThis->szName), "%04RU32-%s", pSet->cObj, pszName) <= 0)
+ AssertFailedReturn(VERR_BUFFER_OVERFLOW);
+
+ /** @todo Generalize this function more once we have more object types. */
+
+ char szObjPathAbs[RTPATH_MAX];
+ int rc = audioTestSetGetObjPath(pSet, szObjPathAbs, sizeof(szObjPathAbs), pThis->szName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&pThis->File.hFile, szObjPathAbs, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->enmType = AUDIOTESTOBJTYPE_FILE;
+ pThis->cRefs = 1; /* Currently only 1:1 mapping. */
+
+ RTListAppend(&pSet->lstObj, &pThis->Node);
+ pSet->cObj++;
+
+ /* Generate + set an UUID for the object and assign it to the current test. */
+ rc = RTUuidCreate(&pThis->Uuid);
+ AssertRCReturn(rc, rc);
+ char szUuid[AUDIOTEST_MAX_OBJ_LEN];
+ rc = RTUuidToStr(&pThis->Uuid, szUuid, sizeof(szUuid));
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "obj%RU32_uuid=%s\n", pSet->pTestCur->cObj, szUuid);
+ AssertRCReturn(rc, rc);
+
+ AssertPtr(pSet->pTestCur);
+ pSet->pTestCur->cObj++;
+
+ *pObj = pThis;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pThis);
+
+ return rc;
+}
+
+/**
+ * Writes to a created audio test object.
+ *
+ * @returns VBox status code.
+ * @param hObj Handle to the audio test object to write to.
+ * @param pvBuf Pointer to data to write.
+ * @param cbBuf Size (in bytes) of \a pvBuf to write.
+ */
+int AudioTestObjWrite(AUDIOTESTOBJ hObj, const void *pvBuf, size_t cbBuf)
+{
+ AUDIOTESTOBJINT *pThis = hObj;
+
+ /** @todo Generalize this function more once we have more object types. */
+ AssertReturn(pThis->enmType == AUDIOTESTOBJTYPE_FILE, VERR_INVALID_PARAMETER);
+
+ return RTFileWrite(pThis->File.hFile, pvBuf, cbBuf, NULL);
+}
+
+/**
+ * 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 Variable arguments list to use for the format string.
+ */
+static int audioTestObjAddMetadataStrV(PAUDIOTESTOBJINT 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 hObj Handle to the test object to add meta data for.
+ * @param pszFormat Format string to add.
+ * @param ... Variable arguments for the format string.
+ */
+int AudioTestObjAddMetadataStr(AUDIOTESTOBJ hObj, const char *pszFormat, ...)
+{
+ AUDIOTESTOBJINT *pThis = hObj;
+
+ va_list va;
+
+ va_start(va, pszFormat);
+ int rc = audioTestObjAddMetadataStrV(pThis, pszFormat, va);
+ va_end(va);
+
+ return rc;
+}
+
+/**
+ * Closes an opened audio test object.
+ *
+ * @returns VBox status code.
+ * @param hObj Handle to the audio test object to close.
+ */
+int AudioTestObjClose(AUDIOTESTOBJ hObj)
+{
+ AUDIOTESTOBJINT *pThis = hObj;
+
+ if (!pThis)
+ return VINF_SUCCESS;
+
+ audioTestObjFinalize(pThis);
+
+ return audioTestObjClose(pThis);
+}
+
+/**
+ * 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
+ */
+int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry)
+{
+ AssertReturn(pSet->cTestsRunning == 0, VERR_WRONG_ORDER); /* No test nesting allowed. */
+
+ PAUDIOTESTENTRY pEntry = (PAUDIOTESTENTRY)RTMemAllocZ(sizeof(AUDIOTESTENTRY));
+ AssertPtrReturn(pEntry, VERR_NO_MEMORY);
+
+ int rc = RTStrCopy(pEntry->szDesc, sizeof(pEntry->szDesc), pszDesc);
+ AssertRCReturn(rc, rc);
+
+ memcpy(&pEntry->Parms, pParms, sizeof(AUDIOTESTPARMS));
+
+ pEntry->pParent = pSet;
+ pEntry->rc = VERR_IPE_UNINITIALIZED_STATUS;
+
+ rc = audioTestManifestWrite(pSet, "\n");
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWriteSectionHdr(pSet, "test_%04RU32", pSet->cTests);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "test_desc=%s\n", pszDesc);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "test_type=%RU32\n", pParms->enmType);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "test_delay_ms=%RU32\n", pParms->msDelay);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "audio_direction=%s\n", PDMAudioDirGetName(pParms->enmDir));
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestManifestWrite(pSet, "obj_count=");
+ AssertRCReturn(rc, rc);
+ pEntry->offObjCount = audioTestManifestGetOffsetAbs(pSet);
+ rc = audioTestManifestWrite(pSet, "0000\n"); /* A bit messy, but does the trick for now. */
+ AssertRCReturn(rc, rc);
+
+ switch (pParms->enmType)
+ {
+ case AUDIOTESTTYPE_TESTTONE_PLAY:
+ RT_FALL_THROUGH();
+ case AUDIOTESTTYPE_TESTTONE_RECORD:
+ {
+ rc = audioTestManifestWrite(pSet, "tone_freq_hz=%RU16\n", (uint16_t)pParms->TestTone.dbFreqHz);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_prequel_ms=%RU32\n", pParms->TestTone.msPrequel);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_duration_ms=%RU32\n", pParms->TestTone.msDuration);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_sequel_ms=%RU32\n", pParms->TestTone.msSequel);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_volume_percent=%RU32\n", pParms->TestTone.uVolumePercent);
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_pcm_hz=%RU32\n", PDMAudioPropsHz(&pParms->TestTone.Props));
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_pcm_channels=%RU8\n", PDMAudioPropsChannels(&pParms->TestTone.Props));
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_pcm_bits=%RU8\n", PDMAudioPropsSampleBits(&pParms->TestTone.Props));
+ AssertRCReturn(rc, rc);
+ rc = audioTestManifestWrite(pSet, "tone_pcm_is_signed=%RTbool\n", PDMAudioPropsIsSigned(&pParms->TestTone.Props));
+ AssertRCReturn(rc, rc);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ RTListAppend(&pSet->lstTest, &pEntry->Node);
+
+ pSet->cTests++;
+ pSet->cTestsRunning++;
+ pSet->pTestCur = pEntry;
+
+ *ppEntry = pEntry;
+
+ return rc;
+}
+
+/**
+ * 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)
+{
+ AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
+ AssertReturn(pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS, VERR_WRONG_ORDER);
+
+ pEntry->rc = rc;
+
+ int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", rc);
+ AssertRCReturn(rc2, rc2);
+ rc2 = audioTestManifestWrite(pEntry->pParent, "error_desc=%s\n", pszErr);
+ AssertRCReturn(rc2, rc2);
+
+ pEntry->pParent->cTestsRunning--;
+ pEntry->pParent->pTestCur = NULL;
+
+ return rc2;
+}
+
+/**
+ * Marks a running test as successfully done.
+ *
+ * @returns VBox status code.
+ * @param pEntry Test to mark.
+ */
+int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry)
+{
+ AssertReturn(pEntry->pParent->cTestsRunning == 1, VERR_WRONG_ORDER); /* No test nesting allowed. */
+ AssertReturn(pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS, VERR_WRONG_ORDER);
+
+ pEntry->rc = VINF_SUCCESS;
+
+ int rc2 = audioTestManifestWrite(pEntry->pParent, "error_rc=%RI32\n", VINF_SUCCESS);
+ AssertRCReturn(rc2, rc2);
+
+ pEntry->pParent->cTestsRunning--;
+ pEntry->pParent->pTestCur = NULL;
+
+ return rc2;
+}
+
+/**
+ * Returns whether a test is still running or not.
+ *
+ * @returns \c true if test is still running, or \c false if not.
+ * @param pEntry Test to get running status for.
+ */
+bool AudioTestSetTestIsRunning(PAUDIOTESTENTRY pEntry)
+{
+ return (pEntry->rc == VERR_IPE_UNINITIALIZED_STATUS);
+}
+
+/**
+ * Packs a closed audio test so that it's ready for transmission.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set to pack.
+ * @param pszOutDir Directory where to store the packed test set.
+ * @param pszFileName Where to return the final name of the packed test set. Optional and can be NULL.
+ * @param cbFileName Size (in bytes) of \a pszFileName.
+ */
+int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName)
+{
+ AssertPtrReturn(pSet, VERR_INVALID_POINTER);
+ AssertReturn(!pszFileName || cbFileName, VERR_INVALID_PARAMETER);
+ AssertReturn(!audioTestManifestIsOpen(pSet), VERR_WRONG_ORDER);
+
+ /* No more validation (no / still running tests) here -- just pack all stuff we got so far
+ * and let the verification routine deal with it later. */
+
+ /** @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",
+ AUDIOTEST_PATH_PREFIX_STR, pSet->szTag, AUDIOTEST_ARCHIVE_SUFF_STR) <= 0)
+ AssertFailedReturn(VERR_BUFFER_OVERFLOW);
+
+ char szOutPath[RTPATH_MAX];
+ rc = RTPathJoin(szOutPath, sizeof(szOutPath), pszOutDir, szOutName);
+ AssertRCReturn(rc, rc);
+
+ const char *apszArgs[10];
+ unsigned cArgs = 0;
+
+ apszArgs[cArgs++] = "vkat";
+ apszArgs[cArgs++] = "--create";
+ apszArgs[cArgs++] = "--gzip";
+ apszArgs[cArgs++] = "--directory";
+ apszArgs[cArgs++] = pSet->szPathAbs;
+ apszArgs[cArgs++] = "--file";
+ apszArgs[cArgs++] = szOutPath;
+ apszArgs[cArgs++] = ".";
+
+ RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pszFileName)
+ rc = RTStrCopy(pszFileName, cbFileName, szOutPath);
+ }
+
+ return rc;
+}
+
+/**
+ * Returns whether a test set archive is packed (as .tar.gz by default) or
+ * a plain directory.
+ *
+ * @returns \c true if packed (as .tar.gz), or \c false if not (directory).
+ * @param pszPath Path to return packed staus for.
+ */
+bool AudioTestSetIsPacked(const char *pszPath)
+{
+ /** @todo Improve this, good enough for now. */
+ return (RTStrIStr(pszPath, AUDIOTEST_ARCHIVE_SUFF_STR) != NULL);
+}
+
+/**
+ * Returns whether a test set has running (active) tests or not.
+ *
+ * @returns \c true if it has running tests, or \c false if not.
+ * @param pSet Test set to return status for.
+ */
+bool AudioTestSetIsRunning(PAUDIOTESTSET pSet)
+{
+ return (pSet->cTestsRunning > 0);
+}
+
+/**
+ * Unpacks a formerly packed audio test set.
+ *
+ * @returns VBox status code.
+ * @param pszFile Test set file to unpack. Must contain the absolute path.
+ * @param pszOutDir Directory where to unpack the test set into.
+ * If the directory does not exist it will be created.
+ */
+int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir)
+{
+ AssertReturn(pszFile && pszOutDir, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ if (!RTDirExists(pszOutDir))
+ {
+ rc = RTDirCreateFullPath(pszOutDir, 0755);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ const char *apszArgs[8];
+ unsigned cArgs = 0;
+
+ apszArgs[cArgs++] = "vkat";
+ apszArgs[cArgs++] = "--extract";
+ apszArgs[cArgs++] = "--gunzip";
+ apszArgs[cArgs++] = "--directory";
+ apszArgs[cArgs++] = pszOutDir;
+ apszArgs[cArgs++] = "--file";
+ apszArgs[cArgs++] = pszFile;
+
+ RTEXITCODE rcExit = RTZipTarCmd(cArgs, (char **)apszArgs);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ return rc;
+}
+
+/**
+ * Retrieves an object handle of a specific test set section.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set the section contains.
+ * @param pszSec Name of section to retrieve object handle for.
+ * @param phSec Where to store the object handle on success.
+ */
+static int audioTestSetGetSection(PAUDIOTESTSET pSet, const char *pszSec, PAUDIOTESTOBJINT phSec)
+{
+ int rc = RTStrCopy(phSec->szSec, sizeof(phSec->szSec), pszSec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ phSec->pSet = pSet;
+
+ /** @todo Check for section existence. */
+ RT_NOREF(pSet);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves an object handle of a specific test.
+ *
+ * @returns VBox status code.
+ * @param pSet Test set the test contains.
+ * @param idxTst Index of test to retrieve the object handle for.
+ * @param phTst Where to store the object handle on success.
+ */
+static int audioTestSetGetTest(PAUDIOTESTSET pSet, uint32_t idxTst, PAUDIOTESTOBJINT phTst)
+{
+ char szSec[AUDIOTEST_MAX_SEC_LEN];
+ if (RTStrPrintf2(szSec, sizeof(szSec), "test_%04RU32", idxTst) <= 0)
+ return VERR_BUFFER_OVERFLOW;
+
+ return audioTestSetGetSection(pSet, szSec, phTst);
+}
+
+/**
+ * Initializes a test object.
+ *
+ * @param pObj Object to initialize.
+ */
+static void audioTestObjInit(PAUDIOTESTOBJINT pObj)
+{
+ RT_BZERO(pObj, sizeof(AUDIOTESTOBJINT));
+
+ pObj->cRefs = 1;
+
+ RTListInit(&pObj->lstMeta);
+}
+
+/**
+ * Retrieves a child object of a specific parent object.
+ *
+ * @returns VBox status code.
+ * @param pParent Parent object the child object contains.
+ * @param idxObj Index of object to retrieve the object handle for.
+ * @param pObj Where to store the object handle on success.
+ */
+static int audioTestObjGetChild(PAUDIOTESTOBJINT pParent, uint32_t idxObj, PAUDIOTESTOBJINT pObj)
+{
+ char szObj[AUDIOTEST_MAX_SEC_LEN];
+ if (RTStrPrintf2(szObj, sizeof(szObj), "obj%RU32_uuid", idxObj) <= 0)
+ AssertFailedReturn(VERR_BUFFER_OVERFLOW);
+
+ char szUuid[AUDIOTEST_MAX_SEC_LEN];
+ int rc = audioTestObjGetStr(pParent, szObj, szUuid, sizeof(szUuid));
+ if (RT_SUCCESS(rc))
+ {
+ audioTestObjInit(pObj);
+
+ AssertReturn(RTStrPrintf2(pObj->szSec, sizeof(pObj->szSec), "obj_%s", szUuid) > 0, VERR_BUFFER_OVERFLOW);
+
+ /** @todo Check test section existence. */
+
+ pObj->pSet = pParent->pSet;
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies a value of a test verification job.
+ *
+ * @returns VBox status code.
+ * @returns Error if the verification failed and test verification job has fKeepGoing not set.
+ * @param pVerJob Verification job to verify value for.
+ * @param pObjA Object handle A to verify value for.
+ * @param pObjB Object handle B to verify value for.
+ * @param pszKey Key to verify.
+ * @param pszVal Value to verify.
+ * @param pszErrFmt Error format string in case the verification failed.
+ * @param ... Variable aruments for error format string.
+ */
+static int audioTestVerifyValue(PAUDIOTESTVERIFYJOB pVerJob,
+ PAUDIOTESTOBJINT pObjA, PAUDIOTESTOBJINT pObjB, const char *pszKey, const char *pszVal, const char *pszErrFmt, ...)
+{
+ va_list va;
+ va_start(va, pszErrFmt);
+
+ char szValA[_1K];
+ int rc = audioTestObjGetStr(pObjA, pszKey, szValA, sizeof(szValA));
+ if (RT_SUCCESS(rc))
+ {
+ char szValB[_1K];
+ rc = audioTestObjGetStr(pObjB, pszKey, szValB, sizeof(szValB));
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrCmp(szValA, szValB))
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
+ "Values are not equal ('%s' vs. '%s')", szValA, szValB);
+ AssertRC(rc2);
+ rc = VERR_WRONG_TYPE; /** @todo Fudge! */
+ }
+
+ if (pszVal)
+ {
+ if (RTStrCmp(szValA, pszVal))
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
+ "Values don't match expected value (got '%s', expected '%s')", szValA, pszVal);
+ AssertRC(rc2);
+ rc = VERR_WRONG_TYPE; /** @todo Fudge! */
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = audioTestErrorDescAddV(pVerJob->pErr, pVerJob->idxTest, rc, pszErrFmt, va);
+ AssertRC(rc2);
+ }
+
+ va_end(va);
+
+ return pVerJob->Opts.fKeepGoing ? VINF_SUCCESS : rc;
+}
+
+/**
+ * Opens a test object which is a regular file.
+ *
+ * @returns VBox status code.
+ * @param pObj Test object to open.
+ * @param pszFile Absolute file path of file to open.
+ */
+static int audioTestObjOpenFile(PAUDIOTESTOBJINT pObj, const char *pszFile)
+{
+ int rc = RTFileOpen(&pObj->File.hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = RTStrCopy(pObj->szName, sizeof(pObj->szName), pszFile);
+ AssertRC(rc2);
+
+ pObj->enmType = AUDIOTESTOBJTYPE_FILE;
+ }
+
+ return rc;
+}
+
+/**
+ * Opens an existing audio test object.
+ *
+ * @returns VBox status code.
+ * @param pObj Object to open.
+ */
+static int audioTestObjOpen(PAUDIOTESTOBJINT pObj)
+{
+ AssertReturn(pObj->enmType == AUDIOTESTOBJTYPE_UNKNOWN, VERR_WRONG_ORDER);
+
+ char szFileName[AUDIOTEST_MAX_SEC_LEN];
+ int rc = audioTestObjGetStr(pObj, "obj_name", szFileName, sizeof(szFileName));
+ if (RT_SUCCESS(rc))
+ {
+ char szFilePath[RTPATH_MAX];
+ rc = RTPathJoin(szFilePath, sizeof(szFilePath), pObj->pSet->szPathAbs, szFileName);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Check "obj_type". */
+ rc = audioTestObjOpenFile(pObj, szFilePath);
+ }
+ }
+ return rc;
+}
+
+/**
+ * Closes an audio test set object.
+ *
+ * @returns VBox status code.
+ * @param pObj Object to close.
+ */
+static int audioTestObjClose(PAUDIOTESTOBJINT pObj)
+{
+ if (!audioTestObjIsOpen(pObj))
+ return VINF_SUCCESS;
+
+ int rc;
+
+ /** @todo Generalize this function more once we have more object types. */
+
+ if (RTFileIsValid(pObj->File.hFile))
+ {
+ rc = RTFileClose(pObj->File.hFile);
+ if (RT_SUCCESS(rc))
+ pObj->File.hFile = NIL_RTFILE;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * Returns whether a test set object is in opened state or not.
+ *
+ * @returns \c true if open, or \c false if not.
+ * @param pObj Object to return status for.
+ */
+static bool audioTestObjIsOpen(PAUDIOTESTOBJINT pObj)
+{
+ return pObj->enmType != AUDIOTESTOBJTYPE_UNKNOWN;
+}
+
+/**
+ * Finalizes an audio test set object.
+ *
+ * @param pObj Test object to finalize.
+ */
+static void audioTestObjFinalize(PAUDIOTESTOBJINT pObj)
+{
+ /** @todo Generalize this function more once we have more object types. */
+ AssertReturnVoid(pObj->enmType == AUDIOTESTOBJTYPE_FILE);
+
+ if (RTFileIsValid(pObj->File.hFile))
+ pObj->File.cbSize = RTFileTell(pObj->File.hFile);
+}
+
+/**
+ * Retrieves tone PCM properties of an object.
+ *
+ * @returns VBox status code.
+ * @param pObj Object to retrieve PCM properties for.
+ * @param pProps Where to store the PCM properties on success.
+ */
+static int audioTestObjGetTonePcmProps(PAUDIOTESTOBJINT pObj, PPDMAUDIOPCMPROPS pProps)
+{
+ int rc;
+ uint32_t uHz;
+ rc = audioTestObjGetUInt32(pObj, "tone_pcm_hz", &uHz);
+ AssertRCReturn(rc, rc);
+ uint8_t cBits;
+ rc = audioTestObjGetUInt8(pObj, "tone_pcm_bits", &cBits);
+ AssertRCReturn(rc, rc);
+ uint8_t cChan;
+ rc = audioTestObjGetUInt8(pObj, "tone_pcm_channels", &cChan);
+ AssertRCReturn(rc, rc);
+ bool fSigned;
+ rc = audioTestObjGetBool(pObj, "tone_pcm_is_signed", &fSigned);
+ AssertRCReturn(rc, rc);
+
+ PDMAudioPropsInit(pProps, (cBits / 8), fSigned, cChan, uHz);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Normalizes PCM audio data.
+ * Only supports 16 bit stereo PCM data for now.
+ *
+ * @returns VBox status code.
+ * @param hFileSrc Source file handle of audio data to normalize.
+ * @param pProps PCM properties to use for normalization.
+ * @param cbSize Size (in bytes) of audio data to normalize.
+ * @param dbNormalizePercent Normalization (percent) to achieve.
+ * @param hFileDst Destiation file handle (must be open) where to write the normalized audio data to.
+ * @param pdbRatio Where to store the normalization ratio used on success. Optional and can be NULL.
+ * A ration of exactly 1 means no normalization.
+ *
+ * @note The source file handle must point at the beginning of the PCM audio data to normalize.
+ */
+static int audioTestFileNormalizePCM(RTFILE hFileSrc, PCPDMAUDIOPCMPROPS pProps, uint64_t cbSize,
+ double dbNormalizePercent, RTFILE hFileDst, double *pdbRatio)
+{
+ if ( !pProps->fSigned
+ || pProps->cbSampleX != 2) /* Fend-off non-supported stuff first. */
+ return VERR_NOT_SUPPORTED;
+
+ int rc = VINF_SUCCESS; /* Shut up MSVC. */
+
+ if (!cbSize)
+ {
+ rc = RTFileQuerySize(hFileSrc, &cbSize);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ AssertReturn(PDMAudioPropsIsSizeAligned(pProps, (uint32_t)cbSize), VERR_INVALID_PARAMETER);
+
+ uint64_t offStart = RTFileTell(hFileSrc);
+ size_t cbToRead = cbSize;
+
+ /* Find minimum and maximum peaks. */
+ int16_t iMin = 0;
+ int16_t iMax = 0;
+ double dbRatio = 0.0;
+
+ uint8_t auBuf[_64K];
+ while (cbToRead)
+ {
+ size_t const cbChunk = RT_MIN(cbToRead, sizeof(auBuf));
+ size_t cbRead = 0;
+ rc = RTFileRead(hFileSrc, auBuf, cbChunk, &cbRead);
+ if (rc == VERR_EOF)
+ break;
+ AssertRCBreak(rc);
+
+ AssertBreak(PDMAudioPropsIsSizeAligned(pProps, (uint32_t)cbRead));
+
+ switch (pProps->cbSampleX)
+ {
+ case 2: /* 16 bit signed */
+ {
+ int16_t *pi16Src = (int16_t *)auBuf;
+ for (size_t i = 0; i < cbRead / pProps->cbSampleX; i += pProps->cbSampleX)
+ {
+ if (*pi16Src < iMin)
+ iMin = *pi16Src;
+ if (*pi16Src > iMax)
+ iMax = *pi16Src;
+ pi16Src++;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailedBreakStmt(("Invalid bytes per sample: %RU8\n", pProps->cbSampleX), rc = VERR_NOT_SUPPORTED);
+ }
+
+ Assert(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Now rewind and do the actual gain / attenuation. */
+ rc = RTFileSeek(hFileSrc, offStart, RTFILE_SEEK_BEGIN, NULL /* poffActual */);
+ AssertRCReturn(rc, rc);
+ cbToRead = cbSize;
+
+ switch (pProps->cbSampleX)
+ {
+ case 2: /* 16 bit signed */
+ {
+ if (iMin == INT16_MIN)
+ iMin = INT16_MIN + 1;
+ if ((-iMin) > iMax)
+ iMax = -iMin;
+
+ dbRatio = iMax == 0 ? 1.0 : ((double)INT16_MAX * dbNormalizePercent) / ((double)iMax * 100.0);
+
+ while (cbToRead)
+ {
+ size_t const cbChunk = RT_MIN(cbToRead, sizeof(auBuf));
+ size_t cbRead;
+ rc = RTFileRead(hFileSrc, auBuf, cbChunk, &cbRead);
+ if (rc == VERR_EOF)
+ break;
+ AssertRCBreak(rc);
+
+ int16_t *pi16Src = (int16_t *)auBuf;
+ for (size_t i = 0; i < cbRead / pProps->cbSampleX; i += pProps->cbSampleX)
+ {
+ /** @todo Optimize this -- use a lookup table for sample indices? */
+ if ((*pi16Src * dbRatio) > INT16_MAX)
+ *pi16Src = INT16_MAX;
+ else if ((*pi16Src * dbRatio) < INT16_MIN)
+ *pi16Src = INT16_MIN;
+ else
+ *pi16Src = (int16_t)(*pi16Src * dbRatio);
+ pi16Src++;
+ }
+
+ size_t cbWritten;
+ rc = RTFileWrite(hFileDst, auBuf, cbChunk, &cbWritten);
+ AssertRCBreak(rc);
+ Assert(cbWritten == cbChunk);
+
+ Assert(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailedBreakStmt(("Invalid bytes per sample: %RU8\n", pProps->cbSampleX), rc = VERR_NOT_SUPPORTED);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pdbRatio)
+ *pdbRatio = dbRatio;
+ }
+
+ return rc;
+}
+
+/**
+ * Normalizes a test set audio object's audio data, extended version.
+ *
+ * @returns VBox status code. On success the test set object will point to the (temporary) normalized file data.
+ * @param pVerJob Verification job that contains \a pObj.
+ * @param pObj Test set object to normalize.
+ * @param pProps PCM properties to use for normalization.
+ * @param cbSize Size (in bytes) of audio data to normalize.
+ * @param dbNormalizePercent Normalization to achieve (in percent).
+ *
+ * @note The test set's file pointer must point to beginning of PCM data to normalize.
+ */
+static int audioTestObjFileNormalizeEx(PAUDIOTESTVERIFYJOB pVerJob,
+ PAUDIOTESTOBJINT pObj, PPDMAUDIOPCMPROPS pProps, uint64_t cbSize, double dbNormalizePercent)
+{
+ /* Store normalized file into a temporary file. */
+ char szFileDst[RTPATH_MAX];
+ int rc = RTPathTemp(szFileDst, sizeof(szFileDst));
+ AssertRCReturn(rc, rc);
+
+ rc = RTPathAppend(szFileDst, sizeof(szFileDst), "VBoxAudioTest-normalized-XXX.pcm");
+ AssertRCReturn(rc, rc);
+
+ rc = RTFileCreateTemp(szFileDst, 0600);
+ AssertRCReturn(rc, rc);
+
+ RTFILE hFileDst;
+ rc = RTFileOpen(&hFileDst, szFileDst, RTFILE_O_OPEN | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ AssertRCReturn(rc, rc);
+
+ double dbRatio = 0.0;
+ rc = audioTestFileNormalizePCM(pObj->File.hFile, pProps, cbSize, dbNormalizePercent, hFileDst, &dbRatio);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Normalized '%s' -> '%s' (ratio is %u.%02u%%)\n",
+ pObj->szName, szFileDst, (unsigned)dbRatio, (unsigned)(dbRatio * 100) % 100);
+ AssertRC(rc2);
+ }
+
+ int rc2 = RTFileClose(hFileDst);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Close the original test set object and use the (temporary) normalized file instead now. */
+ rc = audioTestObjClose(pObj);
+ if (RT_SUCCESS(rc))
+ rc = audioTestObjOpenFile(pObj, szFileDst);
+ }
+
+ return rc;
+}
+
+/**
+ * Normalizes a test set audio object's audio data.
+ *
+ * @returns VBox status code.
+ * @param pVerJob Verification job that contains \a pObj.
+ * @param pObj Test set object to normalize.
+ * @param pProps PCM properties to use for normalization.
+ *
+ * @note The test set's file pointer must point to beginning of PCM data to normalize.
+ */
+static int audioTestObjFileNormalize(PAUDIOTESTVERIFYJOB pVerJob, PAUDIOTESTOBJINT pObj, PPDMAUDIOPCMPROPS pProps)
+{
+ return audioTestObjFileNormalizeEx(pVerJob,
+ pObj, pProps, 0 /* cbSize, 0 means all */, 100.0 /* dbNormalizePercent */);
+}
+
+/**
+ * Structure for keeping file comparison parameters for one file.
+ */
+typedef struct AUDIOTESTFILECMPPARMS
+{
+ /** File name for logging purposes. */
+ const char *pszName;
+ /** File handle to file to compare. */
+ RTFILE hFile;
+ /** Absolute offset (in bytes) to start comparing.
+ * Ignored when set to 0. */
+ uint64_t offStart;
+ /** Size (in bytes) of area to compare.
+ * Starts at \a offStart. */
+ uint64_t cbSize;
+} AUDIOTESTFILECMPPARMS;
+/** Pointer to file comparison parameters for one file. */
+typedef AUDIOTESTFILECMPPARMS *PAUDIOTESTFILECMPPARMS;
+
+/**
+ * Determines if a given file chunk contains all silence (i.e. non-audible audio data) or not.
+ *
+ * What "silence" means depends on the given PCM parameters.
+ *
+ * @returns VBox status code.
+ * @param phFile File handle of file to determine silence for.
+ * @param pProps PCM properties to use.
+ * @param offStart Start offset (absolute, in bytes) to start at.
+ * @param cbSize Size (in bytes) to process.
+ * @param pfIsSilence Where to return the result.
+ *
+ * @note Does *not* modify the file's current position.
+ */
+static int audioTestFileChunkIsSilence(PRTFILE phFile, PPDMAUDIOPCMPROPS pProps, uint64_t offStart, size_t cbSize,
+ bool *pfIsSilence)
+{
+ bool fIsSilence = true;
+
+ int rc = RTFileSeek(*phFile, offStart, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, rc);
+
+ uint8_t auBuf[_64K];
+ while (cbSize)
+ {
+ size_t cbRead;
+ rc = RTFileRead(*phFile, auBuf, RT_MIN(cbSize, sizeof(auBuf)), &cbRead);
+ AssertRC(rc);
+
+ if (!PDMAudioPropsIsBufferSilence(pProps, auBuf, cbRead))
+ {
+ fIsSilence = false;
+ break;
+ }
+
+ AssertBreak(cbSize >= cbRead);
+ cbSize -= cbRead;
+ }
+
+ if (RT_SUCCESS(rc))
+ *pfIsSilence = fIsSilence;
+
+ return RTFileSeek(*phFile, offStart, RTFILE_SEEK_BEGIN, NULL);
+}
+
+/**
+ * Finds differences in two audio test files by binary comparing chunks.
+ *
+ * @returns Number of differences. 0 means they are equal (but not necessarily identical).
+ * @param pVerJob Verification job to verify PCM data for.
+ * @param pCmpA File comparison parameters to file A to compare file B with.
+ * @param pCmpB File comparison parameters to file B to compare file A with.
+ * @param pToneParms Tone parameters to use for comparison.
+ */
+static uint32_t audioTestFilesFindDiffsBinary(PAUDIOTESTVERIFYJOB pVerJob,
+ PAUDIOTESTFILECMPPARMS pCmpA, PAUDIOTESTFILECMPPARMS pCmpB,
+ PAUDIOTESTTONEPARMS pToneParms)
+{
+ uint8_t auBufA[_4K];
+ uint8_t auBufB[_4K];
+
+ int rc = RTFileSeek(pCmpA->hFile, pCmpA->offStart, RTFILE_SEEK_BEGIN, NULL);
+ AssertRC(rc);
+
+ rc = RTFileSeek(pCmpB->hFile, pCmpB->offStart, RTFILE_SEEK_BEGIN, NULL);
+ AssertRC(rc);
+
+ uint32_t cDiffs = 0;
+ uint64_t cbDiffs = 0;
+
+ uint32_t const cbChunkSize = PDMAudioPropsFrameSize(&pToneParms->Props); /* Use the audio frame size as chunk size. */
+
+ uint64_t offCur = 0;
+ uint64_t offDiffStart = 0;
+ bool fInDiff = false;
+ uint64_t cbSize = RT_MIN(pCmpA->cbSize, pCmpB->cbSize);
+ uint64_t cbToCompare = cbSize;
+
+ while (cbToCompare)
+ {
+ size_t cbReadA;
+ rc = RTFileRead(pCmpA->hFile, auBufA, RT_MIN(cbToCompare, cbChunkSize), &cbReadA);
+ AssertRCBreak(rc);
+ size_t cbReadB;
+ rc = RTFileRead(pCmpB->hFile, auBufB, RT_MIN(cbToCompare, cbChunkSize), &cbReadB);
+ AssertRCBreak(rc);
+ AssertBreakStmt(cbReadA == cbReadB, rc = VERR_INVALID_PARAMETER); /** @todo Find a better rc. */
+
+ const size_t cbToCmp = RT_MIN(cbReadA, cbReadB);
+ if (memcmp(auBufA, auBufB, cbToCmp) != 0)
+ {
+ if (!fInDiff) /* No consequitive different chunk? Count as new then. */
+ {
+ cDiffs++;
+ offDiffStart = offCur;
+ fInDiff = true;
+ }
+ }
+ else /* Reset and count next difference as new then. */
+ {
+ if (fInDiff)
+ {
+ bool fIsAllSilenceA;
+ rc = audioTestFileChunkIsSilence(&pCmpA->hFile, &pToneParms->Props,
+ pCmpA->offStart + offDiffStart, offCur - offDiffStart, &fIsAllSilenceA);
+ AssertRCBreak(rc);
+
+ bool fIsAllSilenceB;
+ rc = audioTestFileChunkIsSilence(&pCmpB->hFile, &pToneParms->Props,
+ pCmpB->offStart + offDiffStart, offCur - offDiffStart, &fIsAllSilenceB);
+ AssertRCBreak(rc);
+
+ uint32_t const cbDiff = offCur - offDiffStart;
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Chunks differ: '%s' @ %#x [%08RU64-%08RU64] vs. '%s' @ %#x [%08RU64-%08RU64] (%RU64 bytes, %RU64ms)",
+ pCmpA->pszName, pCmpA->offStart + offDiffStart, pCmpA->offStart + offDiffStart, pCmpA->offStart + offCur,
+ pCmpB->pszName, pCmpB->offStart + offDiffStart, pCmpB->offStart + offDiffStart, pCmpB->offStart + offCur,
+ cbDiff, PDMAudioPropsBytesToMilli(&pToneParms->Props, cbDiff));
+ AssertRC(rc2);
+ if ( fIsAllSilenceA
+ || fIsAllSilenceB)
+ {
+ rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Chunk %s @ %#x (%RU64 bytes, %RU64ms) is all silence",
+ fIsAllSilenceA ? pCmpA->pszName : pCmpB->pszName,
+ offDiffStart, cbDiff, PDMAudioPropsBytesToMilli(&pToneParms->Props, cbDiff));
+ AssertRC(rc2);
+ }
+
+ cbDiffs += cbDiff;
+ }
+ fInDiff = false;
+ }
+
+ AssertBreakStmt(cbToCompare >= cbReadA, VERR_INTERNAL_ERROR);
+ cbToCompare -= cbReadA;
+ offCur += cbReadA;
+ }
+
+ /* If we didn't mention the last diff yet, do so now. */
+ if (fInDiff)
+ {
+ uint32_t const cbDiff = offCur - offDiffStart;
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Chunks differ: '%s' @ %#x [%08RU64-%08RU64] vs. '%s' @ %#x [%08RU64-%08RU64] (%RU64 bytes, %RU64ms)",
+ pCmpA->pszName, pCmpA->offStart + offDiffStart, pCmpA->offStart + offDiffStart, pCmpA->offStart + offCur,
+ pCmpB->pszName, pCmpB->offStart + offDiffStart, pCmpB->offStart + offDiffStart, pCmpB->offStart + offCur,
+ cbDiff, PDMAudioPropsBytesToMilli(&pToneParms->Props, cbDiff));
+ AssertRC(rc2);
+
+ cbDiffs += cbDiff;
+ }
+
+ if ( cbSize
+ && cbDiffs)
+ {
+ uint8_t const uDiffPercent = cbDiffs / (cbSize * 100);
+ if (uDiffPercent > pVerJob->Opts.uMaxDiffPercent)
+ {
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Files binary-differ too much (expected maximum %RU8%%, got %RU8%%)",
+ pVerJob->Opts.uMaxDiffPercent, uDiffPercent);
+ AssertRC(rc2);
+ }
+ }
+
+ return cDiffs;
+}
+
+/**
+ * Initializes a audio test audio beacon.
+ *
+ * @param pBeacon Audio test beacon to (re-)initialize.
+ * @param uTest Test number to set beacon to.
+ * @param enmType Beacon type to set.
+ * @param pProps PCM properties to use for producing audio beacon data.
+ */
+void AudioTestBeaconInit(PAUDIOTESTTONEBEACON pBeacon, uint8_t uTest, AUDIOTESTTONEBEACONTYPE enmType, PPDMAUDIOPCMPROPS pProps)
+{
+ AssertReturnVoid(PDMAudioPropsFrameSize(pProps) == 4); /** @todo Make this more dynamic. */
+
+ RT_BZERO(pBeacon, sizeof(AUDIOTESTTONEBEACON));
+
+ pBeacon->uTest = uTest;
+ pBeacon->enmType = enmType;
+ memcpy(&pBeacon->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
+
+ pBeacon->cbSize = PDMAudioPropsFramesToBytes(&pBeacon->Props, AUDIOTEST_BEACON_SIZE_FRAMES);
+}
+
+/**
+ * Returns the beacon byte of a beacon type.
+ *
+ * @returns Beacon byte if found, 0 otherwise.
+ * @param uTest Test number to get beacon byte for.
+ * @param enmType Beacon type to get beacon byte for.
+ */
+DECLINLINE(uint8_t) AudioTestBeaconByteFromType(uint8_t uTest, AUDIOTESTTONEBEACONTYPE enmType)
+{
+ switch (enmType)
+ {
+ case AUDIOTESTTONEBEACONTYPE_PLAY_PRE: return AUDIOTEST_BEACON_MAKE_PRE(uTest);
+ case AUDIOTESTTONEBEACONTYPE_PLAY_POST: return AUDIOTEST_BEACON_MAKE_POST(uTest);
+ case AUDIOTESTTONEBEACONTYPE_REC_PRE: return AUDIOTEST_BEACON_MAKE_PRE(uTest);
+ case AUDIOTESTTONEBEACONTYPE_REC_POST: return AUDIOTEST_BEACON_MAKE_POST(uTest);
+ default: break;
+ }
+
+ AssertFailed();
+ return 0;
+}
+
+/**
+ * Returns the total expected (total) size of an audio beacon (in bytes).
+ *
+ * @returns Beacon size in bytes.
+ * @param pBeacon Beacon to get beacon size for.
+ */
+uint32_t AudioTestBeaconGetSize(PCAUDIOTESTTONEBEACON pBeacon)
+{
+ return pBeacon->cbSize;
+}
+
+/**
+ * Returns the beacon type of an audio beacon.
+ *
+ * @returns Beacon type.
+ * @param pBeacon Beacon to get beacon size for.
+ */
+AUDIOTESTTONEBEACONTYPE AudioTestBeaconGetType(PCAUDIOTESTTONEBEACON pBeacon)
+{
+ return pBeacon->enmType;
+}
+
+/**
+ * Returns the remaining bytes (to be complete) of an audio beacon.
+ *
+ * @returns Remaining bytes.
+ * @param pBeacon Beacon to get remaining size for.
+ */
+uint32_t AudioTestBeaconGetRemaining(PCAUDIOTESTTONEBEACON pBeacon)
+{
+ return pBeacon->cbSize - pBeacon->cbUsed;
+}
+
+/**
+ * Returns the already used (received) bytes (to be complete) of an audio beacon.
+ *
+ * @returns Used bytes.
+ * @param pBeacon Beacon to get remaining size for.
+ */
+uint32_t AudioTestBeaconGetUsed(PCAUDIOTESTTONEBEACON pBeacon)
+{
+ return pBeacon->cbUsed;
+}
+
+/**
+ * Writes audio beacon data to a given buffer.
+ *
+ * @returns VBox status code.
+ * @param pBeacon Beacon to write to buffer.
+ * @param pvBuf Buffer to write to.
+ * @param cbBuf Size (in bytes) of buffer to write to.
+ */
+int AudioTestBeaconWrite(PAUDIOTESTTONEBEACON pBeacon, void *pvBuf, uint32_t cbBuf)
+{
+ AssertReturn(pBeacon->cbUsed + cbBuf <= pBeacon->cbSize, VERR_BUFFER_OVERFLOW);
+
+ memset(pvBuf, AudioTestBeaconByteFromType(pBeacon->uTest, pBeacon->enmType), cbBuf);
+
+ pBeacon->cbUsed += cbBuf;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Converts an audio beacon type to a string.
+ *
+ * @returns Pointer to read-only audio beacon type string on success,
+ * "illegal" if invalid command value.
+ * @param enmType The type to convert.
+ */
+const char *AudioTestBeaconTypeGetName(AUDIOTESTTONEBEACONTYPE enmType)
+{
+ switch (enmType)
+ {
+ case AUDIOTESTTONEBEACONTYPE_PLAY_PRE: return "pre-playback";
+ case AUDIOTESTTONEBEACONTYPE_PLAY_POST: return "post-playback";
+ case AUDIOTESTTONEBEACONTYPE_REC_PRE: return "pre-recording";
+ case AUDIOTESTTONEBEACONTYPE_REC_POST: return "post-recording";
+ default: break;
+ }
+ AssertMsgFailedReturn(("Invalid beacon type: #%x\n", enmType), "illegal");
+}
+
+/**
+ * Adds audio data to a given beacon.
+ *
+ * @returns VBox status code, VERR_NOT_FOUND if not beacon data was not found.
+ * @param pBeacon Beacon to add data for.
+ * @param pauBuf Buffer of audio data to add.
+ * @param cbBuf Size (in bytes) of \a pauBuf.
+ * @param pOff Where to return the offset within \a pauBuf where beacon ended on success.
+ * Optional and can be NULL.
+ *
+ * @note The audio data must be a) match the beacon type and b) consecutive, that is, without any gaps,
+ * to be added as valid to the beacon.
+ */
+int AudioTestBeaconAddConsecutive(PAUDIOTESTTONEBEACON pBeacon, const uint8_t *pauBuf, size_t cbBuf, size_t *pOff)
+{
+ AssertPtrReturn(pBeacon, VERR_INVALID_POINTER);
+ AssertPtrReturn(pauBuf, VERR_INVALID_POINTER);
+ /* pOff is optional. */
+
+ uint64_t offBeacon = UINT64_MAX;
+ uint32_t const cbFrameSize = PDMAudioPropsFrameSize(&pBeacon->Props); /* Use the audio frame size as chunk size. */
+
+ uint8_t const byBeacon = AudioTestBeaconByteFromType(pBeacon->uTest, pBeacon->enmType);
+ unsigned const cbStep = cbFrameSize;
+
+ /* Make sure that we do frame-aligned reads. */
+ cbBuf = PDMAudioPropsFloorBytesToFrame(&pBeacon->Props, (uint32_t)cbBuf);
+
+ for (size_t i = 0; i < cbBuf; i += cbStep)
+ {
+ if ( pauBuf[i] == byBeacon
+ && pauBuf[i + 1] == byBeacon
+ && pauBuf[i + 2] == byBeacon
+ && pauBuf[i + 3] == byBeacon)
+ {
+ /* Make sure to handle overflows and let beacon start from scratch. */
+ pBeacon->cbUsed = (pBeacon->cbUsed + cbStep) % pBeacon->cbSize;
+ if (pBeacon->cbUsed == 0) /* Beacon complete (see module line above)? */
+ {
+ pBeacon->cbUsed = pBeacon->cbSize;
+ offBeacon = i + cbStep; /* Point to data right *after* the beacon. */
+ }
+ }
+ else
+ {
+ /* If beacon is not complete yet, we detected a gap here. Start all over then. */
+ if (RT_LIKELY(pBeacon->cbUsed != pBeacon->cbSize))
+ pBeacon->cbUsed = 0;
+ }
+ }
+
+ if (offBeacon != UINT64_MAX)
+ {
+ if (pOff)
+ *pOff = offBeacon;
+ }
+
+ return offBeacon == UINT64_MAX ? VERR_NOT_FOUND : VINF_SUCCESS;
+}
+
+/**
+ * Returns whether a beacon is considered to be complete or not.
+ *
+ * A complete beacon means that all data for it has been retrieved.
+ *
+ * @returns \c true if complete, or \c false if not.
+ * @param pBeacon Beacon to get completion status for.
+ */
+bool AudioTestBeaconIsComplete(PCAUDIOTESTTONEBEACON pBeacon)
+{
+ AssertReturn(pBeacon->cbUsed <= pBeacon->cbSize, true);
+ return (pBeacon->cbUsed == pBeacon->cbSize);
+}
+
+/**
+ * Verifies a pre/post beacon of a test tone.
+ *
+ * @returns VBox status code, VERR_NOT_FOUND if beacon was not found.
+ * @param pVerJob Verification job to verify PCM data for.
+ * @param fIn Set to \c true for recording, \c false for playback.
+ * @param fPre Set to \c true to verify a pre beacon, or \c false to verify a post beacon.
+ * @param pCmp File comparison parameters to file to verify beacon for.
+ * @param pToneParms Tone parameters to use for verification.
+ * @param puOff Where to return the absolute file offset (in bytes) right after the found beacon on success.
+ * Optional and can be NULL.
+ */
+static int audioTestToneVerifyBeacon(PAUDIOTESTVERIFYJOB pVerJob,
+ bool fIn, bool fPre, PAUDIOTESTFILECMPPARMS pCmp, PAUDIOTESTTONEPARMS pToneParms,
+ uint64_t *puOff)
+{
+ int rc = RTFileSeek(pCmp->hFile, pCmp->offStart, RTFILE_SEEK_BEGIN, NULL);
+ AssertRCReturn(rc, rc);
+
+ AUDIOTESTTONEBEACON Beacon;
+ RT_ZERO(Beacon);
+ AudioTestBeaconInit(&Beacon, pVerJob->idxTest,
+ fIn
+ ? (fPre ? AUDIOTESTTONEBEACONTYPE_PLAY_PRE : AUDIOTESTTONEBEACONTYPE_PLAY_POST)
+ : (fPre ? AUDIOTESTTONEBEACONTYPE_REC_PRE : AUDIOTESTTONEBEACONTYPE_REC_POST), &pToneParms->Props);
+
+ uint8_t auBuf[_64K];
+ uint64_t cbToCompare = pCmp->cbSize;
+ uint32_t const cbFrameSize = PDMAudioPropsFrameSize(&Beacon.Props);
+ uint64_t offBeaconLast = UINT64_MAX;
+
+ Assert(sizeof(auBuf) % cbFrameSize == 0);
+
+ while (cbToCompare)
+ {
+ size_t cbRead;
+ rc = RTFileRead(pCmp->hFile, auBuf, RT_MIN(cbToCompare, sizeof(auBuf)), &cbRead);
+ AssertRCBreak(rc);
+
+ if (cbRead < cbFrameSize)
+ break;
+
+ size_t uOff;
+ int rc2 = AudioTestBeaconAddConsecutive(&Beacon, auBuf, cbRead, &uOff);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Save the last found (absolute bytes, in file) position of a (partially) found beacon. */
+ offBeaconLast = RTFileTell(pCmp->hFile) - (cbRead - uOff);
+ }
+
+ Assert(cbToCompare >= cbRead);
+ cbToCompare -= cbRead;
+ }
+
+ uint32_t const cbBeacon = AudioTestBeaconGetUsed(&Beacon);
+
+ if (!AudioTestBeaconIsComplete(&Beacon))
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "File '%s': %s beacon %s (got %RU32 bytes, expected %RU32)",
+ pCmp->pszName,
+ AudioTestBeaconTypeGetName(Beacon.enmType),
+ cbBeacon ? "found" : "not found", cbBeacon,
+ AudioTestBeaconGetSize(&Beacon));
+ AssertRC(rc2);
+ return VERR_NOT_FOUND;
+ }
+ else
+ {
+ AssertReturn(AudioTestBeaconGetRemaining(&Beacon) == 0, VERR_INTERNAL_ERROR);
+ AssertReturn(offBeaconLast != UINT32_MAX, VERR_INTERNAL_ERROR);
+ AssertReturn(offBeaconLast >= AudioTestBeaconGetSize(&Beacon), VERR_INTERNAL_ERROR);
+
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File '%s': %s beacon found at offset %RU64 and valid",
+ pCmp->pszName, AudioTestBeaconTypeGetName(Beacon.enmType),
+ offBeaconLast - AudioTestBeaconGetSize(&Beacon));
+ AssertRC(rc2);
+
+ if (puOff)
+ *puOff = offBeaconLast;
+ }
+
+ return rc;
+}
+
+#define CHECK_RC_MAYBE_RET(a_rc, a_pVerJob) \
+ if (RT_FAILURE(a_rc)) \
+ { \
+ if (!a_pVerJob->Opts.fKeepGoing) \
+ return VINF_SUCCESS; \
+ }
+
+#define CHECK_RC_MSG_MAYBE_RET(a_rc, a_pVerJob, a_Msg) \
+ if (RT_FAILURE(a_rc)) \
+ { \
+ int rc3 = audioTestErrorDescAddError(a_pVerJob->pErr, a_pVerJob->idxTest, a_Msg); \
+ AssertRC(rc3); \
+ if (!a_pVerJob->Opts.fKeepGoing) \
+ return VINF_SUCCESS; \
+ }
+
+#define CHECK_RC_MSG_VA_MAYBE_RET(a_rc, a_pVerJob, a_Msg, ...) \
+ if (RT_FAILURE(a_rc)) \
+ { \
+ int rc3 = audioTestErrorDescAddError(a_pVerJob->pErr, a_pVerJob->idxTest, a_Msg, __VA_ARGS__); \
+ AssertRC(rc3); \
+ if (!a_pVerJob->Opts.fKeepGoing) \
+ return VINF_SUCCESS; \
+
+/**
+ * Does the actual PCM data verification of a test tone.
+ *
+ * @returns VBox status code.
+ * @param pVerJob Verification job to verify PCM data for.
+ * @param phTestA Test handle A of test to verify PCM data for.
+ * @param phTestB Test handle B of test to verify PCM data for.
+ */
+static int audioTestVerifyTestToneData(PAUDIOTESTVERIFYJOB pVerJob, PAUDIOTESTOBJINT phTestA, PAUDIOTESTOBJINT phTestB)
+{
+ int rc;
+
+ /** @todo For now ASSUME that we only have one object per test. */
+
+ AUDIOTESTOBJINT ObjA;
+ rc = audioTestObjGetChild(phTestA, 0 /* idxObj */, &ObjA);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to get object A");
+
+ rc = audioTestObjOpen(&ObjA);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to open object A");
+
+ AUDIOTESTOBJINT ObjB;
+ rc = audioTestObjGetChild(phTestB, 0 /* idxObj */, &ObjB);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to get object B");
+
+ rc = audioTestObjOpen(&ObjB);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Unable to open object B");
+
+ /*
+ * Start with most obvious methods first.
+ */
+ uint64_t cbFileSizeA, cbFileSizeB;
+ rc = RTFileQuerySize(ObjA.File.hFile, &cbFileSizeA);
+ AssertRCReturn(rc, rc);
+ rc = RTFileQuerySize(ObjB.File.hFile, &cbFileSizeB);
+ AssertRCReturn(rc, rc);
+
+ if (!cbFileSizeA)
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "File '%s' is empty", ObjA.szName);
+ AssertRC(rc2);
+ }
+
+ if (!cbFileSizeB)
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "File '%s' is empty", ObjB.szName);
+ AssertRC(rc2);
+ }
+
+ if (cbFileSizeA != cbFileSizeB)
+ {
+ size_t const cbDiffAbs = cbFileSizeA > cbFileSizeB ? cbFileSizeA - cbFileSizeB : cbFileSizeB - cbFileSizeA;
+
+ int rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File '%s': %zu bytes (%RU64ms)",
+ ObjA.szName, cbFileSizeA, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, cbFileSizeA));
+ AssertRC(rc2);
+ rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "File '%s': %zu bytes (%RU64ms)",
+ ObjB.szName, cbFileSizeB, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, cbFileSizeB));
+ AssertRC(rc2);
+
+ uint8_t const uSizeDiffPercentAbs
+ = cbFileSizeA > cbFileSizeB ? 100 - ((cbFileSizeB * 100) / cbFileSizeA) : 100 - ((cbFileSizeA * 100) / cbFileSizeB);
+
+ if (uSizeDiffPercentAbs > pVerJob->Opts.uMaxSizePercent)
+ {
+ rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
+ "File '%s' is %RU8%% (%zu bytes, %RU64ms) %s than '%s' (threshold is %RU8%%)",
+ ObjA.szName,
+ uSizeDiffPercentAbs,
+ cbDiffAbs, PDMAudioPropsBytesToMilli(&pVerJob->PCMProps, (uint32_t)cbDiffAbs),
+ cbFileSizeA > cbFileSizeB ? "bigger" : "smaller",
+ ObjB.szName, pVerJob->Opts.uMaxSizePercent);
+ AssertRC(rc2);
+ }
+ }
+
+ /* Do normalization first if enabled. */
+ if (pVerJob->Opts.fNormalize)
+ {
+ rc = audioTestObjFileNormalize(pVerJob, &ObjA, &pVerJob->PCMProps);
+ if (RT_SUCCESS(rc))
+ rc = audioTestObjFileNormalize(pVerJob, &ObjB, &pVerJob->PCMProps);
+ }
+
+ /** @todo For now we only support comparison of data which do have identical PCM properties! */
+
+ AUDIOTESTTONEPARMS ToneParmsA;
+ RT_ZERO(ToneParmsA);
+ ToneParmsA.Props = pVerJob->PCMProps;
+
+ size_t cbSearchWindow = PDMAudioPropsMilliToBytes(&ToneParmsA.Props, pVerJob->Opts.msSearchWindow);
+
+ AUDIOTESTFILECMPPARMS FileA;
+ RT_ZERO(FileA);
+ FileA.pszName = ObjA.szName;
+ FileA.hFile = ObjA.File.hFile;
+ FileA.offStart = audioTestToneFileFind(ObjA.File.hFile, true /* fFindSilence */,
+ 0 /* uOff */, cbFileSizeA /* cbMax */, &ToneParmsA, cbSearchWindow);
+ FileA.cbSize = audioTestToneFileFind(ObjA.File.hFile, false /* fFindSilence */,
+ FileA.offStart /* uOff */, cbFileSizeA - FileA.offStart /* cbMax */, &ToneParmsA, cbSearchWindow);
+ AssertReturn(FileA.offStart + FileA.cbSize <= cbFileSizeA, VERR_INTERNAL_ERROR);
+
+ AUDIOTESTTONEPARMS ToneParmsB;
+ RT_ZERO(ToneParmsB);
+ ToneParmsB.Props = pVerJob->PCMProps;
+
+ AUDIOTESTFILECMPPARMS FileB;
+ RT_ZERO(FileB);
+ FileB.pszName = ObjB.szName;
+ FileB.hFile = ObjB.File.hFile;
+ FileB.offStart = audioTestToneFileFind(ObjB.File.hFile, true /* fFindSilence */,
+ 0 /* uOff */, cbFileSizeB /* cbMax */, &ToneParmsB, cbSearchWindow);
+ FileB.cbSize = audioTestToneFileFind(ObjB.File.hFile, false /* fFindSilence */,
+ FileB.offStart /* uOff */, cbFileSizeB - FileB.offStart /* cbMax */, &ToneParmsB, cbSearchWindow);
+ AssertReturn(FileB.offStart + FileB.cbSize <= cbFileSizeB, VERR_INTERNAL_ERROR);
+
+ int rc2;
+
+ uint64_t offBeaconAbs;
+ rc = audioTestToneVerifyBeacon(pVerJob, phTestA->enmTestType == AUDIOTESTTYPE_TESTTONE_PLAY /* fIn */,
+ true /* fPre */, &FileA, &ToneParmsA, &offBeaconAbs);
+ if (RT_SUCCESS(rc))
+ {
+ FileA.offStart = offBeaconAbs;
+ FileA.cbSize = cbFileSizeA - FileA.offStart;
+ rc = audioTestToneVerifyBeacon(pVerJob, phTestA->enmTestType == AUDIOTESTTYPE_TESTTONE_PLAY /* fIn */,
+ false /* fPre */, &FileA, &ToneParmsA, &offBeaconAbs);
+ if (RT_SUCCESS(rc))
+ {
+ /* Adjust the size of the area to compare so that it's within the pre + post beacons. */
+ Assert(offBeaconAbs >= FileA.offStart);
+ FileA.cbSize = offBeaconAbs - FileA.offStart;
+ }
+ }
+
+ rc = audioTestToneVerifyBeacon(pVerJob, phTestB->enmTestType == AUDIOTESTTYPE_TESTTONE_RECORD /* fIn */,
+ true /* fPre */, &FileB, &ToneParmsB, &offBeaconAbs);
+ if (RT_SUCCESS(rc))
+ {
+ FileB.offStart = offBeaconAbs;
+ FileB.cbSize = cbFileSizeB - FileB.offStart;
+ rc = audioTestToneVerifyBeacon(pVerJob, phTestB->enmTestType == AUDIOTESTTYPE_TESTTONE_RECORD /* fIn */,
+ false /* fPre */, &FileB, &ToneParmsB, &offBeaconAbs);
+ if (RT_SUCCESS(rc))
+ {
+ /* Adjust the size of the area to compare so that it's within the pre + post beacons. */
+ Assert(offBeaconAbs >= FileB.offStart);
+ FileB.cbSize = offBeaconAbs - FileB.offStart;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cDiffs = audioTestFilesFindDiffsBinary(pVerJob, &FileA, &FileB, &ToneParmsA);
+
+ if (cDiffs > pVerJob->Opts.cMaxDiff)
+ {
+ rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest,
+ "Files '%s' and '%s' have too many different chunks (got %RU32, expected %RU32)",
+ ObjA.szName, ObjB.szName, cDiffs, pVerJob->Opts.cMaxDiff);
+ AssertRC(rc2);
+ }
+ }
+
+ if (AudioTestErrorDescFailed(pVerJob->pErr))
+ {
+ rc2 = audioTestErrorDescAddInfo(pVerJob->pErr, pVerJob->idxTest, "Files '%s' and '%s' do not match",
+ ObjA.szName, ObjB.szName);
+ AssertRC(rc2);
+ }
+
+ rc = audioTestObjClose(&ObjA);
+ AssertRCReturn(rc, rc);
+ rc = audioTestObjClose(&ObjB);
+ AssertRCReturn(rc, rc);
+
+ return rc;
+}
+
+/**
+ * Verifies a test tone test.
+ *
+ * @returns VBox status code.
+ * @returns Error if the verification failed and test verification job has fKeepGoing not set.
+ * @retval VERR_
+ * @param pVerJob Verification job to verify test tone for.
+ * @param phTestA Test handle of test tone A to verify tone B with.
+ * @param phTestB Test handle of test tone B to verify tone A with.*
+ */
+static int audioTestVerifyTestTone(PAUDIOTESTVERIFYJOB pVerJob, PAUDIOTESTOBJINT phTestA, PAUDIOTESTOBJINT phTestB)
+{
+ int rc;
+
+ /*
+ * Verify test parameters.
+ * More important items have precedence.
+ */
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "error_rc", "0", "Test was reported as failed");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "obj_count", NULL, "Object counts don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_freq_hz", NULL, "Tone frequency doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_prequel_ms", NULL, "Tone prequel (ms) doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_duration_ms", NULL, "Tone duration (ms) doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_sequel_ms", NULL, "Tone sequel (ms) doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_volume_percent", NULL, "Tone volume (percent) doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_hz", NULL, "Tone PCM Hz doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_channels", NULL, "Tone PCM channels don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_bits", NULL, "Tone PCM bits don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(pVerJob, phTestA, phTestB, "tone_pcm_is_signed", NULL, "Tone PCM signed bit doesn't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+
+ rc = audioTestObjGetTonePcmProps(phTestA, &pVerJob->PCMProps);
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+
+ /*
+ * Now the fun stuff, PCM data analysis.
+ */
+ rc = audioTestVerifyTestToneData(pVerJob, phTestA, phTestB);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = audioTestErrorDescAddError(pVerJob->pErr, pVerJob->idxTest, "Verififcation of test tone data failed\n");
+ AssertRC(rc2);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Verifies an opened audio test set, extended version.
+ *
+ * @returns VBox status code.
+ * @param pSetA Test set A to verify.
+ * @param pSetB Test set to verify test set A with.
+ * @param pOpts Verification options to use.
+ * @param pErrDesc Where to return the test verification errors.
+ *
+ * @note Test verification errors have to be checked for errors, regardless of the
+ * actual return code.
+ * @note Uses the standard verification options. Use AudioTestSetVerifyEx() to specify
+ * own options.
+ */
+int AudioTestSetVerifyEx(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTVERIFYOPTS pOpts, PAUDIOTESTERRORDESC pErrDesc)
+{
+ AssertPtrReturn(pSetA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSetB, VERR_INVALID_POINTER);
+ AssertReturn(audioTestManifestIsOpen(pSetA), VERR_WRONG_ORDER);
+ AssertReturn(audioTestManifestIsOpen(pSetB), VERR_WRONG_ORDER);
+ AssertPtrReturn(pOpts, VERR_INVALID_POINTER);
+
+ /* We ASSUME the caller has not init'd pErrDesc. */
+ audioTestErrorDescInit(pErrDesc);
+
+ AUDIOTESTVERIFYJOB VerJob;
+ RT_ZERO(VerJob);
+ VerJob.pErr = pErrDesc;
+ VerJob.pSetA = pSetA;
+ VerJob.pSetB = pSetB;
+
+ memcpy(&VerJob.Opts, pOpts, sizeof(AUDIOTESTVERIFYOPTS));
+
+ PAUDIOTESTVERIFYJOB pVerJob = &VerJob;
+
+ int rc;
+
+ /*
+ * Compare obvious values first.
+ */
+ AUDIOTESTOBJINT hHdrA;
+ rc = audioTestSetGetSection(pVerJob->pSetA, AUDIOTEST_SEC_HDR_STR, &hHdrA);
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+
+ AUDIOTESTOBJINT hHdrB;
+ rc = audioTestSetGetSection(pVerJob->pSetB, AUDIOTEST_SEC_HDR_STR, &hHdrB);
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+
+ rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "magic", "vkat_ini", "Manifest magic wrong");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "ver", "1" , "Manifest version wrong");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "tag", NULL, "Manifest tags don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "test_count", NULL, "Test counts don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+ rc = audioTestVerifyValue(&VerJob, &hHdrA, &hHdrB, "obj_count", NULL, "Object counts don't match");
+ CHECK_RC_MAYBE_RET(rc, pVerJob);
+
+ /*
+ * Compare ran tests.
+ */
+ uint32_t cTests;
+ rc = audioTestObjGetUInt32(&hHdrA, "test_count", &cTests);
+ AssertRCReturn(rc, rc);
+
+ for (uint32_t i = 0; i < cTests; i++)
+ {
+ VerJob.idxTest = i;
+
+ AUDIOTESTOBJINT hTestA;
+ rc = audioTestSetGetTest(VerJob.pSetA, i, &hTestA);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test A not found");
+
+ AUDIOTESTOBJINT hTestB;
+ rc = audioTestSetGetTest(VerJob.pSetB, i, &hTestB);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test B not found");
+
+ rc = audioTestObjGetUInt32(&hTestA, "test_type", (uint32_t *)&hTestA.enmTestType);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test type A not found");
+
+ rc = audioTestObjGetUInt32(&hTestB, "test_type", (uint32_t *)&hTestB.enmTestType);
+ CHECK_RC_MSG_MAYBE_RET(rc, pVerJob, "Test type B not found");
+
+ switch (hTestA.enmTestType)
+ {
+ case AUDIOTESTTYPE_TESTTONE_PLAY:
+ {
+ if (hTestB.enmTestType == AUDIOTESTTYPE_TESTTONE_RECORD)
+ rc = audioTestVerifyTestTone(&VerJob, &hTestA, &hTestB);
+ else
+ rc = audioTestErrorDescAddError(pErrDesc, i, "Playback test types don't match (set A=%#x, set B=%#x)",
+ hTestA.enmTestType, hTestB.enmTestType);
+ break;
+ }
+
+ case AUDIOTESTTYPE_TESTTONE_RECORD:
+ {
+ if (hTestB.enmTestType == AUDIOTESTTYPE_TESTTONE_PLAY)
+ rc = audioTestVerifyTestTone(&VerJob, &hTestB, &hTestA);
+ else
+ rc = audioTestErrorDescAddError(pErrDesc, i, "Recording test types don't match (set A=%#x, set B=%#x)",
+ hTestA.enmTestType, hTestB.enmTestType);
+ break;
+ }
+
+ case AUDIOTESTTYPE_INVALID:
+ rc = VERR_INVALID_PARAMETER;
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ AssertRC(rc);
+ }
+
+ /* Only return critical stuff not related to actual testing here. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes audio test verification options in a strict manner.
+ *
+ * @param pOpts Verification options to initialize.
+ */
+void AudioTestSetVerifyOptsInitStrict(PAUDIOTESTVERIFYOPTS pOpts)
+{
+ RT_BZERO(pOpts, sizeof(AUDIOTESTVERIFYOPTS));
+
+ pOpts->fKeepGoing = true;
+ pOpts->fNormalize = false; /* Skip normalization by default now, as we now use the OS' master volume to play/record tones. */
+ pOpts->cMaxDiff = 0; /* By default we're very strict and consider any diff as being erroneous. */
+ pOpts->uMaxSizePercent = 10; /* 10% is okay for us for now; might be due to any buffering / setup phase.
+ Anything above this is suspicious and should be reported for further investigation. */
+ pOpts->msSearchWindow = 10; /* We use a search window of 10ms by default for finding (non-)silent parts. */
+}
+
+/**
+ * Initializes audio test verification options with default values (strict!).
+ *
+ * @param pOpts Verification options to initialize.
+ */
+void AudioTestSetVerifyOptsInit(PAUDIOTESTVERIFYOPTS pOpts)
+{
+ AudioTestSetVerifyOptsInitStrict(pOpts);
+}
+
+/**
+ * Returns whether two audio test verification options are equal.
+ *
+ * @returns \c true if equal, or \c false if not.
+ * @param pOptsA Options A to compare.
+ * @param pOptsB Options B to compare Options A with.
+ */
+bool AudioTestSetVerifyOptsAreEqual(PAUDIOTESTVERIFYOPTS pOptsA, PAUDIOTESTVERIFYOPTS pOptsB)
+{
+ if (pOptsA == pOptsB)
+ return true;
+
+ return ( pOptsA->cMaxDiff == pOptsB->cMaxDiff
+ && pOptsA->fKeepGoing == pOptsB->fKeepGoing
+ && pOptsA->fNormalize == pOptsB->fNormalize
+ && pOptsA->uMaxDiffPercent == pOptsB->uMaxDiffPercent
+ && pOptsA->uMaxSizePercent == pOptsB->uMaxSizePercent
+ && pOptsA->msSearchWindow == pOptsB->msSearchWindow);
+}
+
+/**
+ * Verifies an opened audio test set.
+ *
+ * @returns VBox status code.
+ * @param pSetA Test set A to verify.
+ * @param pSetB Test set to verify test set A with.
+ * @param pErrDesc Where to return the test verification errors.
+ *
+ * @note Test verification errors have to be checked for errors, regardless of the
+ * actual return code.
+ * @note Uses the standard verification options (strict!).
+ * Use AudioTestSetVerifyEx() to specify own options.
+ */
+int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc)
+{
+ AUDIOTESTVERIFYOPTS Opts;
+ AudioTestSetVerifyOptsInitStrict(&Opts);
+
+ return AudioTestSetVerifyEx(pSetA,pSetB, &Opts, pErrDesc);
+}
+
+#undef CHECK_RC_MAYBE_RET
+#undef CHECK_RC_MSG_MAYBE_RET
+
+/**
+ * Converts an audio test state enum value to a string.
+ *
+ * @returns Pointer to read-only internal test state string on success,
+ * "illegal" if invalid command value.
+ * @param enmState The state to convert.
+ */
+const char *AudioTestStateToStr(AUDIOTESTSTATE enmState)
+{
+ switch (enmState)
+ {
+ case AUDIOTESTSTATE_INIT: return "init";
+ case AUDIOTESTSTATE_PRE: return "pre";
+ case AUDIOTESTSTATE_RUN: return "run";
+ case AUDIOTESTSTATE_POST: return "post";
+ case AUDIOTESTSTATE_DONE: return "done";
+ case AUDIOTESTSTATE_32BIT_HACK:
+ break;
+ }
+ AssertMsgFailedReturn(("Invalid test state: #%x\n", enmState), "illegal");
+}
+
+
+/*********************************************************************************************************************************
+* WAVE File Reader. *
+*********************************************************************************************************************************/
+
+/**
+ * Counts the number of set bits in @a fMask.
+ */
+static unsigned audioTestWaveCountBits(uint32_t fMask)
+{
+ unsigned cBits = 0;
+ while (fMask)
+ {
+ if (fMask & 1)
+ cBits++;
+ fMask >>= 1;
+ }
+ return cBits;
+}
+
+/**
+ * Opens a wave (.WAV) file for reading.
+ *
+ * @returns VBox status code.
+ * @param pszFile The file to open.
+ * @param pWaveFile The open wave file structure to fill in on success.
+ * @param pErrInfo Where to return addition error details on failure.
+ */
+int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
+{
+ pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
+ RT_ZERO(pWaveFile->Props);
+ pWaveFile->hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
+ uint64_t cbFile = 0;
+ rc = RTFileQuerySize(pWaveFile->hFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ union
+ {
+ uint8_t ab[512];
+ struct
+ {
+ RTRIFFHDR Hdr;
+ union
+ {
+ RTRIFFWAVEFMTCHUNK Fmt;
+ RTRIFFWAVEFMTEXTCHUNK FmtExt;
+ } u;
+ } Wave;
+ RTRIFFLIST List;
+ RTRIFFCHUNK Chunk;
+ RTRIFFWAVEDATACHUNK Data;
+ } uBuf;
+
+ rc = RTFileRead(pWaveFile->hFile, &uBuf.Wave, sizeof(uBuf.Wave), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_VFS_UNKNOWN_FORMAT;
+ if ( uBuf.Wave.Hdr.uMagic == RTRIFFHDR_MAGIC
+ && uBuf.Wave.Hdr.uFileType == RTRIFF_FILE_TYPE_WAVE
+ && uBuf.Wave.u.Fmt.Chunk.uMagic == RTRIFFWAVEFMT_MAGIC
+ && uBuf.Wave.u.Fmt.Chunk.cbChunk >= sizeof(uBuf.Wave.u.Fmt.Data))
+ {
+ if (uBuf.Wave.Hdr.cbFile != cbFile - sizeof(RTRIFFCHUNK))
+ RTErrInfoSetF(pErrInfo, rc, "File size mismatch: %#x, actual %#RX64 (ignored)",
+ uBuf.Wave.Hdr.cbFile, cbFile - sizeof(RTRIFFCHUNK));
+ rc = VERR_VFS_BOGUS_FORMAT;
+ if ( uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_PCM
+ && uBuf.Wave.u.Fmt.Data.uFormatTag != RTRIFFWAVEFMT_TAG_EXTENSIBLE)
+ RTErrInfoSetF(pErrInfo, rc, "Unsupported uFormatTag value: %#x (expected %#x or %#x)",
+ uBuf.Wave.u.Fmt.Data.uFormatTag, RTRIFFWAVEFMT_TAG_PCM, RTRIFFWAVEFMT_TAG_EXTENSIBLE);
+ else if ( uBuf.Wave.u.Fmt.Data.cBitsPerSample != 8
+ && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 16
+ /* && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 24 - not supported by our stack */
+ && uBuf.Wave.u.Fmt.Data.cBitsPerSample != 32)
+ RTErrInfoSetF(pErrInfo, rc, "Unsupported cBitsPerSample value: %u", uBuf.Wave.u.Fmt.Data.cBitsPerSample);
+ else if ( uBuf.Wave.u.Fmt.Data.cChannels < 1
+ || uBuf.Wave.u.Fmt.Data.cChannels >= 16)
+ RTErrInfoSetF(pErrInfo, rc, "Unsupported cChannels value: %u (expected 1..15)", uBuf.Wave.u.Fmt.Data.cChannels);
+ else if ( uBuf.Wave.u.Fmt.Data.uHz < 4096
+ || uBuf.Wave.u.Fmt.Data.uHz > 768000)
+ RTErrInfoSetF(pErrInfo, rc, "Unsupported uHz value: %u (expected 4096..768000)", uBuf.Wave.u.Fmt.Data.uHz);
+ else if (uBuf.Wave.u.Fmt.Data.cbFrame != uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8)
+ RTErrInfoSetF(pErrInfo, rc, "Invalid cbFrame value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbFrame,
+ uBuf.Wave.u.Fmt.Data.cChannels * uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8);
+ else if (uBuf.Wave.u.Fmt.Data.cbRate != uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz)
+ RTErrInfoSetF(pErrInfo, rc, "Invalid cbRate value: %u (expected %u)", uBuf.Wave.u.Fmt.Data.cbRate,
+ uBuf.Wave.u.Fmt.Data.cbFrame * uBuf.Wave.u.Fmt.Data.uHz);
+ else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
+ && uBuf.Wave.u.FmtExt.Data.cbExtra < RTRIFFWAVEFMTEXT_EXTRA_SIZE)
+ RTErrInfoSetF(pErrInfo, rc, "Invalid cbExtra value: %#x (expected at least %#x)",
+ uBuf.Wave.u.FmtExt.Data.cbExtra, RTRIFFWAVEFMTEXT_EXTRA_SIZE);
+ else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
+ && audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask) != uBuf.Wave.u.Fmt.Data.cChannels)
+ RTErrInfoSetF(pErrInfo, rc, "fChannelMask does not match cChannels: %#x (%u bits set) vs %u channels",
+ uBuf.Wave.u.FmtExt.Data.fChannelMask,
+ audioTestWaveCountBits(uBuf.Wave.u.FmtExt.Data.fChannelMask), uBuf.Wave.u.Fmt.Data.cChannels);
+ else if ( uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE
+ && RTUuidCompareStr(&uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM) != 0)
+ RTErrInfoSetF(pErrInfo, rc, "SubFormat is not PCM: %RTuuid (expected %s)",
+ &uBuf.Wave.u.FmtExt.Data.SubFormat, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
+ else
+ {
+ /*
+ * Copy out the data we need from the file format structure.
+ */
+ PDMAudioPropsInit(&pWaveFile->Props, uBuf.Wave.u.Fmt.Data.cBitsPerSample / 8, true /*fSigned*/,
+ uBuf.Wave.u.Fmt.Data.cChannels, uBuf.Wave.u.Fmt.Data.uHz);
+ pWaveFile->offSamples = sizeof(RTRIFFHDR) + sizeof(RTRIFFCHUNK) + uBuf.Wave.u.Fmt.Chunk.cbChunk;
+
+ /*
+ * Pick up channel assignments if present.
+ */
+ if (uBuf.Wave.u.Fmt.Data.uFormatTag == RTRIFFWAVEFMT_TAG_EXTENSIBLE)
+ {
+ static unsigned const s_cStdIds = (unsigned)PDMAUDIOCHANNELID_END_STANDARD
+ - (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD;
+ unsigned iCh = 0;
+ for (unsigned idCh = 0; idCh < 32 && iCh < uBuf.Wave.u.Fmt.Data.cChannels; idCh++)
+ if (uBuf.Wave.u.FmtExt.Data.fChannelMask & RT_BIT_32(idCh))
+ {
+ pWaveFile->Props.aidChannels[iCh] = idCh < s_cStdIds
+ ? idCh + (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD
+ : (unsigned)PDMAUDIOCHANNELID_UNKNOWN;
+ iCh++;
+ }
+ }
+
+ /*
+ * Find the 'data' chunk with the audio samples.
+ *
+ * There can be INFO lists both preceeding this and succeeding
+ * it, containing IART and other things we can ignored. Thus
+ * we read a list header here rather than just a chunk header,
+ * since it doesn't matter if we read 4 bytes extra as
+ * AudioTestWaveFileRead uses RTFileReadAt anyway.
+ */
+ rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
+ for (uint32_t i = 0;
+ i < 128
+ && RT_SUCCESS(rc)
+ && uBuf.Chunk.uMagic != RTRIFFWAVEDATACHUNK_MAGIC
+ && (uint64_t)uBuf.Chunk.cbChunk + sizeof(RTRIFFCHUNK) * 2 <= cbFile - pWaveFile->offSamples;
+ i++)
+ {
+ if ( uBuf.List.uMagic == RTRIFFLIST_MAGIC
+ && uBuf.List.uListType == RTRIFFLIST_TYPE_INFO)
+ { /*skip*/ }
+ else if (uBuf.Chunk.uMagic == RTRIFFPADCHUNK_MAGIC)
+ { /*skip*/ }
+ else
+ break;
+ pWaveFile->offSamples += sizeof(RTRIFFCHUNK) + uBuf.Chunk.cbChunk;
+ rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples, &uBuf, sizeof(uBuf.List), NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pWaveFile->offSamples += sizeof(uBuf.Data.Chunk);
+ pWaveFile->cbSamples = (uint32_t)cbFile - pWaveFile->offSamples;
+
+ rc = VERR_VFS_BOGUS_FORMAT;
+ if ( uBuf.Data.Chunk.uMagic == RTRIFFWAVEDATACHUNK_MAGIC
+ && uBuf.Data.Chunk.cbChunk <= pWaveFile->cbSamples
+ && PDMAudioPropsIsSizeAligned(&pWaveFile->Props, uBuf.Data.Chunk.cbChunk))
+ {
+ pWaveFile->cbSamples = uBuf.Data.Chunk.cbChunk;
+
+ /*
+ * We're good!
+ */
+ pWaveFile->offCur = 0;
+ pWaveFile->fReadMode = true;
+ pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
+ return VINF_SUCCESS;
+ }
+
+ RTErrInfoSetF(pErrInfo, rc, "Bad data header: uMagic=%#x (expected %#x), cbChunk=%#x (max %#RX64, align %u)",
+ uBuf.Data.Chunk.uMagic, RTRIFFWAVEDATACHUNK_MAGIC,
+ uBuf.Data.Chunk.cbChunk, pWaveFile->cbSamples, PDMAudioPropsFrameSize(&pWaveFile->Props));
+ }
+ else
+ RTErrInfoSet(pErrInfo, rc, "Failed to read data header");
+ }
+ }
+ else
+ RTErrInfoSetF(pErrInfo, rc, "Bad file header: uMagic=%#x (vs. %#x), uFileType=%#x (vs %#x), uFmtMagic=%#x (vs %#x) cbFmtChunk=%#x (min %#x)",
+ uBuf.Wave.Hdr.uMagic, RTRIFFHDR_MAGIC, uBuf.Wave.Hdr.uFileType, RTRIFF_FILE_TYPE_WAVE,
+ uBuf.Wave.u.Fmt.Chunk.uMagic, RTRIFFWAVEFMT_MAGIC,
+ uBuf.Wave.u.Fmt.Chunk.cbChunk, sizeof(uBuf.Wave.u.Fmt.Data));
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, rc, "Failed to read file header");
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, rc, "Failed to query file size");
+
+ RTFileClose(pWaveFile->hFile);
+ pWaveFile->hFile = NIL_RTFILE;
+ return rc;
+}
+
+
+/**
+ * Creates a new wave file.
+ *
+ * @returns VBox status code.
+ * @param pszFile The filename.
+ * @param pProps The audio format properties.
+ * @param pWaveFile The wave file structure to fill in on success.
+ * @param pErrInfo Where to return addition error details on failure.
+ */
+int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo)
+{
+ /*
+ * Construct the file header first (we'll do some input validation
+ * here, so better do it before creating the file).
+ */
+ struct
+ {
+ RTRIFFHDR Hdr;
+ RTRIFFWAVEFMTEXTCHUNK FmtExt;
+ RTRIFFCHUNK Data;
+ } FileHdr;
+
+ FileHdr.Hdr.uMagic = RTRIFFHDR_MAGIC;
+ FileHdr.Hdr.cbFile = 0; /* need to update this later */
+ FileHdr.Hdr.uFileType = RTRIFF_FILE_TYPE_WAVE;
+ FileHdr.FmtExt.Chunk.uMagic = RTRIFFWAVEFMT_MAGIC;
+ FileHdr.FmtExt.Chunk.cbChunk = sizeof(RTRIFFWAVEFMTEXTCHUNK) - sizeof(RTRIFFCHUNK);
+ FileHdr.FmtExt.Data.Core.uFormatTag = RTRIFFWAVEFMT_TAG_EXTENSIBLE;
+ FileHdr.FmtExt.Data.Core.cChannels = PDMAudioPropsChannels(pProps);
+ FileHdr.FmtExt.Data.Core.uHz = PDMAudioPropsHz(pProps);
+ FileHdr.FmtExt.Data.Core.cbRate = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
+ FileHdr.FmtExt.Data.Core.cbFrame = PDMAudioPropsFrameSize(pProps);
+ FileHdr.FmtExt.Data.Core.cBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ FileHdr.FmtExt.Data.cbExtra = sizeof(FileHdr.FmtExt.Data) - sizeof(FileHdr.FmtExt.Data.Core);
+ FileHdr.FmtExt.Data.cValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ FileHdr.FmtExt.Data.fChannelMask = 0;
+ for (uintptr_t idxCh = 0; idxCh < FileHdr.FmtExt.Data.Core.cChannels; idxCh++)
+ {
+ PDMAUDIOCHANNELID const idCh = (PDMAUDIOCHANNELID)pProps->aidChannels[idxCh];
+ if ( idCh >= PDMAUDIOCHANNELID_FIRST_STANDARD
+ && idCh < PDMAUDIOCHANNELID_END_STANDARD)
+ {
+ if (!(FileHdr.FmtExt.Data.fChannelMask & RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD)))
+ FileHdr.FmtExt.Data.fChannelMask |= RT_BIT_32(idCh - PDMAUDIOCHANNELID_FIRST_STANDARD);
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Channel #%u repeats channel ID %d", idxCh, idCh);
+ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid channel ID %d for channel #%u", idCh, idxCh);
+ }
+
+ RTUUID UuidTmp;
+ int rc = RTUuidFromStr(&UuidTmp, RTRIFFWAVEFMTEXT_SUBTYPE_PCM);
+ AssertRCReturn(rc, rc);
+ FileHdr.FmtExt.Data.SubFormat = UuidTmp; /* (64-bit field maybe unaligned) */
+
+ FileHdr.Data.uMagic = RTRIFFWAVEDATACHUNK_MAGIC;
+ FileHdr.Data.cbChunk = 0; /* need to update this later */
+
+ /*
+ * Create the file and write the header.
+ */
+ pWaveFile->hFile = NIL_RTFILE;
+ rc = RTFileOpen(&pWaveFile->hFile, pszFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "RTFileOpen failed");
+
+ rc = RTFileWrite(pWaveFile->hFile, &FileHdr, sizeof(FileHdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the wave file structure.
+ */
+ pWaveFile->fReadMode = false;
+ pWaveFile->offCur = 0;
+ pWaveFile->offSamples = 0;
+ pWaveFile->cbSamples = 0;
+ pWaveFile->Props = *pProps;
+ pWaveFile->offSamples = RTFileTell(pWaveFile->hFile);
+ if (pWaveFile->offSamples != UINT32_MAX)
+ {
+ pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC;
+ return VINF_SUCCESS;
+ }
+ rc = RTErrInfoSet(pErrInfo, VERR_SEEK, "RTFileTell failed");
+ }
+ else
+ RTErrInfoSet(pErrInfo, rc, "RTFileWrite failed writing header");
+
+ RTFileClose(pWaveFile->hFile);
+ pWaveFile->hFile = NIL_RTFILE;
+ pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
+
+ RTFileDelete(pszFile);
+ return rc;
+}
+
+
+/**
+ * Closes a wave file.
+ */
+int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile)
+{
+ AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
+ int rcRet = VINF_SUCCESS;
+ int rc;
+
+ /*
+ * Update the size fields if writing.
+ */
+ if (!pWaveFile->fReadMode)
+ {
+ uint64_t cbFile = RTFileTell(pWaveFile->hFile);
+ if (cbFile != UINT64_MAX)
+ {
+ uint32_t cbFile32 = cbFile - sizeof(RTRIFFCHUNK);
+ rc = RTFileWriteAt(pWaveFile->hFile, RT_OFFSETOF(RTRIFFHDR, cbFile), &cbFile32, sizeof(cbFile32), NULL);
+ AssertRCStmt(rc, rcRet = rc);
+
+ uint32_t cbSamples = cbFile - pWaveFile->offSamples;
+ rc = RTFileWriteAt(pWaveFile->hFile, pWaveFile->offSamples - sizeof(uint32_t), &cbSamples, sizeof(cbSamples), NULL);
+ AssertRCStmt(rc, rcRet = rc);
+ }
+ else
+ rcRet = VERR_SEEK;
+ }
+
+ /*
+ * Close it.
+ */
+ rc = RTFileClose(pWaveFile->hFile);
+ AssertRCStmt(rc, rcRet = rc);
+
+ pWaveFile->hFile = NIL_RTFILE;
+ pWaveFile->u32Magic = AUDIOTESTWAVEFILE_MAGIC_DEAD;
+ return rcRet;
+}
+
+/**
+ * Reads samples from a wave file.
+ *
+ * @returns VBox status code. See RTVfsFileRead for EOF status handling.
+ * @param pWaveFile The file to read from.
+ * @param pvBuf Where to put the samples.
+ * @param cbBuf How much to read at most.
+ * @param pcbRead Where to return the actual number of bytes read,
+ * optional.
+ */
+int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pWaveFile->fReadMode, VERR_ACCESS_DENIED);
+
+ bool fEofAdjusted;
+ if (pWaveFile->offCur + cbBuf <= pWaveFile->cbSamples)
+ fEofAdjusted = false;
+ else if (pcbRead)
+ {
+ fEofAdjusted = true;
+ cbBuf = pWaveFile->cbSamples - pWaveFile->offCur;
+ }
+ else
+ return VERR_EOF;
+
+ int rc = RTFileReadAt(pWaveFile->hFile, pWaveFile->offSamples + pWaveFile->offCur, pvBuf, cbBuf, pcbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ {
+ pWaveFile->offCur += (uint32_t)*pcbRead;
+ if (fEofAdjusted || cbBuf > *pcbRead)
+ rc = VINF_EOF;
+ else if (!cbBuf && pWaveFile->offCur == pWaveFile->cbSamples)
+ rc = VINF_EOF;
+ }
+ else
+ pWaveFile->offCur += (uint32_t)cbBuf;
+ }
+ return rc;
+}
+
+
+/**
+ * Writes samples to a wave file.
+ *
+ * @returns VBox status code.
+ * @param pWaveFile The file to write to.
+ * @param pvBuf The samples to write.
+ * @param cbBuf How many bytes of samples to write.
+ */
+int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf)
+{
+ AssertReturn(pWaveFile->u32Magic == AUDIOTESTWAVEFILE_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(!pWaveFile->fReadMode, VERR_ACCESS_DENIED);
+
+ pWaveFile->cbSamples += (uint32_t)cbBuf;
+ return RTFileWrite(pWaveFile->hFile, pvBuf, cbBuf, NULL);
+}
+
diff --git a/src/VBox/Devices/Audio/AudioTest.h b/src/VBox/Devices/Audio/AudioTest.h
new file mode 100644
index 00000000..3dff6af2
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTest.h
@@ -0,0 +1,491 @@
+/* $Id: AudioTest.h $ */
+/** @file
+ * Audio testing routines.
+ * Common code which is being used by the ValidationKit audio test (VKAT)
+ * and the debug / ValdikationKit audio driver(s).
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioTest_h
+#define VBOX_INCLUDED_SRC_Audio_AudioTest_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** @todo Some stuff here can be private-only to the implementation. */
+
+/** Maximum length in characters an audio test tag can have. */
+#define AUDIOTEST_TAG_MAX 64
+/** Maximum length in characters a single audio test error description can have. */
+#define AUDIOTEST_ERROR_DESC_MAX 256
+/** Prefix for audio test (set) directories. */
+#define AUDIOTEST_PATH_PREFIX_STR "vkat"
+/** Maximum tests a beacon can have.
+ * Maximum number of tests is 240 (so it can fit into 8-bit mono channels). */
+#define AUDIOTEST_BEACON_TESTS_MAX 240
+/** Returns a pre-beacon for a given test number.
+ * Maximum number of tests is 240 (so it can fit into 8-bit mono channels).
+ *
+ * That way it's easy to visually inspect beacon data in a hex editor --
+ * e.g. for test #5 a pre-beacon would be 0x5A + post-beacon 0x5B. */
+#define AUDIOTEST_BEACON_MAKE_PRE(a_TstNum) \
+ ( (uint8_t)((a_TstNum) & 0xf) << 4 \
+ | (uint8_t)(0xA))
+/** Returns a post-beacon for a given test number.
+ * Maximum number of tests is 250 (so it can fit into 8-bit mono channels).
+ *
+ * That way it's easy to visually inspect beacon data in a hex editor --
+ * e.g. for test #2 a pre-beacon would be 0x2A + post-beacon 0x2B. */
+#define AUDIOTEST_BEACON_MAKE_POST(a_TstNum) \
+ ( (uint8_t)((a_TstNum) & 0xf) << 4 \
+ | (uint8_t)(0xB))
+/** Pre / post audio beacon size (in audio frames). */
+#define AUDIOTEST_BEACON_SIZE_FRAMES 1024
+
+/**
+ * Enumeration for an audio test tone (wave) type.
+ */
+typedef enum AUDIOTESTTONETYPE
+{
+ /** Invalid type. */
+ AUDIOTESTTONETYPE_INVALID = 0,
+ /** Sine wave. */
+ AUDIOTESTTONETYPE_SINE,
+ /** Square wave. Not implemented yet. */
+ AUDIOTESTTONETYPE_SQUARE,
+ /** Triangluar wave. Not implemented yet. */
+ AUDIOTESTTONETYPE_TRIANGLE,
+ /** Sawtooth wave. Not implemented yet. */
+ AUDIOTESTTONETYPE_SAWTOOTH,
+ /** The usual 32-bit hack. */
+ AUDIOTESTTONETYPE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTTONETYPE;
+
+/**
+ * Structure for handling an audio (sine wave) test tone.
+ */
+typedef struct AUDIOTESTTONE
+{
+ /** The tone's wave type. */
+ AUDIOTESTTONETYPE enmType;
+ /** The PCM properties. */
+ PDMAUDIOPCMPROPS Props;
+ /** Current sample index for generate the sine wave. */
+ uint64_t uSample;
+ /** The fixed portion of the sin() input. */
+ double rdFixed;
+ /** Frequency (in Hz) of the sine wave to generate. */
+ double rdFreqHz;
+} AUDIOTESTTONE;
+/** Pointer to an audio test tone. */
+typedef AUDIOTESTTONE *PAUDIOTESTTONE;
+
+/**
+ * Structure for a common test parameters header.
+ */
+typedef struct AUDIOTESTPARMSHDR
+{
+ /** Test index these test parameters belong to.
+ * Set to UINT32_MAX if not being used. */
+ uint32_t idxTest;
+ /** Time of the caller when this test was being created. */
+ RTTIME tsCreated;
+} AUDIOTESTPARMSHDR;
+/** Pointer to an audio test tone. */
+typedef AUDIOTESTPARMSHDR *PAUDIOTESTPARMSHDR;
+
+/**
+ * Structure for handling audio test tone parameters.
+ */
+typedef struct AUDIOTESTTONEPARMS
+{
+ /** Common test header. */
+ AUDIOTESTPARMSHDR Hdr;
+ /** The PCM properties. */
+ PDMAUDIOPCMPROPS Props;
+ /** Tone frequency (in Hz) to use.
+ * Will be later converted to a double value. */
+ double dbFreqHz;
+ /** Prequel (in ms) to play silence. Optional and can be set to 0. */
+ RTMSINTERVAL msPrequel;
+ /** Duration (in ms) to play the test tone. */
+ RTMSINTERVAL msDuration;
+ /** Sequel (in ms) to play silence. Optional and can be set to 0. */
+ RTMSINTERVAL msSequel;
+ /** Volume (in percent, 0-100) to use.
+ * If set to 0, the tone is muted (i.e. silent). */
+ uint8_t uVolumePercent;
+} AUDIOTESTTONEPARMS;
+/** Pointer to audio test tone parameters. */
+typedef AUDIOTESTTONEPARMS *PAUDIOTESTTONEPARMS;
+
+/**
+ * Enumeration defining an audio test beacon type.
+ */
+typedef enum AUDIOTESTTONEBEACONTYPE
+{
+ /** Invalid type. */
+ AUDIOTESTTONEBEACONTYPE_INVALID = 0,
+ /** Playback beacon (pre). */
+ AUDIOTESTTONEBEACONTYPE_PLAY_PRE = 1,
+ /** Playback beacon (post). */
+ AUDIOTESTTONEBEACONTYPE_PLAY_POST = 2,
+ /** Recording beacon (pre). */
+ AUDIOTESTTONEBEACONTYPE_REC_PRE = 3,
+ /** Recording beacon (post). */
+ AUDIOTESTTONEBEACONTYPE_REC_POST = 4,
+ /** The usual 32-bit hack. */
+ OTESTTONEBEACONTYPE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTTONEBEACONTYPE;
+
+/**
+ * Structure defining an audio test tone beacon.
+ *
+ * This is being used for (optionally) marking beginning/ending of audio test data.
+ */
+typedef struct AUDIOTESTTONEBEACON
+{
+ /** Test number this beacon is for. */
+ uint8_t uTest;
+ /** The beacon type. */
+ AUDIOTESTTONEBEACONTYPE enmType;
+ /** PCM properties to use for this beacon. */
+ PDMAUDIOPCMPROPS Props;
+ /** Beacon bytes to process.
+ * When doing test tone playback: Beacon bytes to write.
+ * When doing test tone recording: Beacon bytes to read. */
+ uint32_t cbSize;
+ /** Beacon bytes already processed.
+ * When doing test tone playback: Beacon bytes written.
+ * When doing test tone recording: Beacon bytes read. */
+ uint32_t cbUsed;
+} AUDIOTESTTONEBEACON;
+/** Pointer to audio test tone beacon. */
+typedef AUDIOTESTTONEBEACON *PAUDIOTESTTONEBEACON;
+/** Pointer (const) to audio test tone beacon. */
+typedef AUDIOTESTTONEBEACON const *PCAUDIOTESTTONEBEACON;
+
+/**
+ * Enumeration for the test set mode.
+ */
+typedef enum AUDIOTESTSETMODE
+{
+ /** Invalid test set mode. */
+ AUDIOTESTSETMODE_INVALID = 0,
+ /** Test set is being created (testing in progress). */
+ AUDIOTESTSETMODE_TEST,
+ /** Existing test set is being verified. */
+ AUDIOTESTSETMODE_VERIFY,
+ /** The usual 32-bit hack. */
+ AUDIOTESTSETMODE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTSETMODE;
+
+/**
+ * Enumeration to specify an audio test type.
+ */
+typedef enum AUDIOTESTTYPE
+{
+ /** Invalid test type, do not use. */
+ AUDIOTESTTYPE_INVALID = 0,
+ /** Play a test tone. */
+ AUDIOTESTTYPE_TESTTONE_PLAY,
+ /** Record a test tone. */
+ AUDIOTESTTYPE_TESTTONE_RECORD,
+ /** The usual 32-bit hack. */
+ AUDIOTESTTYPE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTTYPE;
+
+/**
+ * Audio test request data.
+ */
+typedef struct AUDIOTESTPARMS
+{
+ /** Audio device to use. */
+ PDMAUDIOHOSTDEV Dev;
+ /** How much to delay (wait, in ms) the test being executed. */
+ RTMSINTERVAL msDelay;
+ /** The test direction. */
+ PDMAUDIODIR enmDir;
+ /** The test type. */
+ AUDIOTESTTYPE enmType;
+ /** Union for test type-specific data. */
+ union
+ {
+ AUDIOTESTTONEPARMS TestTone;
+ };
+} AUDIOTESTPARMS;
+/** Pointer to a test parameter structure. */
+typedef AUDIOTESTPARMS *PAUDIOTESTPARMS;
+
+/** Test object handle. */
+typedef R3R0PTRTYPE(struct AUDIOTESTOBJINT RT_FAR *) AUDIOTESTOBJ;
+/** Pointer to test object handle. */
+typedef AUDIOTESTOBJ RT_FAR *PAUDIOTESTOBJ;
+/** Nil test object handle. */
+#define NIL_AUDIOTESTOBJ ((AUDIOTESTOBJ)~(RTHCINTPTR)0)
+
+struct AUDIOTESTSET;
+
+/**
+ * Structure specifying a single audio test entry of a test set.
+ *
+ * A test set can contain zero or more test entry (tests).
+ */
+typedef struct AUDIOTESTENTRY
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Pointer to test set parent. */
+ AUDIOTESTSET *pParent;
+ /** Friendly description of the test. */
+ char szDesc[64];
+ /** Audio test parameters this test needs to perform the actual test. */
+ AUDIOTESTPARMS Parms;
+ /** Number of test objects bound to this test. */
+ uint32_t cObj;
+ /** Absolute offset (in bytes) where to write the "obj_count" value later. */
+ uint64_t offObjCount;
+ /** Overall test result. */
+ int rc;
+} AUDIOTESTENTRY;
+/** Pointer to an audio test entry. */
+typedef AUDIOTESTENTRY *PAUDIOTESTENTRY;
+
+/**
+ * Structure specifying an audio test set.
+ */
+typedef struct AUDIOTESTSET
+{
+ /** The set's tag. */
+ char szTag[AUDIOTEST_TAG_MAX];
+ /** Absolute path where to store the test audio data. */
+ char szPathAbs[RTPATH_MAX];
+ /** Current mode the test set is in. */
+ AUDIOTESTSETMODE enmMode;
+ union
+ {
+ /** @todo r=bird: RTSTREAM not RTFILE. That means you don't have to check
+ * every write status code and it's buffered and thus faster. Also,
+ * you don't have to re-invent fprintf-style RTFileWrite wrappers. */
+ RTFILE hFile;
+ RTINIFILE hIniFile;
+ } f;
+ /** Number of test objects in lstObj. */
+ uint32_t cObj;
+ /** Absolute offset (in bytes) where to write the "obj_count" value later. */
+ uint64_t offObjCount;
+ /** List containing PAUDIOTESTOBJ test object entries. */
+ RTLISTANCHOR lstObj;
+ /** Number of performed tests.
+ * Not necessarily bound to the test object entries above. */
+ uint32_t cTests;
+ /** Absolute offset (in bytes) where to write the "test_count" value later. */
+ uint64_t offTestCount;
+ /** List containing PAUDIOTESTENTRY test entries. */
+ RTLISTANCHOR lstTest;
+ /** Current test running. Can be NULL if no test is running. */
+ PAUDIOTESTENTRY pTestCur;
+ /** Number of tests currently running.
+ * Currently we only allow one concurrent test running at a given time. */
+ uint32_t cTestsRunning;
+ /** Number of total (test) failures. */
+ uint32_t cTotalFailures;
+} AUDIOTESTSET;
+/** Pointer to an audio test set. */
+typedef AUDIOTESTSET *PAUDIOTESTSET;
+
+/**
+ * Audio test verification options.
+ */
+typedef struct AUDIOTESTVERIFYOPTS
+{
+ /** Flag indicating whether to keep going after an error has occurred. */
+ bool fKeepGoing;
+ /** Whether to perform audio normalization or not. */
+ bool fNormalize;
+ /** Threshold of file differences (number of chunks) at when we consider audio files
+ * as not matching. 0 means an exact match. */
+ uint32_t cMaxDiff;
+ /** Threshold of file differences (difference in percent) at when we consider audio files
+ * as not matching. 0 means an exact match. */
+ uint8_t uMaxDiffPercent;
+ /** Threshold of file size (+/-, in percent) at when we consider audio files
+ * as not matching. 0 means an exact match.*/
+ uint8_t uMaxSizePercent;
+ /** Search window (in ms) to use for treating / classifying audio data. */
+ uint32_t msSearchWindow;
+} AUDIOTESTVERIFYOPTS;
+/** Pointer to audio test verification options. */
+typedef AUDIOTESTVERIFYOPTS *PAUDIOTESTVERIFYOPTS;
+
+/**
+ * Structure for holding a single audio test error entry.
+ */
+typedef struct AUDIOTESTERRORENTRY
+{
+ /** The entrie's list node. */
+ RTLISTNODE Node;
+ /** Additional rc. */
+ int rc;
+ /** Actual error description. */
+ char szDesc[AUDIOTEST_ERROR_DESC_MAX];
+} AUDIOTESTERRORENTRY;
+/** Pointer to an audio test error description. */
+typedef AUDIOTESTERRORENTRY *PAUDIOTESTERRORENTRY;
+
+/**
+ * Structure for holding an audio test error description.
+ * This can contain multiple errors (FIFO list).
+ */
+typedef struct AUDIOTESTERRORDESC
+{
+ /** List entries containing the (FIFO-style) errors of type AUDIOTESTERRORENTRY. */
+ RTLISTANCHOR List;
+ /** Number of errors in the list. */
+ uint32_t cErrors;
+} AUDIOTESTERRORDESC;
+/** Pointer to an audio test error description. */
+typedef AUDIOTESTERRORDESC *PAUDIOTESTERRORDESC;
+/** Const pointer to an audio test error description. */
+typedef AUDIOTESTERRORDESC const *PCAUDIOTESTERRORDESC;
+
+/**
+ * Enumeration specifying an internal test state.
+ */
+typedef enum AUDIOTESTSTATE
+{
+ /** Test is initializing. */
+ AUDIOTESTSTATE_INIT = 0,
+ /** Test is in pre-run phase. */
+ AUDIOTESTSTATE_PRE,
+ /** Test is running */
+ AUDIOTESTSTATE_RUN,
+ /** Test is in post-run phase. */
+ AUDIOTESTSTATE_POST,
+ /** Test has been run. */
+ AUDIOTESTSTATE_DONE,
+ /** The usual 32-bit hack. */
+ AUDIOTESTSTATE_32BIT_HACK = 0x7fffffff
+} AUDIOTESTSTATE;
+
+double AudioTestToneInit(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps, double dbFreq);
+double AudioTestToneInitRandom(PAUDIOTESTTONE pTone, PPDMAUDIOPCMPROPS pProps);
+double AudioTestToneGetRandomFreq(void);
+int AudioTestToneGenerate(PAUDIOTESTTONE pTone, void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
+
+void AudioTestBeaconInit(PAUDIOTESTTONEBEACON pBeacon, uint8_t uTest, AUDIOTESTTONEBEACONTYPE enmType, PPDMAUDIOPCMPROPS pProps);
+int AudioTestBeaconAddConsecutive(PAUDIOTESTTONEBEACON pBeacon, const uint8_t *auBuf, size_t cbBuf, size_t *pOff);
+int AudioTestBeaconWrite(PAUDIOTESTTONEBEACON pBeacon, void *pvBuf, uint32_t cbBuf);
+uint32_t AudioTestBeaconGetSize(PCAUDIOTESTTONEBEACON pBeacon);
+const char *AudioTestBeaconTypeGetName(AUDIOTESTTONEBEACONTYPE enmType);
+AUDIOTESTTONEBEACONTYPE AudioTestBeaconGetType(PCAUDIOTESTTONEBEACON pBeacon);
+uint32_t AudioTestBeaconGetRemaining(PCAUDIOTESTTONEBEACON pBeacon);
+uint32_t AudioTestBeaconGetUsed(PCAUDIOTESTTONEBEACON pBeacon);
+bool AudioTestBeaconIsComplete(PCAUDIOTESTTONEBEACON pBeacon);
+
+int AudioTestGenTag(char *pszTag, size_t cbTag);
+
+int AudioTestPathGetTemp(char *pszPath, size_t cbPath);
+int AudioTestPathCreateTemp(char *pszPath, size_t cbPath, const char *pszUUID);
+int AudioTestPathCreate(char *pszPath, size_t cbPath, const char *pszUUID);
+
+int AudioTestSetObjCreateAndRegister(PAUDIOTESTSET pSet, const char *pszName, PAUDIOTESTOBJ pObj);
+
+int AudioTestObjWrite(AUDIOTESTOBJ Obj, const void *pvBuf, size_t cbBuf);
+int AudioTestObjAddMetadataStr(AUDIOTESTOBJ Obj, const char *pszFormat, ...);
+int AudioTestObjClose(AUDIOTESTOBJ Obj);
+
+int AudioTestSetTestBegin(PAUDIOTESTSET pSet, const char *pszDesc, PAUDIOTESTPARMS pParms, PAUDIOTESTENTRY *ppEntry);
+int AudioTestSetTestFailed(PAUDIOTESTENTRY pEntry, int rc, const char *pszErr);
+int AudioTestSetTestDone(PAUDIOTESTENTRY pEntry);
+bool AudioTestSetTestIsRunning(PAUDIOTESTENTRY pEntry);
+
+int AudioTestSetCreate(PAUDIOTESTSET pSet, const char *pszPath, const char *pszTag);
+int AudioTestSetDestroy(PAUDIOTESTSET pSet);
+int AudioTestSetOpen(PAUDIOTESTSET pSet, const char *pszPath);
+int AudioTestSetClose(PAUDIOTESTSET pSet);
+int AudioTestSetWipe(PAUDIOTESTSET pSet);
+const char *AudioTestSetGetTag(PAUDIOTESTSET pSet);
+uint32_t AudioTestSetGetTestsTotal(PAUDIOTESTSET pSet);
+uint32_t AudioTestSetGetTestsRunning(PAUDIOTESTSET pSet);
+uint32_t AudioTestSetGetTotalFailures(PAUDIOTESTSET pSet);
+bool AudioTestSetIsPacked(const char *pszPath);
+bool AudioTestSetIsRunning(PAUDIOTESTSET pSet);
+int AudioTestSetPack(PAUDIOTESTSET pSet, const char *pszOutDir, char *pszFileName, size_t cbFileName);
+int AudioTestSetUnpack(const char *pszFile, const char *pszOutDir);
+
+void AudioTestSetVerifyOptsInitStrict(PAUDIOTESTVERIFYOPTS pOpts);
+void AudioTestSetVerifyOptsInit(PAUDIOTESTVERIFYOPTS pOpts);
+bool AudioTestSetVerifyOptsAreEqual(PAUDIOTESTVERIFYOPTS pOptsA, PAUDIOTESTVERIFYOPTS pOptsB);
+
+int AudioTestSetVerify(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTERRORDESC pErrDesc);
+int AudioTestSetVerifyEx(PAUDIOTESTSET pSetA, PAUDIOTESTSET pSetB, PAUDIOTESTVERIFYOPTS pOpts, PAUDIOTESTERRORDESC pErrDesc);
+
+uint32_t AudioTestErrorDescCount(PCAUDIOTESTERRORDESC pErr);
+bool AudioTestErrorDescFailed(PCAUDIOTESTERRORDESC pErr);
+
+void AudioTestErrorDescDestroy(PAUDIOTESTERRORDESC pErr);
+
+const char *AudioTestStateToStr(AUDIOTESTSTATE enmState);
+
+/** @name Wave File Accessors
+ * @{ */
+/**
+ * An open wave (.WAV) file.
+ */
+typedef struct AUDIOTESTWAVEFILE
+{
+ /** Magic value (AUDIOTESTWAVEFILE_MAGIC). */
+ uint32_t u32Magic;
+ /** Set if we're in read-mode, clear if in write mode. */
+ bool fReadMode;
+ /** The file handle. */
+ RTFILE hFile;
+ /** The absolute file offset of the first sample */
+ uint32_t offSamples;
+ /** Number of bytes of samples. */
+ uint32_t cbSamples;
+ /** The current read position relative to @a offSamples. */
+ uint32_t offCur;
+ /** The PCM properties for the file format. */
+ PDMAUDIOPCMPROPS Props;
+} AUDIOTESTWAVEFILE;
+/** Pointer to an open wave file. */
+typedef AUDIOTESTWAVEFILE *PAUDIOTESTWAVEFILE;
+
+/** Magic value for AUDIOTESTWAVEFILE::u32Magic (Miles Dewey Davis III). */
+#define AUDIOTESTWAVEFILE_MAGIC UINT32_C(0x19260526)
+/** Magic value for AUDIOTESTWAVEFILE::u32Magic after closing. */
+#define AUDIOTESTWAVEFILE_MAGIC_DEAD UINT32_C(0x19910928)
+
+int AudioTestWaveFileOpen(const char *pszFile, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo);
+int AudioTestWaveFileCreate(const char *pszFile, PCPDMAUDIOPCMPROPS pProps, PAUDIOTESTWAVEFILE pWaveFile, PRTERRINFO pErrInfo);
+int AudioTestWaveFileRead(PAUDIOTESTWAVEFILE pWaveFile, void *pvBuf, size_t cbBuf, size_t *pcbRead);
+int AudioTestWaveFileWrite(PAUDIOTESTWAVEFILE pWaveFile, const void *pvBuf, size_t cbBuf);
+int AudioTestWaveFileClose(PAUDIOTESTWAVEFILE pWaveFile);
+
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioTest_h */
+
diff --git a/src/VBox/Devices/Audio/AudioTestService.cpp b/src/VBox/Devices/Audio/AudioTestService.cpp
new file mode 100644
index 00000000..8ea544e9
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestService.cpp
@@ -0,0 +1,1322 @@
+/* $Id: AudioTestService.cpp $ */
+/** @file
+ * AudioTestService - Audio test execution server.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_TEST
+#include <iprt/log.h>
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/crc.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/initterm.h>
+#include <iprt/json.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include <VBox/log.h>
+
+#include "AudioTestService.h"
+#include "AudioTestServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * A generic ATS reply, used by the client
+ * to process the incoming packets.
+ */
+typedef struct ATSSRVREPLY
+{
+ char szOp[ATSPKT_OPCODE_MAX_LEN];
+ void *pvPayload;
+ size_t cbPayload;
+} ATSSRVREPLY;
+/** Pointer to a generic ATS reply. */
+typedef struct ATSSRVREPLY *PATSSRVREPLY;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Transport layers.
+ */
+const PCATSTRANSPORT g_apTransports[] =
+{
+ &g_TcpTransport
+};
+/** Number of transport layers in \a g_apTransports. */
+const size_t g_cTransports = RT_ELEMENTS(g_apTransports);
+
+/**
+ * ATS client state.
+ */
+typedef enum ATSCLIENTSTATE
+{
+ /** Invalid client state. */
+ ATSCLIENTSTATE_INVALID = 0,
+ /** Client is initialising, only the HOWDY and BYE packets are allowed. */
+ ATSCLIENTSTATE_INITIALISING,
+ /** Client is in fully cuntional state and ready to process all requests. */
+ ATSCLIENTSTATE_READY,
+ /** Client is destroying. */
+ ATSCLIENTSTATE_DESTROYING,
+ /** 32bit hack. */
+ ATSCLIENTSTATE_32BIT_HACK = 0x7fffffff
+} ATSCLIENTSTATE;
+
+/**
+ * ATS client instance.
+ */
+typedef struct ATSCLIENTINST
+{
+ /** List node for new clients. */
+ RTLISTNODE NdLst;
+ /** The current client state. */
+ ATSCLIENTSTATE enmState;
+ /** Transport backend specific data. */
+ PATSTRANSPORTCLIENT pTransportClient;
+ /** Client hostname. */
+ char *pszHostname;
+} ATSCLIENTINST;
+/** Pointer to a ATS client instance. */
+typedef ATSCLIENTINST *PATSCLIENTINST;
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int atsClientDisconnect(PATSSERVER pThis, PATSCLIENTINST pInst);
+
+
+
+/**
+ * Returns the string represenation of the given state.
+ */
+static const char *atsClientStateStringify(ATSCLIENTSTATE enmState)
+{
+ switch (enmState)
+ {
+ case ATSCLIENTSTATE_INVALID:
+ return "INVALID";
+ case ATSCLIENTSTATE_INITIALISING:
+ return "INITIALISING";
+ case ATSCLIENTSTATE_READY:
+ return "READY";
+ case ATSCLIENTSTATE_DESTROYING:
+ return "DESTROYING";
+ case ATSCLIENTSTATE_32BIT_HACK:
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Unknown state %#x\n", enmState));
+ return "UNKNOWN";
+}
+
+/**
+ * Calculates the checksum value, zero any padding space and send the packet.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The ATS client structure.
+ * @param pPkt The packet to send. Must point to a correctly
+ * aligned buffer.
+ */
+static int atsSendPkt(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPkt)
+{
+ Assert(pPkt->cb >= sizeof(*pPkt));
+ pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(ATSPKTHDR, achOpcode));
+ if (pPkt->cb != RT_ALIGN_32(pPkt->cb, ATSPKT_ALIGNMENT))
+ memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, ATSPKT_ALIGNMENT) - pPkt->cb);
+
+ LogFlowFunc(("cb=%RU32 (%#x), payload=%RU32 (%#x), opcode=%.8s\n",
+ pPkt->cb, pPkt->cb, pPkt->cb - sizeof(ATSPKTHDR), pPkt->cb - sizeof(ATSPKTHDR), pPkt->achOpcode));
+ int rc = pThis->pTransport->pfnSendPkt(pThis->pTransportInst, pInst->pTransportClient, pPkt);
+ while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !pThis->fTerminate)
+ rc = pThis->pTransport->pfnSendPkt(pThis->pTransportInst, pInst->pTransportClient, pPkt);
+
+ return rc;
+}
+
+/**
+ * Sends a babble reply and disconnects the client (if applicable).
+ *
+ * @param pThis The ATS instance.
+ * @param pInst The ATS server instance.
+ * @param pszOpcode The BABBLE opcode.
+ */
+static void atsReplyBabble(PATSSERVER pThis, PATSCLIENTINST pInst, const char *pszOpcode)
+{
+ ATSPKTHDR Reply;
+ Reply.cb = sizeof(Reply);
+ Reply.uCrc32 = 0;
+ memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode));
+
+ pThis->pTransport->pfnBabble(pThis->pTransportInst, pInst->pTransportClient, &Reply, 20*1000);
+}
+
+/**
+ * Receive and validate a packet.
+ *
+ * Will send bable responses to malformed packets that results in a error status
+ * code.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param ppPktHdr Where to return the packet on success. Free
+ * with RTMemFree.
+ * @param fAutoRetryOnFailure Whether to retry on error.
+ */
+static int atsRecvPkt(PATSSERVER pThis, PATSCLIENTINST pInst, PPATSPKTHDR ppPktHdr, bool fAutoRetryOnFailure)
+{
+ for (;;)
+ {
+ PATSPKTHDR pPktHdr;
+ int rc = pThis->pTransport->pfnRecvPkt(pThis->pTransportInst, pInst->pTransportClient, &pPktHdr);
+ if (RT_SUCCESS(rc))
+ {
+ /* validate the packet. */
+ if ( pPktHdr->cb >= sizeof(ATSPKTHDR)
+ && pPktHdr->cb < ATSPKT_MAX_SIZE)
+ {
+ Log2Func(("pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n",
+ pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode));
+ uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0
+ ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(ATSPKTHDR, achOpcode))
+ : 0;
+ if (pPktHdr->uCrc32 == uCrc32Calc)
+ {
+ AssertCompileMemberSize(ATSPKTHDR, achOpcode, 8);
+ if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0])
+ && RT_C_IS_UPPER(pPktHdr->achOpcode[1])
+ && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ')
+ && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ')
+ )
+ {
+ Log(("cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode));
+ *ppPktHdr = pPktHdr;
+ return rc;
+ }
+
+ rc = VERR_IO_BAD_COMMAND;
+ }
+ else
+ {
+ Log(("cb=%#x opcode=%.8s crc32=%#x actual=%#x\n",
+ pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc));
+ rc = VERR_IO_CRC;
+ }
+ }
+ else
+ rc = VERR_IO_BAD_LENGTH;
+
+ /* Send babble reply and disconnect the client if the transport is
+ connection oriented. */
+ if (rc == VERR_IO_BAD_LENGTH)
+ atsReplyBabble(pThis, pInst, "BABBLE L");
+ else if (rc == VERR_IO_CRC)
+ atsReplyBabble(pThis, pInst, "BABBLE C");
+ else if (rc == VERR_IO_BAD_COMMAND)
+ atsReplyBabble(pThis, pInst, "BABBLE O");
+ else
+ atsReplyBabble(pThis, pInst, "BABBLE ");
+ RTMemFree(pPktHdr);
+ }
+
+ /* Try again or return failure? */
+ if ( pThis->fTerminate
+ || rc != VERR_INTERRUPTED
+ || !fAutoRetryOnFailure
+ )
+ {
+ Log(("rc=%Rrc\n", rc));
+ return rc;
+ }
+ }
+}
+
+/**
+ * Make a simple reply, only status opcode.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pReply The reply packet.
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param cbExtra Bytes in addition to the header.
+ */
+static int atsReplyInternal(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pReply, const char *pszOpcode, size_t cbExtra)
+{
+ /* copy the opcode, don't be too strict in case of a padding screw up. */
+ size_t cchOpcode = strlen(pszOpcode);
+ if (RT_LIKELY(cchOpcode == sizeof(pReply->achOpcode)))
+ memcpy(pReply->achOpcode, pszOpcode, sizeof(pReply->achOpcode));
+ else
+ {
+ Assert(cchOpcode == sizeof(pReply->achOpcode));
+ while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ')
+ cchOpcode--;
+ AssertMsgReturn(cchOpcode < sizeof(pReply->achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4);
+ memcpy(pReply->achOpcode, pszOpcode, cchOpcode);
+ memset(&pReply->achOpcode[cchOpcode], ' ', sizeof(pReply->achOpcode) - cchOpcode);
+ }
+
+ pReply->cb = (uint32_t)sizeof(ATSPKTHDR) + (uint32_t)cbExtra;
+ pReply->uCrc32 = 0;
+
+ return atsSendPkt(pThis, pInst, pReply);
+}
+
+/**
+ * Make a simple reply, only status opcode.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ */
+static int atsReplySimple(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr, const char *pszOpcode)
+{
+ return atsReplyInternal(pThis, pInst, pPktHdr, pszOpcode, 0);
+}
+
+/**
+ * Acknowledges a packet with success.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The original packet (for future use).
+ */
+static int atsReplyAck(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ return atsReplySimple(pThis, pInst, pPktHdr, "ACK ");
+}
+
+/**
+ * Replies with a failure.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param rcReq The status code of the request.
+ * @param pszDetailFmt Longer description of the problem (format string).
+ * @param va Format arguments.
+ */
+static int atsReplyFailureV(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr,
+ const char *pszOpcode, int rcReq, const char *pszDetailFmt, va_list va)
+{
+ RT_NOREF(pPktHdr);
+
+ ATSPKTREPFAIL Rep;
+ RT_ZERO(Rep);
+
+ size_t cchDetail = RTStrPrintfV(Rep.ach, sizeof(Rep.ach), pszDetailFmt, va);
+
+ Rep.rc = rcReq;
+
+ return atsReplyInternal(pThis, pInst, &Rep.Hdr, pszOpcode, sizeof(Rep.rc) + cchDetail + 1);
+}
+
+/**
+ * Replies with a failure.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The original packet (for future use).
+ * @param pszOpcode The status opcode. Exactly 8 chars long, padd
+ * with space.
+ * @param rcReq Status code.
+ * @param pszDetailFmt Longer description of the problem (format string).
+ * @param ... Format arguments.
+ */
+static int atsReplyFailure(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr,
+ const char *pszOpcode, int rcReq, const char *pszDetailFmt, ...)
+{
+ va_list va;
+ va_start(va, pszDetailFmt);
+ int rc = atsReplyFailureV(pThis, pInst, pPktHdr, pszOpcode, rcReq, pszDetailFmt, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Replies according to the return code.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet to reply to.
+ * @param rcOperation The status code to report.
+ * @param pszOperationFmt The operation that failed. Typically giving the
+ * function call with important arguments.
+ * @param ... Arguments to the format string.
+ */
+static int atsReplyRC(PATSSERVER pThis,
+ PATSCLIENTINST pInst, PATSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...)
+{
+ if (RT_SUCCESS(rcOperation))
+ return atsReplyAck(pThis, pInst, pPktHdr);
+
+ char szOperation[128];
+ va_list va;
+ va_start(va, pszOperationFmt);
+ RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va);
+ va_end(va);
+
+ return atsReplyFailure(pThis, pInst, pPktHdr, "FAILED ", rcOperation, "%s failed with rc=%Rrc (opcode '%.8s')",
+ szOperation, rcOperation, pPktHdr->achOpcode);
+}
+
+/**
+ * Signal a bad packet exact size.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet to reply to.
+ * @param cb The wanted size.
+ */
+static int atsReplyBadSize(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr, size_t cb)
+{
+ return atsReplyFailure(pThis, pInst, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at %zu bytes, got %u (opcode '%.8s')",
+ cb, pPktHdr->cb, pPktHdr->achOpcode);
+}
+
+/**
+ * Deals with a unknown command.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet to reply to.
+ */
+static int atsReplyUnknown(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ return atsReplyFailure(pThis, pInst, pPktHdr, "UNKNOWN ", VERR_NOT_FOUND, "Opcode '%.8s' is not known", pPktHdr->achOpcode);
+}
+
+/**
+ * Deals with a command sent in an invalid client state.
+ *
+ * @returns IPRT status code of the send.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet containing the unterminated string.
+ */
+static int atsReplyInvalidState(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ return atsReplyFailure(pThis, pInst, pPktHdr, "INVSTATE", VERR_INVALID_STATE, "Opcode '%.8s' is not supported at client state '%s",
+ pPktHdr->achOpcode, atsClientStateStringify(pInst->enmState));
+}
+
+/**
+ * Verifies and acknowledges a "BYE" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The bye packet.
+ */
+static int atsDoBye(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ int rc;
+ if (pPktHdr->cb == sizeof(ATSPKTHDR))
+ {
+ if (pThis->Callbacks.pfnBye)
+ {
+ rc = pThis->Callbacks.pfnBye(pThis->Callbacks.pvUser);
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = atsReplyAck(pThis, pInst, pPktHdr);
+ }
+ else
+ rc = atsReplyRC(pThis, pInst, pPktHdr, rc, "Disconnecting client failed");
+ }
+ else
+ rc = atsReplyBadSize(pThis, pInst, pPktHdr, sizeof(ATSPKTHDR));
+ return rc;
+}
+
+/**
+ * Verifies and acknowledges a "HOWDY" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The howdy packet.
+ */
+static int atsDoHowdy(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pPktHdr->cb != sizeof(ATSPKTREQHOWDY))
+ return atsReplyBadSize(pThis, pInst, pPktHdr, sizeof(ATSPKTREQHOWDY));
+
+ if (pInst->enmState != ATSCLIENTSTATE_INITIALISING)
+ return atsReplyInvalidState(pThis, pInst, pPktHdr);
+
+ PATSPKTREQHOWDY pReq = (PATSPKTREQHOWDY)pPktHdr;
+
+ if (pReq->uVersion != ATS_PROTOCOL_VS)
+ return atsReplyRC(pThis, pInst, pPktHdr, VERR_VERSION_MISMATCH, "The given version %#x is not supported", pReq->uVersion);
+
+ ATSPKTREPHOWDY Rep;
+ RT_ZERO(Rep);
+
+ Rep.uVersion = ATS_PROTOCOL_VS;
+
+ rc = atsReplyInternal(pThis, pInst, &Rep.Hdr, "ACK ", sizeof(Rep) - sizeof(ATSPKTHDR));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pTransport->pfnNotifyHowdy(pThis->pTransportInst, pInst->pTransportClient);
+
+ if (pThis->Callbacks.pfnHowdy)
+ rc = pThis->Callbacks.pfnHowdy(pThis->Callbacks.pvUser);
+
+ if (RT_SUCCESS(rc))
+ pInst->enmState = ATSCLIENTSTATE_READY;
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies and acknowledges a "TSET BEG" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The test set begin packet.
+ */
+static int atsDoTestSetBegin(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(ATSPKTREQTSETBEG))
+ return atsReplyBadSize(pThis, pInst, 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_SUCCESS(rc))
+ rc = atsReplyAck(pThis, pInst, pPktHdr);
+ else
+ rc = atsReplyRC(pThis, pInst, 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 pInst The opaque ATS instance structure.
+ * @param pPktHdr The test set end packet.
+ */
+static int atsDoTestSetEnd(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(ATSPKTREQTSETEND))
+ return atsReplyBadSize(pThis, pInst, 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_SUCCESS(rc))
+ rc = atsReplyAck(pThis, pInst, pPktHdr);
+ else
+ rc = atsReplyRC(pThis, pInst, pPktHdr, rc, "Ending test set failed");
+ return rc;
+}
+
+/**
+ * Used by atsDoTestSetSend to wait for a reply ACK from the client.
+ *
+ * @returns VINF_SUCCESS on ACK, VERR_GENERAL_FAILURE on NACK,
+ * VERR_NET_NOT_CONNECTED on unknown response (sending a bable reply),
+ * or whatever atsRecvPkt returns.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The original packet (for future use).
+ */
+static int atsWaitForAck(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ RT_NOREF(pPktHdr);
+ /** @todo timeout? */
+ PATSPKTHDR pReply;
+ int rc = atsRecvPkt(pThis, pInst, &pReply, false /*fAutoRetryOnFailure*/);
+ if (RT_SUCCESS(rc))
+ {
+ if (atsIsSameOpcode(pReply, "ACK"))
+ rc = VINF_SUCCESS;
+ else if (atsIsSameOpcode(pReply, "NACK"))
+ rc = VERR_GENERAL_FAILURE;
+ else
+ {
+ atsReplyBabble(pThis, pInst, "BABBLE ");
+ rc = VERR_NET_NOT_CONNECTED;
+ }
+ RTMemFree(pReply);
+ }
+ return rc;
+}
+
+/**
+ * Verifies and acknowledges a "TSET SND" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The test set end packet.
+ */
+static int atsDoTestSetSend(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb != sizeof(ATSPKTREQTSETSND))
+ return atsReplyBadSize(pThis, pInst, pPktHdr, sizeof(ATSPKTREQTSETSND));
+
+ PATSPKTREQTSETSND pReq = (PATSPKTREQTSETSND)pPktHdr;
+
+ int rc = VINF_SUCCESS;
+
+ if (!pThis->Callbacks.pfnTestSetSendRead)
+ return atsReplyRC(pThis, pInst, pPktHdr, VERR_NOT_SUPPORTED, "Sending test set not implemented");
+
+ if (pThis->Callbacks.pfnTestSetSendBegin)
+ {
+ rc = pThis->Callbacks.pfnTestSetSendBegin(pThis->Callbacks.pvUser, pReq->szTag);
+ if (RT_FAILURE(rc))
+ return atsReplyRC(pThis, pInst, pPktHdr, rc, "Beginning sending test set '%s' failed", pReq->szTag);
+ }
+
+ for (;;)
+ {
+ uint32_t uMyCrc32 = RTCrc32Start();
+ struct
+ {
+ ATSPKTHDR Hdr;
+ uint32_t uCrc32;
+ char ab[_64K];
+ char abPadding[ATSPKT_ALIGNMENT];
+ } Pkt;
+#ifdef DEBUG
+ RT_ZERO(Pkt);
+#endif
+ size_t cbRead = 0;
+ rc = pThis->Callbacks.pfnTestSetSendRead(pThis->Callbacks.pvUser, pReq->szTag, &Pkt.ab, sizeof(Pkt.ab), &cbRead);
+ if ( RT_FAILURE(rc)
+ || cbRead == 0)
+ {
+ if ( rc == VERR_EOF
+ || (RT_SUCCESS(rc) && cbRead == 0))
+ {
+ Pkt.uCrc32 = RTCrc32Finish(uMyCrc32);
+ rc = atsReplyInternal(pThis, pInst, &Pkt.Hdr, "DATA EOF", sizeof(uint32_t) /* uCrc32 */);
+ if (RT_SUCCESS(rc))
+ rc = atsWaitForAck(pThis, pInst, &Pkt.Hdr);
+ }
+ else
+ rc = atsReplyRC(pThis, pInst, pPktHdr, rc, "Sending data for test set '%s' failed", pReq->szTag);
+ break;
+ }
+
+ uMyCrc32 = RTCrc32Process(uMyCrc32, &Pkt.ab[0], cbRead);
+ Pkt.uCrc32 = RTCrc32Finish(uMyCrc32);
+
+ Log2Func(("cbRead=%zu -> uCrc32=%#x\n", cbRead, Pkt.uCrc32));
+
+ Assert(cbRead <= sizeof(Pkt.ab));
+
+ rc = atsReplyInternal(pThis, pInst, &Pkt.Hdr, "DATA ", sizeof(uint32_t) /* uCrc32 */ + cbRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = atsWaitForAck(pThis, pInst, &Pkt.Hdr);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (pThis->Callbacks.pfnTestSetSendEnd)
+ {
+ int rc2 = pThis->Callbacks.pfnTestSetSendEnd(pThis->Callbacks.pvUser, pReq->szTag);
+ if (RT_FAILURE(rc2))
+ return atsReplyRC(pThis, pInst, pPktHdr, rc2, "Ending sending test set '%s' failed", pReq->szTag);
+ }
+
+ return rc;
+}
+
+/**
+ * Verifies and processes a "TN PLY" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet header.
+ */
+static int atsDoTonePlay(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb < sizeof(ATSPKTREQTONEPLAY))
+ return atsReplyBadSize(pThis, pInst, pPktHdr, sizeof(ATSPKTREQTONEPLAY));
+
+ if (pInst->enmState != ATSCLIENTSTATE_READY)
+ return atsReplyInvalidState(pThis, pInst, pPktHdr);
+
+ int rc = VINF_SUCCESS;
+
+ PATSPKTREQTONEPLAY pReq = (PATSPKTREQTONEPLAY)pPktHdr;
+
+ if (pThis->Callbacks.pfnTonePlay)
+ rc = pThis->Callbacks.pfnTonePlay(pThis->Callbacks.pvUser, &pReq->ToneParms);
+
+ if (RT_SUCCESS(rc))
+ rc = atsReplyAck(pThis, pInst, pPktHdr);
+ else
+ rc = atsReplyRC(pThis, pInst, pPktHdr, rc, "Playing test tone failed");
+ return rc;
+}
+
+/**
+ * Verifies and processes a "TN REC" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The opaque ATS instance structure.
+ * @param pPktHdr The packet header.
+ */
+static int atsDoToneRecord(PATSSERVER pThis, PATSCLIENTINST pInst, PATSPKTHDR pPktHdr)
+{
+ if (pPktHdr->cb < sizeof(ATSPKTREQTONEREC))
+ return atsReplyBadSize(pThis, pInst, pPktHdr, sizeof(ATSPKTREQTONEREC));
+
+ if (pInst->enmState != ATSCLIENTSTATE_READY)
+ return atsReplyInvalidState(pThis, pInst, pPktHdr);
+
+ int rc = VINF_SUCCESS;
+
+ PATSPKTREQTONEREC pReq = (PATSPKTREQTONEREC)pPktHdr;
+
+ if (pThis->Callbacks.pfnToneRecord)
+ rc = pThis->Callbacks.pfnToneRecord(pThis->Callbacks.pvUser, &pReq->ToneParms);
+
+ if (RT_SUCCESS(rc))
+ rc = atsReplyAck(pThis, pInst, pPktHdr);
+ else
+ rc = atsReplyRC(pThis, pInst, pPktHdr, rc, "Recording test tone failed");
+ return rc;
+}
+
+/**
+ * Main request processing routine for each client.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ATS instance.
+ * @param pInst The ATS client structure sending the request.
+ * @param pfDisconnect Where to return whether to disconnect the client on success or not.
+ */
+static int atsClientReqProcess(PATSSERVER pThis, PATSCLIENTINST pInst, bool *pfDisconnect)
+{
+ LogRelFlowFuncEnter();
+
+ /*
+ * Read client command packet and process it.
+ */
+ PATSPKTHDR pPktHdr = NULL;
+ int rc = atsRecvPkt(pThis, pInst, &pPktHdr, true /*fAutoRetryOnFailure*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do a string switch on the opcode bit.
+ */
+ /* Connection: */
+ if ( atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_HOWDY))
+ rc = atsDoHowdy(pThis, pInst, pPktHdr);
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_BYE))
+ {
+ rc = atsDoBye(pThis, pInst, pPktHdr);
+ if (RT_SUCCESS(rc))
+ *pfDisconnect = true;
+ }
+ /* Test set handling: */
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TESTSET_BEGIN))
+ rc = atsDoTestSetBegin(pThis, pInst, pPktHdr);
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TESTSET_END))
+ rc = atsDoTestSetEnd(pThis, pInst, pPktHdr);
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TESTSET_SEND))
+ rc = atsDoTestSetSend(pThis, pInst, pPktHdr);
+ /* Audio testing: */
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TONE_PLAY))
+ rc = atsDoTonePlay(pThis, pInst, pPktHdr);
+ else if (atsIsSameOpcode(pPktHdr, ATSPKT_OPCODE_TONE_RECORD))
+ rc = atsDoToneRecord(pThis, pInst, pPktHdr);
+ /* Misc: */
+ else
+ rc = atsReplyUnknown(pThis, pInst, pPktHdr);
+
+ RTMemFree(pPktHdr);
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Disconnects a client.
+ *
+ * @returns VBox status code.
+ * @param pThis The ATS instance.
+ * @param pInst The ATS client to disconnect.
+ */
+static int atsClientDisconnect(PATSSERVER pThis, PATSCLIENTINST pInst)
+{
+ AssertReturn(pInst->enmState != ATSCLIENTSTATE_DESTROYING, VERR_WRONG_ORDER);
+
+ pInst->enmState = ATSCLIENTSTATE_DESTROYING;
+
+ if ( pThis->pTransportInst
+ && pInst->pTransportClient)
+ {
+ if (pThis->pTransport->pfnNotifyBye)
+ pThis->pTransport->pfnNotifyBye(pThis->pTransportInst, pInst->pTransportClient);
+
+ pThis->pTransport->pfnDisconnect(pThis->pTransportInst, pInst->pTransportClient);
+ /* Pointer is now invalid due to the call above. */
+ pInst->pTransportClient = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Free's (destroys) a client instance.
+ *
+ * @param pInst The opaque ATS instance structure.
+ */
+static void atsClientFree(PATSCLIENTINST pInst)
+{
+ if (!pInst)
+ return;
+
+ /* Make sure that there is no transport client associated with it anymore. */
+ AssertReturnVoid(pInst->enmState == ATSCLIENTSTATE_DESTROYING);
+ AssertReturnVoid(pInst->pTransportClient == NULL);
+
+ if (pInst->pszHostname)
+ {
+ RTStrFree(pInst->pszHostname);
+ pInst->pszHostname = NULL;
+ }
+
+ RTMemFree(pInst);
+ pInst = NULL;
+}
+
+/**
+ * The main thread worker serving the clients.
+ */
+static DECLCALLBACK(int) atsClientWorker(RTTHREAD hThread, void *pvUser)
+{
+ RT_NOREF(hThread);
+
+ PATSSERVER pThis = (PATSSERVER)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ unsigned cClientsMax = 0;
+ unsigned cClientsCur = 0;
+ PATSCLIENTINST *papInsts = NULL;
+
+ /* Add the pipe to the poll set. */
+ int rc = RTPollSetAddPipe(pThis->hPollSet, pThis->hPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 0);
+ if (RT_SUCCESS(rc))
+ {
+ while (!pThis->fTerminate)
+ {
+ uint32_t fEvts;
+ uint32_t uId;
+ rc = RTPoll(pThis->hPollSet, RT_INDEFINITE_WAIT, &fEvts, &uId);
+ LogRelFlowFunc(("RTPoll(...) returned fEvts=#%x, uId=%RU32 -> %Rrc\n", fEvts, uId, rc));
+ if (RT_SUCCESS(rc))
+ {
+ if (uId == 0)
+ {
+ if (fEvts & RTPOLL_EVT_ERROR)
+ break;
+
+ /* We got woken up because of a new client. */
+ Assert(fEvts & RTPOLL_EVT_READ);
+
+ uint8_t bRead;
+ size_t cbRead = 0;
+ rc = RTPipeRead(pThis->hPipeR, &bRead, 1, &cbRead);
+ AssertRC(rc);
+
+ RTCritSectEnter(&pThis->CritSectClients);
+ /* Walk the list and add all new clients. */
+ PATSCLIENTINST pIt, pItNext;
+ RTListForEachSafe(&pThis->LstClientsNew, pIt, pItNext, ATSCLIENTINST, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ Assert(cClientsCur <= cClientsMax);
+ if (cClientsCur == cClientsMax)
+ {
+ /* Realloc to accommodate for the new clients. */
+ PATSCLIENTINST *papInstsNew = (PATSCLIENTINST *)RTMemReallocZ(papInsts, cClientsMax * sizeof(PATSCLIENTINST), (cClientsMax + 10) * sizeof(PATSCLIENTINST));
+ if (RT_LIKELY(papInstsNew))
+ {
+ cClientsMax += 10;
+ papInsts = papInstsNew;
+ }
+ }
+ if (cClientsCur < cClientsMax)
+ {
+ /* Find a free slot in the client array. */
+ unsigned idxSlt = 0;
+ while ( idxSlt < cClientsMax
+ && papInsts[idxSlt] != NULL)
+ idxSlt++;
+
+ rc = pThis->pTransport->pfnPollSetAdd(pThis->pTransportInst, pThis->hPollSet, pIt->pTransportClient, idxSlt + 1);
+ if (RT_SUCCESS(rc))
+ {
+ cClientsCur++;
+ papInsts[idxSlt] = pIt;
+ }
+ else
+ {
+ atsClientDisconnect(pThis, pIt);
+ atsClientFree(pIt);
+ pIt = NULL;
+ }
+ }
+ else
+ {
+ atsClientDisconnect(pThis, pIt);
+ atsClientFree(pIt);
+ pIt = NULL;
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectClients);
+ }
+ else
+ {
+ bool fDisconnect = false;
+
+ /* Client sends a request, pick the right client and process it. */
+ PATSCLIENTINST pInst = papInsts[uId - 1];
+ AssertPtr(pInst);
+ if (fEvts & RTPOLL_EVT_READ)
+ rc = atsClientReqProcess(pThis, pInst, &fDisconnect);
+
+ if ( (fEvts & RTPOLL_EVT_ERROR)
+ || RT_FAILURE(rc)
+ || fDisconnect)
+ {
+ /* Close connection and remove client from array. */
+ int rc2 = pThis->pTransport->pfnPollSetRemove(pThis->pTransportInst, pThis->hPollSet, pInst->pTransportClient, uId);
+ AssertRC(rc2);
+
+ atsClientDisconnect(pThis, pInst);
+ atsClientFree(pInst);
+ pInst = NULL;
+
+ papInsts[uId - 1] = NULL;
+ Assert(cClientsCur);
+ cClientsCur--;
+ }
+ }
+ }
+ }
+ }
+
+ if (papInsts)
+ {
+ for (size_t i = 0; i < cClientsMax; i++)
+ RTMemFree(papInsts[i]);
+ RTMemFree(papInsts);
+ }
+
+ return rc;
+}
+
+/**
+ * The main thread waiting for new client connections.
+ *
+ * @returns VBox status code.
+ */
+static DECLCALLBACK(int) atsMainThread(RTTHREAD hThread, void *pvUser)
+{
+ RT_NOREF(hThread);
+
+ LogRelFlowFuncEnter();
+
+ PATSSERVER pThis = (PATSSERVER)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = RTThreadUserSignal(hThread);
+ AssertRCReturn(rc, rc);
+
+ while (!pThis->fTerminate)
+ {
+ /*
+ * Wait for new connection and spin off a new thread
+ * for every new client.
+ */
+ bool fFromServer;
+ PATSTRANSPORTCLIENT pTransportClient;
+ rc = pThis->pTransport->pfnWaitForConnect(pThis->pTransportInst, 1000 /* msTimeout */, &fFromServer, &pTransportClient);
+ if (RT_FAILURE(rc))
+ continue;
+
+ /*
+ * New connection, create new client structure and spin off
+ * the request handling thread.
+ */
+ PATSCLIENTINST pInst = (PATSCLIENTINST)RTMemAllocZ(sizeof(ATSCLIENTINST));
+ if (RT_LIKELY(pInst))
+ {
+ pInst->enmState = ATSCLIENTSTATE_INITIALISING;
+ pInst->pTransportClient = pTransportClient;
+ pInst->pszHostname = NULL;
+
+ /* Add client to the new list and inform the worker thread. */
+ RTCritSectEnter(&pThis->CritSectClients);
+ RTListAppend(&pThis->LstClientsNew, &pInst->NdLst);
+ RTCritSectLeave(&pThis->CritSectClients);
+
+ size_t cbWritten = 0;
+ rc = RTPipeWrite(pThis->hPipeW, "", 1, &cbWritten);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("Failed to inform worker thread of a new client, rc=%Rrc\n", rc));
+ }
+ else
+ {
+ LogRelFunc(("Creating new client structure failed with out of memory error\n"));
+ pThis->pTransport->pfnNotifyBye(pThis->pTransportInst, pTransportClient);
+ rc = VERR_NO_MEMORY;
+ break; /* This is fatal, break out of the loop. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRelFunc(("New connection established (%s)\n", fFromServer ? "from server" : "as client"));
+
+ /**
+ * If the new client is not from our server but from a remote server (also called a reverse connection),
+ * exit this loop and stop trying to connect to the remote server.
+ *
+ * Otherwise we would connect lots and lots of clients without any real use.
+ *
+ ** @todo Improve this handling -- there might be a better / more elegant solution.
+ */
+ if (!fFromServer)
+ break;
+ }
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Initializes an ATS instance.
+ *
+ * @note This does *not* start the server!
+ *
+ * @returns VBox status code.
+ * @param pThis The ATS instance.
+ * @param pCallbacks The callbacks table to use.
+ */
+int AudioTestSvcInit(PATSSERVER pThis, PCATSCALLBACKS pCallbacks)
+{
+ LogRelFlowFuncEnter();
+
+ RT_BZERO(pThis, sizeof(ATSSERVER));
+
+ pThis->hPipeR = NIL_RTPIPE;
+ pThis->hPipeW = NIL_RTPIPE;
+
+ RTListInit(&pThis->LstClientsNew);
+
+ /* Copy callback table. */
+ memcpy(&pThis->Callbacks, pCallbacks, sizeof(ATSCALLBACKS));
+
+ int rc = RTCritSectInit(&pThis->CritSectClients);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&pThis->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeCreate(&pThis->hPipeR, &pThis->hPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The default transporter is the first one.
+ */
+ pThis->pTransport = g_apTransports[0]; /** @todo Make this dynamic. */
+
+ rc = pThis->pTransport->pfnCreate(&pThis->pTransportInst);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTPipeClose(pThis->hPipeR);
+ RTPipeClose(pThis->hPipeW);
+ }
+ else
+ LogRel(("Creating communications pipe failed with %Rrc\n", rc));
+
+ RTPollSetDestroy(pThis->hPollSet);
+ }
+ else
+ LogRel(("Creating pollset failed with %Rrc\n", rc));
+
+ RTCritSectDelete(&pThis->CritSectClients);
+ }
+ else
+ LogRel(("Creating critical section failed with %Rrc\n", rc));
+
+ if (RT_FAILURE(rc))
+ LogRel(("Creating server failed with %Rrc\n", rc));
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles a command line option.
+ *
+ * @returns VBox status code.
+ * @param pThis The ATS instance to handle option for.
+ * @param ch Option (short) to handle.
+ * @param pVal Option union to store the result in on success.
+ */
+int AudioTestSvcHandleOption(PATSSERVER pThis, int ch, PCRTGETOPTUNION pVal)
+{
+ AssertPtrReturn(pThis->pTransport, VERR_WRONG_ORDER); /* Must be creatd first. */
+ if (!pThis->pTransport->pfnOption)
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ return pThis->pTransport->pfnOption(pThis->pTransportInst, ch, pVal);
+}
+
+/**
+ * Starts a formerly initialized ATS instance.
+ *
+ * @returns VBox status code.
+ * @param pThis The ATS instance to start.
+ */
+int AudioTestSvcStart(PATSSERVER pThis)
+{
+ LogRelFlowFuncEnter();
+
+ /* Spin off the thread serving connections. */
+ int rc = RTThreadCreate(&pThis->hThreadServing, atsClientWorker, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE,
+ "ATSCLWORK");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Creating the client worker thread failed with %Rrc\n", rc));
+ return rc;
+ }
+
+ rc = pThis->pTransport->pfnStart(pThis->pTransportInst);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin off the connection thread. */
+ rc = RTThreadCreate(&pThis->hThreadMain, atsMainThread, pThis, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+ "ATSMAIN");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(pThis->hThreadMain, RT_MS_30SEC);
+ if (RT_SUCCESS(rc))
+ pThis->fStarted = true;
+ }
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Stops (shuts down) a formerly started ATS instance.
+ *
+ * @returns VBox status code.
+ * @param pThis The ATS instance.
+ */
+int AudioTestSvcStop(PATSSERVER pThis)
+{
+ if (!pThis->fStarted)
+ return VINF_SUCCESS;
+
+ LogRelFlowFuncEnter();
+
+ ASMAtomicXchgBool(&pThis->fTerminate, true);
+
+ if (pThis->pTransport)
+ pThis->pTransport->pfnStop(pThis->pTransportInst);
+
+ size_t cbWritten;
+ int rc = RTPipeWrite(pThis->hPipeW, "", 1, &cbWritten);
+ AssertRCReturn(rc, rc);
+
+ /* First close serving thread. */
+ int rcThread;
+ rc = RTThreadWait(pThis->hThreadServing, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rcThread;
+ if (RT_SUCCESS(rc))
+ {
+ /* Close the main thread last. */
+ rc = RTThreadWait(pThis->hThreadMain, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ if (rc == VERR_TCP_SERVER_DESTROYED)
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ pThis->fStarted = false;
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys an ATS instance, internal version.
+ *
+ * @returns VBox status code.
+ * @param pThis ATS instance to destroy.
+ */
+static int audioTestSvcDestroyInternal(PATSSERVER pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->hPipeR != NIL_RTPIPE)
+ {
+ rc = RTPipeClose(pThis->hPipeR);
+ AssertRCReturn(rc, rc);
+ pThis->hPipeR = NIL_RTPIPE;
+ }
+
+ if (pThis->hPipeW != NIL_RTPIPE)
+ {
+ rc = RTPipeClose(pThis->hPipeW);
+ AssertRCReturn(rc, rc);
+ pThis->hPipeW = NIL_RTPIPE;
+ }
+
+ RTPollSetDestroy(pThis->hPollSet);
+ pThis->hPollSet = NIL_RTPOLLSET;
+
+ PATSCLIENTINST pIt, pItNext;
+ RTListForEachSafe(&pThis->LstClientsNew, pIt, pItNext, ATSCLIENTINST, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ atsClientDisconnect(pThis, pIt);
+ atsClientFree(pIt);
+ }
+
+ if (RTCritSectIsInitialized(&pThis->CritSectClients))
+ {
+ rc = RTCritSectDelete(&pThis->CritSectClients);
+ AssertRCReturn(rc, rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys an ATS instance.
+ *
+ * @returns VBox status code.
+ * @param pThis ATS instance to destroy.
+ */
+int AudioTestSvcDestroy(PATSSERVER pThis)
+{
+ LogRelFlowFuncEnter();
+
+ int rc = audioTestSvcDestroyInternal(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pTransport)
+ {
+ if ( pThis->pTransport->pfnDestroy
+ && pThis->pTransportInst)
+ {
+ pThis->pTransport->pfnDestroy(pThis->pTransportInst);
+ pThis->pTransportInst = NULL;
+ }
+ }
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
diff --git a/src/VBox/Devices/Audio/AudioTestService.h b/src/VBox/Devices/Audio/AudioTestService.h
new file mode 100644
index 00000000..d7f6c651
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestService.h
@@ -0,0 +1,217 @@
+/* $Id: AudioTestService.h $ */
+/** @file
+ * AudioTestService - Audio test execution server, Public Header.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioTestService_h
+#define VBOX_INCLUDED_SRC_Audio_AudioTestService_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/tcp.h>
+
+#include "AudioTestServiceInternal.h"
+
+extern const PCATSTRANSPORT g_apTransports[];
+extern const size_t g_cTransports;
+
+/** Default TCP/IP bind port the guest ATS (Audio Test Service) is listening on. */
+#define ATS_TCP_DEF_BIND_PORT_GUEST 6042
+/** Default TCP/IP bind port the host ATS is listening on. */
+#define ATS_TCP_DEF_BIND_PORT_HOST 6052
+/** Default TCP/IP ATS bind port the ValidationKit Audio Driver ATS is listening on. */
+#define ATS_TCP_DEF_BIND_PORT_VALKIT 6062
+/** Default TCP/IP port the guest ATS is connecting to. */
+#define ATS_TCP_DEF_CONNECT_PORT_GUEST ATS_TCP_DEF_BIND_PORT_HOST
+/** Default TCP/IP port the host ATS is connecting to the guest (needs NAT port forwarding). */
+#define ATS_TCP_DEF_CONNECT_PORT_HOST_PORT_FWD 6072
+/** Default TCP/IP port the host ATS is connecting to. */
+#define ATS_TCP_DEF_CONNECT_PORT_VALKIT ATS_TCP_DEF_BIND_PORT_VALKIT
+/** Default TCP/IP address the host is connecting to. */
+#define ATS_TCP_DEF_CONNECT_HOST_ADDR_STR "127.0.0.1"
+/** Default TCP/IP address the guest ATS connects to when
+ * running in client mode (reversed mode, needed for NATed VMs). */
+#define ATS_TCP_DEF_CONNECT_GUEST_STR "10.0.2.2"
+
+/**
+ * Structure for keeping an Audio Test Service (ATS) callback table.
+ */
+typedef struct ATSCALLBACKS
+{
+ /**
+ * Tells the implementation that a new client connected. Optional.
+ *
+ * @param pvUser User-supplied pointer to context data. Optional.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnHowdy, (void const *pvUser));
+
+ /**
+ * Tells the implementation that a client disconnected. Optional.
+ *
+ * @param pvUser User-supplied pointer to context data. Optional.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnBye, (void const *pvUser));
+
+ /**
+ * 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));
+
+ /**
+ * Marks the begin of sending 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 sending.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnTestSetSendBegin, (void const *pvUser, const char *pszTag));
+
+ /**
+ * Reads data from a test set for sending it.
+ *
+ * @returns VBox status code.
+ * @param pvUser User-supplied pointer to context data. Optional.
+ * @param pszTag Tag of test set to begin sending.
+ * @param pvBuf Where to store the read test set data.
+ * @param cbBuf Size of \a pvBuf (in bytes).
+ * @param pcbRead Where to return the amount of read data in bytes. Optional and can be NULL.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnTestSetSendRead, (void const *pvUser, const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead));
+
+ /**
+ * Marks the end of sending a test set. Optional.
+ *
+ * @returns VBox status code.
+ * @param pvUser User-supplied pointer to context data. Optional.
+ * @param pszTag Tag of test set to end sending.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnTestSetSendEnd, (void const *pvUser, const char *pszTag));
+
+ /**
+ * Plays a test tone.
+ *
+ * @returns VBox status code.
+ * @param pvUser User-supplied pointer to context data. Optional.
+ * @param pToneParms Tone parameters to use for playback.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnTonePlay, (void const *pvUser, PAUDIOTESTTONEPARMS pToneParms));
+
+ /**
+ * Records a test tone.
+ *
+ * @returns VBox status code.
+ * @param pvUser User-supplied pointer to context data. Optional.
+ * @param pToneParms Tone parameters to use for recording.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnToneRecord, (void const *pvUser, PAUDIOTESTTONEPARMS pToneParms));
+
+ /** Pointer to opaque user-provided context data. */
+ void const *pvUser;
+} ATSCALLBACKS;
+/** Pointer to a const ATS callbacks table. */
+typedef const struct ATSCALLBACKS *PCATSCALLBACKS;
+
+/**
+ * Structure for keeping an Audio Test Service (ATS) instance.
+ */
+typedef struct ATSSERVER
+{
+ /** Pointer to the selected transport layer. */
+ PCATSTRANSPORT pTransport;
+ /** Pointer to the transport instance. */
+ PATSTRANSPORTINST pTransportInst;
+ /** The callbacks table. */
+ ATSCALLBACKS Callbacks;
+ /** Whether server is in started state or not. */
+ bool volatile fStarted;
+ /** Whether to terminate or not. */
+ bool volatile fTerminate;
+ /** The main thread's poll set to handle new clients. */
+ RTPOLLSET hPollSet;
+ /** Pipe for communicating with the serving thread about new clients. - read end */
+ RTPIPE hPipeR;
+ /** Pipe for communicating with the serving thread about new clients. - write end */
+ RTPIPE hPipeW;
+ /** Main thread waiting for connections. */
+ RTTHREAD hThreadMain;
+ /** Thread serving connected clients. */
+ RTTHREAD hThreadServing;
+ /** Critical section protecting the list of new clients. */
+ RTCRITSECT CritSectClients;
+ /** List of new clients waiting to be picked up by the client worker thread. */
+ RTLISTANCHOR LstClientsNew;
+} ATSSERVER;
+/** Pointer to an Audio Test Service (ATS) instance. */
+typedef ATSSERVER *PATSSERVER;
+
+int AudioTestSvcInit(PATSSERVER pThis, PCATSCALLBACKS pCallbacks);
+int AudioTestSvcDestroy(PATSSERVER pThis);
+int AudioTestSvcHandleOption(PATSSERVER pThis, int ch, PCRTGETOPTUNION pVal);
+int AudioTestSvcStart(PATSSERVER pThis);
+int AudioTestSvcStop(PATSSERVER pThis);
+
+/**
+ * Enumeration for the server connection mode.
+ * Only applies to certain transport implementation like TCP/IP.
+ */
+typedef enum ATSCONNMODE
+{
+ /** Both: Uses parallel client and server connection methods (via threads). */
+ ATSCONNMODE_BOTH = 0,
+ /** Client only: Connects to a server. */
+ ATSCONNMODE_CLIENT,
+ /** Server only: Listens for new incoming client connections. */
+ ATSCONNMODE_SERVER,
+ /** 32bit hack. */
+ ATSCONNMODE_32BIT_HACK = 0x7fffffff
+} ATSCONNMODE;
+
+/** TCP/IP options for the ATS server.
+ * @todo Make this more abstract later. */
+enum ATSTCPOPT
+{
+ ATSTCPOPT_CONN_MODE = 5000,
+ ATSTCPOPT_BIND_ADDRESS,
+ ATSTCPOPT_BIND_PORT,
+ ATSTCPOPT_CONNECT_ADDRESS,
+ ATSTCPOPT_CONNECT_PORT
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioTestService_h */
+
diff --git a/src/VBox/Devices/Audio/AudioTestServiceClient.cpp b/src/VBox/Devices/Audio/AudioTestServiceClient.cpp
new file mode 100644
index 00000000..e826a9c5
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceClient.cpp
@@ -0,0 +1,608 @@
+/* $Id: AudioTestServiceClient.cpp $ */
+/** @file
+ * AudioTestServiceClient - Audio Test Service (ATS), Client helpers.
+ *
+ * Note: Only does TCP/IP as transport layer for now.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_TEST
+
+#include <iprt/crc.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+
+#include <VBox/log.h>
+
+#include "AudioTestService.h"
+#include "AudioTestServiceInternal.h"
+#include "AudioTestServiceClient.h"
+
+/** @todo Use common defines between server protocol and this client. */
+
+/**
+ * A generic ATS reply, used by the client
+ * to process the incoming packets.
+ */
+typedef struct ATSSRVREPLY
+{
+ char szOp[ATSPKT_OPCODE_MAX_LEN];
+ /** Pointer to payload data.
+ * This does *not* include the header! */
+ void *pvPayload;
+ /** Size (in bytes) of the payload data.
+ * This does *not* include the header! */
+ size_t cbPayload;
+} ATSSRVREPLY;
+/** Pointer to a generic ATS reply. */
+typedef struct ATSSRVREPLY *PATSSRVREPLY;
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int audioTestSvcClientDisconnectInternal(PATSCLIENT pClient);
+
+/**
+ * Initializes an ATS client, internal version.
+ *
+ * @param pClient Client to initialize.
+ */
+static void audioTestSvcClientInit(PATSCLIENT pClient)
+{
+ RT_BZERO(pClient, sizeof(ATSCLIENT));
+}
+
+/**
+ * Destroys an ATS server reply.
+ *
+ * @param pReply Reply to destroy.
+ */
+static void audioTestSvcClientReplyDestroy(PATSSRVREPLY pReply)
+{
+ if (!pReply)
+ return;
+
+ if (pReply->pvPayload)
+ {
+ Assert(pReply->cbPayload);
+ RTMemFree(pReply->pvPayload);
+ pReply->pvPayload = NULL;
+ }
+
+ pReply->cbPayload = 0;
+}
+
+/**
+ * Receives a reply from an ATS server.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to receive reply for.
+ * @param pReply Where to store the reply.
+ * The reply must be destroyed with audioTestSvcClientReplyDestroy() then.
+ * @param fNoDataOk If it's okay that the reply is not expected to have any payload.
+ */
+static int audioTestSvcClientRecvReply(PATSCLIENT pClient, PATSSRVREPLY pReply, bool fNoDataOk)
+{
+ LogFlowFuncEnter();
+
+ PATSPKTHDR pPktHdr;
+ int rc = pClient->pTransport->pfnRecvPkt(pClient->pTransportInst, pClient->pTransportClient, &pPktHdr);
+ if (RT_SUCCESS(rc))
+ {
+ AssertReleaseMsgReturn(pPktHdr->cb >= sizeof(ATSPKTHDR),
+ ("audioTestSvcClientRecvReply: Received invalid packet size (%RU32)\n", pPktHdr->cb),
+ VERR_NET_PROTOCOL_ERROR);
+ pReply->cbPayload = pPktHdr->cb - sizeof(ATSPKTHDR);
+ Log3Func(("szOp=%.8s, cb=%RU32\n", pPktHdr->achOpcode, pPktHdr->cb));
+ if (pReply->cbPayload)
+ {
+ pReply->pvPayload = RTMemDup((uint8_t *)pPktHdr + sizeof(ATSPKTHDR), pReply->cbPayload);
+ }
+ else
+ pReply->pvPayload = NULL;
+
+ if ( !pReply->cbPayload
+ && !fNoDataOk)
+ {
+ LogRelFunc(("Payload is empty (%zu), but caller expected data\n", pReply->cbPayload));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ else
+ {
+ memcpy(&pReply->szOp, &pPktHdr->achOpcode, sizeof(pReply->szOp));
+ }
+
+ RTMemFree(pPktHdr);
+ pPktHdr = NULL;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRelFunc(("Receiving reply from server failed with %Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a reply for an ATS server and checks if it is an acknowledge (success) one.
+ *
+ * @returns VBox status code.
+ * @retval VERR_NET_PROTOCOL_ERROR if the reply indicates a failure.
+ * @param pClient Client to receive reply for.
+ */
+static int audioTestSvcClientRecvAck(PATSCLIENT pClient)
+{
+ ATSSRVREPLY Reply;
+ RT_ZERO(Reply);
+
+ int rc = audioTestSvcClientRecvReply(pClient, &Reply, true /* fNoDataOk */);
+ if (RT_SUCCESS(rc))
+ {
+ /* Most likely cases first. */
+ if ( RTStrNCmp(Reply.szOp, "ACK ", ATSPKT_OPCODE_MAX_LEN) == 0)
+ {
+ /* Nothing to do here. */
+ }
+ else if (RTStrNCmp(Reply.szOp, "FAILED ", ATSPKT_OPCODE_MAX_LEN) == 0)
+ {
+ LogRelFunc(("Received error from server (cbPayload=%zu)\n", Reply.cbPayload));
+
+ if (Reply.cbPayload)
+ {
+ if ( Reply.cbPayload >= sizeof(int) /* At least the rc must be present. */
+ && Reply.cbPayload <= sizeof(ATSPKTREPFAIL) - sizeof(ATSPKTHDR))
+ {
+ rc = *(int *)Reply.pvPayload; /* Reach error code back to caller. */
+
+ const char *pcszMsg = (char *)Reply.pvPayload + sizeof(int);
+ /** @todo Check NULL termination of pcszMsg? */
+
+ LogRelFunc(("Error message: %s (%Rrc)\n", pcszMsg, rc));
+ }
+ else
+ {
+ LogRelFunc(("Received invalid failure payload (cb=%zu)\n", Reply.cbPayload));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ }
+ }
+ else
+ {
+ LogRelFunc(("Received invalid opcode ('%.8s')\n", Reply.szOp));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+
+ audioTestSvcClientReplyDestroy(&Reply);
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a message plus optional payload to an ATS server.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to send message for.
+ * @param pvHdr Pointer to header data to send.
+ * @param cbHdr Size (in bytes) of \a pvHdr to send.
+ */
+static int audioTestSvcClientSendMsg(PATSCLIENT pClient, void *pvHdr, size_t cbHdr)
+{
+ RT_NOREF(cbHdr);
+ AssertPtrReturn(pClient->pTransport, VERR_INVALID_POINTER);
+ AssertPtrReturn(pClient->pTransportInst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pClient->pTransportClient, VERR_INVALID_POINTER);
+ return pClient->pTransport->pfnSendPkt(pClient->pTransportInst, pClient->pTransportClient, (PCATSPKTHDR)pvHdr);
+}
+
+/**
+ * Initializes a client request header.
+ *
+ * @param pReqHdr Request header to initialize.
+ * @param cbReq Size (in bytes) the request will have (does *not* include payload).
+ * @param pszOp Operation to perform with the request.
+ * @param cbPayload Size (in bytes) of payload that will follow the header. Optional and can be 0.
+ */
+DECLINLINE(void) audioTestSvcClientReqHdrInit(PATSPKTHDR pReqHdr, size_t cbReq, const char *pszOp, size_t cbPayload)
+{
+ AssertReturnVoid(strlen(pszOp) >= 2);
+ AssertReturnVoid(strlen(pszOp) <= ATSPKT_OPCODE_MAX_LEN);
+
+ /** @todo Validate opcode. */
+
+ RT_BZERO(pReqHdr, sizeof(ATSPKTHDR));
+
+ memcpy(pReqHdr->achOpcode, pszOp, strlen(pszOp));
+ pReqHdr->uCrc32 = 0; /** @todo Do CRC-32 calculation. */
+ pReqHdr->cb = (uint32_t)cbReq + (uint32_t)cbPayload;
+
+ Assert(pReqHdr->cb <= ATSPKT_MAX_SIZE);
+}
+
+/**
+ * Sends an acknowledege response back to the server.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to send command for.
+ */
+static int audioTestSvcClientSendAck(PATSCLIENT pClient)
+{
+ ATSPKTHDR Req;
+ audioTestSvcClientReqHdrInit(&Req, sizeof(Req), "ACK ", 0);
+
+ return audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+}
+
+/**
+ * Sends a greeting command (handshake) to an ATS server.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to send command for.
+ */
+static int audioTestSvcClientDoGreet(PATSCLIENT pClient)
+{
+ ATSPKTREQHOWDY Req;
+ Req.uVersion = ATS_PROTOCOL_VS;
+ audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_HOWDY, 0);
+ int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = audioTestSvcClientRecvAck(pClient);
+ return rc;
+}
+
+/**
+ * Tells the ATS server that we want to disconnect.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to disconnect.
+ */
+static int audioTestSvcClientDoBye(PATSCLIENT pClient)
+{
+ ATSPKTHDR Req;
+ audioTestSvcClientReqHdrInit(&Req, sizeof(Req), ATSPKT_OPCODE_BYE, 0);
+ int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = audioTestSvcClientRecvAck(pClient);
+ return rc;
+}
+
+/**
+ * Creates an ATS client.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to create.
+ */
+int AudioTestSvcClientCreate(PATSCLIENT pClient)
+{
+ audioTestSvcClientInit(pClient);
+
+ /*
+ * The default transporter is the first one.
+ */
+ pClient->pTransport = g_apTransports[0]; /** @todo Make this dynamic. */
+
+ return pClient->pTransport->pfnCreate(&pClient->pTransportInst);
+}
+
+/**
+ * Destroys an ATS client.
+ *
+ * @param pClient Client to destroy.
+ */
+void AudioTestSvcClientDestroy(PATSCLIENT pClient)
+{
+ if (!pClient)
+ return;
+
+ /* ignore rc */ audioTestSvcClientDisconnectInternal(pClient);
+
+ if (pClient->pTransport)
+ {
+ pClient->pTransport->pfnDestroy(pClient->pTransportInst);
+ pClient->pTransportInst = NULL; /* Invalidate pointer. */
+ }
+}
+
+/**
+ * Handles a command line option.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to handle option for.
+ * @param ch Option (short) to handle.
+ * @param pVal Option union to store the result in on success.
+ */
+int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal)
+{
+ AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER); /* Must be created first via AudioTestSvcClientCreate(). */
+ if (!pClient->pTransport->pfnOption)
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ return pClient->pTransport->pfnOption(pClient->pTransportInst, ch, pVal);
+}
+
+/**
+ * Connects to an ATS peer, extended version.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to connect.
+ * @param msTimeout Timeout (in ms) waiting for a connection to be established.
+ * Use RT_INDEFINITE_WAIT to wait indefinitely.
+ */
+int AudioTestSvcClientConnectEx(PATSCLIENT pClient, RTMSINTERVAL msTimeout)
+{
+ if (pClient->pTransportClient)
+ return VERR_NET_ALREADY_CONNECTED;
+
+ int rc = pClient->pTransport->pfnStart(pClient->pTransportInst);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pClient->pTransport->pfnWaitForConnect(pClient->pTransportInst,
+ msTimeout, NULL /* pfFromServer */, &pClient->pTransportClient);
+ if (RT_SUCCESS(rc))
+ {
+ rc = audioTestSvcClientDoGreet(pClient);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRelFunc(("Connecting to server (%RU32ms timeout) failed with %Rrc\n", msTimeout, rc));
+
+ return rc;
+}
+
+/**
+ * Connects to an ATS peer.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to connect.
+ */
+int AudioTestSvcClientConnect(PATSCLIENT pClient)
+{
+ return AudioTestSvcClientConnectEx(pClient, 30 * 1000 /* msTimeout */);
+}
+
+/**
+ * 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));
+ 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));
+ if (RT_SUCCESS(rc))
+ rc = audioTestSvcClientRecvAck(pClient);
+
+ return rc;
+}
+
+/**
+ * Tells the server to play a (test) tone.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to issue command for.
+ * @param pToneParms Tone parameters to use.
+ * @note How (and if) the server plays a tone depends on the actual implementation side.
+ */
+int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
+{
+ ATSPKTREQTONEPLAY Req;
+
+ memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
+
+ audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
+
+ int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = audioTestSvcClientRecvAck(pClient);
+
+ return rc;
+}
+
+/**
+ * Tells the server to record a (test) tone.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to issue command for.
+ * @param pToneParms Tone parameters to use.
+ * @note How (and if) the server plays a tone depends on the actual implementation side.
+ */
+int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
+{
+ ATSPKTREQTONEREC Req;
+
+ memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
+
+ audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
+
+ int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = audioTestSvcClientRecvAck(pClient);
+
+ return rc;
+}
+
+/**
+ * Tells the server to send (download) a (packed up) test set archive.
+ * The test set must not be running / open anymore.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to issue command for.
+ * @param pszTag Tag of test set to send.
+ * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
+ */
+int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
+{
+ ATSPKTREQTSETSND Req;
+
+ int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
+ AssertRCReturn(rc, rc);
+
+ audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
+
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
+ AssertRCReturn(rc, rc);
+
+ rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
+ while (RT_SUCCESS(rc))
+ {
+ ATSSRVREPLY Reply;
+ RT_ZERO(Reply);
+
+ rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
+ if (RT_SUCCESS(rc))
+ {
+ /* Extract received CRC32 checksum. */
+ const size_t cbCrc32 = sizeof(uint32_t); /* Skip CRC32 in payload for actual CRC verification. */
+
+ uint32_t uSrcCrc32;
+ memcpy(&uSrcCrc32, Reply.pvPayload, cbCrc32);
+
+ if (uSrcCrc32)
+ {
+ const uint32_t uDstCrc32 = RTCrc32((uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32);
+
+ Log2Func(("uSrcCrc32=%#x, cbRead=%zu -> uDstCrc32=%#x\n"
+ "%.*Rhxd\n",
+ uSrcCrc32, Reply.cbPayload - cbCrc32, uDstCrc32,
+ RT_MIN(64, Reply.cbPayload - cbCrc32), (uint8_t *)Reply.pvPayload + cbCrc32));
+
+ if (uSrcCrc32 != uDstCrc32)
+ rc = VERR_TAR_CHKSUM_MISMATCH; /** @todo Fudge! */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
+ && Reply.pvPayload
+ && Reply.cbPayload)
+ {
+ rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32, NULL);
+ }
+ else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
+ {
+ rc = VINF_EOF;
+ }
+ else
+ {
+ AssertMsgFailed(("Got unexpected reply '%s'", Reply.szOp));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ audioTestSvcClientReplyDestroy(&Reply);
+
+ int rc2 = audioTestSvcClientSendAck(pClient);
+ if (rc == VINF_SUCCESS) /* Might be VINF_EOF already. */
+ rc = rc2;
+
+ if (rc == VINF_EOF)
+ break;
+ }
+
+ int rc2 = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Disconnects from an ATS server, internal version.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to disconnect.
+ */
+static int audioTestSvcClientDisconnectInternal(PATSCLIENT pClient)
+{
+ if (!pClient->pTransportClient) /* Not connected (yet)? Bail out early. */
+ return VINF_SUCCESS;
+
+ int rc = audioTestSvcClientDoBye(pClient);
+ if (RT_SUCCESS(rc))
+ {
+ if (pClient->pTransport->pfnNotifyBye)
+ pClient->pTransport->pfnNotifyBye(pClient->pTransportInst, pClient->pTransportClient);
+
+ pClient->pTransport->pfnDisconnect(pClient->pTransportInst, pClient->pTransportClient);
+ pClient->pTransportClient = NULL;
+
+ pClient->pTransport->pfnStop(pClient->pTransportInst);
+ }
+
+ return rc;
+}
+
+/**
+ * Disconnects from an ATS server.
+ *
+ * @returns VBox status code.
+ * @param pClient Client to disconnect.
+ */
+int AudioTestSvcClientDisconnect(PATSCLIENT pClient)
+{
+ return audioTestSvcClientDisconnectInternal(pClient);
+}
+
diff --git a/src/VBox/Devices/Audio/AudioTestServiceClient.h b/src/VBox/Devices/Audio/AudioTestServiceClient.h
new file mode 100644
index 00000000..00c567c2
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceClient.h
@@ -0,0 +1,83 @@
+/* $Id: AudioTestServiceClient.h $ */
+/** @file
+ * AudioTestServiceClient - Audio test execution server, Public Header.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioTestServiceClient_h
+#define VBOX_INCLUDED_SRC_Audio_AudioTestServiceClient_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "AudioTestServiceInternal.h"
+
+/**
+ * Structure for maintaining an ATS client.
+ */
+typedef struct ATSCLIENT
+{
+ /** Pointer to the selected transport layer. */
+ PCATSTRANSPORT pTransport;
+ /** Pointer to the selected transport instance to use. */
+ PATSTRANSPORTINST pTransportInst;
+ /** The opaque client instance. */
+ PATSTRANSPORTCLIENT pTransportClient;
+} ATSCLIENT;
+/** Pointer to an ATS client. */
+typedef ATSCLIENT *PATSCLIENT;
+
+/** @name Creation / destruction.
+ * @{ */
+int AudioTestSvcClientCreate(PATSCLIENT pClient);
+void AudioTestSvcClientDestroy(PATSCLIENT pClient);
+/** @} */
+
+/** @name Connection handling.
+ * @{ */
+int AudioTestSvcClientConnectEx(PATSCLIENT pClient, RTMSINTERVAL msTimeout);
+int AudioTestSvcClientConnect(PATSCLIENT pClient);
+int AudioTestSvcClientDisconnect(PATSCLIENT pClient);
+/** @} */
+
+/** @name Option handling.
+ * @{ */
+int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal);
+/** @} */
+
+/** @name Test set handling.
+ * @{ */
+int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag);
+int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag);
+int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs);
+/** @} */
+
+/** @name Tone handling.
+ * @{ */
+int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms);
+int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms);
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioTestServiceClient_h */
+
diff --git a/src/VBox/Devices/Audio/AudioTestServiceInternal.h b/src/VBox/Devices/Audio/AudioTestServiceInternal.h
new file mode 100644
index 00000000..50c38edf
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceInternal.h
@@ -0,0 +1,276 @@
+/* $Id: AudioTestServiceInternal.h $ */
+/** @file
+ * AudioTestService - Audio test execution server, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioTestServiceInternal_h
+#define VBOX_INCLUDED_SRC_Audio_AudioTestServiceInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/getopt.h>
+#include <iprt/stream.h>
+
+#include "AudioTestServiceProtocol.h"
+
+RT_C_DECLS_BEGIN
+
+/** Opaque ATS transport layer specific client data. */
+typedef struct ATSTRANSPORTCLIENT *PATSTRANSPORTCLIENT;
+typedef PATSTRANSPORTCLIENT *PPATSTRANSPORTCLIENT;
+
+/** Opaque ATS transport specific instance data. */
+typedef struct ATSTRANSPORTINST *PATSTRANSPORTINST;
+typedef PATSTRANSPORTINST *PPATSTRANSPORTINST;
+
+/**
+ * Transport layer descriptor.
+ */
+typedef struct ATSTRANSPORT
+{
+ /** The name. */
+ char szName[16];
+ /** The description. */
+ const char *pszDesc;
+ /** Pointer to an array of options. */
+ PCRTGETOPTDEF paOpts;
+ /** The number of options in the array. */
+ size_t cOpts;
+
+ /**
+ * Print the usage information for this transport layer.
+ *
+ * @param pStream The stream to print the usage info to.
+ *
+ * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnUsage,(PRTSTREAM pStream));
+
+ /**
+ * Creates a transport instance.
+ *
+ * @returns IPRT status code. On errors, the transport layer shall call
+ * RTMsgError to display the error details to the user.
+ * @param ppThis Where to return the created transport instance on success.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnCreate, (PPATSTRANSPORTINST ppThis));
+
+ /**
+ * Destroys a transport instance.
+ *
+ * On errors, the transport layer shall call RTMsgError to display the error
+ * details to the user.
+ *
+ * @returns IPRT status code. On errors, the transport layer shall call
+ * RTMsgError to display the error details to the user.
+ * @param pThis The transport instance.
+ * The pointer will be invalid on return.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnDestroy, (PATSTRANSPORTINST pThis));
+
+ /**
+ * Handle an option.
+ *
+ * When encountering an options that is not part of the base options, we'll call
+ * this method for each transport layer until one handles it.
+ *
+ * @retval VINF_SUCCESS if handled.
+ * @retval VERR_TRY_AGAIN if not handled.
+ * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status.
+ *
+ * @param pThis Transport instance to set options for.
+ * @param ch The short option value.
+ * @param pVal Pointer to the value union.
+ *
+ * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOption,(PATSTRANSPORTINST pThis, int ch, PCRTGETOPTUNION pVal));
+
+ /**
+ * Starts a transport instance.
+ *
+ * @returns IPRT status code. On errors, the transport layer shall call
+ * RTMsgError to display the error details to the user.
+ * @param pThis Transport instance to initialize.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnStart, (PATSTRANSPORTINST pThis));
+
+ /**
+ * Stops a transport instance, closing and freeing resources.
+ *
+ * On errors, the transport layer shall call RTMsgError to display the error
+ * details to the user.
+ *
+ * @param pThis The transport instance.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnStop, (PATSTRANSPORTINST pThis));
+
+ /**
+ * Waits for a new client to connect and returns the client specific data on
+ * success.
+ *
+ * @returns VBox status code.
+ * @param pThis The transport instance.
+ * @param msTimeout Timeout (in ms) waiting for a connection to be established.
+ * Use RT_INDEFINITE_WAIT to wait indefinitely.
+ * This might or might not be supported by the specific transport implementation.
+ * @param pfFromServer Returns \c true if the returned client is from a remote server (called a reverse connection),
+ * or \c false if not (regular client). Optional and can be NULL.
+ * @param ppClientNew Where to return the allocated client on success.
+ * Must be destroyed with pfnDisconnect() when done.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnWaitForConnect, (PATSTRANSPORTINST pThis, RTMSINTERVAL msTimeout, bool *pfFromServer, PPATSTRANSPORTCLIENT ppClientNew));
+
+ /**
+ * Disconnects a client and frees up its resources.
+ *
+ * @param pThis The transport instance.
+ * @param pClient Client to disconnect.
+ * The pointer will be invalid after calling.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnDisconnect, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient));
+
+ /**
+ * Polls for incoming packets.
+ *
+ * @returns true if there are pending packets, false if there isn't.
+ * @param pThis The transport instance.
+ * @param pClient The client to poll for data.
+ */
+ DECLR3CALLBACKMEMBER(bool, pfnPollIn, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient));
+
+ /**
+ * Adds any pollable handles to the poll set.
+ *
+ * @returns IPRT status code.
+ * @param pThis The transport instance.
+ * @param hPollSet The poll set to add them to.
+ * @param pClient The transport client structure.
+ * @param idStart The handle ID to start at.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (PATSTRANSPORTINST pThis, RTPOLLSET hPollSet, PATSTRANSPORTCLIENT pClient, uint32_t idStart));
+
+ /**
+ * Removes the given client frmo the given pollset.
+ *
+ * @returns IPRT status code.
+ * @param pThis The transport instance.
+ * @param hPollSet The poll set to remove from.
+ * @param pClient The transport client structure.
+ * @param idStart The handle ID to remove.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnPollSetRemove, (PATSTRANSPORTINST pThis, RTPOLLSET hPollSet, PATSTRANSPORTCLIENT pClient, uint32_t idStart));
+
+ /**
+ * Receives an incoming packet.
+ *
+ * This will block until the data becomes available or we're interrupted by a
+ * signal or something.
+ *
+ * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED,
+ * the current operation will be aborted when applicable. When
+ * interrupted, the transport layer will store the data until the next
+ * receive call.
+ *
+ * @param pThis The transport instance.
+ * @param pClient The transport client structure.
+ * @param ppPktHdr Where to return the pointer to the packet we've
+ * read. This is allocated from the heap using
+ * RTMemAlloc (w/ ATSPKT_ALIGNMENT) and must be
+ * free by calling RTMemFree.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnRecvPkt, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PPATSPKTHDR ppPktHdr));
+
+ /**
+ * Sends an outgoing packet.
+ *
+ * This will block until the data has been written.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_INTERRUPTED if interrupted before anything was sent.
+ *
+ * @param pThis The transport instance.
+ * @param pClient The transport client structure.
+ * @param pPktHdr The packet to send. The size is given by
+ * aligning the size in the header by
+ * ATSPKT_ALIGNMENT.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSendPkt, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PCATSPKTHDR pPktHdr));
+
+ /**
+ * Sends a babble packet and disconnects the client (if applicable).
+ *
+ * @param pThis The transport instance.
+ * @param pClient The transport client structure.
+ * @param pPktHdr The packet to send. The size is given by
+ * aligning the size in the header by
+ * ATSPKT_ALIGNMENT.
+ * @param cMsSendTimeout The send timeout measured in milliseconds.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnBabble, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PCATSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout));
+
+ /**
+ * Notification about a client HOWDY.
+ *
+ * @param pThis The transport instance.
+ * @param pClient The transport client structure.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient));
+
+ /**
+ * Notification about a client BYE.
+ *
+ * For connection oriented transport layers, it would be good to disconnect the
+ * client at this point.
+ *
+ * @param pThis The transport instance.
+ * @param pClient The transport client structure.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyBye, (PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient));
+
+ /**
+ * Notification about a REBOOT or SHUTDOWN.
+ *
+ * For connection oriented transport layers, stop listening for and
+ * accepting at this point.
+ *
+ * @param pThis The transport instance.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnNotifyReboot, (PATSTRANSPORTINST pThis));
+
+ /** Non-zero end marker. */
+ uint32_t u32EndMarker;
+} ATSTRANSPORT;
+/** Pointer to a const transport layer descriptor. */
+typedef const struct ATSTRANSPORT *PCATSTRANSPORT;
+
+
+extern ATSTRANSPORT const g_TcpTransport;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioTestServiceInternal_h */
+
diff --git a/src/VBox/Devices/Audio/AudioTestServiceProtocol.cpp b/src/VBox/Devices/Audio/AudioTestServiceProtocol.cpp
new file mode 100644
index 00000000..14ff0986
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceProtocol.cpp
@@ -0,0 +1,37 @@
+/* $Id: AudioTestServiceProtocol.cpp $ */
+/** @file
+ * AudioTestService - Audio test execution server, Protocol helpers.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+
+#include "AudioTestServiceProtocol.h"
+
diff --git a/src/VBox/Devices/Audio/AudioTestServiceProtocol.h b/src/VBox/Devices/Audio/AudioTestServiceProtocol.h
new file mode 100644
index 00000000..873d9ca0
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceProtocol.h
@@ -0,0 +1,268 @@
+/* $Id: AudioTestServiceProtocol.h $ */
+/** @file
+ * AudioTestServiceProtocol - Audio test execution server, Protocol Header.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_AudioTestServiceProtocol_h
+#define VBOX_INCLUDED_SRC_Audio_AudioTestServiceProtocol_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <iprt/list.h>
+
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "AudioTest.h"
+
+RT_C_DECLS_BEGIN
+
+/** Maximum length (in bytes) an opcode can have. */
+#define ATSPKT_OPCODE_MAX_LEN 8
+/** Packet alignment. */
+#define ATSPKT_ALIGNMENT 16
+/** Max packet size. */
+#define ATSPKT_MAX_SIZE _256K
+
+/**
+ * Common Packet header (for requests and replies).
+ */
+typedef struct ATSPKTHDR
+{
+ /** The unpadded packet length. This include this header. */
+ uint32_t cb;
+ /** The CRC-32 for the packet starting from the opcode field. 0 if the packet
+ * hasn't been CRCed. */
+ uint32_t uCrc32;
+ /** Packet opcode, an unterminated ASCII string. */
+ uint8_t achOpcode[ATSPKT_OPCODE_MAX_LEN];
+} ATSPKTHDR;
+AssertCompileSize(ATSPKTHDR, 16);
+/** Pointer to a packet header. */
+typedef ATSPKTHDR *PATSPKTHDR;
+/** Pointer to a packet header. */
+typedef ATSPKTHDR const *PCATSPKTHDR;
+/** Pointer to a packet header pointer. */
+typedef PATSPKTHDR *PPATSPKTHDR;
+
+#define ATSPKT_OPCODE_HOWDY "HOWDY "
+
+/** 32bit protocol version consisting of a 16bit major and 16bit minor part. */
+#define ATS_PROTOCOL_VS (ATS_PROTOCOL_VS_MAJOR | ATS_PROTOCOL_VS_MINOR)
+/** The major version part of the protocol version. */
+#define ATS_PROTOCOL_VS_MAJOR (1 << 16)
+/** The minor version part of the protocol version. */
+#define ATS_PROTOCOL_VS_MINOR (0)
+
+/**
+ * The HOWDY request structure.
+ */
+typedef struct ATSPKTREQHOWDY
+{
+ /** Embedded packet header. */
+ ATSPKTHDR Hdr;
+ /** Version of the protocol the client wants to use. */
+ uint32_t uVersion;
+ /** Alignment. */
+ uint8_t au8Padding[12];
+} ATSPKTREQHOWDY;
+AssertCompileSizeAlignment(ATSPKTREQHOWDY, ATSPKT_ALIGNMENT);
+/** Pointer to a HOWDY request structure. */
+typedef ATSPKTREQHOWDY *PATSPKTREQHOWDY;
+
+/**
+ * The HOWDY reply structure.
+ */
+typedef struct ATSPKTREPHOWDY
+{
+ /** Packet header. */
+ ATSPKTHDR Hdr;
+ /** Version to use for the established connection. */
+ uint32_t uVersion;
+ /** Padding - reserved. */
+ uint8_t au8Padding[12];
+} ATSPKTREPHOWDY;
+AssertCompileSizeAlignment(ATSPKTREPHOWDY, ATSPKT_ALIGNMENT);
+/** Pointer to a HOWDY reply structure. */
+typedef ATSPKTREPHOWDY *PATSPKTREPHOWDY;
+
+#define ATSPKT_OPCODE_BYE "BYE "
+
+/* No additional structures for BYE. */
+
+#define ATSPKT_OPCODE_TESTSET_BEGIN "TSET BEG"
+
+/**
+ * The TSET BEG (test set begin) 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 (test set 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_TESTSET_SEND "TSET SND"
+
+/**
+ * The TSET SND (test set send) request structure.
+ */
+typedef struct ATSPKTREQTSETSND
+{
+ /** Embedded packet header. */
+ ATSPKTHDR Hdr;
+ /** Audio test set tag to use. */
+ char szTag[AUDIOTEST_TAG_MAX];
+} ATSPKTREQTSETSND;
+AssertCompileSizeAlignment(ATSPKTREQTSETSND, ATSPKT_ALIGNMENT);
+/** Pointer to a ATSPKTREQTSETSND structure. */
+typedef ATSPKTREQTSETSND *PATSPKTREQTSETSND;
+
+#define ATSPKT_OPCODE_TONE_PLAY "TN PLY "
+
+/**
+ * The TN PLY (tone play) request structure.
+ */
+typedef struct ATSPKTREQTONEPLAY
+{
+ /** Embedded packet header. */
+ ATSPKTHDR Hdr;
+ /** Test tone parameters for playback. */
+ AUDIOTESTTONEPARMS ToneParms;
+#if ARCH_BITS == 64
+ uint8_t aPadding[8];
+#else
+# ifdef RT_OS_WINDOWS
+ uint8_t aPadding[4];
+# else
+ uint8_t aPadding[12];
+# endif
+#endif
+} ATSPKTREQTONEPLAY;
+AssertCompileSizeAlignment(ATSPKTREQTONEPLAY, ATSPKT_ALIGNMENT);
+/** Pointer to a ATSPKTREQTONEPLAY structure. */
+typedef ATSPKTREQTONEPLAY *PATSPKTREQTONEPLAY;
+
+#define ATSPKT_OPCODE_TONE_RECORD "TN REC "
+
+/**
+ * The TN REC (tone record) request structure.
+ */
+typedef struct ATSPKTREQTONEREC
+{
+ /** Embedded packet header. */
+ ATSPKTHDR Hdr;
+ /** Test tone parameters for playback. */
+ AUDIOTESTTONEPARMS ToneParms;
+#if ARCH_BITS == 64
+ uint8_t aPadding[8];
+#else
+# ifdef RT_OS_WINDOWS
+ uint8_t aPadding[4];
+# else
+ uint8_t aPadding[12];
+# endif
+#endif
+} ATSPKTREQTONEREC;
+AssertCompileSizeAlignment(ATSPKTREQTONEREC, ATSPKT_ALIGNMENT);
+/** Pointer to a ATSPKTREQTONEREC structure. */
+typedef ATSPKTREQTONEREC *PATSPKTREQTONEREC;
+
+/* No additional structure for the reply (just standard STATUS packet). */
+
+/**
+ * The failure reply structure.
+ */
+typedef struct ATSPKTREPFAIL
+{
+ /** Embedded packet header. */
+ ATSPKTHDR Hdr;
+ /** Error code (IPRT-style). */
+ int rc;
+ /** Error description. */
+ char ach[256];
+} ATSPKTREPFAIL;
+/** Pointer to a ATSPKTREPFAIL structure. */
+typedef ATSPKTREPFAIL *PATSPKTREPFAIL;
+
+/**
+ * Checks if the two opcodes match.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pPktHdr The packet header.
+ * @param pszOpcode2 The opcode we're comparing with. Does not have
+ * to be the whole 8 chars long.
+ */
+DECLINLINE(bool) atsIsSameOpcode(PCATSPKTHDR pPktHdr, const char *pszOpcode2)
+{
+ if (pPktHdr->achOpcode[0] != pszOpcode2[0])
+ return false;
+ if (pPktHdr->achOpcode[1] != pszOpcode2[1])
+ return false;
+
+ unsigned i = 2;
+ while ( i < RT_SIZEOFMEMB(ATSPKTHDR, achOpcode)
+ && pszOpcode2[i] != '\0')
+ {
+ if (pPktHdr->achOpcode[i] != pszOpcode2[i])
+ break;
+ i++;
+ }
+
+ if ( i < RT_SIZEOFMEMB(ATSPKTHDR, achOpcode)
+ && pszOpcode2[i] == '\0')
+ {
+ while ( i < RT_SIZEOFMEMB(ATSPKTHDR, achOpcode)
+ && pPktHdr->achOpcode[i] == ' ')
+ i++;
+ }
+
+ return i == RT_SIZEOFMEMB(ATSPKTHDR, achOpcode);
+}
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioTestServiceProtocol_h */
diff --git a/src/VBox/Devices/Audio/AudioTestServiceTcp.cpp b/src/VBox/Devices/Audio/AudioTestServiceTcp.cpp
new file mode 100644
index 00000000..8177a008
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioTestServiceTcp.cpp
@@ -0,0 +1,967 @@
+/* $Id: AudioTestServiceTcp.cpp $ */
+/** @file
+ * AudioTestServiceTcp - Audio test execution server, TCP/IP Transport Layer.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_TEST
+#include <iprt/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/log.h>
+
+#include "AudioTestService.h"
+#include "AudioTestServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * TCP specific client data.
+ */
+typedef struct ATSTRANSPORTCLIENT
+{
+ /** Socket of the current client. */
+ RTSOCKET hTcpClient;
+ /** Indicates whether \a hTcpClient comes from the server or from a client
+ * connect (relevant when closing it). */
+ bool fFromServer;
+ /** The size of the stashed data. */
+ size_t cbTcpStashed;
+ /** The size of the stashed data allocation. */
+ size_t cbTcpStashedAlloced;
+ /** The stashed data. */
+ uint8_t *pbTcpStashed;
+} ATSTRANSPORTCLIENT;
+
+/**
+ * Structure for keeping Audio Test Service (ATS) transport instance-specific data.
+ */
+typedef struct ATSTRANSPORTINST
+{
+ /** Critical section for serializing access. */
+ RTCRITSECT CritSect;
+ /** Connection mode to use. */
+ ATSCONNMODE enmConnMode;
+ /** The addresses to bind to. Empty string means any. */
+ char szBindAddr[256];
+ /** The TCP port to listen to. */
+ uint32_t uBindPort;
+ /** The addresses to connect to if running in reversed (VM NATed) mode. */
+ char szConnectAddr[256];
+ /** The TCP port to connect to if running in reversed (VM NATed) mode. */
+ uint32_t uConnectPort;
+ /** Pointer to the TCP server instance. */
+ PRTTCPSERVER pTcpServer;
+ /** Thread calling RTTcpServerListen2. */
+ RTTHREAD hThreadServer;
+ /** Thread calling RTTcpClientConnect. */
+ RTTHREAD hThreadConnect;
+ /** The main thread handle (for signalling). */
+ RTTHREAD hThreadMain;
+ /** Stop connecting attempts when set. */
+ bool fStopConnecting;
+ /** Connect cancel cookie. */
+ PRTTCPCLIENTCONNECTCANCEL volatile pConnectCancelCookie;
+} ATSTRANSPORTINST;
+/** Pointer to an Audio Test Service (ATS) TCP/IP transport instance. */
+typedef ATSTRANSPORTINST *PATSTRANSPORTINST;
+
+/**
+ * Structure holding an ATS connection context, which is
+ * required when connecting a client via server (listening) or client (connecting).
+ */
+typedef struct ATSCONNCTX
+{
+ /** Pointer to transport instance to use. */
+ PATSTRANSPORTINST pInst;
+ /** Pointer to transport client to connect. */
+ PATSTRANSPORTCLIENT pClient;
+ /** Connection timeout (in ms).
+ * Use RT_INDEFINITE_WAIT to wait indefinitely. */
+ uint32_t msTimeout;
+} ATSCONNCTX;
+/** Pointer to an Audio Test Service (ATS) TCP/IP connection context. */
+typedef ATSCONNCTX *PATSCONNCTX;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Disconnects the current client and frees all stashed data.
+ *
+ * @param pThis Transport instance.
+ * @param pClient Client to disconnect.
+ */
+static void atsTcpDisconnectClient(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ RT_NOREF(pThis);
+
+ LogRelFlowFunc(("pClient=%RTsock\n", pClient->hTcpClient));
+
+ if (pClient->hTcpClient != NIL_RTSOCKET)
+ {
+ LogRelFlowFunc(("%RTsock\n", pClient->hTcpClient));
+
+ int rc;
+ if (pClient->fFromServer)
+ rc = RTTcpServerDisconnectClient2(pClient->hTcpClient);
+ else
+ rc = RTTcpClientClose(pClient->hTcpClient);
+ pClient->hTcpClient = NIL_RTSOCKET;
+ AssertRCSuccess(rc);
+ }
+
+ if (pClient->pbTcpStashed)
+ {
+ RTMemFree(pClient->pbTcpStashed);
+ pClient->pbTcpStashed = NULL;
+ }
+}
+
+/**
+ * Free's a client.
+ *
+ * @param pThis Transport instance.
+ * @param pClient Client to free.
+ * The pointer will be invalid after calling.
+ */
+static void atsTcpFreeClient(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ if (!pClient)
+ return;
+
+ /* Make sure to disconnect first. */
+ atsTcpDisconnectClient(pThis, pClient);
+
+ RTMemFree(pClient);
+ pClient = NULL;
+}
+
+/**
+ * Sets the current client socket in a safe manner.
+ *
+ * @returns NIL_RTSOCKET if consumed, otherwise hTcpClient.
+ * @param pThis Transport instance.
+ * @param pClient Client to set the socket for.
+ * @param fFromServer Whether the socket is from a server (listening) or client (connecting) call.
+ * Important when closing / disconnecting.
+ * @param hTcpClient The client socket.
+ */
+static RTSOCKET atsTcpSetClient(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, bool fFromServer, RTSOCKET hTcpClient)
+{
+ RTCritSectEnter(&pThis->CritSect);
+ if ( pClient->hTcpClient == NIL_RTSOCKET
+ && !pThis->fStopConnecting)
+ {
+ LogRelFlowFunc(("New client %RTsock connected (fFromServer=%RTbool)\n", hTcpClient, fFromServer));
+
+ pClient->fFromServer = fFromServer;
+ pClient->hTcpClient = hTcpClient;
+ hTcpClient = NIL_RTSOCKET; /* Invalidate, as pClient has now ownership. */
+ }
+ RTCritSectLeave(&pThis->CritSect);
+ return hTcpClient;
+}
+
+/**
+ * Checks if it's a fatal RTTcpClientConnect return code.
+ *
+ * @returns true / false.
+ * @param rc The IPRT status code.
+ */
+static bool atsTcpIsFatalClientConnectStatus(int rc)
+{
+ return rc != VERR_NET_UNREACHABLE
+ && rc != VERR_NET_HOST_DOWN
+ && rc != VERR_NET_HOST_UNREACHABLE
+ && rc != VERR_NET_CONNECTION_REFUSED
+ && rc != VERR_TIMEOUT
+ && rc != VERR_NET_CONNECTION_TIMED_OUT;
+}
+
+/**
+ * Server mode connection thread.
+ *
+ * @returns iprt status code.
+ * @param hSelf Thread handle. Ignored.
+ * @param pvUser Pointer to ATSTRANSPORTINST the thread is bound to.
+ */
+static DECLCALLBACK(int) atsTcpServerConnectThread(RTTHREAD hSelf, void *pvUser)
+{
+ RT_NOREF(hSelf);
+
+ PATSCONNCTX pConnCtx = (PATSCONNCTX)pvUser;
+ PATSTRANSPORTINST pThis = pConnCtx->pInst;
+ PATSTRANSPORTCLIENT pClient = pConnCtx->pClient;
+
+ /** @todo Implement cancellation support for using pConnCtx->msTimeout. */
+
+ LogRelFlowFuncEnter();
+
+ RTSOCKET hTcpClient;
+ int rc = RTTcpServerListen2(pThis->pTcpServer, &hTcpClient);
+ if (RT_SUCCESS(rc))
+ {
+ hTcpClient = atsTcpSetClient(pThis, pClient, true /* fFromServer */, hTcpClient);
+ RTTcpServerDisconnectClient2(hTcpClient);
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Client mode connection thread.
+ *
+ * @returns iprt status code.
+ * @param hSelf Thread handle. Use to sleep on. The main thread will
+ * signal it to speed up thread shutdown.
+ * @param pvUser Pointer to a connection context (PATSCONNCTX) the thread is bound to.
+ */
+static DECLCALLBACK(int) atsTcpClientConnectThread(RTTHREAD hSelf, void *pvUser)
+{
+ PATSCONNCTX pConnCtx = (PATSCONNCTX)pvUser;
+ PATSTRANSPORTINST pThis = pConnCtx->pInst;
+ PATSTRANSPORTCLIENT pClient = pConnCtx->pClient;
+
+ uint64_t msStartTs = RTTimeMilliTS();
+
+ LogRelFlowFuncEnter();
+
+ for (;;)
+ {
+ /* Stop? */
+ RTCritSectEnter(&pThis->CritSect);
+ bool fStop = pThis->fStopConnecting;
+ RTCritSectLeave(&pThis->CritSect);
+ if (fStop)
+ return VINF_SUCCESS;
+
+ /* Try connect. */ /** @todo make cancelable! */
+ RTSOCKET hTcpClient;
+ int rc = RTTcpClientConnectEx(pThis->szConnectAddr, pThis->uConnectPort, &hTcpClient,
+ RT_SOCKETCONNECT_DEFAULT_WAIT, &pThis->pConnectCancelCookie);
+ if (RT_SUCCESS(rc))
+ {
+ hTcpClient = atsTcpSetClient(pThis, pClient, false /* fFromServer */, hTcpClient);
+ RTTcpClientCloseEx(hTcpClient, true /* fGracefulShutdown*/);
+ break;
+ }
+
+ if (atsTcpIsFatalClientConnectStatus(rc))
+ return rc;
+
+ if ( pConnCtx->msTimeout != RT_INDEFINITE_WAIT
+ && RTTimeMilliTS() - msStartTs >= pConnCtx->msTimeout)
+ {
+ LogRelFlowFunc(("Timed out (%RU32ms)\n", pConnCtx->msTimeout));
+ return VERR_TIMEOUT;
+ }
+
+ /* Delay a wee bit before retrying. */
+ RTThreadUserWait(hSelf, 1536);
+ }
+
+ LogRelFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Wait on the threads to complete.
+ *
+ * @returns Thread status (if collected), otherwise VINF_SUCCESS.
+ * @param pThis Transport instance.
+ * @param cMillies The period to wait on each thread.
+ */
+static int atsTcpConnectWaitOnThreads(PATSTRANSPORTINST pThis, RTMSINTERVAL cMillies)
+{
+ int rcRet = VINF_SUCCESS;
+
+ LogRelFlowFuncEnter();
+
+ if (pThis->hThreadConnect != NIL_RTTHREAD)
+ {
+ int rcThread;
+ int rc2 = RTThreadWait(pThis->hThreadConnect, cMillies, &rcThread);
+ if (RT_SUCCESS(rc2))
+ {
+ pThis->hThreadConnect = NIL_RTTHREAD;
+ rcRet = rcThread;
+ }
+ }
+
+ if (pThis->hThreadServer != NIL_RTTHREAD)
+ {
+ int rcThread;
+ int rc2 = RTThreadWait(pThis->hThreadServer, cMillies, &rcThread);
+ if (RT_SUCCESS(rc2))
+ {
+ pThis->hThreadServer = NIL_RTTHREAD;
+ if (RT_SUCCESS(rc2))
+ rcRet = rcThread;
+ }
+ }
+
+ LogRelFlowFuncLeaveRC(rcRet);
+ return rcRet;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnWaitForConnect}
+ */
+static DECLCALLBACK(int) atsTcpWaitForConnect(PATSTRANSPORTINST pThis, RTMSINTERVAL msTimeout,
+ bool *pfFromServer, PPATSTRANSPORTCLIENT ppClientNew)
+{
+ PATSTRANSPORTCLIENT pClient = (PATSTRANSPORTCLIENT)RTMemAllocZ(sizeof(ATSTRANSPORTCLIENT));
+ AssertPtrReturn(pClient, VERR_NO_MEMORY);
+
+ int rc;
+
+ LogRelFlowFunc(("msTimeout=%RU32, enmConnMode=%#x\n", msTimeout, pThis->enmConnMode));
+
+ uint64_t msStartTs = RTTimeMilliTS();
+
+ if (pThis->enmConnMode == ATSCONNMODE_SERVER)
+ {
+ /** @todo Implement cancellation support for using \a msTimeout. */
+
+ pClient->fFromServer = true;
+ rc = RTTcpServerListen2(pThis->pTcpServer, &pClient->hTcpClient);
+ LogRelFlowFunc(("RTTcpServerListen2(%RTsock) -> %Rrc\n", pClient->hTcpClient, rc));
+ }
+ else if (pThis->enmConnMode == ATSCONNMODE_CLIENT)
+ {
+ pClient->fFromServer = false;
+ for (;;)
+ {
+ LogRelFlowFunc(("Calling RTTcpClientConnect(%s, %u,)...\n", pThis->szConnectAddr, pThis->uConnectPort));
+ rc = RTTcpClientConnect(pThis->szConnectAddr, pThis->uConnectPort, &pClient->hTcpClient);
+ LogRelFlowFunc(("RTTcpClientConnect(%RTsock) -> %Rrc\n", pClient->hTcpClient, rc));
+ if (RT_SUCCESS(rc) || atsTcpIsFatalClientConnectStatus(rc))
+ break;
+
+ if ( msTimeout != RT_INDEFINITE_WAIT
+ && RTTimeMilliTS() - msStartTs >= msTimeout)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+
+ if (pThis->fStopConnecting)
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ /* Delay a wee bit before retrying. */
+ RTThreadSleep(1536);
+ }
+ }
+ else
+ {
+ Assert(pThis->enmConnMode == ATSCONNMODE_BOTH);
+
+ /*
+ * Create client threads.
+ */
+ RTCritSectEnter(&pThis->CritSect);
+
+ pThis->fStopConnecting = false;
+ RTCritSectLeave(&pThis->CritSect);
+
+ atsTcpConnectWaitOnThreads(pThis, 32 /* cMillies */);
+
+ ATSCONNCTX ConnCtx;
+ RT_ZERO(ConnCtx);
+ ConnCtx.pInst = pThis;
+ ConnCtx.pClient = pClient;
+ ConnCtx.msTimeout = msTimeout;
+
+ rc = VINF_SUCCESS;
+ if (pThis->hThreadConnect == NIL_RTTHREAD)
+ {
+ pThis->pConnectCancelCookie = NULL;
+ rc = RTThreadCreate(&pThis->hThreadConnect, atsTcpClientConnectThread, &ConnCtx, 0, RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE, "tcpconn");
+ }
+ if (pThis->hThreadServer == NIL_RTTHREAD && RT_SUCCESS(rc))
+ rc = RTThreadCreate(&pThis->hThreadServer, atsTcpServerConnectThread, &ConnCtx, 0, RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE, "tcpserv");
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ /*
+ * Wait for connection to be established.
+ */
+ while ( RT_SUCCESS(rc)
+ && pClient->hTcpClient == NIL_RTSOCKET)
+ {
+ RTCritSectLeave(&pThis->CritSect);
+ rc = atsTcpConnectWaitOnThreads(pThis, 10 /* cMillies */);
+ RTCritSectEnter(&pThis->CritSect);
+ }
+
+ /*
+ * Cancel the threads.
+ */
+ pThis->fStopConnecting = true;
+
+ RTCritSectLeave(&pThis->CritSect);
+ RTTcpClientCancelConnect(&pThis->pConnectCancelCookie);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pfFromServer)
+ *pfFromServer = pClient->fFromServer;
+ *ppClientNew = pClient;
+ }
+ else
+ {
+ if (pClient)
+ {
+ atsTcpFreeClient(pThis, pClient);
+ pClient = NULL;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRelFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnNotifyReboot}
+ */
+static DECLCALLBACK(void) atsTcpNotifyReboot(PATSTRANSPORTINST pThis)
+{
+ LogRelFlowFuncEnter();
+ if (pThis->pTcpServer)
+ {
+ int rc = RTTcpServerDestroy(pThis->pTcpServer);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("RTTcpServerDestroy failed, rc=%Rrc", rc));
+ pThis->pTcpServer = NULL;
+ }
+ LogRelFlowFuncLeave();
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnNotifyBye}
+ */
+static DECLCALLBACK(void) atsTcpNotifyBye(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ LogRelFlowFunc(("pClient=%RTsock\n", pClient->hTcpClient));
+ atsTcpDisconnectClient(pThis, pClient);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnNotifyHowdy}
+ */
+static DECLCALLBACK(void) atsTcpNotifyHowdy(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ LogRelFlowFunc(("pClient=%RTsock\n", pClient->hTcpClient));
+
+ /* nothing to do here */
+ RT_NOREF(pThis);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnBabble}
+ */
+static DECLCALLBACK(void) atsTcpBabble(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PCATSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)
+{
+ /*
+ * Try send the babble reply.
+ */
+ RT_NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */
+ int rc;
+ size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, ATSPKT_ALIGNMENT);
+ do rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend);
+ while (rc == VERR_INTERRUPTED);
+
+ LogRelFlowFunc(("pClient=%RTsock, rc=%Rrc\n", pClient->hTcpClient, rc));
+
+ /*
+ * Disconnect the client.
+ */
+ atsTcpDisconnectClient(pThis, pClient);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnSendPkt}
+ */
+static DECLCALLBACK(int) atsTcpSendPkt(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PCATSPKTHDR pPktHdr)
+{
+ AssertReturn(pPktHdr->cb >= sizeof(ATSPKTHDR), VERR_INVALID_PARAMETER);
+
+ /*
+ * Write it.
+ */
+ size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, ATSPKT_ALIGNMENT);
+
+ Log3Func(("%RU32 -> %zu\n", pPktHdr->cb, cbToSend));
+
+ LogRel4(("pClient=%RTsock\n", pClient->hTcpClient));
+ LogRel4(("Header:\n"
+ "%.*Rhxd\n", RT_MIN(sizeof(ATSPKTHDR), cbToSend), pPktHdr));
+
+ if (cbToSend > sizeof(ATSPKTHDR))
+ LogRel4(("Payload:\n"
+ "%.*Rhxd\n",
+ RT_MIN(64, cbToSend - sizeof(ATSPKTHDR)), (uint8_t *)pPktHdr + sizeof(ATSPKTHDR)));
+
+ int rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_INTERRUPTED)
+ {
+ /* assume fatal connection error. */
+ LogRelFunc(("RTTcpWrite -> %Rrc -> atsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient));
+ atsTcpDisconnectClient(pThis, pClient);
+ }
+
+ LogRel3(("atsTcpSendPkt: pClient=%RTsock, achOpcode=%.8s, cbSent=%zu -> %Rrc\n", pClient->hTcpClient, (const char *)pPktHdr->achOpcode, cbToSend, rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnRecvPkt}
+ */
+static DECLCALLBACK(int) atsTcpRecvPkt(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient, PPATSPKTHDR ppPktHdr)
+{
+ int rc = VINF_SUCCESS;
+ *ppPktHdr = NULL;
+
+ LogRel4(("pClient=%RTsock (cbTcpStashed=%zu, cbTcpStashedAlloced=%zu)\n",
+ pClient->hTcpClient, pClient->cbTcpStashed, pClient->cbTcpStashedAlloced));
+
+ /*
+ * Read state.
+ */
+ size_t offData = 0;
+ size_t cbData = 0;
+ size_t cbDataAlloced;
+ uint8_t *pbData = NULL;
+
+ /*
+ * Any stashed data?
+ */
+ if (pClient->cbTcpStashedAlloced)
+ {
+ offData = pClient->cbTcpStashed;
+ cbDataAlloced = pClient->cbTcpStashedAlloced;
+ pbData = pClient->pbTcpStashed;
+
+ pClient->cbTcpStashed = 0;
+ pClient->cbTcpStashedAlloced = 0;
+ pClient->pbTcpStashed = NULL;
+ }
+ else
+ {
+ cbDataAlloced = RT_ALIGN_Z(64, ATSPKT_ALIGNMENT);
+ pbData = (uint8_t *)RTMemAlloc(cbDataAlloced);
+ AssertPtrReturn(pbData, VERR_NO_MEMORY);
+ }
+
+ /*
+ * Read and validate the length.
+ */
+ while (offData < sizeof(uint32_t))
+ {
+ size_t cbRead;
+ rc = RTTcpRead(pClient->hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (cbRead == 0)
+ {
+ LogRelFunc(("RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc));
+ rc = VERR_NET_NOT_CONNECTED;
+ break;
+ }
+ offData += cbRead;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ ASMCompilerBarrier(); /* paranoia^3 */
+ cbData = *(uint32_t volatile *)pbData;
+ if (cbData >= sizeof(ATSPKTHDR) && cbData <= ATSPKT_MAX_SIZE)
+ {
+ /*
+ * Align the length and reallocate the return packet it necessary.
+ */
+ cbData = RT_ALIGN_Z(cbData, ATSPKT_ALIGNMENT);
+ if (cbData > cbDataAlloced)
+ {
+ void *pvNew = RTMemRealloc(pbData, cbData);
+ if (pvNew)
+ {
+ pbData = (uint8_t *)pvNew;
+ cbDataAlloced = cbData;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the remainder of the data.
+ */
+ while (offData < cbData)
+ {
+ size_t cbRead;
+ rc = RTTcpRead(pClient->hTcpClient, pbData + offData, cbData - offData, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (cbRead == 0)
+ {
+ LogRelFunc(("RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc));
+ rc = VERR_NET_NOT_CONNECTED;
+ break;
+ }
+
+ offData += cbRead;
+ }
+
+ LogRel4(("Header:\n"
+ "%.*Rhxd\n", sizeof(ATSPKTHDR), pbData));
+
+ if ( RT_SUCCESS(rc)
+ && cbData > sizeof(ATSPKTHDR))
+ LogRel4(("Payload:\n"
+ "%.*Rhxd\n", RT_MIN(64, cbData - sizeof(ATSPKTHDR)), (uint8_t *)pbData + sizeof(ATSPKTHDR)));
+ }
+ }
+ else
+ {
+ LogRelFunc(("Received invalid packet size (%zu)\n", cbData));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ *ppPktHdr = (PATSPKTHDR)pbData;
+ else
+ {
+ /*
+ * Deal with errors.
+ */
+ if (rc == VERR_INTERRUPTED)
+ {
+ /* stash it away for the next call. */
+ pClient->cbTcpStashed = cbData;
+ pClient->cbTcpStashedAlloced = cbDataAlloced;
+ pClient->pbTcpStashed = pbData;
+ }
+ else
+ {
+ RTMemFree(pbData);
+
+ /* assume fatal connection error. */
+ LogRelFunc(("RTTcpRead -> %Rrc -> atsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient));
+ atsTcpDisconnectClient(pThis, pClient);
+ }
+ }
+
+ PATSPKTHDR pPktHdr = (PATSPKTHDR)pbData;
+ LogRel3(("atsTcpRecvPkt: pClient=%RTsock, achOpcode=%.8s, cbRead=%zu -> %Rrc\n",
+ pClient->hTcpClient, pPktHdr ? (const char *)pPktHdr->achOpcode : "NONE ", cbData, rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnPollSetAdd}
+ */
+static DECLCALLBACK(int) atsTcpPollSetAdd(PATSTRANSPORTINST pThis, RTPOLLSET hPollSet, PATSTRANSPORTCLIENT pClient, uint32_t idStart)
+{
+ RT_NOREF(pThis);
+ return RTPollSetAddSocket(hPollSet, pClient->hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnPollSetRemove}
+ */
+static DECLCALLBACK(int) atsTcpPollSetRemove(PATSTRANSPORTINST pThis, RTPOLLSET hPollSet, PATSTRANSPORTCLIENT pClient, uint32_t idStart)
+{
+ RT_NOREF(pThis, pClient);
+ return RTPollSetRemove(hPollSet, idStart);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnDisconnect}
+ */
+static DECLCALLBACK(void) atsTcpDisconnect(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ atsTcpFreeClient(pThis, pClient);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnPollIn}
+ */
+static DECLCALLBACK(bool) atsTcpPollIn(PATSTRANSPORTINST pThis, PATSTRANSPORTCLIENT pClient)
+{
+ RT_NOREF(pThis);
+ int rc = RTTcpSelectOne(pClient->hTcpClient, 0/*cMillies*/);
+ return RT_SUCCESS(rc);
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnStop}
+ */
+static DECLCALLBACK(void) atsTcpStop(PATSTRANSPORTINST pThis)
+{
+ LogRelFlowFuncEnter();
+
+ /* Signal thread */
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->fStopConnecting = true;
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ if (pThis->hThreadConnect != NIL_RTTHREAD)
+ {
+ RTThreadUserSignal(pThis->hThreadConnect);
+ RTTcpClientCancelConnect(&pThis->pConnectCancelCookie);
+ }
+
+ /* Shut down the server (will wake up thread). */
+ if (pThis->pTcpServer)
+ {
+ LogRelFlowFunc(("Destroying server...\n"));
+ int rc = RTTcpServerDestroy(pThis->pTcpServer);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("RTTcpServerDestroy failed with %Rrc", rc));
+ pThis->pTcpServer = NULL;
+ }
+
+ /* Wait for the thread (they should've had some time to quit by now). */
+ atsTcpConnectWaitOnThreads(pThis, 15000);
+
+ LogRelFlowFuncLeave();
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnCreate}
+ */
+static DECLCALLBACK(int) atsTcpCreate(PATSTRANSPORTINST *ppThis)
+{
+ PATSTRANSPORTINST pThis = (PATSTRANSPORTINST)RTMemAllocZ(sizeof(ATSTRANSPORTINST));
+ AssertPtrReturn(pThis, VERR_NO_MEMORY);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ *ppThis = pThis;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnDestroy}
+ */
+static DECLCALLBACK(int) atsTcpDestroy(PATSTRANSPORTINST pThis)
+{
+ /* Stop things first. */
+ atsTcpStop(pThis);
+
+ /* Finally, clean up the critical section. */
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ RTCritSectDelete(&pThis->CritSect);
+
+ RTMemFree(pThis);
+ pThis = NULL;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnStart}
+ */
+static DECLCALLBACK(int) atsTcpStart(PATSTRANSPORTINST pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->enmConnMode != ATSCONNMODE_CLIENT)
+ {
+ rc = RTTcpServerCreateEx(pThis->szBindAddr[0] ? pThis->szBindAddr : NULL, pThis->uBindPort, &pThis->pTcpServer);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NET_DOWN)
+ {
+ LogRelFunc(("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n",
+ pThis->szBindAddr[0] ? pThis->szBindAddr : NULL, pThis->uBindPort, rc));
+ uint64_t StartMs = RTTimeMilliTS();
+ do
+ {
+ RTThreadSleep(1000);
+ rc = RTTcpServerCreateEx(pThis->szBindAddr[0] ? pThis->szBindAddr : NULL, pThis->uBindPort, &pThis->pTcpServer);
+ } while ( rc == VERR_NET_DOWN
+ && RTTimeMilliTS() - StartMs < 20000);
+ if (RT_SUCCESS(rc))
+ LogRelFunc(("RTTcpServerCreateEx succceeded\n"));
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRelFunc(("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n",
+ pThis->szBindAddr[0] ? pThis->szBindAddr : NULL, pThis->uBindPort, rc));
+ }
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnOption}
+ */
+static DECLCALLBACK(int) atsTcpOption(PATSTRANSPORTINST pThis, int ch, PCRTGETOPTUNION pVal)
+{
+ int rc;
+
+ switch (ch)
+ {
+ case ATSTCPOPT_CONN_MODE:
+ pThis->enmConnMode = (ATSCONNMODE)pVal->u32;
+ return VINF_SUCCESS;
+
+ case ATSTCPOPT_BIND_ADDRESS:
+ rc = RTStrCopy(pThis->szBindAddr, sizeof(pThis->szBindAddr), pVal->psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc);
+ if (!pThis->szBindAddr[0])
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "No TCP bind address specified: %s", pThis->szBindAddr);
+ return VINF_SUCCESS;
+
+ case ATSTCPOPT_BIND_PORT:
+ pThis->uBindPort = pVal->u16;
+ return VINF_SUCCESS;
+
+ case ATSTCPOPT_CONNECT_ADDRESS:
+ rc = RTStrCopy(pThis->szConnectAddr, sizeof(pThis->szConnectAddr), pVal->psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP connect address is too long (%Rrc)", rc);
+ if (!pThis->szConnectAddr[0])
+ return RTMsgErrorRc(VERR_INVALID_PARAMETER, "No TCP connect address specified");
+ return VINF_SUCCESS;
+
+ case ATSTCPOPT_CONNECT_PORT:
+ pThis->uConnectPort = pVal->u16;
+ return VINF_SUCCESS;
+
+ default:
+ break;
+ }
+ return VERR_TRY_AGAIN;
+}
+
+/**
+ * @interface_method_impl{ATSTRANSPORT,pfnUsage}
+ */
+DECLCALLBACK(void) atsTcpUsage(PRTSTREAM pStream)
+{
+ RTStrmPrintf(pStream,
+ " --tcp-conn-mode <0=both|1=client|2=server>\n"
+ " Selects the connection mode\n"
+ " Default: 0 (both)\n"
+ " --tcp-bind-addr[ess] <address>\n"
+ " The address(es) to listen to TCP connection on. Empty string\n"
+ " means any address, this is the default\n"
+ " --tcp-bind-port <port>\n"
+ " The port to listen to TCP connections on\n"
+ " Default: %u\n"
+ " --tcp-connect-addr[ess] <address>\n"
+ " The address of the server to try connect to in client mode\n"
+ " Default: " ATS_TCP_DEF_CONNECT_GUEST_STR "\n"
+ " --tcp-connect-port <port>\n"
+ " The port on the server to connect to in client mode\n"
+ " Default: %u\n"
+ , ATS_TCP_DEF_BIND_PORT_GUEST, ATS_TCP_DEF_CONNECT_PORT_GUEST);
+}
+
+/** Command line options for the TCP/IP transport layer. */
+static const RTGETOPTDEF g_TcpOpts[] =
+{
+ { "--tcp-conn-mode", ATSTCPOPT_CONN_MODE, RTGETOPT_REQ_STRING },
+ { "--tcp-bind-addr", ATSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING },
+ { "--tcp-bind-address", ATSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING },
+ { "--tcp-bind-port", ATSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 },
+ { "--tcp-connect-addr", ATSTCPOPT_CONNECT_ADDRESS, RTGETOPT_REQ_STRING },
+ { "--tcp-connect-address", ATSTCPOPT_CONNECT_ADDRESS, RTGETOPT_REQ_STRING },
+ { "--tcp-connect-port", ATSTCPOPT_CONNECT_PORT, RTGETOPT_REQ_UINT16 }
+};
+
+/** TCP/IP transport layer. */
+const ATSTRANSPORT g_TcpTransport =
+{
+ /* .szName = */ "tcp",
+ /* .pszDesc = */ "TCP/IP",
+ /* .cOpts = */ &g_TcpOpts[0],
+ /* .paOpts = */ RT_ELEMENTS(g_TcpOpts),
+ /* .pfnUsage = */ atsTcpUsage,
+ /* .pfnCreate = */ atsTcpCreate,
+ /* .pfnDestroy = */ atsTcpDestroy,
+ /* .pfnOption = */ atsTcpOption,
+ /* .pfnStart = */ atsTcpStart,
+ /* .pfnStop = */ atsTcpStop,
+ /* .pfnWaitForConnect = */ atsTcpWaitForConnect,
+ /* .pfnDisconnect = */ atsTcpDisconnect,
+ /* .pfnPollIn = */ atsTcpPollIn,
+ /* .pfnPollSetAdd = */ atsTcpPollSetAdd,
+ /* .pfnPollSetRemove = */ atsTcpPollSetRemove,
+ /* .pfnRecvPkt = */ atsTcpRecvPkt,
+ /* .pfnSendPkt = */ atsTcpSendPkt,
+ /* .pfnBabble = */ atsTcpBabble,
+ /* .pfnNotifyHowdy = */ atsTcpNotifyHowdy,
+ /* .pfnNotifyBye = */ atsTcpNotifyBye,
+ /* .pfnNotifyReboot = */ atsTcpNotifyReboot,
+ /* .u32EndMarker = */ UINT32_C(0x12345678)
+};
+
diff --git a/src/VBox/Devices/Audio/DevHda.cpp b/src/VBox/Devices/Audio/DevHda.cpp
new file mode 100644
index 00000000..9c033b9c
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHda.cpp
@@ -0,0 +1,5469 @@
+/* $Id: DevHda.cpp $ */
+/** @file
+ * Intel HD Audio Controller Emulation.
+ *
+ * Implemented against the specifications found in "High Definition Audio
+ * Specification", Revision 1.0a June 17, 2010, and "Intel I/O Controller
+ * HUB 6 (ICH6) Family, Datasheet", document number 301473-002.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#ifdef HDA_DEBUG_GUEST_RIP
+# include <VBox/vmm/cpum.h>
+#endif
+#include <VBox/version.h>
+#include <VBox/AssertGuest.h>
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/asm-math.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+# include <iprt/string.h>
+#ifdef IN_RING3
+# include <iprt/mem.h>
+# include <iprt/semaphore.h>
+# include <iprt/uuid.h>
+#endif
+
+#include "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+
+#define VBOX_HDA_CAN_ACCESS_REG_MAP /* g_aHdaRegMap is accessible */
+#include "DevHda.h"
+
+#include "AudioHlp.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(VBOX_WITH_HP_HDA)
+/* HP Pavilion dv4t-1300 */
+# define HDA_PCI_VENDOR_ID 0x103c
+# define HDA_PCI_DEVICE_ID 0x30f7
+#elif defined(VBOX_WITH_INTEL_HDA)
+/* Intel HDA controller */
+# define HDA_PCI_VENDOR_ID 0x8086
+# define HDA_PCI_DEVICE_ID 0x2668
+#elif defined(VBOX_WITH_NVIDIA_HDA)
+/* nVidia HDA controller */
+# define HDA_PCI_VENDOR_ID 0x10de
+# define HDA_PCI_DEVICE_ID 0x0ac0
+#else
+# error "Please specify your HDA device vendor/device IDs"
+#endif
+
+/**
+ * Acquires the HDA lock.
+ */
+#define DEVHDA_LOCK(a_pDevIns, a_pThis) \
+ do { \
+ int const rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV((a_pDevIns), &(a_pThis)->CritSect, rcLock); \
+ } while (0)
+
+/**
+ * Acquires the HDA lock or returns.
+ */
+#define DEVHDA_LOCK_RETURN(a_pDevIns, a_pThis, a_rcBusy) \
+ do { \
+ int const rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, a_rcBusy); \
+ if (rcLock == VINF_SUCCESS) \
+ { /* likely */ } \
+ else \
+ { \
+ AssertRC(rcLock); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Acquires the HDA lock or returns.
+ */
+# define DEVHDA_LOCK_RETURN_VOID(a_pDevIns, a_pThis) \
+ do { \
+ int const rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \
+ if (rcLock == VINF_SUCCESS) \
+ { /* likely */ } \
+ else \
+ { \
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV((a_pDevIns), &(a_pThis)->CritSect, rcLock); \
+ return; \
+ } \
+ } while (0)
+
+/**
+ * Releases the HDA lock.
+ */
+#define DEVHDA_UNLOCK(a_pDevIns, a_pThis) \
+ do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0)
+
+/**
+ * Acquires the TM lock and HDA lock, returns on failure.
+ */
+#define DEVHDA_LOCK_BOTH_RETURN(a_pDevIns, a_pThis, a_pStream, a_rcBusy) \
+ do { \
+ VBOXSTRICTRC rcLock = PDMDevHlpTimerLockClock2(pDevIns, (a_pStream)->hTimer, &(a_pThis)->CritSect, (a_rcBusy)); \
+ if (RT_LIKELY(rcLock == VINF_SUCCESS)) \
+ { /* likely */ } \
+ else \
+ return VBOXSTRICTRC_TODO(rcLock); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Structure defining a (host backend) driver stream.
+ * Each driver has its own instances of audio mixer streams, which then
+ * can go into the same (or even different) audio mixer sinks.
+ */
+typedef struct HDADRIVERSTREAM
+{
+ /** Associated mixer handle. */
+ R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
+} HDADRIVERSTREAM, *PHDADRIVERSTREAM;
+
+/**
+ * Struct for maintaining a host backend driver.
+ * This driver must be associated to one, and only one,
+ * HDA codec. The HDA controller does the actual multiplexing
+ * of HDA codec data to various host backend drivers then.
+ *
+ * This HDA device uses a timer in order to synchronize all
+ * read/write accesses across all attached LUNs / backends.
+ */
+typedef struct HDADRIVER
+{
+ /** Node for storing this driver in our device driver list of HDASTATE. */
+ RTLISTNODER3 Node;
+ /** Pointer to shared HDA device state. */
+ R3PTRTYPE(PHDASTATE) pHDAStateShared;
+ /** Pointer to the ring-3 HDA device state. */
+ R3PTRTYPE(PHDASTATER3) pHDAStateR3;
+ /** LUN to which this driver has been assigned. */
+ uint8_t uLUN;
+ /** Whether this driver is in an attached state or not. */
+ bool fAttached;
+ uint8_t u32Padding0[6];
+ /** Pointer to attached driver base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Audio connector interface to the underlying host backend. */
+ R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector;
+ /** Mixer stream for line input. */
+ HDADRIVERSTREAM LineIn;
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ /** Mixer stream for mic input. */
+ HDADRIVERSTREAM MicIn;
+#endif
+ /** Mixer stream for front output. */
+ HDADRIVERSTREAM Front;
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ /** Mixer stream for center/LFE output. */
+ HDADRIVERSTREAM CenterLFE;
+ /** Mixer stream for rear output. */
+ HDADRIVERSTREAM Rear;
+#endif
+ /** The LUN description. */
+ char szDesc[48 - 2];
+} HDADRIVER;
+/** The HDA host driver backend. */
+typedef struct HDADRIVER *PHDADRIVER;
+
+
+/** Internal state of this BDLE.
+ * Not part of the actual BDLE registers.
+ * @note Only for saved state. */
+typedef struct HDABDLESTATELEGACY
+{
+ /** Own index within the BDL (Buffer Descriptor List). */
+ uint32_t u32BDLIndex;
+ /** Number of bytes below the stream's FIFO watermark (SDFIFOW).
+ * Used to check if we need fill up the FIFO again. */
+ uint32_t cbBelowFIFOW;
+ /** Current offset in DMA buffer (in bytes).*/
+ uint32_t u32BufOff;
+ uint32_t Padding;
+} HDABDLESTATELEGACY;
+
+/**
+ * BDLE and state.
+ * @note Only for saved state.
+ */
+typedef struct HDABDLELEGACY
+{
+ /** The actual BDL description. */
+ HDABDLEDESC Desc;
+ HDABDLESTATELEGACY State;
+} HDABDLELEGACY;
+AssertCompileSize(HDABDLELEGACY, 32);
+
+
+/** Read callback. */
+typedef VBOXSTRICTRC FNHDAREGREAD(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+/** Write callback. */
+typedef VBOXSTRICTRC FNHDAREGWRITE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+
+/**
+ * HDA register descriptor.
+ */
+typedef struct HDAREGDESC
+{
+ /** Register offset in the register space. */
+ uint32_t off;
+ /** Size in bytes. Registers of size > 4 are in fact tables. */
+ uint8_t cb;
+ /** Register descriptor (RD) flags of type HDA_RD_F_XXX. These are used to
+ * specify the read/write handling policy of the register. */
+ uint8_t fFlags;
+ /** Index into the register storage array (HDASTATE::au32Regs). */
+ uint8_t idxReg;
+ uint8_t bUnused;
+ /** Readable bits. */
+ uint32_t fReadableMask;
+ /** Writable bits. */
+ uint32_t fWritableMask;
+ /** Read callback. */
+ FNHDAREGREAD *pfnRead;
+ /** Write callback. */
+ FNHDAREGWRITE *pfnWrite;
+#if defined(IN_RING3) || defined(LOG_ENABLED) /* Saves 0x2f23 - 0x1888 = 0x169B (5787) bytes in VBoxDDR0. */
+ /** Abbreviated name. */
+ const char *pszName;
+# ifdef IN_RING3
+ /** Description (for stats). */
+ const char *pszDesc;
+# endif
+#endif
+} HDAREGDESC;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+#ifdef IN_RING3
+static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC);
+#endif
+
+/** @name Register read/write stubs.
+ * @{
+ */
+static FNHDAREGREAD hdaRegReadUnimpl;
+static FNHDAREGWRITE hdaRegWriteUnimpl;
+/** @} */
+
+/** @name Global register set read/write functions.
+ * @{
+ */
+static FNHDAREGWRITE hdaRegWriteGCTL;
+static FNHDAREGREAD hdaRegReadLPIB;
+static FNHDAREGREAD hdaRegReadWALCLK;
+static FNHDAREGWRITE hdaRegWriteSSYNC;
+static FNHDAREGWRITE hdaRegWriteNewSSYNC;
+static FNHDAREGWRITE hdaRegWriteCORBWP;
+static FNHDAREGWRITE hdaRegWriteCORBRP;
+static FNHDAREGWRITE hdaRegWriteCORBCTL;
+static FNHDAREGWRITE hdaRegWriteCORBSIZE;
+static FNHDAREGWRITE hdaRegWriteCORBSTS;
+static FNHDAREGWRITE hdaRegWriteRINTCNT;
+static FNHDAREGWRITE hdaRegWriteRIRBWP;
+static FNHDAREGWRITE hdaRegWriteRIRBSTS;
+static FNHDAREGWRITE hdaRegWriteSTATESTS;
+static FNHDAREGWRITE hdaRegWriteIRS;
+static FNHDAREGREAD hdaRegReadIRS;
+static FNHDAREGWRITE hdaRegWriteBase;
+/** @} */
+
+/** @name {IOB}SDn read/write functions.
+ * @{
+ */
+static FNHDAREGWRITE hdaRegWriteSDCBL;
+static FNHDAREGWRITE hdaRegWriteSDCTL;
+static FNHDAREGWRITE hdaRegWriteSDSTS;
+static FNHDAREGWRITE hdaRegWriteSDLVI;
+static FNHDAREGWRITE hdaRegWriteSDFIFOW;
+static FNHDAREGWRITE hdaRegWriteSDFIFOS;
+static FNHDAREGWRITE hdaRegWriteSDFMT;
+static FNHDAREGWRITE hdaRegWriteSDBDPL;
+static FNHDAREGWRITE hdaRegWriteSDBDPU;
+static FNHDAREGREAD hdaRegReadSDnPIB;
+static FNHDAREGREAD hdaRegReadSDnEFIFOS;
+/** @} */
+
+/** @name Generic register read/write functions.
+ * @{
+ */
+static FNHDAREGREAD hdaRegReadU32;
+static FNHDAREGWRITE hdaRegWriteU32;
+static FNHDAREGREAD hdaRegReadU24;
+#ifdef IN_RING3
+static FNHDAREGWRITE hdaRegWriteU24;
+#endif
+static FNHDAREGREAD hdaRegReadU16;
+static FNHDAREGWRITE hdaRegWriteU16;
+static FNHDAREGREAD hdaRegReadU8;
+static FNHDAREGWRITE hdaRegWriteU8;
+/** @} */
+
+/** @name HDA device functions.
+ * @{
+ */
+#ifdef IN_RING3
+static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg);
+static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg);
+#endif /* IN_RING3 */
+/** @} */
+
+/** @name HDA mixer functions.
+ * @{
+ */
+#ifdef IN_RING3
+static int hdaR3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv);
+#endif
+/** @} */
+
+#ifdef IN_RING3
+static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLEDESC_fFlags_6;
+static FNSSMFIELDGETPUT hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4;
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** No register description (RD) flags defined. */
+#define HDA_RD_F_NONE 0
+/** Writes to SD are allowed while RUN bit is set. */
+#define HDA_RD_F_SD_WRITE_RUN RT_BIT(0)
+
+/** @def HDA_REG_ENTRY_EX
+ * Maps the entry values to the actual HDAREGDESC layout, which is differs
+ * depending on context and build type. */
+#if defined(IN_RING3) || defined(LOG_ENABLED)
+# ifdef IN_RING3
+# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \
+ { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite, a_szName, a_szDesc }
+# else
+# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \
+ { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite, a_szName }
+# endif
+#else
+# define HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_idxMap, a_szName, a_szDesc) \
+ { a_offBar, a_cbReg, a_fFlags, a_idxMap, 0, a_fReadMask, a_fWriteMask, a_pfnRead, a_pfnWrite }
+#endif
+
+#define HDA_REG_ENTRY(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_ShortRegNm, a_szDesc) \
+ HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, HDA_MEM_IND_NAME(a_ShortRegNm), #a_ShortRegNm, a_szDesc)
+#define HDA_REG_ENTRY_STR(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, a_StrPrefix, a_ShortRegNm, a_szDesc) \
+ HDA_REG_ENTRY_EX(a_offBar, a_cbReg, a_fReadMask, a_fWriteMask, a_fFlags, a_pfnRead, a_pfnWrite, HDA_MEM_IND_NAME(a_StrPrefix ## a_ShortRegNm), #a_StrPrefix #a_ShortRegNm, #a_StrPrefix ": " a_szDesc)
+
+/** Emits a single audio stream register set (e.g. OSD0) at a specified offset. */
+#define HDA_REG_MAP_STRM(offset, name) \
+ /* offset size read mask write mask flags read callback write callback index, abbrev, description */ \
+ /* ------- ------- ---------- ---------- ---------------------- -------------- ----------------- ----------------------------- ----------- */ \
+ /* Offset 0x80 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset, 0x00003, 0x00FF001F, 0x00F0001F, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU24 , hdaRegWriteSDCTL , name, CTL , "Stream Descriptor Control"), \
+ /* Offset 0x83 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x3, 0x00001, 0x0000003C, 0x0000001C, HDA_RD_F_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , name, STS , "Status" ), \
+ /* Offset 0x84 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadLPIB, hdaRegWriteU32 , name, LPIB , "Link Position In Buffer" ), \
+ /* Offset 0x88 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , name, CBL , "Cyclic Buffer Length" ), \
+ /* Offset 0x8C (SD0) -- upper 8 bits are reserved */ \
+ HDA_REG_ENTRY_STR(offset + 0xC, 0x00002, 0x0000FFFF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDLVI , name, LVI , "Last Valid Index" ), \
+ /* Reserved: FIFO Watermark. ** @todo Document this! */ \
+ HDA_REG_ENTRY_STR(offset + 0xE, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, name, FIFOW, "FIFO Watermark" ), \
+ /* Offset 0x90 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, name, FIFOS, "FIFO Size" ), \
+ /* Offset 0x92 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteSDFMT , name, FMT , "Stream Format" ), \
+ /* Reserved: 0x94 - 0x98. */ \
+ /* Offset 0x98 (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x18, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPL , name, BDPL , "Buffer Descriptor List Pointer-Lower Base Address" ), \
+ /* Offset 0x9C (SD0) */ \
+ HDA_REG_ENTRY_STR(offset + 0x1C, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSDBDPU , name, BDPU , "Buffer Descriptor List Pointer-Upper Base Address" )
+
+/** Defines a single audio stream register set (e.g. OSD0). */
+#define HDA_REG_MAP_DEF_STREAM(index, name) \
+ HDA_REG_MAP_STRM(HDA_REG_DESC_SD0_BASE + (index * 32 /* 0x20 */), name)
+
+/** Skylake stream registers. */
+#define HDA_REG_MAP_SKYLAKE_STRM(a_off, a_StrPrefix) \
+ /* offset size read mask write mask flags read callback write callback index, abbrev, description */ \
+ /* ------- ------- ---------- ---------- -------------- -------------- ----------------- ----------------------------- ----------- */ \
+ /* 0x1084 */ \
+ HDA_REG_ENTRY_STR(a_off + 0x04, 0x00004, 0xffffffff, 0x00000000, HDA_RD_F_NONE, hdaRegReadSDnPIB, hdaRegWriteUnimpl, a_StrPrefix, DPIB, "DMA Position In Buffer" ), \
+ /* 0x1094 */ \
+ HDA_REG_ENTRY_STR(a_off + 0x14, 0x00004, 0xffffffff, 0x00000000, HDA_RD_F_NONE, hdaRegReadSDnEFIFOS, hdaRegWriteUnimpl, a_StrPrefix, EFIFOS, "Extended FIFO Size" )
+
+
+/** See 302349 p 6.2. */
+static const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS] =
+{
+ /* offset size read mask write mask flags read callback write callback index + abbrev */
+ /*------- ------- ---------- ---------- -------------- ---------------- ------------------- ------------------------ */
+ HDA_REG_ENTRY(0x00000, 0x00002, 0x0000FFFB, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , GCAP, "Global Capabilities" ),
+ HDA_REG_ENTRY(0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , VMIN, "Minor Version" ),
+ HDA_REG_ENTRY(0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , VMAJ, "Major Version" ),
+ HDA_REG_ENTRY(0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , OUTPAY, "Output Payload Capabilities" ),
+ HDA_REG_ENTRY(0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , INPAY, "Input Payload Capabilities" ),
+ HDA_REG_ENTRY(0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteGCTL , GCTL, "Global Control" ),
+ HDA_REG_ENTRY(0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , WAKEEN, "Wake Enable" ),
+ HDA_REG_ENTRY(0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, STATESTS, "State Change Status" ),
+ HDA_REG_ENTRY(0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , GSTS, "Global Status" ),
+ HDA_REG_ENTRY(0x00014, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , LLCH, "Linked List Capabilities Header" ),
+ HDA_REG_ENTRY(0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteU16 , OUTSTRMPAY, "Output Stream Payload Capability" ),
+ HDA_REG_ENTRY(0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , INSTRMPAY, "Input Stream Payload Capability" ),
+ HDA_REG_ENTRY(0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , INTCTL, "Interrupt Control" ),
+ HDA_REG_ENTRY(0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , INTSTS, "Interrupt Status" ),
+ HDA_REG_ENTRY_EX(0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , 0, "WALCLK", "Wall Clock Counter" ),
+ HDA_REG_ENTRY(0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteSSYNC , SSYNC, "Stream Synchronization (old)" ),
+ HDA_REG_ENTRY(0x00038, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteNewSSYNC, SSYNC, "Stream Synchronization (new)" ),
+ HDA_REG_ENTRY(0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , CORBLBASE, "CORB Lower Base Address" ),
+ HDA_REG_ENTRY(0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , CORBUBASE, "CORB Upper Base Address" ),
+ HDA_REG_ENTRY(0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , CORBWP, "CORB Write Pointer" ),
+ HDA_REG_ENTRY(0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , CORBRP, "CORB Read Pointer" ),
+ HDA_REG_ENTRY(0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , CORBCTL, "CORB Control" ),
+ HDA_REG_ENTRY(0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , CORBSTS, "CORB Status" ),
+ HDA_REG_ENTRY(0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, CORBSIZE, "CORB Size" ),
+ HDA_REG_ENTRY(0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , RIRBLBASE, "RIRB Lower Base Address" ),
+ HDA_REG_ENTRY(0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , RIRBUBASE, "RIRB Upper Base Address" ),
+ HDA_REG_ENTRY(0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , RIRBWP, "RIRB Write Pointer" ),
+ HDA_REG_ENTRY(0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_F_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , RINTCNT, "Response Interrupt Count" ),
+ HDA_REG_ENTRY(0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteU8 , RIRBCTL, "RIRB Control" ),
+ HDA_REG_ENTRY(0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , RIRBSTS, "RIRB Status" ),
+ HDA_REG_ENTRY(0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_F_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , RIRBSIZE, "RIRB Size" ),
+ HDA_REG_ENTRY(0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteU32 , IC, "Immediate Command" ),
+ HDA_REG_ENTRY(0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , IR, "Immediate Response" ),
+ HDA_REG_ENTRY(0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_F_NONE, hdaRegReadIRS , hdaRegWriteIRS , IRS, "Immediate Command Status" ),
+ HDA_REG_ENTRY(0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , DPLBASE, "DMA Position Lower Base" ),
+ HDA_REG_ENTRY(0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteBase , DPUBASE, "DMA Position Upper Base" ),
+ /* 4 Serial Data In (SDI). */
+ HDA_REG_MAP_DEF_STREAM(0, SD0),
+ HDA_REG_MAP_DEF_STREAM(1, SD1),
+ HDA_REG_MAP_DEF_STREAM(2, SD2),
+ HDA_REG_MAP_DEF_STREAM(3, SD3),
+ /* 4 Serial Data Out (SDO). */
+ HDA_REG_MAP_DEF_STREAM(4, SD4),
+ HDA_REG_MAP_DEF_STREAM(5, SD5),
+ HDA_REG_MAP_DEF_STREAM(6, SD6),
+ HDA_REG_MAP_DEF_STREAM(7, SD7),
+ HDA_REG_ENTRY(0x00c00, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , MLCH, "Multiple Links Capability Header" ),
+ HDA_REG_ENTRY(0x00c04, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_F_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , MLCD, "Multiple Links Capability Declaration" ),
+ HDA_REG_MAP_SKYLAKE_STRM(0x01080, SD0),
+ HDA_REG_MAP_SKYLAKE_STRM(0x010a0, SD1),
+ HDA_REG_MAP_SKYLAKE_STRM(0x010c0, SD2),
+ HDA_REG_MAP_SKYLAKE_STRM(0x010e0, SD3),
+ HDA_REG_MAP_SKYLAKE_STRM(0x01100, SD4),
+ HDA_REG_MAP_SKYLAKE_STRM(0x01120, SD5),
+ HDA_REG_MAP_SKYLAKE_STRM(0x01140, SD6),
+ HDA_REG_MAP_SKYLAKE_STRM(0x01160, SD7),
+};
+
+#undef HDA_REG_ENTRY_EX
+#undef HDA_REG_ENTRY
+#undef HDA_REG_ENTRY_STR
+#undef HDA_REG_MAP_STRM
+#undef HDA_REG_MAP_DEF_STREAM
+
+/**
+ * HDA register aliases (HDA spec 3.3.45).
+ * @remarks Sorted by offReg.
+ * @remarks Lookup code ASSUMES this starts somewhere after g_aHdaRegMap ends.
+ */
+static struct HDAREGALIAS
+{
+ /** The alias register offset. */
+ uint32_t offReg;
+ /** The register index. */
+ int idxAlias;
+} const g_aHdaRegAliases[] =
+{
+ { 0x2030, HDA_REG_WALCLK },
+ { 0x2084, HDA_REG_SD0LPIB },
+ { 0x20a4, HDA_REG_SD1LPIB },
+ { 0x20c4, HDA_REG_SD2LPIB },
+ { 0x20e4, HDA_REG_SD3LPIB },
+ { 0x2104, HDA_REG_SD4LPIB },
+ { 0x2124, HDA_REG_SD5LPIB },
+ { 0x2144, HDA_REG_SD6LPIB },
+ { 0x2164, HDA_REG_SD7LPIB }
+};
+
+#ifdef IN_RING3
+
+/** HDABDLEDESC field descriptors for the v7+ saved state. */
+static SSMFIELD const g_aSSMBDLEDescFields7[] =
+{
+ SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr),
+ SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize),
+ SSMFIELD_ENTRY(HDABDLEDESC, fFlags),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDABDLEDESC field descriptors for the v6 saved states. */
+static SSMFIELD const g_aSSMBDLEDescFields6[] =
+{
+ SSMFIELD_ENTRY(HDABDLEDESC, u64BufAddr),
+ SSMFIELD_ENTRY(HDABDLEDESC, u32BufSize),
+ SSMFIELD_ENTRY_CALLBACK(HDABDLEDESC, fFlags, hdaR3GetPutTrans_HDABDLEDESC_fFlags_6),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDABDLESTATE field descriptors for the v6 saved state. */
+static SSMFIELD const g_aSSMBDLEStateFields6[] =
+{
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex),
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW),
+ SSMFIELD_ENTRY_OLD(FIFO, 256), /* Deprecated; now is handled in the stream's circular buffer. */
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDABDLESTATE field descriptors for the v7+ saved state. */
+static SSMFIELD const g_aSSMBDLEStateFields7[] =
+{
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BDLIndex),
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, cbBelowFIFOW),
+ SSMFIELD_ENTRY(HDABDLESTATELEGACY, u32BufOff),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDASTREAMSTATE field descriptors for the v6 saved state. */
+static SSMFIELD const g_aSSMStreamStateFields6[] =
+{
+ SSMFIELD_ENTRY_OLD(cBDLE, sizeof(uint16_t)), /* Deprecated. */
+ SSMFIELD_ENTRY_OLD(uCurBDLE, sizeof(uint16_t)), /* We figure it out from LPID */
+ SSMFIELD_ENTRY_OLD(fStop, 1), /* Deprecated; see SSMR3PutBool(). */
+ SSMFIELD_ENTRY_OLD(fRunning, 1), /* Deprecated; using the HDA_SDCTL_RUN bit is sufficient. */
+ SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDASTREAMSTATE field descriptors for the v7+ saved state. */
+static SSMFIELD const g_aSSMStreamStateFields7[] =
+{
+ SSMFIELD_ENTRY(HDASTREAMSTATE, idxCurBdle), /* For backward compatibility we save this. We use LPIB on restore. */
+ SSMFIELD_ENTRY_OLD(uCurBDLEHi, sizeof(uint8_t)), /* uCurBDLE was 16-bit for some reason, so store/ignore the zero top byte. */
+ SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset),
+ SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDABDLE field descriptors for the v1 thru v4 saved states. */
+static SSMFIELD const g_aSSMStreamBdleFields1234[] =
+{
+ SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u64BufAddr), /* u64BdleCviAddr */
+ SSMFIELD_ENTRY_OLD(u32BdleMaxCvi, sizeof(uint32_t)), /* u32BdleMaxCvi */
+ SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BDLIndex), /* u32BdleCvi */
+ SSMFIELD_ENTRY(HDABDLELEGACY, Desc.u32BufSize), /* u32BdleCviLen */
+ SSMFIELD_ENTRY(HDABDLELEGACY, State.u32BufOff), /* u32BdleCviPos */
+ SSMFIELD_ENTRY_CALLBACK(HDABDLELEGACY, Desc.fFlags, hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4), /* fBdleCviIoc */
+ SSMFIELD_ENTRY(HDABDLELEGACY, State.cbBelowFIFOW), /* cbUnderFifoW */
+ SSMFIELD_ENTRY_OLD(au8FIFO, 256), /* au8FIFO */
+ SSMFIELD_ENTRY_TERM()
+};
+
+#endif /* IN_RING3 */
+
+/**
+ * 32-bit size indexed masks, i.e. g_afMasks[2 bytes] = 0xffff.
+ */
+static uint32_t const g_afMasks[5] =
+{
+ UINT32_C(0), UINT32_C(0x000000ff), UINT32_C(0x0000ffff), UINT32_C(0x00ffffff), UINT32_C(0xffffffff)
+};
+
+
+#ifdef VBOX_STRICT
+
+/**
+ * Strict register accessor verifing defines and mapping table.
+ * @see HDA_REG
+ */
+DECLINLINE(uint32_t *) hdaStrictRegAccessor(PHDASTATE pThis, uint32_t idxMap, uint32_t idxReg)
+{
+ Assert(idxMap < RT_ELEMENTS(g_aHdaRegMap));
+ AssertMsg(idxReg == g_aHdaRegMap[idxMap].idxReg, ("idxReg=%d\n", idxReg));
+ return &pThis->au32Regs[idxReg];
+}
+
+/**
+ * Strict stream register accessor verifing defines and mapping table.
+ * @see HDA_STREAM_REG
+ */
+DECLINLINE(uint32_t *) hdaStrictStreamRegAccessor(PHDASTATE pThis, uint32_t idxMap0, uint32_t idxReg0, size_t idxStream)
+{
+ Assert(idxMap0 < RT_ELEMENTS(g_aHdaRegMap));
+ AssertMsg(idxStream < RT_ELEMENTS(pThis->aStreams), ("%#zx\n", idxStream));
+ AssertMsg(idxReg0 + idxStream * 10 == g_aHdaRegMap[idxMap0 + idxStream * 10].idxReg,
+ ("idxReg0=%d idxStream=%zx\n", idxReg0, idxStream));
+ return &pThis->au32Regs[idxReg0 + idxStream * 10];
+}
+
+#endif /* VBOX_STRICT */
+
+
+/**
+ * Returns a new INTSTS value based on the current device state.
+ *
+ * @returns Determined INTSTS register value.
+ * @param pThis The shared HDA device state.
+ *
+ * @remarks This function does *not* set INTSTS!
+ */
+static uint32_t hdaGetINTSTS(PHDASTATE pThis)
+{
+ uint32_t intSts = 0;
+
+ /* Check controller interrupts (RIRB, STATEST). */
+ if (HDA_REG(pThis, RIRBSTS) & HDA_REG(pThis, RIRBCTL) & (HDA_RIRBCTL_ROIC | HDA_RIRBCTL_RINTCTL))
+ {
+ intSts |= HDA_INTSTS_CIS; /* Set the Controller Interrupt Status (CIS). */
+ }
+
+ /* Check SDIN State Change Status Flags. */
+ if (HDA_REG(pThis, STATESTS) & HDA_REG(pThis, WAKEEN))
+ {
+ intSts |= HDA_INTSTS_CIS; /* Touch Controller Interrupt Status (CIS). */
+ }
+
+ /* For each stream, check if any interrupt status bit is set and enabled. */
+ for (uint8_t iStrm = 0; iStrm < HDA_MAX_STREAMS; ++iStrm)
+ {
+ if (HDA_STREAM_REG(pThis, STS, iStrm) & HDA_STREAM_REG(pThis, CTL, iStrm) & (HDA_SDCTL_DEIE | HDA_SDCTL_FEIE | HDA_SDCTL_IOCE))
+ {
+ Log3Func(("[SD%d] interrupt status set\n", iStrm));
+ intSts |= RT_BIT(iStrm);
+ }
+ }
+
+ if (intSts)
+ intSts |= HDA_INTSTS_GIS; /* Set the Global Interrupt Status (GIS). */
+
+ Log3Func(("-> 0x%x\n", intSts));
+
+ return intSts;
+}
+
+
+/**
+ * Processes (asserts/deasserts) the HDA interrupt according to the current state.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pszSource Caller information.
+ */
+#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING)
+void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource)
+#else
+void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis)
+#endif
+{
+ uint32_t uIntSts = hdaGetINTSTS(pThis);
+
+ HDA_REG(pThis, INTSTS) = uIntSts;
+
+ /* NB: It is possible to have GIS set even when CIE/SIEn are all zero; the GIS bit does
+ * not control the interrupt signal. See Figure 4 on page 54 of the HDA 1.0a spec.
+ */
+ /* Global Interrupt Enable (GIE) set? */
+ if ( (HDA_REG(pThis, INTCTL) & HDA_INTCTL_GIE)
+ && (HDA_REG(pThis, INTSTS) & HDA_REG(pThis, INTCTL) & (HDA_INTCTL_CIE | HDA_STRMINT_MASK)))
+ {
+ Log3Func(("Asserted (%s)\n", pszSource));
+
+ PDMDevHlpPCISetIrq(pDevIns, 0, 1 /* Assert */);
+ pThis->u8IRQL = 1;
+
+#ifdef DEBUG
+ pThis->Dbg.IRQ.tsAssertedNs = RTTimeNanoTS();
+ pThis->Dbg.IRQ.tsProcessedLastNs = pThis->Dbg.IRQ.tsAssertedNs;
+#endif
+ }
+ else
+ {
+ Log3Func(("Deasserted (%s)\n", pszSource));
+
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0 /* Deassert */);
+ pThis->u8IRQL = 0;
+ }
+}
+
+
+/**
+ * Looks up a register at the exact offset given by @a offReg.
+ *
+ * @returns Register index on success, -1 if not found.
+ * @param offReg The register offset.
+ */
+static int hdaRegLookup(uint32_t offReg)
+{
+ /*
+ * Aliases.
+ */
+ if (offReg >= g_aHdaRegAliases[0].offReg)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++)
+ if (offReg == g_aHdaRegAliases[i].offReg)
+ return g_aHdaRegAliases[i].idxAlias;
+ Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].off < offReg);
+ return -1;
+ }
+
+ /*
+ * Binary search the
+ */
+ int idxEnd = RT_ELEMENTS(g_aHdaRegMap);
+ int idxLow = 0;
+ for (;;)
+ {
+ int idxMiddle = idxLow + (idxEnd - idxLow) / 2;
+ if (offReg < g_aHdaRegMap[idxMiddle].off)
+ {
+ if (idxLow != idxMiddle)
+ idxEnd = idxMiddle;
+ else
+ break;
+ }
+ else if (offReg > g_aHdaRegMap[idxMiddle].off)
+ {
+ idxLow = idxMiddle + 1;
+ if (idxLow < idxEnd)
+ { /* likely */ }
+ else
+ break;
+ }
+ else
+ return idxMiddle;
+ }
+
+#ifdef RT_STRICT
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ Assert(g_aHdaRegMap[i].off != offReg);
+#endif
+ return -1;
+}
+
+#ifdef IN_RING3
+/**
+ * Looks up a register covering the offset given by @a offReg.
+ *
+ * @returns Register index on success, -1 if not found.
+ * @param offReg The register offset.
+ * @param pcbBefore Where to return the number of bytes in the matching
+ * register preceeding @a offReg.
+ */
+static int hdaR3RegLookupWithin(uint32_t offReg, uint32_t *pcbBefore)
+{
+ /*
+ * Aliases.
+ *
+ * We ASSUME the aliases are for whole registers and that they have the
+ * same alignment (release-asserted in the constructor), so we don't need
+ * to calculate the within-register-offset twice here.
+ */
+ if (offReg >= g_aHdaRegAliases[0].offReg)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++)
+ {
+ uint32_t const off = offReg - g_aHdaRegAliases[i].offReg;
+ if (off < 4) /* No register is wider than 4 bytes (release-asserted in constructor). */
+ {
+ const uint32_t idxAlias = g_aHdaRegAliases[i].idxAlias;
+ if (off < g_aHdaRegMap[idxAlias].cb)
+ {
+ Assert(off > 0); /* ASSUMES the caller already did a hdaRegLookup which failed. */
+ Assert((g_aHdaRegAliases[i].offReg & 3) == (g_aHdaRegMap[idxAlias].off & 3));
+ *pcbBefore = off;
+ return idxAlias;
+ }
+ }
+ }
+ Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].off < offReg);
+ *pcbBefore = 0;
+ return -1;
+ }
+
+ /*
+ * Binary search the register map.
+ */
+ int idxEnd = RT_ELEMENTS(g_aHdaRegMap);
+ int idxLow = 0;
+ for (;;)
+ {
+ int idxMiddle = idxLow + (idxEnd - idxLow) / 2;
+ if (offReg < g_aHdaRegMap[idxMiddle].off)
+ {
+ if (idxLow == idxMiddle)
+ break;
+ idxEnd = idxMiddle;
+ }
+ else if (offReg >= g_aHdaRegMap[idxMiddle].off + g_aHdaRegMap[idxMiddle].cb)
+ {
+ idxLow = idxMiddle + 1;
+ if (idxLow >= idxEnd)
+ break;
+ }
+ else
+ {
+ offReg -= g_aHdaRegMap[idxMiddle].off;
+ *pcbBefore = offReg;
+ Assert(offReg > 0); /* ASSUMES the caller already did a hdaRegLookup which failed. */
+ Assert(g_aHdaRegMap[idxMiddle].cb <= 4); /* This is release-asserted in the constructor. */
+ return idxMiddle;
+ }
+ }
+
+# ifdef RT_STRICT
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ Assert(offReg - g_aHdaRegMap[i].off >= g_aHdaRegMap[i].cb);
+# endif
+ *pcbBefore = 0;
+ return -1;
+}
+#endif /* IN_RING3 */
+
+#ifdef IN_RING3 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */
+
+/**
+ * Synchronizes the CORB / RIRB buffers between internal <-> device state.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param fLocal Specify true to synchronize HDA state's CORB buffer with the device state,
+ * or false to synchronize the device state's RIRB buffer with the HDA state.
+ *
+ * @todo r=andy Break this up into two functions?
+ */
+static int hdaR3CmdSync(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fLocal)
+{
+ int rc = VINF_SUCCESS;
+ if (fLocal)
+ {
+ if (pThis->u64CORBBase)
+ {
+ Assert(pThis->cbCorbBuf);
+ rc = PDMDevHlpPCIPhysRead(pDevIns, pThis->u64CORBBase, pThis->au32CorbBuf,
+ RT_MIN(pThis->cbCorbBuf, sizeof(pThis->au32CorbBuf)));
+ Log3Func(("CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ if (pThis->u64RIRBBase)
+ {
+ Assert(pThis->cbRirbBuf);
+
+ rc = PDMDevHlpPCIPhysWrite(pDevIns, pThis->u64RIRBBase, pThis->au64RirbBuf,
+ RT_MIN(pThis->cbRirbBuf, sizeof(pThis->au64RirbBuf)));
+ Log3Func(("RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->cbRirbBuf, rc));
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+# ifdef DEBUG_CMD_BUFFER
+ LogFunc(("fLocal=%RTbool\n", fLocal));
+
+ uint8_t i = 0;
+ do
+ {
+ LogFunc(("CORB%02x: ", i));
+ uint8_t j = 0;
+ do
+ {
+ const char *pszPrefix;
+ if ((i + j) == HDA_REG(pThis, CORBRP))
+ pszPrefix = "[R]";
+ else if ((i + j) == HDA_REG(pThis, CORBWP))
+ pszPrefix = "[W]";
+ else
+ pszPrefix = " "; /* three spaces */
+ Log((" %s%08x", pszPrefix, pThis->pu32CorbBuf[i + j]));
+ j++;
+ } while (j < 8);
+ Log(("\n"));
+ i += 8;
+ } while (i != 0);
+
+ do
+ {
+ LogFunc(("RIRB%02x: ", i));
+ uint8_t j = 0;
+ do
+ {
+ const char *prefix;
+ if ((i + j) == HDA_REG(pThis, RIRBWP))
+ prefix = "[W]";
+ else
+ prefix = " ";
+ Log((" %s%016lx", prefix, pThis->pu64RirbBuf[i + j]));
+ } while (++j < 8);
+ Log(("\n"));
+ i += 8;
+ } while (i != 0);
+# endif
+ return rc;
+}
+
+
+/**
+ * Processes the next CORB buffer command in the queue.
+ *
+ * This will invoke the HDA codec ring-3 verb dispatcher.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-0 HDA device state.
+ */
+static int hdaR3CORBCmdProcess(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATECC pThisCC)
+{
+ Log3Func(("ENTER CORB(RP:%x, WP:%x) RIRBWP:%x\n", HDA_REG(pThis, CORBRP), HDA_REG(pThis, CORBWP), HDA_REG(pThis, RIRBWP)));
+
+ if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA))
+ {
+ LogFunc(("CORB DMA not active, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ Assert(pThis->cbCorbBuf);
+
+ int rc = hdaR3CmdSync(pDevIns, pThis, true /* Sync from guest */);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Prepare local copies of relevant registers.
+ */
+ uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff;
+ if (!cIntCnt) /* 0 means 256 interrupts. */
+ cIntCnt = HDA_MAX_RINTCNT;
+
+ uint32_t const cCorbEntries = RT_MIN(RT_MAX(pThis->cbCorbBuf, 1), sizeof(pThis->au32CorbBuf)) / HDA_CORB_ELEMENT_SIZE;
+ uint8_t const corbWp = HDA_REG(pThis, CORBWP) % cCorbEntries;
+ uint8_t corbRp = HDA_REG(pThis, CORBRP);
+ uint8_t rirbWp = HDA_REG(pThis, RIRBWP);
+
+ /*
+ * The loop.
+ */
+ Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt));
+ while (corbRp != corbWp)
+ {
+ /* Fetch the command from the CORB. */
+ corbRp = (corbRp + 1) /* Advance +1 as the first command(s) are at CORBWP + 1. */ % cCorbEntries;
+ uint32_t const uCmd = pThis->au32CorbBuf[corbRp];
+
+ /*
+ * Execute the command.
+ */
+ uint64_t uResp = 0;
+ rc = hdaR3CodecLookup(&pThisCC->Codec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp);
+ if (RT_SUCCESS(rc))
+ AssertRCSuccess(rc); /* no informational statuses */
+ else
+ Log3Func(("Lookup for codec verb %08x failed: %Rrc\n", uCmd, rc));
+ Log3Func(("Codec verb %08x -> response %016RX64\n", uCmd, uResp));
+
+ if ( (uResp & CODEC_RESPONSE_UNSOLICITED)
+ && !(HDA_REG(pThis, GCTL) & HDA_GCTL_UNSOL))
+ {
+ LogFunc(("Unexpected unsolicited response.\n"));
+ HDA_REG(pThis, CORBRP) = corbRp;
+ /** @todo r=andy No RIRB syncing to guest required in that case? */
+ /** @todo r=bird: Why isn't RIRBWP updated here. The response might come
+ * after already processing several commands, can't it? (When you think
+ * about it, it is bascially the same question as Andy is asking.) */
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Store the response in the RIRB.
+ */
+ AssertCompile(HDA_RIRB_SIZE == RT_ELEMENTS(pThis->au64RirbBuf));
+ rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE;
+ pThis->au64RirbBuf[rirbWp] = uResp;
+
+ /*
+ * Send interrupt if needed.
+ */
+ bool fSendInterrupt = false;
+ pThis->u16RespIntCnt++;
+ if (pThis->u16RespIntCnt >= cIntCnt) /* Response interrupt count reached? */
+ {
+ pThis->u16RespIntCnt = 0; /* Reset internal interrupt response counter. */
+
+ Log3Func(("Response interrupt count reached (%RU16)\n", pThis->u16RespIntCnt));
+ fSendInterrupt = true;
+ }
+ else if (corbRp == corbWp) /* Did we reach the end of the current command buffer? */
+ {
+ Log3Func(("Command buffer empty\n"));
+ fSendInterrupt = true;
+ }
+ if (fSendInterrupt)
+ {
+ if (HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RINTCTL) /* Response Interrupt Control (RINTCTL) enabled? */
+ {
+ HDA_REG(pThis, RIRBSTS) |= HDA_RIRBSTS_RINTFL;
+ HDA_PROCESS_INTERRUPT(pDevIns, pThis);
+ }
+ }
+ }
+
+ /*
+ * Put register locals back.
+ */
+ Log3Func(("END CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n", corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt));
+ HDA_REG(pThis, CORBRP) = corbRp;
+ HDA_REG(pThis, RIRBWP) = rirbWp;
+
+ /*
+ * Write out the response.
+ */
+ rc = hdaR3CmdSync(pDevIns, pThis, false /* Sync to guest */);
+ AssertRC(rc);
+
+ return rc;
+}
+
+#endif /* IN_RING3 - @bugref{9890c64} */
+
+#ifdef IN_RING3
+/**
+ * @callback_method_impl{FNPDMTASKDEV, Continue CORB DMA in ring-3}
+ */
+static DECLCALLBACK(void) hdaR3CorbDmaTaskWorker(PPDMDEVINS pDevIns, void *pvUser)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ RT_NOREF(pvUser);
+ LogFlowFunc(("\n"));
+
+ DEVHDA_LOCK(pDevIns, pThis);
+ hdaR3CORBCmdProcess(pDevIns, pThis, pThisCC);
+ DEVHDA_UNLOCK(pDevIns, pThis);
+
+}
+#endif /* IN_RING3 */
+
+/* Register access handlers. */
+
+static VBOXSTRICTRC hdaRegReadUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ *pu32Value = 0;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteUnimpl(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg, u32Value);
+ return VINF_SUCCESS;
+}
+
+/* U8 */
+static VBOXSTRICTRC hdaRegReadU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xffffff00)) == 0);
+ return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value);
+}
+
+static VBOXSTRICTRC hdaRegWriteU8(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xffffff00) == 0);
+ return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+}
+
+/* U16 */
+static VBOXSTRICTRC hdaRegReadU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xffff0000)) == 0);
+ return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value);
+}
+
+static VBOXSTRICTRC hdaRegWriteU16(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xffff0000) == 0);
+ return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+}
+
+/* U24 */
+static VBOXSTRICTRC hdaRegReadU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].idxReg] & g_aHdaRegMap[iReg].fReadableMask) & UINT32_C(0xff000000)) == 0);
+ return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value);
+}
+
+#ifdef IN_RING3
+static VBOXSTRICTRC hdaRegWriteU24(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xff000000) == 0);
+ return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+}
+#endif
+
+/* U32 */
+static VBOXSTRICTRC hdaRegReadU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns);
+
+ uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg;
+ *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].fReadableMask;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteU32(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+
+ uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg;
+ pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].fWritableMask)
+ | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].fWritableMask);
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteGCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ if (u32Value & HDA_GCTL_CRST)
+ {
+ /* Set the CRST bit to indicate that we're leaving reset mode. */
+ HDA_REG(pThis, GCTL) |= HDA_GCTL_CRST;
+ LogFunc(("Guest leaving HDA reset\n"));
+ }
+ else
+ {
+#ifdef IN_RING3
+ /* Enter reset state. */
+ LogFunc(("Guest entering HDA reset with DMA(RIRB:%s, CORB:%s)\n",
+ HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA ? "on" : "off",
+ HDA_REG(pThis, RIRBCTL) & HDA_RIRBCTL_RDMAEN ? "on" : "off"));
+
+ /* Clear the CRST bit to indicate that we're in reset state. */
+ HDA_REG(pThis, GCTL) &= ~HDA_GCTL_CRST;
+
+ hdaR3GCTLReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3));
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+
+ if (u32Value & HDA_GCTL_FCNTRL)
+ {
+ /* Flush: GSTS:1 set, see 6.2.6. */
+ HDA_REG(pThis, GSTS) |= HDA_GSTS_FSTS; /* Set the flush status. */
+ /* DPLBASE and DPUBASE should be initialized with initial value (see 6.2.6). */
+ }
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteSTATESTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+
+ uint32_t v = HDA_REG_IND(pThis, iReg);
+ uint32_t nv = u32Value & HDA_STATESTS_SCSF_MASK;
+
+ HDA_REG(pThis, STATESTS) &= ~(v & nv); /* Write of 1 clears corresponding bit. */
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegReadLPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns);
+ uint8_t const uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg);
+ uint32_t const uLPIB = HDA_STREAM_REG(pThis, LPIB, uSD);
+
+#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ /*
+ * Should we consider doing DMA work while we're here? That would require
+ * the stream to have the DMA engine enabled and be an output stream.
+ */
+ if ( (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_RUN)
+ && hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT
+ && uSD < RT_ELEMENTS(pThis->aStreams) /* paranoia */)
+ {
+ PHDASTREAM const pStreamShared = &pThis->aStreams[uSD];
+ Assert(pStreamShared->u8SD == uSD);
+ if (pStreamShared->State.fRunning /* should be same as HDA_SDCTL_RUN, but doesn't hurt to check twice */)
+ {
+ /*
+ * Calculate where the DMA engine should be according to the clock, if we can.
+ */
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props);
+ uint32_t const cbPeriod = pStreamShared->State.cbCurDmaPeriod;
+ if (cbPeriod > cbFrame)
+ {
+ AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod));
+ uint64_t const tsTransferNext = pStreamShared->State.tsTransferNext;
+ uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer); /* only #0 works in r0 */
+ uint32_t cbFuture;
+ if (tsNow < tsTransferNext)
+ {
+ /** @todo ASSUMES nanosecond clock ticks, need to make this
+ * resolution independent. */
+ cbFuture = PDMAudioPropsNanoToBytes(&pStreamShared->State.Cfg.Props, tsTransferNext - tsNow);
+ cbFuture = RT_MIN(cbFuture, cbPeriod - cbFrame);
+ }
+ else
+ {
+ /* We've hit/overshot the timer deadline. Return to ring-3 if we're
+ not already there to increase the chance that we'll help expidite
+ the timer. If we're already in ring-3, do all but the last frame. */
+# ifndef IN_RING3
+ LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> VINF_IOM_R3_MMIO_READ\n",
+ tsNow, tsTransferNext));
+ return VINF_IOM_R3_MMIO_READ;
+# else
+ cbFuture = cbPeriod - cbFrame;
+ LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> cbFuture=%#x (cbPeriod=%#x - cbFrame=%#x)\n",
+ tsNow, tsTransferNext, cbFuture, cbPeriod, cbFrame));
+# endif
+ }
+ uint32_t const offNow = PDMAudioPropsFloorBytesToFrame(&pStreamShared->State.Cfg.Props, cbPeriod - cbFuture);
+
+ /*
+ * Should we transfer a little? Minimum is 64 bytes (semi-random,
+ * suspect real hardware might be doing some cache aligned stuff,
+ * which might soon get complicated if you take unaligned buffers
+ * into consideration and which cache line size (128 bytes is just
+ * as likely as 64 or 32 bytes)).
+ */
+ uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal;
+ if (cbDmaTotal + 64 <= offNow)
+ {
+ VBOXSTRICTRC rcStrict = hdaStreamDoOnAccessDmaOutput(pDevIns, pThis, pStreamShared,
+ tsNow, offNow - cbDmaTotal);
+
+ /* LPIB is updated by hdaStreamDoOnAccessDmaOutput, so get the new value. */
+ uint32_t const uNewLpib = HDA_STREAM_REG(pThis, LPIB, uSD);
+ *pu32Value = uNewLpib;
+
+ LogFlowFunc(("[SD%RU8] LPIB=%#RX32 (CBL=%#RX32 PrevLPIB=%#x offNow=%#x) rcStrict=%Rrc\n", uSD,
+ uNewLpib, HDA_STREAM_REG(pThis, CBL, uSD), uLPIB, offNow, VBOXSTRICTRC_VAL(rcStrict) ));
+ return rcStrict;
+ }
+
+ /*
+ * Do nothing, just return LPIB as it is.
+ */
+ LogFlowFunc(("[SD%RU8] Skipping DMA transfer: cbDmaTotal=%#x offNow=%#x\n", uSD, cbDmaTotal, offNow));
+ }
+ else
+ LogFunc(("[SD%RU8] cbPeriod=%#x <= cbFrame=%#x!!\n", uSD, cbPeriod, cbFrame));
+ }
+ else
+ LogFunc(("[SD%RU8] fRunning=0 SDnCTL=%#x!!\n", uSD, HDA_STREAM_REG(pThis, CTL, uSD) ));
+ }
+#endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */
+
+ LogFlowFunc(("[SD%RU8] LPIB=%#RX32 (CBL=%#RX32 CTL=%#RX32)\n",
+ uSD, uLPIB, HDA_STREAM_REG(pThis, CBL, uSD), HDA_STREAM_REG(pThis, CTL, uSD) ));
+ *pu32Value = uLPIB;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Gets the wall clock.
+ *
+ * Used by hdaRegReadWALCLK() and 'info hda'.
+ *
+ * @returns Strict VBox status code if @a fDoDma is @c true, otherwise
+ * VINF_SUCCESS.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param fDoDma Whether to consider doing DMA work or not.
+ * @param puWallNow Where to return the current wall clock time.
+ */
+static VBOXSTRICTRC hdaQueryWallClock(PPDMDEVINS pDevIns, PHDASTATE pThis, bool fDoDma, uint64_t *puWallNow)
+{
+ /*
+ * The wall clock is calculated from the virtual sync clock. Since
+ * the clock is supposed to reset to zero on controller reset, a
+ * start offset is subtracted.
+ *
+ * In addition, we hold the clock back when there are active DMA engines
+ * so that the guest won't conclude we've gotten further in the buffer
+ * processing than what we really have. (We generally read a whole buffer
+ * at once when the IOC is due, so we're a lot later than what real
+ * hardware would be in reading/writing the buffers.)
+ *
+ * Here are some old notes from the DMA engine that might be useful even
+ * if a little dated:
+ *
+ * Note 1) Only certain guests (like Linux' snd_hda_intel) rely on the WALCLK register
+ * in order to determine the correct timing of the sound device. Other guests
+ * like Windows 7 + 10 (or even more exotic ones like Haiku) will completely
+ * ignore this.
+ *
+ * Note 2) When updating the WALCLK register too often / early (or even in a non-monotonic
+ * fashion) this *will* upset guest device drivers and will completely fuck up the
+ * sound output. Running VLC on the guest will tell!
+ */
+ uint64_t const uFreq = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer);
+ Assert(uFreq <= UINT32_MAX);
+ uint64_t const tsStart = 0; /** @todo pThis->tsWallClkStart (as it is reset on controller reset) */
+ uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer);
+
+ /* Find the oldest DMA transfer timestamp from the active streams. */
+ int iDmaNow = -1;
+ uint64_t tsDmaNow = tsNow;
+ for (size_t i = 0; i < RT_ELEMENTS(pThis->aStreams); i++)
+ if (pThis->aStreams[i].State.fRunning)
+ {
+#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ /* Linux is reading WALCLK before one of the DMA position reads and
+ we've already got the current time from TM, so check if we should
+ do a little bit of DMA'ing here to help WALCLK ahead. */
+ if (fDoDma)
+ {
+ if (hdaGetDirFromSD((uint8_t)i) == PDMAUDIODIR_OUT)
+ {
+ VBOXSTRICTRC rcStrict = hdaStreamMaybeDoOnAccessDmaOutput(pDevIns, pThis, &pThis->aStreams[i], tsNow);
+ if (rcStrict == VINF_SUCCESS)
+ { /* likely */ }
+ else
+ return rcStrict;
+ }
+ }
+#endif
+
+ if ( pThis->aStreams[i].State.tsTransferLast < tsDmaNow
+ && pThis->aStreams[i].State.tsTransferLast > tsStart)
+ {
+ tsDmaNow = pThis->aStreams[i].State.tsTransferLast;
+ iDmaNow = (int)i;
+ }
+ }
+
+ /* Convert it to wall clock ticks. */
+ uint64_t const uWallClkNow = ASMMultU64ByU32DivByU32(tsDmaNow - tsStart,
+ 24000000 /*Wall clock frequency */,
+ uFreq);
+ Log3Func(("Returning %#RX64 - tsNow=%#RX64 tsDmaNow=%#RX64 (%d) -> %#RX64\n",
+ uWallClkNow, tsNow, tsDmaNow, iDmaNow, tsNow - tsDmaNow));
+ RT_NOREF(iDmaNow, fDoDma);
+ *puWallNow = uWallClkNow;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegReadWALCLK(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint64_t uWallNow = 0;
+ VBOXSTRICTRC rcStrict = hdaQueryWallClock(pDevIns, pThis, true /*fDoDma*/, &uWallNow);
+ if (rcStrict == VINF_SUCCESS)
+ {
+ *pu32Value = (uint32_t)uWallNow;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF(iReg);
+ return rcStrict;
+}
+
+static VBOXSTRICTRC hdaRegWriteSSYNCWorker(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, const char *pszCaller)
+{
+ RT_NOREF(pszCaller);
+
+ /*
+ * The SSYNC register is a DMA pause mask where each bit represents a stream.
+ * There should be no DMA transfers going down the driver chains when the a
+ * stream has its bit set here. There are two scenarios described in the
+ * specification, starting and stopping, though it can probably be used for
+ * other purposes if the guest gets creative...
+ *
+ * Anyway, if we ever want to implement this, we'd be manipulating the DMA
+ * timers of the affected streams here, I think. At least in the start
+ * scenario, we would run the first DMA transfers from here.
+ */
+ uint32_t const fOld = HDA_REG(pThis, SSYNC);
+ uint32_t const fNew = (u32Value & g_aHdaRegMap[iReg].fWritableMask)
+ | (fOld & ~g_aHdaRegMap[iReg].fWritableMask);
+ uint32_t const fChanged = (fNew ^ fOld) & (RT_BIT_32(HDA_MAX_STREAMS) - 1);
+ if (fChanged)
+ {
+#if 0 /** @todo implement SSYNC: ndef IN_RING3 */
+ Log3(("%s: Going to ring-3 to handle SSYNC change: %#x\n", pszCaller, fChanged));
+ return VINF_IOM_R3_MMIO_WRITE;
+#else
+ for (uint32_t fMask = 1, i = 0; fMask < RT_BIT_32(HDA_MAX_STREAMS); i++, fMask <<= 1)
+ if (!(fChanged & fMask))
+ { /* nothing */ }
+ else if (fNew & fMask)
+ {
+ Log3(("%Rfn: SSYNC bit %u set\n", pszCaller, i));
+ /* See code in SDCTL around hdaR3StreamTimerMain call. */
+ }
+ else
+ {
+ Log3(("%Rfn: SSYNC bit %u cleared\n", pszCaller, i));
+ /* The next DMA timer callout will not do anything. */
+ }
+#endif
+ }
+
+ HDA_REG(pThis, SSYNC) = fNew;
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteSSYNC(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+ return hdaRegWriteSSYNCWorker(pThis, iReg, u32Value, __FUNCTION__);
+}
+
+static VBOXSTRICTRC hdaRegWriteNewSSYNC(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+ return hdaRegWriteSSYNCWorker(pThis, iReg, u32Value, __FUNCTION__);
+}
+
+static VBOXSTRICTRC hdaRegWriteCORBRP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ if (u32Value & HDA_CORBRP_RST)
+ {
+ /* Do a CORB reset. */
+ if (pThis->cbCorbBuf)
+ RT_ZERO(pThis->au32CorbBuf);
+
+ LogRel2(("HDA: CORB reset\n"));
+ HDA_REG(pThis, CORBRP) = HDA_CORBRP_RST; /* Clears the pointer. */
+ }
+ else
+ HDA_REG(pThis, CORBRP) &= ~HDA_CORBRP_RST; /* Only CORBRP_RST bit is writable. */
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteCORBCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ VBOXSTRICTRC rc = hdaRegWriteU8(pDevIns, pThis, iReg, u32Value);
+ AssertRCSuccess(VBOXSTRICTRC_VAL(rc));
+
+ if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* DMA engine started? */
+ {
+#ifdef IN_RING3 /** @todo do PDMDevHlpTaskTrigger everywhere? */
+ rc = hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATECC));
+#else
+ rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hCorbDmaTask);
+ if (rc != VINF_SUCCESS && RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+#endif
+ }
+ else
+ LogFunc(("CORB DMA not running, skipping\n"));
+
+ return rc;
+}
+
+static VBOXSTRICTRC hdaRegWriteCORBSIZE(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ if (!(HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA)) /* Ignore request if CORB DMA engine is (still) running. */
+ {
+ u32Value = (u32Value & HDA_CORBSIZE_SZ);
+
+ uint16_t cEntries;
+ switch (u32Value)
+ {
+ case 0: /* 8 byte; 2 entries. */
+ cEntries = 2;
+ break;
+ case 1: /* 64 byte; 16 entries. */
+ cEntries = 16;
+ break;
+ case 2: /* 1 KB; 256 entries. */
+ cEntries = HDA_CORB_SIZE; /* default. */
+ break;
+ default:
+ LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value));
+ u32Value = 2;
+ cEntries = HDA_CORB_SIZE; /* Use default size. */
+ break;
+ }
+
+ uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE;
+ Assert(cbCorbBuf <= sizeof(pThis->au32CorbBuf)); /* paranoia */
+
+ if (cbCorbBuf != pThis->cbCorbBuf)
+ {
+ RT_ZERO(pThis->au32CorbBuf); /* Clear CORB when setting a new size. */
+ pThis->cbCorbBuf = cbCorbBuf;
+ }
+
+ LogFunc(("CORB buffer size is now %RU32 bytes (%u entries)\n", pThis->cbCorbBuf, pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE));
+
+ HDA_REG(pThis, CORBSIZE) = u32Value;
+ }
+ else
+ LogFunc(("CORB DMA is (still) running, skipping\n"));
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteCORBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ uint32_t v = HDA_REG(pThis, CORBSTS);
+ HDA_REG(pThis, CORBSTS) &= ~(v & u32Value);
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteCORBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value);
+ AssertRCSuccess(VBOXSTRICTRC_VAL(rc));
+
+#ifdef IN_RING3 /** @todo do PDMDevHlpTaskTrigger everywhere? */
+ return hdaR3CORBCmdProcess(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATECC));
+#else
+ rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hCorbDmaTask);
+ return RT_SUCCESS(rc) ? VINF_SUCCESS : rc;
+#endif
+}
+
+static VBOXSTRICTRC hdaRegWriteSDCBL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+}
+
+static VBOXSTRICTRC hdaRegWriteSDCTL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ /* Get the stream descriptor number. */
+ const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg);
+ AssertReturn(uSD < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */
+
+ /*
+ * Extract the stream tag the guest wants to use for this specific
+ * stream descriptor (SDn). This only can happen if the stream is in a non-running
+ * state, so we're doing the lookup and assignment here.
+ *
+ * So depending on the guest OS, SD3 can use stream tag 4, for example.
+ */
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK;
+ ASSERT_GUEST_MSG_RETURN(uTag < RT_ELEMENTS(pThisCC->aTags),
+ ("SD%RU8: Invalid stream tag %RU8 (u32Value=%#x)!\n", uSD, uTag, u32Value),
+ VINF_SUCCESS /* Always return success to the MMIO handler. */);
+
+ PHDASTREAM const pStreamShared = &pThis->aStreams[uSD];
+ PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[uSD];
+
+ const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN);
+ const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST);
+
+ /* If the run bit is set, we take the virtual-sync clock lock as well so we
+ can safely update timers via hdaR3TimerSet if necessary. We need to be
+ very careful with the fInReset and fInRun indicators here, as they may
+ change during the relocking if we need to acquire the clock lock. */
+ const bool fNeedVirtualSyncClockLock = (u32Value & (HDA_SDCTL_RUN | HDA_SDCTL_SRST)) == HDA_SDCTL_RUN
+ && (HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN) == 0;
+ if (fNeedVirtualSyncClockLock)
+ {
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ DEVHDA_LOCK_BOTH_RETURN(pDevIns, pThis, pStreamShared, VINF_IOM_R3_MMIO_WRITE);
+ }
+
+ const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN);
+ const bool fInReset = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_SRST);
+
+ /*LogFunc(("[SD%RU8] fRun=%RTbool, fInRun=%RTbool, fReset=%RTbool, fInReset=%RTbool, %R[sdctl]\n",
+ uSD, fRun, fInRun, fReset, fInReset, u32Value));*/
+ if (fInReset)
+ {
+ ASSERT_GUEST(!fReset);
+ ASSERT_GUEST(!fInRun && !fRun);
+
+ /* Exit reset state. */
+ ASMAtomicXchgBool(&pStreamShared->State.fInReset, false);
+
+ /* Report that we're done resetting this stream by clearing SRST. */
+ HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_SRST;
+
+ LogFunc(("[SD%RU8] Reset exit\n", uSD));
+ }
+ else if (fReset)
+ {
+ /* ICH6 datasheet 18.2.33 says that RUN bit should be cleared before initiation of reset. */
+ ASSERT_GUEST(!fInRun && !fRun);
+
+ LogFunc(("[SD%RU8] Reset enter\n", uSD));
+
+ STAM_REL_PROFILE_START_NS(&pStreamR3->State.StatReset, a);
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pMixSink)
+ AudioMixerSinkLock(pMixSink);
+
+ /* Deal with reset while running. */
+ if (pStreamShared->State.fRunning)
+ {
+ int rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, false /* fEnable */);
+ AssertRC(rc2); Assert(!pStreamShared->State.fRunning);
+ pStreamShared->State.fRunning = false;
+ }
+
+ hdaR3StreamReset(pThis, pThisCC, pStreamShared, pStreamR3, uSD);
+
+ if (pMixSink) /* (FYI. pMixSink might not be what pStreamR3->pMixSink->pMixSink points at any longer) */
+ AudioMixerSinkUnlock(pMixSink);
+ STAM_REL_PROFILE_STOP_NS(&pStreamR3->State.StatReset, a);
+ }
+ else
+ {
+ /*
+ * We enter here to change DMA states only.
+ */
+ if (fInRun != fRun)
+ {
+ STAM_REL_PROFILE_START_NS((fRun ? &pStreamR3->State.StatStart : &pStreamR3->State.StatStop), r);
+ Assert(!fReset && !fInReset); /* (code change paranoia, currently impossible ) */
+ LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun));
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ /** @todo bird: It's not clear to me when the pMixSink is actually
+ * assigned to the stream, so being paranoid till I find out... */
+ PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pMixSink)
+ AudioMixerSinkLock(pMixSink);
+
+ int rc2 = VINF_SUCCESS;
+ if (fRun)
+ {
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT)
+ {
+ const uint8_t uStripeCtl = ((u32Value >> HDA_SDCTL_STRIPE_SHIFT) & HDA_SDCTL_STRIPE_MASK) + 1;
+ LogFunc(("[SD%RU8] Using %RU8 SDOs (stripe control)\n", uSD, uStripeCtl));
+ if (uStripeCtl > 1)
+ LogRel2(("HDA: Warning: Striping output over more than one SDO for stream #%RU8 currently is not implemented " \
+ "(%RU8 SDOs requested)\n", uSD, uStripeCtl));
+ }
+
+ /* Assign new values. */
+ LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag));
+ PHDATAG pTag = &pThisCC->aTags[uTag];
+ pTag->uTag = uTag;
+ pTag->pStreamR3 = &pThisCC->aStreams[uSD];
+
+# ifdef LOG_ENABLED
+ if (LogIsEnabled())
+ {
+ PDMAUDIOPCMPROPS Props = { 0 };
+ rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, uSD), &Props); AssertRC(rc2);
+ LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n",
+ uSD, Props.uHz, PDMAudioPropsSampleBits(&Props), PDMAudioPropsChannels(&Props)));
+ }
+# endif
+ /* (Re-)initialize the stream with current values. */
+ rc2 = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, uSD);
+ if ( RT_SUCCESS(rc2)
+ /* Any vital stream change occurred so that we need to (re-)add the stream to our setup?
+ * Otherwise just skip this, as this costs a lot of performance. */
+ /** @todo r=bird: hdaR3StreamSetUp does not return VINF_NO_CHANGE since r142810. */
+ && rc2 != VINF_NO_CHANGE)
+ {
+ /* Remove the old stream from the device setup. */
+ rc2 = hdaR3RemoveStream(pThisCC, &pStreamShared->State.Cfg);
+ AssertRC(rc2);
+
+ /* Add the stream to the device setup. */
+ rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg);
+ AssertRC(rc2);
+ }
+ }
+
+ if (RT_SUCCESS(rc2))
+ {
+ /* Enable/disable the stream. */
+ rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, fRun /* fEnable */);
+ AssertRC(rc2);
+
+ if (fRun)
+ {
+ /** @todo move this into a HDAStream.cpp function. */
+ uint64_t tsNow;
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT)
+ {
+ /* Output streams: Avoid going through the timer here by calling the stream's timer
+ function directly. Should speed up starting the stream transfers. */
+ tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
+ }
+ else
+ {
+ /* Input streams: Arm the timer and kick the AIO thread. */
+ tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer);
+ pStreamShared->State.tsTransferLast = tsNow; /* for WALCLK */
+
+ uint64_t tsTransferNext = tsNow + pStreamShared->State.aSchedule[0].cPeriodTicks;
+ pStreamShared->State.tsTransferNext = tsTransferNext; /* legacy */
+ pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[0].cbPeriod;
+ Log3Func(("[SD%RU8] tsTransferNext=%RU64 (in %RU64)\n",
+ pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow));
+
+ int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext);
+ AssertRC(rc);
+
+ /** @todo we should have a delayed AIO thread kick off, really... */
+ if (pStreamR3->pMixSink && pStreamR3->pMixSink->pMixSink)
+ AudioMixerSinkSignalUpdateJob(pStreamR3->pMixSink->pMixSink);
+ else
+ AssertFailed();
+ }
+ hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow);
+ }
+ else
+ hdaR3StreamMarkStopped(pStreamShared);
+ }
+
+ /* Make sure to leave the lock before (eventually) starting the timer. */
+ if (pMixSink)
+ AudioMixerSinkUnlock(pMixSink);
+ STAM_REL_PROFILE_STOP_NS((fRun ? &pStreamR3->State.StatStart : &pStreamR3->State.StatStop), r);
+ }
+ }
+
+ if (fNeedVirtualSyncClockLock)
+ PDMDevHlpTimerUnlockClock(pDevIns, pStreamShared->hTimer); /* Caller will unlock pThis->CritSect. */
+
+ return hdaRegWriteU24(pDevIns, pThis, iReg, u32Value);
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns, pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+static VBOXSTRICTRC hdaRegWriteSDSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ uint32_t v = HDA_REG_IND(pThis, iReg);
+
+ /* Clear (zero) FIFOE, DESE and BCIS bits when writing 1 to it (6.2.33). */
+ HDA_REG_IND(pThis, iReg) &= ~(u32Value & v);
+
+ HDA_PROCESS_INTERRUPT(pDevIns, pThis);
+
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteSDLVI(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ const size_t idxStream = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg);
+ AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */
+
+ ASSERT_GUEST_LOGREL_MSG(u32Value <= UINT8_MAX, /* Should be covered by the register write mask, but just to make sure. */
+ ("LVI for stream #%zu must not be bigger than %RU8\n", idxStream, UINT8_MAX - 1));
+ return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value);
+}
+
+/**
+ * Calculates the number of bytes of a FIFOW register.
+ *
+ * @return Number of bytes of a given FIFOW register.
+ * @param u16RegFIFOW FIFOW register to convert.
+ */
+uint8_t hdaSDFIFOWToBytes(uint16_t u16RegFIFOW)
+{
+ uint32_t cb;
+ switch (u16RegFIFOW)
+ {
+ case HDA_SDFIFOW_8B: cb = 8; break;
+ case HDA_SDFIFOW_16B: cb = 16; break;
+ case HDA_SDFIFOW_32B: cb = 32; break;
+ default:
+ AssertFailedStmt(cb = 32); /* Paranoia. */
+ break;
+ }
+
+ Assert(RT_IS_POWER_OF_TWO(cb));
+ return cb;
+}
+
+static VBOXSTRICTRC hdaRegWriteSDFIFOW(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ size_t const idxStream = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg);
+ AssertReturn(idxStream < RT_ELEMENTS(pThis->aStreams), VERR_INTERNAL_ERROR_3); /* paranoia^2: Bad g_aHdaRegMap. */
+
+ if (RT_LIKELY(hdaGetDirFromSD((uint8_t)idxStream) == PDMAUDIODIR_IN)) /* FIFOW for input streams only. */
+ { /* likely */ }
+ else
+ {
+#ifndef IN_RING0
+ LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", idxStream));
+ return VINF_SUCCESS;
+#else
+ return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */
+#endif
+ }
+
+ uint16_t u16FIFOW = 0;
+ switch (u32Value)
+ {
+ case HDA_SDFIFOW_8B:
+ case HDA_SDFIFOW_16B:
+ case HDA_SDFIFOW_32B:
+ u16FIFOW = RT_LO_U16(u32Value); /* Only bits 2:0 are used; see ICH-6, 18.2.38. */
+ break;
+ default:
+ ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOW (0x%zx) to stream #%RU8, defaulting to 32 bytes\n",
+ u32Value, idxStream));
+ u16FIFOW = HDA_SDFIFOW_32B;
+ break;
+ }
+
+ pThis->aStreams[idxStream].u8FIFOW = hdaSDFIFOWToBytes(u16FIFOW);
+ LogFunc(("[SD%zu] Updating FIFOW to %RU8 bytes\n", idxStream, pThis->aStreams[idxStream].u8FIFOW));
+ return hdaRegWriteU16(pDevIns, pThis, iReg, u16FIFOW);
+}
+
+/**
+ * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39).
+ */
+static VBOXSTRICTRC hdaRegWriteSDFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg);
+
+ ASSERT_GUEST_LOGREL_MSG_RETURN(hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT, /* FIFOS for output streams only. */
+ ("Guest tried writing read-only FIFOS to input stream #%RU8, ignoring\n", uSD),
+ VINF_SUCCESS);
+
+ uint32_t u32FIFOS;
+ switch (u32Value)
+ {
+ case HDA_SDOFIFO_16B:
+ case HDA_SDOFIFO_32B:
+ case HDA_SDOFIFO_64B:
+ case HDA_SDOFIFO_128B:
+ case HDA_SDOFIFO_192B:
+ case HDA_SDOFIFO_256B:
+ u32FIFOS = u32Value;
+ break;
+
+ default:
+ ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried writing unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n",
+ u32Value, uSD));
+ u32FIFOS = HDA_SDOFIFO_192B;
+ break;
+ }
+
+ return hdaRegWriteU16(pDevIns, pThis, iReg, u32FIFOS);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Adds an audio output stream to the device setup using the given configuration.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStreamOut(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("Stream=%s\n", pCfg->szName));
+
+ int rc = VINF_SUCCESS;
+
+ bool fUseFront = true; /* Always use front out by default. */
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ bool fUseRear;
+ bool fUseCenter;
+ bool fUseLFE;
+
+ fUseRear = fUseCenter = fUseLFE = false;
+
+ /*
+ * Use commonly used setups for speaker configurations.
+ */
+
+ /** @todo Make the following configurable through mixer API and/or CFGM? */
+ switch (PDMAudioPropsChannels(&pCfg->Props))
+ {
+ case 3: /* 2.1: Front (Stereo) + LFE. */
+ {
+ fUseLFE = true;
+ break;
+ }
+
+ case 4: /* Quadrophonic: Front (Stereo) + Rear (Stereo). */
+ {
+ fUseRear = true;
+ break;
+ }
+
+ case 5: /* 4.1: Front (Stereo) + Rear (Stereo) + LFE. */
+ {
+ fUseRear = true;
+ fUseLFE = true;
+ break;
+ }
+
+ case 6: /* 5.1: Front (Stereo) + Rear (Stereo) + Center/LFE. */
+ {
+ fUseRear = true;
+ fUseCenter = true;
+ fUseLFE = true;
+ break;
+ }
+
+ default: /* Unknown; fall back to 2 front channels (stereo). */
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+# endif /* !VBOX_WITH_AUDIO_HDA_51_SURROUND */
+
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ LogRel2(("HDA: Warning: Unsupported channel count (%RU8), falling back to stereo channels (2)\n",
+ PDMAudioPropsChannels(&pCfg->Props) ));
+
+ /* Fall back to 2 channels (see below in fUseFront block). */
+ rc = VINF_SUCCESS;
+ }
+
+ do
+ {
+ if (RT_FAILURE(rc))
+ break;
+
+ if (fUseFront)
+ {
+ RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Front");
+
+ pCfg->enmPath = PDMAUDIOPATH_OUT_FRONT;
+ /// @todo PDMAudioPropsSetChannels(&pCfg->Props, 2); ?
+
+ rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_FRONT, pCfg);
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if ( RT_SUCCESS(rc)
+ && (fUseCenter || fUseLFE))
+ {
+ RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Center/LFE");
+
+ pCfg->enmPath = PDMAUDIOPATH_OUT_CENTER_LFE;
+ PDMAudioPropsSetChannels(&pCfg->Props, fUseCenter && fUseLFE ? 2 : 1);
+
+ rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fUseRear)
+ {
+ RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear");
+
+ pCfg->enmPath = PDMAUDIOPATH_OUT_REAR;
+ PDMAudioPropsSetChannels(&pCfg->Props, 2);
+
+ rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_REAR, pCfg);
+ }
+# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds an audio input stream to the device setup using the given configuration.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStreamIn(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("Stream=%s enmPath=%ld\n", pCfg->szName, pCfg->enmPath));
+
+ int rc;
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_IN_LINE:
+ rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_LINE_IN, pCfg);
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOPATH_IN_MIC:
+ rc = hdaR3CodecAddStream(&pThisCC->Codec, PDMAUDIOMIXERCTL_MIC_IN, pCfg);
+ break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds an audio stream to the device setup using the given configuration.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ int rc;
+ switch (pCfg->enmDir)
+ {
+ case PDMAUDIODIR_OUT:
+ rc = hdaR3AddStreamOut(pThisCC, pCfg);
+ break;
+
+ case PDMAUDIODIR_IN:
+ rc = hdaR3AddStreamIn(pThisCC, pCfg);
+ break;
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ AssertFailed();
+ break;
+ }
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Removes an audio stream from the device setup using the given configuration.
+ *
+ * Used by hdaRegWriteSDCTL().
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pCfg Stream configuration to use for removing a stream.
+ */
+static int hdaR3RemoveStream(PHDASTATER3 pThisCC, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ PDMAUDIOMIXERCTL enmMixerCtl = PDMAUDIOMIXERCTL_UNKNOWN;
+ switch (pCfg->enmDir)
+ {
+ case PDMAUDIODIR_IN:
+ {
+ LogFlowFunc(("Stream=%s enmPath=%d (src)\n", pCfg->szName, pCfg->enmPath));
+
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_UNKNOWN: break;
+ case PDMAUDIOPATH_IN_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOPATH_IN_MIC: enmMixerCtl = PDMAUDIOMIXERCTL_MIC_IN; break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ break;
+ }
+
+ case PDMAUDIODIR_OUT:
+ {
+ LogFlowFunc(("Stream=%s, enmPath=%d (dst)\n", pCfg->szName, pCfg->enmPath));
+
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_UNKNOWN: break;
+ case PDMAUDIOPATH_OUT_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOPATH_OUT_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break;
+ case PDMAUDIOPATH_OUT_REAR: enmMixerCtl = PDMAUDIOMIXERCTL_REAR; break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && enmMixerCtl != PDMAUDIOMIXERCTL_UNKNOWN)
+ {
+ rc = hdaR3CodecRemoveStream(&pThisCC->Codec, enmMixerCtl, false /*fImmediate*/);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+static VBOXSTRICTRC hdaRegWriteSDFMT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ PDMAUDIOPCMPROPS Props;
+ int rc2 = hdaR3SDFMTToPCMProps(RT_LO_U16(u32Value), &Props);
+ AssertRC(rc2);
+ LogFunc(("[SD%RU8] Set to %#x (%RU32Hz, %RU8bit, %RU8 channel(s))\n", HDA_SD_NUM_FROM_REG(pThis, FMT, iReg), u32Value,
+ PDMAudioPropsHz(&Props), PDMAudioPropsSampleBits(&Props), PDMAudioPropsChannels(&Props)));
+
+ /*
+ * Write the wanted stream format into the register in any case.
+ *
+ * This is important for e.g. MacOS guests, as those try to initialize streams which are not reported
+ * by the device emulation (wants 4 channels, only have 2 channels at the moment).
+ *
+ * When ignoring those (invalid) formats, this leads to MacOS thinking that the device is malfunctioning
+ * and therefore disabling the device completely.
+ */
+ return hdaRegWriteU16(pDevIns, pThis, iReg, u32Value);
+#else
+ RT_NOREF(pDevIns, pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+}
+
+/**
+ * Worker for writes to the BDPL and BDPU registers.
+ */
+DECLINLINE(VBOXSTRICTRC) hdaRegWriteSDBDPX(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD)
+{
+ RT_NOREF(uSD);
+ return hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+}
+
+static VBOXSTRICTRC hdaRegWriteSDBDPL(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg));
+}
+
+static VBOXSTRICTRC hdaRegWriteSDBDPU(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ return hdaRegWriteSDBDPX(pDevIns, pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg));
+}
+
+/** Skylake specific. */
+static VBOXSTRICTRC hdaRegReadSDnPIB(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint8_t const uSD = HDA_SD_NUM_FROM_SKYLAKE_REG(DPIB, iReg);
+ LogFlowFunc(("uSD=%u -> SDnLPIB\n", uSD));
+ return hdaRegReadLPIB(pDevIns, pThis, HDA_SD_TO_REG(LPIB, uSD), pu32Value);
+}
+
+/** Skylake specific. */
+static VBOXSTRICTRC hdaRegReadSDnEFIFOS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ /** @todo This is not implemented as I have found no specs yet. */
+ RT_NOREF(pDevIns, pThis, iReg);
+ LogFunc(("TODO - need register spec: uSD=%u\n", HDA_SD_NUM_FROM_SKYLAKE_REG(DPIB, iReg)));
+ *pu32Value = 256;
+ return VINF_SUCCESS;
+}
+
+
+static VBOXSTRICTRC hdaRegReadIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ /* regarding 3.4.3 we should mark IRS as busy in case CORB is active */
+ if ( HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP)
+ || (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA))
+ HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */
+
+ return hdaRegReadU32(pDevIns, pThis, iReg, pu32Value);
+}
+
+static VBOXSTRICTRC hdaRegWriteIRS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ /*
+ * If the guest set the ICB bit of IRS register, HDA should process the verb in IC register,
+ * write the response to IR register, and set the IRV (valid in case of success) bit of IRS register.
+ */
+ if ( (u32Value & HDA_IRS_ICB)
+ && !(HDA_REG(pThis, IRS) & HDA_IRS_ICB))
+ {
+#ifdef IN_RING3
+ uint32_t uCmd = HDA_REG(pThis, IC);
+
+ if (HDA_REG(pThis, CORBWP) != HDA_REG(pThis, CORBRP))
+ {
+ /*
+ * 3.4.3: Defines behavior of immediate Command status register.
+ */
+ LogRel(("HDA: Guest attempted process immediate verb (%x) with active CORB\n", uCmd));
+ return VINF_SUCCESS;
+ }
+
+ HDA_REG(pThis, IRS) = HDA_IRS_ICB; /* busy */
+
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ uint64_t uResp = 0;
+ int rc2 = hdaR3CodecLookup(&pThisCC->Codec, HDA_CODEC_CMD(uCmd, 0 /* LUN */), &uResp);
+ if (RT_FAILURE(rc2))
+ LogFunc(("Codec lookup failed with rc2=%Rrc\n", rc2));
+
+ HDA_REG(pThis, IR) = (uint32_t)uResp; /** @todo r=andy Do we need a 64-bit response? */
+ HDA_REG(pThis, IRS) = HDA_IRS_IRV; /* result is ready */
+ /** @todo r=michaln We just set the IRS value, why are we clearing unset bits? */
+ HDA_REG(pThis, IRS) &= ~HDA_IRS_ICB; /* busy is clear */
+
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+ }
+
+ /*
+ * Once the guest read the response, it should clear the IRV bit of the IRS register.
+ */
+ HDA_REG(pThis, IRS) &= ~(u32Value & HDA_IRS_IRV);
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteRIRBWP(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */
+ LogFunc(("CORB DMA (still) running, skipping\n"));
+ else
+ {
+ if (u32Value & HDA_RIRBWP_RST)
+ {
+ /* Do a RIRB reset. */
+ if (pThis->cbRirbBuf)
+ RT_ZERO(pThis->au64RirbBuf);
+
+ LogRel2(("HDA: RIRB reset\n"));
+
+ HDA_REG(pThis, RIRBWP) = 0;
+ }
+ /* The remaining bits are O, see 6.2.22. */
+ }
+ return VINF_SUCCESS;
+}
+
+static VBOXSTRICTRC hdaRegWriteRINTCNT(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+ if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */
+ {
+ LogFunc(("CORB DMA is (still) running, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ VBOXSTRICTRC rc = hdaRegWriteU16(pDevIns, pThis, iReg, u32Value);
+ AssertRC(VBOXSTRICTRC_VAL(rc));
+
+ /** @todo r=bird: Shouldn't we make sure the HDASTATE::u16RespIntCnt is below
+ * the new RINTCNT value? Or alterantively, make the DMA look take
+ * this into account instead... I'll do the later for now. */
+
+ LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF));
+ return rc;
+}
+
+static VBOXSTRICTRC hdaRegWriteBase(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns);
+
+ VBOXSTRICTRC rc = hdaRegWriteU32(pDevIns, pThis, iReg, u32Value);
+ AssertRCSuccess(VBOXSTRICTRC_VAL(rc));
+
+ uint32_t const iRegMem = g_aHdaRegMap[iReg].idxReg;
+ switch (iReg)
+ {
+ case HDA_REG_CORBLBASE:
+ pThis->u64CORBBase &= UINT64_C(0xFFFFFFFF00000000);
+ pThis->u64CORBBase |= pThis->au32Regs[iRegMem];
+ break;
+ case HDA_REG_CORBUBASE:
+ pThis->u64CORBBase &= UINT64_C(0x00000000FFFFFFFF);
+ pThis->u64CORBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32;
+ break;
+ case HDA_REG_RIRBLBASE:
+ pThis->u64RIRBBase &= UINT64_C(0xFFFFFFFF00000000);
+ pThis->u64RIRBBase |= pThis->au32Regs[iRegMem];
+ break;
+ case HDA_REG_RIRBUBASE:
+ pThis->u64RIRBBase &= UINT64_C(0x00000000FFFFFFFF);
+ pThis->u64RIRBBase |= (uint64_t)pThis->au32Regs[iRegMem] << 32;
+ break;
+ case HDA_REG_DPLBASE:
+ pThis->u64DPBase = pThis->au32Regs[iRegMem] & DPBASE_ADDR_MASK;
+ Assert(pThis->u64DPBase % 128 == 0); /* Must be 128-byte aligned. */
+
+ /* Also make sure to handle the DMA position enable bit. */
+ pThis->fDMAPosition = pThis->au32Regs[iRegMem] & RT_BIT_32(0);
+
+#ifndef IN_RING0
+ LogRel(("HDA: DP base (lower) set: %#RGp\n", pThis->u64DPBase));
+ LogRel(("HDA: DMA position buffer is %s\n", pThis->fDMAPosition ? "enabled" : "disabled"));
+#else
+ return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */
+#endif
+ break;
+ case HDA_REG_DPUBASE:
+ pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]);
+#ifndef IN_RING0
+ LogRel(("HDA: DP base (upper) set: %#RGp\n", pThis->u64DPBase));
+#else
+ return VINF_IOM_R3_MMIO_WRITE; /* (Go to ring-3 for release logging.) */
+#endif
+ break;
+ default:
+ AssertMsgFailed(("Invalid index\n"));
+ break;
+ }
+
+ LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n",
+ pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase));
+ return rc;
+}
+
+static VBOXSTRICTRC hdaRegWriteRIRBSTS(PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ uint8_t v = HDA_REG(pThis, RIRBSTS);
+ HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value);
+
+ HDA_PROCESS_INTERRUPT(pDevIns, pThis);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Retrieves a corresponding sink for a given mixer control.
+ *
+ * @return Pointer to the sink, NULL if no sink is found.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param enmMixerCtl Mixer control to get the corresponding sink for.
+ */
+static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATER3 pThisCC, PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ PHDAMIXERSINK pSink;
+
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ /* Fall through is intentional. */
+ case PDMAUDIOMIXERCTL_FRONT:
+ pSink = &pThisCC->SinkFront;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOMIXERCTL_CENTER_LFE:
+ pSink = &pThisCC->SinkCenterLFE;
+ break;
+ case PDMAUDIOMIXERCTL_REAR:
+ pSink = &pThisCC->SinkRear;
+ break;
+# endif
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pSink = &pThisCC->SinkLineIn;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pSink = &pThisCC->SinkMicIn;
+ break;
+# endif
+ default:
+ AssertMsgFailed(("Unhandled mixer control\n"));
+ pSink = NULL;
+ break;
+ }
+
+ return pSink;
+}
+
+/**
+ * Adds a specific HDA driver to the driver chain.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The HDA device instance.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pDrv HDA driver to add.
+ */
+static int hdaR3MixerAddDrv(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ PHDASTREAM pStream = pThisCC->SinkLineIn.pStreamShared;
+ if ( pStream
+ && AudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pStream = pThisCC->SinkMicIn.pStreamShared;
+ if ( pStream
+ && AudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+# endif
+
+ pStream = pThisCC->SinkFront.pStreamShared;
+ if ( pStream
+ && AudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkFront.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ pStream = pThisCC->SinkCenterLFE.pStreamShared;
+ if ( pStream
+ && AudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ pStream = pThisCC->SinkRear.pStreamShared;
+ if ( pStream
+ && AudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pThisCC->SinkRear.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+# endif
+
+ return rc;
+}
+
+/**
+ * Removes a specific HDA driver from the driver chain and destroys its
+ * associated streams.
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pDrv HDA driver to remove.
+ */
+static void hdaR3MixerRemoveDrv(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv)
+{
+ AssertPtrReturnVoid(pDrv);
+
+ if (pDrv->LineIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->SinkLineIn.pMixSink, pDrv->LineIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->LineIn.pMixStrm = NULL;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ if (pDrv->MicIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->MicIn.pMixStrm = NULL;
+ }
+# endif
+
+ if (pDrv->Front.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->SinkFront.pMixSink, pDrv->Front.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Front.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->Front.pMixStrm = NULL;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (pDrv->CenterLFE.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->CenterLFE.pMixStrm = NULL;
+ }
+
+ if (pDrv->Rear.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->SinkRear.pMixSink, pDrv->Rear.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Rear.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->Rear.pMixStrm = NULL;
+ }
+# endif
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * @returns VBox status code (ignored by caller).
+ * @param pDevIns The HDA device instance.
+ * @param pMixSink Audio mixer sink to add audio streams to.
+ * @param pCfg Audio stream configuration to use for the audio
+ * streams to add.
+ * @param pDrv Driver stream to add.
+ */
+static int hdaR3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFunc(("szSink=%s, szStream=%s, cChannels=%RU8\n", pMixSink->pszName, pCfg->szName, PDMAudioPropsChannels(&pCfg->Props)));
+
+ /*
+ * Get the matching stream driver.
+ */
+ PHDADRIVERSTREAM pDrvStream = NULL;
+ if (pCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("enmPath=%d (src)\n", pCfg->enmPath));
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_IN_LINE:
+ pDrvStream = &pDrv->LineIn;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOPATH_IN_MIC:
+ pDrvStream = &pDrv->MicIn;
+ break;
+# endif
+ default:
+ LogFunc(("returns VERR_NOT_SUPPORTED - enmPath=%d\n", pCfg->enmPath));
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+ else if (pCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmDst=%d %s (dst)\n", pCfg->enmPath, PDMAudioPathGetName(pCfg->enmPath)));
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_OUT_FRONT:
+ pDrvStream = &pDrv->Front;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOPATH_OUT_CENTER_LFE:
+ pDrvStream = &pDrv->CenterLFE;
+ break;
+ case PDMAUDIOPATH_OUT_REAR:
+ pDrvStream = &pDrv->Rear;
+ break;
+# endif
+ default:
+ LogFunc(("returns VERR_NOT_SUPPORTED - enmPath=%d %s\n", pCfg->enmPath, PDMAudioPathGetName(pCfg->enmPath)));
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName));
+
+ AssertPtr(pDrvStream);
+ AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
+
+ PAUDMIXSTREAM pMixStrm = NULL;
+ int rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_FAILURE(rc))
+ AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/);
+ }
+
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The HDA device instance.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pMixSink Audio mixer sink to add stream to.
+ * @param pCfg Audio stream configuration to use for the audio streams
+ * to add.
+ */
+static int hdaR3MixerAddDrvStreams(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFunc(("Sink=%s, Stream=%s\n", pMixSink->pszName, pCfg->szName));
+
+ int rc;
+ if (AudioHlpStreamCfgIsValid(pCfg))
+ {
+ rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint);
+ if (RT_SUCCESS(rc))
+ {
+ PHDADRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node)
+ {
+ /* We ignore failures here because one non-working driver shouldn't
+ be allowed to spoil it for everyone else. */
+ int rc2 = hdaR3MixerAddDrvStream(pDevIns, pMixSink, pCfg, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("Attaching stream failed with %Rrc (ignored)\n", rc2));
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+
+
+/**
+ * Adds a new audio stream to a specific mixer control.
+ *
+ * Depending on the mixer control the stream then gets assigned to one of the
+ * internal mixer sinks, which in turn then handle the mixing of all connected
+ * streams to that sink.
+ *
+ * @return VBox status code.
+ * @param pCodec The codec instance data.
+ * @param enmMixerCtl Mixer control to assign new stream to.
+ * @param pCfg Stream configuration for the new stream.
+ */
+DECLHIDDEN(int) hdaR3MixerAddStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PCPDMAUDIOSTREAMCFG pCfg)
+{
+ PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc;
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl);
+ if (pSink)
+ {
+ rc = hdaR3MixerAddDrvStreams(pThisCC->pDevIns, pThisCC, pSink->pMixSink, pCfg);
+
+ AssertPtr(pSink->pMixSink);
+ LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, PDMAudioMixerCtlGetName(enmMixerCtl)));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Removes a specified mixer control from the HDA's mixer.
+ *
+ * @return VBox status code.
+ * @param pCodec The codec instance data.
+ * @param enmMixerCtl Mixer control to remove.
+ * @param fImmediate Whether the backend should be allowed to
+ * finished draining (@c false) or if it must be
+ * destroyed immediately (@c true).
+ */
+DECLHIDDEN(int) hdaR3MixerRemoveStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate)
+{
+ PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec);
+ int rc;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl);
+ if (pSink)
+ {
+ PHDADRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node)
+ {
+ PAUDMIXSTREAM pMixStream = NULL;
+ switch (enmMixerCtl)
+ {
+ /*
+ * Input.
+ */
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pMixStream = pDrv->LineIn.pMixStrm;
+ pDrv->LineIn.pMixStrm = NULL;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pMixStream = pDrv->MicIn.pMixStrm;
+ pDrv->MicIn.pMixStrm = NULL;
+ break;
+# endif
+ /*
+ * Output.
+ */
+ case PDMAUDIOMIXERCTL_FRONT:
+ pMixStream = pDrv->Front.pMixStrm;
+ pDrv->Front.pMixStrm = NULL;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOMIXERCTL_CENTER_LFE:
+ pMixStream = pDrv->CenterLFE.pMixStrm;
+ pDrv->CenterLFE.pMixStrm = NULL;
+ break;
+ case PDMAUDIOMIXERCTL_REAR:
+ pMixStream = pDrv->Rear.pMixStrm;
+ pDrv->Rear.pMixStrm = NULL;
+ break;
+# endif
+ default:
+ AssertMsgFailed(("Mixer control %d not implemented\n", enmMixerCtl));
+ break;
+ }
+
+ if (pMixStream)
+ {
+ AudioMixerSinkRemoveStream(pSink->pMixSink, pMixStream);
+ AudioMixerStreamDestroy(pMixStream, pThisCC->pDevIns, fImmediate);
+
+ pMixStream = NULL;
+ }
+ }
+
+ AudioMixerSinkRemoveAllStreams(pSink->pMixSink);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFunc(("Mixer control=%s, rc=%Rrc\n", PDMAudioMixerCtlGetName(enmMixerCtl), rc));
+ return rc;
+}
+
+/**
+ * Controls an input / output converter widget, that is, which converter is
+ * connected to which stream (and channel).
+ *
+ * @return VBox status code.
+ * @param pCodec The codec instance data.
+ * @param enmMixerCtl Mixer control to set SD stream number and channel for.
+ * @param uSD SD stream number (number + 1) to set. Set to 0 for unassign.
+ * @param uChannel Channel to set. Only valid if a valid SD stream number is specified.
+ *
+ * @note Is also called directly by the DevHDA code.
+ */
+DECLHIDDEN(int) hdaR3MixerControl(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel)
+{
+ PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", PDMAudioMixerCtlGetName(enmMixerCtl), uSD, uChannel));
+
+ if (uSD == 0) /* Stream number 0 is reserved. */
+ {
+ Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, PDMAudioMixerCtlGetName(enmMixerCtl)));
+ return VINF_SUCCESS;
+ }
+ /* uChannel is optional. */
+
+ /* SDn0 starts as 1. */
+ Assert(uSD);
+ uSD--;
+
+# ifndef VBOX_WITH_AUDIO_HDA_MIC_IN
+ /* Only SDI0 (Line-In) is supported. */
+ if ( hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN
+ && uSD >= 1)
+ {
+ LogRel2(("HDA: Dedicated Mic-In support not imlpemented / built-in (stream #%RU8), using Line-In (stream #0) instead\n", uSD));
+ uSD = 0;
+ }
+# endif
+
+ int rc = VINF_SUCCESS;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl);
+ if (pSink)
+ {
+ AssertPtr(pSink->pMixSink);
+
+ /* If this an output stream, determine the correct SD#. */
+ if ( uSD < HDA_MAX_SDI
+ && AudioMixerSinkGetDir(pSink->pMixSink) == PDMAUDIODIR_OUT)
+ uSD += HDA_MAX_SDI;
+
+ /* Make 100% sure we got a good stream number before continuing. */
+ AssertLogRelReturn(uSD < RT_ELEMENTS(pThisCC->aStreams), VERR_NOT_IMPLEMENTED);
+
+ /* Detach the existing stream from the sink. */
+ PHDASTREAM const pOldStreamShared = pSink->pStreamShared;
+ PHDASTREAMR3 const pOldStreamR3 = pSink->pStreamR3;
+ if ( pOldStreamShared
+ && pOldStreamR3
+ && ( pOldStreamShared->u8SD != uSD
+ || pOldStreamShared->u8Channel != uChannel)
+ )
+ {
+ LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n",
+ pSink->pMixSink->pszName, pOldStreamShared->u8SD, pOldStreamShared->u8Channel));
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+
+ /* Only disable the stream if the stream descriptor # has changed. */
+ if (pOldStreamShared->u8SD != uSD)
+ hdaR3StreamEnable(pThis, pOldStreamShared, pOldStreamR3, false /*fEnable*/);
+
+ if (pOldStreamR3->State.pAioRegSink)
+ {
+ AudioMixerSinkRemoveUpdateJob(pOldStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pOldStreamR3);
+ pOldStreamR3->State.pAioRegSink = NULL;
+ }
+
+ pOldStreamR3->pMixSink = NULL;
+
+
+ pSink->pStreamShared = NULL;
+ pSink->pStreamR3 = NULL;
+ }
+
+ /* Attach the new stream to the sink.
+ * Enabling the stream will be done by the guest via a separate SDnCTL call then. */
+ if (pSink->pStreamShared == NULL)
+ {
+ LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n",
+ pSink->pMixSink->pszName, uSD, uChannel, PDMAudioMixerCtlGetName(enmMixerCtl)));
+
+ PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[uSD];
+ PHDASTREAM pStreamShared = &pThis->aStreams[uSD];
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+
+ pSink->pStreamR3 = pStreamR3;
+ pSink->pStreamShared = pStreamShared;
+
+ pStreamShared->u8Channel = uChannel;
+ pStreamR3->pMixSink = pSink;
+
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ LogRel(("HDA: Converter control for stream #%RU8 (channel %RU8) / mixer control '%s' failed with %Rrc, skipping\n",
+ uSD, uChannel, PDMAudioMixerCtlGetName(enmMixerCtl), rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sets the volume of a specified mixer control.
+ *
+ * @return IPRT status code.
+ * @param pCodec The codec instance data.
+ * @param enmMixerCtl Mixer control to set volume for.
+ * @param pVol Pointer to volume data to set.
+ */
+DECLHIDDEN(int) hdaR3MixerSetVolume(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol)
+{
+ PHDASTATER3 pThisCC = RT_FROM_MEMBER(pCodec, HDASTATER3, Codec);
+ int rc;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThisCC, enmMixerCtl);
+ if ( pSink
+ && pSink->pMixSink)
+ {
+ LogRel2(("HDA: Setting volume for mixer sink '%s' to fMuted=%RTbool auChannels=%.*Rhxs\n",
+ pSink->pMixSink->pszName, pVol->fMuted, sizeof(pVol->auChannels), pVol->auChannels));
+
+ /* Set the volume.
+ * We assume that the codec already converted it to the correct range. */
+ rc = AudioMixerSinkSetVolume(pSink->pMixSink, pVol);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV, Main routine for the stream's timer.}
+ */
+static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ uintptr_t idxStream = (uintptr_t)pvUser;
+ AssertReturnVoid(idxStream < RT_ELEMENTS(pThis->aStreams));
+ PHDASTREAM pStreamShared = &pThis->aStreams[idxStream];
+ PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[idxStream];
+ Assert(hTimer == pStreamShared->hTimer);
+
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ Assert(PDMDevHlpTimerIsLockOwner(pDevIns, hTimer));
+
+ RT_NOREF(hTimer);
+
+ hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
+}
+
+/**
+ * Soft reset of the device triggered via GCTL.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-3 HDA device state.
+ */
+static void hdaR3GCTLReset(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC)
+{
+ LogFlowFuncEnter();
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+
+ /*
+ * Make sure all streams have stopped as these have both timers and
+ * asynchronous worker threads that would race us if we delay this work.
+ */
+ for (size_t idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
+ {
+ PHDASTREAM const pStreamShared = &pThis->aStreams[idxStream];
+ PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[idxStream];
+ PAUDMIXSINK const pMixSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pMixSink)
+ AudioMixerSinkLock(pMixSink);
+
+ /* We're doing this unconditionally, hope that's not problematic in any way... */
+ int rc = hdaR3StreamEnable(pThis, pStreamShared, &pThisCC->aStreams[idxStream], false /* fEnable */);
+ AssertLogRelMsg(RT_SUCCESS(rc) && !pStreamShared->State.fRunning,
+ ("Disabling stream #%u failed: %Rrc, fRunning=%d\n", idxStream, rc, pStreamShared->State.fRunning));
+ pStreamShared->State.fRunning = false;
+
+ hdaR3StreamReset(pThis, pThisCC, pStreamShared, &pThisCC->aStreams[idxStream], (uint8_t)idxStream);
+
+ if (pMixSink) /* (FYI. pMixSink might not be what pStreamR3->pMixSink->pMixSink points at any longer) */
+ AudioMixerSinkUnlock(pMixSink);
+ }
+
+ /*
+ * Reset registers.
+ */
+ HDA_REG(pThis, GCAP) = HDA_MAKE_GCAP(HDA_MAX_SDO, HDA_MAX_SDI, 0, 0, 1); /* see 6.2.1 */
+ HDA_REG(pThis, VMIN) = 0x00; /* see 6.2.2 */
+ HDA_REG(pThis, VMAJ) = 0x01; /* see 6.2.3 */
+ HDA_REG(pThis, OUTPAY) = 0x003C; /* see 6.2.4 */
+ HDA_REG(pThis, INPAY) = 0x001D; /* see 6.2.5 */
+ HDA_REG(pThis, CORBSIZE) = 0x42; /* Up to 256 CORB entries see 6.2.1 */
+ HDA_REG(pThis, RIRBSIZE) = 0x42; /* Up to 256 RIRB entries see 6.2.1 */
+ HDA_REG(pThis, CORBRP) = 0x0;
+ HDA_REG(pThis, CORBWP) = 0x0;
+ HDA_REG(pThis, RIRBWP) = 0x0;
+ /* Some guests (like Haiku) don't set RINTCNT explicitly but expect an interrupt after each
+ * RIRB response -- so initialize RINTCNT to 1 by default. */
+ HDA_REG(pThis, RINTCNT) = 0x1;
+ /* For newer devices, there is a capability list offset word at 0x14, linux read it, does
+ no checking and simply reads the dword it specifies. The list terminates when the lower
+ 16 bits are zero. See snd_hdac_bus_parse_capabilities. Table 5-2 in intel 341081-002
+ specifies this to be 0xc00 and chaining with 0x800, 0x500 and 0x1f00. We just terminate
+ it at 0xc00 for now. */
+ HDA_REG(pThis, LLCH) = 0xc00;
+ HDA_REG(pThis, MLCH) = 0x0;
+ HDA_REG(pThis, MLCD) = 0x0;
+
+ /*
+ * Stop any audio currently playing and/or recording.
+ */
+ pThisCC->SinkFront.pStreamShared = NULL;
+ pThisCC->SinkFront.pStreamR3 = NULL;
+ if (pThisCC->SinkFront.pMixSink)
+ AudioMixerSinkReset(pThisCC->SinkFront.pMixSink);
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pThisCC->SinkMicIn.pStreamShared = NULL;
+ pThisCC->SinkMicIn.pStreamR3 = NULL;
+ if (pThisCC->SinkMicIn.pMixSink)
+ AudioMixerSinkReset(pThisCC->SinkMicIn.pMixSink);
+# endif
+ pThisCC->SinkLineIn.pStreamShared = NULL;
+ pThisCC->SinkLineIn.pStreamR3 = NULL;
+ if (pThisCC->SinkLineIn.pMixSink)
+ AudioMixerSinkReset(pThisCC->SinkLineIn.pMixSink);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ pThisCC->SinkCenterLFE = NULL;
+ if (pThisCC->SinkCenterLFE.pMixSink)
+ AudioMixerSinkReset(pThisCC->SinkCenterLFE.pMixSink);
+ pThisCC->SinkRear.pStreamShared = NULL;
+ pThisCC->SinkRear.pStreamR3 = NULL;
+ if (pThisCC->SinkRear.pMixSink)
+ AudioMixerSinkReset(pThisCC->SinkRear.pMixSink);
+# endif
+
+ /*
+ * Reset the codec.
+ */
+ hdaCodecReset(&pThisCC->Codec);
+
+ /*
+ * Set some sensible defaults for which HDA sinks
+ * are connected to which stream number.
+ *
+ * We use SD0 for input and SD4 for output by default.
+ * These stream numbers can be changed by the guest dynamically lateron.
+ */
+ ASMCompilerBarrier(); /* paranoia */
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */);
+# endif
+ hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */);
+
+ hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */);
+ hdaR3MixerControl(&pThisCC->Codec, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */);
+# endif
+ ASMCompilerBarrier(); /* paranoia */
+
+ /* Reset CORB. */
+ pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE;
+ RT_ZERO(pThis->au32CorbBuf);
+
+ /* Reset RIRB. */
+ pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE;
+ RT_ZERO(pThis->au64RirbBuf);
+
+ /* Clear our internal response interrupt counter. */
+ pThis->u16RespIntCnt = 0;
+
+ /* Clear stream tags <-> objects mapping table. */
+ RT_ZERO(pThisCC->aTags);
+
+ /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */
+ HDA_REG(pThis, STATESTS) = 0x1;
+
+ /* Reset the wall clock. */
+ pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer);
+
+ LogFlowFuncLeave();
+ LogRel(("HDA: Reset\n"));
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * Checks if a dword read starting with @a idxRegDsc is safe.
+ *
+ * We can guarentee it only standard reader callbacks are used.
+ * @returns true if it will always succeed, false if it may return back to
+ * ring-3 or we're just not sure.
+ * @param idxRegDsc The first register descriptor in the DWORD being read.
+ */
+DECLINLINE(bool) hdaIsMultiReadSafeInRZ(unsigned idxRegDsc)
+{
+ int32_t cbLeft = 4; /* signed on purpose */
+ do
+ {
+ if ( g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU24
+ || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU16
+ || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadU8
+ || g_aHdaRegMap[idxRegDsc].pfnRead == hdaRegReadUnimpl)
+ { /* okay */ }
+ else
+ {
+ Log4(("hdaIsMultiReadSafeInRZ: idxRegDsc=%u %s\n", idxRegDsc, g_aHdaRegMap[idxRegDsc].pszName));
+ return false;
+ }
+
+ idxRegDsc++;
+ if (idxRegDsc < RT_ELEMENTS(g_aHdaRegMap))
+ cbLeft -= g_aHdaRegMap[idxRegDsc].off - g_aHdaRegMap[idxRegDsc - 1].off;
+ else
+ break;
+ } while (cbLeft > 0);
+ return true;
+}
+
+
+#endif /* !IN_RING3 */
+
+
+/* MMIO callbacks */
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD, Looks up and calls the appropriate handler.}
+ *
+ * @note During implementation, we discovered so-called "forgotten" or "hole"
+ * registers whose description is not listed in the RPM, datasheet, or
+ * spec.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) hdaMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ VBOXSTRICTRC rc;
+ RT_NOREF_PV(pvUser);
+ Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC);
+
+ /*
+ * Look up and log.
+ */
+ int idxRegDsc = hdaRegLookup(off); /* Register descriptor index. */
+#ifdef LOG_ENABLED
+ unsigned const cbLog = cb;
+ uint32_t offRegLog = (uint32_t)off;
+# ifdef HDA_DEBUG_GUEST_RIP
+ if (LogIs6Enabled())
+ {
+ PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns);
+ Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu)));
+ }
+# endif
+#endif
+
+ Log3Func(("off=%#x cb=%#x\n", offRegLog, cb));
+ Assert(cb == 4); Assert((off & 3) == 0);
+
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_MMIO_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL)
+ LogFunc(("Access to registers except GCTL is blocked while resetting\n"));
+
+ if (idxRegDsc >= 0)
+ {
+ /* ASSUMES gapless DWORD at end of map. */
+ if (g_aHdaRegMap[idxRegDsc].cb == 4)
+ {
+ /*
+ * Straight forward DWORD access.
+ */
+ rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, (uint32_t *)pv);
+ Log3Func((" Read %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].pszName, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]);
+ }
+#ifndef IN_RING3
+ else if (!hdaIsMultiReadSafeInRZ(idxRegDsc))
+
+ {
+ STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]);
+ rc = VINF_IOM_R3_MMIO_READ;
+ }
+#endif
+ else
+ {
+ /*
+ * Multi register read (unless there are trailing gaps).
+ * ASSUMES that only DWORD reads have sideeffects.
+ */
+ STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiReads));
+ Log4(("hdaMmioRead: multi read: %#x LB %#x %s\n", off, cb, g_aHdaRegMap[idxRegDsc].pszName));
+ uint32_t u32Value = 0;
+ unsigned cbLeft = 4;
+ do
+ {
+ uint32_t const cbReg = g_aHdaRegMap[idxRegDsc].cb;
+ uint32_t u32Tmp = 0;
+
+ rc = g_aHdaRegMap[idxRegDsc].pfnRead(pDevIns, pThis, idxRegDsc, &u32Tmp);
+ Log4Func((" Read %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].pszName, cbReg, u32Tmp, VBOXSTRICTRC_VAL(rc)));
+ STAM_COUNTER_INC(&pThis->aStatRegReads[idxRegDsc]);
+#ifdef IN_RING3
+ if (rc != VINF_SUCCESS)
+ break;
+#else
+ AssertMsgBreak(rc == VINF_SUCCESS, ("rc=%Rrc - impossible, we sanitized the readers!\n", VBOXSTRICTRC_VAL(rc)));
+#endif
+ u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8);
+
+ cbLeft -= cbReg;
+ off += cbReg;
+ idxRegDsc++;
+ } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].off == off);
+
+ if (rc == VINF_SUCCESS)
+ *(uint32_t *)pv = u32Value;
+ else
+ Assert(!IOM_SUCCESS(rc));
+ }
+ }
+ else
+ {
+ LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", (uint32_t)off, cb));
+ Log3Func((" Hole at %x is accessed for read\n", offRegLog));
+ STAM_COUNTER_INC(&pThis->StatRegUnknownReads);
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+
+ /*
+ * Log the outcome.
+ */
+#ifdef LOG_ENABLED
+ if (cbLog == 4)
+ Log3Func((" Returning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ else if (cbLog == 2)
+ Log3Func((" Returning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ else if (cbLog == 1)
+ Log3Func((" Returning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, VBOXSTRICTRC_VAL(rc)));
+#endif
+ }
+ else
+ {
+ if (idxRegDsc >= 0)
+ STAM_COUNTER_INC(&pThis->aStatRegReadsToR3[idxRegDsc]);
+ }
+ return rc;
+}
+
+
+DECLINLINE(VBOXSTRICTRC) hdaWriteReg(PPDMDEVINS pDevIns, PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog)
+{
+ if ( (HDA_REG(pThis, GCTL) & HDA_GCTL_CRST)
+ || idxRegDsc == HDA_REG_GCTL)
+ { /* likely */ }
+ else
+ {
+ Log(("hdaWriteReg: Warning: Access to %s is blocked while controller is in reset mode\n", g_aHdaRegMap[idxRegDsc].pszName));
+#if defined(IN_RING3) || defined(LOG_ENABLED)
+ LogRel2(("HDA: Warning: Access to register %s is blocked while controller is in reset mode\n",
+ g_aHdaRegMap[idxRegDsc].pszName));
+#endif
+ STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByReset);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Handle RD (register description) flags.
+ */
+
+ /* For SDI / SDO: Check if writes to those registers are allowed while SDCTL's RUN bit is set. */
+ if (idxRegDsc >= HDA_NUM_GENERAL_REGS)
+ {
+ /*
+ * Some OSes (like Win 10 AU) violate the spec by writing stuff to registers which are not supposed to be be touched
+ * while SDCTL's RUN bit is set. So just ignore those values.
+ */
+ const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc));
+ if ( !(uSDCTL & HDA_SDCTL_RUN)
+ || (g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_F_SD_WRITE_RUN))
+ { /* likely */ }
+ else
+ {
+ Log(("hdaWriteReg: Warning: Access to %s is blocked! %R[sdctl]\n", g_aHdaRegMap[idxRegDsc].pszName, uSDCTL));
+#if defined(IN_RING3) || defined(LOG_ENABLED)
+ LogRel2(("HDA: Warning: Access to register %s is blocked while the stream's RUN bit is set\n",
+ g_aHdaRegMap[idxRegDsc].pszName));
+#endif
+ STAM_COUNTER_INC(&pThis->StatRegWritesBlockedByRun);
+ return VINF_SUCCESS;
+ }
+ }
+
+#ifdef LOG_ENABLED
+ uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg;
+ uint32_t const u32OldValue = pThis->au32Regs[idxRegMem];
+#endif
+ VBOXSTRICTRC rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pDevIns, pThis, idxRegDsc, u32Value);
+ Log3Func(("Written value %#x to %s[%d byte]; %x => %x%s, rc=%d\n", u32Value, g_aHdaRegMap[idxRegDsc].pszName,
+ g_aHdaRegMap[idxRegDsc].cb, u32OldValue, pThis->au32Regs[idxRegMem], pszLog, VBOXSTRICTRC_VAL(rc)));
+#ifndef IN_RING3
+ if (rc == VINF_IOM_R3_MMIO_WRITE)
+ STAM_COUNTER_INC(&pThis->aStatRegWritesToR3[idxRegDsc]);
+ else
+#endif
+ STAM_COUNTER_INC(&pThis->aStatRegWrites[idxRegDsc]);
+
+ RT_NOREF(pszLog);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE,
+ * Looks up and calls the appropriate handler.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) hdaMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ RT_NOREF_PV(pvUser);
+ Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC);
+
+ /*
+ * Look up and log the access.
+ */
+ int idxRegDsc = hdaRegLookup(off);
+#if defined(IN_RING3) || defined(LOG_ENABLED)
+ uint32_t idxRegMem = idxRegDsc != -1 ? g_aHdaRegMap[idxRegDsc].idxReg : UINT32_MAX;
+#endif
+ uint64_t u64Value;
+ if (cb == 4) u64Value = *(uint32_t const *)pv;
+ else if (cb == 2) u64Value = *(uint16_t const *)pv;
+ else if (cb == 1) u64Value = *(uint8_t const *)pv;
+ else if (cb == 8) u64Value = *(uint64_t const *)pv;
+ else
+ ASSERT_GUEST_MSG_FAILED_RETURN(("cb=%u %.*Rhxs\n", cb, cb, pv),
+ PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "odd write size: off=%RGp cb=%u\n", off, cb));
+
+ /*
+ * The behavior of accesses that aren't aligned on natural boundraries is
+ * undefined. Just reject them outright.
+ */
+ ASSERT_GUEST_MSG_RETURN((off & (cb - 1)) == 0, ("off=%RGp cb=%u %.*Rhxs\n", off, cb, cb, pv),
+ PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: off=%RGp cb=%u\n", off, cb));
+
+#ifdef LOG_ENABLED
+ uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX;
+# ifdef HDA_DEBUG_GUEST_RIP
+ if (LogIs6Enabled())
+ {
+ PVMCPU pVCpu = (PVMCPU)PDMDevHlpGetVMCPU(pDevIns);
+ Log6Func(("cs:rip=%04x:%016RX64 rflags=%08RX32\n", CPUMGetGuestCS(pVCpu), CPUMGetGuestRIP(pVCpu), CPUMGetGuestEFlags(pVCpu)));
+ }
+# endif
+#endif
+
+ /*
+ * Try for a direct hit first.
+ */
+ VBOXSTRICTRC rc;
+ if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].cb == cb)
+ {
+ DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ Log3Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].pszName));
+ rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, "");
+ Log3Func((" %#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX));
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ }
+ /*
+ * Sub-register access. Supply missing bits as needed.
+ */
+ else if ( idxRegDsc >= 0
+ && cb < g_aHdaRegMap[idxRegDsc].cb)
+ {
+ DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ u64Value |= pThis->au32Regs[g_aHdaRegMap[idxRegDsc].idxReg]
+ & g_afMasks[g_aHdaRegMap[idxRegDsc].cb]
+ & ~g_afMasks[cb];
+ Log4Func(("@%#05x u%u=%#0*RX64 cb=%#x cbReg=%x %s\n"
+ "hdaMmioWrite: Supplying missing bits (%#x): %#llx -> %#llx ...\n",
+ (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, cb, g_aHdaRegMap[idxRegDsc].cb, g_aHdaRegMap[idxRegDsc].pszName,
+ g_afMasks[g_aHdaRegMap[idxRegDsc].cb] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value));
+ rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value, "");
+ Log4Func((" %#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX));
+ STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegSubWrite));
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ }
+ /*
+ * Partial or multiple register access, loop thru the requested memory.
+ */
+ else
+ {
+#ifdef IN_RING3
+ DEVHDA_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ if (idxRegDsc == -1)
+ Log4Func(("@%#05x u32=%#010x cb=%d\n", (uint32_t)off, *(uint32_t const *)pv, cb));
+ else if (g_aHdaRegMap[idxRegDsc].cb == cb)
+ Log4Func(("@%#05x u%u=%#0*RX64 %s\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value, g_aHdaRegMap[idxRegDsc].pszName));
+ else
+ Log4Func(("@%#05x u%u=%#0*RX64 %s - mismatch cbReg=%u\n", (uint32_t)off, cb * 8, 2 + cb * 2, u64Value,
+ g_aHdaRegMap[idxRegDsc].pszName, g_aHdaRegMap[idxRegDsc].cb));
+
+ /*
+ * If it's an access beyond the start of the register, shift the input
+ * value and fill in missing bits. Natural alignment rules means we
+ * will only see 1 or 2 byte accesses of this kind, so no risk of
+ * shifting out input values.
+ */
+ if (idxRegDsc < 0)
+ {
+ uint32_t cbBefore;
+ idxRegDsc = hdaR3RegLookupWithin(off, &cbBefore);
+ if (idxRegDsc != -1)
+ {
+ Assert(cbBefore > 0 && cbBefore < 4 /* no register is wider than 4 bytes, we check in the constructor */);
+ off -= cbBefore;
+ idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg;
+ u64Value <<= cbBefore * 8;
+ u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore];
+ Log4Func((" Within register, supplied %u leading bits: %#llx -> %#llx ...\n",
+ cbBefore * 8, ~(uint64_t)g_afMasks[cbBefore] & u64Value, u64Value));
+ STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites));
+ }
+ else
+ STAM_COUNTER_INC(&pThis->StatRegUnknownWrites);
+ }
+ else
+ {
+ Log4(("hdaMmioWrite: multi write: %s\n", g_aHdaRegMap[idxRegDsc].pszName));
+ STAM_COUNTER_INC(&pThis->CTX_SUFF_Z(StatRegMultiWrites));
+ }
+
+ /* Loop thru the write area, it may cover multiple registers. */
+ rc = VINF_SUCCESS;
+ for (;;)
+ {
+ uint32_t cbReg;
+ if (idxRegDsc >= 0)
+ {
+ idxRegMem = g_aHdaRegMap[idxRegDsc].idxReg;
+ cbReg = g_aHdaRegMap[idxRegDsc].cb;
+ if (cb < cbReg)
+ {
+ u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb];
+ Log4Func((" Supplying missing bits (%#x): %#llx -> %#llx ...\n",
+ g_afMasks[cbReg] & ~g_afMasks[cb], u64Value & g_afMasks[cb], u64Value));
+ }
+# ifdef LOG_ENABLED
+ uint32_t uLogOldVal = pThis->au32Regs[idxRegMem];
+# endif
+ rc = hdaWriteReg(pDevIns, pThis, idxRegDsc, u64Value & g_afMasks[cbReg], "*");
+ Log4Func((" %#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem]));
+ }
+ else
+ {
+ LogRel(("HDA: Invalid write access @0x%x\n", (uint32_t)off));
+ cbReg = 1;
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ if (cbReg >= cb)
+ break;
+
+ /* Advance. */
+ off += cbReg;
+ cb -= cbReg;
+ u64Value >>= cbReg * 8;
+ if (idxRegDsc == -1)
+ idxRegDsc = hdaRegLookup(off);
+ else
+ {
+ /** @todo r=bird: This doesn't work for aliased registers, since the incremented
+ * offset won't match as it's still the aliased one. Only scenario, though
+ * would be misaligned accesses (2, 4 or 8 bytes), and the result would be that
+ * only the first part will be written. Given that the aliases we have are lone
+ * registers, that seems like they shouldn't have anything else around them,
+ * this is probably the correct behaviour, though real hw may of course
+ * disagree. Only look into it if we have a sane guest running into this. */
+ idxRegDsc++;
+ if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap)
+ || g_aHdaRegMap[idxRegDsc].off != off)
+ idxRegDsc = -1;
+ }
+ }
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+
+#else /* !IN_RING3 */
+ /* Take the simple way out. */
+ rc = VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+ }
+
+ return rc;
+}
+
+#ifdef IN_RING3
+
+
+/*********************************************************************************************************************************
+* Saved state *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNSSMFIELDGETPUT,
+ * Version 6 saves the IOC flag in HDABDLEDESC::fFlags as a bool}
+ */
+static DECLCALLBACK(int)
+hdaR3GetPutTrans_HDABDLEDESC_fFlags_6(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct,
+ uint32_t fFlags, bool fGetOrPut, void *pvUser)
+{
+ PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser;
+ RT_NOREF(pSSM, pField, pvStruct, fFlags);
+ AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4);
+ bool fIoc;
+ int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc);
+ if (RT_SUCCESS(rc))
+ {
+ PHDABDLEDESC pDesc = (PHDABDLEDESC)pvStruct;
+ pDesc->fFlags = fIoc ? HDA_BDLE_F_IOC : 0;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMFIELDGETPUT,
+ * Versions 1 thru 4 save the IOC flag in HDASTREAMSTATE::DescfFlags as a bool}
+ */
+static DECLCALLBACK(int)
+hdaR3GetPutTrans_HDABDLE_Desc_fFlags_1thru4(PSSMHANDLE pSSM, const struct SSMFIELD *pField, void *pvStruct,
+ uint32_t fFlags, bool fGetOrPut, void *pvUser)
+{
+ PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser;
+ RT_NOREF(pSSM, pField, pvStruct, fFlags);
+ AssertReturn(fGetOrPut, VERR_INTERNAL_ERROR_4);
+ bool fIoc;
+ int rc = pDevIns->pHlpR3->pfnSSMGetBool(pSSM, &fIoc);
+ if (RT_SUCCESS(rc))
+ {
+ HDABDLELEGACY *pState = (HDABDLELEGACY *)pvStruct;
+ pState->Desc.fFlags = fIoc ? HDA_BDLE_F_IOC : 0;
+ }
+ return rc;
+}
+
+
+static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+# ifdef LOG_ENABLED
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+# endif
+
+ Log2Func(("[SD%RU8]\n", pStreamShared->u8SD));
+
+ /* Save stream ID. */
+ Assert(pStreamShared->u8SD < HDA_MAX_STREAMS);
+ int rc = pHlp->pfnSSMPutU8(pSSM, pStreamShared->u8SD);
+ AssertRCReturn(rc, rc);
+
+ rc = pHlp->pfnSSMPutStructEx(pSSM, &pStreamShared->State, sizeof(pStreamShared->State),
+ 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ AssertCompile(sizeof(pStreamShared->State.idxCurBdle) == sizeof(uint8_t) && RT_ELEMENTS(pStreamShared->State.aBdl) == 256);
+ HDABDLEDESC TmpDesc = *(HDABDLEDESC *)&pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle];
+ rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpDesc, sizeof(TmpDesc), 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ HDABDLESTATELEGACY TmpState = { pStreamShared->State.idxCurBdle, 0, pStreamShared->State.offCurBdle, 0 };
+ rc = pHlp->pfnSSMPutStructEx(pSSM, &TmpState, sizeof(TmpState), 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ PAUDMIXSINK pSink = NULL;
+ uint32_t cbCircBuf = 0;
+ uint32_t cbCircBufUsed = 0;
+ if (pStreamR3->State.pCircBuf)
+ {
+ cbCircBuf = (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf);
+
+ /* We take the AIO lock here and releases it after saving the buffer,
+ otherwise the AIO thread could race us reading out the buffer data. */
+ pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if ( !pSink
+ || RT_SUCCESS(AudioMixerSinkTryLock(pSink)))
+ {
+ cbCircBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+ if (cbCircBufUsed == 0 && pSink)
+ AudioMixerSinkUnlock(pSink);
+ }
+ }
+
+ pHlp->pfnSSMPutU32(pSSM, cbCircBuf);
+ rc = pHlp->pfnSSMPutU32(pSSM, cbCircBufUsed);
+
+ if (cbCircBufUsed > 0)
+ {
+ /* HACK ALERT! We cannot remove data from the buffer (live snapshot),
+ we use RTCircBufOffsetRead and RTCircBufAcquireReadBlock
+ creatively to get at the other buffer segment in case
+ of a wraparound. */
+ size_t const offBuf = RTCircBufOffsetRead(pStreamR3->State.pCircBuf);
+ void *pvBuf = NULL;
+ size_t cbBuf = 0;
+ RTCircBufAcquireReadBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf);
+ Assert(cbBuf);
+ rc = pHlp->pfnSSMPutMem(pSSM, pvBuf, cbBuf);
+ if (cbBuf < cbCircBufUsed)
+ rc = pHlp->pfnSSMPutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf);
+ RTCircBufReleaseReadBlock(pStreamR3->State.pCircBuf, 0 /* Don't advance read pointer! */);
+
+ if (pSink)
+ AudioMixerSinkUnlock(pSink);
+ }
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", pStreamR3->u8SD, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD),
+ HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD), HDA_STREAM_REG(pThis, LVI, pStreamShared->u8SD)));
+
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
+#endif
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /* Save Codec nodes states. */
+ hdaCodecSaveState(pDevIns, &pThisCC->Codec, pSSM);
+
+ /* Save MMIO registers. */
+ pHlp->pfnSSMPutU32(pSSM, RT_ELEMENTS(pThis->au32Regs));
+ pHlp->pfnSSMPutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+
+ /* Save controller-specifc internals. */
+ pHlp->pfnSSMPutU64(pSSM, pThis->tsWalClkStart);
+ pHlp->pfnSSMPutU8(pSSM, pThis->u8IRQL);
+
+ /* Save number of streams. */
+ pHlp->pfnSSMPutU32(pSSM, HDA_MAX_STREAMS);
+
+ /* Save stream states. */
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++)
+ {
+ int rc = hdaR3SaveStream(pDevIns, pSSM, &pThis->aStreams[i], &pThisCC->aStreams[i]);
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE,
+ * Finishes stream setup and resuming.}
+ */
+static DECLCALLBACK(int) hdaR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ LogFlowFuncEnter();
+
+ /*
+ * Enable all previously active streams.
+ */
+ for (size_t i = 0; i < HDA_MAX_STREAMS; i++)
+ {
+ PHDASTREAM pStreamShared = &pThis->aStreams[i];
+
+ bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN);
+ if (fActive)
+ {
+ PHDASTREAMR3 pStreamR3 = &pThisCC->aStreams[i];
+
+ /* (Re-)enable the stream. */
+ int rc2 = hdaR3StreamEnable(pThis, pStreamShared, pStreamR3, true /* fEnable */);
+ AssertRC(rc2);
+
+ /* Add the stream to the device setup. */
+ rc2 = hdaR3AddStream(pThisCC, &pStreamShared->State.Cfg);
+ AssertRC(rc2);
+
+ /* Use the LPIB to find the current scheduling position. If this isn't
+ exactly on a scheduling item adjust LPIB down to the start of the
+ current. This isn't entirely ideal, but it avoid the IRQ counting
+ issue if we round it upwards. (it is also a lot simpler) */
+ uint32_t uLpib = HDA_STREAM_REG(pThis, LPIB, i);
+ AssertLogRelMsgStmt(uLpib < pStreamShared->u32CBL, ("LPIB=%#RX32 CBL=%#RX32\n", uLpib, pStreamShared->u32CBL),
+ HDA_STREAM_REG(pThis, LPIB, i) = uLpib = 0);
+
+ uint32_t off = 0;
+ for (uint32_t j = 0; j < pStreamShared->State.cSchedule; j++)
+ {
+ AssertReturn(pStreamShared->State.aSchedule[j].cbPeriod >= 1 && pStreamShared->State.aSchedule[j].cLoops >= 1,
+ pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_2, RT_SRC_POS,
+ "Stream #%u, sched #%u: cbPeriod=%u cLoops=%u\n",
+ pStreamShared->u8SD, j,
+ pStreamShared->State.aSchedule[j].cbPeriod,
+ pStreamShared->State.aSchedule[j].cLoops));
+ uint32_t cbCur = pStreamShared->State.aSchedule[j].cbPeriod
+ * pStreamShared->State.aSchedule[j].cLoops;
+ if (uLpib >= off + cbCur)
+ off += cbCur;
+ else
+ {
+ uint32_t const offDelta = uLpib - off;
+ uint32_t idxLoop = offDelta / pStreamShared->State.aSchedule[j].cbPeriod;
+ uint32_t offLoop = offDelta % pStreamShared->State.aSchedule[j].cbPeriod;
+ if (offLoop)
+ {
+ /** @todo somehow bake this into the DMA timer logic. */
+ LogFunc(("stream #%u: LPIB=%#RX32; adjusting due to scheduling clash: -%#x (j=%u idxLoop=%u cbPeriod=%#x)\n",
+ pStreamShared->u8SD, uLpib, offLoop, j, idxLoop, pStreamShared->State.aSchedule[j].cbPeriod));
+ uLpib -= offLoop;
+ HDA_STREAM_REG(pThis, LPIB, i) = uLpib;
+ }
+ pStreamShared->State.idxSchedule = (uint16_t)j;
+ pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop;
+ off = UINT32_MAX;
+ break;
+ }
+ }
+ Assert(off == UINT32_MAX);
+
+ /* Now figure out the current BDLE and the offset within it. */
+ off = 0;
+ for (uint32_t j = 0; j < pStreamShared->State.cBdles; j++)
+ if (uLpib >= off + pStreamShared->State.aBdl[j].cb)
+ off += pStreamShared->State.aBdl[j].cb;
+ else
+ {
+ pStreamShared->State.idxCurBdle = j;
+ pStreamShared->State.offCurBdle = uLpib - off;
+ off = UINT32_MAX;
+ break;
+ }
+ AssertReturn(off == UINT32_MAX, pDevIns->pHlpR3->pfnSSMSetLoadError(pSSM, VERR_INTERNAL_ERROR_3, RT_SRC_POS,
+ "Stream #%u: LPIB=%#RX32 not found in loaded BDL\n",
+ pStreamShared->u8SD, uLpib));
+
+ /* Avoid going through the timer here by calling the stream's timer function directly.
+ * Should speed up starting the stream transfers. */
+ PDMDevHlpTimerLockClock2(pDevIns, pStreamShared->hTimer, &pThis->CritSect, VERR_IGNORED);
+ uint64_t tsNow = hdaR3StreamTimerMain(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
+ PDMDevHlpTimerUnlockClock2(pDevIns, pStreamShared->hTimer, &pThis->CritSect);
+
+ hdaR3StreamMarkStarted(pDevIns, pThis, pStreamShared, tsNow);
+ }
+ }
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles loading of all saved state versions older than the current one.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the shared HDA state.
+ * @param pThisCC Pointer to the ring-3 HDA state.
+ * @param pSSM The saved state handle.
+ * @param uVersion Saved state version to load.
+ */
+static int hdaR3LoadExecLegacy(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+
+ /*
+ * Load MMIO registers.
+ */
+ uint32_t cRegs;
+ switch (uVersion)
+ {
+ case HDA_SAVED_STATE_VERSION_1:
+ /* Starting with r71199, we would save 112 instead of 113
+ registers due to some code cleanups. This only affected trunk
+ builds in the 4.1 development period. */
+ cRegs = 113;
+ if (pHlp->pfnSSMHandleRevision(pSSM) >= 71199)
+ {
+ uint32_t uVer = pHlp->pfnSSMHandleVersion(pSSM);
+ if ( VBOX_FULL_VERSION_GET_MAJOR(uVer) == 4
+ && VBOX_FULL_VERSION_GET_MINOR(uVer) == 0
+ && VBOX_FULL_VERSION_GET_BUILD(uVer) >= 51)
+ cRegs = 112;
+ }
+ break;
+
+ case HDA_SAVED_STATE_VERSION_2:
+ case HDA_SAVED_STATE_VERSION_3:
+ cRegs = 112;
+ AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112);
+ break;
+
+ /* Since version 4 we store the register count to stay flexible. */
+ case HDA_SAVED_STATE_VERSION_4:
+ case HDA_SAVED_STATE_VERSION_5:
+ case HDA_SAVED_STATE_VERSION_6:
+ rc = pHlp->pfnSSMGetU32(pSSM, &cRegs);
+ AssertRCReturn(rc, rc);
+ if (cRegs != RT_ELEMENTS(pThis->au32Regs))
+ LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs)));
+ break;
+
+ default:
+ AssertLogRelMsgFailedReturn(("HDA: Internal Error! Didn't expect saved state version %RU32 ending up in hdaR3LoadExecLegacy!\n",
+ uVersion), VERR_INTERNAL_ERROR_5);
+ }
+
+ if (cRegs >= RT_ELEMENTS(pThis->au32Regs))
+ {
+ pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+ pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs)));
+ }
+ else
+ pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs);
+
+ /* Make sure to update the base addresses first before initializing any streams down below. */
+ pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE));
+ pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE));
+ pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE));
+
+ /* Also make sure to update the DMA position bit if this was enabled when saving the state. */
+ pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0));
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ *
+ * Note: Saved states < v5 store LVI (u32BdleMaxCvi) for
+ * *every* BDLE state, whereas it only needs to be stored
+ * *once* for every stream. Most of the BDLE state we can
+ * get out of the registers anyway, so just ignore those values.
+ *
+ * Also, only the current BDLE was saved, regardless whether
+ * there were more than one (and there are at least two entries,
+ * according to the spec).
+ */
+ switch (uVersion)
+ {
+ case HDA_SAVED_STATE_VERSION_1:
+ case HDA_SAVED_STATE_VERSION_2:
+ case HDA_SAVED_STATE_VERSION_3:
+ case HDA_SAVED_STATE_VERSION_4:
+ {
+ /* Only load the internal states.
+ * The rest will be initialized from the saved registers later. */
+
+ /* Note 1: Only the *current* BDLE for a stream was saved! */
+ /* Note 2: The stream's saving order is/was fixed, so don't touch! */
+
+ HDABDLELEGACY BDLE;
+
+ /* Output */
+ PHDASTREAM pStreamShared = &pThis->aStreams[4];
+ rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[4], 4 /* Stream descriptor, hardcoded */);
+ AssertRCReturn(rc, rc);
+ RT_ZERO(BDLE);
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+ AssertRCReturn(rc, rc);
+ pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
+
+ /* Microphone-In */
+ pStreamShared = &pThis->aStreams[2];
+ rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[2], 2 /* Stream descriptor, hardcoded */);
+ AssertRCReturn(rc, rc);
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+ AssertRCReturn(rc, rc);
+ pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
+
+ /* Line-In */
+ pStreamShared = &pThis->aStreams[0];
+ rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, &pThisCC->aStreams[0], 0 /* Stream descriptor, hardcoded */);
+ AssertRCReturn(rc, rc);
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &BDLE, sizeof(BDLE), 0 /* fFlags */, g_aSSMStreamBdleFields1234, pDevIns);
+ AssertRCReturn(rc, rc);
+ pStreamShared->State.idxCurBdle = (uint8_t)BDLE.State.u32BDLIndex; /* not necessary */
+ break;
+ }
+
+ /*
+ * v5 & v6 - Since v5 we support flexible stream and BDLE counts.
+ */
+ default:
+ {
+ /* Stream count. */
+ uint32_t cStreams;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cStreams);
+ AssertRCReturn(rc, rc);
+ if (cStreams > HDA_MAX_STREAMS)
+ return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
+ N_("State contains %u streams while %u is the maximum supported"),
+ cStreams, HDA_MAX_STREAMS);
+
+ /* Load stream states. */
+ for (uint32_t i = 0; i < cStreams; i++)
+ {
+ uint8_t idStream;
+ rc = pHlp->pfnSSMGetU8(pSSM, &idStream);
+ AssertRCReturn(rc, rc);
+
+ HDASTREAM StreamDummyShared;
+ HDASTREAMR3 StreamDummyR3;
+ PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared;
+ PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3;
+ AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams),
+ ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream),
+ RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3));
+
+ rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Stream #%RU32: Setting up of stream %RU8 failed, rc=%Rrc\n", i, idStream, rc));
+ break;
+ }
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ */
+ if (uVersion == HDA_SAVED_STATE_VERSION_5)
+ {
+ struct V5HDASTREAMSTATE /* HDASTREAMSTATE + HDABDLE */
+ {
+ uint16_t cBLDEs;
+ uint16_t uCurBDLE;
+ uint32_t u32BDLEIndex;
+ uint32_t cbBelowFIFOW;
+ uint32_t u32BufOff;
+ } Tmp;
+ static SSMFIELD const g_aV5State1Fields[] =
+ {
+ SSMFIELD_ENTRY(V5HDASTREAMSTATE, cBLDEs),
+ SSMFIELD_ENTRY(V5HDASTREAMSTATE, uCurBDLE),
+ SSMFIELD_ENTRY_TERM()
+ };
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State1Fields, NULL);
+ AssertRCReturn(rc, rc);
+ pStreamShared->State.idxCurBdle = (uint8_t)Tmp.uCurBDLE; /* not necessary */
+
+ for (uint16_t a = 0; a < Tmp.cBLDEs; a++)
+ {
+ static SSMFIELD const g_aV5State2Fields[] =
+ {
+ SSMFIELD_ENTRY(V5HDASTREAMSTATE, u32BDLEIndex),
+ SSMFIELD_ENTRY_OLD(au8FIFO, 256),
+ SSMFIELD_ENTRY(V5HDASTREAMSTATE, cbBelowFIFOW),
+ SSMFIELD_ENTRY_TERM()
+ };
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &Tmp, sizeof(Tmp), 0 /* fFlags */, g_aV5State2Fields, NULL);
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE),
+ 0 /* fFlags */, g_aSSMStreamStateFields6, NULL);
+ AssertRCReturn(rc, rc);
+
+ HDABDLEDESC IgnDesc;
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields6, pDevIns);
+ AssertRCReturn(rc, rc);
+
+ HDABDLESTATELEGACY IgnState;
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL);
+ AssertRCReturn(rc, rc);
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream),
+ HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream)));
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
+#endif
+ }
+
+ } /* for cStreams */
+ break;
+ } /* default */
+ }
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) hdaR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass));
+
+ /*
+ * Load Codec nodes states.
+ */
+ int rc = hdaR3CodecLoadState(pDevIns, &pThisCC->Codec, pSSM, uVersion);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Failed loading codec state (version %RU32, pass 0x%x), rc=%Rrc\n", uVersion, uPass, rc));
+ return rc;
+ }
+
+ if (uVersion <= HDA_SAVED_STATE_VERSION_6) /* Handle older saved states? */
+ return hdaR3LoadExecLegacy(pDevIns, pThis, pThisCC, pSSM, uVersion);
+
+ /*
+ * Load MMIO registers.
+ */
+ uint32_t cRegs;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cRegs); AssertRCReturn(rc, rc);
+ AssertRCReturn(rc, rc);
+ if (cRegs != RT_ELEMENTS(pThis->au32Regs))
+ LogRel(("HDA: SSM version cRegs is %RU32, expected %RU32\n", cRegs, RT_ELEMENTS(pThis->au32Regs)));
+
+ if (cRegs >= RT_ELEMENTS(pThis->au32Regs))
+ {
+ pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+ rc = pHlp->pfnSSMSkip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs)));
+ AssertRCReturn(rc, rc);
+ }
+ else
+ {
+ rc = pHlp->pfnSSMGetMem(pSSM, pThis->au32Regs, sizeof(uint32_t) * cRegs);
+ AssertRCReturn(rc, rc);
+ }
+
+ /* Make sure to update the base addresses first before initializing any streams down below. */
+ pThis->u64CORBBase = RT_MAKE_U64(HDA_REG(pThis, CORBLBASE), HDA_REG(pThis, CORBUBASE));
+ pThis->u64RIRBBase = RT_MAKE_U64(HDA_REG(pThis, RIRBLBASE), HDA_REG(pThis, RIRBUBASE));
+ pThis->u64DPBase = RT_MAKE_U64(HDA_REG(pThis, DPLBASE) & DPBASE_ADDR_MASK, HDA_REG(pThis, DPUBASE));
+
+ /* Also make sure to update the DMA position bit if this was enabled when saving the state. */
+ pThis->fDMAPosition = RT_BOOL(HDA_REG(pThis, DPLBASE) & RT_BIT_32(0));
+
+ /*
+ * Load controller-specific internals.
+ */
+ if ( uVersion >= HDA_SAVED_STATE_WITHOUT_PERIOD
+ /* Don't annoy other team mates (forgot this for state v7): */
+ || pHlp->pfnSSMHandleRevision(pSSM) >= 116273
+ || pHlp->pfnSSMHandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0))
+ {
+ pHlp->pfnSSMGetU64(pSSM, &pThis->tsWalClkStart); /* Was current wall clock */
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->u8IRQL);
+ AssertRCReturn(rc, rc);
+
+ /* Convert the saved wall clock timestamp to a start timestamp. */
+ if (uVersion < HDA_SAVED_STATE_WITHOUT_PERIOD && pThis->tsWalClkStart != 0)
+ {
+ uint64_t const cTimerTicksPerSec = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[0].hTimer);
+ AssertLogRel(cTimerTicksPerSec <= UINT32_MAX);
+ pThis->tsWalClkStart = ASMMultU64ByU32DivByU32(pThis->tsWalClkStart,
+ cTimerTicksPerSec,
+ 24000000 /* wall clock freq */);
+ pThis->tsWalClkStart = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[0].hTimer) - pThis->tsWalClkStart;
+ }
+ }
+
+ /*
+ * Load streams.
+ */
+ uint32_t cStreams;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cStreams);
+ AssertRCReturn(rc, rc);
+ if (cStreams > HDA_MAX_STREAMS)
+ return pHlp->pfnSSMSetLoadError(pSSM, VERR_SSM_DATA_UNIT_FORMAT_CHANGED, RT_SRC_POS,
+ N_("State contains %u streams while %u is the maximum supported"),
+ cStreams, HDA_MAX_STREAMS);
+ Log2Func(("cStreams=%RU32\n", cStreams));
+
+ /* Load stream states. */
+ for (uint32_t i = 0; i < cStreams; i++)
+ {
+ uint8_t idStream;
+ rc = pHlp->pfnSSMGetU8(pSSM, &idStream);
+ AssertRCReturn(rc, rc);
+
+ /* Paranoia. */
+ AssertLogRelMsgReturn(idStream < HDA_MAX_STREAMS,
+ ("HDA: Saved state contains bogus stream ID %RU8 for stream #%RU8", idStream, i),
+ VERR_SSM_INVALID_STATE);
+
+ HDASTREAM StreamDummyShared;
+ HDASTREAMR3 StreamDummyR3;
+ PHDASTREAM pStreamShared = idStream < RT_ELEMENTS(pThis->aStreams) ? &pThis->aStreams[idStream] : &StreamDummyShared;
+ PHDASTREAMR3 pStreamR3 = idStream < RT_ELEMENTS(pThisCC->aStreams) ? &pThisCC->aStreams[idStream] : &StreamDummyR3;
+ AssertLogRelMsgStmt(idStream < RT_ELEMENTS(pThisCC->aStreams),
+ ("HDA stream ID=%RU8 not supported, skipping loadingit ...\n", idStream),
+ RT_ZERO(StreamDummyShared); RT_ZERO(StreamDummyR3));
+
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED); /* timer code requires this */
+ AssertRCReturn(rc, rc);
+ rc = hdaR3StreamSetUp(pDevIns, pThis, pStreamShared, pStreamR3, idStream);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Stream #%RU8: Setting up failed, rc=%Rrc\n", idStream, rc));
+ /* Continue. */
+ }
+
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &pStreamShared->State, sizeof(HDASTREAMSTATE),
+ 0 /* fFlags */, g_aSSMStreamStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ * Obsolete. Derived from LPID now.
+ */
+ HDABDLEDESC IgnDesc;
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnDesc, sizeof(IgnDesc), 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ HDABDLESTATELEGACY IgnState;
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &IgnState, sizeof(IgnState), 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ Log2Func(("[SD%RU8]\n", pStreamShared->u8SD));
+
+ /*
+ * Load period state if present.
+ */
+ if (uVersion < HDA_SAVED_STATE_WITHOUT_PERIOD)
+ {
+ static SSMFIELD const s_aSSMStreamPeriodFields7[] = /* For the removed HDASTREAMPERIOD structure. */
+ {
+ SSMFIELD_ENTRY_OLD(u64StartWalClk, sizeof(uint64_t)),
+ SSMFIELD_ENTRY_OLD(u64ElapsedWalClk, sizeof(uint64_t)),
+ SSMFIELD_ENTRY_OLD(cFramesTransferred, sizeof(uint32_t)),
+ SSMFIELD_ENTRY_OLD(cIntPending, sizeof(uint8_t)), /** @todo Not sure what we should for non-zero values on restore... ignoring it for now. */
+ SSMFIELD_ENTRY_TERM()
+ };
+ uint8_t bWhatever = 0;
+ rc = pHlp->pfnSSMGetStructEx(pSSM, &bWhatever, sizeof(bWhatever), 0 /* fFlags */, s_aSSMStreamPeriodFields7, NULL);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Load internal DMA buffer.
+ */
+ uint32_t cbCircBuf = 0;
+ pHlp->pfnSSMGetU32(pSSM, &cbCircBuf); /* cbCircBuf */
+ uint32_t cbCircBufUsed = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */
+ AssertRCReturn(rc, rc);
+
+ if (cbCircBuf) /* If 0, skip the buffer. */
+ {
+ /* Paranoia. */
+ AssertLogRelMsgReturn(cbCircBuf <= _32M,
+ ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8",
+ cbCircBuf, idStream),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ AssertLogRelMsgReturn(cbCircBufUsed <= cbCircBuf,
+ ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8",
+ cbCircBufUsed, cbCircBuf, idStream),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ /* Do we need to cre-create the circular buffer do fit the data size? */
+ if ( pStreamR3->State.pCircBuf
+ && cbCircBuf != (uint32_t)RTCircBufSize(pStreamR3->State.pCircBuf))
+ {
+ RTCircBufDestroy(pStreamR3->State.pCircBuf);
+ pStreamR3->State.pCircBuf = NULL;
+ }
+
+ rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf);
+ AssertRCReturn(rc, rc);
+ pStreamR3->State.StatDmaBufSize = cbCircBuf;
+
+ if (cbCircBufUsed)
+ {
+ void *pvBuf = NULL;
+ size_t cbBuf = 0;
+ RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf);
+
+ AssertLogRelMsgReturn(cbBuf == cbCircBufUsed, ("cbBuf=%zu cbCircBufUsed=%zu\n", cbBuf, cbCircBufUsed),
+ VERR_INTERNAL_ERROR_3);
+ rc = pHlp->pfnSSMGetMem(pSSM, pvBuf, cbBuf);
+ AssertRCReturn(rc, rc);
+ pStreamShared->State.offWrite = cbCircBufUsed;
+
+ RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbBuf);
+
+ Assert(cbBuf == cbCircBufUsed);
+ }
+ }
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n", idStream, HDA_STREAM_REG(pThis, LPIB, idStream),
+ HDA_STREAM_REG(pThis, CBL, idStream), HDA_STREAM_REG(pThis, LVI, idStream)));
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
+#endif
+ /** @todo (Re-)initialize active periods? */
+
+ } /* for cStreams */
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* IPRT format type handlers *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE}
+ */
+static DECLCALLBACK(size_t) hdaR3StrFmtSDCTL(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser);
+ uint32_t uSDCTL = (uint32_t)(uintptr_t)pvValue;
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "SDCTL(raw:%#x, DIR:%s, TP:%RTbool, STRIPE:%x, DEIE:%RTbool, FEIE:%RTbool, IOCE:%RTbool, RUN:%RTbool, RESET:%RTbool)",
+ uSDCTL,
+ uSDCTL & HDA_SDCTL_DIR ? "OUT" : "IN",
+ RT_BOOL(uSDCTL & HDA_SDCTL_TP),
+ (uSDCTL & HDA_SDCTL_STRIPE_MASK) >> HDA_SDCTL_STRIPE_SHIFT,
+ RT_BOOL(uSDCTL & HDA_SDCTL_DEIE),
+ RT_BOOL(uSDCTL & HDA_SDCTL_FEIE),
+ RT_BOOL(uSDCTL & HDA_SDCTL_IOCE),
+ RT_BOOL(uSDCTL & HDA_SDCTL_RUN),
+ RT_BOOL(uSDCTL & HDA_SDCTL_SRST));
+}
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE}
+ */
+static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser);
+ uint32_t uSDFIFOS = (uint32_t)(uintptr_t)pvValue;
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOS(raw:%#x, sdfifos:%RU8 B)", uSDFIFOS, uSDFIFOS ? uSDFIFOS + 1 : 0);
+}
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE}
+ */
+static DECLCALLBACK(size_t) hdaR3StrFmtSDFIFOW(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser);
+ uint32_t uSDFIFOW = (uint32_t)(uintptr_t)pvValue;
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "SDFIFOW(raw: %#0x, sdfifow:%d B)", uSDFIFOW, hdaSDFIFOWToBytes(uSDFIFOW));
+}
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE}
+ */
+static DECLCALLBACK(size_t) hdaR3StrFmtSDSTS(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char *pszType, void const *pvValue,
+ int cchWidth, int cchPrecision, unsigned fFlags,
+ void *pvUser)
+{
+ RT_NOREF(pszType, cchWidth, cchPrecision, fFlags, pvUser);
+ uint32_t uSdSts = (uint32_t)(uintptr_t)pvValue;
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "SDSTS(raw:%#0x, fifordy:%RTbool, dese:%RTbool, fifoe:%RTbool, bcis:%RTbool)",
+ uSdSts,
+ RT_BOOL(uSdSts & HDA_SDSTS_FIFORDY),
+ RT_BOOL(uSdSts & HDA_SDSTS_DESE),
+ RT_BOOL(uSdSts & HDA_SDSTS_FIFOE),
+ RT_BOOL(uSdSts & HDA_SDSTS_BCIS));
+}
+
+
+/*********************************************************************************************************************************
+* Debug Info Item Handlers *
+*********************************************************************************************************************************/
+
+/** Worker for hdaR3DbgInfo. */
+static int hdaR3DbgLookupRegByName(const char *pszArgs)
+{
+ if (pszArgs && *pszArgs != '\0')
+ for (int iReg = 0; iReg < HDA_NUM_REGS; ++iReg)
+ if (!RTStrICmp(g_aHdaRegMap[iReg].pszName, pszArgs))
+ return iReg;
+ return -1;
+}
+
+/** Worker for hdaR3DbgInfo. */
+static void hdaR3DbgPrintRegister(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex)
+{
+ /** @todo HDA_REG_IDX_NOMEM & GCAP both uses idxReg zero, no flag or anything
+ * to tell them appart. */
+ if (g_aHdaRegMap[iHdaIndex].idxReg != 0 || g_aHdaRegMap[iHdaIndex].pfnRead != hdaRegReadWALCLK)
+ pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].pszName, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].idxReg]);
+ else
+ {
+ uint64_t uWallNow = 0;
+ hdaQueryWallClock(pDevIns, pThis, false /*fDoDma*/, &uWallNow);
+ pHlp->pfnPrintf(pHlp, "%s: 0x%RX64\n", g_aHdaRegMap[iHdaIndex].pszName, uWallNow);
+ }
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ int idxReg = hdaR3DbgLookupRegByName(pszArgs);
+ if (idxReg != -1)
+ hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg);
+ else
+ for (idxReg = 0; idxReg < HDA_NUM_REGS; ++idxReg)
+ hdaR3DbgPrintRegister(pDevIns, pThis, pHlp, idxReg);
+}
+
+/** Worker for hdaR3DbgInfoStream. */
+static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream)
+{
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ PHDASTREAM const pStream = &pThis->aStreams[idxStream];
+ pHlp->pfnPrintf(pHlp, "Stream #%d: %s\n", idxStream, PDMAudioStrmCfgToString(&pStream->State.Cfg, szTmp, sizeof(szTmp)));
+ pHlp->pfnPrintf(pHlp, " SD%dCTL : %R[sdctl]\n", idxStream, HDA_STREAM_REG(pThis, CTL, idxStream));
+ pHlp->pfnPrintf(pHlp, " SD%dCTS : %R[sdsts]\n", idxStream, HDA_STREAM_REG(pThis, STS, idxStream));
+ pHlp->pfnPrintf(pHlp, " SD%dFIFOS: %R[sdfifos]\n", idxStream, HDA_STREAM_REG(pThis, FIFOS, idxStream));
+ pHlp->pfnPrintf(pHlp, " SD%dFIFOW: %R[sdfifow]\n", idxStream, HDA_STREAM_REG(pThis, FIFOW, idxStream));
+ pHlp->pfnPrintf(pHlp, " Current BDLE%02u: %s%#RX64 LB %#x%s - off=%#x\n", pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/,
+ pStream->State.aBdl[pStream->State.idxCurBdle].GCPhys, pStream->State.aBdl[pStream->State.idxCurBdle].cb,
+ pStream->State.aBdl[pStream->State.idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle);
+}
+
+/** Worker for hdaR3DbgInfoBDL. */
+static void hdaR3DbgPrintBDL(PPDMDEVINS pDevIns, PHDASTATE pThis, PCDBGFINFOHLP pHlp, int idxStream)
+{
+ const PHDASTREAM pStream = &pThis->aStreams[idxStream];
+ PCPDMAUDIOPCMPROPS pProps = &pStream->State.Cfg.Props;
+ uint64_t const u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, idxStream),
+ HDA_STREAM_REG(pThis, BDPU, idxStream));
+ uint16_t const u16LVI = HDA_STREAM_REG(pThis, LVI, idxStream);
+ uint32_t const u32CBL = HDA_STREAM_REG(pThis, CBL, idxStream);
+ uint8_t const idxCurBdle = pStream->State.idxCurBdle;
+ pHlp->pfnPrintf(pHlp, "Stream #%d BDL: %s%#011RX64 LB %#x (LVI=%u)\n", idxStream, "%%" /*vboxdbg phys prefix*/,
+ u64BaseDMA, u16LVI * sizeof(HDABDLEDESC), u16LVI);
+ if (u64BaseDMA || idxCurBdle != 0 || pStream->State.aBdl[idxCurBdle].GCPhys != 0 || pStream->State.aBdl[idxCurBdle].cb != 0)
+ pHlp->pfnPrintf(pHlp, " Current: BDLE%03u: %s%#011RX64 LB %#x%s - off=%#x LPIB=%#RX32\n",
+ pStream->State.idxCurBdle, "%%" /*vboxdbg phys prefix*/,
+ pStream->State.aBdl[idxCurBdle].GCPhys, pStream->State.aBdl[idxCurBdle].cb,
+ pStream->State.aBdl[idxCurBdle].fFlags ? " IOC" : "", pStream->State.offCurBdle,
+ HDA_STREAM_REG(pThis, LPIB, idxStream));
+ if (!u64BaseDMA)
+ return;
+
+ /*
+ * The BDL:
+ */
+ uint64_t cbTotal = 0;
+ for (uint16_t i = 0; i < u16LVI + 1; i++)
+ {
+ HDABDLEDESC bd = {0, 0, 0};
+ PDMDevHlpPCIPhysRead(pDevIns, u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
+
+ char szFlags[64];
+ szFlags[0] = '\0';
+ if (bd.fFlags & ~HDA_BDLE_F_IOC)
+ RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags);
+ pHlp->pfnPrintf(pHlp, " %sBDLE%03u: %s%#011RX64 LB %#06x (%RU64 us) %s%s\n", idxCurBdle == i ? "=>" : " ", i, "%%",
+ bd.u64BufAddr, bd.u32BufSize, PDMAudioPropsBytesToMicro(pProps, bd.u32BufSize),
+ bd.fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags);
+
+ if (memcmp(&bd, &pStream->State.aBdl[i], sizeof(bd)) != 0)
+ {
+ szFlags[0] = '\0';
+ if (bd.fFlags & ~HDA_BDLE_F_IOC)
+ RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", bd.fFlags);
+ pHlp->pfnPrintf(pHlp, " !!!loaded: %s%#011RX64 LB %#06x %s%s\n", "%%", pStream->State.aBdl[i].GCPhys,
+ pStream->State.aBdl[i].cb, pStream->State.aBdl[i].fFlags & HDA_BDLE_F_IOC ? " IOC=1" : "", szFlags);
+ }
+
+ cbTotal += bd.u32BufSize;
+ }
+ pHlp->pfnPrintf(pHlp, " Total: %#RX64 bytes (%RU64), %RU64 ms\n", cbTotal, cbTotal,
+ PDMAudioPropsBytesToMilli(pProps, (uint32_t)cbTotal));
+ if (cbTotal != u32CBL)
+ pHlp->pfnPrintf(pHlp, " Warning: %#RX64 bytes does not match CBL (%#RX64)!\n", cbTotal, u32CBL);
+
+ /*
+ * The scheduling plan.
+ */
+ uint16_t const idxSchedule = pStream->State.idxSchedule;
+ pHlp->pfnPrintf(pHlp, " Scheduling: %u items, %u prologue. Current: %u, loop %u.\n", pStream->State.cSchedule,
+ pStream->State.cSchedulePrologue, idxSchedule, pStream->State.idxScheduleLoop);
+ for (uint16_t i = 0; i < pStream->State.cSchedule; i++)
+ pHlp->pfnPrintf(pHlp, " %s#%02u: %#x bytes, %u loop%s, %RU32 ticks. BDLE%u thru BDLE%u\n",
+ i == idxSchedule ? "=>" : " ", i,
+ pStream->State.aSchedule[i].cbPeriod, pStream->State.aSchedule[i].cLoops,
+ pStream->State.aSchedule[i].cLoops == 1 ? "" : "s",
+ pStream->State.aSchedule[i].cPeriodTicks, pStream->State.aSchedule[i].idxFirst,
+ pStream->State.aSchedule[i].idxFirst + pStream->State.aSchedule[i].cEntries - 1);
+}
+
+/** Used by hdaR3DbgInfoStream and hdaR3DbgInfoBDL. */
+static int hdaR3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ if (pszArgs && *pszArgs)
+ {
+ int32_t idxStream;
+ int rc = RTStrToInt32Full(pszArgs, 0, &idxStream);
+ if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < HDA_MAX_STREAMS)
+ return idxStream;
+ pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs);
+ }
+ return -1;
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, hdastream}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ hdaR3DbgPrintStream(pThis, pHlp, idxStream);
+ else
+ for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream)
+ hdaR3DbgPrintStream(pThis, pHlp, idxStream);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, hdabdl}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ int idxStream = hdaR3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream);
+ else
+ {
+ for (idxStream = 0; idxStream < HDA_MAX_STREAMS; ++idxStream)
+ hdaR3DbgPrintBDL(pDevIns, pThis, pHlp, idxStream);
+ idxStream = -1;
+ }
+
+ /*
+ * DMA stream positions:
+ */
+ uint64_t const uDPBase = pThis->u64DPBase & DPBASE_ADDR_MASK;
+ pHlp->pfnPrintf(pHlp, "DMA counters %#011RX64 LB %#x, %s:\n", uDPBase, HDA_MAX_STREAMS * 2 * sizeof(uint32_t),
+ pThis->fDMAPosition ? "enabled" : "disabled");
+ if (uDPBase)
+ {
+ struct
+ {
+ uint32_t off, uReserved;
+ } aPositions[HDA_MAX_STREAMS];
+ RT_ZERO(aPositions);
+ PDMDevHlpPCIPhysRead(pDevIns, uDPBase , &aPositions[0], sizeof(aPositions));
+
+ for (unsigned i = 0; i < RT_ELEMENTS(aPositions); i++)
+ if (idxStream == -1 || i == (unsigned)idxStream) /* lazy bird */
+ {
+ char szReserved[64];
+ szReserved[0] = '\0';
+ if (aPositions[i].uReserved != 0)
+ RTStrPrintf(szReserved, sizeof(szReserved), " reserved=%#x", aPositions[i].uReserved);
+ pHlp->pfnPrintf(pHlp, " Stream #%u DMA @ %#x%s\n", i, aPositions[i].off, szReserved);
+ }
+ }
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, hdcnodes}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ hdaR3CodecDbgListNodes(&pThisCC->Codec, pHlp, pszArgs);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, hdcselector}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ hdaR3CodecDbgSelector(&pThisCC->Codec, pHlp, pszArgs);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, hdamixer}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ if (pThisCC->pMixer)
+ AudioMixerDebug(pThisCC->pMixer, pHlp, pszArgs);
+ else
+ pHlp->pfnPrintf(pHlp, "Mixer not available\n");
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) hdaR3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
+{
+ PHDASTATER3 pThisCC = RT_FROM_MEMBER(pInterface, HDASTATER3, IBase);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDEVREGR3 *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for hdaR3Construct() and hdaR3Attach().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param uLUN The logical unit which is being detached.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int hdaR3AttachInternal(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC, unsigned uLUN, PHDADRIVER *ppDrv)
+{
+ PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER));
+ AssertPtrReturn(pDrv, VERR_NO_MEMORY);
+ RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (HDA) for LUN #%u", uLUN);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pDrv->szDesc);
+ if (RT_SUCCESS(rc))
+ {
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pDrv->pConnector);
+ if (RT_VALID_PTR(pDrv->pConnector))
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->pHDAStateShared = pThis;
+ pDrv->pHDAStateR3 = pThisCC;
+ pDrv->uLUN = uLUN;
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThisCC->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+
+ /*
+ * While we're here, give the windows backends a hint about our typical playback
+ * configuration.
+ * Note! If 48000Hz is advertised to the guest, add it here.
+ */
+ if ( pDrv->pConnector
+ && pDrv->pConnector->pfnStreamConfigHint)
+ {
+ PDMAUDIOSTREAMCFG Cfg;
+ RT_ZERO(Cfg);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ Cfg.Device.cMsSchedulingHint = 10;
+ Cfg.Backend.cFramesPreBuffering = UINT32_MAX;
+ PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 44100);
+ RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 44.1kHz 2ch S16 (HDA config hint)");
+
+ pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */
+ }
+
+ LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+ else
+ LogFunc(("Failed attaching driver for LUN #%u: %Rrc\n", uLUN, rc));
+ RTMemFree(pDrv);
+
+ LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) hdaR3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ RT_NOREF(fFlags);
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ DEVHDA_LOCK_RETURN(pDevIns, pThis, VERR_IGNORED);
+
+ PHDADRIVER pDrv;
+ int rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, uLUN, &pDrv);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = hdaR3MixerAddDrv(pDevIns, pThisCC, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("hdaR3MixerAddDrv failed with %Rrc (ignored)\n", rc2));
+ }
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ return rc;
+}
+
+
+/**
+ * Worker for hdaR3Detach that does all but free pDrv.
+ *
+ * This is called to let the device detach from a driver for a specified LUN
+ * at runtime.
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pDrv Driver to detach from device.
+ */
+static void hdaR3DetachInternal(PPDMDEVINS pDevIns, PHDASTATER3 pThisCC, PHDADRIVER pDrv)
+{
+ /* Remove the driver from our list and destory it's associated streams.
+ This also will un-set the driver as a recording source (if associated). */
+ hdaR3MixerRemoveDrv(pDevIns, pThisCC, pDrv);
+ LogFunc(("LUN#%u detached\n", pDrv->uLUN));
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ RT_NOREF(fFlags);
+ LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
+
+ DEVHDA_LOCK(pDevIns, pThis);
+
+ PHDADRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, HDADRIVER, Node)
+ {
+ if (pDrv->uLUN == iLUN)
+ {
+ hdaR3DetachInternal(pDevIns, pThisCC, pDrv);
+ RTMemFree(pDrv);
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ return;
+ }
+ }
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+ LogFunc(("LUN#%u was not found\n", iLUN));
+}
+
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+
+ DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis);
+
+ LogRel2(("HDA: Powering off ...\n"));
+
+/** @todo r=bird: What this "releasing references" and whatever here is
+ * referring to, is apparently that the device is destroyed after the
+ * drivers, so creating trouble as those structures have been torn down
+ * already... Reverse order, like we do for power off? Need a new
+ * PDMDEVREG flag. */
+
+ /* Ditto goes for the codec, which in turn uses the mixer. */
+ hdaR3CodecPowerOff(&pThisCC->Codec);
+
+ /* This is to prevent us from calling into the mixer and mixer sink code
+ after it has been destroyed below. */
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++)
+ pThisCC->aStreams[i].State.pAioRegSink = NULL; /* don't need to remove, we're destorying it. */
+
+ /*
+ * Note: Destroy the mixer while powering off and *not* in hdaR3Destruct,
+ * giving the mixer the chance to release any references held to
+ * PDM audio streams it maintains.
+ */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns)
+{
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+
+ LogFlowFuncEnter();
+
+ DEVHDA_LOCK_RETURN_VOID(pDevIns, pThis);
+
+ /*
+ * 18.2.6,7 defines that values of this registers might be cleared on power on/reset
+ * hdaR3Reset shouldn't affects these registers.
+ */
+ HDA_REG(pThis, WAKEEN) = 0x0;
+
+ hdaR3GCTLReset(pDevIns, pThis, pThisCC);
+
+ /* Indicate that HDA is not in reset. The firmware is supposed to (un)reset HDA,
+ * but we can take a shortcut.
+ */
+ HDA_REG(pThis, GCTL) = HDA_GCTL_CRST;
+
+ DEVHDA_UNLOCK(pDevIns, pThis);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+
+ if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->CritSect))
+ {
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRC(rc);
+ }
+
+ PHDADRIVER pDrv;
+ while (!RTListIsEmpty(&pThisCC->lstDrv))
+ {
+ pDrv = RTListGetFirst(&pThisCC->lstDrv, HDADRIVER, Node);
+
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ hdaCodecDestruct(&pThisCC->Codec);
+
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++)
+ hdaR3StreamDestroy(&pThisCC->aStreams[i]);
+
+ /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+
+ if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->CritSect))
+ {
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CritSect);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) hdaR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ Assert(iInstance == 0); RT_NOREF(iInstance);
+
+ /*
+ * Initialize the state sufficently to make the destructor work.
+ */
+ pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC;
+ RTListInit(&pThisCC->lstDrv);
+ pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE;
+ pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE;
+ pThis->hCorbDmaTask = NIL_PDMTASKHANDLE;
+
+ /** @todo r=bird: There are probably other things which should be
+ * initialized here before we start failing. */
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns,
+ "BufSizeInMs"
+ "|BufSizeOutMs"
+ "|DebugEnabled"
+ "|DebugPathOut"
+ "|DeviceName",
+ "");
+
+ /** @devcfgm{hda,BufSizeInMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for input streams expressed in milliseconds. */
+ int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cMsCircBufIn, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read 'BufSizeInMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufIn > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("HDA configuration error: 'BufSizeInMs' is out of bound, max 2000 ms"));
+
+ /** @devcfgm{hda,BufSizeOutMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for output streams expressed in milliseconds. */
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cMsCircBufOut, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read 'BufSizeOutMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufOut > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("HDA configuration error: 'BufSizeOutMs' is out of bound, max 2000 ms"));
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read debugging enabled flag as boolean"));
+
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read debugging output path flag as string"));
+ if (pThisCC->Dbg.fEnabled)
+ LogRel2(("HDA: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath));
+
+ /** @devcfgm{hda,DeviceName,string}
+ * Override the default device/vendor IDs for the emulated device:
+ * - "" - default
+ * - "Intel ICH6"
+ * - "Intel Sunrise Point" - great for macOS 10.15
+ */
+ char szDeviceName[32];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DeviceName", szDeviceName, sizeof(szDeviceName), "");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("HDA configuration error: failed to read 'DeviceName' name string"));
+ enum
+ {
+ kDevice_Default,
+ kDevice_IntelIch6,
+ kDevice_IntelSunrisePoint /*skylake timeframe*/
+ } enmDevice;
+ if (strcmp(szDeviceName, "") == 0)
+ enmDevice = kDevice_Default;
+ else if (strcmp(szDeviceName, "Intel ICH6") == 0)
+ enmDevice = kDevice_IntelIch6;
+ else if (strcmp(szDeviceName, "Intel Sunrise Point") == 0)
+ enmDevice = kDevice_IntelSunrisePoint;
+ else
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("HDA configuration error: Unknown 'DeviceName' name '%s'"), szDeviceName);
+
+ /*
+ * Use our own critical section for the device instead of the default
+ * one provided by PDM. This allows fine-grained locking in combination
+ * with TM when timer-specific stuff is being called in e.g. the MMIO handlers.
+ */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "HDA");
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ pThisCC->pDevIns = pDevIns;
+ /* IBase */
+ pThisCC->IBase.pfnQueryInterface = hdaR3QueryInterface;
+
+ /* PCI Device */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ switch (enmDevice)
+ {
+ case kDevice_Default:
+ PDMPciDevSetVendorId(pPciDev, HDA_PCI_VENDOR_ID);
+ PDMPciDevSetDeviceId(pPciDev, HDA_PCI_DEVICE_ID);
+ break;
+ case kDevice_IntelIch6: /* Our default intel device. */
+ PDMPciDevSetVendorId(pPciDev, 0x8086);
+ PDMPciDevSetDeviceId(pPciDev, 0x2668);
+ break;
+ case kDevice_IntelSunrisePoint: /* this is supported by more recent macOS version, at least 10.15 */
+ PDMPciDevSetVendorId(pPciDev, 0x8086);
+ PDMPciDevSetDeviceId(pPciDev, 0x9d70);
+ break;
+ }
+
+ PDMPciDevSetCommand( pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */
+ PDMPciDevSetStatus( pPciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */
+ PDMPciDevSetRevisionId( pPciDev, 0x01); /* 08 ro - rid. */
+ PDMPciDevSetClassProg( pPciDev, 0x00); /* 09 ro - pi. */
+ PDMPciDevSetClassSub( pPciDev, 0x03); /* 0a ro - scc; 03 == HDA. */
+ PDMPciDevSetClassBase( pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */
+ PDMPciDevSetHeaderType( pPciDev, 0x00); /* 0e ro - headtyp. */
+ PDMPciDevSetBaseAddress( pPciDev, 0, /* 10 rw - MMIO */
+ false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000);
+ PDMPciDevSetInterruptLine( pPciDev, 0x00); /* 3c rw. */
+ PDMPciDevSetInterruptPin( pPciDev, 0x01); /* 3d ro - INTA#. */
+
+# if defined(HDA_AS_PCI_EXPRESS)
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+# elif defined(VBOX_WITH_MSI_DEVICES)
+ PDMPciDevSetCapabilityList(pPciDev, 0x60);
+# else
+ PDMPciDevSetCapabilityList(pPciDev, 0x50); /* ICH6 datasheet 18.1.16 */
+# endif
+
+ /// @todo r=michaln: If there are really no PDMPciDevSetXx for these, the
+ /// meaning of these values needs to be properly documented!
+ /* HDCTL off 0x40 bit 0 selects signaling mode (1-HDA, 0 - Ac97) 18.1.19 */
+ PDMPciDevSetByte( pPciDev, 0x40, 0x01);
+
+ /* Power Management */
+ PDMPciDevSetByte( pPciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM);
+ PDMPciDevSetByte( pPciDev, 0x50 + 1, 0x0); /* next */
+ PDMPciDevSetWord( pPciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ );
+
+# ifdef HDA_AS_PCI_EXPRESS
+ /* PCI Express */
+ PDMPciDevSetByte( pPciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */
+ PDMPciDevSetByte( pPciDev, 0x80 + 1, 0x60); /* next */
+ /* Device flags */
+ PDMPciDevSetWord( pPciDev, 0x80 + 2,
+ 1 /* version */
+ | (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) /* Root Complex Integrated Endpoint */
+ | (100 << 9) /* MSI */ );
+ /* Device capabilities */
+ PDMPciDevSetDWord( pPciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET);
+ /* Device control */
+ PDMPciDevSetWord( pPciDev, 0x80 + 8, 0);
+ /* Device status */
+ PDMPciDevSetWord( pPciDev, 0x80 + 10, 0);
+ /* Link caps */
+ PDMPciDevSetDWord( pPciDev, 0x80 + 12, 0);
+ /* Link control */
+ PDMPciDevSetWord( pPciDev, 0x80 + 16, 0);
+ /* Link status */
+ PDMPciDevSetWord( pPciDev, 0x80 + 18, 0);
+ /* Slot capabilities */
+ PDMPciDevSetDWord( pPciDev, 0x80 + 20, 0);
+ /* Slot control */
+ PDMPciDevSetWord( pPciDev, 0x80 + 24, 0);
+ /* Slot status */
+ PDMPciDevSetWord( pPciDev, 0x80 + 26, 0);
+ /* Root control */
+ PDMPciDevSetWord( pPciDev, 0x80 + 28, 0);
+ /* Root capabilities */
+ PDMPciDevSetWord( pPciDev, 0x80 + 30, 0);
+ /* Root status */
+ PDMPciDevSetDWord( pPciDev, 0x80 + 32, 0);
+ /* Device capabilities 2 */
+ PDMPciDevSetDWord( pPciDev, 0x80 + 36, 0);
+ /* Device control 2 */
+ PDMPciDevSetQWord( pPciDev, 0x80 + 40, 0);
+ /* Link control 2 */
+ PDMPciDevSetQWord( pPciDev, 0x80 + 48, 0);
+ /* Slot control 2 */
+ PDMPciDevSetWord( pPciDev, 0x80 + 56, 0);
+# endif /* HDA_AS_PCI_EXPRESS */
+
+ /*
+ * Register the PCI device.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ AssertRCReturn(rc, rc);
+
+ /** @todo r=bird: The IOMMMIO_FLAGS_READ_DWORD flag isn't entirely optimal,
+ * as several frequently used registers aren't dword sized. 6.0 and earlier
+ * will go to ring-3 to handle accesses to any such register, where-as 6.1 and
+ * later will do trivial register reads in ring-0. Real optimal code would use
+ * IOMMMIO_FLAGS_READ_PASSTHRU and do the necessary extra work to deal with
+ * anything the guest may throw at us. */
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_PASSTHRU, "HDA", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+# ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x60;
+ MsiReg.iMsiNextOffset = 0x50;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ /* That's OK, we can work without MSI */
+ PDMPciDevSetCapabilityList(pPciDev, 0x50);
+ }
+# endif
+
+ /* Create task for continuing CORB DMA in ring-3. */
+ rc = PDMDevHlpTaskCreate(pDevIns, PDMTASK_F_RZ, "HDA CORB DMA",
+ hdaR3CorbDmaTaskWorker, NULL /*pvUser*/, &pThis->hCorbDmaTask);
+ AssertRCReturn(rc,rc);
+
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, HDA_SAVED_STATE_VERSION, sizeof(*pThis), NULL /*pszBefore*/,
+ NULL /*pfnLivePrep*/, NULL /*pfnLiveExec*/, NULL /*pfnLiveVote*/,
+ NULL /*pfnSavePrep*/, hdaR3SaveExec, NULL /*pfnSaveDone*/,
+ NULL /*pfnLoadPrep*/, hdaR3LoadExec, hdaR3LoadDone);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach drivers. We ASSUME they are configured consecutively without any
+ * gaps, so we stop when we hit the first LUN w/o a driver configured.
+ */
+ for (unsigned iLun = 0; ; iLun++)
+ {
+ AssertBreak(iLun < UINT8_MAX);
+ LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun));
+ rc = hdaR3AttachInternal(pDevIns, pThis, pThisCC, iLun, NULL /* ppDrv */);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ LogFunc(("cLUNs=%u\n", iLun));
+ break;
+ }
+ AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
+ }
+
+ /*
+ * Create the mixer.
+ */
+ uint32_t fMixer = AUDMIXER_FLAGS_NONE;
+ if (pThisCC->Dbg.fEnabled)
+ fMixer |= AUDMIXER_FLAGS_DEBUG;
+ rc = AudioMixerCreate("HDA Mixer", fMixer, &pThisCC->pMixer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Add mixer output sinks.
+ */
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Front",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkFront.pMixSink);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Center+Subwoofer",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkCenterLFE.pMixSink);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Rear",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkRear.pMixSink);
+ AssertRCReturn(rc, rc);
+# else
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "PCM Output",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->SinkFront.pMixSink);
+ AssertRCReturn(rc, rc);
+# endif /* VBOX_WITH_AUDIO_HDA_51_SURROUND */
+
+ /*
+ * Add mixer input sinks.
+ */
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Line In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->SinkLineIn.pMixSink);
+ AssertRCReturn(rc, rc);
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Microphone In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->SinkMicIn.pMixSink);
+ AssertRCReturn(rc, rc);
+# endif
+
+ /* There is no master volume control. Set the master to max. */
+ PDMAUDIOVOLUME Vol = PDMAUDIOVOLUME_INITIALIZER_MAX;
+ rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize the codec.
+ */
+ /* Construct the common + R3 codec part. */
+ rc = hdaR3CodecConstruct(pDevIns, &pThisCC->Codec, 0 /* Codec index */, pCfg);
+ AssertRCReturn(rc, rc);
+
+ /* ICH6 datasheet defines 0 values for SVID and SID (18.1.14-15), which together with values returned for
+ verb F20 should provide device/codec recognition. */
+ Assert(pThisCC->Codec.Cfg.idVendor);
+ Assert(pThisCC->Codec.Cfg.idDevice);
+ PDMPciDevSetSubSystemVendorId(pPciDev, pThisCC->Codec.Cfg.idVendor); /* 2c ro - intel.) */
+ PDMPciDevSetSubSystemId( pPciDev, pThisCC->Codec.Cfg.idDevice); /* 2e ro. */
+
+ /*
+ * Create the per stream timers and the asso.
+ *
+ * We must the critical section for the timers as the device has a
+ * noop section associated with it.
+ *
+ * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's HDA driver relies
+ * on exact (virtual) DMA timing and uses DMA Position Buffers
+ * instead of the LPIB registers.
+ */
+ /** @todo r=bird: The need to use virtual sync is perhaps because TM
+ * doesn't schedule regular TMCLOCK_VIRTUAL timers as accurately as it
+ * should (VT-x preemption timer, etc). Hope to address that before
+ * long. @bugref{9943}. */
+ static const char * const s_apszNames[] =
+ { "HDA SD0", "HDA SD1", "HDA SD2", "HDA SD3", "HDA SD4", "HDA SD5", "HDA SD6", "HDA SD7", };
+ AssertCompile(RT_ELEMENTS(s_apszNames) == HDA_MAX_STREAMS);
+ for (size_t i = 0; i < HDA_MAX_STREAMS; i++)
+ {
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, (void *)(uintptr_t)i,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, s_apszNames[i], &pThis->aStreams[i].hTimer);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Create all hardware streams.
+ */
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i)
+ {
+ rc = hdaR3StreamConstruct(&pThis->aStreams[i], &pThisCC->aStreams[i], pThis, pThisCC, i /* u8SD */);
+ AssertRCReturn(rc, rc);
+ }
+
+ hdaR3Reset(pDevIns);
+
+ /*
+ * Info items and string formatter types. The latter is non-optional as
+ * the info handles use (at least some of) the custom types and we cannot
+ * accept screwing formatting.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA registers. (hda [register case-insensitive])", hdaR3DbgInfo);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdl",
+ "HDA buffer descriptor list (BDL) and DMA stream positions. (hdabdl [stream number])",
+ hdaR3DbgInfoBDL);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdastream", "HDA stream info. (hdastream [stream number])", hdaR3DbgInfoStream);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdcnodes", "HDA codec nodes.", hdaR3DbgInfoCodecNodes);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdcselector", "HDA codec's selector states [node number].", hdaR3DbgInfoCodecSelector);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdamixer", "HDA mixer state.", hdaR3DbgInfoMixer);
+
+ rc = RTStrFormatTypeRegister("sdctl", hdaR3StrFmtSDCTL, NULL);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
+ rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
+ /** @todo the next two are rather pointless. */
+ rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
+ rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS, ("%Rrc\n", rc), rc);
+
+ /*
+ * Asserting sanity.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->au32Regs) < 256 /* assumption by HDAREGDESC::idxReg */);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ {
+ struct HDAREGDESC const *pReg = &g_aHdaRegMap[i];
+ struct HDAREGDESC const *pNextReg = i + 1 < RT_ELEMENTS(g_aHdaRegMap) ? &g_aHdaRegMap[i + 1] : NULL;
+
+ /* binary search order. */
+ AssertReleaseMsg(!pNextReg || pReg->off + pReg->cb <= pNextReg->off,
+ ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n",
+ i, pReg->off, pReg->cb, i + 1, pNextReg->off, pNextReg->cb));
+
+ /* alignment. */
+ AssertReleaseMsg( pReg->cb == 1
+ || (pReg->cb == 2 && (pReg->off & 1) == 0)
+ || (pReg->cb == 3 && (pReg->off & 3) == 0)
+ || (pReg->cb == 4 && (pReg->off & 3) == 0),
+ ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb));
+
+ /* registers are packed into dwords - with 3 exceptions with gaps at the end of the dword. */
+ AssertRelease(((pReg->off + pReg->cb) & 3) == 0 || pNextReg);
+ if (pReg->off & 3)
+ {
+ struct HDAREGDESC const *pPrevReg = i > 0 ? &g_aHdaRegMap[i - 1] : NULL;
+ AssertReleaseMsg(pPrevReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb));
+ if (pPrevReg)
+ AssertReleaseMsg(pPrevReg->off + pPrevReg->cb == pReg->off,
+ ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n",
+ i - 1, pPrevReg->off, pPrevReg->cb, i + 1, pReg->off, pReg->cb));
+ }
+#if 0
+ if ((pReg->offset + pReg->size) & 3)
+ {
+ AssertReleaseMsg(pNextReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size));
+ if (pNextReg)
+ AssertReleaseMsg(pReg->offset + pReg->size == pNextReg->offset,
+ ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n",
+ i, pReg->offset, pReg->size, i + 1, pNextReg->offset, pNextReg->size));
+ }
+#endif
+ /* The final entry is a full DWORD, no gaps! Allows shortcuts. */
+ AssertReleaseMsg(pNextReg || ((pReg->off + pReg->cb) & 3) == 0,
+ ("[%#x] = {%#x LB %#x}\n", i, pReg->off, pReg->cb));
+ }
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++)
+ {
+ /* Valid alias index. */
+ uint32_t const idxAlias = g_aHdaRegAliases[i].idxAlias;
+ AssertReleaseMsg(g_aHdaRegAliases[i].idxAlias < (int)RT_ELEMENTS(g_aHdaRegMap), ("[%#x] idxAlias=%#x\n", i, idxAlias));
+ /* Same register alignment. */
+ AssertReleaseMsg((g_aHdaRegAliases[i].offReg & 3) == (g_aHdaRegMap[idxAlias].off & 3),
+ ("[%#x] idxAlias=%#x offReg=%#x vs off=%#x\n",
+ i, idxAlias, g_aHdaRegAliases[i].offReg, g_aHdaRegMap[idxAlias].off));
+ /* Register is four or fewer bytes wide (already checked above). */
+ AssertReleaseMsg(g_aHdaRegMap[idxAlias].cb <= 4, ("[%#x] idxAlias=%#x cb=%d\n", i, idxAlias, g_aHdaRegMap[idxAlias].cb));
+ }
+ Assert(strcmp(g_aHdaRegMap[HDA_REG_SSYNC].pszName, "SSYNC") == 0);
+ Assert(strcmp(g_aHdaRegMap[HDA_REG_DPUBASE].pszName, "DPUBASE") == 0);
+ Assert(strcmp(g_aHdaRegMap[HDA_REG_MLCH].pszName, "MLCH") == 0);
+ Assert(strcmp(g_aHdaRegMap[HDA_REG_SD3DPIB].pszName, "SD3DPIB") == 0);
+ Assert(strcmp(g_aHdaRegMap[HDA_REG_SD7EFIFOS].pszName, "SD7EFIFOS") == 0);
+
+ /*
+ * Register statistics.
+ */
+# ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "Input", STAMUNIT_TICKS_PER_CALL, "Profiling input.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "Output", STAMUNIT_TICKS_PER_CALL, "Profiling output.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead" , STAMUNIT_BYTES, "Bytes read (DMA) from the guest.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "BytesWritten", STAMUNIT_BYTES, "Bytes written (DMA) to the guest.");
+# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatAccessDmaOutput, STAMTYPE_COUNTER, "AccessDmaOutput", STAMUNIT_COUNT, "Number of on-register-access DMA sub-transfers we've made.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatAccessDmaOutputToR3,STAMTYPE_COUNTER, "AccessDmaOutputToR3", STAMUNIT_COUNT, "Number of time the on-register-access DMA forced a ring-3 return.");
+# endif
+
+ AssertCompile(RT_ELEMENTS(g_aHdaRegMap) == HDA_NUM_REGS);
+ AssertCompile(RT_ELEMENTS(pThis->aStatRegReads) == HDA_NUM_REGS);
+ AssertCompile(RT_ELEMENTS(pThis->aStatRegReadsToR3) == HDA_NUM_REGS);
+ AssertCompile(RT_ELEMENTS(pThis->aStatRegWrites) == HDA_NUM_REGS);
+ AssertCompile(RT_ELEMENTS(pThis->aStatRegWritesToR3) == HDA_NUM_REGS);
+ for (size_t i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReads[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Reads", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegReadsToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Reads-ToR3", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWrites[i], STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES,
+ g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Writes", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStatRegWritesToR3[i], STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ g_aHdaRegMap[i].pszDesc, "Regs/%03x-%s-Writes-ToR3", g_aHdaRegMap[i].off, g_aHdaRegMap[i].pszName);
+ }
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsR3, STAMTYPE_COUNTER, "RegMultiReadsR3", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-3");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiReadsRZ, STAMTYPE_COUNTER, "RegMultiReadsRZ", STAMUNIT_OCCURENCES, "Register read not targeting just one register, handled in ring-0");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesR3, STAMTYPE_COUNTER, "RegMultiWritesR3", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-3");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegMultiWritesRZ, STAMTYPE_COUNTER, "RegMultiWritesRZ", STAMUNIT_OCCURENCES, "Register writes not targeting just one register, handled in ring-0");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteR3, STAMTYPE_COUNTER, "RegSubWritesR3", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-3");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegSubWriteRZ, STAMTYPE_COUNTER, "RegSubWritesRZ", STAMUNIT_OCCURENCES, "Trucated register writes, handled in ring-0");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownReads, STAMTYPE_COUNTER, "RegUnknownReads", STAMUNIT_OCCURENCES, "Reads of unknown registers.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegUnknownWrites, STAMTYPE_COUNTER, "RegUnknownWrites", STAMUNIT_OCCURENCES, "Writes to unknown registers.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByReset, STAMTYPE_COUNTER, "RegWritesBlockedByReset", STAMUNIT_OCCURENCES, "Writes blocked by pending reset (GCTL/CRST)");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRegWritesBlockedByRun, STAMTYPE_COUNTER, "RegWritesBlockedByRun", STAMUNIT_OCCURENCES, "Writes blocked by byte RUN bit.");
+# endif
+
+ for (uint8_t idxStream = 0; idxStream < RT_ELEMENTS(pThisCC->aStreams); idxStream++)
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowProblems, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer problems.", "Stream%u/DMABufferProblems", idxStream);
+ if (hdaGetDirFromSD(idxStream) == PDMAUDIODIR_OUT)
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer overflows.", "Stream%u/DMABufferOverflows", idxStream);
+ else
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer underuns.", "Stream%u/DMABufferUnderruns", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrorBytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes of silence added to cope with underruns.", "Stream%u/DMABufferSilence", idxStream);
+ }
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedPendingBcis, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "DMA transfer period skipped because of BCIS pending.", "Stream%u/DMASkippedPendingBCIS", idxStream);
+
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer read position.", "Stream%u/offRead", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.cbCurDmaPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Bytes transfered per DMA timer callout.", "Stream%u/cbCurDmaPeriod", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, (void*)&pThis->aStreams[idxStream].State.fRunning, STAMTYPE_BOOL, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "True if the stream is in RUN mode.", "Stream%u/fRunning", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ,
+ "The stream frequency.", "Stream%u/Cfg/Hz", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The frame size.", "Stream%u/Cfg/FrameSize", idxStream);
+#if 0 /** @todo this would require some callback or expansion. */
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cChannelsX, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The number of channels.", "Stream%u/Cfg/Channels-Host", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Mapping.GuestProps.cChannels, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The number of channels.", "Stream%u/Cfg/Channels-Guest", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.Cfg.Props.cbSample, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The size of a sample (per channel).", "Stream%u/Cfg/cbSample", idxStream);
+#endif
+
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream);
+
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStart, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Starting the stream.", "Stream%u/Start", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStop, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Stopping the stream.", "Stream%u/Stop", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReset, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Resetting the stream.", "Stream%u/Reset", idxStream);
+ }
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) hdaRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PHDASTATE pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER0 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER0);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, hdaMmioWrite, hdaMmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+# if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */
+ /* Construct the R0 codec part. */
+ rc = hdaR0CodecConstruct(pDevIns, &pThis->Codec, &pThisCC->Codec);
+ AssertRCReturn(rc, rc);
+# else
+ RT_NOREF(pThisCC);
+# endif
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceHDA =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "hda",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
+ /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(HDASTATE),
+ /* .cbInstanceCC = */ CTX_EXPR(sizeof(HDASTATER3), sizeof(HDASTATER0), 0),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Intel HD Audio Controller",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ hdaR3Construct,
+ /* .pfnDestruct = */ hdaR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ hdaR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ hdaR3Attach,
+ /* .pfnDetach = */ hdaR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ hdaR3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ hdaRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ hdaRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/Audio/DevHda.h b/src/VBox/Devices/Audio/DevHda.h
new file mode 100644
index 00000000..5c3bad58
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHda.h
@@ -0,0 +1,918 @@
+/* $Id: DevHda.h $ */
+/** @file
+ * Intel HD Audio Controller Emulation - Structures.
+ */
+
+/*
+ * 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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h
+#define VBOX_INCLUDED_SRC_Audio_DevHda_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/path.h>
+#include <VBox/vmm/pdmdev.h>
+#include "AudioMixer.h"
+
+/*
+ * Compile time feature configuration.
+ */
+
+/** @def VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ * Enables doing DMA work on certain register accesses (LPIB, WALCLK) in
+ * addition to the DMA timer. All but the last frame can be done during
+ * register accesses (as we don't wish to leave the DMA timer w/o work to
+ * do in case that upsets it). */
+#if defined(DOXYGEN_RUNNING) || 0
+# define VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+#endif
+
+#ifdef DEBUG_andy
+/** Enables strict mode, which checks for stuff which isn't supposed to happen.
+ * Be prepared for assertions coming in! */
+//# define HDA_STRICT
+#endif
+
+/** @def HDA_AS_PCI_EXPRESS
+ * Enables PCI express hardware. */
+#if defined(DOXYGEN_RUNNING) || 0
+# define HDA_AS_PCI_EXPRESS
+#endif
+
+/** @def HDA_DEBUG_SILENCE
+ * To debug silence coming from the guest in form of audio gaps.
+ * Very crude implementation for now.
+ * @todo probably borked atm */
+#if defined(DOXYGEN_RUNNING) || 0
+# define HDA_DEBUG_SILENCE
+#endif
+
+
+/*
+ * Common pointer types.
+ */
+/** Pointer to an HDA stream (SDI / SDO). */
+typedef struct HDASTREAMR3 *PHDASTREAMR3;
+/** Pointer to a shared HDA device state. */
+typedef struct HDASTATE *PHDASTATE;
+/** Pointer to a ring-3 HDA device state. */
+typedef struct HDASTATER3 *PHDASTATER3;
+/** Pointer to an HDA mixer sink definition (ring-3). */
+typedef struct HDAMIXERSINK *PHDAMIXERSINK;
+
+
+/*
+ * The rest of the headers.
+ */
+#include "DevHdaStream.h"
+#include "DevHdaCodec.h"
+
+
+
+/** @name Stream counts.
+ *
+ * At the moment we support 4 input + 4 output streams max, which is 8 in total.
+ * Bidirectional streams are currently *not* supported.
+ *
+ * @note When changing any of those values, be prepared for some saved state
+ * fixups / trouble!
+ * @{
+ */
+#define HDA_MAX_SDI 4
+#define HDA_MAX_SDO 4
+#define HDA_MAX_STREAMS (HDA_MAX_SDI + HDA_MAX_SDO)
+/** @} */
+AssertCompile(HDA_MAX_SDI <= HDA_MAX_SDO);
+
+
+/** @defgroup grp_hda_regs HDA Register Definitions
+ *
+ * There are two variants for most register defines:
+ * - HDA_REG_XXX: Index into g_aHdaRegMap
+ * - HDA_RMX_XXX: Index into HDASTATE::au32Regs
+ *
+ * Use the HDA_REG and HDA_STREAM_REG macros to access registers where possible.
+ *
+ * @note The au32Regs[] layout is kept unchanged for saved state compatibility,
+ * thus the HDA_RMX_XXX assignments are for all purposes set in stone.
+ *
+ * @{ */
+
+/** Number of general registers. */
+#define HDA_NUM_GENERAL_REGS 36
+/** Number of stream registers (10 registers per stream). */
+#define HDA_NUM_STREAM_REGS (HDA_MAX_STREAMS * 10 /* Each stream descriptor has 10 registers */)
+/** Number of register after the stream registers. */
+#define HDA_NUM_POST_STREAM_REGS (2 + HDA_MAX_STREAMS * 2)
+/** Number of total registers in the HDA's register map. */
+#define HDA_NUM_REGS (HDA_NUM_GENERAL_REGS + HDA_NUM_STREAM_REGS + HDA_NUM_POST_STREAM_REGS)
+/** Total number of stream tags (channels). Index 0 is reserved / invalid. */
+#define HDA_MAX_TAGS 16
+
+
+/** Offset of the SD0 register map. */
+#define HDA_REG_DESC_SD0_BASE 0x80
+
+/* Registers */
+#define HDA_REG_IND_NAME(x) HDA_REG_##x
+#define HDA_MEM_IND_NAME(x) HDA_RMX_##x
+
+/** Direct register access by HDASTATE::au32Reg index. */
+#define HDA_REG_BY_IDX(a_pThis, a_idxReg) ((a_pThis)->au32Regs[(a_idxReg)])
+
+/** Accesses register @a ShortRegNm. */
+#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP)
+# define HDA_REG(a_pThis, a_ShortRegNm) (*hdaStrictRegAccessor(a_pThis, HDA_REG_IND_NAME(a_ShortRegNm), HDA_MEM_IND_NAME(a_ShortRegNm)))
+#else
+# define HDA_REG(a_pThis, a_ShortRegNm) HDA_REG_BY_IDX(a_pThis, HDA_MEM_IND_NAME(a_ShortRegNm))
+#endif
+
+/** Indirect register access via g_aHdaRegMap[].idxReg. */
+#define HDA_REG_IND(a_pThis, a_idxMap) HDA_REG_BY_IDX(a_pThis, g_aHdaRegMap[(a_idxMap)].idxReg)
+
+
+#define HDA_REG_GCAP 0 /* Range 0x00 - 0x01 */
+#define HDA_RMX_GCAP 0
+/**
+ * GCAP HDASpec 3.3.2 This macro encodes the following information about HDA in a compact manner:
+ *
+ * oss (15:12) - Number of output streams supported.
+ * iss (11:8) - Number of input streams supported.
+ * bss (7:3) - Number of bidirectional streams supported.
+ * bds (2:1) - Number of serial data out (SDO) signals supported.
+ * b64sup (0) - 64 bit addressing supported.
+ */
+#define HDA_MAKE_GCAP(oss, iss, bss, bds, b64sup) \
+ ( (((oss) & 0xF) << 12) \
+ | (((iss) & 0xF) << 8) \
+ | (((bss) & 0x1F) << 3) \
+ | (((bds) & 0x3) << 2) \
+ | ((b64sup) & 1))
+
+#define HDA_REG_VMIN 1 /* 0x02 */
+#define HDA_RMX_VMIN 1
+
+#define HDA_REG_VMAJ 2 /* 0x03 */
+#define HDA_RMX_VMAJ 2
+
+#define HDA_REG_OUTPAY 3 /* 0x04-0x05 */
+#define HDA_RMX_OUTPAY 3
+
+#define HDA_REG_INPAY 4 /* 0x06-0x07 */
+#define HDA_RMX_INPAY 4
+
+#define HDA_REG_GCTL 5 /* 0x08-0x0B */
+#define HDA_RMX_GCTL 5
+#define HDA_GCTL_UNSOL RT_BIT(8) /* Accept Unsolicited Response Enable */
+#define HDA_GCTL_FCNTRL RT_BIT(1) /* Flush Control */
+#define HDA_GCTL_CRST RT_BIT(0) /* Controller Reset */
+
+#define HDA_REG_WAKEEN 6 /* 0x0C */
+#define HDA_RMX_WAKEEN 6
+
+#define HDA_REG_STATESTS 7 /* 0x0E */
+#define HDA_RMX_STATESTS 7
+#define HDA_STATESTS_SCSF_MASK 0x7 /* State Change Status Flags (6.2.8). */
+
+#define HDA_REG_GSTS 8 /* 0x10-0x11*/
+#define HDA_RMX_GSTS 8
+#define HDA_GSTS_FSTS RT_BIT(1) /* Flush Status */
+
+#define HDA_REG_LLCH 9 /* 0x14 */
+#define HDA_RMX_LLCH 114
+
+#define HDA_REG_OUTSTRMPAY 10 /* 0x18 */
+#define HDA_RMX_OUTSTRMPAY 112
+
+#define HDA_REG_INSTRMPAY 11 /* 0x1a */
+#define HDA_RMX_INSTRMPAY 113
+
+#define HDA_REG_INTCTL 12 /* 0x20 */
+#define HDA_RMX_INTCTL 9
+#define HDA_INTCTL_GIE RT_BIT(31) /* Global Interrupt Enable */
+#define HDA_INTCTL_CIE RT_BIT(30) /* Controller Interrupt Enable */
+/** Bits 0-29 correspond to streams 0-29. */
+#define HDA_STRMINT_MASK 0xFF /* Streams 0-7 implemented. Applies to INTCTL and INTSTS. */
+
+#define HDA_REG_INTSTS 13 /* 0x24 */
+#define HDA_RMX_INTSTS 10
+#define HDA_INTSTS_GIS RT_BIT(31) /* Global Interrupt Status */
+#define HDA_INTSTS_CIS RT_BIT(30) /* Controller Interrupt Status */
+
+#define HDA_REG_WALCLK 14 /* 0x30 */
+/* NB: HDA_RMX_WALCLK is not defined because the register is not stored in memory. */
+
+/**
+ * @note The HDA specification defines a SSYNC register at offset 0x38. The ICH6/ICH9
+ * datahseet defines SSYNC at offset 0x34. The Linux HDA driver matches the datasheet.
+ * See also https://mailman.alsa-project.org/pipermail/alsa-devel/2011-March/037819.html
+ */
+#define HDA_REG_SSYNC 15 /* 0x34 */
+#define HDA_RMX_SSYNC 12
+
+#define HDA_REG_NEW_SSYNC 16 /* 0x38 */
+#define HDA_RMX_NEW_SSYNC HDA_RMX_SSYNC
+
+#define HDA_REG_CORBLBASE 17 /* 0x40 */
+#define HDA_RMX_CORBLBASE 13
+
+#define HDA_REG_CORBUBASE 18 /* 0x44 */
+#define HDA_RMX_CORBUBASE 14
+
+#define HDA_REG_CORBWP 19 /* 0x48 */
+#define HDA_RMX_CORBWP 15
+
+#define HDA_REG_CORBRP 20 /* 0x4A */
+#define HDA_RMX_CORBRP 16
+#define HDA_CORBRP_RST RT_BIT(15) /* CORB Read Pointer Reset */
+
+#define HDA_REG_CORBCTL 21 /* 0x4C */
+#define HDA_RMX_CORBCTL 17
+#define HDA_CORBCTL_DMA RT_BIT(1) /* Enable CORB DMA Engine */
+#define HDA_CORBCTL_CMEIE RT_BIT(0) /* CORB Memory Error Interrupt Enable */
+
+#define HDA_REG_CORBSTS 22 /* 0x4D */
+#define HDA_RMX_CORBSTS 18
+
+#define HDA_REG_CORBSIZE 23 /* 0x4E */
+#define HDA_RMX_CORBSIZE 19
+#define HDA_CORBSIZE_SZ_CAP 0xF0
+#define HDA_CORBSIZE_SZ 0x3
+
+/** Number of CORB buffer entries. */
+#define HDA_CORB_SIZE 256
+/** CORB element size (in bytes). */
+#define HDA_CORB_ELEMENT_SIZE 4
+/** Number of RIRB buffer entries. */
+#define HDA_RIRB_SIZE 256
+/** RIRB element size (in bytes). */
+#define HDA_RIRB_ELEMENT_SIZE 8
+
+#define HDA_REG_RIRBLBASE 24 /* 0x50 */
+#define HDA_RMX_RIRBLBASE 20
+
+#define HDA_REG_RIRBUBASE 25 /* 0x54 */
+#define HDA_RMX_RIRBUBASE 21
+
+#define HDA_REG_RIRBWP 26 /* 0x58 */
+#define HDA_RMX_RIRBWP 22
+#define HDA_RIRBWP_RST RT_BIT(15) /* RIRB Write Pointer Reset */
+
+#define HDA_REG_RINTCNT 27 /* 0x5A */
+#define HDA_RMX_RINTCNT 23
+
+/** Maximum number of Response Interrupts. */
+#define HDA_MAX_RINTCNT 256
+
+#define HDA_REG_RIRBCTL 28 /* 0x5C */
+#define HDA_RMX_RIRBCTL 24
+#define HDA_RIRBCTL_ROIC RT_BIT(2) /* Response Overrun Interrupt Control */
+#define HDA_RIRBCTL_RDMAEN RT_BIT(1) /* RIRB DMA Enable */
+#define HDA_RIRBCTL_RINTCTL RT_BIT(0) /* Response Interrupt Control */
+
+#define HDA_REG_RIRBSTS 29 /* 0x5D */
+#define HDA_RMX_RIRBSTS 25
+#define HDA_RIRBSTS_RIRBOIS RT_BIT(2) /* Response Overrun Interrupt Status */
+#define HDA_RIRBSTS_RINTFL RT_BIT(0) /* Response Interrupt Flag */
+
+#define HDA_REG_RIRBSIZE 30 /* 0x5E */
+#define HDA_RMX_RIRBSIZE 26
+
+#define HDA_REG_IC 31 /* 0x60 */
+#define HDA_RMX_IC 27
+
+#define HDA_REG_IR 32 /* 0x64 */
+#define HDA_RMX_IR 28
+
+#define HDA_REG_IRS 33 /* 0x68 */
+#define HDA_RMX_IRS 29
+#define HDA_IRS_IRV RT_BIT(1) /* Immediate Result Valid */
+#define HDA_IRS_ICB RT_BIT(0) /* Immediate Command Busy */
+
+#define HDA_REG_DPLBASE 34 /* 0x70 */
+#define HDA_RMX_DPLBASE 30
+
+#define HDA_REG_DPUBASE 35 /* 0x74 */
+#define HDA_RMX_DPUBASE 31
+
+#define DPBASE_ADDR_MASK (~(uint64_t)0x7f)
+
+#define HDA_STREAM_REG_DEF(name, num) (HDA_REG_SD##num##name)
+#define HDA_STREAM_RMX_DEF(name, num) (HDA_RMX_SD##num##name)
+/** @note sdnum here _MUST_ be stream reg number [0,7]. */
+#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP)
+# define HDA_STREAM_REG(pThis, name, sdnum) (*hdaStrictStreamRegAccessor((pThis), HDA_REG_SD0##name, HDA_RMX_SD0##name, (sdnum)))
+#else
+# define HDA_STREAM_REG(pThis, name, sdnum) (HDA_REG_BY_IDX((pThis), HDA_RMX_SD0##name + (sdnum) * 10))
+#endif
+
+#define HDA_SD_NUM_FROM_REG(pThis, func, reg) ((reg - HDA_STREAM_REG_DEF(func, 0)) / 10)
+#define HDA_SD_TO_REG(a_Name, uSD) (HDA_STREAM_REG_DEF(a_Name, 0) + (uSD) * 10)
+
+/** @todo Condense marcos! */
+
+#define HDA_REG_SD0CTL HDA_NUM_GENERAL_REGS /* 0x80; other streams offset by 0x20 */
+#define HDA_RMX_SD0CTL 32
+#define HDA_RMX_SD1CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 10)
+#define HDA_RMX_SD2CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 20)
+#define HDA_RMX_SD3CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 30)
+#define HDA_RMX_SD4CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 40)
+#define HDA_RMX_SD5CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 50)
+#define HDA_RMX_SD6CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 60)
+#define HDA_RMX_SD7CTL (HDA_STREAM_RMX_DEF(CTL, 0) + 70)
+
+#define HDA_SDCTL_NUM_MASK 0xF
+#define HDA_SDCTL_NUM_SHIFT 20
+#define HDA_SDCTL_DIR RT_BIT(19) /* Direction (Bidirectional streams only!) */
+#define HDA_SDCTL_TP RT_BIT(18) /* Traffic Priority (PCI Express) */
+#define HDA_SDCTL_STRIPE_MASK 0x3
+#define HDA_SDCTL_STRIPE_SHIFT 16
+#define HDA_SDCTL_DEIE RT_BIT(4) /* Descriptor Error Interrupt Enable */
+#define HDA_SDCTL_FEIE RT_BIT(3) /* FIFO Error Interrupt Enable */
+#define HDA_SDCTL_IOCE RT_BIT(2) /* Interrupt On Completion Enable */
+#define HDA_SDCTL_RUN RT_BIT(1) /* Stream Run */
+#define HDA_SDCTL_SRST RT_BIT(0) /* Stream Reset */
+
+#define HDA_REG_SD0STS (HDA_NUM_GENERAL_REGS + 1) /* 0x83; other streams offset by 0x20 */
+#define HDA_RMX_SD0STS 33
+#define HDA_RMX_SD1STS (HDA_STREAM_RMX_DEF(STS, 0) + 10)
+#define HDA_RMX_SD2STS (HDA_STREAM_RMX_DEF(STS, 0) + 20)
+#define HDA_RMX_SD3STS (HDA_STREAM_RMX_DEF(STS, 0) + 30)
+#define HDA_RMX_SD4STS (HDA_STREAM_RMX_DEF(STS, 0) + 40)
+#define HDA_RMX_SD5STS (HDA_STREAM_RMX_DEF(STS, 0) + 50)
+#define HDA_RMX_SD6STS (HDA_STREAM_RMX_DEF(STS, 0) + 60)
+#define HDA_RMX_SD7STS (HDA_STREAM_RMX_DEF(STS, 0) + 70)
+
+#define HDA_SDSTS_FIFORDY RT_BIT(5) /* FIFO Ready */
+#define HDA_SDSTS_DESE RT_BIT(4) /* Descriptor Error */
+#define HDA_SDSTS_FIFOE RT_BIT(3) /* FIFO Error */
+#define HDA_SDSTS_BCIS RT_BIT(2) /* Buffer Completion Interrupt Status */
+
+#define HDA_REG_SD0LPIB (HDA_NUM_GENERAL_REGS + 2) /* 0x84; other streams offset by 0x20 */
+#define HDA_REG_SD1LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 10) /* 0xA4 */
+#define HDA_REG_SD2LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 20) /* 0xC4 */
+#define HDA_REG_SD3LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 30) /* 0xE4 */
+#define HDA_REG_SD4LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 40) /* 0x104 */
+#define HDA_REG_SD5LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 50) /* 0x124 */
+#define HDA_REG_SD6LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 60) /* 0x144 */
+#define HDA_REG_SD7LPIB (HDA_STREAM_REG_DEF(LPIB, 0) + 70) /* 0x164 */
+#define HDA_RMX_SD0LPIB 34
+#define HDA_RMX_SD1LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 10)
+#define HDA_RMX_SD2LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 20)
+#define HDA_RMX_SD3LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 30)
+#define HDA_RMX_SD4LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 40)
+#define HDA_RMX_SD5LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 50)
+#define HDA_RMX_SD6LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 60)
+#define HDA_RMX_SD7LPIB (HDA_STREAM_RMX_DEF(LPIB, 0) + 70)
+
+#define HDA_REG_SD0CBL (HDA_NUM_GENERAL_REGS + 3) /* 0x88; other streams offset by 0x20 */
+#define HDA_RMX_SD0CBL 35
+#define HDA_RMX_SD1CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 10)
+#define HDA_RMX_SD2CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 20)
+#define HDA_RMX_SD3CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 30)
+#define HDA_RMX_SD4CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 40)
+#define HDA_RMX_SD5CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 50)
+#define HDA_RMX_SD6CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 60)
+#define HDA_RMX_SD7CBL (HDA_STREAM_RMX_DEF(CBL, 0) + 70)
+
+#define HDA_REG_SD0LVI (HDA_NUM_GENERAL_REGS + 4) /* 0x8C; other streams offset by 0x20 */
+#define HDA_RMX_SD0LVI 36
+#define HDA_RMX_SD1LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 10)
+#define HDA_RMX_SD2LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 20)
+#define HDA_RMX_SD3LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 30)
+#define HDA_RMX_SD4LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 40)
+#define HDA_RMX_SD5LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 50)
+#define HDA_RMX_SD6LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 60)
+#define HDA_RMX_SD7LVI (HDA_STREAM_RMX_DEF(LVI, 0) + 70)
+
+#define HDA_REG_SD0FIFOW (HDA_NUM_GENERAL_REGS + 5) /* 0x8E; other streams offset by 0x20 */
+#define HDA_RMX_SD0FIFOW 37
+#define HDA_RMX_SD1FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 10)
+#define HDA_RMX_SD2FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 20)
+#define HDA_RMX_SD3FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 30)
+#define HDA_RMX_SD4FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 40)
+#define HDA_RMX_SD5FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 50)
+#define HDA_RMX_SD6FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 60)
+#define HDA_RMX_SD7FIFOW (HDA_STREAM_RMX_DEF(FIFOW, 0) + 70)
+
+/*
+ * ICH6 datasheet defined limits for FIFOW values (18.2.38).
+ */
+#define HDA_SDFIFOW_8B 0x2
+#define HDA_SDFIFOW_16B 0x3
+#define HDA_SDFIFOW_32B 0x4
+
+#define HDA_REG_SD0FIFOS (HDA_NUM_GENERAL_REGS + 6) /* 0x90; other streams offset by 0x20 */
+#define HDA_RMX_SD0FIFOS 38
+#define HDA_RMX_SD1FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 10)
+#define HDA_RMX_SD2FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 20)
+#define HDA_RMX_SD3FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 30)
+#define HDA_RMX_SD4FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 40)
+#define HDA_RMX_SD5FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 50)
+#define HDA_RMX_SD6FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 60)
+#define HDA_RMX_SD7FIFOS (HDA_STREAM_RMX_DEF(FIFOS, 0) + 70)
+
+/* The ICH6 datasheet defines limits for FIFOS registers (18.2.39).
+ Formula: size - 1
+ Other values not listed are not supported. */
+
+#define HDA_SDIFIFO_120B 0x77 /* 8-, 16-, 20-, 24-, 32-bit Input Streams */
+#define HDA_SDIFIFO_160B 0x9F /* 20-, 24-bit Input Streams Streams */
+
+#define HDA_SDOFIFO_16B 0x0F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */
+#define HDA_SDOFIFO_32B 0x1F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */
+#define HDA_SDOFIFO_64B 0x3F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */
+#define HDA_SDOFIFO_128B 0x7F /* 8-, 16-, 20-, 24-, 32-bit Output Streams */
+#define HDA_SDOFIFO_192B 0xBF /* 8-, 16-, 20-, 24-, 32-bit Output Streams */
+#define HDA_SDOFIFO_256B 0xFF /* 20-, 24-bit Output Streams */
+
+#define HDA_REG_SD0FMT (HDA_NUM_GENERAL_REGS + 7) /* 0x92; other streams offset by 0x20 */
+#define HDA_RMX_SD0FMT 39
+#define HDA_RMX_SD1FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 10)
+#define HDA_RMX_SD2FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 20)
+#define HDA_RMX_SD3FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 30)
+#define HDA_RMX_SD4FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 40)
+#define HDA_RMX_SD5FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 50)
+#define HDA_RMX_SD6FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 60)
+#define HDA_RMX_SD7FMT (HDA_STREAM_RMX_DEF(FMT, 0) + 70)
+
+#define HDA_REG_SD0BDPL (HDA_NUM_GENERAL_REGS + 8) /* 0x98; other streams offset by 0x20 */
+#define HDA_RMX_SD0BDPL 40
+#define HDA_RMX_SD1BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 10)
+#define HDA_RMX_SD2BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 20)
+#define HDA_RMX_SD3BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 30)
+#define HDA_RMX_SD4BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 40)
+#define HDA_RMX_SD5BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 50)
+#define HDA_RMX_SD6BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 60)
+#define HDA_RMX_SD7BDPL (HDA_STREAM_RMX_DEF(BDPL, 0) + 70)
+
+#define HDA_REG_SD0BDPU (HDA_NUM_GENERAL_REGS + 9) /* 0x9C; other streams offset by 0x20 */
+#define HDA_RMX_SD0BDPU 41
+#define HDA_RMX_SD1BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 10)
+#define HDA_RMX_SD2BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 20)
+#define HDA_RMX_SD3BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 30)
+#define HDA_RMX_SD4BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 40)
+#define HDA_RMX_SD5BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 50)
+#define HDA_RMX_SD6BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 60)
+#define HDA_RMX_SD7BDPU (HDA_STREAM_RMX_DEF(BDPU, 0) + 70)
+
+#define HDA_CODEC_CAD_SHIFT 28
+/** Encodes the (required) LUN into a codec command. */
+#define HDA_CODEC_CMD(cmd, lun) ((cmd) | (lun << HDA_CODEC_CAD_SHIFT))
+
+#define HDA_SDFMT_NON_PCM_SHIFT 15
+#define HDA_SDFMT_NON_PCM_MASK 0x1
+#define HDA_SDFMT_BASE_RATE_SHIFT 14
+#define HDA_SDFMT_BASE_RATE_MASK 0x1
+#define HDA_SDFMT_MULT_SHIFT 11
+#define HDA_SDFMT_MULT_MASK 0x7
+#define HDA_SDFMT_DIV_SHIFT 8
+#define HDA_SDFMT_DIV_MASK 0x7
+#define HDA_SDFMT_BITS_SHIFT 4
+#define HDA_SDFMT_BITS_MASK 0x7
+#define HDA_SDFMT_CHANNELS_MASK 0xF
+
+#define HDA_SDFMT_TYPE RT_BIT(15)
+#define HDA_SDFMT_TYPE_PCM (0)
+#define HDA_SDFMT_TYPE_NON_PCM (1)
+
+#define HDA_SDFMT_BASE RT_BIT(14)
+#define HDA_SDFMT_BASE_48KHZ (0)
+#define HDA_SDFMT_BASE_44KHZ (1)
+
+#define HDA_SDFMT_MULT_1X (0)
+#define HDA_SDFMT_MULT_2X (1)
+#define HDA_SDFMT_MULT_3X (2)
+#define HDA_SDFMT_MULT_4X (3)
+
+#define HDA_SDFMT_DIV_1X (0)
+#define HDA_SDFMT_DIV_2X (1)
+#define HDA_SDFMT_DIV_3X (2)
+#define HDA_SDFMT_DIV_4X (3)
+#define HDA_SDFMT_DIV_5X (4)
+#define HDA_SDFMT_DIV_6X (5)
+#define HDA_SDFMT_DIV_7X (6)
+#define HDA_SDFMT_DIV_8X (7)
+
+#define HDA_SDFMT_8_BIT (0)
+#define HDA_SDFMT_16_BIT (1)
+#define HDA_SDFMT_20_BIT (2)
+#define HDA_SDFMT_24_BIT (3)
+#define HDA_SDFMT_32_BIT (4)
+
+#define HDA_SDFMT_CHAN_MONO (0)
+#define HDA_SDFMT_CHAN_STEREO (1)
+
+/** Emits a SDnFMT register format.
+ * Also being used in the codec's converter format. */
+#define HDA_SDFMT_MAKE(_afNonPCM, _aBaseRate, _aMult, _aDiv, _aBits, _aChan) \
+ ( (((_afNonPCM) & HDA_SDFMT_NON_PCM_MASK) << HDA_SDFMT_NON_PCM_SHIFT) \
+ | (((_aBaseRate) & HDA_SDFMT_BASE_RATE_MASK) << HDA_SDFMT_BASE_RATE_SHIFT) \
+ | (((_aMult) & HDA_SDFMT_MULT_MASK) << HDA_SDFMT_MULT_SHIFT) \
+ | (((_aDiv) & HDA_SDFMT_DIV_MASK) << HDA_SDFMT_DIV_SHIFT) \
+ | (((_aBits) & HDA_SDFMT_BITS_MASK) << HDA_SDFMT_BITS_SHIFT) \
+ | ( (_aChan) & HDA_SDFMT_CHANNELS_MASK))
+
+
+/* Post stream registers: */
+#define HDA_REG_MLCH (HDA_NUM_GENERAL_REGS + HDA_NUM_STREAM_REGS) /* 0xc00 */
+#define HDA_RMX_MLCH 115
+#define HDA_REG_MLCD (HDA_REG_MLCH + 1) /* 0xc04 */
+#define HDA_RMX_MLCD 116
+
+/* Registers added/specific-to skylake/broxton: */
+#define HDA_SD_NUM_FROM_SKYLAKE_REG(a_Name, a_iMap) (((a_iMap) - HDA_STREAM_REG_DEF(a_Name, 0)) / 2)
+
+#define HDA_REG_SD0DPIB (HDA_REG_MLCD + 1) /* 0x1084 */
+#define HDA_REG_SD1DPIB (HDA_REG_SD0DPIB + 1*2)
+#define HDA_REG_SD2DPIB (HDA_REG_SD0DPIB + 2*2)
+#define HDA_REG_SD3DPIB (HDA_REG_SD0DPIB + 3*2)
+#define HDA_REG_SD4DPIB (HDA_REG_SD0DPIB + 4*2)
+#define HDA_REG_SD5DPIB (HDA_REG_SD0DPIB + 5*2)
+#define HDA_REG_SD6DPIB (HDA_REG_SD0DPIB + 6*2)
+#define HDA_REG_SD7DPIB (HDA_REG_SD0DPIB + 7*2)
+
+#define HDA_RMX_SD0DPIB HDA_RMX_SD0LPIB
+#define HDA_RMX_SD1DPIB HDA_RMX_SD1LPIB
+#define HDA_RMX_SD2DPIB HDA_RMX_SD2LPIB
+#define HDA_RMX_SD3DPIB HDA_RMX_SD3LPIB
+#define HDA_RMX_SD4DPIB HDA_RMX_SD4LPIB
+#define HDA_RMX_SD5DPIB HDA_RMX_SD5LPIB
+#define HDA_RMX_SD6DPIB HDA_RMX_SD6LPIB
+#define HDA_RMX_SD7DPIB HDA_RMX_SD7LPIB
+
+#define HDA_REG_SD0EFIFOS (HDA_REG_SD0DPIB + 1) /* 0x1094 */
+#define HDA_REG_SD1EFIFOS (HDA_REG_SD0EFIFOS + 1*2)
+#define HDA_REG_SD2EFIFOS (HDA_REG_SD0EFIFOS + 2*2)
+#define HDA_REG_SD3EFIFOS (HDA_REG_SD0EFIFOS + 3*2)
+#define HDA_REG_SD4EFIFOS (HDA_REG_SD0EFIFOS + 4*2)
+#define HDA_REG_SD5EFIFOS (HDA_REG_SD0EFIFOS + 5*2)
+#define HDA_REG_SD6EFIFOS (HDA_REG_SD0EFIFOS + 6*2)
+#define HDA_REG_SD7EFIFOS (HDA_REG_SD0EFIFOS + 7*2)
+
+#define HDA_RMX_SD0EFIFOS 117
+#define HDA_RMX_SD1EFIFOS (HDA_RMX_SD0EFIFOS + 1)
+#define HDA_RMX_SD2EFIFOS (HDA_RMX_SD0EFIFOS + 2)
+#define HDA_RMX_SD3EFIFOS (HDA_RMX_SD0EFIFOS + 3)
+#define HDA_RMX_SD4EFIFOS (HDA_RMX_SD0EFIFOS + 4)
+#define HDA_RMX_SD5EFIFOS (HDA_RMX_SD0EFIFOS + 5)
+#define HDA_RMX_SD6EFIFOS (HDA_RMX_SD0EFIFOS + 6)
+#define HDA_RMX_SD7EFIFOS (HDA_RMX_SD0EFIFOS + 7)
+
+/** @} */ /* grp_hda_regs */
+
+
+/**
+ * Buffer descriptor list entry (BDLE).
+ *
+ * See 3.6.3 in HDA specs rev 1.0a (2010-06-17).
+ */
+typedef struct HDABDLEDESC
+{
+ /** Starting address of the actual buffer. Must be 128-bit aligned. */
+ uint64_t u64BufAddr;
+ /** Size of the actual buffer (in bytes). */
+ uint32_t u32BufSize;
+ /** HDA_BDLE_F_XXX.
+ *
+ * Bit 0: IOC - Interrupt on completion / HDA_BDLE_F_IOC.
+ * The controller will generate an interrupt when the last byte of the buffer
+ * has been fetched by the DMA engine.
+ *
+ * Bits 31:1 are reserved for further use and must be 0. */
+ uint32_t fFlags;
+} HDABDLEDESC, *PHDABDLEDESC;
+AssertCompileSize(HDABDLEDESC, 16); /* Always 16 byte. Also must be aligned on 128-byte boundary. */
+
+/** Interrupt on completion (IOC) flag. */
+#define HDA_BDLE_F_IOC RT_BIT(0)
+
+
+/**
+ * HDA mixer sink definition (ring-3).
+ *
+ * Its purpose is to know which audio mixer sink is bound to which SDn
+ * (SDI/SDO) device stream.
+ *
+ * This is needed in order to handle interleaved streams (that is, multiple
+ * channels in one stream) or non-interleaved streams (each channel has a
+ * dedicated stream).
+ *
+ * This is only known to the actual device emulation level.
+ */
+typedef struct HDAMIXERSINK
+{
+ R3PTRTYPE(PHDASTREAM) pStreamShared;
+ R3PTRTYPE(PHDASTREAMR3) pStreamR3;
+ /** Pointer to the actual audio mixer sink. */
+ R3PTRTYPE(PAUDMIXSINK) pMixSink;
+} HDAMIXERSINK;
+
+/**
+ * Mapping a stream tag to an HDA stream (ring-3).
+ */
+typedef struct HDATAG
+{
+ /** Own stream tag. */
+ uint8_t uTag;
+ uint8_t Padding[7];
+ /** Pointer to associated stream. */
+ R3PTRTYPE(PHDASTREAMR3) pStreamR3;
+} HDATAG;
+/** Pointer to a HDA stream tag mapping. */
+typedef HDATAG *PHDATAG;
+
+/**
+ * Shared ICH Intel HD audio controller state.
+ */
+typedef struct HDASTATE
+{
+ /** Critical section protecting the HDA state. */
+ PDMCRITSECT CritSect;
+ /** Internal stream states (aligned on 64 byte boundrary). */
+ HDASTREAM aStreams[HDA_MAX_STREAMS];
+ /** The HDA's register set. */
+ uint32_t au32Regs[HDA_NUM_REGS];
+ /** CORB buffer base address. */
+ uint64_t u64CORBBase;
+ /** RIRB buffer base address. */
+ uint64_t u64RIRBBase;
+ /** DMA base address.
+ * Made out of DPLBASE + DPUBASE (3.3.32 + 3.3.33). */
+ uint64_t u64DPBase;
+ /** Size in bytes of CORB buffer (#au32CorbBuf). */
+ uint32_t cbCorbBuf;
+ /** Size in bytes of RIRB buffer (#au64RirbBuf). */
+ uint32_t cbRirbBuf;
+ /** Response Interrupt Count (RINTCNT). */
+ uint16_t u16RespIntCnt;
+ /** DMA position buffer enable bit. */
+ bool fDMAPosition;
+ /** Current IRQ level. */
+ uint8_t u8IRQL;
+ /** Config: Internal input DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeInMs config value. */
+ uint16_t cMsCircBufIn;
+ /** Config: Internal output DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeOutMs config value. */
+ uint16_t cMsCircBufOut;
+ /** The start time of the wall clock (WALCLK), measured on the virtual sync clock. */
+ uint64_t tsWalClkStart;
+ /** CORB DMA task handle.
+ * We use this when there is stuff we cannot handle in ring-0. */
+ PDMTASKHANDLE hCorbDmaTask;
+ /** The CORB buffer. */
+ uint32_t au32CorbBuf[HDA_CORB_SIZE];
+ /** Pointer to RIRB buffer. */
+ uint64_t au64RirbBuf[HDA_RIRB_SIZE];
+
+ /** PCI Region \#0: 16KB of MMIO stuff. */
+ IOMMMIOHANDLE hMmio;
+
+#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ STAMCOUNTER StatAccessDmaOutput;
+ STAMCOUNTER StatAccessDmaOutputToR3;
+#endif
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatIn;
+ STAMPROFILE StatOut;
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+
+ /** @name Register statistics.
+ * The array members run parallel to g_aHdaRegMap.
+ * @{ */
+ STAMCOUNTER aStatRegReads[HDA_NUM_REGS];
+ STAMCOUNTER aStatRegReadsToR3[HDA_NUM_REGS];
+ STAMCOUNTER aStatRegWrites[HDA_NUM_REGS];
+ STAMCOUNTER aStatRegWritesToR3[HDA_NUM_REGS];
+ STAMCOUNTER StatRegMultiReadsRZ;
+ STAMCOUNTER StatRegMultiReadsR3;
+ STAMCOUNTER StatRegMultiWritesRZ;
+ STAMCOUNTER StatRegMultiWritesR3;
+ STAMCOUNTER StatRegSubWriteRZ;
+ STAMCOUNTER StatRegSubWriteR3;
+ STAMCOUNTER StatRegUnknownReads;
+ STAMCOUNTER StatRegUnknownWrites;
+ STAMCOUNTER StatRegWritesBlockedByReset;
+ STAMCOUNTER StatRegWritesBlockedByRun;
+ /** @} */
+#endif
+
+#ifdef DEBUG
+ /** Debug stuff.
+ * @todo Make STAM values out some of this? */
+ struct
+ {
+# if 0 /* unused */
+ /** Timestamp (in ns) of the last timer callback (hdaTimer).
+ * Used to calculate the time actually elapsed between two timer callbacks. */
+ uint64_t tsTimerLastCalledNs;
+# endif
+ /** IRQ debugging information. */
+ struct
+ {
+ /** Timestamp (in ns) of last processed (asserted / deasserted) IRQ. */
+ uint64_t tsProcessedLastNs;
+ /** Timestamp (in ns) of last asserted IRQ. */
+ uint64_t tsAssertedNs;
+# if 0 /* unused */
+ /** How many IRQs have been asserted already. */
+ uint64_t cAsserted;
+ /** Accumulated elapsed time (in ns) of all IRQ being asserted. */
+ uint64_t tsAssertedTotalNs;
+ /** Timestamp (in ns) of last deasserted IRQ. */
+ uint64_t tsDeassertedNs;
+ /** How many IRQs have been deasserted already. */
+ uint64_t cDeasserted;
+ /** Accumulated elapsed time (in ns) of all IRQ being deasserted. */
+ uint64_t tsDeassertedTotalNs;
+# endif
+ } IRQ;
+ } Dbg;
+#endif
+ /** This is for checking that the build was correctly configured in all contexts.
+ * This is set to HDASTATE_ALIGNMENT_CHECK_MAGIC. */
+ uint64_t uAlignmentCheckMagic;
+} HDASTATE;
+AssertCompileMemberAlignment(HDASTATE, aStreams, 64);
+/** Pointer to a shared HDA device state. */
+typedef HDASTATE *PHDASTATE;
+
+/** Value for HDASTATE:uAlignmentCheckMagic. */
+#define HDASTATE_ALIGNMENT_CHECK_MAGIC UINT64_C(0x1298afb75893e059)
+
+/**
+ * Ring-0 ICH Intel HD audio controller state.
+ */
+typedef struct HDASTATER0
+{
+# if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */
+ /** Pointer to HDA codec to use. */
+ HDACODECR0 Codec;
+# else
+ uint32_t u32Dummy;
+# endif
+} HDASTATER0;
+/** Pointer to a ring-0 HDA device state. */
+typedef HDASTATER0 *PHDASTATER0;
+
+/**
+ * Ring-3 ICH Intel HD audio controller state.
+ */
+typedef struct HDASTATER3
+{
+ /** Internal stream states. */
+ HDASTREAMR3 aStreams[HDA_MAX_STREAMS];
+ /** Mapping table between stream tags and stream states. */
+ HDATAG aTags[HDA_MAX_TAGS];
+ /** R3 Pointer to the device instance. */
+ PPDMDEVINSR3 pDevIns;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** List of associated LUN drivers (HDADRIVER). */
+ RTLISTANCHORR3 lstDrv;
+ /** The device' software mixer. */
+ R3PTRTYPE(PAUDIOMIXER) pMixer;
+ /** HDA sink for (front) output. */
+ HDAMIXERSINK SinkFront;
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ /** HDA sink for center / LFE output. */
+ HDAMIXERSINK SinkCenterLFE;
+ /** HDA sink for rear output. */
+ HDAMIXERSINK SinkRear;
+#endif
+ /** HDA mixer sink for line input. */
+ HDAMIXERSINK SinkLineIn;
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ /** Audio mixer sink for microphone input. */
+ HDAMIXERSINK SinkMicIn;
+#endif
+ /** Debug stuff. */
+ struct
+ {
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ /** Path where to dump the debug output to.
+ * Can be NULL, in which the system's temporary directory will be used then. */
+ R3PTRTYPE(char *) pszOutPath;
+ } Dbg;
+ /** Align the codec state on a cache line. */
+ uint64_t au64Padding[3];
+ /** The HDA codec state. */
+ HDACODECR3 Codec;
+} HDASTATER3;
+AssertCompileMemberAlignment(HDASTATER3, Codec, 64);
+
+
+/** Pointer to the context specific HDA state (HDASTATER3 or HDASTATER0). */
+typedef CTX_SUFF(PHDASTATE) PHDASTATECC;
+
+
+/** @def HDA_PROCESS_INTERRUPT
+ * Wrapper around hdaProcessInterrupt that supplies the source function name
+ * string in logging builds. */
+#if defined(LOG_ENABLED) || defined(DOXYGEN_RUNNING)
+void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis, const char *pszSource);
+# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis), __FUNCTION__)
+#else
+void hdaProcessInterrupt(PPDMDEVINS pDevIns, PHDASTATE pThis);
+# define HDA_PROCESS_INTERRUPT(a_pDevIns, a_pThis) hdaProcessInterrupt((a_pDevIns), (a_pThis))
+#endif
+
+/**
+ * Returns the audio direction of a specified stream descriptor.
+ *
+ * The register layout specifies that input streams (SDI) come first,
+ * followed by the output streams (SDO). So every stream ID below HDA_MAX_SDI
+ * is an input stream, whereas everything >= HDA_MAX_SDI is an output stream.
+ *
+ * @note SDnFMT register does not provide that information, so we have to judge
+ * for ourselves.
+ *
+ * @return Audio direction.
+ * @param uSD The stream number.
+ */
+DECLINLINE(PDMAUDIODIR) hdaGetDirFromSD(uint8_t uSD)
+{
+ if (uSD < HDA_MAX_SDI)
+ return PDMAUDIODIR_IN;
+ AssertReturn(uSD < HDA_MAX_STREAMS, PDMAUDIODIR_UNKNOWN);
+ return PDMAUDIODIR_OUT;
+}
+
+/* Used by hdaR3StreamSetUp: */
+uint8_t hdaSDFIFOWToBytes(uint16_t u16RegFIFOW);
+
+#if defined(VBOX_STRICT) && defined(VBOX_HDA_CAN_ACCESS_REG_MAP)
+/* Only in DevHda.cpp: */
+DECLINLINE(uint32_t *) hdaStrictRegAccessor(PHDASTATE pThis, uint32_t idxMap, uint32_t idxReg);
+DECLINLINE(uint32_t *) hdaStrictStreamRegAccessor(PHDASTATE pThis, uint32_t idxMap0, uint32_t idxReg0, size_t idxStream);
+#endif /* VBOX_STRICT && VBOX_HDA_CAN_ACCESS_REG_MAP */
+
+
+/** @name HDA device functions used by the codec.
+ * @{ */
+DECLHIDDEN(int) hdaR3MixerAddStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PCPDMAUDIOSTREAMCFG pCfg);
+DECLHIDDEN(int) hdaR3MixerRemoveStream(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate);
+DECLHIDDEN(int) hdaR3MixerControl(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel);
+DECLHIDDEN(int) hdaR3MixerSetVolume(PHDACODECR3 pCodec, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol);
+/** @} */
+
+
+/** @name Saved state versions for the HDA device
+ * @{ */
+/** The current staved state version.
+ * @note Only for the registration call. Never used for tests. */
+#define HDA_SAVED_STATE_VERSION HDA_SAVED_STATE_WITHOUT_PERIOD
+
+/** Removed period and redefined wall clock. */
+#define HDA_SAVED_STATE_WITHOUT_PERIOD 8
+/** Added (Controller): Current wall clock value (this independent from WALCLK register value).
+ * Added (Controller): Current IRQ level.
+ * Added (Per stream): Ring buffer. This is optional and can be skipped if (not) needed.
+ * Added (Per stream): Struct g_aSSMStreamStateFields7.
+ * Added (Per stream): Struct g_aSSMStreamPeriodFields7.
+ * Added (Current BDLE per stream): Struct g_aSSMBDLEDescFields7.
+ * Added (Current BDLE per stream): Struct g_aSSMBDLEStateFields7. */
+#define HDA_SAVED_STATE_VERSION_7 7
+/** Saves the current BDLE state.
+ * @since 5.0.14 (r104839) */
+#define HDA_SAVED_STATE_VERSION_6 6
+/** Introduced dynamic number of streams + stream identifiers for serialization.
+ * Bug: Did not save the BDLE states correctly.
+ * Those will be skipped on load then.
+ * @since 5.0.12 (r104520) */
+#define HDA_SAVED_STATE_VERSION_5 5
+/** Since this version the number of MMIO registers can be flexible. */
+#define HDA_SAVED_STATE_VERSION_4 4
+#define HDA_SAVED_STATE_VERSION_3 3
+#define HDA_SAVED_STATE_VERSION_2 2
+#define HDA_SAVED_STATE_VERSION_1 1
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DevHda_h */
+
diff --git a/src/VBox/Devices/Audio/DevHdaCodec.cpp b/src/VBox/Devices/Audio/DevHdaCodec.cpp
new file mode 100644
index 00000000..9782ac7f
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHdaCodec.cpp
@@ -0,0 +1,2893 @@
+/* $Id: DevHdaCodec.cpp $ */
+/** @file
+ * Intel HD Audio Controller Emulation - Codec, Sigmatel/IDT STAC9220.
+ *
+ * Implemented based on the Intel HD Audio specification and the
+ * Sigmatel/IDT STAC9220 datasheet.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA_CODEC
+#include <VBox/log.h>
+
+#include <VBox/AssertGuest.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+#include <iprt/cpp/utils.h>
+
+#include "VBoxDD.h"
+#include "AudioMixer.h"
+#include "DevHda.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define AMPLIFIER_IN 0
+#define AMPLIFIER_OUT 1
+#define AMPLIFIER_LEFT 1
+#define AMPLIFIER_RIGHT 0
+#define AMPLIFIER_REGISTER(amp, inout, side, index) ((amp)[30*(inout) + 15*(side) + (index)])
+
+
+/** @name STAC9220 - Nodes IDs / Names.
+ * @{ */
+#define STAC9220_NID_ROOT 0x0 /* Root node */
+#define STAC9220_NID_AFG 0x1 /* Audio Configuration Group */
+#define STAC9220_NID_DAC0 0x2 /* Out */
+#define STAC9220_NID_DAC1 0x3 /* Out */
+#define STAC9220_NID_DAC2 0x4 /* Out */
+#define STAC9220_NID_DAC3 0x5 /* Out */
+#define STAC9220_NID_ADC0 0x6 /* In */
+#define STAC9220_NID_ADC1 0x7 /* In */
+#define STAC9220_NID_SPDIF_OUT 0x8 /* Out */
+#define STAC9220_NID_SPDIF_IN 0x9 /* In */
+/** Also known as PIN_A. */
+#define STAC9220_NID_PIN_HEADPHONE0 0xA /* In, Out */
+#define STAC9220_NID_PIN_B 0xB /* In, Out */
+#define STAC9220_NID_PIN_C 0xC /* In, Out */
+/** Also known as PIN D. */
+#define STAC9220_NID_PIN_HEADPHONE1 0xD /* In, Out */
+#define STAC9220_NID_PIN_E 0xE /* In */
+#define STAC9220_NID_PIN_F 0xF /* In, Out */
+/** Also known as DIGOUT0. */
+#define STAC9220_NID_PIN_SPDIF_OUT 0x10 /* Out */
+/** Also known as DIGIN. */
+#define STAC9220_NID_PIN_SPDIF_IN 0x11 /* In */
+#define STAC9220_NID_ADC0_MUX 0x12 /* In */
+#define STAC9220_NID_ADC1_MUX 0x13 /* In */
+#define STAC9220_NID_PCBEEP 0x14 /* Out */
+#define STAC9220_NID_PIN_CD 0x15 /* In */
+#define STAC9220_NID_VOL_KNOB 0x16
+#define STAC9220_NID_AMP_ADC0 0x17 /* In */
+#define STAC9220_NID_AMP_ADC1 0x18 /* In */
+/* Only for STAC9221. */
+#define STAC9221_NID_ADAT_OUT 0x19 /* Out */
+#define STAC9221_NID_I2S_OUT 0x1A /* Out */
+#define STAC9221_NID_PIN_I2S_OUT 0x1B /* Out */
+
+/** Number of total nodes emulated. */
+#define STAC9221_NUM_NODES 0x1C
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/**
+ * A codec verb descriptor.
+ */
+typedef struct CODECVERB
+{
+ /** Verb. */
+ uint32_t uVerb;
+ /** Verb mask. */
+ uint32_t fMask;
+ /**
+ * Function pointer for implementation callback.
+ *
+ * This is always a valid pointer in ring-3, while elsewhere a NULL indicates
+ * that we must return to ring-3 to process it.
+ *
+ * @returns VBox status code (99.9% is VINF_SUCCESS, caller doesn't care much
+ * what you return at present).
+ *
+ * @param pThis The shared codec intance data.
+ * @param uCmd The command.
+ * @param puResp Where to return the response value.
+ *
+ * @thread EMT or task worker thread (see HDASTATE::hCorbDmaTask).
+ */
+ DECLCALLBACKMEMBER(int, pfn, (PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp));
+ /** Friendly name, for debugging. */
+ const char *pszName;
+} CODECVERB;
+/** Pointer to a const codec verb descriptor. */
+typedef CODECVERB const *PCCODECVERB;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name STAC9220 Node Classifications.
+ * @note Referenced through STAC9220WIDGET in the constructor below.
+ * @{ */
+static uint8_t const g_abStac9220Ports[] = { STAC9220_NID_PIN_HEADPHONE0, STAC9220_NID_PIN_B, STAC9220_NID_PIN_C, STAC9220_NID_PIN_HEADPHONE1, STAC9220_NID_PIN_E, STAC9220_NID_PIN_F, 0 };
+static uint8_t const g_abStac9220Dacs[] = { STAC9220_NID_DAC0, STAC9220_NID_DAC1, STAC9220_NID_DAC2, STAC9220_NID_DAC3, 0 };
+static uint8_t const g_abStac9220Adcs[] = { STAC9220_NID_ADC0, STAC9220_NID_ADC1, 0 };
+static uint8_t const g_abStac9220SpdifOuts[] = { STAC9220_NID_SPDIF_OUT, 0 };
+static uint8_t const g_abStac9220SpdifIns[] = { STAC9220_NID_SPDIF_IN, 0 };
+static uint8_t const g_abStac9220DigOutPins[] = { STAC9220_NID_PIN_SPDIF_OUT, 0 };
+static uint8_t const g_abStac9220DigInPins[] = { STAC9220_NID_PIN_SPDIF_IN, 0 };
+static uint8_t const g_abStac9220AdcVols[] = { STAC9220_NID_AMP_ADC0, STAC9220_NID_AMP_ADC1, 0 };
+static uint8_t const g_abStac9220AdcMuxs[] = { STAC9220_NID_ADC0_MUX, STAC9220_NID_ADC1_MUX, 0 };
+static uint8_t const g_abStac9220Pcbeeps[] = { STAC9220_NID_PCBEEP, 0 };
+static uint8_t const g_abStac9220Cds[] = { STAC9220_NID_PIN_CD, 0 };
+static uint8_t const g_abStac9220VolKnobs[] = { STAC9220_NID_VOL_KNOB, 0 };
+/** @} */
+
+/** @name STAC 9221 Values.
+ * @note Referenced through STAC9220WIDGET in the constructor below
+ * @{ */
+/** @todo Is STAC9220_NID_SPDIF_IN really correct for reserved nodes? */
+static uint8_t const g_abStac9220Reserveds[] = { STAC9220_NID_SPDIF_IN, STAC9221_NID_ADAT_OUT, STAC9221_NID_I2S_OUT, STAC9221_NID_PIN_I2S_OUT, 0 };
+/** @} */
+
+
+/** SSM description of CODECCOMMONNODE. */
+static SSMFIELD const g_aCodecNodeFields[] =
+{
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID),
+ SSMFIELD_ENTRY_PAD_HC_AUTO(3, 3),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** Backward compatibility with v1 of CODECCOMMONNODE. */
+static SSMFIELD const g_aCodecNodeFieldsV1[] =
+{
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.uID),
+ SSMFIELD_ENTRY_PAD_HC_AUTO(3, 7),
+ SSMFIELD_ENTRY_OLD_HCPTR(Core.name),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F00_param),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, Core.au32F02_param),
+ SSMFIELD_ENTRY( CODECSAVEDSTATENODE, au32Params),
+ SSMFIELD_ENTRY_TERM()
+};
+
+
+
+/*********************************************************************************************************************************
+* STAC9220 Constructor / Reset *
+*********************************************************************************************************************************/
+
+/**
+ * Resets a single node of the codec.
+ *
+ * @param pThis HDA codec of node to reset.
+ * @param uNID Node ID to set node to.
+ * @param pNode Node to reset.
+ * @param fInReset Set if we're called from hdaCodecReset via
+ * stac9220Reset, clear if called from stac9220Construct.
+ */
+static void stac9220NodeReset(PHDACODECR3 pThis, uint8_t uNID, PCODECNODE pNode, bool const fInReset)
+{
+ LogFlowFunc(("NID=0x%x (%RU8)\n", uNID, uNID));
+
+ if ( !fInReset
+ && ( uNID != STAC9220_NID_ROOT
+ && uNID != STAC9220_NID_AFG)
+ )
+ {
+ RT_ZERO(pNode->node);
+ }
+
+ /* Set common parameters across all nodes. */
+ pNode->node.uID = uNID;
+ pNode->node.uSD = 0;
+
+ switch (uNID)
+ {
+ /* Root node. */
+ case STAC9220_NID_ROOT:
+ {
+ /* Set the revision ID. */
+ pNode->root.node.au32F00_param[0x02] = CODEC_MAKE_F00_02(0x1, 0x0, 0x3, 0x4, 0x0, 0x1);
+ break;
+ }
+
+ /*
+ * AFG (Audio Function Group).
+ */
+ case STAC9220_NID_AFG:
+ {
+ pNode->afg.node.au32F00_param[0x08] = CODEC_MAKE_F00_08(1, 0xd, 0xd);
+ /* We set the AFG's PCM capabitilies fixed to 16kHz, 22.5kHz + 44.1kHz, 16-bit signed. */
+ pNode->afg.node.au32F00_param[0x0A] = CODEC_F00_0A_44_1KHZ /* 44.1 kHz */
+ | CODEC_F00_0A_44_1KHZ_1_2X /* Messed up way of saying 22.05 kHz */
+ | CODEC_F00_0A_48KHZ_1_3X /* Messed up way of saying 16 kHz. */
+ | CODEC_F00_0A_16_BIT; /* 16-bit signed */
+ /* Note! We do not set CODEC_F00_0A_48KHZ here because we end up with
+ S/PDIF output showing up in windows and it trying to configure
+ streams other than 0 and 4 and stuff going sideways in the
+ stream setup/removal area. */
+ pNode->afg.node.au32F00_param[0x0B] = CODEC_F00_0B_PCM;
+ pNode->afg.node.au32F00_param[0x0C] = CODEC_MAKE_F00_0C(0x17)
+ | CODEC_F00_0C_CAP_BALANCED_IO
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT
+ | CODEC_F00_0C_CAP_TRIGGER_REQUIRED
+ | CODEC_F00_0C_CAP_IMPENDANCE_SENSE;
+
+ /* Default input amplifier capabilities. */
+ pNode->node.au32F00_param[0x0D] = CODEC_MAKE_F00_0D(CODEC_AMP_CAP_MUTE,
+ CODEC_AMP_STEP_SIZE,
+ CODEC_AMP_NUM_STEPS,
+ CODEC_AMP_OFF_INITIAL);
+ /* Default output amplifier capabilities. */
+ pNode->node.au32F00_param[0x12] = CODEC_MAKE_F00_12(CODEC_AMP_CAP_MUTE,
+ CODEC_AMP_STEP_SIZE,
+ CODEC_AMP_NUM_STEPS,
+ CODEC_AMP_OFF_INITIAL);
+
+ pNode->afg.node.au32F00_param[0x11] = CODEC_MAKE_F00_11(1, 1, 0, 0, 4);
+ pNode->afg.node.au32F00_param[0x0F] = CODEC_F00_0F_D3
+ | CODEC_F00_0F_D2
+ | CODEC_F00_0F_D1
+ | CODEC_F00_0F_D0;
+
+ pNode->afg.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D2, CODEC_F05_D2); /* PS-Act: D2, PS->Set D2. */
+ pNode->afg.u32F08_param = 0;
+ pNode->afg.u32F17_param = 0;
+ break;
+ }
+
+ /*
+ * DACs.
+ */
+ case STAC9220_NID_DAC0: /* DAC0: Headphones 0 + 1 */
+ case STAC9220_NID_DAC1: /* DAC1: PIN C */
+ case STAC9220_NID_DAC2: /* DAC2: PIN B */
+ case STAC9220_NID_DAC3: /* DAC3: PIN F */
+ {
+ pNode->dac.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ,
+ HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT,
+ HDA_SDFMT_CHAN_STEREO);
+
+ /* 7.3.4.6: Audio widget capabilities. */
+ pNode->dac.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 13, 0)
+ | CODEC_F00_09_CAP_L_R_SWAP
+ | CODEC_F00_09_CAP_POWER_CTRL
+ | CODEC_F00_09_CAP_OUT_AMP_PRESENT
+ | CODEC_F00_09_CAP_STEREO;
+
+ /* Connection list; must be 0 if the only connection for the widget is
+ * to the High Definition Audio Link. */
+ pNode->dac.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 0 /* Entries */);
+
+ pNode->dac.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3);
+
+ RT_ZERO(pNode->dac.B_params);
+ AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) = 0x7F | RT_BIT(7);
+ AMPLIFIER_REGISTER(pNode->dac.B_params, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) = 0x7F | RT_BIT(7);
+ break;
+ }
+
+ /*
+ * ADCs.
+ */
+ case STAC9220_NID_ADC0: /* Analog input. */
+ {
+ pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC0;
+ goto adc_init;
+ }
+
+ case STAC9220_NID_ADC1: /* Analog input (CD). */
+ {
+ pNode->node.au32F02_param[0] = STAC9220_NID_AMP_ADC1;
+
+ /* Fall through is intentional. */
+ adc_init:
+
+ pNode->adc.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ,
+ HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT,
+ HDA_SDFMT_CHAN_STEREO);
+
+ pNode->adc.u32F03_param = RT_BIT(0);
+ pNode->adc.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 Set: D3 */
+
+ pNode->adc.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 0xD, 0)
+ | CODEC_F00_09_CAP_POWER_CTRL
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_PROC_WIDGET
+ | CODEC_F00_09_CAP_STEREO;
+ /* Connection list entries. */
+ pNode->adc.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+ break;
+ }
+
+ /*
+ * SP/DIF In/Out.
+ */
+ case STAC9220_NID_SPDIF_OUT:
+ {
+ pNode->spdifout.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ,
+ HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT,
+ HDA_SDFMT_CHAN_STEREO);
+ pNode->spdifout.u32F06_param = 0;
+ pNode->spdifout.u32F0d_param = 0;
+
+ pNode->spdifout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 4, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_FMT_OVERRIDE
+ | CODEC_F00_09_CAP_STEREO;
+
+ /* Use a fixed format from AFG. */
+ pNode->spdifout.node.au32F00_param[0xA] = pThis->aNodes[STAC9220_NID_AFG].node.au32F00_param[0xA];
+ pNode->spdifout.node.au32F00_param[0xB] = CODEC_F00_0B_PCM;
+ break;
+ }
+
+ case STAC9220_NID_SPDIF_IN:
+ {
+ pNode->spdifin.u32A_param = CODEC_MAKE_A(HDA_SDFMT_TYPE_PCM, HDA_SDFMT_BASE_44KHZ,
+ HDA_SDFMT_MULT_1X, HDA_SDFMT_DIV_2X, HDA_SDFMT_16_BIT,
+ HDA_SDFMT_CHAN_STEREO);
+
+ pNode->spdifin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_INPUT, 4, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_FMT_OVERRIDE
+ | CODEC_F00_09_CAP_STEREO;
+
+ /* Use a fixed format from AFG. */
+ pNode->spdifin.node.au32F00_param[0xA] = pThis->aNodes[STAC9220_NID_AFG].node.au32F00_param[0xA];
+ pNode->spdifin.node.au32F00_param[0xB] = CODEC_F00_0B_PCM;
+
+ /* Connection list entries. */
+ pNode->spdifin.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+ pNode->spdifin.node.au32F02_param[0] = 0x11;
+ break;
+ }
+
+ /*
+ * PINs / Ports.
+ */
+ case STAC9220_NID_PIN_HEADPHONE0: /* Port A: Headphone in/out (front). */
+ {
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /*fPresent*/, CODEC_F09_ANALOG_NA);
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17)
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT
+ | CODEC_F00_0C_CAP_HEADPHONE_AMP
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT
+ | CODEC_F00_0C_CAP_TRIGGER_REQUIRED;
+
+ /* Connection list entry 0: Goes to DAC0. */
+ pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC0;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_FRONT,
+ CODEC_F1C_DEVICE_HP,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_GREEN,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_1, 0x0 /* Seq */);
+ goto port_init;
+ }
+
+ case STAC9220_NID_PIN_B: /* Port B: Rear CLFE (Center / Subwoofer). */
+ {
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA);
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17)
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT
+ | CODEC_F00_0C_CAP_TRIGGER_REQUIRED;
+
+ /* Connection list entry 0: Goes to DAC2. */
+ pNode->port.node.au32F02_param[0] = STAC9220_NID_DAC2;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_REAR,
+ CODEC_F1C_DEVICE_SPEAKER,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_BLACK,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_0, 0x1 /* Seq */);
+ goto port_init;
+ }
+
+ case STAC9220_NID_PIN_C: /* Rear Speaker. */
+ {
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA);
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17)
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT
+ | CODEC_F00_0C_CAP_TRIGGER_REQUIRED;
+
+ /* Connection list entry 0: Goes to DAC1. */
+ pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC1;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_REAR,
+ CODEC_F1C_DEVICE_SPEAKER,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_GREEN,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_0, 0x0 /* Seq */);
+ goto port_init;
+ }
+
+ case STAC9220_NID_PIN_HEADPHONE1: /* Also known as PIN_D. */
+ {
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /*fPresent*/, CODEC_F09_ANALOG_NA);
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_MAKE_F00_0C(0x17)
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT
+ | CODEC_F00_0C_CAP_HEADPHONE_AMP
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT
+ | CODEC_F00_0C_CAP_TRIGGER_REQUIRED;
+
+ /* Connection list entry 0: Goes to DAC1. */
+ pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC0;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_FRONT,
+ CODEC_F1C_DEVICE_MIC,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_PINK,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */);
+ /* Fall through is intentional. */
+
+ port_init:
+
+ pNode->port.u32F07_param = CODEC_F07_IN_ENABLE
+ | CODEC_F07_OUT_ENABLE;
+ pNode->port.u32F08_param = 0;
+
+ pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_UNSOL
+ | CODEC_F00_09_CAP_STEREO;
+ /* Connection list entries. */
+ pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+ break;
+ }
+
+ case STAC9220_NID_PIN_E:
+ {
+ pNode->port.u32F07_param = CODEC_F07_IN_ENABLE;
+ pNode->port.u32F08_param = 0;
+ /* If Line in is reported as enabled, OS X sees no speakers! Windows does
+ * not care either way, although Linux does.
+ */
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(0 /* fPresent */, 0);
+
+ pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_UNSOL
+ | CODEC_F00_09_CAP_STEREO;
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_REAR,
+ CODEC_F1C_DEVICE_LINE_IN,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_BLUE,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_4, 0x1 /* Seq */);
+ break;
+ }
+
+ case STAC9220_NID_PIN_F:
+ {
+ pNode->port.u32F07_param = CODEC_F07_IN_ENABLE | CODEC_F07_OUT_ENABLE;
+ pNode->port.u32F08_param = 0;
+ pNode->port.u32F09_param = CODEC_MAKE_F09_ANALOG(1 /* fPresent */, CODEC_F09_ANALOG_NA);
+
+ pNode->port.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_UNSOL
+ | CODEC_F00_09_CAP_OUT_AMP_PRESENT
+ | CODEC_F00_09_CAP_STEREO;
+
+ pNode->port.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_OUTPUT;
+
+ /* Connection list entry 0: Goes to DAC3. */
+ pNode->port.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+ pNode->port.node.au32F02_param[0x0] = STAC9220_NID_DAC3;
+
+ if (!fInReset)
+ pNode->port.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_INTERNAL,
+ CODEC_F1C_DEVICE_SPEAKER,
+ CODEC_F1C_CONNECTION_TYPE_1_8INCHES,
+ CODEC_F1C_COLOR_ORANGE,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_0, 0x2 /* Seq */);
+ break;
+ }
+
+ case STAC9220_NID_PIN_SPDIF_OUT: /* Rear SPDIF Out. */
+ {
+ pNode->digout.u32F07_param = CODEC_F07_OUT_ENABLE;
+ pNode->digout.u32F09_param = 0;
+
+ pNode->digout.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_STEREO;
+ pNode->digout.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT;
+
+ /* Connection list entries. */
+ pNode->digout.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 3 /* Entries */);
+ pNode->digout.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_SPDIF_OUT,
+ STAC9220_NID_AMP_ADC0, STAC9221_NID_ADAT_OUT, 0);
+ if (!fInReset)
+ pNode->digout.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_REAR,
+ CODEC_F1C_DEVICE_SPDIF_OUT,
+ CODEC_F1C_CONNECTION_TYPE_DIN,
+ CODEC_F1C_COLOR_BLACK,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_2, 0x0 /* Seq */);
+ break;
+ }
+
+ case STAC9220_NID_PIN_SPDIF_IN:
+ {
+ pNode->digin.u32F05_param = CODEC_MAKE_F05(0, 0, 0, CODEC_F05_D3, CODEC_F05_D3); /* PS-Act: D3 -> D3 */
+ pNode->digin.u32F07_param = CODEC_F07_IN_ENABLE;
+ pNode->digin.u32F08_param = 0;
+ pNode->digin.u32F09_param = CODEC_MAKE_F09_DIGITAL(0, 0);
+ pNode->digin.u32F0c_param = 0;
+
+ pNode->digin.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 3, 0)
+ | CODEC_F00_09_CAP_POWER_CTRL
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_UNSOL
+ | CODEC_F00_09_CAP_STEREO;
+
+ pNode->digin.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_EAPD
+ | CODEC_F00_0C_CAP_INPUT
+ | CODEC_F00_0C_CAP_PRESENCE_DETECT;
+ if (!fInReset)
+ pNode->digin.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_COMPLEX,
+ CODEC_F1C_LOCATION_REAR,
+ CODEC_F1C_DEVICE_SPDIF_IN,
+ CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL,
+ CODEC_F1C_COLOR_BLACK,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_5, 0x0 /* Seq */);
+ break;
+ }
+
+ case STAC9220_NID_ADC0_MUX:
+ {
+ pNode->adcmux.u32F01_param = 0; /* Connection select control index (STAC9220_NID_PIN_E). */
+ goto adcmux_init;
+ }
+
+ case STAC9220_NID_ADC1_MUX:
+ {
+ pNode->adcmux.u32F01_param = 1; /* Connection select control index (STAC9220_NID_PIN_CD). */
+ /* Fall through is intentional. */
+
+ adcmux_init:
+
+ pNode->adcmux.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0)
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE
+ | CODEC_F00_09_CAP_OUT_AMP_PRESENT
+ | CODEC_F00_09_CAP_STEREO;
+
+ pNode->adcmux.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 27, 4, 0);
+
+ /* Connection list entries. */
+ pNode->adcmux.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 7 /* Entries */);
+ pNode->adcmux.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_E,
+ STAC9220_NID_PIN_CD,
+ STAC9220_NID_PIN_F,
+ STAC9220_NID_PIN_B);
+ pNode->adcmux.node.au32F02_param[0x4] = RT_MAKE_U32_FROM_U8(STAC9220_NID_PIN_C,
+ STAC9220_NID_PIN_HEADPHONE1,
+ STAC9220_NID_PIN_HEADPHONE0,
+ 0x0 /* Unused */);
+
+ /* STAC 9220 v10 6.21-22.{4,5} both(left and right) out amplifiers initialized with 0. */
+ RT_ZERO(pNode->adcmux.B_params);
+ break;
+ }
+
+ case STAC9220_NID_PCBEEP:
+ {
+ pNode->pcbeep.u32F0a_param = 0;
+
+ pNode->pcbeep.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_BEEP_GEN, 0, 0)
+ | CODEC_F00_09_CAP_AMP_FMT_OVERRIDE
+ | CODEC_F00_09_CAP_OUT_AMP_PRESENT;
+ pNode->pcbeep.node.au32F00_param[0xD] = CODEC_MAKE_F00_0D(0, 17, 3, 3);
+
+ RT_ZERO(pNode->pcbeep.B_params);
+ break;
+ }
+
+ case STAC9220_NID_PIN_CD:
+ {
+ pNode->cdnode.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_STEREO;
+ pNode->cdnode.node.au32F00_param[0xC] = CODEC_F00_0C_CAP_INPUT;
+
+ if (!fInReset)
+ pNode->cdnode.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_FIXED,
+ CODEC_F1C_LOCATION_INTERNAL,
+ CODEC_F1C_DEVICE_CD,
+ CODEC_F1C_CONNECTION_TYPE_ATAPI,
+ CODEC_F1C_COLOR_UNKNOWN,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_4, 0x2 /* Seq */);
+ break;
+ }
+
+ case STAC9220_NID_VOL_KNOB:
+ {
+ pNode->volumeKnob.u32F08_param = 0;
+ pNode->volumeKnob.u32F0f_param = 0x7f;
+
+ pNode->volumeKnob.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VOLUME_KNOB, 0, 0);
+ pNode->volumeKnob.node.au32F00_param[0xD] = RT_BIT(7) | 0x7F;
+
+ /* Connection list entries. */
+ pNode->volumeKnob.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 4 /* Entries */);
+ pNode->volumeKnob.node.au32F02_param[0x0] = RT_MAKE_U32_FROM_U8(STAC9220_NID_DAC0,
+ STAC9220_NID_DAC1,
+ STAC9220_NID_DAC2,
+ STAC9220_NID_DAC3);
+ break;
+ }
+
+ case STAC9220_NID_AMP_ADC0: /* ADC0Vol */
+ {
+ pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC0_MUX;
+ goto adcvol_init;
+ }
+
+ case STAC9220_NID_AMP_ADC1: /* ADC1Vol */
+ {
+ pNode->adcvol.node.au32F02_param[0] = STAC9220_NID_ADC1_MUX;
+ /* Fall through is intentional. */
+
+ adcvol_init:
+
+ pNode->adcvol.node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_SELECTOR, 0, 0)
+ | CODEC_F00_09_CAP_L_R_SWAP
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_IN_AMP_PRESENT
+ | CODEC_F00_09_CAP_STEREO;
+
+
+ pNode->adcvol.node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+
+ RT_ZERO(pNode->adcvol.B_params);
+ AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_LEFT, 0) = RT_BIT(7);
+ AMPLIFIER_REGISTER(pNode->adcvol.B_params, AMPLIFIER_IN, AMPLIFIER_RIGHT, 0) = RT_BIT(7);
+ break;
+ }
+
+ /*
+ * STAC9221 nodes.
+ */
+
+ case STAC9221_NID_ADAT_OUT:
+ {
+ pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_VENDOR_DEFINED, 3, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_STEREO;
+ break;
+ }
+
+ case STAC9221_NID_I2S_OUT:
+ {
+ pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_AUDIO_OUTPUT, 3, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_STEREO;
+ break;
+ }
+
+ case STAC9221_NID_PIN_I2S_OUT:
+ {
+ pNode->node.au32F00_param[0x9] = CODEC_MAKE_F00_09(CODEC_F00_09_TYPE_PIN_COMPLEX, 0, 0)
+ | CODEC_F00_09_CAP_DIGITAL
+ | CODEC_F00_09_CAP_CONNECTION_LIST
+ | CODEC_F00_09_CAP_STEREO;
+
+ pNode->node.au32F00_param[0xC] = CODEC_F00_0C_CAP_OUTPUT;
+
+ /* Connection list entries. */
+ pNode->node.au32F00_param[0xE] = CODEC_MAKE_F00_0E(CODEC_F00_0E_LIST_NID_SHORT, 1 /* Entries */);
+ pNode->node.au32F02_param[0] = STAC9221_NID_I2S_OUT;
+
+ if (!fInReset)
+ pNode->reserved.u32F1c_param = CODEC_MAKE_F1C(CODEC_F1C_PORT_NO_PHYS,
+ CODEC_F1C_LOCATION_NA,
+ CODEC_F1C_DEVICE_LINE_OUT,
+ CODEC_F1C_CONNECTION_TYPE_UNKNOWN,
+ CODEC_F1C_COLOR_UNKNOWN,
+ CODEC_F1C_MISC_NONE,
+ CODEC_F1C_ASSOCIATION_GROUP_15, 0x0 /* Ignored */);
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Node %RU8 not implemented\n", uNID));
+ break;
+ }
+}
+
+
+/**
+ * Resets the codec with all its connected nodes.
+ *
+ * @param pThis HDA codec to reset.
+ */
+static void stac9220Reset(PHDACODECR3 pThis)
+{
+ AssertPtrReturnVoid(pThis->aNodes);
+
+ LogRel(("HDA: Codec reset\n"));
+
+ uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes));
+ for (uint8_t i = 0; i < cTotalNodes; i++)
+ stac9220NodeReset(pThis, i, &pThis->aNodes[i], true /*fInReset*/);
+}
+
+
+static int stac9220Construct(PHDACODECR3 pThis, HDACODECCFG *pCfg)
+{
+ /*
+ * Note: The Linux kernel uses "patch_stac922x" for the fixups,
+ * which in turn uses "ref922x_pin_configs" for the configuration
+ * defaults tweaking in sound/pci/hda/patch_sigmatel.c.
+ */
+ pCfg->idVendor = 0x8384; /* SigmaTel */
+ pCfg->idDevice = 0x7680; /* STAC9221 A1 */
+ pCfg->bBSKU = 0x76;
+ pCfg->idAssembly = 0x80;
+
+ AssertCompile(STAC9221_NUM_NODES <= RT_ELEMENTS(pThis->aNodes));
+ pCfg->cTotalNodes = STAC9221_NUM_NODES;
+ pCfg->idxAdcVolsLineIn = STAC9220_NID_AMP_ADC0;
+ pCfg->idxDacLineOut = STAC9220_NID_DAC1;
+
+ /* Copy over the node class lists and popuplate afNodeClassifications. */
+#define STAC9220WIDGET(a_Type) do { \
+ AssertCompile(RT_ELEMENTS(g_abStac9220##a_Type##s) <= RT_ELEMENTS(pCfg->ab##a_Type##s)); \
+ uint8_t *pbDst = (uint8_t *)&pCfg->ab##a_Type##s[0]; \
+ uintptr_t i; \
+ for (i = 0; i < RT_ELEMENTS(g_abStac9220##a_Type##s); i++) \
+ { \
+ uint8_t const idNode = g_abStac9220##a_Type##s[i]; \
+ if (idNode == 0) \
+ break; \
+ AssertReturn(idNode < RT_ELEMENTS(pThis->aNodes), VERR_INTERNAL_ERROR_3); \
+ pCfg->afNodeClassifications[idNode] |= RT_CONCAT(CODEC_NODE_CLS_,a_Type); \
+ pbDst[i] = idNode; \
+ } \
+ Assert(i + 1 == RT_ELEMENTS(g_abStac9220##a_Type##s)); \
+ for (; i < RT_ELEMENTS(pCfg->ab##a_Type##s); i++) \
+ pbDst[i] = 0; \
+ } while (0)
+ STAC9220WIDGET(Port);
+ STAC9220WIDGET(Dac);
+ STAC9220WIDGET(Adc);
+ STAC9220WIDGET(AdcVol);
+ STAC9220WIDGET(AdcMux);
+ STAC9220WIDGET(Pcbeep);
+ STAC9220WIDGET(SpdifIn);
+ STAC9220WIDGET(SpdifOut);
+ STAC9220WIDGET(DigInPin);
+ STAC9220WIDGET(DigOutPin);
+ STAC9220WIDGET(Cd);
+ STAC9220WIDGET(VolKnob);
+ STAC9220WIDGET(Reserved);
+#undef STAC9220WIDGET
+
+ /*
+ * Initialize all codec nodes.
+ * This is specific to the codec, so do this here.
+ *
+ * Note: Do *not* call stac9220Reset() here, as this would not
+ * initialize the node default configuration values then!
+ */
+ for (uint8_t i = 0; i < STAC9221_NUM_NODES; i++)
+ stac9220NodeReset(pThis, i, &pThis->aNodes[i], false /*fInReset*/);
+
+ /* Common root node initializers. */
+ pThis->aNodes[STAC9220_NID_ROOT].root.node.au32F00_param[0] = CODEC_MAKE_F00_00(pCfg->idVendor, pCfg->idDevice);
+ pThis->aNodes[STAC9220_NID_ROOT].root.node.au32F00_param[4] = CODEC_MAKE_F00_04(0x1, 0x1);
+
+ /* Common AFG node initializers. */
+ pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x4] = CODEC_MAKE_F00_04(0x2, STAC9221_NUM_NODES - 2);
+ pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x5] = CODEC_MAKE_F00_05(1, CODEC_F00_05_AFG);
+ pThis->aNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0xA] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT;
+ pThis->aNodes[STAC9220_NID_AFG].afg.u32F20_param = CODEC_MAKE_F20(pCfg->idVendor, pCfg->bBSKU, pCfg->idAssembly);
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Common Helpers *
+*********************************************************************************************************************************/
+
+/*
+ * Some generic predicate functions.
+ */
+#define HDA_CODEC_IS_NODE_OF_TYPE_FUNC(a_Type) \
+ DECLINLINE(bool) hdaCodecIs##a_Type##Node(PHDACODECR3 pThis, uint8_t idNode) \
+ { \
+ Assert(idNode < RT_ELEMENTS(pThis->Cfg.afNodeClassifications)); \
+ Assert( (memchr(&pThis->Cfg.RT_CONCAT3(ab,a_Type,s)[0], idNode, sizeof(pThis->Cfg.RT_CONCAT3(ab,a_Type,s))) != NULL) \
+ == RT_BOOL(pThis->Cfg.afNodeClassifications[idNode] & RT_CONCAT(CODEC_NODE_CLS_,a_Type))); \
+ return RT_BOOL(pThis->Cfg.afNodeClassifications[idNode] & RT_CONCAT(CODEC_NODE_CLS_,a_Type)); \
+ }
+/* hdaCodecIsPortNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Port)
+/* hdaCodecIsDacNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Dac)
+/* hdaCodecIsAdcVolNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(AdcVol)
+/* hdaCodecIsAdcNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Adc)
+/* hdaCodecIsAdcMuxNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(AdcMux)
+/* hdaCodecIsPcbeepNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Pcbeep)
+/* hdaCodecIsSpdifOutNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(SpdifOut)
+/* hdaCodecIsSpdifInNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(SpdifIn)
+/* hdaCodecIsDigInPinNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(DigInPin)
+/* hdaCodecIsDigOutPinNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(DigOutPin)
+/* hdaCodecIsCdNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Cd)
+/* hdaCodecIsVolKnobNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(VolKnob)
+/* hdaCodecIsReservedNode */
+HDA_CODEC_IS_NODE_OF_TYPE_FUNC(Reserved)
+
+
+/*
+ * Misc helpers.
+ */
+static int hdaR3CodecToAudVolume(PHDACODECR3 pThis, PCODECNODE pNode, AMPLIFIER *pAmp, PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ RT_NOREF(pNode);
+
+ uint8_t iDir;
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ case PDMAUDIOMIXERCTL_FRONT:
+ iDir = AMPLIFIER_OUT;
+ break;
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ iDir = AMPLIFIER_IN;
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid mixer control %RU32\n", enmMixerCtl), VERR_INVALID_PARAMETER);
+ break;
+ }
+
+ int iMute;
+ iMute = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & RT_BIT(7);
+ iMute |= AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & RT_BIT(7);
+ iMute >>=7;
+ iMute &= 0x1;
+
+ uint8_t bLeft = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & 0x7f;
+ uint8_t bRight = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_RIGHT, 0) & 0x7f;
+
+ /*
+ * The STAC9220 volume controls have 0 to -96dB attenuation range in 128 steps.
+ * We have 0 to -96dB range in 256 steps. HDA volume setting of 127 must map
+ * to 255 internally (0dB), while HDA volume setting of 0 (-96dB) should map
+ * to 1 (rather than zero) internally.
+ */
+ bLeft = (bLeft + 1) * (2 * 255) / 256;
+ bRight = (bRight + 1) * (2 * 255) / 256;
+
+ PDMAUDIOVOLUME Vol;
+ PDMAudioVolumeInitFromStereo(&Vol, RT_BOOL(iMute), bLeft, bRight);
+
+ LogFunc(("[NID0x%02x] %RU8/%RU8%s\n", pNode->node.uID, bLeft, bRight, Vol.fMuted ? "- Muted!" : ""));
+ LogRel2(("HDA: Setting volume for mixer control '%s' to %RU8/%RU8%s\n",
+ PDMAudioMixerCtlGetName(enmMixerCtl), bLeft, bRight, Vol.fMuted ? "- Muted!" : ""));
+
+ return hdaR3MixerSetVolume(pThis, enmMixerCtl, &Vol);
+}
+
+
+DECLINLINE(void) hdaCodecSetRegister(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset, uint32_t mask)
+{
+ Assert((pu32Reg && u8Offset < 32));
+ *pu32Reg &= ~(mask << u8Offset);
+ *pu32Reg |= (u32Cmd & mask) << u8Offset;
+}
+
+DECLINLINE(void) hdaCodecSetRegisterU8(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset)
+{
+ hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_8BIT_DATA);
+}
+
+DECLINLINE(void) hdaCodecSetRegisterU16(uint32_t *pu32Reg, uint32_t u32Cmd, uint8_t u8Offset)
+{
+ hdaCodecSetRegister(pu32Reg, u32Cmd, u8Offset, CODEC_VERB_16BIT_DATA);
+}
+
+
+/*********************************************************************************************************************************
+* Verb Processor Functions. *
+*********************************************************************************************************************************/
+#if 0 /* unused */
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, Unimplemented}
+ */
+static DECLCALLBACK(int) vrbProcUnimplemented(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ RT_NOREF(pThis, uCmd);
+ LogFlowFunc(("uCmd(raw:%x: cad:%x, d:%c, nid:%x, verb:%x)\n", uCmd,
+ CODEC_CAD(uCmd), CODEC_DIRECT(uCmd) ? 'N' : 'Y', CODEC_NID(uCmd), CODEC_VERBDATA(uCmd)));
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, ??? }
+ */
+static DECLCALLBACK(int) vrbProcBreak(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ int rc;
+ rc = vrbProcUnimplemented(pThis, uCmd, puResp);
+ *puResp |= CODEC_RESPONSE_UNSOLICITED;
+ return rc;
+}
+
+#endif /* unused */
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, b-- }
+ */
+static DECLCALLBACK(int) vrbProcGetAmplifier(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ /* HDA spec 7.3.3.7 Note A */
+ /** @todo If index out of range response should be 0. */
+ uint8_t u8Index = CODEC_GET_AMP_DIRECTION(uCmd) == AMPLIFIER_OUT ? 0 : CODEC_GET_AMP_INDEX(uCmd);
+
+ PCODECNODE pNode = &pThis->aNodes[CODEC_NID(uCmd)];
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->dac.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->adcvol.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->adcmux.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->pcbeep.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->port.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = AMPLIFIER_REGISTER(pNode->adc.B_params,
+ CODEC_GET_AMP_DIRECTION(uCmd),
+ CODEC_GET_AMP_SIDE(uCmd),
+ u8Index);
+ else
+ LogRel2(("HDA: Warning: Unhandled get amplifier command: 0x%x (NID=0x%x [%RU8])\n", uCmd, CODEC_NID(uCmd), CODEC_NID(uCmd)));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, ??? }
+ */
+static DECLCALLBACK(int) vrbProcGetParameter(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ Assert((uCmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F00_PARAM_LENGTH);
+ if ((uCmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F00_PARAM_LENGTH)
+ {
+ *puResp = 0;
+
+ LogFlowFunc(("invalid F00 parameter %d\n", (uCmd & CODEC_VERB_8BIT_DATA)));
+ return VINF_SUCCESS;
+ }
+
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].node.au32F00_param[uCmd & CODEC_VERB_8BIT_DATA];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f01 }
+ */
+static DECLCALLBACK(int) vrbProcGetConSelectCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcmux.u32F01_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F01_param;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F01_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F01_param;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F01_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 701 }
+ */
+static DECLCALLBACK(int) vrbProcSetConSelectCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcmux.u32F01_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F01_param;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F01_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F01_param;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F01_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f07 }
+ */
+static DECLCALLBACK(int) vrbProcGetPinCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F07_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F07_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F07_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F07_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F07_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F07_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get pin control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 707 }
+ */
+static DECLCALLBACK(int) vrbProcSetPinCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F07_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F07_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F07_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F07_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F07_param;
+ else if ( hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd))
+ && CODEC_NID(uCmd) == 0x1b)
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F07_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set pin control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f08 }
+ */
+static DECLCALLBACK(int) vrbProcGetUnsolicitedEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param;
+ else if ((uCmd) == STAC9220_NID_AFG)
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F08_param;
+ else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F08_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 708 }
+ */
+static DECLCALLBACK(int) vrbProcSetUnsolicitedEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param;
+ else if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F08_param;
+ else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F08_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F08_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f09 }
+ */
+static DECLCALLBACK(int) vrbProcGetPinSense(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F09_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F09_param;
+ else
+ {
+ AssertFailed();
+ LogRel2(("HDA: Warning: Unhandled get pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 709 }
+ */
+static DECLCALLBACK(int) vrbProcSetPinSense(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F09_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F09_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, ??? }
+ */
+static DECLCALLBACK(int) vrbProcGetConnectionListEntry(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ Assert((uCmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F02_PARAM_LENGTH);
+ if ((uCmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F02_PARAM_LENGTH)
+ {
+ LogFlowFunc(("access to invalid F02 index %d\n", (uCmd & CODEC_VERB_8BIT_DATA)));
+ return VINF_SUCCESS;
+ }
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].node.au32F02_param[uCmd & CODEC_VERB_8BIT_DATA];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f03 }
+ */
+static DECLCALLBACK(int) vrbProcGetProcessingState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F03_param;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 703 }
+ */
+static DECLCALLBACK(int) vrbProcSetProcessingState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].adc.u32F03_param, uCmd, 0);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f0d }
+ */
+static DECLCALLBACK(int) vrbProcGetDigitalConverter(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F0d_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F0d_param;
+ return VINF_SUCCESS;
+}
+
+
+static int codecSetDigitalConverter(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F0d_param, uCmd, u8Offset);
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU8(&pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F0d_param, uCmd, u8Offset);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 70d }
+ */
+static DECLCALLBACK(int) vrbProcSetDigitalConverter1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ return codecSetDigitalConverter(pThis, uCmd, 0, puResp);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 70e }
+ */
+static DECLCALLBACK(int) vrbProcSetDigitalConverter2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ return codecSetDigitalConverter(pThis, uCmd, 8, puResp);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f20 }
+ */
+static DECLCALLBACK(int) vrbProcGetSubId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ Assert(CODEC_CAD(uCmd) == pThis->Cfg.id);
+ uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes));
+ Assert(CODEC_NID(uCmd) < cTotalNodes);
+ if (CODEC_NID(uCmd) >= cTotalNodes)
+ {
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd)));
+ *puResp = 0;
+ return VINF_SUCCESS;
+ }
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F20_param;
+ else
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+static int codecSetSubIdX(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset)
+{
+ Assert(CODEC_CAD(uCmd) == pThis->Cfg.id);
+ uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes));
+ Assert(CODEC_NID(uCmd) < cTotalNodes);
+ if (CODEC_NID(uCmd) >= cTotalNodes)
+ {
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd)));
+ return VINF_SUCCESS;
+ }
+ uint32_t *pu32Reg;
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F20_param;
+ else
+ AssertFailedReturn(VINF_SUCCESS);
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, u8Offset);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 720 }
+ */
+static DECLCALLBACK(int) vrbProcSetSubId0(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetSubIdX(pThis, uCmd, 0);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 721 }
+ */
+static DECLCALLBACK(int) vrbProcSetSubId1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetSubIdX(pThis, uCmd, 8);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 722 }
+ */
+static DECLCALLBACK(int) vrbProcSetSubId2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetSubIdX(pThis, uCmd, 16);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 723 }
+ */
+static DECLCALLBACK(int) vrbProcSetSubId3(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetSubIdX(pThis, uCmd, 24);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, ??? }
+ */
+static DECLCALLBACK(int) vrbProcReset(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ Assert(CODEC_CAD(uCmd) == pThis->Cfg.id);
+
+ if (pThis->Cfg.enmType == CODECTYPE_STAC9220)
+ {
+ Assert(CODEC_NID(uCmd) == STAC9220_NID_AFG);
+
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ stac9220Reset(pThis);
+ }
+ else
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f05 }
+ */
+static DECLCALLBACK(int) vrbProcGetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get power state command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ LogFunc(("[NID0x%02x]: fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n",
+ CODEC_NID(uCmd), CODEC_F05_IS_RESET(*puResp), CODEC_F05_IS_STOPOK(*puResp), CODEC_F05_ACT(*puResp), CODEC_F05_SET(*puResp)));
+ return VINF_SUCCESS;
+}
+
+#if 1
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 705 }
+ */
+static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param;
+ else
+ {
+ LogRel2(("HDA: Warning: Unhandled set power state command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+ }
+
+ if (!pu32Reg)
+ return VINF_SUCCESS;
+
+ uint8_t uPwrCmd = CODEC_F05_SET (uCmd);
+ bool fReset = CODEC_F05_IS_RESET (*pu32Reg);
+ bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg);
+#ifdef LOG_ENABLED
+ bool fError = CODEC_F05_IS_ERROR (*pu32Reg);
+ uint8_t uPwrAct = CODEC_F05_ACT (*pu32Reg);
+ uint8_t uPwrSet = CODEC_F05_SET (*pu32Reg);
+ LogFunc(("[NID0x%02x] Cmd=D%RU8, fReset=%RTbool, fStopOk=%RTbool, fError=%RTbool, uPwrAct=D%RU8, uPwrSet=D%RU8\n",
+ CODEC_NID(uCmd), uPwrCmd, fReset, fStopOk, fError, uPwrAct, uPwrSet));
+ LogFunc(("AFG: Act=D%RU8, Set=D%RU8\n",
+ CODEC_F05_ACT(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param),
+ CODEC_F05_SET(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param)));
+#endif
+
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uPwrCmd /* PS-Act */, uPwrCmd /* PS-Set */);
+
+ const uint8_t uAFGPwrAct = CODEC_F05_ACT(pThis->aNodes[STAC9220_NID_AFG].afg.u32F05_param);
+ if (uAFGPwrAct == CODEC_F05_D0) /* Only propagate power state if AFG is on (D0). */
+ {
+ /* Propagate to all other nodes under this AFG. */
+ LogFunc(("Propagating Act=D%RU8 (AFG), Set=D%RU8 to all AFG child nodes ...\n", uAFGPwrAct, uPwrCmd));
+
+#define PROPAGATE_PWR_STATE(a_abList, a_Member) \
+ do { \
+ for (uintptr_t idxList = 0; idxList < RT_ELEMENTS(a_abList); idxList++) \
+ { \
+ uint8_t const idxNode = a_abList[idxList]; \
+ if (idxNode) \
+ { \
+ pThis->aNodes[idxNode].a_Member.u32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); \
+ LogFunc(("\t[NID0x%02x]: Act=D%RU8, Set=D%RU8\n", idxNode, \
+ CODEC_F05_ACT(pThis->aNodes[idxNode].a_Member.u32F05_param), \
+ CODEC_F05_SET(pThis->aNodes[idxNode].a_Member.u32F05_param))); \
+ } \
+ else \
+ break; \
+ } \
+ } while (0)
+
+ PROPAGATE_PWR_STATE(pThis->Cfg.abDacs, dac);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abAdcs, adc);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abDigInPins, digin);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abDigOutPins, digout);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abSpdifIns, spdifin);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abSpdifOuts, spdifout);
+ PROPAGATE_PWR_STATE(pThis->Cfg.abReserveds, reserved);
+
+#undef PROPAGATE_PWR_STATE
+ }
+ /*
+ * If this node is a reqular node (not the AFG one), adopt PS-Set of the AFG node
+ * as PS-Set of this node. PS-Act always is one level under PS-Set here.
+ */
+ else
+ {
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd);
+ }
+
+ LogFunc(("[NID0x%02x] fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n",
+ CODEC_NID(uCmd),
+ CODEC_F05_IS_RESET(*pu32Reg), CODEC_F05_IS_STOPOK(*pu32Reg), CODEC_F05_ACT(*pu32Reg), CODEC_F05_SET(*pu32Reg)));
+
+ return VINF_SUCCESS;
+}
+
+#else
+
+DECLINLINE(void) codecPropogatePowerState(uint32_t *pu32F05_param)
+{
+ Assert(pu32F05_param);
+ if (!pu32F05_param)
+ return;
+ bool fReset = CODEC_F05_IS_RESET(*pu32F05_param);
+ bool fStopOk = CODEC_F05_IS_STOPOK(*pu32F05_param);
+ uint8_t u8SetPowerState = CODEC_F05_SET(*pu32F05_param);
+ *pu32F05_param = CODEC_MAKE_F05(fReset, fStopOk, 0, u8SetPowerState, u8SetPowerState);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 705 }
+ */
+static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ Assert(CODEC_CAD(uCmd) == pThis->Cfg.id);
+ uint8_t const cTotalNodes = (uint8_t)RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes));
+ Assert(CODEC_NID(uCmd) < cTotalNodes);
+ if (CODEC_NID(uCmd) >= cTotalNodes)
+ {
+ *puResp = 0;
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(uCmd)));
+ return VINF_SUCCESS;
+ }
+ *puResp = 0;
+ uint32_t *pu32Reg;
+ if (CODEC_NID(uCmd) == 1 /* AFG */)
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F05_param;
+ else
+ AssertFailedReturn(VINF_SUCCESS);
+
+ bool fReset = CODEC_F05_IS_RESET(*pu32Reg);
+ bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg);
+
+ if (CODEC_NID(uCmd) != 1 /* AFG */)
+ {
+ /*
+ * We shouldn't propogate actual power state, which actual for AFG
+ */
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0,
+ CODEC_F05_ACT(pThis->aNodes[1].afg.u32F05_param),
+ CODEC_F05_SET(uCmd));
+ }
+
+ /* Propagate next power state only if AFG is on or verb modifies AFG power state */
+ if ( CODEC_NID(uCmd) == 1 /* AFG */
+ || !CODEC_F05_ACT(pThis->aNodes[1].afg.u32F05_param))
+ {
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, CODEC_F05_SET(uCmd), CODEC_F05_SET(uCmd));
+ if ( CODEC_NID(uCmd) == 1 /* AFG */
+ && (CODEC_F05_SET(uCmd)) == CODEC_F05_D0)
+ {
+ /* now we're powered on AFG and may propogate power states on nodes */
+ const uint8_t *pu8NodeIndex = &pThis->abDacs[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].dac.u32F05_param);
+
+ pu8NodeIndex = &pThis->abAdcs[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].adc.u32F05_param);
+
+ pu8NodeIndex = &pThis->abDigInPins[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->aNodes[*pu8NodeIndex].digin.u32F05_param);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+#endif
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f06 }
+ */
+static DECLCALLBACK(int) vrbProcGetStreamId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F06_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32F06_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F06_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F06_param;
+ else if (CODEC_NID(uCmd) == STAC9221_NID_I2S_OUT)
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F06_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ LogFlowFunc(("[NID0x%02x] Stream ID=%RU8, channel=%RU8\n",
+ CODEC_NID(uCmd), CODEC_F00_06_GET_STREAM_ID(uCmd), CODEC_F00_06_GET_CHANNEL_ID(uCmd)));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, a0 }
+ */
+static DECLCALLBACK(int) vrbProcGetConverterFormat(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32A_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adc.u32A_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32A_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32A_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32A_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get converter format command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, ??? - Also see section 3.7.1. }
+ */
+static DECLCALLBACK(int) vrbProcSetConverterFormat(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].dac.u32A_param, uCmd, 0);
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].adc.u32A_param, uCmd, 0);
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32A_param, uCmd, 0);
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ hdaCodecSetRegisterU16(&pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32A_param, uCmd, 0);
+ else
+ LogRel2(("HDA: Warning: Unhandled set converter format command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f0c }
+ */
+static DECLCALLBACK(int) vrbProcGetEAPD_BTLEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F0c_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F0c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F0c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 70c }
+ */
+static DECLCALLBACK(int) vrbProcSetEAPD_BTLEnabled(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].adcvol.u32F0c_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F0c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F0c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f0f }
+ */
+static DECLCALLBACK(int) vrbProcGetVolumeKnobCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F0f_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 70f }
+ */
+static DECLCALLBACK(int) vrbProcSetVolumeKnobCtrl(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].volumeKnob.u32F0f_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f15 }
+ */
+static DECLCALLBACK(int) vrbProcGetGPIOData(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ RT_NOREF(pThis, uCmd);
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 715 }
+ */
+static DECLCALLBACK(int) vrbProcSetGPIOData(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ RT_NOREF(pThis, uCmd);
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f16 }
+ */
+static DECLCALLBACK(int) vrbProcGetGPIOEnableMask(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ RT_NOREF(pThis, uCmd);
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 716 }
+ */
+static DECLCALLBACK(int) vrbProcSetGPIOEnableMask(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ RT_NOREF(pThis, uCmd);
+ *puResp = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f17 }
+ */
+static DECLCALLBACK(int) vrbProcGetGPIODirection(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ /* Note: this is true for ALC885. */
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ *puResp = pThis->aNodes[1].afg.u32F17_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 717 }
+ */
+static DECLCALLBACK(int) vrbProcSetGPIODirection(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (CODEC_NID(uCmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->aNodes[1].afg.u32F17_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f1c }
+ */
+static DECLCALLBACK(int) vrbProcGetConfig(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].port.u32F1c_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digout.u32F1c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].digin.u32F1c_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F1c_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F1c_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F1c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get config command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+static int codecSetConfigX(PHDACODECR3 pThis, uint32_t uCmd, uint8_t u8Offset)
+{
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].port.u32F1c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digin.u32F1c_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].digout.u32F1c_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].cdnode.u32F1c_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].pcbeep.u32F1c_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].reserved.u32F1c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set config command (%RU8) for NID0x%02x: 0x%x\n", u8Offset, CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, u8Offset);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 71c }
+ */
+static DECLCALLBACK(int) vrbProcSetConfig0(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetConfigX(pThis, uCmd, 0);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 71d }
+ */
+static DECLCALLBACK(int) vrbProcSetConfig1(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetConfigX(pThis, uCmd, 8);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 71e }
+ */
+static DECLCALLBACK(int) vrbProcSetConfig2(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetConfigX(pThis, uCmd, 16);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 71e }
+ */
+static DECLCALLBACK(int) vrbProcSetConfig3(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+ return codecSetConfigX(pThis, uCmd, 24);
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, f04 }
+ */
+static DECLCALLBACK(int) vrbProcGetSDISelect(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ *puResp = pThis->aNodes[CODEC_NID(uCmd)].dac.u32F04_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 704 }
+ */
+static DECLCALLBACK(int) vrbProcSetSDISelect(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ pu32Reg = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F04_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 3-- }
+ */
+static DECLCALLBACK(int) vrbProcR3SetAmplifier(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ PCODECNODE pNode = &pThis->aNodes[CODEC_NID(uCmd)];
+ AMPLIFIER *pAmplifier = NULL;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->dac.B_params;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->adcvol.B_params;
+ else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->adcmux.B_params;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->pcbeep.B_params;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->port.B_params;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ pAmplifier = &pNode->adc.B_params;
+ else
+ LogRel2(("HDA: Warning: Unhandled set amplifier command: 0x%x (Payload=%RU16, NID=0x%x [%RU8])\n",
+ uCmd, CODEC_VERB_PAYLOAD16(uCmd), CODEC_NID(uCmd), CODEC_NID(uCmd)));
+
+ if (!pAmplifier)
+ return VINF_SUCCESS;
+
+ bool fIsOut = CODEC_SET_AMP_IS_OUT_DIRECTION(uCmd);
+ bool fIsIn = CODEC_SET_AMP_IS_IN_DIRECTION(uCmd);
+ bool fIsLeft = CODEC_SET_AMP_IS_LEFT_SIDE(uCmd);
+ bool fIsRight = CODEC_SET_AMP_IS_RIGHT_SIDE(uCmd);
+ uint8_t u8Index = CODEC_SET_AMP_INDEX(uCmd);
+
+ if ( (!fIsLeft && !fIsRight)
+ || (!fIsOut && !fIsIn))
+ return VINF_SUCCESS;
+
+ LogFunc(("[NID0x%02x] fIsOut=%RTbool, fIsIn=%RTbool, fIsLeft=%RTbool, fIsRight=%RTbool, Idx=%RU8\n",
+ CODEC_NID(uCmd), fIsOut, fIsIn, fIsLeft, fIsRight, u8Index));
+
+ if (fIsIn)
+ {
+ if (fIsLeft)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_LEFT, u8Index), uCmd, 0);
+ if (fIsRight)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_RIGHT, u8Index), uCmd, 0);
+
+ /*
+ * Check if the node ID is the one we use for controlling the line-in volume;
+ * with STAC9220 this is connected to STAC9220_NID_AMP_ADC0 (ID 0x17).
+ *
+ * If we don't do this check here, some guests like newer Ubuntus mute mic-in
+ * afterwards (connected to STAC9220_NID_AMP_ADC1 (ID 0x18)). This then would
+ * also mute line-in, which breaks audio recording.
+ *
+ * See STAC9220 V1.0 01/08, p. 30 + oem2ticketref:53.
+ */
+ if (CODEC_NID(uCmd) == pThis->Cfg.idxAdcVolsLineIn)
+ hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_LINE_IN);
+
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+# error "Implement mic-in volume / mute setting."
+ else if (CODEC_NID(uCmd) == pThis->Cfg.idxAdcVolsMicIn)
+ hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_MIC_IN);
+#endif
+
+ }
+ if (fIsOut)
+ {
+ if (fIsLeft)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_LEFT, u8Index), uCmd, 0);
+ if (fIsRight)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_RIGHT, u8Index), uCmd, 0);
+
+ if (CODEC_NID(uCmd) == pThis->Cfg.idxDacLineOut)
+ hdaR3CodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_FRONT);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{CODECVERB,pfn, 706 }
+ */
+static DECLCALLBACK(int) vrbProcR3SetStreamId(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ *puResp = 0;
+
+ uint8_t uSD = CODEC_F00_06_GET_STREAM_ID(uCmd);
+ uint8_t uChannel = CODEC_F00_06_GET_CHANNEL_ID(uCmd);
+
+ LogFlowFunc(("[NID0x%02x] Setting to stream ID=%RU8, channel=%RU8\n",
+ CODEC_NID(uCmd), uSD, uChannel));
+
+ ASSERT_GUEST_LOGREL_MSG_RETURN(uSD < HDA_MAX_STREAMS,
+ ("Setting stream ID #%RU8 is invalid\n", uSD), VERR_INVALID_PARAMETER);
+
+ PDMAUDIODIR enmDir;
+ uint32_t *pu32Addr;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(uCmd)))
+ {
+ pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].dac.u32F06_param;
+ enmDir = PDMAUDIODIR_OUT;
+ }
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(uCmd)))
+ {
+ pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].adc.u32F06_param;
+ enmDir = PDMAUDIODIR_IN;
+ }
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(uCmd)))
+ {
+ pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].spdifout.u32F06_param;
+ enmDir = PDMAUDIODIR_OUT;
+ }
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(uCmd)))
+ {
+ pu32Addr = &pThis->aNodes[CODEC_NID(uCmd)].spdifin.u32F06_param;
+ enmDir = PDMAUDIODIR_IN;
+ }
+ else
+ {
+ enmDir = PDMAUDIODIR_UNKNOWN;
+ LogRel2(("HDA: Warning: Unhandled set stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(uCmd), uCmd));
+ return VINF_SUCCESS;
+ }
+
+ /* Do we (re-)assign our input/output SDn (SDI/SDO) IDs? */
+ pThis->aNodes[CODEC_NID(uCmd)].node.uSD = uSD;
+ pThis->aNodes[CODEC_NID(uCmd)].node.uChannel = uChannel;
+
+ if (enmDir == PDMAUDIODIR_OUT)
+ {
+ /** @todo Check if non-interleaved streams need a different channel / SDn? */
+
+ /* Propagate to the controller. */
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_FRONT, uSD, uChannel);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, uSD, uChannel);
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_REAR, uSD, uChannel);
+# endif
+ }
+ else if (enmDir == PDMAUDIODIR_IN)
+ {
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_LINE_IN, uSD, uChannel);
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_MIC_IN, uSD, uChannel);
+# endif
+ }
+
+ hdaCodecSetRegisterU8(pu32Addr, uCmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * HDA codec verb descriptors.
+ *
+ * @note This must be ordered by uVerb so we can do a binary lookup.
+ */
+static const CODECVERB g_aCodecVerbs[] =
+{
+ /* Verb Verb mask Callback Name
+ ---------- --------------------- ------------------------------------------------------------------- */
+ { 0x00020000, CODEC_VERB_16BIT_CMD, vrbProcSetConverterFormat , "SetConverterFormat " },
+ { 0x00030000, CODEC_VERB_16BIT_CMD, vrbProcR3SetAmplifier , "SetAmplifier " },
+ { 0x00070100, CODEC_VERB_8BIT_CMD , vrbProcSetConSelectCtrl , "SetConSelectCtrl " },
+ { 0x00070300, CODEC_VERB_8BIT_CMD , vrbProcSetProcessingState , "SetProcessingState " },
+ { 0x00070400, CODEC_VERB_8BIT_CMD , vrbProcSetSDISelect , "SetSDISelect " },
+ { 0x00070500, CODEC_VERB_8BIT_CMD , vrbProcSetPowerState , "SetPowerState " },
+ { 0x00070600, CODEC_VERB_8BIT_CMD , vrbProcR3SetStreamId , "SetStreamId " },
+ { 0x00070700, CODEC_VERB_8BIT_CMD , vrbProcSetPinCtrl , "SetPinCtrl " },
+ { 0x00070800, CODEC_VERB_8BIT_CMD , vrbProcSetUnsolicitedEnabled , "SetUnsolicitedEnabled " },
+ { 0x00070900, CODEC_VERB_8BIT_CMD , vrbProcSetPinSense , "SetPinSense " },
+ { 0x00070C00, CODEC_VERB_8BIT_CMD , vrbProcSetEAPD_BTLEnabled , "SetEAPD_BTLEnabled " },
+ { 0x00070D00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter1 , "SetDigitalConverter1 " },
+ { 0x00070E00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter2 , "SetDigitalConverter2 " },
+ { 0x00070F00, CODEC_VERB_8BIT_CMD , vrbProcSetVolumeKnobCtrl , "SetVolumeKnobCtrl " },
+ { 0x00071500, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOData , "SetGPIOData " },
+ { 0x00071600, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOEnableMask , "SetGPIOEnableMask " },
+ { 0x00071700, CODEC_VERB_8BIT_CMD , vrbProcSetGPIODirection , "SetGPIODirection " },
+ { 0x00071C00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig0 , "SetConfig0 " },
+ { 0x00071D00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig1 , "SetConfig1 " },
+ { 0x00071E00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig2 , "SetConfig2 " },
+ { 0x00071F00, CODEC_VERB_8BIT_CMD , vrbProcSetConfig3 , "SetConfig3 " },
+ { 0x00072000, CODEC_VERB_8BIT_CMD , vrbProcSetSubId0 , "SetSubId0 " },
+ { 0x00072100, CODEC_VERB_8BIT_CMD , vrbProcSetSubId1 , "SetSubId1 " },
+ { 0x00072200, CODEC_VERB_8BIT_CMD , vrbProcSetSubId2 , "SetSubId2 " },
+ { 0x00072300, CODEC_VERB_8BIT_CMD , vrbProcSetSubId3 , "SetSubId3 " },
+ { 0x0007FF00, CODEC_VERB_8BIT_CMD , vrbProcReset , "Reset " },
+ { 0x000A0000, CODEC_VERB_16BIT_CMD, vrbProcGetConverterFormat , "GetConverterFormat " },
+ { 0x000B0000, CODEC_VERB_16BIT_CMD, vrbProcGetAmplifier , "GetAmplifier " },
+ { 0x000F0000, CODEC_VERB_8BIT_CMD , vrbProcGetParameter , "GetParameter " },
+ { 0x000F0100, CODEC_VERB_8BIT_CMD , vrbProcGetConSelectCtrl , "GetConSelectCtrl " },
+ { 0x000F0200, CODEC_VERB_8BIT_CMD , vrbProcGetConnectionListEntry , "GetConnectionListEntry" },
+ { 0x000F0300, CODEC_VERB_8BIT_CMD , vrbProcGetProcessingState , "GetProcessingState " },
+ { 0x000F0400, CODEC_VERB_8BIT_CMD , vrbProcGetSDISelect , "GetSDISelect " },
+ { 0x000F0500, CODEC_VERB_8BIT_CMD , vrbProcGetPowerState , "GetPowerState " },
+ { 0x000F0600, CODEC_VERB_8BIT_CMD , vrbProcGetStreamId , "GetStreamId " },
+ { 0x000F0700, CODEC_VERB_8BIT_CMD , vrbProcGetPinCtrl , "GetPinCtrl " },
+ { 0x000F0800, CODEC_VERB_8BIT_CMD , vrbProcGetUnsolicitedEnabled , "GetUnsolicitedEnabled " },
+ { 0x000F0900, CODEC_VERB_8BIT_CMD , vrbProcGetPinSense , "GetPinSense " },
+ { 0x000F0C00, CODEC_VERB_8BIT_CMD , vrbProcGetEAPD_BTLEnabled , "GetEAPD_BTLEnabled " },
+ { 0x000F0D00, CODEC_VERB_8BIT_CMD , vrbProcGetDigitalConverter , "GetDigitalConverter " },
+ { 0x000F0F00, CODEC_VERB_8BIT_CMD , vrbProcGetVolumeKnobCtrl , "GetVolumeKnobCtrl " },
+ { 0x000F1500, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOData , "GetGPIOData " },
+ { 0x000F1600, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOEnableMask , "GetGPIOEnableMask " },
+ { 0x000F1700, CODEC_VERB_8BIT_CMD , vrbProcGetGPIODirection , "GetGPIODirection " },
+ { 0x000F1C00, CODEC_VERB_8BIT_CMD , vrbProcGetConfig , "GetConfig " },
+ { 0x000F2000, CODEC_VERB_8BIT_CMD , vrbProcGetSubId , "GetSubId " },
+ /** @todo Implement 0x7e7: IDT Set GPIO (STAC922x only). */
+};
+
+
+/**
+ * Implements codec lookup and will call the handler on the verb it finds,
+ * returning the handler response.
+ *
+ * @returns VBox status code (not strict).
+ */
+DECLHIDDEN(int) hdaR3CodecLookup(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp)
+{
+ /*
+ * Clear the return value and assert some sanity.
+ */
+ AssertPtr(puResp);
+ *puResp = 0;
+ AssertPtr(pThis);
+ AssertMsgReturn(CODEC_CAD(uCmd) == pThis->Cfg.id,
+ ("Unknown codec address 0x%x\n", CODEC_CAD(uCmd)),
+ VERR_INVALID_PARAMETER);
+ uint32_t const uCmdData = CODEC_VERBDATA(uCmd);
+ AssertMsgReturn( uCmdData != 0
+ && CODEC_NID(uCmd) < RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes)),
+ ("[NID0x%02x] Unknown / invalid node or data (0x%x)\n", CODEC_NID(uCmd), uCmdData),
+ VERR_INVALID_PARAMETER);
+ STAM_COUNTER_INC(&pThis->CTX_SUFF(StatLookups));
+
+ /*
+ * Do a binary lookup of the verb.
+ * Note! if we want other verb tables, add a table selector before the loop.
+ */
+ size_t iFirst = 0;
+ size_t iEnd = RT_ELEMENTS(g_aCodecVerbs);
+ for (;;)
+ {
+ size_t const iCur = iFirst + (iEnd - iFirst) / 2;
+ uint32_t const uVerb = g_aCodecVerbs[iCur].uVerb;
+ if (uCmdData < uVerb)
+ {
+ if (iCur > iFirst)
+ iEnd = iCur;
+ else
+ break;
+ }
+ else if ((uCmdData & g_aCodecVerbs[iCur].fMask) != uVerb)
+ {
+ if (iCur + 1 < iEnd)
+ iFirst = iCur + 1;
+ else
+ break;
+ }
+ else
+ {
+ /*
+ * Found it! Run the callback and return.
+ */
+ AssertPtrReturn(g_aCodecVerbs[iCur].pfn, VERR_INTERNAL_ERROR_5); /* Paranoia^2. */
+
+ int rc = g_aCodecVerbs[iCur].pfn(pThis, uCmd, puResp);
+ AssertRC(rc);
+ Log3Func(("[NID0x%02x] (0x%x) %s: 0x%x -> 0x%x\n",
+ CODEC_NID(uCmd), g_aCodecVerbs[iCur].uVerb, g_aCodecVerbs[iCur].pszName, CODEC_VERB_PAYLOAD8(uCmd), *puResp));
+ return rc;
+ }
+ }
+
+#ifdef VBOX_STRICT
+ for (size_t i = 0; i < RT_ELEMENTS(g_aCodecVerbs); i++)
+ {
+ AssertMsg(i == 0 || g_aCodecVerbs[i - 1].uVerb < g_aCodecVerbs[i].uVerb,
+ ("i=%#x uVerb[-1]=%#x uVerb=%#x - buggy table!\n", i, g_aCodecVerbs[i - 1].uVerb, g_aCodecVerbs[i].uVerb));
+ AssertMsg((uCmdData & g_aCodecVerbs[i].fMask) != g_aCodecVerbs[i].uVerb,
+ ("i=%#x uVerb=%#x uCmd=%#x - buggy binary search or table!\n", i, g_aCodecVerbs[i].uVerb, uCmd));
+ }
+#endif
+ LogFunc(("[NID0x%02x] Callback for %x not found\n", CODEC_NID(uCmd), CODEC_VERBDATA(uCmd)));
+ return VERR_NOT_FOUND;
+}
+
+
+/*********************************************************************************************************************************
+* Debug *
+*********************************************************************************************************************************/
+/**
+ * CODEC debug info item printing state.
+ */
+typedef struct CODECDEBUG
+{
+ /** DBGF info helpers. */
+ PCDBGFINFOHLP pHlp;
+ /** Current recursion level. */
+ uint8_t uLevel;
+ /** Pointer to codec state. */
+ PHDACODECR3 pThis;
+} CODECDEBUG;
+/** Pointer to the debug info item printing state for the codec. */
+typedef CODECDEBUG *PCODECDEBUG;
+
+#define CODECDBG_INDENT pInfo->uLevel++;
+#define CODECDBG_UNINDENT if (pInfo->uLevel) pInfo->uLevel--;
+
+#define CODECDBG_PRINT(...) pInfo->pHlp->pfnPrintf(pInfo->pHlp, __VA_ARGS__)
+#define CODECDBG_PRINTI(...) codecDbgPrintf(pInfo, __VA_ARGS__)
+
+
+/** Wrapper around DBGFINFOHLP::pfnPrintf that adds identation. */
+static void codecDbgPrintf(PCODECDEBUG pInfo, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ pInfo->pHlp->pfnPrintf(pInfo->pHlp, "%*s%N", pInfo->uLevel * 4, "", pszFormat, &va);
+ va_end(va);
+}
+
+
+/** Power state */
+static void codecDbgPrintNodeRegF05(PCODECDEBUG pInfo, uint32_t u32Reg)
+{
+ codecDbgPrintf(pInfo, "Power (F05): fReset=%RTbool, fStopOk=%RTbool, Set=%RU8, Act=%RU8\n",
+ CODEC_F05_IS_RESET(u32Reg), CODEC_F05_IS_STOPOK(u32Reg), CODEC_F05_SET(u32Reg), CODEC_F05_ACT(u32Reg));
+}
+
+
+static void codecDbgPrintNodeRegA(PCODECDEBUG pInfo, uint32_t u32Reg)
+{
+ codecDbgPrintf(pInfo, "RegA: %x\n", u32Reg);
+}
+
+
+static void codecDbgPrintNodeRegF00(PCODECDEBUG pInfo, uint32_t *paReg00)
+{
+ codecDbgPrintf(pInfo, "Parameters (F00):\n");
+
+ CODECDBG_INDENT
+ codecDbgPrintf(pInfo, "Connections: %RU8\n", CODEC_F00_0E_COUNT(paReg00[0xE]));
+ codecDbgPrintf(pInfo, "Amplifier Caps:\n");
+ uint32_t uReg = paReg00[0xD];
+ CODECDBG_INDENT
+ codecDbgPrintf(pInfo, "Input Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n",
+ CODEC_F00_0D_NUM_STEPS(uReg),
+ CODEC_F00_0D_STEP_SIZE(uReg),
+ CODEC_F00_0D_OFFSET(uReg),
+ RT_BOOL(CODEC_F00_0D_IS_CAP_MUTE(uReg)));
+
+ uReg = paReg00[0x12];
+ codecDbgPrintf(pInfo, "Output Steps=%02RU8, StepSize=%02RU8, StepOff=%02RU8, fCanMute=%RTbool\n",
+ CODEC_F00_12_NUM_STEPS(uReg),
+ CODEC_F00_12_STEP_SIZE(uReg),
+ CODEC_F00_12_OFFSET(uReg),
+ RT_BOOL(CODEC_F00_12_IS_CAP_MUTE(uReg)));
+ CODECDBG_UNINDENT
+ CODECDBG_UNINDENT
+}
+
+
+static void codecDbgPrintNodeAmp(PCODECDEBUG pInfo, uint32_t *paReg, uint8_t uIdx, uint8_t uDir)
+{
+#define CODECDBG_AMP(reg, chan) \
+ codecDbgPrintf(pInfo, "Amp %RU8 %s %s: In=%RTbool, Out=%RTbool, Left=%RTbool, Right=%RTbool, Idx=%RU8, fMute=%RTbool, uGain=%RU8\n", \
+ uIdx, chan, uDir == AMPLIFIER_IN ? "In" : "Out", \
+ RT_BOOL(CODEC_SET_AMP_IS_IN_DIRECTION(reg)), RT_BOOL(CODEC_SET_AMP_IS_OUT_DIRECTION(reg)), \
+ RT_BOOL(CODEC_SET_AMP_IS_LEFT_SIDE(reg)), RT_BOOL(CODEC_SET_AMP_IS_RIGHT_SIDE(reg)), \
+ CODEC_SET_AMP_INDEX(reg), RT_BOOL(CODEC_SET_AMP_MUTE(reg)), CODEC_SET_AMP_GAIN(reg))
+
+ uint32_t regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_LEFT, uIdx);
+ CODECDBG_AMP(regAmp, "Left");
+ regAmp = AMPLIFIER_REGISTER(paReg, uDir, AMPLIFIER_RIGHT, uIdx);
+ CODECDBG_AMP(regAmp, "Right");
+
+#undef CODECDBG_AMP
+}
+
+
+#if 0 /* unused */
+static void codecDbgPrintNodeConnections(PCODECDEBUG pInfo, PCODECNODE pNode)
+{
+ if (pNode->node.au32F00_param[0xE] == 0) /* Directly connected to HDA link. */
+ {
+ codecDbgPrintf(pInfo, "[HDA LINK]\n");
+ return;
+ }
+}
+#endif
+
+
+static void codecDbgPrintNode(PCODECDEBUG pInfo, PCODECNODE pNode, bool fRecursive)
+{
+ codecDbgPrintf(pInfo, "Node 0x%02x (%02RU8): ", pNode->node.uID, pNode->node.uID);
+
+ if (pNode->node.uID == STAC9220_NID_ROOT)
+ CODECDBG_PRINT("ROOT\n");
+ else if (pNode->node.uID == STAC9220_NID_AFG)
+ {
+ CODECDBG_PRINT("AFG\n");
+ CODECDBG_INDENT
+ codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param);
+ codecDbgPrintNodeRegF05(pInfo, pNode->afg.u32F05_param);
+ CODECDBG_UNINDENT
+ }
+ else if (hdaCodecIsPortNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("PORT\n");
+ else if (hdaCodecIsDacNode(pInfo->pThis, pNode->node.uID))
+ {
+ CODECDBG_PRINT("DAC\n");
+ CODECDBG_INDENT
+ codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param);
+ codecDbgPrintNodeRegF05(pInfo, pNode->dac.u32F05_param);
+ codecDbgPrintNodeRegA (pInfo, pNode->dac.u32A_param);
+ codecDbgPrintNodeAmp (pInfo, pNode->dac.B_params, 0, AMPLIFIER_OUT);
+ CODECDBG_UNINDENT
+ }
+ else if (hdaCodecIsAdcVolNode(pInfo->pThis, pNode->node.uID))
+ {
+ CODECDBG_PRINT("ADC VOLUME\n");
+ CODECDBG_INDENT
+ codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param);
+ codecDbgPrintNodeRegA (pInfo, pNode->adcvol.u32A_params);
+ codecDbgPrintNodeAmp (pInfo, pNode->adcvol.B_params, 0, AMPLIFIER_IN);
+ CODECDBG_UNINDENT
+ }
+ else if (hdaCodecIsAdcNode(pInfo->pThis, pNode->node.uID))
+ {
+ CODECDBG_PRINT("ADC\n");
+ CODECDBG_INDENT
+ codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param);
+ codecDbgPrintNodeRegF05(pInfo, pNode->adc.u32F05_param);
+ codecDbgPrintNodeRegA (pInfo, pNode->adc.u32A_param);
+ codecDbgPrintNodeAmp (pInfo, pNode->adc.B_params, 0, AMPLIFIER_IN);
+ CODECDBG_UNINDENT
+ }
+ else if (hdaCodecIsAdcMuxNode(pInfo->pThis, pNode->node.uID))
+ {
+ CODECDBG_PRINT("ADC MUX\n");
+ CODECDBG_INDENT
+ codecDbgPrintNodeRegF00(pInfo, pNode->node.au32F00_param);
+ codecDbgPrintNodeRegA (pInfo, pNode->adcmux.u32A_param);
+ codecDbgPrintNodeAmp (pInfo, pNode->adcmux.B_params, 0, AMPLIFIER_IN);
+ CODECDBG_UNINDENT
+ }
+ else if (hdaCodecIsPcbeepNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("PC BEEP\n");
+ else if (hdaCodecIsSpdifOutNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("SPDIF OUT\n");
+ else if (hdaCodecIsSpdifInNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("SPDIF IN\n");
+ else if (hdaCodecIsDigInPinNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("DIGITAL IN PIN\n");
+ else if (hdaCodecIsDigOutPinNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("DIGITAL OUT PIN\n");
+ else if (hdaCodecIsCdNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("CD\n");
+ else if (hdaCodecIsVolKnobNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("VOLUME KNOB\n");
+ else if (hdaCodecIsReservedNode(pInfo->pThis, pNode->node.uID))
+ CODECDBG_PRINT("RESERVED\n");
+ else
+ CODECDBG_PRINT("UNKNOWN TYPE 0x%x\n", pNode->node.uID);
+
+ if (fRecursive)
+ {
+#define CODECDBG_PRINT_CONLIST_ENTRY(_aNode, _aEntry) \
+ if (cCnt >= _aEntry) \
+ { \
+ const uint8_t uID = RT_BYTE##_aEntry(_aNode->node.au32F02_param[0x0]); \
+ if (pNode->node.uID == uID) \
+ codecDbgPrintNode(pInfo, _aNode, false /* fRecursive */); \
+ }
+
+ /* Slow recursion, but this is debug stuff anyway. */
+ for (uint8_t i = 0; i < pInfo->pThis->Cfg.cTotalNodes; i++)
+ {
+ const PCODECNODE pSubNode = &pInfo->pThis->aNodes[i];
+ if (pSubNode->node.uID == pNode->node.uID)
+ continue;
+
+ const uint8_t cCnt = CODEC_F00_0E_COUNT(pSubNode->node.au32F00_param[0xE]);
+ if (cCnt == 0) /* No connections present? Skip. */
+ continue;
+
+ CODECDBG_INDENT
+ CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 1)
+ CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 2)
+ CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 3)
+ CODECDBG_PRINT_CONLIST_ENTRY(pSubNode, 4)
+ CODECDBG_UNINDENT
+ }
+
+#undef CODECDBG_PRINT_CONLIST_ENTRY
+ }
+}
+
+
+/**
+ * Worker for hdaR3DbgInfoCodecNodes implementing the 'hdcnodes' info item.
+ */
+DECLHIDDEN(void) hdaR3CodecDbgListNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+
+ pHlp->pfnPrintf(pHlp, "HDA LINK / INPUTS\n");
+
+ CODECDEBUG DbgInfo;
+ DbgInfo.pHlp = pHlp;
+ DbgInfo.pThis = pThis;
+ DbgInfo.uLevel = 0;
+
+ PCODECDEBUG pInfo = &DbgInfo;
+
+ CODECDBG_INDENT
+ for (uint8_t i = 0; i < pThis->Cfg.cTotalNodes; i++)
+ {
+ PCODECNODE pNode = &pThis->aNodes[i];
+
+ /* Start with all nodes which have connection entries set. */
+ if (CODEC_F00_0E_COUNT(pNode->node.au32F00_param[0xE]))
+ codecDbgPrintNode(&DbgInfo, pNode, true /* fRecursive */);
+ }
+ CODECDBG_UNINDENT
+}
+
+
+/**
+ * Worker for hdaR3DbgInfoCodecSelector implementing the 'hdcselector' info item.
+ */
+DECLHIDDEN(void) hdaR3CodecDbgSelector(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pThis, pHlp, pszArgs);
+}
+
+
+#if 0 /* unused */
+static DECLCALLBACK(void) stac9220DbgNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ uint8_t const cTotalNodes = RT_MIN(pThis->Cfg.cTotalNodes, RT_ELEMENTS(pThis->aNodes));
+ for (uint8_t i = 1; i < cTotalNodes; i++)
+ {
+ PCODECNODE pNode = &pThis->aNodes[i];
+ AMPLIFIER *pAmp = &pNode->dac.B_params;
+
+ uint8_t lVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_LEFT, 0) & 0x7f;
+ uint8_t rVol = AMPLIFIER_REGISTER(*pAmp, AMPLIFIER_OUT, AMPLIFIER_RIGHT, 0) & 0x7f;
+
+ pHlp->pfnPrintf(pHlp, "0x%x: lVol=%RU8, rVol=%RU8\n", i, lVol, rVol);
+ }
+}
+#endif
+
+
+/*********************************************************************************************************************************
+* Stream and State Management *
+*********************************************************************************************************************************/
+
+int hdaR3CodecAddStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ case PDMAUDIOMIXERCTL_FRONT:
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOMIXERCTL_CENTER_LFE:
+ case PDMAUDIOMIXERCTL_REAR:
+#endif
+ break;
+
+ case PDMAUDIOMIXERCTL_LINE_IN:
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOMIXERCTL_MIC_IN:
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Mixer control %#x not implemented\n", enmMixerCtl));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = hdaR3MixerAddStream(pThis, enmMixerCtl, pCfg);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+int hdaR3CodecRemoveStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = hdaR3MixerRemoveStream(pThis, enmMixerCtl, fImmediate);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Saved the codec state.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance of the HDA device.
+ * @param pThis The codec instance data.
+ * @param pSSM The saved state handle.
+ */
+int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ AssertLogRelMsgReturn(pThis->Cfg.cTotalNodes == STAC9221_NUM_NODES, ("cTotalNodes=%#x, should be 0x1c", pThis->Cfg.cTotalNodes),
+ VERR_INTERNAL_ERROR);
+ pHlp->pfnSSMPutU32(pSSM, pThis->Cfg.cTotalNodes);
+ for (unsigned idxNode = 0; idxNode < pThis->Cfg.cTotalNodes; ++idxNode)
+ pHlp->pfnSSMPutStructEx(pSSM, &pThis->aNodes[idxNode].SavedState, sizeof(pThis->aNodes[idxNode].SavedState),
+ 0 /*fFlags*/, g_aCodecNodeFields, NULL /*pvUser*/);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads the codec state.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance of the HDA device.
+ * @param pThis The codec instance data.
+ * @param pSSM The saved state handle.
+ * @param uVersion The state version.
+ */
+int hdaR3CodecLoadState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ PCSSMFIELD pFields = NULL;
+ uint32_t fFlags = 0;
+ if (uVersion >= HDA_SAVED_STATE_VERSION_4)
+ {
+ /* Since version 4 a flexible node count is supported. */
+ uint32_t cNodes;
+ int rc2 = pHlp->pfnSSMGetU32(pSSM, &cNodes);
+ AssertRCReturn(rc2, rc2);
+ AssertReturn(cNodes == 0x1c, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+
+ pFields = g_aCodecNodeFields;
+ fFlags = 0;
+ }
+ else if (uVersion >= HDA_SAVED_STATE_VERSION_2)
+ {
+ AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+ pFields = g_aCodecNodeFields;
+ fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED;
+ }
+ else if (uVersion >= HDA_SAVED_STATE_VERSION_1)
+ {
+ AssertReturn(pThis->Cfg.cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+ pFields = g_aCodecNodeFieldsV1;
+ fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED;
+ }
+ else
+ AssertFailedReturn(VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+
+ for (unsigned idxNode = 0; idxNode < pThis->Cfg.cTotalNodes; ++idxNode)
+ {
+ uint8_t idOld = pThis->aNodes[idxNode].SavedState.Core.uID;
+ int rc = pHlp->pfnSSMGetStructEx(pSSM, &pThis->aNodes[idxNode].SavedState, sizeof(pThis->aNodes[idxNode].SavedState),
+ fFlags, pFields, NULL);
+ AssertRCReturn(rc, rc);
+ AssertLogRelMsgReturn(idOld == pThis->aNodes[idxNode].SavedState.Core.uID,
+ ("loaded %#x, expected %#x\n", pThis->aNodes[idxNode].SavedState.Core.uID, idOld),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ /*
+ * Update stuff after changing the state.
+ */
+ PCODECNODE pNode;
+ if (hdaCodecIsDacNode(pThis, pThis->Cfg.idxDacLineOut))
+ {
+ pNode = &pThis->aNodes[pThis->Cfg.idxDacLineOut];
+ hdaR3CodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT);
+ }
+ else if (hdaCodecIsSpdifOutNode(pThis, pThis->Cfg.idxDacLineOut))
+ {
+ pNode = &pThis->aNodes[pThis->Cfg.idxDacLineOut];
+ hdaR3CodecToAudVolume(pThis, pNode, &pNode->spdifout.B_params, PDMAUDIOMIXERCTL_FRONT);
+ }
+
+ pNode = &pThis->aNodes[pThis->Cfg.idxAdcVolsLineIn];
+ hdaR3CodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN);
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Powers off the codec (ring-3).
+ *
+ * @param pThis The codec data.
+ */
+void hdaR3CodecPowerOff(PHDACODECR3 pThis)
+{
+ LogFlowFuncEnter();
+ LogRel2(("HDA: Powering off codec ...\n"));
+
+ int rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_FRONT, true /*fImmediate*/);
+ AssertRC(rc2);
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, true /*fImmediate*/);
+ AssertRC(rc2);
+ rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_REAR, true /*fImmediate*/);
+ AssertRC(rc2);
+#endif
+
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_MIC_IN, true /*fImmediate*/);
+ AssertRC(rc2);
+#endif
+ rc2 = hdaR3CodecRemoveStream(pThis, PDMAUDIOMIXERCTL_LINE_IN, true /*fImmediate*/);
+ AssertRC(rc2);
+}
+
+
+/**
+ * Constructs a codec (ring-3).
+ *
+ * @returns VBox status code.
+ * @param pDevIns The associated device instance.
+ * @param pThis The codec data.
+ * @param uLUN Device LUN to assign.
+ * @param pCfg CFGM node to use for configuration.
+ */
+int hdaR3CodecConstruct(PPDMDEVINS pDevIns, PHDACODECR3 pThis, uint16_t uLUN, PCFGMNODE pCfg)
+{
+ AssertPtrReturn(pDevIns, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ HDACODECCFG *pCodecCfg = (HDACODECCFG *)&pThis->Cfg;
+
+ pCodecCfg->id = uLUN;
+ pCodecCfg->enmType = CODECTYPE_STAC9220; /** @todo Make this dynamic. */
+
+ int rc;
+
+ switch (pCodecCfg->enmType)
+ {
+ case CODECTYPE_STAC9220:
+ {
+ rc = stac9220Construct(pThis, pCodecCfg);
+ AssertRCReturn(rc, rc);
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+ break;
+ }
+
+ /*
+ * Set initial volume.
+ */
+ PCODECNODE pNode = &pThis->aNodes[pCodecCfg->idxDacLineOut];
+ rc = hdaR3CodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT);
+ AssertRCReturn(rc, rc);
+
+ pNode = &pThis->aNodes[pCodecCfg->idxAdcVolsLineIn];
+ rc = hdaR3CodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN);
+ AssertRCReturn(rc, rc);
+
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+# error "Implement mic-in support!"
+#endif
+
+ /*
+ * Statistics
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatLookupsR3, STAMTYPE_COUNTER, "Codec/LookupsR0", STAMUNIT_OCCURENCES, "Number of R0 codecLookup calls");
+#if 0 /* Codec is not yet kosher enough for ring-0. @bugref{9890c64} */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatLookupsR0, STAMTYPE_COUNTER, "Codec/LookupsR3", STAMUNIT_OCCURENCES, "Number of R3 codecLookup calls");
+#endif
+
+ return rc;
+}
+
+
+/**
+ * Destructs a codec.
+ *
+ * @param pThis Codec to destruct.
+ */
+void hdaCodecDestruct(PHDACODECR3 pThis)
+{
+ LogFlowFuncEnter();
+
+ /* Nothing to do here atm. */
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Resets a codec.
+ *
+ * @param pThis Codec to reset.
+ */
+void hdaCodecReset(PHDACODECR3 pThis)
+{
+ switch (pThis->Cfg.enmType)
+ {
+ case CODECTYPE_STAC9220:
+ stac9220Reset(pThis);
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+}
+
diff --git a/src/VBox/Devices/Audio/DevHdaCodec.h b/src/VBox/Devices/Audio/DevHdaCodec.h
new file mode 100644
index 00000000..00890922
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHdaCodec.h
@@ -0,0 +1,909 @@
+/* $Id: DevHdaCodec.h $ */
+/** @file
+ * Intel HD Audio Controller Emulation - Codec, Sigmatel/IDT STAC9220.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h
+#define VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h
+# error "Only include DevHda.h!"
+#endif
+
+#include <iprt/list.h>
+#include "AudioMixer.h"
+
+
+/** The ICH HDA (Intel) ring-3 codec state. */
+typedef struct HDACODECR3 *PHDACODECR3;
+
+/**
+ * Enumeration specifying the codec type to use.
+ */
+typedef enum CODECTYPE
+{
+ /** Invalid, do not use. */
+ CODECTYPE_INVALID = 0,
+ /** SigmaTel 9220 (922x). */
+ CODECTYPE_STAC9220,
+ /** Hack to blow the type up to 32-bit. */
+ CODECTYPE_32BIT_HACK = 0x7fffffff
+} CODECTYPE;
+
+/* PRM 5.3.1 */
+/** Codec address mask. */
+#define CODEC_CAD_MASK 0xF0000000
+/** Codec address shift. */
+#define CODEC_CAD_SHIFT 28
+#define CODEC_DIRECT_MASK RT_BIT(27)
+/** Node ID mask. */
+#define CODEC_NID_MASK 0x07F00000
+/** Node ID shift. */
+#define CODEC_NID_SHIFT 20
+#define CODEC_VERBDATA_MASK 0x000FFFFF
+#define CODEC_VERB_4BIT_CMD 0x000FFFF0
+#define CODEC_VERB_4BIT_DATA 0x0000000F
+#define CODEC_VERB_8BIT_CMD 0x000FFF00
+#define CODEC_VERB_8BIT_DATA 0x000000FF
+#define CODEC_VERB_16BIT_CMD 0x000F0000
+#define CODEC_VERB_16BIT_DATA 0x0000FFFF
+
+#define CODEC_CAD(cmd) (((cmd) & CODEC_CAD_MASK) >> CODEC_CAD_SHIFT)
+#define CODEC_DIRECT(cmd) ((cmd) & CODEC_DIRECT_MASK)
+#define CODEC_NID(cmd) ((((cmd) & CODEC_NID_MASK)) >> CODEC_NID_SHIFT)
+#define CODEC_VERBDATA(cmd) ((cmd) & CODEC_VERBDATA_MASK)
+#define CODEC_VERB_CMD(cmd, mask, x) (((cmd) & (mask)) >> (x))
+#define CODEC_VERB_CMD4(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_4BIT_CMD, 4))
+#define CODEC_VERB_CMD8(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_8BIT_CMD, 8))
+#define CODEC_VERB_CMD16(cmd) (CODEC_VERB_CMD((cmd), CODEC_VERB_16BIT_CMD, 16))
+#define CODEC_VERB_PAYLOAD4(cmd) ((cmd) & CODEC_VERB_4BIT_DATA)
+#define CODEC_VERB_PAYLOAD8(cmd) ((cmd) & CODEC_VERB_8BIT_DATA)
+#define CODEC_VERB_PAYLOAD16(cmd) ((cmd) & CODEC_VERB_16BIT_DATA)
+
+#define CODEC_VERB_GET_AMP_DIRECTION RT_BIT(15)
+#define CODEC_VERB_GET_AMP_SIDE RT_BIT(13)
+#define CODEC_VERB_GET_AMP_INDEX 0x7
+
+/* HDA spec 7.3.3.7 NoteA */
+#define CODEC_GET_AMP_DIRECTION(cmd) (((cmd) & CODEC_VERB_GET_AMP_DIRECTION) >> 15)
+#define CODEC_GET_AMP_SIDE(cmd) (((cmd) & CODEC_VERB_GET_AMP_SIDE) >> 13)
+#define CODEC_GET_AMP_INDEX(cmd) (CODEC_GET_AMP_DIRECTION(cmd) ? 0 : ((cmd) & CODEC_VERB_GET_AMP_INDEX))
+
+/* HDA spec 7.3.3.7 NoteC */
+#define CODEC_VERB_SET_AMP_OUT_DIRECTION RT_BIT(15)
+#define CODEC_VERB_SET_AMP_IN_DIRECTION RT_BIT(14)
+#define CODEC_VERB_SET_AMP_LEFT_SIDE RT_BIT(13)
+#define CODEC_VERB_SET_AMP_RIGHT_SIDE RT_BIT(12)
+#define CODEC_VERB_SET_AMP_INDEX (0x7 << 8)
+#define CODEC_VERB_SET_AMP_MUTE RT_BIT(7)
+/** Note: 7-bit value [6:0]. */
+#define CODEC_VERB_SET_AMP_GAIN 0x7F
+
+#define CODEC_SET_AMP_IS_OUT_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_OUT_DIRECTION) != 0)
+#define CODEC_SET_AMP_IS_IN_DIRECTION(cmd) (((cmd) & CODEC_VERB_SET_AMP_IN_DIRECTION) != 0)
+#define CODEC_SET_AMP_IS_LEFT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_LEFT_SIDE) != 0)
+#define CODEC_SET_AMP_IS_RIGHT_SIDE(cmd) (((cmd) & CODEC_VERB_SET_AMP_RIGHT_SIDE) != 0)
+#define CODEC_SET_AMP_INDEX(cmd) (((cmd) & CODEC_VERB_SET_AMP_INDEX) >> 7)
+#define CODEC_SET_AMP_MUTE(cmd) ((cmd) & CODEC_VERB_SET_AMP_MUTE)
+#define CODEC_SET_AMP_GAIN(cmd) ((cmd) & CODEC_VERB_SET_AMP_GAIN)
+
+/* HDA spec 7.3.3.1 defines layout of configuration registers/verbs (0xF00) */
+/* VendorID (7.3.4.1) */
+#define CODEC_MAKE_F00_00(vendorID, deviceID) (((vendorID) << 16) | (deviceID))
+#define CODEC_F00_00_VENDORID(f00_00) (((f00_00) >> 16) & 0xFFFF)
+#define CODEC_F00_00_DEVICEID(f00_00) ((f00_00) & 0xFFFF)
+
+/** RevisionID (7.3.4.2). */
+#define CODEC_MAKE_F00_02(majRev, minRev, venFix, venProg, stepFix, stepProg) \
+ ( (((majRev) & 0xF) << 20) \
+ | (((minRev) & 0xF) << 16) \
+ | (((venFix) & 0xF) << 12) \
+ | (((venProg) & 0xF) << 8) \
+ | (((stepFix) & 0xF) << 4) \
+ | ((stepProg) & 0xF))
+
+/** Subordinate node count (7.3.4.3). */
+#define CODEC_MAKE_F00_04(startNodeNumber, totalNodeNumber) ((((startNodeNumber) & 0xFF) << 16)|((totalNodeNumber) & 0xFF))
+#define CODEC_F00_04_TO_START_NODE_NUMBER(f00_04) (((f00_04) >> 16) & 0xFF)
+#define CODEC_F00_04_TO_NODE_COUNT(f00_04) ((f00_04) & 0xFF)
+/*
+ * Function Group Type (7.3.4.4)
+ * 0 & [0x3-0x7f] are reserved types
+ * [0x80 - 0xff] are vendor defined function groups
+ */
+#define CODEC_MAKE_F00_05(UnSol, NodeType) (((UnSol) << 8)|(NodeType))
+#define CODEC_F00_05_UNSOL RT_BIT(8)
+#define CODEC_F00_05_AFG (0x1)
+#define CODEC_F00_05_MFG (0x2)
+#define CODEC_F00_05_IS_UNSOL(f00_05) RT_BOOL((f00_05) & RT_BIT(8))
+#define CODEC_F00_05_GROUP(f00_05) ((f00_05) & 0xff)
+/* Audio Function Group capabilities (7.3.4.5). */
+#define CODEC_MAKE_F00_08(BeepGen, InputDelay, OutputDelay) ((((BeepGen) & 0x1) << 16)| (((InputDelay) & 0xF) << 8) | ((OutputDelay) & 0xF))
+#define CODEC_F00_08_BEEP_GEN(f00_08) ((f00_08) & RT_BIT(16)
+
+/* Converter Stream, Channel (7.3.3.11). */
+#define CODEC_F00_06_GET_STREAM_ID(cmd) (((cmd) >> 4) & 0x0F)
+#define CODEC_F00_06_GET_CHANNEL_ID(cmd) (((cmd) & 0x0F))
+
+/* Widget Capabilities (7.3.4.6). */
+#define CODEC_MAKE_F00_09(type, delay, chan_ext) \
+ ( (((type) & 0xF) << 20) \
+ | (((delay) & 0xF) << 16) \
+ | (((chan_ext) & 0xF) << 13))
+/* note: types 0x8-0xe are reserved */
+#define CODEC_F00_09_TYPE_AUDIO_OUTPUT (0x0)
+#define CODEC_F00_09_TYPE_AUDIO_INPUT (0x1)
+#define CODEC_F00_09_TYPE_AUDIO_MIXER (0x2)
+#define CODEC_F00_09_TYPE_AUDIO_SELECTOR (0x3)
+#define CODEC_F00_09_TYPE_PIN_COMPLEX (0x4)
+#define CODEC_F00_09_TYPE_POWER_WIDGET (0x5)
+#define CODEC_F00_09_TYPE_VOLUME_KNOB (0x6)
+#define CODEC_F00_09_TYPE_BEEP_GEN (0x7)
+#define CODEC_F00_09_TYPE_VENDOR_DEFINED (0xF)
+
+#define CODEC_F00_09_CAP_CP RT_BIT(12)
+#define CODEC_F00_09_CAP_L_R_SWAP RT_BIT(11)
+#define CODEC_F00_09_CAP_POWER_CTRL RT_BIT(10)
+#define CODEC_F00_09_CAP_DIGITAL RT_BIT(9)
+#define CODEC_F00_09_CAP_CONNECTION_LIST RT_BIT(8)
+#define CODEC_F00_09_CAP_UNSOL RT_BIT(7)
+#define CODEC_F00_09_CAP_PROC_WIDGET RT_BIT(6)
+#define CODEC_F00_09_CAP_STRIPE RT_BIT(5)
+#define CODEC_F00_09_CAP_FMT_OVERRIDE RT_BIT(4)
+#define CODEC_F00_09_CAP_AMP_FMT_OVERRIDE RT_BIT(3)
+#define CODEC_F00_09_CAP_OUT_AMP_PRESENT RT_BIT(2)
+#define CODEC_F00_09_CAP_IN_AMP_PRESENT RT_BIT(1)
+#define CODEC_F00_09_CAP_STEREO RT_BIT(0)
+
+#define CODEC_F00_09_TYPE(f00_09) (((f00_09) >> 20) & 0xF)
+
+#define CODEC_F00_09_IS_CAP_CP(f00_09) RT_BOOL((f00_09) & RT_BIT(12))
+#define CODEC_F00_09_IS_CAP_L_R_SWAP(f00_09) RT_BOOL((f00_09) & RT_BIT(11))
+#define CODEC_F00_09_IS_CAP_POWER_CTRL(f00_09) RT_BOOL((f00_09) & RT_BIT(10))
+#define CODEC_F00_09_IS_CAP_DIGITAL(f00_09) RT_BOOL((f00_09) & RT_BIT(9))
+#define CODEC_F00_09_IS_CAP_CONNECTION_LIST(f00_09) RT_BOOL((f00_09) & RT_BIT(8))
+#define CODEC_F00_09_IS_CAP_UNSOL(f00_09) RT_BOOL((f00_09) & RT_BIT(7))
+#define CODEC_F00_09_IS_CAP_PROC_WIDGET(f00_09) RT_BOOL((f00_09) & RT_BIT(6))
+#define CODEC_F00_09_IS_CAP_STRIPE(f00_09) RT_BOOL((f00_09) & RT_BIT(5))
+#define CODEC_F00_09_IS_CAP_FMT_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(4))
+#define CODEC_F00_09_IS_CAP_AMP_OVERRIDE(f00_09) RT_BOOL((f00_09) & RT_BIT(3))
+#define CODEC_F00_09_IS_CAP_OUT_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(2))
+#define CODEC_F00_09_IS_CAP_IN_AMP_PRESENT(f00_09) RT_BOOL((f00_09) & RT_BIT(1))
+#define CODEC_F00_09_IS_CAP_LSB(f00_09) RT_BOOL((f00_09) & RT_BIT(0))
+
+/* Supported PCM size, rates (7.3.4.7) */
+#define CODEC_F00_0A_32_BIT RT_BIT(19)
+#define CODEC_F00_0A_24_BIT RT_BIT(18)
+#define CODEC_F00_0A_16_BIT RT_BIT(17)
+#define CODEC_F00_0A_8_BIT RT_BIT(16)
+
+#define CODEC_F00_0A_48KHZ_MULT_8X RT_BIT(11)
+#define CODEC_F00_0A_48KHZ_MULT_4X RT_BIT(10)
+#define CODEC_F00_0A_44_1KHZ_MULT_4X RT_BIT(9)
+#define CODEC_F00_0A_48KHZ_MULT_2X RT_BIT(8)
+#define CODEC_F00_0A_44_1KHZ_MULT_2X RT_BIT(7)
+#define CODEC_F00_0A_48KHZ RT_BIT(6)
+#define CODEC_F00_0A_44_1KHZ RT_BIT(5)
+/* 2/3 * 48kHz */
+#define CODEC_F00_0A_48KHZ_2_3X RT_BIT(4)
+/* 1/2 * 44.1kHz */
+#define CODEC_F00_0A_44_1KHZ_1_2X RT_BIT(3)
+/* 1/3 * 48kHz */
+#define CODEC_F00_0A_48KHZ_1_3X RT_BIT(2)
+/* 1/4 * 44.1kHz */
+#define CODEC_F00_0A_44_1KHZ_1_4X RT_BIT(1)
+/* 1/6 * 48kHz */
+#define CODEC_F00_0A_48KHZ_1_6X RT_BIT(0)
+
+/* Supported streams formats (7.3.4.8) */
+#define CODEC_F00_0B_AC3 RT_BIT(2)
+#define CODEC_F00_0B_FLOAT32 RT_BIT(1)
+#define CODEC_F00_0B_PCM RT_BIT(0)
+
+/* Pin Capabilities (7.3.4.9)*/
+#define CODEC_MAKE_F00_0C(vref_ctrl) (((vref_ctrl) & 0xFF) << 8)
+#define CODEC_F00_0C_CAP_HBR RT_BIT(27)
+#define CODEC_F00_0C_CAP_DP RT_BIT(24)
+#define CODEC_F00_0C_CAP_EAPD RT_BIT(16)
+#define CODEC_F00_0C_CAP_HDMI RT_BIT(7)
+#define CODEC_F00_0C_CAP_BALANCED_IO RT_BIT(6)
+#define CODEC_F00_0C_CAP_INPUT RT_BIT(5)
+#define CODEC_F00_0C_CAP_OUTPUT RT_BIT(4)
+#define CODEC_F00_0C_CAP_HEADPHONE_AMP RT_BIT(3)
+#define CODEC_F00_0C_CAP_PRESENCE_DETECT RT_BIT(2)
+#define CODEC_F00_0C_CAP_TRIGGER_REQUIRED RT_BIT(1)
+#define CODEC_F00_0C_CAP_IMPENDANCE_SENSE RT_BIT(0)
+
+#define CODEC_F00_0C_IS_CAP_HBR(f00_0c) ((f00_0c) & RT_BIT(27))
+#define CODEC_F00_0C_IS_CAP_DP(f00_0c) ((f00_0c) & RT_BIT(24))
+#define CODEC_F00_0C_IS_CAP_EAPD(f00_0c) ((f00_0c) & RT_BIT(16))
+#define CODEC_F00_0C_IS_CAP_HDMI(f00_0c) ((f00_0c) & RT_BIT(7))
+#define CODEC_F00_0C_IS_CAP_BALANCED_IO(f00_0c) ((f00_0c) & RT_BIT(6))
+#define CODEC_F00_0C_IS_CAP_INPUT(f00_0c) ((f00_0c) & RT_BIT(5))
+#define CODEC_F00_0C_IS_CAP_OUTPUT(f00_0c) ((f00_0c) & RT_BIT(4))
+#define CODEC_F00_0C_IS_CAP_HP(f00_0c) ((f00_0c) & RT_BIT(3))
+#define CODEC_F00_0C_IS_CAP_PRESENCE_DETECT(f00_0c) ((f00_0c) & RT_BIT(2))
+#define CODEC_F00_0C_IS_CAP_TRIGGER_REQUIRED(f00_0c) ((f00_0c) & RT_BIT(1))
+#define CODEC_F00_0C_IS_CAP_IMPENDANCE_SENSE(f00_0c) ((f00_0c) & RT_BIT(0))
+
+/* Input Amplifier capabilities (7.3.4.10). */
+#define CODEC_MAKE_F00_0D(mute_cap, step_size, num_steps, offset) \
+ ( (((mute_cap) & UINT32_C(0x1)) << 31) \
+ | (((step_size) & UINT32_C(0xFF)) << 16) \
+ | (((num_steps) & UINT32_C(0xFF)) << 8) \
+ | ((offset) & UINT32_C(0xFF)))
+
+#define CODEC_F00_0D_CAP_MUTE RT_BIT(7)
+
+#define CODEC_F00_0D_IS_CAP_MUTE(f00_0d) ( ( f00_0d) & RT_BIT(31))
+#define CODEC_F00_0D_STEP_SIZE(f00_0d) ((( f00_0d) & (0x7F << 16)) >> 16)
+#define CODEC_F00_0D_NUM_STEPS(f00_0d) ((((f00_0d) & (0x7F << 8)) >> 8) + 1)
+#define CODEC_F00_0D_OFFSET(f00_0d) ( (f00_0d) & 0x7F)
+
+/** Indicates that the amplifier can be muted. */
+#define CODEC_AMP_CAP_MUTE 0x1
+/** The amplifier's maximum number of steps. We want
+ * a ~90dB dynamic range, so 64 steps with 1.25dB each
+ * should do the trick.
+ *
+ * As we want to map our range to [0..128] values we can avoid
+ * multiplication and simply doing a shift later.
+ *
+ * Produces -96dB to +0dB.
+ * "0" indicates a step of 0.25dB, "127" indicates a step of 32dB.
+ */
+#define CODEC_AMP_NUM_STEPS 0x7F
+/** The initial gain offset (and when doing a node reset). */
+#define CODEC_AMP_OFF_INITIAL 0x7F
+/** The amplifier's gain step size. */
+#define CODEC_AMP_STEP_SIZE 0x2
+
+/* Output Amplifier capabilities (7.3.4.10) */
+#define CODEC_MAKE_F00_12 CODEC_MAKE_F00_0D
+
+#define CODEC_F00_12_IS_CAP_MUTE(f00_12) CODEC_F00_0D_IS_CAP_MUTE(f00_12)
+#define CODEC_F00_12_STEP_SIZE(f00_12) CODEC_F00_0D_STEP_SIZE(f00_12)
+#define CODEC_F00_12_NUM_STEPS(f00_12) CODEC_F00_0D_NUM_STEPS(f00_12)
+#define CODEC_F00_12_OFFSET(f00_12) CODEC_F00_0D_OFFSET(f00_12)
+
+/* Connection list lenght (7.3.4.11). */
+#define CODEC_MAKE_F00_0E(long_form, length) \
+ ( (((long_form) & 0x1) << 7) \
+ | ((length) & 0x7F))
+/* Indicates short-form NIDs. */
+#define CODEC_F00_0E_LIST_NID_SHORT 0
+/* Indicates long-form NIDs. */
+#define CODEC_F00_0E_LIST_NID_LONG 1
+#define CODEC_F00_0E_IS_LONG(f00_0e) RT_BOOL((f00_0e) & RT_BIT(7))
+#define CODEC_F00_0E_COUNT(f00_0e) ((f00_0e) & 0x7F)
+/* Supported Power States (7.3.4.12) */
+#define CODEC_F00_0F_EPSS RT_BIT(31)
+#define CODEC_F00_0F_CLKSTOP RT_BIT(30)
+#define CODEC_F00_0F_S3D3 RT_BIT(29)
+#define CODEC_F00_0F_D3COLD RT_BIT(4)
+#define CODEC_F00_0F_D3 RT_BIT(3)
+#define CODEC_F00_0F_D2 RT_BIT(2)
+#define CODEC_F00_0F_D1 RT_BIT(1)
+#define CODEC_F00_0F_D0 RT_BIT(0)
+
+/* Processing capabilities 7.3.4.13 */
+#define CODEC_MAKE_F00_10(num, benign) ((((num) & 0xFF) << 8) | ((benign) & 0x1))
+#define CODEC_F00_10_NUM(f00_10) (((f00_10) & (0xFF << 8)) >> 8)
+#define CODEC_F00_10_BENING(f00_10) ((f00_10) & 0x1)
+
+/* GPIO count (7.3.4.14). */
+#define CODEC_MAKE_F00_11(wake, unsol, numgpi, numgpo, numgpio) \
+ ( (((wake) & UINT32_C(0x1)) << 31) \
+ | (((unsol) & UINT32_C(0x1)) << 30) \
+ | (((numgpi) & UINT32_C(0xFF)) << 16) \
+ | (((numgpo) & UINT32_C(0xFF)) << 8) \
+ | ((numgpio) & UINT32_C(0xFF)))
+
+/* Processing States (7.3.3.4). */
+#define CODEC_F03_OFF (0)
+#define CODEC_F03_ON RT_BIT(0)
+#define CODEC_F03_BENING RT_BIT(1)
+/* Power States (7.3.3.10). */
+#define CODEC_MAKE_F05(reset, stopok, error, act, set) \
+ ( (((reset) & 0x1) << 10) \
+ | (((stopok) & 0x1) << 9) \
+ | (((error) & 0x1) << 8) \
+ | (((act) & 0xF) << 4) \
+ | ((set) & 0xF))
+#define CODEC_F05_D3COLD (4)
+#define CODEC_F05_D3 (3)
+#define CODEC_F05_D2 (2)
+#define CODEC_F05_D1 (1)
+#define CODEC_F05_D0 (0)
+
+#define CODEC_F05_IS_RESET(value) (((value) & RT_BIT(10)) != 0)
+#define CODEC_F05_IS_STOPOK(value) (((value) & RT_BIT(9)) != 0)
+#define CODEC_F05_IS_ERROR(value) (((value) & RT_BIT(8)) != 0)
+#define CODEC_F05_ACT(value) (((value) & 0xF0) >> 4)
+#define CODEC_F05_SET(value) (((value) & 0xF))
+
+#define CODEC_F05_GE(p0, p1) ((p0) <= (p1))
+#define CODEC_F05_LE(p0, p1) ((p0) >= (p1))
+
+/* Converter Stream, Channel (7.3.3.11). */
+#define CODEC_MAKE_F06(stream, channel) \
+ ( (((stream) & 0xF) << 4) \
+ | ((channel) & 0xF))
+#define CODEC_F06_STREAM(value) ((value) & 0xF0)
+#define CODEC_F06_CHANNEL(value) ((value) & 0xF)
+
+/* Pin Widged Control (7.3.3.13). */
+#define CODEC_F07_VREF_HIZ (0)
+#define CODEC_F07_VREF_50 (0x1)
+#define CODEC_F07_VREF_GROUND (0x2)
+#define CODEC_F07_VREF_80 (0x4)
+#define CODEC_F07_VREF_100 (0x5)
+#define CODEC_F07_IN_ENABLE RT_BIT(5)
+#define CODEC_F07_OUT_ENABLE RT_BIT(6)
+#define CODEC_F07_OUT_H_ENABLE RT_BIT(7)
+
+/* Volume Knob Control (7.3.3.29). */
+#define CODEC_F0F_IS_DIRECT RT_BIT(7)
+#define CODEC_F0F_VOLUME (0x7F)
+
+/* Unsolicited enabled (7.3.3.14). */
+#define CODEC_MAKE_F08(enable, tag) ((((enable) & 1) << 7) | ((tag) & 0x3F))
+
+/* Converter formats (7.3.3.8) and (3.7.1). */
+/* This is the same format as SDnFMT. */
+#define CODEC_MAKE_A HDA_SDFMT_MAKE
+
+#define CODEC_A_TYPE HDA_SDFMT_TYPE
+#define CODEC_A_TYPE_PCM HDA_SDFMT_TYPE_PCM
+#define CODEC_A_TYPE_NON_PCM HDA_SDFMT_TYPE_NON_PCM
+
+#define CODEC_A_BASE HDA_SDFMT_BASE
+#define CODEC_A_BASE_48KHZ HDA_SDFMT_BASE_48KHZ
+#define CODEC_A_BASE_44KHZ HDA_SDFMT_BASE_44KHZ
+
+/* Pin Sense (7.3.3.15). */
+#define CODEC_MAKE_F09_ANALOG(fPresent, impedance) \
+( (((fPresent) & 0x1) << 31) \
+ | (((impedance) & UINT32_C(0x7FFFFFFF))))
+#define CODEC_F09_ANALOG_NA UINT32_C(0x7FFFFFFF)
+#define CODEC_MAKE_F09_DIGITAL(fPresent, fELDValid) \
+( (((fPresent) & UINT32_C(0x1)) << 31) \
+ | (((fELDValid) & UINT32_C(0x1)) << 30))
+
+#define CODEC_MAKE_F0C(lrswap, eapd, btl) ((((lrswap) & 1) << 2) | (((eapd) & 1) << 1) | ((btl) & 1))
+#define CODEC_FOC_IS_LRSWAP(f0c) RT_BOOL((f0c) & RT_BIT(2))
+#define CODEC_FOC_IS_EAPD(f0c) RT_BOOL((f0c) & RT_BIT(1))
+#define CODEC_FOC_IS_BTL(f0c) RT_BOOL((f0c) & RT_BIT(0))
+/* HDA spec 7.3.3.31 defines layout of configuration registers/verbs (0xF1C) */
+/* Configuration's port connection */
+#define CODEC_F1C_PORT_MASK (0x3)
+#define CODEC_F1C_PORT_SHIFT (30)
+
+#define CODEC_F1C_PORT_COMPLEX (0x0)
+#define CODEC_F1C_PORT_NO_PHYS (0x1)
+#define CODEC_F1C_PORT_FIXED (0x2)
+#define CODEC_F1C_BOTH (0x3)
+
+/* Configuration default: connection */
+#define CODEC_F1C_PORT_MASK (0x3)
+#define CODEC_F1C_PORT_SHIFT (30)
+
+/* Connected to a jack (1/8", ATAPI, ...). */
+#define CODEC_F1C_PORT_COMPLEX (0x0)
+/* No physical connection. */
+#define CODEC_F1C_PORT_NO_PHYS (0x1)
+/* Fixed function device (integrated speaker, integrated mic, ...). */
+#define CODEC_F1C_PORT_FIXED (0x2)
+/* Both, a jack and an internal device are attached. */
+#define CODEC_F1C_BOTH (0x3)
+
+/* Configuration default: Location */
+#define CODEC_F1C_LOCATION_MASK (0x3F)
+#define CODEC_F1C_LOCATION_SHIFT (24)
+
+/* [4:5] bits of location region means chassis attachment */
+#define CODEC_F1C_LOCATION_PRIMARY_CHASSIS (0)
+#define CODEC_F1C_LOCATION_INTERNAL RT_BIT(4)
+#define CODEC_F1C_LOCATION_SECONDRARY_CHASSIS RT_BIT(5)
+#define CODEC_F1C_LOCATION_OTHER RT_BIT(5)
+
+/* [0:3] bits of location region means geometry location attachment */
+#define CODEC_F1C_LOCATION_NA (0)
+#define CODEC_F1C_LOCATION_REAR (0x1)
+#define CODEC_F1C_LOCATION_FRONT (0x2)
+#define CODEC_F1C_LOCATION_LEFT (0x3)
+#define CODEC_F1C_LOCATION_RIGTH (0x4)
+#define CODEC_F1C_LOCATION_TOP (0x5)
+#define CODEC_F1C_LOCATION_BOTTOM (0x6)
+#define CODEC_F1C_LOCATION_SPECIAL_0 (0x7)
+#define CODEC_F1C_LOCATION_SPECIAL_1 (0x8)
+#define CODEC_F1C_LOCATION_SPECIAL_2 (0x9)
+
+/* Configuration default: Device type */
+#define CODEC_F1C_DEVICE_MASK (0xF)
+#define CODEC_F1C_DEVICE_SHIFT (20)
+#define CODEC_F1C_DEVICE_LINE_OUT (0)
+#define CODEC_F1C_DEVICE_SPEAKER (0x1)
+#define CODEC_F1C_DEVICE_HP (0x2)
+#define CODEC_F1C_DEVICE_CD (0x3)
+#define CODEC_F1C_DEVICE_SPDIF_OUT (0x4)
+#define CODEC_F1C_DEVICE_DIGITAL_OTHER_OUT (0x5)
+#define CODEC_F1C_DEVICE_MODEM_LINE_SIDE (0x6)
+#define CODEC_F1C_DEVICE_MODEM_HANDSET_SIDE (0x7)
+#define CODEC_F1C_DEVICE_LINE_IN (0x8)
+#define CODEC_F1C_DEVICE_AUX (0x9)
+#define CODEC_F1C_DEVICE_MIC (0xA)
+#define CODEC_F1C_DEVICE_PHONE (0xB)
+#define CODEC_F1C_DEVICE_SPDIF_IN (0xC)
+#define CODEC_F1C_DEVICE_RESERVED (0xE)
+#define CODEC_F1C_DEVICE_OTHER (0xF)
+
+/* Configuration default: Connection type */
+#define CODEC_F1C_CONNECTION_TYPE_MASK (0xF)
+#define CODEC_F1C_CONNECTION_TYPE_SHIFT (16)
+
+#define CODEC_F1C_CONNECTION_TYPE_UNKNOWN (0)
+#define CODEC_F1C_CONNECTION_TYPE_1_8INCHES (0x1)
+#define CODEC_F1C_CONNECTION_TYPE_1_4INCHES (0x2)
+#define CODEC_F1C_CONNECTION_TYPE_ATAPI (0x3)
+#define CODEC_F1C_CONNECTION_TYPE_RCA (0x4)
+#define CODEC_F1C_CONNECTION_TYPE_OPTICAL (0x5)
+#define CODEC_F1C_CONNECTION_TYPE_OTHER_DIGITAL (0x6)
+#define CODEC_F1C_CONNECTION_TYPE_ANALOG (0x7)
+#define CODEC_F1C_CONNECTION_TYPE_DIN (0x8)
+#define CODEC_F1C_CONNECTION_TYPE_XLR (0x9)
+#define CODEC_F1C_CONNECTION_TYPE_RJ_11 (0xA)
+#define CODEC_F1C_CONNECTION_TYPE_COMBO (0xB)
+#define CODEC_F1C_CONNECTION_TYPE_OTHER (0xF)
+
+/* Configuration's color */
+#define CODEC_F1C_COLOR_MASK (0xF)
+#define CODEC_F1C_COLOR_SHIFT (12)
+#define CODEC_F1C_COLOR_UNKNOWN (0)
+#define CODEC_F1C_COLOR_BLACK (0x1)
+#define CODEC_F1C_COLOR_GREY (0x2)
+#define CODEC_F1C_COLOR_BLUE (0x3)
+#define CODEC_F1C_COLOR_GREEN (0x4)
+#define CODEC_F1C_COLOR_RED (0x5)
+#define CODEC_F1C_COLOR_ORANGE (0x6)
+#define CODEC_F1C_COLOR_YELLOW (0x7)
+#define CODEC_F1C_COLOR_PURPLE (0x8)
+#define CODEC_F1C_COLOR_PINK (0x9)
+#define CODEC_F1C_COLOR_RESERVED_0 (0xA)
+#define CODEC_F1C_COLOR_RESERVED_1 (0xB)
+#define CODEC_F1C_COLOR_RESERVED_2 (0xC)
+#define CODEC_F1C_COLOR_RESERVED_3 (0xD)
+#define CODEC_F1C_COLOR_WHITE (0xE)
+#define CODEC_F1C_COLOR_OTHER (0xF)
+
+/* Configuration's misc */
+#define CODEC_F1C_MISC_MASK (0xF)
+#define CODEC_F1C_MISC_SHIFT (8)
+#define CODEC_F1C_MISC_NONE 0
+#define CODEC_F1C_MISC_JACK_NO_PRESENCE_DETECT RT_BIT(0)
+#define CODEC_F1C_MISC_RESERVED_0 RT_BIT(1)
+#define CODEC_F1C_MISC_RESERVED_1 RT_BIT(2)
+#define CODEC_F1C_MISC_RESERVED_2 RT_BIT(3)
+
+/* Configuration default: Association */
+#define CODEC_F1C_ASSOCIATION_MASK (0xF)
+#define CODEC_F1C_ASSOCIATION_SHIFT (4)
+
+/** Reserved; don't use. */
+#define CODEC_F1C_ASSOCIATION_INVALID 0x0
+#define CODEC_F1C_ASSOCIATION_GROUP_0 0x1
+#define CODEC_F1C_ASSOCIATION_GROUP_1 0x2
+#define CODEC_F1C_ASSOCIATION_GROUP_2 0x3
+#define CODEC_F1C_ASSOCIATION_GROUP_3 0x4
+#define CODEC_F1C_ASSOCIATION_GROUP_4 0x5
+#define CODEC_F1C_ASSOCIATION_GROUP_5 0x6
+#define CODEC_F1C_ASSOCIATION_GROUP_6 0x7
+#define CODEC_F1C_ASSOCIATION_GROUP_7 0x8
+/* Note: Windows OSes will treat group 15 (0xF) as single PIN devices.
+ * The sequence number associated with that group then will be ignored. */
+#define CODEC_F1C_ASSOCIATION_GROUP_15 0xF
+
+/* Configuration default: Association Sequence. */
+#define CODEC_F1C_SEQ_MASK (0xF)
+#define CODEC_F1C_SEQ_SHIFT (0)
+
+/* Implementation identification (7.3.3.30). */
+#define CODEC_MAKE_F20(bmid, bsku, aid) \
+ ( (((bmid) & 0xFFFF) << 16) \
+ | (((bsku) & 0xFF) << 8) \
+ | (((aid) & 0xFF)) \
+ )
+
+/* Macro definition helping in filling the configuration registers. */
+#define CODEC_MAKE_F1C(port_connectivity, location, device, connection_type, color, misc, association, sequence) \
+ ( (((port_connectivity) & 0xF) << CODEC_F1C_PORT_SHIFT) \
+ | (((location) & 0xF) << CODEC_F1C_LOCATION_SHIFT) \
+ | (((device) & 0xF) << CODEC_F1C_DEVICE_SHIFT) \
+ | (((connection_type) & 0xF) << CODEC_F1C_CONNECTION_TYPE_SHIFT) \
+ | (((color) & 0xF) << CODEC_F1C_COLOR_SHIFT) \
+ | (((misc) & 0xF) << CODEC_F1C_MISC_SHIFT) \
+ | (((association) & 0xF) << CODEC_F1C_ASSOCIATION_SHIFT) \
+ | (((sequence) & 0xF)))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The F00 parameter length (in dwords). */
+#define CODECNODE_F00_PARAM_LENGTH 20
+/** The F02 parameter length (in dwords). */
+#define CODECNODE_F02_PARAM_LENGTH 16
+
+/* PRM 5.3.1 */
+#define CODEC_RESPONSE_UNSOLICITED RT_BIT_64(34)
+
+#define AMPLIFIER_SIZE 60
+
+typedef uint32_t AMPLIFIER[AMPLIFIER_SIZE];
+
+/**
+ * Common (or core) codec node structure.
+ */
+typedef struct CODECCOMMONNODE
+{
+ /** The node's ID. */
+ uint8_t uID;
+ /** The node's name. */
+ /** The SDn ID this node is assigned to.
+ * 0 means not assigned, 1 is SDn0. */
+ uint8_t uSD;
+ /** The SDn's channel to use.
+ * Only valid if a valid SDn ID is set. */
+ uint8_t uChannel;
+ /* PRM 5.3.6 */
+ uint32_t au32F00_param[CODECNODE_F00_PARAM_LENGTH];
+ uint32_t au32F02_param[CODECNODE_F02_PARAM_LENGTH];
+} CODECCOMMONNODE;
+AssertCompile(CODECNODE_F00_PARAM_LENGTH == 20); /* saved state */
+AssertCompile(CODECNODE_F02_PARAM_LENGTH == 16); /* saved state */
+AssertCompileSize(CODECCOMMONNODE, (1 + 20 + 16) * sizeof(uint32_t));
+typedef CODECCOMMONNODE *PCODECCOMMONNODE;
+
+/**
+ * Compile time assertion on the expected node size.
+ */
+#define AssertNodeSize(a_Node, a_cParams) \
+ AssertCompile((a_cParams) <= (60 + 6)); /* the max size - saved state */ \
+ AssertCompile( sizeof(a_Node) - sizeof(CODECCOMMONNODE) \
+ == ((a_cParams) * sizeof(uint32_t)) )
+
+typedef struct ROOTCODECNODE
+{
+ CODECCOMMONNODE node;
+} ROOTCODECNODE, *PROOTCODECNODE;
+AssertNodeSize(ROOTCODECNODE, 0);
+
+typedef struct DACNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F0d_param;
+ uint32_t u32F04_param;
+ uint32_t u32F05_param;
+ uint32_t u32F06_param;
+ uint32_t u32F0c_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+
+} DACNODE, *PDACNODE;
+AssertNodeSize(DACNODE, 6 + 60);
+
+typedef struct ADCNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F01_param;
+ uint32_t u32F03_param;
+ uint32_t u32F05_param;
+ uint32_t u32F06_param;
+ uint32_t u32F09_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+} ADCNODE, *PADCNODE;
+AssertNodeSize(DACNODE, 6 + 60);
+
+typedef struct SPDIFOUTNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F05_param;
+ uint32_t u32F06_param;
+ uint32_t u32F09_param;
+ uint32_t u32F0d_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+} SPDIFOUTNODE, *PSPDIFOUTNODE;
+AssertNodeSize(SPDIFOUTNODE, 5 + 60);
+
+typedef struct SPDIFINNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F05_param;
+ uint32_t u32F06_param;
+ uint32_t u32F09_param;
+ uint32_t u32F0d_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+} SPDIFINNODE, *PSPDIFINNODE;
+AssertNodeSize(SPDIFINNODE, 5 + 60);
+
+typedef struct AFGCODECNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F05_param;
+ uint32_t u32F08_param;
+ uint32_t u32F17_param;
+ uint32_t u32F20_param;
+} AFGCODECNODE, *PAFGCODECNODE;
+AssertNodeSize(AFGCODECNODE, 4);
+
+typedef struct PORTNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F01_param;
+ uint32_t u32F07_param;
+ uint32_t u32F08_param;
+ uint32_t u32F09_param;
+ uint32_t u32F1c_param;
+ AMPLIFIER B_params;
+} PORTNODE, *PPORTNODE;
+AssertNodeSize(PORTNODE, 5 + 60);
+
+typedef struct DIGOUTNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F01_param;
+ uint32_t u32F05_param;
+ uint32_t u32F07_param;
+ uint32_t u32F08_param;
+ uint32_t u32F09_param;
+ uint32_t u32F1c_param;
+} DIGOUTNODE, *PDIGOUTNODE;
+AssertNodeSize(DIGOUTNODE, 6);
+
+typedef struct DIGINNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F05_param;
+ uint32_t u32F07_param;
+ uint32_t u32F08_param;
+ uint32_t u32F09_param;
+ uint32_t u32F0c_param;
+ uint32_t u32F1c_param;
+ uint32_t u32F1e_param;
+} DIGINNODE, *PDIGINNODE;
+AssertNodeSize(DIGINNODE, 7);
+
+typedef struct ADCMUXNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F01_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+} ADCMUXNODE, *PADCMUXNODE;
+AssertNodeSize(ADCMUXNODE, 2 + 60);
+
+typedef struct PCBEEPNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F07_param;
+ uint32_t u32F0a_param;
+
+ uint32_t u32A_param;
+ AMPLIFIER B_params;
+ uint32_t u32F1c_param;
+} PCBEEPNODE, *PPCBEEPNODE;
+AssertNodeSize(PCBEEPNODE, 3 + 60 + 1);
+
+typedef struct CDNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F07_param;
+ uint32_t u32F1c_param;
+} CDNODE, *PCDNODE;
+AssertNodeSize(CDNODE, 2);
+
+typedef struct VOLUMEKNOBNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F08_param;
+ uint32_t u32F0f_param;
+} VOLUMEKNOBNODE, *PVOLUMEKNOBNODE;
+AssertNodeSize(VOLUMEKNOBNODE, 2);
+
+typedef struct ADCVOLNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F0c_param;
+ uint32_t u32F01_param;
+ uint32_t u32A_params;
+ AMPLIFIER B_params;
+} ADCVOLNODE, *PADCVOLNODE;
+AssertNodeSize(ADCVOLNODE, 3 + 60);
+
+typedef struct RESNODE
+{
+ CODECCOMMONNODE node;
+ uint32_t u32F05_param;
+ uint32_t u32F06_param;
+ uint32_t u32F07_param;
+ uint32_t u32F1c_param;
+
+ uint32_t u32A_param;
+} RESNODE, *PRESNODE;
+AssertNodeSize(RESNODE, 5);
+
+/**
+ * Used for the saved state.
+ */
+typedef struct CODECSAVEDSTATENODE
+{
+ CODECCOMMONNODE Core;
+ uint32_t au32Params[60 + 6];
+} CODECSAVEDSTATENODE;
+AssertNodeSize(CODECSAVEDSTATENODE, 60 + 6);
+
+typedef union CODECNODE
+{
+ CODECCOMMONNODE node;
+ ROOTCODECNODE root;
+ AFGCODECNODE afg;
+ DACNODE dac;
+ ADCNODE adc;
+ SPDIFOUTNODE spdifout;
+ SPDIFINNODE spdifin;
+ PORTNODE port;
+ DIGOUTNODE digout;
+ DIGINNODE digin;
+ ADCMUXNODE adcmux;
+ PCBEEPNODE pcbeep;
+ CDNODE cdnode;
+ VOLUMEKNOBNODE volumeKnob;
+ ADCVOLNODE adcvol;
+ RESNODE reserved;
+ CODECSAVEDSTATENODE SavedState;
+} CODECNODE, *PCODECNODE;
+AssertNodeSize(CODECNODE, 60 + 6);
+
+#define CODEC_NODES_MAX 32
+
+/** @name CODEC_NODE_CLS_XXX - node classification flags.
+ * @{ */
+#define CODEC_NODE_CLS_Port UINT16_C(0x0001)
+#define CODEC_NODE_CLS_Dac UINT16_C(0x0002)
+#define CODEC_NODE_CLS_AdcVol UINT16_C(0x0004)
+#define CODEC_NODE_CLS_Adc UINT16_C(0x0008)
+#define CODEC_NODE_CLS_AdcMux UINT16_C(0x0010)
+#define CODEC_NODE_CLS_Pcbeep UINT16_C(0x0020)
+#define CODEC_NODE_CLS_SpdifIn UINT16_C(0x0040)
+#define CODEC_NODE_CLS_SpdifOut UINT16_C(0x0080)
+#define CODEC_NODE_CLS_DigInPin UINT16_C(0x0100)
+#define CODEC_NODE_CLS_DigOutPin UINT16_C(0x0200)
+#define CODEC_NODE_CLS_Cd UINT16_C(0x0400)
+#define CODEC_NODE_CLS_VolKnob UINT16_C(0x0800)
+#define CODEC_NODE_CLS_Reserved UINT16_C(0x1000)
+/** @} */
+
+/**
+ * Codec configuration.
+ *
+ * This will not change after construction and is therefore kept in a const
+ * member of HDACODECR3 to encourage compiler optimizations and avoid accidental
+ * modification.
+ */
+typedef struct HDACODECCFG
+{
+ /** Codec implementation type. */
+ CODECTYPE enmType;
+ /** Codec ID. */
+ uint16_t id;
+ uint16_t idVendor;
+ uint16_t idDevice;
+ uint8_t bBSKU;
+ uint8_t idAssembly;
+
+ uint8_t cTotalNodes;
+ uint8_t idxAdcVolsLineIn;
+ uint8_t idxDacLineOut;
+
+ /** Align the lists below so they don't cross cache lines (assumes
+ * CODEC_NODES_MAX is 32). */
+ uint8_t const abPadding1[CODEC_NODES_MAX - 15];
+
+ /** @name Node classifications.
+ * @note These are copies of the g_abStac9220Xxxx arrays in DevHdaCodec.cpp.
+ * They are used both for classifying a node and for processing a class of
+ * nodes.
+ * @{ */
+ uint8_t abPorts[CODEC_NODES_MAX];
+ uint8_t abDacs[CODEC_NODES_MAX];
+ uint8_t abAdcVols[CODEC_NODES_MAX];
+ uint8_t abAdcs[CODEC_NODES_MAX];
+ uint8_t abAdcMuxs[CODEC_NODES_MAX];
+ uint8_t abPcbeeps[CODEC_NODES_MAX];
+ uint8_t abSpdifIns[CODEC_NODES_MAX];
+ uint8_t abSpdifOuts[CODEC_NODES_MAX];
+ uint8_t abDigInPins[CODEC_NODES_MAX];
+ uint8_t abDigOutPins[CODEC_NODES_MAX];
+ uint8_t abCds[CODEC_NODES_MAX];
+ uint8_t abVolKnobs[CODEC_NODES_MAX];
+ uint8_t abReserveds[CODEC_NODES_MAX];
+ /** @} */
+
+ /** The CODEC_NODE_CLS_XXX flags for each node. */
+ uint16_t afNodeClassifications[CODEC_NODES_MAX];
+} HDACODECCFG;
+AssertCompileMemberAlignment(HDACODECCFG, abPorts, CODEC_NODES_MAX);
+AssertCompileSizeAlignment(HDACODECCFG, 64);
+
+
+/**
+ * HDA codec state (ring-3, no shared state).
+ */
+typedef struct HDACODECR3
+{
+ /** The codec configuration - initialized at construction time. */
+ HDACODECCFG const Cfg;
+ /** The state data for each node. */
+ CODECNODE aNodes[CODEC_NODES_MAX];
+ /** Statistics. */
+ STAMCOUNTER StatLookupsR3;
+ /** Size alignment padding. */
+ uint64_t const au64Padding1[7];
+} HDACODECR3;
+AssertCompile(RT_IS_POWER_OF_TWO(CODEC_NODES_MAX));
+AssertCompileMemberAlignment(HDACODECR3, aNodes, 64);
+AssertCompileSizeAlignment(HDACODECR3, 64);
+
+
+/** @name HDA Codec API used by the device emulation.
+ * @{ */
+int hdaR3CodecConstruct(PPDMDEVINS pDevIns, PHDACODECR3 pThis, uint16_t uLUN, PCFGMNODE pCfg);
+void hdaR3CodecPowerOff(PHDACODECR3 pThis);
+int hdaR3CodecLoadState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM, uint32_t uVersion);
+int hdaR3CodecAddStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg);
+int hdaR3CodecRemoveStream(PHDACODECR3 pThis, PDMAUDIOMIXERCTL enmMixerCtl, bool fImmediate);
+
+int hdaCodecSaveState(PPDMDEVINS pDevIns, PHDACODECR3 pThis, PSSMHANDLE pSSM);
+void hdaCodecDestruct(PHDACODECR3 pThis);
+void hdaCodecReset(PHDACODECR3 pThis);
+
+DECLHIDDEN(int) hdaR3CodecLookup(PHDACODECR3 pThis, uint32_t uCmd, uint64_t *puResp);
+DECLHIDDEN(void) hdaR3CodecDbgListNodes(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs);
+DECLHIDDEN(void) hdaR3CodecDbgSelector(PHDACODECR3 pThis, PCDBGFINFOHLP pHlp, const char *pszArgs);
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DevHdaCodec_h */
+
diff --git a/src/VBox/Devices/Audio/DevHdaStream.cpp b/src/VBox/Devices/Audio/DevHdaStream.cpp
new file mode 100644
index 00000000..aa6b0535
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHdaStream.cpp
@@ -0,0 +1,2540 @@
+/* $Id: DevHdaStream.cpp $ */
+/** @file
+ * Intel HD Audio Controller Emulation - Streams.
+ */
+
+/*
+ * Copyright (C) 2017-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/zero.h>
+
+#include <VBox/AssertGuest.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "AudioHlp.h"
+
+#include "DevHda.h"
+
+#ifdef VBOX_WITH_DTRACE
+# include "dtrace/VBoxDD.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA)
+static void hdaStreamSetPositionAbs(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t uLPIB);
+#endif
+#ifdef IN_RING3
+# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+static void hdaR3StreamFlushDmaBounceBufferOutput(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3);
+# endif
+static uint32_t hdaR3StreamHandleDmaBufferOverrun(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink,
+ uint32_t cbNeeded, uint64_t nsNow,
+ const char *pszCaller, uint32_t const cbStreamFree);
+static void hdaR3StreamUpdateDma(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC,
+ PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3);
+#endif
+
+
+#ifdef IN_RING3
+
+/**
+ * Creates an HDA stream.
+ *
+ * @returns VBox status code.
+ * @param pStreamShared The HDA stream to construct - shared bits.
+ * @param pStreamR3 The HDA stream to construct - ring-3 bits.
+ * @param pThis The shared HDA device instance.
+ * @param pThisCC The ring-3 HDA device instance.
+ * @param uSD Stream descriptor number to assign.
+ */
+int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis, PHDASTATER3 pThisCC, uint8_t uSD)
+{
+ pStreamR3->u8SD = uSD;
+ pStreamShared->u8SD = uSD;
+ pStreamR3->pMixSink = NULL;
+ pStreamR3->pHDAStateShared = pThis;
+ pStreamR3->pHDAStateR3 = pThisCC;
+ Assert(pStreamShared->hTimer != NIL_TMTIMERHANDLE); /* hdaR3Construct initalized this one already. */
+
+ pStreamShared->State.fInReset = false;
+ pStreamShared->State.fRunning = false;
+
+ AssertPtr(pStreamR3->pHDAStateR3);
+ AssertPtr(pStreamR3->pHDAStateR3->pDevIns);
+
+ const bool fIsInput = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN;
+
+ if (fIsInput)
+ {
+ pStreamShared->State.Cfg.enmPath = PDMAUDIOPATH_UNKNOWN;
+ pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_IN;
+ }
+ else
+ {
+ pStreamShared->State.Cfg.enmPath = PDMAUDIOPATH_UNKNOWN;
+ pStreamShared->State.Cfg.enmDir = PDMAUDIODIR_OUT;
+ }
+
+ pStreamR3->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled;
+
+ if (pStreamR3->Dbg.Runtime.fEnabled)
+ {
+ int rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileStream, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ fIsInput ? "hdaStreamWriteSD%RU8" : "hdaStreamReadSD%RU8", uSD);
+ AssertRC(rc2);
+
+ /* pFileDMARaw */
+ rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileDMARaw, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ fIsInput ? "hdaDMARawWriteSD%RU8" : "hdaDMARawReadSD%RU8", uSD);
+ AssertRC(rc2);
+
+ /* pFileDMAMapped */
+ rc2 = AudioHlpFileCreateF(&pStreamR3->Dbg.Runtime.pFileDMAMapped, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ fIsInput ? "hdaDMAWriteMappedSD%RU8" : "hdaDMAReadMappedSD%RU8", uSD);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileStream);
+ AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMARaw);
+ AudioHlpFileDelete(pStreamR3->Dbg.Runtime.pFileDMAMapped);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys an HDA stream.
+ *
+ * @param pStreamR3 The HDA stream to destroy - ring-3 bits.
+ */
+void hdaR3StreamDestroy(PHDASTREAMR3 pStreamR3)
+{
+ LogFlowFunc(("[SD%RU8] Destroying ...\n", pStreamR3->u8SD));
+ int rc2;
+
+ if (pStreamR3->State.pAioRegSink)
+ {
+ rc2 = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3);
+ AssertRC(rc2);
+ pStreamR3->State.pAioRegSink = NULL;
+ }
+
+ if (pStreamR3->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamR3->State.pCircBuf);
+ pStreamR3->State.pCircBuf = NULL;
+ pStreamR3->State.StatDmaBufSize = 0;
+ pStreamR3->State.StatDmaBufUsed = 0;
+ }
+
+ if (pStreamR3->Dbg.Runtime.fEnabled)
+ {
+ AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileStream);
+ pStreamR3->Dbg.Runtime.pFileStream = NULL;
+
+ AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMARaw);
+ pStreamR3->Dbg.Runtime.pFileDMARaw = NULL;
+
+ AudioHlpFileDestroy(pStreamR3->Dbg.Runtime.pFileDMAMapped);
+ pStreamR3->Dbg.Runtime.pFileDMAMapped = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Converts an HDA stream's SDFMT register into a given PCM properties structure.
+ *
+ * @returns VBox status code.
+ * @param u16SDFMT The HDA stream's SDFMT value to convert.
+ * @param pProps PCM properties structure to hold converted result on success.
+ */
+int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+
+# define EXTRACT_VALUE(v, mask, shift) ((v & ((mask) << (shift))) >> (shift))
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t u32Hz = EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BASE_RATE_MASK, HDA_SDFMT_BASE_RATE_SHIFT)
+ ? 44100 : 48000;
+ uint32_t u32HzMult = 1;
+ uint32_t u32HzDiv = 1;
+
+ switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT))
+ {
+ case 0: u32HzMult = 1; break;
+ case 1: u32HzMult = 2; break;
+ case 2: u32HzMult = 3; break;
+ case 3: u32HzMult = 4; break;
+ default:
+ LogFunc(("Unsupported multiplier %x\n",
+ EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_MULT_MASK, HDA_SDFMT_MULT_SHIFT)));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT))
+ {
+ case 0: u32HzDiv = 1; break;
+ case 1: u32HzDiv = 2; break;
+ case 2: u32HzDiv = 3; break;
+ case 3: u32HzDiv = 4; break;
+ case 4: u32HzDiv = 5; break;
+ case 5: u32HzDiv = 6; break;
+ case 6: u32HzDiv = 7; break;
+ case 7: u32HzDiv = 8; break;
+ default:
+ LogFunc(("Unsupported divisor %x\n",
+ EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_DIV_MASK, HDA_SDFMT_DIV_SHIFT)));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ uint8_t cbSample = 0;
+ switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT))
+ {
+ case 0:
+ cbSample = 1;
+ break;
+ case 1:
+ cbSample = 2;
+ break;
+ case 4:
+ cbSample = 4;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported bits per sample %x\n",
+ EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT)));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ PDMAudioPropsInit(pProps, cbSample, true /*fSigned*/, (u16SDFMT & 0xf) + 1 /*cChannels*/, u32Hz * u32HzMult / u32HzDiv);
+ /** @todo is there anything we need to / can do about channel assignments? */
+ }
+
+# undef EXTRACT_VALUE
+ return rc;
+}
+
+# ifdef LOG_ENABLED
+void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE)
+{
+ LogFlowFunc(("BDLEs @ 0x%x (%RU16):\n", u64BDLBase, cBDLE));
+ if (!u64BDLBase)
+ return;
+
+ uint32_t cbBDLE = 0;
+ for (uint16_t i = 0; i < cBDLE; i++)
+ {
+ HDABDLEDESC bd;
+ PDMDevHlpPhysRead(pDevIns, u64BDLBase + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
+
+ LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n",
+ i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_F_IOC));
+
+ cbBDLE += bd.u32BufSize;
+ }
+
+ LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE));
+
+ if (!pThis->u64DPBase) /* No DMA base given? Bail out. */
+ return;
+
+ LogFlowFunc(("DMA counters:\n"));
+
+ for (int i = 0; i < cBDLE; i++)
+ {
+ uint32_t uDMACnt;
+ PDMDevHlpPhysRead(pDevIns, (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)),
+ &uDMACnt, sizeof(uDMACnt));
+
+ LogFlowFunc(("\t#%03d DMA @ 0x%x\n", i , uDMACnt));
+ }
+}
+# endif /* LOG_ENABLED */
+
+
+/**
+ * Appends a item to the scheduler.
+ *
+ * @returns VBox status code.
+ * @param pStreamShared The stream which scheduler should be modified.
+ * @param cbCur The period length in guest bytes.
+ * @param cbMaxPeriod The max period in guest bytes.
+ * @param idxLastBdle The last BDLE in the period.
+ * @param pProps The PCM properties.
+ * @param pcbBorrow Where to account for bytes borrowed across buffers
+ * to align scheduling items on frame boundraries.
+ */
+static int hdaR3StreamAddScheduleItem(PHDASTREAM pStreamShared, uint32_t cbCur, uint32_t cbMaxPeriod,
+ uint32_t idxLastBdle, PCPDMAUDIOPCMPROPS pProps, uint32_t *pcbBorrow)
+{
+ /* Check that we've got room (shouldn't ever be a problem). */
+ size_t idx = pStreamShared->State.cSchedule;
+ AssertLogRelReturn(idx + 1 < RT_ELEMENTS(pStreamShared->State.aSchedule), VERR_INTERNAL_ERROR_5);
+
+ /* Figure out the BDLE range for this period. */
+ uint32_t const idxFirstBdle = idx == 0 ? 0
+ : RT_MIN((uint32_t)( pStreamShared->State.aSchedule[idx - 1].idxFirst
+ + pStreamShared->State.aSchedule[idx - 1].cEntries),
+ idxLastBdle);
+
+ pStreamShared->State.aSchedule[idx].idxFirst = (uint8_t)idxFirstBdle;
+ pStreamShared->State.aSchedule[idx].cEntries = idxLastBdle >= idxFirstBdle
+ ? idxLastBdle - idxFirstBdle + 1
+ : pStreamShared->State.cBdles - idxFirstBdle + idxLastBdle + 1;
+
+ /* Deal with borrowing due to unaligned IOC buffers. */
+ uint32_t const cbBorrowed = *pcbBorrow;
+ if (cbBorrowed < cbCur)
+ cbCur -= cbBorrowed;
+ else
+ {
+ /* Note. We can probably gloss over this, but it's not a situation a sane guest would put us, so don't bother for now. */
+ ASSERT_GUEST_MSG_FAILED(("#%u: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n",
+ pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle));
+ LogRelMax(32, ("HDA: Stream #%u has a scheduling error: cbBorrow=%#x cbCur=%#x BDLE[%u..%u]\n",
+ pStreamShared->u8SD, cbBorrowed, cbCur, idxFirstBdle, idxLastBdle));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ uint32_t cbCurAligned = PDMAudioPropsRoundUpBytesToFrame(pProps, cbCur);
+ *pcbBorrow = cbCurAligned - cbCur;
+
+ /* Do we need to split up the period? */
+ if (cbCurAligned <= cbMaxPeriod)
+ {
+ pStreamShared->State.aSchedule[idx].cbPeriod = cbCurAligned;
+ pStreamShared->State.aSchedule[idx].cLoops = 1;
+ }
+ else
+ {
+ /* Reduce till we've below the threshold. */
+ uint32_t cbLoop = cbCurAligned;
+ do
+ cbLoop = cbLoop / 2;
+ while (cbLoop > cbMaxPeriod);
+ cbLoop = PDMAudioPropsRoundUpBytesToFrame(pProps, cbLoop);
+
+ /* Complete the scheduling item. */
+ pStreamShared->State.aSchedule[idx].cbPeriod = cbLoop;
+ pStreamShared->State.aSchedule[idx].cLoops = cbCurAligned / cbLoop;
+
+ /* If there is a remainder, add it as a separate entry (this is
+ why the schedule must be more than twice the size of the BDL).*/
+ cbCurAligned %= cbLoop;
+ if (cbCurAligned)
+ {
+ pStreamShared->State.aSchedule[idx + 1] = pStreamShared->State.aSchedule[idx];
+ idx++;
+ pStreamShared->State.aSchedule[idx].cbPeriod = cbCurAligned;
+ pStreamShared->State.aSchedule[idx].cLoops = 1;
+ }
+ }
+
+ /* Done. */
+ pStreamShared->State.cSchedule = (uint16_t)(idx + 1);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the DMA timer schedule for the stream
+ *
+ * This is called from the stream setup code.
+ *
+ * @returns VBox status code.
+ * @param pStreamShared The stream to create a schedule for. The BDL
+ * must be loaded.
+ * @param cSegments Number of BDL segments.
+ * @param cBufferIrqs Number of the BDLEs with IOC=1.
+ * @param cbTotal The total BDL length in guest bytes.
+ * @param cbMaxPeriod Max period in guest bytes. This is in case the
+ * guest want to play the whole "Der Ring des
+ * Nibelungen" cycle in one go.
+ * @param cTimerTicksPerSec The DMA timer frequency.
+ * @param pProps The PCM properties.
+ */
+static int hdaR3StreamCreateSchedule(PHDASTREAM pStreamShared, uint32_t cSegments, uint32_t cBufferIrqs, uint32_t cbTotal,
+ uint32_t cbMaxPeriod, uint64_t cTimerTicksPerSec, PCPDMAUDIOPCMPROPS pProps)
+{
+ int rc;
+
+ /*
+ * Reset scheduling state.
+ */
+ RT_ZERO(pStreamShared->State.aSchedule);
+ pStreamShared->State.cSchedule = 0;
+ pStreamShared->State.cSchedulePrologue = 0;
+ pStreamShared->State.idxSchedule = 0;
+ pStreamShared->State.idxScheduleLoop = 0;
+
+ /*
+ * Do the basic schedule compilation.
+ */
+ uint32_t cPotentialPrologue = 0;
+ uint32_t cbBorrow = 0;
+ uint32_t cbCur = 0;
+ uint32_t cbMin = UINT32_MAX;
+ pStreamShared->State.aSchedule[0].idxFirst = 0;
+ for (uint32_t i = 0; i < cSegments; i++)
+ {
+ cbCur += pStreamShared->State.aBdl[i].cb;
+ if (pStreamShared->State.aBdl[i].cb < cbMin)
+ cbMin = pStreamShared->State.aBdl[i].cb;
+ if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+ {
+ rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pProps, &cbBorrow);
+ ASSERT_GUEST_RC_RETURN(rc, rc);
+
+ if (cPotentialPrologue == 0)
+ cPotentialPrologue = pStreamShared->State.cSchedule;
+ cbCur = 0;
+ }
+ }
+
+ /*
+ * Deal with any loose ends.
+ */
+ if (cbCur && cBufferIrqs == 0)
+ {
+ /*
+ * No IOC. Vista ends up here, typically with three buffers configured.
+ *
+ * The perferred option here is to aim at processing one average BDLE with
+ * each DMA timer period, since that best matches how we update LPIB at
+ * present.
+ *
+ * The second alternative is to divide the whole span up into 3-4 periods
+ * to try increase our chances of keeping ahead of the guest. We may need
+ * to pick this if there are too few buffer descriptor or they are too small.
+ *
+ * However, what we probably should be doing is to do real DMA work whenever
+ * the guest reads a DMA related register (like LPIB) and just do 3-4 DMA
+ * timer periods, however we'll be postponing the DMA timer every time we
+ * return to ring-3 and signal the AIO, so in the end we'd probably not use
+ * the timer callback at all. (This is assuming a small shared per-stream
+ * buffer for keeping the DMA data in and that it's size will force a return
+ * to ring-3 often enough to keep the AIO thread going at a reasonable rate.)
+ */
+ Assert(cbCur == cbTotal);
+
+ /* Match the BDLEs 1:1 if there are 3 or more and that the smallest one
+ is at least 5ms big. */
+ if (cSegments >= 3 && PDMAudioPropsBytesToMilli(pProps, cbMin) >= 5 /*ms*/)
+ {
+ for (uint32_t i = 0; i < cSegments; i++)
+ {
+ rc = hdaR3StreamAddScheduleItem(pStreamShared, pStreamShared->State.aBdl[i].cb, cbMaxPeriod, i, pProps, &cbBorrow);
+ ASSERT_GUEST_RC_RETURN(rc, rc);
+ }
+ }
+ /* Otherwise, just divide the work into 3 or 4 portions and hope for the best.
+ It seems, though, that this only really work for windows vista if we avoid
+ working accross buffer lines. */
+ /** @todo This can be simplified/relaxed/uncluttered if we do DMA work when LPIB
+ * is read, assuming ofc that LPIB is read before each buffer update. */
+ else
+ {
+ uint32_t const cPeriods = cSegments != 3 && PDMAudioPropsBytesToMilli(pProps, cbCur) >= 4 * 5 /*ms*/
+ ? 4 : cSegments != 2 ? 3 : 2;
+ uint32_t const cbPeriod = PDMAudioPropsFloorBytesToFrame(pProps, cbCur / cPeriods);
+ uint32_t iBdle = 0;
+ uint32_t offBdle = 0;
+ for (uint32_t iPeriod = 0; iPeriod < cPeriods; iPeriod++)
+ {
+ if (iPeriod + 1 < cPeriods)
+ {
+ offBdle += cbPeriod;
+ while (iBdle < cSegments && offBdle >= pStreamShared->State.aBdl[iBdle].cb)
+ offBdle -= pStreamShared->State.aBdl[iBdle++].cb;
+ rc = hdaR3StreamAddScheduleItem(pStreamShared, cbPeriod, cbMaxPeriod, offBdle != 0 ? iBdle : iBdle - 1,
+ pProps, &cbBorrow);
+ }
+ else
+ rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur - iPeriod * cbPeriod, cbMaxPeriod, cSegments - 1,
+ pProps, &cbBorrow);
+ ASSERT_GUEST_RC_RETURN(rc, rc);
+ }
+
+ }
+ }
+ else if (cbCur)
+ {
+ /* The last BDLE didn't have IOC set, so we must continue processing
+ from the start till we hit one that has. */
+ uint32_t i;
+ for (i = 0; i < cSegments; i++)
+ {
+ cbCur += pStreamShared->State.aBdl[i].cb;
+ if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+ break;
+ }
+ rc = hdaR3StreamAddScheduleItem(pStreamShared, cbCur, cbMaxPeriod, i, pProps, &cbBorrow);
+ ASSERT_GUEST_RC_RETURN(rc, rc);
+
+ /* The initial scheduling items covering the wrap around area are
+ considered a prologue and must not repeated later. */
+ Assert(cPotentialPrologue);
+ pStreamShared->State.cSchedulePrologue = (uint8_t)cPotentialPrologue;
+ }
+
+ AssertLogRelMsgReturn(cbBorrow == 0, ("HDA: Internal scheduling error on stream #%u: cbBorrow=%#x cbTotal=%#x cbCur=%#x\n",
+ pStreamShared->u8SD, cbBorrow, cbTotal, cbCur),
+ VERR_INTERNAL_ERROR_3);
+
+ /*
+ * If there is just one BDLE with IOC set, we have to make sure
+ * we've got at least two periods scheduled, otherwise there is
+ * a very good chance the guest will overwrite the start of the
+ * buffer before we ever get around to reading it.
+ */
+ if (cBufferIrqs == 1)
+ {
+ uint32_t i = pStreamShared->State.cSchedulePrologue;
+ Assert(i < pStreamShared->State.cSchedule);
+ if ( i + 1 == pStreamShared->State.cSchedule
+ && pStreamShared->State.aSchedule[i].cLoops == 1)
+ {
+ uint32_t const cbFirstHalf = PDMAudioPropsFloorBytesToFrame(pProps, pStreamShared->State.aSchedule[i].cbPeriod / 2);
+ uint32_t const cbOtherHalf = pStreamShared->State.aSchedule[i].cbPeriod - cbFirstHalf;
+ pStreamShared->State.aSchedule[i].cbPeriod = cbFirstHalf;
+ if (cbFirstHalf == cbOtherHalf)
+ pStreamShared->State.aSchedule[i].cLoops = 2;
+ else
+ {
+ pStreamShared->State.aSchedule[i + 1] = pStreamShared->State.aSchedule[i];
+ pStreamShared->State.aSchedule[i].cbPeriod = cbOtherHalf;
+ pStreamShared->State.cSchedule++;
+ }
+ }
+ }
+
+ /*
+ * Go over the schduling entries and calculate the timer ticks for each period.
+ */
+ LogRel2(("HDA: Stream #%u schedule: %u items, %u prologue\n",
+ pStreamShared->u8SD, pStreamShared->State.cSchedule, pStreamShared->State.cSchedulePrologue));
+ uint64_t const cbPerSec = PDMAudioPropsFramesToBytes(pProps, pProps->uHz);
+ for (uint32_t i = 0; i < pStreamShared->State.cSchedule; i++)
+ {
+ uint64_t const cTicks = ASMMultU64ByU32DivByU32(cTimerTicksPerSec, pStreamShared->State.aSchedule[i].cbPeriod, cbPerSec);
+ AssertLogRelMsgReturn((uint32_t)cTicks == cTicks, ("cTicks=%RU64 (%#RX64)\n", cTicks, cTicks), VERR_INTERNAL_ERROR_4);
+ pStreamShared->State.aSchedule[i].cPeriodTicks = RT_MAX((uint32_t)cTicks, 16);
+ LogRel2(("HDA: #%u: %u ticks / %u bytes, %u loops, BDLE%u L %u\n", i, pStreamShared->State.aSchedule[i].cPeriodTicks,
+ pStreamShared->State.aSchedule[i].cbPeriod, pStreamShared->State.aSchedule[i].cLoops,
+ pStreamShared->State.aSchedule[i].idxFirst, pStreamShared->State.aSchedule[i].cEntries));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets up ((re-)iniitalizes) an HDA stream.
+ *
+ * @returns VBox status code. VINF_NO_CHANGE if the stream does not need
+ * be set-up again because the stream's (hardware) parameters did
+ * not change.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state (for HW register
+ * parameters).
+ * @param pStreamShared HDA stream to set up, shared portion.
+ * @param pStreamR3 HDA stream to set up, ring-3 portion.
+ * @param uSD Stream descriptor number to assign it.
+ */
+int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD)
+{
+ /* This must be valid all times. */
+ AssertReturn(uSD < HDA_MAX_STREAMS, VERR_INVALID_PARAMETER);
+
+ /* These member can only change on data corruption, despite what the code does further down (bird). */
+ AssertReturn(pStreamShared->u8SD == uSD, VERR_WRONG_ORDER);
+ AssertReturn(pStreamR3->u8SD == uSD, VERR_WRONG_ORDER);
+
+ const uint64_t u64BDLBase = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, uSD),
+ HDA_STREAM_REG(pThis, BDPU, uSD));
+ const uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, uSD);
+ const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD);
+ const uint8_t u8FIFOS = HDA_STREAM_REG(pThis, FIFOS, uSD) + 1;
+ uint8_t u8FIFOW = hdaSDFIFOWToBytes(HDA_STREAM_REG(pThis, FIFOW, uSD));
+ const uint16_t u16FMT = HDA_STREAM_REG(pThis, FMT, uSD);
+
+ /* Is the bare minimum set of registers configured for the stream?
+ * If not, bail out early, as there's nothing to do here for us (yet). */
+ if ( !u64BDLBase
+ || !u16LVI
+ || !u32CBL
+ || !u8FIFOS
+ || !u8FIFOW
+ || !u16FMT)
+ {
+ LogFunc(("[SD%RU8] Registers not set up yet, skipping (re-)initialization\n", uSD));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Convert the config to PDM PCM properties and configure the stream.
+ */
+ PPDMAUDIOSTREAMCFG pCfg = &pStreamShared->State.Cfg;
+ int rc = hdaR3SDFMTToPCMProps(u16FMT, &pCfg->Props);
+ if (RT_SUCCESS(rc))
+ pCfg->enmDir = hdaGetDirFromSD(uSD);
+ else
+ {
+ LogRelMax(32, ("HDA: Warning: Format 0x%x for stream #%RU8 not supported\n", HDA_STREAM_REG(pThis, FMT, uSD), uSD));
+ return rc;
+ }
+
+ ASSERT_GUEST_LOGREL_MSG_RETURN( PDMAudioPropsFrameSize(&pCfg->Props) > 0
+ && u32CBL % PDMAudioPropsFrameSize(&pCfg->Props) == 0,
+ ("CBL for stream #%RU8 does not align to frame size (u32CBL=%u cbFrameSize=%u)\n",
+ uSD, u32CBL, PDMAudioPropsFrameSize(&pCfg->Props)),
+ VERR_INVALID_PARAMETER);
+
+ /* Make sure the guest behaves regarding the stream's FIFO. */
+ ASSERT_GUEST_LOGREL_MSG_STMT(u8FIFOW <= u8FIFOS,
+ ("Guest tried setting a bigger FIFOW (%RU8) than FIFOS (%RU8), limiting\n", u8FIFOW, u8FIFOS),
+ u8FIFOW = u8FIFOS /* ASSUMES that u8FIFOS has been validated. */);
+
+ pStreamShared->u8SD = uSD;
+
+ /* Update all register copies so that we later know that something has changed. */
+ pStreamShared->u64BDLBase = u64BDLBase;
+ pStreamShared->u16LVI = u16LVI;
+ pStreamShared->u32CBL = u32CBL;
+ pStreamShared->u8FIFOS = u8FIFOS;
+ pStreamShared->u8FIFOW = u8FIFOW;
+ pStreamShared->u16FMT = u16FMT;
+
+ /* The the stream's name, based on the direction. */
+ switch (pCfg->enmDir)
+ {
+ case PDMAUDIODIR_IN:
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+# error "Implement me!"
+# else
+ pCfg->enmPath = PDMAUDIOPATH_IN_LINE;
+ RTStrCopy(pCfg->szName, sizeof(pCfg->szName), "Line In");
+# endif
+ break;
+
+ case PDMAUDIODIR_OUT:
+ /* Destination(s) will be set in hdaR3AddStreamOut(),
+ * based on the channels / stream layout. */
+ break;
+
+ default:
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ break;
+ }
+
+ LogRel2(("HDA: Stream #%RU8 DMA @ 0x%x (%RU32 bytes = %RU64ms total)\n", uSD, pStreamShared->u64BDLBase,
+ pStreamShared->u32CBL, PDMAudioPropsBytesToMilli(&pCfg->Props, pStreamShared->u32CBL)));
+
+ /*
+ * Load the buffer descriptor list.
+ *
+ * Section 3.6.2 states that "the BDL should not be modified unless the RUN
+ * bit is 0", so it should be within the specs to read it once here and not
+ * re-read any BDLEs later.
+ */
+ /* Reset BDL state. */
+ RT_ZERO(pStreamShared->State.aBdl);
+ pStreamShared->State.offCurBdle = 0;
+ pStreamShared->State.idxCurBdle = 0;
+
+ uint32_t /*const*/ cTransferFragments = (pStreamShared->u16LVI & 0xff) + 1;
+ if (cTransferFragments <= 1)
+ LogRel(("HDA: Warning: Stream #%RU8 transfer buffer count invalid: (%RU16)! Buggy guest audio driver!\n", uSD, pStreamShared->u16LVI));
+ AssertLogRelReturn(cTransferFragments <= RT_ELEMENTS(pStreamShared->State.aBdl), VERR_INTERNAL_ERROR_5);
+ pStreamShared->State.cBdles = cTransferFragments;
+
+ /* Load them. */
+ rc = PDMDevHlpPCIPhysRead(pDevIns, u64BDLBase, pStreamShared->State.aBdl,
+ sizeof(pStreamShared->State.aBdl[0]) * cTransferFragments);
+ AssertRC(rc);
+
+ /* Check what we just loaded. Refuse overly large buffer lists. */
+ uint64_t cbTotal = 0;
+ uint32_t cBufferIrqs = 0;
+ for (uint32_t i = 0; i < cTransferFragments; i++)
+ {
+ if (pStreamShared->State.aBdl[i].fFlags & HDA_BDLE_F_IOC)
+ cBufferIrqs++;
+ cbTotal += pStreamShared->State.aBdl[i].cb;
+ }
+ ASSERT_GUEST_STMT_RETURN(cbTotal < _2G,
+ LogRelMax(32, ("HDA: Error: Stream #%u is configured with an insane amount of buffer space - refusing do work with it: %RU64 (%#RX64) bytes.\n",
+ uSD, cbTotal, cbTotal)),
+ VERR_NOT_SUPPORTED);
+ ASSERT_GUEST_STMT_RETURN(cbTotal == u32CBL,
+ LogRelMax(32, ("HDA: Warning: Stream #%u has a mismatch between CBL and configured buffer space: %RU32 (%#RX32) vs %RU64 (%#RX64)\n",
+ uSD, u32CBL, u32CBL, cbTotal, cbTotal)),
+ VERR_NOT_SUPPORTED);
+
+ /*
+ * Create a DMA timer schedule.
+ */
+ rc = hdaR3StreamCreateSchedule(pStreamShared, cTransferFragments, cBufferIrqs, (uint32_t)cbTotal,
+ PDMAudioPropsMilliToBytes(&pCfg->Props, 100 /** @todo make configurable */),
+ PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer), &pCfg->Props);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[0].cbPeriod;
+
+ /*
+ * Calculate the transfer Hz for use in the circular buffer calculation
+ * and the average period for the scheduling hint.
+ */
+ uint32_t cbMaxPeriod = 0;
+ uint32_t cbMinPeriod = UINT32_MAX;
+ uint64_t cTicks = 0;
+ uint32_t cPeriods = 0;
+ for (uint32_t i = pStreamShared->State.cSchedulePrologue; i < pStreamShared->State.cSchedule; i++)
+ {
+ uint32_t cbPeriod = pStreamShared->State.aSchedule[i].cbPeriod;
+ cbMaxPeriod = RT_MAX(cbMaxPeriod, cbPeriod);
+ cbMinPeriod = RT_MIN(cbMinPeriod, cbPeriod);
+ cPeriods += pStreamShared->State.aSchedule[i].cLoops;
+ cTicks += pStreamShared->State.aSchedule[i].cPeriodTicks * pStreamShared->State.aSchedule[i].cLoops;
+ }
+ /* Only consider the prologue in relation to the max period. */
+ for (uint32_t i = 0; i < pStreamShared->State.cSchedulePrologue; i++)
+ cbMaxPeriod = RT_MAX(cbMaxPeriod, pStreamShared->State.aSchedule[i].cbPeriod);
+
+ AssertLogRelReturn(cPeriods > 0, VERR_INTERNAL_ERROR_3);
+ uint64_t const cbTransferPerSec = RT_MAX(PDMAudioPropsFramesToBytes(&pCfg->Props, pCfg->Props.uHz),
+ 4096 /* zero div prevention: min is 6kHz, picked 4k in case I'm mistaken */);
+ unsigned uTransferHz = cbTransferPerSec * 1000 / cbMaxPeriod;
+ LogRel2(("HDA: Stream #%RU8 needs a %u.%03u Hz timer rate (period: %u..%u host bytes)\n",
+ uSD, uTransferHz / 1000, uTransferHz % 1000, cbMinPeriod, cbMaxPeriod));
+ uTransferHz /= 1000;
+
+ if (uTransferHz > 400) /* Anything above 400 Hz looks fishy -- tell the user. */
+ LogRelMax(32, ("HDA: Warning: Calculated transfer Hz rate for stream #%RU8 looks incorrect (%u), please re-run with audio debug mode and report a bug\n",
+ uSD, uTransferHz));
+
+ pStreamShared->State.cbAvgTransfer = (uint32_t)(cbTotal + cPeriods - 1) / cPeriods;
+
+ /* Calculate the average scheduling period length in nanoseconds. */
+ uint64_t const cTimerResolution = PDMDevHlpTimerGetFreq(pDevIns, pStreamShared->hTimer);
+ Assert(cTimerResolution <= UINT32_MAX);
+ uint64_t const cNsPerPeriod = ASMMultU64ByU32DivByU32(cTicks / cPeriods, RT_NS_1SEC, cTimerResolution);
+ AssertLogRelReturn(cNsPerPeriod > 0, VERR_INTERNAL_ERROR_3);
+
+ /* For input streams we must determin a pre-buffering requirement.
+ We use the initial delay as a basis here, though we must have at
+ least two max periods worth of data queued up due to the way we
+ work the AIO thread. */
+ pStreamShared->State.fInputPreBuffered = false;
+ pStreamShared->State.cbInputPreBuffer = cbMaxPeriod * 2;
+
+ /*
+ * Set up data transfer stuff.
+ */
+ /* Set I/O scheduling hint for the backends. */
+ pCfg->Device.cMsSchedulingHint = cNsPerPeriod > RT_NS_1MS ? (cNsPerPeriod + RT_NS_1MS / 2) / RT_NS_1MS : 1;
+ LogRel2(("HDA: Stream #%RU8 set scheduling hint for the backends to %RU32ms\n", uSD, pCfg->Device.cMsSchedulingHint));
+
+ /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */
+ /** @todo r=bird: We use LPIB as-is here, so if it's not zero we have to
+ * locate the right place in the schedule and whatnot...
+ *
+ * This is a similar scenario as when loading state saved, btw.
+ */
+ if (HDA_STREAM_REG(pThis, LPIB, uSD) != 0)
+ LogRel2(("HDA: Warning! Stream #%RU8 is set up with LPIB=%#RX32 instead of zero!\n", uSD, HDA_STREAM_REG(pThis, LPIB, uSD)));
+ hdaStreamSetPositionAbs(pStreamShared, pDevIns, pThis, HDA_STREAM_REG(pThis, LPIB, uSD));
+
+# ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pDevIns, pThis, pStreamShared->u64BDLBase, pStreamShared->u16LVI + 1);
+# endif
+
+ /*
+ * Set up internal ring buffer.
+ */
+
+ /* (Re-)Allocate the stream's internal DMA buffer,
+ * based on the timing *and* PCM properties we just got above. */
+ if (pStreamR3->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamR3->State.pCircBuf);
+ pStreamR3->State.pCircBuf = NULL;
+ pStreamR3->State.StatDmaBufSize = 0;
+ pStreamR3->State.StatDmaBufUsed = 0;
+ }
+ pStreamShared->State.offWrite = 0;
+ pStreamShared->State.offRead = 0;
+
+ /*
+ * The default internal ring buffer size must be:
+ *
+ * - Large enough for at least three periodic DMA transfers.
+ *
+ * It is critically important that we don't experience underruns
+ * in the DMA OUT code, because it will cause the buffer processing
+ * to get skewed and possibly overlap with what the guest is updating.
+ * At the time of writing (2021-03-05) there is no code for getting
+ * back into sync there.
+ *
+ * - Large enough for at least three I/O scheduling hints.
+ *
+ * We want to lag behind a DMA period or two, but there must be
+ * sufficent space for the AIO thread to get schedule and shuffle
+ * data thru the mixer and onto the host audio hardware.
+ *
+ * - Both above with plenty to spare.
+ *
+ * So, just take the longest of the two periods and multipling it by 6.
+ * We aren't not talking about very large base buffers heres, so size isn't
+ * an issue.
+ *
+ * Note: Use pCfg->Props as PCM properties here, as we only want to store the
+ * samples we actually need, in other words, skipping the interleaved
+ * channels we don't support / need to save space.
+ */
+ uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, RT_MS_1SEC * 6 / uTransferHz);
+ LogRel2(("HDA: Stream #%RU8 default ring buffer size is %RU32 bytes / %RU64 ms\n",
+ uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
+
+ uint32_t msCircBufCfg = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? pThis->cMsCircBufIn : pThis->cMsCircBufOut;
+ if (msCircBufCfg) /* Anything set via CFGM? */
+ {
+ cbCircBuf = PDMAudioPropsMilliToBytes(&pCfg->Props, msCircBufCfg);
+ LogRel2(("HDA: Stream #%RU8 is using a custom ring buffer size of %RU32 bytes / %RU64 ms\n",
+ uSD, cbCircBuf, PDMAudioPropsBytesToMilli(&pCfg->Props, cbCircBuf)));
+ }
+
+ /* Serious paranoia: */
+ ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf % PDMAudioPropsFrameSize(&pCfg->Props) == 0,
+ ("Ring buffer size (%RU32) for stream #%RU8 not aligned to the (host) frame size (%RU8)\n",
+ cbCircBuf, uSD, PDMAudioPropsFrameSize(&pCfg->Props)),
+ rc = VERR_INVALID_PARAMETER);
+ ASSERT_GUEST_LOGREL_MSG_STMT(cbCircBuf, ("Ring buffer size for stream #%RU8 is invalid\n", uSD),
+ rc = VERR_INVALID_PARAMETER);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCircBufCreate(&pStreamR3->State.pCircBuf, cbCircBuf);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamR3->State.StatDmaBufSize = cbCircBuf;
+
+ /*
+ * Forward the timer frequency hint to TM as well for better accuracy on
+ * systems w/o preemption timers (also good for 'info timers').
+ */
+ PDMDevHlpTimerSetFrequencyHint(pDevIns, pStreamShared->hTimer, uTransferHz);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRelMax(32, ("HDA: Initializing stream #%RU8 failed with %Rrc\n", uSD, rc));
+
+# ifdef VBOX_WITH_DTRACE
+ VBOXDD_HDA_STREAM_SETUP((uint32_t)uSD, rc, pStreamShared->State.Cfg.Props.uHz,
+ pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cPeriodTicks,
+ pStreamShared->State.aSchedule[pStreamShared->State.cSchedule - 1].cbPeriod);
+# endif
+ return rc;
+}
+
+
+/**
+ * Worker for hdaR3StreamReset().
+ *
+ * @returns The default mixer sink, NULL if none found.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param uSD SD# to return mixer sink for.
+ * NULL if not found / handled.
+ */
+static PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATER3 pThisCC, uint8_t uSD)
+{
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN)
+ {
+ const uint8_t uFirstSDI = 0;
+
+ if (uSD == uFirstSDI) /* First SDI. */
+ return &pThisCC->SinkLineIn;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ if (uSD == uFirstSDI + 1)
+ return &pThisCC->SinkMicIn;
+# else
+ /* If we don't have a dedicated Mic-In sink, use the always present Line-In sink. */
+ return &pThisCC->SinkLineIn;
+# endif
+ }
+ else
+ {
+ const uint8_t uFirstSDO = HDA_MAX_SDI;
+
+ if (uSD == uFirstSDO)
+ return &pThisCC->SinkFront;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (uSD == uFirstSDO + 1)
+ return &pThisCC->SinkCenterLFE;
+ if (uSD == uFirstSDO + 2)
+ return &pThisCC->SinkRear;
+# endif
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Resets an HDA stream.
+ *
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pStreamShared HDA stream to reset (shared).
+ * @param pStreamR3 HDA stream to reset (ring-3).
+ * @param uSD Stream descriptor (SD) number to use for this stream.
+ */
+void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD)
+{
+ LogFunc(("[SD%RU8] Reset\n", uSD));
+
+ /*
+ * Assert some sanity.
+ */
+ AssertPtr(pThis);
+ AssertPtr(pStreamShared);
+ AssertPtr(pStreamR3);
+ Assert(uSD < HDA_MAX_STREAMS);
+ Assert(pStreamShared->u8SD == uSD);
+ Assert(pStreamR3->u8SD == uSD);
+ AssertMsg(!pStreamShared->State.fRunning, ("[SD%RU8] Cannot reset stream while in running state\n", uSD));
+
+ /*
+ * Set reset state.
+ */
+ Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false); /* No nested calls. */
+ ASMAtomicXchgBool(&pStreamShared->State.fInReset, true);
+
+ /*
+ * Second, initialize the registers.
+ */
+ /* See 6.2.33: Clear on reset. */
+ HDA_STREAM_REG(pThis, STS, uSD) = 0;
+ /* According to the ICH6 datasheet, 0x40000 is the default value for stream descriptor register 23:20
+ * bits are reserved for stream number 18.2.33, resets SDnCTL except SRST bit. */
+ HDA_STREAM_REG(pThis, CTL, uSD) = HDA_SDCTL_TP | (HDA_STREAM_REG(pThis, CTL, uSD) & HDA_SDCTL_SRST);
+ /* ICH6 defines default values (120 bytes for input and 192 bytes for output descriptors) of FIFO size. 18.2.39. */
+ HDA_STREAM_REG(pThis, FIFOS, uSD) = hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN ? HDA_SDIFIFO_120B : HDA_SDOFIFO_192B;
+ /* See 18.2.38: Always defaults to 0x4 (32 bytes). */
+ HDA_STREAM_REG(pThis, FIFOW, uSD) = HDA_SDFIFOW_32B;
+ HDA_STREAM_REG(pThis, LPIB, uSD) = 0;
+ HDA_STREAM_REG(pThis, CBL, uSD) = 0;
+ HDA_STREAM_REG(pThis, LVI, uSD) = 0;
+ HDA_STREAM_REG(pThis, FMT, uSD) = 0;
+ HDA_STREAM_REG(pThis, BDPU, uSD) = 0;
+ HDA_STREAM_REG(pThis, BDPL, uSD) = 0;
+
+ /* Assign the default mixer sink to the stream. */
+ pStreamR3->pMixSink = hdaR3GetDefaultSink(pThisCC, uSD);
+ if (pStreamR3->State.pAioRegSink)
+ {
+ int rc2 = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3);
+ AssertRC(rc2);
+ pStreamR3->State.pAioRegSink = NULL;
+ }
+
+ /* Reset transfer stuff. */
+ pStreamShared->State.cTransferPendingInterrupts = 0;
+ pStreamShared->State.tsTransferLast = 0;
+ pStreamShared->State.tsTransferNext = 0;
+
+ /* Initialize timestamps. */
+ pStreamShared->State.tsLastTransferNs = 0;
+ pStreamShared->State.tsLastReadNs = 0;
+ pStreamShared->State.tsStart = 0;
+
+ RT_ZERO(pStreamShared->State.aBdl);
+ RT_ZERO(pStreamShared->State.aSchedule);
+ pStreamShared->State.offCurBdle = 0;
+ pStreamShared->State.cBdles = 0;
+ pStreamShared->State.idxCurBdle = 0;
+ pStreamShared->State.cSchedulePrologue = 0;
+ pStreamShared->State.cSchedule = 0;
+ pStreamShared->State.idxSchedule = 0;
+ pStreamShared->State.idxScheduleLoop = 0;
+ pStreamShared->State.fInputPreBuffered = false;
+
+ if (pStreamR3->State.pCircBuf)
+ RTCircBufReset(pStreamR3->State.pCircBuf);
+ pStreamShared->State.offWrite = 0;
+ pStreamShared->State.offRead = 0;
+
+ /* Report that we're done resetting this stream. */
+ HDA_STREAM_REG(pThis, CTL, uSD) = 0;
+
+# ifdef VBOX_WITH_DTRACE
+ VBOXDD_HDA_STREAM_RESET((uint32_t)uSD);
+# endif
+ LogFunc(("[SD%RU8] Reset\n", uSD));
+
+ /* Exit reset mode. */
+ ASMAtomicXchgBool(&pStreamShared->State.fInReset, false);
+}
+
+/**
+ * Enables or disables an HDA audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared HDA device state.
+ * @param pStreamShared HDA stream to enable or disable - shared bits.
+ * @param pStreamR3 HDA stream to enable or disable - ring-3 bits.
+ * @param fEnable Whether to enable or disble the stream.
+ */
+int hdaR3StreamEnable(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable)
+{
+ AssertPtr(pStreamR3);
+ AssertPtr(pStreamShared);
+
+ LogFunc(("[SD%RU8] fEnable=%RTbool, pMixSink=%p\n", pStreamShared->u8SD, fEnable, pStreamR3->pMixSink));
+
+ /* First, enable or disable the stream and the stream's sink, if any. */
+ int rc = VINF_SUCCESS;
+ PAUDMIXSINK const pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pSink)
+ {
+ if (fEnable)
+ {
+ if (pStreamR3->State.pAioRegSink != pSink)
+ {
+ if (pStreamR3->State.pAioRegSink)
+ {
+ rc = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3);
+ AssertRC(rc);
+ }
+ rc = AudioMixerSinkAddUpdateJob(pSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3,
+ pStreamShared->State.Cfg.Device.cMsSchedulingHint);
+ AssertLogRelRC(rc);
+ pStreamR3->State.pAioRegSink = RT_SUCCESS(rc) ? pSink : NULL;
+ }
+ rc = AudioMixerSinkStart(pSink);
+ }
+ else
+ rc = AudioMixerSinkDrainAndStop(pSink,
+ pStreamR3->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf) : 0);
+ }
+ if ( RT_SUCCESS(rc)
+ && fEnable
+ && pStreamR3->Dbg.Runtime.fEnabled)
+ {
+ Assert(AudioHlpPcmPropsAreValidAndSupported(&pStreamShared->State.Cfg.Props));
+
+ if (fEnable)
+ {
+ if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileStream))
+ {
+ int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileStream, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamShared->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+
+ if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMARaw))
+ {
+ int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMARaw, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamShared->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+
+ if (!AudioHlpFileIsOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped))
+ {
+ int rc2 = AudioHlpFileOpen(pStreamR3->Dbg.Runtime.pFileDMAMapped, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamShared->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fEnable)
+ pStreamShared->State.tsTransferLast = 0; /* Make sure it's not stale and messes up WALCLK calculations. */
+ pStreamShared->State.fRunning = fEnable;
+
+ /*
+ * Set the FIFORDY bit when we start running and clear it when stopping.
+ *
+ * This prevents Linux from timing out in snd_hdac_stream_sync when starting
+ * a stream. Technically, Linux also uses the SSYNC feature there, but we
+ * can get away with just setting the FIFORDY bit for now.
+ */
+ if (fEnable)
+ HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) |= HDA_SDSTS_FIFORDY;
+ else
+ HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) &= ~HDA_SDSTS_FIFORDY;
+ }
+
+ LogFunc(("[SD%RU8] rc=%Rrc\n", pStreamShared->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Marks the stream as started.
+ *
+ * Used after the stream has been enabled and the DMA timer has been armed.
+ */
+void hdaR3StreamMarkStarted(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow)
+{
+ pStreamShared->State.tsLastReadNs = RTTimeNanoTS();
+ pStreamShared->State.tsStart = tsNow;
+ Log3Func(("#%u: tsStart=%RU64 tsLastReadNs=%RU64\n",
+ pStreamShared->u8SD, pStreamShared->State.tsStart, pStreamShared->State.tsLastReadNs));
+ RT_NOREF(pDevIns, pThis);
+}
+
+/**
+ * Marks the stream as stopped.
+ */
+void hdaR3StreamMarkStopped(PHDASTREAM pStreamShared)
+{
+ Log3Func(("#%u\n", pStreamShared->u8SD));
+ RT_NOREF(pStreamShared);
+}
+
+#endif /* IN_RING3 */
+#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA)
+
+/**
+ * Updates an HDA stream's current read or write buffer position (depending on the stream type) by
+ * setting its associated LPIB register and DMA position buffer (if enabled) to an absolute value.
+ *
+ * @param pStreamShared HDA stream to update read / write position for (shared).
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param uLPIB Absolute position (in bytes) to set current read / write position to.
+ */
+static void hdaStreamSetPositionAbs(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t uLPIB)
+{
+ AssertPtrReturnVoid(pStreamShared);
+ AssertMsgStmt(uLPIB <= pStreamShared->u32CBL, ("%#x\n", uLPIB), uLPIB = pStreamShared->u32CBL);
+
+ Log3Func(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n", pStreamShared->u8SD, uLPIB, pThis->fDMAPosition));
+
+ /* Update LPIB in any case. */
+ HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) = uLPIB;
+
+ /* Do we need to tell the current DMA position? */
+ if (pThis->fDMAPosition)
+ {
+ /*
+ * Linux switched to using the position buffers some time during 2.6.x.
+ * 2.6.12 used LPIB, 2.6.17 defaulted to DMA position buffers, between
+ * the two version things were being changing quite a bit.
+ *
+ * Since 2.6.17, they will treat a zero DMA position value during the first
+ * period/IRQ as reason to fall back to LPIB mode (see azx_position_ok in
+ * 2.6.27+, and azx_pcm_pointer before that). They later also added
+ * UINT32_MAX to the values causing same.
+ *
+ * Since 2.6.35 azx_position_ok will read the wall clock register before
+ * determining the position.
+ */
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns,
+ pThis->u64DPBase + (pStreamShared->u8SD * 2 * sizeof(uint32_t)),
+ (void *)&uLPIB, sizeof(uint32_t));
+ AssertRC(rc2);
+ }
+}
+
+
+/**
+ * Updates an HDA stream's current read or write buffer position (depending on the stream type) by
+ * adding a value to its associated LPIB register and DMA position buffer (if enabled).
+ *
+ * @note Handles automatic CBL wrap-around.
+ *
+ * @param pStreamShared HDA stream to update read / write position for (shared).
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param cbToAdd Position (in bytes) to add to the current read / write position.
+ */
+static void hdaStreamSetPositionAdd(PHDASTREAM pStreamShared, PPDMDEVINS pDevIns, PHDASTATE pThis, uint32_t cbToAdd)
+{
+ if (cbToAdd) /* No need to update anything if 0. */
+ {
+ uint32_t const uCBL = pStreamShared->u32CBL;
+ if (uCBL) /* paranoia */
+ {
+ uint32_t uNewLpid = HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD) + cbToAdd;
+# if 1 /** @todo r=bird: this is wrong according to the spec */
+ uNewLpid %= uCBL;
+# else
+ /* The spec says it goes to CBL then wraps arpimd to 1, not back to zero. See 3.3.37. */
+ if (uNewLpid > uCBL)
+ uNewLpid %= uCBL;
+# endif
+ hdaStreamSetPositionAbs(pStreamShared, pDevIns, pThis, uNewLpid);
+ }
+ }
+}
+
+#endif /* IN_RING3 || VBOX_HDA_WITH_ON_REG_ACCESS_DMA */
+#ifdef IN_RING3
+
+/**
+ * Retrieves the available size of (buffered) audio data (in bytes) of a given HDA stream.
+ *
+ * @returns Available data (in bytes).
+ * @param pStreamR3 HDA stream to retrieve size for (ring-3).
+ */
+static uint32_t hdaR3StreamGetUsed(PHDASTREAMR3 pStreamR3)
+{
+ AssertPtrReturn(pStreamR3, 0);
+
+ if (pStreamR3->State.pCircBuf)
+ return (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+ return 0;
+}
+
+/**
+ * Retrieves the free size of audio data (in bytes) of a given HDA stream.
+ *
+ * @returns Free data (in bytes).
+ * @param pStreamR3 HDA stream to retrieve size for (ring-3).
+ */
+static uint32_t hdaR3StreamGetFree(PHDASTREAMR3 pStreamR3)
+{
+ AssertPtrReturn(pStreamR3, 0);
+
+ if (pStreamR3->State.pCircBuf)
+ return (uint32_t)RTCircBufFree(pStreamR3->State.pCircBuf);
+ return 0;
+}
+
+#endif /* IN_RING3 */
+#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA)
+
+/**
+ * Get the current address and number of bytes left in the current BDLE.
+ *
+ * @returns The current physical address.
+ * @param pStreamShared The stream to check.
+ * @param pcbLeft The number of bytes left at the returned address.
+ */
+DECLINLINE(RTGCPHYS) hdaStreamDmaBufGet(PHDASTREAM pStreamShared, uint32_t *pcbLeft)
+{
+ uint8_t idxBdle = pStreamShared->State.idxCurBdle;
+ AssertStmt(idxBdle < pStreamShared->State.cBdles, idxBdle = 0);
+
+ uint32_t const cbCurBdl = pStreamShared->State.aBdl[idxBdle].cb;
+ uint32_t offCurBdle = pStreamShared->State.offCurBdle;
+ AssertStmt(pStreamShared->State.offCurBdle <= cbCurBdl, offCurBdle = cbCurBdl);
+
+ *pcbLeft = cbCurBdl - offCurBdle;
+ return pStreamShared->State.aBdl[idxBdle].GCPhys + offCurBdle;
+}
+
+/**
+ * Checks if the current BDLE is completed.
+ *
+ * @retval true if complete
+ * @retval false if not.
+ * @param pStreamShared The stream to check.
+ */
+DECLINLINE(bool) hdaStreamDmaBufIsComplete(PHDASTREAM pStreamShared)
+{
+ uint8_t const idxBdle = pStreamShared->State.idxCurBdle;
+ AssertReturn(idxBdle < pStreamShared->State.cBdles, true);
+
+ uint32_t const cbCurBdl = pStreamShared->State.aBdl[idxBdle].cb;
+ uint32_t const offCurBdle = pStreamShared->State.offCurBdle;
+ Assert(offCurBdle <= cbCurBdl);
+ return offCurBdle >= cbCurBdl;
+}
+
+/**
+ * Checks if the current BDLE needs a completion IRQ.
+ *
+ * @retval true if IRQ is needed.
+ * @retval false if not.
+ * @param pStreamShared The stream to check.
+ */
+DECLINLINE(bool) hdaStreamDmaBufNeedsIrq(PHDASTREAM pStreamShared)
+{
+ uint8_t const idxBdle = pStreamShared->State.idxCurBdle;
+ AssertReturn(idxBdle < pStreamShared->State.cBdles, false);
+ return (pStreamShared->State.aBdl[idxBdle].fFlags & HDA_BDLE_F_IOC) != 0;
+}
+
+/**
+ * Advances the DMA engine to the next BDLE.
+ *
+ * @param pStreamShared The stream which DMA engine is to be updated.
+ */
+DECLINLINE(void) hdaStreamDmaBufAdvanceToNext(PHDASTREAM pStreamShared)
+{
+ uint8_t idxBdle = pStreamShared->State.idxCurBdle;
+ Assert(pStreamShared->State.offCurBdle == pStreamShared->State.aBdl[idxBdle].cb);
+
+ if (idxBdle < pStreamShared->State.cBdles - 1)
+ idxBdle++;
+ else
+ idxBdle = 0;
+ pStreamShared->State.idxCurBdle = idxBdle;
+ pStreamShared->State.offCurBdle = 0;
+}
+
+#endif /* defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA) */
+#ifdef IN_RING3
+
+/**
+ * Common do-DMA prologue code.
+ *
+ * @retval true if DMA processing can take place
+ * @retval false if caller should return immediately.
+ * @param pThis The shared HDA device state.
+ * @param pStreamShared HDA stream to update (shared).
+ * @param pStreamR3 HDA stream to update (ring-3).
+ * @param uSD The stream ID (for asserting).
+ * @param tsNowNs The current RTTimeNano() value.
+ * @param pszFunction The function name (for logging).
+ */
+DECLINLINE(bool) hdaR3StreamDoDmaPrologue(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD,
+ uint64_t tsNowNs, const char *pszFunction)
+{
+ RT_NOREF(uSD, pszFunction);
+
+ /*
+ * Check if we should skip town...
+ */
+ /* Stream not running (anymore)? */
+ if (pStreamShared->State.fRunning)
+ { /* likely */ }
+ else
+ {
+ Log3(("%s: [SD%RU8] Not running, skipping transfer\n", pszFunction, uSD));
+ return false;
+ }
+
+ if (!(HDA_STREAM_REG(pThis, STS, uSD) & HDA_SDSTS_BCIS))
+ { /* likely */ }
+ else
+ {
+ /** @todo r=bird: This is a bit fishy. We should make effort the reschedule
+ * the transfer immediately after the guest clears the interrupt.
+ * The same fishy code is present in AC'97 with just a little
+ * explanation as here, see @bugref{9890#c95}.
+ *
+ * The reasoning is probably that the developer noticed some windows
+ * versions don't like having their BCIS interrupts bundled. There were
+ * comments to that effect elsewhere, probably as a result of a fixed
+ * uTimerHz approach to DMA scheduling. However, pausing DMA for a
+ * period isn't going to help us with the host backends, as they don't
+ * pause and will want samples ASAP. So, we should at least unpause
+ * DMA as quickly as we possible when BCIS is cleared. We might even
+ * not skip it iff the DMA work here doesn't involve raising any IOC,
+ * which is possible although unlikely. */
+ Log3(("%s: [SD%RU8] BCIS bit set, skipping transfer\n", pszFunction, uSD));
+ STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaSkippedPendingBcis);
+ Log(("%s: [SD%RU8] BCIS bit set, skipping transfer\n", pszFunction, uSD));
+# ifdef HDA_STRICT
+ /* Timing emulation bug or guest is misbehaving -- let me know. */
+ AssertMsgFailed(("%s: BCIS bit for stream #%RU8 still set when it shouldn't\n", pszFunction, uSD));
+# endif
+ return false;
+ }
+
+ /*
+ * Stream sanity checks.
+ */
+ /* Register sanity checks. */
+ Assert(uSD < HDA_MAX_STREAMS);
+ Assert(pStreamShared->u64BDLBase);
+ Assert(pStreamShared->u32CBL);
+ Assert(pStreamShared->u8FIFOS);
+
+ /* State sanity checks. */
+ Assert(ASMAtomicReadBool(&pStreamShared->State.fInReset) == false);
+ Assert(ASMAtomicReadBool(&pStreamShared->State.fRunning));
+
+ /*
+ * Some timestamp stuff for logging/debugging.
+ */
+ /*const uint64_t tsNowNs = RTTimeNanoTS();*/
+ Log3(("%s: [SD%RU8] tsDeltaNs=%'RU64 ns\n", pszFunction, uSD, tsNowNs - pStreamShared->State.tsLastTransferNs));
+ pStreamShared->State.tsLastTransferNs = tsNowNs;
+
+ return true;
+}
+
+/**
+ * Common do-DMA epilogue.
+ *
+ * @param pDevIns The device instance.
+ * @param pStreamShared The HDA stream (shared).
+ * @param pStreamR3 The HDA stream (ring-3).
+ */
+DECLINLINE(void) hdaR3StreamDoDmaEpilogue(PPDMDEVINS pDevIns, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3)
+{
+ /*
+ * We must update this in the epilogue rather than in the prologue
+ * as it is used for WALCLK calculation and we must make sure the
+ * guest doesn't think we've processed the current period till we
+ * actually have.
+ */
+ pStreamShared->State.tsTransferLast = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer);
+
+ /*
+ * Update the buffer statistics.
+ */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+#endif /* IN_RING3 */
+
+#if defined(IN_RING3) || defined(VBOX_HDA_WITH_ON_REG_ACCESS_DMA)
+/**
+ * Completes a BDLE at the end of a DMA loop iteration, if possible.
+ *
+ * @retval true if buffer completed and new loaded.
+ * @retval false if buffer not completed.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pStreamShared HDA stream to update (shared).
+ * @param pszFunction The function name (for logging).
+ */
+DECLINLINE(bool) hdaStreamDoDmaMaybeCompleteBuffer(PPDMDEVINS pDevIns, PHDASTATE pThis,
+ PHDASTREAM pStreamShared, const char *pszFunction)
+{
+ RT_NOREF(pszFunction);
+
+ /*
+ * Is the buffer descriptor complete.
+ */
+ if (hdaStreamDmaBufIsComplete(pStreamShared))
+ {
+ Log3(("%s: [SD%RU8] Completed BDLE%u %#RX64 LB %#RX32 fFlags=%#x\n", pszFunction, pStreamShared->u8SD,
+ pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+ pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+ pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags));
+
+ /* Does the current BDLE require an interrupt to be sent? */
+ if (hdaStreamDmaBufNeedsIrq(pStreamShared))
+ {
+ /* If the IOCE ("Interrupt On Completion Enable") bit of the SDCTL
+ register is set we need to generate an interrupt. */
+ if (HDA_STREAM_REG(pThis, CTL, pStreamShared->u8SD) & HDA_SDCTL_IOCE)
+ {
+ /* Assert the interrupt before actually fetching the next BDLE below. */
+ pStreamShared->State.cTransferPendingInterrupts = 1;
+ Log3(("%s: [SD%RU8] Scheduling interrupt\n", pszFunction, pStreamShared->u8SD));
+
+ /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with
+ * ending / beginning of a period. */
+ /** @todo r=bird: What does the above comment mean? */
+ HDA_STREAM_REG(pThis, STS, pStreamShared->u8SD) |= HDA_SDSTS_BCIS;
+ HDA_PROCESS_INTERRUPT(pDevIns, pThis);
+ }
+ }
+
+ /*
+ * Advance to the next BDLE.
+ */
+ hdaStreamDmaBufAdvanceToNext(pStreamShared);
+ return true;
+ }
+
+ Log3(("%s: [SD%RU8] Incomplete BDLE%u %#RX64 LB %#RX32 fFlags=%#x: off=%#RX32\n", pszFunction, pStreamShared->u8SD,
+ pStreamShared->State.idxCurBdle, pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].GCPhys,
+ pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].cb,
+ pStreamShared->State.aBdl[pStreamShared->State.idxCurBdle].fFlags, pStreamShared->State.offCurBdle));
+ return false;
+}
+#endif /* IN_RING3 || VBOX_HDA_WITH_ON_REG_ACCESS_DMA */
+
+#ifdef IN_RING3
+
+/**
+ * Does DMA transfer for an HDA input stream.
+ *
+ * Reads audio data from the HDA stream's internal DMA buffer and writing to
+ * guest memory.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pStreamShared HDA stream to update (shared).
+ * @param pStreamR3 HDA stream to update (ring-3).
+ * @param cbToConsume The max amount of data to consume from the
+ * internal DMA buffer. The caller will make sure
+ * this is always the transfer size fo the current
+ * period (unless something is seriously wrong).
+ * @param fWriteSilence Whether to feed the guest silence rather than
+ * fetching bytes from the internal DMA buffer.
+ * This is set initially while we pre-buffer a
+ * little bit of input, so we can better handle
+ * time catch-ups and other schduling fun.
+ * @param tsNowNs The current RTTimeNano() value.
+ *
+ * @remarks Caller owns the stream lock.
+ */
+static void hdaR3StreamDoDmaInput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared,
+ PHDASTREAMR3 pStreamR3, uint32_t const cbToConsume, bool fWriteSilence, uint64_t tsNowNs)
+{
+ uint8_t const uSD = pStreamShared->u8SD;
+ LogFlowFunc(("ENTER - #%u cbToConsume=%#x%s\n", uSD, cbToConsume, fWriteSilence ? " silence" : ""));
+
+ /*
+ * Common prologue.
+ */
+ if (hdaR3StreamDoDmaPrologue(pThis, pStreamShared, pStreamR3, uSD, tsNowNs, "hdaR3StreamDoDmaInput"))
+ { /* likely */ }
+ else
+ return;
+
+ /*
+ *
+ * The DMA copy loop.
+ *
+ * Note! Unaligned BDLEs shouldn't be a problem since the circular buffer
+ * doesn't care about alignment. Only, we have to read the rest
+ * of the incomplete frame from it ASAP.
+ */
+ PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf;
+ uint32_t cbLeft = cbToConsume;
+ Assert(cbLeft == pStreamShared->State.cbCurDmaPeriod);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft));
+
+ while (cbLeft > 0)
+ {
+ STAM_PROFILE_START(&pThis->StatIn, a);
+
+ /*
+ * Figure out how much we can read & write in this iteration.
+ */
+ uint32_t cbChunk = 0;
+ RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk);
+
+ if (cbChunk <= cbLeft)
+ { /* very likely */ }
+ else
+ cbChunk = cbLeft;
+
+ uint32_t cbWritten = 0;
+ if (!fWriteSilence)
+ {
+ /*
+ * Write the host data directly into the guest buffers.
+ */
+ while (cbChunk > 0)
+ {
+ /* Grab internal DMA buffer space and read into it. */
+ void /*const*/ *pvBufSrc;
+ size_t cbBufSrc;
+ RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvBufSrc, &cbBufSrc);
+ AssertBreakStmt(cbBufSrc, RTCircBufReleaseReadBlock(pCircBuf, 0));
+
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, GCPhys, pvBufSrc, cbBufSrc);
+ AssertRC(rc2);
+
+# ifdef HDA_DEBUG_SILENCE
+ fix me if relevant;
+# endif
+ if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufSrc, cbBufSrc);
+
+# ifdef VBOX_WITH_DTRACE
+ VBOXDD_HDA_STREAM_DMA_IN((uint32_t)uSD, (uint32_t)cbBufSrc, pStreamShared->State.offRead);
+# endif
+ pStreamShared->State.offRead += cbBufSrc;
+ RTCircBufReleaseReadBlock(pCircBuf, cbBufSrc);
+ STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbBufSrc);
+
+ /* advance */
+ cbChunk -= (uint32_t)cbBufSrc;
+ cbWritten += (uint32_t)cbBufSrc;
+ GCPhys += cbBufSrc;
+ pStreamShared->State.offCurBdle += (uint32_t)cbBufSrc;
+ }
+ }
+ /*
+ * Write silence. Since we only do signed formats, we can use the zero
+ * buffers from IPRT as source here.
+ */
+ else
+ {
+ Assert(PDMAudioPropsIsSigned(&pStreamShared->State.Cfg.Props));
+ while (cbChunk > 0)
+ {
+ /* Write it to the guest buffer. */
+ uint32_t cbToWrite = RT_MIN(sizeof(g_abRTZero64K), cbChunk);
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, GCPhys, g_abRTZero64K, cbToWrite);
+ AssertRC(rc2);
+ STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbToWrite);
+
+ /* advance */
+ cbWritten += cbToWrite;
+ cbChunk -= cbToWrite;
+ GCPhys += cbToWrite;
+ pStreamShared->State.offCurBdle += cbToWrite;
+ }
+ }
+
+ cbLeft -= cbWritten;
+ STAM_PROFILE_STOP(&pThis->StatIn, a);
+
+ /*
+ * Complete the buffer if necessary (common with the output DMA code).
+ *
+ * Must update the DMA position before we do this as the buffer IRQ may
+ * fire on another vCPU and run in parallel to us, although it is very
+ * unlikely it can make much progress as long as we're sitting on the
+ * lock, it could still read the DMA position (Linux won't, as it reads
+ * WALCLK and possibly SDnSTS before the DMA position).
+ */
+ hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbWritten);
+ hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaR3StreamDoDmaInput");
+ }
+
+ Assert(cbLeft == 0); /* There shall be no break statements in the above loop, so cbLeft is always zero here! */
+
+ /*
+ * Common epilogue.
+ */
+ hdaR3StreamDoDmaEpilogue(pDevIns, pStreamShared, pStreamR3);
+
+ /*
+ * Log and leave.
+ */
+ Log3Func(("LEAVE - [SD%RU8] %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n",
+ uSD, cbToConsume, pStreamShared->State.cbCurDmaPeriod, pStreamShared->State.offRead - cbToConsume,
+ pStreamShared->State.cTransferPendingInterrupts));
+}
+
+
+/**
+ * Input streams: Pulls data from the mixer, putting it in the internal DMA
+ * buffer.
+ *
+ * @param pStreamShared HDA stream to update (shared).
+ * @param pStreamR3 HDA stream to update (ring-3 bits).
+ * @param pSink The mixer sink to pull from.
+ */
+static void hdaR3StreamPullFromMixer(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offWriteOld = pStreamShared->State.offWrite;
+# endif
+ pStreamShared->State.offWrite = AudioMixerSinkTransferToCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamShared->State.offWrite,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ pStreamShared->State.offWrite - offWriteOld, pStreamShared->State.offWrite));
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Does DMA transfer for an HDA output stream.
+ *
+ * This transfers one DMA timer period worth of data from the guest and into the
+ * internal DMA buffer.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pStreamShared HDA stream to update (shared).
+ * @param pStreamR3 HDA stream to update (ring-3).
+ * @param cbToProduce The max amount of data to produce (i.e. put into
+ * the circular buffer). Unless something is going
+ * seriously wrong, this will always be transfer
+ * size for the current period.
+ * @param tsNowNs The current RTTimeNano() value.
+ *
+ * @remarks Caller owns the stream lock.
+ */
+static void hdaR3StreamDoDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared,
+ PHDASTREAMR3 pStreamR3, uint32_t const cbToProduce, uint64_t tsNowNs)
+{
+ uint8_t const uSD = pStreamShared->u8SD;
+ LogFlowFunc(("ENTER - #%u cbToProduce=%#x\n", uSD, cbToProduce));
+
+ /*
+ * Common prologue.
+ */
+ if (hdaR3StreamDoDmaPrologue(pThis, pStreamShared, pStreamR3, uSD, tsNowNs, "hdaR3StreamDoDmaOutput"))
+ { /* likely */ }
+ else
+ return;
+
+ /*
+ *
+ * The DMA copy loop.
+ *
+ * Note! Unaligned BDLEs shouldn't be a problem since the circular buffer
+ * doesn't care about alignment. Only, we have to write the rest
+ * of the incomplete frame to it ASAP.
+ */
+ PRTCIRCBUF pCircBuf = pStreamR3->State.pCircBuf;
+ uint32_t cbLeft = cbToProduce;
+# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ Assert(cbLeft <= pStreamShared->State.cbCurDmaPeriod); /* a little pointless with the DMA'ing on LPIB read. */
+# else
+ Assert(cbLeft == pStreamShared->State.cbCurDmaPeriod);
+# endif
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamShared->State.Cfg.Props, cbLeft));
+
+ while (cbLeft > 0)
+ {
+ STAM_PROFILE_START(&pThis->StatOut, a);
+
+ /*
+ * Figure out how much we can read & write in this iteration.
+ */
+ uint32_t cbChunk = 0;
+ RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk);
+
+ if (cbChunk <= cbLeft)
+ { /* very likely */ }
+ else
+ cbChunk = cbLeft;
+
+ /*
+ * Read the guest data directly into the internal DMA buffer.
+ */
+ uint32_t cbRead = 0;
+ while (cbChunk > 0)
+ {
+ /* Grab internal DMA buffer space and read into it. */
+ void *pvBufDst;
+ size_t cbBufDst;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvBufDst, &cbBufDst);
+ AssertBreakStmt(cbBufDst, RTCircBufReleaseWriteBlock(pCircBuf, 0));
+
+ int rc2 = PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvBufDst, cbBufDst);
+ AssertRC(rc2);
+
+# ifdef HDA_DEBUG_SILENCE
+ fix me if relevant;
+# endif
+ if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufDst, cbBufDst);
+
+# ifdef VBOX_WITH_DTRACE
+ VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)uSD, (uint32_t)cbBufDst, pStreamShared->State.offWrite);
+# endif
+ pStreamShared->State.offWrite += cbBufDst;
+ RTCircBufReleaseWriteBlock(pCircBuf, cbBufDst);
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, cbBufDst);
+
+ /* advance */
+ cbChunk -= (uint32_t)cbBufDst;
+ cbRead += (uint32_t)cbBufDst;
+ GCPhys += cbBufDst;
+ pStreamShared->State.offCurBdle += (uint32_t)cbBufDst;
+ }
+
+ cbLeft -= cbRead;
+ STAM_PROFILE_STOP(&pThis->StatOut, a);
+
+ /*
+ * Complete the buffer if necessary (common with the input DMA code).
+ *
+ * Must update the DMA position before we do this as the buffer IRQ may
+ * fire on another vCPU and run in parallel to us, although it is very
+ * unlikely it can make much progress as long as we're sitting on the
+ * lock, it could still read the DMA position (Linux won't, as it reads
+ * WALCLK and possibly SDnSTS before the DMA position).
+ */
+ hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbRead);
+ hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaR3StreamDoDmaOutput");
+ }
+
+ Assert(cbLeft == 0); /* There shall be no break statements in the above loop, so cbLeft is always zero here! */
+
+ /*
+ * Common epilogue.
+ */
+ hdaR3StreamDoDmaEpilogue(pDevIns, pStreamShared, pStreamR3);
+
+ /*
+ * Log and leave.
+ */
+ Log3Func(("LEAVE - [SD%RU8] %#RX32/%#RX32 @ %#RX64 - cTransferPendingInterrupts=%RU8\n",
+ uSD, cbToProduce, pStreamShared->State.cbCurDmaPeriod, pStreamShared->State.offWrite - cbToProduce,
+ pStreamShared->State.cTransferPendingInterrupts));
+}
+
+#endif /* IN_RING3 */
+#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+
+/**
+ * Do DMA output transfer on LPIB/WALCLK register access.
+ *
+ * @returns VINF_SUCCESS or VINF_IOM_R3_MMIO_READ.
+ * @param pDevIns The device instance.
+ * @param pThis The shared instance data.
+ * @param pStreamShared The shared stream data.
+ * @param tsNow The current time on the timer clock.
+ * @param cbToTransfer How much to transfer.
+ */
+VBOXSTRICTRC hdaStreamDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared,
+ uint64_t tsNow, uint32_t cbToTransfer)
+{
+ AssertReturn(cbToTransfer > 0, VINF_SUCCESS);
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Check if we're exceeding the available buffer, go to ring-3 to
+ * handle that (we would perhaps always take this path when in ring-3).
+ */
+ uint32_t cbDma = pStreamShared->State.cbDma;
+ ASMCompilerBarrier();
+ if ( cbDma >= sizeof(pStreamShared->State.abDma) /* paranoia */
+ || cbToTransfer >= sizeof(pStreamShared->State.abDma) /* paranoia */
+ || cbDma + cbToTransfer > sizeof(pStreamShared->State.abDma))
+ {
+# ifndef IN_RING3
+ STAM_REL_COUNTER_INC(&pThis->StatAccessDmaOutputToR3);
+ LogFlowFunc(("[SD%RU8] out of DMA buffer space (%#x, need %#x) -> VINF_IOM_R3_MMIO_READ\n",
+ pStreamShared->u8SD, sizeof(pStreamShared->State.abDma) - pStreamShared->State.cbDma, cbToTransfer));
+ return VINF_IOM_R3_MMIO_READ;
+# else /* IN_RING3 */
+ /*
+ * Flush the bounce buffer, then do direct transfers to the
+ * internal DMA buffer (updates LPIB).
+ */
+ PHDASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ uintptr_t const idxStream = pStreamShared->u8SD;
+ AssertReturn(idxStream < RT_ELEMENTS(pThisCC->aStreams), VERR_INTERNAL_ERROR_4);
+ PHDASTREAMR3 const pStreamR3 = &pThisCC->aStreams[idxStream];
+
+ hdaR3StreamFlushDmaBounceBufferOutput(pStreamShared, pStreamR3);
+
+ uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3);
+ if (cbStreamFree >= cbToTransfer)
+ { /* likely */ }
+ else
+ {
+ PAUDMIXSINK pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pSink)
+ cbStreamFree = hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbToTransfer, RTTimeNanoTS(),
+ "hdaStreamDoOnAccessDmaOutput", cbStreamFree);
+ else
+ {
+ LogFunc(("[SD%RU8] No sink and insufficient internal DMA buffer space (%#x) - won't do anything\n",
+ pStreamShared->u8SD, cbStreamFree));
+ return VINF_SUCCESS;
+ }
+ cbToTransfer = RT_MIN(cbToTransfer, cbStreamFree);
+ if (cbToTransfer < PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props))
+ {
+ LogFunc(("[SD%RU8] No internal DMA buffer space (%#x) - won't do anything\n", pStreamShared->u8SD, cbStreamFree));
+ return VINF_SUCCESS;
+ }
+ }
+ hdaR3StreamDoDmaOutput(pDevIns, pThis, pStreamShared, pStreamR3, cbToTransfer, RTTimeNanoTS());
+ pStreamShared->State.cbDmaTotal += cbToTransfer;
+# endif /* IN_RING3 */
+ }
+ else
+ {
+ /*
+ * Transfer into the DMA bounce buffer.
+ */
+ LogFlowFunc(("[SD%RU8] Transfering %#x bytes to DMA bounce buffer (cbDma=%#x cbDmaTotal=%#x) (%p/%u)\n",
+ pStreamShared->u8SD, cbToTransfer, cbDma, pStreamShared->State.cbDmaTotal, pStreamShared, pStreamShared->u8SD));
+ uint32_t cbLeft = cbToTransfer;
+ do
+ {
+ uint32_t cbChunk = 0;
+ RTGCPHYS GCPhys = hdaStreamDmaBufGet(pStreamShared, &cbChunk);
+
+ bool fMustAdvanceBuffer;
+ if (cbLeft < cbChunk)
+ {
+ fMustAdvanceBuffer = false;
+ cbChunk = cbLeft;
+ }
+ else
+ fMustAdvanceBuffer = true;
+
+ /* Read the guest data directly into the DMA bounce buffer. */
+ int rc2 = PDMDevHlpPCIPhysRead(pDevIns, GCPhys, &pStreamShared->State.abDma[cbDma], cbChunk);
+ AssertRC(rc2);
+
+ /* We update offWrite and StatBytesRead here even if we haven't moved the data
+ to the internal DMA buffer yet, because we want the dtrace even to fire here. */
+# ifdef VBOX_WITH_DTRACE
+ VBOXDD_HDA_STREAM_DMA_OUT((uint32_t)pStreamShared->u8SD, cbChunk, pStreamShared->State.offWrite);
+# endif
+ pStreamShared->State.offWrite += cbChunk;
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk);
+
+ /* advance */
+ pStreamShared->State.offCurBdle += cbChunk;
+ pStreamShared->State.cbDmaTotal += cbChunk;
+ cbDma += cbChunk;
+ pStreamShared->State.cbDma = cbDma;
+ cbLeft -= cbChunk;
+ Log6Func(("cbLeft=%#x cbDma=%#x cbDmaTotal=%#x offCurBdle=%#x idxCurBdle=%#x (%p/%u)\n",
+ cbLeft, cbDma, pStreamShared->State.cbDmaTotal, pStreamShared->State.offCurBdle,
+ pStreamShared->State.idxCurBdle, pStreamShared, pStreamShared->u8SD));
+
+ /* Next buffer. */
+ bool fAdvanced = hdaStreamDoDmaMaybeCompleteBuffer(pDevIns, pThis, pStreamShared, "hdaStreamDoOnAccessDmaOutput");
+ AssertMsgStmt(fMustAdvanceBuffer == fAdvanced, ("%d %d\n", fMustAdvanceBuffer, fAdvanced), rc = VERR_INTERNAL_ERROR_3);
+ } while (cbLeft > 0);
+
+ /*
+ * Advance LPIB and update the last transfer time (for WALCLK).
+ */
+ pStreamShared->State.tsTransferLast = tsNow;
+ hdaStreamSetPositionAdd(pStreamShared, pDevIns, pThis, cbToTransfer - cbLeft);
+ }
+
+# ifdef VBOX_STRICT
+ uint32_t idxSched = pStreamShared->State.idxSchedule;
+ AssertStmt(idxSched < RT_MIN(RT_ELEMENTS(pStreamShared->State.aSchedule), pStreamShared->State.cSchedule), idxSched = 0);
+ uint32_t const cbPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod;
+ AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod));
+# endif
+
+ STAM_REL_COUNTER_INC(&pThis->StatAccessDmaOutput);
+ return rc;
+}
+
+
+/**
+ * Consider doing DMA output transfer on LPIB/WALCLK register access.
+ *
+ * @returns VINF_SUCCESS or VINF_IOM_R3_MMIO_READ.
+ * @param pDevIns The device instance.
+ * @param pThis The shared instance data.
+ * @param pStreamShared The shared stream data.
+ * @param tsNow The current time on the timer clock. Used to do the
+ * calculation.
+ */
+VBOXSTRICTRC hdaStreamMaybeDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow)
+{
+ Assert(pStreamShared->State.fRunning); /* caller should check this */
+
+ /*
+ * Calculate where the DMA engine should be according to the clock, if we can.
+ */
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamShared->State.Cfg.Props);
+ uint32_t const cbPeriod = pStreamShared->State.cbCurDmaPeriod;
+ if (cbPeriod > cbFrame)
+ {
+ AssertMsg(pStreamShared->State.cbDmaTotal < cbPeriod, ("%#x vs %#x\n", pStreamShared->State.cbDmaTotal, cbPeriod));
+ uint64_t const tsTransferNext = pStreamShared->State.tsTransferNext;
+ uint32_t cbFuture;
+ if (tsNow < tsTransferNext)
+ {
+ /** @todo ASSUMES nanosecond clock ticks, need to make this
+ * resolution independent. */
+ cbFuture = PDMAudioPropsNanoToBytes(&pStreamShared->State.Cfg.Props, tsTransferNext - tsNow);
+ cbFuture = RT_MIN(cbFuture, cbPeriod - cbFrame);
+ }
+ else
+ {
+ /* We've hit/overshot the timer deadline. Return to ring-3 if we're
+ not already there to increase the chance that we'll help expidite
+ the timer. If we're already in ring-3, do all but the last frame. */
+# ifndef IN_RING3
+ LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> VINF_IOM_R3_MMIO_READ\n",
+ tsNow, tsTransferNext));
+ return VINF_IOM_R3_MMIO_READ;
+# else
+ cbFuture = cbPeriod - cbFrame;
+ LogFunc(("[SD%RU8] DMA period expired: tsNow=%RU64 >= tsTransferNext=%RU64 -> cbFuture=%#x (cbPeriod=%#x - cbFrame=%#x)\n",
+ tsNow, tsTransferNext, cbFuture, cbPeriod, cbFrame));
+# endif
+ }
+ uint32_t const offNow = PDMAudioPropsFloorBytesToFrame(&pStreamShared->State.Cfg.Props, cbPeriod - cbFuture);
+
+ /*
+ * Should we transfer a little? Minimum is 64 bytes (semi-random,
+ * suspect real hardware might be doing some cache aligned stuff,
+ * which might soon get complicated if you take unaligned buffers
+ * into consideration and which cache line size (128 bytes is just
+ * as likely as 64 or 32 bytes)).
+ */
+ uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal;
+ if (cbDmaTotal + 64 <= offNow)
+ {
+# ifdef LOG_ENABLED
+ uint32_t const uOldLpib = HDA_STREAM_REG(pThis, CBL, pStreamShared->u8SD);
+# endif
+ VBOXSTRICTRC rcStrict = hdaStreamDoOnAccessDmaOutput(pDevIns, pThis, pStreamShared, tsNow, offNow - cbDmaTotal);
+ LogFlowFunc(("[SD%RU8] LPIB=%#RX32 -> LPIB=%#RX32 offNow=%#x rcStrict=%Rrc\n", pStreamShared->u8SD,
+ uOldLpib, HDA_STREAM_REG(pThis, LPIB, pStreamShared->u8SD), offNow, VBOXSTRICTRC_VAL(rcStrict) ));
+ return rcStrict;
+ }
+
+ /*
+ * Do nothing.
+ */
+ LogFlowFunc(("[SD%RU8] Skipping DMA transfer: cbDmaTotal=%#x offNow=%#x\n", pStreamShared->u8SD, cbDmaTotal, offNow));
+ }
+ else
+ LogFunc(("[SD%RU8] cbPeriod=%#x <= cbFrame=%#x\n", pStreamShared->u8SD, cbPeriod, cbFrame));
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */
+#ifdef IN_RING3
+
+/**
+ * Output streams: Pushes data to the mixer.
+ *
+ * @param pStreamShared HDA stream to update (shared bits).
+ * @param pStreamR3 HDA stream to update (ring-3 bits).
+ * @param pSink The mixer sink to push to.
+ * @param nsNow The current RTTimeNanoTS() value.
+ */
+static void hdaR3StreamPushToMixer(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink, uint64_t nsNow)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offReadOld = pStreamShared->State.offRead;
+# endif
+ pStreamShared->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamShared->State.offRead,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Assert(nsNow >= pStreamShared->State.tsLastReadNs);
+ Log3Func(("[SD%RU8] nsDeltaLastRead=%RI64 transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ nsNow - pStreamShared->State.tsLastReadNs, pStreamShared->State.offRead - offReadOld, pStreamShared->State.offRead));
+ RT_NOREF(pStreamShared, nsNow);
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Deals with a DMA buffer overrun.
+ *
+ * Makes sure we return with @a cbNeeded bytes of free space in pCircBuf.
+ *
+ * @returns Number of bytes free in the internal DMA buffer.
+ * @param pStreamShared The shared data for the HDA stream.
+ * @param pStreamR3 The ring-3 data for the HDA stream.
+ * @param pSink The mixer sink (valid).
+ * @param cbNeeded How much space we need (in bytes).
+ * @param nsNow Current RTNanoTimeTS() timestamp.
+ * @param cbStreamFree The current amount of free buffer space.
+ * @param pszCaller The caller (for logging).
+ */
+static uint32_t hdaR3StreamHandleDmaBufferOverrun(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PAUDMIXSINK pSink,
+ uint32_t cbNeeded, uint64_t nsNow,
+ const char *pszCaller, uint32_t const cbStreamFree)
+{
+ STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowProblems);
+ Log(("%s: Warning! Stream #%u has insufficient space free: %#x bytes, need %#x. Will try move data out of the buffer...\n",
+ pszCaller, pStreamShared->u8SD, cbStreamFree, cbNeeded));
+ RT_NOREF(pszCaller, cbStreamFree);
+
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ hdaR3StreamPushToMixer(pStreamShared, pStreamR3, pSink, nsNow);
+ AudioMixerSinkUpdate(pSink, 0, 0);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+
+ uint32_t const cbRet = hdaR3StreamGetFree(pStreamR3);
+ Log(("%s: Gained %u bytes.\n", pszCaller, cbRet - cbStreamFree));
+ if (cbRet >= cbNeeded)
+ return cbRet;
+
+ /*
+ * Unable to make sufficient space. Drop the whole buffer content.
+ *
+ * This is needed in order to keep the device emulation running at a
+ * constant rate, at the cost of losing valid (but too much) data.
+ */
+ STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowErrors);
+ LogRel2(("HDA: Warning: Hit stream #%RU8 overflow, dropping %u bytes of audio data (%s)\n",
+ pStreamShared->u8SD, hdaR3StreamGetUsed(pStreamR3), pszCaller));
+# ifdef HDA_STRICT
+ AssertMsgFailed(("Hit stream #%RU8 overflow -- timing bug?\n", pStreamShared->u8SD));
+# endif
+/**
+ *
+ * @todo r=bird: I don't think RTCircBufReset is entirely safe w/o
+ * owning the AIO lock. See the note in the documentation about it not being
+ * multi-threading aware (safe). Wish I'd verified this code much earlier.
+ * Sigh^3!
+ *
+ */
+ RTCircBufReset(pStreamR3->State.pCircBuf);
+ pStreamShared->State.offWrite = 0;
+ pStreamShared->State.offRead = 0;
+ return hdaR3StreamGetFree(pStreamR3);
+}
+
+
+# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+/**
+ * Flushes the DMA bounce buffer content to the internal DMA buffer.
+ *
+ * @param pStreamShared The shared data of the stream to have its DMA bounce
+ * buffer flushed.
+ * @param pStreamR3 The ring-3 stream data for same.
+ */
+static void hdaR3StreamFlushDmaBounceBufferOutput(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3)
+{
+ uint32_t cbDma = pStreamShared->State.cbDma;
+ LogFlowFunc(("cbDma=%#x\n", cbDma));
+ if (cbDma)
+ {
+ AssertReturnVoid(cbDma <= sizeof(pStreamShared->State.abDma));
+ PRTCIRCBUF const pCircBuf = pStreamR3->State.pCircBuf;
+ if (pCircBuf)
+ {
+ uint32_t offDma = 0;
+ while (offDma < cbDma)
+ {
+ uint32_t const cbSrcLeft = cbDma - offDma;
+
+ /*
+ * Grab a chunk of the internal DMA buffer.
+ */
+ void *pvBufDst = NULL;
+ size_t cbBufDst = 0;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbSrcLeft, &pvBufDst, &cbBufDst);
+ if (cbBufDst > 0)
+ { /* likely */ }
+ else
+ {
+ /* We've got buffering trouble. */
+ RTCircBufReleaseWriteBlock(pCircBuf, 0);
+
+ PAUDMIXSINK pSink = pStreamR3->pMixSink ? pStreamR3->pMixSink->pMixSink : NULL;
+ if (pSink)
+ hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbSrcLeft, RTTimeNanoTS(),
+ "hdaR3StreamFlushDmaBounceBufferOutput", 0 /*cbStreamFree*/);
+ else
+ {
+ LogFunc(("Stream #%u has no sink. Dropping the rest of the data\n", pStreamR3->u8SD));
+ break;
+ }
+
+ RTCircBufAcquireWriteBlock(pCircBuf, cbSrcLeft, &pvBufDst, &cbBufDst);
+ AssertBreakStmt(cbBufDst, RTCircBufReleaseWriteBlock(pCircBuf, 0));
+ }
+
+ /*
+ * Copy the samples into it and write it to the debug file if open.
+ *
+ * We do not fire the dtrace probe here nor update offRead as that was
+ * done already (not sure that was a good idea?).
+ */
+ memcpy(pvBufDst, &pStreamShared->State.abDma[offDma], cbBufDst);
+
+ if (RT_LIKELY(!pStreamR3->Dbg.Runtime.pFileDMARaw))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamR3->Dbg.Runtime.pFileDMARaw, pvBufDst, cbBufDst);
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbBufDst);
+
+ offDma += (uint32_t)cbBufDst;
+ }
+ }
+
+ /*
+ * Mark the buffer empty.
+ */
+ pStreamShared->State.cbDma = 0;
+ }
+}
+# endif /* VBOX_HDA_WITH_ON_REG_ACCESS_DMA */
+
+
+/**
+ * The stream's main function when called by the timer.
+ *
+ * @note This function also will be called without timer invocation when
+ * starting (enabling) the stream to minimize startup latency.
+ *
+ * @returns Current timer time if the timer is enabled, otherwise zero.
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pStreamShared HDA stream to update (shared bits).
+ * @param pStreamR3 HDA stream to update (ring-3 bits).
+ */
+uint64_t hdaR3StreamTimerMain(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC,
+ PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3)
+{
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStreamShared->hTimer));
+
+ /* Do the work: */
+ hdaR3StreamUpdateDma(pDevIns, pThis, pThisCC, pStreamShared, pStreamR3);
+
+ /* Re-arm the timer if the sink is still active: */
+ if ( pStreamShared->State.fRunning
+ && pStreamR3->pMixSink
+ && AudioMixerSinkIsActive(pStreamR3->pMixSink->pMixSink))
+ {
+ /* Advance the schduling: */
+ uint32_t idxSched = pStreamShared->State.idxSchedule;
+ AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0);
+ uint32_t idxLoop = pStreamShared->State.idxScheduleLoop + 1;
+ if (idxLoop >= pStreamShared->State.aSchedule[idxSched].cLoops)
+ {
+ idxSched += 1;
+ if ( idxSched >= pStreamShared->State.cSchedule
+ || idxSched >= RT_ELEMENTS(pStreamShared->State.aSchedule) /*paranoia^2*/)
+ {
+ idxSched = pStreamShared->State.cSchedulePrologue;
+ AssertStmt(idxSched < RT_ELEMENTS(pStreamShared->State.aSchedule), idxSched = 0);
+ }
+ pStreamShared->State.idxSchedule = idxSched;
+ idxLoop = 0;
+ }
+ pStreamShared->State.idxScheduleLoop = (uint16_t)idxLoop;
+
+ /* Do the actual timer re-arming. */
+ uint64_t const tsNow = PDMDevHlpTimerGet(pDevIns, pStreamShared->hTimer); /* (For virtual sync this remains the same for the whole callout IIRC) */
+ uint64_t const tsTransferNext = tsNow + pStreamShared->State.aSchedule[idxSched].cPeriodTicks;
+ Log3Func(("[SD%RU8] fSinkActive=true, tsTransferNext=%RU64 (in %RU64)\n",
+ pStreamShared->u8SD, tsTransferNext, tsTransferNext - tsNow));
+ int rc = PDMDevHlpTimerSet(pDevIns, pStreamShared->hTimer, tsTransferNext);
+ AssertRC(rc);
+
+ /* Some legacy stuff: */
+ pStreamShared->State.tsTransferNext = tsTransferNext;
+ pStreamShared->State.cbCurDmaPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod;
+
+ return tsNow;
+ }
+
+ Log3Func(("[SD%RU8] fSinkActive=false\n", pStreamShared->u8SD));
+ return 0;
+}
+
+
+/**
+ * Updates a HDA stream by doing DMA transfers.
+ *
+ * Will do mixer transfers too to try fix an overrun/underrun situation.
+ *
+ * The host sink(s) set the overall pace (bird: no it doesn't, the DMA timer
+ * does - we just hope like heck it matches the speed at which the *backend*
+ * host audio driver processes samples).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared HDA device state.
+ * @param pThisCC The ring-3 HDA device state.
+ * @param pStreamShared HDA stream to update (shared bits).
+ * @param pStreamR3 HDA stream to update (ring-3 bits).
+ */
+static void hdaR3StreamUpdateDma(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC,
+ PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3)
+{
+ RT_NOREF(pThisCC);
+ int rc2;
+
+ /*
+ * Make sure we're running and got an active mixer sink.
+ */
+ if (RT_LIKELY(pStreamShared->State.fRunning))
+ { /* likely */ }
+ else
+ return;
+
+ PAUDMIXSINK pSink = NULL;
+ if (pStreamR3->pMixSink)
+ pSink = pStreamR3->pMixSink->pMixSink;
+ if (RT_LIKELY(AudioMixerSinkIsActive(pSink)))
+ { /* likely */ }
+ else
+ return;
+
+ /*
+ * Get scheduling info common to both input and output streams.
+ */
+ const uint64_t tsNowNs = RTTimeNanoTS();
+ uint32_t idxSched = pStreamShared->State.idxSchedule;
+ AssertStmt(idxSched < RT_MIN(RT_ELEMENTS(pStreamShared->State.aSchedule), pStreamShared->State.cSchedule), idxSched = 0);
+ uint32_t cbPeriod = pStreamShared->State.aSchedule[idxSched].cbPeriod;
+
+ /*
+ * Output streams (SDO).
+ */
+ if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT)
+ {
+# ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ /* Subtract already transferred bytes and flush the DMA bounce buffer. */
+ uint32_t cbDmaTotal = pStreamShared->State.cbDmaTotal;
+ if (cbDmaTotal > 0)
+ {
+ AssertStmt(cbDmaTotal < cbPeriod, cbDmaTotal = cbPeriod);
+ cbPeriod -= cbDmaTotal;
+ pStreamShared->State.cbDmaTotal = 0;
+ hdaR3StreamFlushDmaBounceBufferOutput(pStreamShared, pStreamR3);
+ }
+ else
+ Assert(pStreamShared->State.cbDma == 0);
+# endif
+
+ /*
+ * Check how much room we have in our DMA buffer. There should be at
+ * least one period worth of space there or we're in an overflow situation.
+ */
+ uint32_t cbStreamFree = hdaR3StreamGetFree(pStreamR3);
+ if (cbStreamFree >= cbPeriod)
+ { /* likely */ }
+ else
+ cbStreamFree = hdaR3StreamHandleDmaBufferOverrun(pStreamShared, pStreamR3, pSink, cbPeriod, tsNowNs,
+ "hdaR3StreamUpdateDma", cbStreamFree);
+
+ /*
+ * Do the DMA transfer.
+ */
+ uint64_t const offWriteBefore = pStreamShared->State.offWrite;
+ hdaR3StreamDoDmaOutput(pDevIns, pThis, pStreamShared, pStreamR3, RT_MIN(cbStreamFree, cbPeriod), tsNowNs);
+
+ /*
+ * Should we push data to down thru the mixer to and to the host drivers?
+ */
+ bool fKickAioThread = pStreamShared->State.offWrite > offWriteBefore
+ || hdaR3StreamGetFree(pStreamR3) < pStreamShared->State.cbAvgTransfer * 2;
+
+ Log3Func(("msDelta=%RU64 (vs %u) cbStreamFree=%#x (vs %#x) => fKickAioThread=%RTbool\n",
+ (tsNowNs - pStreamShared->State.tsLastReadNs) / RT_NS_1MS,
+ pStreamShared->State.Cfg.Device.cMsSchedulingHint, cbStreamFree,
+ pStreamShared->State.cbAvgTransfer * 2, fKickAioThread));
+
+ if (fKickAioThread)
+ {
+ /* Notify the async I/O worker thread that there's work to do. */
+ Log5Func(("Notifying AIO thread\n"));
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ /* Update last read timestamp for logging/debugging. */
+ pStreamShared->State.tsLastReadNs = tsNowNs;
+ }
+ }
+ /*
+ * Input stream (SDI).
+ */
+ else
+ {
+ Assert(hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_IN);
+
+ /*
+ * See how much data we've got buffered...
+ */
+ bool fWriteSilence = false;
+ uint32_t cbStreamUsed = hdaR3StreamGetUsed(pStreamR3);
+ if (pStreamShared->State.fInputPreBuffered && cbStreamUsed >= cbPeriod)
+ { /*likely*/ }
+ /*
+ * Because it may take a while for the input stream to get going (at
+ * least with pulseaudio), we feed the guest silence till we've
+ * pre-buffer a reasonable amount of audio.
+ */
+ else if (!pStreamShared->State.fInputPreBuffered)
+ {
+ if (cbStreamUsed < pStreamShared->State.cbInputPreBuffer)
+ {
+ Log3(("hdaR3StreamUpdateDma: Pre-buffering (got %#x out of %#x bytes)...\n",
+ cbStreamUsed, pStreamShared->State.cbInputPreBuffer));
+ fWriteSilence = true;
+ }
+ else
+ {
+ Log3(("hdaR3StreamUpdateDma: Completed pre-buffering (got %#x, needed %#x bytes).\n",
+ cbStreamUsed, pStreamShared->State.cbInputPreBuffer));
+ pStreamShared->State.fInputPreBuffered = true;
+ fWriteSilence = true; /* For now, just do the most conservative thing. */
+ }
+ cbStreamUsed = cbPeriod;
+ }
+ /*
+ * When we're low on data, we must really try fetch some ourselves
+ * as buffer underruns must not happen.
+ */
+ else
+ {
+ /** @todo We're ending up here to frequently with pulse audio at least (just
+ * watch the stream stats in the statistcs viewer, and way to often we
+ * have to inject silence bytes. I suspect part of the problem is
+ * that the HDA device require a much better latency than what the
+ * pulse audio is configured for by default (10 ms vs 150ms). */
+ STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowProblems);
+ Log(("hdaR3StreamUpdateDma: Warning! Stream #%u has insufficient data available: %u bytes, need %u. Will try move pull more data into the buffer...\n",
+ pStreamShared->u8SD, cbStreamUsed, cbPeriod));
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ AudioMixerSinkUpdate(pSink, cbStreamUsed, cbPeriod);
+ hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+ Log(("hdaR3StreamUpdateDma: Gained %u bytes.\n", hdaR3StreamGetUsed(pStreamR3) - cbStreamUsed));
+ cbStreamUsed = hdaR3StreamGetUsed(pStreamR3);
+ if (cbStreamUsed < cbPeriod)
+ {
+ /* Unable to find sufficient input data by simple prodding.
+ In order to keep a constant byte stream following thru the DMA
+ engine into the guest, we will try again and then fall back on
+ filling the gap with silence. */
+ uint32_t cbSilence = 0;
+ do
+ {
+ AudioMixerSinkLock(pSink);
+
+ cbStreamUsed = hdaR3StreamGetUsed(pStreamR3);
+ if (cbStreamUsed < cbPeriod)
+ {
+ hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink);
+ cbStreamUsed = hdaR3StreamGetUsed(pStreamR3);
+ while (cbStreamUsed < cbPeriod)
+ {
+ void *pvDstBuf;
+ size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pStreamR3->State.pCircBuf, cbPeriod - cbStreamUsed,
+ &pvDstBuf, &cbDstBuf);
+ RT_BZERO(pvDstBuf, cbDstBuf);
+ RTCircBufReleaseWriteBlock(pStreamR3->State.pCircBuf, cbDstBuf);
+ cbSilence += (uint32_t)cbDstBuf;
+ cbStreamUsed += (uint32_t)cbDstBuf;
+ }
+ }
+
+ AudioMixerSinkUnlock(pSink);
+ } while (cbStreamUsed < cbPeriod);
+ if (cbSilence > 0)
+ {
+ STAM_REL_COUNTER_INC(&pStreamR3->State.StatDmaFlowErrors);
+ STAM_REL_COUNTER_ADD(&pStreamR3->State.StatDmaFlowErrorBytes, cbSilence);
+ LogRel2(("HDA: Warning: Stream #%RU8 underrun, added %u bytes of silence (%u us)\n", pStreamShared->u8SD,
+ cbSilence, PDMAudioPropsBytesToMicro(&pStreamShared->State.Cfg.Props, cbSilence)));
+ }
+ }
+ }
+
+ /*
+ * Do the DMA'ing.
+ */
+ if (cbStreamUsed)
+ hdaR3StreamDoDmaInput(pDevIns, pThis, pStreamShared, pStreamR3,
+ RT_MIN(cbStreamUsed, cbPeriod), fWriteSilence, tsNowNs);
+
+ /*
+ * We should always kick the AIO thread.
+ */
+ /** @todo This isn't entirely ideal. If we get into an underrun situation,
+ * we ideally want the AIO thread to run right before the DMA timer
+ * rather than right after it ran. */
+ Log5Func(("Notifying AIO thread\n"));
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ pStreamShared->State.tsLastReadNs = tsNowNs;
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNAUDMIXSINKUPDATE}
+ *
+ * For output streams this moves data from the internal DMA buffer (in which
+ * hdaR3StreamUpdateDma put it), thru the mixer and to the various backend audio
+ * devices.
+ *
+ * For input streams this pulls data from the backend audio device(s), thru the
+ * mixer and puts it in the internal DMA buffer ready for hdaR3StreamUpdateDma
+ * to pump into guest memory.
+ */
+DECLCALLBACK(void) hdaR3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
+{
+ PHDASTATE const pThis = PDMDEVINS_2_DATA(pDevIns, PHDASTATE);
+ PHDASTATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PHDASTATER3);
+ PHDASTREAMR3 const pStreamR3 = (PHDASTREAMR3)pvUser;
+ PHDASTREAM const pStreamShared = &pThis->aStreams[pStreamR3 - &pThisCC->aStreams[0]];
+ Assert(pStreamR3 - &pThisCC->aStreams[0] == pStreamR3->u8SD);
+ Assert(pStreamShared->u8SD == pStreamR3->u8SD);
+ RT_NOREF(pSink);
+
+ /*
+ * Make sure we haven't change sink and that it's still active (it
+ * should be or we wouldn't have been called).
+ */
+ AssertReturnVoid(pStreamR3->pMixSink && pSink == pStreamR3->pMixSink->pMixSink);
+ AssertReturnVoid(AudioMixerSinkIsActive(pSink));
+
+ /*
+ * Output streams (SDO).
+ */
+ if (hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_OUT)
+ hdaR3StreamPushToMixer(pStreamShared, pStreamR3, pSink, RTTimeNanoTS());
+ /*
+ * Input stream (SDI).
+ */
+ else
+ {
+ Assert(hdaGetDirFromSD(pStreamShared->u8SD) == PDMAUDIODIR_IN);
+ hdaR3StreamPullFromMixer(pStreamShared, pStreamR3, pSink);
+ }
+}
+
+#endif /* IN_RING3 */
+
diff --git a/src/VBox/Devices/Audio/DevHdaStream.h b/src/VBox/Devices/Audio/DevHdaStream.h
new file mode 100644
index 00000000..6b23d6c4
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHdaStream.h
@@ -0,0 +1,330 @@
+/* $Id: DevHdaStream.h $ */
+/** @file
+ * Intel HD Audio Controller Emulation - Streams.
+ */
+
+/*
+ * Copyright (C) 2017-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHdaStream_h
+#define VBOX_INCLUDED_SRC_Audio_DevHdaStream_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHda_h
+# error "Only include DevHda.h!"
+#endif
+
+
+/**
+ * Structure containing HDA stream debug stuff, configurable at runtime.
+ */
+typedef struct HDASTREAMDEBUGRT
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ uint8_t Padding[7];
+ /** File for dumping stream reads / writes.
+ * For input streams, this dumps data being written to the device FIFO,
+ * whereas for output streams this dumps data being read from the device FIFO. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileStream;
+ /** File for dumping raw DMA reads / writes.
+ * For input streams, this dumps data being written to the device DMA,
+ * whereas for output streams this dumps data being read from the device DMA. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileDMARaw;
+ /** File for dumping mapped (that is, extracted) DMA reads / writes. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileDMAMapped;
+} HDASTREAMDEBUGRT;
+
+/**
+ * Structure containing HDA stream debug information.
+ */
+typedef struct HDASTREAMDEBUG
+{
+ /** Runtime debug info. */
+ HDASTREAMDEBUGRT Runtime;
+ uint64_t au64Alignment[2];
+} HDASTREAMDEBUG;
+
+/**
+ * Internal state of a HDA stream.
+ */
+typedef struct HDASTREAMSTATE
+{
+ /** Flag indicating whether this stream currently is
+ * in reset mode and therefore not acccessible by the guest. */
+ volatile bool fInReset;
+ /** Flag indicating if the stream is in running state or not. */
+ volatile bool fRunning;
+ /** How many interrupts are pending due to
+ * BDLE interrupt-on-completion (IOC) bits set. */
+ uint8_t cTransferPendingInterrupts;
+ /** Input streams only: Set when we switch from feeding the guest silence and
+ * commits to proving actual audio input bytes. */
+ bool fInputPreBuffered;
+ /** Input streams only: The number of bytes we need to prebuffer. */
+ uint32_t cbInputPreBuffer;
+ /** Timestamp (absolute, in timer ticks) of the last DMA data transfer.
+ * @note This is used for wall clock (WALCLK) calculations. */
+ uint64_t volatile tsTransferLast;
+ /** The stream's current configuration (matches SDnFMT). */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** Timestamp (real time, in ns) of last DMA transfer. */
+ uint64_t tsLastTransferNs;
+ /** Timestamp (real time, in ns) of last stream read (to backends).
+ * When running in async I/O mode, this differs from \a tsLastTransferNs,
+ * because reading / processing will be done in a separate stream. */
+ uint64_t tsLastReadNs;
+
+ /** The start time for the playback (on the timer clock). */
+ uint64_t tsStart;
+
+ /** @name DMA engine
+ * @{ */
+ /** Timestamp (absolute, in timer ticks) of the next DMA data transfer.
+ * Next for determining the next scheduling window.
+ * Can be 0 if no next transfer is scheduled. */
+ uint64_t tsTransferNext;
+ /** The size of the current DMA transfer period. */
+ uint32_t cbCurDmaPeriod;
+ /** The size of an average transfer. */
+ uint32_t cbAvgTransfer;
+
+ /** Current circular buffer read offset (for tracing & logging). */
+ uint64_t offRead;
+ /** Current circular buffer write offset (for tracing & logging). */
+ uint64_t offWrite;
+
+ /** The offset into the current BDLE. */
+ uint32_t offCurBdle;
+ /** LVI + 1 */
+ uint16_t cBdles;
+ /** The index of the current BDLE.
+ * This is the entry which period is currently "running" on the DMA timer. */
+ uint8_t idxCurBdle;
+ /** The number of prologue scheduling steps.
+ * This is used when the tail BDLEs doesn't have IOC set. */
+ uint8_t cSchedulePrologue;
+ /** Number of scheduling steps. */
+ uint16_t cSchedule;
+ /** Current scheduling step. */
+ uint16_t idxSchedule;
+ /** Current loop number within the current scheduling step. */
+ uint32_t idxScheduleLoop;
+
+ /** Buffer descriptors and additional timer scheduling state.
+ * (Same as HDABDLEDESC, with more sensible naming.) */
+ struct
+ {
+ /** The buffer address. */
+ uint64_t GCPhys;
+ /** The buffer size (guest bytes). */
+ uint32_t cb;
+ /** The flags (only bit 0 is defined). */
+ uint32_t fFlags;
+ } aBdl[256];
+ /** Scheduling steps. */
+ struct
+ {
+ /** Number of timer ticks per period.
+ * ASSUMES that we don't need a full second and that the timer resolution
+ * isn't much higher than nanoseconds. */
+ uint32_t cPeriodTicks;
+ /** The period length in host bytes. */
+ uint32_t cbPeriod;
+ /** Number of times to repeat the period. */
+ uint32_t cLoops;
+ /** The BDL index of the first entry. */
+ uint8_t idxFirst;
+ /** The number of BDL entries. */
+ uint8_t cEntries;
+ uint8_t abPadding[2];
+ } aSchedule[512+8];
+
+#ifdef VBOX_HDA_WITH_ON_REG_ACCESS_DMA
+ /** Number of valid bytes in abDma.
+ * @note Volatile to prevent the compiler from re-reading it after we've
+ * validated the value in ring-0. */
+ uint32_t volatile cbDma;
+ /** Total number of bytes going via abDma this timer period. */
+ uint32_t cbDmaTotal;
+ /** DMA bounce buffer for ring-0 register reads (LPIB). */
+ uint8_t abDma[2048 - 8];
+#endif
+ /** @} */
+} HDASTREAMSTATE;
+AssertCompileSizeAlignment(HDASTREAMSTATE, 16);
+AssertCompileMemberAlignment(HDASTREAMSTATE, aBdl, 8);
+AssertCompileMemberAlignment(HDASTREAMSTATE, aBdl, 16);
+AssertCompileMemberAlignment(HDASTREAMSTATE, aSchedule, 16);
+
+/**
+ * An HDA stream (SDI / SDO) - shared.
+ *
+ * @note This HDA stream has nothing to do with a regular audio stream handled
+ * by the audio connector or the audio mixer. This HDA stream is a serial
+ * data in/out stream (SDI/SDO) defined in hardware and can contain
+ * multiple audio streams in one single SDI/SDO (interleaving streams).
+ *
+ * Contains only register values which do *not* change until a stream reset
+ * occurs.
+ */
+typedef struct HDASTREAM
+{
+ /** Internal state of this stream. */
+ HDASTREAMSTATE State;
+
+ /** Stream descriptor number (SDn). */
+ uint8_t u8SD;
+ /** Current channel index.
+ * For a stereo stream, this is u8Channel + 1. */
+ uint8_t u8Channel;
+ /** FIFO Watermark (checked + translated in bytes, FIFOW).
+ * This will be update from hdaRegWriteSDFIFOW() and also copied
+ * hdaR3StreamInit() for some reason. */
+ uint8_t u8FIFOW;
+
+ /** @name Register values at stream setup.
+ * These will all be copied in hdaR3StreamInit().
+ * @{ */
+ /** FIFO Size (checked + translated in bytes, FIFOS).
+ * This is supposedly the max number of bytes we'll be DMA'ing in one chunk
+ * and correspondingly the LPIB & wall clock update jumps. However, we're
+ * not at all being honest with the guest about this. */
+ uint8_t u8FIFOS;
+ /** Cyclic Buffer Length (SDnCBL) - Represents the size of the ring buffer. */
+ uint32_t u32CBL;
+ /** Last Valid Index (SDnLVI). */
+ uint16_t u16LVI;
+ /** Format (SDnFMT). */
+ uint16_t u16FMT;
+ uint8_t abPadding[4];
+ /** DMA base address (SDnBDPU - SDnBDPL). */
+ uint64_t u64BDLBase;
+ /** @} */
+
+ /** The timer for pumping data thru the attached LUN drivers. */
+ TMTIMERHANDLE hTimer;
+
+ /** Pad the structure size to a 64 byte alignment. */
+ uint64_t au64Padding1[2];
+} HDASTREAM;
+AssertCompileMemberAlignment(HDASTREAM, State.aBdl, 16);
+AssertCompileMemberAlignment(HDASTREAM, State.aSchedule, 16);
+AssertCompileSizeAlignment(HDASTREAM, 64);
+/** Pointer to an HDA stream (SDI / SDO). */
+typedef HDASTREAM *PHDASTREAM;
+
+
+/**
+ * An HDA stream (SDI / SDO) - ring-3 bits.
+ */
+typedef struct HDASTREAMR3
+{
+ /** Stream descriptor number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding[7];
+ /** The shared state for the parent HDA device. */
+ R3PTRTYPE(PHDASTATE) pHDAStateShared;
+ /** The ring-3 state for the parent HDA device. */
+ R3PTRTYPE(PHDASTATER3) pHDAStateR3;
+ /** Pointer to HDA sink this stream is attached to. */
+ R3PTRTYPE(PHDAMIXERSINK) pMixSink;
+ /** Internal state of this stream. */
+ struct
+ {
+ /** Circular buffer (FIFO) for holding DMA'ed data. */
+ R3PTRTYPE(PRTCIRCBUF) pCircBuf;
+ /** The mixer sink this stream has registered AIO update callback with.
+ * This is NULL till we register it, typically in hdaR3StreamEnable.
+ * (The problem with following the pMixSink assignment is that hdaR3StreamReset
+ * sets it without updating the HDA sink structure, so things get out of
+ * wack in hdaR3MixerControl later in the initial device reset.) */
+ PAUDMIXSINK pAioRegSink;
+
+ /** Size of the DMA buffer (pCircBuf) in bytes. */
+ uint32_t StatDmaBufSize;
+ /** Number of used bytes in the DMA buffer (pCircBuf). */
+ uint32_t StatDmaBufUsed;
+ /** Counter for all under/overflows problems. */
+ STAMCOUNTER StatDmaFlowProblems;
+ /** Counter for unresovled under/overflows problems. */
+ STAMCOUNTER StatDmaFlowErrors;
+ /** Number of bytes involved in unresolved flow errors. */
+ STAMCOUNTER StatDmaFlowErrorBytes;
+ /** DMA skipped because buffer interrupt pending. */
+ STAMCOUNTER StatDmaSkippedPendingBcis;
+
+ STAMPROFILE StatStart;
+ STAMPROFILE StatReset;
+ STAMPROFILE StatStop;
+ } State;
+ /** Debug bits. */
+ HDASTREAMDEBUG Dbg;
+ uint64_t au64Alignment[3];
+} HDASTREAMR3;
+AssertCompileSizeAlignment(HDASTREAMR3, 64);
+/** Pointer to an HDA stream (SDI / SDO). */
+typedef HDASTREAMR3 *PHDASTREAMR3;
+
+/** @name Stream functions (all contexts).
+ * @{
+ */
+VBOXSTRICTRC hdaStreamDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared,
+ uint64_t tsNow, uint32_t cbToTransfer);
+VBOXSTRICTRC hdaStreamMaybeDoOnAccessDmaOutput(PPDMDEVINS pDevIns, PHDASTATE pThis,
+ PHDASTREAM pStreamShared, uint64_t tsNow);
+/** @} */
+
+#ifdef IN_RING3
+
+/** @name Stream functions (ring-3).
+ * @{
+ */
+int hdaR3StreamConstruct(PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, PHDASTATE pThis,
+ PHDASTATER3 pThisCC, uint8_t uSD);
+void hdaR3StreamDestroy(PHDASTREAMR3 pStreamR3);
+int hdaR3StreamSetUp(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared,
+ PHDASTREAMR3 pStreamR3, uint8_t uSD);
+void hdaR3StreamReset(PHDASTATE pThis, PHDASTATER3 pThisCC,
+ PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, uint8_t uSD);
+int hdaR3StreamEnable(PHDASTATE pThis, PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3, bool fEnable);
+void hdaR3StreamMarkStarted(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTREAM pStreamShared, uint64_t tsNow);
+void hdaR3StreamMarkStopped(PHDASTREAM pStreamShared);
+
+uint64_t hdaR3StreamTimerMain(PPDMDEVINS pDevIns, PHDASTATE pThis, PHDASTATER3 pThisCC,
+ PHDASTREAM pStreamShared, PHDASTREAMR3 pStreamR3);
+DECLCALLBACK(void) hdaR3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser);
+/** @} */
+
+/** @name Helper functions associated with the stream code.
+ * @{ */
+int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps);
+# ifdef LOG_ENABLED
+void hdaR3BDLEDumpAll(PPDMDEVINS pDevIns, PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE);
+# endif
+/** @} */
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_INCLUDED_SRC_Audio_DevHdaStream_h */
+
diff --git a/src/VBox/Devices/Audio/DevIchAc97.cpp b/src/VBox/Devices/Audio/DevIchAc97.cpp
new file mode 100644
index 00000000..97743372
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevIchAc97.cpp
@@ -0,0 +1,4829 @@
+/* $Id: DevIchAc97.cpp $ */
+/** @file
+ * DevIchAc97 - VBox ICH AC97 Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_AC97
+#include <VBox/log.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/AssertGuest.h>
+
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# ifdef DEBUG
+# include <iprt/file.h>
+# endif
+# include <iprt/mem.h>
+# include <iprt/semaphore.h>
+# include <iprt/string.h>
+# include <iprt/uuid.h>
+# include <iprt/zero.h>
+#endif
+
+#include "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+#include "AudioHlp.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Current saved state version. */
+#define AC97_SAVED_STATE_VERSION 1
+
+/** Default timer frequency (in Hz). */
+#define AC97_TIMER_HZ_DEFAULT 100
+
+/** Maximum number of streams we support. */
+#define AC97_MAX_STREAMS 3
+
+/** Maximum FIFO size (in bytes) - unused. */
+#define AC97_FIFO_MAX 256
+
+/** @name AC97_SR_XXX - Status Register Bits (AC97_NABM_OFF_SR, PI_SR, PO_SR, MC_SR).
+ * @{ */
+#define AC97_SR_FIFOE RT_BIT(4) /**< rwc, FIFO error. */
+#define AC97_SR_BCIS RT_BIT(3) /**< rwc, Buffer completion interrupt status. */
+#define AC97_SR_LVBCI RT_BIT(2) /**< rwc, Last valid buffer completion interrupt. */
+#define AC97_SR_CELV RT_BIT(1) /**< ro, Current equals last valid. */
+#define AC97_SR_DCH RT_BIT(0) /**< ro, Controller halted. */
+#define AC97_SR_VALID_MASK (RT_BIT(5) - 1)
+#define AC97_SR_WCLEAR_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI)
+#define AC97_SR_RO_MASK (AC97_SR_DCH | AC97_SR_CELV)
+#define AC97_SR_INT_MASK (AC97_SR_FIFOE | AC97_SR_BCIS | AC97_SR_LVBCI)
+/** @} */
+
+/** @name AC97_CR_XXX - Control Register Bits (AC97_NABM_OFF_CR, PI_CR, PO_CR, MC_CR).
+ * @{ */
+#define AC97_CR_IOCE RT_BIT(4) /**< rw, Interrupt On Completion Enable. */
+#define AC97_CR_FEIE RT_BIT(3) /**< rw FIFO Error Interrupt Enable. */
+#define AC97_CR_LVBIE RT_BIT(2) /**< rw Last Valid Buffer Interrupt Enable. */
+#define AC97_CR_RR RT_BIT(1) /**< rw Reset Registers. */
+#define AC97_CR_RPBM RT_BIT(0) /**< rw Run/Pause Bus Master. */
+#define AC97_CR_VALID_MASK (RT_BIT(5) - 1)
+#define AC97_CR_DONT_CLEAR_MASK (AC97_CR_IOCE | AC97_CR_FEIE | AC97_CR_LVBIE)
+/** @} */
+
+/** @name AC97_GC_XXX - Global Control Bits (see AC97_GLOB_CNT).
+ * @{ */
+#define AC97_GC_WR 4 /**< rw Warm reset. */
+#define AC97_GC_CR 2 /**< rw Cold reset. */
+#define AC97_GC_VALID_MASK (RT_BIT(6) - 1)
+/** @} */
+
+/** @name AC97_GS_XXX - Global Status Bits (AC97_GLOB_STA).
+ * @{ */
+#define AC97_GS_MD3 RT_BIT(17) /**< rw */
+#define AC97_GS_AD3 RT_BIT(16) /**< rw */
+#define AC97_GS_RCS RT_BIT(15) /**< rwc */
+#define AC97_GS_B3S12 RT_BIT(14) /**< ro */
+#define AC97_GS_B2S12 RT_BIT(13) /**< ro */
+#define AC97_GS_B1S12 RT_BIT(12) /**< ro */
+#define AC97_GS_S1R1 RT_BIT(11) /**< rwc */
+#define AC97_GS_S0R1 RT_BIT(10) /**< rwc */
+#define AC97_GS_S1CR RT_BIT(9) /**< ro */
+#define AC97_GS_S0CR RT_BIT(8) /**< ro */
+#define AC97_GS_MINT RT_BIT(7) /**< ro */
+#define AC97_GS_POINT RT_BIT(6) /**< ro */
+#define AC97_GS_PIINT RT_BIT(5) /**< ro */
+#define AC97_GS_RSRVD (RT_BIT(4) | RT_BIT(3))
+#define AC97_GS_MOINT RT_BIT(2) /**< ro */
+#define AC97_GS_MIINT RT_BIT(1) /**< ro */
+#define AC97_GS_GSCI RT_BIT(0) /**< rwc */
+#define AC97_GS_RO_MASK ( AC97_GS_B3S12 \
+ | AC97_GS_B2S12 \
+ | AC97_GS_B1S12 \
+ | AC97_GS_S1CR \
+ | AC97_GS_S0CR \
+ | AC97_GS_MINT \
+ | AC97_GS_POINT \
+ | AC97_GS_PIINT \
+ | AC97_GS_RSRVD \
+ | AC97_GS_MOINT \
+ | AC97_GS_MIINT)
+#define AC97_GS_VALID_MASK (RT_BIT(18) - 1)
+#define AC97_GS_WCLEAR_MASK (AC97_GS_RCS | AC97_GS_S1R1 | AC97_GS_S0R1 | AC97_GS_GSCI)
+/** @} */
+
+/** @name Buffer Descriptor (BDLE, BDL).
+ * @{ */
+#define AC97_BD_IOC RT_BIT(31) /**< Interrupt on Completion. */
+#define AC97_BD_BUP RT_BIT(30) /**< Buffer Underrun Policy. */
+
+#define AC97_BD_LEN_MASK 0xFFFF /**< Mask for the BDL buffer length. */
+
+#define AC97_BD_LEN_CTL_MBZ UINT32_C(0x3fff0000) /**< Must-be-zero mask for AC97BDLE.ctl_len. */
+
+#define AC97_MAX_BDLE 32 /**< Maximum number of BDLEs. */
+/** @} */
+
+/** @name Extended Audio ID Register (EAID).
+ * @{ */
+#define AC97_EAID_VRA RT_BIT(0) /**< Variable Rate Audio. */
+#define AC97_EAID_VRM RT_BIT(3) /**< Variable Rate Mic Audio. */
+#define AC97_EAID_REV0 RT_BIT(10) /**< AC'97 revision compliance. */
+#define AC97_EAID_REV1 RT_BIT(11) /**< AC'97 revision compliance. */
+/** @} */
+
+/** @name Extended Audio Control and Status Register (EACS).
+ * @{ */
+#define AC97_EACS_VRA RT_BIT(0) /**< Variable Rate Audio (4.2.1.1). */
+#define AC97_EACS_VRM RT_BIT(3) /**< Variable Rate Mic Audio (4.2.1.1). */
+/** @} */
+
+/** @name Baseline Audio Register Set (BARS).
+ * @{ */
+#define AC97_BARS_VOL_MASK 0x1f /**< Volume mask for the Baseline Audio Register Set (5.7.2). */
+#define AC97_BARS_GAIN_MASK 0x0f /**< Gain mask for the Baseline Audio Register Set. */
+#define AC97_BARS_VOL_MUTE_SHIFT 15 /**< Mute bit shift for the Baseline Audio Register Set (5.7.2). */
+/** @} */
+
+/** AC'97 uses 1.5dB steps, we use 0.375dB steps: 1 AC'97 step equals 4 PDM steps. */
+#define AC97_DB_FACTOR 4
+
+/** @name Recording inputs?
+ * @{ */
+#define AC97_REC_MIC UINT8_C(0)
+#define AC97_REC_CD UINT8_C(1)
+#define AC97_REC_VIDEO UINT8_C(2)
+#define AC97_REC_AUX UINT8_C(3)
+#define AC97_REC_LINE_IN UINT8_C(4)
+#define AC97_REC_STEREO_MIX UINT8_C(5)
+#define AC97_REC_MONO_MIX UINT8_C(6)
+#define AC97_REC_PHONE UINT8_C(7)
+#define AC97_REC_MASK UINT8_C(7)
+/** @} */
+
+/** @name Mixer registers / NAM BAR registers?
+ * @{ */
+#define AC97_Reset 0x00
+#define AC97_Master_Volume_Mute 0x02
+#define AC97_Headphone_Volume_Mute 0x04 /**< Also known as AUX, see table 16, section 5.7. */
+#define AC97_Master_Volume_Mono_Mute 0x06
+#define AC97_Master_Tone_RL 0x08
+#define AC97_PC_BEEP_Volume_Mute 0x0a
+#define AC97_Phone_Volume_Mute 0x0c
+#define AC97_Mic_Volume_Mute 0x0e
+#define AC97_Line_In_Volume_Mute 0x10
+#define AC97_CD_Volume_Mute 0x12
+#define AC97_Video_Volume_Mute 0x14
+#define AC97_Aux_Volume_Mute 0x16
+#define AC97_PCM_Out_Volume_Mute 0x18
+#define AC97_Record_Select 0x1a
+#define AC97_Record_Gain_Mute 0x1c
+#define AC97_Record_Gain_Mic_Mute 0x1e
+#define AC97_General_Purpose 0x20
+#define AC97_3D_Control 0x22
+#define AC97_AC_97_RESERVED 0x24
+#define AC97_Powerdown_Ctrl_Stat 0x26
+#define AC97_Extended_Audio_ID 0x28
+#define AC97_Extended_Audio_Ctrl_Stat 0x2a
+#define AC97_PCM_Front_DAC_Rate 0x2c
+#define AC97_PCM_Surround_DAC_Rate 0x2e
+#define AC97_PCM_LFE_DAC_Rate 0x30
+#define AC97_PCM_LR_ADC_Rate 0x32
+#define AC97_MIC_ADC_Rate 0x34
+#define AC97_6Ch_Vol_C_LFE_Mute 0x36
+#define AC97_6Ch_Vol_L_R_Surround_Mute 0x38
+#define AC97_Vendor_Reserved 0x58
+#define AC97_AD_Misc 0x76
+#define AC97_Vendor_ID1 0x7c
+#define AC97_Vendor_ID2 0x7e
+/** @} */
+
+/** @name Analog Devices miscellaneous regiter bits used in AD1980.
+ * @{ */
+#define AC97_AD_MISC_LOSEL RT_BIT(5) /**< Surround (rear) goes to line out outputs. */
+#define AC97_AD_MISC_HPSEL RT_BIT(10) /**< PCM (front) goes to headphone outputs. */
+/** @} */
+
+
+/** @name BUP flag values.
+ * @{ */
+#define BUP_SET RT_BIT_32(0)
+#define BUP_LAST RT_BIT_32(1)
+/** @} */
+
+/** @name AC'97 source indices.
+ * @note The order of these indices is fixed (also applies for saved states) for
+ * the moment. So make sure you know what you're done when altering this!
+ * @{
+ */
+#define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */
+#define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */
+#define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */
+#define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */
+/** @} */
+
+/** Port number (offset into NABM BAR) to stream index. */
+#define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 )
+/** Port number (offset into NABM BAR) to stream index, but no masking. */
+#define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) )
+
+/** @name Stream offsets
+ * @{ */
+#define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */
+#define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */
+#define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */
+#define AC97_NABM_OFF_SR 0x6 /**< Status Register */
+#define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */
+#define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */
+#define AC97_NABM_OFF_CR 0xb /**< Control Register */
+#define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */
+/** @} */
+
+
+/** @name PCM in NABM BAR registers (0x00..0x0f).
+ * @{ */
+#define PI_BDBAR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x0) /**< PCM in: Buffer Descriptor Base Address */
+#define PI_CIV (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x4) /**< PCM in: Current Index Value */
+#define PI_LVI (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x5) /**< PCM in: Last Valid Index */
+#define PI_SR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x6) /**< PCM in: Status Register */
+#define PI_PICB (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0x8) /**< PCM in: Position in Current Buffer */
+#define PI_PIV (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0xa) /**< PCM in: Prefetched Index Value */
+#define PI_CR (AC97SOUNDSOURCE_PI_INDEX * 0x10 + 0xb) /**< PCM in: Control Register */
+/** @} */
+
+/** @name PCM out NABM BAR registers (0x10..0x1f).
+ * @{ */
+#define PO_BDBAR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x0) /**< PCM out: Buffer Descriptor Base Address */
+#define PO_CIV (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x4) /**< PCM out: Current Index Value */
+#define PO_LVI (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x5) /**< PCM out: Last Valid Index */
+#define PO_SR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x6) /**< PCM out: Status Register */
+#define PO_PICB (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0x8) /**< PCM out: Position in Current Buffer */
+#define PO_PIV (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0xa) /**< PCM out: Prefetched Index Value */
+#define PO_CR (AC97SOUNDSOURCE_PO_INDEX * 0x10 + 0xb) /**< PCM out: Control Register */
+/** @} */
+
+/** @name Mic in NABM BAR registers (0x20..0x2f).
+ * @{ */
+#define MC_BDBAR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x0) /**< PCM in: Buffer Descriptor Base Address */
+#define MC_CIV (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x4) /**< PCM in: Current Index Value */
+#define MC_LVI (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x5) /**< PCM in: Last Valid Index */
+#define MC_SR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x6) /**< PCM in: Status Register */
+#define MC_PICB (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0x8) /**< PCM in: Position in Current Buffer */
+#define MC_PIV (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0xa) /**< PCM in: Prefetched Index Value */
+#define MC_CR (AC97SOUNDSOURCE_MC_INDEX * 0x10 + 0xb) /**< PCM in: Control Register */
+/** @} */
+
+/** @name Misc NABM BAR registers.
+ * @{ */
+/** NABMBAR: Global Control Register.
+ * @note This is kind of in the MIC IN area. */
+#define AC97_GLOB_CNT 0x2c
+/** NABMBAR: Global Status. */
+#define AC97_GLOB_STA 0x30
+/** Codec Access Semaphore Register. */
+#define AC97_CAS 0x34
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The ICH AC'97 (Intel) controller (shared). */
+typedef struct AC97STATE *PAC97STATE;
+/** The ICH AC'97 (Intel) controller (ring-3). */
+typedef struct AC97STATER3 *PAC97STATER3;
+
+/**
+ * Buffer Descriptor List Entry (BDLE).
+ *
+ * (See section 3.2.1 in Intel document number 252751-001, or section 1.2.2.1 in
+ * Intel document number 302349-003.)
+ */
+typedef struct AC97BDLE
+{
+ /** Location of data buffer (bits 31:1). */
+ uint32_t addr;
+ /** Flags (bits 31 + 30) and length (bits 15:0) of data buffer (in audio samples).
+ * @todo split up into two 16-bit fields. */
+ uint32_t ctl_len;
+} AC97BDLE;
+AssertCompileSize(AC97BDLE, 8);
+/** Pointer to BDLE. */
+typedef AC97BDLE *PAC97BDLE;
+
+/**
+ * Bus master register set for an audio stream.
+ *
+ * (See section 16.2 in Intel document 301473-002, or section 2.2 in Intel
+ * document 302349-003.)
+ */
+typedef struct AC97BMREGS
+{
+ uint32_t bdbar; /**< rw 0, Buffer Descriptor List: BAR (Base Address Register). */
+ uint8_t civ; /**< ro 0, Current index value. */
+ uint8_t lvi; /**< rw 0, Last valid index. */
+ uint16_t sr; /**< rw 1, Status register. */
+ uint16_t picb; /**< ro 0, Position in current buffer (samples left to process). */
+ uint8_t piv; /**< ro 0, Prefetched index value. */
+ uint8_t cr; /**< rw 0, Control register. */
+ int32_t bd_valid; /**< Whether current BDLE is initialized or not. */
+ AC97BDLE bd; /**< Current Buffer Descriptor List Entry (BDLE). */
+} AC97BMREGS;
+AssertCompileSizeAlignment(AC97BMREGS, 8);
+/** Pointer to the BM registers of an audio stream. */
+typedef AC97BMREGS *PAC97BMREGS;
+
+/**
+ * The internal state of an AC'97 stream.
+ */
+typedef struct AC97STREAMSTATE
+{
+ /** Critical section for this stream. */
+ RTCRITSECT CritSect;
+ /** Circular buffer (FIFO) for holding DMA'ed data. */
+ R3PTRTYPE(PRTCIRCBUF) pCircBuf;
+#if HC_ARCH_BITS == 32
+ uint32_t Padding;
+#endif
+ /** Current circular buffer read offset (for tracing & logging). */
+ uint64_t offRead;
+ /** Current circular buffer write offset (for tracing & logging). */
+ uint64_t offWrite;
+ /** The stream's current configuration. */
+ PDMAUDIOSTREAMCFG Cfg; //+108
+ /** Timestamp of the last DMA data transfer. */
+ uint64_t tsTransferLast;
+ /** Timestamp of the next DMA data transfer.
+ * Next for determining the next scheduling window.
+ * Can be 0 if no next transfer is scheduled. */
+ uint64_t tsTransferNext;
+ /** The stream's timer Hz rate.
+ * This value can can be different from the device's default Hz rate,
+ * depending on the rate the stream expects (e.g. for 5.1 speaker setups).
+ * Set in R3StreamInit(). */
+ uint16_t uTimerHz;
+ /** Set if we've registered the asynchronous update job. */
+ bool fRegisteredAsyncUpdateJob;
+ /** Input streams only: Set when we switch from feeding the guest silence and
+ * commits to proving actual audio input bytes. */
+ bool fInputPreBuffered;
+ /** This is ZERO if stream setup succeeded, otherwise it's the RTTimeNanoTS() at
+ * which to retry setting it up. The latter applies only to same
+ * parameters. */
+ uint64_t nsRetrySetup;
+ /** Timestamp (in ns) of last stream update. */
+ uint64_t tsLastUpdateNs;
+
+ /** Size of the DMA buffer (pCircBuf) in bytes. */
+ uint32_t StatDmaBufSize;
+ /** Number of used bytes in the DMA buffer (pCircBuf). */
+ uint32_t StatDmaBufUsed;
+ /** Counter for all under/overflows problems. */
+ STAMCOUNTER StatDmaFlowProblems;
+ /** Counter for unresovled under/overflows problems. */
+ STAMCOUNTER StatDmaFlowErrors;
+ /** Number of bytes involved in unresolved flow errors. */
+ STAMCOUNTER StatDmaFlowErrorBytes;
+ STAMCOUNTER StatDmaSkippedDch;
+ STAMCOUNTER StatDmaSkippedPendingBcis;
+ STAMPROFILE StatStart;
+ STAMPROFILE StatReset;
+ STAMPROFILE StatStop;
+ STAMPROFILE StatReSetUpChanged;
+ STAMPROFILE StatReSetUpSame;
+ STAMCOUNTER StatWriteLviRecover;
+ STAMCOUNTER StatWriteCr;
+} AC97STREAMSTATE;
+AssertCompileSizeAlignment(AC97STREAMSTATE, 8);
+/** Pointer to internal state of an AC'97 stream. */
+typedef AC97STREAMSTATE *PAC97STREAMSTATE;
+
+/**
+ * Runtime configurable debug stuff for an AC'97 stream.
+ */
+typedef struct AC97STREAMDEBUGRT
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ uint8_t Padding[7];
+ /** File for dumping stream reads / writes.
+ * For input streams, this dumps data being written to the device FIFO,
+ * whereas for output streams this dumps data being read from the device FIFO. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileStream;
+ /** File for dumping DMA reads / writes.
+ * For input streams, this dumps data being written to the device DMA,
+ * whereas for output streams this dumps data being read from the device DMA. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileDMA;
+} AC97STREAMDEBUGRT;
+
+/**
+ * Debug stuff for an AC'97 stream.
+ */
+typedef struct AC97STREAMDEBUG
+{
+ /** Runtime debug stuff. */
+ AC97STREAMDEBUGRT Runtime;
+} AC97STREAMDEBUG;
+
+/**
+ * The shared AC'97 stream state.
+ */
+typedef struct AC97STREAM
+{
+ /** Bus master registers of this stream. */
+ AC97BMREGS Regs;
+ /** Stream number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding0[7];
+
+ /** The timer for pumping data thru the attached LUN drivers. */
+ TMTIMERHANDLE hTimer;
+ /** When the timer was armed (timer clock). */
+ uint64_t uArmedTs;
+ /** (Virtual) clock ticks per transfer. */
+ uint64_t cDmaPeriodTicks;
+ /** Transfer chunk size (in bytes) of a transfer period. */
+ uint32_t cbDmaPeriod;
+ /** DMA period counter (for logging). */
+ uint32_t uDmaPeriod;
+
+ STAMCOUNTER StatWriteLvi;
+ STAMCOUNTER StatWriteSr1;
+ STAMCOUNTER StatWriteSr2;
+ STAMCOUNTER StatWriteBdBar;
+} AC97STREAM;
+AssertCompileSizeAlignment(AC97STREAM, 8);
+/** Pointer to a shared AC'97 stream state. */
+typedef AC97STREAM *PAC97STREAM;
+
+
+/**
+ * The ring-3 AC'97 stream state.
+ */
+typedef struct AC97STREAMR3
+{
+ /** Stream number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding0[7];
+ /** Internal state of this stream. */
+ AC97STREAMSTATE State;
+ /** Debug stuff. */
+ AC97STREAMDEBUG Dbg;
+} AC97STREAMR3;
+AssertCompileSizeAlignment(AC97STREAMR3, 8);
+/** Pointer to an AC'97 stream state for ring-3. */
+typedef AC97STREAMR3 *PAC97STREAMR3;
+
+
+/**
+ * A driver stream (host backend).
+ *
+ * Each driver has its own instances of audio mixer streams, which then
+ * can go into the same (or even different) audio mixer sinks.
+ */
+typedef struct AC97DRIVERSTREAM
+{
+ /** Associated mixer stream handle. */
+ R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
+} AC97DRIVERSTREAM;
+/** Pointer to a driver stream. */
+typedef AC97DRIVERSTREAM *PAC97DRIVERSTREAM;
+
+/**
+ * A host backend driver (LUN).
+ */
+typedef struct AC97DRIVER
+{
+ /** Node for storing this driver in our device driver list of AC97STATE. */
+ RTLISTNODER3 Node;
+ /** LUN # to which this driver has been assigned. */
+ uint8_t uLUN;
+ /** Whether this driver is in an attached state or not. */
+ bool fAttached;
+ uint8_t abPadding[6];
+ /** Pointer to attached driver base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Audio connector interface to the underlying host backend. */
+ R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector;
+ /** Driver stream for line input. */
+ AC97DRIVERSTREAM LineIn;
+ /** Driver stream for mic input. */
+ AC97DRIVERSTREAM MicIn;
+ /** Driver stream for output. */
+ AC97DRIVERSTREAM Out;
+ /** The LUN description. */
+ char szDesc[48 - 2];
+} AC97DRIVER;
+/** Pointer to a host backend driver (LUN). */
+typedef AC97DRIVER *PAC97DRIVER;
+
+/**
+ * Debug settings.
+ */
+typedef struct AC97STATEDEBUG
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ bool afAlignment[7];
+ /** Path where to dump the debug output to.
+ * Can be NULL, in which the system's temporary directory will be used then. */
+ R3PTRTYPE(char *) pszOutPath;
+} AC97STATEDEBUG;
+
+
+/* Codec models. */
+typedef enum AC97CODEC
+{
+ AC97CODEC_INVALID = 0, /**< Customary illegal zero value. */
+ AC97CODEC_STAC9700, /**< SigmaTel STAC9700 */
+ AC97CODEC_AD1980, /**< Analog Devices AD1980 */
+ AC97CODEC_AD1981B, /**< Analog Devices AD1981B */
+ AC97CODEC_32BIT_HACK = 0x7fffffff
+} AC97CODEC;
+
+
+/**
+ * The shared AC'97 device state.
+ */
+typedef struct AC97STATE
+{
+ /** Critical section protecting the AC'97 state. */
+ PDMCRITSECT CritSect;
+ /** Global Control (Bus Master Control Register). */
+ uint32_t glob_cnt;
+ /** Global Status (Bus Master Control Register). */
+ uint32_t glob_sta;
+ /** Codec Access Semaphore Register (Bus Master Control Register). */
+ uint32_t cas;
+ uint32_t last_samp;
+ uint8_t mixer_data[256];
+ /** Array of AC'97 streams (parallel to AC97STATER3::aStreams). */
+ AC97STREAM aStreams[AC97_MAX_STREAMS];
+ /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */
+ uint16_t uTimerHz;
+ /** Config: Internal input DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeInMs config value. */
+ uint16_t cMsCircBufIn;
+ /** Config: Internal output DMA buffer size override, specified in milliseconds.
+ * Zero means default size according to buffer and stream config.
+ * @sa BufSizeOutMs config value. */
+ uint16_t cMsCircBufOut;
+ uint16_t au16Padding1[1];
+ uint8_t silence[128];
+ uint32_t bup_flag;
+ /** Codec model. */
+ AC97CODEC enmCodecModel;
+
+ /** PCI region \#0: NAM I/O ports. */
+ IOMIOPORTHANDLE hIoPortsNam;
+ /** PCI region \#0: NANM I/O ports. */
+ IOMIOPORTHANDLE hIoPortsNabm;
+
+ STAMCOUNTER StatUnimplementedNabmReads;
+ STAMCOUNTER StatUnimplementedNabmWrites;
+ STAMCOUNTER StatUnimplementedNamReads;
+ STAMCOUNTER StatUnimplementedNamWrites;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatTimer;
+#endif
+} AC97STATE;
+AssertCompileMemberAlignment(AC97STATE, aStreams, 8);
+AssertCompileMemberAlignment(AC97STATE, StatUnimplementedNabmReads, 8);
+
+
+/**
+ * The ring-3 AC'97 device state.
+ */
+typedef struct AC97STATER3
+{
+ /** Array of AC'97 streams (parallel to AC97STATE:aStreams). */
+ AC97STREAMR3 aStreams[AC97_MAX_STREAMS];
+ /** R3 pointer to the device instance. */
+ PPDMDEVINSR3 pDevIns;
+ /** List of associated LUN drivers (AC97DRIVER). */
+ RTLISTANCHORR3 lstDrv;
+ /** The device's software mixer. */
+ R3PTRTYPE(PAUDIOMIXER) pMixer;
+ /** Audio sink for PCM output. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkOut;
+ /** Audio sink for line input. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkLineIn;
+ /** Audio sink for microphone input. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkMicIn;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** Debug settings. */
+ AC97STATEDEBUG Dbg;
+} AC97STATER3;
+AssertCompileMemberAlignment(AC97STATER3, aStreams, 8);
+/** Pointer to the ring-3 AC'97 device state. */
+typedef AC97STATER3 *PAC97STATER3;
+
+
+/**
+ * Acquires the AC'97 lock.
+ */
+#define DEVAC97_LOCK(a_pDevIns, a_pThis) \
+ do { \
+ int const rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, VERR_IGNORED); \
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV((a_pDevIns), &(a_pThis)->CritSect, rcLock); \
+ } while (0)
+
+/**
+ * Acquires the AC'97 lock or returns.
+ */
+# define DEVAC97_LOCK_RETURN(a_pDevIns, a_pThis, a_rcBusy) \
+ do { \
+ int rcLock = PDMDevHlpCritSectEnter((a_pDevIns), &(a_pThis)->CritSect, a_rcBusy); \
+ if (rcLock == VINF_SUCCESS) \
+ { /* likely */ } \
+ else \
+ { \
+ AssertRC(rcLock); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Releases the AC'97 lock.
+ */
+#define DEVAC97_UNLOCK(a_pDevIns, a_pThis) \
+ do { PDMDevHlpCritSectLeave((a_pDevIns), &(a_pThis)->CritSect); } while (0)
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr);
+static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx);
+#ifdef IN_RING3
+DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC);
+DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC);
+static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PCDBGFINFOHLP pHlp, const char *pszPrefix);
+static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns);
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+/** NABM I/O port descriptions. */
+static const IOMIOPORTDESC g_aNabmPorts[] =
+{
+ { "PCM IN - BDBAR", "PCM IN - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - CIV", "PCM IN - CIV", NULL, NULL },
+ { "PCM IN - LVI", "PCM IN - LIV", NULL, NULL },
+ { "PCM IN - SR", "PCM IN - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - PICB", "PCM IN - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM IN - PIV", "PCM IN - PIV", NULL, NULL },
+ { "PCM IN - CR", "PCM IN - CR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "PCM OUT - BDBAR", "PCM OUT - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - CIV", "PCM OUT - CIV", NULL, NULL },
+ { "PCM OUT - LVI", "PCM OUT - LIV", NULL, NULL },
+ { "PCM OUT - SR", "PCM OUT - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - PICB", "PCM OUT - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "PCM OUT - PIV", "PCM OUT - PIV", NULL, NULL },
+ { "PCM OUT - CR", "PCM IN - CR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "MIC IN - BDBAR", "MIC IN - BDBAR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - CIV", "MIC IN - CIV", NULL, NULL },
+ { "MIC IN - LVI", "MIC IN - LIV", NULL, NULL },
+ { "MIC IN - SR", "MIC IN - SR", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - PICB", "MIC IN - PICB", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "MIC IN - PIV", "MIC IN - PIV", NULL, NULL },
+ { "MIC IN - CR", "MIC IN - CR", NULL, NULL },
+ { "GLOB CNT", "GLOB CNT", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+
+ { "GLOB STA", "GLOB STA", NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "", NULL, NULL, NULL },
+ { "CAS", "CAS", NULL, NULL },
+ { NULL, NULL, NULL, NULL },
+};
+
+/** @name Source indices
+ * @{ */
+# define AC97SOUNDSOURCE_PI_INDEX 0 /**< PCM in */
+# define AC97SOUNDSOURCE_PO_INDEX 1 /**< PCM out */
+# define AC97SOUNDSOURCE_MC_INDEX 2 /**< Mic in */
+# define AC97SOUNDSOURCE_MAX 3 /**< Max sound sources. */
+/** @} */
+
+/** Port number (offset into NABM BAR) to stream index. */
+# define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 )
+/** Port number (offset into NABM BAR) to stream index, but no masking. */
+# define AC97_PORT2IDX_UNMASKED(a_idx) ( ((a_idx) >> 4) )
+
+/** @name Stream offsets
+ * @{ */
+# define AC97_NABM_OFF_BDBAR 0x0 /**< Buffer Descriptor Base Address */
+# define AC97_NABM_OFF_CIV 0x4 /**< Current Index Value */
+# define AC97_NABM_OFF_LVI 0x5 /**< Last Valid Index */
+# define AC97_NABM_OFF_SR 0x6 /**< Status Register */
+# define AC97_NABM_OFF_PICB 0x8 /**< Position in Current Buffer */
+# define AC97_NABM_OFF_PIV 0xa /**< Prefetched Index Value */
+# define AC97_NABM_OFF_CR 0xb /**< Control Register */
+# define AC97_NABM_OFF_MASK 0xf /**< Mask for getting the the per-stream register. */
+/** @} */
+
+#endif /* IN_RING3 */
+
+
+
+static void ichac97WarmReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+static void ichac97ColdReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+
+#ifdef IN_RING3
+
+/**
+ * Returns the audio direction of a specified stream descriptor.
+ *
+ * @return Audio direction.
+ */
+DECLINLINE(PDMAUDIODIR) ichac97R3GetDirFromSD(uint8_t uSD)
+{
+ switch (uSD)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX: return PDMAUDIODIR_IN;
+ case AC97SOUNDSOURCE_PO_INDEX: return PDMAUDIODIR_OUT;
+ case AC97SOUNDSOURCE_MC_INDEX: return PDMAUDIODIR_IN;
+ }
+
+ AssertFailed();
+ return PDMAUDIODIR_UNKNOWN;
+}
+
+
+/**
+ * Retrieves the audio mixer sink of a corresponding AC'97 stream index.
+ *
+ * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param uIndex Stream index to get audio mixer sink for.
+ */
+DECLINLINE(PAUDMIXSINK) ichac97R3IndexToSink(PAC97STATER3 pThisCC, uint8_t uIndex)
+{
+ switch (uIndex)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX: return pThisCC->pSinkLineIn;
+ case AC97SOUNDSOURCE_PO_INDEX: return pThisCC->pSinkOut;
+ case AC97SOUNDSOURCE_MC_INDEX: return pThisCC->pSinkMicIn;
+ default:
+ AssertMsgFailedReturn(("Wrong index %RU8\n", uIndex), NULL);
+ }
+}
+
+
+/*********************************************************************************************************************************
+* Stream DMA *
+*********************************************************************************************************************************/
+
+/**
+ * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Available data (in bytes).
+ * @param pStreamCC The AC'97 stream to retrieve size for (ring-3).
+ */
+DECLINLINE(uint32_t) ichac97R3StreamGetUsed(PAC97STREAMR3 pStreamCC)
+{
+ PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf;
+ if (pCircBuf)
+ return (uint32_t)RTCircBufUsed(pCircBuf);
+ return 0;
+}
+
+
+/**
+ * Retrieves the free size of audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Free data (in bytes).
+ * @param pStreamCC AC'97 stream to retrieve size for (ring-3).
+ */
+DECLINLINE(uint32_t) ichac97R3StreamGetFree(PAC97STREAMR3 pStreamCC)
+{
+ PRTCIRCBUF const pCircBuf = pStreamCC->State.pCircBuf;
+ if (pCircBuf)
+ return (uint32_t)RTCircBufFree(pCircBuf);
+ return 0;
+}
+
+
+# if 0 /* Unused */
+static void ichac97R3WriteBUP(PAC97STATE pThis, uint32_t cbElapsed)
+{
+ LogFlowFunc(("cbElapsed=%RU32\n", cbElapsed));
+
+ if (!(pThis->bup_flag & BUP_SET))
+ {
+ if (pThis->bup_flag & BUP_LAST)
+ {
+ unsigned int i;
+ uint32_t *p = (uint32_t*)pThis->silence;
+ for (i = 0; i < sizeof(pThis->silence) / 4; i++) /** @todo r=andy Assumes 16-bit samples, stereo. */
+ *p++ = pThis->last_samp;
+ }
+ else
+ RT_ZERO(pThis->silence);
+
+ pThis->bup_flag |= BUP_SET;
+ }
+
+ while (cbElapsed)
+ {
+ uint32_t cbToWrite = RT_MIN(cbElapsed, (uint32_t)sizeof(pThis->silence));
+ uint32_t cbWrittenToStream;
+
+ int rc2 = AudioMixerSinkWrite(pThisCC->pSinkOut, AUDMIXOP_COPY,
+ pThis->silence, cbToWrite, &cbWrittenToStream);
+ if (RT_SUCCESS(rc2))
+ {
+ if (cbWrittenToStream < cbToWrite) /* Lagging behind? */
+ LogFlowFunc(("Warning: Only written %RU32 / %RU32 bytes, expect lags\n", cbWrittenToStream, cbToWrite));
+ }
+
+ /* Always report all data as being written;
+ * backends who were not able to catch up have to deal with it themselves. */
+ Assert(cbElapsed >= cbToWrite);
+ cbElapsed -= cbToWrite;
+ }
+}
+# endif /* Unused */
+
+
+/**
+ * Fetches the next buffer descriptor (BDLE) updating the stream registers.
+ *
+ * This will skip zero length descriptors.
+ *
+ * @returns Zero, or AC97_SR_BCIS if skipped zero length buffer with IOC set.
+ * @param pDevIns The device instance.
+ * @param pStream AC'97 stream to fetch BDLE for.
+ * @param pStreamCC The AC'97 stream, ring-3 state.
+ *
+ * @remarks Updates CIV, PIV, BD and PICB.
+ *
+ * @note Both PIV and CIV will be zero after a stream reset, so the first
+ * time we advance the buffer position afterwards, CIV will remain zero
+ * and PIV becomes 1. Thus we will start processing from BDLE00 and
+ * not BDLE01 as CIV=0 may lead you to think.
+ */
+static uint32_t ichac97R3StreamFetchNextBdle(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ RT_NOREF(pStreamCC);
+ uint32_t fSrBcis = 0;
+ uint32_t cbTotal = 0; /* Counts the total length (in bytes) of the buffer descriptor list (BDL). */
+
+ /*
+ * Loop for skipping zero length entries.
+ */
+ for (;;)
+ {
+ /* Advance the buffer. */
+ pStream->Regs.civ = pStream->Regs.piv % AC97_MAX_BDLE /* (paranoia) */;
+ pStream->Regs.piv = (pStream->Regs.piv + 1) % AC97_MAX_BDLE;
+
+ /* Load it. */
+ AC97BDLE Bdle = { 0, 0 };
+ PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar + pStream->Regs.civ * sizeof(AC97BDLE), &Bdle, sizeof(AC97BDLE));
+ pStream->Regs.bd_valid = 1;
+ pStream->Regs.bd.addr = RT_H2LE_U32(Bdle.addr) & ~3;
+ pStream->Regs.bd.ctl_len = RT_H2LE_U32(Bdle.ctl_len);
+ pStream->Regs.picb = pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK;
+
+ cbTotal += pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK;
+
+ LogFlowFunc(("BDLE%02u: %#RX32 L %#x / LB %#x, ctl=%#06x%s%s\n",
+ pStream->Regs.civ, pStream->Regs.bd.addr, pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK,
+ (pStream->Regs.bd.ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props),
+ pStream->Regs.bd.ctl_len >> 16,
+ pStream->Regs.bd.ctl_len & AC97_BD_IOC ? " ioc" : "",
+ pStream->Regs.bd.ctl_len & AC97_BD_BUP ? " bup" : ""));
+
+ /* Complain about any reserved bits set in CTL and ADDR: */
+ ASSERT_GUEST_MSG(!(pStream->Regs.bd.ctl_len & AC97_BD_LEN_CTL_MBZ),
+ ("Reserved bits set: %#RX32\n", pStream->Regs.bd.ctl_len));
+ ASSERT_GUEST_MSG(!(RT_H2LE_U32(Bdle.addr) & 3),
+ ("Reserved addr bits set: %#RX32\n", RT_H2LE_U32(Bdle.addr) ));
+
+ /* If the length is non-zero or if we've reached LVI, we're done regardless
+ of what's been loaded. Otherwise, we skip zero length buffers. */
+ if (pStream->Regs.picb)
+ break;
+ if (pStream->Regs.civ == (pStream->Regs.lvi % AC97_MAX_BDLE /* (paranoia) */))
+ {
+ LogFunc(("BDLE%02u is zero length! Can't skip (CIV=LVI). %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len));
+ break;
+ }
+ LogFunc(("BDLE%02u is zero length! Skipping. %#RX32 %#RX32\n", pStream->Regs.civ, Bdle.addr, Bdle.ctl_len));
+
+ /* If the buffer has IOC set, make sure it's triggered by the caller. */
+ if (pStream->Regs.bd.ctl_len & AC97_BD_IOC)
+ fSrBcis |= AC97_SR_BCIS;
+ }
+
+ /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #1 */
+ ASSERT_GUEST_MSG(!(pStream->Regs.picb & 1),
+ ("Odd lengths buffers are not allowed: %#x (%d) samples\n", pStream->Regs.picb, pStream->Regs.picb));
+
+ /* 1.2.4.2 PCM Buffer Restrictions (in 302349-003) - #2
+ *
+ * Note: Some guests (like older NetBSDs) first seem to set up the BDL a tad later so that cbTotal is 0.
+ * This means that the BDL is not set up at all.
+ * In such cases pStream->Regs.picb also will be 0 here and (debug) asserts here, which is annoying for debug builds.
+ * So first check if we have *any* BDLE set up before checking if PICB is > 0.
+ */
+ ASSERT_GUEST_MSG(cbTotal == 0 || pStream->Regs.picb > 0, ("Zero length buffers not allowed to terminate list (LVI=%u CIV=%u, cbTotal=%zu)\n",
+ pStream->Regs.lvi, pStream->Regs.civ, cbTotal));
+
+ return fSrBcis;
+}
+
+
+/**
+ * Transfers data of an AC'97 stream according to its usage (input / output).
+ *
+ * For an SDO (output) stream this means reading DMA data from the device to
+ * the AC'97 stream's internal FIFO buffer.
+ *
+ * For an SDI (input) stream this is reading audio data from the AC'97 stream's
+ * internal FIFO buffer and writing it as DMA data to the device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ * @param cbToProcess The max amount of data to process (i.e.
+ * put into / remove from the circular buffer).
+ * Unless something is going seriously wrong, this
+ * will always be transfer size for the current
+ * period. The current period will never be
+ * larger than what can be stored in the current
+ * buffer (i.e. what PICB indicates).
+ * @param fWriteSilence Whether to write silence if this is an input
+ * stream (done while waiting for backend to get
+ * going).
+ * @param fInput Set if input, clear if output.
+ */
+static int ichac97R3StreamTransfer(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PAC97STREAMR3 pStreamCC, uint32_t cbToProcess, bool fWriteSilence, bool fInput)
+{
+ if (RT_LIKELY(cbToProcess > 0))
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbToProcess));
+ else
+ return VINF_SUCCESS;
+
+ ichac97R3StreamLock(pStreamCC);
+
+ /*
+ * Check that the controller is not halted (DCH) and that the buffer
+ * completion interrupt isn't pending.
+ */
+ /** @todo r=bird: Why do we not just barge ahead even when BCIS is set? Can't
+ * find anything in spec indicating that we shouldn't. Linux shouldn't
+ * care if be bundle IOCs, as it checks how many steps we've taken using
+ * CIV. The Windows AC'97 sample driver doesn't care at all, since it
+ * just sets LIV to CIV-1 (thought that's probably not what the real
+ * windows driver does)...
+ *
+ * This is not going to sound good if it happens often enough, because
+ * each time we'll lose one DMA period (exact length depends on the
+ * buffer here).
+ *
+ * If we're going to keep this hack, there should be a
+ * PDMDevHlpTimerSetRelative call arm-ing the DMA timer to fire shortly
+ * after BCIS is cleared. Otherwise, we might lag behind even more
+ * before we get stuff going again.
+ *
+ * I just wish there was some clear reasoning in the source code for
+ * weird shit like this. This is just random voodoo. Sigh^3! */
+ if (!(pStream->Regs.sr & (AC97_SR_DCH | AC97_SR_BCIS))) /* Controller halted? */
+ { /* not halted nor does it have pending interrupt - likely */ }
+ else
+ {
+ /** @todo Stop DMA timer when DCH is set. */
+ if (pStream->Regs.sr & AC97_SR_DCH)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedDch);
+ LogFunc(("[SD%RU8] DCH set\n", pStream->u8SD));
+ }
+ if (pStream->Regs.sr & AC97_SR_BCIS)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaSkippedPendingBcis);
+ LogFunc(("[SD%RU8] BCIS set\n", pStream->u8SD));
+ }
+ if ((pStream->Regs.cr & AC97_CR_RPBM) /* Bus master operation started. */ && !fInput)
+ {
+ /*ichac97R3WriteBUP(pThis, cbToProcess);*/
+ }
+
+ ichac97R3StreamUnlock(pStreamCC);
+ return VINF_SUCCESS;
+ }
+
+ /* 0x1ba*2 = 0x374 (884) 0x3c0
+ * Transfer loop.
+ */
+#ifdef LOG_ENABLED
+ uint32_t cbProcessedTotal = 0;
+#endif
+ int rc = VINF_SUCCESS;
+ PRTCIRCBUF pCircBuf = pStreamCC->State.pCircBuf;
+ AssertReturnStmt(pCircBuf, ichac97R3StreamUnlock(pStreamCC), VINF_SUCCESS);
+ Assert((uint32_t)pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props) >= cbToProcess);
+ Log3Func(("[SD%RU8] cbToProcess=%#x PICB=%#x/%#x\n", pStream->u8SD, cbToProcess,
+ pStream->Regs.picb, pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props)));
+
+ while (cbToProcess > 0)
+ {
+ uint32_t cbChunk = cbToProcess;
+
+ /*
+ * Output.
+ */
+ if (!fInput)
+ {
+ void *pvDst = NULL;
+ size_t cbDst = 0;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst);
+
+ if (cbDst)
+ {
+ int rc2 = PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bd.addr, pvDst, cbDst);
+ AssertRC(rc2);
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvDst, cbDst);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDst);
+
+ cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */
+ }
+ /*
+ * Input.
+ */
+ else if (!fWriteSilence)
+ {
+ void *pvSrc = NULL;
+ size_t cbSrc = 0;
+ RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc);
+
+ if (cbSrc)
+ {
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, pvSrc, cbSrc);
+ AssertRC(rc2);
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.pFileDMA))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamCC->Dbg.Runtime.pFileDMA, pvSrc, cbSrc);
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbSrc);
+
+ cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */
+ }
+ else
+ {
+ /* Since the format is signed 16-bit or 32-bit integer samples, we can
+ use g_abRTZero64K as source and avoid some unnecessary bzero() work. */
+ cbChunk = RT_MIN(cbChunk, sizeof(g_abRTZero64K));
+ cbChunk = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbChunk);
+
+ int rc2 = PDMDevHlpPCIPhysWrite(pDevIns, pStream->Regs.bd.addr, g_abRTZero64K, cbChunk);
+ AssertRC(rc2);
+ }
+
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCC->State.Cfg.Props, cbChunk));
+ Assert(cbChunk <= cbToProcess);
+
+ /*
+ * Advance.
+ */
+ pStream->Regs.picb -= cbChunk / PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props);
+ pStream->Regs.bd.addr += cbChunk;
+ cbToProcess -= cbChunk;
+#ifdef LOG_ENABLED
+ cbProcessedTotal += cbChunk;
+#endif
+ LogFlowFunc(("[SD%RU8] cbChunk=%#x, cbToProcess=%#x, cbTotal=%#x picb=%#x\n",
+ pStream->u8SD, cbChunk, cbToProcess, cbProcessedTotal, pStream->Regs.picb));
+ }
+
+ /*
+ * Fetch a new buffer descriptor if we've exhausted the current one.
+ */
+ if (!pStream->Regs.picb)
+ {
+ uint32_t fNewSr = pStream->Regs.sr & ~AC97_SR_CELV;
+
+ if (pStream->Regs.bd.ctl_len & AC97_BD_IOC)
+ fNewSr |= AC97_SR_BCIS;
+
+ if (pStream->Regs.civ != pStream->Regs.lvi)
+ fNewSr |= ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC);
+ else
+ {
+ LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pStream->Regs.civ, pStream->Regs.lvi));
+ fNewSr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV;
+ pThis->bup_flag = (pStream->Regs.bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0;
+ /** @todo r=bird: The bup_flag isn't cleared anywhere else. We should probably
+ * do what the spec says, and keep writing zeros (silence).
+ * Alternatively, we could hope the guest will pause the DMA engine
+ * immediately after seeing this condition, in which case we should
+ * stop the DMA timer from being re-armed. */
+ }
+
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, fNewSr);
+ }
+
+ ichac97R3StreamUnlock(pStreamCC);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Input streams: Pulls data from the mixer, putting it in the internal DMA
+ * buffer.
+ *
+ * @param pStreamR3 The AC'97 stream (ring-3 bits).
+ * @param pSink The mixer sink to pull from.
+ */
+static void ichac97R3StreamPullFromMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offWriteOld = pStreamR3->State.offWrite;
+# endif
+ pStreamR3->State.offWrite = AudioMixerSinkTransferToCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamR3->State.offWrite,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ pStreamR3->State.offWrite - offWriteOld, pStreamR3->State.offWrite));
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Output streams: Pushes data to the mixer.
+ *
+ * @param pStreamR3 The AC'97 stream (ring-3 bits).
+ * @param pSink The mixer sink to push to.
+ */
+static void ichac97R3StreamPushToMixer(PAC97STREAMR3 pStreamR3, PAUDMIXSINK pSink)
+{
+# ifdef LOG_ENABLED
+ uint64_t const offReadOld = pStreamR3->State.offRead;
+# endif
+ pStreamR3->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
+ pStreamR3->State.pCircBuf,
+ pStreamR3->State.offRead,
+ pStreamR3->u8SD,
+ pStreamR3->Dbg.Runtime.fEnabled
+ ? pStreamR3->Dbg.Runtime.pFileStream : NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStreamR3->u8SD,
+ pStreamR3->State.offRead - offReadOld, pStreamR3->State.offRead));
+
+ /* Update buffer stats. */
+ pStreamR3->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf);
+}
+
+
+/**
+ * Updates an AC'97 stream by doing its DMA transfers.
+ *
+ * The host sink(s) set the overall pace (bird: no it doesn't, the DMA timer
+ * does - we just hope like heck it matches the speed at which the *backend*
+ * host audio driver processes samples).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ * @param pSink The sink being updated.
+ */
+static void ichac97R3StreamUpdateDma(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, PAUDMIXSINK pSink)
+{
+ RT_NOREF(pThisCC);
+ int rc2;
+
+ /* The amount we're supposed to be transfering in this DMA period. */
+ uint32_t cbPeriod = pStream->cbDmaPeriod;
+
+ /*
+ * Output streams (SDO).
+ */
+ if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ /*
+ * Check how much room we have in our DMA buffer. There should be at
+ * least one period worth of space there or we're in an overflow situation.
+ */
+ uint32_t cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ if (cbStreamFree >= cbPeriod)
+ { /* likely */ }
+ else
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems);
+ LogFunc(("Warning! Stream #%u has insufficient space free: %u bytes, need %u. Will try move data out of the buffer...\n",
+ pStreamCC->u8SD, cbStreamFree, cbPeriod));
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ ichac97R3StreamPushToMixer(pStreamCC, pSink);
+ AudioMixerSinkUpdate(pSink, 0, 0);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+ LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetFree(pStreamCC) - cbStreamFree));
+
+ cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ if (cbStreamFree < cbPeriod)
+ {
+ /* Unable to make sufficient space. Drop the whole buffer content.
+ * This is needed in order to keep the device emulation running at a constant rate,
+ * at the cost of losing valid (but too much) data. */
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors);
+ LogRel2(("AC97: Warning: Hit stream #%RU8 overflow, dropping %u bytes of audio data\n",
+ pStreamCC->u8SD, ichac97R3StreamGetUsed(pStreamCC)));
+# ifdef AC97_STRICT
+ AssertMsgFailed(("Hit stream #%RU8 overflow -- timing bug?\n", pStreamCC->u8SD));
+# endif
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+ pStreamCC->State.offWrite = 0;
+ pStreamCC->State.offRead = 0;
+ cbStreamFree = ichac97R3StreamGetFree(pStreamCC);
+ Assert(cbStreamFree >= cbPeriod);
+ }
+ }
+
+ /*
+ * Do the DMA transfer.
+ */
+ Log3Func(("[SD%RU8] PICB=%#x samples / %RU64 ms, cbFree=%#x / %RU64 ms, cbTransferChunk=%#x / %RU64 ms\n", pStream->u8SD,
+ pStream->Regs.picb, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props,
+ PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props)
+ * pStream->Regs.picb),
+ cbStreamFree, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbStreamFree),
+ cbPeriod, PDMAudioPropsBytesToMilli(&pStreamCC->State.Cfg.Props, cbPeriod)));
+
+ rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbStreamFree, cbPeriod),
+ false /*fWriteSilence*/, false /*fInput*/);
+ AssertRC(rc2);
+
+ pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS();
+
+
+ /*
+ * Notify the AIO thread.
+ */
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ }
+ /*
+ * Input stream (SDI).
+ */
+ else
+ {
+ /*
+ * See how much data we've got buffered...
+ */
+ bool fWriteSilence = false;
+ uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (pStreamCC->State.fInputPreBuffered && cbStreamUsed >= cbPeriod)
+ { /*likely*/ }
+ /*
+ * Because it may take a while for the input stream to get going (at least
+ * with pulseaudio), we feed the guest silence till we've pre-buffer a
+ * couple of timer Hz periods. (This avoid lots of bogus buffer underruns
+ * when starting an input stream and hogging the timer EMT.)
+ */
+ else if (!pStreamCC->State.fInputPreBuffered)
+ {
+ uint32_t const cbPreBuffer = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props,
+ RT_NS_1SEC / pStreamCC->State.uTimerHz);
+ if (cbStreamUsed < cbPreBuffer)
+ {
+ Log3Func(("Pre-buffering (got %#x out of %#x bytes)...\n", cbStreamUsed, cbPreBuffer));
+ fWriteSilence = true;
+ cbStreamUsed = cbPeriod;
+ }
+ else
+ {
+ Log3Func(("Completed pre-buffering (got %#x, needed %#x bytes).\n", cbStreamUsed, cbPreBuffer));
+ pStreamCC->State.fInputPreBuffered = true;
+ fWriteSilence = ichac97R3StreamGetFree(pStreamCC) >= cbPreBuffer + cbPreBuffer / 2;
+ if (fWriteSilence)
+ cbStreamUsed = cbPeriod;
+ }
+ }
+ /*
+ * When we're low on data, we must really try fetch some ourselves
+ * as buffer underruns must not happen.
+ */
+ else
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowProblems);
+ LogFunc(("Warning! Stream #%u has insufficient data available: %u bytes, need %u. Will try move pull more data into the buffer...\n",
+ pStreamCC->u8SD, cbStreamUsed, cbPeriod));
+ int rc = AudioMixerSinkTryLock(pSink);
+ if (RT_SUCCESS(rc))
+ {
+ AudioMixerSinkUpdate(pSink, cbStreamUsed, cbPeriod);
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+ AudioMixerSinkUnlock(pSink);
+ }
+ else
+ RTThreadYield();
+ LogFunc(("Gained %u bytes.\n", ichac97R3StreamGetUsed(pStreamCC) - cbStreamUsed));
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (cbStreamUsed < cbPeriod)
+ {
+ /* Unable to find sufficient input data by simple prodding.
+ In order to keep a constant byte stream following thru the DMA
+ engine into the guest, we will try again and then fall back on
+ filling the gap with silence. */
+ uint32_t cbSilence = 0;
+ do
+ {
+ AudioMixerSinkLock(pSink);
+
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ if (cbStreamUsed < cbPeriod)
+ {
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+ cbStreamUsed = ichac97R3StreamGetUsed(pStreamCC);
+ while (cbStreamUsed < cbPeriod)
+ {
+ void *pvDstBuf;
+ size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pStreamCC->State.pCircBuf, cbPeriod - cbStreamUsed,
+ &pvDstBuf, &cbDstBuf);
+ RT_BZERO(pvDstBuf, cbDstBuf);
+ RTCircBufReleaseWriteBlock(pStreamCC->State.pCircBuf, cbDstBuf);
+ cbSilence += (uint32_t)cbDstBuf;
+ cbStreamUsed += (uint32_t)cbDstBuf;
+ }
+ }
+
+ AudioMixerSinkUnlock(pSink);
+ } while (cbStreamUsed < cbPeriod);
+ if (cbSilence > 0)
+ {
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatDmaFlowErrors);
+ STAM_REL_COUNTER_ADD(&pStreamCC->State.StatDmaFlowErrorBytes, cbSilence);
+ LogRel2(("AC97: Warning: Stream #%RU8 underrun, added %u bytes of silence (%u us)\n", pStreamCC->u8SD,
+ cbSilence, PDMAudioPropsBytesToMicro(&pStreamCC->State.Cfg.Props, cbSilence)));
+ }
+ }
+ }
+
+ /*
+ * Do the DMA'ing.
+ */
+ if (cbStreamUsed)
+ {
+ rc2 = ichac97R3StreamTransfer(pDevIns, pThis, pStream, pStreamCC, RT_MIN(cbPeriod, cbStreamUsed),
+ fWriteSilence, true /*fInput*/);
+ AssertRC(rc2);
+
+ pStreamCC->State.tsLastUpdateNs = RTTimeNanoTS();
+ }
+
+ /*
+ * We should always kick the AIO thread.
+ */
+ /** @todo This isn't entirely ideal. If we get into an underrun situation,
+ * we ideally want the AIO thread to run right before the DMA timer
+ * rather than right after it ran. */
+ Log5Func(("Notifying AIO thread\n"));
+ rc2 = AudioMixerSinkSignalUpdateJob(pSink);
+ AssertRC(rc2);
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNAUDMIXSINKUPDATE}
+ *
+ * For output streams this moves data from the internal DMA buffer (in which
+ * ichac97R3StreamUpdateDma put it), thru the mixer and to the various backend
+ * audio devices.
+ *
+ * For input streams this pulls data from the backend audio device(s), thru the
+ * mixer and puts it in the internal DMA buffer ready for
+ * ichac97R3StreamUpdateDma to pump into guest memory.
+ */
+static DECLCALLBACK(void) ichac97R3StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
+{
+ PAC97STATER3 const pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PAC97STREAMR3 const pStreamCC = (PAC97STREAMR3)pvUser;
+ Assert(pStreamCC->u8SD == (uintptr_t)(pStreamCC - &pThisCC->aStreams[0]));
+ Assert(pSink == ichac97R3IndexToSink(pThisCC, pStreamCC->u8SD));
+ RT_NOREF(pThisCC);
+
+ /*
+ * Output (SDO).
+ */
+ if (pStreamCC->State.Cfg.enmDir == PDMAUDIODIR_OUT)
+ ichac97R3StreamPushToMixer(pStreamCC, pSink);
+ /*
+ * Input (SDI).
+ */
+ else
+ ichac97R3StreamPullFromMixer(pStreamCC, pSink);
+}
+
+
+/**
+ * Updates the next transfer based on a specific amount of bytes.
+ *
+ * @param pDevIns The device instance.
+ * @param pStream The AC'97 stream to update (shared).
+ * @param pStreamCC The AC'97 stream to update (ring-3).
+ */
+static void ichac97R3StreamTransferUpdate(PPDMDEVINS pDevIns, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ /*
+ * Get the number of bytes left in the current buffer.
+ *
+ * This isn't entirely optimal iff the current entry doesn't have IOC set, in
+ * that case we should use the number of bytes to the next IOC. Unfortuantely,
+ * it seems the spec doesn't allow us to prefetch more than one BDLE, so we
+ * probably cannot look ahead without violating that restriction. This is
+ * probably a purely theoretical problem at this point.
+ */
+ uint32_t const cbLeftInBdle = pStream->Regs.picb * PDMAudioPropsSampleSize(&pStreamCC->State.Cfg.Props);
+ if (cbLeftInBdle > 0) /** @todo r=bird: see todo about this in ichac97R3StreamFetchBDLE. */
+ {
+ /*
+ * Since the buffer can be up to 0xfffe samples long (frame aligning stereo
+ * prevents 0xffff), which translates to 743ms at a 44.1kHz rate, we must
+ * also take the nominal timer frequency into account here so we keep
+ * moving data at a steady rate. (In theory, I think the guest can even
+ * set up just one buffer and anticipate where we are in the buffer
+ * processing when it writes/reads from it. Linux seems to be doing such
+ * configs when not playing or something.)
+ */
+ uint32_t const cbMaxPerHz = PDMAudioPropsNanoToBytes(&pStreamCC->State.Cfg.Props, RT_NS_1SEC / pStreamCC->State.uTimerHz);
+
+ if (cbLeftInBdle <= cbMaxPerHz)
+ pStream->cbDmaPeriod = cbLeftInBdle;
+ /* Try avoid leaving a very short period at the end of a buffer. */
+ else if (cbLeftInBdle >= cbMaxPerHz + cbMaxPerHz / 2)
+ pStream->cbDmaPeriod = cbMaxPerHz;
+ else
+ pStream->cbDmaPeriod = PDMAudioPropsFloorBytesToFrame(&pStreamCC->State.Cfg.Props, cbLeftInBdle / 2);
+
+ /*
+ * Translate the chunk size to timer ticks.
+ */
+ uint64_t const cNsXferChunk = PDMAudioPropsBytesToNano(&pStreamCC->State.Cfg.Props, pStream->cbDmaPeriod);
+ pStream->cDmaPeriodTicks = PDMDevHlpTimerFromNano(pDevIns, pStream->hTimer, cNsXferChunk);
+ Assert(pStream->cDmaPeriodTicks > 0);
+
+ Log3Func(("[SD%RU8] cbLeftInBdle=%#RX32 cbMaxPerHz=%#RX32 (%RU16Hz) -> cbDmaPeriod=%#RX32 cDmaPeriodTicks=%RX64\n",
+ pStream->u8SD, cbLeftInBdle, cbMaxPerHz, pStreamCC->State.uTimerHz, pStream->cbDmaPeriod, pStream->cDmaPeriodTicks));
+ }
+}
+
+
+/**
+ * Sets the virtual device timer to a new expiration time.
+ *
+ * @param pDevIns The device instance.
+ * @param pStream AC'97 stream to set timer for.
+ * @param cTicksToDeadline The number of ticks to the new deadline.
+ *
+ * @remarks This used to be more complicated a long time ago...
+ */
+DECLINLINE(void) ichac97R3TimerSet(PPDMDEVINS pDevIns, PAC97STREAM pStream, uint64_t cTicksToDeadline)
+{
+ int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc);
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * Timer callback which handles the audio data transfers on a periodic basis.}
+ */
+static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ STAM_PROFILE_START(&pThis->StatTimer, a);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PAC97STREAM pStream = (PAC97STREAM)pvUser;
+ PAC97STREAMR3 pStreamCC = &RT_SAFE_SUBSCRIPT8(pThisCC->aStreams, pStream->u8SD);
+ Assert(hTimer == pStream->hTimer); RT_NOREF(hTimer);
+
+ Assert(pStream - &pThis->aStreams[0] == pStream->u8SD);
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ Assert(PDMDevHlpTimerIsLockOwner(pDevIns, pStream->hTimer));
+
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink && AudioMixerSinkIsActive(pSink))
+ {
+ ichac97R3StreamUpdateDma(pDevIns, pThis, pThisCC, pStream, pStreamCC, pSink);
+
+ pStream->uDmaPeriod++;
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks);
+ }
+
+ STAM_PROFILE_STOP(&pThis->StatTimer, a);
+}
+
+#endif /* IN_RING3 */
+
+
+/*********************************************************************************************************************************
+* AC'97 Stream Management *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+
+/**
+ * Locks an AC'97 stream for serialized access.
+ *
+ * @param pStreamCC The AC'97 stream to lock (ring-3).
+ */
+DECLINLINE(void) ichac97R3StreamLock(PAC97STREAMR3 pStreamCC)
+{
+ int rc2 = RTCritSectEnter(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks a formerly locked AC'97 stream.
+ *
+ * @param pStreamCC The AC'97 stream to unlock (ring-3).
+ */
+DECLINLINE(void) ichac97R3StreamUnlock(PAC97STREAMR3 pStreamCC)
+{
+ int rc2 = RTCritSectLeave(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Updates the status register (SR) of an AC'97 audio stream.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pStream AC'97 stream to update SR for.
+ * @param new_sr New value for status register (SR).
+ */
+static void ichac97StreamUpdateSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr)
+{
+ bool fSignal = false;
+ int iIRQL = 0;
+
+ uint32_t new_mask = new_sr & AC97_SR_INT_MASK;
+ uint32_t old_mask = pStream->Regs.sr & AC97_SR_INT_MASK;
+
+ if (new_mask ^ old_mask)
+ {
+ /** @todo Is IRQ deasserted when only one of status bits is cleared? */
+ if (!new_mask)
+ {
+ fSignal = true;
+ iIRQL = 0;
+ }
+ else if ((new_mask & AC97_SR_LVBCI) && (pStream->Regs.cr & AC97_CR_LVBIE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ else if ((new_mask & AC97_SR_BCIS) && (pStream->Regs.cr & AC97_CR_IOCE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ }
+
+ pStream->Regs.sr = new_sr;
+
+ LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n",
+ pStream->Regs.sr & AC97_SR_BCIS, pStream->Regs.sr & AC97_SR_LVBCI, pStream->Regs.sr, fSignal, iIRQL));
+
+ if (fSignal)
+ {
+ static uint32_t const s_aMasks[] = { AC97_GS_PIINT, AC97_GS_POINT, AC97_GS_MINT };
+ Assert(pStream->u8SD < AC97_MAX_STREAMS);
+ if (iIRQL)
+ pThis->glob_sta |= s_aMasks[pStream->u8SD];
+ else
+ pThis->glob_sta &= ~s_aMasks[pStream->u8SD];
+
+ LogFlowFunc(("Setting IRQ level=%d\n", iIRQL));
+ PDMDevHlpPCISetIrq(pDevIns, 0, iIRQL);
+ }
+}
+
+/**
+ * Writes a new value to a stream's status register (SR).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state.
+ * @param pStream Stream to update SR for.
+ * @param u32Val New value to set the stream's SR to.
+ */
+static void ichac97StreamWriteSR(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val)
+{
+ Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pStream->Regs.sr));
+
+ pStream->Regs.sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK);
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr & ~(u32Val & AC97_SR_WCLEAR_MASK));
+}
+
+#ifdef IN_RING3
+
+/**
+ * Resets an AC'97 stream.
+ *
+ * @param pThis The shared AC'97 state.
+ * @param pStream The AC'97 stream to reset (shared).
+ * @param pStreamCC The AC'97 stream to reset (ring-3).
+ */
+static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ ichac97R3StreamLock(pStreamCC);
+
+ LogFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ if (pStreamCC->State.pCircBuf)
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+
+ pStream->Regs.bdbar = 0;
+ pStream->Regs.civ = 0;
+ pStream->Regs.lvi = 0;
+
+ pStream->Regs.picb = 0;
+ pStream->Regs.piv = 0; /* Note! Because this is also zero, we will actually start transferring with BDLE00. */
+ pStream->Regs.cr &= AC97_CR_DONT_CLEAR_MASK;
+ pStream->Regs.bd_valid = 0;
+
+ RT_ZERO(pThis->silence);
+
+ ichac97R3StreamUnlock(pStreamCC);
+}
+
+/**
+ * Retrieves a specific driver stream of a AC'97 driver.
+ *
+ * @returns Pointer to driver stream if found, or NULL if not found.
+ * @param pDrv Driver to retrieve driver stream for.
+ * @param enmDir Stream direction to retrieve.
+ * @param enmPath Stream destination / source to retrieve.
+ */
+static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ if (enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("enmRecSource=%d\n", enmPath));
+ switch (enmPath)
+ {
+ case PDMAUDIOPATH_IN_LINE:
+ return &pDrv->LineIn;
+ case PDMAUDIOPATH_IN_MIC:
+ return &pDrv->MicIn;
+ default:
+ AssertFailedBreak();
+ }
+ }
+ else if (enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmPlaybackDst=%d\n", enmPath));
+ switch (enmPath)
+ {
+ case PDMAUDIOPATH_OUT_FRONT:
+ return &pDrv->Out;
+ default:
+ AssertFailedBreak();
+ }
+ }
+ else
+ AssertFailed();
+
+ return NULL;
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * Called by ichac97R3MixerAddDrvStreams() and ichac97R3MixerAddDrv().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to add driver stream to.
+ * @param pCfg Stream configuration to use.
+ * @param pDrv Driver stream to add.
+ */
+static int ichac97R3MixerAddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName));
+
+ int rc;
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath);
+ if (pDrvStream)
+ {
+ AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
+
+ PAUDMIXSTREAM pMixStrm;
+ rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+ else
+ AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * Called by ichac97R3StreamSetUp().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pMixSink Mixer sink to add stream to.
+ * @param pCfg Stream configuration to use.
+ */
+static int ichac97R3MixerAddDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+
+ int rc;
+ if (AudioHlpStreamCfgIsValid(pCfg))
+ {
+ rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint);
+ if (RT_SUCCESS(rc))
+ {
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pMixSink, pCfg, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("Attaching stream failed with %Rrc\n", rc2));
+
+ /* Do not pass failure to rc here, as there might be drivers which aren't
+ configured / ready yet. */
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Removes a driver stream from a specific mixer sink.
+ *
+ * Worker for ichac97R3MixerRemoveDrvStreams.
+ *
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ * @param pDrv Driver stream to remove.
+ */
+static void ichac97R3MixerRemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir,
+ PDMAUDIOPATH enmPath, PAC97DRIVER pDrv)
+{
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pDrv, enmDir, enmPath);
+ if (pDrvStream)
+ {
+ if (pDrvStream->pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm);
+
+ AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/);
+ pDrvStream->pMixStrm = NULL;
+ }
+ }
+}
+
+/**
+ * Removes all driver streams from a specific mixer sink.
+ *
+ * Called by ichac97R3StreamSetUp() and ichac97R3StreamsDestroy().
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ */
+static void ichac97R3MixerRemoveDrvStreams(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAUDMIXSINK pMixSink,
+ PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ AssertPtrReturnVoid(pMixSink);
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ ichac97R3MixerRemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv);
+ }
+}
+
+
+/**
+ * Gets the frequency of a given stream.
+ *
+ * @returns The frequency. Zero if invalid stream index.
+ * @param pThis The shared AC'97 device state.
+ * @param idxStream The stream.
+ */
+DECLINLINE(uint32_t) ichach97R3CalcStreamHz(PAC97STATE pThis, uint8_t idxStream)
+{
+ switch (idxStream)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX:
+ return ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate);
+
+ case AC97SOUNDSOURCE_MC_INDEX:
+ return ichac97MixerGet(pThis, AC97_MIC_ADC_Rate);
+
+ case AC97SOUNDSOURCE_PO_INDEX:
+ return ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate);
+
+ default:
+ AssertMsgFailedReturn(("%d\n", idxStream), 0);
+ }
+}
+
+
+/**
+ * Gets the PCM properties for a given stream.
+ *
+ * @returns pProps.
+ * @param pThis The shared AC'97 device state.
+ * @param idxStream Which stream
+ * @param pProps Where to return the stream properties.
+ */
+DECLINLINE(PPDMAUDIOPCMPROPS) ichach97R3CalcStreamProps(PAC97STATE pThis, uint8_t idxStream, PPDMAUDIOPCMPROPS pProps)
+{
+ PDMAudioPropsInit(pProps, 2 /*16-bit*/, true /*signed*/, 2 /*stereo*/, ichach97R3CalcStreamHz(pThis, idxStream));
+ return pProps;
+}
+
+
+/**
+ * Sets up an AC'97 stream with its current mixer settings.
+ *
+ * This will set up an AC'97 stream with 2 (stereo) channels, 16-bit samples and
+ * the last set sample rate in the AC'97 mixer for this stream.
+ *
+ * @returns VBox status code.
+ * @retval VINF_NO_CHANGE if the streams weren't re-created.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state (shared).
+ * @param pThisCC The shared AC'97 device state (ring-3).
+ * @param pStream The AC'97 stream to open (shared).
+ * @param pStreamCC The AC'97 stream to open (ring-3).
+ * @param fForce Whether to force re-opening the stream or not.
+ * Otherwise re-opening only will happen if the PCM properties have changed.
+ *
+ * @remarks This is called holding:
+ * -# The AC'97 device lock.
+ * -# The AC'97 stream lock.
+ * -# The mixer sink lock (to prevent racing AIO thread).
+ */
+static int ichac97R3StreamSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC, PAC97STREAM pStream,
+ PAC97STREAMR3 pStreamCC, bool fForce)
+{
+ /*
+ * Assemble the stream config and get the associated mixer sink.
+ */
+ PDMAUDIOPCMPROPS PropsTmp;
+ PDMAUDIOSTREAMCFG Cfg;
+ PDMAudioStrmCfgInitWithProps(&Cfg, ichach97R3CalcStreamProps(pThis, pStream->u8SD, &PropsTmp));
+ Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN);
+
+ PAUDMIXSINK pMixSink;
+ switch (pStream->u8SD)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.enmPath = PDMAUDIOPATH_IN_LINE;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Line-In");
+
+ pMixSink = pThisCC->pSinkLineIn;
+ break;
+
+ case AC97SOUNDSOURCE_MC_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.enmPath = PDMAUDIOPATH_IN_MIC;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Mic-In");
+
+ pMixSink = pThisCC->pSinkMicIn;
+ break;
+
+ case AC97SOUNDSOURCE_PO_INDEX:
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Output");
+
+ pMixSink = pThisCC->pSinkOut;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("u8SD=%d\n", pStream->u8SD), VERR_INTERNAL_ERROR_3);
+ }
+
+ /* Validate locks -- see @bugref{10350}. */
+ Assert(PDMDevHlpCritSectIsOwner(pDevIns, &pThis->CritSect));
+ Assert(RTCritSectIsOwned(&pStreamCC->State.CritSect));
+ Assert(AudioMixerSinkLockIsOwner(pMixSink));
+
+ /*
+ * Don't continue if the frequency is out of range (the rest of the
+ * properties should be okay).
+ * Note! Don't assert on this as we may easily end up here with Hz=0.
+ */
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ if (AudioHlpStreamCfgIsValid(&Cfg))
+ { }
+ else
+ {
+ LogFunc(("Invalid stream #%u rate: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) ));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Read the buffer descriptors and check what the max distance between
+ * interrupts are, so we can more correctly size the internal DMA buffer.
+ *
+ * Note! The buffer list are not fixed once the stream starts running as
+ * with HDA, so this is just a general idea of what the guest is
+ * up to and we cannot really make much of a plan out of it.
+ */
+ uint8_t const bLvi = pStream->Regs.lvi % AC97_MAX_BDLE /* paranoia */;
+ uint8_t const bCiv = pStream->Regs.civ % AC97_MAX_BDLE /* paranoia */;
+ uint32_t const uAddrBdl = pStream->Regs.bdbar;
+
+ /* Linux does this a number of times while probing/whatever the device. The
+ IOMMU usually does allow us to read address zero, so let's skip and hope
+ for a better config before the guest actually wants to play/record.
+ (Note that bLvi and bCiv are also zero then, but I'm not entirely sure if
+ that can be taken to mean anything as such, as it still indicates that
+ BDLE00 is valid (LVI == last valid index).) */
+ /** @todo Instead of refusing to read address zero, we should probably allow
+ * reading address zero if explicitly programmed. But, too much work now. */
+ if (uAddrBdl != 0)
+ LogFlowFunc(("bdbar=%#x bLvi=%#x bCiv=%#x\n", uAddrBdl, bLvi, bCiv));
+ else
+ {
+ LogFunc(("Invalid stream #%u: bdbar=%#x bLvi=%#x bCiv=%#x (%s)\n", pStreamCC->u8SD, uAddrBdl, bLvi, bCiv,
+ PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp))));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ AC97BDLE aBdl[AC97_MAX_BDLE];
+ RT_ZERO(aBdl);
+ PDMDevHlpPCIPhysRead(pDevIns, uAddrBdl, aBdl, sizeof(aBdl));
+
+ uint32_t cSamplesMax = 0;
+ uint32_t cSamplesMin = UINT32_MAX;
+ uint32_t cSamplesCur = 0;
+ uint32_t cSamplesTotal = 0;
+ uint32_t cBuffers = 1;
+ for (uintptr_t i = bCiv; ; cBuffers++)
+ {
+ Log2Func(("BDLE%02u: %#x LB %#x; %#x\n", i, aBdl[i].addr, aBdl[i].ctl_len & AC97_BD_LEN_MASK, aBdl[i].ctl_len >> 16));
+ cSamplesTotal += aBdl[i].ctl_len & AC97_BD_LEN_MASK;
+ cSamplesCur += aBdl[i].ctl_len & AC97_BD_LEN_MASK;
+ if (aBdl[i].ctl_len & AC97_BD_IOC)
+ {
+ if (cSamplesCur > cSamplesMax)
+ cSamplesMax = cSamplesCur;
+ if (cSamplesCur < cSamplesMin)
+ cSamplesMin = cSamplesCur;
+ cSamplesCur = 0;
+ }
+
+ /* Advance. */
+ if (i != bLvi)
+ i = (i + 1) % RT_ELEMENTS(aBdl);
+ else
+ break;
+ }
+ if (!cSamplesCur)
+ { /* likely */ }
+ else if (!cSamplesMax)
+ {
+ LogFlowFunc(("%u buffers without IOC set, assuming %#x samples as the IOC period.\n", cBuffers, cSamplesMax));
+ cSamplesMin = cSamplesMax = cSamplesCur;
+ }
+ else if (cSamplesCur > cSamplesMax)
+ {
+ LogFlowFunc(("final buffer is without IOC, using open period as max (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax));
+ cSamplesMax = cSamplesCur;
+ }
+ else
+ LogFlowFunc(("final buffer is without IOC, ignoring (%#x vs current max %#x).\n", cSamplesCur, cSamplesMax));
+
+ uint32_t const cbDmaMinBuf = cSamplesMax * PDMAudioPropsSampleSize(&Cfg.Props) * 3; /* see further down */
+ uint32_t const cMsDmaMinBuf = PDMAudioPropsBytesToMilli(&Cfg.Props, cbDmaMinBuf);
+ LogRel3(("AC97: [SD%RU8] buffer length stats: total=%#x in %u buffers, min=%#x, max=%#x => min DMA buffer %u ms / %#x bytes\n",
+ pStream->u8SD, cSamplesTotal, cBuffers, cSamplesMin, cSamplesMax, cMsDmaMinBuf, cbDmaMinBuf));
+
+ /*
+ * Calculate the timer Hz / scheduling hint based on the stream frame rate.
+ */
+ uint32_t uTimerHz;
+ if (pThis->uTimerHz == AC97_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */
+ {
+ if (Cfg.Props.uHz > 44100) /* E.g. 48000 Hz. */
+ uTimerHz = 200;
+ else
+ uTimerHz = AC97_TIMER_HZ_DEFAULT;
+ }
+ else
+ uTimerHz = pThis->uTimerHz;
+
+ if ( uTimerHz >= 10
+ && uTimerHz <= 500)
+ { /* likely */ }
+ else
+ {
+ LogFunc(("[SD%RU8] Adjusting uTimerHz=%u to %u\n", pStream->u8SD, uTimerHz,
+ Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT));
+ uTimerHz = Cfg.Props.uHz > 44100 ? 200 : AC97_TIMER_HZ_DEFAULT;
+ }
+
+ /* Translate it to a scheduling hint. */
+ uint32_t const cMsSchedulingHint = RT_MS_1SEC / uTimerHz;
+
+ /*
+ * Calculate the circular buffer size so we can decide whether to recreate
+ * the stream or not.
+ *
+ * As mentioned in the HDA code, this should be at least able to hold the
+ * data transferred in three DMA periods and in three AIO period (whichever
+ * is higher). However, if we assume that the DMA code will engage the DMA
+ * timer thread (currently EMT) if the AIO thread isn't getting schduled to
+ * transfer data thru the stack, we don't need to go overboard and double
+ * the minimums here. The less buffer the less possible delay can build when
+ * TM is doing catch up.
+ */
+ uint32_t cMsCircBuf = Cfg.enmDir == PDMAUDIODIR_IN ? pThis->cMsCircBufIn : pThis->cMsCircBufOut;
+ cMsCircBuf = RT_MAX(cMsCircBuf, cMsDmaMinBuf);
+ cMsCircBuf = RT_MAX(cMsCircBuf, cMsSchedulingHint * 3);
+ cMsCircBuf = RT_MIN(cMsCircBuf, RT_MS_1SEC * 2);
+ uint32_t const cbCircBuf = PDMAudioPropsMilliToBytes(&Cfg.Props, cMsCircBuf);
+
+ LogFlowFunc(("Stream %u: uTimerHz: %u -> %u; cMsSchedulingHint: %u -> %u; cbCircBuf: %#zx -> %#x (%u ms, cMsDmaMinBuf=%u)%s\n",
+ pStreamCC->u8SD, pStreamCC->State.uTimerHz, uTimerHz,
+ pStreamCC->State.Cfg.Device.cMsSchedulingHint, cMsSchedulingHint,
+ pStreamCC->State.pCircBuf ? RTCircBufSize(pStreamCC->State.pCircBuf) : 0, cbCircBuf, cMsCircBuf, cMsDmaMinBuf,
+ !pStreamCC->State.pCircBuf || RTCircBufSize(pStreamCC->State.pCircBuf) != cbCircBuf ? " - re-creating DMA buffer" : ""));
+
+ /*
+ * Update the stream's timer rate and scheduling hint, re-registering the AIO
+ * update job if necessary.
+ */
+ if ( pStreamCC->State.Cfg.Device.cMsSchedulingHint != cMsSchedulingHint
+ || !pStreamCC->State.fRegisteredAsyncUpdateJob)
+ {
+ if (pStreamCC->State.fRegisteredAsyncUpdateJob)
+ AudioMixerSinkRemoveUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC);
+ int rc2 = AudioMixerSinkAddUpdateJob(pMixSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC,
+ pStreamCC->State.Cfg.Device.cMsSchedulingHint);
+ AssertRC(rc2);
+ pStreamCC->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc2) || rc2 == VERR_ALREADY_EXISTS;
+ }
+
+ pStreamCC->State.uTimerHz = uTimerHz;
+ Cfg.Device.cMsSchedulingHint = cMsSchedulingHint;
+
+ /*
+ * Re-create the circular buffer if necessary, resetting if not.
+ */
+ if ( pStreamCC->State.pCircBuf
+ && RTCircBufSize(pStreamCC->State.pCircBuf) == cbCircBuf)
+ RTCircBufReset(pStreamCC->State.pCircBuf);
+ else
+ {
+ if (pStreamCC->State.pCircBuf)
+ RTCircBufDestroy(pStreamCC->State.pCircBuf);
+
+ int rc = RTCircBufCreate(&pStreamCC->State.pCircBuf, cbCircBuf);
+ AssertRCReturnStmt(rc, pStreamCC->State.pCircBuf = NULL, rc);
+
+ pStreamCC->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStreamCC->State.pCircBuf);
+ }
+ Assert(pStreamCC->State.StatDmaBufSize == cbCircBuf);
+
+ /*
+ * Only (re-)create the stream (and driver chain) if we really have to.
+ * Otherwise avoid this and just reuse it, as this costs performance.
+ */
+ int rc = VINF_SUCCESS;
+ if ( fForce
+ || !PDMAudioStrmCfgMatchesProps(&Cfg, &pStreamCC->State.Cfg.Props)
+ || (pStreamCC->State.nsRetrySetup && RTTimeNanoTS() >= pStreamCC->State.nsRetrySetup))
+ {
+ LogRel2(("AC97: Setting up stream #%u: %s\n", pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp)) ));
+
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pMixSink, Cfg.enmDir, Cfg.enmPath);
+
+ rc = ichac97R3MixerAddDrvStreams(pDevIns, pThisCC, pMixSink, &Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ PDMAudioStrmCfgCopy(&pStreamCC->State.Cfg, &Cfg);
+ pStreamCC->State.nsRetrySetup = 0;
+ LogFlowFunc(("[SD%RU8] success (uHz=%u)\n", pStreamCC->u8SD, PDMAudioPropsHz(&Cfg.Props)));
+ }
+ else
+ {
+ LogFunc(("[SD%RU8] ichac97R3MixerAddDrvStreams failed: %Rrc (uHz=%u)\n",
+ pStreamCC->u8SD, rc, PDMAudioPropsHz(&Cfg.Props)));
+ pStreamCC->State.nsRetrySetup = RTTimeNanoTS() + 5*RT_NS_1SEC_64; /* retry in 5 seconds, unless config changes. */
+ }
+ }
+ else
+ {
+ LogFlowFunc(("[SD%RU8] Skipping set-up (unchanged: %s)\n",
+ pStreamCC->u8SD, PDMAudioStrmCfgToString(&Cfg, szTmp, sizeof(szTmp))));
+ rc = VINF_NO_CHANGE;
+ }
+ return rc;
+}
+
+
+/**
+ * Tears down an AC'97 stream (counter part to ichac97R3StreamSetUp).
+ *
+ * Empty stub at present, nothing to do here as we reuse streams and only really
+ * re-open them if parameters changed (seldom).
+ *
+ * @param pStream The AC'97 stream to close (shared).
+ */
+static void ichac97R3StreamTearDown(PAC97STREAM pStream)
+{
+ RT_NOREF(pStream);
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+}
+
+
+/**
+ * Tears down and sets up an AC'97 stream on the backend side with the current
+ * AC'97 mixer settings for this stream.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 device state.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pStream The AC'97 stream to re-open (shared).
+ * @param pStreamCC The AC'97 stream to re-open (ring-3).
+ * @param fForce Whether to force re-opening the stream or not.
+ * Otherwise re-opening only will happen if the PCM properties have changed.
+ *
+ * @remarks This is called holding:
+ * -# The AC'97 device lock.
+ *
+ * Will acquire the stream and mixer sink locks. See @bugref{10350}
+ */
+static int ichac97R3StreamReSetUp(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fForce)
+{
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReSetUpChanged, r);
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+ Assert(pStream->u8SD == pStreamCC->u8SD);
+ Assert(pStream - &pThis->aStreams[0] == pStream->u8SD);
+ Assert(pStreamCC - &pThisCC->aStreams[0] == pStream->u8SD);
+
+ ichac97R3StreamLock(pStreamCC);
+ PAUDMIXSINK const pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink)
+ AudioMixerSinkLock(pSink);
+
+ ichac97R3StreamTearDown(pStream);
+ int rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, fForce);
+ if (rc == VINF_NO_CHANGE)
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpSame, r);
+ else
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReSetUpChanged, r);
+
+ if (pSink)
+ AudioMixerSinkUnlock(pSink);
+ ichac97R3StreamUnlock(pStreamCC);
+
+ return rc;
+}
+
+
+/**
+ * Enables or disables an AC'97 audio stream.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to enable or disable (shared state).
+ * @param pStreamCC The ring-3 stream state (matching to @a pStream).
+ * @param fEnable Whether to enable or disable the stream.
+ *
+ */
+static int ichac97R3StreamEnable(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC,
+ PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, bool fEnable)
+{
+ ichac97R3StreamLock(pStreamCC);
+ PAUDMIXSINK const pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink)
+ AudioMixerSinkLock(pSink);
+
+ int rc = VINF_SUCCESS;
+ /*
+ * Enable.
+ */
+ if (fEnable)
+ {
+ /* Reset the input pre-buffering state and DMA period counter. */
+ pStreamCC->State.fInputPreBuffered = false;
+ pStream->uDmaPeriod = 0;
+
+ /* Set up (update) the AC'97 stream as needed. */
+ rc = ichac97R3StreamSetUp(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fForce */);
+ if (RT_SUCCESS(rc))
+ {
+ /* Open debug files. */
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileStream))
+ AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamCC->State.Cfg.Props);
+ if (!AudioHlpFileIsOpen(pStreamCC->Dbg.Runtime.pFileDMA))
+ AudioHlpFileOpen(pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStreamCC->State.Cfg.Props);
+ }
+
+ /* Do the actual enabling (won't fail as long as pSink is valid). */
+ if (pSink)
+ rc = AudioMixerSinkStart(pSink);
+ }
+ }
+ /*
+ * Disable
+ */
+ else
+ {
+ rc = AudioMixerSinkDrainAndStop(pSink, pStreamCC->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamCC->State.pCircBuf) : 0);
+ ichac97R3StreamTearDown(pStream);
+ }
+
+ /* Make sure to leave the lock before (eventually) starting the timer. */
+ if (pSink)
+ AudioMixerSinkUnlock(pSink);
+ ichac97R3StreamUnlock(pStreamCC);
+ LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc));
+ return rc;
+}
+
+
+/**
+ * Returns whether an AC'97 stream is enabled or not.
+ *
+ * Only used by ichac97R3SaveExec().
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pStream Stream to return status for.
+ */
+static bool ichac97R3StreamIsEnabled(PAC97STATER3 pThisCC, PAC97STREAM pStream)
+{
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ bool fIsEnabled = pSink && (AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING);
+
+ LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled));
+ return fIsEnabled;
+}
+
+
+/**
+ * Terminates an AC'97 audio stream (VM destroy).
+ *
+ * This is called by ichac97R3StreamsDestroy during VM poweroff & destruction.
+ *
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to destroy (shared).
+ * @param pStreamCC The AC'97 stream to destroy (ring-3).
+ * @sa ichac97R3StreamConstruct
+ */
+static void ichac97R3StreamDestroy(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC)
+{
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ ichac97R3StreamTearDown(pStream);
+
+ int rc2 = RTCritSectDelete(&pStreamCC->State.CritSect);
+ AssertRC(rc2);
+
+ if (pStreamCC->State.fRegisteredAsyncUpdateJob)
+ {
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThisCC, pStream->u8SD);
+ if (pSink)
+ AudioMixerSinkRemoveUpdateJob(pSink, ichac97R3StreamUpdateAsyncIoJob, pStreamCC);
+ pStreamCC->State.fRegisteredAsyncUpdateJob = false;
+ }
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileStream);
+ pStreamCC->Dbg.Runtime.pFileStream = NULL;
+
+ AudioHlpFileDestroy(pStreamCC->Dbg.Runtime.pFileDMA);
+ pStreamCC->Dbg.Runtime.pFileDMA = NULL;
+ }
+
+ if (pStreamCC->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStreamCC->State.pCircBuf);
+ pStreamCC->State.pCircBuf = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Initializes an AC'97 audio stream (VM construct).
+ *
+ * This is only called by ichac97R3Construct.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param pStream The AC'97 stream to create (shared).
+ * @param pStreamCC The AC'97 stream to create (ring-3).
+ * @param u8SD Stream descriptor number to assign.
+ * @sa ichac97R3StreamDestroy
+ */
+static int ichac97R3StreamConstruct(PAC97STATER3 pThisCC, PAC97STREAM pStream, PAC97STREAMR3 pStreamCC, uint8_t u8SD)
+{
+ LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream));
+
+ AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER);
+ pStream->u8SD = u8SD;
+ pStreamCC->u8SD = u8SD;
+
+ int rc = RTCritSectInit(&pStreamCC->State.CritSect);
+ AssertRCReturn(rc, rc);
+
+ pStreamCC->Dbg.Runtime.fEnabled = pThisCC->Dbg.fEnabled;
+
+ if (RT_LIKELY(!pStreamCC->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ int rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileStream, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN
+ ? "ac97StreamWriteSD%RU8" : "ac97StreamReadSD%RU8", pStream->u8SD);
+ AssertRC(rc2);
+
+ rc2 = AudioHlpFileCreateF(&pStreamCC->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThisCC->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ ichac97R3GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN
+ ? "ac97DMAWriteSD%RU8" : "ac97DMAReadSD%RU8", pStream->u8SD);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileStream);
+ AudioHlpFileDelete(pStreamCC->Dbg.Runtime.pFileDMA);
+ }
+
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+
+/*********************************************************************************************************************************
+* NABM I/O Port Handlers (Global + Stream) *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNabmRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ);
+
+ /* Get the index of the NABMBAR port. */
+ if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS
+ && offPort != AC97_GLOB_CNT)
+ {
+ PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)];
+
+ switch (cb)
+ {
+ case 1:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_CIV:
+ /* Current Index Value Register */
+ *pu32 = pStream->Regs.civ;
+ Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_LVI:
+ /* Last Valid Index Register */
+ *pu32 = pStream->Regs.lvi;
+ Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_PIV:
+ /* Prefetched Index Value Register */
+ *pu32 = pStream->Regs.piv;
+ Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_CR:
+ /* Control Register */
+ *pu32 = pStream->Regs.cr;
+ Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_SR:
+ /* Status Register (lower part) */
+ *pu32 = RT_LO_U8(pStream->Regs.sr);
+ Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 2:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_SR:
+ /* Status Register */
+ *pu32 = pStream->Regs.sr;
+ Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_PICB:
+ /* Position in Current Buffer
+ * ---
+ * We can do DMA work here if we want to give the guest a better impression of
+ * the DMA engine of a real device. For ring-0 we'd have to add some buffering
+ * to AC97STREAM (4K or so), only going to ring-3 if full. Ring-3 would commit
+ * that buffer and write directly to the internal DMA pCircBuf.
+ *
+ * Checking a Linux guest (knoppix 8.6.2), I see some PIC reads each DMA cycle,
+ * however most of these happen very very early, 1-10% into the buffer. So, I'm
+ * not sure if it's worth it, as it'll be a big complication... */
+#if 1
+ *pu32 = pStream->Regs.picb;
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ uint64_t offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs;
+ Log3Func(("PICB[%d] -> %#x (%RU64 of %RU64 ticks / %RU64%% into DMA period #%RU32)\n",
+ AC97_PORT2IDX(offPort), *pu32, offPeriod, pStream->cDmaPeriodTicks,
+ pStream->cDmaPeriodTicks ? offPeriod * 100 / pStream->cDmaPeriodTicks : 0,
+ pStream->uDmaPeriod));
+ }
+# endif
+#else /* For trying out sub-buffer PICB. Will cause distortions, but can be helpful to see if it help eliminate other issues. */
+ if ( (pStream->Regs.cr & AC97_CR_RPBM)
+ && !(pStream->Regs.sr & AC97_SR_DCH)
+ && pStream->uArmedTs > 0
+ && pStream->cDmaPeriodTicks > 0)
+ {
+ uint64_t const offPeriod = PDMDevHlpTimerGet(pDevIns, pStream->hTimer) - pStream->uArmedTs;
+ uint32_t cSamples;
+ if (offPeriod < pStream->cDmaPeriodTicks)
+ cSamples = pStream->Regs.picb * offPeriod / pStream->cDmaPeriodTicks;
+ else
+ cSamples = pStream->Regs.picb;
+ if (cSamples + 8 < pStream->Regs.picb)
+ { /* likely */ }
+ else if (pStream->Regs.picb > 8)
+ cSamples = pStream->Regs.picb - 8;
+ else
+ cSamples = 0;
+ *pu32 = pStream->Regs.picb - cSamples;
+ Log3Func(("PICB[%d] -> %#x (PICB=%#x cSamples=%#x offPeriod=%RU64 of %RU64 / %RU64%%)\n",
+ AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, cSamples, offPeriod,
+ pStream->cDmaPeriodTicks, offPeriod * 100 / pStream->cDmaPeriodTicks));
+ }
+ else
+ {
+ *pu32 = pStream->Regs.picb;
+ Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ }
+#endif
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 4:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_BDBAR:
+ /* Buffer Descriptor Base Address Register */
+ *pu32 = pStream->Regs.bdbar;
+ Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(offPort), *pu32));
+ break;
+ case AC97_NABM_OFF_CIV:
+ /* 32-bit access: Current Index Value Register +
+ * Last Valid Index Register +
+ * Status Register */
+ *pu32 = pStream->Regs.civ | ((uint32_t)pStream->Regs.lvi << 8) | ((uint32_t)pStream->Regs.sr << 16);
+ Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n",
+ AC97_PORT2IDX(offPort), pStream->Regs.civ, pStream->Regs.lvi, pStream->Regs.sr));
+ break;
+ case AC97_NABM_OFF_PICB:
+ /* 32-bit access: Position in Current Buffer Register +
+ * Prefetched Index Value Register +
+ * Control Register */
+ *pu32 = pStream->Regs.picb | ((uint32_t)pStream->Regs.piv << 16) | ((uint32_t)pStream->Regs.cr << 24);
+ Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n",
+ AC97_PORT2IDX(offPort), *pu32, pStream->Regs.picb, pStream->Regs.piv, pStream->Regs.cr));
+ break;
+
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ default:
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ AssertFailed();
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+ else
+ {
+ switch (cb)
+ {
+ case 1:
+ switch (offPort)
+ {
+ case AC97_CAS:
+ /* Codec Access Semaphore Register */
+ Log3Func(("CAS %d\n", pThis->cas));
+ *pu32 = pThis->cas;
+ pThis->cas = 1;
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ case 2:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+
+ case 4:
+ switch (offPort)
+ {
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ *pu32 = pThis->glob_cnt;
+ Log3Func(("glob_cnt -> %#x\n", *pu32));
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ *pu32 = pThis->glob_sta | AC97_GS_S0CR;
+ Log3Func(("glob_sta -> %#x\n", *pu32));
+ break;
+ default:
+ *pu32 = UINT32_MAX;
+ LogRel2(("AC97: Warning: Unimplemented NAMB read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmReads);
+ break;
+ }
+ break;
+
+ default:
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ AssertFailed();
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNabmWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+#ifdef IN_RING3
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+#endif
+ RT_NOREF(pvUser);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ if ( AC97_PORT2IDX_UNMASKED(offPort) < AC97_MAX_STREAMS
+ && offPort != AC97_GLOB_CNT)
+ {
+#ifdef IN_RING3
+ PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[AC97_PORT2IDX(offPort)];
+#endif
+ PAC97STREAM pStream = &pThis->aStreams[AC97_PORT2IDX(offPort)];
+
+ switch (cb)
+ {
+ case 1:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ /*
+ * Last Valid Index.
+ */
+ case AC97_NABM_OFF_LVI:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+
+ if ( !(pStream->Regs.sr & AC97_SR_DCH)
+ || !(pStream->Regs.cr & AC97_CR_RPBM))
+ {
+ pStream->Regs.lvi = u32 % AC97_MAX_BDLE;
+ STAM_REL_COUNTER_INC(&pStream->StatWriteLvi);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32));
+ }
+ else
+ {
+#ifdef IN_RING3
+ /* Recover from underflow situation where CIV caught up with LVI
+ and the DMA processing stopped. We clear the status condition,
+ update LVI and then try to load the next BDLE. Unfortunately,
+ we cannot do this from ring-0 as much of the BDLE state is
+ ring-3 only. */
+ pStream->Regs.sr &= ~(AC97_SR_DCH | AC97_SR_CELV);
+ pStream->Regs.lvi = u32 % AC97_MAX_BDLE;
+ if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC))
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS);
+
+ /* We now have to re-arm the DMA timer according to the new BDLE length.
+ This means leaving the device lock to avoid virtual sync lock order issues. */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks;
+
+ /** @todo Stop the DMA timer when we get into the AC97_SR_CELV situation to
+ * avoid potential race here. */
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteLviRecover);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ LogFunc(("[SD%RU8] LVI <- %#x; CIV=%#x PIV=%#x SR=%#x cTicksToDeadline=%#RX64 [recovering]\n",
+ pStream->u8SD, u32, pStream->Regs.civ, pStream->Regs.piv, pStream->Regs.sr, cTicksToDeadline));
+
+ int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc2);
+#else
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ break;
+
+ /*
+ * Control Registers.
+ */
+ case AC97_NABM_OFF_CR:
+ {
+#ifdef IN_RING3
+ DEVAC97_LOCK(pDevIns, pThis);
+ STAM_REL_COUNTER_INC(&pStreamCC->State.StatWriteCr);
+
+ uint32_t const fCrChanged = pStream->Regs.cr ^ u32;
+ Log3Func(("[SD%RU8] CR <- %#x (was %#x; changed %#x)\n", pStream->u8SD, u32, pStream->Regs.cr, fCrChanged));
+
+ /*
+ * Busmaster reset.
+ */
+ if (u32 & AC97_CR_RR)
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatReset, r);
+ LogFunc(("[SD%RU8] Reset\n", pStream->u8SD));
+
+ /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0).
+ 3.2.7 in 302349-003 says RPBM be must be clear when resetting
+ and that behavior is undefined if it's set. */
+ ASSERT_GUEST_STMT((pStream->Regs.cr & AC97_CR_RPBM) == 0,
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream,
+ pStreamCC, false /* fEnable */));
+
+ ichac97R3StreamReset(pThis, pStream, pStreamCC);
+
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatReset, r);
+ break;
+ }
+
+ /*
+ * Write the new value to the register and if RPBM didn't change we're done.
+ */
+ pStream->Regs.cr = u32 & AC97_CR_VALID_MASK;
+
+ if (!(fCrChanged & AC97_CR_RPBM))
+ DEVAC97_UNLOCK(pDevIns, pThis); /* Probably not so likely, but avoid one extra intentation level. */
+ /*
+ * Pause busmaster.
+ */
+ else if (!(pStream->Regs.cr & AC97_CR_RPBM))
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStop, p);
+ LogFunc(("[SD%RU8] Pause busmaster (disable stream) SR=%#x -> %#x\n",
+ pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr | AC97_SR_DCH));
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, false /* fEnable */);
+ pStream->Regs.sr |= AC97_SR_DCH;
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStop, p);
+ }
+ /*
+ * Run busmaster.
+ */
+ else
+ {
+ STAM_REL_PROFILE_START_NS(&pStreamCC->State.StatStart, r);
+ LogFunc(("[SD%RU8] Run busmaster (enable stream) SR=%#x -> %#x\n",
+ pStream->u8SD, pStream->Regs.sr, pStream->Regs.sr & ~AC97_SR_DCH));
+ pStream->Regs.sr &= ~AC97_SR_DCH;
+
+ if (ichac97R3StreamFetchNextBdle(pDevIns, pStream, pStreamCC))
+ ichac97StreamUpdateSR(pDevIns, pThis, pStream, pStream->Regs.sr | AC97_SR_BCIS);
+# ifdef LOG_ENABLED
+ if (LogIsFlowEnabled())
+ ichac97R3DbgPrintBdl(pDevIns, pThis, pStream, PDMDevHlpDBGFInfoLogHlp(pDevIns), "ichac97IoPortNabmWrite: ");
+# endif
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, true /* fEnable */);
+
+ /*
+ * Arm the DMA timer. Must drop the AC'97 device lock first as it would
+ * create a lock order violation with the virtual sync time lock otherwise.
+ */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+ uint64_t const cTicksToDeadline = pStream->cDmaPeriodTicks;
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ /** @todo for output streams we could probably service this a little bit
+ * earlier if we push it, just to reduce the lag... For HDA we do a
+ * DMA run immediately after the stream is enabled. */
+ int rc2 = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimer, cTicksToDeadline, &pStream->uArmedTs);
+ AssertRC(rc2);
+
+ STAM_REL_PROFILE_STOP_NS(&pStreamCC->State.StatStart, r);
+ }
+#else /* !IN_RING3 */
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ }
+
+ /*
+ * Status Registers.
+ */
+ case AC97_NABM_OFF_SR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ ichac97StreamWriteSR(pDevIns, pThis, pStream, u32);
+ STAM_REL_COUNTER_INC(&pStream->StatWriteSr1);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+
+ default:
+ /* Linux tries to write CIV. */
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x%s <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n",
+ offPort, (offPort & AC97_NABM_OFF_MASK) == AC97_NABM_OFF_CIV ? " (CIV)" : "" , u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ case 2:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_SR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ ichac97StreamWriteSR(pDevIns, pThis, pStream, u32);
+ STAM_REL_COUNTER_INC(&pStream->StatWriteSr2);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ case 4:
+ switch (offPort & AC97_NABM_OFF_MASK)
+ {
+ case AC97_NABM_OFF_BDBAR:
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ /* Buffer Descriptor list Base Address Register */
+ pStream->Regs.bdbar = u32 & ~(uint32_t)3;
+ Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(offPort), u32, pStream->Regs.bdbar));
+ STAM_REL_COUNTER_INC(&pStream->StatWriteBdBar);
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb));
+ break;
+ }
+ }
+ else
+ {
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+
+ case 2:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+
+ case 4:
+ switch (offPort)
+ {
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ if (u32 & AC97_GC_WR)
+ ichac97WarmReset(pThis);
+ if (u32 & AC97_GC_CR)
+ ichac97ColdReset(pThis);
+ if (!(u32 & (AC97_GC_WR | AC97_GC_CR)))
+ pThis->glob_cnt = u32 & AC97_GC_VALID_MASK;
+ Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32, pThis->glob_cnt));
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+ pThis->glob_sta &= ~(u32 & AC97_GS_WCLEAR_MASK);
+ pThis->glob_sta |= (u32 & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK;
+ Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32, pThis->glob_sta));
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMB write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNabmWrites);
+ break;
+ }
+ break;
+
+ default:
+ AssertMsgFailed(("offPort=%#x <- %#x LB %u\n", offPort, u32, cb));
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Mixer & NAM I/O handlers *
+*********************************************************************************************************************************/
+
+/**
+ * Sets a AC'97 mixer control to a specific value.
+ *
+ * @param pThis The shared AC'97 state.
+ * @param uMixerIdx Mixer control to set value for.
+ * @param uVal Value to set.
+ */
+static void ichac97MixerSet(PAC97STATE pThis, uint8_t uMixerIdx, uint16_t uVal)
+{
+ AssertMsgReturnVoid(uMixerIdx + 2U <= sizeof(pThis->mixer_data),
+ ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)));
+
+ LogRel2(("AC97: Setting mixer index #%RU8 to %RU16 (%RU8 %RU8)\n", uMixerIdx, uVal, RT_HI_U8(uVal), RT_LO_U8(uVal)));
+
+ pThis->mixer_data[uMixerIdx + 0] = RT_LO_U8(uVal);
+ pThis->mixer_data[uMixerIdx + 1] = RT_HI_U8(uVal);
+}
+
+
+/**
+ * Gets a value from a specific AC'97 mixer control.
+ *
+ * @returns Retrieved mixer control value.
+ * @param pThis The shared AC'97 state.
+ * @param uMixerIdx Mixer control to get value for.
+ */
+static uint16_t ichac97MixerGet(PAC97STATE pThis, uint32_t uMixerIdx)
+{
+ AssertMsgReturn(uMixerIdx + 2U <= sizeof(pThis->mixer_data),
+ ("Index %RU8 out of bounds (%zu)\n", uMixerIdx, sizeof(pThis->mixer_data)),
+ UINT16_MAX);
+ return RT_MAKE_U16(pThis->mixer_data[uMixerIdx + 0], pThis->mixer_data[uMixerIdx + 1]);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Sets the volume of a specific AC'97 mixer control.
+ *
+ * This currently only supports attenuation -- gain support is currently not implemented.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param index AC'97 mixer index to set volume for.
+ * @param enmMixerCtl Corresponding audio mixer sink.
+ * @param uVal Volume value to set.
+ */
+static int ichac97R3MixerSetVolume(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal)
+{
+ /*
+ * From AC'97 SoundMax Codec AD1981A/AD1981B:
+ * "Because AC '97 defines 6-bit volume registers, to maintain compatibility whenever the
+ * D5 or D13 bits are set to 1, their respective lower five volume bits are automatically
+ * set to 1 by the Codec logic. On readback, all lower 5 bits will read ones whenever
+ * these bits are set to 1."
+ *
+ * Linux ALSA depends on this behavior to detect that only 5 bits are used for volume
+ * control and the optional 6th bit is not used. Note that this logic only applies to the
+ * master volume controls.
+ */
+ if ( index == AC97_Master_Volume_Mute
+ || index == AC97_Headphone_Volume_Mute
+ || index == AC97_Master_Volume_Mono_Mute)
+ {
+ if (uVal & RT_BIT(5)) /* D5 bit set? */
+ uVal |= RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1) | RT_BIT(0);
+ if (uVal & RT_BIT(13)) /* D13 bit set? */
+ uVal |= RT_BIT(12) | RT_BIT(11) | RT_BIT(10) | RT_BIT(9) | RT_BIT(8);
+ }
+
+ const bool fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1;
+ uint8_t uCtlAttLeft = (uVal >> 8) & AC97_BARS_VOL_MASK;
+ uint8_t uCtlAttRight = uVal & AC97_BARS_VOL_MASK;
+
+ /* For the master and headphone volume, 0 corresponds to 0dB attenuation. For the other
+ * volume controls, 0 means 12dB gain and 8 means unity gain.
+ */
+ if (index != AC97_Master_Volume_Mute && index != AC97_Headphone_Volume_Mute)
+ {
+# ifndef VBOX_WITH_AC97_GAIN_SUPPORT
+ /* NB: Currently there is no gain support, only attenuation. */
+ uCtlAttLeft = uCtlAttLeft < 8 ? 0 : uCtlAttLeft - 8;
+ uCtlAttRight = uCtlAttRight < 8 ? 0 : uCtlAttRight - 8;
+# endif
+ }
+ Assert(uCtlAttLeft <= 255 / AC97_DB_FACTOR);
+ Assert(uCtlAttRight <= 255 / AC97_DB_FACTOR);
+
+ LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl));
+ LogFunc(("uCtlAttLeft=%RU8, uCtlAttRight=%RU8 ", uCtlAttLeft, uCtlAttRight));
+
+ /*
+ * For AC'97 volume controls, each additional step means -1.5dB attenuation with
+ * zero being maximum. In contrast, we're internally using 255 (PDMAUDIO_VOLUME_MAX)
+ * steps, each -0.375dB, where 0 corresponds to -96dB and 255 corresponds to 0dB.
+ */
+ uint8_t lVol = PDMAUDIO_VOLUME_MAX - uCtlAttLeft * AC97_DB_FACTOR;
+ uint8_t rVol = PDMAUDIO_VOLUME_MAX - uCtlAttRight * AC97_DB_FACTOR;
+
+ Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol));
+
+ int rc = VINF_SUCCESS;
+
+ if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol;
+ PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol);
+
+ PAUDMIXSINK pSink = NULL;
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ rc = AudioMixerSetMasterVolume(pThisCC->pMixer, &Vol);
+ break;
+
+ case PDMAUDIOMIXERCTL_FRONT:
+ pSink = pThisCC->pSinkOut;
+ break;
+
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ /* These are recognized but do nothing. */
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (pSink)
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ }
+
+ ichac97MixerSet(pThis, index, uVal);
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Sets the gain of a specific AC'97 recording control.
+ *
+ * @note Gain support is currently not implemented in PDM audio.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ * @param index AC'97 mixer index to set volume for.
+ * @param enmMixerCtl Corresponding audio mixer sink.
+ * @param uVal Volume value to set.
+ */
+static int ichac97R3MixerSetGain(PAC97STATE pThis, PAC97STATER3 pThisCC, int index, PDMAUDIOMIXERCTL enmMixerCtl, uint32_t uVal)
+{
+ /*
+ * For AC'97 recording controls, each additional step means +1.5dB gain with
+ * zero being 0dB gain and 15 being +22.5dB gain.
+ */
+ bool const fCtlMuted = (uVal >> AC97_BARS_VOL_MUTE_SHIFT) & 1;
+ uint8_t uCtlGainLeft = (uVal >> 8) & AC97_BARS_GAIN_MASK;
+ uint8_t uCtlGainRight = uVal & AC97_BARS_GAIN_MASK;
+
+ Assert(uCtlGainLeft <= 255 / AC97_DB_FACTOR);
+ Assert(uCtlGainRight <= 255 / AC97_DB_FACTOR);
+
+ LogFunc(("index=0x%x, uVal=%RU32, enmMixerCtl=%RU32\n", index, uVal, enmMixerCtl));
+ LogFunc(("uCtlGainLeft=%RU8, uCtlGainRight=%RU8 ", uCtlGainLeft, uCtlGainRight));
+
+ uint8_t lVol = PDMAUDIO_VOLUME_MAX + uCtlGainLeft * AC97_DB_FACTOR;
+ uint8_t rVol = PDMAUDIO_VOLUME_MAX + uCtlGainRight * AC97_DB_FACTOR;
+
+ /* We do not currently support gain. Since AC'97 does not support attenuation
+ * for the recording input, the best we can do is set the maximum volume.
+ */
+# ifndef VBOX_WITH_AC97_GAIN_SUPPORT
+ /* NB: Currently there is no gain support, only attenuation. Since AC'97 does not
+ * support attenuation for the recording inputs, the best we can do is set the
+ * maximum volume.
+ */
+ lVol = rVol = PDMAUDIO_VOLUME_MAX;
+# endif
+
+ Log(("-> fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n", fCtlMuted, lVol, rVol));
+
+ int rc = VINF_SUCCESS;
+
+ if (pThisCC->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol;
+ PDMAudioVolumeInitFromStereo(&Vol, fCtlMuted, lVol, rVol);
+
+ PAUDMIXSINK pSink = NULL;
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pSink = pThisCC->pSinkMicIn;
+ break;
+
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pSink = pThisCC->pSinkLineIn;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (pSink)
+ {
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ /* There is only one AC'97 recording gain control. If line in
+ * is changed, also update the microphone. If the optional dedicated
+ * microphone is changed, only change that.
+ * NB: The codecs we support do not have the dedicated microphone control.
+ */
+ if (pSink == pThisCC->pSinkLineIn && pThisCC->pSinkMicIn)
+ rc = AudioMixerSinkSetVolume(pSink, &Vol);
+ }
+ }
+
+ ichac97MixerSet(pThis, index, uVal);
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * Converts an AC'97 recording source index to a PDM audio recording source.
+ *
+ * @returns PDM audio recording source.
+ * @param uIdx AC'97 index to convert.
+ */
+static PDMAUDIOPATH ichac97R3IdxToRecSource(uint8_t uIdx)
+{
+ switch (uIdx)
+ {
+ case AC97_REC_MIC: return PDMAUDIOPATH_IN_MIC;
+ case AC97_REC_CD: return PDMAUDIOPATH_IN_CD;
+ case AC97_REC_VIDEO: return PDMAUDIOPATH_IN_VIDEO;
+ case AC97_REC_AUX: return PDMAUDIOPATH_IN_AUX;
+ case AC97_REC_LINE_IN: return PDMAUDIOPATH_IN_LINE;
+ case AC97_REC_PHONE: return PDMAUDIOPATH_IN_PHONE;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx));
+ return PDMAUDIOPATH_IN_MIC;
+}
+
+
+/**
+ * Converts a PDM audio recording source to an AC'97 recording source index.
+ *
+ * @returns AC'97 recording source index.
+ * @param enmRecSrc PDM audio recording source to convert.
+ */
+static uint8_t ichac97R3RecSourceToIdx(PDMAUDIOPATH enmRecSrc)
+{
+ switch (enmRecSrc)
+ {
+ case PDMAUDIOPATH_IN_MIC: return AC97_REC_MIC;
+ case PDMAUDIOPATH_IN_CD: return AC97_REC_CD;
+ case PDMAUDIOPATH_IN_VIDEO: return AC97_REC_VIDEO;
+ case PDMAUDIOPATH_IN_AUX: return AC97_REC_AUX;
+ case PDMAUDIOPATH_IN_LINE: return AC97_REC_LINE_IN;
+ case PDMAUDIOPATH_IN_PHONE: return AC97_REC_PHONE;
+ default:
+ AssertMsgFailedBreak(("%d\n", enmRecSrc));
+ }
+
+ LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc));
+ return AC97_REC_MIC;
+}
+
+
+/**
+ * Performs an AC'97 mixer record select to switch to a different recording
+ * source.
+ *
+ * @param pThis The shared AC'97 state.
+ * @param val AC'97 recording source index to set.
+ */
+static void ichac97R3MixerRecordSelect(PAC97STATE pThis, uint32_t val)
+{
+ uint8_t rs = val & AC97_REC_MASK;
+ uint8_t ls = (val >> 8) & AC97_REC_MASK;
+
+ PDMAUDIOPATH const ars = ichac97R3IdxToRecSource(rs);
+ PDMAUDIOPATH const als = ichac97R3IdxToRecSource(ls);
+
+ rs = ichac97R3RecSourceToIdx(ars);
+ ls = ichac97R3RecSourceToIdx(als);
+
+ LogRel(("AC97: Record select to left=%s, right=%s\n", PDMAudioPathGetName(ars), PDMAudioPathGetName(als)));
+
+ ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8));
+}
+
+/**
+ * Resets the AC'97 mixer.
+ *
+ * @returns VBox status code.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ */
+static int ichac97R3MixerReset(PAC97STATE pThis, PAC97STATER3 pThisCC)
+{
+ LogFlowFuncEnter();
+
+ RT_ZERO(pThis->mixer_data);
+
+ /* Note: Make sure to reset all registers first before bailing out on error. */
+
+ ichac97MixerSet(pThis, AC97_Reset , 0x0000); /* 6940 */
+ ichac97MixerSet(pThis, AC97_Master_Volume_Mono_Mute , 0x8000);
+ ichac97MixerSet(pThis, AC97_PC_BEEP_Volume_Mute , 0x0000);
+
+ ichac97MixerSet(pThis, AC97_Phone_Volume_Mute , 0x8008);
+ ichac97MixerSet(pThis, AC97_Mic_Volume_Mute , 0x8008);
+ ichac97MixerSet(pThis, AC97_CD_Volume_Mute , 0x8808);
+ ichac97MixerSet(pThis, AC97_Aux_Volume_Mute , 0x8808);
+ ichac97MixerSet(pThis, AC97_Record_Gain_Mic_Mute , 0x8000);
+ ichac97MixerSet(pThis, AC97_General_Purpose , 0x0000);
+ ichac97MixerSet(pThis, AC97_3D_Control , 0x0000);
+ ichac97MixerSet(pThis, AC97_Powerdown_Ctrl_Stat , 0x000f);
+
+ /* Configure Extended Audio ID (EAID) + Control & Status (EACS) registers. */
+ const uint16_t fEAID = AC97_EAID_REV1 | AC97_EACS_VRA | AC97_EACS_VRM; /* Our hardware is AC'97 rev2.3 compliant. */
+ const uint16_t fEACS = AC97_EACS_VRA | AC97_EACS_VRM; /* Variable Rate PCM Audio (VRA) + Mic-In (VRM) capable. */
+
+ LogRel(("AC97: Mixer reset (EAID=0x%x, EACS=0x%x)\n", fEAID, fEACS));
+
+ ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID);
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS);
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80 /* 48000 Hz by default */);
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80 /* 48000 Hz by default */);
+
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ /* Analog Devices 1980 (AD1980) */
+ ichac97MixerSet(pThis, AC97_Reset , 0x0010); /* Headphones. */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5370);
+ ichac97MixerSet(pThis, AC97_Headphone_Volume_Mute , 0x8000);
+ }
+ else if (pThis->enmCodecModel == AC97CODEC_AD1981B)
+ {
+ /* Analog Devices 1981B (AD1981B) */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x4144);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x5374);
+ }
+ else
+ {
+ /* Sigmatel 9700 (STAC9700) */
+ ichac97MixerSet(pThis, AC97_Vendor_ID1 , 0x8384);
+ ichac97MixerSet(pThis, AC97_Vendor_ID2 , 0x7600); /* 7608 */
+ }
+ ichac97R3MixerRecordSelect(pThis, 0);
+
+ /* The default value is 8000h, which corresponds to 0 dB attenuation with mute on. */
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, 0x8000);
+
+ /* The default value for stereo registers is 8808h, which corresponds to 0 dB gain with mute on.*/
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808);
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808);
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008);
+
+ /* The default for record controls is 0 dB gain with mute on. */
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000);
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNamRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+ Assert(offPort < 256);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_READ);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads);
+ pThis->cas = 0;
+ *pu32 = UINT32_MAX;
+ break;
+
+ case 2:
+ pThis->cas = 0;
+ *pu32 = ichac97MixerGet(pThis, offPort);
+ break;
+
+ case 4:
+ LogRel2(("AC97: Warning: Unimplemented NAM read offPort=%#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamReads);
+ pThis->cas = 0;
+ *pu32 = UINT32_MAX;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_IOM_IOPORT_UNUSED;
+ break;
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+ichac97IoPortNamWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+#ifdef IN_RING3
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+#endif
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pDevIns, pThis, VINF_IOM_R3_IOPORT_WRITE);
+
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ switch (cb)
+ {
+ case 1:
+ LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 1 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ pThis->cas = 0;
+ break;
+
+ case 2:
+ {
+ pThis->cas = 0;
+ switch (offPort)
+ {
+ case AC97_Reset:
+#ifdef IN_RING3
+ ichac97R3Reset(pDevIns);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Powerdown_Ctrl_Stat:
+ u32 &= ~0xf;
+ u32 |= ichac97MixerGet(pThis, offPort) & 0xf;
+ ichac97MixerSet(pThis, offPort, u32);
+ break;
+ case AC97_Master_Volume_Mute:
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_LOSEL)
+ break; /* Register controls surround (rear), do nothing. */
+ }
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Headphone_Volume_Mute:
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ {
+ /* Register controls PCM (front) outputs. */
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ }
+ break;
+ case AC97_PCM_Out_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_FRONT, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Line_In_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_LINE_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Select:
+#ifdef IN_RING3
+ ichac97R3MixerRecordSelect(pThis, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Gain_Mute:
+#ifdef IN_RING3
+ /* Newer Ubuntu guests rely on that when controlling gain and muting
+ * the recording (capturing) levels. */
+ ichac97R3MixerSetGain(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_LINE_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Gain_Mic_Mute:
+#ifdef IN_RING3
+ /* Ditto; see note above. */
+ ichac97R3MixerSetGain(pThis, pThisCC, offPort, PDMAUDIOMIXERCTL_MIC_IN, u32);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Vendor_ID1:
+ case AC97_Vendor_ID2:
+ LogFunc(("Attempt to write vendor ID to %#x\n", u32));
+ break;
+ case AC97_Extended_Audio_ID:
+ LogFunc(("Attempt to write extended audio ID to %#x\n", u32));
+ break;
+ case AC97_Extended_Audio_Ctrl_Stat:
+#ifdef IN_RING3
+ /*
+ * Handle VRA bits.
+ */
+ if (!(u32 & AC97_EACS_VRA)) /* Check if VRA bit is not set. */
+ {
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */);
+
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Variable rate audio (VRA) is not supported\n"));
+
+ /*
+ * Handle VRM bits.
+ */
+ if (!(u32 & AC97_EACS_VRM)) /* Check if VRM bit is not set. */
+ {
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate, 0xbb80); /* Set default (48000 Hz). */
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Variable rate microphone audio (VRM) is not supported\n"));
+
+ LogRel2(("AC97: Setting extended audio control to %#x\n", u32));
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, u32);
+#else /* !IN_RING3 */
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_PCM_Front_DAC_Rate: /* Output slots 3, 4, 6. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+ LogRel2(("AC97: Setting front DAC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting front DAC rate (0x%x) when VRA is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_MIC_ADC_Rate: /* Input slot 6. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRM)
+ {
+ LogRel2(("AC97: Setting microphone ADC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting microphone ADC rate (0x%x) when VRM is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_PCM_LR_ADC_Rate: /* Input slots 3, 4. */
+#ifdef IN_RING3
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+ LogRel2(("AC97: Setting line-in ADC rate to 0x%x\n", u32));
+ ichac97MixerSet(pThis, offPort, u32);
+ /** @todo r=bird: Why reopen it now? Can't we put that off till it's
+ * actually used? */
+ ichac97R3StreamReSetUp(pDevIns, pThis, pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX],
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX], true /* fForce */);
+ }
+ else
+ LogRel2(("AC97: Setting line-in ADC rate (0x%x) when VRA is not set is forbidden, ignoring\n", u32));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ default:
+ /* Most of these are to register we don't care about like AC97_CD_Volume_Mute
+ and AC97_Master_Volume_Mono_Mute or things we don't need to handle specially.
+ Thus this is not a 'warning' but an 'info log message. */
+ LogRel2(("AC97: Info: Unimplemented NAM write offPort=%#x <- %#x LB 2 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ ichac97MixerSet(pThis, offPort, u32);
+ break;
+ }
+ break;
+ }
+
+ case 4:
+ LogRel2(("AC97: Warning: Unimplemented NAM write offPort=%#x <- %#x LB 4 (line " RT_XSTR(__LINE__) ")\n", offPort, u32));
+ STAM_REL_COUNTER_INC(&pThis->StatUnimplementedNamWrites);
+ pThis->cas = 0;
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled NAM write offPort=%#x, cb=%u u32=%#x\n", offPort, cb, u32));
+ break;
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ return rc;
+}
+
+#ifdef IN_RING3
+
+
+/*********************************************************************************************************************************
+* State Saving & Loading *
+*********************************************************************************************************************************/
+
+/**
+ * Saves (serializes) an AC'97 stream using SSM.
+ *
+ * @param pDevIns Device instance.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to save.
+ */
+static void ichac97R3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bdbar);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.civ);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.lvi);
+ pHlp->pfnSSMPutU16(pSSM, pStream->Regs.sr);
+ pHlp->pfnSSMPutU16(pSSM, pStream->Regs.picb);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.piv);
+ pHlp->pfnSSMPutU8( pSSM, pStream->Regs.cr);
+ pHlp->pfnSSMPutS32(pSSM, pStream->Regs.bd_valid);
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.addr);
+ pHlp->pfnSSMPutU32(pSSM, pStream->Regs.bd.ctl_len);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ LogFlowFuncEnter();
+
+ pHlp->pfnSSMPutU32(pSSM, pThis->glob_cnt);
+ pHlp->pfnSSMPutU32(pSSM, pThis->glob_sta);
+ pHlp->pfnSSMPutU32(pSSM, pThis->cas);
+
+ /*
+ * The order that the streams are saved here is fixed, so don't change.
+ */
+ /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ ichac97R3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]);
+
+ pHlp->pfnSSMPutMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ /* The stream order is against fixed and set in stone. */
+ uint8_t afActiveStrms[AC97SOUNDSOURCE_MAX];
+ afActiveStrms[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
+ afActiveStrms[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
+ afActiveStrms[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThisCC, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
+ AssertCompile(RT_ELEMENTS(afActiveStrms) == 3);
+ pHlp->pfnSSMPutMem(pSSM, afActiveStrms, sizeof(afActiveStrms));
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads an AC'97 stream from SSM.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to load.
+ */
+static int ichac97R3LoadStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bdbar);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.civ);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.lvi);
+ pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.sr);
+ pHlp->pfnSSMGetU16(pSSM, &pStream->Regs.picb);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.piv);
+ pHlp->pfnSSMGetU8( pSSM, &pStream->Regs.cr);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->Regs.bd_valid);
+ pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.addr);
+ return pHlp->pfnSSMGetU32(pSSM, &pStream->Regs.bd.ctl_len);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ LogRel2(("ichac97LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass));
+
+ AssertMsgReturn (uVersion == AC97_SAVED_STATE_VERSION, ("%RU32\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ pHlp->pfnSSMGetU32(pSSM, &pThis->glob_cnt);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->glob_sta);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->cas);
+
+ /*
+ * The order the streams are loaded here is critical (defined by
+ * AC97SOUNDSOURCE_XX_INDEX), so don't touch!
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ int rc = ichac97R3LoadStream(pDevIns, pSSM, &pThis->aStreams[i]);
+ AssertRCReturn(rc, rc);
+ }
+
+ pHlp->pfnSSMGetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ ichac97R3MixerRecordSelect(pThis, ichac97MixerGet(pThis, AC97_Record_Select));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER,
+ ichac97MixerGet(pThis, AC97_Master_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT,
+ ichac97MixerGet(pThis, AC97_PCM_Out_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN,
+ ichac97MixerGet(pThis, AC97_Line_In_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN,
+ ichac97MixerGet(pThis, AC97_Mic_Volume_Mute));
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN,
+ ichac97MixerGet(pThis, AC97_Record_Gain_Mic_Mute));
+ ichac97R3MixerSetGain(pThis, pThisCC, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN,
+ ichac97MixerGet(pThis, AC97_Record_Gain_Mute));
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ ichac97R3MixerSetVolume(pThis, pThisCC, AC97_Headphone_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER,
+ ichac97MixerGet(pThis, AC97_Headphone_Volume_Mute));
+
+ /*
+ * Again the stream order is set is stone.
+ */
+ uint8_t afActiveStrms[AC97SOUNDSOURCE_MAX];
+ int rc = pHlp->pfnSSMGetMem(pSSM, afActiveStrms, sizeof(afActiveStrms));
+ AssertRCReturn(rc, rc);
+
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ const bool fEnable = RT_BOOL(afActiveStrms[i]);
+ const PAC97STREAM pStream = &pThis->aStreams[i];
+ const PAC97STREAMR3 pStreamCC = &pThisCC->aStreams[i];
+
+ rc = ichac97R3StreamEnable(pDevIns, pThis, pThisCC, pStream, pStreamCC, fEnable);
+ AssertRC(rc);
+ if ( fEnable
+ && RT_SUCCESS(rc))
+ {
+ /*
+ * We need to make sure to update the stream's next transfer (if any) when
+ * restoring from a saved state.
+ *
+ * Otherwise pStream->cDmaPeriodTicks always will be 0 and thus streams won't
+ * resume when running while the saved state has been taken.
+ *
+ * Also see oem2ticketref:52.
+ */
+ ichac97R3StreamTransferUpdate(pDevIns, pStream, pStreamCC);
+
+ /* Re-arm the timer for this stream. */
+ /** @todo r=aeichner This causes a VM hang upon saved state resume when NetBSD is used as a guest
+ * Stopping the timer if cDmaPeriodTicks is 0 is a workaround but needs further investigation,
+ * see @bugref{9759} for more information. */
+ if (pStream->cDmaPeriodTicks)
+ ichac97R3TimerSet(pDevIns, pStream, pStream->cDmaPeriodTicks);
+ else
+ PDMDevHlpTimerStop(pDevIns, pStream->hTimer);
+ }
+
+ /* Keep going. */
+ }
+
+ pThis->bup_flag = 0;
+ pThis->last_samp = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Debug Info Items *
+*********************************************************************************************************************************/
+
+/** Used by ichac97R3DbgInfoStream and ichac97R3DbgInfoBDL. */
+static int ichac97R3DbgLookupStrmIdx(PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ if (pszArgs && *pszArgs)
+ {
+ int32_t idxStream;
+ int rc = RTStrToInt32Full(pszArgs, 0, &idxStream);
+ if (RT_SUCCESS(rc) && idxStream >= -1 && idxStream < AC97_MAX_STREAMS)
+ return idxStream;
+ pHlp->pfnPrintf(pHlp, "Argument '%s' is not a valid stream number!\n", pszArgs);
+ }
+ return -1;
+}
+
+
+/**
+ * Generic buffer descriptor list dumper.
+ */
+static void ichac97R3DbgPrintBdl(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STREAM pStream,
+ PCDBGFINFOHLP pHlp, const char *pszPrefix)
+{
+ uint8_t const bLvi = pStream->Regs.lvi;
+ uint8_t const bCiv = pStream->Regs.civ;
+ pHlp->pfnPrintf(pHlp, "%sBDL for stream #%u: @ %#RX32 LB 0x100; CIV=%#04x LVI=%#04x:\n",
+ pszPrefix, pStream->u8SD, pStream->Regs.bdbar, bCiv, bLvi);
+ if (pStream->Regs.bdbar != 0)
+ {
+ /* Read all in one go. */
+ AC97BDLE aBdl[AC97_MAX_BDLE];
+ RT_ZERO(aBdl);
+ PDMDevHlpPCIPhysRead(pDevIns, pStream->Regs.bdbar, aBdl, sizeof(aBdl));
+
+ /* Get the audio props for the stream so we can translate the sizes correctly. */
+ PDMAUDIOPCMPROPS Props;
+ ichach97R3CalcStreamProps(pThis, pStream->u8SD, &Props);
+
+ /* Dump them. */
+ uint64_t cbTotal = 0;
+ uint64_t cbValid = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(aBdl); i++)
+ {
+ aBdl[i].addr = RT_LE2H_U32(aBdl[i].addr);
+ aBdl[i].ctl_len = RT_LE2H_U32(aBdl[i].ctl_len);
+
+ bool const fValid = bCiv <= bLvi
+ ? i >= bCiv && i <= bLvi
+ : i >= bCiv || i <= bLvi;
+
+ uint32_t const cb = (aBdl[i].ctl_len & AC97_BD_LEN_MASK) * PDMAudioPropsSampleSize(&Props); /** @todo or frame size? OSDev says frame... */
+ cbTotal += cb;
+ if (fValid)
+ cbValid += cb;
+
+ char szFlags[64];
+ szFlags[0] = '\0';
+ if (aBdl[i].ctl_len & ~(AC97_BD_LEN_MASK | AC97_BD_IOC | AC97_BD_BUP))
+ RTStrPrintf(szFlags, sizeof(szFlags), " !!fFlags=%#x!!\n", aBdl[i].ctl_len & ~AC97_BD_LEN_MASK);
+
+ pHlp->pfnPrintf(pHlp, "%s %cBDLE%02u: %#010RX32 L %#06x / LB %#RX32 / %RU64ms%s%s%s%s\n",
+ pszPrefix, fValid ? ' ' : '?', i, aBdl[i].addr,
+ aBdl[i].ctl_len & AC97_BD_LEN_MASK, cb, PDMAudioPropsBytesToMilli(&Props, cb),
+ aBdl[i].ctl_len & AC97_BD_IOC ? " ioc" : "",
+ aBdl[i].ctl_len & AC97_BD_BUP ? " bup" : "",
+ szFlags, !(aBdl[i].addr & 3) ? "" : " !!Addr!!");
+ }
+
+ pHlp->pfnPrintf(pHlp, "%sTotal: %#RX64 bytes (%RU64), %RU64 ms; Valid: %#RX64 bytes (%RU64), %RU64 ms\n", pszPrefix,
+ cbTotal, cbTotal, PDMAudioPropsBytesToMilli(&Props, cbTotal),
+ cbValid, cbValid, PDMAudioPropsBytesToMilli(&Props, cbValid) );
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97bdl}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoBDL(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, "");
+ else
+ for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream)
+ ichac97R3DbgPrintBdl(pDevIns, pThis, &pThis->aStreams[idxStream], pHlp, "");
+}
+
+
+/** Worker for ichac97R3DbgInfoStream. */
+static void ichac97R3DbgPrintStream(PCDBGFINFOHLP pHlp, PAC97STREAM pStream, PAC97STREAMR3 pStreamR3)
+{
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ pHlp->pfnPrintf(pHlp, "Stream #%d: %s\n", pStream->u8SD,
+ PDMAudioStrmCfgToString(&pStreamR3->State.Cfg, szTmp, sizeof(szTmp)));
+ pHlp->pfnPrintf(pHlp, " BDBAR %#010RX32\n", pStream->Regs.bdbar);
+ pHlp->pfnPrintf(pHlp, " CIV %#04RX8\n", pStream->Regs.civ);
+ pHlp->pfnPrintf(pHlp, " LVI %#04RX8\n", pStream->Regs.lvi);
+ pHlp->pfnPrintf(pHlp, " SR %#06RX16\n", pStream->Regs.sr);
+ pHlp->pfnPrintf(pHlp, " PICB %#06RX16\n", pStream->Regs.picb);
+ pHlp->pfnPrintf(pHlp, " PIV %#04RX8\n", pStream->Regs.piv);
+ pHlp->pfnPrintf(pHlp, " CR %#04RX8\n", pStream->Regs.cr);
+ if (pStream->Regs.bd_valid)
+ {
+ pHlp->pfnPrintf(pHlp, " BD.ADDR %#010RX32\n", pStream->Regs.bd.addr);
+ pHlp->pfnPrintf(pHlp, " BD.LEN %#04RX16\n", (uint16_t)pStream->Regs.bd.ctl_len);
+ pHlp->pfnPrintf(pHlp, " BD.CTL %#04RX16\n", (uint16_t)(pStream->Regs.bd.ctl_len >> 16));
+ }
+
+ pHlp->pfnPrintf(pHlp, " offRead %#RX64\n", pStreamR3->State.offRead);
+ pHlp->pfnPrintf(pHlp, " offWrite %#RX64\n", pStreamR3->State.offWrite);
+ pHlp->pfnPrintf(pHlp, " uTimerHz %RU16\n", pStreamR3->State.uTimerHz);
+ pHlp->pfnPrintf(pHlp, " cDmaPeriodTicks %RU64\n", pStream->cDmaPeriodTicks);
+ pHlp->pfnPrintf(pHlp, " cbDmaPeriod %#RX32\n", pStream->cbDmaPeriod);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97stream}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ int idxStream = ichac97R3DbgLookupStrmIdx(pHlp, pszArgs);
+ if (idxStream != -1)
+ ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]);
+ else
+ for (idxStream = 0; idxStream < AC97_MAX_STREAMS; ++idxStream)
+ ichac97R3DbgPrintStream(pHlp, &pThis->aStreams[idxStream], &pThisCC->aStreams[idxStream]);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, ac97mixer}
+ */
+static DECLCALLBACK(void) ichac97R3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ if (pThisCC->pMixer)
+ AudioMixerDebug(pThisCC->pMixer, pHlp, pszArgs);
+ else
+ pHlp->pfnPrintf(pHlp, "Mixer not available\n");
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ichac97R3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
+{
+ PAC97STATER3 pThisCC = RT_FROM_MEMBER(pInterface, AC97STATER3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDEVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Destroys all AC'97 audio streams of the device.
+ *
+ * @param pDevIns The device AC'97 instance.
+ * @param pThis The shared AC'97 state.
+ * @param pThisCC The ring-3 AC'97 state.
+ */
+static void ichac97R3StreamsDestroy(PPDMDEVINS pDevIns, PAC97STATE pThis, PAC97STATER3 pThisCC)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Destroy all AC'97 streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ ichac97R3StreamDestroy(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i]);
+
+ /*
+ * Destroy all sinks.
+ */
+ if (pThisCC->pSinkLineIn)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkLineIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_LINE);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkLineIn, pDevIns);
+ pThisCC->pSinkLineIn = NULL;
+ }
+
+ if (pThisCC->pSinkMicIn)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkMicIn, PDMAUDIODIR_IN, PDMAUDIOPATH_IN_MIC);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkMicIn, pDevIns);
+ pThisCC->pSinkMicIn = NULL;
+ }
+
+ if (pThisCC->pSinkOut)
+ {
+ ichac97R3MixerRemoveDrvStreams(pDevIns, pThisCC, pThisCC->pSinkOut, PDMAUDIODIR_OUT, PDMAUDIOPATH_OUT_FRONT);
+
+ AudioMixerSinkDestroy(pThisCC->pSinkOut, pDevIns);
+ pThisCC->pSinkOut = NULL;
+ }
+}
+
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) ichac97R3PowerOff(PPDMDEVINS pDevIns)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogRel2(("AC97: Powering off ...\n"));
+
+ /* Note: Involves mixer stream / sink destruction, so also do this here
+ * instead of in ichac97R3Destruct(). */
+ ichac97R3StreamsDestroy(pDevIns, pThis, pThisCC);
+
+ /*
+ * Note: Destroy the mixer while powering off and *not* in ichac97R3Destruct,
+ * giving the mixer the chance to release any references held to
+ * PDM audio streams it maintains.
+ */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ *
+ * @remarks The original sources didn't install a reset handler, but it seems to
+ * make sense to me so we'll do it.
+ */
+static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogRel(("AC97: Reset\n"));
+
+ /*
+ * Reset the mixer too. The Windows XP driver seems to rely on
+ * this. At least it wants to read the vendor id before it resets
+ * the codec manually.
+ */
+ ichac97R3MixerReset(pThis, pThisCC);
+
+ /*
+ * Reset all streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ ichac97R3StreamEnable(pDevIns, pThis, pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], false /* fEnable */);
+ ichac97R3StreamReset(pThis, &pThis->aStreams[i], &pThisCC->aStreams[i]);
+ }
+
+ /*
+ * Reset mixer sinks.
+ *
+ * Do the reset here instead of in ichac97R3StreamReset();
+ * the mixer sink(s) might still have data to be processed when an audio stream gets reset.
+ */
+ AudioMixerSinkReset(pThisCC->pSinkLineIn);
+ AudioMixerSinkReset(pThisCC->pSinkMicIn);
+ AudioMixerSinkReset(pThisCC->pSinkOut);
+}
+
+
+/**
+ * Adds a specific AC'97 driver to the driver chain.
+ *
+ * Only called from ichac97R3Attach().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pDrv The AC'97 driver to add.
+ */
+static int ichac97R3MixerAddDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg))
+ rc = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkLineIn,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv);
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkOut,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (AudioHlpStreamCfgIsValid(&pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pDevIns, pThisCC->pSinkMicIn,
+ &pThisCC->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for ichac97R3Construct() and ichac97R3Attach().
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param uLUN The logical unit which is being attached.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int ichac97R3AttachInternal(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, unsigned uLUN, PAC97DRIVER *ppDrv)
+{
+ /*
+ * Allocate a new driver structure and try attach the driver.
+ */
+ PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER));
+ AssertPtrReturn(pDrv, VERR_NO_MEMORY);
+ RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (AC'97) for LUN #%u", uLUN);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pDevIns, uLUN, &pThisCC->IBase, &pDrvBase, pDrv->szDesc);
+ if (RT_SUCCESS(rc))
+ {
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pDrv->pConnector);
+ if (RT_VALID_PTR(pDrv->pConnector))
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->uLUN = uLUN;
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThisCC->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+
+ /*
+ * While we're here, give the windows backends a hint about our typical playback
+ * configuration.
+ */
+ if ( pDrv->pConnector
+ && pDrv->pConnector->pfnStreamConfigHint)
+ {
+ /* 48kHz */
+ PDMAUDIOSTREAMCFG Cfg;
+ RT_ZERO(Cfg);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ Cfg.Device.cMsSchedulingHint = 5;
+ Cfg.Backend.cFramesPreBuffering = UINT32_MAX;
+ PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 48000);
+ RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 48kHz 2ch S16 (HDA config hint)");
+
+ pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */
+# if 0
+ /* 44.1kHz */
+ RT_ZERO(Cfg);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+ Cfg.Device.cMsSchedulingHint = 10;
+ Cfg.Backend.cFramesPreBuffering = UINT32_MAX;
+ PDMAudioPropsInit(&Cfg.Props, 2, true /*fSigned*/, 2, 44100);
+ RTStrPrintf(Cfg.szName, sizeof(Cfg.szName), "output 44.1kHz 2ch S16 (HDA config hint)");
+
+ pDrv->pConnector->pfnStreamConfigHint(pDrv->pConnector, &Cfg); /* (may trash CfgReq) */
+# endif
+ }
+
+ LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pDrv);
+ rc = VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+ else
+ LogFunc(("Attached driver for LUN #%u failed: %Rrc\n", uLUN, rc));
+ RTMemFree(pDrv);
+
+ LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnAttach}
+ */
+static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ RT_NOREF(fFlags);
+ LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
+
+ DEVAC97_LOCK(pDevIns, pThis);
+
+ PAC97DRIVER pDrv;
+ int rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLUN, &pDrv);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = ichac97R3MixerAddDrv(pDevIns, pThisCC, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("ichac97R3MixerAddDrv failed with %Rrc (ignored)\n", rc2));
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ return rc;
+}
+
+
+/**
+ * Removes a specific AC'97 driver from the driver chain and destroys its
+ * associated streams.
+ *
+ * Only called from ichac97R3Detach().
+ *
+ * @param pDevIns The device instance.
+ * @param pThisCC The ring-3 AC'97 device state.
+ * @param pDrv AC'97 driver to remove.
+ */
+static void ichac97R3MixerRemoveDrv(PPDMDEVINS pDevIns, PAC97STATER3 pThisCC, PAC97DRIVER pDrv)
+{
+ if (pDrv->MicIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkMicIn, pDrv->MicIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->MicIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->LineIn.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkLineIn, pDrv->LineIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->LineIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->Out.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThisCC->pSinkOut, pDrv->Out.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->Out.pMixStrm = NULL;
+ }
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) ichac97R3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ RT_NOREF(fFlags);
+
+ LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags));
+
+ DEVAC97_LOCK(pDevIns, pThis);
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThisCC->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ if (pDrv->uLUN == iLUN)
+ {
+ /* Remove the driver from our list and destory it's associated streams.
+ This also will un-set the driver as a recording source (if associated). */
+ ichac97R3MixerRemoveDrv(pDevIns, pThisCC, pDrv);
+ LogFunc(("Detached LUN#%u\n", pDrv->uLUN));
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+
+ RTMemFree(pDrv);
+ return;
+ }
+ }
+
+ DEVAC97_UNLOCK(pDevIns, pThis);
+ LogFunc(("LUN#%u was not found\n", iLUN));
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) ichac97R3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+
+ LogFlowFuncEnter();
+
+ PAC97DRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThisCC->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node)
+ {
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThisCC->lstDrv));
+
+ /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
+ if (pThisCC->pMixer)
+ {
+ AudioMixerDestroy(pThisCC->pMixer, pDevIns);
+ pThisCC->pMixer = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) ichac97R3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+ PAC97STATER3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PAC97STATER3);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ Assert(iInstance == 0); RT_NOREF(iInstance);
+
+ /*
+ * Initialize data so we can run the destructor without scewing up.
+ */
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->IBase.pfnQueryInterface = ichac97R3QueryInterface;
+ RTListInit(&pThisCC->lstDrv);
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "BufSizeInMs|BufSizeOutMs|Codec|TimerHz|DebugEnabled|DebugPathOut", "");
+
+ /** @devcfgm{ac97,BufSizeInMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for input streams expressed in milliseconds. */
+ int rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeInMs", &pThis->cMsCircBufIn, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read 'BufSizeInMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufIn > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC97 configuration error: 'BufSizeInMs' is out of bound, max 2000 ms"));
+
+ /** @devcfgm{ac97,BufSizeOutMs,uint16_t,0,2000,0,ms}
+ * The size of the DMA buffer for output streams expressed in milliseconds. */
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "BufSizeOutMs", &pThis->cMsCircBufOut, 0);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read 'BufSizeOutMs' as 16-bit unsigned integer"));
+ if (pThis->cMsCircBufOut > 2000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC97 configuration error: 'BufSizeOutMs' is out of bound, max 2000 ms"));
+
+ /** @devcfgm{ac97,TimerHz,uint16_t,10,1000,100,ms}
+ * Currently the approximate rate at which the asynchronous I/O threads move
+ * data from/to the DMA buffer, thru the mixer and drivers stack, and
+ * to/from the host device/whatever. (It does NOT govern any DMA timer rate any
+ * more as might be hinted at by the name.) */
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC'97 configuration error: failed to read 'TimerHz' as a 16-bit unsigned integer"));
+ if (pThis->uTimerHz < 10 || pThis->uTimerHz > 1000)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_OUT_OF_RANGE,
+ N_("AC'97 configuration error: 'TimerHz' is out of range (10-1000 Hz)"));
+
+ if (pThis->uTimerHz != AC97_TIMER_HZ_DEFAULT)
+ LogRel(("AC97: Using custom device timer rate: %RU16 Hz\n", pThis->uTimerHz));
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThisCC->Dbg.fEnabled, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read debugging enabled flag as boolean"));
+
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThisCC->Dbg.pszOutPath, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read debugging output path flag as string"));
+
+ if (pThisCC->Dbg.fEnabled)
+ LogRel2(("AC97: Debug output will be saved to '%s'\n", pThisCC->Dbg.pszOutPath));
+
+ /*
+ * The AD1980 codec (with corresponding PCI subsystem vendor ID) is whitelisted
+ * in the Linux kernel; Linux makes no attempt to measure the data rate and assumes
+ * 48 kHz rate, which is exactly what we need. Same goes for AD1981B.
+ */
+ char szCodec[20];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "Codec", &szCodec[0], sizeof(szCodec), "STAC9700");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("AC'97 configuration error: Querying \"Codec\" as string failed"));
+ if (!strcmp(szCodec, "STAC9700"))
+ pThis->enmCodecModel = AC97CODEC_STAC9700;
+ else if (!strcmp(szCodec, "AD1980"))
+ pThis->enmCodecModel = AC97CODEC_AD1980;
+ else if (!strcmp(szCodec, "AD1981B"))
+ pThis->enmCodecModel = AC97CODEC_AD1981B;
+ else
+ return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS,
+ N_("AC'97 configuration error: The \"Codec\" value \"%s\" is unsupported"), szCodec);
+
+ LogRel(("AC97: Using codec '%s'\n", szCodec));
+
+ /*
+ * Use an own critical section for the device instead of the default
+ * one provided by PDM. This allows fine-grained locking in combination
+ * with TM when timer-specific stuff is being called in e.g. the MMIO handlers.
+ */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "AC'97");
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ /* PCI Device */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PCIDevSetVendorId(pPciDev, 0x8086); /* 00 ro - intel. */ Assert(pPciDev->abConfig[0x00] == 0x86); Assert(pPciDev->abConfig[0x01] == 0x80);
+ PCIDevSetDeviceId(pPciDev, 0x2415); /* 02 ro - 82801 / 82801aa(?). */ Assert(pPciDev->abConfig[0x02] == 0x15); Assert(pPciDev->abConfig[0x03] == 0x24);
+ PCIDevSetCommand(pPciDev, 0x0000); /* 04 rw,ro - pcicmd. */ Assert(pPciDev->abConfig[0x04] == 0x00); Assert(pPciDev->abConfig[0x05] == 0x00);
+ PCIDevSetStatus(pPciDev, VBOX_PCI_STATUS_DEVSEL_MEDIUM | VBOX_PCI_STATUS_FAST_BACK); /* 06 rwc?,ro? - pcists. */ Assert(pPciDev->abConfig[0x06] == 0x80); Assert(pPciDev->abConfig[0x07] == 0x02);
+ PCIDevSetRevisionId(pPciDev, 0x01); /* 08 ro - rid. */ Assert(pPciDev->abConfig[0x08] == 0x01);
+ PCIDevSetClassProg(pPciDev, 0x00); /* 09 ro - pi. */ Assert(pPciDev->abConfig[0x09] == 0x00);
+ PCIDevSetClassSub(pPciDev, 0x01); /* 0a ro - scc; 01 == Audio. */ Assert(pPciDev->abConfig[0x0a] == 0x01);
+ PCIDevSetClassBase(pPciDev, 0x04); /* 0b ro - bcc; 04 == multimedia.*/Assert(pPciDev->abConfig[0x0b] == 0x04);
+ PCIDevSetHeaderType(pPciDev, 0x00); /* 0e ro - headtyp. */ Assert(pPciDev->abConfig[0x0e] == 0x00);
+ PCIDevSetBaseAddress(pPciDev, 0, /* 10 rw - nambar - native audio mixer base. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pPciDev->abConfig[0x10] == 0x01); Assert(pPciDev->abConfig[0x11] == 0x00); Assert(pPciDev->abConfig[0x12] == 0x00); Assert(pPciDev->abConfig[0x13] == 0x00);
+ PCIDevSetBaseAddress(pPciDev, 1, /* 14 rw - nabmbar - native audio bus mastering. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pPciDev->abConfig[0x14] == 0x01); Assert(pPciDev->abConfig[0x15] == 0x00); Assert(pPciDev->abConfig[0x16] == 0x00); Assert(pPciDev->abConfig[0x17] == 0x00);
+ PCIDevSetInterruptLine(pPciDev, 0x00); /* 3c rw. */ Assert(pPciDev->abConfig[0x3c] == 0x00);
+ PCIDevSetInterruptPin(pPciDev, 0x01); /* 3d ro - INTA#. */ Assert(pPciDev->abConfig[0x3d] == 0x01);
+
+ if (pThis->enmCodecModel == AC97CODEC_AD1980)
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId(pPciDev, 0x0177); /* 2e ro. */
+ }
+ else if (pThis->enmCodecModel == AC97CODEC_AD1981B)
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId(pPciDev, 0x01ad); /* 2e ro. */
+ }
+ else
+ {
+ PCIDevSetSubSystemVendorId(pPciDev, 0x8086); /* 2c ro - Intel.) */
+ PCIDevSetSubSystemId(pPciDev, 0x0000); /* 2e ro. */
+ }
+
+ /*
+ * Register the PCI device and associated I/O regions.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 0 /*iPciRegion*/, 256 /*cPorts*/,
+ ichac97IoPortNamWrite, ichac97IoPortNamRead, NULL /*pvUser*/,
+ "ICHAC97 NAM", NULL /*paExtDescs*/, &pThis->hIoPortsNam);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, 1 /*iPciRegion*/, 64 /*cPorts*/,
+ ichac97IoPortNabmWrite, ichac97IoPortNabmRead, NULL /*pvUser*/,
+ "ICHAC97 NABM", g_aNabmPorts, &pThis->hIoPortsNabm);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Saved state.
+ */
+ rc = PDMDevHlpSSMRegister(pDevIns, AC97_SAVED_STATE_VERSION, sizeof(*pThis), ichac97R3SaveExec, ichac97R3LoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach drivers. We ASSUME they are configured consecutively without any
+ * gaps, so we stop when we hit the first LUN w/o a driver configured.
+ */
+ for (unsigned iLun = 0; ; iLun++)
+ {
+ AssertBreak(iLun < UINT8_MAX);
+ LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun));
+ rc = ichac97R3AttachInternal(pDevIns, pThisCC, iLun, NULL /* ppDrv */);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ LogFunc(("cLUNs=%u\n", iLun));
+ break;
+ }
+ AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
+ }
+
+ uint32_t fMixer = AUDMIXER_FLAGS_NONE;
+ if (pThisCC->Dbg.fEnabled)
+ fMixer |= AUDMIXER_FLAGS_DEBUG;
+
+ rc = AudioMixerCreate("AC'97 Mixer", 0 /* uFlags */, &pThisCC->pMixer);
+ AssertRCReturn(rc, rc);
+
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Line In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkLineIn);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "Microphone In",
+ PDMAUDIODIR_IN, pDevIns, &pThisCC->pSinkMicIn);
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThisCC->pMixer, "PCM Output",
+ PDMAUDIODIR_OUT, pDevIns, &pThisCC->pSinkOut);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create all hardware streams.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->aStreams) == AC97_MAX_STREAMS);
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ rc = ichac97R3StreamConstruct(pThisCC, &pThis->aStreams[i], &pThisCC->aStreams[i], i /* SD# */);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Create the emulation timers (one per stream).
+ *
+ * We must the critical section for the timers as the device has a
+ * noop section associated with it.
+ *
+ * Note: Use TMCLOCK_VIRTUAL_SYNC here, as the guest's AC'97 driver
+ * relies on exact (virtual) DMA timing and uses DMA Position Buffers
+ * instead of the LPIB registers.
+ */
+ /** @todo r=bird: The need to use virtual sync is perhaps because TM
+ * doesn't schedule regular TMCLOCK_VIRTUAL timers as accurately as it
+ * should (VT-x preemption timer, etc). Hope to address that before
+ * long. @bugref{9943}. */
+ static const char * const s_apszNames[] = { "AC97 PI", "AC97 PO", "AC97 MC" };
+ AssertCompile(RT_ELEMENTS(s_apszNames) == AC97_MAX_STREAMS);
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, &pThis->aStreams[i],
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, s_apszNames[i], &pThis->aStreams[i].hTimer);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->aStreams[i].hTimer, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ }
+
+ ichac97R3Reset(pDevIns);
+
+ /*
+ * Info items.
+ */
+ //PDMDevHlpDBGFInfoRegister(pDevIns, "ac97", "AC'97 registers. (ac97 [register case-insensitive])", ichac97R3DbgInfo);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97bdl", "AC'97 buffer descriptor list (BDL). (ac97bdl [stream number])",
+ ichac97R3DbgInfoBDL);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97stream", "AC'97 stream info. (ac97stream [stream number])", ichac97R3DbgInfoStream);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ac97mixer", "AC'97 mixer state.", ichac97R3DbgInfoMixer);
+
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmReads, STAMTYPE_COUNTER, "UnimplementedNabmReads", STAMUNIT_OCCURENCES, "Unimplemented NABM register reads.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNabmWrites, STAMTYPE_COUNTER, "UnimplementedNabmWrites", STAMUNIT_OCCURENCES, "Unimplemented NABM register writes.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamReads, STAMTYPE_COUNTER, "UnimplementedNamReads", STAMUNIT_OCCURENCES, "Unimplemented NAM register reads.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUnimplementedNamWrites, STAMTYPE_COUNTER, "UnimplementedNamWrites", STAMUNIT_OCCURENCES, "Unimplemented NAM register writes.");
+# ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling ichac97Timer.");
+# endif
+ for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].cbDmaPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Bytes to transfer in the current DMA period.", "Stream%u/cbTransferChunk", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.cr, STAMTYPE_X8, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Control register (CR), bit 0 is the run bit.", "Stream%u/reg-CR", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].Regs.sr, STAMTYPE_X16, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE,
+ "Status register (SR).", "Stream%u/reg-SR", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ,
+ "The stream frequency.", "Stream%u/Hz", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "The frame size.", "Stream%u/FrameSize", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer read position.", "Stream%u/offRead", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowProblems, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer problems.", "Stream%u/DMABufferProblems", idxStream);
+ if (ichac97R3GetDirFromSD(idxStream) == PDMAUDIODIR_OUT)
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer overflows.", "Stream%u/DMABufferOverflows", idxStream);
+ else
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrors, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Number of internal DMA buffer underuns.", "Stream%u/DMABufferUnderruns", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaFlowErrorBytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes of silence added to cope with underruns.", "Stream%u/DMABufferSilence", idxStream);
+ }
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedDch, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "DMA transfer period skipped, controller halted (DCH).", "Stream%u/DMASkippedDch", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatDmaSkippedPendingBcis, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "DMA transfer period skipped because of BCIS pending.", "Stream%u/DMASkippedPendingBCIS", idxStream);
+
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStart, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Starting the stream.", "Stream%u/Start", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatStop, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Stopping the stream.", "Stream%u/Stop", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReset, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "Resetting the stream.", "Stream%u/Reset", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpChanged, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "ichac97R3StreamReSetUp when recreating the streams.", "Stream%u/ReSetUp-Change", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatReSetUpSame, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_NS_PER_CALL,
+ "ichac97R3StreamReSetUp when no change.", "Stream%u/ReSetUp-NoChange", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteCr, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "CR register writes.", "Stream%u/WriteCr", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThisCC->aStreams[idxStream].State.StatWriteLviRecover, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "LVI register writes recovering from underflow.", "Stream%u/WriteLviRecover", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteLvi, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "LVI register writes (non-recoving).", "Stream%u/WriteLvi", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr1, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "SR register 1-byte writes.", "Stream%u/WriteSr-1byte", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteSr2, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "SR register 2-byte writes.", "Stream%u/WriteSr-2byte", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].StatWriteBdBar, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "BDBAR register writes.", "Stream%u/WriteBdBar", idxStream);
+ }
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ichac97RZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PAC97STATE pThis = PDMDEVINS_2_DATA(pDevIns, PAC97STATE);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsNam, ichac97IoPortNamWrite, ichac97IoPortNamRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPortsNabm, ichac97IoPortNabmWrite, ichac97IoPortNabmRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceICHAC97 =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "ichac97",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
+ /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(AC97STATE),
+ /* .cbInstanceCC = */ CTX_EXPR(sizeof(AC97STATER3), 0, 0),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "ICH AC'97 Audio Controller",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ichac97R3Construct,
+ /* .pfnDestruct = */ ichac97R3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ichac97R3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ ichac97R3Attach,
+ /* .pfnDetach = */ ichac97R3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ ichac97R3PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ichac97RZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ichac97RZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/Audio/DevSB16.cpp b/src/VBox/Devices/Audio/DevSB16.cpp
new file mode 100644
index 00000000..a405f311
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevSB16.cpp
@@ -0,0 +1,3159 @@
+/* $Id: DevSB16.cpp $ */
+/** @file
+ * DevSB16 - VBox SB16 Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2015-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
+ * --------------------------------------------------------------------
+ *
+ * This code is based on: sb16.c from QEMU AUDIO subsystem (r3917).
+ * QEMU Soundblaster 16 emulation
+ *
+ * Copyright (c) 2003-2005 Vassili Karpov (malc)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_SB16
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#ifdef IN_RING3
+# include <iprt/mem.h>
+# include <iprt/string.h>
+# include <iprt/uuid.h>
+#endif
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/AssertGuest.h>
+
+#include "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+#include "AudioHlp.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Default timer frequency (in Hz). */
+#define SB16_TIMER_HZ_DEFAULT 100
+/** The maximum number of separate streams we currently implement.
+ * Currently we only support one stream only, namely the output stream. */
+#define SB16_MAX_STREAMS 1
+/** The (zero-based) index of the output stream in \a aStreams. */
+#define SB16_IDX_OUT 0
+
+/** Current saved state version. */
+#define SB16_SAVE_STATE_VERSION 2
+/** The version used in VirtualBox version 3.0 and earlier. This didn't include the config dump. */
+#define SB16_SAVE_STATE_VERSION_VBOX_30 1
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char e3[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the SB16 state. */
+typedef struct SB16STATE *PSB16STATE;
+
+/**
+ * The internal state of a SB16 stream.
+ */
+typedef struct SB16STREAMSTATE
+{
+ /** Flag indicating whether this stream is in enabled state or not. */
+ bool fEnabled;
+ /** Set if we've registered the asynchronous update job. */
+ bool fRegisteredAsyncUpdateJob;
+ /** DMA cache to read data from / write data to. */
+ PRTCIRCBUF pCircBuf;
+ /** Current circular buffer read offset (for tracing & logging). */
+ uint64_t offRead;
+ /** Current circular buffer write offset (for tracing & logging). */
+ uint64_t offWrite;
+
+ /** Size of the DMA buffer (pCircBuf) in bytes. */
+ uint32_t StatDmaBufSize;
+ /** Number of used bytes in the DMA buffer (pCircBuf). */
+ uint32_t StatDmaBufUsed;
+} SB16STREAMSTATE;
+/** Pointer to internal state of an SB16 stream. */
+typedef SB16STREAMSTATE *PSB16STREAMSTATE;
+
+/**
+ * Structure defining a (host backend) driver stream.
+ * Each driver has its own instances of audio mixer streams, which then
+ * can go into the same (or even different) audio mixer sinks.
+ */
+typedef struct SB16DRIVERSTREAM
+{
+ /** Associated mixer stream handle. */
+ R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
+ /** The stream's current configuration. */
+} SB16DRIVERSTREAM, *PSB16DRIVERSTREAM;
+
+/**
+ * Struct for tracking a host backend driver, i.e. our per-LUN data.
+ */
+typedef struct SB16DRIVER
+{
+ /** Node for storing this driver in our device driver list of SB16STATE. */
+ RTLISTNODER3 Node;
+ /** Pointer to SB16 controller (state). */
+ R3PTRTYPE(PSB16STATE) pSB16State;
+ /** Pointer to attached driver base interface. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Audio connector interface to the underlying host backend. */
+ R3PTRTYPE(PPDMIAUDIOCONNECTOR) pConnector;
+ /** Stream for output. */
+ SB16DRIVERSTREAM Out;
+ /** LUN # to which this driver has been assigned. */
+ uint8_t uLUN;
+ /** Whether this driver is in an attached state or not. */
+ bool fAttached;
+ /** The LUN description. */
+ char szDesc[48 - 2];
+} SB16DRIVER;
+/** Pointer to the per-LUN data. */
+typedef SB16DRIVER *PSB16DRIVER;
+
+/**
+ * Runtime configurable debug stuff for a SB16 stream.
+ */
+typedef struct SB16STREAMDEBUGRT
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ uint8_t Padding[7];
+ /** File for dumping DMA reads / writes.
+ * For input streams, this dumps data being written to the device DMA,
+ * whereas for output streams this dumps data being read from the device DMA. */
+ R3PTRTYPE(PAUDIOHLPFILE) pFileDMA;
+} SB16STREAMDEBUGRT;
+
+/**
+ * Debug stuff for a SB16 stream.
+ */
+typedef struct SB16STREAMDEBUG
+{
+ /** Runtime debug stuff. */
+ SB16STREAMDEBUGRT Runtime;
+} SB16STREAMDEBUG;
+
+/**
+ * Structure for keeping a SB16 hardware stream configuration.
+ */
+typedef struct SB16STREAMHWCFG
+{
+ /** IRQ # to use. */
+ uint8_t uIrq;
+ /** Low DMA channel to use. */
+ uint8_t uDmaChanLow;
+ /** High DMA channel to use. */
+ uint8_t uDmaChanHigh;
+ /** IO port to use. */
+ RTIOPORT uPort;
+ /** DSP version to expose. */
+ uint16_t uVer;
+} SB16STREAMHWCFG;
+
+/**
+ * Structure for a SB16 stream.
+ */
+typedef struct SB16STREAM
+{
+ /** The stream's own index in \a aStreams of SB16STATE.
+ * Set to UINT8_MAX if not set (yet). */
+ uint8_t uIdx;
+ uint16_t uTimerHz;
+ /** The timer for pumping data thru the attached LUN drivers. */
+ TMTIMERHANDLE hTimerIO;
+ /** The timer interval for pumping data thru the LUN drivers in timer ticks. */
+ uint64_t cTicksTimerIOInterval;
+ /** Timestamp of the last timer callback (sb16TimerIO).
+ * Used to calculate thetime actually elapsed between two timer callbacks.
+ * This currently ASSMUMES that we only have one single (output) stream. */
+ uint64_t tsTimerIO; /** @todo Make this a per-stream value. */
+ /** The stream's currentconfiguration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** The stream's defaulthardware configuration, mostly done by jumper settings back then. */
+ SB16STREAMHWCFG HwCfgDefault;
+ /** The stream's hardware configuration set at runtime.
+ * Might differ from the default configuration above and is needed for live migration. */
+ SB16STREAMHWCFG HwCfgRuntime;
+
+ int fifo;
+ int dma_auto;
+ /** Whether to use the high (\c true) or the low (\c false) DMA channel. */
+ int fDmaUseHigh;
+ int can_write; /** @todo r=andy BUGBUG Value never gets set to 0! */
+ int time_const;
+ /** The DMA transfer (block)size in bytes. */
+ int32_t cbDmaBlockSize;
+ int32_t cbDmaLeft; /** Note: Can be < 0. Needs to 32-bit for backwards compatibility. */
+ /** Internal state of this stream. */
+ SB16STREAMSTATE State;
+ /** Debug stuff. */
+ SB16STREAMDEBUG Dbg;
+} SB16STREAM;
+/** Pointer to a SB16 stream */
+typedef SB16STREAM *PSB16STREAM;
+
+/**
+ * SB16 debug settings.
+ */
+typedef struct SB16STATEDEBUG
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ bool afAlignment[7];
+ /** Path where to dump the debug output to.
+ * Can be NULL, in which the system's temporary directory will be used then. */
+ R3PTRTYPE(char *) pszOutPath;
+} SB16STATEDEBUG;
+
+/**
+ * The SB16 state.
+ */
+typedef struct SB16STATE
+{
+ /** Pointer to the device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the connector of the attached audio driver. */
+ PPDMIAUDIOCONNECTOR pDrv;
+
+ int dsp_in_idx;
+ int dsp_out_data_len;
+ int dsp_in_needed_bytes;
+ int cmd;
+ int highspeed;
+
+ int v2x6;
+
+ uint8_t csp_param;
+ uint8_t csp_value;
+ uint8_t csp_mode;
+ uint8_t csp_index;
+ uint8_t csp_regs[256];
+ uint8_t csp_reg83[4];
+ int csp_reg83r;
+ int csp_reg83w;
+
+ uint8_t dsp_in_data[10];
+ uint8_t dsp_out_data[50];
+ uint8_t test_reg;
+ uint8_t last_read_byte;
+ int nzero;
+
+ RTLISTANCHOR lstDrv;
+ /** IRQ timer */
+ TMTIMERHANDLE hTimerIRQ;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+
+ /** Array of all SB16 hardware audio stream. */
+ SB16STREAM aStreams[SB16_MAX_STREAMS];
+ /** The device's software mixer. */
+ R3PTRTYPE(PAUDIOMIXER) pMixer;
+ /** Audio sink for PCM output. */
+ R3PTRTYPE(PAUDMIXSINK) pSinkOut;
+
+ /** The two mixer I/O ports (port + 4). */
+ IOMIOPORTHANDLE hIoPortsMixer;
+ /** The 10 DSP I/O ports (port + 6). */
+ IOMIOPORTHANDLE hIoPortsDsp;
+
+ /** Debug settings. */
+ SB16STATEDEBUG Dbg;
+
+ /* mixer state */
+ uint8_t mixer_nreg;
+ uint8_t mixer_regs[256];
+
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatTimerIO;
+ STAMCOUNTER StatBytesRead;
+#endif
+} SB16STATE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx);
+
+static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce);
+static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream);
+static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream);
+static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream);
+DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx);
+static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cSamples);
+static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma, uint32_t cbToRead, uint32_t *pcbRead);
+static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser);
+
+static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser);
+static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser);
+DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline);
+
+static void sb16SpeakerControl(PSB16STATE pThis, bool fOn);
+static void sb16UpdateVolume(PSB16STATE pThis);
+
+
+
+static void sb16SpeakerControl(PSB16STATE pThis, bool fOn)
+{
+ RT_NOREF(pThis, fOn);
+
+ /** @todo This currently does nothing. */
+}
+
+static void sb16StreamControl(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, bool fRun)
+{
+ unsigned uDmaChan = pStream->fDmaUseHigh ? pStream->HwCfgRuntime.uDmaChanHigh : pStream->HwCfgRuntime.uDmaChanLow;
+
+ LogFunc(("fRun=%RTbool, fDmaUseHigh=%RTbool, uDmaChan=%u\n", fRun, pStream->fDmaUseHigh, uDmaChan));
+
+ PDMDevHlpDMASetDREQ(pThis->pDevInsR3, uDmaChan, fRun ? 1 : 0);
+
+ if (fRun != pStream->State.fEnabled)
+ {
+ if (fRun)
+ {
+ int rc = VINF_SUCCESS;
+
+ if (pStream->Cfg.Props.uHz > 0)
+ {
+ rc = sb16StreamOpen(pDevIns, pThis, pStream);
+ if (RT_SUCCESS(rc))
+ sb16UpdateVolume(pThis);
+ }
+ else
+ AssertFailed(); /** @todo Buggy code? */
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = sb16StreamEnable(pThis, pStream, true /* fEnable */, false /* fForce */);
+ if (RT_SUCCESS(rc))
+ {
+ sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval);
+
+ PDMDevHlpDMASchedule(pThis->pDevInsR3);
+ }
+ }
+ }
+ else
+ {
+ sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */);
+ }
+ }
+}
+
+#define DMA8_AUTO 1
+#define DMA8_HIGH 2
+
+static void sb16DmaCmdContinue8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
+{
+ sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
+}
+
+static void sb16DmaCmd8(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream,
+ int mask, int dma_len)
+{
+ pStream->fDmaUseHigh = 0;
+
+ if (-1 == pStream->time_const)
+ {
+ if (pStream->Cfg.Props.uHz == 0)
+ pStream->Cfg.Props.uHz = 11025;
+ }
+ else
+ {
+ int tmp = (256 - pStream->time_const);
+ pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp;
+ }
+
+ /** @todo r=bird: Use '(pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2' like below? */
+ unsigned cShiftChannels = PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0;
+
+ if (dma_len != -1)
+ {
+ pStream->cbDmaBlockSize = dma_len << cShiftChannels;
+ }
+ else
+ {
+ /* This is apparently the only way to make both Act1/PL
+ and SecondReality/FC work
+
+ r=andy Wow, actually someone who remembers Future Crew :-)
+
+ Act1 sets block size via command 0x48 and it's an odd number
+ SR does the same with even number
+ Both use stereo, and Creatives own documentation states that
+ 0x48 sets block size in bytes less one.. go figure */
+ pStream->cbDmaBlockSize &= ~cShiftChannels;
+ }
+
+ pStream->Cfg.Props.uHz >>= cShiftChannels;
+ pStream->cbDmaLeft = pStream->cbDmaBlockSize;
+ /* pThis->highspeed = (mask & DMA8_HIGH) != 0; */
+ pStream->dma_auto = (mask & DMA8_AUTO) != 0;
+
+ PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */,
+ false /* fSigned */,
+ (pThis->mixer_regs[0x0e] & 2) == 0 ? 1 : 2 /* Mono/Stereo */,
+ pStream->Cfg.Props.uHz);
+
+ /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */
+
+ sb16DmaCmdContinue8(pDevIns, pThis, pStream);
+ sb16SpeakerControl(pThis, true /* fOn */);
+}
+
+static void sb16DmaCmd(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream,
+ uint8_t cmd, uint8_t d0, int dma_len)
+{
+ pStream->fDmaUseHigh = cmd < 0xc0;
+ pStream->fifo = (cmd >> 1) & 1;
+ pStream->dma_auto = (cmd >> 2) & 1;
+
+ pStream->Cfg.Props.fSigned = RT_BOOL(d0 & RT_BIT_32(4));
+ PDMAudioPropsSetChannels(&pStream->Cfg.Props, 1 + ((d0 >> 5) & 1));
+
+ switch (cmd >> 4)
+ {
+ case 11:
+ PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 2 /*16-bit*/);
+ break;
+
+ case 12:
+ PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, 1 /*8-bit*/);
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ if (-1 != pStream->time_const)
+ {
+#if 1
+ int tmp = 256 - pStream->time_const;
+ pStream->Cfg.Props.uHz = (1000000 + (tmp / 2)) / tmp;
+#else
+ /* pThis->freq = 1000000 / ((255 - pStream->time_const) << pThis->fmt_stereo); */
+ pThis->freq = 1000000 / ((255 - pStream->time_const));
+#endif
+ pStream->time_const = -1;
+ }
+
+ pStream->cbDmaBlockSize = dma_len + 1;
+ pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0;
+ if (!pStream->dma_auto)
+ {
+ /*
+ * It is clear that for DOOM and auto-init this value
+ * shouldn't take stereo into account, while Miles Sound Systems
+ * setsound.exe with single transfer mode wouldn't work without it
+ * wonders of SB16 yet again.
+ */
+ pStream->cbDmaBlockSize <<= PDMAudioPropsSampleSize(&pStream->Cfg.Props) == 2 ? 1 : 0;
+ }
+
+ pStream->cbDmaLeft = pStream->cbDmaBlockSize;
+
+ pThis->highspeed = 0;
+
+ /** @todo Check if stream's DMA block size is properly aligned to the set PCM props. */
+
+ sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
+ sb16SpeakerControl(pThis, true /* fOn */);
+}
+
+static inline void sb16DspSeData(PSB16STATE pThis, uint8_t val)
+{
+ LogFlowFunc(("%#x\n", val));
+ if ((size_t) pThis->dsp_out_data_len < sizeof (pThis->dsp_out_data))
+ pThis->dsp_out_data[pThis->dsp_out_data_len++] = val;
+}
+
+static inline uint8_t sb16DspGetData(PSB16STATE pThis)
+{
+ if (pThis->dsp_in_idx)
+ return pThis->dsp_in_data[--pThis->dsp_in_idx];
+ AssertMsgFailed(("DSP input buffer underflow\n"));
+ return 0;
+}
+
+static void sb16DspCmdLookup(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream, uint8_t cmd)
+{
+ LogFlowFunc(("command %#x\n", cmd));
+
+ if (cmd > 0xaf && cmd < 0xd0)
+ {
+ if (cmd & 8) /** @todo Handle recording. */
+ LogFlowFunc(("ADC not yet supported (command %#x)\n", cmd));
+
+ switch (cmd >> 4)
+ {
+ case 11:
+ case 12:
+ break;
+ default:
+ LogFlowFunc(("%#x wrong bits\n", cmd));
+ }
+
+ pThis->dsp_in_needed_bytes = 3;
+ }
+ else
+ {
+ pThis->dsp_in_needed_bytes = 0;
+
+ /** @todo Use a mapping table with
+ * - a command verb (binary search)
+ * - required bytes
+ * - function callback handler
+ */
+
+ switch (cmd)
+ {
+ case 0x03: /* ASP Status */
+ sb16DspSeData(pThis, 0x10); /* pThis->csp_param); */
+ goto warn;
+
+ case 0x04: /* DSP Status (Obsolete) / ASP ??? */
+ pThis->dsp_in_needed_bytes = 1;
+ goto warn;
+
+ case 0x05: /* ASP ??? */
+ pThis->dsp_in_needed_bytes = 2;
+ goto warn;
+
+ case 0x08: /* ??? */
+ /* __asm__ ("int3"); */
+ goto warn;
+
+ case 0x09: /* ??? */
+ sb16DspSeData(pThis, 0xf8);
+ goto warn;
+
+ case 0x0e: /* ??? */
+ pThis->dsp_in_needed_bytes = 2;
+ goto warn;
+
+ case 0x0f: /* ??? */
+ pThis->dsp_in_needed_bytes = 1;
+ goto warn;
+
+ case 0x10: /* Direct mode DAC */
+ pThis->dsp_in_needed_bytes = 1;
+ goto warn;
+
+ case 0x14: /* DAC DMA, 8-bit, uncompressed */
+ pThis->dsp_in_needed_bytes = 2;
+ pStream->cbDmaBlockSize = 0;
+ break;
+
+ case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */
+ sb16DmaCmd8(pDevIns, pThis, pStream, DMA8_AUTO, -1);
+ break;
+
+ case 0x20: /* Direct ADC, Juice/PL */
+ sb16DspSeData(pThis, 0xff);
+ goto warn;
+
+ case 0x35: /* MIDI Read Interrupt + Write Poll (UART) */
+ LogRelMax2(32, ("SB16: MIDI support not implemented yet\n"));
+ break;
+
+ case 0x40: /* Set Time Constant */
+ pStream->time_const = -1;
+ pThis->dsp_in_needed_bytes = 1;
+ break;
+
+ case 0x41: /* Set sample rate for input */
+ pStream->Cfg.Props.uHz = 0; /** @todo r=andy Why do we reset output stuff here? */
+ pStream->time_const = -1;
+ pThis->dsp_in_needed_bytes = 2;
+ break;
+
+ case 0x42: /* Set sample rate for output */
+ pStream->Cfg.Props.uHz = 0;
+ pStream->time_const = -1;
+ pThis->dsp_in_needed_bytes = 2;
+ goto warn;
+
+ case 0x45: /* Continue Auto-Initialize DMA, 8-bit */
+ sb16DspSeData(pThis, 0xaa);
+ goto warn;
+
+ case 0x47: /* Continue Auto-Initialize DMA, 16-bit */
+ break;
+
+ case 0x48: /* Set DMA Block Size */
+ pThis->dsp_in_needed_bytes = 2;
+ break;
+
+ case 0x74: /* DMA DAC, 4-bit ADPCM */
+ pThis->dsp_in_needed_bytes = 2;
+ LogFlowFunc(("4-bit ADPCM not implemented yet\n"));
+ break;
+
+ case 0x75: /* DMA DAC, 4-bit ADPCM Reference */
+ pThis->dsp_in_needed_bytes = 2;
+ LogFlowFunc(("DMA DAC, 4-bit ADPCM Reference not implemented\n"));
+ break;
+
+ case 0x76: /* DMA DAC, 2.6-bit ADPCM */
+ pThis->dsp_in_needed_bytes = 2;
+ LogFlowFunc(("DMA DAC, 2.6-bit ADPCM not implemented yet\n"));
+ break;
+
+ case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */
+ pThis->dsp_in_needed_bytes = 2;
+ LogFlowFunc(("ADPCM reference not implemented yet\n"));
+ break;
+
+ case 0x7d: /* Auto-Initialize DMA DAC, 4-bit ADPCM Reference */
+ LogFlowFunc(("Autio-Initialize DMA DAC, 4-bit ADPCM reference not implemented yet\n"));
+ break;
+
+ case 0x7f: /* Auto-Initialize DMA DAC, 16-bit ADPCM Reference */
+ LogFlowFunc(("Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference not implemented yet\n"));
+ break;
+
+ case 0x80: /* Silence DAC */
+ pThis->dsp_in_needed_bytes = 2;
+ break;
+
+ case 0x90: /* Auto-Initialize DMA DAC, 8-bit (High Speed) */
+ RT_FALL_THROUGH();
+ case 0x91: /* Normal DMA DAC, 8-bit (High Speed) */
+ sb16DmaCmd8(pDevIns, pThis, pStream, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1);
+ break;
+
+ case 0xd0: /* Halt DMA operation. 8bit */
+ sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
+ break;
+
+ case 0xd1: /* Speaker on */
+ sb16SpeakerControl(pThis, true /* fOn */);
+ break;
+
+ case 0xd3: /* Speaker off */
+ sb16SpeakerControl(pThis, false /* fOn */);
+ break;
+
+ case 0xd4: /* Continue DMA operation, 8-bit */
+ /* KQ6 (or maybe Sierras audblst.drv in general) resets
+ the frequency between halt/continue */
+ sb16DmaCmdContinue8(pDevIns, pThis, pStream);
+ break;
+
+ case 0xd5: /* Halt DMA operation, 16-bit */
+ sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
+ break;
+
+ case 0xd6: /* Continue DMA operation, 16-bit */
+ sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
+ break;
+
+ case 0xd9: /* Exit auto-init DMA after this block, 16-bit */
+ pStream->dma_auto = 0;
+ break;
+
+ case 0xda: /* Exit auto-init DMA after this block, 8-bit */
+ pStream->dma_auto = 0;
+ break;
+
+ case 0xe0: /* DSP identification */
+ pThis->dsp_in_needed_bytes = 1;
+ break;
+
+ case 0xe1: /* DSP version */
+ sb16DspSeData(pThis, RT_LO_U8(pStream->HwCfgRuntime.uVer));
+ sb16DspSeData(pThis, RT_HI_U8(pStream->HwCfgRuntime.uVer));
+ break;
+
+ case 0xe2: /* ??? */
+ pThis->dsp_in_needed_bytes = 1;
+ goto warn;
+
+ case 0xe3: /* DSP copyright */
+ {
+ for (int i = sizeof(e3) - 1; i >= 0; --i)
+ sb16DspSeData(pThis, e3[i]);
+ break;
+ }
+
+ case 0xe4: /* Write test register */
+ pThis->dsp_in_needed_bytes = 1;
+ break;
+
+ case 0xe7: /* ??? */
+ LogFlowFunc(("Attempt to probe for ESS (0xe7)?\n"));
+ break;
+
+ case 0xe8: /* Read test register */
+ sb16DspSeData(pThis, pThis->test_reg);
+ break;
+
+ case 0xf2: /* IRQ Request, 8-bit */
+ RT_FALL_THROUGH();
+ case 0xf3: /* IRQ Request, 16-bit */
+ {
+ sb16DspSeData(pThis, 0xaa);
+ pThis->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
+ break;
+ }
+
+ case 0xf8: /* Undocumented, used by old Creative diagnostic programs */
+ sb16DspSeData(pThis, 0);
+ goto warn;
+
+ case 0xf9: /* ??? */
+ pThis->dsp_in_needed_bytes = 1;
+ goto warn;
+
+ case 0xfa: /* ??? */
+ sb16DspSeData(pThis, 0);
+ goto warn;
+
+ case 0xfc: /* ??? */
+ sb16DspSeData(pThis, 0);
+ goto warn;
+
+ default:
+ LogFunc(("Unrecognized DSP command %#x, ignored\n", cmd));
+ break;
+ }
+ }
+
+exit:
+
+ if (!pThis->dsp_in_needed_bytes)
+ pThis->cmd = -1;
+ else
+ pThis->cmd = cmd;
+
+ return;
+
+warn:
+ LogFunc(("warning: command %#x,%d is not truly understood yet\n", cmd, pThis->dsp_in_needed_bytes));
+ goto exit;
+}
+
+DECLINLINE(uint16_t) sb16DspGetLoHi(PSB16STATE pThis)
+{
+ const uint8_t hi = sb16DspGetData(pThis);
+ const uint8_t lo = sb16DspGetData(pThis);
+ return RT_MAKE_U16(lo, hi);
+}
+
+DECLINLINE(uint16_t) sb16DspGetHiLo(PSB16STATE pThis)
+{
+ const uint8_t lo = sb16DspGetData(pThis);
+ const uint8_t hi = sb16DspGetData(pThis);
+ return RT_MAKE_U16(lo, hi);
+}
+
+static void sb16DspCmdComplete(PPDMDEVINS pDevIns, PSB16STATE pThis)
+{
+ LogFlowFunc(("Command %#x, in_index %d, needed_bytes %d\n", pThis->cmd, pThis->dsp_in_idx, pThis->dsp_in_needed_bytes));
+
+ int v0, v1, v2;
+
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /** @ŧodo Improve this. */
+
+ if (pThis->cmd > 0xaf && pThis->cmd < 0xd0)
+ {
+ v2 = sb16DspGetData(pThis);
+ v1 = sb16DspGetData(pThis);
+ v0 = sb16DspGetData(pThis);
+
+ if (pThis->cmd & 8)
+ LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2));
+ else
+ {
+ LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, v0, v1, v2));
+ sb16DmaCmd(pDevIns, pThis, pStream, pThis->cmd, v0, v1 + (v2 << 8));
+ }
+ }
+ else
+ {
+ switch (pThis->cmd)
+ {
+ case 0x04:
+ pThis->csp_mode = sb16DspGetData(pThis);
+ pThis->csp_reg83r = 0;
+ pThis->csp_reg83w = 0;
+ LogFlowFunc(("CSP command 0x04: mode=%#x\n", pThis->csp_mode));
+ break;
+
+ case 0x05:
+ pThis->csp_param = sb16DspGetData(pThis);
+ pThis->csp_value = sb16DspGetData(pThis);
+ LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n", pThis->csp_param, pThis->csp_value));
+ break;
+
+ case 0x0e:
+ v0 = sb16DspGetData(pThis);
+ v1 = sb16DspGetData(pThis);
+ LogFlowFunc(("write CSP register %d <- %#x\n", v1, v0));
+ if (v1 == 0x83)
+ {
+ LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, v0));
+ pThis->csp_reg83[pThis->csp_reg83r % 4] = v0;
+ pThis->csp_reg83r += 1;
+ }
+ else
+ pThis->csp_regs[v1] = v0;
+ break;
+
+ case 0x0f:
+ v0 = sb16DspGetData(pThis);
+ LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", v0, pThis->csp_regs[v0], pThis->csp_mode));
+ if (v0 == 0x83)
+ {
+ LogFlowFunc(("0x83[%d] -> %#x\n", pThis->csp_reg83w, pThis->csp_reg83[pThis->csp_reg83w % 4]));
+ sb16DspSeData(pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]);
+ pThis->csp_reg83w += 1;
+ }
+ else
+ sb16DspSeData(pThis, pThis->csp_regs[v0]);
+ break;
+
+ case 0x10:
+ v0 = sb16DspGetData(pThis);
+ LogFlowFunc(("cmd 0x10 d0=%#x\n", v0));
+ break;
+
+ case 0x14:
+ sb16DmaCmd8(pDevIns, pThis, pStream, 0, sb16DspGetLoHi(pThis) + 1);
+ break;
+
+ case 0x22: /* Sets the master volume. */
+ /** @todo Setting the master volume is not implemented yet. */
+ break;
+
+ case 0x40: /* Sets the timer constant; SB16 is able to use sample rates via 0x41 instead. */
+ pStream->time_const = sb16DspGetData(pThis);
+ LogFlowFunc(("set time const %d\n", pStream->time_const));
+ break;
+
+ case 0x42: /* Sets the input rate (in Hz). */
+#if 0
+ LogFlowFunc(("cmd 0x42 might not do what it think it should\n"));
+#endif
+ RT_FALL_THROUGH(); /** @todo BUGBUG FT2 sets output freq with this, go figure. */
+
+ case 0x41: /* Sets the output rate (in Hz). */
+ pStream->Cfg.Props.uHz = sb16DspGetHiLo(pThis);
+ LogFlowFunc(("set freq to %RU16Hz\n", pStream->Cfg.Props.uHz));
+ break;
+
+ case 0x48:
+ pStream->cbDmaBlockSize = sb16DspGetLoHi(pThis) + 1;
+ LogFlowFunc(("set dma block len %d\n", pStream->cbDmaBlockSize));
+ break;
+
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ /* ADPCM stuff, ignore. */
+ break;
+
+ case 0x80: /* Sets the IRQ. */
+ sb16StreamTransferScheduleNext(pThis, pStream, sb16DspGetLoHi(pThis) + 1);
+ break;
+
+ case 0xe0:
+ v0 = sb16DspGetData(pThis);
+ pThis->dsp_out_data_len = 0;
+ LogFlowFunc(("E0=%#x\n", v0));
+ sb16DspSeData(pThis, ~v0);
+ break;
+
+ case 0xe2:
+ v0 = sb16DspGetData(pThis);
+ LogFlowFunc(("E2=%#x\n", v0));
+ break;
+
+ case 0xe4:
+ pThis->test_reg = sb16DspGetData(pThis);
+ break;
+
+ case 0xf9:
+ v0 = sb16DspGetData(pThis);
+ switch (v0)
+ {
+ case 0x0e:
+ sb16DspSeData(pThis, 0xff);
+ break;
+
+ case 0x0f:
+ sb16DspSeData(pThis, 0x07);
+ break;
+
+ case 0x37:
+ sb16DspSeData(pThis, 0x38);
+ break;
+
+ default:
+ sb16DspSeData(pThis, 0x00);
+ break;
+ }
+ break;
+
+ default:
+ LogRel2(("SB16: Unrecognized command %#x, skipping\n", pThis->cmd));
+ return;
+ }
+ }
+
+ pThis->cmd = -1;
+ return;
+}
+
+static void sb16DspCmdResetLegacy(PSB16STATE pThis)
+{
+ LogFlowFuncEnter();
+
+ /* Disable speaker(s). */
+ sb16SpeakerControl(pThis, false /* fOn */);
+
+ /*
+ * Reset all streams.
+ */
+ for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
+ sb16StreamReset(pThis, &pThis->aStreams[i]);
+}
+
+static void sb16DspCmdReset(PSB16STATE pThis)
+{
+ pThis->mixer_regs[0x82] = 0;
+ pThis->dsp_in_idx = 0;
+ pThis->dsp_out_data_len = 0;
+ pThis->dsp_in_needed_bytes = 0;
+ pThis->nzero = 0;
+ pThis->highspeed = 0;
+ pThis->v2x6 = 0;
+ pThis->cmd = -1;
+
+ sb16DspSeData(pThis, 0xaa);
+
+ sb16DspCmdResetLegacy(pThis);
+}
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortDspWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(pvUser, cb);
+
+ /** @todo Figure out how we can distinguish between streams. DSP port #, e.g. 0x220? */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ LogFlowFunc(("write %#x <- %#x\n", offPort, u32));
+ switch (offPort)
+ {
+ case 0:
+ switch (u32)
+ {
+ case 0x00:
+ {
+ if (pThis->v2x6 == 1)
+ {
+ if (0 && pThis->highspeed)
+ {
+ pThis->highspeed = 0;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
+ sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
+ }
+ else
+ sb16DspCmdReset(pThis);
+ }
+ pThis->v2x6 = 0;
+ break;
+ }
+
+ case 0x01:
+ case 0x03: /* FreeBSD kludge */
+ pThis->v2x6 = 1;
+ break;
+
+ case 0xc6:
+ pThis->v2x6 = 0; /* Prince of Persia, csp.sys, diagnose.exe */
+ break;
+
+ case 0xb8: /* Panic */
+ sb16DspCmdReset(pThis);
+ break;
+
+ case 0x39:
+ sb16DspSeData(pThis, 0x38);
+ sb16DspCmdReset(pThis);
+ pThis->v2x6 = 0x39;
+ break;
+
+ default:
+ pThis->v2x6 = u32;
+ break;
+ }
+ break;
+
+ case 6: /* Write data or command | write status */
+#if 0
+ if (pThis->highspeed)
+ break;
+#endif
+ if (0 == pThis->dsp_in_needed_bytes)
+ {
+ sb16DspCmdLookup(pDevIns, pThis, pStream, u32);
+ }
+ else
+ {
+ if (pThis->dsp_in_idx == sizeof (pThis->dsp_in_data))
+ {
+ AssertMsgFailed(("DSP input data overrun\n"));
+ }
+ else
+ {
+ pThis->dsp_in_data[pThis->dsp_in_idx++] = u32;
+ if (pThis->dsp_in_idx == pThis->dsp_in_needed_bytes)
+ {
+ pThis->dsp_in_needed_bytes = 0;
+ sb16DspCmdComplete(pDevIns, pThis);
+ }
+ }
+ }
+ break;
+
+ default:
+ LogFlowFunc(("offPort=%#x, u32=%#x)\n", offPort, u32));
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortDspRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pvUser, cb);
+
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+
+ uint32_t retval;
+ int ack = 0;
+
+ /** @todo Figure out how we can distinguish between streams. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ /** @todo reject non-byte access?
+ * The spec does not mention a non-byte access so we should check how real hardware behaves. */
+
+ switch (offPort)
+ {
+ case 0: /* reset */
+ retval = 0xff;
+ break;
+
+ case 4: /* read data */
+ if (pThis->dsp_out_data_len)
+ {
+ retval = pThis->dsp_out_data[--pThis->dsp_out_data_len];
+ pThis->last_read_byte = retval;
+ }
+ else
+ {
+ if (pThis->cmd != -1)
+ LogFlowFunc(("empty output buffer for command %#x\n", pThis->cmd));
+ retval = pThis->last_read_byte;
+ /* goto error; */
+ }
+ break;
+
+ case 6: /* 0 can write */
+ retval = pStream->can_write ? 0 : 0x80;
+ break;
+
+ case 7: /* timer interrupt clear */
+ /* LogFlowFunc(("timer interrupt clear\n")); */
+ retval = 0;
+ break;
+
+ case 8: /* data available status | irq 8 ack */
+ retval = (!pThis->dsp_out_data_len || pThis->highspeed) ? 0 : 0x80;
+ if (pThis->mixer_regs[0x82] & 1)
+ {
+ ack = 1;
+ pThis->mixer_regs[0x82] &= ~1;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
+ }
+ break;
+
+ case 9: /* irq 16 ack */
+ retval = 0xff;
+ if (pThis->mixer_regs[0x82] & 2)
+ {
+ ack = 1;
+ pThis->mixer_regs[0x82] &= ~2;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
+ }
+ break;
+
+ default:
+ LogFlowFunc(("warning: sb16IoPortDspRead %#x error\n", offPort));
+ return VERR_IOM_IOPORT_UNUSED;
+ }
+
+ if (!ack)
+ LogFlowFunc(("read %#x -> %#x\n", offPort, retval));
+
+ *pu32 = retval;
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Mixer functions *
+*********************************************************************************************************************************/
+
+static uint8_t sb16MixRegToVol(PSB16STATE pThis, int reg)
+{
+ /* The SB16 mixer has a 0 to -62dB range in 32 levels (2dB each step).
+ * We use a 0 to -96dB range in 256 levels (0.375dB each step).
+ * Only the top 5 bits of a mixer register are used.
+ */
+ uint8_t steps = 31 - (pThis->mixer_regs[reg] >> 3);
+ uint8_t vol = 255 - steps * 16 / 3; /* (2dB*8) / (0.375dB*8) */
+ return vol;
+}
+
+/**
+ * Returns the device's current master volume.
+ *
+ * @param pThis SB16 state.
+ * @param pVol Where to store the master volume information.
+ */
+DECLINLINE(void) sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
+{
+ /* There's no mute switch, only volume controls. */
+ PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x30), sb16MixRegToVol(pThis, 0x31));
+}
+
+/**
+ * Returns the device's current output stream volume.
+ *
+ * @param pThis SB16 state.
+ * @param pVol Where to store the output stream volume information.
+ */
+DECLINLINE(void) sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
+{
+ /* There's no mute switch, only volume controls. */
+ PDMAudioVolumeInitFromStereo(pVol, false /*fMuted*/, sb16MixRegToVol(pThis, 0x32), sb16MixRegToVol(pThis, 0x33));
+}
+
+static void sb16UpdateVolume(PSB16STATE pThis)
+{
+ PDMAUDIOVOLUME VolMaster;
+ sb16GetMasterVolume(pThis, &VolMaster);
+
+ PDMAUDIOVOLUME VolOut;
+ sb16GetPcmOutVolume(pThis, &VolOut);
+
+ /* Combine the master + output stream volume. */
+ PDMAUDIOVOLUME VolCombined;
+ PDMAudioVolumeCombine(&VolCombined, &VolMaster, &VolOut);
+
+ int rc2 = AudioMixerSinkSetVolume(pThis->pSinkOut, &VolCombined);
+ AssertRC(rc2);
+}
+
+static void sb16MixerReset(PSB16STATE pThis)
+{
+ memset(pThis->mixer_regs, 0xff, 0x7f);
+ memset(pThis->mixer_regs + 0x83, 0xff, sizeof (pThis->mixer_regs) - 0x83);
+
+ pThis->mixer_regs[0x02] = 4; /* master volume 3bits */
+ pThis->mixer_regs[0x06] = 4; /* MIDI volume 3bits */
+ pThis->mixer_regs[0x08] = 0; /* CD volume 3bits */
+ pThis->mixer_regs[0x0a] = 0; /* voice volume 2bits */
+
+ /* d5=input filt, d3=lowpass filt, d1,d2=input source */
+ pThis->mixer_regs[0x0c] = 0;
+
+ /* d5=output filt, d1=stereo switch */
+ pThis->mixer_regs[0x0e] = 0;
+
+ /* voice volume L d5,d7, R d1,d3 */
+ pThis->mixer_regs[0x04] = (12 << 4) | 12;
+ /* master ... */
+ pThis->mixer_regs[0x22] = (12 << 4) | 12;
+ /* MIDI ... */
+ pThis->mixer_regs[0x26] = (12 << 4) | 12;
+
+ /* master/voice/MIDI L/R volume */
+ for (int i = 0x30; i < 0x36; i++)
+ pThis->mixer_regs[i] = 24 << 3; /* -14 dB */
+
+ /* treble/bass */
+ for (int i = 0x44; i < 0x48; i++)
+ pThis->mixer_regs[i] = 0x80;
+
+ /* Update the master (mixer) and PCM out volumes. */
+ sb16UpdateVolume(pThis);
+
+ /*
+ * Reset mixer sinks.
+ *
+ * Do the reset here instead of in sb16StreamReset();
+ * the mixer sink(s) might still have data to be processed when an audio stream gets reset.
+ */
+ if (pThis->pSinkOut)
+ AudioMixerSinkReset(pThis->pSinkOut);
+}
+
+static int magic_of_irq(int irq)
+{
+ switch (irq)
+ {
+ case 5:
+ return 2;
+ case 7:
+ return 4;
+ case 9:
+ return 1;
+ case 10:
+ return 8;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("bad irq %d\n", irq));
+ return 2;
+}
+
+static int irq_of_magic(int magic)
+{
+ switch (magic)
+ {
+ case 1:
+ return 9;
+ case 2:
+ return 5;
+ case 4:
+ return 7;
+ case 8:
+ return 10;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("bad irq magic %d\n", magic));
+ return -1;
+}
+
+static int sb16MixerWriteIndex(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val)
+{
+ RT_NOREF(pStream);
+ pThis->mixer_nreg = val;
+ return VINF_SUCCESS;
+}
+
+#ifndef VBOX
+static uint32_t popcount(uint32_t u)
+{
+ u = ((u&0x55555555) + ((u>>1)&0x55555555));
+ u = ((u&0x33333333) + ((u>>2)&0x33333333));
+ u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f));
+ u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff));
+ u = ( u&0x0000ffff) + (u>>16);
+ return u;
+}
+#endif
+
+static uint32_t lsbindex(uint32_t u)
+{
+#ifdef VBOX
+ return u ? ASMBitFirstSetU32(u) - 1 : 32;
+#else
+ return popcount((u & -(int32_t)u) - 1);
+#endif
+}
+
+/* Convert SB16 to SB Pro mixer volume (left). */
+static inline void sb16ConvVolumeL(PSB16STATE pThis, unsigned reg, uint8_t val)
+{
+ /* High nibble in SBP mixer. */
+ pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0x0f) | (val & 0xf0);
+}
+
+/* Convert SB16 to SB Pro mixer volume (right). */
+static inline void sb16ConvVolumeR(PSB16STATE pThis, unsigned reg, uint8_t val)
+{
+ /* Low nibble in SBP mixer. */
+ pThis->mixer_regs[reg] = (pThis->mixer_regs[reg] & 0xf0) | (val >> 4);
+}
+
+/* Convert SB Pro to SB16 mixer volume (left + right). */
+static inline void sb16ConvVolumeOldToNew(PSB16STATE pThis, unsigned reg, uint8_t val)
+{
+ /* Left channel. */
+ pThis->mixer_regs[reg + 0] = (val & 0xf0) | RT_BIT(3);
+ /* Right channel (the register immediately following). */
+ pThis->mixer_regs[reg + 1] = (val << 4) | RT_BIT(3);
+}
+
+
+static int sb16MixerWriteData(PSB16STATE pThis, PSB16STREAM pStream, uint8_t val)
+{
+ bool fUpdateMaster = false;
+ bool fUpdateStream = false;
+
+ LogFlowFunc(("[%#x] <- %#x\n", pThis->mixer_nreg, val));
+
+ switch (pThis->mixer_nreg)
+ {
+ case 0x00:
+ sb16MixerReset(pThis);
+ /* And update the actual volume, too. */
+ fUpdateMaster = true;
+ fUpdateStream = true;
+ break;
+
+ case 0x04: /* Translate from old style voice volume (L/R). */
+ sb16ConvVolumeOldToNew(pThis, 0x32, val);
+ fUpdateStream = true;
+ break;
+
+ case 0x22: /* Translate from old style master volume (L/R). */
+ sb16ConvVolumeOldToNew(pThis, 0x30, val);
+ fUpdateMaster = true;
+ break;
+
+ case 0x26: /* Translate from old style MIDI volume (L/R). */
+ sb16ConvVolumeOldToNew(pThis, 0x34, val);
+ break;
+
+ case 0x28: /* Translate from old style CD volume (L/R). */
+ sb16ConvVolumeOldToNew(pThis, 0x36, val);
+ break;
+
+ case 0x2E: /* Translate from old style line volume (L/R). */
+ sb16ConvVolumeOldToNew(pThis, 0x38, val);
+ break;
+
+ case 0x30: /* Translate to old style master volume (L). */
+ sb16ConvVolumeL(pThis, 0x22, val);
+ fUpdateMaster = true;
+ break;
+
+ case 0x31: /* Translate to old style master volume (R). */
+ sb16ConvVolumeR(pThis, 0x22, val);
+ fUpdateMaster = true;
+ break;
+
+ case 0x32: /* Translate to old style voice volume (L). */
+ sb16ConvVolumeL(pThis, 0x04, val);
+ fUpdateStream = true;
+ break;
+
+ case 0x33: /* Translate to old style voice volume (R). */
+ sb16ConvVolumeR(pThis, 0x04, val);
+ fUpdateStream = true;
+ break;
+
+ case 0x34: /* Translate to old style MIDI volume (L). */
+ sb16ConvVolumeL(pThis, 0x26, val);
+ break;
+
+ case 0x35: /* Translate to old style MIDI volume (R). */
+ sb16ConvVolumeR(pThis, 0x26, val);
+ break;
+
+ case 0x36: /* Translate to old style CD volume (L). */
+ sb16ConvVolumeL(pThis, 0x28, val);
+ break;
+
+ case 0x37: /* Translate to old style CD volume (R). */
+ sb16ConvVolumeR(pThis, 0x28, val);
+ break;
+
+ case 0x38: /* Translate to old style line volume (L). */
+ sb16ConvVolumeL(pThis, 0x2E, val);
+ break;
+
+ case 0x39: /* Translate to old style line volume (R). */
+ sb16ConvVolumeR(pThis, 0x2E, val);
+ break;
+
+ case 0x80:
+ {
+ int irq = irq_of_magic(val);
+ LogRelMax2(64, ("SB16: Setting IRQ to %d\n", irq));
+ if (irq > 0)
+ pStream->HwCfgRuntime.uIrq = irq;
+ break;
+ }
+
+ case 0x81:
+ {
+ int dma = lsbindex(val & 0xf);
+ int hdma = lsbindex(val & 0xf0);
+ if ( dma != pStream->HwCfgRuntime.uDmaChanLow
+ || hdma != pStream->HwCfgRuntime.uDmaChanHigh)
+ {
+ LogRelMax2(64, ("SB16: Attempt to change DMA 8bit %d(%d), 16bit %d(%d)\n",
+ dma, pStream->HwCfgRuntime.uDmaChanLow, hdma, pStream->HwCfgRuntime.uDmaChanHigh));
+ }
+#if 0
+ pStream->dma = dma;
+ pStream->hdma = hdma;
+#endif
+ break;
+ }
+
+ case 0x82:
+ LogRelMax2(64, ("SB16: Attempt to write into IRQ status register to %#x\n", val));
+ return VINF_SUCCESS;
+
+ default:
+ if (pThis->mixer_nreg >= 0x80)
+ LogFlowFunc(("attempt to write mixer[%#x] <- %#x\n", pThis->mixer_nreg, val));
+ break;
+ }
+
+ pThis->mixer_regs[pThis->mixer_nreg] = val;
+
+ /* Update the master (mixer) volume. */
+ if ( fUpdateMaster
+ || fUpdateStream)
+ {
+ sb16UpdateVolume(pThis);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortMixerWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(pvUser);
+
+ /** @todo Figure out how we can distinguish between streams. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ switch (cb)
+ {
+ case 1:
+ switch (offPort)
+ {
+ case 0:
+ sb16MixerWriteIndex(pThis, pStream, u32);
+ break;
+ case 1:
+ sb16MixerWriteData(pThis, pStream, u32);
+ break;
+ default:
+ AssertFailed();
+ }
+ break;
+ case 2:
+ sb16MixerWriteIndex(pThis, pStream, u32 & 0xff);
+ sb16MixerWriteData(pThis, pStream, (u32 >> 8) & 0xff);
+ break;
+ default:
+ ASSERT_GUEST_MSG_FAILED(("offPort=%#x cb=%d u32=%#x\n", offPort, cb, u32));
+ break;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) sb16IoPortMixerRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(pvUser, cb, offPort);
+
+#ifndef DEBUG_SB16_MOST
+ if (pThis->mixer_nreg != 0x82)
+ LogFlowFunc(("sb16IoPortMixerRead[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
+#else
+ LogFlowFunc(("sb16IoPortMixerRead[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
+#endif
+ *pu32 = pThis->mixer_regs[pThis->mixer_nreg];
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* DMA handling *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for sb16DMARead.
+ */
+
+/**
+ * @callback_method_impl{FNDMATRANSFERHANDLER,
+ * Worker callback for both DMA channels.}
+ */
+static DECLCALLBACK(uint32_t) sb16DMARead(PPDMDEVINS pDevIns, void *pvUser, unsigned uChannel, uint32_t off, uint32_t cb)
+
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ AssertPtr(pThis);
+ PSB16STREAM pStream = (PSB16STREAM)pvUser;
+ AssertPtr(pStream);
+
+ int till, copy, free;
+
+ if (pStream->cbDmaBlockSize <= 0)
+ {
+ LogFlowFunc(("invalid block size=%d uChannel=%d off=%d cb=%d\n", pStream->cbDmaBlockSize, uChannel, off, cb));
+ return off;
+ }
+
+ if (pStream->cbDmaLeft < 0)
+ pStream->cbDmaLeft = pStream->cbDmaBlockSize;
+
+ free = cb;
+
+ copy = free;
+ till = pStream->cbDmaLeft;
+
+ Log4Func(("pos=%d %d, till=%d, len=%d\n", off, free, till, cb));
+
+ if (copy >= till)
+ {
+ if (0 == pStream->dma_auto)
+ {
+ copy = till;
+ }
+ else
+ {
+ if (copy >= till + pStream->cbDmaBlockSize)
+ copy = till; /* Make sure we won't skip IRQs. */
+ }
+ }
+
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, copy);
+
+ uint32_t written = 0; /* Shut up GCC. */
+ int rc = sb16StreamDoDmaOutput(pThis, pStream, uChannel, off, cb, copy, &written);
+ AssertRC(rc);
+
+ /** @todo Convert the rest to uin32_t / size_t. */
+ off = (off + (int)written) % cb;
+ pStream->cbDmaLeft -= (int)written; /** @todo r=andy left_till_irq can be < 0. Correct? Revisit this. */
+
+ Log3Func(("pos %d/%d, free=%d, till=%d, copy=%d, written=%RU32, block_size=%d\n",
+ off, cb, free, pStream->cbDmaLeft, copy, copy, pStream->cbDmaBlockSize));
+
+ if (pStream->cbDmaLeft <= 0)
+ {
+ pThis->mixer_regs[0x82] |= (uChannel & 4) ? 2 : 1;
+
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
+
+ if (0 == pStream->dma_auto) /** @todo r=andy BUGBUG Why do we first assert the IRQ if dma_auto is 0? Revisit this. */
+ {
+ sb16StreamControl(pDevIns, pThis, pStream, false /* fRun */);
+ sb16SpeakerControl(pThis, false /* fOn */);
+ }
+ }
+
+ while (pStream->cbDmaLeft <= 0)
+ pStream->cbDmaLeft += pStream->cbDmaBlockSize;
+
+ return off;
+}
+
+
+/*********************************************************************************************************************************
+* Timer-related code *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{PFNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(hTimer, pThis);
+
+ PSB16STREAM pStream = (PSB16STREAM)pvUser;
+ AssertPtrReturnVoid(pStream);
+
+ LogFlowFuncEnter();
+
+ pStream->can_write = 1;
+ PDMDevHlpISASetIrq(pDevIns, pStream->HwCfgRuntime.uIrq, 1);
+}
+
+/**
+ * Sets the stream's I/O timer to a new expiration time.
+ *
+ * @param pDevIns The device instance.
+ * @param pStream SB16 stream to set timer for.
+ * @param cTicksToDeadline The number of ticks to the new deadline.
+ */
+DECLINLINE(void) sb16TimerSet(PPDMDEVINS pDevIns, PSB16STREAM pStream, uint64_t cTicksToDeadline)
+{
+ int rc = PDMDevHlpTimerSetRelative(pDevIns, pStream->hTimerIO, cTicksToDeadline, NULL /*pu64Now*/);
+ AssertRC(rc);
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ STAM_PROFILE_START(&pThis->StatTimerIO, a);
+
+ PSB16STREAM pStream = (PSB16STREAM)pvUser;
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(hTimer == pStream->hTimerIO);
+
+ const uint64_t cTicksNow = PDMDevHlpTimerGet(pDevIns, pStream->hTimerIO);
+
+ pStream->tsTimerIO = cTicksNow;
+
+ PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
+ AssertPtrReturnVoid(pSink);
+
+ const bool fSinkActive = AudioMixerSinkIsActive(pSink);
+
+ LogFlowFunc(("fSinkActive=%RTbool\n", fSinkActive));
+
+ /* Schedule the next transfer. */
+ PDMDevHlpDMASchedule(pDevIns);
+
+ if (fSinkActive)
+ {
+ /** @todo adjust cTicks down by now much cbOutMin represents. */
+ sb16TimerSet(pDevIns, pStream, pStream->cTicksTimerIOInterval);
+ }
+
+ AudioMixerSinkSignalUpdateJob(pSink);
+
+ STAM_PROFILE_STOP(&pThis->StatTimerIO, a);
+}
+
+
+/*********************************************************************************************************************************
+* LUN (driver) management *
+*********************************************************************************************************************************/
+
+/**
+ * Retrieves a specific driver stream of a SB16 driver.
+ *
+ * @returns Pointer to driver stream if found, or NULL if not found.
+ * @param pDrv Driver to retrieve driver stream for.
+ * @param enmDir Stream direction to retrieve.
+ * @param enmPath Stream destination / source to retrieve.
+ */
+static PSB16DRIVERSTREAM sb16GetDrvStream(PSB16DRIVER pDrv, PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ PSB16DRIVERSTREAM pDrvStream = NULL;
+
+ if (enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmPath=%d\n", enmPath));
+
+ switch (enmPath)
+ {
+ case PDMAUDIOPATH_OUT_FRONT:
+ pDrvStream = &pDrv->Out;
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ else
+ Assert(enmDir == PDMAUDIODIR_IN /** @todo Recording not implemented yet. */);
+
+ return pDrvStream;
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to add driver stream to.
+ * @param pCfg Stream configuration to use.
+ * @param pDrv Driver stream to add.
+ */
+static int sb16AddDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv)
+{
+ AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_NOT_IMPLEMENTED); /* We don't support recording for SB16 so far. */
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfg->szName));
+
+ int rc;
+ PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, pCfg->enmDir, pCfg->enmPath);
+ if (pDrvStream)
+ {
+ AssertMsg(pDrvStream->pMixStrm == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
+
+ PAUDMIXSTREAM pMixStrm;
+ rc = AudioMixerSinkCreateStream(pMixSink, pDrv->pConnector, pCfg, pDevIns, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+ else
+ AudioMixerStreamDestroy(pMixStrm, pDevIns, true /*fImmediate*/);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 state.
+ * @param pMixSink Mixer sink to add stream to.
+ * @param pCfg Stream configuration to use.
+ */
+static int sb16AddDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink, PCPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+
+ int rc;
+ if (AudioHlpStreamCfgIsValid(pCfg))
+ {
+ rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props, pCfg->Device.cMsSchedulingHint);
+ if (RT_SUCCESS(rc))
+ {
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ int rc2 = sb16AddDrvStream(pDevIns, pMixSink, pCfg, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("Attaching stream failed with %Rrc\n", rc2));
+
+ /* Do not pass failure to rc here, as there might be drivers which aren't
+ * configured / ready yet. */
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Removes a driver stream from a specific mixer sink.
+ *
+ * @param pDevIns The device instance.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ * @param pDrv Driver stream to remove.
+ */
+static void sb16RemoveDrvStream(PPDMDEVINS pDevIns, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir,
+ PDMAUDIOPATH enmPath, PSB16DRIVER pDrv)
+{
+ PSB16DRIVERSTREAM pDrvStream = sb16GetDrvStream(pDrv, enmDir, enmPath);
+ if (pDrvStream)
+ {
+ if (pDrvStream->pMixStrm)
+ {
+ LogFlowFunc(("[LUN#%RU8]\n", pDrv->uLUN));
+
+ AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm);
+
+ AudioMixerStreamDestroy(pDrvStream->pMixStrm, pDevIns, false /*fImmediate*/);
+ pDrvStream->pMixStrm = NULL;
+ }
+ }
+}
+
+/**
+ * Removes all driver streams from a specific mixer sink.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 state.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param enmPath Stream destination / source to remove.
+ */
+static void sb16RemoveDrvStreams(PPDMDEVINS pDevIns, PSB16STATE pThis, PAUDMIXSINK pMixSink,
+ PDMAUDIODIR enmDir, PDMAUDIOPATH enmPath)
+{
+ AssertPtrReturnVoid(pMixSink);
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ sb16RemoveDrvStream(pDevIns, pMixSink, enmDir, enmPath, pDrv);
+ }
+}
+
+/**
+ * Adds a specific SB16 driver to the driver chain.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 device state.
+ * @param pDrv The SB16 driver to add.
+ */
+static int sb16AddDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
+ {
+ if (AudioHlpStreamCfgIsValid(&pThis->aStreams[i].Cfg))
+ {
+ int rc2 = sb16AddDrvStream(pDevIns, sb16StreamIndexToSink(pThis, pThis->aStreams[i].uIdx),
+ &pThis->aStreams[i].Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Removes a specific SB16 driver from the driver chain and destroys its
+ * associated streams.
+ *
+ * This is only used by sb16Detach.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 device state.
+ * @param pDrv SB16 driver to remove.
+ */
+static void sb16RemoveDrv(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16DRIVER pDrv)
+{
+ RT_NOREF(pDevIns);
+
+ /** @todo We only implement one single output (playback) stream at the moment. */
+
+ if (pDrv->Out.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThis->pSinkOut, pDrv->Out.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Out.pMixStrm, pDevIns, true /*fImmediate*/);
+ pDrv->Out.pMixStrm = NULL;
+ }
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+
+/*********************************************************************************************************************************
+* Stream handling *
+*********************************************************************************************************************************/
+
+static int sb16StreamDoDmaOutput(PSB16STATE pThis, PSB16STREAM pStream, int uDmaChan, uint32_t offDma, uint32_t cbDma,
+ uint32_t cbToRead, uint32_t *pcbRead)
+{
+ uint32_t cbFree = (uint32_t)RTCircBufFree(pStream->State.pCircBuf);
+ //Assert(cbToRead <= cbFree); /** @todo Add statistics for overflows. */
+ cbToRead = RT_MIN(cbToRead, cbFree);
+
+ uint32_t cbReadTotal = 0;
+ while (cbToRead)
+ {
+ void *pv = NULL;
+ size_t cb = 0;
+ RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, RT_MIN(cbDma - offDma, cbToRead), &pv, &cb);
+
+ uint32_t cbRead = 0;
+ int rc = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, uDmaChan, pv, offDma, (uint32_t)cb, &cbRead);
+ if (RT_SUCCESS(rc))
+ Assert(cbRead == cb);
+ else
+ {
+ AssertMsgFailed(("Reading from DMA failed: %Rrc (cbReadTotal=%#x)\n", rc, cbReadTotal));
+ RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, 0);
+ if (cbReadTotal > 0)
+ break;
+ *pcbRead = 0;
+ return rc;
+ }
+
+ if (RT_LIKELY(!pStream->Dbg.Runtime.pFileDMA))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pv, cbRead);
+
+ RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbRead);
+
+ Assert(cbToRead >= cbRead);
+ pStream->State.offWrite += cbRead;
+ offDma = (offDma + cbRead) % cbDma;
+ cbReadTotal += cbRead;
+ cbToRead -= cbRead;
+ }
+
+ *pcbRead = cbReadTotal;
+
+ /* Update buffer stats. */
+ pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Enables or disables a SB16 audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis The SB16 state.
+ * @param pStream The SB16 stream to enable or disable.
+ * @param fEnable Whether to enable or disable the stream.
+ * @param fForce Whether to force re-opening the stream or not.
+ * Otherwise re-opening only will happen if the PCM properties have changed.
+ */
+static int sb16StreamEnable(PSB16STATE pThis, PSB16STREAM pStream, bool fEnable, bool fForce)
+{
+ if ( !fForce
+ && fEnable == pStream->State.fEnabled)
+ return VINF_SUCCESS;
+
+ LogFlowFunc(("fEnable=%RTbool, fForce=%RTbool, fStreamEnabled=%RTbool\n", fEnable, fForce, pStream->State.fEnabled));
+
+ PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
+ AssertPtrReturn(pSink, VERR_INTERNAL_ERROR_2);
+
+ /* We only need to register the AIO update job the first time around as the requence doesn't change. */
+ int rc;
+ if (fEnable && !pStream->State.fRegisteredAsyncUpdateJob)
+ {
+ rc = AudioMixerSinkAddUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream, RT_MS_1SEC / pStream->uTimerHz);
+ AssertRC(rc);
+ pStream->State.fRegisteredAsyncUpdateJob = RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS;
+ }
+
+ /* Tell the mixer. */
+ if (fEnable)
+ {
+ rc = AudioMixerSinkStart(pSink);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ {
+ rc = AudioMixerSinkDrainAndStop(pSink, pStream->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStream->State.pCircBuf) : 0);
+ AssertRCReturn(rc, rc);
+ }
+
+ pStream->State.fEnabled = fEnable;
+
+ return rc;
+}
+
+/**
+ * Retrieves the audio mixer sink of a corresponding SB16 stream.
+ *
+ * @returns Pointer to audio mixer sink if found, or NULL if not found / invalid.
+ * @param pThis The SB16 state.
+ * @param uIdx Stream index to get audio mixer sink for.
+ */
+DECLINLINE(PAUDMIXSINK) sb16StreamIndexToSink(PSB16STATE pThis, uint8_t uIdx)
+{
+ AssertReturn(uIdx <= SB16_MAX_STREAMS, NULL);
+
+ /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
+ if (uIdx == SB16_IDX_OUT)
+ return pThis->pSinkOut; /* Can be NULL if not configured / set up yet. */
+
+ AssertMsgFailed(("No sink attached (yet) for index %RU8\n", uIdx));
+ return NULL;
+}
+
+/**
+ * Returns the audio direction of a specified stream descriptor.
+ *
+ * @returns Audio direction.
+ * @param uIdx Stream index to get audio direction for.
+ */
+DECLINLINE(PDMAUDIODIR) sb16GetDirFromIndex(uint8_t uIdx)
+{
+ AssertReturn(uIdx <= SB16_MAX_STREAMS, PDMAUDIODIR_INVALID);
+
+ /* Dead simple for now; make this more sophisticated if we have more stuff to cover. */
+ if (uIdx == SB16_IDX_OUT)
+ return PDMAUDIODIR_OUT;
+
+ return PDMAUDIODIR_INVALID;
+}
+
+/**
+ * Creates a SB16 audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis The SB16 state.
+ * @param pStream The SB16 stream to create.
+ * @param uIdx Stream index to assign.
+ */
+static int sb16StreamCreate(PSB16STATE pThis, PSB16STREAM pStream, uint8_t uIdx)
+{
+ LogFlowFuncEnter();
+
+ pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled;
+
+ if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ int rc2 = AudioHlpFileCreateF(&pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_FLAGS_NONE, AUDIOHLPFILETYPE_WAV,
+ pThis->Dbg.pszOutPath, AUDIOHLPFILENAME_FLAGS_NONE, 0 /*uInstance*/,
+ sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_IN
+ ? "sb16StreamWriteSD%RU8" : "sb16StreamReadSD%RU8", pStream->uIdx);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
+ }
+
+ pStream->uIdx = uIdx;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a SB16 audio stream.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 state.
+ * @param pStream The SB16 stream to destroy.
+ */
+static int sb16StreamDestroy(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
+{
+ LogFlowFuncEnter();
+
+ sb16StreamClose(pDevIns, pThis, pStream);
+
+ if (pStream->State.fRegisteredAsyncUpdateJob)
+ {
+ PAUDMIXSINK pSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
+ if (pSink)
+ AudioMixerSinkRemoveUpdateJob(pSink, sb16StreamUpdateAsyncIoJob, pStream);
+ pStream->State.fRegisteredAsyncUpdateJob = false;
+ }
+
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ AudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA);
+ pStream->Dbg.Runtime.pFileDMA = NULL;
+ }
+
+ pStream->uIdx = UINT8_MAX;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Resets a SB16 stream.
+ *
+ * @param pThis The SB16 state.
+ * @param pStream The SB16 stream to reset.
+ */
+static void sb16StreamReset(PSB16STATE pThis, PSB16STREAM pStream)
+{
+ LogFlowFuncEnter();
+
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
+ if (pStream->dma_auto)
+ {
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 0);
+
+ pStream->dma_auto = 0;
+ }
+
+ sb16StreamControl(pThis->pDevInsR3, pThis, pStream, false /* fRun */);
+ sb16StreamEnable(pThis, pStream, false /* fEnable */, false /* fForce */);
+
+ switch (pStream->uIdx)
+ {
+ case SB16_IDX_OUT:
+ {
+ pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
+ pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+
+ PDMAudioPropsInit(&pStream->Cfg.Props, 1 /* 8-bit */, false /* fSigned */, 1 /* Mono */, 11025 /* uHz */);
+ RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
+ break;
+ }
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ pStream->cbDmaLeft = 0;
+ pStream->cbDmaBlockSize = 0;
+ pStream->can_write = 1; /** @ŧodo r=andy BUGBUG Figure out why we (still) need this. */
+
+ /** @todo Also reset corresponding DSP values here? */
+}
+
+/**
+ * Opens a SB16 stream with its current mixer settings.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The SB16 device state.
+ * @param pStream The SB16 stream to open.
+ *
+ * @note This currently only supports the one and only output stream.
+ */
+static int sb16StreamOpen(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
+{
+ LogFlowFuncEnter();
+ AssertLogRelReturn(PDMAudioPropsAreValid(&pStream->Cfg.Props), VERR_INTERNAL_ERROR_5);
+
+ switch (pStream->uIdx)
+ {
+ case SB16_IDX_OUT:
+ pStream->Cfg.enmDir = PDMAUDIODIR_OUT;
+ pStream->Cfg.enmPath = PDMAUDIOPATH_OUT_FRONT;
+
+ RTStrCopy(pStream->Cfg.szName, sizeof(pStream->Cfg.szName), "Output");
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+
+ LogRel2(("SB16: (Re-)Opening stream '%s' (%RU32Hz, %RU8 channels, %s%RU8)\n", pStream->Cfg.szName, pStream->Cfg.Props.uHz,
+ PDMAudioPropsChannels(&pStream->Cfg.Props), pStream->Cfg.Props.fSigned ? "S" : "U",
+ PDMAudioPropsSampleBits(&pStream->Cfg.Props)));
+
+ /* (Re-)create the stream's internal ring buffer. */
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ /** @todo r=bird: two DMA periods is probably too little. */
+ const uint32_t cbCircBuf = PDMAudioPropsMilliToBytes(&pStream->Cfg.Props,
+ (RT_MS_1SEC / pStream->uTimerHz) * 2 /* Use double buffering here */);
+
+ int rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBuf);
+ AssertRCReturn(rc, rc);
+ pStream->State.StatDmaBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf);
+
+ /* Set scheduling hint. */
+ pStream->Cfg.Device.cMsSchedulingHint = RT_MS_1SEC / RT_MIN(pStream->uTimerHz, 1);
+
+ PAUDMIXSINK pMixerSink = sb16StreamIndexToSink(pThis, pStream->uIdx);
+ AssertPtrReturn(pMixerSink, VERR_INVALID_POINTER);
+
+ sb16RemoveDrvStreams(pDevIns, pThis,
+ sb16StreamIndexToSink(pThis, pStream->uIdx), pStream->Cfg.enmDir, pStream->Cfg.enmPath);
+
+ rc = sb16AddDrvStreams(pDevIns, pThis, pMixerSink, &pStream->Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (RT_LIKELY(!pStream->Dbg.Runtime.fEnabled))
+ { /* likely */ }
+ else
+ {
+ /* Make sure to close + delete a former debug file, as the PCM format has changed (e.g. U8 -> S16). */
+ if (AudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA))
+ {
+ AudioHlpFileClose(pStream->Dbg.Runtime.pFileDMA);
+ AudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
+ }
+
+ int rc2 = AudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, AUDIOHLPFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Cfg.Props);
+ AssertRC(rc2);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes a SB16 stream.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis SB16 state.
+ * @param pStream The SB16 stream to close.
+ */
+static void sb16StreamClose(PPDMDEVINS pDevIns, PSB16STATE pThis, PSB16STREAM pStream)
+{
+ RT_NOREF(pDevIns, pThis, pStream);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do in here right now. */
+}
+
+static void sb16StreamTransferScheduleNext(PSB16STATE pThis, PSB16STREAM pStream, uint32_t cbBytes)
+{
+ RT_NOREF(pStream);
+
+ uint64_t const uTimerHz = PDMDevHlpTimerGetFreq(pThis->pDevInsR3, pThis->hTimerIRQ);
+
+ const uint64_t usBytes = PDMAudioPropsBytesToMicro(&pStream->Cfg.Props, cbBytes);
+ const uint64_t cTransferTicks = PDMDevHlpTimerFromMicro(pThis->pDevInsR3, pThis->hTimerIRQ, usBytes);
+
+ LogFlowFunc(("%RU32 bytes -> %RU64 ticks\n", cbBytes, cTransferTicks));
+
+ if (cTransferTicks < uTimerHz / 1024) /** @todo Explain this. */
+ {
+ LogFlowFunc(("IRQ\n"));
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pStream->HwCfgRuntime.uIrq, 1);
+ }
+ else
+ {
+ LogFlowFunc(("Scheduled\n"));
+ PDMDevHlpTimerSetRelative(pThis->pDevInsR3, pThis->hTimerIRQ, cTransferTicks, NULL);
+ }
+}
+
+
+/**
+ * Output streams: Pushes data to the mixer.
+ *
+ * @param pStream The SB16 stream.
+ * @param pSink The mixer sink to push to.
+ */
+static void sb16StreamPushToMixer(PSB16STREAM pStream, PAUDMIXSINK pSink)
+{
+#ifdef LOG_ENABLED
+ uint64_t const offReadOld = pStream->State.offRead;
+#endif
+ pStream->State.offRead = AudioMixerSinkTransferFromCircBuf(pSink,
+ pStream->State.pCircBuf,
+ pStream->State.offRead,
+ pStream->uIdx,
+ /** @todo pStream->Dbg.Runtime.fEnabled
+ ? pStream->Dbg.Runtime.pFileStream :*/ NULL);
+
+ Log3Func(("[SD%RU8] transferred=%#RX64 bytes -> @%#RX64\n", pStream->uIdx,
+ pStream->State.offRead - offReadOld, pStream->State.offRead));
+
+ /* Update buffer stats. */
+ pStream->State.StatDmaBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
+}
+
+
+/**
+ * @callback_method_impl{FNAUDMIXSINKUPDATE}
+ *
+ * For output streams this moves data from the internal DMA buffer (in which
+ * ichac97R3StreamUpdateDma put it), thru the mixer and to the various backend
+ * audio devices.
+ */
+static DECLCALLBACK(void) sb16StreamUpdateAsyncIoJob(PPDMDEVINS pDevIns, PAUDMIXSINK pSink, void *pvUser)
+{
+ PSB16STATE const pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ PSB16STREAM const pStream = (PSB16STREAM)pvUser;
+ Assert(pStream->uIdx == (uintptr_t)(pStream - &pThis->aStreams[0]));
+ Assert(pSink == sb16StreamIndexToSink(pThis, pStream->uIdx));
+ RT_NOREF(pThis);
+
+ /*
+ * Output.
+ */
+ if (sb16GetDirFromIndex(pStream->uIdx) == PDMAUDIODIR_OUT)
+ sb16StreamPushToMixer(pStream, pSink);
+ /*
+ * No input streams at present.
+ */
+ else
+ AssertFailed();
+}
+
+
+/*********************************************************************************************************************************
+* Saved state handling *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ /** Currently the saved state only contains the one-and-only output stream. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uIrq);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanLow);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uDmaChanHigh);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uPort);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgDefault.uVer);
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * Worker for sb16SaveExec.
+ */
+static int sb16Save(PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM, PSB16STATE pThis)
+{
+ /* The saved state only contains the one-and-only output stream. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uIrq);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanLow);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uDmaChanHigh);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uPort);
+ pHlp->pfnSSMPutS32(pSSM, pStream->HwCfgRuntime.uVer);
+ pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_idx);
+ pHlp->pfnSSMPutS32(pSSM, pThis->dsp_out_data_len);
+
+ pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsChannels(&pStream->Cfg.Props) >= 2 ? 1 : 0);
+ pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsIsSigned(&pStream->Cfg.Props) ? 1 : 0);
+ pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsSampleBits(&pStream->Cfg.Props));
+ pHlp->pfnSSMPutU32(pSSM, 0); /* Legacy; was PDMAUDIOFMT, unused now. */
+
+ pHlp->pfnSSMPutS32(pSSM, pStream->dma_auto);
+ pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaBlockSize);
+ pHlp->pfnSSMPutS32(pSSM, pStream->fifo);
+ pHlp->pfnSSMPutS32(pSSM, PDMAudioPropsHz(&pStream->Cfg.Props));
+ pHlp->pfnSSMPutS32(pSSM, pStream->time_const);
+ pHlp->pfnSSMPutS32(pSSM, 0); /* Legacy; was speaker control (on/off) for output stream. */
+ pHlp->pfnSSMPutS32(pSSM, pThis->dsp_in_needed_bytes);
+ pHlp->pfnSSMPutS32(pSSM, pThis->cmd);
+ pHlp->pfnSSMPutS32(pSSM, pStream->fDmaUseHigh);
+ pHlp->pfnSSMPutS32(pSSM, pThis->highspeed);
+ pHlp->pfnSSMPutS32(pSSM, pStream->can_write);
+ pHlp->pfnSSMPutS32(pSSM, pThis->v2x6);
+
+ pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->csp_value);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->csp_mode);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->csp_param); /* Bug compatible! */
+ pHlp->pfnSSMPutMem(pSSM, pThis->csp_regs, 256);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->csp_index);
+ pHlp->pfnSSMPutMem(pSSM, pThis->csp_reg83, 4);
+ pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83r);
+ pHlp->pfnSSMPutS32(pSSM, pThis->csp_reg83w);
+
+ pHlp->pfnSSMPutMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
+ pHlp->pfnSSMPutMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
+ pHlp->pfnSSMPutU8 (pSSM, pThis->test_reg);
+ pHlp->pfnSSMPutU8 (pSSM, pThis->last_read_byte);
+
+ pHlp->pfnSSMPutS32(pSSM, pThis->nzero);
+ pHlp->pfnSSMPutS32(pSSM, pStream->cbDmaLeft);
+ pHlp->pfnSSMPutS32(pSSM, pStream->State.fEnabled ? 1 : 0);
+ /* The stream's bitrate. Needed for backwards (legacy) compatibility. */
+ pHlp->pfnSSMPutS32(pSSM, AudioHlpCalcBitrate(PDMAudioPropsSampleBits(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
+ PDMAudioPropsHz(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props),
+ PDMAudioPropsChannels(&pThis->aStreams[SB16_IDX_OUT].Cfg.Props)));
+ /* Block size alignment, superfluous and thus not saved anymore. Needed for backwards (legacy) compatibility. */
+ pHlp->pfnSSMPutS32(pSSM, 0);
+
+ pHlp->pfnSSMPutS32(pSSM, pThis->mixer_nreg);
+ return pHlp->pfnSSMPutMem(pSSM, pThis->mixer_regs, 256);
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) sb16SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ sb16LiveExec(pDevIns, pSSM, 0);
+ return sb16Save(pHlp, pSSM, pThis);
+}
+
+/**
+ * Worker for sb16LoadExec.
+ */
+static int sb16Load(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PSB16STATE pThis)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT]; /* The saved state only contains the one-and-only output stream. */
+ int rc;
+
+ int32_t i32Tmp;
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
+ pStream->HwCfgRuntime.uIrq = i32Tmp; /* IRQ. */
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
+ pStream->HwCfgRuntime.uDmaChanLow = i32Tmp; /* Low (8-bit) DMA channel. */
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp);
+ pStream->HwCfgRuntime.uDmaChanHigh = i32Tmp; /* High (16-bit) DMA channel. */
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Used I/O port. */
+ pStream->HwCfgRuntime.uPort = i32Tmp;
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* DSP version running. */
+ pStream->HwCfgRuntime.uVer = i32Tmp;
+ pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_idx);
+ pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_out_data_len);
+
+ rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Number of channels. */
+ AssertRCReturn(rc, rc);
+ AssertReturn((uint32_t)i32Tmp <= 1, VERR_INVALID_PARAMETER); /* Paranoia. */
+ if (i32Tmp) /* PDMAudioPropsSetChannels() will assert if channels are 0 (will be re-set on DMA run command). */
+ PDMAudioPropsSetChannels(&pStream->Cfg.Props, (uint8_t)i32Tmp);
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Signed format bit. */
+ pStream->Cfg.Props.fSigned = i32Tmp != 0;
+ rc = pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: Sample size in bits. */
+ AssertRCReturn(rc, rc);
+ if (i32Tmp) /* PDMAudioPropsSetSampleSize() will assert if sample size is 0 (will be re-set on DMA run command). */
+ PDMAudioPropsSetSampleSize(&pStream->Cfg.Props, (uint8_t)(i32Tmp / 8));
+
+ pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was PDMAUDIOFMT, unused now. */
+ pHlp->pfnSSMGetS32(pSSM, &pStream->dma_auto);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaBlockSize);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->fifo);
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp); pStream->Cfg.Props.uHz = i32Tmp;
+ pHlp->pfnSSMGetS32(pSSM, &pStream->time_const);
+ pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was speaker (on / off) for output stream. */
+ pHlp->pfnSSMGetS32(pSSM, &pThis->dsp_in_needed_bytes);
+ pHlp->pfnSSMGetS32(pSSM, &pThis->cmd);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->fDmaUseHigh); /* Output stream: Whether to use the high or low DMA channel. */
+ pHlp->pfnSSMGetS32(pSSM, &pThis->highspeed);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->can_write);
+ pHlp->pfnSSMGetS32(pSSM, &pThis->v2x6);
+
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_value);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_mode);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */
+ pHlp->pfnSSMGetMem(pSSM, pThis->csp_regs, 256);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->csp_index);
+ pHlp->pfnSSMGetMem(pSSM, pThis->csp_reg83, 4);
+ pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83r);
+ pHlp->pfnSSMGetS32(pSSM, &pThis->csp_reg83w);
+
+ pHlp->pfnSSMGetMem(pSSM, pThis->dsp_in_data, sizeof(pThis->dsp_in_data));
+ pHlp->pfnSSMGetMem(pSSM, pThis->dsp_out_data, sizeof(pThis->dsp_out_data));
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->test_reg);
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->last_read_byte);
+
+ pHlp->pfnSSMGetS32(pSSM, &pThis->nzero);
+ pHlp->pfnSSMGetS32(pSSM, &pStream->cbDmaLeft);
+ pHlp->pfnSSMGetS32(pSSM, &i32Tmp); /* Output stream: DMA currently running bit. */
+ const bool fStreamEnabled = i32Tmp != 0;
+ pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's current bitrate (in bytes). */
+ pHlp->pfnSSMSkip (pSSM, sizeof(int32_t)); /* Legacy; was the output stream's DMA block alignment. */
+
+ int32_t mixer_nreg = 0;
+ rc = pHlp->pfnSSMGetS32(pSSM, &mixer_nreg);
+ AssertRCReturn(rc, rc);
+ pThis->mixer_nreg = (uint8_t)mixer_nreg;
+ rc = pHlp->pfnSSMGetMem(pSSM, pThis->mixer_regs, 256);
+ AssertRCReturn(rc, rc);
+
+ if (fStreamEnabled)
+ {
+ /* Sanity: If stream is going be enabled, PCM props must be valid. Otherwise the saved state is borked somehow. */
+ AssertMsgReturn(AudioHlpPcmPropsAreValidAndSupported(&pStream->Cfg.Props),
+ ("PCM properties for stream #%RU8 are invalid\n", pStream->uIdx), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ sb16StreamControl(pDevIns, pThis, pStream, true /* fRun */);
+ }
+
+ /* Update the master (mixer) and PCM out volumes. */
+ sb16UpdateVolume(pThis);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) sb16LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ AssertMsgReturn( uVersion == SB16_SAVE_STATE_VERSION
+ || uVersion == SB16_SAVE_STATE_VERSION_VBOX_30,
+ ("%u\n", uVersion),
+ VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ if (uVersion > SB16_SAVE_STATE_VERSION_VBOX_30)
+ {
+ /** Currently the saved state only contains the one-and-only output stream. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ int32_t irq;
+ pHlp->pfnSSMGetS32(pSSM, &irq);
+ int32_t dma;
+ pHlp->pfnSSMGetS32(pSSM, &dma);
+ int32_t hdma;
+ pHlp->pfnSSMGetS32(pSSM, &hdma);
+ int32_t port;
+ pHlp->pfnSSMGetS32(pSSM, &port);
+ int32_t ver;
+ int rc = pHlp->pfnSSMGetS32(pSSM, &ver);
+ AssertRCReturn (rc, rc);
+
+ if ( irq != pStream->HwCfgDefault.uIrq
+ || dma != pStream->HwCfgDefault.uDmaChanLow
+ || hdma != pStream->HwCfgDefault.uDmaChanHigh
+ || port != pStream->HwCfgDefault.uPort
+ || ver != pStream->HwCfgDefault.uVer)
+ {
+ return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS,
+ N_("config changed: irq=%x/%x dma=%x/%x hdma=%x/%x port=%x/%x ver=%x/%x (saved/config)"),
+ irq, pStream->HwCfgDefault.uIrq,
+ dma, pStream->HwCfgDefault.uDmaChanLow,
+ hdma, pStream->HwCfgDefault.uDmaChanHigh,
+ port, pStream->HwCfgDefault.uPort,
+ ver, pStream->HwCfgDefault.uVer);
+ }
+ }
+
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ return sb16Load(pDevIns, pSSM, pThis);
+}
+
+
+/*********************************************************************************************************************************
+* Debug Info Items *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, sb16mixer}
+ */
+static DECLCALLBACK(void) sb16DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ if (pThis->pMixer)
+ AudioMixerDebug(pThis->pMixer, pHlp, pszArgs);
+ else
+ pHlp->pfnPrintf(pHlp, "Mixer not available\n");
+}
+
+
+/*********************************************************************************************************************************
+* IBase implementation *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) sb16QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
+{
+ PSB16STATE pThis = RT_FROM_MEMBER(pInterface, SB16STATE, IBase);
+ Assert(&pThis->IBase == pInterface);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* Device (PDM) handling *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for sb16Construct() and sb16Attach().
+ *
+ * @returns VBox status code.
+ * @param pThis SB16 state.
+ * @param uLUN The logical unit which is being detached.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, PSB16DRIVER *ppDrv)
+{
+ /*
+ * Allocate a new driver structure and try attach the driver.
+ */
+ PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER));
+ AssertPtrReturn(pDrv, VERR_NO_MEMORY);
+ RTStrPrintf(pDrv->szDesc, sizeof(pDrv->szDesc), "Audio driver port (SB16) for LUN #%u", uLUN);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN, &pThis->IBase, &pDrvBase, pDrv->szDesc);
+ if (RT_SUCCESS(rc))
+ {
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pDrv->pConnector);
+ if (RT_VALID_PTR(pDrv->pConnector))
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->pSB16State = pThis;
+ pDrv->uLUN = uLUN;
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThis->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+ LogFunc(("LUN#%u: returns VINF_SUCCESS (pCon=%p)\n", uLUN, pDrv->pConnector));
+ return VINF_SUCCESS;
+ }
+ rc = VERR_PDM_MISSING_INTERFACE_BELOW;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+ else
+ LogFunc(("Failed to attached driver for LUN #%u: %Rrc\n", uLUN, rc));
+ RTMemFree(pDrv);
+
+ LogFunc(("LUN#%u: rc=%Rrc\n", uLUN, rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(fFlags);
+ LogFunc(("iLUN=%u, fFlags=%#x\n", iLUN, fFlags));
+
+ /** @todo r=andy Any locking required here? */
+
+ PSB16DRIVER pDrv;
+ int rc = sb16AttachInternal(pThis, iLUN, &pDrv);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = sb16AddDrv(pDevIns, pThis, pDrv);
+ if (RT_FAILURE(rc2))
+ LogFunc(("sb16AddDrv failed with %Rrc (ignored)\n", rc2));
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) sb16Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ RT_NOREF(fFlags);
+
+ LogFunc(("iLUN=%u, fFlags=0x%x\n", iLUN, fFlags));
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ if (pDrv->uLUN == iLUN)
+ {
+ sb16RemoveDrv(pDevIns, pThis, pDrv);
+ RTMemFree(pDrv);
+ return;
+ }
+ }
+ LogFunc(("LUN#%u was not found\n", iLUN));
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) sb16DevReset(PPDMDEVINS pDevIns)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogRel2(("SB16: Reset\n"));
+
+ pThis->mixer_regs[0x82] = 0;
+ pThis->csp_regs[5] = 1;
+ pThis->csp_regs[9] = 0xf8;
+
+ pThis->dsp_in_idx = 0;
+ pThis->dsp_out_data_len = 0;
+ pThis->dsp_in_needed_bytes = 0;
+ pThis->nzero = 0;
+ pThis->highspeed = 0;
+ pThis->v2x6 = 0;
+ pThis->cmd = -1;
+
+ sb16MixerReset(pThis);
+ sb16SpeakerControl(pThis, false /* fOn */);
+ sb16DspCmdResetLegacy(pThis);
+}
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) sb16PowerOff(PPDMDEVINS pDevIns)
+{
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogRel2(("SB16: Powering off ...\n"));
+
+ /*
+ * Destroy all streams.
+ */
+ for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
+ sb16StreamDestroy(pDevIns, pThis, &pThis->aStreams[i]);
+
+ /*
+ * Destroy all sinks.
+ */
+ if (pThis->pSinkOut)
+ {
+ AudioMixerSinkDestroy(pThis->pSinkOut, pDevIns);
+ pThis->pSinkOut = NULL;
+ }
+ /** @todo Ditto for sinks. */
+
+ /*
+ * Note: Destroy the mixer while powering off and *not* in sb16Destruct,
+ * giving the mixer the chance to release any references held to
+ * PDM audio streams it maintains.
+ */
+ if (pThis->pMixer)
+ {
+ AudioMixerDestroy(pThis->pMixer, pDevIns);
+ pThis->pMixer = NULL;
+ }
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) sb16Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogFlowFuncEnter();
+
+ PSB16DRIVER pDrv;
+ while (!RTListIsEmpty(&pThis->lstDrv))
+ {
+ pDrv = RTListGetFirst(&pThis->lstDrv, SB16DRIVER, Node);
+
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ /* We don't always go via PowerOff, so make sure the mixer is destroyed. */
+ if (pThis->pMixer)
+ {
+ AudioMixerDestroy(pThis->pMixer, pDevIns);
+ pThis->pMixer = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREGR3,pfnConstruct}
+ */
+static DECLCALLBACK(int) sb16Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PSB16STATE pThis = PDMDEVINS_2_DATA(pDevIns, PSB16STATE);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(iInstance);
+
+ Assert(iInstance == 0);
+
+ /*
+ * Initialize the data so sb16Destruct runs without a hitch if we return early.
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->IBase.pfnQueryInterface = sb16QueryInterface;
+ pThis->cmd = -1;
+
+ pThis->csp_regs[5] = 1;
+ pThis->csp_regs[9] = 0xf8;
+
+ RTListInit(&pThis->lstDrv);
+
+ /*
+ * Validate and read config data.
+ */
+ /* Note: For now we only support the one-and-only output stream. */
+ PSB16STREAM pStream = &pThis->aStreams[SB16_IDX_OUT];
+
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|DMA|DMA16|Port|Version|TimerHz|DebugEnabled|DebugPathOut", "");
+ int rc = pHlp->pfnCFGMQueryU8Def(pCfg, "IRQ", &pStream->HwCfgDefault.uIrq, 5);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"IRQ\" value"));
+ /* Sanity-check supported SB16 IRQs. */
+ if ( 2 != pStream->HwCfgDefault.uIrq
+ && 5 != pStream->HwCfgDefault.uIrq
+ && 7 != pStream->HwCfgDefault.uIrq
+ && 10 != pStream->HwCfgDefault.uIrq)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"IRQ\" value."));
+ pStream->HwCfgRuntime.uIrq = pStream->HwCfgDefault.uIrq;
+
+ rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA", &pStream->HwCfgDefault.uDmaChanLow, 1);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA\" value"));
+ if ( 0 != pStream->HwCfgDefault.uDmaChanLow
+ && 1 != pStream->HwCfgDefault.uDmaChanLow
+ && 3 != pStream->HwCfgDefault.uDmaChanLow)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA\" value."));
+ pStream->HwCfgRuntime.uDmaChanLow = pStream->HwCfgDefault.uDmaChanLow;
+
+ rc = pHlp->pfnCFGMQueryU8Def(pCfg, "DMA16", &pStream->HwCfgDefault.uDmaChanHigh, 5);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"DMA16\" value"));
+ if ( 5 != pStream->HwCfgDefault.uDmaChanHigh
+ && 6 != pStream->HwCfgDefault.uDmaChanHigh
+ && 7 != pStream->HwCfgDefault.uDmaChanHigh)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"DMA16\" value."));
+ pStream->HwCfgRuntime.uDmaChanHigh = pStream->HwCfgDefault.uDmaChanHigh;
+
+ rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pStream->HwCfgDefault.uPort, 0x220);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Port\" value"));
+ /* Sanity-check supported SB16 ports. */
+ if ( 0x220 != pStream->HwCfgDefault.uPort
+ && 0x240 != pStream->HwCfgDefault.uPort
+ && 0x260 != pStream->HwCfgDefault.uPort
+ && 0x280 != pStream->HwCfgDefault.uPort)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Invalid \"Port\" value. Did you specify it as a hex value (e.g. 0x220)?"));
+ pStream->HwCfgRuntime.uPort = pStream->HwCfgDefault.uPort;
+
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Version", &pStream->HwCfgDefault.uVer, 0x0405);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Failed to get the \"Version\" value"));
+ pStream->HwCfgRuntime.uVer = pStream->HwCfgDefault.uVer;
+
+ rc = pHlp->pfnCFGMQueryU16Def(pCfg, "TimerHz", &pStream->uTimerHz, SB16_TIMER_HZ_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: failed to read Hertz rate as unsigned integer"));
+ if (pStream->uTimerHz == 0)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Hertz rate is invalid"));
+ if (pStream->uTimerHz > 2048)
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("SB16 configuration error: Maximum Hertz rate is 2048"));
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->Dbg.fEnabled, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: failed to read debugging enabled flag as boolean"));
+
+ rc = pHlp->pfnCFGMQueryStringAllocDef(pCfg, "DebugPathOut", &pThis->Dbg.pszOutPath, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: failed to read debugging output path flag as string"));
+
+ if (pThis->Dbg.fEnabled)
+ LogRel2(("SB16: Debug output will be saved to '%s'\n", pThis->Dbg.pszOutPath));
+
+ /*
+ * Create internal software mixer.
+ * Must come before we do the device's mixer reset.
+ */
+ rc = AudioMixerCreate("SB16 Mixer", 0 /* uFlags */, &pThis->pMixer);
+ AssertRCReturn(rc, rc);
+
+ AssertRCReturn(rc, rc);
+ rc = AudioMixerCreateSink(pThis->pMixer, "PCM Output",
+ PDMAUDIODIR_OUT, pDevIns, &pThis->pSinkOut);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create all hardware streams.
+ * For now we have one stream only, namely the output (playback) stream.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->aStreams) == SB16_MAX_STREAMS);
+ for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
+ {
+ rc = sb16StreamCreate(pThis, &pThis->aStreams[i], i /* uIdx */);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Setup the mixer now that we've got the irq and dma channel numbers.
+ */
+ pThis->mixer_regs[0x80] = magic_of_irq(pStream->HwCfgRuntime.uIrq);
+ pThis->mixer_regs[0x81] = (1 << pStream->HwCfgRuntime.uDmaChanLow) | (1 << pStream->HwCfgRuntime.uDmaChanHigh);
+ pThis->mixer_regs[0x82] = 2 << 5;
+
+ /*
+ * Perform a device reset before we set up the mixer below,
+ * to have a defined state. This includes the mixer reset + legacy reset.
+ */
+ sb16DevReset(pThis->pDevInsR3);
+
+ /*
+ * Make sure that the mixer sink(s) have a valid format set.
+ *
+ * This is needed in order to make the driver attaching logic working done by Main
+ * for machine construction. Must come after sb16DevReset().
+ */
+ PSB16STREAM const pStreamOut = &pThis->aStreams[SB16_IDX_OUT];
+ AudioMixerSinkSetFormat(pThis->pSinkOut, &pStreamOut->Cfg.Props, pStreamOut->Cfg.Device.cMsSchedulingHint);
+
+ /*
+ * Create timers.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, "SB16 IRQ", &pThis->hTimerIRQ);
+ AssertRCReturn(rc, rc);
+
+ static const char * const s_apszNames[] = { "SB16 OUT" };
+ AssertCompile(RT_ELEMENTS(s_apszNames) == SB16_MAX_STREAMS);
+ for (unsigned i = 0; i < SB16_MAX_STREAMS; i++)
+ {
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, &pThis->aStreams[i],
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, s_apszNames[i], &pThis->aStreams[i].hTimerIO);
+ AssertRCReturn(rc, rc);
+
+ pThis->aStreams[i].cTicksTimerIOInterval = PDMDevHlpTimerGetFreq(pDevIns, pThis->aStreams[i].hTimerIO)
+ / pThis->aStreams[i].uTimerHz;
+ pThis->aStreams[i].tsTimerIO = PDMDevHlpTimerGet(pDevIns, pThis->aStreams[i].hTimerIO);
+ }
+
+ /*
+ * Register I/O and DMA.
+ */
+ static const IOMIOPORTDESC s_aAllDescs[] =
+ {
+ { "FM Music Status Port", "FM Music Register Address Port", NULL, NULL }, // 00h
+ { NULL, "FM Music Data Port", NULL, NULL }, // 01h
+ { "Advanced FM Music Status Port", "Advanced FM Music Register Address Port", NULL, NULL }, // 02h
+ { NULL, "Advanced FM Music Data Port", NULL, NULL }, // 03h
+ { NULL, "Mixer chip Register Address Port", NULL, NULL }, // 04h
+ { "Mixer chip Data Port", NULL, NULL, NULL }, // 05h
+ { NULL, "DSP Reset", NULL, NULL }, // 06h
+ { "Unused7", "Unused7", NULL, NULL }, // 07h
+ { "FM Music Status Port", "FM Music Register Port", NULL, NULL }, // 08h
+ { NULL, "FM Music Data Port", NULL, NULL }, // 09h
+ { "DSP Read Data Port", NULL, NULL, NULL }, // 0Ah
+ { "UnusedB", "UnusedB", NULL, NULL }, // 0Bh
+ { "DSP Write-Buffer Status", "DSP Write Command/Data", NULL, NULL }, // 0Ch
+ { "UnusedD", "UnusedD", NULL, NULL }, // 0Dh
+ { "DSP Read-Buffer Status", NULL, NULL, NULL }, // 0Eh
+ { "IRQ16ACK", NULL, NULL, NULL }, // 0Fh
+ { "CD-ROM Data Register", "CD-ROM Command Register", NULL, NULL }, // 10h
+ { "CD-ROM Status Register", NULL, NULL, NULL }, // 11h
+ { NULL, "CD-ROM Reset Register", NULL, NULL }, // 12h
+ { NULL, "CD-ROM Enable Register", NULL, NULL }, // 13h
+ { NULL, NULL, NULL, NULL },
+ };
+
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x04 /*uPort*/, 2 /*cPorts*/,
+ sb16IoPortMixerWrite, sb16IoPortMixerRead,
+ "SB16 - Mixer", &s_aAllDescs[4], &pThis->hIoPortsMixer);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, pStream->HwCfgRuntime.uPort + 0x06 /*uPort*/, 10 /*cPorts*/,
+ sb16IoPortDspWrite, sb16IoPortDspRead,
+ "SB16 - DSP", &s_aAllDescs[6], &pThis->hIoPortsDsp);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanHigh, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpDMARegister(pDevIns, pStream->HwCfgRuntime.uDmaChanLow, sb16DMARead, &pThis->aStreams[SB16_IDX_OUT] /* pvUser */);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register Saved state.
+ */
+ rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec);
+ AssertRCReturn(rc, rc);
+
+ LogRel2(("SB16: Using port %#x, DMA%RU8, IRQ%RU8\n",
+ pStream->HwCfgRuntime.uPort, pStream->HwCfgRuntime.uDmaChanLow, pStream->HwCfgRuntime.uIrq));
+
+ /*
+ * Attach drivers. We ASSUME they are configured consecutively without any
+ * gaps, so we stop when we hit the first LUN w/o a driver configured.
+ */
+ for (unsigned iLun = 0; ; iLun++)
+ {
+ AssertBreak(iLun < UINT8_MAX);
+ LogFunc(("Trying to attach driver for LUN#%u ...\n", iLun));
+ rc = sb16AttachInternal(pThis, iLun, NULL /* ppDrv */);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ LogFunc(("cLUNs=%u\n", iLun));
+ break;
+ }
+ AssertLogRelMsgReturn(RT_SUCCESS(rc), ("LUN#%u: rc=%Rrc\n", iLun, rc), rc);
+ }
+
+ /*
+ * Register statistics.
+ */
+# ifdef VBOX_WITH_STATISTICS
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimerIO, STAMTYPE_PROFILE, "Timer", STAMUNIT_TICKS_PER_CALL, "Profiling sb16TimerIO.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "BytesRead", STAMUNIT_BYTES, "Bytes read from SB16 emulation.");
+# endif
+ for (unsigned idxStream = 0; idxStream < RT_ELEMENTS(pThis->aStreams); idxStream++)
+ {
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offRead, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer read position.", "Stream%u/offRead", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.offWrite, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Virtual internal buffer write position.", "Stream%u/offWrite", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Size of the internal DMA buffer.", "Stream%u/DMABufSize", idxStream);
+ PDMDevHlpSTAMRegisterF(pDevIns, &pThis->aStreams[idxStream].State.StatDmaBufUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Number of bytes used in the internal DMA buffer.", "Stream%u/DMABufUsed", idxStream);
+ }
+
+ /*
+ * Debug info items.
+ */
+ //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16", "SB16 registers. (sb16 [register case-insensitive])", sb16DbgInfo);
+ //PDMDevHlpDBGFInfoRegister(pDevIns, "sb16stream", "SB16 stream info. (sb16stream [stream number])", sb16DbgInfoStream);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "sb16mixer", "SB16 mixer state.", sb16DbgInfoMixer);
+
+ return VINF_SUCCESS;
+}
+
+const PDMDEVREG g_DeviceSB16 =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "sb16",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE
+ | PDM_DEVREG_FLAGS_FIRST_POWEROFF_NOTIFICATION /* stream clearnup with working drivers */,
+ /* .fClass = */ PDM_DEVREG_CLASS_AUDIO,
+ /* .cMaxInstances = */ 1,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(SB16STATE),
+ /* .cbInstanceCC = */ 0,
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 0,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Sound Blaster 16 Controller",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "",
+ /* .pszR0Mod = */ "",
+ /* .pfnConstruct = */ sb16Construct,
+ /* .pfnDestruct = */ sb16Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ sb16DevReset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ sb16Attach,
+ /* .pfnDetach = */ sb16Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ sb16PowerOff,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ NULL,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvAudio.cpp b/src/VBox/Devices/Audio/DrvAudio.cpp
new file mode 100644
index 00000000..2257033e
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudio.cpp
@@ -0,0 +1,5045 @@
+/* $Id: DrvAudio.cpp $ */
+/** @file
+ * Intermediate audio driver - Connects the audio device emulation with the host backend.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/circbuf.h>
+#include <iprt/req.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "AudioHlp.h"
+#include "AudioMixBuffer.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name PDMAUDIOSTREAM_STS_XXX - Used internally by DRVAUDIOSTREAM::fStatus.
+ * @{ */
+/** No flags being set. */
+#define PDMAUDIOSTREAM_STS_NONE UINT32_C(0)
+/** Set if the stream is enabled, clear if disabled. */
+#define PDMAUDIOSTREAM_STS_ENABLED RT_BIT_32(0)
+/** Set if the stream is paused.
+ * Requires the ENABLED status to be set when used. */
+#define PDMAUDIOSTREAM_STS_PAUSED RT_BIT_32(1)
+/** Output only: Set when the stream is draining.
+ * Requires the ENABLED status to be set when used. */
+#define PDMAUDIOSTREAM_STS_PENDING_DISABLE RT_BIT_32(2)
+
+/** Set if the backend for the stream has been created.
+ *
+ * This is generally always set after stream creation, but
+ * can be cleared if the re-initialization of the stream fails later on.
+ * Asynchronous init may still be incomplete, see
+ * PDMAUDIOSTREAM_STS_BACKEND_READY. */
+#define PDMAUDIOSTREAM_STS_BACKEND_CREATED RT_BIT_32(3)
+/** The backend is ready (PDMIHOSTAUDIO::pfnStreamInitAsync is done).
+ * Requires the BACKEND_CREATED status to be set. */
+#define PDMAUDIOSTREAM_STS_BACKEND_READY RT_BIT_32(4)
+/** Set if the stream needs to be re-initialized by the device (i.e. call
+ * PDMIAUDIOCONNECTOR::pfnStreamReInit). (The other status bits are preserved
+ * and are worked as normal while in this state, so that the stream can
+ * resume operation where it left off.) */
+#define PDMAUDIOSTREAM_STS_NEED_REINIT RT_BIT_32(5)
+/** Validation mask for PDMIAUDIOCONNECTOR. */
+#define PDMAUDIOSTREAM_STS_VALID_MASK UINT32_C(0x0000003f)
+/** Asserts the validity of the given stream status mask for PDMIAUDIOCONNECTOR. */
+#define PDMAUDIOSTREAM_STS_ASSERT_VALID(a_fStreamStatus) do { \
+ AssertMsg(!((a_fStreamStatus) & ~PDMAUDIOSTREAM_STS_VALID_MASK), ("%#x\n", (a_fStreamStatus))); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PAUSED) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PENDING_DISABLE) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_READY) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_CREATED)); \
+ } while (0)
+
+/** @} */
+
+/**
+ * Experimental code for destroying all streams in a disabled direction rather
+ * than just disabling them.
+ *
+ * Cannot be enabled yet because the code isn't complete and DrvAudio will
+ * behave differently (incorrectly), see @bugref{9558#c5} for details.
+ */
+#if defined(DOXYGEN_RUNNING) || 0
+# define DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Audio stream context.
+ *
+ * Needed for separating data from the guest and host side (per stream).
+ */
+typedef struct DRVAUDIOSTREAMCTX
+{
+ /** The stream's audio configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+} DRVAUDIOSTREAMCTX;
+
+/**
+ * Capture state of a stream wrt backend.
+ */
+typedef enum DRVAUDIOCAPTURESTATE
+{
+ /** Invalid zero value. */
+ DRVAUDIOCAPTURESTATE_INVALID = 0,
+ /** No capturing or pre-buffering. */
+ DRVAUDIOCAPTURESTATE_NO_CAPTURE,
+ /** Regular capturing. */
+ DRVAUDIOCAPTURESTATE_CAPTURING,
+ /** Returning silence till the backend buffer has reched the configured
+ * pre-buffering level. */
+ DRVAUDIOCAPTURESTATE_PREBUF,
+ /** End of valid values. */
+ DRVAUDIOCAPTURESTATE_END
+} DRVAUDIOCAPTURESTATE;
+
+/**
+ * Play state of a stream wrt backend.
+ */
+typedef enum DRVAUDIOPLAYSTATE
+{
+ /** Invalid zero value. */
+ DRVAUDIOPLAYSTATE_INVALID = 0,
+ /** No playback or pre-buffering. */
+ DRVAUDIOPLAYSTATE_NOPLAY,
+ /** Playing w/o any prebuffering. */
+ DRVAUDIOPLAYSTATE_PLAY,
+ /** Parallel pre-buffering prior to a device switch (i.e. we're outputting to
+ * the old device and pre-buffering the same data in parallel). */
+ DRVAUDIOPLAYSTATE_PLAY_PREBUF,
+ /** Initial pre-buffering or the pre-buffering for a device switch (if it
+ * the device setup took less time than filling up the pre-buffer). */
+ DRVAUDIOPLAYSTATE_PREBUF,
+ /** The device initialization is taking too long, pre-buffering wraps around
+ * and drops samples. */
+ DRVAUDIOPLAYSTATE_PREBUF_OVERDUE,
+ /** Same as play-prebuf, but we don't have a working output device any more. */
+ DRVAUDIOPLAYSTATE_PREBUF_SWITCHING,
+ /** Working on committing the pre-buffered data.
+ * We'll typically leave this state immediately and go to PLAY, however if
+ * the backend cannot handle all the pre-buffered data at once, we'll stay
+ * here till it does. */
+ DRVAUDIOPLAYSTATE_PREBUF_COMMITTING,
+ /** End of valid values. */
+ DRVAUDIOPLAYSTATE_END
+} DRVAUDIOPLAYSTATE;
+
+
+/**
+ * Extended stream structure.
+ */
+typedef struct DRVAUDIOSTREAM
+{
+ /** The publicly visible bit. */
+ PDMAUDIOSTREAM Core;
+
+ /** Just an extra magic to verify that we allocated the stream rather than some
+ * faked up stuff from the device (DRVAUDIOSTREAM_MAGIC). */
+ uintptr_t uMagic;
+
+ /** List entry in DRVAUDIO::LstStreams. */
+ RTLISTNODE ListEntry;
+
+ /** Number of references to this stream.
+ * Only can be destroyed when the reference count reaches 0. */
+ uint32_t volatile cRefs;
+ /** Stream status - PDMAUDIOSTREAM_STS_XXX. */
+ uint32_t fStatus;
+
+ /** Data to backend-specific stream data.
+ * This data block will be casted by the backend to access its backend-dependent data.
+ *
+ * That way the backends do not have access to the audio connector's data. */
+ PPDMAUDIOBACKENDSTREAM pBackend;
+
+ /** Set if pfnStreamCreate returned VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED. */
+ bool fNeedAsyncInit;
+ /** The fImmediate parameter value for pfnStreamDestroy. */
+ bool fDestroyImmediate;
+ bool afPadding[2];
+
+ /** Number of (re-)tries while re-initializing the stream. */
+ uint32_t cTriesReInit;
+
+ /** The last backend state we saw.
+ * This is used to detect state changes (for what that is worth). */
+ PDMHOSTAUDIOSTREAMSTATE enmLastBackendState;
+
+ /** The pre-buffering threshold expressed in bytes. */
+ uint32_t cbPreBufThreshold;
+
+ /** The pfnStreamInitAsync request handle. */
+ PRTREQ hReqInitAsync;
+
+ /** The nanosecond timestamp when the stream was started. */
+ uint64_t nsStarted;
+ /** Internal stream position (as per pfnStreamPlay/pfnStreamCapture). */
+ uint64_t offInternal;
+
+ /** Timestamp (in ns) since last trying to re-initialize.
+ * Might be 0 if has not been tried yet. */
+ uint64_t nsLastReInit;
+ /** Timestamp (in ns) since last iteration. */
+ uint64_t nsLastIterated;
+ /** Timestamp (in ns) since last playback / capture. */
+ uint64_t nsLastPlayedCaptured;
+ /** Timestamp (in ns) since last read (input streams) or
+ * write (output streams). */
+ uint64_t nsLastReadWritten;
+
+
+ /** Union for input/output specifics depending on enmDir. */
+ union
+ {
+ /**
+ * The specifics for an audio input stream.
+ */
+ struct
+ {
+ /** The capture state. */
+ DRVAUDIOCAPTURESTATE enmCaptureState;
+
+ struct
+ {
+ /** File for writing non-interleaved captures. */
+ PAUDIOHLPFILE pFileCapture;
+ } Dbg;
+ struct
+ {
+ uint32_t cbBackendReadableBefore;
+ uint32_t cbBackendReadableAfter;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE ProfCapture;
+ STAMPROFILE ProfGetReadable;
+ STAMPROFILE ProfGetReadableBytes;
+#endif
+ } Stats;
+ } In;
+
+ /**
+ * The specifics for an audio output stream.
+ */
+ struct
+ {
+ /** Space for pre-buffering. */
+ uint8_t *pbPreBuf;
+ /** The size of the pre-buffer allocation (in bytes). */
+ uint32_t cbPreBufAlloc;
+ /** The current pre-buffering read offset. */
+ uint32_t offPreBuf;
+ /** Number of bytes we've pre-buffered. */
+ uint32_t cbPreBuffered;
+ /** The play state. */
+ DRVAUDIOPLAYSTATE enmPlayState;
+
+ struct
+ {
+ /** File for writing stream playback. */
+ PAUDIOHLPFILE pFilePlay;
+ } Dbg;
+ struct
+ {
+ uint32_t cbBackendWritableBefore;
+ uint32_t cbBackendWritableAfter;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE ProfPlay;
+ STAMPROFILE ProfGetWritable;
+ STAMPROFILE ProfGetWritableBytes;
+#endif
+ } Stats;
+ } Out;
+ } RT_UNION_NM(u);
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatProfGetState;
+ STAMPROFILE StatXfer;
+#endif
+} DRVAUDIOSTREAM;
+/** Pointer to an extended stream structure. */
+typedef DRVAUDIOSTREAM *PDRVAUDIOSTREAM;
+
+/** Value for DRVAUDIOSTREAM::uMagic (Johann Sebastian Bach). */
+#define DRVAUDIOSTREAM_MAGIC UINT32_C(0x16850331)
+/** Value for DRVAUDIOSTREAM::uMagic after destruction */
+#define DRVAUDIOSTREAM_MAGIC_DEAD UINT32_C(0x17500728)
+
+
+/**
+ * Audio driver configuration data, tweakable via CFGM.
+ */
+typedef struct DRVAUDIOCFG
+{
+ /** PCM properties to use. */
+ PDMAUDIOPCMPROPS Props;
+ /** Whether using signed sample data or not.
+ * Needed in order to know whether there is a custom value set in CFGM or not.
+ * By default set to UINT8_MAX if not set to a custom value. */
+ uint8_t uSigned;
+ /** Whether swapping endianess of sample data or not.
+ * Needed in order to know whether there is a custom value set in CFGM or not.
+ * By default set to UINT8_MAX if not set to a custom value. */
+ uint8_t uSwapEndian;
+ /** Configures the period size (in ms).
+ * This value reflects the time in between each hardware interrupt on the
+ * backend (host) side. */
+ uint32_t uPeriodSizeMs;
+ /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */
+ uint32_t uBufferSizeMs;
+ /** Configures the pre-buffering size (in ms).
+ * Time needed in buffer before the stream becomes active (pre buffering).
+ * The bigger this value is, the more latency for the stream will occur.
+ * Set to 0 to disable pre-buffering completely.
+ * By default set to UINT32_MAX if not set to a custom value. */
+ uint32_t uPreBufSizeMs;
+ /** The driver's debugging configuration. */
+ struct
+ {
+ /** Whether audio debugging is enabled or not. */
+ bool fEnabled;
+ /** Where to store the debugging files. */
+ char szPathOut[RTPATH_MAX];
+ } Dbg;
+} DRVAUDIOCFG;
+/** Pointer to tweakable audio configuration. */
+typedef DRVAUDIOCFG *PDRVAUDIOCFG;
+/** Pointer to const tweakable audio configuration. */
+typedef DRVAUDIOCFG const *PCDRVAUDIOCFG;
+
+
+/**
+ * Audio driver instance data.
+ *
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVAUDIO
+{
+ /** Read/Write critical section for guarding changes to pHostDrvAudio and
+ * BackendCfg during deteach/attach. Mostly taken in shared mode.
+ * @note Locking order: Must be entered after CritSectGlobals.
+ * @note Locking order: Must be entered after PDMAUDIOSTREAM::CritSect. */
+ RTCRITSECTRW CritSectHotPlug;
+ /** Critical section for protecting:
+ * - LstStreams
+ * - cStreams
+ * - In.fEnabled
+ * - In.cStreamsFree
+ * - Out.fEnabled
+ * - Out.cStreamsFree
+ * @note Locking order: Must be entered before PDMAUDIOSTREAM::CritSect.
+ * @note Locking order: Must be entered before CritSectHotPlug. */
+ RTCRITSECTRW CritSectGlobals;
+ /** List of audio streams (DRVAUDIOSTREAM). */
+ RTLISTANCHOR LstStreams;
+ /** Number of streams in the list. */
+ size_t cStreams;
+ struct
+ {
+ /** Whether this driver's input streams are enabled or not.
+ * This flag overrides all the attached stream statuses. */
+ bool fEnabled;
+ /** Max. number of free input streams.
+ * UINT32_MAX for unlimited streams. */
+ uint32_t cStreamsFree;
+ } In;
+ struct
+ {
+ /** Whether this driver's output streams are enabled or not.
+ * This flag overrides all the attached stream statuses. */
+ bool fEnabled;
+ /** Max. number of free output streams.
+ * UINT32_MAX for unlimited streams. */
+ uint32_t cStreamsFree;
+ } Out;
+
+ /** Audio configuration settings retrieved from the backend.
+ * The szName field is used for the DriverName config value till we get the
+ * authoritative name from the backend (only for logging). */
+ PDMAUDIOBACKENDCFG BackendCfg;
+ /** Our audio connector interface. */
+ PDMIAUDIOCONNECTOR IAudioConnector;
+ /** Interface used by the host backend. */
+ PDMIHOSTAUDIOPORT IHostAudioPort;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to audio driver below us. */
+ PPDMIHOSTAUDIO pHostDrvAudio;
+
+ /** Request pool if the backend needs it for async stream creation. */
+ RTREQPOOL hReqPool;
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /** Handle to the timer for delayed re-enumeration of backend devices. */
+ TMTIMERHANDLE hEnumTimer;
+ /** Unique name for the the disable-iteration timer. */
+ char szEnumTimerName[24];
+#endif
+
+ /** Input audio configuration values (static). */
+ DRVAUDIOCFG CfgIn;
+ /** Output audio configuration values (static). */
+ DRVAUDIOCFG CfgOut;
+
+ STAMCOUNTER StatTotalStreamsCreated;
+} DRVAUDIO;
+/** Pointer to the instance data of an audio driver. */
+typedef DRVAUDIO *PDRVAUDIO;
+/** Pointer to const instance data of an audio driver. */
+typedef DRVAUDIO const *PCDRVAUDIO;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+#endif
+static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx);
+static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy);
+static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx);
+
+
+/** Buffer size for drvAudioStreamStatusToStr. */
+# define DRVAUDIO_STATUS_STR_MAX sizeof("BACKEND_CREATED BACKEND_READY ENABLED PAUSED PENDING_DISABLED NEED_REINIT 0x12345678")
+
+/**
+ * Converts an audio stream status to a string.
+ *
+ * @returns pszDst
+ * @param pszDst Buffer to convert into, at least minimum size is
+ * DRVAUDIO_STATUS_STR_MAX.
+ * @param fStatus Stream status flags to convert.
+ */
+static const char *drvAudioStreamStatusToStr(char pszDst[DRVAUDIO_STATUS_STR_MAX], uint32_t fStatus)
+{
+ static const struct
+ {
+ const char *pszMnemonic;
+ uint32_t cchMnemnonic;
+ uint32_t fFlag;
+ } s_aFlags[] =
+ {
+ { RT_STR_TUPLE("BACKEND_CREATED "), PDMAUDIOSTREAM_STS_BACKEND_CREATED },
+ { RT_STR_TUPLE("BACKEND_READY "), PDMAUDIOSTREAM_STS_BACKEND_READY },
+ { RT_STR_TUPLE("ENABLED "), PDMAUDIOSTREAM_STS_ENABLED },
+ { RT_STR_TUPLE("PAUSED "), PDMAUDIOSTREAM_STS_PAUSED },
+ { RT_STR_TUPLE("PENDING_DISABLE "), PDMAUDIOSTREAM_STS_PENDING_DISABLE },
+ { RT_STR_TUPLE("NEED_REINIT "), PDMAUDIOSTREAM_STS_NEED_REINIT },
+ };
+ if (!fStatus)
+ strcpy(pszDst, "NONE");
+ else
+ {
+ char *psz = pszDst;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
+ if (fStatus & s_aFlags[i].fFlag)
+ {
+ memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemnonic);
+ psz += s_aFlags[i].cchMnemnonic;
+ fStatus &= ~s_aFlags[i].fFlag;
+ if (!fStatus)
+ break;
+ }
+ if (fStatus == 0)
+ psz[-1] = '\0';
+ else
+ psz += RTStrPrintf(psz, DRVAUDIO_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus);
+ Assert((uintptr_t)(psz - pszDst) <= DRVAUDIO_STATUS_STR_MAX);
+ }
+ return pszDst;
+}
+
+
+/**
+ * Get play state name string.
+ */
+static const char *drvAudioPlayStateName(DRVAUDIOPLAYSTATE enmState)
+{
+ switch (enmState)
+ {
+ case DRVAUDIOPLAYSTATE_INVALID: return "INVALID";
+ case DRVAUDIOPLAYSTATE_NOPLAY: return "NOPLAY";
+ case DRVAUDIOPLAYSTATE_PLAY: return "PLAY";
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF: return "PLAY_PREBUF";
+ case DRVAUDIOPLAYSTATE_PREBUF: return "PREBUF";
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: return "PREBUF_OVERDUE";
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: return "PREBUF_SWITCHING";
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: return "PREBUF_COMMITTING";
+ case DRVAUDIOPLAYSTATE_END:
+ break;
+ }
+ return "BAD";
+}
+
+#ifdef LOG_ENABLED
+/**
+ * Get capture state name string.
+ */
+static const char *drvAudioCaptureStateName(DRVAUDIOCAPTURESTATE enmState)
+{
+ switch (enmState)
+ {
+ case DRVAUDIOCAPTURESTATE_INVALID: return "INVALID";
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE: return "NO_CAPTURE";
+ case DRVAUDIOCAPTURESTATE_CAPTURING: return "CAPTURING";
+ case DRVAUDIOCAPTURESTATE_PREBUF: return "PREBUF";
+ case DRVAUDIOCAPTURESTATE_END:
+ break;
+ }
+ return "BAD";
+}
+#endif
+
+/**
+ * Checks if the stream status is one that can be read from.
+ *
+ * @returns @c true if ready to be read from, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanRead)!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusCanRead(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_PAUSED
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+/**
+ * Checks if the stream status is one that can be written to.
+ *
+ * @returns @c true if ready to be written to, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanWrite)!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusCanWrite(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_PAUSED
+ | PDMAUDIOSTREAM_STS_PENDING_DISABLE
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+/**
+ * Checks if the stream status is a ready-to-operate one.
+ *
+ * @returns @c true if ready to operate, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusIsReady(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+
+/**
+ * Wrapper around PDMIHOSTAUDIO::pfnStreamGetStatus and checks the result.
+ *
+ * @returns A PDMHOSTAUDIOSTREAMSTATE value.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to get the backend status for.
+ */
+DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendState(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ if (pThis->pHostDrvAudio)
+ {
+ /* Don't call if the backend wasn't created for this stream (disabled). */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ AssertPtrReturn(pThis->pHostDrvAudio->pfnStreamGetState, PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING);
+ PDMHOSTAUDIOSTREAMSTATE enmState = pThis->pHostDrvAudio->pfnStreamGetState(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ Log9Func(("%s: %s\n", pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmState) ));
+ Assert( enmState > PDMHOSTAUDIOSTREAMSTATE_INVALID
+ && enmState < PDMHOSTAUDIOSTREAMSTATE_END
+ && (enmState != PDMHOSTAUDIOSTREAMSTATE_DRAINING || pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT));
+ return enmState;
+ }
+ }
+ Log9Func(("%s: not-working\n", pStreamEx->Core.Cfg.szName));
+ return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+}
+
+
+/**
+ * Worker for drvAudioStreamProcessBackendStateChange that completes draining.
+ */
+DECLINLINE(void) drvAudioStreamProcessBackendStateChangeWasDraining(PDRVAUDIOSTREAM pStreamEx)
+{
+ Log(("drvAudioStreamProcessBackendStateChange: Stream '%s': Done draining - disabling stream.\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ drvAudioStreamResetInternal(pStreamEx);
+}
+
+
+/**
+ * Processes backend state change.
+ *
+ * @returns the new state value.
+ */
+static PDMHOSTAUDIOSTREAMSTATE drvAudioStreamProcessBackendStateChange(PDRVAUDIOSTREAM pStreamEx,
+ PDMHOSTAUDIOSTREAMSTATE enmNewState,
+ PDMHOSTAUDIOSTREAMSTATE enmOldState)
+{
+ PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir;
+#ifdef LOG_ENABLED
+ DRVAUDIOPLAYSTATE const enmPlayState = enmDir == PDMAUDIODIR_OUT
+ ? pStreamEx->Out.enmPlayState : DRVAUDIOPLAYSTATE_INVALID;
+ DRVAUDIOCAPTURESTATE const enmCaptureState = enmDir == PDMAUDIODIR_IN
+ ? pStreamEx->In.enmCaptureState : DRVAUDIOCAPTURESTATE_INVALID;
+#endif
+ Assert(enmNewState != enmOldState);
+ Assert(enmOldState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmOldState < PDMHOSTAUDIOSTREAMSTATE_END);
+ AssertReturn(enmNewState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmNewState < PDMHOSTAUDIOSTREAMSTATE_END, enmOldState);
+
+ /*
+ * Figure out what happend and how that reflects on the playback state and stuff.
+ */
+ switch (enmNewState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ /* Guess we're switching device. Nothing to do because the backend will tell us, right? */
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ /* The stream has stopped working or is inactive. Switch stop any draining & to noplay mode. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
+ if (enmDir == PDMAUDIODIR_OUT)
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ else
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE;
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ switch (enmOldState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ /* Should be taken care of elsewhere, so do nothing. */
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ /* Go back to pre-buffering/playing depending on whether it is enabled
+ or not, resetting the stream state. */
+ drvAudioStreamResetInternal(pStreamEx);
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ /* Complete the draining. May race the iterate code. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
+ break;
+
+ /* no default: */
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY: /* impossible */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ /* We do all we need to do when issuing the DRAIN command. */
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ break;
+
+ /* no default: */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+
+ if (enmDir == PDMAUDIODIR_OUT)
+ LogFunc(("Output stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmOldState), drvAudioPlayStateName(enmPlayState),
+ PDMHostAudioStreamStateGetName(enmNewState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ else
+ LogFunc(("Input stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmOldState), drvAudioCaptureStateName(enmCaptureState),
+ PDMHostAudioStreamStateGetName(enmNewState), drvAudioCaptureStateName(pStreamEx->In.enmCaptureState) ));
+
+ pStreamEx->enmLastBackendState = enmNewState;
+ return enmNewState;
+}
+
+
+/**
+ * This gets the backend state and handles changes compared to
+ * DRVAUDIOSTREAM::enmLastBackendState (updated).
+ *
+ * @returns A PDMHOSTAUDIOSTREAMSTATE value.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to get the backend status for.
+ */
+DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendStateAndProcessChanges(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if (pStreamEx->enmLastBackendState == enmBackendState)
+ return enmBackendState;
+ return drvAudioStreamProcessBackendStateChange(pStreamEx, enmBackendState, pStreamEx->enmLastBackendState);
+}
+
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+/**
+ * Enumerates all host audio devices.
+ *
+ * This functionality might not be implemented by all backends and will return
+ * VERR_NOT_SUPPORTED if not being supported.
+ *
+ * @note Must not hold the driver's critical section!
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance to be called.
+ * @param fLog Whether to print the enumerated device to the release log or not.
+ * @param pDevEnum Where to store the device enumeration.
+ *
+ * @remarks This is currently ONLY used for release logging.
+ */
+static DECLCALLBACK(int) drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum)
+{
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ int rc;
+
+ /*
+ * If the backend supports it, do a device enumeration.
+ */
+ if (pThis->pHostDrvAudio->pfnGetDevices)
+ {
+ PDMAUDIOHOSTENUM DevEnum;
+ rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
+ if (RT_SUCCESS(rc))
+ {
+ if (fLog)
+ {
+ LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->BackendCfg.szName));
+
+ PPDMAUDIOHOSTDEV pDev;
+ RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
+ {
+ char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
+ LogRel(("Audio: Device '%s':\n"
+ "Audio: ID = %s\n"
+ "Audio: Usage = %s\n"
+ "Audio: Flags = %s\n"
+ "Audio: Input channels = %RU8\n"
+ "Audio: Output channels = %RU8\n",
+ pDev->pszName, pDev->pszId ? pDev->pszId : "",
+ PDMAudioDirGetName(pDev->enmUsage), PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags),
+ pDev->cMaxInputChannels, pDev->cMaxOutputChannels));
+ }
+ }
+
+ if (pDevEnum)
+ rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
+
+ PDMAudioHostEnumDelete(&DevEnum);
+ }
+ else
+ {
+ if (fLog)
+ LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc));
+ /* Not fatal. */
+ }
+ }
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+
+ if (fLog)
+ LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->BackendCfg.szName));
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+
+/*********************************************************************************************************************************
+* PDMIAUDIOCONNECTOR *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
+ */
+static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ LogFlowFunc(("enmDir=%s fEnable=%d\n", PDMAudioDirGetName(enmDir), fEnable));
+
+ /*
+ * Figure which status flag variable is being updated.
+ */
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ /*
+ * Grab the driver wide lock and check it. Ignore call if no change.
+ */
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+
+ if (fEnable != *pfEnabled)
+ {
+ LogRel(("Audio: %s %s for driver '%s'\n",
+ fEnable ? "Enabling" : "Disabling", PDMAudioDirGetName(enmDir), pThis->BackendCfg.szName));
+
+ /*
+ * When enabling, we must update flag before calling drvAudioStreamControlInternalBackend.
+ */
+ if (fEnable)
+ *pfEnabled = true;
+
+ /*
+ * Update the backend status for the streams in the given direction.
+ *
+ * The pThis->Out.fEnable / pThis->In.fEnable status flags only reflect in the
+ * direction of the backend, drivers and devices above us in the chain does not
+ * know about this. When disabled playback goes to /dev/null and we capture
+ * only silence. This means pStreamEx->fStatus holds the nominal status
+ * and we'll use it to restore the operation. (See also @bugref{9882}.)
+ *
+ * The DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION build time define
+ * controls how this is implemented.
+ */
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ /** @todo duplex streams */
+ if (pStreamEx->Core.Cfg.enmDir == enmDir)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * When (re-)enabling a stream, clear the disabled warning bit again.
+ */
+ if (fEnable)
+ pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
+
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /*
+ * When enabling, we must make sure the stream has been created with the
+ * backend before enabling and maybe pausing it. When disabling we must
+ * destroy the stream. Paused includes enabled, as does draining, but we
+ * only want the former.
+ */
+#else
+ /*
+ * We don't need to do anything unless the stream is enabled.
+ * Paused includes enabled, as does draining, but we only want the former.
+ */
+#endif
+ uint32_t const fStatus = pStreamEx->fStatus;
+
+#ifndef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+#endif
+ {
+ const char *pszOperation;
+ int rc2;
+ if (fEnable)
+ {
+ if (!(fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /* The backend shouldn't have been created, so do that before enabling
+ and possibly pausing the stream. */
+ if (!(fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ rc2 = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ else
+ rc2 = VINF_SUCCESS;
+ pszOperation = "re-init";
+ if (RT_SUCCESS(rc2) && (fStatus & PDMAUDIOSTREAM_STS_ENABLED))
+#endif
+ {
+ /** @todo r=bird: We need to redo pre-buffering OR switch to
+ * DRVAUDIOPLAYSTATE_PREBUF_SWITCHING playback mode when disabling
+ * output streams. The former is preferred if associated with
+ * reporting the stream as INACTIVE. */
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ pszOperation = "enable";
+ if (RT_SUCCESS(rc2) && (fStatus & PDMAUDIOSTREAM_STS_PAUSED))
+ {
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ pszOperation = "pause";
+ }
+ }
+ }
+ else
+ {
+ rc2 = VINF_SUCCESS;
+ pszOperation = NULL;
+ }
+ }
+ else
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ rc2 = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+ else
+ rc2 = VINF_SUCCESS;
+ pszOperation = "destroy";
+#else
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ pszOperation = "disable";
+#endif
+ }
+ if (RT_FAILURE(rc2))
+ {
+ LogRel(("Audio: Failed to %s %s stream '%s': %Rrc\n",
+ pszOperation, PDMAudioDirGetName(enmDir), pStreamEx->Core.Cfg.szName, rc2));
+ if (RT_SUCCESS(rc))
+ rc = rc2; /** @todo r=bird: This isn't entirely helpful to the caller since we'll update the status
+ * regardless of the status code we return. And anyway, there is nothing that can be done
+ * about individual stream by the caller... */
+ }
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ }
+
+ /*
+ * When disabling, we must update the status flag after the
+ * drvAudioStreamControlInternalBackend(DISABLE) calls.
+ */
+ *pfEnabled = fEnable;
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
+ */
+static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, false);
+
+ bool fEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ fEnabled = pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ fEnabled = pThis->Out.fEnabled;
+ else
+ AssertFailedStmt(fEnabled = false);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ return fEnabled;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
+ else
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ PDMAUDIOBACKENDSTS fBackendStatus;
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetStatus)
+ fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
+ else
+ fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN;
+ }
+ else
+ fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("LEAVE - %#x\n", fBackendStatus));
+ return fBackendStatus;
+}
+
+
+/**
+ * Frees an audio stream and its allocated resources.
+ *
+ * @param pStreamEx Audio stream to free. After this call the pointer will
+ * not be valid anymore.
+ */
+static void drvAudioStreamFree(PDRVAUDIOSTREAM pStreamEx)
+{
+ if (pStreamEx)
+ {
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+ Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ pStreamEx->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
+ pStreamEx->pBackend = NULL;
+ pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC_DEAD;
+
+ RTCritSectDelete(&pStreamEx->Core.CritSect);
+
+ RTMemFree(pStreamEx);
+ }
+}
+
+
+/**
+ * Adjusts the request stream configuration, applying our settings.
+ *
+ * This also does some basic validations.
+ *
+ * Used by both the stream creation and stream configuration hinting code.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pCfg The configuration that should be adjusted.
+ * @param pszName Stream name to use when logging warnings and errors.
+ */
+static int drvAudioStreamAdjustConfig(PCDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg, const char *pszName)
+{
+ /* Get the right configuration for the stream to be created. */
+ PCDRVAUDIOCFG pDrvCfg = pCfg->enmDir == PDMAUDIODIR_IN ? &pThis->CfgIn: &pThis->CfgOut;
+
+ /* Fill in the tweakable parameters into the requested host configuration.
+ * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
+
+ /*
+ * PCM
+ */
+ if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
+ {
+ PDMAudioPropsSetSampleSize(&pCfg->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props));
+ LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n",
+ PDMAudioPropsSampleSize(&pCfg->Props), pszName));
+ }
+
+ if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.uHz = pDrvCfg->Props.uHz;
+ LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfg->Props.uHz, pszName));
+ }
+
+ if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.fSigned = RT_BOOL(pDrvCfg->uSigned);
+ LogRel2(("Audio: Using custom %s sample format for stream '%s'\n",
+ pCfg->Props.fSigned ? "signed" : "unsigned", pszName));
+ }
+
+ if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian);
+ LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n",
+ pCfg->Props.fSwapEndian ? "swapped" : "original", pszName));
+ }
+
+ if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
+ {
+ PDMAudioPropsSetChannels(&pCfg->Props, PDMAudioPropsChannels(&pDrvCfg->Props));
+ LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName));
+ }
+
+ /* Validate PCM properties. */
+ if (!AudioHlpPcmPropsAreValidAndSupported(&pCfg->Props))
+ {
+ LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Buffer size
+ */
+ const char *pszWhat = "device-specific";
+ if (pDrvCfg->uBufferSizeMs)
+ {
+ pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uBufferSizeMs);
+ pszWhat = "custom";
+ }
+
+ if (!pCfg->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */
+ {
+ pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, 300 /*ms*/);
+ pszWhat = "default";
+ }
+
+ LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize),
+ pCfg->Backend.cFramesBufferSize, pszName));
+
+ /*
+ * Period size
+ */
+ pszWhat = "device-specific";
+ if (pDrvCfg->uPeriodSizeMs)
+ {
+ pCfg->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPeriodSizeMs);
+ pszWhat = "custom";
+ }
+
+ if (!pCfg->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */
+ {
+ pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 4;
+ pszWhat = "default";
+ }
+
+ if (pCfg->Backend.cFramesPeriod >= pCfg->Backend.cFramesBufferSize / 2)
+ {
+ LogRel(("Audio: Warning! Stream '%s': The stream period size (%RU64ms, %s) cannot be more than half the buffer size (%RU64ms)!\n",
+ pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod), pszWhat,
+ PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize)));
+ pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 2;
+ }
+
+ LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod),
+ pCfg->Backend.cFramesPeriod, pszName));
+
+ /*
+ * Pre-buffering size
+ */
+ pszWhat = "device-specific";
+ if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
+ {
+ pCfg->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPreBufSizeMs);
+ pszWhat = "custom";
+ }
+ else /* No, then either use the default or device-specific settings (if any). */
+ {
+ if (pCfg->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
+ {
+ /* Pre-buffer 50% for both output & input. Capping both at 200ms.
+ The 50% reasoning being that we need to have sufficient slack space
+ in both directions as the guest DMA timer might be delayed by host
+ scheduling as well as sped up afterwards because of TM catch-up. */
+ uint32_t const cFramesMax = PDMAudioPropsMilliToFrames(&pCfg->Props, 200);
+ pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize / 2;
+ pCfg->Backend.cFramesPreBuffering = RT_MIN(pCfg->Backend.cFramesPreBuffering, cFramesMax);
+ pszWhat = "default";
+ }
+ }
+
+ if (pCfg->Backend.cFramesPreBuffering >= pCfg->Backend.cFramesBufferSize)
+ {
+ LogRel(("Audio: Warning! Stream '%s': Pre-buffering (%RU64ms, %s) cannot equal or exceed the buffer size (%RU64ms)!\n",
+ pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize), pszWhat,
+ PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering) ));
+ pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize - 1;
+ }
+
+ LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering),
+ pCfg->Backend.cFramesPreBuffering, pszName));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker thread function for drvAudioStreamConfigHint that's used when
+ * PDMAUDIOBACKEND_F_ASYNC_HINT is in effect.
+ */
+static DECLCALLBACK(void) drvAudioStreamConfigHintWorker(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ LogFlowFunc(("pThis=%p pCfg=%p\n", pThis, pCfg));
+ AssertPtrReturnVoid(pCfg);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ PPDMIHOSTAUDIO const pHostDrvAudio = pThis->pHostDrvAudio;
+ if (pHostDrvAudio)
+ {
+ AssertPtr(pHostDrvAudio->pfnStreamConfigHint);
+ if (pHostDrvAudio->pfnStreamConfigHint)
+ pHostDrvAudio->pfnStreamConfigHint(pHostDrvAudio, pCfg);
+ }
+ PDMAudioStrmCfgFree(pCfg);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Checks whether a given stream direction is enabled (permitted) or not.
+ *
+ * Currently there are only per-direction enabling/disabling of audio streams.
+ * This lets a user disabling input so it an untrusted VM cannot listen in
+ * without the user explicitly allowing it, or disable output so it won't
+ * disturb your and cannot communicate with other VMs or machines
+ *
+ * See @bugref{9882}.
+ *
+ * @retval true if the stream configuration is enabled/allowed.
+ * @retval false if not permitted.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param enmDir The stream direction to check.
+ */
+DECLINLINE(bool) drvAudioStreamIsDirectionEnabled(PDRVAUDIO pThis, PDMAUDIODIR enmDir)
+{
+ switch (enmDir)
+ {
+ case PDMAUDIODIR_IN:
+ return pThis->In.fEnabled;
+ case PDMAUDIODIR_OUT:
+ return pThis->Out.fEnabled;
+ case PDMAUDIODIR_DUPLEX:
+ return pThis->Out.fEnabled && pThis->In.fEnabled;
+ default:
+ AssertFailedReturn(false);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint}
+ */
+static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT);
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Don't do anything unless the backend has a pfnStreamConfigHint method
+ * and the direction is currently enabled.
+ */
+ if ( pThis->pHostDrvAudio
+ && pThis->pHostDrvAudio->pfnStreamConfigHint)
+ {
+ if (drvAudioStreamIsDirectionEnabled(pThis, pCfg->enmDir))
+ {
+ /*
+ * Adjust the configuration (applying out settings) then call the backend driver.
+ */
+ rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName);
+ AssertLogRelRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_CALLBACK_RETURN;
+ if (pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_HINT)
+ {
+ PPDMAUDIOSTREAMCFG pDupCfg = PDMAudioStrmCfgDup(pCfg);
+ if (pDupCfg)
+ {
+ rc = RTReqPoolCallVoidNoWait(pThis->hReqPool, (PFNRT)drvAudioStreamConfigHintWorker, 2, pThis, pDupCfg);
+ if (RT_SUCCESS(rc))
+ LogFlowFunc(("Asynchronous call running on worker thread.\n"));
+ else
+ PDMAudioStrmCfgFree(pDupCfg);
+ }
+ }
+ if (RT_FAILURE_NP(rc))
+ {
+ LogFlowFunc(("Doing synchronous call...\n"));
+ pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg);
+ }
+ }
+ }
+ else
+ LogFunc(("Ignoring hint because direction is not currently enabled\n"));
+ }
+ else
+ LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n"));
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+}
+
+
+/**
+ * Common worker for synchronizing the ENABLED and PAUSED status bits with the
+ * backend after it becomes ready.
+ *
+ * Used by async init and re-init.
+ *
+ * @note Is sometimes called w/o having entered DRVAUDIO::CritSectHotPlug.
+ * Caller must however own the stream critsect.
+ */
+static int drvAudioStreamUpdateBackendOnStatus(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, const char *pszWhen)
+{
+ int rc = VINF_SUCCESS;
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ if (RT_SUCCESS(rc))
+ {
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_FAILURE(rc))
+ LogRelMax(64, ("Audio: Failed to pause stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc));
+ }
+ }
+ else
+ LogRelMax(64, ("Audio: Failed to enable stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc));
+ }
+ return rc;
+}
+
+
+/**
+ * For performing PDMIHOSTAUDIO::pfnStreamInitAsync on a worker thread.
+ *
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream. One reference for us to release.
+ */
+static DECLCALLBACK(void) drvAudioStreamInitAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ LogFlow(("pThis=%p pStreamEx=%p (%s)\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName));
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Do the init job.
+ */
+ bool fDestroyed;
+ PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtr(pIHostDrvAudio);
+ if (pIHostDrvAudio && pIHostDrvAudio->pfnStreamInitAsync)
+ {
+ fDestroyed = pStreamEx->cRefs <= 1;
+ rc = pIHostDrvAudio->pfnStreamInitAsync(pIHostDrvAudio, pStreamEx->pBackend, fDestroyed);
+ LogFlow(("pfnStreamInitAsync returns %Rrc (on %p, fDestroyed=%d)\n", rc, pStreamEx, fDestroyed));
+ }
+ else
+ {
+ fDestroyed = true;
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * On success, update the backend on the stream status and mark it ready for business.
+ */
+ if (RT_SUCCESS(rc) && !fDestroyed)
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Update the backend state.
+ */
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY; /* before the backend control call! */
+
+ rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "asynchronous initialization completed");
+
+ /*
+ * Modify the play state if output stream.
+ */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ switch (enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ break;
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ break;
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ break;
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ break; /* possible race here, so don't assert. */
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ AssertFailedBreak();
+ /* no default */
+ case DRVAUDIOPLAYSTATE_END:
+ case DRVAUDIOPLAYSTATE_INVALID:
+ break;
+ }
+ LogFunc(("enmPlayState: %s -> %s\n", drvAudioPlayStateName(enmPlayState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ }
+
+ /*
+ * Update the last backend state.
+ */
+ pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ /*
+ * Don't quite know what to do on failure...
+ */
+ else if (!fDestroyed)
+ {
+ LogRelMax(64, ("Audio: Failed to initialize stream '%s': %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ }
+
+ /*
+ * Release the request handle, must be done while inside the critical section.
+ */
+ if (pStreamEx->hReqInitAsync != NIL_RTREQ)
+ {
+ LogFlowFunc(("Releasing hReqInitAsync=%p\n", pStreamEx->hReqInitAsync));
+ RTReqRelease(pStreamEx->hReqInitAsync);
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Release our stream reference.
+ */
+ uint32_t cRefs = drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ LogFlowFunc(("returns (fDestroyed=%d, cRefs=%u)\n", fDestroyed, cRefs)); RT_NOREF(cRefs);
+}
+
+
+/**
+ * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that
+ * creates the backend (host driver) side of an audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to create backend for. The Core.Cfg field
+ * contains the requested configuration when we're called
+ * and the actual configuration when successfully
+ * returning.
+ *
+ * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):
+ * - per global extra-data
+ * - per-VM extra-data
+ * - requested configuration (by pCfgReq)
+ * - default value
+ */
+static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertMsg((pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) == 0,
+ ("Stream '%s' already initialized in backend\n", pStreamEx->Core.Cfg.szName));
+
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /*
+ * Check if the stream direction is enabled (permitted).
+ */
+ if (!drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir))
+ {
+ LogFunc(("Stream direction is disbled, returning w/o doing anything\n"));
+ return VINF_SUCCESS;
+ }
+#endif
+
+ /*
+ * Adjust the stream config, applying defaults and any overriding settings.
+ */
+ int rc = drvAudioStreamAdjustConfig(pThis, &pStreamEx->Core.Cfg, pStreamEx->Core.Cfg.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ PDMAUDIOSTREAMCFG const CfgReq = pStreamEx->Core.Cfg;
+
+ /*
+ * Call the host driver to create the stream.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ AssertLogRelMsgStmt(RT_VALID_PTR(pThis->pHostDrvAudio),
+ ("Audio: %p\n", pThis->pHostDrvAudio), rc = VERR_PDM_NO_ATTACHED_DRIVER);
+ if (RT_SUCCESS(rc))
+ AssertLogRelMsgStmt(pStreamEx->Core.cbBackend == pThis->BackendCfg.cbStream,
+ ("Audio: Backend changed? cbBackend changed from %#x to %#x\n",
+ pStreamEx->Core.cbBackend, pThis->BackendCfg.cbStream),
+ rc = VERR_STATE_CHANGED);
+ if (RT_SUCCESS(rc))
+ rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, &CfgReq, &pStreamEx->Core.Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ AssertLogRelReturn(pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INTERNAL_ERROR_3);
+ AssertLogRelReturn(pStreamEx->pBackend->pStream == &pStreamEx->Core, VERR_INTERNAL_ERROR_3);
+
+ /* Must set the backend-initialized flag now or the backend won't be
+ destroyed (this used to be done at the end of this function, with
+ several possible early return paths before it). */
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_CREATED;
+ }
+ else
+ {
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (rc == VERR_NOT_SUPPORTED)
+ LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.Cfg.szName));
+ else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
+ LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n",
+ pStreamEx->Core.Cfg.szName));
+ else
+ LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+ }
+
+ /* Remember if we need to call pfnStreamInitAsync. */
+ pStreamEx->fNeedAsyncInit = rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
+ AssertStmt(rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED || pThis->pHostDrvAudio->pfnStreamInitAsync != NULL,
+ pStreamEx->fNeedAsyncInit = false);
+ AssertMsg( rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED
+ || pStreamEx->enmLastBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING,
+ ("rc=%Rrc %s\n", rc, PDMHostAudioStreamStateGetName(pStreamEx->enmLastBackendState)));
+
+ PPDMAUDIOSTREAMCFG const pCfgAcq = &pStreamEx->Core.Cfg;
+
+ /*
+ * Validate acquired configuration.
+ */
+ char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
+ LogFunc(("Backend returned: %s\n", PDMAudioStrmCfgToString(pCfgAcq, szTmp, sizeof(szTmp)) ));
+ AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq),
+ ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n",
+ pCfgAcq->szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))),
+ VERR_INVALID_PARAMETER);
+
+ /* Let the user know that the backend changed one of the values requested above. */
+ if (pCfgAcq->Backend.cFramesBufferSize != CfgReq.Backend.cFramesBufferSize)
+ LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesBufferSize), CfgReq.Backend.cFramesBufferSize,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
+
+ if (pCfgAcq->Backend.cFramesPeriod != CfgReq.Backend.cFramesPeriod)
+ LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPeriod), CfgReq.Backend.cFramesPeriod,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod));
+
+ /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
+ if (CfgReq.Backend.cFramesPreBuffering)
+ {
+ if (pCfgAcq->Backend.cFramesPreBuffering != CfgReq.Backend.cFramesPreBuffering)
+ LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPreBuffering), CfgReq.Backend.cFramesPreBuffering,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+
+ if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize)
+ {
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize;
+ LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+ }
+ }
+ else if (CfgReq.Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
+ {
+ LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pCfgAcq->szName));
+ pCfgAcq->Backend.cFramesPreBuffering = 0;
+ }
+
+ /*
+ * Check if the backend did return sane values and correct if necessary.
+ */
+ uint32_t const cFramesPreBufferingMax = pCfgAcq->Backend.cFramesBufferSize - RT_MIN(16, pCfgAcq->Backend.cFramesBufferSize);
+ if (pCfgAcq->Backend.cFramesPreBuffering > cFramesPreBufferingMax)
+ {
+ LogRel2(("Audio: Warning! Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
+ pCfgAcq->Backend.cFramesPreBuffering, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, cFramesPreBufferingMax));
+ AssertMsgFailed(("cFramesPreBuffering=%#x vs cFramesPreBufferingMax=%#x\n", pCfgAcq->Backend.cFramesPreBuffering, cFramesPreBufferingMax));
+ pCfgAcq->Backend.cFramesPreBuffering = cFramesPreBufferingMax;
+ }
+
+ if (pCfgAcq->Backend.cFramesPeriod > pCfgAcq->Backend.cFramesBufferSize)
+ {
+ LogRel2(("Audio: Warning! Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
+ pCfgAcq->Backend.cFramesPeriod, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, pCfgAcq->Backend.cFramesBufferSize / 2));
+ AssertMsgFailed(("cFramesPeriod=%#x vs cFramesBufferSize=%#x\n", pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize));
+ pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 2;
+ }
+
+ LogRel2(("Audio: Buffer size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
+ LogRel2(("Audio: Pre-buffering size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+ LogRel2(("Audio: Scheduling hint for stream '%s' is %RU32ms / %RU32 frames\n", pCfgAcq->szName,
+ pCfgAcq->Device.cMsSchedulingHint, PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCfgAcq->Device.cMsSchedulingHint)));
+
+ /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
+ uint32_t const cMsPeriod = PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod);
+ LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n",
+ pCfgAcq->szName, cMsPeriod, pCfgAcq->Backend.cFramesPeriod));
+ /** @todo r=bird: This is probably a misleading/harmless warning as we'd just
+ * have to transfer more each time we move data. The period is generally
+ * pure irrelevant fiction anyway. A more relevant comparison would
+ * be to half the buffer size, i.e. making sure we get scheduled often
+ * enough to keep the buffer at least half full (probably more
+ * sensible if the buffer size was more than 2x scheduling periods). */
+ if ( CfgReq.Device.cMsSchedulingHint /* Any scheduling hint set? */
+ && CfgReq.Device.cMsSchedulingHint > cMsPeriod) /* This might lead to buffer underflows. */
+ LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
+ pCfgAcq->szName, CfgReq.Device.cMsSchedulingHint, cMsPeriod));
+
+ /*
+ * Done, just log the result:
+ */
+ LogFunc(("Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ LogRel2(("Audio: Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamCreate that initializes the audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to initialize. Caller already set a few fields.
+ * The Core.Cfg field contains the requested configuration
+ * when we're called and the actual configuration when
+ * successfully returning.
+ */
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ /*
+ * Init host stream.
+ */
+ pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
+
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ LogFunc(("Requested stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ LogRel2(("Audio: Creating stream: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+
+ int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Configure host buffers.
+ */
+ Assert(pStreamEx->cbPreBufThreshold == 0);
+ if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0)
+ pStreamEx->cbPreBufThreshold = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ pStreamEx->Core.Cfg.Backend.cFramesPreBuffering);
+
+ /* Allocate space for pre-buffering of output stream w/o mixing buffers. */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ Assert(pStreamEx->Out.cbPreBufAlloc == 0);
+ Assert(pStreamEx->Out.cbPreBuffered == 0);
+ Assert(pStreamEx->Out.offPreBuf == 0);
+ if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0)
+ {
+ uint32_t cbPreBufAlloc = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ pStreamEx->Core.Cfg.Backend.cFramesBufferSize);
+ cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->cbPreBufThreshold + _8K, _4K), cbPreBufAlloc);
+ cbPreBufAlloc = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbPreBufAlloc);
+ pStreamEx->Out.cbPreBufAlloc = cbPreBufAlloc;
+ pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(cbPreBufAlloc);
+ AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY);
+ }
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; /* Changed upon enable. */
+ }
+
+ /*
+ * Register statistics.
+ */
+ PPDMDRVINS const pDrvIns = pThis->pDrvIns;
+ /** @todo expose config and more. */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "The size of the backend period (in frames)", "%s/0-HostBackendPeriodSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPreBuffering, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Pre-buffer size (in frames)", "%s/0-HostBackendPreBufferSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Device.cMsSchedulingHint, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Device DMA scheduling hint (in milliseconds)", "%s/0-DeviceSchedulingHint", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ,
+ "Backend stream frequency", "%s/Hz", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Backend frame size", "%s/Framesize", pStreamEx->Core.Cfg.szName);
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer before play", "%s/0-HostBackendBufReadableBefore", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer after play", "%s/0-HostBackendBufReadableAfter", pStreamEx->Core.Cfg.szName);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfCapture, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamCapture", "%s/ProfStreamCapture", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetReadable", "%s/ProfStreamGetReadable", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Readable byte stats", "%s/ProfStreamGetReadableBytes", pStreamEx->Core.Cfg.szName);
+#endif
+ }
+ else
+ {
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer before play", "%s/0-HostBackendBufWritableBefore", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer after play", "%s/0-HostBackendBufWritableAfter", pStreamEx->Core.Cfg.szName);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfPlay, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamPlay", "%s/ProfStreamPlay", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetWritable", "%s/ProfStreamGetWritable", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Writeable byte stats", "%s/ProfStreamGetWritableBytes", pStreamEx->Core.Cfg.szName);
+#endif
+ }
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatProfGetState, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetState", "%s/ProfStreamGetState", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatXfer, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Byte transfer stats (excluding pre-buffering)", "%s/Transfers", pStreamEx->Core.Cfg.szName);
+#endif
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->offInternal, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Internal stream offset", "%s/offInternal", pStreamEx->Core.Cfg.szName);
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAM *ppStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Assert sanity.
+ */
+ AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
+ *ppStream = NULL;
+ LogFlowFunc(("pCfgReq=%s\n", pCfgReq->szName));
+#ifdef LOG_ENABLED
+ PDMAudioStrmCfgLog(pCfgReq);
+#endif
+ AssertReturn(AudioHlpStreamCfgIsValid(pCfgReq), VERR_INVALID_PARAMETER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED);
+
+ /*
+ * Grab a free stream count now.
+ */
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+
+ uint32_t * const pcFreeStreams = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.cStreamsFree : &pThis->Out.cStreamsFree;
+ if (*pcFreeStreams > 0)
+ *pcFreeStreams -= 1;
+ else
+ {
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ LogFlowFunc(("Maximum number of host %s streams reached\n", PDMAudioDirGetName(pCfgReq->enmDir) ));
+ return pCfgReq->enmDir == PDMAUDIODIR_IN ? VERR_AUDIO_NO_FREE_INPUT_STREAMS : VERR_AUDIO_NO_FREE_OUTPUT_STREAMS;
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ /*
+ * Get and check the backend size.
+ *
+ * Since we'll have to leave the hot-plug lock before we call the backend,
+ * we'll have revalidate the size at that time.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ size_t const cbHstStrm = pThis->BackendCfg.cbStream;
+ AssertStmt(cbHstStrm >= sizeof(PDMAUDIOBACKENDSTREAM), rc = VERR_OUT_OF_RANGE);
+ AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize common state.
+ */
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64));
+ if (pStreamEx)
+ {
+ rc = RTCritSectInit(&pStreamEx->Core.CritSect); /* (drvAudioStreamFree assumes it's initailized) */
+ if (RT_SUCCESS(rc))
+ {
+ PPDMAUDIOBACKENDSTREAM pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1);
+ pBackend->uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
+ pBackend->pStream = &pStreamEx->Core;
+
+ pStreamEx->pBackend = pBackend;
+ pStreamEx->Core.Cfg = *pCfgReq;
+ pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm;
+ pStreamEx->fDestroyImmediate = true;
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+ pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC;
+
+ /* Make a unqiue stream name including the host (backend) driver name. */
+ AssertPtr(pThis->pHostDrvAudio);
+ size_t cchName = RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:0",
+ pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : "<NoName>");
+ if (cchName < sizeof(pStreamEx->Core.Cfg.szName))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ bool fDone = true;
+ PDRVAUDIOSTREAM pIt;
+ RTListForEach(&pThis->LstStreams, pIt, DRVAUDIOSTREAM, ListEntry)
+ {
+ if (strcmp(pIt->Core.Cfg.szName, pStreamEx->Core.Cfg.szName) == 0)
+ {
+ RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:%u",
+ pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : "<NoName>",
+ i);
+ fDone = false;
+ break;
+ }
+ }
+ if (fDone)
+ break;
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ }
+
+ /*
+ * Try to init the rest.
+ */
+ rc = drvAudioStreamInitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Set initial reference counts. */
+ pStreamEx->cRefs = pStreamEx->fNeedAsyncInit ? 2 : 1;
+
+ /* Add it to the list. */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ RTListAppend(&pThis->LstStreams, &pStreamEx->ListEntry);
+ pThis->cStreams++;
+ STAM_REL_COUNTER_INC(&pThis->StatTotalStreamsCreated);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ /*
+ * Init debug stuff if enabled (ignore failures).
+ */
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->CfgIn.Dbg.fEnabled)
+ AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCapture, pThis->CfgIn.Dbg.szPathOut,
+ "DrvAudioCapture", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props);
+ }
+ else /* Out */
+ {
+ if (pThis->CfgOut.Dbg.fEnabled)
+ AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlay, pThis->CfgOut.Dbg.szPathOut,
+ "DrvAudioPlay", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props);
+ }
+
+ /*
+ * Kick off the asynchronous init.
+ */
+ if (!pStreamEx->fNeedAsyncInit)
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /* drvAudioStreamInitInternal returns success for disable stream directions w/o actually
+ creating a backend, so we need to check that before marking the backend ready.. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+#endif
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx));
+ }
+
+#ifdef VBOX_STRICT
+ /*
+ * Assert lock order to make sure the lock validator picks up on it.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+#endif
+
+ *ppStream = &pStreamEx->Core;
+ LogFlowFunc(("returns VINF_SUCCESS (pStreamEx=%p)\n", pStreamEx));
+ return VINF_SUCCESS;
+ }
+
+ LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc));
+ int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ AssertRC(rc2);
+ drvAudioStreamFree(pStreamEx);
+ }
+ else
+ RTMemFree(pStreamEx);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /*
+ * Give back the stream count, we couldn't use it after all.
+ */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ *pcFreeStreams += 1;
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Calls the backend to give it the chance to destroy its part of the audio stream.
+ *
+ * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and
+ * drvAudioStreamReInitInternal.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Audio stream destruct backend for.
+ */
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+
+ int rc = VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ AssertPtr(pStreamEx->pBackend);
+
+ /* Check if the pointer to the host audio driver is still valid.
+ * It can be NULL if we were called in drvAudioDestruct, for example. */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug); /** @todo needed? */
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend, pStreamEx->fDestroyImmediate);
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY);
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * Uninitializes an audio stream - worker for drvAudioStreamDestroy,
+ * drvAudioDestruct and drvAudioStreamCreate.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Pointer to audio stream to uninitialize.
+ */
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertMsgReturn(pStreamEx->cRefs <= 1,
+ ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs),
+ VERR_WRONG_ORDER);
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs));
+
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * ...
+ */
+ if (pStreamEx->fDestroyImmediate)
+ drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ int rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+
+ /* Free pre-buffer space. */
+ if ( pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT
+ && pStreamEx->Out.pbPreBuf)
+ {
+ RTMemFree(pStreamEx->Out.pbPreBuf);
+ pStreamEx->Out.pbPreBuf = NULL;
+ pStreamEx->Out.cbPreBufAlloc = 0;
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ if (pStreamEx->fStatus != PDMAUDIOSTREAM_STS_NONE)
+ {
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+ LogFunc(("[%s] Warning: Still has %s set when uninitializing\n",
+ pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+ }
+#endif
+ pStreamEx->fStatus = PDMAUDIOSTREAM_STS_NONE;
+ }
+
+ PPDMDRVINS const pDrvIns = pThis->pDrvIns;
+ PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.Cfg.szName);
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->CfgIn.Dbg.fEnabled)
+ {
+ AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCapture);
+ pStreamEx->In.Dbg.pFileCapture = NULL;
+ }
+ }
+ else
+ {
+ Assert(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT);
+ if (pThis->CfgOut.Dbg.fEnabled)
+ {
+ AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlay);
+ pStreamEx->Out.Dbg.pFilePlay = NULL;
+ }
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Internal release function.
+ *
+ * @returns New reference count, UINT32_MAX if bad stream.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to reference.
+ * @param fMayDestroy Whether the caller is allowed to implicitly destroy
+ * the stream or not.
+ */
+static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy)
+{
+ AssertPtrReturn(pStreamEx, UINT32_MAX);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
+ Assert(!RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ uint32_t cRefs = ASMAtomicDecU32(&pStreamEx->cRefs);
+ if (cRefs != 0)
+ Assert(cRefs < _1K);
+ else if (fMayDestroy)
+ {
+/** @todo r=bird: Caching one stream in each direction for some time,
+ * depending on the time it took to create it. drvAudioStreamCreate can use it
+ * if the configuration matches, otherwise it'll throw it away. This will
+ * provide a general speedup independ of device (HDA used to do this, but
+ * doesn't) and backend implementation. Ofc, the backend probably needs an
+ * opt-out here. */
+ int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ pThis->In.cStreamsFree++;
+ else /* Out */
+ pThis->Out.cStreamsFree++;
+ pThis->cStreams--;
+
+ RTListNodeRemove(&pStreamEx->ListEntry);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ drvAudioStreamFree(pStreamEx);
+ }
+ else
+ {
+ LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ /** @todo r=bird: What's the plan now? */
+ }
+ }
+ else
+ {
+ cRefs = ASMAtomicIncU32(&pStreamEx->cRefs);
+ AssertFailed();
+ }
+
+ Log12Func(("returns %u (%s)\n", cRefs, cRefs > 0 ? pStreamEx->Core.Cfg.szName : "destroyed"));
+ return cRefs;
+}
+
+
+/**
+ * Asynchronous worker for drvAudioStreamDestroy.
+ *
+ * Does DISABLE and releases reference, possibly destroying the stream.
+ *
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream. One reference for us to release.
+ * @param fImmediate How to treat draining streams.
+ */
+static DECLCALLBACK(void) drvAudioStreamDestroyAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fImmediate)
+{
+ LogFlowFunc(("pThis=%p pStreamEx=%p (%s) fImmediate=%RTbool\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate));
+#ifdef LOG_ENABLED
+ uint64_t const nsStart = RTTimeNanoTS();
+#endif
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ pStreamEx->fDestroyImmediate = fImmediate; /* Do NOT adjust for draining status, just pass it as-is. CoreAudio needs this. */
+
+ if (!fImmediate && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ LogFlowFunc(("No DISABLE\n"));
+ else
+ {
+ int rc2 = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ LogFlowFunc(("DISABLE done: %Rrc\n", rc2));
+ AssertRC(rc2);
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+
+ LogFlowFunc(("returning (after %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, bool fImmediate)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /* Ignore NULL streams. */
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ LogFlowFunc(("ENTER - %p (%s) fImmediate=%RTbool\n", pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate));
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->pBackend && pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * The main difference from a regular release is that this will disable
+ * (or drain if we could) the stream and we can cancel any pending
+ * pfnStreamInitAsync call.
+ */
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ if (pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC)
+ {
+ if (pStreamEx->cRefs > 0 && pStreamEx->cRefs < UINT32_MAX / 4)
+ {
+ char szStatus[DRVAUDIO_STATUS_STR_MAX];
+ LogRel2(("Audio: Destroying stream '%s': cRefs=%u; status: %s; backend: %s; hReqInitAsync=%p\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->cRefs, drvAudioStreamStatusToStr(szStatus, pStreamEx->fStatus),
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)),
+ pStreamEx->hReqInitAsync));
+
+ /* Try cancel pending async init request and release the it. */
+ if (pStreamEx->hReqInitAsync != NIL_RTREQ)
+ {
+ Assert(pStreamEx->cRefs >= 2);
+ int rc2 = RTReqCancel(pStreamEx->hReqInitAsync);
+
+ RTReqRelease(pStreamEx->hReqInitAsync);
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /* (exit before releasing the stream to avoid assertion) */
+
+ if (RT_SUCCESS(rc2))
+ {
+ LogFlowFunc(("Successfully cancelled pending pfnStreamInitAsync call (hReqInitAsync=%p).\n",
+ pStreamEx->hReqInitAsync));
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ }
+ else
+ {
+ LogFlowFunc(("Failed to cancel pending pfnStreamInitAsync call (hReqInitAsync=%p): %Rrc\n",
+ pStreamEx->hReqInitAsync, rc2));
+ Assert(rc2 == VERR_RT_REQUEST_STATE);
+ }
+ }
+ else
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Now, if the backend requests asynchronous disabling and destruction
+ * push the disabling and destroying over to a worker thread.
+ *
+ * This is a general offloading feature that all backends should make use of,
+ * however it's rather precarious on macs where stopping an already draining
+ * stream may take 8-10ms which naturally isn't something we should be doing
+ * on an EMT.
+ */
+ if (!(pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY))
+ drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate);
+ else
+ {
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamDestroyAsync, 3, pThis, pStreamEx, fImmediate);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailedStmt(("%p cRefs=%#x\n", pStreamEx, pStreamEx->cRefs), rc = VERR_CALLER_NO_REFERENCE);
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailedStmt(("%p uMagic=%#x\n", pStreamEx, pStreamEx->uMagic), rc = VERR_INVALID_MAGIC);
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Drops all audio data (and associated state) of a stream.
+ *
+ * Used by drvAudioStreamIterateInternal(), drvAudioStreamResetOnDisable(), and
+ * drvAudioStreamReInitInternal().
+ *
+ * @param pStreamEx Stream to drop data for.
+ */
+static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx)
+{
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ pStreamEx->nsLastIterated = 0;
+ pStreamEx->nsLastPlayedCaptured = 0;
+ pStreamEx->nsLastReadWritten = 0;
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
+ }
+ else
+ pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING;
+}
+
+
+/**
+ * Re-initializes an audio stream with its existing host and guest stream
+ * configuration.
+ *
+ * This might be the case if the backend told us we need to re-initialize
+ * because something on the host side has changed.
+ *
+ * @note Does not touch the stream's status flags.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to re-initialize.
+ */
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ char szTmp[RT_MAX(PDMAUDIOSTRMCFGTOSTRING_MAX, DRVAUDIO_STATUS_STR_MAX)];
+ LogFlowFunc(("[%s] status: %s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Destroy and re-create stream on backend side.
+ */
+ if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
+ == (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+
+ int rc = VERR_AUDIO_STREAM_NOT_READY;
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ drvAudioStreamResetInternal(pStreamEx);
+
+ RT_BZERO(pStreamEx->pBackend + 1, pStreamEx->Core.cbBackend - sizeof(*pStreamEx->pBackend));
+
+ rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("Acquired host config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ /** @todo Validate (re-)acquired configuration with pStreamEx->Core.Core.Cfg?
+ * drvAudioStreamInitInternal() does some setup and a bunch of
+ * validations + adjustments of the stream config, so this surely is quite
+ * optimistic. */
+ if (true)
+ {
+ /*
+ * Kick off the asynchronous init.
+ */
+ if (!pStreamEx->fNeedAsyncInit)
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ drvAudioStreamRetainInternal(pStreamEx);
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx));
+ }
+
+ /*
+ * Update the backend on the stream state if it's ready, otherwise
+ * let the worker thread do it after the async init has completed.
+ */
+ if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ == (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "re-initializing");
+ /** @todo not sure if we really need to care about this status code... */
+ }
+ else if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ Assert(pStreamEx->hReqInitAsync != NIL_RTREQ);
+ LogFunc(("Asynchronous stream init (%p) ...\n", pStreamEx->hReqInitAsync));
+ }
+ else
+ {
+ LogRel(("Audio: Re-initializing stream '%s' somehow failed, status: %s\n", pStreamEx->Core.Cfg.szName,
+ drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
+ AssertFailed();
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ }
+ }
+ else
+ LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ }
+ else
+ {
+ LogRel(("Audio: Re-initializing stream '%s' failed to destroy previous backend.\n", pStreamEx->Core.Cfg.szName));
+ AssertFailed();
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamReInit}
+ */
+static DECLCALLBACK(int) drvAudioStreamReInit(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT, VERR_INVALID_STATE);
+ LogFlowFunc(("\n"));
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT)
+ {
+ const unsigned cMaxTries = 5;
+ const uint64_t nsNow = RTTimeNanoTS();
+
+ /* Throttle re-initializing streams on failure. */
+ if ( pStreamEx->cTriesReInit < cMaxTries
+ && pStreamEx->hReqInitAsync == NIL_RTREQ
+ && ( pStreamEx->nsLastReInit == 0
+ || nsNow - pStreamEx->nsLastReInit >= RT_NS_1SEC * pStreamEx->cTriesReInit))
+ {
+ rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Remove the pending re-init flag on success. */
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ pStreamEx->nsLastReInit = nsNow;
+ pStreamEx->cTriesReInit++;
+
+ /* Did we exceed our tries re-initializing the stream?
+ * Then this one is dead-in-the-water, so disable it for further use. */
+ if (pStreamEx->cTriesReInit >= cMaxTries)
+ {
+ LogRel(("Audio: Re-initializing stream '%s' exceeded maximum retries (%u), leaving as disabled\n",
+ pStreamEx->Core.Cfg.szName, cMaxTries));
+
+ /* Don't try to re-initialize anymore and mark as disabled. */
+ /** @todo should mark it as not-initialized too, shouldn't we? */
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_NEED_REINIT | PDMAUDIOSTREAM_STS_ENABLED);
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+
+ /* Note: Further writes to this stream go to / will be read from the bit bucket (/dev/null) from now on. */
+ }
+ }
+ }
+ else
+ Log8Func(("cTriesReInit=%d hReqInitAsync=%p nsLast=%RU64 nsNow=%RU64 nsDelta=%RU64\n", pStreamEx->cTriesReInit,
+ pStreamEx->hReqInitAsync, pStreamEx->nsLastReInit, nsNow, nsNow - pStreamEx->nsLastReInit));
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+ }
+ else
+ {
+ AssertFailed();
+ rc = VERR_INVALID_STATE;
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Internal retain function.
+ *
+ * @returns New reference count, UINT32_MAX if bad stream.
+ * @param pStreamEx The stream to reference.
+ */
+static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pStreamEx, UINT32_MAX);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
+
+ uint32_t const cRefs = ASMAtomicIncU32(&pStreamEx->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _1K);
+
+ Log12Func(("returns %u (%s)\n", cRefs, pStreamEx->Core.Cfg.szName));
+ return cRefs;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ return drvAudioStreamRetainInternal((PDRVAUDIOSTREAM)pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ return drvAudioStreamReleaseInternal(RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector),
+ (PDRVAUDIOSTREAM)pStream,
+ false /*fMayDestroy*/);
+}
+
+
+/**
+ * Controls a stream's backend.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to control.
+ * @param enmStreamCmd Control command.
+ *
+ * @note Caller has entered the critical section of the stream.
+ * @note Can be called w/o having entered DRVAUDIO::CritSectHotPlug.
+ */
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Whether to propagate commands down to the backend.
+ *
+ * 1. If the stream direction is disabled on the driver level, we should
+ * obviously not call the backend. Our stream status will reflect the
+ * actual state so drvAudioEnable() can tell the backend if the user
+ * re-enables the stream direction.
+ *
+ * 2. If the backend hasn't finished initializing yet, don't try call
+ * it to start/stop/pause/whatever the stream. (Better to do it here
+ * than to replicate this in the relevant backends.) When the backend
+ * finish initializing the stream, we'll update it about the stream state.
+ */
+ bool const fDirEnabled = drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir);
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ /* ^^^ (checks pThis->pHostDrvAudio != NULL too) */
+
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+ LogRel2(("Audio: %s stream '%s' backend (%s is %s; status: %s; backend-status: %s)\n",
+ PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir),
+ fDirEnabled ? "enabled" : "disabled", drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus),
+ PDMHostAudioStreamStateGetName(enmBackendState) ));
+
+ if (fDirEnabled)
+ {
+ if ( (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY /* don't really need this check, do we? */)
+ && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) )
+ {
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ rc = pThis->pHostDrvAudio->pfnStreamEnable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ rc = pThis->pHostDrvAudio->pfnStreamDisable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ rc = pThis->pHostDrvAudio->pfnStreamPause(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ rc = pThis->pHostDrvAudio->pfnStreamResume(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ if (pThis->pHostDrvAudio->pfnStreamDrain)
+ rc = pThis->pHostDrvAudio->pfnStreamDrain(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ default:
+ AssertMsgFailedBreakStmt(("Command %RU32 not implemented\n", enmStreamCmd), rc = VERR_INTERNAL_ERROR_2);
+ }
+ if (RT_SUCCESS(rc))
+ Log2Func(("[%s] %s succeeded (%Rrc)\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
+ else
+ {
+ LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
+ if ( rc != VERR_NOT_IMPLEMENTED
+ && rc != VERR_NOT_SUPPORTED
+ && rc != VERR_AUDIO_STREAM_NOT_READY)
+ LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, rc));
+ }
+ }
+ else
+ LogFlowFunc(("enmBackendStat(=%s) != OKAY || !(fStatus(=%#x) & BACKEND_READY)\n",
+ PDMHostAudioStreamStateGetName(enmBackendState), pStreamEx->fStatus));
+ }
+ else
+ LogFlowFunc(("fDirEnabled=false\n"));
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ return rc;
+}
+
+
+/**
+ * Resets the given audio stream.
+ *
+ * @param pStreamEx Stream to reset.
+ */
+static void drvAudioStreamResetOnDisable(PDRVAUDIOSTREAM pStreamEx)
+{
+ drvAudioStreamResetInternal(pStreamEx);
+
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+
+ pStreamEx->fStatus &= PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY;
+ pStreamEx->Core.fWarningsShown = PDMAUDIOSTREAM_WARN_FLAGS_NONE;
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Reset statistics.
+ */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ }
+ else if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ }
+ else
+ AssertFailed();
+#endif
+}
+
+
+/**
+ * Controls an audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd),
+ drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ if (RT_FAILURE(rc))
+ break;
+ }
+#endif /* DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION */
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED))
+ {
+ /* Are we still draining this stream? Then we must disable it first. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ {
+ LogFunc(("Stream '%s' is still draining - disabling...\n", pStreamEx->Core.Cfg.szName));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ if (drvAudioStreamGetBackendState(pThis, pStreamEx) != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ drvAudioStreamResetInternal(pStreamEx);
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Reset the state before we try to start. */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ pStreamEx->enmLastBackendState = enmBackendState;
+ pStreamEx->offInternal = 0;
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ switch (enmBackendState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ if (pStreamEx->cbPreBufThreshold > 0)
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ AssertFailed();
+ RT_FALL_THROUGH();
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ break;
+ /* no default */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ LogFunc(("ENABLE: enmBackendState=%s enmPlayState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ }
+ else
+ {
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE;
+ switch (enmBackendState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_PREBUF;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ AssertFailed();
+ RT_FALL_THROUGH();
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ break;
+ /* no default */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ LogFunc(("ENABLE: enmBackendState=%s enmCaptureState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState),
+ drvAudioCaptureStateName(pStreamEx->In.enmCaptureState)));
+ }
+
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->nsStarted = RTTimeNanoTS();
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_ENABLED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+#ifndef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ LogFunc(("DISABLE '%s': Backend DISABLE -> %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ if (RT_SUCCESS(rc)) /** @todo ignore this and reset it anyway? */
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+#else
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+#endif /* DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION */
+ break;
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) == PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PAUSED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED)
+ {
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED);
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ /*
+ * Only for output streams and we don't want this command more than once.
+ */
+ AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_FUNCTION);
+ AssertBreak(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE));
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = VERR_INTERNAL_ERROR_2;
+ switch (pStreamEx->Out.enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ if (pStreamEx->Out.cbPreBuffered > 0)
+ {
+ LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data...\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ RT_FALL_THROUGH();
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ LogFunc(("DRAIN '%s': Nothing to drain (enmPlayState=%s)\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ LogFunc(("DRAIN '%s': Initiating backend draining (enmPlayState=%s -> NOPLAY) ...\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ LogFunc(("DRAIN '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+ pStreamEx->Core.Cfg.szName, rc));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data (already committing)...\n",
+ pStreamEx->Core.Cfg.szName));
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ rc = VINF_SUCCESS;
+ break;
+
+ /* no default */
+ case DRVAUDIOPLAYSTATE_INVALID:
+ case DRVAUDIOPLAYSTATE_END:
+ AssertFailedBreak();
+ }
+ }
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /** @todo r=bird: why? It's not documented to ignore NULL streams. */
+ if (!pStream)
+ return VINF_SUCCESS;
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd)));
+
+ rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/**
+ * Copy data to the pre-buffer, ring-buffer style.
+ *
+ * The @a cbMax parameter is almost always set to the threshold size, the
+ * exception is when commiting the buffer and we want to top it off to reduce
+ * the number of transfers to the backend (the first transfer may start
+ * playback, so more data is better).
+ */
+static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax)
+{
+ uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
+ AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3);
+ AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4);
+ AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5);
+
+ uint32_t offRead = pStreamEx->Out.offPreBuf;
+ uint32_t cbCur = pStreamEx->Out.cbPreBuffered;
+ AssertStmt(offRead < cbAlloc, offRead %= cbAlloc);
+ AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax);
+
+ /*
+ * First chunk.
+ */
+ uint32_t offWrite = (offRead + cbCur) % cbAlloc;
+ uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf);
+ memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy);
+
+ /* Advance. */
+ offWrite = (offWrite + cbToCopy) % cbAlloc;
+ for (;;)
+ {
+ pbBuf += cbToCopy;
+ cbCur += cbToCopy;
+ if (cbCur > cbMax)
+ offRead = (offRead + cbCur - cbMax) % cbAlloc;
+ cbBuf -= cbToCopy;
+ if (!cbBuf)
+ break;
+
+ /*
+ * Second+ chunk, from the start of the buffer.
+ *
+ * Note! It is assumed very unlikely that we will ever see a cbBuf larger than
+ * cbMax, so we don't waste space on clipping cbBuf here (can happen with
+ * custom pre-buffer sizes).
+ */
+ Assert(offWrite == 0);
+ cbToCopy = RT_MIN(cbAlloc, cbBuf);
+ memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy);
+ }
+
+ /*
+ * Update the pre-buffering size and position.
+ */
+ pStreamEx->Out.cbPreBuffered = RT_MIN(cbCur, cbMax);
+ pStreamEx->Out.offPreBuf = offRead;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
+ *
+ * Caller owns the lock.
+ */
+static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf));
+
+ uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
+
+ uint32_t cbWritten = 0;
+ int rc = VINF_SUCCESS;
+ uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props);
+ while (cbBuf >= cbFrame && cbWritable >= cbFrame)
+ {
+ uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbWritable));
+ uint32_t cbWrittenNow = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbWrittenNow != cbToWrite)
+ Log3Func(("%s: @%#RX64: Wrote fewer bytes than requested: %#x, requested %#x\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
+#ifdef DEBUG_bird
+ Assert(cbWrittenNow == cbToWrite);
+#endif
+ AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
+ cbWritten += cbWrittenNow;
+ cbBuf -= cbWrittenNow;
+ pbBuf += cbWrittenNow;
+ pStreamEx->offInternal += cbWrittenNow;
+ }
+ else
+ {
+ *pcbWritten = cbWritten;
+ LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
+ return cbWritten ? VINF_SUCCESS : rc;
+ }
+
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ }
+
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbWritten);
+ *pcbWritten = cbWritten;
+ pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
+ if (cbWritten)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWritten, cbBuf));
+ return rc;
+}
+
+
+/**
+ * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
+ */
+static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax,
+ uint32_t *pcbWritten)
+{
+ int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered,
+ pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->cbPreBufThreshold, 1)));
+
+ }
+ else
+ *pcbWritten = 0;
+ return rc;
+}
+
+
+/**
+ * Used when we're committing (transfering) the pre-buffered bytes to the
+ * device.
+ *
+ * This is called both from drvAudioStreamPlay() and
+ * drvAudioStreamIterateInternal().
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to commit the pre-buffering for.
+ * @param pbBuf Buffer with new bytes to write. Can be NULL when called
+ * in the PENDING_DISABLE state from
+ * drvAudioStreamIterateInternal().
+ * @param cbBuf Number of new bytes. Can be zero.
+ * @param pcbWritten Where to return the number of bytes written.
+ *
+ * @note Locking: Stream critsect and hot-plug in shared mode.
+ */
+static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ /*
+ * First, top up the buffer with new data from pbBuf.
+ */
+ *pcbWritten = 0;
+ if (cbBuf > 0)
+ {
+ uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf);
+ if (cbToCopy > 0)
+ {
+ int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten);
+ AssertRCReturn(rc, rc);
+ pbBuf += cbToCopy;
+ cbBuf -= cbToCopy;
+ }
+ }
+
+ AssertReturn(pThis->pHostDrvAudio, VERR_AUDIO_BACKEND_NOT_ATTACHED);
+
+ /*
+ * Write the pre-buffered chunk.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
+ AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2);
+ uint32_t off = pStreamEx->Out.offPreBuf;
+ AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc);
+ uint32_t cbLeft = pStreamEx->Out.cbPreBuffered;
+ while (cbLeft > 0)
+ {
+ uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft);
+ Assert(cbToWrite > 0);
+
+ uint32_t cbPreBufWritten = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
+ cbToWrite, &cbPreBufWritten);
+ AssertRCBreak(rc);
+ if (!cbPreBufWritten)
+ break;
+ AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite);
+ off = (off + cbPreBufWritten) % cbAlloc;
+ cbLeft -= cbPreBufWritten;
+ }
+
+ if (cbLeft == 0)
+ {
+ LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal,
+ pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY;
+
+ if (cbBuf > 0)
+ {
+ uint32_t cbWritten2 = 0;
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2);
+ if (RT_SUCCESS(rc))
+ *pcbWritten += cbWritten2;
+ }
+ else
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+ }
+ else
+ {
+ if (cbLeft != pStreamEx->Out.cbPreBuffered)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n",
+ pStreamEx->offInternal, pStreamEx->Core.Cfg.szName, pStreamEx->Out.cbPreBuffered - cbLeft,
+ pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ AssertMsg( pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING
+ || pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF
+ || RT_FAILURE(rc),
+ ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x enmPlayState=%s\n",
+ cbLeft, pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+
+ pStreamEx->Out.cbPreBuffered = cbLeft;
+ pStreamEx->Out.offPreBuf = off;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ }
+
+ return *pcbWritten ? VINF_SUCCESS : rc;
+}
+
+
+/**
+ * Does one iteration of an audio stream.
+ *
+ * This function gives the backend the chance of iterating / altering data and
+ * does the actual mixing between the guest <-> host mixing buffers.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to iterate.
+ */
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ /* Not enabled or paused? Skip iteration. */
+ if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) != PDMAUDIOSTREAM_STS_ENABLED)
+ return VINF_SUCCESS;
+
+ /*
+ * Pending disable is really what we're here for.
+ *
+ * This only happens to output streams. We ASSUME the caller (MixerBuffer)
+ * implements a timeout on the draining, so we skip that here.
+ */
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ { /* likely until we get to the end of the stream at least. */ }
+ else
+ {
+ AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Move pre-buffered samples to the backend.
+ */
+ if (pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING)
+ {
+ if (pStreamEx->Out.cbPreBuffered > 0)
+ {
+ uint32_t cbIgnored = 0;
+ drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
+ Log3Func(("Stream '%s': Transferred %#x bytes\n", pStreamEx->Core.Cfg.szName, cbIgnored));
+ }
+ if (pStreamEx->Out.cbPreBuffered == 0)
+ {
+ Log3Func(("Stream '%s': No more pre-buffered data -> NOPLAY + backend DRAIN\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+
+ int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Stream '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+ pStreamEx->Core.Cfg.szName, rc));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+ }
+ }
+ else
+ Assert(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_NOPLAY);
+
+ /*
+ * Check the backend status to see if it's still draining and to
+ * update our status when it stops doing so.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ uint32_t cbIgnored = 0;
+ pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, NULL, 0, &cbIgnored);
+ }
+ else
+ {
+ LogFunc(("Stream '%s': Backend finished draining.\n", pStreamEx->Core.Cfg.szName));
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+
+ /* Update timestamps. */
+ pStreamEx->nsLastIterated = RTTimeNanoTS();
+
+ return VINF_SUCCESS; /** @todo r=bird: What can the caller do with an error status here? */
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = drvAudioStreamIterateInternal(pThis, pStreamEx);
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTATE) drvAudioStreamGetState(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, PDMAUDIOSTREAMSTATE_INVALID);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
+ STAM_PROFILE_START(&pStreamEx->StatProfGetState, a);
+
+ /*
+ * Get the status mask.
+ */
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, PDMAUDIOSTREAMSTATE_INVALID);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+ uint32_t const fStrmStatus = pStreamEx->fStatus;
+ PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir;
+ Assert(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Translate it to state enum value.
+ */
+ PDMAUDIOSTREAMSTATE enmState;
+ if (!(fStrmStatus & PDMAUDIOSTREAM_STS_NEED_REINIT))
+ {
+ if (fStrmStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ if ( (fStrmStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ && drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir)
+ && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING ))
+ enmState = enmDir == PDMAUDIODIR_IN ? PDMAUDIOSTREAMSTATE_ENABLED_READABLE : PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE;
+ else
+ enmState = PDMAUDIOSTREAMSTATE_INACTIVE;
+ }
+ else
+ enmState = PDMAUDIOSTREAMSTATE_NOT_WORKING;
+ }
+ else
+ enmState = PDMAUDIOSTREAMSTATE_NEED_REINIT;
+
+ STAM_PROFILE_STOP(&pStreamEx->StatProfGetState, a);
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] returns %s (status: %s)\n", pStreamEx->Core.Cfg.szName, PDMAudioStreamStateGetName(enmState),
+ drvAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
+ return enmState;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, 0);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0);
+ STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfGetWritable, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, 0);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Use the playback and backend states to determin how much can be written, if anything.
+ */
+ uint32_t cbWritable = 0;
+ DRVAUDIOPLAYSTATE const enmPlayMode = pStreamEx->Out.enmPlayState;
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if ( PDMAudioStrmStatusCanWrite(pStreamEx->fStatus)
+ && pThis->pHostDrvAudio != NULL
+ && enmBackendState != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ switch (enmPlayMode)
+ {
+ /*
+ * Whatever the backend can hold.
+ */
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ /*
+ * Whatever we've got of available space in the pre-buffer.
+ * Note! For the last round when we pass the pre-buffering threshold, we may
+ * report fewer bytes than what a DMA timer period for the guest device
+ * typically produces, however that should be transfered in the following
+ * round that goes directly to the backend buffer.
+ */
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
+ if (!cbWritable)
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 2);
+ break;
+
+ /*
+ * These are slightly more problematic and can go wrong if the pre-buffer is
+ * manually configured to be smaller than the output of a typeical DMA timer
+ * period for the guest device. So, to overcompensate, we just report back
+ * the backend buffer size (the pre-buffer is circular, so no overflow issue).
+ */
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ RT_MAX(pStreamEx->Core.Cfg.Backend.cFramesBufferSize,
+ pStreamEx->Core.Cfg.Backend.cFramesPreBuffering));
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ {
+ /* Buggy backend: We weren't able to copy all the pre-buffered data to it
+ when reaching the threshold. Try escape this situation, or at least
+ keep the extra buffering to a minimum. We must try write something
+ as long as there is space for it, as we need the pfnStreamWrite call
+ to move the data. */
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 8);
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin)
+ cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2;
+ else
+ cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered);
+ AssertLogRel(cbWritable);
+ break;
+ }
+
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ break;
+ case DRVAUDIOPLAYSTATE_INVALID:
+ case DRVAUDIOPLAYSTATE_END:
+ AssertFailed();
+ break;
+ }
+
+ /* Make sure to align the writable size to the host's frame size. */
+ cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbWritable);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->Out.Stats.ProfGetWritableBytes, cbWritable);
+ STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfGetWritable, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ Log3Func(("[%s] cbWritable=%#RX32 (%RU64ms) enmPlayMode=%s enmBackendState=%s\n",
+ pStreamEx->Core.Cfg.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbWritable),
+ drvAudioPlayStateName(enmPlayMode), PDMHostAudioStreamStateGetName(enmBackendState) ));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Check input and sanity.
+ */
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ uint32_t uTmp;
+ if (pcbWritten)
+ AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);
+ else
+ pcbWritten = &uTmp;
+
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
+ pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED);
+
+ AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf),
+ ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf));
+ STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfPlay, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * First check that we can write to the stream, and if not,
+ * whether to just drop the input into the bit bucket.
+ */
+ if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if ( pThis->Out.fEnabled /* (see @bugref{9882}) */
+ && pThis->pHostDrvAudio != NULL)
+ {
+ /*
+ * Get the backend state and process changes to it since last time we checked.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+
+ /*
+ * Do the transfering.
+ */
+ switch (pStreamEx->Out.enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PLAY:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->cbPreBufThreshold);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->cbPreBufThreshold)
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ else if ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY))
+ {
+ Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n",
+ pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
+ cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold));
+ rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ }
+ else
+ {
+ Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n",
+ pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
+ cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE;
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ }
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ Assert( !(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)
+ || enmBackendState != PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ RT_FALL_THRU();
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmBackendState) ));
+ break;
+
+ default:
+ *pcbWritten = cbBuf;
+ AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf));
+ }
+
+ if (!pStreamEx->Out.Dbg.pFilePlay || RT_FAILURE(rc))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlay, pvBuf, *pcbWritten);
+ }
+ else
+ {
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.Cfg.szName,
+ !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+
+ STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfPlay, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, 0);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
+ AssertMsg(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
+ STAM_PROFILE_START(&pStreamEx->In.Stats.ProfGetReadable, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, 0);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Use the capture state to determin how much can be written, if anything.
+ */
+ uint32_t cbReadable = 0;
+ DRVAUDIOCAPTURESTATE const enmCaptureState = pStreamEx->In.enmCaptureState;
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); RT_NOREF(enmBackendState);
+ if ( PDMAudioStrmStatusCanRead(pStreamEx->fStatus)
+ && pThis->pHostDrvAudio != NULL)
+ {
+ switch (enmCaptureState)
+ {
+ /*
+ * Whatever the backend has to offer when in capture mode.
+ */
+ case DRVAUDIOCAPTURESTATE_CAPTURING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ /*
+ * Same calculation as in drvAudioStreamCaptureSilence, only we cap it
+ * at the pre-buffering threshold so we don't get into trouble when we
+ * switch to capture mode between now and pfnStreamCapture.
+ */
+ case DRVAUDIOCAPTURESTATE_PREBUF:
+ {
+ uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted;
+ uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream);
+ if (offCur > pStreamEx->offInternal)
+ {
+ uint64_t const cbUnread = offCur - pStreamEx->offInternal;
+ cbReadable = (uint32_t)RT_MIN(pStreamEx->cbPreBufThreshold, cbUnread);
+ }
+ break;
+ }
+
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE:
+ break;
+
+ case DRVAUDIOCAPTURESTATE_INVALID:
+ case DRVAUDIOCAPTURESTATE_END:
+ AssertFailed();
+ break;
+ }
+
+ /* Make sure to align the readable size to the host's frame size. */
+ cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbReadable);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->In.Stats.ProfGetReadableBytes, cbReadable);
+ STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfGetReadable, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ Log3Func(("[%s] cbReadable=%#RX32 (%RU64ms) enmCaptureMode=%s enmBackendState=%s\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbReadable),
+ drvAudioCaptureStateName(enmCaptureState), PDMHostAudioStreamStateGetName(enmBackendState) ));
+ return cbReadable;
+}
+
+
+/**
+ * Worker for drvAudioStreamCapture that returns silence.
+ *
+ * The amount of silence returned is a function of how long the stream has been
+ * enabled.
+ *
+ * @returns VINF_SUCCESS
+ * @param pStreamEx The stream to commit the pre-buffering for.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The size of the output buffer.
+ * @param pcbRead Where to return the number of bytes actually read.
+ */
+static int drvAudioStreamCaptureSilence(PDRVAUDIOSTREAM pStreamEx, uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ /** @todo Does not take paused time into account... */
+ uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted;
+ uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream);
+ if (offCur > pStreamEx->offInternal)
+ {
+ uint64_t const cbUnread = offCur - pStreamEx->offInternal;
+ uint32_t const cbToClear = (uint32_t)RT_MIN(cbBuf, cbUnread);
+ *pcbRead = cbToClear;
+ pStreamEx->offInternal += cbToClear;
+ cbBuf -= cbToClear;
+ PDMAudioPropsClearBuffer(&pStreamEx->Core.Cfg.Props, pbBuf, cbToClear,
+ PDMAudioPropsBytesToFrames(&pStreamEx->Core.Cfg.Props, cbToClear));
+ }
+ else
+ *pcbRead = 0;
+ Log4Func(("%s: @%#RX64: Read %#x bytes of silence (%#x bytes left)\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, *pcbRead, cbBuf));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamCapture.
+ */
+static int drvAudioStreamCaptureLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ Log4Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf));
+
+ uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable;
+
+ uint32_t cbRead = 0;
+ int rc = VINF_SUCCESS;
+ uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props);
+ while (cbBuf >= cbFrame && cbReadable >= cbFrame)
+ {
+ uint32_t const cbToRead = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbReadable));
+ uint32_t cbReadNow = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToRead, &cbReadNow);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbReadNow != cbToRead)
+ Log4Func(("%s: @%#RX64: Read fewer bytes than requested: %#x, requested %#x\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbReadNow, cbToRead));
+#ifdef DEBUG_bird
+ Assert(cbReadNow == cbToRead);
+#endif
+ AssertStmt(cbReadNow <= cbToRead, cbReadNow = cbToRead);
+ cbRead += cbReadNow;
+ cbBuf -= cbReadNow;
+ pbBuf += cbReadNow;
+ pStreamEx->offInternal += cbReadNow;
+ }
+ else
+ {
+ *pcbRead = cbRead;
+ LogFunc(("%s: @%#RX64: pfnStreamCapture failed read %#x bytes (%#x previous read, %#x readable): %Rrc\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToRead, cbRead, cbReadable, rc));
+ return cbRead ? VINF_SUCCESS : rc;
+ }
+
+ cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ }
+
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbRead);
+ *pcbRead = cbRead;
+ pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable;
+ if (cbRead)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ Log4Func(("%s: @%#RX64: Read %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbRead, cbBuf));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Check input and sanity.
+ */
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ uint32_t uTmp;
+ if (pcbRead)
+ AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
+ else
+ pcbRead = &uTmp;
+
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be read from (direction is '%s')\n",
+ pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED);
+
+ AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf),
+ ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf));
+ STAM_PROFILE_START(&pStreamEx->In.Stats.ProfCapture, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * First check that we can read from the stream, and if not,
+ * whether to just drop the input into the bit bucket.
+ */
+ if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if ( pThis->In.fEnabled /* (see @bugref{9882}) */
+ && pThis->pHostDrvAudio != NULL)
+ {
+ /*
+ * Get the backend state and process changes to it since last time we checked.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+
+ /*
+ * Do the transfering.
+ */
+ switch (pStreamEx->In.enmCaptureState)
+ {
+ case DRVAUDIOCAPTURESTATE_CAPTURING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+
+ case DRVAUDIOCAPTURESTATE_PREBUF:
+ if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
+ {
+ uint32_t const cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio,
+ pStreamEx->pBackend);
+ if (cbReadable >= pStreamEx->cbPreBufThreshold)
+ {
+ Log4Func(("[%s] Pre-buffering completed: cbReadable=%#x vs cbPreBufThreshold=%#x (cbBuf=%#x)\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold, cbBuf));
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_CAPTURING;
+ rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+ }
+ pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable;
+ pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable;
+ Log4Func(("[%s] Pre-buffering: Got %#x out of %#x\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold));
+ }
+ else
+ Log4Func(("[%s] Pre-buffering: Backend status %s\n",
+ pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) ));
+ drvAudioStreamCaptureSilence(pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE:
+ *pcbRead = 0;
+ Log4Func(("[%s] Not capturing - backend state: %s\n",
+ pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) ));
+ break;
+
+ default:
+ *pcbRead = 0;
+ AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->In.enmCaptureState, cbBuf));
+ }
+
+ if (!pStreamEx->In.Dbg.pFileCapture || RT_FAILURE(rc))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCapture, pvBuf, *pcbRead);
+ }
+ else
+ {
+ *pcbRead = 0;
+ Log4Func(("[%s] Backend stream %s, returning no data\n", pStreamEx->Core.Cfg.szName,
+ !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+
+ STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfCapture, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIOPORT interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for drvAudioHostPort_DoOnWorkerThread with stream argument, called on
+ * worker thread.
+ */
+static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadStreamWorker(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ uintptr_t uUser, void *pvUser)
+{
+ LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ /*
+ * The CritSectHotPlug lock should not be needed here as detach will destroy
+ * the thread pool. So, we'll leave taking the stream lock to the worker we're
+ * calling as there are no lock order concerns.
+ */
+ PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturnVoid(pIHostDrvAudio);
+ AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
+ pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, pStreamEx->pBackend, uUser, pvUser);
+
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Worker for drvAudioHostPort_DoOnWorkerThread without stream argument, called
+ * on worker thread.
+ *
+ * This wrapper isn't technically required, but it helps with logging and a few
+ * extra sanity checks.
+ */
+static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadWorker(PDRVAUDIO pThis, uintptr_t uUser, void *pvUser)
+{
+ LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
+ AssertPtrReturnVoid(pThis);
+
+ /*
+ * The CritSectHotPlug lock should not be needed here as detach will destroy
+ * the thread pool.
+ */
+ PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturnVoid(pIHostDrvAudio);
+ AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
+
+ pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, NULL, uUser, pvUser);
+
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnDoOnWorkerThread}
+ */
+static DECLCALLBACK(int) drvAudioHostPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ uintptr_t uUser, void *pvUser)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ LogFlowFunc(("pStream=%p uUser=%#zx pvUser=%p\n", pStream, uUser, pvUser));
+
+ /*
+ * Assert some sanity.
+ */
+ PDRVAUDIOSTREAM pStreamEx;
+ if (!pStream)
+ pStreamEx = NULL;
+ else
+ {
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertReturn(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ }
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ Assert(pThis->hReqPool != NIL_RTREQPOOL);
+ AssertPtr(pThis->pHostDrvAudio);
+ if ( pThis->hReqPool != NIL_RTREQPOOL
+ && pThis->pHostDrvAudio != NULL)
+ {
+ AssertPtr(pThis->pHostDrvAudio->pfnDoOnWorkerThread);
+ if (pThis->pHostDrvAudio->pfnDoOnWorkerThread)
+ {
+ /*
+ * Try do the work.
+ */
+ if (!pStreamEx)
+ {
+ rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioHostPort_DoOnWorkerThreadWorker, 3, pThis, uUser, pvUser);
+ AssertRC(rc);
+ }
+ else
+ {
+ uint32_t cRefs = drvAudioStreamRetainInternal(pStreamEx);
+ if (cRefs != UINT32_MAX)
+ {
+ rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioHostPort_DoOnWorkerThreadStreamWorker,
+ 4, pThis, pStreamEx, uUser, pvUser);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ rc = VERR_INVALID_FUNCTION;
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Marks a stream for re-init.
+ */
+static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller)
+{
+ LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pszCaller, pStreamEx->Core.Cfg.szName)); RT_NOREF(pszCaller);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ pStreamEx->cTriesReInit = 0;
+ pStreamEx->nsLastReInit = 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDeviceChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
+ LogRel(("Audio: The %s device for %s is changing.\n", PDMAudioDirGetName(enmDir), pThis->BackendCfg.szName));
+
+ /*
+ * Grab the list lock in shared mode and do the work.
+ */
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ if (pStreamEx->Core.Cfg.enmDir == enmDir)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
+ {
+ LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend state: %s...\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
+ pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser);
+ LogFlowFunc(("New stream backend state: %s\n",
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
+ }
+ else
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyPreparingDeviceSwitch}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+
+ /*
+ * Backend stream to validated DrvAudio stream:
+ */
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+ LogFlowFunc(("pStreamEx=%p '%s'\n", pStreamEx, pStreamEx->Core.Cfg.szName));
+
+ /*
+ * Grab the lock and do switch the state (only needed for output streams for now).
+ */
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ if (pStreamEx->cbPreBufThreshold > 0)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ switch (enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
+ break;
+ case DRVAUDIOPLAYSTATE_PLAY:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF;
+ break;
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ break;
+ /* no default */
+ case DRVAUDIOPLAYSTATE_END:
+ case DRVAUDIOPLAYSTATE_INVALID:
+ break;
+ }
+ LogFunc(("%s -> %s\n", drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ }
+ else
+ LogFunc(("No pre-buffering configured.\n"));
+ }
+ else
+ LogFunc(("input stream, nothing to do.\n"));
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyDeviceChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+
+ /*
+ * Backend stream to validated DrvAudio stream:
+ */
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ /*
+ * Grab the lock and do the requested work.
+ */
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
+
+ if (fReInit)
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+ else
+ {
+ /*
+ * Adjust the stream state now that the device has (perhaps finally) been switched.
+ *
+ * For enabled output streams, we must update the play state. We could try commit
+ * pre-buffered data here, but it's really not worth the hazzle and risk (don't
+ * know which thread we're on, do we now).
+ */
+ AssertStmt(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT),
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT);
+
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ LogFunc(("%s: %s -> %s\n", pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(enmPlayState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ RT_NOREF(enmPlayState);
+ }
+
+ /* Disable and then fully resync. */
+ /** @todo This doesn't work quite reliably if we're in draining mode
+ * (PENDING_DISABLE, so the backend needs to take care of that prior to calling
+ * us. Sigh. The idea was to avoid extra state mess in the backend... */
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "device changed");
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+}
+
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+/**
+ * @callback_method_impl{FNTMTIMERDRV, Re-enumerate backend devices.}
+ *
+ * Used to do/trigger re-enumeration of backend devices with a delay after we
+ * got notification as there can be further notifications following shortly
+ * after the first one. Also good to get it of random COM/whatever threads.
+ */
+static DECLCALLBACK(void) drvAudioEnumerateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ RT_NOREF(hTimer, pvUser);
+
+ /* Try push the work over to the thread-pool if we've got one. */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if (pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ int rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioDevicesEnumerateInternal,
+ 3, pThis, true /*fLog*/, (PPDMAUDIOHOSTENUM)NULL /*pDevEnum*/);
+ LogFunc(("RTReqPoolCallEx: %Rrc\n", rc));
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (RT_SUCCESS(rc))
+ return;
+ }
+ else
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ LogFunc(("Calling drvAudioDevicesEnumerateInternal...\n"));
+ drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDevicesChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->BackendCfg.szName));
+
+#ifdef RT_OS_DARWIN /** @todo Remove legacy behaviour: */
+ /* Mark all host streams to re-initialize. */
+ int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc2);
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+#endif
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /*
+ * Re-enumerate all host devices with a tiny delay to avoid re-doing this
+ * when a bunch of changes happens at once (they typically do on windows).
+ * We'll keep postponing it till it quiesces for a fraction of a second.
+ */
+ int rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hEnumTimer, RT_MS_1SEC / 3);
+ AssertRC(rc);
+#endif
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &pThis->IHostAudioPort);
+
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ /** @todo locking? */
+ if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ {
+ /*
+ * Just destroy the host stream on the backend side.
+ * The rest will either be destructed by the device emulation or
+ * in drvAudioDestruct().
+ */
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Detach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Detach flags.
+ */
+static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ RT_NOREF(fFlags);
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ AssertLogRelRCReturnVoid(rc);
+
+ LogFunc(("%s (detached %p, hReqPool=%p)\n", pThis->BackendCfg.szName, pThis->pHostDrvAudio, pThis->hReqPool));
+
+ /*
+ * Must first destroy the thread pool first so we are certain no threads
+ * are still using the instance being detached. Release lock while doing
+ * this as the thread functions may need to take it to complete.
+ */
+ if (pThis->pHostDrvAudio && pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ RTREQPOOL hReqPool = pThis->hReqPool;
+ pThis->hReqPool = NIL_RTREQPOOL;
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+
+ RTReqPoolRelease(hReqPool);
+
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ }
+
+ /*
+ * Now we can safely set pHostDrvAudio to NULL.
+ */
+ pThis->pHostDrvAudio = NULL;
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+}
+
+
+/**
+ * Initializes the host backend and queries its initial configuration.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance to be called.
+ */
+static int drvAudioHostInit(PDRVAUDIO pThis)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Check the function pointers, make sure the ones we define as
+ * mandatory are present.
+ */
+ PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturn(pIHostDrvAudio, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnSetDevice, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnDoOnWorkerThread, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamInitAsync, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamEnable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamDisable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamPause, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamResume, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamDrain, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetState, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
+
+ /*
+ * Get the backend configuration.
+ *
+ * Note! Limit the number of streams to max 128 in each direction to
+ * prevent wasting resources.
+ * Note! Take care not to wipe the DriverName config value on failure.
+ */
+ PDMAUDIOBACKENDCFG BackendCfg;
+ RT_ZERO(BackendCfg);
+ int rc = pIHostDrvAudio->pfnGetConfig(pIHostDrvAudio, &BackendCfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (LogIsEnabled() && strcmp(BackendCfg.szName, pThis->BackendCfg.szName) != 0)
+ LogFunc(("BackendCfg.szName: '%s' -> '%s'\n", pThis->BackendCfg.szName, BackendCfg.szName));
+ pThis->BackendCfg = BackendCfg;
+ pThis->In.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsIn, 128);
+ pThis->Out.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsOut, 128);
+
+ LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+ }
+ else
+ {
+ LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
+ pThis->BackendCfg.szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+ if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
+ AssertRC(rc2);
+ /* Ignore rc2. */
+#endif
+
+ /*
+ * Create a thread pool if stream creation can be asynchronous.
+ *
+ * The pool employs no pushback as the caller is typically EMT and
+ * shouldn't be delayed.
+ *
+ * The number of threads limits and the device implementations use
+ * of pfnStreamDestroy limits the number of streams pending async
+ * init. We use RTReqCancel in drvAudioStreamDestroy to allow us
+ * to release extra reference held by the pfnStreamInitAsync call
+ * if successful. Cancellation will only be possible if the call
+ * hasn't been picked up by a worker thread yet, so the max number
+ * of threads in the pool defines how many destroyed streams that
+ * can be lingering. (We must keep this under control, otherwise
+ * an evil guest could just rapidly trigger stream creation and
+ * destruction to consume host heap and hog CPU resources for
+ * configuring audio backends.)
+ */
+ if ( pThis->hReqPool == NIL_RTREQPOOL
+ && ( pIHostDrvAudio->pfnStreamInitAsync
+ || pIHostDrvAudio->pfnDoOnWorkerThread
+ || (pThis->BackendCfg.fFlags & (PDMAUDIOBACKEND_F_ASYNC_HINT | PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY)) ))
+ {
+ char szName[16];
+ RTStrPrintf(szName, sizeof(szName), "Aud%uWr", pThis->pDrvIns->iInstance);
+ RTREQPOOL hReqPool = NIL_RTREQPOOL;
+ rc = RTReqPoolCreate(3 /*cMaxThreads*/, RT_MS_30SEC /*cMsMinIdle*/, UINT32_MAX /*cThreadsPushBackThreshold*/,
+ 1 /*cMsMaxPushBack*/, szName, &hReqPool);
+ LogFlowFunc(("Creating thread pool '%s': %Rrc, hReqPool=%p\n", szName, rc, hReqPool));
+ AssertRCReturn(rc, rc);
+
+ rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_THREAD_FLAGS, RTTHREADFLAGS_COM_MTA);
+ AssertRCReturnStmt(rc, RTReqPoolRelease(hReqPool), rc);
+
+ rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_MIN_THREADS, 1);
+ AssertRC(rc); /* harmless */
+
+ pThis->hReqPool = hReqPool;
+ }
+ else
+ LogFlowFunc(("No thread pool.\n"));
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does the actual backend driver attaching and queries the backend's interface.
+ *
+ * This is a worker for both drvAudioAttach and drvAudioConstruct.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pThis Pointer to driver instance.
+ * @param fFlags Attach flags; see PDMDrvHlpAttach().
+ */
+static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags)
+{
+ Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
+
+ /*
+ * Attach driver below and query its connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
+ if (pThis->pHostDrvAudio)
+ {
+ /*
+ * If everything went well, initialize the lower driver.
+ */
+ rc = drvAudioHostInit(pThis);
+ if (RT_FAILURE(rc))
+ pThis->pHostDrvAudio = NULL;
+ }
+ else
+ {
+ LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->BackendCfg.szName));
+ rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
+ }
+ }
+ /*
+ * If the host driver below us failed to construct for some beningn reason,
+ * we'll report it as a runtime error and replace it with the Null driver.
+ *
+ * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase
+ * will remain NULL in this case.
+ */
+ else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED
+ || rc == VERR_MODULE_NOT_FOUND
+ || rc == VERR_SYMBOL_NOT_FOUND
+ || rc == VERR_FILE_NOT_FOUND
+ || rc == VERR_PATH_NOT_FOUND)
+ {
+ /* Complain: */
+ LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n",
+ pThis->BackendCfg.szName, rc));
+ PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"),
+ pThis->BackendCfg.szName);
+
+ /* Replace with null audio: */
+ pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull;
+ RTStrCopy(pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "NULL");
+ rc = drvAudioHostInit(pThis);
+ AssertRC(rc);
+ }
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->BackendCfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * Attach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Attach flags.
+ */
+static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ LogFunc(("%s\n", pThis->BackendCfg.szName));
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ return rc;
+}
+
+
+/**
+ * Handles state changes for all audio streams.
+ *
+ * @param pDrvIns Pointer to driver instance.
+ * @param enmCmd Stream command to set for all streams.
+ */
+static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
+
+ int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc2);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
+}
+
+
+/**
+ * Destructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ /*
+ * We must start by setting pHostDrvAudio to NULL here as the anything below
+ * us has already been destroyed at this point.
+ */
+ if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ pThis->pHostDrvAudio = NULL;
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ }
+ else
+ {
+ Assert(pThis->pHostDrvAudio == NULL);
+ pThis->pHostDrvAudio = NULL;
+ }
+
+ /*
+ * Make sure the thread pool is out of the picture before we terminate all the streams.
+ */
+ if (pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ uint32_t cRefs = RTReqPoolRelease(pThis->hReqPool);
+ Assert(cRefs == 0); RT_NOREF(cRefs);
+ pThis->hReqPool = NIL_RTREQPOOL;
+ }
+
+ /*
+ * Destroy all streams.
+ */
+ if (RTCritSectRwIsInitialized(&pThis->CritSectGlobals))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
+ RTListForEachSafe(&pThis->LstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
+ {
+ int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pStreamEx->ListEntry);
+ drvAudioStreamFree(pStreamEx);
+ }
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ RTCritSectRwDelete(&pThis->CritSectGlobals);
+ }
+
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThis->LstStreams));
+
+ if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
+ RTCritSectRwDelete(&pThis->CritSectHotPlug);
+
+ PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, "");
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Constructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
+
+ /*
+ * Basic instance init.
+ */
+ RTListInit(&pThis->LstStreams);
+ pThis->hReqPool = NIL_RTREQPOOL;
+
+ /*
+ * Read configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
+ "DriverName|"
+ "InputEnabled|"
+ "OutputEnabled|"
+ "DebugEnabled|"
+ "DebugPathOut|"
+ /* Deprecated: */
+ "PCMSampleBitIn|"
+ "PCMSampleBitOut|"
+ "PCMSampleHzIn|"
+ "PCMSampleHzOut|"
+ "PCMSampleSignedIn|"
+ "PCMSampleSignedOut|"
+ "PCMSampleSwapEndianIn|"
+ "PCMSampleSwapEndianOut|"
+ "PCMSampleChannelsIn|"
+ "PCMSampleChannelsOut|"
+ "PeriodSizeMsIn|"
+ "PeriodSizeMsOut|"
+ "BufferSizeMsIn|"
+ "BufferSizeMsOut|"
+ "PreBufferSizeMsIn|"
+ "PreBufferSizeMsOut",
+ "In|Out");
+
+ int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DriverName", pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "Untitled");
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Neither input nor output by default for security reasons. */
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Debug stuff (same for both directions). */
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->CfgIn.Dbg.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DebugPathOut", pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut), "");
+ AssertLogRelRCReturn(rc, rc);
+ if (pThis->CfgIn.Dbg.szPathOut[0] == '\0')
+ {
+ rc = RTPathTemp(pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
+ pThis->CfgIn.Dbg.szPathOut[0] = '\0';
+ pThis->CfgIn.Dbg.fEnabled = false;
+ }
+ }
+ if (pThis->CfgIn.Dbg.fEnabled)
+ LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n",
+ pThis->BackendCfg.szName, pThis->CfgIn.Dbg.szPathOut));
+
+ /* Copy debug setup to the output direction. */
+ pThis->CfgOut.Dbg = pThis->CfgIn.Dbg;
+
+ LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->BackendCfg.szName));
+ /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
+ LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
+ pThis->BackendCfg.szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
+
+ /*
+ * Per direction configuration. A bit complicated as
+ * these wasn't originally in sub-nodes.
+ */
+ for (unsigned iDir = 0; iDir < 2; iDir++)
+ {
+ char szNm[48];
+ PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->CfgIn : &pThis->CfgOut;
+ const char *pszDir = iDir == 0 ? "In" : "Out";
+
+#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
+ do { \
+ rc = RT_CONCAT(pHlp->pfnCFGMQueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
+ { \
+ rc = RT_CONCAT(pHlp->pfnCFGMQueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
+ { \
+ *(a_pValue) = a_uDefault; \
+ rc = VINF_SUCCESS; \
+ } \
+ else \
+ LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
+ } \
+ AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
+ N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
+ if (!(a_ExprValid)) \
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
+ N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
+ } while (0)
+
+ PCFGMNODE const pDirNode = pHlp->pfnCFGMGetChild(pCfg, pszDir);
+ rc = pHlp->pfnCFGMValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
+ "PCMSampleBit|"
+ "PCMSampleHz|"
+ "PCMSampleSigned|"
+ "PCMSampleSwapEndian|"
+ "PCMSampleChannels|"
+ "PeriodSizeMs|"
+ "BufferSizeMs|"
+ "PreBufferSizeMs",
+ "", pDrvIns->pReg->szName, pDrvIns->iInstance);
+ AssertRCReturn(rc, rc);
+
+ uint8_t cSampleBits = 0;
+ QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
+ cSampleBits == 0
+ || cSampleBits == 8
+ || cSampleBits == 16
+ || cSampleBits == 32
+ || cSampleBits == 64,
+ "Must be either 0, 8, 16, 32 or 64");
+ if (cSampleBits)
+ PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
+
+ uint8_t cChannels;
+ QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
+ if (cChannels)
+ PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
+
+ QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
+ pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
+ "In the range 6000 thru 768000, or 0");
+
+ QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
+ pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
+ "Must be either 0, 1, or 255");
+
+ QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
+ pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
+ "Must be either 0, 1, or 255");
+
+ QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
+ pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
+
+ QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
+ pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
+
+ QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
+ pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
+ "Max 1000, or 0xffffffff");
+#undef QUERY_VAL_RET
+ }
+
+ /*
+ * Init the rest of the driver instance data.
+ */
+ rc = RTCritSectRwInit(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+ rc = RTCritSectRwInit(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+#ifdef VBOX_STRICT
+ /* Define locking order: */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+#endif
+
+ pThis->pDrvIns = pDrvIns;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
+ /* IAudioConnector. */
+ pThis->IAudioConnector.pfnEnable = drvAudioEnable;
+ pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
+ pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
+ pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
+ pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint;
+ pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
+ pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
+ pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit;
+ pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
+ pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
+ pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
+ pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
+ pThis->IAudioConnector.pfnStreamGetState = drvAudioStreamGetState;
+ pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
+ pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
+ pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
+ pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
+ /* IHostAudioPort */
+ pThis->IHostAudioPort.pfnDoOnWorkerThread = drvAudioHostPort_DoOnWorkerThread;
+ pThis->IHostAudioPort.pfnNotifyDeviceChanged = drvAudioHostPort_NotifyDeviceChanged;
+ pThis->IHostAudioPort.pfnStreamNotifyPreparingDeviceSwitch = drvAudioHostPort_StreamNotifyPreparingDeviceSwitch;
+ pThis->IHostAudioPort.pfnStreamNotifyDeviceChanged = drvAudioHostPort_StreamNotifyDeviceChanged;
+ pThis->IHostAudioPort.pfnNotifyDevicesChanged = drvAudioHostPort_NotifyDevicesChanged;
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /*
+ * Create a timer to trigger delayed device enumeration on device changes.
+ */
+ RTStrPrintf(pThis->szEnumTimerName, sizeof(pThis->szEnumTimerName), "AudioEnum-%u", pDrvIns->iInstance);
+ rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvAudioEnumerateTimer, NULL /*pvUser*/,
+ 0 /*fFlags*/, pThis->szEnumTimerName, &pThis->hEnumTimer);
+ AssertRCReturn(rc, rc);
+#endif
+
+ /*
+ * Attach the host driver, if present.
+ */
+ rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+
+ /*
+ * Statistics (afte driver attach for name).
+ */
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->BackendCfg.fFlags, STAMTYPE_U32, "BackendFlags", STAMUNIT_COUNT, pThis->BackendCfg.szName); /* Mainly for the name. */
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->cStreams, STAMTYPE_U32, "Streams", STAMUNIT_COUNT, "Current streams count.");
+ PDMDrvHlpSTAMRegCounter(pDrvIns, &pThis->StatTotalStreamsCreated, "TotalStreamsCreated", "Number of stream ever created.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.fEnabled, STAMTYPE_BOOL, "InputEnabled", STAMUNIT_NONE, "Whether input is enabled or not.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.cStreamsFree, STAMTYPE_U32, "InputStreamFree", STAMUNIT_COUNT, "Number of free input stream slots");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.fEnabled, STAMTYPE_BOOL, "OutputEnabled", STAMUNIT_NONE, "Whether output is enabled or not.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.cStreamsFree, STAMTYPE_U32, "OutputStreamFree", STAMUNIT_COUNT, "Number of free output stream slots");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Audio driver registration record.
+ */
+const PDMDRVREG g_DrvAUDIO =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AUDIO",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio connector driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ UINT32_MAX,
+ /* cbInstance */
+ sizeof(DRVAUDIO),
+ /* pfnConstruct */
+ drvAudioConstruct,
+ /* pfnDestruct */
+ drvAudioDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ drvAudioSuspend,
+ /* pfnResume */
+ drvAudioResume,
+ /* pfnAttach */
+ drvAudioAttach,
+ /* pfnDetach */
+ drvAudioDetach,
+ /* pfnPowerOff */
+ drvAudioPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp b/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp
new file mode 100644
index 00000000..a7154177
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp
@@ -0,0 +1,1611 @@
+/* $Id: DrvHostAudioAlsa.cpp $ */
+/** @file
+ * Host audio driver - Advanced Linux Sound Architecture (ALSA).
+ */
+
+/*
+ * Copyright (C) 2006-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
+ * --------------------------------------------------------------------
+ *
+ * This code is based on: alsaaudio.c
+ *
+ * QEMU ALSA audio driver
+ *
+ * Copyright (c) 2005 Vassili Karpov (malc)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include "DrvHostAudioAlsaStubsMangling.h"
+#include <alsa/asoundlib.h>
+#include <alsa/control.h> /* For device enumeration. */
+#include <alsa/version.h>
+#include "DrvHostAudioAlsaStubs.h"
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of tries to recover a broken pipe. */
+#define ALSA_RECOVERY_TRIES_MAX 5
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+/**
+ * ALSA host audio specific stream data.
+ */
+typedef struct DRVHSTAUDALSASTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+
+ /** Handle to the ALSA PCM stream. */
+ snd_pcm_t *hPCM;
+ /** Internal stream offset (for debugging). */
+ uint64_t offInternal;
+
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+} DRVHSTAUDALSASTREAM;
+/** Pointer to the ALSA host audio specific stream data. */
+typedef DRVHSTAUDALSASTREAM *PDRVHSTAUDALSASTREAM;
+
+
+/**
+ * Host Alsa audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHSTAUDALSA
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Error count for not flooding the release log.
+ * UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+
+ /** Critical section protecting the default device strings. */
+ RTCRITSECT CritSect;
+ /** Default input device name. */
+ char szInputDev[256];
+ /** Default output device name. */
+ char szOutputDev[256];
+ /** Upwards notification interface. */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort;
+} DRVHSTAUDALSA;
+/** Pointer to the instance data of an ALSA host audio driver. */
+typedef DRVHSTAUDALSA *PDRVHSTAUDALSA;
+
+
+
+/**
+ * Closes an ALSA stream
+ *
+ * @returns VBox status code.
+ * @param phPCM Pointer to the ALSA stream handle to close. Will be set to
+ * NULL.
+ */
+static int drvHstAudAlsaStreamClose(snd_pcm_t **phPCM)
+{
+ if (!phPCM || !*phPCM)
+ return VINF_SUCCESS;
+
+ LogRelFlowFuncEnter();
+
+ int rc;
+ int rc2 = snd_pcm_close(*phPCM);
+ if (rc2 == 0)
+ {
+ *phPCM = NULL;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrConvertFromErrno(-rc2);
+ LogRel(("ALSA: Closing PCM descriptor failed: %s (%d, %Rrc)\n", snd_strerror(rc2), rc2, rc));
+ }
+
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+#ifdef DEBUG
+static void drvHstAudAlsaDbgErrorHandler(const char *file, int line, const char *function,
+ int err, const char *fmt, ...)
+{
+ /** @todo Implement me! */
+ RT_NOREF(file, line, function, err, fmt);
+}
+#endif
+
+
+/**
+ * Tries to recover an ALSA stream.
+ *
+ * @returns VBox status code.
+ * @param hPCM ALSA stream handle.
+ */
+static int drvHstAudAlsaStreamRecover(snd_pcm_t *hPCM)
+{
+ AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
+
+ int rc = snd_pcm_prepare(hPCM);
+ if (rc >= 0)
+ {
+ LogFlowFunc(("Successfully recovered %p.\n", hPCM));
+ return VINF_SUCCESS;
+ }
+ LogFunc(("Failed to recover stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
+ return RTErrConvertFromErrno(-rc);
+}
+
+
+/**
+ * Resumes an ALSA stream.
+ *
+ * Used by drvHstAudAlsaHA_StreamPlay() and drvHstAudAlsaHA_StreamCapture().
+ *
+ * @returns VBox status code.
+ * @param hPCM ALSA stream to resume.
+ */
+static int drvHstAudAlsaStreamResume(snd_pcm_t *hPCM)
+{
+ AssertPtrReturn(hPCM, VERR_INVALID_POINTER);
+
+ int rc = snd_pcm_resume(hPCM);
+ if (rc >= 0)
+ {
+ LogFlowFunc(("Successfuly resumed %p.\n", hPCM));
+ return VINF_SUCCESS;
+ }
+ LogFunc(("Failed to resume stream %p: %s (%d)\n", hPCM, snd_strerror(rc), rc));
+ return RTErrConvertFromErrno(-rc);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA");
+ pBackendCfg->cbStream = sizeof(DRVHSTAUDALSASTREAM);
+ pBackendCfg->fFlags = 0;
+ /* ALSA allows exactly one input and one output used at a time for the selected device(s). */
+ pBackendCfg->cMaxStreamsIn = 1;
+ pBackendCfg->cMaxStreamsOut = 1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ RT_NOREF(pInterface);
+ PDMAudioHostEnumInit(pDeviceEnum);
+
+ char **papszHints = NULL;
+ int rc = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&papszHints);
+ if (rc == 0)
+ {
+ rc = VINF_SUCCESS;
+ for (size_t iHint = 0; papszHints[iHint] != NULL && RT_SUCCESS(rc); iHint++)
+ {
+ /*
+ * Retrieve the available info:
+ */
+ const char * const pszHint = papszHints[iHint];
+ char * const pszDev = snd_device_name_get_hint(pszHint, "NAME");
+ char * const pszInOutId = snd_device_name_get_hint(pszHint, "IOID");
+ char * const pszDesc = snd_device_name_get_hint(pszHint, "DESC");
+
+ if (pszDev && RTStrICmpAscii(pszDev, "null") != 0)
+ {
+ /* Detect and log presence of pulse audio plugin. */
+ if (RTStrIStr("pulse", pszDev) != NULL)
+ LogRel(("ALSA: The ALSAAudio plugin for pulse audio is being used (%s).\n", pszDev));
+
+ /*
+ * Add an entry to the enumeration result.
+ * We engage in some trickery here to deal with device names that
+ * are more than 63 characters long.
+ */
+ size_t const cbId = pszDev ? strlen(pszDev) + 1 : 1;
+ size_t const cbName = pszDesc ? strlen(pszDesc) + 2 + 1 : cbId;
+ PPDMAUDIOHOSTDEV pDev = PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId);
+ if (pDev)
+ {
+ RTStrCopy(pDev->pszId, cbId, pszDev);
+ if (pDev->pszId)
+ {
+ pDev->fFlags = PDMAUDIOHOSTDEV_F_NONE;
+ pDev->enmType = PDMAUDIODEVICETYPE_UNKNOWN;
+
+ if (pszInOutId == NULL)
+ {
+ pDev->enmUsage = PDMAUDIODIR_DUPLEX;
+ pDev->cMaxInputChannels = 2;
+ pDev->cMaxOutputChannels = 2;
+ }
+ else if (RTStrICmpAscii(pszInOutId, "Input") == 0)
+ {
+ pDev->enmUsage = PDMAUDIODIR_IN;
+ pDev->cMaxInputChannels = 2;
+ pDev->cMaxOutputChannels = 0;
+ }
+ else
+ {
+ AssertMsg(RTStrICmpAscii(pszInOutId, "Output") == 0, ("%s (%s)\n", pszInOutId, pszHint));
+ pDev->enmUsage = PDMAUDIODIR_OUT;
+ pDev->cMaxInputChannels = 0;
+ pDev->cMaxOutputChannels = 2;
+ }
+
+ if (pszDesc && *pszDesc)
+ {
+ char *pszDesc2 = strchr(pszDesc, '\n');
+ if (!pszDesc2)
+ RTStrCopy(pDev->pszName, cbName, pszDesc);
+ else
+ {
+ *pszDesc2++ = '\0';
+ char *psz;
+ while ((psz = strchr(pszDesc2, '\n')) != NULL)
+ *psz = ' ';
+ RTStrPrintf(pDev->pszName, cbName, "%s (%s)", pszDesc2, pszDesc);
+ }
+ }
+ else
+ RTStrCopy(pDev->pszName, cbName, pszDev);
+
+ PDMAudioHostEnumAppend(pDeviceEnum, pDev);
+
+ LogRel2(("ALSA: Device #%u: '%s' enmDir=%s: %s\n", iHint, pszDev,
+ PDMAudioDirGetName(pDev->enmUsage), pszDesc));
+ }
+ else
+ {
+ PDMAudioHostDevFree(pDev);
+ rc = VERR_NO_STR_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /*
+ * Clean up.
+ */
+ if (pszInOutId)
+ free(pszInOutId);
+ if (pszDesc)
+ free(pszDesc);
+ if (pszDev)
+ free(pszDev);
+ }
+
+ snd_device_name_free_hint((void **)papszHints);
+
+ if (RT_FAILURE(rc))
+ {
+ PDMAudioHostEnumDelete(pDeviceEnum);
+ PDMAudioHostEnumInit(pDeviceEnum);
+ }
+ }
+ else
+ {
+ int rc2 = RTErrConvertFromErrno(-rc);
+ LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", rc2, rc));
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
+{
+ PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio);
+
+ /*
+ * Validate and normalize input.
+ */
+ AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
+ if (!pszId || !*pszId)
+ pszId = "default";
+ else
+ {
+ size_t cch = strlen(pszId);
+ AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME);
+ }
+ LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
+
+ /*
+ * Update input.
+ */
+ if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ if (strcmp(pThis->szInputDev, pszId) == 0)
+ RTCritSectLeave(&pThis->CritSect);
+ else
+ {
+ LogRel(("ALSA: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId));
+ RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId);
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
+ RTCritSectLeave(&pThis->CritSect);
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about input device change...\n"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+ }
+
+ /*
+ * Update output.
+ */
+ if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ if (strcmp(pThis->szOutputDev, pszId) == 0)
+ RTCritSectLeave(&pThis->CritSect);
+ else
+ {
+ LogRel(("ALSA: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId));
+ RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId);
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
+ RTCritSectLeave(&pThis->CritSect);
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about output device change...\n"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudAlsaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Converts internal audio PCM properties to an ALSA PCM format.
+ *
+ * @returns Converted ALSA PCM format.
+ * @param pProps Internal audio PCM configuration to convert.
+ */
+static snd_pcm_format_t alsaAudioPropsToALSA(PCPDMAUDIOPCMPROPS pProps)
+{
+ switch (PDMAudioPropsSampleSize(pProps))
+ {
+ case 1:
+ return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
+
+ case 2:
+ if (PDMAudioPropsIsLittleEndian(pProps))
+ return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE;
+ return pProps->fSigned ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_U16_BE;
+
+ case 4:
+ if (PDMAudioPropsIsLittleEndian(pProps))
+ return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE;
+ return pProps->fSigned ? SND_PCM_FORMAT_S32_BE : SND_PCM_FORMAT_U32_BE;
+
+ default:
+ AssertLogRelMsgFailed(("%RU8 bytes not supported\n", PDMAudioPropsSampleSize(pProps)));
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+
+/**
+ * Sets the software parameters of an ALSA stream.
+ *
+ * @returns 0 on success, negative errno on failure.
+ * @param hPCM ALSA stream to set software parameters for.
+ * @param pCfgReq Requested stream configuration (PDM).
+ * @param pCfgAcq The actual stream configuration (PDM). Updated as
+ * needed.
+ */
+static int alsaStreamSetSWParams(snd_pcm_t *hPCM, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN) /* For input streams there's nothing to do in here right now. */
+ return 0;
+
+ snd_pcm_sw_params_t *pSWParms = NULL;
+ snd_pcm_sw_params_alloca(&pSWParms);
+ AssertReturn(pSWParms, -ENOMEM);
+
+ int err = snd_pcm_sw_params_current(hPCM, pSWParms);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)), err);
+
+ /* Under normal circumstance, we don't need to set a playback threshold
+ because DrvAudio will do the pre-buffering and hand us everything in
+ one continuous chunk when we should start playing. But since it is
+ configurable, we'll set a reasonable minimum of two DMA periods or
+ max 50 milliseconds (the pAlsaCfgReq->threshold value).
+
+ Of course we also have to make sure the threshold is below the buffer
+ size, or ALSA will never start playing. */
+ unsigned long const cFramesMax = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 50);
+ unsigned long cFramesThreshold = RT_MIN(pCfgAcq->Backend.cFramesPeriod * 2, cFramesMax);
+ if (cFramesThreshold >= pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16)
+ cFramesThreshold = pCfgAcq->Backend.cFramesBufferSize - pCfgAcq->Backend.cFramesBufferSize / 16;
+
+ err = snd_pcm_sw_params_set_start_threshold(hPCM, pSWParms, cFramesThreshold);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set software threshold to %lu: %s\n", cFramesThreshold, snd_strerror(err)), err);
+
+ err = snd_pcm_sw_params_set_avail_min(hPCM, pSWParms, pCfgReq->Backend.cFramesPeriod);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set available minimum to %u: %s\n",
+ pCfgReq->Backend.cFramesPeriod, snd_strerror(err)), err);
+
+ /* Commit the software parameters: */
+ err = snd_pcm_sw_params(hPCM, pSWParms);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)), err);
+
+ /* Get the actual parameters: */
+ snd_pcm_uframes_t cFramesThresholdActual = cFramesThreshold;
+ err = snd_pcm_sw_params_get_start_threshold(pSWParms, &cFramesThresholdActual);
+ AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get start threshold: %s\n", snd_strerror(err)),
+ cFramesThresholdActual = cFramesThreshold);
+
+ LogRel2(("ALSA: SW params: %lu frames threshold, %u frames avail minimum\n",
+ cFramesThresholdActual, pCfgAcq->Backend.cFramesPeriod));
+ return 0;
+}
+
+
+/**
+ * Maps a PDM channel ID to an ASLA channel map position.
+ */
+static unsigned int drvHstAudAlsaPdmChToAlsa(PDMAUDIOCHANNELID enmId, uint8_t cChannels)
+{
+ switch (enmId)
+ {
+ case PDMAUDIOCHANNELID_UNKNOWN: return SND_CHMAP_UNKNOWN;
+ case PDMAUDIOCHANNELID_UNUSED_ZERO: return SND_CHMAP_NA;
+ case PDMAUDIOCHANNELID_UNUSED_SILENCE: return SND_CHMAP_NA;
+
+ case PDMAUDIOCHANNELID_FRONT_LEFT: return SND_CHMAP_FL;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT: return SND_CHMAP_FR;
+ case PDMAUDIOCHANNELID_FRONT_CENTER: return cChannels == 1 ? SND_CHMAP_MONO : SND_CHMAP_FC;
+ case PDMAUDIOCHANNELID_LFE: return SND_CHMAP_LFE;
+ case PDMAUDIOCHANNELID_REAR_LEFT: return SND_CHMAP_RL;
+ case PDMAUDIOCHANNELID_REAR_RIGHT: return SND_CHMAP_RR;
+ case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return SND_CHMAP_FLC;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return SND_CHMAP_FRC;
+ case PDMAUDIOCHANNELID_REAR_CENTER: return SND_CHMAP_RC;
+ case PDMAUDIOCHANNELID_SIDE_LEFT: return SND_CHMAP_SL;
+ case PDMAUDIOCHANNELID_SIDE_RIGHT: return SND_CHMAP_SR;
+ case PDMAUDIOCHANNELID_TOP_CENTER: return SND_CHMAP_TC;
+ case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return SND_CHMAP_TFL;
+ case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return SND_CHMAP_TFC;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return SND_CHMAP_TFR;
+ case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return SND_CHMAP_TRL;
+ case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return SND_CHMAP_TRC;
+ case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return SND_CHMAP_TRR;
+
+ case PDMAUDIOCHANNELID_INVALID:
+ case PDMAUDIOCHANNELID_END:
+ case PDMAUDIOCHANNELID_32BIT_HACK:
+ break;
+ }
+ AssertFailed();
+ return SND_CHMAP_NA;
+}
+
+
+/**
+ * Sets the hardware parameters of an ALSA stream.
+ *
+ * @returns 0 on success, negative errno on failure.
+ * @param hPCM ALSA stream to set software parameters for.
+ * @param enmAlsaFmt The ALSA format to use.
+ * @param pCfgReq Requested stream configuration (PDM).
+ * @param pCfgAcq The actual stream configuration (PDM). This is assumed
+ * to be a copy of pCfgReq on input, at least for
+ * properties handled here. On output some of the
+ * properties may be updated to match the actual stream
+ * configuration.
+ */
+static int alsaStreamSetHwParams(snd_pcm_t *hPCM, snd_pcm_format_t enmAlsaFmt,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ /*
+ * Get the current hardware parameters.
+ */
+ snd_pcm_hw_params_t *pHWParms = NULL;
+ snd_pcm_hw_params_alloca(&pHWParms);
+ AssertReturn(pHWParms, -ENOMEM);
+
+ int err = snd_pcm_hw_params_any(hPCM, pHWParms);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)), err);
+
+ /*
+ * Modify them according to pAlsaCfgReq.
+ * We update pAlsaCfgObt as we go for parameters set by "near" methods.
+ */
+ /* We'll use snd_pcm_writei/snd_pcm_readi: */
+ err = snd_pcm_hw_params_set_access(hPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set access type: %s\n", snd_strerror(err)), err);
+
+ /* Set the format and frequency. */
+ err = snd_pcm_hw_params_set_format(hPCM, pHWParms, enmAlsaFmt);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set audio format to %d: %s\n", enmAlsaFmt, snd_strerror(err)), err);
+
+ unsigned int uFreq = PDMAudioPropsHz(&pCfgReq->Props);
+ err = snd_pcm_hw_params_set_rate_near(hPCM, pHWParms, &uFreq, NULL /*dir*/);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set frequency to %uHz: %s\n",
+ PDMAudioPropsHz(&pCfgReq->Props), snd_strerror(err)), err);
+ pCfgAcq->Props.uHz = uFreq;
+
+ /* Channel count currently does not change with the mapping translations,
+ as ALSA can express both silent and unknown channel positions. */
+ union
+ {
+ snd_pcm_chmap_t Map;
+ unsigned int padding[1 + PDMAUDIO_MAX_CHANNELS];
+ } u;
+ uint8_t aidSrcChannels[PDMAUDIO_MAX_CHANNELS];
+ unsigned int *aidDstChannels = u.Map.pos;
+ unsigned int cChannels = u.Map.channels = PDMAudioPropsChannels(&pCfgReq->Props);
+ unsigned int iDst = 0;
+ for (unsigned int iSrc = 0; iSrc < cChannels; iSrc++)
+ {
+ uint8_t const idSrc = pCfgReq->Props.aidChannels[iSrc];
+ aidSrcChannels[iDst] = idSrc;
+ aidDstChannels[iDst] = drvHstAudAlsaPdmChToAlsa((PDMAUDIOCHANNELID)idSrc, cChannels);
+ iDst++;
+ }
+ u.Map.channels = cChannels = iDst;
+ for (; iDst < PDMAUDIO_MAX_CHANNELS; iDst++)
+ {
+ aidSrcChannels[iDst] = PDMAUDIOCHANNELID_INVALID;
+ aidDstChannels[iDst] = SND_CHMAP_NA;
+ }
+
+ err = snd_pcm_hw_params_set_channels_near(hPCM, pHWParms, &cChannels);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set number of channels to %d\n", PDMAudioPropsChannels(&pCfgReq->Props)),
+ err);
+ if (cChannels == PDMAudioPropsChannels(&pCfgReq->Props))
+ memcpy(pCfgAcq->Props.aidChannels, aidSrcChannels, sizeof(pCfgAcq->Props.aidChannels));
+ else
+ {
+ LogRel2(("ALSA: Requested %u channels, got %u\n", u.Map.channels, cChannels));
+ AssertLogRelMsgReturn(cChannels > 0 && cChannels <= PDMAUDIO_MAX_CHANNELS,
+ ("ALSA: Unsupported channel count: %u (requested %d)\n",
+ cChannels, PDMAudioPropsChannels(&pCfgReq->Props)), -ERANGE);
+ PDMAudioPropsSetChannels(&pCfgAcq->Props, (uint8_t)cChannels);
+ /** @todo Can we somehow guess channel IDs? snd_pcm_get_chmap? */
+ }
+
+ /* The period size (reportedly frame count per hw interrupt): */
+ int dir = 0;
+ snd_pcm_uframes_t minval = pCfgReq->Backend.cFramesPeriod;
+ err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not determine minimal period size: %s\n", snd_strerror(err)), err);
+
+ snd_pcm_uframes_t period_size_f = pCfgReq->Backend.cFramesPeriod;
+ if (period_size_f < minval)
+ period_size_f = minval;
+ err = snd_pcm_hw_params_set_period_size_near(hPCM, pHWParms, &period_size_f, 0);
+ LogRel2(("ALSA: Period size is: %lu frames (min %lu, requested %u)\n", period_size_f, minval, pCfgReq->Backend.cFramesPeriod));
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)), err);
+
+ /* The buffer size: */
+ minval = pCfgReq->Backend.cFramesBufferSize;
+ err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Could not retrieve minimal buffer size: %s\n", snd_strerror(err)), err);
+
+ snd_pcm_uframes_t buffer_size_f = pCfgReq->Backend.cFramesBufferSize;
+ if (buffer_size_f < minval)
+ buffer_size_f = minval;
+ err = snd_pcm_hw_params_set_buffer_size_near(hPCM, pHWParms, &buffer_size_f);
+ LogRel2(("ALSA: Buffer size is: %lu frames (min %lu, requested %u)\n", buffer_size_f, minval, pCfgReq->Backend.cFramesBufferSize));
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)), err);
+
+ /*
+ * Set the hardware parameters.
+ */
+ err = snd_pcm_hw_params(hPCM, pHWParms);
+ AssertLogRelMsgReturn(err >= 0, ("ALSA: Failed to apply audio parameters: %s\n", snd_strerror(err)), err);
+
+ /*
+ * Get relevant parameters and put them in the pAlsaCfgObt structure.
+ */
+ snd_pcm_uframes_t obt_buffer_size = buffer_size_f;
+ err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
+ AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get buffer size: %s\n", snd_strerror(err)), obt_buffer_size = buffer_size_f);
+ pCfgAcq->Backend.cFramesBufferSize = obt_buffer_size;
+
+ snd_pcm_uframes_t obt_period_size = period_size_f;
+ err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
+ AssertLogRelMsgStmt(err >= 0, ("ALSA: Failed to get period size: %s\n", snd_strerror(err)), obt_period_size = period_size_f);
+ pCfgAcq->Backend.cFramesPeriod = obt_period_size;
+
+ LogRel2(("ALSA: HW params: %u Hz, %u frames period, %u frames buffer, %u channel(s), enmAlsaFmt=%d\n",
+ PDMAudioPropsHz(&pCfgAcq->Props), pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize,
+ PDMAudioPropsChannels(&pCfgAcq->Props), enmAlsaFmt));
+
+#if 0 /* Disabled in the hope to resolve testboxes not being able to drain + crashing when closing the PCM streams. */
+ /*
+ * Channel config (not fatal).
+ */
+ if (PDMAudioPropsChannels(&pCfgAcq->Props) == PDMAudioPropsChannels(&pCfgReq->Props))
+ {
+ err = snd_pcm_set_chmap(hPCM, &u.Map);
+ if (err < 0)
+ {
+ if (err == -ENXIO)
+ LogRel2(("ALSA: Audio device does not support channel maps, skipping\n"));
+ else
+ LogRel2(("ALSA: snd_pcm_set_chmap failed: %s (%d)\n", snd_strerror(err), err));
+ }
+ }
+#endif
+
+ return 0;
+}
+
+
+/**
+ * Opens (creates) an ALSA stream.
+ *
+ * @returns VBox status code.
+ * @param pThis The alsa driver instance data.
+ * @param enmAlsaFmt The ALSA format to use.
+ * @param pCfgReq Requested configuration to create stream with (PDM).
+ * @param pCfgAcq The actual stream configuration (PDM). This is assumed
+ * to be a copy of pCfgReq on input, at least for
+ * properties handled here. On output some of the
+ * properties may be updated to match the actual stream
+ * configuration.
+ * @param phPCM Where to store the ALSA stream handle on success.
+ */
+static int alsaStreamOpen(PDRVHSTAUDALSA pThis, snd_pcm_format_t enmAlsaFmt, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAMCFG pCfgAcq, snd_pcm_t **phPCM)
+{
+ /*
+ * Open the stream.
+ */
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ const char * const pszType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output";
+ const char * const pszDev = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->szInputDev : pThis->szOutputDev;
+ snd_pcm_stream_t enmType = pCfgReq->enmDir == PDMAUDIODIR_IN ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK;
+
+ snd_pcm_t *hPCM = NULL;
+ LogRel(("ALSA: Using %s device \"%s\"\n", pszType, pszDev));
+ int err = snd_pcm_open(&hPCM, pszDev, enmType, SND_PCM_NONBLOCK);
+ if (err >= 0)
+ {
+ err = snd_pcm_nonblock(hPCM, 1);
+ if (err >= 0)
+ {
+ /*
+ * Configure hardware stream parameters.
+ */
+ err = alsaStreamSetHwParams(hPCM, enmAlsaFmt, pCfgReq, pCfgAcq);
+ if (err >= 0)
+ {
+ /*
+ * Prepare it.
+ */
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED;
+ err = snd_pcm_prepare(hPCM);
+ if (err >= 0)
+ {
+ /*
+ * Configure software stream parameters.
+ */
+ rc = alsaStreamSetSWParams(hPCM, pCfgReq, pCfgAcq);
+ if (RT_SUCCESS(rc))
+ {
+ *phPCM = hPCM;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ LogRel(("ALSA: snd_pcm_prepare failed: %s\n", snd_strerror(err)));
+ }
+ }
+ else
+ LogRel(("ALSA: Error setting non-blocking mode for %s stream: %s\n", pszType, snd_strerror(err)));
+ drvHstAudAlsaStreamClose(&hPCM);
+ }
+ else
+ LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, pszType, snd_strerror(err)));
+ *phPCM = NULL;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHSTAUDALSA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDALSA, IHostAudio);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgReq);
+
+ int rc;
+ snd_pcm_format_t const enmFmt = alsaAudioPropsToALSA(&pCfgReq->Props);
+ if (enmFmt != SND_PCM_FORMAT_UNKNOWN)
+ {
+ rc = alsaStreamOpen(pThis, enmFmt, pCfgReq, pCfgAcq, &pStreamALSA->hPCM);
+ if (RT_SUCCESS(rc))
+ {
+ /* We have no objections to the pre-buffering that DrvAudio applies,
+ only we need to adjust it relative to the actual buffer size. */
+ pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
+ * pCfgAcq->Backend.cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ PDMAudioStrmCfgCopy(&pStreamALSA->Cfg, pCfgAcq);
+ LogFlowFunc(("returns success - hPCM=%p\n", pStreamALSA->hPCM));
+ return rc;
+ }
+ }
+ else
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ LogFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ LogRelFlowFunc(("Stream '%s' state is '%s'\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
+
+ /** @todo r=bird: It's not like we can do much with a bad status... Check
+ * what the caller does... */
+ int rc = drvHstAudAlsaStreamClose(&pStreamALSA->hPCM);
+
+ LogRelFlowFunc(("returns %Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+
+ /*
+ * Prepare the stream.
+ */
+ int rc = snd_pcm_prepare(pStreamALSA->hPCM);
+ if (rc >= 0)
+ {
+ Assert(snd_pcm_state(pStreamALSA->hPCM) == SND_PCM_STATE_PREPARED);
+
+ /*
+ * Input streams should be started now, whereas output streams must
+ * pre-buffer sufficent data before starting.
+ */
+ if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ rc = snd_pcm_start(pStreamALSA->hPCM);
+ if (rc >= 0)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("ALSA: Error starting input stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
+ rc = RTErrConvertFromErrno(-rc);
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRel(("ALSA: Error preparing stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
+ rc = RTErrConvertFromErrno(-rc);
+ }
+ LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+
+ int rc = snd_pcm_drop(pStreamALSA->hPCM);
+ if (rc >= 0)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("ALSA: Error stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
+ rc = RTErrConvertFromErrno(-rc);
+ }
+ LogFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /* Same as disable. */
+ /** @todo r=bird: Try use pause and fallback on disable/enable if it isn't
+ * supported or doesn't work. */
+ return drvHstAudAlsaHA_StreamDisable(pInterface, pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /* Same as enable. */
+ return drvHstAudAlsaHA_StreamEnable(pInterface, pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+
+ snd_pcm_state_t const enmState = snd_pcm_state(pStreamALSA->hPCM);
+ LogRelFlowFunc(("Stream '%s' input state: %s (%d)\n", pStreamALSA->Cfg.szName, snd_pcm_state_name(enmState), enmState));
+
+ /* Only for output streams. */
+ AssertReturn(pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
+
+ int rc;
+ switch (enmState)
+ {
+ case SND_PCM_STATE_RUNNING:
+ case SND_PCM_STATE_PREPARED: /* not yet started */
+ {
+ /* Do not change to blocking here! */
+ rc = snd_pcm_drain(pStreamALSA->hPCM);
+ if (rc >= 0 || rc == -EAGAIN)
+ rc = VINF_SUCCESS;
+ else
+ {
+ snd_pcm_state_t const enmState2 = snd_pcm_state(pStreamALSA->hPCM);
+ if (rc == -EPIPE && enmState2 == enmState)
+ {
+ /* Not entirely sure, but possibly an underrun, so just disable the stream. */
+ LogRel2(("ALSA: snd_pcm_drain failed with -EPIPE, stopping stream (%s)\n", pStreamALSA->Cfg.szName));
+ rc = snd_pcm_drop(pStreamALSA->hPCM);
+ if (rc >= 0)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("ALSA: Error draining/stopping stream '%s': %s (%d)\n", pStreamALSA->Cfg.szName, snd_strerror(rc), rc));
+ rc = RTErrConvertFromErrno(-rc);
+ }
+ }
+ else
+ {
+ LogRel(("ALSA: Error draining output of '%s': %s (%d; %s -> %s)\n", pStreamALSA->Cfg.szName, snd_strerror(rc),
+ rc, snd_pcm_state_name(enmState), snd_pcm_state_name(enmState2)));
+ rc = RTErrConvertFromErrno(-rc);
+ }
+ }
+ break;
+ }
+
+ default:
+ rc = VINF_SUCCESS;
+ break;
+ }
+ LogRelFlowFunc(("returns %Rrc (state %s)\n", rc, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudAlsaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ snd_pcm_state_t enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
+ if (enmAlsaState == SND_PCM_STATE_DRAINING)
+ {
+ /* We're operating in non-blocking mode, so we must (at least for a demux
+ config) call snd_pcm_drain again to drive it forward. Otherwise we
+ might be stuck in the drain state forever. */
+ Log5Func(("Calling snd_pcm_drain again...\n"));
+ snd_pcm_drain(pStreamALSA->hPCM);
+ enmAlsaState = snd_pcm_state(pStreamALSA->hPCM);
+ }
+
+ if (enmAlsaState == SND_PCM_STATE_DRAINING)
+ enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
+ else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
+ enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+#endif
+
+ Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
+ pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
+ return enmStreamState;
+}
+
+
+/**
+ * Returns the available audio frames queued.
+ *
+ * @returns VBox status code.
+ * @param hPCM ALSA stream handle.
+ * @param pcFramesAvail Where to store the available frames.
+ */
+static int alsaStreamGetAvail(snd_pcm_t *hPCM, snd_pcm_sframes_t *pcFramesAvail)
+{
+ AssertPtr(hPCM);
+ AssertPtr(pcFramesAvail);
+
+ int rc;
+ snd_pcm_sframes_t cFramesAvail = snd_pcm_avail_update(hPCM);
+ if (cFramesAvail > 0)
+ {
+ LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
+ *pcFramesAvail = cFramesAvail;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * We can maybe recover from an EPIPE...
+ */
+ if (cFramesAvail == -EPIPE)
+ {
+ rc = drvHstAudAlsaStreamRecover(hPCM);
+ if (RT_SUCCESS(rc))
+ {
+ cFramesAvail = snd_pcm_avail_update(hPCM);
+ if (cFramesAvail >= 0)
+ {
+ LogFunc(("cFramesAvail=%ld\n", cFramesAvail));
+ *pcFramesAvail = cFramesAvail;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ *pcFramesAvail = 0;
+ return rc;
+ }
+ }
+
+ rc = RTErrConvertFromErrno(-(int)cFramesAvail);
+ LogFunc(("failed - cFramesAvail=%ld rc=%Rrc\n", cFramesAvail, rc));
+ *pcFramesAvail = 0;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ AssertPtrReturn(pStreamALSA, 0);
+
+ /*
+ * This is only relevant to output streams (input streams can't have
+ * any pending, unplayed data).
+ */
+ uint32_t cbPending = 0;
+ if (pStreamALSA->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ /*
+ * Getting the delay (in audio frames) reports the time it will take
+ * to hear a new sample after all queued samples have been played out.
+ *
+ * We use snd_pcm_avail_delay instead of snd_pcm_delay here as it will
+ * update the buffer positions, and we can use the extra value against
+ * the buffer size to double check since the delay value may include
+ * fixed built-in delays in the processing chain and hardware.
+ */
+ snd_pcm_sframes_t cFramesAvail = 0;
+ snd_pcm_sframes_t cFramesDelay = 0;
+ int rc = snd_pcm_avail_delay(pStreamALSA->hPCM, &cFramesAvail, &cFramesDelay);
+
+ /*
+ * We now also get the state as the pending value should be zero when
+ * we're not in a playing state.
+ */
+ snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
+ switch (enmState)
+ {
+ case SND_PCM_STATE_RUNNING:
+ case SND_PCM_STATE_DRAINING:
+ if (rc >= 0)
+ {
+ if ((uint32_t)cFramesAvail >= pStreamALSA->Cfg.Backend.cFramesBufferSize)
+ cbPending = 0;
+ else
+ cbPending = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesDelay);
+ }
+ break;
+
+ default:
+ break;
+ }
+ Log2Func(("returns %u (%#x) - cFramesBufferSize=%RU32 cFramesAvail=%ld cFramesDelay=%ld rc=%d; enmState=%s (%d) \n",
+ cbPending, cbPending, pStreamALSA->Cfg.Backend.cFramesBufferSize, cFramesAvail, cFramesDelay, rc,
+ snd_pcm_state_name(enmState), enmState));
+ }
+ return cbPending;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+
+ uint32_t cbAvail = 0;
+ snd_pcm_sframes_t cFramesAvail = 0;
+ int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
+ if (RT_SUCCESS(rc))
+ cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+ Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
+ snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ else
+ {
+ /* Fend off draining calls. */
+ *pcbWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Determine how much we can write (caller actually did this
+ * already, but we repeat it just to be sure or something).
+ */
+ snd_pcm_sframes_t cFramesAvail;
+ int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cFramesAvail);
+ if (cFramesAvail)
+ {
+ PCPDMAUDIOPCMPROPS pProps = &pStreamALSA->Cfg.Props;
+ uint32_t cbToWrite = PDMAudioPropsFramesToBytes(pProps, (uint32_t)cFramesAvail);
+ if (cbToWrite)
+ {
+ if (cbToWrite > cbBuf)
+ cbToWrite = cbBuf;
+
+ /*
+ * Try write the data.
+ */
+ uint32_t cFramesToWrite = PDMAudioPropsBytesToFrames(pProps, cbToWrite);
+ snd_pcm_sframes_t cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
+ if (cFramesWritten > 0)
+ {
+ Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
+ cbToWrite, cFramesWritten, cFramesAvail));
+ *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
+ pStreamALSA->offInternal += *pcbWritten;
+ return VINF_SUCCESS;
+ }
+ LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld]\n", cbToWrite, cFramesWritten, cFramesAvail));
+
+
+ /*
+ * There are a couple of error we can recover from, try to do so.
+ * Only don't try too many times.
+ */
+ for (unsigned iTry = 0;
+ (cFramesWritten == -EPIPE || cFramesWritten == -ESTRPIPE) && iTry < ALSA_RECOVERY_TRIES_MAX;
+ iTry++)
+ {
+ if (cFramesWritten == -EPIPE)
+ {
+ /* Underrun occurred. */
+ rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM);
+ if (RT_FAILURE(rc))
+ break;
+ LogFlowFunc(("Recovered from playback (iTry=%u)\n", iTry));
+ }
+ else
+ {
+ /* An suspended event occurred, needs resuming. */
+ rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("ALSA: Failed to resume output stream (iTry=%u, rc=%Rrc)\n", iTry, rc));
+ break;
+ }
+ LogFlowFunc(("Resumed suspended output stream (iTry=%u)\n", iTry));
+ }
+
+ cFramesWritten = snd_pcm_writei(pStreamALSA->hPCM, pvBuf, cFramesToWrite);
+ if (cFramesWritten > 0)
+ {
+ Log4Func(("snd_pcm_writei w/ cbToWrite=%u -> %ld (frames) [cFramesAvail=%ld]\n",
+ cbToWrite, cFramesWritten, cFramesAvail));
+ *pcbWritten = PDMAudioPropsFramesToBytes(pProps, cFramesWritten);
+ pStreamALSA->offInternal += *pcbWritten;
+ return VINF_SUCCESS;
+ }
+ LogFunc(("snd_pcm_writei w/ cbToWrite=%u -> %ld [cFramesAvail=%ld, iTry=%d]\n", cbToWrite, cFramesWritten, cFramesAvail, iTry));
+ }
+
+ /* Make sure we return with an error status. */
+ if (RT_SUCCESS_NP(rc))
+ {
+ if (cFramesWritten == 0)
+ rc = VERR_ACCESS_DENIED;
+ else
+ {
+ rc = RTErrConvertFromErrno(-(int)cFramesWritten);
+ LogFunc(("Failed to write %RU32 bytes: %ld (%Rrc)\n", cbToWrite, cFramesWritten, rc));
+ }
+ }
+ }
+ }
+ }
+ else
+ LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
+ *pcbWritten = 0;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudAlsaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+
+ uint32_t cbAvail = 0;
+ snd_pcm_sframes_t cFramesAvail = 0;
+ int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cFramesAvail);
+ if (RT_SUCCESS(rc))
+ cbAvail = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesAvail);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF_PV(pInterface);
+ PDRVHSTAUDALSASTREAM pStreamALSA = (PDRVHSTAUDALSASTREAM)pStream;
+ AssertPtrReturn(pStreamALSA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+ Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
+ snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
+
+ /*
+ * Figure out how much we can read without trouble (we're doing
+ * non-blocking reads, but whatever).
+ */
+ snd_pcm_sframes_t cAvail;
+ int rc = alsaStreamGetAvail(pStreamALSA->hPCM, &cAvail);
+ if (RT_SUCCESS(rc))
+ {
+ if (!cAvail) /* No data yet? */
+ {
+ snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->hPCM);
+ switch (enmState)
+ {
+ case SND_PCM_STATE_PREPARED:
+ /** @todo r=bird: explain the logic here... */
+ cAvail = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbBuf);
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ rc = drvHstAudAlsaStreamResume(pStreamALSA->hPCM);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("Resumed suspended input stream.\n"));
+ break;
+ }
+ LogFunc(("Failed resuming suspended input stream: %Rrc\n", rc));
+ return rc;
+
+ default:
+ LogFlow(("No frames available: state=%s (%d)\n", snd_pcm_state_name(enmState), enmState));
+ break;
+ }
+ if (!cAvail)
+ {
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ {
+ LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ size_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cAvail);
+ cbToRead = RT_MIN(cbToRead, cbBuf);
+ LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
+
+ /*
+ * Read loop.
+ */
+ uint32_t cbReadTotal = 0;
+ while (cbToRead > 0)
+ {
+ /*
+ * Do the reading.
+ */
+ snd_pcm_uframes_t const cFramesToRead = PDMAudioPropsBytesToFrames(&pStreamALSA->Cfg.Props, cbToRead);
+ AssertBreakStmt(cFramesToRead > 0, rc = VERR_NO_DATA);
+
+ snd_pcm_sframes_t cFramesRead = snd_pcm_readi(pStreamALSA->hPCM, pvBuf, cFramesToRead);
+ if (cFramesRead > 0)
+ {
+ /*
+ * We should not run into a full mixer buffer or we lose samples and
+ * run into an endless loop if ALSA keeps producing samples ("null"
+ * capture device for example).
+ */
+ uint32_t const cbRead = PDMAudioPropsFramesToBytes(&pStreamALSA->Cfg.Props, cFramesRead);
+ Assert(cbRead <= cbToRead);
+
+ cbToRead -= cbRead;
+ cbReadTotal += cbRead;
+ pvBuf = (uint8_t *)pvBuf + cbRead;
+ pStreamALSA->offInternal += cbRead;
+ }
+ else
+ {
+ /*
+ * Try recover from overrun and re-try.
+ * Other conditions/errors we cannot and will just quit the loop.
+ */
+ if (cFramesRead == -EPIPE)
+ {
+ rc = drvHstAudAlsaStreamRecover(pStreamALSA->hPCM);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("Successfully recovered from overrun\n"));
+ continue;
+ }
+ LogFunc(("Failed to recover from overrun: %Rrc\n", rc));
+ }
+ else if (cFramesRead == -EAGAIN)
+ LogFunc(("No input frames available (EAGAIN)\n"));
+ else if (cFramesRead == 0)
+ LogFunc(("No input frames available (0)\n"));
+ else
+ {
+ rc = RTErrConvertFromErrno(-(int)cFramesRead);
+ LogFunc(("Failed to read input frames: %s (%ld, %Rrc)\n", snd_strerror(cFramesRead), cFramesRead, rc));
+ }
+
+ /* If we've read anything, suppress the error. */
+ if (RT_FAILURE(rc) && cbReadTotal > 0)
+ {
+ LogFunc(("Suppressing %Rrc because %#x bytes has been read already\n", rc, cbReadTotal));
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc and %#x (%d) bytes (%u bytes left); state %s\n",
+ rc, cbReadTotal, cbReadTotal, cbToRead, snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM))));
+ *pcbRead = cbReadTotal;
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudAlsaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnDestruct,
+ * Destructs an ALSA host audio driver instance.}
+ */
+static DECLCALLBACK(void) drvHstAudAlsaDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
+ LogFlowFuncEnter();
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+ pThis->pIHostAudioPort = NULL;
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnConstruct,
+ * Construct an ALSA host audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHstAudAlsaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHSTAUDALSA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDALSA);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ LogRel(("Audio: Initializing ALSA driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudAlsaQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHstAudAlsaHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = drvHstAudAlsaHA_GetDevices;
+ pThis->IHostAudio.pfnSetDevice = drvHstAudAlsaHA_SetDevice;
+ pThis->IHostAudio.pfnGetStatus = drvHstAudAlsaHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHstAudAlsaHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHstAudAlsaHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHstAudAlsaHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHstAudAlsaHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHstAudAlsaHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHstAudAlsaHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHstAudAlsaHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetPending = drvHstAudAlsaHA_StreamGetPending;
+ pThis->IHostAudio.pfnStreamGetState = drvHstAudAlsaHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHstAudAlsaHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHstAudAlsaHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHstAudAlsaHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHstAudAlsaHA_StreamCapture;
+
+ /*
+ * Read configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "OutputDeviceID|InputDeviceID", "");
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), "default");
+ AssertRCReturn(rc, rc);
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), "default");
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Init the alsa library.
+ */
+ rc = audioLoadAlsaLib();
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("ALSA: Failed to load the ALSA shared library: %Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Query the notification interface from the driver/device above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+#ifdef DEBUG
+ /*
+ * Some debug stuff we don't use for anything at all.
+ */
+ snd_lib_error_set_handler(drvHstAudAlsaDbgErrorHandler);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * ALSA audio driver registration record.
+ */
+const PDMDRVREG g_DrvHostALSAAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "ALSAAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "ALSA host audio driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHSTAUDALSA),
+ /* pfnConstruct */
+ drvHstAudAlsaConstruct,
+ /* pfnDestruct */
+ drvHstAudAlsaDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp
new file mode 100644
index 00000000..847ed696
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.cpp
@@ -0,0 +1,364 @@
+/* $Id: DrvHostAudioAlsaStubs.cpp $ */
+/** @file
+ * Stubs for libasound.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <VBox/log.h>
+#include <iprt/once.h>
+
+#include <alsa/asoundlib.h>
+#include <errno.h>
+
+#include "DrvHostAudioAlsaStubs.h"
+
+#define VBOX_ALSA_LIB "libasound.so.2"
+
+#define PROXY_STUB(function, rettype, signature, shortsig) \
+ static rettype (*pfn_ ## function) signature; \
+ \
+ extern "C" rettype VBox_##function signature; \
+ rettype VBox_##function signature \
+ { \
+ return pfn_ ## function shortsig; \
+ }
+
+PROXY_STUB(snd_lib_error_set_handler, int, (snd_lib_error_handler_t handler),
+ (handler))
+PROXY_STUB(snd_strerror, const char *, (int errnum), (errnum))
+
+PROXY_STUB(snd_device_name_hint, int,
+ (int card, const char *iface, void ***hints),
+ (card, iface, hints))
+PROXY_STUB(snd_device_name_free_hint, int,
+ (void **hints),
+ (hints))
+PROXY_STUB(snd_device_name_get_hint, char *,
+ (const void *hint, const char *id),
+ (hint, id))
+
+static int fallback_snd_device_name_hint(int card, const char *iface, void ***hints)
+{
+ RT_NOREF(card, iface);
+ *hints = NULL;
+ return -ENOSYS;
+}
+
+static int fallback_snd_device_name_free_hint(void **hints)
+{
+ RT_NOREF(hints);
+ return 0;
+}
+
+static char *fallback_snd_device_name_get_hint(const void *hint, const char *id)
+{
+ RT_NOREF(hint, id);
+ return NULL;
+}
+
+/*
+ * PCM
+ */
+
+PROXY_STUB(snd_pcm_avail_update, snd_pcm_sframes_t, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_avail_delay, int,
+ (snd_pcm_t *pcm, snd_pcm_sframes_t *availp, snd_pcm_sframes_t *delayp),
+ (pcm, availp, delayp))
+PROXY_STUB(snd_pcm_close, int, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_delay, int, (snd_pcm_t *pcm, snd_pcm_sframes_t *delayp), (pcm, delayp))
+PROXY_STUB(snd_pcm_nonblock, int, (snd_pcm_t *pcm, int *onoff),
+ (pcm, onoff))
+PROXY_STUB(snd_pcm_drain, int, (snd_pcm_t *pcm),
+ (pcm))
+PROXY_STUB(snd_pcm_drop, int, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_open, int,
+ (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode),
+ (pcm, name, stream, mode))
+PROXY_STUB(snd_pcm_prepare, int, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_readi, snd_pcm_sframes_t,
+ (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size),
+ (pcm, buffer, size))
+PROXY_STUB(snd_pcm_resume, int, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_set_chmap, int, (snd_pcm_t *pcm, snd_pcm_chmap_t const *map), (pcm, map))
+PROXY_STUB(snd_pcm_state, snd_pcm_state_t, (snd_pcm_t *pcm), (pcm))
+PROXY_STUB(snd_pcm_state_name, const char *, (snd_pcm_state_t state), (state))
+PROXY_STUB(snd_pcm_writei, snd_pcm_sframes_t,
+ (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size),
+ (pcm, buffer, size))
+PROXY_STUB(snd_pcm_start, int, (snd_pcm_t *pcm), (pcm))
+
+static int fallback_snd_pcm_avail_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *availp, snd_pcm_sframes_t *delayp)
+{
+ *availp = pfn_snd_pcm_avail_update(pcm);
+ int ret = pfn_snd_pcm_delay(pcm, delayp);
+ if (ret >= 0 && *availp < 0)
+ ret = (int)*availp;
+ return ret;
+}
+
+static int fallback_snd_pcm_set_chmap(snd_pcm_t *pcm, snd_pcm_chmap_t const *map)
+{
+ RT_NOREF(pcm, map);
+ return 0;
+}
+
+/*
+ * HW
+ */
+
+PROXY_STUB(snd_pcm_hw_params, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params),
+ (pcm, params))
+PROXY_STUB(snd_pcm_hw_params_any, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params),
+ (pcm, params))
+PROXY_STUB(snd_pcm_hw_params_get_buffer_size, int,
+ (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val),
+ (params, val))
+PROXY_STUB(snd_pcm_hw_params_get_buffer_size_min, int,
+ (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val),
+ (params, val))
+PROXY_STUB(snd_pcm_hw_params_get_period_size, int,
+ (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir),
+ (params, frames, dir))
+PROXY_STUB(snd_pcm_hw_params_get_period_size_min, int,
+ (const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir),
+ (params, frames, dir))
+PROXY_STUB(snd_pcm_hw_params_set_rate_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir),
+ (pcm, params, val, dir))
+PROXY_STUB(snd_pcm_hw_params_set_access, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access),
+ (pcm, params, _access))
+PROXY_STUB(snd_pcm_hw_params_set_buffer_time_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir),
+ (pcm, params, val, dir))
+PROXY_STUB(snd_pcm_hw_params_set_buffer_size_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val),
+ (pcm, params, val))
+PROXY_STUB(snd_pcm_hw_params_set_channels_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val),
+ (pcm, params, val))
+PROXY_STUB(snd_pcm_hw_params_set_period_size_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir),
+ (pcm, params, val, dir))
+PROXY_STUB(snd_pcm_hw_params_set_period_time_near, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir),
+ (pcm, params, val, dir))
+PROXY_STUB(snd_pcm_hw_params_sizeof, size_t, (void), ())
+PROXY_STUB(snd_pcm_hw_params_set_format, int,
+ (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val),
+ (pcm, params, val))
+
+/*
+ * SW
+ */
+
+PROXY_STUB(snd_pcm_sw_params, int,
+ (snd_pcm_t *pcm, snd_pcm_sw_params_t *params),
+ (pcm, params))
+PROXY_STUB(snd_pcm_sw_params_current, int,
+ (snd_pcm_t *pcm, snd_pcm_sw_params_t *params),
+ (pcm, params))
+PROXY_STUB(snd_pcm_sw_params_get_start_threshold, int,
+ (const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val),
+ (params, val))
+PROXY_STUB(snd_pcm_sw_params_set_avail_min, int,
+ (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val),
+ (pcm, params, val))
+PROXY_STUB(snd_pcm_sw_params_set_start_threshold, int,
+ (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val),
+ (pcm, params, val))
+PROXY_STUB(snd_pcm_sw_params_sizeof, size_t, (void), ())
+
+/*
+ * Mixer
+ */
+
+PROXY_STUB(snd_mixer_selem_id_sizeof, size_t,
+ (void), ())
+PROXY_STUB(snd_mixer_open, int,
+ (snd_mixer_t **mixer, int mode),
+ (mixer, mode))
+PROXY_STUB(snd_mixer_attach, int,
+ (snd_mixer_t *mixer, const char *name),
+ (mixer, name))
+PROXY_STUB(snd_mixer_close, int,
+ (snd_mixer_t *mixer),
+ (mixer))
+PROXY_STUB(snd_mixer_selem_id_set_index, void,
+ (snd_mixer_selem_id_t *obj, unsigned int val),
+ (obj, val))
+PROXY_STUB(snd_mixer_selem_id_set_name, void,
+ (snd_mixer_selem_id_t *obj, const char *val),
+ (obj, val))
+PROXY_STUB(snd_mixer_selem_set_playback_volume, int,
+ (snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value),
+ (elem, channel, value))
+PROXY_STUB(snd_mixer_selem_get_playback_volume_range, int,
+ (snd_mixer_elem_t *elem, long *min, long *max),
+ (elem, min, max))
+PROXY_STUB(snd_mixer_selem_set_capture_volume, int,
+ (snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel, long value),
+ (elem, channel, value))
+PROXY_STUB(snd_mixer_selem_get_capture_volume_range, int,
+ (snd_mixer_elem_t *elem, long *min, long *max),
+ (elem, min, max))
+PROXY_STUB(snd_mixer_selem_register, int,
+ (snd_mixer_t *mixer, snd_mixer_selem_regopt *options, snd_mixer_class_t **classp),
+ (mixer, options, classp))
+PROXY_STUB(snd_mixer_load, int,
+ (snd_mixer_t *mixer),
+ (mixer))
+PROXY_STUB(snd_mixer_find_selem, snd_mixer_elem_t *,
+ (snd_mixer_t *mixer, const snd_mixer_selem_id_t *id),
+ (mixer, id))
+
+typedef struct
+{
+ const char *name;
+ void (**pfn)(void);
+ void (*pfnFallback)(void);
+} SHARED_FUNC;
+
+#define ELEMENT(function) { #function , (void (**)(void)) & pfn_ ## function, NULL }
+#define ELEMENT_FALLBACK(function) { #function , (void (**)(void)) & pfn_ ## function, (void (*)(void))fallback_ ## function }
+static SHARED_FUNC SharedFuncs[] =
+{
+ ELEMENT(snd_lib_error_set_handler),
+ ELEMENT(snd_strerror),
+
+ ELEMENT_FALLBACK(snd_device_name_hint),
+ ELEMENT_FALLBACK(snd_device_name_get_hint),
+ ELEMENT_FALLBACK(snd_device_name_free_hint),
+
+ ELEMENT(snd_pcm_avail_update),
+ ELEMENT_FALLBACK(snd_pcm_avail_delay),
+ ELEMENT(snd_pcm_close),
+ ELEMENT(snd_pcm_delay),
+ ELEMENT(snd_pcm_drain),
+ ELEMENT(snd_pcm_drop),
+ ELEMENT(snd_pcm_nonblock),
+ ELEMENT(snd_pcm_open),
+ ELEMENT(snd_pcm_prepare),
+ ELEMENT(snd_pcm_resume),
+ ELEMENT_FALLBACK(snd_pcm_set_chmap),
+ ELEMENT(snd_pcm_state),
+ ELEMENT(snd_pcm_state_name),
+
+ ELEMENT(snd_pcm_readi),
+ ELEMENT(snd_pcm_start),
+ ELEMENT(snd_pcm_writei),
+
+ ELEMENT(snd_pcm_hw_params),
+ ELEMENT(snd_pcm_hw_params_any),
+ ELEMENT(snd_pcm_hw_params_sizeof),
+ ELEMENT(snd_pcm_hw_params_get_buffer_size),
+ ELEMENT(snd_pcm_hw_params_get_buffer_size_min),
+ ELEMENT(snd_pcm_hw_params_get_period_size_min),
+ ELEMENT(snd_pcm_hw_params_set_access),
+ ELEMENT(snd_pcm_hw_params_set_buffer_size_near),
+ ELEMENT(snd_pcm_hw_params_set_buffer_time_near),
+ ELEMENT(snd_pcm_hw_params_set_channels_near),
+ ELEMENT(snd_pcm_hw_params_set_format),
+ ELEMENT(snd_pcm_hw_params_get_period_size),
+ ELEMENT(snd_pcm_hw_params_set_period_size_near),
+ ELEMENT(snd_pcm_hw_params_set_period_time_near),
+ ELEMENT(snd_pcm_hw_params_set_rate_near),
+
+ ELEMENT(snd_pcm_sw_params),
+ ELEMENT(snd_pcm_sw_params_current),
+ ELEMENT(snd_pcm_sw_params_get_start_threshold),
+ ELEMENT(snd_pcm_sw_params_set_avail_min),
+ ELEMENT(snd_pcm_sw_params_set_start_threshold),
+ ELEMENT(snd_pcm_sw_params_sizeof),
+
+ ELEMENT(snd_mixer_selem_id_sizeof),
+ ELEMENT(snd_mixer_open),
+ ELEMENT(snd_mixer_attach),
+ ELEMENT(snd_mixer_close),
+ ELEMENT(snd_mixer_selem_id_set_index),
+ ELEMENT(snd_mixer_selem_id_set_name),
+ ELEMENT(snd_mixer_selem_set_playback_volume),
+ ELEMENT(snd_mixer_selem_get_playback_volume_range),
+ ELEMENT(snd_mixer_selem_set_capture_volume),
+ ELEMENT(snd_mixer_selem_get_capture_volume_range),
+ ELEMENT(snd_mixer_selem_register),
+ ELEMENT(snd_mixer_load),
+ ELEMENT(snd_mixer_find_selem),
+
+};
+#undef ELEMENT
+
+/** Init once. */
+static RTONCE g_AlsaLibInitOnce = RTONCE_INITIALIZER;
+
+
+/** @callback_method_impl{FNRTONCE} */
+static DECLCALLBACK(int32_t) drvHostAudioAlsaLibInitOnce(void *pvUser)
+{
+ RT_NOREF(pvUser);
+ LogFlowFunc(("\n"));
+
+ RTLDRMOD hMod = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystemEx(VBOX_ALSA_LIB, RTLDRLOAD_FLAGS_NO_UNLOAD, &hMod);
+ if (RT_SUCCESS(rc))
+ {
+ for (uintptr_t i = 0; i < RT_ELEMENTS(SharedFuncs); i++)
+ {
+ rc = RTLdrGetSymbol(hMod, SharedFuncs[i].name, (void **)SharedFuncs[i].pfn);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (SharedFuncs[i].pfnFallback && rc == VERR_SYMBOL_NOT_FOUND)
+ *SharedFuncs[i].pfn = SharedFuncs[i].pfnFallback;
+ else
+ {
+ LogRelFunc(("Failed to load library %s: Getting symbol %s failed: %Rrc\n", VBOX_ALSA_LIB, SharedFuncs[i].name, rc));
+ return rc;
+ }
+ }
+
+ RTLdrClose(hMod);
+ }
+ else
+ LogRelFunc(("Failed to load library %s (%Rrc)\n", VBOX_ALSA_LIB, rc));
+ return rc;
+}
+
+
+/**
+ * Try to dynamically load the ALSA libraries.
+ *
+ * @returns VBox status code.
+ */
+int audioLoadAlsaLib(void)
+{
+ LogFlowFunc(("\n"));
+ return RTOnce(&g_AlsaLibInitOnce, drvHostAudioAlsaLibInitOnce, NULL);
+}
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h
new file mode 100644
index 00000000..05e4e311
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubs.h
@@ -0,0 +1,72 @@
+/* $Id: DrvHostAudioAlsaStubs.h $ */
+/** @file
+ * Stubs for libasound.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h
+#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+#include <alsa/version.h>
+
+#define VBOX_ALSA_MAKE_VER(a,b,c) ( ((a) << 24) | ((b) << 16) | (c) )
+#define VBOX_ALSA_VER VBOX_ALSA_MAKE_VER(SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR)
+
+RT_C_DECLS_BEGIN
+extern int audioLoadAlsaLib(void);
+
+#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,18) /* added in 1.0.18 */
+extern int snd_pcm_avail_delay(snd_pcm_t *, snd_pcm_sframes_t *, snd_pcm_sframes_t *);
+#endif
+
+#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,14) /* added in 1.0.14a */
+extern int snd_device_name_hint(int, const char *, void ***);
+extern int snd_device_name_free_hint(void **);
+extern char *snd_device_name_get_hint(const void *, const char *);
+#endif
+
+#if VBOX_ALSA_VER < VBOX_ALSA_MAKE_VER(1,0,27) /* added in 1.0.27 */
+enum snd_pcm_chmap_position { SND_CHMAP_UNKNOWN = 0, SND_CHMAP_NA, SND_CHMAP_MONO, SND_CHMAP_FL, SND_CHMAP_FR,
+ SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR, SND_CHMAP_RC,
+ SND_CHMAP_FLC, SND_CHMAP_FRC, SND_CHMAP_RLC, SND_CHMAP_RRC, SND_CHMAP_FLW, SND_CHMAP_FRW, SND_CHMAP_FLH,
+ SND_CHMAP_FCH, SND_CHMAP_FRH, SND_CHMAP_TC, SND_CHMAP_TFL, SND_CHMAP_TFR, SND_CHMAP_TFC, SND_CHMAP_TRL,
+ SND_CHMAP_TRR, SND_CHMAP_TRC, SND_CHMAP_TFLC, SND_CHMAP_TFRC, SND_CHMAP_TSL, SND_CHMAP_TSR, SND_CHMAP_LLFE,
+ SND_CHMAP_RLFE, SND_CHMAP_BC, SND_CHMAP_BLC, SND_CHMAP_BRC };
+typedef struct snd_pcm_chmap
+{
+ unsigned int channels;
+ RT_GCC_EXTENSION
+ unsigned int pos[RT_FLEXIBLE_ARRAY_IN_NESTED_UNION];
+} snd_pcm_chmap_t;
+extern int snd_pcm_set_chmap(snd_pcm_t *, snd_pcm_chmap_t const *);
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubs_h */
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h
new file mode 100644
index 00000000..414f3553
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioAlsaStubsMangling.h
@@ -0,0 +1,99 @@
+/* $Id: DrvHostAudioAlsaStubsMangling.h $ */
+/** @file
+ * Mangle libasound symbols.
+ *
+ * This is necessary on hosts which don't support the -fvisibility gcc switch.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h
+#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define ALSA_MANGLER(symbol) VBox_##symbol
+
+#define snd_lib_error_set_handler ALSA_MANGLER(snd_lib_error_set_handler)
+#define snd_strerror ALSA_MANGLER(snd_strerror)
+
+#define snd_device_name_hint ALSA_MANGLER(snd_device_name_hint)
+#define snd_device_name_get_hint ALSA_MANGLER(snd_device_name_get_hint)
+#define snd_device_name_free_hint ALSA_MANGLER(snd_device_name_free_hint)
+
+#define snd_pcm_avail_update ALSA_MANGLER(snd_pcm_avail_update)
+#define snd_pcm_close ALSA_MANGLER(snd_pcm_close)
+#define snd_pcm_avail_delay ALSA_MANGLER(snd_pcm_avail_delay)
+#define snd_pcm_delay ALSA_MANGLER(snd_pcm_delay)
+#define snd_pcm_drain ALSA_MANGLER(snd_pcm_drain)
+#define snd_pcm_drop ALSA_MANGLER(snd_pcm_drop)
+#define snd_pcm_nonblock ALSA_MANGLER(snd_pcm_nonblock)
+#define snd_pcm_open ALSA_MANGLER(snd_pcm_open)
+#define snd_pcm_prepare ALSA_MANGLER(snd_pcm_prepare)
+#define snd_pcm_readi ALSA_MANGLER(snd_pcm_readi)
+#define snd_pcm_resume ALSA_MANGLER(snd_pcm_resume)
+#define snd_pcm_set_chmap ALSA_MANGLER(snd_pcm_set_chmap)
+#define snd_pcm_start ALSA_MANGLER(snd_pcm_start)
+#define snd_pcm_state ALSA_MANGLER(snd_pcm_state)
+#define snd_pcm_state_name ALSA_MANGLER(snd_pcm_state_name)
+#define snd_pcm_writei ALSA_MANGLER(snd_pcm_writei)
+
+#define snd_pcm_hw_params ALSA_MANGLER(snd_pcm_hw_params)
+#define snd_pcm_hw_params_any ALSA_MANGLER(snd_pcm_hw_params_any)
+#define snd_pcm_hw_params_sizeof ALSA_MANGLER(snd_pcm_hw_params_sizeof)
+#define snd_pcm_hw_params_get_buffer_size ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size)
+#define snd_pcm_hw_params_get_period_size_min ALSA_MANGLER(snd_pcm_hw_params_get_period_size_min)
+#define snd_pcm_hw_params_set_rate_near ALSA_MANGLER(snd_pcm_hw_params_set_rate_near)
+#define snd_pcm_hw_params_set_access ALSA_MANGLER(snd_pcm_hw_params_set_access)
+#define snd_pcm_hw_params_set_buffer_time_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_time_near)
+#define snd_pcm_hw_params_set_buffer_size_near ALSA_MANGLER(snd_pcm_hw_params_set_buffer_size_near)
+#define snd_pcm_hw_params_get_buffer_size_min ALSA_MANGLER(snd_pcm_hw_params_get_buffer_size_min)
+#define snd_pcm_hw_params_set_channels_near ALSA_MANGLER(snd_pcm_hw_params_set_channels_near)
+#define snd_pcm_hw_params_set_format ALSA_MANGLER(snd_pcm_hw_params_set_format)
+#define snd_pcm_hw_params_get_period_size ALSA_MANGLER(snd_pcm_hw_params_get_period_size)
+#define snd_pcm_hw_params_set_period_size_near ALSA_MANGLER(snd_pcm_hw_params_set_period_size_near)
+#define snd_pcm_hw_params_set_period_time_near ALSA_MANGLER(snd_pcm_hw_params_set_period_time_near)
+
+#define snd_pcm_sw_params ALSA_MANGLER(snd_pcm_sw_params)
+#define snd_pcm_sw_params_current ALSA_MANGLER(snd_pcm_sw_params_current)
+#define snd_pcm_sw_params_get_start_threshold ALSA_MANGLER(snd_pcm_sw_params_get_start_threshold)
+#define snd_pcm_sw_params_set_avail_min ALSA_MANGLER(snd_pcm_sw_params_set_avail_min)
+#define snd_pcm_sw_params_set_start_threshold ALSA_MANGLER(snd_pcm_sw_params_set_start_threshold)
+#define snd_pcm_sw_params_sizeof ALSA_MANGLER(snd_pcm_sw_params_sizeof)
+
+#define snd_mixer_selem_id_sizeof ALSA_MANGLER(snd_mixer_selem_id_sizeof)
+#define snd_mixer_open ALSA_MANGLER(snd_mixer_open)
+#define snd_mixer_attach ALSA_MANGLER(snd_mixer_attach)
+#define snd_mixer_close ALSA_MANGLER(snd_mixer_close)
+#define snd_mixer_selem_id_set_index ALSA_MANGLER(snd_mixer_selem_id_set_index)
+#define snd_mixer_selem_id_set_name ALSA_MANGLER(snd_mixer_selem_id_set_name)
+#define snd_mixer_selem_set_playback_volume ALSA_MANGLER(snd_mixer_selem_set_playback_volume)
+#define snd_mixer_selem_get_playback_volume_range ALSA_MANGLER(snd_mixer_selem_get_playback_volume_range)
+#define snd_mixer_selem_set_capture_volume ALSA_MANGLER(snd_mixer_selem_set_capture_volume)
+#define snd_mixer_selem_get_capture_volume_range ALSA_MANGLER(snd_mixer_selem_get_capture_volume_range)
+#define snd_mixer_selem_register ALSA_MANGLER(snd_mixer_selem_register)
+#define snd_mixer_load ALSA_MANGLER(snd_mixer_load)
+#define snd_mixer_find_selem ALSA_MANGLER(snd_mixer_find_selem)
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioAlsaStubsMangling_h */
diff --git a/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp b/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp
new file mode 100644
index 00000000..60697c1d
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp
@@ -0,0 +1,2925 @@
+/* $Id: DrvHostAudioCoreAudio.cpp $ */
+/** @file
+ * Host audio driver - Mac OS X CoreAudio.
+ *
+ * For relevant Apple documentation, here are some starters:
+ * - Core Audio Essentials
+ * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
+ * - TN2097: Playing a sound file using the Default Output Audio Unit
+ * https://developer.apple.com/library/archive/technotes/tn2097/
+ * - TN2091: Device input using the HAL Output Audio Unit
+ * https://developer.apple.com/library/archive/technotes/tn2091/
+ * - Audio Component Services
+ * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc
+ * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice
+ * https://developer.apple.com/library/archive/qa/qa1533/
+ * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer
+ * https://developer.apple.com/library/archive/qa/qa1317/
+ */
+
+/*
+ * Copyright (C) 2010-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include "VBoxDD.h"
+
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+#include <iprt/circbuf.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+#include <iprt/timer.h>
+
+#include <CoreAudio/CoreAudio.h>
+#include <CoreServices/CoreServices.h>
+#include <AudioToolbox/AudioQueue.h>
+#include <AudioUnit/AudioUnit.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 /* possibly 1080 */
+# define kAudioHardwarePropertyTranslateUIDToDevice (AudioObjectPropertySelector)'uidd'
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max number of queue buffers we'll use. */
+#define COREAUDIO_MAX_BUFFERS 1024
+/** The minimum number of queue buffers. */
+#define COREAUDIO_MIN_BUFFERS 4
+
+/** Enables the worker thread.
+ * This saves CoreAudio from creating an additional thread upon queue
+ * creation. (It does not help with the slow AudioQueueDispose fun.) */
+#define CORE_AUDIO_WITH_WORKER_THREAD
+#if 0
+/** Enables the AudioQueueDispose breakpoint timer (debugging help). */
+# define CORE_AUDIO_WITH_BREAKPOINT_TIMER
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the instance data for a Core Audio driver instance. */
+typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
+/** Pointer to the Core Audio specific backend data for an audio stream. */
+typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
+
+/**
+ * Core Audio device entry (enumeration).
+ *
+ * @note This is definitely not safe to just copy!
+ */
+typedef struct COREAUDIODEVICEDATA
+{
+ /** The core PDM structure. */
+ PDMAUDIOHOSTDEV Core;
+
+ /** The audio device ID of the currently used device (UInt32 typedef). */
+ AudioDeviceID idDevice;
+} COREAUDIODEVICEDATA;
+/** Pointer to a Core Audio device entry (enumeration). */
+typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA;
+
+
+/**
+ * Audio device information.
+ *
+ * We do not use COREAUDIODEVICEDATA here as it contains lots more than what we
+ * need and care to query. We also don't want to depend on DrvAudio making
+ * PDMIHOSTAUDIO::pfnGetDevices callbacks to keep this information up to date.
+ */
+typedef struct DRVHSTAUDCADEVICE
+{
+ /** The audio device ID. kAudioDeviceUnknown if not available. */
+ AudioObjectID idDevice;
+ /** Indicates whether we've registered device change listener. */
+ bool fRegisteredListeners;
+ /** The UID string (must release). NULL if not available. */
+ CFStringRef hStrUid;
+ /** The UID string for a specific device, NULL if we're using the default device. */
+ char *pszSpecific;
+} DRVHSTAUDCADEVICE;
+/** Pointer to info about a default device. */
+typedef DRVHSTAUDCADEVICE *PDRVHSTAUDCADEVICE;
+
+
+/**
+ * Core Audio stream state.
+ */
+typedef enum COREAUDIOINITSTATE
+{
+ /** The device is uninitialized. */
+ COREAUDIOINITSTATE_UNINIT = 0,
+ /** The device is currently initializing. */
+ COREAUDIOINITSTATE_IN_INIT,
+ /** The device is initialized. */
+ COREAUDIOINITSTATE_INIT,
+ /** The device is currently uninitializing. */
+ COREAUDIOINITSTATE_IN_UNINIT,
+ /** The usual 32-bit hack. */
+ COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff
+} COREAUDIOINITSTATE;
+
+
+/**
+ * Core audio buffer tracker.
+ *
+ * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to
+ * track how much we've written. When a buffer is full, or if we run low on
+ * queued bufferes, it will be queued.
+ *
+ * For input buffer we'll be using offRead to track how much we've read.
+ *
+ * The queued/not-queued state is stored in the first bit of
+ * AudioQueueBuffer::mUserData. While bits 8 and up holds the index into
+ * COREAUDIOSTREAM::paBuffers.
+ */
+typedef struct COREAUDIOBUF
+{
+ /** The buffer. */
+ AudioQueueBufferRef pBuf;
+ /** The buffer read offset (input only). */
+ uint32_t offRead;
+} COREAUDIOBUF;
+/** Pointer to a core audio buffer tracker. */
+typedef COREAUDIOBUF *PCOREAUDIOBUF;
+
+
+/**
+ * Core Audio specific data for an audio stream.
+ */
+typedef struct COREAUDIOSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** List node for the device's stream list. */
+ RTLISTNODE Node;
+ /** The acquired (final) audio format for this stream.
+ * @note This what the device requests, we don't alter anything. */
+ AudioStreamBasicDescription BasicStreamDesc;
+ /** The actual audio queue being used. */
+ AudioQueueRef hAudioQueue;
+
+ /** Number of buffers. */
+ uint32_t cBuffers;
+ /** The array of buffer. */
+ PCOREAUDIOBUF paBuffers;
+
+ /** Initialization status tracker, actually COREAUDIOINITSTATE.
+ * Used when some of the device parameters or the device itself is changed
+ * during the runtime. */
+ volatile uint32_t enmInitState;
+ /** The current buffer being written to / read from. */
+ uint32_t idxBuffer;
+ /** Set if the stream is enabled. */
+ bool fEnabled;
+ /** Set if the stream is started (playing/capturing). */
+ bool fStarted;
+ /** Set if the stream is draining (output only). */
+ bool fDraining;
+ /** Set if we should restart the stream on resume (saved pause state). */
+ bool fRestartOnResume;
+// /** Set if we're switching to a new output/input device. */
+// bool fSwitchingDevice;
+ /** Internal stream offset (bytes). */
+ uint64_t offInternal;
+ /** The RTTimeMilliTS() at the end of the last transfer. */
+ uint64_t msLastTransfer;
+
+ /** Critical section for serializing access between thread + callbacks. */
+ RTCRITSECT CritSect;
+ /** Buffer that drvHstAudCaStreamStatusString uses. */
+ char szStatus[64];
+} COREAUDIOSTREAM;
+
+
+/**
+ * Instance data for a Core Audio host audio driver.
+ *
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTCOREAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** The input device. */
+ DRVHSTAUDCADEVICE InputDevice;
+ /** The output device. */
+ DRVHSTAUDCADEVICE OutputDevice;
+ /** Upwards notification interface. */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort;
+ /** Indicates whether we've registered default input device change listener. */
+ bool fRegisteredDefaultInputListener;
+ /** Indicates whether we've registered default output device change listener. */
+ bool fRegisteredDefaultOutputListener;
+
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+ /** @name Worker Thread For Queue callbacks and stuff.
+ * @{ */
+ /** The worker thread. */
+ RTTHREAD hThread;
+ /** The runloop of the worker thread. */
+ CFRunLoopRef hThreadRunLoop;
+ /** The message port we use to talk to the thread.
+ * @note While we don't currently use the port, it is necessary to prevent
+ * the thread from spinning or stopping prematurely because of
+ * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */
+ CFMachPortRef hThreadPort;
+ /** Runloop source for hThreadPort. */
+ CFRunLoopSourceRef hThreadPortSrc;
+ /** @} */
+#endif
+
+ /** Critical section to serialize access. */
+ RTCRITSECT CritSect;
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ /** Timder for debugging AudioQueueDispose slowness. */
+ RTTIMERLR hBreakpointTimer;
+#endif
+} DRVHOSTCOREAUDIO;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify);
+
+/* DrvHostAudioCoreAudioAuth.mm: */
+DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
+
+
+#ifdef LOG_ENABLED
+/**
+ * Gets the stream status.
+ *
+ * @returns Pointer to stream status string.
+ * @param pStreamCA The stream to get the status for.
+ */
+static const char *drvHstAudCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA)
+{
+ static RTSTRTUPLE const s_aInitState[5] =
+ {
+ { RT_STR_TUPLE("UNINIT") },
+ { RT_STR_TUPLE("IN_INIT") },
+ { RT_STR_TUPLE("INIT") },
+ { RT_STR_TUPLE("IN_UNINIT") },
+ { RT_STR_TUPLE("BAD") },
+ };
+ uint32_t enmInitState = pStreamCA->enmInitState;
+ PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)];
+ memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
+ size_t off = pTuple->cch;
+
+ static RTSTRTUPLE const s_aEnable[2] =
+ {
+ { RT_STR_TUPLE("DISABLED") },
+ { RT_STR_TUPLE("ENABLED ") },
+ };
+ pTuple = &s_aEnable[pStreamCA->fEnabled];
+ memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
+ off += pTuple->cch;
+
+ static RTSTRTUPLE const s_aStarted[2] =
+ {
+ { RT_STR_TUPLE(" STOPPED") },
+ { RT_STR_TUPLE(" STARTED") },
+ };
+ pTuple = &s_aStarted[pStreamCA->fStarted];
+ memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
+ off += pTuple->cch;
+
+ static RTSTRTUPLE const s_aDraining[2] =
+ {
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE(" DRAINING") },
+ };
+ pTuple = &s_aDraining[pStreamCA->fDraining];
+ memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
+ off += pTuple->cch;
+
+ Assert(off < sizeof(pStreamCA->szStatus));
+ pStreamCA->szStatus[off] = '\0';
+ return pStreamCA->szStatus;
+}
+#endif /*LOG_ENABLED*/
+
+
+
+
+#if 0 /* unused */
+static int drvHstAudCaCFStringToCString(const CFStringRef pCFString, char **ppszString)
+{
+ CFIndex cLen = CFStringGetLength(pCFString) + 1;
+ char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
+ if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
+ {
+ RTMemFree(pszResult);
+ return VERR_NOT_FOUND;
+ }
+
+ *ppszString = pszResult;
+ return VINF_SUCCESS;
+}
+
+static AudioDeviceID drvHstAudCaDeviceUIDtoID(const char* pszUID)
+{
+ /* Create a CFString out of our CString. */
+ CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
+
+ /* Fill the translation structure. */
+ AudioDeviceID deviceID;
+
+ AudioValueTranslation translation;
+ translation.mInputData = &strUID;
+ translation.mInputDataSize = sizeof(CFStringRef);
+ translation.mOutputData = &deviceID;
+ translation.mOutputDataSize = sizeof(AudioDeviceID);
+
+ /* Fetch the translation from the UID to the device ID. */
+ AudioObjectPropertyAddress PropAddr =
+ {
+ kAudioHardwarePropertyDeviceForUID,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ UInt32 uSize = sizeof(AudioValueTranslation);
+ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation);
+
+ /* Release the temporary CFString */
+ CFRelease(strUID);
+
+ if (RT_LIKELY(err == noErr))
+ return deviceID;
+
+ /* Return the unknown device on error. */
+ return kAudioDeviceUnknown;
+}
+#endif /* unused */
+
+
+/**
+ * Wrapper around AudioObjectGetPropertyData and AudioObjectGetPropertyDataSize.
+ *
+ * @returns Pointer to temp heap allocation with the data on success, free using
+ * RTMemTmpFree. NULL on failure, fully logged.
+ */
+static void *drvHstAudCaGetPropertyDataEx(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
+ AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
+ const char *pszWhat, UInt32 *pcb)
+{
+ AudioObjectPropertyAddress const PropAddr =
+ {
+ /*.mSelector = */ enmSelector,
+ /*.mScope = */ enmScope,
+ /*.mElement = */ enmElement
+ };
+
+ /*
+ * Have to retry here in case the size isn't stable (like if a new device/whatever is added).
+ */
+ for (uint32_t iTry = 0; ; iTry++)
+ {
+ UInt32 cb = 0;
+ OSStatus orc = AudioObjectGetPropertyDataSize(idObject, &PropAddr, 0, NULL, &cb);
+ if (orc == noErr)
+ {
+ cb = RT_MAX(cb, 1); /* we're allergic to zero allocations. */
+ void *pv = RTMemTmpAllocZ(cb);
+ if (pv)
+ {
+ orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
+ if (orc == noErr)
+ {
+ Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n",
+ idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
+ if (pcb)
+ *pcb = cb;
+ return pv;
+ }
+
+ RTMemTmpFree(pv);
+ LogFunc(("AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) -> %#x, iTry=%d\n",
+ idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc, iTry));
+ if (iTry < 3)
+ continue;
+ LogRelMax(32, ("CoreAudio: AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) failed: %#x\n",
+ idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc));
+ }
+ else
+ LogRelMax(32, ("CoreAudio: Failed to allocate %#x bytes (to get %s for %s).\n", cb, pszWhat, idObject));
+ }
+ else
+ LogRelMax(32, ("CoreAudio: Failed to get %s for %u: %#x\n", pszWhat, idObject, orc));
+ if (pcb)
+ *pcb = 0;
+ return NULL;
+ }
+}
+
+
+/**
+ * Wrapper around AudioObjectGetPropertyData.
+ *
+ * @returns Success indicator. Failures (@c false) are fully logged.
+ */
+static bool drvHstAudCaGetPropertyData(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
+ AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
+ const char *pszWhat, void *pv, UInt32 cb)
+{
+ AudioObjectPropertyAddress const PropAddr =
+ {
+ /*.mSelector = */ enmSelector,
+ /*.mScope = */ enmScope,
+ /*.mElement = */ enmElement
+ };
+
+ OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
+ if (orc == noErr)
+ {
+ Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
+ return true;
+ }
+ LogRelMax(64, ("CoreAudio: Failed to query %s (%u/%#x/%#x/%x, cb=%#x): %#x\n",
+ pszWhat, idObject, enmSelector, enmScope, enmElement, cb, orc));
+ return false;
+}
+
+
+/**
+ * Count the number of channels in one direction.
+ *
+ * @returns Channel count.
+ */
+static uint32_t drvHstAudCaEnumCountChannels(AudioObjectID idObject, AudioObjectPropertyScope enmScope)
+{
+ uint32_t cChannels = 0;
+
+ AudioBufferList *pBufs
+ = (AudioBufferList *)drvHstAudCaGetPropertyDataEx(idObject, kAudioDevicePropertyStreamConfiguration,
+ enmScope, kAudioObjectPropertyElementMaster, "stream config", NULL);
+ if (pBufs)
+ {
+ UInt32 idxBuf = pBufs->mNumberBuffers;
+ while (idxBuf-- > 0)
+ {
+ Log9Func(("%u/%#x[%u]: %u\n", idObject, enmScope, idxBuf, pBufs->mBuffers[idxBuf].mNumberChannels));
+ cChannels += pBufs->mBuffers[idxBuf].mNumberChannels;
+ }
+
+ RTMemTmpFree(pBufs);
+ }
+
+ return cChannels;
+}
+
+
+/**
+ * Translates a UID to an audio device ID.
+ *
+ * @returns Audio device ID on success, kAudioDeviceUnknown on failure.
+ * @param hStrUid The UID string to convert.
+ * @param pszUid The C-string vresion of @a hStrUid.
+ * @param pszWhat What we're converting (for logging).
+ */
+static AudioObjectID drvHstAudCaDeviceUidToId(CFStringRef hStrUid, const char *pszUid, const char *pszWhat)
+{
+ AudioObjectPropertyAddress const PropAddr =
+ {
+ /*.mSelector = */ kAudioHardwarePropertyTranslateUIDToDevice,
+ /*.mScope = */ kAudioObjectPropertyScopeGlobal,
+ /*.mElement = */ kAudioObjectPropertyElementMaster
+ };
+ AudioObjectID idDevice = 0;
+ UInt32 cb = sizeof(idDevice);
+ OSStatus orc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr,
+ sizeof(hStrUid), &hStrUid, &cb, &idDevice);
+ if (orc == noErr)
+ {
+ Log9Func(("%s device UID '%s' -> %RU32\n", pszWhat, pszUid, idDevice));
+ return idDevice;
+ }
+ /** @todo test on < 10.9, see which status code and do a fallback using the
+ * enumeration code. */
+ LogRelMax(64, ("CoreAudio: Failed to translate %s device UID '%s' to audio device ID: %#x\n", pszWhat, pszUid, orc));
+ return kAudioDeviceUnknown;
+}
+
+
+/**
+ * Copies a CFString to a buffer (UTF-8).
+ *
+ * @returns VBox status code. In the case of a buffer overflow, the buffer will
+ * contain data and be correctly terminated (provided @a cbDst is not
+ * zero.)
+ */
+static int drvHstAudCaCFStringToBuf(CFStringRef hStr, char *pszDst, size_t cbDst)
+{
+ AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW);
+
+ if (CFStringGetCString(hStr, pszDst, cbDst, kCFStringEncodingUTF8))
+ return VINF_SUCCESS;
+
+ /* First fallback: */
+ const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
+ if (pszSrc)
+ return RTStrCopy(pszDst, cbDst, pszSrc);
+
+ /* Second fallback: */
+ CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
+ AssertReturn(cbMax > 0, VERR_INVALID_UTF8_ENCODING);
+ AssertReturn(cbMax < (CFIndex)_16M, VERR_OUT_OF_RANGE);
+
+ char *pszTmp = (char *)RTMemTmpAlloc(cbMax);
+ AssertReturn(pszTmp, VERR_NO_TMP_MEMORY);
+
+ int rc;
+ if (CFStringGetCString(hStr, pszTmp, cbMax, kCFStringEncodingUTF8))
+ rc = RTStrCopy(pszDst, cbDst, pszTmp);
+ else
+ {
+ *pszDst = '\0';
+ rc = VERR_INVALID_UTF8_ENCODING;
+ }
+
+ RTMemTmpFree(pszTmp);
+ return rc;
+}
+
+
+/**
+ * Copies a CFString to a heap buffer (UTF-8).
+ *
+ * @returns Pointer to the heap buffer on success, NULL if out of heap or some
+ * conversion/extraction problem
+ */
+static char *drvHstAudCaCFStringToHeap(CFStringRef hStr)
+{
+ const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
+ if (pszSrc)
+ return RTStrDup(pszSrc);
+
+ /* Fallback: */
+ CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
+ AssertReturn(cbMax > 0, NULL);
+ AssertReturn(cbMax < (CFIndex)_16M, NULL);
+
+ char *pszDst = RTStrAlloc(cbMax);
+ if (pszDst)
+ {
+ AssertReturnStmt(CFStringGetCString(hStr, pszDst, cbMax, kCFStringEncodingUTF8), RTStrFree(pszDst), NULL);
+ size_t const cchDst = strlen(pszDst);
+ if (cbMax - cchDst > 32)
+ RTStrRealloc(&pszDst, cchDst + 1);
+ }
+ return pszDst;
+}
+
+
+/*********************************************************************************************************************************
+* Device Change Notification Callbacks *
+*********************************************************************************************************************************/
+
+#ifdef LOG_ENABLED
+/**
+ * Called when the kAudioDevicePropertyNominalSampleRate or
+ * kAudioDeviceProcessorOverload properties changes on a default device.
+ *
+ * Registered on default devices after device enumeration.
+ * Not sure on which thread/runloop this runs.
+ *
+ * (See AudioObjectPropertyListenerProc in the SDK headers.)
+ */
+static OSStatus drvHstAudCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
+ const AudioObjectPropertyAddress paAddresses[], void *pvUser)
+{
+ LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pvUser=%p\n", idObject, idObject, cAddresses, pvUser));
+ for (UInt32 idx = 0; idx < cAddresses; idx++)
+ LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
+ idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
+
+/** @todo r=bird: What's the plan here exactly? I've changed it to
+ * LOG_ENABLED only for now, as this has no other purpose. */
+ switch (idObject)
+ {
+ case kAudioDeviceProcessorOverload:
+ LogFunc(("Processor overload detected!\n"));
+ break;
+ case kAudioDevicePropertyNominalSampleRate:
+ LogFunc(("kAudioDevicePropertyNominalSampleRate!\n"));
+ break;
+ default:
+ /* Just skip. */
+ break;
+ }
+
+ return noErr;
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a
+ * default device.
+ *
+ * The purpose is mainly to log the event. There isn't much we can do about
+ * active streams or future one, other than waiting for a default device change
+ * notification callback. In the mean time, active streams should start failing
+ * to work and new ones fail on creation. This is the same for when we're
+ * configure to use specific devices, only we don't get any device change
+ * callback like for default ones.
+ *
+ * Not sure on which thread/runloop this runs.
+ *
+ * (See AudioObjectPropertyListenerProc in the SDK headers.)
+ */
+static OSStatus drvHstAudCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
+ const AudioObjectPropertyAddress paAddresses[], void *pvUser)
+{
+ PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
+ AssertPtr(pThis);
+ RT_NOREF(cAddresses, paAddresses);
+
+ /*
+ * Log everything.
+ */
+ LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
+ for (UInt32 idx = 0; idx < cAddresses; idx++)
+ LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
+ idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
+
+ /*
+ * Check which devices are affected.
+ */
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, noErr); /* could be a destruction race */
+
+ for (unsigned i = 0; i < 2; i++)
+ {
+ if (idObject == (i == 0 ? pThis->InputDevice.idDevice : pThis->OutputDevice.idDevice))
+ {
+ AudioObjectPropertyAddress const PropAddr =
+ {
+ kAudioDevicePropertyDeviceIsAlive,
+ i == 0 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+ UInt32 fAlive = 0;
+ UInt32 cb = sizeof(fAlive);
+ OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, &fAlive);
+ if ( orc == kAudioHardwareBadDeviceError
+ || (orc == noErr && !fAlive))
+ {
+ LogRel(("CoreAudio: The default %s device (%u) stopped functioning.\n", idObject, i == 0 ? "input" : "output"));
+#if 0 /* This will only cause an extra re-init (in addition to the default device change) and likely do no good even if that
+ default device change callback doesn't arrive. So, don't do it! (bird) */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
+ if (pIHostAudioPort)
+ {
+ RTCritSectLeave(&pThis->CritSect);
+
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, i == 0 ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL);
+
+ rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, noErr); /* could be a destruction race */
+ }
+#endif
+ }
+ }
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ return noErr;
+}
+
+
+/**
+ * Called when the default recording or playback device has changed.
+ *
+ * Registered by the constructor. Not sure on which thread/runloop this runs.
+ *
+ * (See AudioObjectPropertyListenerProc in the SDK headers.)
+ */
+static OSStatus drvHstAudCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
+ const AudioObjectPropertyAddress *paAddresses, void *pvUser)
+
+{
+ PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
+ AssertPtr(pThis);
+ RT_NOREF(idObject, cAddresses, paAddresses);
+
+ /*
+ * Log everything.
+ */
+ LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
+ for (UInt32 idx = 0; idx < cAddresses; idx++)
+ LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
+ idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
+
+ /*
+ * Update the default devices and notify parent driver if anything actually changed.
+ */
+ drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/);
+ drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/);
+
+ return noErr;
+}
+
+
+/**
+ * Registers callbacks for a specific Core Audio device.
+ *
+ * @returns true if idDevice isn't kAudioDeviceUnknown and callbacks were
+ * registered, otherwise false.
+ * @param pThis The core audio driver instance data.
+ * @param idDevice The device ID to deregister callbacks for.
+ */
+static bool drvHstAudCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
+{
+ if (idDevice != kAudioDeviceUnknown)
+ {
+ LogFunc(("idDevice=%RU32\n", idDevice));
+ AudioObjectPropertyAddress PropAddr =
+ {
+ kAudioDevicePropertyDeviceIsAlive,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus orc;
+ orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
+ unsigned cRegistrations = orc == noErr;
+ if ( orc != noErr
+ && orc != kAudioHardwareIllegalOperationError)
+ LogRel(("CoreAudio: Failed to add the recording device state changed listener (%#x)\n", orc));
+
+#ifdef LOG_ENABLED
+ PropAddr.mSelector = kAudioDeviceProcessorOverload;
+ PropAddr.mScope = kAudioUnitScope_Global;
+ orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
+ cRegistrations += orc == noErr;
+ if (orc != noErr)
+ LogRel(("CoreAudio: Failed to register processor overload listener (%#x)\n", orc));
+
+ PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ PropAddr.mScope = kAudioUnitScope_Global;
+ orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
+ cRegistrations += orc == noErr;
+ if (orc != noErr)
+ LogRel(("CoreAudio: Failed to register sample rate changed listener (%#x)\n", orc));
+#endif
+ return cRegistrations > 0;
+ }
+ return false;
+}
+
+
+/**
+ * Undoes what drvHstAudCaDeviceRegisterCallbacks() did.
+ *
+ * @param pThis The core audio driver instance data.
+ * @param idDevice The device ID to deregister callbacks for.
+ */
+static void drvHstAudCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
+{
+ if (idDevice != kAudioDeviceUnknown)
+ {
+ LogFunc(("idDevice=%RU32\n", idDevice));
+ AudioObjectPropertyAddress PropAddr =
+ {
+ kAudioDevicePropertyDeviceIsAlive,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus orc;
+ orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
+ if ( orc != noErr
+ && orc != kAudioHardwareBadObjectError)
+ LogRel(("CoreAudio: Failed to remove the device alive listener (%#x)\n", orc));
+
+#ifdef LOG_ENABLED
+ PropAddr.mSelector = kAudioDeviceProcessorOverload;
+ orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
+ if ( orc != noErr
+ && orc != kAudioHardwareBadObjectError)
+ LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%#x)\n", orc));
+
+ PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
+ if ( orc != noErr
+ && orc != kAudioHardwareBadObjectError)
+ LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%#x)\n", orc));
+#endif
+ }
+}
+
+
+/**
+ * Updates the default device for one direction.
+ *
+ * @param pThis The core audio driver instance data.
+ * @param pDevice The device information to update.
+ * @param fInput Set if input device, clear if output.
+ * @param fNotify Whether to notify the parent driver if something
+ * changed.
+ */
+static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify)
+{
+ /*
+ * Skip if there is a specific device we should use for this direction.
+ */
+ if (pDevice->pszSpecific)
+ return;
+
+ /*
+ * Get the information before we enter the critical section.
+ *
+ * (Yeah, this may make us get things wrong if the defaults changes really
+ * fast and we get notifications in parallel on multiple threads. However,
+ * the first is a don't-do-that situation and the latter is unlikely.)
+ */
+ AudioDeviceID idDefaultDev = kAudioDeviceUnknown;
+ if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject,
+ fInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
+ fInput ? "default input device" : "default output device",
+ &idDefaultDev, sizeof(idDefaultDev)))
+ idDefaultDev = kAudioDeviceUnknown;
+
+ CFStringRef hStrUid = NULL;
+ if (idDefaultDev != kAudioDeviceUnknown)
+ {
+ if (!drvHstAudCaGetPropertyData(idDefaultDev, kAudioDevicePropertyDeviceUID,
+ fInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster,
+ fInput ? "default input device UID" : "default output device UID",
+ &hStrUid, sizeof(hStrUid)))
+ hStrUid = NULL;
+ }
+ char szUid[128];
+ if (hStrUid)
+ drvHstAudCaCFStringToBuf(hStrUid, szUid, sizeof(szUid));
+ else
+ szUid[0] = '\0';
+
+ /*
+ * Grab the lock and do the updating.
+ *
+ * We're a little paranoid wrt the locking in case there turn out to be some kind
+ * of race around destruction (there really can't be, but better play safe).
+ */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if (idDefaultDev != pDevice->idDevice)
+ {
+ if (idDefaultDev != kAudioDeviceUnknown)
+ {
+ LogRel(("CoreAudio: Default %s device: %u (was %u), ID '%s'\n",
+ fInput ? "input" : "output", idDefaultDev, pDevice->idDevice, szUid));
+ pIHostAudioPort = fNotify ? pThis->pIHostAudioPort : NULL; /* (only if there is a new device) */
+ }
+ else
+ LogRel(("CoreAudio: Default %s device is gone (was %u)\n", fInput ? "input" : "output", pDevice->idDevice));
+
+ if (pDevice->hStrUid)
+ CFRelease(pDevice->hStrUid);
+ if (pDevice->fRegisteredListeners)
+ drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
+ pDevice->hStrUid = hStrUid;
+ pDevice->idDevice = idDefaultDev;
+ pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
+ hStrUid = NULL;
+ }
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ if (hStrUid != NULL)
+ CFRelease(hStrUid);
+
+ /*
+ * Notify parent driver to trigger a re-init of any associated streams.
+ */
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about %s default device change...\n", fInput ? "input" : "output"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ }
+}
+
+
+/**
+ * Sets the device to use in one or the other direction (@a fInput).
+ *
+ * @returns VBox status code.
+ * @param pThis The core audio driver instance data.
+ * @param pDevice The device info structure to update.
+ * @param fInput Set if input, clear if output.
+ * @param fNotify Whether to notify the parent driver if something
+ * changed.
+ * @param pszUid The UID string for the device to use. NULL or empty
+ * string if default should be used.
+ */
+static int drvHstAudCaSetDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify,
+ const char *pszUid)
+{
+ if (!pszUid || !*pszUid)
+ {
+ /*
+ * Use default. Always refresh the given default device.
+ */
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ if (pDevice->pszSpecific)
+ {
+ LogRel(("CoreAudio: Changing %s device from '%s' to default.\n", fInput ? "input" : "output", pDevice->pszSpecific));
+ RTStrFree(pDevice->pszSpecific);
+ pDevice->pszSpecific = NULL;
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ drvHstAudCaUpdateOneDefaultDevice(pThis, pDevice, fInput, fNotify);
+ }
+ else
+ {
+ /*
+ * Use device specified by pszUid. If not change, search for the device
+ * again if idDevice is unknown.
+ */
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ bool fSkip = false;
+ bool fSame = false;
+ if (pDevice->pszSpecific)
+ {
+ if (strcmp(pszUid, pDevice->pszSpecific) != 0)
+ {
+ LogRel(("CoreAudio: Changing %s device from '%s' to '%s'.\n",
+ fInput ? "input" : "output", pDevice->pszSpecific, pszUid));
+ RTStrFree(pDevice->pszSpecific);
+ pDevice->pszSpecific = NULL;
+ }
+ else
+ {
+ fSkip = pDevice->idDevice != kAudioDeviceUnknown;
+ fSame = true;
+ }
+ }
+ else
+ LogRel(("CoreAudio: Changing %s device from default to '%s'.\n", fInput ? "input" : "output", pszUid));
+
+ /*
+ * Allocate and swap the strings. This is the bit that might fail.
+ */
+ if (!fSame)
+ {
+ CFStringRef hStrUid = CFStringCreateWithBytes(NULL /*allocator*/, (UInt8 const *)pszUid, (CFIndex)strlen(pszUid),
+ kCFStringEncodingUTF8, false /*isExternalRepresentation*/);
+ char *pszSpecific = RTStrDup(pszUid);
+ if (hStrUid && pszSpecific)
+ {
+ if (pDevice->hStrUid)
+ CFRelease(pDevice->hStrUid);
+ pDevice->hStrUid = hStrUid;
+ RTStrFree(pDevice->pszSpecific);
+ pDevice->pszSpecific = pszSpecific;
+ }
+ else
+ {
+ RTCritSectLeave(&pThis->CritSect);
+
+ LogFunc(("returns VERR_NO_STR_MEMORY!\n"));
+ if (hStrUid)
+ CFRelease(hStrUid);
+ RTStrFree(pszSpecific);
+ return VERR_NO_STR_MEMORY;
+ }
+
+ if (pDevice->fRegisteredListeners)
+ {
+ drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
+ pDevice->fRegisteredListeners = false;
+ }
+ }
+
+ /*
+ * Locate the device ID corresponding to the UID string.
+ */
+ if (!fSkip)
+ {
+ pDevice->idDevice = drvHstAudCaDeviceUidToId(pDevice->hStrUid, pszUid, fInput ? "input" : "output");
+ pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
+ }
+
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = fNotify && !fSame ? pThis->pIHostAudioPort : NULL;
+ RTCritSectLeave(&pThis->CritSect);
+
+ /*
+ * Notify parent driver to trigger a re-init of any associated streams.
+ */
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about %s device change...\n", fInput ? "input" : "output"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Worker Thread *
+*********************************************************************************************************************************/
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+
+/**
+ * Message handling callback for CFMachPort.
+ */
+static void drvHstAudCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser)
+{
+ RT_NOREF(hPort, pvMsg, cbMsg, pvUser);
+ LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser));
+}
+
+
+/**
+ * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.}
+ */
+static DECLCALLBACK(int) drvHstAudCaThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
+
+ /*
+ * Get the runloop, add the mach port to it and signal the constructor thread that we're ready.
+ */
+ pThis->hThreadRunLoop = CFRunLoopGetCurrent();
+ CFRetain(pThis->hThreadRunLoop);
+
+ CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
+
+ int rc = RTThreadUserSignal(hThreadSelf);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do work.
+ */
+ for (;;)
+ {
+ SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE);
+ Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop));
+ Assert(rcRunLoop != kCFRunLoopRunFinished);
+ if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished)
+ { /* likely */ }
+ else
+ break;
+ }
+
+ /*
+ * Clean up.
+ */
+ CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
+ LogFunc(("The thread quits!\n"));
+ return VINF_SUCCESS;
+}
+
+#endif /* CORE_AUDIO_WITH_WORKER_THREAD */
+
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
+ pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
+ pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY;
+
+ RTCritSectEnter(&pThis->CritSect);
+#if 0 /** @todo r=bird: This looks like complete utter non-sense to me. */
+ /* For Core Audio we provide one stream per device for now. */
+ pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
+ pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
+#else
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+#endif
+ RTCritSectLeave(&pThis->CritSect);
+
+ LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates an enumeration of the host's playback and capture devices.
+ *
+ * @returns VBox status code.
+ * @param pDevEnm Where to store the enumerated devices. Caller is
+ * expected to clean this up on failure, if so desired.
+ *
+ * @note Handling of out-of-memory conditions isn't perhaps as good as it
+ * could be, but it was done so to make the drvHstAudCaGetPropertyData*
+ * functions as uncomplicated as possible.
+ */
+static int drvHstAudCaDevicesEnumerateAll(PPDMAUDIOHOSTENUM pDevEnm)
+{
+ AssertPtr(pDevEnm);
+
+ /*
+ * First get the UIDs for the default devices.
+ */
+ AudioDeviceID idDefaultDevIn = kAudioDeviceUnknown;
+ if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
+ "default input device", &idDefaultDevIn, sizeof(idDefaultDevIn)))
+ idDefaultDevIn = kAudioDeviceUnknown;
+ if (idDefaultDevIn == kAudioDeviceUnknown)
+ LogFunc(("No default input device\n"));
+
+ AudioDeviceID idDefaultDevOut = kAudioDeviceUnknown;
+ if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
+ "default output device", &idDefaultDevOut, sizeof(idDefaultDevOut)))
+ idDefaultDevOut = kAudioDeviceUnknown;
+ if (idDefaultDevOut == kAudioDeviceUnknown)
+ LogFunc(("No default output device\n"));
+
+ /*
+ * Get a list of all audio devices.
+ * (We have to retry as the we may race new devices being inserted.)
+ */
+ UInt32 cDevices = 0;
+ AudioDeviceID *paidDevices
+ = (AudioDeviceID *)drvHstAudCaGetPropertyDataEx(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
+ "devices", &cDevices);
+ cDevices /= sizeof(paidDevices[0]);
+
+ /*
+ * Try get details on each device and try add them to the enumeration result.
+ */
+ for (uint32_t i = 0; i < cDevices; i++)
+ {
+ AudioDeviceID const idDevice = paidDevices[i];
+
+ /*
+ * Allocate a new device entry and populate it.
+ *
+ * The only relevant information here is channel counts and the UID(s),
+ * everything else is just extras we can live without.
+ */
+ PCOREAUDIODEVICEDATA pDevEntry = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevEntry), 0, 0);
+ AssertReturnStmt(pDevEntry, RTMemTmpFree(paidDevices), VERR_NO_MEMORY);
+
+ pDevEntry->idDevice = idDevice;
+ if (idDevice != kAudioDeviceUnknown)
+ {
+ if (idDevice == idDefaultDevIn)
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN;
+ if (idDevice == idDefaultDevOut)
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
+ }
+
+ /* Count channels and determin the usage. */
+ pDevEntry->Core.cMaxInputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeInput);
+ pDevEntry->Core.cMaxOutputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeOutput);
+ if (pDevEntry->Core.cMaxInputChannels > 0 && pDevEntry->Core.cMaxOutputChannels > 0)
+ pDevEntry->Core.enmUsage = PDMAUDIODIR_DUPLEX;
+ else if (pDevEntry->Core.cMaxInputChannels > 0)
+ pDevEntry->Core.enmUsage = PDMAUDIODIR_IN;
+ else if (pDevEntry->Core.cMaxOutputChannels > 0)
+ pDevEntry->Core.enmUsage = PDMAUDIODIR_OUT;
+ else
+ {
+ pDevEntry->Core.enmUsage = PDMAUDIODIR_UNKNOWN;
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
+ /** @todo drop & skip? */
+ }
+
+ /* Get the device UID. (We ASSUME this is the same for both input and
+ output sides of the device.) */
+ CFStringRef hStrUid;
+ if (!drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceUID, kAudioDevicePropertyDeviceUID,
+ kAudioObjectPropertyElementMaster,
+ "device UID", &hStrUid, sizeof(hStrUid)))
+ hStrUid = NULL;
+
+ if (hStrUid)
+ {
+ pDevEntry->Core.pszId = drvHstAudCaCFStringToHeap(hStrUid);
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC;
+ }
+ else
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
+
+ /* Get the device name (ignore failures). */
+ CFStringRef hStrName = NULL;
+ if (drvHstAudCaGetPropertyData(idDevice, kAudioObjectPropertyName,
+ pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
+ ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster, "device name", &hStrName, sizeof(hStrName)))
+ {
+ pDevEntry->Core.pszName = drvHstAudCaCFStringToHeap(hStrName);
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC;
+ CFRelease(hStrName);
+ }
+
+ /* Check if the device is alive for the intended usage. For duplex
+ devices we'll flag it as dead if either of the directions are dead,
+ as there is no convenient way of saying otherwise. It's acadmic as
+ nobody currently 2021-05-22) uses the flag for anything. */
+ UInt32 fAlive = 0;
+ if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive,
+ pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
+ ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
+ if (!fAlive)
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
+ fAlive = 0;
+ if ( pDevEntry->Core.enmUsage == PDMAUDIODIR_DUPLEX
+ && !(pDevEntry->Core.fFlags == PDMAUDIOHOSTDEV_F_DEAD)
+ && drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput,
+ kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
+ if (!fAlive)
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
+
+ /* Check if the device is being hogged by someone else. */
+ pid_t pidHogger = -2;
+ if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster, "hog-mode", &pidHogger, sizeof(pidHogger)))
+ if (pidHogger >= 0)
+ pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED;
+
+ /*
+ * Try make sure we've got a name... Only add it to the enumeration if we have one.
+ */
+ if (!pDevEntry->Core.pszName)
+ {
+ pDevEntry->Core.pszName = pDevEntry->Core.pszId;
+ pDevEntry->Core.fFlags &= ~PDMAUDIOHOSTDEV_F_NAME_ALLOC;
+ }
+
+ if (pDevEntry->Core.pszName)
+ PDMAudioHostEnumAppend(pDevEnm, &pDevEntry->Core);
+ else
+ PDMAudioHostDevFree(&pDevEntry->Core);
+ }
+
+ RTMemTmpFree(paidDevices);
+
+ LogFunc(("Returning %u devices\n", pDevEnm->cDevices));
+ PDMAudioHostEnumLog(pDevEnm, "Core Audio");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
+
+ PDMAudioHostEnumInit(pDeviceEnum);
+ int rc = drvHstAudCaDevicesEnumerateAll(pDeviceEnum);
+ if (RT_FAILURE(rc))
+ PDMAudioHostEnumDelete(pDeviceEnum);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
+{
+ PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
+ AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
+ if (pszId && !*pszId)
+ pszId = NULL;
+ AssertMsgReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX,
+ ("enmDir=%d\n", enmDir, pszId), VERR_INVALID_PARAMETER);
+
+ /*
+ * Make the change.
+ */
+ int rc = VINF_SUCCESS;
+ if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
+ rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/, pszId);
+ if (enmDir == PDMAUDIODIR_OUT || (enmDir == PDMAUDIODIR_DUPLEX && RT_SUCCESS(rc)))
+ rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/, pszId);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Marks the given buffer as queued or not-queued.
+ *
+ * @returns Old queued value.
+ * @param pAudioBuffer The buffer.
+ * @param fQueued The new queued state.
+ */
+DECLINLINE(bool) drvHstAudCaSetBufferQueued(AudioQueueBufferRef pAudioBuffer, bool fQueued)
+{
+ if (fQueued)
+ return ASMAtomicBitTestAndSet(&pAudioBuffer->mUserData, 0);
+ return ASMAtomicBitTestAndClear(&pAudioBuffer->mUserData, 0);
+}
+
+
+/**
+ * Gets the queued state of the buffer.
+ * @returns true if queued, false if not.
+ * @param pAudioBuffer The buffer.
+ */
+DECLINLINE(bool) drvHstAudCaIsBufferQueued(AudioQueueBufferRef pAudioBuffer)
+{
+ return ((uintptr_t)pAudioBuffer->mUserData & 1) == 1;
+}
+
+
+/**
+ * Output audio queue buffer callback.
+ *
+ * Called whenever an audio queue is done processing a buffer. This routine
+ * will set the data fill size to zero and mark it as unqueued so that
+ * drvHstAudCaHA_StreamPlay knowns it can use it.
+ *
+ * @param pvUser User argument.
+ * @param hAudioQueue Audio queue to process output data for.
+ * @param pAudioBuffer Audio buffer to store output data in.
+ *
+ * @thread queue thread.
+ */
+static void drvHstAudCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer)
+{
+#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
+ AssertPtr(pStreamCA);
+ Assert(pStreamCA->hAudioQueue == hAudioQueue);
+
+ uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
+ Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer));
+ AssertReturnVoid( idxBuf < pStreamCA->cBuffers
+ && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
+#endif
+
+ pAudioBuffer->mAudioDataByteSize = 0;
+ bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
+ Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
+ Assert(fWasQueued); RT_NOREF(fWasQueued);
+
+ RT_NOREF(pvUser, hAudioQueue);
+}
+
+
+/**
+ * Input audio queue buffer callback.
+ *
+ * Called whenever input data from the audio queue becomes available. This
+ * routine will mark the buffer unqueued so that drvHstAudCaHA_StreamCapture can
+ * read the data from it.
+ *
+ * @param pvUser User argument.
+ * @param hAudioQueue Audio queue to process input data from.
+ * @param pAudioBuffer Audio buffer to process input data from.
+ * @param pAudioTS Audio timestamp.
+ * @param cPacketDesc Number of packet descriptors.
+ * @param paPacketDesc Array of packet descriptors.
+ */
+static void drvHstAudCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue,
+ AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS,
+ UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
+{
+#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
+ AssertPtr(pStreamCA);
+ Assert(pStreamCA->hAudioQueue == hAudioQueue);
+
+ uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
+ Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize));
+ AssertReturnVoid( idxBuf < pStreamCA->cBuffers
+ && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
+#endif
+
+ bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
+ Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
+ Assert(fWasQueued); RT_NOREF(fWasQueued);
+
+ RT_NOREF(pvUser, hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc);
+}
+
+
+static void drvHstAudCaLogAsbd(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
+{
+ LogRel2(("CoreAudio: %s description:\n", pszDesc));
+ LogRel2(("CoreAudio: Format ID: %#RX32 (%c%c%c%c)\n", pASBD->mFormatID,
+ RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
+ RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
+ LogRel2(("CoreAudio: Flags: %#RX32", pASBD->mFormatFlags));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
+ LogRel2((" Float"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
+ LogRel2((" BigEndian"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
+ LogRel2((" SignedInteger"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
+ LogRel2((" Packed"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
+ LogRel2((" AlignedHigh"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
+ LogRel2((" NonInterleaved"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
+ LogRel2((" NonMixable"));
+ if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
+ LogRel2((" AllClear"));
+ LogRel2(("\n"));
+ LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n",
+ (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100));
+ LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
+ LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
+ LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
+ LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
+ LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
+}
+
+
+static void drvHstAudCaPropsToAsbd(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD)
+{
+ AssertPtrReturnVoid(pProps);
+ AssertPtrReturnVoid(pASBD);
+
+ RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
+
+ pASBD->mFormatID = kAudioFormatLinearPCM;
+ pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
+ if (pProps->fSigned)
+ pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
+ if (PDMAudioPropsIsBigEndian(pProps))
+ pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian;
+ pASBD->mSampleRate = PDMAudioPropsHz(pProps);
+ pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps);
+ pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps);
+ pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps);
+ pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
+ pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ int rc;
+
+ /** @todo This takes too long. Stats indicates it may take up to 200 ms.
+ * Knoppix guest resets the stream and we hear nada because the
+ * draining is aborted when the stream is destroyed. Should try use
+ * async init for parts (much) of this. */
+
+ /*
+ * Permission check for input devices before we start.
+ */
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ rc = coreAudioInputPermissionCheck();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Do we have a device for the requested stream direction?
+ */
+ RTCritSectEnter(&pThis->CritSect);
+ CFStringRef hDevUidStr = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->InputDevice.hStrUid : pThis->OutputDevice.hStrUid;
+ if (hDevUidStr)
+ CFRetain(hDevUidStr);
+ RTCritSectLeave(&pThis->CritSect);
+
+#ifdef LOG_ENABLED
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+#endif
+ LogFunc(("hDevUidStr=%p *pCfgReq: %s\n", hDevUidStr, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) ));
+ if (hDevUidStr)
+ {
+ /*
+ * Basic structure init.
+ */
+ pStreamCA->fEnabled = false;
+ pStreamCA->fStarted = false;
+ pStreamCA->fDraining = false;
+ pStreamCA->fRestartOnResume = false;
+ pStreamCA->offInternal = 0;
+ pStreamCA->idxBuffer = 0;
+ pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT;
+
+ rc = RTCritSectInit(&pStreamCA->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do format conversion and create the circular buffer we use to shuffle
+ * data to/from the queue thread.
+ */
+ PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq);
+ drvHstAudCaPropsToAsbd(&pCfgReq->Props, &pStreamCA->BasicStreamDesc);
+ /** @todo Do some validation? */
+ drvHstAudCaLogAsbd(pCfgReq->enmDir == PDMAUDIODIR_IN ? "Capturing queue format" : "Playback queue format",
+ &pStreamCA->BasicStreamDesc);
+ /*
+ * Create audio queue.
+ *
+ * Documentation says the callbacks will be run on some core audio
+ * related thread if we don't specify a runloop here. That's simpler.
+ */
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+ CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop;
+ CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode;
+#else
+ CFRunLoopRef const hRunLoop = NULL;
+ CFStringRef const hRunLoopMode = NULL;
+#endif
+ OSStatus orc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
+ orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHstAudCaOutputQueueBufferCallback, pStreamCA,
+ hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
+ else
+ orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHstAudCaInputQueueBufferCallback, pStreamCA,
+ hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
+ LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc));
+ if (orc == noErr)
+ {
+ /*
+ * Assign device to the queue.
+ */
+ UInt32 uSize = sizeof(hDevUidStr);
+ orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &hDevUidStr, uSize);
+ LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc));
+ if (orc == noErr)
+ {
+ /*
+ * Sanity-adjust the requested buffer size.
+ */
+ uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC);
+ uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/);
+ cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize);
+ cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax);
+
+ /*
+ * The queue buffers size is based on cMsSchedulingHint so that we're likely to
+ * have a new one ready/done after each guest DMA transfer. We must however
+ * make sure we don't end up with too may or too few.
+ */
+ Assert(pCfgReq->Device.cMsSchedulingHint > 0);
+ uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props,
+ pCfgReq->Device.cMsSchedulingHint > 0
+ ? pCfgReq->Device.cMsSchedulingHint : 10);
+ uint32_t cQueueBuffers;
+ if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize)
+ {
+ cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer;
+ if (cQueueBuffers > COREAUDIO_MAX_BUFFERS)
+ {
+ cQueueBuffers = COREAUDIO_MAX_BUFFERS;
+ cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS;
+ }
+ }
+ else
+ {
+ cQueueBuffers = COREAUDIO_MIN_BUFFERS;
+ cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS;
+ }
+
+ cFramesBufferSize = cQueueBuffers * cFramesBufferSize;
+
+ /*
+ * Allocate the audio queue buffers.
+ */
+ pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers);
+ if (pStreamCA->paBuffers != NULL)
+ {
+ pStreamCA->cBuffers = cQueueBuffers;
+
+ const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer);
+ LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer));
+ cFramesBufferSize = 0;
+ for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++)
+ {
+ AudioQueueBufferRef pBuf = NULL;
+ orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf);
+ if (RT_LIKELY(orc == noErr))
+ {
+ pBuf->mUserData = (void *)(uintptr_t)(iBuf << 8); /* bit zero is the queued-indicator. */
+ pStreamCA->paBuffers[iBuf].pBuf = pBuf;
+ cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props,
+ pBuf->mAudioDataBytesCapacity);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity));
+ }
+ else
+ {
+ LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n",
+ iBuf, cQueueBuffers, cbQueueBuffer));
+ while (iBuf-- > 0)
+ {
+ AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
+ pStreamCA->paBuffers[iBuf].pBuf = NULL;
+ }
+ break;
+ }
+ }
+ if (orc == noErr)
+ {
+ /*
+ * Update the stream config.
+ */
+ pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize;
+ pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */
+ pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering
+ * pStreamCA->Cfg.Backend.cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg);
+
+ ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
+
+ LogFunc(("returns VINF_SUCCESS\n"));
+ CFRelease(hDevUidStr);
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pStreamCA->paBuffers);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
+ AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
+ }
+ else
+ LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
+ RTCritSectDelete(&pStreamCA->CritSect);
+ }
+ else
+ LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
+ CFRelease(hDevUidStr);
+ }
+ else
+ {
+ LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir)));
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ LogFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
+ LogFunc(("%p: %s fImmediate=%RTbool\n", pStreamCA, pStreamCA->Cfg.szName, fImmediate));
+#ifdef LOG_ENABLED
+ uint64_t const nsStart = RTTimeNanoTS();
+#endif
+
+ /*
+ * Never mind if the status isn't INIT (it should always be, though).
+ */
+ COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
+ AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
+ if (enmInitState == COREAUDIOINITSTATE_INIT)
+ {
+ Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
+
+ /*
+ * Change the stream state and stop the stream (just to be sure).
+ */
+ OSStatus orc;
+ ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
+ if (pStreamCA->hAudioQueue)
+ {
+ orc = AudioQueueStop(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/);
+ LogFlowFunc(("AudioQueueStop -> %#x\n", orc));
+ }
+
+ /*
+ * Enter and leave the critsect afterwards for paranoid reasons.
+ */
+ RTCritSectEnter(&pStreamCA->CritSect);
+ RTCritSectLeave(&pStreamCA->CritSect);
+
+ /*
+ * Free the queue buffers and the queue.
+ *
+ * This may take a while. The AudioQueueReset call seems to helps
+ * reducing time stuck in AudioQueueDispose.
+ */
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ LogRel(("Queue-destruction timer starting...\n"));
+ PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
+ RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS);
+ uint64_t nsStart = RTTimeNanoTS();
+#endif
+
+#if 0 /* This seems to work even when doing a non-immediate stop&dispose. However, it doesn't make sense conceptually. */
+ if (pStreamCA->hAudioQueue /*&& fImmediate*/)
+ {
+ LogFlowFunc(("Calling AudioQueueReset ...\n"));
+ orc = AudioQueueReset(pStreamCA->hAudioQueue);
+ LogFlowFunc(("AudioQueueReset -> %#x\n", orc));
+ }
+#endif
+
+ if (pStreamCA->paBuffers && fImmediate)
+ {
+ LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers));
+ for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
+ {
+ orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
+ AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc));
+ pStreamCA->paBuffers[iBuf].pBuf = NULL;
+ }
+ }
+
+ if (pStreamCA->hAudioQueue)
+ {
+ LogFlowFunc(("Disposing of the queue ...\n"));
+ orc = AudioQueueDispose(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); /* may take some time */
+ LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc));
+ AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc));
+ pStreamCA->hAudioQueue = NULL;
+ }
+
+ /* We should get no further buffer callbacks at this point according to the docs. */
+ if (pStreamCA->paBuffers)
+ {
+ RTMemFree(pStreamCA->paBuffers);
+ pStreamCA->paBuffers = NULL;
+ }
+ pStreamCA->cBuffers = 0;
+
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ RTTimerLRStop(pThis->hBreakpointTimer);
+ LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart));
+#endif
+
+ /*
+ * Delete the critsect and we're done.
+ */
+ RTCritSectDelete(&pStreamCA->CritSect);
+
+ ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
+ }
+ else
+ LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState));
+
+ LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
+ return VINF_SUCCESS;
+}
+
+
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */
+static DECLCALLBACK(void) drvHstAudCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
+{
+ LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick));
+ RT_NOREF(hTimer, pvUser, iTick);
+ RTLogFlush(NULL);
+ RT_BREAKPOINT();
+}
+#endif
+
+
+/**
+ * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
+ RTCritSectEnter(&pStreamCA->CritSect);
+
+ Assert(!pStreamCA->fEnabled);
+ Assert(!pStreamCA->fStarted);
+
+ /*
+ * We always reset the buffer before enabling the stream (normally never necessary).
+ */
+ OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue);
+ if (orc != noErr)
+ LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ Assert(orc == noErr);
+ for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
+ Assert(!drvHstAudCaIsBufferQueued(pStreamCA->paBuffers[iBuf].pBuf));
+
+ pStreamCA->offInternal = 0;
+ pStreamCA->fDraining = false;
+ pStreamCA->fEnabled = true;
+ pStreamCA->fRestartOnResume = false;
+ pStreamCA->idxBuffer = 0;
+
+ /*
+ * Input streams will start capturing, while output streams will only start
+ * playing once we get some audio data to play (see drvHstAudCaHA_StreamPlay).
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ /* Zero (probably not needed) and submit all the buffers first. */
+ for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
+ {
+ AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf;
+
+ RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity);
+ pBuf->mAudioDataByteSize = 0;
+ drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
+
+ orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
+ AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n",
+ iBuf, orc, orc, pStreamCA->Cfg.szName),
+ drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/));
+ }
+
+ /* Start the stream. */
+ if (orc == noErr)
+ {
+ LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName));
+ orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
+ AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc),
+ rc = VERR_AUDIO_STREAM_NOT_READY);
+ pStreamCA->fStarted = orc == noErr;
+ }
+ else
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ else
+ Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
+ pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
+ RTCritSectEnter(&pStreamCA->CritSect);
+
+ /*
+ * Always stop it (draining or no).
+ */
+ pStreamCA->fEnabled = false;
+ pStreamCA->fRestartOnResume = false;
+ Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ int rc = VINF_SUCCESS;
+ if (pStreamCA->fStarted)
+ {
+#if 0
+ OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue);
+ LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
+ orc2 = AudioQueueFlush(pStreamCA->hAudioQueue);
+ LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
+#endif
+
+ OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
+ LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ if (orc != noErr)
+ {
+ LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+ pStreamCA->fStarted = false;
+ pStreamCA->fDraining = false;
+ }
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
+ return rc;
+}
+
+
+/**
+ * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
+ pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
+ RTCritSectEnter(&pStreamCA->CritSect);
+
+ /*
+ * Unless we're draining the stream, pause it if it has started.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamCA->fStarted && !pStreamCA->fDraining)
+ {
+ pStreamCA->fRestartOnResume = true;
+
+ OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue);
+ LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ if (orc != noErr)
+ {
+ LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+ pStreamCA->fStarted = false;
+ }
+ else
+ {
+ pStreamCA->fRestartOnResume = false;
+ if (pStreamCA->fDraining)
+ {
+ LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName));
+ Assert(pStreamCA->fStarted);
+ }
+ }
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
+ return rc;
+}
+
+
+/**
+ * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
+ RTCritSectEnter(&pStreamCA->CritSect);
+
+ /*
+ * Resume according to state saved by drvHstAudCaHA_StreamPause.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamCA->fRestartOnResume)
+ {
+ OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
+ LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ if (orc != noErr)
+ {
+ LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+ pStreamCA->fRestartOnResume = false;
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
+ return rc;
+}
+
+
+/**
+ * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
+ pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
+ RTCritSectEnter(&pStreamCA->CritSect);
+
+ /*
+ * The AudioQueueStop function has both an immediate and a drain mode,
+ * so we'll obviously use the latter here. For checking draining progress,
+ * we will just check if all buffers have been returned or not.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamCA->fStarted)
+ {
+ if (!pStreamCA->fDraining)
+ {
+ OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/);
+ LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ if (orc == noErr)
+ pStreamCA->fDraining = true;
+ else
+ {
+ LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+ }
+ else
+ LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName));
+ }
+ else
+ {
+ LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName));
+ AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false);
+ }
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
+
+ uint32_t cbReadable = 0;
+ if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ RTCritSectEnter(&pStreamCA->CritSect);
+ PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
+ uint32_t const cBuffers = pStreamCA->cBuffers;
+ uint32_t const idxStart = pStreamCA->idxBuffer;
+ uint32_t idxBuffer = idxStart;
+ AudioQueueBufferRef pBuf;
+
+ if ( cBuffers > 0
+ && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
+ {
+ do
+ {
+ uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
+ uint32_t cbFill = pBuf->mAudioDataByteSize;
+ AssertStmt(cbFill <= cbTotal, cbFill = cbTotal);
+ uint32_t off = paBuffers[idxBuffer].offRead;
+ AssertStmt(off < cbFill, off = cbFill);
+
+ cbReadable += cbFill - off;
+
+ /* Advance. */
+ idxBuffer++;
+ if (idxBuffer < cBuffers)
+ { /* likely */ }
+ else
+ idxBuffer = 0;
+ } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
+ }
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ }
+ Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName));
+ return cbReadable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
+ AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
+
+ uint32_t cbWritable = 0;
+ if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ RTCritSectEnter(&pStreamCA->CritSect);
+ PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
+ uint32_t const cBuffers = pStreamCA->cBuffers;
+ uint32_t const idxStart = pStreamCA->idxBuffer;
+ uint32_t idxBuffer = idxStart;
+ AudioQueueBufferRef pBuf;
+
+ if ( cBuffers > 0
+ && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
+ {
+ do
+ {
+ uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
+ uint32_t cbUsed = pBuf->mAudioDataByteSize;
+ AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal);
+
+ cbWritable += cbTotal - cbUsed;
+
+ /* Advance. */
+ idxBuffer++;
+ if (idxBuffer < cBuffers)
+ { /* likely */ }
+ else
+ idxBuffer = 0;
+ } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
+ }
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+ }
+ Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
+ {
+ if (!pStreamCA->fDraining)
+ { /* likely */ }
+ else
+ {
+ /*
+ * If we're draining, we're done when we've got all the buffers back.
+ */
+ RTCritSectEnter(&pStreamCA->CritSect);
+ PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
+ uintptr_t idxBuffer = pStreamCA->cBuffers;
+ while (idxBuffer-- > 0)
+ if (!drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf))
+ { /* likely */ }
+ else
+ {
+#ifdef LOG_ENABLED
+ uint32_t cQueued = 1;
+ while (idxBuffer-- > 0)
+ cQueued += drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf);
+ LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued));
+#endif
+ RTCritSectLeave(&pStreamCA->CritSect);
+ return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+ }
+
+ LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName));
+ pStreamCA->fDraining = false;
+ pStreamCA->fEnabled = false;
+ pStreamCA->fStarted = false;
+ RTCritSectLeave(&pStreamCA->CritSect);
+ }
+
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ }
+ return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
+ AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY);
+
+ RTCritSectEnter(&pStreamCA->CritSect);
+ if (pStreamCA->fEnabled)
+ { /* likely */ }
+ else
+ {
+ RTCritSectLeave(&pStreamCA->CritSect);
+ *pcbWritten = 0;
+ LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
+ return VINF_SUCCESS;
+ }
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
+
+ /*
+ * Transfer loop.
+ */
+ PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
+ uint32_t const cBuffers = pStreamCA->cBuffers;
+ AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
+ RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
+
+ uint32_t idxBuffer = pStreamCA->idxBuffer;
+ AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
+
+ int rc = VINF_SUCCESS;
+ uint32_t cbWritten = 0;
+ while (cbBuf > 0)
+ {
+ AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
+
+ /*
+ * Check out how much we can put into the current buffer.
+ */
+ AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
+ if (!drvHstAudCaIsBufferQueued(pBuf))
+ { /* likely */ }
+ else
+ {
+ LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf));
+ /** @todo stats */
+ break;
+ }
+
+ AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
+ uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
+ uint32_t cbUsed = pBuf->mAudioDataByteSize;
+ AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal);
+ uint32_t const cbAvail = cbTotal - cbUsed;
+
+ /*
+ * Copy over the data.
+ */
+ if (cbBuf < cbAvail)
+ {
+ Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n",
+ pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
+ memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf);
+ pBuf->mAudioDataByteSize = cbUsed + cbBuf;
+ cbWritten += cbBuf;
+ pStreamCA->offInternal += cbBuf;
+ /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of
+ * buffers queued. */
+ break;
+ }
+
+ Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n",
+ pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
+ memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail);
+ pBuf->mAudioDataByteSize = cbTotal;
+ cbWritten += cbAvail;
+ pStreamCA->offInternal += cbAvail;
+ drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
+
+ OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
+ if (orc == noErr)
+ { /* likely */ }
+ else
+ {
+ LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
+ pStreamCA->Cfg.szName, idxBuffer, orc, orc));
+ drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
+ pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Advance.
+ */
+ idxBuffer += 1;
+ if (idxBuffer < cBuffers)
+ { /* likely */ }
+ else
+ idxBuffer = 0;
+ pStreamCA->idxBuffer = idxBuffer;
+
+ pvBuf = (const uint8_t *)pvBuf + cbAvail;
+ cbBuf -= cbAvail;
+ }
+
+ /*
+ * Start the stream if we haven't do so yet.
+ */
+ if ( pStreamCA->fStarted
+ || cbWritten == 0
+ || RT_FAILURE_NP(rc))
+ { /* likely */ }
+ else
+ {
+ UInt32 cFramesPrepared = 0;
+#if 0 /* taking too long? */
+ OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared);
+ LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n",
+ pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
+ AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc));
+#else
+ OSStatus orc;
+#endif
+ orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
+ LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
+ if (orc == noErr)
+ pStreamCA->fStarted = true;
+ else
+ {
+ LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n",
+ pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+
+ /*
+ * Done.
+ */
+#ifdef LOG_ENABLED
+ uint64_t const msPrev = pStreamCA->msLastTransfer;
+#endif
+ uint64_t const msNow = RTTimeMilliTS();
+ if (cbWritten)
+ pStreamCA->msLastTransfer = msNow;
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+
+ *pcbWritten = cbWritten;
+ if (RT_SUCCESS(rc) || !cbWritten)
+ { }
+ else
+ {
+ LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
+ rc = VINF_SUCCESS;
+ }
+ LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten,
+ msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamCA, 0);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
+ AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY);
+
+ RTCritSectEnter(&pStreamCA->CritSect);
+ if (pStreamCA->fEnabled)
+ { /* likely */ }
+ else
+ {
+ RTCritSectLeave(&pStreamCA->CritSect);
+ *pcbRead = 0;
+ LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA)));
+ return VINF_SUCCESS;
+ }
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
+
+
+ /*
+ * Transfer loop.
+ */
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props);
+ PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
+ uint32_t const cBuffers = pStreamCA->cBuffers;
+ AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
+ RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
+
+ uint32_t idxBuffer = pStreamCA->idxBuffer;
+ AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
+
+ int rc = VINF_SUCCESS;
+ uint32_t cbRead = 0;
+ while (cbBuf > cbFrame)
+ {
+ AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
+
+ /*
+ * Check out how much we can read from the current buffer (if anything at all).
+ */
+ AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
+ if (!drvHstAudCaIsBufferQueued(pBuf))
+ { /* likely */ }
+ else
+ {
+ LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf));
+ /** @todo stats */
+ break;
+ }
+
+ AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
+ uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
+ uint32_t cbValid = pBuf->mAudioDataByteSize;
+ AssertStmt(cbValid < cbTotal, cbValid = cbTotal);
+ uint32_t offRead = paBuffers[idxBuffer].offRead;
+ uint32_t const cbLeft = cbValid - offRead;
+
+ /*
+ * Copy over the data.
+ */
+ if (cbBuf < cbLeft)
+ {
+ Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n",
+ pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
+ memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf);
+ paBuffers[idxBuffer].offRead = offRead + cbBuf;
+ cbRead += cbBuf;
+ pStreamCA->offInternal += cbBuf;
+ break;
+ }
+
+ Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n",
+ pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
+ memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft);
+ cbRead += cbLeft;
+ pStreamCA->offInternal += cbLeft;
+
+ RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */
+ paBuffers[idxBuffer].offRead = 0;
+ pBuf->mAudioDataByteSize = 0;
+ drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
+
+ OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
+ if (orc == noErr)
+ { /* likely */ }
+ else
+ {
+ LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
+ pStreamCA->Cfg.szName, idxBuffer, orc, orc));
+ drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Advance.
+ */
+ idxBuffer += 1;
+ if (idxBuffer < cBuffers)
+ { /* likely */ }
+ else
+ idxBuffer = 0;
+ pStreamCA->idxBuffer = idxBuffer;
+
+ pvBuf = (uint8_t *)pvBuf + cbLeft;
+ cbBuf -= cbLeft;
+ }
+
+ /*
+ * Done.
+ */
+#ifdef LOG_ENABLED
+ uint64_t const msPrev = pStreamCA->msLastTransfer;
+#endif
+ uint64_t const msNow = RTTimeMilliTS();
+ if (cbRead)
+ pStreamCA->msLastTransfer = msNow;
+
+ RTCritSectLeave(&pStreamCA->CritSect);
+
+ *pcbRead = cbRead;
+ if (RT_SUCCESS(rc) || !cbRead)
+ { }
+ else
+ {
+ LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
+ rc = VINF_SUCCESS;
+ }
+ LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead,
+ msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for the power off and destructor callbacks.
+ */
+static void drvHstAudCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
+{
+ /*
+ * Unregister system callbacks.
+ */
+ AudioObjectPropertyAddress PropAddr =
+ {
+ kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ OSStatus orc;
+ if (pThis->fRegisteredDefaultInputListener)
+ {
+ orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
+ drvHstAudCaDefaultDeviceChangedCallback, pThis);
+ if ( orc != noErr
+ && orc != kAudioHardwareBadObjectError)
+ LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
+ pThis->fRegisteredDefaultInputListener = false;
+ }
+
+ if (pThis->fRegisteredDefaultOutputListener)
+ {
+ PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
+ drvHstAudCaDefaultDeviceChangedCallback, pThis);
+ if ( orc != noErr
+ && orc != kAudioHardwareBadObjectError)
+ LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
+ pThis->fRegisteredDefaultOutputListener = false;
+ }
+
+ /*
+ * Unregister device callbacks.
+ */
+ RTCritSectEnter(&pThis->CritSect);
+
+ drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->InputDevice.idDevice);
+ pThis->InputDevice.idDevice = kAudioDeviceUnknown;
+
+ drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->OutputDevice.idDevice);
+ pThis->OutputDevice.idDevice = kAudioDeviceUnknown;
+
+ RTCritSectLeave(&pThis->CritSect);
+
+ LogFlowFuncEnter();
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnPowerOff}
+ */
+static DECLCALLBACK(void) drvHstAudCaPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+ drvHstAudCaRemoveDefaultDeviceListners(pThis);
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT}
+ */
+static DECLCALLBACK(void) drvHstAudCaDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ drvHstAudCaRemoveDefaultDeviceListners(pThis);
+
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+ if (pThis->hThread != NIL_RTTHREAD)
+ {
+ for (unsigned iLoop = 0; iLoop < 60; iLoop++)
+ {
+ if (pThis->hThreadRunLoop)
+ CFRunLoopStop(pThis->hThreadRunLoop);
+ if (iLoop > 10)
+ RTThreadPoke(pThis->hThread);
+ int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/);
+ if (RT_SUCCESS(rc))
+ break;
+ AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc));
+ }
+ pThis->hThread = NIL_RTTHREAD;
+ }
+ if (pThis->hThreadPortSrc)
+ {
+ CFRelease(pThis->hThreadPortSrc);
+ pThis->hThreadPortSrc = NULL;
+ }
+ if (pThis->hThreadPort)
+ {
+ CFMachPortInvalidate(pThis->hThreadPort);
+ CFRelease(pThis->hThreadPort);
+ pThis->hThreadPort = NULL;
+ }
+ if (pThis->hThreadRunLoop)
+ {
+ CFRelease(pThis->hThreadRunLoop);
+ pThis->hThreadRunLoop = NULL;
+ }
+#endif
+
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ if (pThis->hBreakpointTimer != NIL_RTTIMERLR)
+ {
+ RTTimerLRDestroy(pThis->hBreakpointTimer);
+ pThis->hBreakpointTimer = NIL_RTTIMERLR;
+ }
+#endif
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ int rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT,
+ * Construct a Core Audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHstAudCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ LogRel(("Audio: Initializing Core Audio driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+ pThis->hThread = NIL_RTTHREAD;
+#endif
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ pThis->hBreakpointTimer = NIL_RTTIMERLR;
+#endif
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudCaQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHstAudCaHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = drvHstAudCaHA_GetDevices;
+ pThis->IHostAudio.pfnSetDevice = drvHstAudCaHA_SetDevice;
+ pThis->IHostAudio.pfnGetStatus = drvHstAudCaHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHstAudCaHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHstAudCaHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHstAudCaHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHstAudCaHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHstAudCaHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHstAudCaHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHstAudCaHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHstAudCaHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHstAudCaHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetState = drvHstAudCaHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamPlay = drvHstAudCaHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamCapture = drvHstAudCaHA_StreamCapture;
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "InputDeviceID|OutputDeviceID", "");
+
+ char *pszTmp = NULL;
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "InputDeviceID", &pszTmp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotify*/, pszTmp);
+ PDMDrvHlpMMHeapFree(pDrvIns, pszTmp);
+ }
+ else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'InputDeviceID'");
+
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "OutputDeviceID", &pszTmp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotify*/, pszTmp);
+ PDMDrvHlpMMHeapFree(pDrvIns, pszTmp);
+ }
+ else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
+ return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'OutputDeviceID'");
+
+ /*
+ * Query the notification interface from the driver/device above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+#ifdef CORE_AUDIO_WITH_WORKER_THREAD
+ /*
+ * Create worker thread for running callbacks on.
+ */
+ CFMachPortContext PortCtx;
+ PortCtx.version = 0;
+ PortCtx.info = pThis;
+ PortCtx.retain = NULL;
+ PortCtx.release = NULL;
+ PortCtx.copyDescription = NULL;
+ pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHstAudCaThreadPortCallback, &PortCtx, NULL);
+ AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY);
+
+ pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/);
+ AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY);
+
+ rc = RTThreadCreateF(&pThis->hThread, drvHstAudCaThread, pThis, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc);
+
+ RTThreadUserWait(pThis->hThread, RT_MS_10SEC);
+ AssertLogRel(pThis->hThreadRunLoop);
+#endif
+
+#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
+ /*
+ * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy.
+ */
+ rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHstAudCaBreakpointTimer, pThis);
+ AssertRCReturn(rc, rc);
+#endif
+
+ /*
+ * Determin the default devices.
+ */
+ drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotifty*/);
+ drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotifty*/);
+
+ /*
+ * Register callbacks for default device input and output changes.
+ * (We just ignore failures here as there isn't much we can do about it,
+ * and it isn't 100% critical.)
+ */
+ AudioObjectPropertyAddress PropAddr =
+ {
+ /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
+ /* .mScope = */ kAudioObjectPropertyScopeGlobal,
+ /* .mElement = */ kAudioObjectPropertyElementMaster
+ };
+
+ OSStatus orc;
+ orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
+ pThis->fRegisteredDefaultInputListener = orc == noErr;
+ if ( orc != noErr
+ && orc != kAudioHardwareIllegalOperationError)
+ LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
+
+ PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
+ pThis->fRegisteredDefaultOutputListener = orc == noErr;
+ if ( orc != noErr
+ && orc != kAudioHardwareIllegalOperationError)
+ LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Char driver registration record.
+ */
+const PDMDRVREG g_DrvHostCoreAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "CoreAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Core Audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTCOREAUDIO),
+ /* pfnConstruct */
+ drvHstAudCaConstruct,
+ /* pfnDestruct */
+ drvHstAudCaDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvHstAudCaPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm b/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm
new file mode 100644
index 00000000..da21bcdd
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioCoreAudioAuth.mm
@@ -0,0 +1,150 @@
+/* $Id: DrvHostAudioCoreAudioAuth.mm $ */
+/** @file
+ * Host audio driver - Mac OS X CoreAudio, authorization helpers for Mojave+.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+
+#include <iprt/errcore.h>
+#include <iprt/semaphore.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
+# import <AVFoundation/AVFoundation.h>
+# import <AVFoundation/AVMediaFormat.h>
+#endif
+#import <Foundation/NSException.h>
+
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400
+
+/* HACK ALERT! It's there in the 10.13 SDK, but only for iOS 7.0+. Deploying CPP trickery to shut up warnings/errors. */
+# if MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
+# define AVAuthorizationStatus OurAVAuthorizationStatus
+# define AVAuthorizationStatusNotDetermined OurAVAuthorizationStatusNotDetermined
+# define AVAuthorizationStatusRestricted OurAVAuthorizationStatusRestricted
+# define AVAuthorizationStatusDenied OurAVAuthorizationStatusDenied
+# define AVAuthorizationStatusAuthorized OurAVAuthorizationStatusAuthorized
+# endif
+
+/**
+ * The authorization status enum.
+ *
+ * Starting macOS 10.14 we need to request permissions in order to use any audio input device
+ * but as we build against an older SDK where this is not available we have to duplicate
+ * AVAuthorizationStatus and do everything dynmically during runtime, sigh...
+ */
+typedef enum AVAuthorizationStatus
+# if RT_CPLUSPLUS_PREREQ(201100)
+ : NSInteger
+# endif
+{
+ AVAuthorizationStatusNotDetermined = 0,
+ AVAuthorizationStatusRestricted = 1,
+ AVAuthorizationStatusDenied = 2,
+ AVAuthorizationStatusAuthorized = 3
+} AVAuthorizationStatus;
+
+#endif
+
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 /** @todo need some better fix/whatever for AudioTest */
+
+/**
+ * Requests camera permissions for Mojave and onwards.
+ *
+ * @returns VBox status code.
+ */
+static int coreAudioInputPermissionRequest(void)
+{
+ __block RTSEMEVENT hEvt = NIL_RTSEMEVENT;
+ __block int rc = RTSemEventCreate(&hEvt);
+ if (RT_SUCCESS(rc))
+ {
+ /* Perform auth request. */
+ [AVCaptureDevice performSelector: @selector(requestAccessForMediaType: completionHandler:)
+ withObject: (id)AVMediaTypeAudio
+ withObject: (id)^(BOOL granted)
+ {
+ if (!granted)
+ {
+ LogRel(("CoreAudio: Access denied!\n"));
+ rc = VERR_ACCESS_DENIED;
+ }
+ RTSemEventSignal(hEvt);
+ }];
+
+ rc = RTSemEventWait(hEvt, RT_MS_10SEC);
+ RTSemEventDestroy(hEvt);
+ }
+
+ return rc;
+}
+
+#endif
+
+/**
+ * Checks permission for capturing devices on Mojave and onwards.
+ *
+ * @returns VBox status code.
+ */
+DECLHIDDEN(int) coreAudioInputPermissionCheck(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (NSFoundationVersionNumber >= 10.14)
+ {
+ /*
+ * Because we build with an older SDK where the authorization APIs are not available
+ * (introduced with Mojave 10.14) we have to resort to resolving the APIs dynamically.
+ */
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 /** @todo need some better fix/whatever for AudioTest */
+ LogRel(("CoreAudio: macOS 10.14+ detected, checking audio input permissions\n"));
+
+ if ([AVCaptureDevice respondsToSelector:@selector(authorizationStatusForMediaType:)])
+ {
+ AVAuthorizationStatus enmAuthSts
+ = (AVAuthorizationStatus)(NSInteger)[AVCaptureDevice performSelector: @selector(authorizationStatusForMediaType:)
+ withObject: (id)AVMediaTypeAudio];
+ if (enmAuthSts == AVAuthorizationStatusNotDetermined)
+ rc = coreAudioInputPermissionRequest();
+ else if ( enmAuthSts == AVAuthorizationStatusRestricted
+ || enmAuthSts == AVAuthorizationStatusDenied)
+ {
+ LogRel(("CoreAudio: Access denied!\n"));
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+#else
+ LogRel(("CoreAudio: WARNING! macOS 10.14+ detected. Audio input probably wont work as this app was compiled using a too old SDK.\n"));
+#endif
+ }
+
+ return rc;
+}
diff --git a/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp b/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp
new file mode 100644
index 00000000..c24e39bc
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp
@@ -0,0 +1,2881 @@
+/* $Id: DrvHostAudioDSound.cpp $ */
+/** @file
+ * Host audio driver - DirectSound (Windows).
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#define INITGUID
+#include <VBox/log.h>
+#include <iprt/win/windows.h>
+#include <dsound.h>
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <iprt/win/mmreg.h> /* WAVEFORMATEXTENSIBLE */
+
+#include <iprt/alloc.h>
+#include <iprt/system.h>
+#include <iprt/uuid.h>
+#include <iprt/utf16.h>
+
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include "VBoxDD.h"
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+# include <new> /* For bad_alloc. */
+# include "DrvHostAudioDSoundMMNotifClient.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * Optional release logging, which a user can turn on with the
+ * 'VBoxManage debugvm' command.
+ * Debug logging still uses the common Log* macros from VBox.
+ * Messages which always should go to the release log use LogRel.
+ *
+ * @deprecated Use LogRelMax, LogRel2 and LogRel3 directly.
+ */
+/** General code behavior. */
+#define DSLOG(a) do { LogRel2(a); } while(0)
+/** Something which produce a lot of logging during playback/recording. */
+#define DSLOGF(a) do { LogRel3(a); } while(0)
+/** Important messages like errors. Limited in the default release log to avoid log flood. */
+#define DSLOGREL(a) \
+ do { \
+ static int8_t s_cLogged = 0; \
+ if (s_cLogged < 8) { \
+ ++s_cLogged; \
+ LogRel(a); \
+ } else DSLOG(a); \
+ } while (0)
+
+/** Maximum number of attempts to restore the sound buffer before giving up. */
+#define DRV_DSOUND_RESTORE_ATTEMPTS_MAX 3
+#if 0 /** @todo r=bird: What are these for? Nobody is using them... */
+/** Default input latency (in ms). */
+#define DRV_DSOUND_DEFAULT_LATENCY_MS_IN 50
+/** Default output latency (in ms). */
+#define DRV_DSOUND_DEFAULT_LATENCY_MS_OUT 50
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* Dynamically load dsound.dll. */
+typedef HRESULT WINAPI FNDIRECTSOUNDENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
+typedef FNDIRECTSOUNDENUMERATEW *PFNDIRECTSOUNDENUMERATEW;
+typedef HRESULT WINAPI FNDIRECTSOUNDCAPTUREENUMERATEW(LPDSENUMCALLBACKW pDSEnumCallback, PVOID pContext);
+typedef FNDIRECTSOUNDCAPTUREENUMERATEW *PFNDIRECTSOUNDCAPTUREENUMERATEW;
+typedef HRESULT WINAPI FNDIRECTSOUNDCAPTURECREATE8(LPCGUID lpcGUID, LPDIRECTSOUNDCAPTURE8 *lplpDSC, LPUNKNOWN pUnkOuter);
+typedef FNDIRECTSOUNDCAPTURECREATE8 *PFNDIRECTSOUNDCAPTURECREATE8;
+
+#define VBOX_DSOUND_MAX_EVENTS 3
+
+typedef enum DSOUNDEVENT
+{
+ DSOUNDEVENT_NOTIFY = 0,
+ DSOUNDEVENT_INPUT,
+ DSOUNDEVENT_OUTPUT,
+} DSOUNDEVENT;
+
+typedef struct DSOUNDHOSTCFG
+{
+ RTUUID uuidPlay;
+ LPCGUID pGuidPlay;
+ RTUUID uuidCapture;
+ LPCGUID pGuidCapture;
+} DSOUNDHOSTCFG, *PDSOUNDHOSTCFG;
+
+typedef struct DSOUNDSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** Entry in DRVHOSTDSOUND::HeadStreams. */
+ RTLISTNODE ListEntry;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** Buffer alignment. */
+ uint8_t uAlign;
+ /** Whether this stream is in an enable state on the DirectSound side. */
+ bool fEnabled;
+ bool afPadding[2];
+ /** Size (in bytes) of the DirectSound buffer. */
+ DWORD cbBufSize;
+ union
+ {
+ struct
+ {
+ /** The actual DirectSound Buffer (DSB) used for the capturing.
+ * This is a secondary buffer and is used as a streaming buffer. */
+ LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB;
+ /** Current read offset (in bytes) within the DSB. */
+ DWORD offReadPos;
+ /** Number of buffer overruns happened. Used for logging. */
+ uint8_t cOverruns;
+ } In;
+ struct
+ {
+ /** The actual DirectSound Buffer (DSB) used for playback.
+ * This is a secondary buffer and is used as a streaming buffer. */
+ LPDIRECTSOUNDBUFFER8 pDSB;
+ /** Current write offset (in bytes) within the DSB.
+ * @note This is needed as the current write position as kept by direct sound
+ * will move ahead if we're too late. */
+ DWORD offWritePos;
+ /** Offset of last play cursor within the DSB when checked for pending. */
+ DWORD offPlayCursorLastPending;
+ /** Offset of last play cursor within the DSB when last played. */
+ DWORD offPlayCursorLastPlayed;
+ /** Total amount (in bytes) written to our internal ring buffer. */
+ uint64_t cbWritten;
+ /** Total amount (in bytes) played (to the DirectSound buffer). */
+ uint64_t cbTransferred;
+ /** Flag indicating whether playback was just (re)started. */
+ bool fFirstTransfer;
+ /** Flag indicating whether this stream is in draining mode, e.g. no new
+ * data is being written to it but DirectSound still needs to be able to
+ * play its remaining (buffered) data. */
+ bool fDrain;
+ /** How much (in bytes) the last transfer from the internal buffer
+ * to the DirectSound buffer was. */
+ uint32_t cbLastTransferred;
+ /** The RTTimeMilliTS() deadline for the draining of this stream. */
+ uint64_t msDrainDeadline;
+ } Out;
+ };
+ /** Timestamp (in ms) of the last transfer from the internal buffer to/from the
+ * DirectSound buffer. */
+ uint64_t msLastTransfer;
+ /** The stream's critical section for synchronizing access. */
+ RTCRITSECT CritSect;
+ /** Used for formatting the current DSound status. */
+ char szStatus[127];
+ /** Fixed zero terminator. */
+ char const chStateZero;
+} DSOUNDSTREAM, *PDSOUNDSTREAM;
+
+/**
+ * DirectSound-specific device entry.
+ */
+typedef struct DSOUNDDEV
+{
+ PDMAUDIOHOSTDEV Core;
+ /** The GUID if handy. */
+ GUID Guid;
+ /** The GUID as a string (empty if default). */
+ char szGuid[RTUUID_STR_LENGTH];
+} DSOUNDDEV;
+/** Pointer to a DirectSound device entry. */
+typedef DSOUNDDEV *PDSOUNDDEV;
+
+/**
+ * Structure for holding a device enumeration context.
+ */
+typedef struct DSOUNDENUMCBCTX
+{
+ /** Enumeration flags. */
+ uint32_t fFlags;
+ /** Pointer to device list to populate. */
+ PPDMAUDIOHOSTENUM pDevEnm;
+} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX;
+
+typedef struct DRVHOSTDSOUND
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Our audio host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Critical section to serialize access. */
+ RTCRITSECT CritSect;
+ /** DirectSound configuration options. */
+ DSOUNDHOSTCFG Cfg;
+ /** List of devices of last enumeration. */
+ PDMAUDIOHOSTENUM DeviceEnum;
+ /** Whether this backend supports any audio input.
+ * @todo r=bird: This is not actually used for anything. */
+ bool fEnabledIn;
+ /** Whether this backend supports any audio output.
+ * @todo r=bird: This is not actually used for anything. */
+ bool fEnabledOut;
+ /** The Direct Sound playback interface. */
+ LPDIRECTSOUND8 pDS;
+ /** The Direct Sound capturing interface. */
+ LPDIRECTSOUNDCAPTURE8 pDSC;
+ /** List of streams (DSOUNDSTREAM).
+ * Requires CritSect ownership. */
+ RTLISTANCHOR HeadStreams;
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ DrvHostAudioDSoundMMNotifClient *m_pNotificationClient;
+#endif
+} DRVHOSTDSOUND, *PDRVHOSTDSOUND;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB);
+static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset);
+
+static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDMAUDIOHOSTENUM pDevEnm, uint32_t fEnum);
+
+static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis);
+
+
+#if defined(LOG_ENABLED) || defined(RTLOG_REL_ENABLED)
+/**
+ * Gets the stream status as a string for logging purposes.
+ *
+ * @returns Status string (pStreamDS->szStatus).
+ * @param pStreamDS The stream to get the status for.
+ */
+static const char *drvHostDSoundStreamStatusString(PDSOUNDSTREAM pStreamDS)
+{
+ /*
+ * Out internal stream status first.
+ */
+ size_t off;
+ if (pStreamDS->fEnabled)
+ {
+ memcpy(pStreamDS->szStatus, RT_STR_TUPLE("ENABLED "));
+ off = sizeof("ENABLED ") - 1;
+ }
+ else
+ {
+ memcpy(pStreamDS->szStatus, RT_STR_TUPLE("DISABLED"));
+ off = sizeof("DISABLED") - 1;
+ }
+
+ /*
+ * Direction specific stuff, returning with a status DWORD and string mappings for it.
+ */
+ typedef struct DRVHOSTDSOUNDSFLAGS2STR
+ {
+ const char *pszMnemonic;
+ uint32_t cchMnemonic;
+ uint32_t fFlag;
+ } DRVHOSTDSOUNDSFLAGS2STR;
+ static const DRVHOSTDSOUNDSFLAGS2STR s_aCaptureFlags[] =
+ {
+ { RT_STR_TUPLE(" CAPTURING"), DSCBSTATUS_CAPTURING },
+ { RT_STR_TUPLE(" LOOPING"), DSCBSTATUS_LOOPING },
+ };
+ static const DRVHOSTDSOUNDSFLAGS2STR s_aPlaybackFlags[] =
+ {
+ { RT_STR_TUPLE(" PLAYING"), DSBSTATUS_PLAYING },
+ { RT_STR_TUPLE(" BUFFERLOST"), DSBSTATUS_BUFFERLOST },
+ { RT_STR_TUPLE(" LOOPING"), DSBSTATUS_LOOPING },
+ { RT_STR_TUPLE(" LOCHARDWARE"), DSBSTATUS_LOCHARDWARE },
+ { RT_STR_TUPLE(" LOCSOFTWARE"), DSBSTATUS_LOCSOFTWARE },
+ { RT_STR_TUPLE(" TERMINATED"), DSBSTATUS_TERMINATED },
+ };
+ DRVHOSTDSOUNDSFLAGS2STR const *paMappings = NULL;
+ size_t cMappings = 0;
+ DWORD fStatus = 0;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ if (pStreamDS->In.pDSCB)
+ {
+ HRESULT hrc = pStreamDS->In.pDSCB->GetStatus(&fStatus);
+ if (SUCCEEDED(hrc))
+ {
+ paMappings = s_aCaptureFlags;
+ cMappings = RT_ELEMENTS(s_aCaptureFlags);
+ }
+ else
+ RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc);
+ }
+ else
+ RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSCB");
+ }
+ else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ if (pStreamDS->Out.fDrain)
+ {
+ memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" DRAINING"));
+ off += sizeof(" DRAINING") - 1;
+ }
+
+ if (pStreamDS->Out.fFirstTransfer)
+ {
+ memcpy(&pStreamDS->szStatus[off], RT_STR_TUPLE(" NOXFER"));
+ off += sizeof(" NOXFER") - 1;
+ }
+
+ if (pStreamDS->Out.pDSB)
+ {
+ HRESULT hrc = pStreamDS->Out.pDSB->GetStatus(&fStatus);
+ if (SUCCEEDED(hrc))
+ {
+ paMappings = s_aPlaybackFlags;
+ cMappings = RT_ELEMENTS(s_aPlaybackFlags);
+ }
+ else
+ RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "GetStatus->%Rhrc", hrc);
+ }
+ else
+ RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "NO-DSB");
+ }
+ else
+ RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, "BAD-DIR");
+
+ /* Format flags. */
+ if (paMappings)
+ {
+ if (fStatus == 0)
+ RTStrCopy(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " 0");
+ else
+ {
+ for (size_t i = 0; i < cMappings; i++)
+ if (fStatus & paMappings[i].fFlag)
+ {
+ memcpy(&pStreamDS->szStatus[off], paMappings[i].pszMnemonic, paMappings[i].cchMnemonic);
+ off += paMappings[i].cchMnemonic;
+
+ fStatus &= ~paMappings[i].fFlag;
+ if (!fStatus)
+ break;
+ }
+ if (fStatus != 0)
+ off += RTStrPrintf(&pStreamDS->szStatus[off], sizeof(pStreamDS->szStatus) - off, " %#x", fStatus);
+ }
+ }
+
+ /*
+ * Finally, terminate the string. By postponing it this long, it won't be
+ * a big deal if two threads go thru here at the same time as long as the
+ * status is the same.
+ */
+ Assert(off < sizeof(pStreamDS->szStatus));
+ pStreamDS->szStatus[off] = '\0';
+
+ return pStreamDS->szStatus;
+}
+#endif /* LOG_ENABLED || RTLOG_REL_ENABLED */
+
+
+static DWORD dsoundRingDistance(DWORD offEnd, DWORD offBegin, DWORD cSize)
+{
+ AssertReturn(offEnd <= cSize, 0);
+ AssertReturn(offBegin <= cSize, 0);
+
+ return offEnd >= offBegin ? offEnd - offBegin : cSize - offBegin + offEnd;
+}
+
+
+static char *dsoundGUIDToUtf8StrA(LPCGUID pGUID)
+{
+ if (pGUID)
+ {
+ LPOLESTR lpOLEStr;
+ HRESULT hr = StringFromCLSID(*pGUID, &lpOLEStr);
+ if (SUCCEEDED(hr))
+ {
+ char *pszGUID;
+ int rc = RTUtf16ToUtf8(lpOLEStr, &pszGUID);
+ CoTaskMemFree(lpOLEStr);
+
+ return RT_SUCCESS(rc) ? pszGUID : NULL;
+ }
+ }
+
+ return RTStrDup("{Default device}");
+}
+
+
+static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB)
+{
+ RT_NOREF(pThis);
+ HRESULT hr = IDirectSoundBuffer8_Restore(pDSB);
+ if (FAILED(hr))
+ DSLOG(("DSound: Restoring playback buffer\n"));
+ else
+ DSLOGREL(("DSound: Restoring playback buffer failed with %Rhrc\n", hr));
+
+ return hr;
+}
+
+
+static HRESULT directSoundPlayUnlock(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB,
+ PVOID pv1, PVOID pv2,
+ DWORD cb1, DWORD cb2)
+{
+ RT_NOREF(pThis);
+ HRESULT hr = IDirectSoundBuffer8_Unlock(pDSB, pv1, cb1, pv2, cb2);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Unlocking playback buffer failed with %Rhrc\n", hr));
+ return hr;
+}
+
+
+static HRESULT directSoundPlayLock(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
+ DWORD dwOffset, DWORD dwBytes,
+ PVOID *ppv1, PVOID *ppv2,
+ DWORD *pcb1, DWORD *pcb2,
+ DWORD dwFlags)
+{
+ AssertReturn(dwBytes, VERR_INVALID_PARAMETER);
+
+ HRESULT hr = E_FAIL;
+ AssertCompile(DRV_DSOUND_RESTORE_ATTEMPTS_MAX > 0);
+ for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
+ {
+ PVOID pv1, pv2;
+ DWORD cb1, cb2;
+ hr = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, dwOffset, dwBytes, &pv1, &cb1, &pv2, &cb2, dwFlags);
+ if (SUCCEEDED(hr))
+ {
+ if ( (!pv1 || !(cb1 & pStreamDS->uAlign))
+ && (!pv2 || !(cb2 & pStreamDS->uAlign)))
+ {
+ if (ppv1)
+ *ppv1 = pv1;
+ if (ppv2)
+ *ppv2 = pv2;
+ if (pcb1)
+ *pcb1 = cb1;
+ if (pcb2)
+ *pcb2 = cb2;
+ return S_OK;
+ }
+ DSLOGREL(("DSound: Locking playback buffer returned misaligned buffer: cb1=%#RX32, cb2=%#RX32 (alignment: %#RX32)\n",
+ *pcb1, *pcb2, pStreamDS->uAlign));
+ directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2);
+ return E_FAIL;
+ }
+
+ if (hr != DSERR_BUFFERLOST)
+ break;
+
+ LogFlowFunc(("Locking failed due to lost buffer, restoring ...\n"));
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+ }
+
+ DSLOGREL(("DSound: Locking playback buffer failed with %Rhrc (dwOff=%ld, dwBytes=%ld)\n", hr, dwOffset, dwBytes));
+ return hr;
+}
+
+
+static HRESULT directSoundCaptureUnlock(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB,
+ PVOID pv1, PVOID pv2,
+ DWORD cb1, DWORD cb2)
+{
+ HRESULT hr = IDirectSoundCaptureBuffer8_Unlock(pDSCB, pv1, cb1, pv2, cb2);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Unlocking capture buffer failed with %Rhrc\n", hr));
+ return hr;
+}
+
+
+static HRESULT directSoundCaptureLock(PDSOUNDSTREAM pStreamDS,
+ DWORD dwOffset, DWORD dwBytes,
+ PVOID *ppv1, PVOID *ppv2,
+ DWORD *pcb1, DWORD *pcb2,
+ DWORD dwFlags)
+{
+ PVOID pv1 = NULL;
+ PVOID pv2 = NULL;
+ DWORD cb1 = 0;
+ DWORD cb2 = 0;
+
+ HRESULT hr = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, dwOffset, dwBytes,
+ &pv1, &cb1, &pv2, &cb2, dwFlags);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Locking capture buffer failed with %Rhrc\n", hr));
+ return hr;
+ }
+
+ if ( (pv1 && (cb1 & pStreamDS->uAlign))
+ || (pv2 && (cb2 & pStreamDS->uAlign)))
+ {
+ DSLOGREL(("DSound: Locking capture buffer returned misaligned buffer: cb1=%RI32, cb2=%RI32 (alignment: %RU32)\n",
+ cb1, cb2, pStreamDS->uAlign));
+ directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2);
+ return E_FAIL;
+ }
+
+ *ppv1 = pv1;
+ *ppv2 = pv2;
+ *pcb1 = cb1;
+ *pcb2 = cb2;
+
+ return S_OK;
+}
+
+
+/*
+ * DirectSound playback
+ */
+
+/**
+ * Creates a DirectSound playback instance.
+ *
+ * @return HRESULT
+ * @param pGUID GUID of device to create the playback interface for. NULL
+ * for the default device.
+ * @param ppDS Where to return the interface to the created instance.
+ */
+static HRESULT drvHostDSoundCreateDSPlaybackInstance(LPCGUID pGUID, LPDIRECTSOUND8 *ppDS)
+{
+ LogFlowFuncEnter();
+
+ LPDIRECTSOUND8 pDS = NULL;
+ HRESULT hrc = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL, IID_IDirectSound8, (void **)&pDS);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = IDirectSound8_Initialize(pDS, pGUID);
+ if (SUCCEEDED(hrc))
+ {
+ HWND hWnd = GetDesktopWindow();
+ hrc = IDirectSound8_SetCooperativeLevel(pDS, hWnd, DSSCL_PRIORITY);
+ if (SUCCEEDED(hrc))
+ {
+ *ppDS = pDS;
+ LogFlowFunc(("LEAVE S_OK\n"));
+ return S_OK;
+ }
+ LogRelMax(64, ("DSound: Setting cooperative level for (hWnd=%p) failed: %Rhrc\n", hWnd, hrc));
+ }
+ else if (hrc == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */
+ LogRelMax(64, ("DSound: DirectSound playback is currently unavailable\n"));
+ else
+ LogRelMax(64, ("DSound: DirectSound playback initialization failed: %Rhrc\n", hrc));
+
+ IDirectSound8_Release(pDS);
+ }
+ else
+ LogRelMax(64, ("DSound: Creating playback instance failed: %Rhrc\n", hrc));
+
+ LogFlowFunc(("LEAVE %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+#if 0 /* not used */
+static HRESULT directSoundPlayGetStatus(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB, DWORD *pdwStatus)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pDSB, E_POINTER);
+
+ AssertPtrNull(pdwStatus);
+
+ DWORD dwStatus = 0;
+ HRESULT hr = E_FAIL;
+ for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
+ {
+ hr = IDirectSoundBuffer8_GetStatus(pDSB, &dwStatus);
+ if ( hr == DSERR_BUFFERLOST
+ || ( SUCCEEDED(hr)
+ && (dwStatus & DSBSTATUS_BUFFERLOST)))
+ {
+ LogFlowFunc(("Getting status failed due to lost buffer, restoring ...\n"));
+ directSoundPlayRestore(pThis, pDSB);
+ }
+ else
+ break;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (pdwStatus)
+ *pdwStatus = dwStatus;
+ }
+ else
+ DSLOGREL(("DSound: Retrieving playback status failed with %Rhrc\n", hr));
+
+ return hr;
+}
+#endif
+
+
+/*
+ * DirectSoundCapture
+ */
+
+#if 0 /* unused */
+static LPCGUID dsoundCaptureSelectDevice(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, NULL);
+ AssertPtrReturn(pCfg, NULL);
+
+ int rc = VINF_SUCCESS;
+
+ LPCGUID pGUID = pThis->Cfg.pGuidCapture;
+ if (!pGUID)
+ {
+ PDSOUNDDEV pDev = NULL;
+ switch (pCfg->enmPath)
+ {
+ case PDMAUDIOPATH_IN_LINE:
+ /*
+ * At the moment we're only supporting line-in in the HDA emulation,
+ * and line-in + mic-in in the AC'97 emulation both are expected
+ * to use the host's mic-in as well.
+ *
+ * So the fall through here is intentional for now.
+ */
+ case PDMAUDIOPATH_IN_MIC:
+ pDev = (PDSOUNDDEV)DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->DeviceEnum, PDMAUDIODIR_IN);
+ break;
+
+ default:
+ AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pDev)
+ {
+ DSLOG(("DSound: Guest source '%s' is using host recording device '%s'\n",
+ PDMAudioPathGetName(pCfg->enmPath), pDev->Core.szName));
+ pGUID = &pDev->Guid;
+ }
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc));
+ return NULL;
+ }
+ }
+
+ /* This always has to be in the release log. */
+ char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
+ LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n",
+ PDMAudioPathGetName(pCfg->enmPath), pszGUID ? pszGUID: "{?}"));
+ RTStrFree(pszGUID);
+
+ return pGUID;
+}
+#endif
+
+
+/**
+ * Creates a DirectSound capture instance.
+ *
+ * @returns HRESULT
+ * @param pGUID GUID of device to create the capture interface for. NULL
+ * for default.
+ * @param ppDSC Where to return the interface to the created instance.
+ */
+static HRESULT drvHostDSoundCreateDSCaptureInstance(LPCGUID pGUID, LPDIRECTSOUNDCAPTURE8 *ppDSC)
+{
+ LogFlowFuncEnter();
+
+ LPDIRECTSOUNDCAPTURE8 pDSC = NULL;
+ HRESULT hrc = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL, IID_IDirectSoundCapture8, (void **)&pDSC);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = IDirectSoundCapture_Initialize(pDSC, pGUID);
+ if (SUCCEEDED(hrc))
+ {
+ *ppDSC = pDSC;
+ LogFlowFunc(("LEAVE S_OK\n"));
+ return S_OK;
+ }
+ if (hrc == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */
+ LogRelMax(64, ("DSound: Capture device currently is unavailable\n"));
+ else
+ LogRelMax(64, ("DSound: Initializing capturing device failed: %Rhrc\n", hrc));
+ IDirectSoundCapture_Release(pDSC);
+ }
+ else
+ LogRelMax(64, ("DSound: Creating capture instance failed: %Rhrc\n", hrc));
+
+ LogFlowFunc(("LEAVE %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/**
+ * Updates this host driver's internal status, according to the global, overall input/output
+ * state and all connected (native) audio streams.
+ *
+ * @todo r=bird: This is a 'ing waste of 'ing time! We're doing this everytime
+ * an 'ing stream is created and we doesn't 'ing use the information here
+ * for any darn thing! Given the reported slowness of enumeration and
+ * issues with the 'ing code the only appropriate response is:
+ * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARG!!!!!!!
+ *
+ * @param pThis Host audio driver instance.
+ */
+static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis)
+{
+#if 0 /** @todo r=bird: This isn't doing *ANYTHING* useful. So, I've just disabled it. */
+ AssertPtrReturnVoid(pThis);
+ LogFlowFuncEnter();
+
+ PDMAudioHostEnumDelete(&pThis->DeviceEnum);
+ int rc = dsoundDevicesEnumerate(pThis, &pThis->DeviceEnum);
+ if (RT_SUCCESS(rc))
+ {
+#if 0
+ if ( pThis->fEnabledOut != RT_BOOL(cbCtx.cDevOut)
+ || pThis->fEnabledIn != RT_BOOL(cbCtx.cDevIn))
+ {
+ /** @todo Use a registered callback to the audio connector (e.g "OnConfigurationChanged") to
+ * let the connector know that something has changed within the host backend. */
+ }
+#endif
+ pThis->fEnabledIn = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_IN) != 0;
+ pThis->fEnabledOut = PDMAudioHostEnumCountMatching(&pThis->DeviceEnum, PDMAUDIODIR_OUT) != 0;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+#else
+ RT_NOREF(pThis);
+#endif
+}
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DirectSound");
+ pBackendCfg->cbStream = sizeof(DSOUNDSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Callback for the playback device enumeration.
+ *
+ * @return TRUE if continuing enumeration, FALSE if not.
+ * @param pGUID Pointer to GUID of enumerated device. Can be NULL.
+ * @param pwszDescription Pointer to (friendly) description of enumerated device.
+ * @param pwszModule Pointer to module name of enumerated device.
+ * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information.
+ *
+ * @note Carbon copy of drvHostDSoundEnumOldStyleCaptureCallback with OUT direction.
+ */
+static BOOL CALLBACK drvHostDSoundEnumOldStylePlaybackCallback(LPGUID pGUID, LPCWSTR pwszDescription,
+ LPCWSTR pwszModule, PVOID lpContext)
+{
+ PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX)lpContext;
+ AssertPtrReturn(pEnumCtx, FALSE);
+
+ PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm;
+ AssertPtrReturn(pDevEnm, FALSE);
+
+ AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */
+ AssertPtrReturn(pwszDescription, FALSE);
+ RT_NOREF(pwszModule); /* Do not care about pwszModule. */
+
+ int rc;
+ size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1;
+ PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0);
+ if (pDev)
+ {
+ pDev->Core.enmUsage = PDMAUDIODIR_OUT;
+ pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
+
+ if (pGUID == NULL)
+ pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
+
+ rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pGUID)
+ pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
+ else
+ {
+ memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid));
+ rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid));
+ AssertRC(rc);
+ }
+ pDev->Core.pszId = &pDev->szGuid[0];
+
+ PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
+
+ /* Note: Querying the actual device information will be done at some
+ * later point in time outside this enumeration callback to prevent
+ * DSound hangs. */
+ return TRUE;
+ }
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogRel(("DSound: Error enumeration playback device '%ls': rc=%Rrc\n", pwszDescription, rc));
+ return FALSE; /* Abort enumeration. */
+}
+
+
+/**
+ * Callback for the capture device enumeration.
+ *
+ * @return TRUE if continuing enumeration, FALSE if not.
+ * @param pGUID Pointer to GUID of enumerated device. Can be NULL.
+ * @param pwszDescription Pointer to (friendly) description of enumerated device.
+ * @param pwszModule Pointer to module name of enumerated device.
+ * @param lpContext Pointer to PDSOUNDENUMCBCTX context for storing the enumerated information.
+ *
+ * @note Carbon copy of drvHostDSoundEnumOldStylePlaybackCallback with IN direction.
+ */
+static BOOL CALLBACK drvHostDSoundEnumOldStyleCaptureCallback(LPGUID pGUID, LPCWSTR pwszDescription,
+ LPCWSTR pwszModule, PVOID lpContext)
+{
+ PDSOUNDENUMCBCTX pEnumCtx = (PDSOUNDENUMCBCTX )lpContext;
+ AssertPtrReturn(pEnumCtx, FALSE);
+
+ PPDMAUDIOHOSTENUM pDevEnm = pEnumCtx->pDevEnm;
+ AssertPtrReturn(pDevEnm, FALSE);
+
+ AssertPtrNullReturn(pGUID, FALSE); /* pGUID can be NULL for default device(s). */
+ AssertPtrReturn(pwszDescription, FALSE);
+ RT_NOREF(pwszModule); /* Do not care about pwszModule. */
+
+ int rc;
+ size_t const cbName = RTUtf16CalcUtf8Len(pwszDescription) + 1;
+ PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0);
+ if (pDev)
+ {
+ pDev->Core.enmUsage = PDMAUDIODIR_IN;
+ pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
+
+ rc = RTUtf16ToUtf8Ex(pwszDescription, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pGUID)
+ pDev->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN;
+ else
+ {
+ memcpy(&pDev->Guid, pGUID, sizeof(pDev->Guid));
+ rc = RTUuidToStr((PCRTUUID)pGUID, pDev->szGuid, sizeof(pDev->szGuid));
+ AssertRC(rc);
+ }
+ pDev->Core.pszId = &pDev->szGuid[0];
+
+ PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
+
+ /* Note: Querying the actual device information will be done at some
+ * later point in time outside this enumeration callback to prevent
+ * DSound hangs. */
+ return TRUE;
+ }
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogRel(("DSound: Error enumeration capture device '%ls', rc=%Rrc\n", pwszDescription, rc));
+ return FALSE; /* Abort enumeration. */
+}
+
+
+/**
+ * Queries information for a given (DirectSound) device.
+ *
+ * @returns VBox status code.
+ * @param pDev Audio device to query information for.
+ */
+static int drvHostDSoundEnumOldStyleQueryDeviceInfo(PDSOUNDDEV pDev)
+{
+ AssertPtr(pDev);
+ int rc;
+
+ if (pDev->Core.enmUsage == PDMAUDIODIR_OUT)
+ {
+ LPDIRECTSOUND8 pDS;
+ HRESULT hr = drvHostDSoundCreateDSPlaybackInstance(&pDev->Guid, &pDS);
+ if (SUCCEEDED(hr))
+ {
+ DSCAPS DSCaps;
+ RT_ZERO(DSCaps);
+ DSCaps.dwSize = sizeof(DSCAPS);
+ hr = IDirectSound_GetCaps(pDS, &DSCaps);
+ if (SUCCEEDED(hr))
+ {
+ pDev->Core.cMaxOutputChannels = DSCaps.dwFlags & DSCAPS_PRIMARYSTEREO ? 2 : 1;
+
+ DWORD dwSpeakerCfg;
+ hr = IDirectSound_GetSpeakerConfig(pDS, &dwSpeakerCfg);
+ if (SUCCEEDED(hr))
+ {
+ unsigned uSpeakerCount = 0;
+ switch (DSSPEAKER_CONFIG(dwSpeakerCfg))
+ {
+ case DSSPEAKER_MONO: uSpeakerCount = 1; break;
+ case DSSPEAKER_HEADPHONE: uSpeakerCount = 2; break;
+ case DSSPEAKER_STEREO: uSpeakerCount = 2; break;
+ case DSSPEAKER_QUAD: uSpeakerCount = 4; break;
+ case DSSPEAKER_SURROUND: uSpeakerCount = 4; break;
+ case DSSPEAKER_5POINT1: uSpeakerCount = 6; break;
+ case DSSPEAKER_5POINT1_SURROUND: uSpeakerCount = 6; break;
+ case DSSPEAKER_7POINT1: uSpeakerCount = 8; break;
+ case DSSPEAKER_7POINT1_SURROUND: uSpeakerCount = 8; break;
+ default: break;
+ }
+
+ if (uSpeakerCount) /* Do we need to update the channel count? */
+ pDev->Core.cMaxOutputChannels = uSpeakerCount;
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRel(("DSound: Error retrieving playback device speaker config, hr=%Rhrc\n", hr));
+ rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
+ }
+ }
+ else
+ {
+ LogRel(("DSound: Error retrieving playback device capabilities, hr=%Rhrc\n", hr));
+ rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
+ }
+
+ IDirectSound8_Release(pDS);
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else if (pDev->Core.enmUsage == PDMAUDIODIR_IN)
+ {
+ LPDIRECTSOUNDCAPTURE8 pDSC;
+ HRESULT hr = drvHostDSoundCreateDSCaptureInstance(&pDev->Guid, &pDSC);
+ if (SUCCEEDED(hr))
+ {
+ DSCCAPS DSCCaps;
+ RT_ZERO(DSCCaps);
+ DSCCaps.dwSize = sizeof(DSCCAPS);
+ hr = IDirectSoundCapture_GetCaps(pDSC, &DSCCaps);
+ if (SUCCEEDED(hr))
+ {
+ pDev->Core.cMaxInputChannels = DSCCaps.dwChannels;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRel(("DSound: Error retrieving capture device capabilities, hr=%Rhrc\n", hr));
+ rc = VERR_ACCESS_DENIED; /** @todo Fudge! */
+ }
+
+ IDirectSoundCapture_Release(pDSC);
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
+
+ return rc;
+}
+
+
+/**
+ * Queries information for @a pDevice and adds an entry to the enumeration.
+ *
+ * @returns VBox status code.
+ * @param pDevEnm The enumeration to add the device to.
+ * @param pDevice The device.
+ * @param enmType The type of device.
+ * @param fDefault Whether it's the default device.
+ */
+static int drvHostDSoundEnumNewStyleAdd(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pDevice, EDataFlow enmType, bool fDefault)
+{
+ int rc = VINF_SUCCESS; /* ignore most errors */
+
+ /*
+ * Gather the necessary properties.
+ */
+ IPropertyStore *pProperties = NULL;
+ HRESULT hrc = pDevice->OpenPropertyStore(STGM_READ, &pProperties);
+ if (SUCCEEDED(hrc))
+ {
+ /* Get the friendly name. */
+ PROPVARIANT VarName;
+ PropVariantInit(&VarName);
+ hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName);
+ if (SUCCEEDED(hrc))
+ {
+ /* Get the DirectSound GUID. */
+ PROPVARIANT VarGUID;
+ PropVariantInit(&VarGUID);
+ hrc = pProperties->GetValue(PKEY_AudioEndpoint_GUID, &VarGUID);
+ if (SUCCEEDED(hrc))
+ {
+ /* Get the device format. */
+ PROPVARIANT VarFormat;
+ PropVariantInit(&VarFormat);
+ hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat);
+ if (SUCCEEDED(hrc))
+ {
+ WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData;
+ AssertPtr(pFormat);
+
+ /*
+ * Create a enumeration entry for it.
+ */
+ size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1;
+ PDSOUNDDEV pDev = (PDSOUNDDEV)PDMAudioHostDevAlloc(sizeof(DSOUNDDEV), cbName, 0);
+ if (pDev)
+ {
+ pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN;
+ pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
+ if (fDefault)
+ pDev->Core.fFlags |= enmType == eRender
+ ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN;
+ if (enmType == eRender)
+ pDev->Core.cMaxOutputChannels = pFormat->nChannels;
+ else
+ pDev->Core.cMaxInputChannels = pFormat->nChannels;
+
+ //if (fDefault)
+ rc = RTUuidFromUtf16((PRTUUID)&pDev->Guid, VarGUID.pwszVal);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidToStr((PCRTUUID)&pDev->Guid, pDev->szGuid, sizeof(pDev->szGuid));
+ AssertRC(rc);
+ pDev->Core.pszId = &pDev->szGuid[0];
+
+ rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
+ if (RT_SUCCESS(rc))
+ PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
+ else
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ else
+ {
+ LogFunc(("RTUuidFromUtf16(%ls): %Rrc\n", VarGUID.pwszVal, rc));
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ PropVariantClear(&VarFormat);
+ }
+ else
+ LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc));
+ PropVariantClear(&VarGUID);
+ }
+ else
+ LogFunc(("Failed to get PKEY_AudioEndpoint_GUID: %Rhrc\n", hrc));
+ PropVariantClear(&VarName);
+ }
+ else
+ LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc));
+ pProperties->Release();
+ }
+ else
+ LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc));
+
+ if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc))
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Does a (Re-)enumeration of the host's playback + capturing devices.
+ *
+ * @return VBox status code.
+ * @param pDevEnm Where to store the enumerated devices.
+ */
+static int drvHostDSoundEnumerateDevices(PPDMAUDIOHOSTENUM pDevEnm)
+{
+ DSLOG(("DSound: Enumerating devices ...\n"));
+
+ /*
+ * Use the Vista+ API.
+ */
+ IMMDeviceEnumerator *pEnumerator;
+ HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator), (void **)&pEnumerator);
+ if (SUCCEEDED(hrc))
+ {
+ int rc = VINF_SUCCESS;
+ for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++)
+ {
+ EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture;
+
+ /* Get the default device first. */
+ IMMDevice *pDefaultDevice = NULL;
+ hrc = pEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pDefaultDevice);
+ if (SUCCEEDED(hrc))
+ rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDefaultDevice, enmType, true);
+ else
+ pDefaultDevice = NULL;
+
+ /* Enumerate the devices. */
+ IMMDeviceCollection *pCollection = NULL;
+ hrc = pEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection);
+ if (SUCCEEDED(hrc) && pCollection != NULL)
+ {
+ UINT cDevices = 0;
+ hrc = pCollection->GetCount(&cDevices);
+ if (SUCCEEDED(hrc))
+ {
+ for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++)
+ {
+ IMMDevice *pDevice = NULL;
+ hrc = pCollection->Item(idxDevice, &pDevice);
+ if (SUCCEEDED(hrc) && pDevice)
+ {
+ if (pDevice != pDefaultDevice)
+ rc = drvHostDSoundEnumNewStyleAdd(pDevEnm, pDevice, enmType, false);
+ pDevice->Release();
+ }
+ }
+ }
+ pCollection->Release();
+ }
+ else
+ LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc));
+
+ if (pDefaultDevice)
+ pDefaultDevice->Release();
+ }
+ pEnumerator->Release();
+ if (pDevEnm->cDevices > 0 || RT_FAILURE(rc))
+ {
+ DSLOG(("DSound: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc));
+ return rc;
+ }
+ }
+
+ /*
+ * Fall back to dsound.
+ */
+ /* Resolve symbols once. */
+ static PFNDIRECTSOUNDENUMERATEW volatile s_pfnDirectSoundEnumerateW = NULL;
+ static PFNDIRECTSOUNDCAPTUREENUMERATEW volatile s_pfnDirectSoundCaptureEnumerateW = NULL;
+
+ PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = s_pfnDirectSoundEnumerateW;
+ PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = s_pfnDirectSoundCaptureEnumerateW;
+ if (!pfnDirectSoundEnumerateW || !pfnDirectSoundCaptureEnumerateW)
+ {
+ RTLDRMOD hModDSound = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hModDSound);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hModDSound, "DirectSoundEnumerateW", (void **)&pfnDirectSoundEnumerateW);
+ if (RT_SUCCESS(rc))
+ s_pfnDirectSoundEnumerateW = pfnDirectSoundEnumerateW;
+ else
+ LogRel(("DSound: Failed to get dsound.dll export DirectSoundEnumerateW: %Rrc\n", rc));
+
+ rc = RTLdrGetSymbol(hModDSound, "DirectSoundCaptureEnumerateW", (void **)&pfnDirectSoundCaptureEnumerateW);
+ if (RT_SUCCESS(rc))
+ s_pfnDirectSoundCaptureEnumerateW = pfnDirectSoundCaptureEnumerateW;
+ else
+ LogRel(("DSound: Failed to get dsound.dll export DirectSoundCaptureEnumerateW: %Rrc\n", rc));
+ RTLdrClose(hModDSound);
+ }
+ else
+ LogRel(("DSound: Unable to load dsound.dll for enumerating devices: %Rrc\n", rc));
+ if (!pfnDirectSoundEnumerateW && !pfnDirectSoundCaptureEnumerateW)
+ return rc;
+ }
+
+ /* Common callback context for both playback and capture enumerations: */
+ DSOUNDENUMCBCTX EnumCtx;
+ EnumCtx.fFlags = 0;
+ EnumCtx.pDevEnm = pDevEnm;
+
+ /* Enumerate playback devices. */
+ if (pfnDirectSoundEnumerateW)
+ {
+ DSLOG(("DSound: Enumerating playback devices ...\n"));
+ HRESULT hr = pfnDirectSoundEnumerateW(&drvHostDSoundEnumOldStylePlaybackCallback, &EnumCtx);
+ if (FAILED(hr))
+ LogRel(("DSound: Error enumerating host playback devices: %Rhrc\n", hr));
+ }
+
+ /* Enumerate capture devices. */
+ if (pfnDirectSoundCaptureEnumerateW)
+ {
+ DSLOG(("DSound: Enumerating capture devices ...\n"));
+ HRESULT hr = pfnDirectSoundCaptureEnumerateW(&drvHostDSoundEnumOldStyleCaptureCallback, &EnumCtx);
+ if (FAILED(hr))
+ LogRel(("DSound: Error enumerating host capture devices: %Rhrc\n", hr));
+ }
+
+ /*
+ * Query Information for all enumerated devices.
+ * Note! This is problematic to do from the enumeration callbacks.
+ */
+ PDSOUNDDEV pDev;
+ RTListForEach(&pDevEnm->LstDevices, pDev, DSOUNDDEV, Core.ListEntry)
+ {
+ drvHostDSoundEnumOldStyleQueryDeviceInfo(pDev); /* ignore rc */
+ }
+
+ DSLOG(("DSound: Enumerating devices done\n"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
+
+ PDMAudioHostEnumInit(pDeviceEnum);
+ int rc = drvHostDSoundEnumerateDevices(pDeviceEnum);
+ if (RT_FAILURE(rc))
+ PDMAudioHostEnumDelete(pDeviceEnum);
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct.
+ *
+ * @param pCfg The PDM audio stream config to convert from.
+ * @param pFmt The windows structure to initialize.
+ */
+static void dsoundWaveFmtFromCfg(PCPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEXTENSIBLE pFmt)
+{
+ RT_ZERO(*pFmt);
+ pFmt->Format.wFormatTag = WAVE_FORMAT_PCM;
+ pFmt->Format.nChannels = PDMAudioPropsChannels(&pCfg->Props);
+ pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props);
+ pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(&pCfg->Props);
+ pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(&pCfg->Props);
+ pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(&pCfg->Props, PDMAudioPropsHz(&pCfg->Props));
+ pFmt->Format.cbSize = 0; /* No extra data specified. */
+
+ /*
+ * We need to use the extensible structure if there are more than two channels
+ * or if the channels have non-standard assignments.
+ */
+ if ( pFmt->Format.nChannels > 2
+ || ( pFmt->Format.nChannels == 1
+ ? pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_MONO
+ : pCfg->Props.aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT
+ || pCfg->Props.aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT))
+ {
+ pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format);
+ pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(&pCfg->Props);
+ pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ pFmt->dwChannelMask = 0;
+ unsigned const cSrcChannels = pFmt->Format.nChannels;
+ for (unsigned i = 0; i < cSrcChannels; i++)
+ if ( pCfg->Props.aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD
+ && pCfg->Props.aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD)
+ pFmt->dwChannelMask |= RT_BIT_32(pCfg->Props.aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD);
+ else
+ pFmt->Format.nChannels -= 1;
+ }
+}
+
+
+/**
+ * Resets the state of a DirectSound stream, clearing the buffer content.
+ *
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to reset state for.
+ */
+static void drvHostDSoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ RT_NOREF(pThis);
+ LogFunc(("Resetting %s\n", pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ /*
+ * Input streams.
+ */
+ LogFunc(("Resetting capture stream '%s'\n", pStreamDS->Cfg.szName));
+
+ /* Reset the state: */
+ pStreamDS->msLastTransfer = 0;
+/** @todo r=bird: We set the read position to zero here, but shouldn't we query it
+ * from the buffer instead given that there isn't any interface for repositioning
+ * to the start of the buffer as with playback buffers? */
+ pStreamDS->In.offReadPos = 0;
+ pStreamDS->In.cOverruns = 0;
+
+ /* Clear the buffer content: */
+ AssertPtr(pStreamDS->In.pDSCB);
+ if (pStreamDS->In.pDSCB)
+ {
+ PVOID pv1 = NULL;
+ DWORD cb1 = 0;
+ PVOID pv2 = NULL;
+ DWORD cb2 = 0;
+ HRESULT hrc = IDirectSoundCaptureBuffer8_Lock(pStreamDS->In.pDSCB, 0, pStreamDS->cbBufSize,
+ &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
+ if (SUCCEEDED(hrc))
+ {
+ PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
+ if (pv2 && cb2)
+ PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
+ hrc = IDirectSoundCaptureBuffer8_Unlock(pStreamDS->In.pDSCB, pv1, cb1, pv2, cb2);
+ if (FAILED(hrc))
+ LogRelMaxFunc(64, ("DSound: Unlocking capture buffer '%s' after reset failed: %Rhrc\n",
+ pStreamDS->Cfg.szName, hrc));
+ }
+ else
+ LogRelMaxFunc(64, ("DSound: Locking capture buffer '%s' for reset failed: %Rhrc\n",
+ pStreamDS->Cfg.szName, hrc));
+ }
+ }
+ else
+ {
+ /*
+ * Output streams.
+ */
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
+ LogFunc(("Resetting playback stream '%s'\n", pStreamDS->Cfg.szName));
+
+ /* If draining was enagaged, make sure dsound has stopped playing: */
+ if (pStreamDS->Out.fDrain && pStreamDS->Out.pDSB)
+ pStreamDS->Out.pDSB->Stop();
+
+ /* Reset the internal state: */
+ pStreamDS->msLastTransfer = 0;
+ pStreamDS->Out.fFirstTransfer = true;
+ pStreamDS->Out.fDrain = false;
+ pStreamDS->Out.cbLastTransferred = 0;
+ pStreamDS->Out.cbTransferred = 0;
+ pStreamDS->Out.cbWritten = 0;
+ pStreamDS->Out.offWritePos = 0;
+ pStreamDS->Out.offPlayCursorLastPending = 0;
+ pStreamDS->Out.offPlayCursorLastPlayed = 0;
+
+ /* Reset the buffer content and repositioning the buffer to the start of the buffer. */
+ AssertPtr(pStreamDS->Out.pDSB);
+ if (pStreamDS->Out.pDSB)
+ {
+ HRESULT hrc = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0);
+ if (FAILED(hrc))
+ LogRelMaxFunc(64, ("DSound: Failed to set buffer position for '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+
+ PVOID pv1 = NULL;
+ DWORD cb1 = 0;
+ PVOID pv2 = NULL;
+ DWORD cb2 = 0;
+ hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
+ if (hrc == DSERR_BUFFERLOST)
+ {
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+ hrc = IDirectSoundBuffer8_Lock(pStreamDS->Out.pDSB, 0, pStreamDS->cbBufSize, &pv1, &cb1, &pv2, &cb2, 0 /*fFlags*/);
+ }
+ if (SUCCEEDED(hrc))
+ {
+ PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
+ if (pv2 && cb2)
+ PDMAudioPropsClearBuffer(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
+
+ hrc = IDirectSoundBuffer8_Unlock(pStreamDS->Out.pDSB, pv1, cb1, pv2, cb2);
+ if (FAILED(hrc))
+ LogRelMaxFunc(64, ("DSound: Unlocking playback buffer '%s' after reset failed: %Rhrc\n",
+ pStreamDS->Cfg.szName, hrc));
+ }
+ else
+ LogRelMaxFunc(64, ("DSound: Locking playback buffer '%s' for reset failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ }
+ }
+}
+
+
+/**
+ * Worker for drvHostDSoundHA_StreamCreate that creates caputre stream.
+ *
+ * @returns Windows COM status code.
+ * @param pThis The DSound instance data.
+ * @param pStreamDS The stream instance data.
+ * @param pCfgReq The requested stream config (input).
+ * @param pCfgAcq Where to return the actual stream config. This is a
+ * copy of @a *pCfgReq when called.
+ * @param pWaveFmtExt On input the requested stream format. Updated to the
+ * actual stream format on successful return.
+ */
+static HRESULT drvHostDSoundStreamCreateCapture(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt)
+{
+ Assert(pStreamDS->In.pDSCB == NULL);
+ HRESULT hrc;
+
+ /*
+ * Create, initialize and set up a IDirectSoundCapture instance the first time
+ * we go thru here.
+ */
+ /** @todo bird: Or should we rather just throw this away after we've gotten the
+ * capture buffer? Old code would just leak it... */
+ if (pThis->pDSC == NULL)
+ {
+ hrc = drvHostDSoundCreateDSCaptureInstance(pThis->Cfg.pGuidCapture, &pThis->pDSC);
+ if (FAILED(hrc))
+ return hrc; /* The worker has complained to the release log already. */
+ }
+
+ /*
+ * Create the capture buffer.
+ */
+ DSCBUFFERDESC BufferDesc =
+ {
+ /*.dwSize = */ sizeof(BufferDesc),
+ /*.dwFlags = */ 0,
+ /*.dwBufferBytes =*/ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
+ /*.dwReserved = */ 0,
+ /*.lpwfxFormat = */ &pWaveFmtExt->Format,
+ /*.dwFXCount = */ 0,
+ /*.lpDSCFXDesc = */ NULL
+ };
+
+ LogRel2(("DSound: Requested capture buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes,
+ PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes)));
+
+ LPDIRECTSOUNDCAPTUREBUFFER pLegacyDSCB = NULL;
+ hrc = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &BufferDesc, &pLegacyDSCB, NULL);
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("DSound: Creating capture buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ return hrc;
+ }
+
+ /* Get the IDirectSoundCaptureBuffer8 version of the interface. */
+ hrc = IDirectSoundCaptureBuffer_QueryInterface(pLegacyDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB);
+ IDirectSoundCaptureBuffer_Release(pLegacyDSCB);
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("DSound: Querying IID_IDirectSoundCaptureBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ return hrc;
+ }
+
+ /*
+ * Query the actual stream configuration.
+ */
+#if 0 /** @todo r=bird: WTF was this for? */
+ DWORD offByteReadPos = 0;
+ hrc = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos);
+ if (FAILED(hrc))
+ {
+ offByteReadPos = 0;
+ DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr));
+ }
+#endif
+ RT_ZERO(*pWaveFmtExt);
+ hrc = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL);
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */
+
+ DSCBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0 };
+ hrc = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &BufferCaps);
+ if (SUCCEEDED(hrc))
+ {
+ LogRel2(("DSound: Acquired capture buffer capabilities for '%s':\n"
+ "DSound: dwFlags = %#RX32\n"
+ "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n"
+ "DSound: dwReserved = %#RX32\n",
+ pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes,
+ PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes), BufferCaps.dwReserved ));
+
+ /* Update buffer related stuff: */
+ pStreamDS->In.offReadPos = 0; /** @todo shouldn't we use offBytReadPos here to "read at the initial capture position"? */
+ pStreamDS->cbBufSize = BufferCaps.dwBufferBytes;
+ pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes);
+
+#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */
+ if (bc.dwBufferBytes & pStreamDS->uAlign)
+ DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n",
+ bc.dwBufferBytes, pStreamDS->uAlign + 1));
+#endif
+ LogFlow(("returns S_OK\n"));
+ return S_OK;
+ }
+ LogRelMax(64, ("DSound: Getting capture buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ }
+ else
+ LogRelMax(64, ("DSound: Getting capture format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+
+ IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
+ pStreamDS->In.pDSCB = NULL;
+ LogFlowFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/**
+ * Worker for drvHostDSoundHA_StreamCreate that creates playback stream.
+ *
+ * @returns Windows COM status code.
+ * @param pThis The DSound instance data.
+ * @param pStreamDS The stream instance data.
+ * @param pCfgReq The requested stream config (input).
+ * @param pCfgAcq Where to return the actual stream config. This is a
+ * copy of @a *pCfgReq when called.
+ * @param pWaveFmtExt On input the requested stream format.
+ * Updated to the actual stream format on successful
+ * return.
+ */
+static HRESULT drvHostDSoundStreamCreatePlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAMCFG pCfgAcq, WAVEFORMATEXTENSIBLE *pWaveFmtExt)
+{
+ Assert(pStreamDS->Out.pDSB == NULL);
+ HRESULT hrc;
+
+ /*
+ * Create, initialize and set up a DirectSound8 instance the first time
+ * we go thru here.
+ */
+ /** @todo bird: Or should we rather just throw this away after we've gotten the
+ * sound buffer? Old code would just leak it... */
+ if (pThis->pDS == NULL)
+ {
+ hrc = drvHostDSoundCreateDSPlaybackInstance(pThis->Cfg.pGuidPlay, &pThis->pDS);
+ if (FAILED(hrc))
+ return hrc; /* The worker has complained to the release log already. */
+ }
+
+ /*
+ * As we reuse our (secondary) buffer for playing out data as it comes in,
+ * we're using this buffer as a so-called streaming buffer.
+ *
+ * See https://msdn.microsoft.com/en-us/library/windows/desktop/ee419014(v=vs.85).aspx
+ *
+ * However, as we do not want to use memory on the sound device directly
+ * (as most modern audio hardware on the host doesn't have this anyway),
+ * we're *not* going to use DSBCAPS_STATIC for that.
+ *
+ * Instead we're specifying DSBCAPS_LOCSOFTWARE, as this fits the bill
+ * of copying own buffer data to our secondary's Direct Sound buffer.
+ */
+ DSBUFFERDESC BufferDesc =
+ {
+ /*.dwSize = */ sizeof(BufferDesc),
+ /*.dwFlags = */ DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE,
+ /*.dwBufferBytes = */ PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
+ /*.dwReserved = */ 0,
+ /*.lpwfxFormat = */ &pWaveFmtExt->Format,
+ /*.guid3DAlgorithm = {0, 0, 0, {0,0,0,0, 0,0,0,0}} */
+ };
+ LogRel2(("DSound: Requested playback buffer is %#x B / %u B / %RU64 ms\n", BufferDesc.dwBufferBytes, BufferDesc.dwBufferBytes,
+ PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferDesc.dwBufferBytes)));
+
+ LPDIRECTSOUNDBUFFER pLegacyDSB = NULL;
+ hrc = IDirectSound8_CreateSoundBuffer(pThis->pDS, &BufferDesc, &pLegacyDSB, NULL);
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("DSound: Creating playback sound buffer for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ return hrc;
+ }
+
+ /* Get the IDirectSoundBuffer8 version of the interface. */
+ hrc = IDirectSoundBuffer_QueryInterface(pLegacyDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB);
+ IDirectSoundBuffer_Release(pLegacyDSB);
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("DSound: Querying IID_IDirectSoundBuffer8 for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ return hrc;
+ }
+
+ /*
+ * Query the actual stream parameters, they may differ from what we requested.
+ */
+ RT_ZERO(*pWaveFmtExt);
+ hrc = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &pWaveFmtExt->Format, sizeof(*pWaveFmtExt), NULL);
+ if (SUCCEEDED(hrc))
+ {
+ /** @todo r=bird: We aren't converting/checking the pWaveFmtX content... */
+
+ DSBCAPS BufferCaps = { /*.dwSize = */ sizeof(BufferCaps), 0, 0, 0, 0 };
+ hrc = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &BufferCaps);
+ if (SUCCEEDED(hrc))
+ {
+ LogRel2(("DSound: Acquired playback buffer capabilities for '%s':\n"
+ "DSound: dwFlags = %#RX32\n"
+ "DSound: dwBufferBytes = %#RX32 B / %RU32 B / %RU64 ms\n"
+ "DSound: dwUnlockTransferRate = %RU32 KB/s\n"
+ "DSound: dwPlayCpuOverhead = %RU32%%\n",
+ pCfgReq->szName, BufferCaps.dwFlags, BufferCaps.dwBufferBytes, BufferCaps.dwBufferBytes,
+ PDMAudioPropsBytesToMilli(&pCfgReq->Props, BufferCaps.dwBufferBytes),
+ BufferCaps.dwUnlockTransferRate, BufferCaps.dwPlayCpuOverhead));
+
+ /* Update buffer related stuff: */
+ pStreamDS->cbBufSize = BufferCaps.dwBufferBytes;
+ pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, BufferCaps.dwBufferBytes);
+ pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 4; /* total fiction */
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+#if 0 /** @todo r=bird: uAlign isn't set anywhere, so this hasn't been checking anything for a while... */
+ if (bc.dwBufferBytes & pStreamDS->uAlign)
+ DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n",
+ bc.dwBufferBytes, pStreamDS->uAlign + 1));
+#endif
+ LogFlow(("returns S_OK\n"));
+ return S_OK;
+ }
+ LogRelMax(64, ("DSound: Getting playback buffer capabilities for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+ }
+ else
+ LogRelMax(64, ("DSound: Getting playback format for '%s' failed: %Rhrc\n", pCfgReq->szName, hrc));
+
+ IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
+ pStreamDS->Out.pDSB = NULL;
+ LogFlowFunc(("returns %Rhrc\n", hrc));
+ return hrc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
+
+ const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType);
+ LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName));
+ RTListInit(&pStreamDS->ListEntry); /* paranoia */
+
+ /* For whatever reason: */
+ dsoundUpdateStatusInternal(pThis);
+
+ /*
+ * DSound has different COM interfaces for working with input and output
+ * streams, so we'll quickly part ways here after some common format
+ * specification setup and logging.
+ */
+#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED)
+ char szTmp[64];
+#endif
+ LogRel2(("DSound: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType,
+ PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp))));
+
+ WAVEFORMATEXTENSIBLE WaveFmtExt;
+ dsoundWaveFmtFromCfg(pCfgReq, &WaveFmtExt);
+ LogRel2(("DSound: Requested %s format for '%s':\n"
+ "DSound: wFormatTag = %RU16\n"
+ "DSound: nChannels = %RU16\n"
+ "DSound: nSamplesPerSec = %RU32\n"
+ "DSound: nAvgBytesPerSec = %RU32\n"
+ "DSound: nBlockAlign = %RU16\n"
+ "DSound: wBitsPerSample = %RU16\n"
+ "DSound: cbSize = %RU16\n",
+ pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels,
+ WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign,
+ WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize));
+ if (WaveFmtExt.Format.cbSize != 0)
+ LogRel2(("DSound: dwChannelMask = %#RX32\n"
+ "DSound: wValidBitsPerSample = %RU16\n",
+ WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample));
+
+ HRESULT hrc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt);
+ else
+ hrc = drvHostDSoundStreamCreatePlayback(pThis, pStreamDS, pCfgReq, pCfgAcq, &WaveFmtExt);
+ int rc;
+ if (SUCCEEDED(hrc))
+ {
+ LogRel2(("DSound: Acquired %s format for '%s':\n"
+ "DSound: wFormatTag = %RU16\n"
+ "DSound: nChannels = %RU16\n"
+ "DSound: nSamplesPerSec = %RU32\n"
+ "DSound: nAvgBytesPerSec = %RU32\n"
+ "DSound: nBlockAlign = %RU16\n"
+ "DSound: wBitsPerSample = %RU16\n"
+ "DSound: cbSize = %RU16\n",
+ pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels,
+ WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign,
+ WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize));
+ if (WaveFmtExt.Format.cbSize != 0)
+ {
+ LogRel2(("DSound: dwChannelMask = %#RX32\n"
+ "DSound: wValidBitsPerSample = %RU16\n",
+ WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample));
+
+ /* Update the channel count and map here. */
+ PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels);
+ uint8_t idCh = 0;
+ for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++)
+ if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit))
+ {
+ pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit;
+ idCh++;
+ }
+ Assert(idCh == WaveFmtExt.Format.nChannels);
+ }
+
+ /*
+ * Copy the acquired config and reset the stream (clears the buffer).
+ */
+ PDMAudioStrmCfgCopy(&pStreamDS->Cfg, pCfgAcq);
+ drvHostDSoundStreamReset(pThis, pStreamDS);
+
+ RTCritSectEnter(&pThis->CritSect);
+ RTListAppend(&pThis->HeadStreams, &pStreamDS->ListEntry);
+ RTCritSectLeave(&pThis->CritSect);
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+ RT_NOREF(fImmediate);
+
+ RTCritSectEnter(&pThis->CritSect);
+ RTListNodeRemove(&pStreamDS->ListEntry);
+ RTCritSectLeave(&pThis->CritSect);
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ /*
+ * Input.
+ */
+ if (pStreamDS->In.pDSCB)
+ {
+ HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
+ if (FAILED(hrc))
+ LogFunc(("IDirectSoundCaptureBuffer_Stop failed: %Rhrc\n", hrc));
+
+ drvHostDSoundStreamReset(pThis, pStreamDS);
+
+ IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
+ pStreamDS->In.pDSCB = NULL;
+ }
+ }
+ else
+ {
+ /*
+ * Output.
+ */
+ if (pStreamDS->Out.pDSB)
+ {
+ drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
+
+ IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
+ pStreamDS->Out.pDSB = NULL;
+ }
+ }
+
+ if (RTCritSectIsInitialized(&pStreamDS->CritSect))
+ RTCritSectDelete(&pStreamDS->CritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvHostDSoundHA_StreamEnable and drvHostDSoundHA_StreamResume.
+ *
+ * This will try re-open the capture device if we're having trouble starting it.
+ *
+ * @returns VBox status code.
+ * @param pThis The DSound host audio driver instance data.
+ * @param pStreamDS The stream instance data.
+ */
+static int drvHostDSoundStreamCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ /*
+ * Check the stream status first.
+ */
+ int rc = VERR_AUDIO_STREAM_NOT_READY;
+ if (pStreamDS->In.pDSCB)
+ {
+ DWORD fStatus = 0;
+ HRESULT hrc = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &fStatus);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Try start capturing if it's not already doing so.
+ */
+ if (!(fStatus & DSCBSTATUS_CAPTURING))
+ {
+ LogRel2(("DSound: Starting capture on '%s' ... \n", pStreamDS->Cfg.szName));
+ hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING);
+ if (SUCCEEDED(hrc))
+ rc = VINF_SUCCESS;
+ else
+ {
+ /*
+ * Failed to start, try re-create the capture buffer.
+ */
+ LogRelMax(64, ("DSound: Starting to capture on '%s' failed: %Rhrc - will try re-open it ...\n",
+ pStreamDS->Cfg.szName, hrc));
+
+ IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
+ pStreamDS->In.pDSCB = NULL;
+
+ PDMAUDIOSTREAMCFG CfgReq = pStreamDS->Cfg;
+ PDMAUDIOSTREAMCFG CfgAcq = pStreamDS->Cfg;
+ WAVEFORMATEXTENSIBLE WaveFmtExt;
+ dsoundWaveFmtFromCfg(&pStreamDS->Cfg, &WaveFmtExt);
+ hrc = drvHostDSoundStreamCreateCapture(pThis, pStreamDS, &CfgReq, &CfgAcq, &WaveFmtExt);
+ if (SUCCEEDED(hrc))
+ {
+ PDMAudioStrmCfgCopy(&pStreamDS->Cfg, &CfgAcq);
+
+ /*
+ * Try starting capture again.
+ */
+ LogRel2(("DSound: Starting capture on re-opened '%s' ... \n", pStreamDS->Cfg.szName));
+ hrc = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, DSCBSTART_LOOPING);
+ if (SUCCEEDED(hrc))
+ rc = VINF_SUCCESS;
+ else
+ LogRelMax(64, ("DSound: Starting to capture on re-opened '%s' failed: %Rhrc\n",
+ pStreamDS->Cfg.szName, hrc));
+ }
+ else
+ LogRelMax(64, ("DSound: Re-opening '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ }
+ }
+ else
+ {
+ LogRel2(("DSound: Already capturing (%#x)\n", fStatus));
+ AssertFailed();
+ }
+ }
+ else
+ LogRelMax(64, ("DSound: Retrieving capture status for '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ }
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+
+ /*
+ * We always reset the buffer before enabling the stream (normally never necessary).
+ */
+ drvHostDSoundStreamReset(pThis, pStreamDS);
+ pStreamDS->fEnabled = true;
+
+ /*
+ * Input streams will start capturing, while output streams will only start
+ * playing once we get some audio data to play.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS);
+ else
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Worker for drvHostDSoundHA_StreamDestroy, drvHostDSoundHA_StreamDisable and
+ * drvHostDSoundHA_StreamPause.
+ *
+ * @returns VBox status code.
+ * @param pThis The DSound host audio driver instance data.
+ * @param pStreamDS The stream instance data.
+ * @param fReset Whether to reset the buffer and state.
+ */
+static int drvHostDSoundStreamStopPlayback(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fReset)
+{
+ if (!pStreamDS->Out.pDSB)
+ return VINF_SUCCESS;
+
+ LogRel2(("DSound: Stopping playback of '%s'...\n", pStreamDS->Cfg.szName));
+ HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (FAILED(hrc))
+ {
+ LogFunc(("IDirectSoundBuffer8_Stop -> %Rhrc; will attempt restoring the stream...\n", hrc));
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+ hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (FAILED(hrc))
+ LogRelMax(64, ("DSound: %s playback of '%s' failed: %Rhrc\n", fReset ? "Stopping" : "Pausing",
+ pStreamDS->Cfg.szName, hrc));
+ }
+ LogRel2(("DSound: Stopped playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+
+ if (fReset)
+ drvHostDSoundStreamReset(pThis, pStreamDS);
+ return SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_AUDIO_STREAM_NOT_READY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
+ pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+ /*
+ * Change the state.
+ */
+ pStreamDS->fEnabled = false;
+
+ /*
+ * Stop the stream and maybe reset the buffer.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ if (pStreamDS->In.pDSCB)
+ {
+ HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
+ if (SUCCEEDED(hrc))
+ LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName));
+ else
+ {
+ LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ /* Don't report errors up to the caller, as it might just be a capture device change. */
+ }
+
+ /* This isn't strictly speaking necessary since StreamEnable does it too... */
+ drvHostDSoundStreamReset(pThis, pStreamDS);
+ }
+ }
+ else
+ {
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
+ if (pStreamDS->Out.pDSB)
+ {
+ rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
+ if (RT_SUCCESS(rc))
+ LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ *
+ * @note Basically the same as drvHostDSoundHA_StreamDisable, just w/o the
+ * buffer resetting and fEnabled change.
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
+ pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+ /*
+ * Stop the stream and maybe reset the buffer.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ if (pStreamDS->In.pDSCB)
+ {
+ HRESULT hrc = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
+ if (SUCCEEDED(hrc))
+ LogRel3(("DSound: Stopped capture on '%s'.\n", pStreamDS->Cfg.szName));
+ else
+ {
+ LogRelMax(64, ("DSound: Stopping capture on '%s' failed: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ /* Don't report errors up to the caller, as it might just be a capture device change. */
+ }
+ }
+ }
+ else
+ {
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
+ if (pStreamDS->Out.pDSB)
+ {
+ /* Don't stop draining buffers, we won't be resuming them right.
+ They'll stop by themselves anyway. */
+ if (pStreamDS->Out.fDrain)
+ LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName));
+ else
+ {
+ rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, false /*fReset*/);
+ if (RT_SUCCESS(rc))
+ LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
+ }
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
+ return rc;
+}
+
+
+/**
+ * Worker for drvHostDSoundHA_StreamResume and drvHostDSoundHA_StreamPlay that
+ * starts playing the DirectSound Buffer.
+ *
+ * @returns VBox status code.
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to start playing.
+ */
+static int directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ if (!pStreamDS->Out.pDSB)
+ return VERR_AUDIO_STREAM_NOT_READY;
+
+ LogRel2(("DSound: Starting playback of '%s' ...\n", pStreamDS->Cfg.szName));
+ HRESULT hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+
+ for (unsigned i = 0; hrc == DSERR_BUFFERLOST && i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
+ {
+ LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n"));
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+
+ hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, DSCBSTART_LOOPING);
+ if (SUCCEEDED(hrc))
+ return VINF_SUCCESS;
+ }
+
+ LogRelMax(64, ("DSound: Failed to start playback of '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ return VERR_AUDIO_STREAM_NOT_READY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+
+ /*
+ * Input streams will start capturing, while output streams will only start
+ * playing if we're past the pre-buffering state.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ rc = drvHostDSoundStreamCaptureStart(pThis, pStreamDS);
+ else
+ {
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT);
+ if (!pStreamDS->Out.fFirstTransfer)
+ rc = directSoundPlayStart(pThis, pStreamDS);
+ }
+
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertReturn(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamDS->msLastTransfer ? RTTimeMilliTS() - pStreamDS->msLastTransfer : -1,
+ pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+ /*
+ * We've started the buffer in looping mode, try switch to non-looping...
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamDS->Out.pDSB && !pStreamDS->Out.fDrain)
+ {
+ LogRel2(("DSound: Switching playback stream '%s' to drain mode...\n", pStreamDS->Cfg.szName));
+ HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, 0);
+ if (SUCCEEDED(hrc))
+ {
+ uint64_t const msNow = RTTimeMilliTS();
+ pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props, pStreamDS->cbBufSize) + msNow;
+ pStreamDS->Out.fDrain = true;
+ }
+ else
+ LogRelMax(64, ("DSound: Failed to restart '%s' in drain mode: %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ }
+ else
+ {
+ Log2Func(("drain: IDirectSoundBuffer8_Stop failed: %Rhrc\n", hrc));
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+
+ HRESULT hrc2 = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (SUCCEEDED(hrc2))
+ LogFunc(("Successfully stopped the stream after restoring it. (hrc=%Rhrc)\n", hrc));
+ else
+ {
+ LogRelMax(64, ("DSound: Failed to stop playback stream '%s' for putting into drain mode: %Rhrc (initial), %Rhrc (after restore)\n",
+ pStreamDS->Cfg.szName, hrc, hrc2));
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ }
+ }
+ }
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostDSoundStreamStatusString(pStreamDS)));
+ return rc;
+}
+
+
+/**
+ * Retrieves the number of free bytes available for writing to a DirectSound output stream.
+ *
+ * @return VBox status code. VERR_NOT_AVAILABLE if unable to determine or the
+ * buffer was not recoverable.
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS DirectSound output stream to retrieve number for.
+ * @param pdwFree Where to return the free amount on success.
+ * @param poffPlayCursor Where to return the play cursor offset.
+ */
+static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree, DWORD *poffPlayCursor)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
+ AssertPtrReturn(pdwFree, VERR_INVALID_POINTER);
+ AssertPtr(poffPlayCursor);
+
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */
+
+ LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
+ AssertPtrReturn(pDSB, VERR_INVALID_POINTER);
+
+ HRESULT hr = S_OK;
+
+ /* Get the current play position which is used for calculating the free space in the buffer. */
+ for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
+ {
+ DWORD offPlayCursor = 0;
+ DWORD offWriteCursor = 0;
+ hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor);
+ if (SUCCEEDED(hr))
+ {
+ int32_t cbDiff = offWriteCursor - offPlayCursor;
+ if (cbDiff < 0)
+ cbDiff += pStreamDS->cbBufSize;
+
+ int32_t cbFree = offPlayCursor - pStreamDS->Out.offWritePos;
+ if (cbFree < 0)
+ cbFree += pStreamDS->cbBufSize;
+
+ if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff)
+ {
+ /** @todo count/log these. */
+ pStreamDS->Out.offWritePos = offWriteCursor;
+ cbFree = pStreamDS->cbBufSize - cbDiff;
+ }
+
+ /* When starting to use a DirectSound buffer, offPlayCursor and offWriteCursor
+ * both point at position 0, so we won't be able to detect how many bytes
+ * are writable that way.
+ *
+ * So use our per-stream written indicator to see if we just started a stream. */
+ if (pStreamDS->Out.cbWritten == 0)
+ cbFree = pStreamDS->cbBufSize;
+
+ LogRel4(("DSound: offPlayCursor=%RU32, offWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n",
+ offPlayCursor, offWriteCursor, pStreamDS->Out.offWritePos, cbFree));
+
+ *pdwFree = cbFree;
+ *poffPlayCursor = offPlayCursor;
+ return VINF_SUCCESS;
+ }
+
+ if (hr != DSERR_BUFFERLOST) /** @todo MSDN doesn't state this error for GetCurrentPosition(). */
+ break;
+
+ LogFunc(("Getting playing position failed due to lost buffer, restoring ...\n"));
+
+ directSoundPlayRestore(pThis, pDSB);
+ }
+
+ if (hr != DSERR_BUFFERLOST) /* Avoid log flooding if the error is still there. */
+ DSLOGREL(("DSound: Getting current playback position failed with %Rhrc\n", hr));
+
+ LogFunc(("Failed with %Rhrc\n", hr));
+
+ *poffPlayCursor = pStreamDS->cbBufSize;
+ return VERR_NOT_AVAILABLE;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostDSoundHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ if ( pStreamDS->Cfg.enmDir != PDMAUDIODIR_OUT
+ || !pStreamDS->Out.fDrain)
+ {
+ LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ }
+ LogFlowFunc(("returns DRAINING for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+ return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+}
+
+#if 0 /* This isn't working as the write cursor is more a function of time than what we do.
+ Previously we only reported the pre-buffering status anyway, so no harm. */
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, 0);
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ /* This is a similar calculation as for StreamGetReadable, only for an output buffer. */
+ AssertPtr(pStreamDS->In.pDSCB);
+ DWORD offPlayCursor = 0;
+ DWORD offWriteCursor = 0;
+ HRESULT hrc = IDirectSoundBuffer8_GetCurrentPosition(pStreamDS->Out.pDSB, &offPlayCursor, &offWriteCursor);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t cbPending = dsoundRingDistance(offWriteCursor, offPlayCursor, pStreamDS->cbBufSize);
+ Log3Func(("cbPending=%RU32\n", cbPending));
+ return cbPending;
+ }
+ AssertMsgFailed(("hrc=%Rhrc\n", hrc));
+ }
+ /* else: For input streams we never have any pending data. */
+
+ return 0;
+}
+#endif
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, 0);
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+
+ DWORD cbFree = 0;
+ DWORD offIgn = 0;
+ int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbFree, &offIgn);
+ AssertRCReturn(rc, 0);
+
+ return cbFree;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, 0);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+
+ if (pStreamDS->fEnabled)
+ AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
+ else
+ {
+ Log2Func(("Skipping disabled stream {%s}\n", drvHostDSoundStreamStatusString(pStreamDS)));
+ return VINF_SUCCESS;
+ }
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+/** @todo Any condition under which we should call dsoundUpdateStatusInternal(pThis) here?
+ * The old code thought it did so in case of failure, only it couldn't ever fails, so it never did. */
+
+ /*
+ * Transfer loop.
+ */
+ uint32_t cbWritten = 0;
+ while (cbBuf > 0)
+ {
+ /*
+ * Figure out how much we can possibly write.
+ */
+ DWORD offPlayCursor = 0;
+ DWORD cbWritable = 0;
+ int rc = dsoundGetFreeOut(pThis, pStreamDS, &cbWritable, &offPlayCursor);
+ AssertRCReturn(rc, rc);
+ if (cbWritable < pStreamDS->Cfg.Props.cbFrame)
+ break;
+
+ uint32_t const cbToWrite = RT_MIN(cbWritable, cbBuf);
+ Log3Func(("offPlay=%#x offWritePos=%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offPlayCursor, pStreamDS->Out.offWritePos,
+ cbWritable, cbToWrite, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+ /*
+ * Lock that amount of buffer.
+ */
+ PVOID pv1 = NULL;
+ DWORD cb1 = 0;
+ PVOID pv2 = NULL;
+ DWORD cb2 = 0;
+ HRESULT hrc = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, cbToWrite,
+ &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
+ //AssertMsg(cb1 + cb2 == cbToWrite, ("%#x + %#x vs %#x\n", cb1, cb2, cbToWrite));
+
+ /*
+ * Copy over the data.
+ */
+ memcpy(pv1, pvBuf, cb1);
+ pvBuf = (uint8_t *)pvBuf + cb1;
+ cbBuf -= cb1;
+ cbWritten += cb1;
+
+ if (pv2)
+ {
+ memcpy(pv2, pvBuf, cb2);
+ pvBuf = (uint8_t *)pvBuf + cb2;
+ cbBuf -= cb2;
+ cbWritten += cb2;
+ }
+
+ /*
+ * Unlock and update the write position.
+ */
+ directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2); /** @todo r=bird: pThis + pDSB parameters here for Unlock, but only pThis for Lock. Why? */
+ pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
+
+ /*
+ * If this was the first chunk, kick off playing.
+ */
+ if (!pStreamDS->Out.fFirstTransfer)
+ { /* likely */ }
+ else
+ {
+ *pcbWritten = cbWritten;
+ rc = directSoundPlayStart(pThis, pStreamDS);
+ AssertRCReturn(rc, rc);
+ pStreamDS->Out.fFirstTransfer = false;
+ }
+ }
+
+ /*
+ * Done.
+ */
+ *pcbWritten = cbWritten;
+
+ pStreamDS->Out.cbTransferred += cbWritten;
+ if (cbWritten)
+ {
+ uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev);
+ pStreamDS->Out.cbLastTransferred = cbWritten;
+ pStreamDS->msLastTransfer = RTTimeMilliTS();
+ LogFlowFunc(("cbLastTransferred=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
+ cbWritten, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
+ drvHostDSoundStreamStatusString(pStreamDS) ));
+ }
+ else if ( pStreamDS->Out.fDrain
+ && RTTimeMilliTS() >= pStreamDS->Out.msDrainDeadline)
+ {
+ LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+ if (pStreamDS->Out.pDSB)
+ {
+ HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (FAILED(hrc))
+ LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+ }
+ pStreamDS->Out.fDrain = false;
+ pStreamDS->fEnabled = false;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio); */ RT_NOREF(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, 0);
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN);
+
+ if (pStreamDS->fEnabled)
+ {
+ /* This is the same calculation as for StreamGetPending. */
+ AssertPtr(pStreamDS->In.pDSCB);
+ DWORD offCaptureCursor = 0;
+ DWORD offReadCursor = 0;
+ HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
+ if (SUCCEEDED(hrc))
+ {
+ uint32_t cbPending = dsoundRingDistance(offCaptureCursor, offReadCursor, pStreamDS->cbBufSize);
+ Log3Func(("cbPending=%RU32\n", cbPending));
+ return cbPending;
+ }
+ AssertMsgFailed(("hrc=%Rhrc\n", hrc));
+ }
+
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostDSoundHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ /*PDRVHOSTDSOUND pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDSOUND, IHostAudio);*/ RT_NOREF(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+ AssertPtrReturn(pStreamDS, 0);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+
+#if 0 /** @todo r=bird: shouldn't we do the same check as for output streams? */
+ if (pStreamDS->fEnabled)
+ AssertReturn(pStreamDS->cbBufSize, VERR_INTERNAL_ERROR_2);
+ else
+ {
+ Log2Func(("Stream disabled, skipping\n"));
+ return VINF_SUCCESS;
+ }
+#endif
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS) ));
+
+ /*
+ * Read loop.
+ */
+ uint32_t cbRead = 0;
+ while (cbBuf > 0)
+ {
+ /*
+ * Figure out how much we can read.
+ */
+ DWORD offCaptureCursor = 0;
+ DWORD offReadCursor = 0;
+ HRESULT hrc = IDirectSoundCaptureBuffer_GetCurrentPosition(pStreamDS->In.pDSCB, &offCaptureCursor, &offReadCursor);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
+ //AssertMsg(offReadCursor == pStreamDS->In.offReadPos, ("%#x %#x\n", offReadCursor, pStreamDS->In.offReadPos));
+
+ uint32_t const cbReadable = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
+
+ if (cbReadable >= pStreamDS->Cfg.Props.cbFrame)
+ { /* likely */ }
+ else
+ {
+ if (cbRead > 0)
+ { /* likely */ }
+ else if (pStreamDS->In.cOverruns < 32)
+ {
+ pStreamDS->In.cOverruns++;
+ DSLOG(("DSound: Warning: Buffer full (size is %zu bytes), skipping to record data (overflow #%RU32)\n",
+ pStreamDS->cbBufSize, pStreamDS->In.cOverruns));
+ }
+ break;
+ }
+
+ uint32_t const cbToRead = RT_MIN(cbReadable, cbBuf);
+ Log3Func(("offCapture=%#x offRead=%#x/%#x -> cbWritable=%#x cbToWrite=%#x {%s}\n", offCaptureCursor, offReadCursor,
+ pStreamDS->In.offReadPos, cbReadable, cbToRead, drvHostDSoundStreamStatusString(pStreamDS)));
+
+ /*
+ * Lock that amount of buffer.
+ */
+ PVOID pv1 = NULL;
+ DWORD cb1 = 0;
+ PVOID pv2 = NULL;
+ DWORD cb2 = 0;
+ hrc = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, cbToRead, &pv1, &pv2, &cb1, &cb2, 0 /*dwFlags*/);
+ AssertMsgReturn(SUCCEEDED(hrc), ("%Rhrc\n", hrc), VERR_ACCESS_DENIED); /** @todo translate these status codes already! */
+ AssertMsg(cb1 + cb2 == cbToRead, ("%#x + %#x vs %#x\n", cb1, cb2, cbToRead));
+
+ /*
+ * Copy over the data.
+ */
+ memcpy(pvBuf, pv1, cb1);
+ pvBuf = (uint8_t *)pvBuf + cb1;
+ cbBuf -= cb1;
+ cbRead += cb1;
+
+ if (pv2)
+ {
+ memcpy(pvBuf, pv2, cb2);
+ pvBuf = (uint8_t *)pvBuf + cb2;
+ cbBuf -= cb2;
+ cbRead += cb2;
+ }
+
+ /*
+ * Unlock and update the write position.
+ */
+ directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2); /** @todo r=bird: pDSB parameter here for Unlock, but pStreamDS for Lock. Why? */
+ pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
+ }
+
+ /*
+ * Done.
+ */
+ *pcbRead = cbRead;
+ if (cbRead)
+ {
+ uint64_t const msPrev = pStreamDS->msLastTransfer; RT_NOREF(msPrev);
+ pStreamDS->msLastTransfer = RTTimeMilliTS();
+ LogFlowFunc(("cbRead=%RU32, msLastTransfer=%RU64 msNow=%RU64 cMsDelta=%RU64 {%s}\n",
+ cbRead, msPrev, pStreamDS->msLastTransfer, msPrev ? pStreamDS->msLastTransfer - msPrev : 0,
+ drvHostDSoundStreamStatusString(pStreamDS) ));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVINS::IBase Interface *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostDSoundQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG Interface *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
+ */
+static DECLCALLBACK(void) drvHostDSoundDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ LogFlowFuncEnter();
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ if (pThis->m_pNotificationClient)
+ {
+ pThis->m_pNotificationClient->Unregister();
+ pThis->m_pNotificationClient->Release();
+
+ pThis->m_pNotificationClient = NULL;
+ }
+#endif
+
+ PDMAudioHostEnumDelete(&pThis->DeviceEnum);
+
+ int rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFuncLeave();
+}
+
+
+static LPCGUID dsoundConfigQueryGUID(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
+{
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ LPCGUID pGuid = NULL;
+
+ char *pszGuid = NULL;
+ int rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, pszName, &pszGuid);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTUuidFromStr(pUuid, pszGuid);
+ if (RT_SUCCESS(rc))
+ pGuid = (LPCGUID)pUuid;
+ else
+ DSLOGREL(("DSound: Error parsing device GUID for device '%s': %Rrc\n", pszName, rc));
+
+ RTStrFree(pszGuid);
+ }
+
+ return pGuid;
+}
+
+
+static void dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
+{
+ pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pThis->pDrvIns, pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
+ pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pThis->pDrvIns, pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
+
+ DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
+ &pThis->Cfg.uuidPlay,
+ &pThis->Cfg.uuidCapture));
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT,
+ * Construct a DirectSound Audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
+ RT_NOREF(fFlags);
+ LogRel(("Audio: Initializing DirectSound audio driver\n"));
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ RTListInit(&pThis->HeadStreams);
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHostDSoundHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = drvHostDSoundHA_GetDevices;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvHostDSoundHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHostDSoundHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHostDSoundHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHostDSoundHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHostDSoundHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHostDSoundHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHostDSoundHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHostDSoundHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvHostDSoundHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHostDSoundHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHostDSoundHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHostDSoundHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHostDSoundHA_StreamCapture;
+
+ /*
+ * Init the static parts.
+ */
+ PDMAudioHostEnumInit(&pThis->DeviceEnum);
+
+ pThis->fEnabledIn = false;
+ pThis->fEnabledOut = false;
+
+ /*
+ * Verify that IDirectSound is available.
+ */
+ LPDIRECTSOUND pDirectSound = NULL;
+ HRESULT hrc = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
+ if (SUCCEEDED(hrc))
+ IDirectSound_Release(pDirectSound);
+ else
+ {
+ LogRel(("DSound: DirectSound not available: %Rhrc\n", hrc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ /*
+ * Set up WASAPI device change notifications (Vista+).
+ */
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0))
+ {
+ /* Get the notification interface (from DrvAudio). */
+# ifdef VBOX_WITH_AUDIO_CALLBACKS
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ Assert(pIHostAudioPort);
+# else
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
+# endif
+#ifdef RT_EXCEPTIONS_ENABLED
+ try
+#endif
+ {
+ pThis->m_pNotificationClient = new DrvHostAudioDSoundMMNotifClient(pIHostAudioPort,
+ pThis->Cfg.pGuidCapture == NULL,
+ pThis->Cfg.pGuidPlay == NULL);
+ }
+#ifdef RT_EXCEPTIONS_ENABLED
+ catch (std::bad_alloc &)
+ {
+ return VERR_NO_MEMORY;
+ }
+#else
+ AssertReturn(pThis->m_pNotificationClient, VERR_NO_MEMORY);
+#endif
+
+ hrc = pThis->m_pNotificationClient->Initialize();
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pThis->m_pNotificationClient->Register();
+ if (SUCCEEDED(hrc))
+ LogRel2(("DSound: Notification client is enabled (ver %#RX64)\n", RTSystemGetNtVersion()));
+ else
+ {
+ LogRel(("DSound: Notification client registration failed: %Rhrc\n", hrc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ }
+ else
+ {
+ LogRel(("DSound: Notification client initialization failed: %Rhrc\n", hrc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ }
+ else
+ LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
+#endif
+
+ /*
+ * Initialize configuration values and critical section.
+ */
+ dsoundConfigInit(pThis, pCfg);
+ return RTCritSectInit(&pThis->CritSect);
+}
+
+
+/**
+ * PDM driver registration.
+ */
+const PDMDRVREG g_DrvHostDSound =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "DSoundAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "DirectSound Audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTDSOUND),
+ /* pfnConstruct */
+ drvHostDSoundConstruct,
+ /* pfnDestruct */
+ drvHostDSoundDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp b/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp
new file mode 100644
index 00000000..2725538a
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.cpp
@@ -0,0 +1,237 @@
+/* $Id: DrvHostAudioDSoundMMNotifClient.cpp $ */
+/** @file
+ * Host audio driver - DSound - Implementation of the IMMNotificationClient interface to detect audio endpoint changes.
+ */
+
+/*
+ * Copyright (C) 2017-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
+ */
+
+#include "DrvHostAudioDSoundMMNotifClient.h"
+
+#include <iprt/win/windows.h>
+#include <mmdeviceapi.h>
+#include <iprt/win/endpointvolume.h>
+#include <iprt/errcore.h>
+
+#ifdef LOG_GROUP /** @todo r=bird: wtf? Put it before all other includes like you're supposed to. */
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+
+
+DrvHostAudioDSoundMMNotifClient::DrvHostAudioDSoundMMNotifClient(PPDMIHOSTAUDIOPORT pInterface, bool fDefaultIn, bool fDefaultOut)
+ : m_fDefaultIn(fDefaultIn)
+ , m_fDefaultOut(fDefaultOut)
+ , m_fRegisteredClient(false)
+ , m_cRef(1)
+ , m_pIAudioNotifyFromHost(pInterface)
+{
+}
+
+DrvHostAudioDSoundMMNotifClient::~DrvHostAudioDSoundMMNotifClient(void)
+{
+}
+
+/**
+ * Registers the mulitmedia notification client implementation.
+ */
+HRESULT DrvHostAudioDSoundMMNotifClient::Register(void)
+{
+ HRESULT hr = m_pEnum->RegisterEndpointNotificationCallback(this);
+ if (SUCCEEDED(hr))
+ m_fRegisteredClient = true;
+
+ return hr;
+}
+
+/**
+ * Unregisters the mulitmedia notification client implementation.
+ */
+void DrvHostAudioDSoundMMNotifClient::Unregister(void)
+{
+ if (m_fRegisteredClient)
+ {
+ m_pEnum->UnregisterEndpointNotificationCallback(this);
+
+ m_fRegisteredClient = false;
+ }
+}
+
+/**
+ * Initializes the mulitmedia notification client implementation.
+ *
+ * @return HRESULT
+ */
+HRESULT DrvHostAudioDSoundMMNotifClient::Initialize(void)
+{
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
+ (void **)&m_pEnum);
+
+ LogFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Handler implementation which is called when an audio device state
+ * has been changed.
+ *
+ * @return HRESULT
+ * @param pwstrDeviceId Device ID the state is announced for.
+ * @param dwNewState New state the device is now in.
+ */
+STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
+{
+ char *pszState = "unknown";
+
+ switch (dwNewState)
+ {
+ case DEVICE_STATE_ACTIVE:
+ pszState = "active";
+ break;
+ case DEVICE_STATE_DISABLED:
+ pszState = "disabled";
+ break;
+ case DEVICE_STATE_NOTPRESENT:
+ pszState = "not present";
+ break;
+ case DEVICE_STATE_UNPLUGGED:
+ pszState = "unplugged";
+ break;
+ default:
+ break;
+ }
+
+ LogRel(("Audio: Device '%ls' has changed state to '%s'\n", pwstrDeviceId, pszState));
+
+ if (m_pIAudioNotifyFromHost)
+ m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost);
+
+ return S_OK;
+}
+
+/**
+ * Handler implementation which is called when a new audio device has been added.
+ *
+ * @return HRESULT
+ * @param pwstrDeviceId Device ID which has been added.
+ */
+STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceAdded(LPCWSTR pwstrDeviceId)
+{
+ LogRel(("Audio: Device '%ls' has been added\n", pwstrDeviceId));
+ /* Note! It is hard to properly support non-default devices when the backend is DSound,
+ as DSound talks GUID where-as the pwszDeviceId string we get here is something
+ completely different. So, ignorining that edge case here. The WasApi backend
+ supports this, though. */
+ if (m_pIAudioNotifyFromHost)
+ m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost);
+ return S_OK;
+}
+
+/**
+ * Handler implementation which is called when an audio device has been removed.
+ *
+ * @return HRESULT
+ * @param pwstrDeviceId Device ID which has been removed.
+ */
+STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId)
+{
+ LogRel(("Audio: Device '%ls' has been removed\n", pwstrDeviceId));
+ if (m_pIAudioNotifyFromHost)
+ m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost);
+ return S_OK;
+}
+
+/**
+ * Handler implementation which is called when the device audio device has been
+ * changed.
+ *
+ * @return HRESULT
+ * @param eFlow Flow direction of the new default device.
+ * @param eRole Role of the new default device.
+ * @param pwstrDefaultDeviceId ID of the new default device.
+ */
+STDMETHODIMP DrvHostAudioDSoundMMNotifClient::OnDefaultDeviceChanged(EDataFlow eFlow, ERole eRole, LPCWSTR pwstrDefaultDeviceId)
+{
+ /* When the user triggers a default device change, we'll typically get two or
+ three notifications. Just pick up the one for the multimedia role for now
+ (dunno if DSound default equals eMultimedia or eConsole, and whether it make
+ any actual difference). */
+ if (eRole == eMultimedia)
+ {
+ PDMAUDIODIR enmDir = PDMAUDIODIR_INVALID;
+ char *pszRole = "unknown";
+ if (eFlow == eRender)
+ {
+ pszRole = "output";
+ if (m_fDefaultOut)
+ enmDir = PDMAUDIODIR_OUT;
+ }
+ else if (eFlow == eCapture)
+ {
+ pszRole = "input";
+ if (m_fDefaultIn)
+ enmDir = PDMAUDIODIR_IN;
+ }
+
+ LogRel(("Audio: Default %s device has been changed to '%ls'\n", pszRole, pwstrDefaultDeviceId));
+
+ if (m_pIAudioNotifyFromHost)
+ {
+ if (enmDir != PDMAUDIODIR_INVALID)
+ m_pIAudioNotifyFromHost->pfnNotifyDeviceChanged(m_pIAudioNotifyFromHost, enmDir, NULL);
+ m_pIAudioNotifyFromHost->pfnNotifyDevicesChanged(m_pIAudioNotifyFromHost);
+ }
+ }
+ return S_OK;
+}
+
+STDMETHODIMP DrvHostAudioDSoundMMNotifClient::QueryInterface(REFIID interfaceID, void **ppvInterface)
+{
+ const IID MY_IID_IMMNotificationClient = __uuidof(IMMNotificationClient);
+
+ if ( IsEqualIID(interfaceID, IID_IUnknown)
+ || IsEqualIID(interfaceID, MY_IID_IMMNotificationClient))
+ {
+ *ppvInterface = static_cast<IMMNotificationClient*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) DrvHostAudioDSoundMMNotifClient::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) DrvHostAudioDSoundMMNotifClient::Release(void)
+{
+ long lRef = InterlockedDecrement(&m_cRef);
+ if (lRef == 0)
+ delete this;
+
+ return lRef;
+}
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h b/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h
new file mode 100644
index 00000000..6c0ccc3b
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioDSoundMMNotifClient.h
@@ -0,0 +1,87 @@
+/* $Id: DrvHostAudioDSoundMMNotifClient.h $ */
+/** @file
+ * Host audio driver - DSound - Implementation of the IMMNotificationClient interface to detect audio endpoint changes.
+ */
+
+/*
+ * Copyright (C) 2017-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h
+#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/critsect.h>
+#include <iprt/win/windows.h>
+#include <mmdeviceapi.h>
+
+#include <VBox/vmm/pdmaudioifs.h>
+
+
+class DrvHostAudioDSoundMMNotifClient : public IMMNotificationClient
+{
+public:
+
+ DrvHostAudioDSoundMMNotifClient(PPDMIHOSTAUDIOPORT pInterface, bool fDefaultIn, bool fDefaultOut);
+ virtual ~DrvHostAudioDSoundMMNotifClient();
+
+ HRESULT Initialize();
+
+ HRESULT Register(void);
+ void Unregister(void);
+
+ /** @name IUnknown interface
+ * @{ */
+ IFACEMETHODIMP_(ULONG) Release();
+ /** @} */
+
+private:
+
+ bool m_fDefaultIn;
+ bool m_fDefaultOut;
+ bool m_fRegisteredClient;
+ IMMDeviceEnumerator *m_pEnum;
+ IMMDevice *m_pEndpoint;
+
+ long m_cRef;
+
+ PPDMIHOSTAUDIOPORT m_pIAudioNotifyFromHost;
+
+ /** @name IMMNotificationClient interface
+ * @{ */
+ IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState);
+ IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId);
+ IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId);
+ IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId);
+ IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) { return S_OK; }
+ /** @} */
+
+ /** @name IUnknown interface
+ * @{ */
+ IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk);
+ IFACEMETHODIMP_(ULONG) AddRef();
+ /** @} */
+};
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioDSoundMMNotifClient_h */
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp b/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp
new file mode 100644
index 00000000..b0c53f14
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioDebug.cpp
@@ -0,0 +1,409 @@
+/* $Id: DrvHostAudioDebug.cpp $ */
+/** @file
+ * Host audio driver - Debug - 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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+
+#include "AudioHlp.h"
+#include "AudioTest.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug host audio stream.
+ */
+typedef struct DRVHSTAUDDEBUGSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** Audio file to dump output to or read input from. */
+ PAUDIOHLPFILE pFile;
+ union
+ {
+ AUDIOTESTTONE In;
+ };
+} DRVHSTAUDDEBUGSTREAM;
+/** Pointer to a debug host audio stream. */
+typedef DRVHSTAUDDEBUGSTREAM *PDRVHSTAUDDEBUGSTREAM;
+
+/**
+ * Debug audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHSTAUDDEBUG
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVHSTAUDDEBUG;
+/** Pointer to a debug host audio driver. */
+typedef DRVHSTAUDDEBUG *PDRVHSTAUDDEBUG;
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "DebugAudio");
+ pBackendCfg->cbStream = sizeof(DRVHSTAUDDEBUGSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsOut = 1; /* Output; writing to a file. */
+ pBackendCfg->cMaxStreamsIn = 1; /* Input; generates a sine wave. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudDebugHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHSTAUDDEBUG pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDDEBUG, IHostAudio);
+ PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream;
+ AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDMAudioStrmCfgCopy(&pStreamDbg->Cfg, pCfgAcq);
+
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ AudioTestToneInitRandom(&pStreamDbg->In, &pStreamDbg->Cfg.Props);
+
+ int rc = AudioHlpFileCreateAndOpenEx(&pStreamDbg->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 ? "DebugAudioIn" : "DebugAudioOut");
+ if (RT_FAILURE(rc))
+ LogRel(("DebugAudio: Failed to creating debug file for %s stream '%s' in the temp directory: %Rrc\n",
+ pCfgReq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pCfgReq->szName, rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ RT_NOREF(pInterface, fImmediate);
+ PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream;
+ AssertPtrReturn(pStreamDbg, VERR_INVALID_POINTER);
+
+ if (pStreamDbg->pFile)
+ {
+ AudioHlpFileDestroy(pStreamDbg->pFile);
+ pStreamDbg->pFile = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudDebugHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream;
+
+ int rc = AudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cbBuf);
+ if (RT_SUCCESS(rc))
+ *pcbWritten = cbBuf;
+ else
+ LogRelMax(32, ("DebugAudio: Writing output failed with %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudDebugHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream;
+
+ return PDMAudioPropsMilliToBytes(&pStreamDbg->Cfg.Props, 10 /*ms*/);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudDebugHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDDEBUGSTREAM pStreamDbg = (PDRVHSTAUDDEBUGSTREAM)pStream;
+/** @todo rate limit this? */
+
+ uint32_t cbWritten;
+ int rc = AudioTestToneGenerate(&pStreamDbg->In, pvBuf, cbBuf, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Write it.
+ */
+ rc = AudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cbWritten);
+ if (RT_SUCCESS(rc))
+ *pcbRead = cbWritten;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRelMax(32, ("DebugAudio: Writing input failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudDebugQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHSTAUDDEBUG pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDDEBUG);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/**
+ * Constructs a Null audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHstAudDebugConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHSTAUDDEBUG pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDDEBUG);
+ LogRel(("Audio: Initializing DEBUG driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudDebugQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHstAudDebugHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvHstAudDebugHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHstAudDebugHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHstAudDebugHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHstAudDebugHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHstAudDebugHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHstAudDebugHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHstAudDebugHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHstAudDebugHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvHstAudDebugHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = drvHstAudDebugHA_StreamGetPending;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHstAudDebugHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHstAudDebugHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHstAudDebugHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHstAudDebugHA_StreamCapture;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Char driver registration record.
+ */
+const PDMDRVREG g_DrvHostDebugAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "DebugAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Debug audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHSTAUDDEBUG),
+ /* pfnConstruct */
+ drvHstAudDebugConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Devices/Audio/DrvHostAudioNull.cpp b/src/VBox/Devices/Audio/DrvHostAudioNull.cpp
new file mode 100644
index 00000000..22eded86
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioNull.cpp
@@ -0,0 +1,328 @@
+/* $Id: DrvHostAudioNull.cpp $ */
+/** @file
+ * Host audio driver - NULL (bitbucket).
+ *
+ * This also acts as a fallback if no other backend is available.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Null audio stream. */
+typedef struct DRVHSTAUDNULLSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+} DRVHSTAUDNULLSTREAM;
+/** Pointer to a null audio stream. */
+typedef DRVHSTAUDNULLSTREAM *PDRVHSTAUDNULLSTREAM;
+
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "NULL audio");
+ pBackendCfg->cbStream = sizeof(DRVHSTAUDNULLSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsOut = 1; /* Output */
+ pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudNullHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDNULLSTREAM pStreamNull = (PDRVHSTAUDNULLSTREAM)pStream;
+ AssertPtrReturn(pStreamNull, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDMAudioStrmCfgCopy(&pStreamNull->Cfg, pCfgAcq);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
+{
+ RT_NOREF(pInterface, pStream, fImmediate);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_StreamControlStub(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudNullHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+#if 0
+ /* Bit bucket appraoch where we ignore the output and silence the input buffers. */
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+#else
+ /* Approach where the mixer in the devices skips us and saves a few CPU cycles. */
+ return PDMHOSTAUDIOSTREAMSTATE_INACTIVE;
+#endif
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface, pStream, pvBuf);
+
+ /* The bitbucket never overflows. */
+ *pcbWritten = cbBuf;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudNullHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ /** @todo rate limit this? */
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudNullHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDNULLSTREAM pStreamNull = (PDRVHSTAUDNULLSTREAM)pStream;
+
+ /** @todo rate limit this? */
+
+ /* Return silence. */
+ PDMAudioPropsClearBuffer(&pStreamNull->Cfg.Props, pvBuf, cbBuf,
+ PDMAudioPropsBytesToFrames(&pStreamNull->Cfg.Props, cbBuf));
+ *pcbRead = cbBuf;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * This is used directly by DrvAudio when a backend fails to initialize in a
+ * non-fatal manner.
+ */
+DECL_HIDDEN_CONST(PDMIHOSTAUDIO) const g_DrvHostAudioNull =
+{
+ /* .pfnGetConfig =*/ drvHstAudNullHA_GetConfig,
+ /* .pfnGetDevices =*/ NULL,
+ /* .pfnSetDevice =*/ NULL,
+ /* .pfnGetStatus =*/ drvHstAudNullHA_GetStatus,
+ /* .pfnDoOnWorkerThread =*/ NULL,
+ /* .pfnStreamConfigHint =*/ NULL,
+ /* .pfnStreamCreate =*/ drvHstAudNullHA_StreamCreate,
+ /* .pfnStreamInitAsync =*/ NULL,
+ /* .pfnStreamDestroy =*/ drvHstAudNullHA_StreamDestroy,
+ /* .pfnStreamNotifyDeviceChanged =*/ NULL,
+ /* .pfnStreamEnable =*/ drvHstAudNullHA_StreamControlStub,
+ /* .pfnStreamDisable =*/ drvHstAudNullHA_StreamControlStub,
+ /* .pfnStreamPause =*/ drvHstAudNullHA_StreamControlStub,
+ /* .pfnStreamResume =*/ drvHstAudNullHA_StreamControlStub,
+ /* .pfnStreamDrain =*/ drvHstAudNullHA_StreamControlStub,
+ /* .pfnStreamGetState =*/ drvHstAudNullHA_StreamGetState,
+ /* .pfnStreamGetPending =*/ drvHstAudNullHA_StreamGetPending,
+ /* .pfnStreamGetWritable =*/ drvHstAudNullHA_StreamGetWritable,
+ /* .pfnStreamPlay =*/ drvHstAudNullHA_StreamPlay,
+ /* .pfnStreamGetReadable =*/ drvHstAudNullHA_StreamGetReadable,
+ /* .pfnStreamCapture =*/ drvHstAudNullHA_StreamCapture,
+};
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudNullQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PPDMIHOSTAUDIO pThis = PDMINS_2_DATA(pDrvIns, PPDMIHOSTAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, pThis);
+ return NULL;
+}
+
+
+/**
+ * Constructs a Null audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHstAudNullConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PPDMIHOSTAUDIO pThis = PDMINS_2_DATA(pDrvIns, PPDMIHOSTAUDIO);
+ RT_NOREF(pCfg, fFlags);
+ LogRel(("Audio: Initializing NULL driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudNullQueryInterface;
+ /* IHostAudio */
+ *pThis = g_DrvHostAudioNull;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Char driver registration record.
+ */
+const PDMDRVREG g_DrvHostNullAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "NullAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "NULL audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(PDMIHOSTAUDIO),
+ /* pfnConstruct */
+ drvHstAudNullConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioOss.cpp b/src/VBox/Devices/Audio/DrvHostAudioOss.cpp
new file mode 100644
index 00000000..5da8535d
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioOss.cpp
@@ -0,0 +1,1027 @@
+/* $Id: DrvHostAudioOss.cpp $ */
+/** @file
+ * Host audio driver - OSS (Open Sound System).
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/soundcard.h>
+#include <unistd.h>
+
+#include <iprt/alloc.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#if ((SOUND_VERSION > 360) && (defined(OSS_SYSINFO)))
+/* OSS > 3.6 has a new syscall available for querying a bit more detailed information
+ * about OSS' audio capabilities. This is handy for e.g. Solaris. */
+# define VBOX_WITH_AUDIO_OSS_SYSINFO 1
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+/**
+ * OSS audio stream configuration.
+ */
+typedef struct DRVHSTAUDOSSSTREAMCFG
+{
+ PDMAUDIOPCMPROPS Props;
+ uint16_t cFragments;
+ /** The log2 of cbFragment. */
+ uint16_t cbFragmentLog2;
+ uint32_t cbFragment;
+} DRVHSTAUDOSSSTREAMCFG;
+/** Pointer to an OSS audio stream configuration. */
+typedef DRVHSTAUDOSSSTREAMCFG *PDRVHSTAUDOSSSTREAMCFG;
+
+/**
+ * OSS audio stream.
+ */
+typedef struct DRVHSTAUDOSSSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The file descriptor. */
+ int hFile;
+ /** Buffer alignment. */
+ uint8_t uAlign;
+ /** Set if we're draining the stream (output only). */
+ bool fDraining;
+ /** Internal stream byte offset. */
+ uint64_t offInternal;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** The acquired OSS configuration. */
+ DRVHSTAUDOSSSTREAMCFG OssCfg;
+ /** Handle to the thread draining output streams. */
+ RTTHREAD hThreadDrain;
+} DRVHSTAUDOSSSTREAM;
+/** Pointer to an OSS audio stream. */
+typedef DRVHSTAUDOSSSTREAM *PDRVHSTAUDOSSSTREAM;
+
+/**
+ * OSS host audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHSTAUDOSS
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Error count for not flooding the release log.
+ * UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+} DRVHSTAUDOSS;
+/** Pointer to the instance data for an OSS host audio driver. */
+typedef DRVHSTAUDOSS *PDRVHSTAUDOSS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The path to the output OSS device. */
+static char g_szPathOutputDev[] = "/dev/dsp";
+/** The path to the input OSS device. */
+static char g_szPathInputDev[] = "/dev/dsp";
+
+
+
+static int drvHstAudOssToPdmAudioProps(PPDMAUDIOPCMPROPS pProps, int fmt, int cChannels, int uHz)
+{
+ switch (fmt)
+ {
+ case AFMT_S8:
+ PDMAudioPropsInit(pProps, 1 /*8-bit*/, true /*signed*/, cChannels, uHz);
+ break;
+
+ case AFMT_U8:
+ PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
+ break;
+
+ case AFMT_S16_LE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+ case AFMT_U16_LE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+ case AFMT_S16_BE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+ case AFMT_U16_BE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, false /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("Format %d not supported\n", fmt), VERR_NOT_SUPPORTED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int drvHstAudOssStreamClose(int *phFile)
+{
+ if (!phFile || !*phFile || *phFile == -1)
+ return VINF_SUCCESS;
+
+ int rc;
+ if (close(*phFile))
+ {
+ rc = RTErrConvertFromErrno(errno);
+ LogRel(("OSS: Closing stream failed: %s / %Rrc\n", strerror(errno), rc));
+ }
+ else
+ {
+ *phFile = -1;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS");
+ pBackendCfg->cbStream = sizeof(DRVHSTAUDOSSSTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsIn = 0;
+ pBackendCfg->cMaxStreamsOut = 0;
+
+ int hFile = open("/dev/dsp", O_WRONLY | O_NONBLOCK, 0);
+ if (hFile == -1)
+ {
+ /* Try opening the mixing device instead. */
+ hFile = open("/dev/mixer", O_RDONLY | O_NONBLOCK, 0);
+ }
+ if (hFile != -1)
+ {
+ int ossVer = -1;
+ int err = ioctl(hFile, OSS_GETVERSION, &ossVer);
+ if (err == 0)
+ {
+ LogRel2(("OSS: Using version: %d\n", ossVer));
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ oss_sysinfo ossInfo;
+ RT_ZERO(ossInfo);
+ err = ioctl(hFile, OSS_SYSINFO, &ossInfo);
+ if (err == 0)
+ {
+ LogRel2(("OSS: Number of DSPs: %d\n", ossInfo.numaudios));
+ LogRel2(("OSS: Number of mixers: %d\n", ossInfo.nummixers));
+
+ int cDev = ossInfo.nummixers;
+ if (!cDev)
+ cDev = ossInfo.numaudios;
+
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+ }
+ else
+#endif
+ {
+ /* Since we cannot query anything, assume that we have at least
+ * one input and one output if we found "/dev/dsp" or "/dev/mixer". */
+
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+ }
+ }
+ else
+ LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err));
+ close(hFile);
+ }
+ else
+ LogRel(("OSS: No devices found, audio is not available\n"));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudOssHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+ RT_NOREF(enmDir);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+static int drvHstAudOssStreamConfigure(int hFile, bool fInput, PDRVHSTAUDOSSSTREAMCFG pOSSReq, PDRVHSTAUDOSSSTREAMCFG pOSSAcq)
+{
+ /*
+ * Format.
+ */
+ int iFormat;
+ switch (PDMAudioPropsSampleSize(&pOSSReq->Props))
+ {
+ case 1:
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8;
+ break;
+
+ case 2:
+ if (PDMAudioPropsIsLittleEndian(&pOSSReq->Props))
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE;
+ else
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S16_BE : AFMT_U16_BE;
+ break;
+
+ default:
+ LogRel2(("OSS: Unsupported sample size: %u\n", PDMAudioPropsSampleSize(&pOSSReq->Props)));
+ return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat) >= 0,
+ ("OSS: Failed to set audio format to %d: %s (%d)\n", iFormat, strerror(errno), errno),
+ RTErrConvertFromErrno(errno));
+
+ /*
+ * Channel count.
+ */
+ int cChannels = PDMAudioPropsChannels(&pOSSReq->Props);
+ AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels) >= 0,
+ ("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n",
+ PDMAudioPropsChannels(&pOSSReq->Props), strerror(errno), errno),
+ RTErrConvertFromErrno(errno));
+
+ /*
+ * Frequency.
+ */
+ int iFrequenc = pOSSReq->Props.uHz;
+ AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SPEED, &iFrequenc) >= 0,
+ ("OSS: Failed to set audio frequency to %d Hz: %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno),
+ RTErrConvertFromErrno(errno));
+
+ /*
+ * Set fragment size and count.
+ */
+ LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSReq->cFragments, fInput ? "input" : "output", pOSSReq->cbFragment));
+
+ int mmmmssss = (pOSSReq->cFragments << 16) | pOSSReq->cbFragmentLog2;
+ AssertLogRelMsgReturn(ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss) >= 0,
+ ("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n",
+ pOSSReq->cFragments, pOSSReq->cbFragment, strerror(errno), errno),
+ RTErrConvertFromErrno(errno));
+
+ /*
+ * Get parameters and popuplate pOSSAcq.
+ */
+ audio_buf_info BufInfo = { 0, 0, 0, 0 };
+ AssertLogRelMsgReturn(ioctl(hFile, fInput ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &BufInfo) >= 0,
+ ("OSS: Failed to retrieve %s buffer length: %s (%d)\n",
+ fInput ? "input" : "output", strerror(errno), errno),
+ RTErrConvertFromErrno(errno));
+
+ int rc = drvHstAudOssToPdmAudioProps(&pOSSAcq->Props, iFormat, cChannels, iFrequenc);
+ if (RT_SUCCESS(rc))
+ {
+ pOSSAcq->cFragments = BufInfo.fragstotal;
+ pOSSAcq->cbFragment = BufInfo.fragsize;
+ pOSSAcq->cbFragmentLog2 = ASMBitFirstSetU32(BufInfo.fragsize) - 1;
+ Assert(RT_BIT_32(pOSSAcq->cbFragmentLog2) == pOSSAcq->cbFragment);
+
+ LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSAcq->cFragments, fInput ? "input" : "output", pOSSAcq->cbFragment));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtr(pInterface); RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ pStreamOSS->hThreadDrain = NIL_RTTHREAD;
+
+ /*
+ * Open the device
+ */
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ pStreamOSS->hFile = open(g_szPathInputDev, O_RDONLY);
+ else
+ pStreamOSS->hFile = open(g_szPathOutputDev, O_WRONLY);
+ if (pStreamOSS->hFile >= 0)
+ {
+ /*
+ * Configure it.
+ *
+ * Note! We limit the output channels to mono or stereo for now just
+ * to keep things simple and avoid wasting time here. If the
+ * channel count isn't a power of two, our code below trips up
+ * on the fragment size. We'd also need to try report/get
+ * channel mappings and whatnot.
+ */
+ DRVHSTAUDOSSSTREAMCFG ReqOssCfg;
+ RT_ZERO(ReqOssCfg);
+
+ memcpy(&ReqOssCfg.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
+ if (PDMAudioPropsChannels(&ReqOssCfg.Props) > 2)
+ {
+ LogRel2(("OSS: Limiting output to two channels, requested %u.\n", PDMAudioPropsChannels(&ReqOssCfg.Props) ));
+ PDMAudioPropsSetChannels(&ReqOssCfg.Props, 2);
+ }
+
+ ReqOssCfg.cbFragmentLog2 = 12;
+ ReqOssCfg.cbFragment = RT_BIT_32(ReqOssCfg.cbFragmentLog2);
+ uint32_t const cbBuffer = PDMAudioPropsFramesToBytes(&ReqOssCfg.Props, pCfgReq->Backend.cFramesBufferSize);
+ ReqOssCfg.cFragments = cbBuffer >> ReqOssCfg.cbFragmentLog2;
+ AssertLogRelStmt(cbBuffer < ((uint32_t)0x7ffe << ReqOssCfg.cbFragmentLog2), ReqOssCfg.cFragments = 0x7ffe);
+
+ rc = drvHstAudOssStreamConfigure(pStreamOSS->hFile, pCfgReq->enmDir == PDMAUDIODIR_IN, &ReqOssCfg, &pStreamOSS->OssCfg);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->uAlign = 0; /** @todo r=bird: Where did the correct assignment of this go? */
+
+ /*
+ * Complete the stream structure and fill in the pCfgAcq bits.
+ */
+ if ((pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment) & pStreamOSS->uAlign)
+ LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n",
+ pStreamOSS->OssCfg.cFragments * pStreamOSS->OssCfg.cbFragment, pStreamOSS->uAlign + 1));
+
+ memcpy(&pCfgAcq->Props, &pStreamOSS->OssCfg.Props, sizeof(PDMAUDIOPCMPROPS));
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamOSS->OssCfg.cbFragment);
+ pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * pStreamOSS->OssCfg.cFragments;
+ pCfgAcq->Backend.cFramesPreBuffering = (uint64_t)pCfgReq->Backend.cFramesPreBuffering
+ * pCfgAcq->Backend.cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ /*
+ * Copy the stream config and we're done!
+ */
+ PDMAudioStrmCfgCopy(&pStreamOSS->Cfg, pCfgAcq);
+ return VINF_SUCCESS;
+ }
+ drvHstAudOssStreamClose(&pStreamOSS->hFile);
+ }
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ LogRel(("OSS: Failed to open '%s': %s (%d) / %Rrc\n",
+ pCfgReq->enmDir == PDMAUDIODIR_IN ? g_szPathInputDev : g_szPathOutputDev, strerror(errno), errno, rc));
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
+{
+ RT_NOREF(pInterface, fImmediate);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
+
+ drvHstAudOssStreamClose(&pStreamOSS->hFile);
+
+ if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(pStreamOSS->hThreadDrain, 1, NULL);
+ AssertRC(rc);
+ pStreamOSS->hThreadDrain = NIL_RTTHREAD;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ int rc;
+
+ /*
+ * This is most probably untested...
+ */
+ if (pStreamOSS->fDraining)
+ {
+ LogFlowFunc(("Still draining...\n"));
+ rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Resetting...\n"));
+ ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL);
+ rc = RTThreadWait(pStreamOSS->hThreadDrain, 0 /*ms*/, NULL);
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Poking...\n"));
+ RTThreadPoke(pStreamOSS->hThreadDrain);
+ rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("Done draining.\n"));
+ pStreamOSS->hThreadDrain = NIL_RTTHREAD;
+ }
+ else
+ LogFlowFunc(("No, still draining...\n"));
+ pStreamOSS->fDraining = false;
+ }
+
+ /*
+ * Enable the stream.
+ */
+ int fMask = pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN ? PCM_ENABLE_INPUT : PCM_ENABLE_OUTPUT;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("OSS: Failed to enable output stream: %s (%d)\n", strerror(errno), errno));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ LogFlowFunc(("Stream '%s'\n", pStreamOSS->Cfg.szName));
+ int rc;
+
+ /*
+ * If we're still draining, try kick the thread before we try disable the stream.
+ */
+ if (pStreamOSS->fDraining)
+ {
+ LogFlowFunc(("Trying to cancel draining...\n"));
+ if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
+ {
+ RTThreadPoke(pStreamOSS->hThreadDrain);
+ rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
+ if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE)
+ pStreamOSS->fDraining = false;
+ else
+ LogFunc(("Failed to cancel draining (%Rrc)\n", rc));
+ }
+ else
+ {
+ LogFlowFunc(("Thread handle is NIL, so we can't be draining\n"));
+ pStreamOSS->fDraining = false;
+ }
+ }
+
+ /*
+ * The Official documentation says this isn't the right way to stop
+ * playback. It may work in some implementations but fail in all others...
+ * Suggest SNDCTL_DSP_RESET / SNDCTL_DSP_HALT.
+ *
+ * So, let's do both and see how that works out...
+ */
+ rc = VINF_SUCCESS;
+ int fMask = 0;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask) >= 0)
+ LogFlowFunc(("SNDCTL_DSP_SETTRIGGER succeeded\n"));
+ else
+ {
+ LogRel(("OSS: Failed to clear triggers for stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_RESET, NULL) >= 0)
+ LogFlowFunc(("SNDCTL_DSP_RESET succeeded\n"));
+ else
+ {
+ LogRel(("OSS: Failed to reset stream '%s': %s (%d)\n", pStreamOSS->Cfg.szName, strerror(errno), errno));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ LogFlowFunc(("returns %Rrc for '%s'\n", rc, pStreamOSS->Cfg.szName));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ return drvHstAudOssHA_StreamDisable(pInterface, pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ return drvHstAudOssHA_StreamEnable(pInterface, pStream);
+}
+
+
+/**
+ * @callback_method_impl{FNRTTHREAD,
+ * Thread for calling SNDCTL_DSP_SYNC (blocking) on an output stream.}
+ */
+static DECLCALLBACK(int) drvHstAudOssDrainThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ RT_NOREF(ThreadSelf);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pvUser;
+ int rc;
+
+ /* Make it blocking (for Linux). */
+ int fOrgFlags = fcntl(pStreamOSS->hFile, F_GETFL, 0);
+ LogFunc(("F_GETFL -> %#x\n", fOrgFlags));
+ Assert(fOrgFlags != -1);
+ if (fOrgFlags != -1 && (fOrgFlags & O_NONBLOCK))
+ {
+ rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags & ~O_NONBLOCK);
+ AssertStmt(rc != -1, fOrgFlags = -1);
+ }
+
+ /* Drain it. */
+ LogFunc(("Calling SNDCTL_DSP_SYNC now...\n"));
+ rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SYNC, NULL);
+ LogFunc(("SNDCTL_DSP_SYNC returned %d / errno=%d\n", rc, errno)); RT_NOREF(rc);
+
+ /* Re-enable non-blocking mode and disable it. */
+ if (fOrgFlags != -1)
+ {
+ rc = fcntl(pStreamOSS->hFile, F_SETFL, fOrgFlags);
+ Assert(rc != -1);
+
+ int fMask = 0;
+ rc = ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &fMask);
+ Assert(rc >= 0);
+
+ pStreamOSS->fDraining = false;
+ LogFunc(("Restored non-block mode and cleared the trigger mask\n"));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDOSS pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDOSS, IHostAudio);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertReturn(pStreamOSS->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_WRONG_ORDER);
+
+ pStreamOSS->fDraining = true;
+
+ /*
+ * Because the SNDCTL_DSP_SYNC call is blocking on real OSS,
+ * we kick off a thread to deal with it as we're probably on EMT
+ * and cannot block for extended periods.
+ */
+ if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(pStreamOSS->hThreadDrain, 0, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->hThreadDrain = NIL_RTTHREAD;
+ LogFunc(("Cleaned up stale thread handle.\n"));
+ }
+ else
+ {
+ LogFunc(("Drain thread already running (%Rrc).\n", rc));
+ AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ return rc == VERR_TIMEOUT ? VINF_SUCCESS : rc;
+ }
+ }
+
+ int rc = RTThreadCreateF(&pStreamOSS->hThreadDrain, drvHstAudOssDrainThread, pStreamOSS, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ossdrai%u", pThis->pDrvIns->iInstance);
+ LogFunc(("Started drain thread: %Rrc\n", rc));
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudOssHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtrReturn(pStreamOSS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+ if (!pStreamOSS->fDraining)
+ return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtr(pStreamOSS);
+
+ /*
+ * The logic here must match what StreamPlay does.
+ *
+ * Note! We now use 'bytes' rather than the fragments * fragsize as we used
+ * to do (up to 2021), as these are documented as obsolete.
+ */
+ audio_buf_info BufInfo = { 0, 0, 0, 0 };
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
+ AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETOSPACE failed: %s (%d)\n", strerror(errno), errno), 0);
+
+ /* Try use the size. */
+ uint32_t cbRet;
+ uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
+ if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf)
+ cbRet = BufInfo.bytes;
+ else
+ {
+ AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
+ AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
+ AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
+ cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
+ AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0);
+ }
+
+ Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf));
+ return cbRet;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
+
+ /*
+ * Return immediately if this is a draining service call.
+ *
+ * Otherwise the ioctl below will race the drain thread and sometimes fail,
+ * triggering annoying assertion and release logging.
+ */
+ if (cbBuf || !pStreamOSS->fDraining)
+ { /* likely */ }
+ else
+ {
+ *pcbWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Figure out now much to write (same as drvHstAudOssHA_StreamGetWritable,
+ * must match exactly).
+ */
+ audio_buf_info BufInfo;
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &BufInfo);
+ AssertLogRelMsgReturn(rc2 >= 0, ("OSS: Failed to retrieve current playback buffer: %s (%d, hFile=%d, rc2=%d)\n",
+ strerror(errno), errno, pStreamOSS->hFile, rc2),
+ RTErrConvertFromErrno(errno));
+
+ uint32_t cbToWrite;
+ uint32_t const cbStreamBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
+ if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbStreamBuf)
+ cbToWrite = BufInfo.bytes;
+ else
+ {
+ AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
+ AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
+ AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
+ cbToWrite = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
+ AssertMsgStmt(cbToWrite <= cbStreamBuf, ("fragsize*fragments: %d, cbStreamBuf=%#x\n", cbToWrite, cbStreamBuf), 0);
+ }
+
+ cbToWrite = RT_MIN(cbToWrite, cbBuf);
+ Log3Func(("@%#RX64 cbBuf=%#x BufInfo: fragments=%#x fragstotal=%#x fragsize=%#x bytes=%#x %s cbToWrite=%#x\n",
+ pStreamOSS->offInternal, cbBuf, BufInfo.fragments, BufInfo.fragstotal, BufInfo.fragsize, BufInfo.bytes,
+ pStreamOSS->Cfg.szName, cbToWrite));
+
+ /*
+ * Write.
+ */
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+ uint32_t cbChunk = cbToWrite;
+ uint32_t offChunk = 0;
+ while (cbChunk > 0)
+ {
+ ssize_t cbWritten = write(pStreamOSS->hFile, &pbBuf[offChunk], RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment));
+ if (cbWritten > 0)
+ {
+ AssertLogRelMsg(!(cbWritten & pStreamOSS->uAlign),
+ ("OSS: Misaligned write (written %#zx, alignment %#x)\n", cbWritten, pStreamOSS->uAlign));
+
+ Assert((uint32_t)cbWritten <= cbChunk);
+ offChunk += (uint32_t)cbWritten;
+ cbChunk -= (uint32_t)cbWritten;
+ pStreamOSS->offInternal += cbWritten;
+ }
+ else if (cbWritten == 0)
+ {
+ LogFunc(("@%#RX64 write(%#x) returned zeroed (previously wrote %#x bytes)!\n",
+ pStreamOSS->offInternal, RT_MIN(cbChunk, pStreamOSS->OssCfg.cbFragment), cbWritten));
+ break;
+ }
+ else
+ {
+ LogRel(("OSS: Failed writing output data: %s (%d)\n", strerror(errno), errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ }
+
+ *pcbWritten = offChunk;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudOssHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtr(pStreamOSS);
+
+ /*
+ * Use SNDCTL_DSP_GETISPACE to see how much we can read.
+ *
+ * Note! We now use 'bytes' rather than the fragments * fragsize as we used
+ * to do (up to 2021), as these are documented as obsolete.
+ */
+ audio_buf_info BufInfo = { 0, 0, 0, 0 };
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETISPACE, &BufInfo);
+ AssertMsgReturn(rc2 >= 0, ("SNDCTL_DSP_GETISPACE failed: %s (%d)\n", strerror(errno), errno), 0);
+
+ uint32_t cbRet;
+ uint32_t const cbBuf = pStreamOSS->OssCfg.cbFragment * pStreamOSS->OssCfg.cFragments;
+ if (BufInfo.bytes >= 0 && (unsigned)BufInfo.bytes <= cbBuf)
+ cbRet = BufInfo.bytes;
+ else
+ {
+ AssertMsgFailed(("Invalid available size: %d\n", BufInfo.bytes));
+ AssertMsgReturn(BufInfo.fragments >= 0, ("fragments: %d\n", BufInfo.fragments), 0);
+ AssertMsgReturn(BufInfo.fragsize >= 0, ("fragsize: %d\n", BufInfo.fragsize), 0);
+ cbRet = (uint32_t)(BufInfo.fragments * BufInfo.fragsize);
+ AssertMsgStmt(cbRet <= cbBuf, ("fragsize*fragments: %d, cbBuf=%#x\n", cbRet, cbBuf), 0);
+ }
+
+ /*
+ * HACK ALERT! To force the stream to start recording, we read a frame
+ * here if we get back that there are zero bytes available
+ * and we're at the start of the stream. (We cannot just
+ * return a frame size, we have to read it, as pre-buffering
+ * would prevent it from being read.)
+ */
+ if (BufInfo.bytes > 0 || pStreamOSS->offInternal != 0)
+ { /* likely */ }
+ else
+ {
+ uint32_t cbToRead = PDMAudioPropsFramesToBytes(&pStreamOSS->Cfg.Props, 1);
+ uint8_t abFrame[256];
+ Assert(cbToRead < sizeof(abFrame));
+ ssize_t cbRead = read(pStreamOSS->hFile, abFrame, cbToRead);
+ RT_NOREF(cbRead);
+ LogFunc(("Dummy read for '%s' returns %zd (errno=%d)\n", pStreamOSS->Cfg.szName, cbRead, errno));
+ }
+
+ Log4Func(("returns %#x (%u) [cbBuf=%#x]\n", cbRet, cbRet, cbBuf));
+ return cbRet;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudOssHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface);
+ PDRVHSTAUDOSSSTREAM pStreamOSS = (PDRVHSTAUDOSSSTREAM)pStream;
+ AssertPtrReturn(pStreamOSS, VERR_INVALID_POINTER);
+ Log3Func(("@%#RX64 cbBuf=%#x %s\n", pStreamOSS->offInternal, cbBuf, pStreamOSS->Cfg.szName));
+
+ size_t cbToRead = cbBuf;
+ uint8_t * const pbDst = (uint8_t *)pvBuf;
+ size_t offWrite = 0;
+ while (cbToRead > 0)
+ {
+ ssize_t cbRead = read(pStreamOSS->hFile, &pbDst[offWrite], cbToRead);
+ if (cbRead > 0)
+ {
+ LogFlowFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu\n", cbRead, offWrite, cbToRead));
+ Assert((ssize_t)cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ offWrite += cbRead;
+ pStreamOSS->offInternal += cbRead;
+ }
+ else
+ {
+ LogFunc(("cbRead=%zi, offWrite=%zu cbToRead=%zu errno=%d\n", cbRead, offWrite, cbToRead, errno));
+
+ /* Don't complain about errors if we've retrieved some audio data already. */
+ if (cbRead < 0 && offWrite == 0 && errno != EINTR && errno != EAGAIN)
+ {
+ AssertStmt(errno != 0, errno = EACCES);
+ int rc = RTErrConvertFromErrno(errno);
+ LogFunc(("Failed to read %zu input frames, errno=%d rc=%Rrc\n", cbToRead, errno, rc));
+ return rc;
+ }
+ break;
+ }
+ }
+
+ *pcbRead = offWrite;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudOssQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/**
+ * @interface_method_impl{PDMDRVREG,pfnConstruct,
+ * Constructs an OSS audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHstAudOssConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHSTAUDOSS pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDOSS);
+ LogRel(("Audio: Initializing OSS driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudOssQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHstAudOssHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = NULL;
+ pThis->IHostAudio.pfnSetDevice = NULL;
+ pThis->IHostAudio.pfnGetStatus = drvHstAudOssHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHstAudOssHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHstAudOssHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHstAudOssHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHstAudOssHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHstAudOssHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHstAudOssHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHstAudOssHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvHstAudOssHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHstAudOssHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHstAudOssHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHstAudOssHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHstAudOssHA_StreamCapture;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * OSS driver registration record.
+ */
+const PDMDRVREG g_DrvHostOSSAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "OSSAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "OSS audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHSTAUDOSS),
+ /* pfnConstruct */
+ drvHstAudOssConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp b/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp
new file mode 100644
index 00000000..c60e17cd
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp
@@ -0,0 +1,2432 @@
+/* $Id: DrvHostAudioPulseAudio.cpp $ */
+/** @file
+ * Host audio driver - Pulse Audio.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include <stdio.h>
+
+#include <iprt/alloc.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+#include <iprt/semaphore.h>
+
+#include "DrvHostAudioPulseAudioStubsMangling.h"
+#include "DrvHostAudioPulseAudioStubs.h"
+
+#include <pulse/pulseaudio.h>
+#ifndef PA_STREAM_NOFLAGS
+# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
+#endif
+#ifndef PA_CONTEXT_NOFLAGS
+# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
+#endif
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+/** Max number of errors reported by drvHstAudPaError per instance.
+ * @todo Make this configurable thru driver config. */
+#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 99
+
+
+/** @name DRVHSTAUDPAENUMCB_F_XXX
+ * @{ */
+/** No flags specified. */
+#define DRVHSTAUDPAENUMCB_F_NONE 0
+/** (Release) log found devices. */
+#define DRVHSTAUDPAENUMCB_F_LOG RT_BIT(0)
+/** Only do default devices. */
+#define DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY RT_BIT(1)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+/** Pointer to the instance data for a pulse audio host audio driver. */
+typedef struct DRVHSTAUDPA *PDRVHSTAUDPA;
+
+
+/**
+ * Callback context for the server init context state changed callback.
+ */
+typedef struct DRVHSTAUDPASTATECHGCTX
+{
+ /** The event semaphore. */
+ RTSEMEVENT hEvtInit;
+ /** The returned context state. */
+ pa_context_state_t volatile enmCtxState;
+} DRVHSTAUDPASTATECHGCTX;
+/** Pointer to a server init context state changed callback context. */
+typedef DRVHSTAUDPASTATECHGCTX *PDRVHSTAUDPASTATECHGCTX;
+
+
+/**
+ * Enumeration callback context used by the pfnGetConfig code.
+ */
+typedef struct DRVHSTAUDPAENUMCBCTX
+{
+ /** Pointer to PulseAudio's threaded main loop. */
+ pa_threaded_mainloop *pMainLoop;
+ /** Enumeration flags, DRVHSTAUDPAENUMCB_F_XXX. */
+ uint32_t fFlags;
+ /** VBox status code for the operation.
+ * The caller sets this to VERR_AUDIO_ENUMERATION_FAILED, the callback never
+ * uses that status code. */
+ int32_t rcEnum;
+ /** Name of default sink being used. Must be free'd using RTStrFree(). */
+ char *pszDefaultSink;
+ /** Name of default source being used. Must be free'd using RTStrFree(). */
+ char *pszDefaultSource;
+ /** The device enumeration to fill, NULL if pfnGetConfig context. */
+ PPDMAUDIOHOSTENUM pDeviceEnum;
+} DRVHSTAUDPAENUMCBCTX;
+/** Pointer to an enumeration callback context. */
+typedef DRVHSTAUDPAENUMCBCTX *PDRVHSTAUDPAENUMCBCTX;
+
+
+/**
+ * Pulse audio device enumeration entry.
+ */
+typedef struct DRVHSTAUDPADEVENTRY
+{
+ /** The part we share with others. */
+ PDMAUDIOHOSTDEV Core;
+} DRVHSTAUDPADEVENTRY;
+/** Pointer to a pulse audio device enumeration entry. */
+typedef DRVHSTAUDPADEVENTRY *PDRVHSTAUDPADEVENTRY;
+
+
+/**
+ * Pulse audio stream data.
+ */
+typedef struct DRVHSTAUDPASTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** Pointer to driver instance. */
+ PDRVHSTAUDPA pDrv;
+ /** Pointer to opaque PulseAudio stream. */
+ pa_stream *pStream;
+ /** Input: Pointer to Pulse sample peek buffer. */
+ const uint8_t *pbPeekBuf;
+ /** Input: Current size (in bytes) of peeked data in buffer. */
+ size_t cbPeekBuf;
+ /** Input: Our offset (in bytes) in peek data buffer. */
+ size_t offPeekBuf;
+ /** Output: Asynchronous drain operation. This is used as an indicator of
+ * whether we're currently draining the stream (will be cleaned up before
+ * resume/re-enable). */
+ pa_operation *pDrainOp;
+ /** Asynchronous cork/uncork operation.
+ * (This solely for cancelling before destroying the stream, so the callback
+ * won't do any after-freed accesses.) */
+ pa_operation *pCorkOp;
+ /** Asynchronous trigger operation.
+ * (This solely for cancelling before destroying the stream, so the callback
+ * won't do any after-freed accesses.) */
+ pa_operation *pTriggerOp;
+ /** Internal byte offset. */
+ uint64_t offInternal;
+#ifdef LOG_ENABLED
+ /** Creation timestamp (in microsecs) of stream playback / recording. */
+ pa_usec_t tsStartUs;
+ /** Timestamp (in microsecs) when last read from / written to the stream. */
+ pa_usec_t tsLastReadWrittenUs;
+#endif
+ /** Number of occurred audio data underflows. */
+ uint32_t cUnderflows;
+ /** Pulse sample format and attribute specification. */
+ pa_sample_spec SampleSpec;
+ /** Channel map. */
+ pa_channel_map ChannelMap;
+ /** Pulse playback and buffer metrics. */
+ pa_buffer_attr BufAttr;
+} DRVHSTAUDPASTREAM;
+/** Pointer to pulse audio stream data. */
+typedef DRVHSTAUDPASTREAM *PDRVHSTAUDPASTREAM;
+
+
+/**
+ * Pulse audio host audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHSTAUDPA
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to PulseAudio's threaded main loop. */
+ pa_threaded_mainloop *pMainLoop;
+ /**
+ * Pointer to our PulseAudio context.
+ * @note We use a pMainLoop in a separate thread (pContext).
+ * So either use callback functions or protect these functions
+ * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
+ */
+ pa_context *pContext;
+ /** Shutdown indicator. */
+ volatile bool fAbortLoop;
+ /** Error count for not flooding the release log.
+ * Specify UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+ /** Don't want to put this on the stack... */
+ DRVHSTAUDPASTATECHGCTX InitStateChgCtx;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Upwards notification interface. */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort;
+
+ /** The stream (base) name.
+ * This is needed for distinguishing streams in the PulseAudio mixer controls if
+ * multiple VMs are running at the same time. */
+ char szStreamName[64];
+ /** The name of the input device to use. Empty string for default. */
+ char szInputDev[256];
+ /** The name of the output device to use. Empty string for default. */
+ char szOutputDev[256];
+
+ /** Number of buffer underruns (for all streams). */
+ STAMCOUNTER StatUnderruns;
+ /** Number of buffer overruns (for all streams). */
+ STAMCOUNTER StatOverruns;
+} DRVHSTAUDPA;
+
+
+
+/*
+ * Glue to make the code work systems with PulseAudio < 0.9.11.
+ */
+#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
+DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState)
+{
+ return enmState == PA_CONTEXT_CONNECTING
+ || enmState == PA_CONTEXT_AUTHORIZING
+ || enmState == PA_CONTEXT_SETTING_NAME
+ || enmState == PA_CONTEXT_READY;
+}
+#endif
+
+#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
+DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState)
+{
+ return enmState == PA_STREAM_CREATING
+ || enmState == PA_STREAM_READY;
+}
+#endif
+
+
+/**
+ * Converts a pulse audio error to a VBox status.
+ *
+ * @returns VBox status code.
+ * @param rcPa The error code to convert.
+ */
+static int drvHstAudPaErrorToVBox(int rcPa)
+{
+ /** @todo Implement some PulseAudio -> VBox mapping here. */
+ RT_NOREF(rcPa);
+ return VERR_GENERAL_FAILURE;
+}
+
+
+/**
+ * Logs a pulse audio (from context) and converts it to VBox status.
+ *
+ * @returns VBox status code.
+ * @param pThis Our instance data.
+ * @param pszFormat The format string for the release log (no newline) .
+ * @param ... Format string arguments.
+ */
+static int drvHstAudPaError(PDRVHSTAUDPA pThis, const char *pszFormat, ...)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtr(pszFormat);
+
+ int const rcPa = pa_context_errno(pThis->pContext);
+ int const rcVBox = drvHstAudPaErrorToVBox(rcPa);
+
+ if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS
+ && LogRelIs2Enabled())
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ LogRel(("PulseAudio: %N: %s (%d, %Rrc)\n", pszFormat, &va, pa_strerror(rcPa), rcPa, rcVBox));
+ va_end(va);
+
+ if (++pThis->cLogErrors == VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
+ LogRel(("PulseAudio: muting errors (max %u)\n", VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS));
+ }
+
+ return rcVBox;
+}
+
+
+/**
+ * Signal the main loop to abort. Just signalling isn't sufficient as the
+ * mainloop might not have been entered yet.
+ */
+static void drvHstAudPaSignalWaiter(PDRVHSTAUDPA pThis)
+{
+ if (pThis)
+ {
+ pThis->fAbortLoop = true;
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
+ }
+}
+
+
+/**
+ * Wrapper around pa_threaded_mainloop_wait().
+ */
+static void drvHstAudPaMainloopWait(PDRVHSTAUDPA pThis)
+{
+ /** @todo r=bird: explain this logic. */
+ if (!pThis->fAbortLoop)
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ pThis->fAbortLoop = false;
+}
+
+
+/**
+ * Pulse audio callback for context status changes, init variant.
+ */
+static void drvHstAudPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser)
+{
+ AssertPtrReturnVoid(pCtx);
+
+ PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_context_get_state(pCtx))
+ {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ drvHstAudPaSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Synchronously wait until an operation completed.
+ *
+ * This will consume the pOperation reference.
+ */
+static int drvHstAudPaWaitForEx(PDRVHSTAUDPA pThis, pa_operation *pOperation, RTMSINTERVAL cMsTimeout)
+{
+ AssertPtrReturn(pOperation, VERR_INVALID_POINTER);
+
+ uint64_t const msStart = RTTimeMilliTS();
+ pa_operation_state_t enmOpState;
+ while ((enmOpState = pa_operation_get_state(pOperation)) == PA_OPERATION_RUNNING)
+ {
+ if (!pThis->fAbortLoop) /** @todo r=bird: I do _not_ get the logic behind this fAbortLoop mechanism, it looks more
+ * than a little mixed up and too much generalized see drvHstAudPaSignalWaiter. */
+ {
+ AssertPtr(pThis->pMainLoop);
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ if ( !pThis->pContext
+ || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
+ {
+ pa_operation_cancel(pOperation);
+ pa_operation_unref(pOperation);
+ LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
+ return VERR_INVALID_STATE;
+ }
+ }
+ pThis->fAbortLoop = false;
+
+ /*
+ * Note! This timeout business is a bit bogus as pa_threaded_mainloop_wait is indefinite.
+ */
+ if (RTTimeMilliTS() - msStart >= cMsTimeout)
+ {
+ enmOpState = pa_operation_get_state(pOperation);
+ if (enmOpState != PA_OPERATION_RUNNING)
+ break;
+ pa_operation_cancel(pOperation);
+ pa_operation_unref(pOperation);
+ return VERR_TIMEOUT;
+ }
+ }
+
+ pa_operation_unref(pOperation);
+ if (enmOpState == PA_OPERATION_DONE)
+ return VINF_SUCCESS;
+ return VERR_CANCELLED;
+}
+
+
+static int drvHstAudPaWaitFor(PDRVHSTAUDPA pThis, pa_operation *pOP)
+{
+ return drvHstAudPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC);
+}
+
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for drvHstAudPaEnumSourceCallback() and
+ * drvHstAudPaEnumSinkCallback() that adds an entry to the enumeration
+ * result.
+ */
+static void drvHstAudPaEnumAddDevice(PDRVHSTAUDPAENUMCBCTX pCbCtx, PDMAUDIODIR enmDir, const char *pszName,
+ const char *pszDesc, uint8_t cChannelsInput, uint8_t cChannelsOutput,
+ const char *pszDefaultName)
+{
+ size_t const cbId = strlen(pszName) + 1;
+ size_t const cbName = pszDesc && *pszDesc ? strlen(pszDesc) + 1 : cbId;
+ PDRVHSTAUDPADEVENTRY pDev = (PDRVHSTAUDPADEVENTRY)PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId);
+ if (pDev != NULL)
+ {
+ pDev->Core.enmUsage = enmDir;
+ pDev->Core.enmType = RTStrIStr(pszDesc, "built-in") != NULL
+ ? PDMAUDIODEVICETYPE_BUILTIN : PDMAUDIODEVICETYPE_UNKNOWN;
+ if (RTStrCmp(pszName, pszDefaultName) != 0)
+ pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_NONE;
+ else
+ pDev->Core.fFlags = enmDir == PDMAUDIODIR_IN ? PDMAUDIOHOSTDEV_F_DEFAULT_IN : PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
+ pDev->Core.cMaxInputChannels = cChannelsInput;
+ pDev->Core.cMaxOutputChannels = cChannelsOutput;
+
+ int rc = RTStrCopy(pDev->Core.pszId, cbId, pszName);
+ AssertRC(rc);
+
+ rc = RTStrCopy(pDev->Core.pszName, cbName, pszDesc && *pszDesc ? pszDesc : pszName);
+ AssertRC(rc);
+
+ PDMAudioHostEnumAppend(pCbCtx->pDeviceEnum, &pDev->Core);
+ }
+ else
+ pCbCtx->rcEnum = VERR_NO_MEMORY;
+}
+
+
+/**
+ * Enumeration callback - source info.
+ *
+ * @param pCtx The context (DRVHSTAUDPA::pContext).
+ * @param pInfo The info. NULL when @a eol is not zero.
+ * @param eol Error-or-last indicator or something like that:
+ * - 0: Normal call with info.
+ * - 1: End of list, no info.
+ * - -1: Error callback, no info.
+ * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure.
+ */
+static void drvHstAudPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
+{
+ LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
+ PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ Assert((pInfo == NULL) == (eol != 0));
+ RT_NOREF(pCtx);
+
+ if (eol == 0 && pInfo != NULL)
+ {
+ LogRel2(("PulseAudio: Source #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
+ pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
+ pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
+ drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_IN, pInfo->name, pInfo->description,
+ pInfo->sample_spec.channels, 0 /*cChannelsOutput*/, pCbCtx->pszDefaultSource);
+ }
+ else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
+ pCbCtx->rcEnum = VINF_SUCCESS;
+
+ /* Wake up the calling thread when done: */
+ if (eol != 0)
+ pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
+}
+
+
+/**
+ * Enumeration callback - sink info.
+ *
+ * @param pCtx The context (DRVHSTAUDPA::pContext).
+ * @param pInfo The info. NULL when @a eol is not zero.
+ * @param eol Error-or-last indicator or something like that:
+ * - 0: Normal call with info.
+ * - 1: End of list, no info.
+ * - -1: Error callback, no info.
+ * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure.
+ */
+static void drvHstAudPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
+{
+ LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
+ PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ Assert((pInfo == NULL) == (eol != 0));
+ RT_NOREF(pCtx);
+
+ if (eol == 0 && pInfo != NULL)
+ {
+ LogRel2(("PulseAudio: Sink #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
+ pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
+ pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
+ drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_OUT, pInfo->name, pInfo->description,
+ 0 /*cChannelsInput*/, pInfo->sample_spec.channels, pCbCtx->pszDefaultSink);
+ }
+ else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
+ pCbCtx->rcEnum = VINF_SUCCESS;
+
+ /* Wake up the calling thread when done: */
+ if (eol != 0)
+ pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
+}
+
+
+/**
+ * Enumeration callback - service info.
+ *
+ * Copy down the default names.
+ */
+static void drvHstAudPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
+{
+ LogFlowFunc(("pCtx=%p pInfo=%p pvUserData=%p\n", pCtx, pInfo, pvUserData));
+ PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ RT_NOREF(pCtx);
+
+ if (pInfo)
+ {
+ LogRel2(("PulseAudio: Server info: user=%s host=%s ver=%s name=%s defsink=%s defsrc=%s spec: %d %uHz %uch\n",
+ pInfo->user_name, pInfo->host_name, pInfo->server_version, pInfo->server_name,
+ pInfo->default_sink_name, pInfo->default_source_name,
+ pInfo->sample_spec.format, pInfo->sample_spec.rate, pInfo->sample_spec.channels));
+
+ Assert(!pCbCtx->pszDefaultSink);
+ Assert(!pCbCtx->pszDefaultSource);
+ Assert(pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED);
+ pCbCtx->rcEnum = VINF_SUCCESS;
+
+ if (pInfo->default_sink_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
+ pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
+ AssertStmt(pCbCtx->pszDefaultSink, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
+ }
+
+ if (pInfo->default_source_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_source_name));
+ pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
+ AssertStmt(pCbCtx->pszDefaultSource, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
+ }
+ }
+ else
+ pCbCtx->rcEnum = VERR_INVALID_POINTER;
+
+ pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
+}
+
+
+/**
+ * @note Called with the PA main loop locked.
+ */
+static int drvHstAudPaEnumerate(PDRVHSTAUDPA pThis, uint32_t fEnum, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ DRVHSTAUDPAENUMCBCTX CbCtx = { pThis->pMainLoop, fEnum, VERR_AUDIO_ENUMERATION_FAILED, NULL, NULL, pDeviceEnum };
+ bool const fLog = (fEnum & DRVHSTAUDPAENUMCB_F_LOG);
+ bool const fOnlyDefault = (fEnum & DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY);
+ int rc;
+
+ /*
+ * Check if server information is available and bail out early if it isn't.
+ * This should give us a default (playback) sink and (recording) source.
+ */
+ LogRel(("PulseAudio: Retrieving server information ...\n"));
+ CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
+ pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHstAudPaEnumServerCallback, &CbCtx);
+ if (paOpServerInfo)
+ rc = drvHstAudPaWaitFor(pThis, paOpServerInfo);
+ else
+ {
+ LogRel(("PulseAudio: Server information not available, skipping enumeration.\n"));
+ return VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ rc = CbCtx.rcEnum;
+ if (RT_FAILURE(rc))
+ {
+ if (fLog)
+ LogRel(("PulseAudio: Error enumerating PulseAudio server properties: %Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Get info about the playback sink.
+ */
+ if (fLog && CbCtx.pszDefaultSink)
+ LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
+ else if (fLog)
+ LogRel2(("PulseAudio: No default output sink found\n"));
+
+ if (CbCtx.pszDefaultSink || !fOnlyDefault)
+ {
+ CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
+ if (!fOnlyDefault)
+ rc = drvHstAudPaWaitFor(pThis,
+ pa_context_get_sink_info_list(pThis->pContext, drvHstAudPaEnumSinkCallback, &CbCtx));
+ else
+ rc = drvHstAudPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
+ drvHstAudPaEnumSinkCallback, &CbCtx));
+ if (RT_SUCCESS(rc))
+ rc = CbCtx.rcEnum;
+ if (fLog && RT_FAILURE(rc))
+ LogRel(("PulseAudio: Error enumerating properties for default output sink '%s': %Rrc\n",
+ CbCtx.pszDefaultSink, rc));
+ }
+
+ /*
+ * Get info about the recording source.
+ */
+ if (fLog && CbCtx.pszDefaultSource)
+ LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
+ else if (fLog)
+ LogRel2(("PulseAudio: No default input source found\n"));
+ if (CbCtx.pszDefaultSource || !fOnlyDefault)
+ {
+ CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
+ int rc2;
+ if (!fOnlyDefault)
+ rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_list(pThis->pContext,
+ drvHstAudPaEnumSourceCallback, &CbCtx));
+ else
+ rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
+ drvHstAudPaEnumSourceCallback, &CbCtx));
+ if (RT_SUCCESS(rc2))
+ rc2 = CbCtx.rcEnum;
+ if (fLog && RT_FAILURE(rc2))
+ LogRel(("PulseAudio: Error enumerating properties for default input source '%s': %Rrc\n",
+ CbCtx.pszDefaultSource, rc));
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /* clean up */
+ RTStrFree(CbCtx.pszDefaultSink);
+ RTStrFree(CbCtx.pszDefaultSource);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ /*
+ * The configuration.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "PulseAudio");
+ pBackendCfg->cbStream = sizeof(DRVHSTAUDPASTREAM);
+ pBackendCfg->fFlags = 0;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+
+#if 0
+ /*
+ * In case we want to gather info about default devices, we can do this:
+ */
+ PDMAUDIOHOSTENUM DeviceEnum;
+ PDMAudioHostEnumInit(&DeviceEnum);
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY | DRVHSTAUDPAENUMCB_F_LOG, &DeviceEnum);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ AssertRCReturn(rc, rc);
+ /** @todo do stuff with DeviceEnum. */
+ PDMAudioHostEnumDelete(&DeviceEnum);
+#else
+ RT_NOREF(pThis);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
+ PDMAudioHostEnumInit(pDeviceEnum);
+
+ /* Refine it or something (currently only some LogRel2 stuff): */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_NONE, pDeviceEnum);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+
+ /*
+ * Validate and normalize input.
+ */
+ AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
+ if (!pszId || !*pszId)
+ pszId = "";
+ else
+ {
+ size_t cch = strlen(pszId);
+ AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME);
+ }
+ LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
+
+ /*
+ * Update input.
+ */
+ if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (strcmp(pThis->szInputDev, pszId) == 0)
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ else
+ {
+ LogRel(("PulseAudio: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId));
+ RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId);
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about input device change...\n"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
+ }
+ }
+ }
+
+ /*
+ * Update output.
+ */
+ if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (strcmp(pThis->szOutputDev, pszId) == 0)
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ else
+ {
+ LogRel(("PulseAudio: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId));
+ RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId);
+ PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about output device change...\n"));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Stream status changed.
+ */
+static void drvHstAudPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_stream_get_state(pStream))
+ {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ drvHstAudPaSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Underflow notification.
+ */
+static void drvHstAudPaStreamUnderflowStatsCallback(pa_stream *pStream, void *pvContext)
+{
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext;
+ AssertPtrReturnVoid(pStreamPA);
+ AssertPtrReturnVoid(pStreamPA->pDrv);
+
+ /* This may happen when draining/corking, so don't count those. */
+ if (!pStreamPA->pDrainOp)
+ STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatUnderruns);
+
+ pStreamPA->cUnderflows++;
+
+ LogRel2(("PulseAudio: Warning: Hit underflow #%RU32%s%s\n", pStreamPA->cUnderflows,
+ pStreamPA->pDrainOp && pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING ? " (draining)" : "",
+ pStreamPA->pCorkOp && pa_operation_get_state(pStreamPA->pCorkOp) == PA_OPERATION_RUNNING ? " (corking)" : "" ));
+
+ if (LogRelIs2Enabled() || LogIs2Enabled())
+ {
+ pa_usec_t cUsLatency = 0;
+ int fNegative = 0;
+ pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
+ LogRel2(("PulseAudio: Latency now is %'RU64 us\n", cUsLatency));
+
+ if (LogRelIs2Enabled())
+ {
+ const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
+ AssertReturnVoid(pTInfo);
+ const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
+ AssertReturnVoid(pSpec);
+ LogRel2(("PulseAudio: Timing info: writepos=%'RU64 us, readpost=%'RU64 us, latency=%'RU64 us (%RU32Hz %RU8ch)\n",
+ pa_bytes_to_usec(pTInfo->write_index, pSpec), pa_bytes_to_usec(pTInfo->read_index, pSpec),
+ cUsLatency, pSpec->rate, pSpec->channels));
+ }
+
+#ifdef LOG_ENABLED
+ Log2Func(("age=%'RU64 us\n", pa_rtclock_now() - pStreamPA->tsStartUs));
+#endif
+ }
+}
+
+
+/**
+ * Overflow notification.
+ */
+static void drvHstAudPaStreamOverflowStatsCallback(pa_stream *pStream, void *pvContext)
+{
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext;
+ AssertPtrReturnVoid(pStreamPA);
+ AssertPtrReturnVoid(pStreamPA->pDrv);
+
+ STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatOverruns);
+ LogRel2(("PulseAudio: Warning: Hit overflow.\n"));
+ RT_NOREF(pStream);
+}
+
+
+#ifdef DEBUG
+/**
+ * Debug PA callback: Need data to output.
+ */
+static void drvHstAudPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext)
+{
+ RT_NOREF(cbLen, pvContext);
+ pa_usec_t cUsLatency = 0;
+ int fNegative = 0;
+ int rcPa = pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
+ Log2Func(("Requesting %zu bytes; Latency: %'RU64 us (rcPa=%d n=%d)\n", cbLen, cUsLatency, rcPa, fNegative));
+}
+#endif /* DEBUG */
+
+/**
+ * Converts from PDM PCM properties to pulse audio format.
+ *
+ * Worker for the stream creation code.
+ *
+ * @returns PA format.
+ * @retval PA_SAMPLE_INVALID if format not supported.
+ * @param pProps The PDM audio source properties.
+ */
+static pa_sample_format_t drvHstAudPaPropsToPulse(PCPDMAUDIOPCMPROPS pProps)
+{
+ switch (PDMAudioPropsSampleSize(pProps))
+ {
+ case 1:
+ if (!PDMAudioPropsIsSigned(pProps))
+ return PA_SAMPLE_U8;
+ break;
+
+ case 2:
+ if (PDMAudioPropsIsSigned(pProps))
+ return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case 4:
+ if (PDMAudioPropsIsSigned(pProps))
+ return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE;
+ break;
+#endif
+ }
+
+ AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), PDMAudioPropsIsSigned(pProps) ? "S" : "U"));
+ return PA_SAMPLE_INVALID;
+}
+
+
+/**
+ * Converts from pulse audio sample specification to PDM PCM audio properties.
+ *
+ * Worker for the stream creation code.
+ *
+ * @returns VBox status code.
+ * @param pProps The PDM audio source properties.
+ * @param enmPulseFmt The PA format.
+ * @param cChannels The number of channels.
+ * @param uHz The frequency.
+ */
+static int drvHstAudPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t enmPulseFmt, uint8_t cChannels, uint32_t uHz)
+{
+ AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
+
+ switch (enmPulseFmt)
+ {
+ case PA_SAMPLE_U8:
+ PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
+ break;
+
+ case PA_SAMPLE_S16LE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+ case PA_SAMPLE_S16BE:
+ PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case PA_SAMPLE_S32LE:
+ PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
+ break;
+#endif
+
+#ifdef PA_SAMPLE_S32BE
+ case PA_SAMPLE_S32BE:
+ PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
+ break;
+#endif
+
+ default:
+ AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", enmPulseFmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#if 0 /* experiment */
+/**
+ * Completion callback used with pa_stream_set_buffer_attr().
+ */
+static void drvHstAudPaStreamSetBufferAttrCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
+ LogFlowFunc(("fSuccess=%d\n", fSuccess));
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0 /*fWaitForAccept*/);
+ RT_NOREF(pStream);
+}
+#endif
+
+
+/**
+ * Worker that does the actual creation of an PA stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Our driver instance data.
+ * @param pStreamPA Our stream data.
+ * @param pszName How we name the stream.
+ * @param pCfgAcq The requested stream properties, the Props member is
+ * updated upon successful return.
+ *
+ * @note Caller owns the mainloop lock.
+ */
+static int drvHstAudPaStreamCreateLocked(PDRVHSTAUDPA pThis, PDRVHSTAUDPASTREAM pStreamPA,
+ const char *pszName, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ /*
+ * Create the stream.
+ */
+ pa_stream *pStream = pa_stream_new(pThis->pContext, pszName, &pStreamPA->SampleSpec, &pStreamPA->ChannelMap);
+ if (!pStream)
+ {
+ LogRel(("PulseAudio: Failed to create stream '%s': %s (%d)\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
+ return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ /*
+ * Set the state callback, and in debug builds a few more...
+ */
+ pa_stream_set_state_callback(pStream, drvHstAudPaStreamStateChangedCallback, pThis);
+ pa_stream_set_underflow_callback(pStream, drvHstAudPaStreamUnderflowStatsCallback, pStreamPA);
+ pa_stream_set_overflow_callback(pStream, drvHstAudPaStreamOverflowStatsCallback, pStreamPA);
+#ifdef DEBUG
+ pa_stream_set_write_callback(pStream, drvHstAudPaStreamReqWriteDebugCallback, pStreamPA);
+#endif
+
+ /*
+ * Connect the stream.
+ */
+ int rc;
+ unsigned const fFlags = PA_STREAM_START_CORKED /* Require explicit starting (uncorking). */
+ /* For using pa_stream_get_latency() and pa_stream_get_time(). */
+ | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE
+#if PA_API_VERSION >= 12
+ | PA_STREAM_ADJUST_LATENCY
+#endif
+ ;
+ if (pCfgAcq->enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
+ pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.fragsize));
+ rc = pa_stream_connect_record(pStream, pThis->szInputDev[0] ? pThis->szInputDev : NULL,
+ &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags);
+ }
+ else
+ {
+ LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
+ pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.minreq));
+ rc = pa_stream_connect_playback(pStream, pThis->szOutputDev[0] ? pThis->szOutputDev : NULL, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags,
+ NULL /*volume*/, NULL /*sync_stream*/);
+ }
+ if (rc >= 0)
+ {
+ /*
+ * Wait for the stream to become ready.
+ */
+ uint64_t const nsStart = RTTimeNanoTS();
+ pa_stream_state_t enmStreamState;
+ while ( (enmStreamState = pa_stream_get_state(pStream)) != PA_STREAM_READY
+ && PA_STREAM_IS_GOOD(enmStreamState)
+ && RTTimeNanoTS() - nsStart < RT_NS_10SEC /* not really timed */ )
+ drvHstAudPaMainloopWait(pThis);
+ if (enmStreamState == PA_STREAM_READY)
+ {
+ LogFunc(("Connecting stream took %'RU64 ns\n", RTTimeNanoTS() - nsStart));
+#ifdef LOG_ENABLED
+ pStreamPA->tsStartUs = pa_rtclock_now();
+#endif
+ /*
+ * Update the buffer attributes.
+ */
+ const pa_buffer_attr *pBufAttribs = pa_stream_get_buffer_attr(pStream);
+#if 0 /* Experiment for getting tlength closer to what we requested (ADJUST_LATENCY effect).
+ Will slow down stream creation, so not pursued any further at present. */
+ if ( pCfgAcq->enmDir == PDMAUDIODIR_OUT
+ && pBufAttribs
+ && pBufAttribs->tlength < pStreamPA->BufAttr.tlength)
+ {
+ pStreamPA->BufAttr.maxlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2;
+ pStreamPA->BufAttr.tlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2;
+ LogRel(("Before pa_stream_set_buffer_attr: tlength=%#x (trying =%#x)\n", pBufAttribs->tlength, pStreamPA->BufAttr.tlength));
+ drvHstAudPaWaitFor(pThis, pa_stream_set_buffer_attr(pStream, &pStreamPA->BufAttr,
+ drvHstAudPaStreamSetBufferAttrCompletionCallback, pThis));
+ pBufAttribs = pa_stream_get_buffer_attr(pStream);
+ LogRel(("After pa_stream_set_buffer_attr: tlength=%#x\n", pBufAttribs->tlength));
+ }
+#endif
+ AssertPtr(pBufAttribs);
+ if (pBufAttribs)
+ {
+ pStreamPA->BufAttr = *pBufAttribs;
+ LogFunc(("Obtained %s buffer attributes: maxlength=%RU32 tlength=%RU32 prebuf=%RU32 minreq=%RU32 fragsize=%RU32\n",
+ pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pBufAttribs->maxlength, pBufAttribs->tlength,
+ pBufAttribs->prebuf, pBufAttribs->minreq, pBufAttribs->fragsize));
+
+ /*
+ * Convert the sample spec back to PDM speak.
+ * Note! This isn't strictly speaking needed as SampleSpec has *not* been
+ * modified since the caller converted it from pCfgReq.
+ */
+ rc = drvHstAudPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
+ pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamPA->pStream = pStream;
+ LogFlowFunc(("returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ LogRelMax(99, ("PulseAudio: Failed to get buffer attribs for stream '%s': %s (%d)\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ }
+ else
+ {
+ LogRelMax(99, ("PulseAudio: Failed to initialize stream '%s': state=%d, waited %'RU64 ns\n",
+ pszName, enmStreamState, RTTimeNanoTS() - nsStart));
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ pa_stream_disconnect(pStream);
+ }
+ else
+ {
+ LogRelMax(99, ("PulseAudio: Could not connect %s stream '%s': %s (%d/%d)\n",
+ pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext), rc));
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ pa_stream_unref(pStream);
+ Assert(RT_FAILURE_NP(rc));
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Translates a PDM channel ID to a PA channel position.
+ *
+ * @returns PA channel position, INVALID if no mapping found.
+ */
+static pa_channel_position_t drvHstAudPaConvertChannelId(uint8_t idChannel)
+{
+ switch (idChannel)
+ {
+ case PDMAUDIOCHANNELID_FRONT_LEFT: return PA_CHANNEL_POSITION_FRONT_LEFT;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT: return PA_CHANNEL_POSITION_FRONT_RIGHT;
+ case PDMAUDIOCHANNELID_FRONT_CENTER: return PA_CHANNEL_POSITION_FRONT_CENTER;
+ case PDMAUDIOCHANNELID_LFE: return PA_CHANNEL_POSITION_LFE;
+ case PDMAUDIOCHANNELID_REAR_LEFT: return PA_CHANNEL_POSITION_REAR_LEFT;
+ case PDMAUDIOCHANNELID_REAR_RIGHT: return PA_CHANNEL_POSITION_REAR_RIGHT;
+ case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+ case PDMAUDIOCHANNELID_REAR_CENTER: return PA_CHANNEL_POSITION_REAR_CENTER;
+ case PDMAUDIOCHANNELID_SIDE_LEFT: return PA_CHANNEL_POSITION_SIDE_LEFT;
+ case PDMAUDIOCHANNELID_SIDE_RIGHT: return PA_CHANNEL_POSITION_SIDE_RIGHT;
+ case PDMAUDIOCHANNELID_TOP_CENTER: return PA_CHANNEL_POSITION_TOP_CENTER;
+ case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
+ case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
+ case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
+ case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
+ case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
+ case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
+ default: return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+
+/**
+ * Translates a PA channel position to a PDM channel ID.
+ *
+ * @returns PDM channel ID, UNKNOWN if no mapping found.
+ */
+static PDMAUDIOCHANNELID drvHstAudPaConvertChannelPos(pa_channel_position_t enmChannelPos)
+{
+ switch (enmChannelPos)
+ {
+ case PA_CHANNEL_POSITION_INVALID: return PDMAUDIOCHANNELID_INVALID;
+ case PA_CHANNEL_POSITION_MONO: return PDMAUDIOCHANNELID_MONO;
+ case PA_CHANNEL_POSITION_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT;
+ case PA_CHANNEL_POSITION_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER;
+ case PA_CHANNEL_POSITION_LFE: return PDMAUDIOCHANNELID_LFE;
+ case PA_CHANNEL_POSITION_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT;
+ case PA_CHANNEL_POSITION_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT;
+ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER;
+ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER;
+ case PA_CHANNEL_POSITION_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER;
+ case PA_CHANNEL_POSITION_SIDE_LEFT: return PDMAUDIOCHANNELID_SIDE_LEFT;
+ case PA_CHANNEL_POSITION_SIDE_RIGHT: return PDMAUDIOCHANNELID_SIDE_RIGHT;
+ case PA_CHANNEL_POSITION_TOP_CENTER: return PDMAUDIOCHANNELID_TOP_CENTER;
+ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT;
+ case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT;
+ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT;
+ case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT;
+ case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT;
+ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT;
+ default: return PDMAUDIOCHANNELID_UNKNOWN;
+ }
+}
+
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
+ int rc;
+
+ /*
+ * Prepare name, sample spec and the stream instance data.
+ */
+ char szName[256];
+ RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", PDMAudioPathGetName(pCfgReq->enmPath), pThis->szStreamName);
+
+ pStreamPA->pDrv = pThis;
+ pStreamPA->pDrainOp = NULL;
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
+ pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
+ pStreamPA->SampleSpec.format = drvHstAudPaPropsToPulse(&pCfgReq->Props);
+
+ /*
+ * Initialize the channelmap. This may change the channel count.
+ */
+ AssertCompile(RT_ELEMENTS(pStreamPA->ChannelMap.map) >= PDMAUDIO_MAX_CHANNELS);
+ uint8_t const cSrcChannels = pStreamPA->ChannelMap.channels = PDMAudioPropsChannels(&pCfgReq->Props);
+ uintptr_t iDst = 0;
+ if (cSrcChannels == 1 && pCfgReq->Props.aidChannels[0] == PDMAUDIOCHANNELID_MONO)
+ pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_MONO;
+ else
+ {
+ uintptr_t iSrc;
+ for (iSrc = iDst = 0; iSrc < cSrcChannels; iSrc++)
+ {
+ pStreamPA->ChannelMap.map[iDst] = drvHstAudPaConvertChannelId(pCfgReq->Props.aidChannels[iSrc]);
+ if (pStreamPA->ChannelMap.map[iDst] != PA_CHANNEL_POSITION_INVALID)
+ iDst++;
+ else
+ {
+ LogRel2(("PulseAudio: Dropping channel #%u (%d/%s)\n", iSrc, pCfgReq->Props.aidChannels[iSrc],
+ PDMAudioChannelIdGetName((PDMAUDIOCHANNELID)pCfgReq->Props.aidChannels[iSrc])));
+ pStreamPA->ChannelMap.channels--;
+ pStreamPA->SampleSpec.channels--;
+ PDMAudioPropsSetChannels(&pCfgAcq->Props, pStreamPA->SampleSpec.channels);
+ }
+ }
+ Assert(iDst == pStreamPA->ChannelMap.channels);
+ }
+ while (iDst < RT_ELEMENTS(pStreamPA->ChannelMap.map))
+ pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_INVALID;
+
+ LogFunc(("Opening '%s', rate=%dHz, channels=%d (%d), format=%s\n", szName, pStreamPA->SampleSpec.rate,
+ pStreamPA->SampleSpec.channels, cSrcChannels, pa_sample_format_to_string(pStreamPA->SampleSpec.format)));
+
+ if (pa_sample_spec_valid(&pStreamPA->SampleSpec))
+ {
+ /*
+ * Convert the requested buffer parameters to PA bytes.
+ */
+ uint32_t const cbBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props,
+ pCfgReq->Backend.cFramesBufferSize),
+ &pStreamPA->SampleSpec);
+ uint32_t const cbPreBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props,
+ pCfgReq->Backend.cFramesPreBuffering),
+ &pStreamPA->SampleSpec);
+ uint32_t const cbSchedHint = pa_usec_to_bytes(pCfgReq->Device.cMsSchedulingHint * RT_US_1MS, &pStreamPA->SampleSpec);
+ RT_NOREF(cbBuffer, cbSchedHint, cbPreBuffer);
+
+ /*
+ * Set up buffer attributes according to the stream type.
+ */
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ /* Set maxlength to the requested buffer size. */
+ pStreamPA->BufAttr.maxlength = cbBuffer;
+
+ /* Set the fragment size according to the scheduling hint (forget
+ cFramesPeriod, it's generally rubbish on input). */
+ pStreamPA->BufAttr.fragsize = cbSchedHint;
+
+ /* (tlength, minreq and prebuf are playback only) */
+ LogRel2(("PulseAudio: Requesting: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n",
+ pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength));
+ }
+ else
+ {
+ /* Set tlength to the desired buffer size as PA doesn't have any way
+ of telling us if anything beyond tlength is writable or not (see
+ drvHstAudPaStreamGetWritableLocked for more). Because of the
+ ADJUST_LATENCY flag, this value will be adjusted down, so we'll
+ end up with less buffer than what we requested, however it should
+ probably reflect the actual latency a bit closer. Probably not
+ worth trying to adjust this via pa_stream_set_buffer_attr. */
+ pStreamPA->BufAttr.tlength = cbBuffer;
+
+ /* Set maxlength to the same as tlength as we won't ever write more
+ than tlength. */
+ pStreamPA->BufAttr.maxlength = pStreamPA->BufAttr.tlength;
+
+ /* According to vlc, pulseaudio goes berserk if the minreq is not
+ significantly smaller than half of tlength. They use a 1:3 ratio
+ between minreq and tlength. Traditionally, we've used to just
+ pass the period value here, however the quality of the incoming
+ cFramesPeriod value is so variable that just ignore it. This
+ minreq value is mainly about updating the pa_stream_writable_size
+ return value, so it makes sense that it need to be well below
+ half of the buffer length, otherwise we will think the buffer
+ is full for too long when it isn't.
+
+ The DMA scheduling hint is often a much better indicator. Just
+ to avoid generating too much IPC, limit this to 10 ms. */
+ uint32_t const cbMinUpdate = pa_usec_to_bytes(RT_US_10MS, &pStreamPA->SampleSpec);
+ pStreamPA->BufAttr.minreq = RT_MIN(RT_MAX(cbSchedHint, cbMinUpdate),
+ pStreamPA->BufAttr.tlength / 4);
+
+ /* Just pass along the requested pre-buffering size. This seems
+ typically to be unaltered by pa_stream_connect_playback. Not
+ sure if tlength is perhaps adjusted relative to it... Ratio
+ seen here is prebuf=93.75% of tlength. This isn't entirely
+ optimal as we use 50% by default (see DrvAudio) so that there
+ is equal room for the guest to run too fast and too slow. Not
+ much we can do about it w/o slowing down stream creation. */
+ pStreamPA->BufAttr.prebuf = cbPreBuffer;
+
+ /* (fragsize is capture only) */
+ LogRel2(("PulseAudio: Requesting: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength));
+ }
+
+ /*
+ * Do the actual PA stream creation.
+ */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ rc = drvHstAudPaStreamCreateLocked(pThis, pStreamPA, szName, pCfgAcq);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Set the acquired stream config according to the actual buffer
+ * attributes we got and the stream type.
+ *
+ * Note! We use maxlength for input buffer and tlength for the
+ * output buffer size. See above for why.
+ */
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ LogRel2(("PulseAudio: Got: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n",
+ pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength));
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.fragsize);
+ pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */
+ ? PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.maxlength)
+ : pCfgAcq->Backend.cFramesPeriod * 3 /* whatever */;
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+ }
+ else
+ {
+ LogRel2(("PulseAudio: Got: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength));
+ pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.minreq);
+ pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.tlength);
+ pCfgAcq->Backend.cFramesPreBuffering = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.prebuf);
+
+ LogRel2(("PulseAudio: Initial output latency is %RU64 us (%RU32 bytes)\n",
+ PDMAudioPropsBytesToMicro(&pCfgAcq->Props, pStreamPA->BufAttr.tlength), pStreamPA->BufAttr.tlength));
+ }
+
+ /*
+ * Translate back the channel mapping.
+ */
+ for (iDst = 0; iDst < pStreamPA->ChannelMap.channels; iDst++)
+ pCfgAcq->Props.aidChannels[iDst] = drvHstAudPaConvertChannelPos(pStreamPA->ChannelMap.map[iDst]);
+ while (iDst < RT_ELEMENTS(pCfgAcq->Props.aidChannels))
+ pCfgAcq->Props.aidChannels[iDst++] = PDMAUDIOCHANNELID_INVALID;
+
+ PDMAudioStrmCfgCopy(&pStreamPA->Cfg, pCfgAcq);
+ }
+ }
+ else
+ {
+ LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", szName));
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Cancel and release any pending stream requests (drain and cork/uncork).
+ *
+ * @note Caller has locked the mainloop.
+ */
+static void drvHstAudPaStreamCancelAndReleaseOperations(PDRVHSTAUDPASTREAM pStreamPA)
+{
+ if (pStreamPA->pDrainOp)
+ {
+ LogFlowFunc(("drain operation (%p) status: %d\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
+ pa_operation_cancel(pStreamPA->pDrainOp);
+ pa_operation_unref(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+
+ if (pStreamPA->pCorkOp)
+ {
+ LogFlowFunc(("cork operation (%p) status: %d\n", pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
+ pa_operation_cancel(pStreamPA->pCorkOp);
+ pa_operation_unref(pStreamPA->pCorkOp);
+ pStreamPA->pCorkOp = NULL;
+ }
+
+ if (pStreamPA->pTriggerOp)
+ {
+ LogFlowFunc(("trigger operation (%p) status: %d\n", pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
+ pa_operation_cancel(pStreamPA->pTriggerOp);
+ pa_operation_unref(pStreamPA->pTriggerOp);
+ pStreamPA->pTriggerOp = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ RT_NOREF(fImmediate);
+
+ if (pStreamPA->pStream)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
+ pa_stream_disconnect(pStreamPA->pStream);
+
+ pa_stream_unref(pStreamPA->pStream);
+ pStreamPA->pStream = NULL;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Common worker for the cork/uncork completion callbacks.
+ * @note This is fully async, so nobody is waiting for this.
+ */
+static void drvHstAudPaStreamCorkUncorkCommon(PDRVHSTAUDPASTREAM pStreamPA, int fSuccess, const char *pszOperation)
+{
+ AssertPtrReturnVoid(pStreamPA);
+ LogFlowFunc(("%s '%s': fSuccess=%RTbool\n", pszOperation, pStreamPA->Cfg.szName, fSuccess));
+
+ if (!fSuccess)
+ drvHstAudPaError(pStreamPA->pDrv, "%s stream '%s' failed", pszOperation, pStreamPA->Cfg.szName);
+
+ if (pStreamPA->pCorkOp)
+ {
+ pa_operation_unref(pStreamPA->pCorkOp);
+ pStreamPA->pCorkOp = NULL;
+ }
+}
+
+
+/**
+ * Completion callback used with pa_stream_cork(,false,).
+ */
+static void drvHstAudPaStreamUncorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ RT_NOREF(pStream);
+ drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Uncorking");
+}
+
+
+/**
+ * Completion callback used with pa_stream_cork(,true,).
+ */
+static void drvHstAudPaStreamCorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ RT_NOREF(pStream);
+ drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Corking");
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ LogFlowFunc(("\n"));
+
+ /*
+ * Uncork (start or resume playback/capture) the stream.
+ */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
+ pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 0 /*uncork it*/,
+ drvHstAudPaStreamUncorkCompletionCallback, pStreamPA);
+ LogFlowFunc(("Uncorking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
+ int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
+ : drvHstAudPaError(pThis, "pa_stream_cork('%s', 0 /*uncork it*/,,) failed", pStreamPA->Cfg.szName);
+
+ pStreamPA->offInternal = 0;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ LogFlowFunc(("\n"));
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ /*
+ * For output streams, we will ignore the request if there is a pending drain
+ * as it will cork the stream in the end.
+ */
+ if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ if (pStreamPA->pDrainOp)
+ {
+ pa_operation_state_t const enmOpState = pa_operation_get_state(pStreamPA->pDrainOp);
+ if (enmOpState == PA_OPERATION_RUNNING)
+ {
+/** @todo consider corking it immediately instead, as that's what the caller
+ * wants now... */
+ LogFlowFunc(("Drain (%p) already running on '%s', skipping.\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ return VINF_SUCCESS;
+ }
+ LogFlowFunc(("Drain (%p) not running: %d\n", pStreamPA->pDrainOp, enmOpState));
+ }
+ }
+ /*
+ * For input stream we always cork it, but we clean up the peek buffer first.
+ */
+ /** @todo r=bird: It is (probably) not technically be correct to drop the peek buffer
+ * here when we're only pausing the stream (VM paused) as it means we'll
+ * risk underruns when later resuming. */
+ else if (pStreamPA->pbPeekBuf) /** @todo Do we need to drop the peek buffer?*/
+ {
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->cbPeekBuf = 0;
+ pa_stream_drop(pStreamPA->pStream);
+ }
+
+ /*
+ * Cork (pause playback/capture) the stream.
+ */
+ drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
+ pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 1 /* cork it */,
+ drvHstAudPaStreamCorkCompletionCallback, pStreamPA);
+ LogFlowFunc(("Corking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
+ int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
+ : drvHstAudPaError(pThis, "pa_stream_cork('%s', 1 /*cork*/,,) failed", pStreamPA->Cfg.szName);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /* Same as disable. */
+ return drvHstAudPaHA_StreamDisable(pInterface, pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ /* Same as enable. */
+ return drvHstAudPaHA_StreamEnable(pInterface, pStream);
+}
+
+
+/**
+ * Pulse audio pa_stream_drain() completion callback.
+ * @note This is fully async, so nobody is waiting for this.
+ */
+static void drvHstAudPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser;
+ AssertPtrReturnVoid(pStreamPA);
+ Assert(pStreamPA->pStream == pStream);
+ LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
+
+ if (!fSuccess)
+ drvHstAudPaError(pStreamPA->pDrv, "Draining stream '%s' failed", pStreamPA->Cfg.szName);
+
+ /* Now cork the stream (doing it unconditionally atm). */
+ if (pStreamPA->pCorkOp)
+ {
+ LogFlowFunc(("Cancelling & releasing cork/uncork operation %p (state: %d)\n",
+ pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
+ pa_operation_cancel(pStreamPA->pCorkOp);
+ pa_operation_unref(pStreamPA->pCorkOp);
+ }
+
+ pStreamPA->pCorkOp = pa_stream_cork(pStream, 1 /* cork it*/, drvHstAudPaStreamCorkCompletionCallback, pStreamPA);
+ if (pStreamPA->pCorkOp)
+ LogFlowFunc(("Started cork operation %p of %s (following drain)\n", pStreamPA->pCorkOp, pStreamPA->Cfg.szName));
+ else
+ drvHstAudPaError(pStreamPA->pDrv, "pa_stream_cork failed on '%s' (following drain)", pStreamPA->Cfg.szName);
+}
+
+
+/**
+ * Callback used with pa_stream_tigger(), starts draining.
+ */
+static void drvHstAudPaStreamTriggerCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser;
+ AssertPtrReturnVoid(pStreamPA);
+ RT_NOREF(pStream);
+ LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
+
+ if (!fSuccess)
+ drvHstAudPaError(pStreamPA->pDrv, "Forcing playback before drainig '%s' failed", pStreamPA->Cfg.szName);
+
+ if (pStreamPA->pTriggerOp)
+ {
+ pa_operation_unref(pStreamPA->pTriggerOp);
+ pStreamPA->pTriggerOp = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertReturn(pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("\n"));
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ /*
+ * If there is a drain running already, don't try issue another as pulse
+ * doesn't support more than one concurrent drain per stream.
+ */
+ if (pStreamPA->pDrainOp)
+ {
+ if (pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING)
+ {
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ LogFlowFunc(("returns VINF_SUCCESS (drain already running)\n"));
+ return VINF_SUCCESS;
+ }
+ LogFlowFunc(("Releasing drain operation %p (state: %d)\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
+ pa_operation_unref(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+
+ /*
+ * Make sure pre-buffered data is played before we drain it.
+ *
+ * ASSUMES that the async stream requests are executed in the order they're
+ * issued here, so that we avoid waiting for the trigger request to complete.
+ */
+ int rc = VINF_SUCCESS;
+ if ( pStreamPA->offInternal
+ < PDMAudioPropsFramesToBytes(&pStreamPA->Cfg.Props, pStreamPA->Cfg.Backend.cFramesPreBuffering) * 2)
+ {
+ if (pStreamPA->pTriggerOp)
+ {
+ LogFlowFunc(("Cancelling & releasing trigger operation %p (state: %d)\n",
+ pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
+ pa_operation_cancel(pStreamPA->pTriggerOp);
+ pa_operation_unref(pStreamPA->pTriggerOp);
+ }
+ pStreamPA->pTriggerOp = pa_stream_trigger(pStreamPA->pStream, drvHstAudPaStreamTriggerCompletionCallback, pStreamPA);
+ if (pStreamPA->pTriggerOp)
+ LogFlowFunc(("Started tigger operation %p on %s\n", pStreamPA->pTriggerOp, pStreamPA->Cfg.szName));
+ else
+ rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_trigger failed on '%s'", pStreamPA->Cfg.szName);
+ }
+
+ /*
+ * Initiate the draining (async), will cork the stream when it completes.
+ */
+ pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHstAudPaStreamDrainCompletionCallback, pStreamPA);
+ if (pStreamPA->pDrainOp)
+ LogFlowFunc(("Started drain operation %p of %s\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
+ else
+ rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_drain failed on '%s'", pStreamPA->Cfg.szName);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudPaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertPtrReturn(pStreamPA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ /* Check PulseAudio's general status. */
+ PDMHOSTAUDIOSTREAMSTATE enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+ if (pThis->pContext)
+ {
+ pa_context_state_t const enmPaCtxState = pa_context_get_state(pThis->pContext);
+ if (PA_CONTEXT_IS_GOOD(enmPaCtxState))
+ {
+ pa_stream_state_t const enmPaStreamState = pa_stream_get_state(pStreamPA->pStream);
+ if (PA_STREAM_IS_GOOD(enmPaStreamState))
+ {
+ if (enmPaStreamState != PA_STREAM_CREATING)
+ {
+ if ( pStreamPA->Cfg.enmDir != PDMAUDIODIR_OUT
+ || pStreamPA->pDrainOp == NULL
+ || pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_RUNNING)
+ enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ else
+ enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+ }
+ else
+ enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
+ }
+ else
+ LogFunc(("non-good PA stream state: %d\n", enmPaStreamState));
+ }
+ else
+ LogFunc(("non-good PA context state: %d\n", enmPaCtxState));
+ }
+ else
+ LogFunc(("No context!\n"));
+ LogFlowFunc(("returns %s for stream '%s'\n", PDMHostAudioStreamStateGetName(enmBackendStreamState), pStreamPA->Cfg.szName));
+ return enmBackendStreamState;
+}
+
+
+/**
+ * Gets the number of bytes that can safely be written to a stream.
+ *
+ * @returns Number of writable bytes, ~(size_t)0 on error.
+ * @param pStreamPA The stream.
+ */
+DECLINLINE(uint32_t) drvHstAudPaStreamGetWritableLocked(PDRVHSTAUDPASTREAM pStreamPA)
+{
+ /* pa_stream_writable_size() returns the amount requested currently by the
+ server, we could write more than this if we liked. The documentation says
+ up to maxlength, whoever I'm not sure how that limitation is enforced or
+ what would happen if we exceed it. There seems to be no (simple) way to
+ figure out how much buffer we have left between what pa_stream_writable_size
+ returns and what maxlength indicates.
+
+ An alternative would be to guess the difference using the read and write
+ positions in the timing info, however the read position is only updated
+ when starting and stopping. In the auto update mode it's updated at a
+ sharply decreasing rate starting at 10ms and ending at 1500ms. So, not
+ all that helpful. (As long as pa_stream_writable_size returns a non-zero
+ value, though, we could just add the maxlength-tlength difference. But
+ the problem is after that.)
+
+ So, for now we just use tlength = maxlength for output streams and
+ problem solved. */
+ size_t const cbWritablePa = pa_stream_writable_size(pStreamPA->pStream);
+#if 1
+ return cbWritablePa;
+#else
+ if (cbWritablePa > 0 && cbWritablePa != (size_t)-1)
+ return cbWritablePa + (pStreamPA->BufAttr.maxlength - pStreamPA->BufAttr.tlength);
+ //const pa_timing_info * const pTimingInfo = pa_stream_get_timing_info(pStreamPA->pStream);
+ return 0;
+#endif
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ uint32_t cbWritable = 0;
+ if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
+ if (PA_STREAM_IS_GOOD(enmState))
+ {
+ size_t cbWritablePa = drvHstAudPaStreamGetWritableLocked(pStreamPA);
+ if (cbWritablePa != (size_t)-1)
+ cbWritable = cbWritablePa <= UINT32_MAX ? (uint32_t)cbWritablePa : UINT32_MAX;
+ else
+ drvHstAudPaError(pThis, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
+ }
+ else
+ drvHstAudPaError(pThis, "Non-good %s stream state for '%s' (%#x)\n",
+ PDMAudioDirGetName(pStreamPA->Cfg.enmDir), pStreamPA->Cfg.szName, enmState);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+ Log3Func(("returns %#x (%u) [max=%#RX32 min=%#RX32]\n",
+ cbWritable, cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ else
+ {
+ /* Fend off draining calls. */
+ *pcbWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+#ifdef LOG_ENABLED
+ const pa_usec_t tsNowUs = pa_rtclock_now();
+ Log3Func(("play delta: %'RI64 us; cbBuf=%#x @%#RX64\n",
+ pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal));
+ pStreamPA->tsLastReadWrittenUs = tsNowUs;
+#endif
+
+ /*
+ * Using a loop here so we can stuff the buffer as full as it gets.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbTotalWritten = 0;
+ uint32_t iLoop;
+ for (iLoop = 0; ; iLoop++)
+ {
+ size_t const cbWriteable = drvHstAudPaStreamGetWritableLocked(pStreamPA);
+ if ( cbWriteable != (size_t)-1
+ && cbWriteable >= PDMAudioPropsFrameSize(&pStreamPA->Cfg.Props))
+ {
+ uint32_t cbToWrite = (uint32_t)RT_MIN(cbWriteable, cbBuf);
+ cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamPA->Cfg.Props, cbToWrite);
+ if (pa_stream_write(pStreamPA->pStream, pvBuf, cbToWrite, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0)
+ {
+ cbTotalWritten += cbToWrite;
+ cbBuf -= cbToWrite;
+ pStreamPA->offInternal += cbToWrite;
+ if (!cbBuf)
+ break;
+ pvBuf = (uint8_t const *)pvBuf + cbToWrite;
+ Log3Func(("%#x left to write\n", cbBuf));
+ }
+ else
+ {
+ rc = drvHstAudPaError(pStreamPA->pDrv, "Failed to write to output stream");
+ break;
+ }
+ }
+ else
+ {
+ if (cbWriteable == (size_t)-1)
+ rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
+ break;
+ }
+ }
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ *pcbWritten = cbTotalWritten;
+ if (RT_SUCCESS(rc) || cbTotalWritten == 0)
+ { /* likely */ }
+ else
+ {
+ LogFunc(("Supressing %Rrc because we wrote %#x bytes\n", rc, cbTotalWritten));
+ rc = VINF_SUCCESS;
+ }
+ Log3Func(("returns %Rrc *pcbWritten=%#x iLoop=%u @%#RX64\n", rc, cbTotalWritten, iLoop, pStreamPA->offInternal));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ uint32_t cbReadable = 0;
+ if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
+ if (PA_STREAM_IS_GOOD(enmState))
+ {
+ size_t cbReadablePa = pa_stream_readable_size(pStreamPA->pStream);
+ if (cbReadablePa != (size_t)-1)
+ {
+ /* As with WASAPI on Windows, the peek buffer must be subtracted.*/
+ if (cbReadablePa >= pStreamPA->cbPeekBuf)
+ cbReadable = (uint32_t)(cbReadablePa - pStreamPA->cbPeekBuf);
+ else
+ {
+ AssertMsgFailed(("%#zx vs %#zx\n", cbReadablePa, pStreamPA->cbPeekBuf));
+ cbReadable = 0;
+ }
+ }
+ else
+ drvHstAudPaError(pThis, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
+ }
+ else
+ drvHstAudPaError(pThis, "Non-good %s stream state for '%s' (%#x)\n",
+ PDMAudioDirGetName(pStreamPA->Cfg.enmDir), pStreamPA->Cfg.szName, enmState);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+ Log3Func(("returns %#x (%u)\n", cbReadable, cbReadable));
+ return cbReadable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHstAudPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
+ PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ const pa_usec_t tsNowUs = pa_rtclock_now();
+ Log3Func(("capture delta: %'RI64 us; cbBuf=%#x @%#RX64\n",
+ pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal));
+ pStreamPA->tsLastReadWrittenUs = tsNowUs;
+#endif
+
+ /*
+ * If we have left over peek buffer space from the last call,
+ * copy out the data from there.
+ */
+ uint32_t cbTotalRead = 0;
+ if ( pStreamPA->pbPeekBuf
+ && pStreamPA->offPeekBuf < pStreamPA->cbPeekBuf)
+ {
+ uint32_t cbToCopy = pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf;
+ if (cbToCopy >= cbBuf)
+ {
+ memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbBuf);
+ pStreamPA->offPeekBuf += cbBuf;
+ pStreamPA->offInternal += cbBuf;
+ *pcbRead = cbBuf;
+
+ if (cbToCopy == cbBuf)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->cbPeekBuf = 0;
+ pa_stream_drop(pStreamPA->pStream);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+ Log3Func(("returns *pcbRead=%#x from prev peek buf (%#x/%#x) @%#RX64\n",
+ cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal));
+ return VINF_SUCCESS;
+ }
+
+ memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbToCopy);
+ cbBuf -= cbToCopy;
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbTotalRead += cbToCopy;
+ pStreamPA->offPeekBuf = pStreamPA->cbPeekBuf;
+ }
+
+ /*
+ * Copy out what we can.
+ */
+ int rc = VINF_SUCCESS;
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ while (cbBuf > 0)
+ {
+ /*
+ * Drop the old peek buffer first, if we have one.
+ */
+ if (pStreamPA->pbPeekBuf)
+ {
+ Assert(pStreamPA->offPeekBuf >= pStreamPA->cbPeekBuf);
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->cbPeekBuf = 0;
+ pa_stream_drop(pStreamPA->pStream);
+ }
+
+ /*
+ * Check if there is anything to read, the get the peek buffer for it.
+ */
+ size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
+ if (cbAvail > 0 && cbAvail != (size_t)-1)
+ {
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->cbPeekBuf = 0;
+ int rcPa = pa_stream_peek(pStreamPA->pStream, (const void **)&pStreamPA->pbPeekBuf, &pStreamPA->cbPeekBuf);
+ if (rcPa == 0)
+ {
+ if (pStreamPA->cbPeekBuf)
+ {
+ if (pStreamPA->pbPeekBuf)
+ {
+ /*
+ * We got data back. Copy it into the return buffer, return if it's full.
+ */
+ if (cbBuf < pStreamPA->cbPeekBuf)
+ {
+ memcpy(pvBuf, pStreamPA->pbPeekBuf, cbBuf);
+ cbTotalRead += cbBuf;
+ pStreamPA->offPeekBuf = cbBuf;
+ pStreamPA->offInternal += cbBuf;
+ cbBuf = 0;
+ break;
+ }
+ memcpy(pvBuf, pStreamPA->pbPeekBuf, pStreamPA->cbPeekBuf);
+ cbBuf -= pStreamPA->cbPeekBuf;
+ pvBuf = (uint8_t *)pvBuf + pStreamPA->cbPeekBuf;
+ cbTotalRead += pStreamPA->cbPeekBuf;
+ pStreamPA->offInternal += cbBuf;
+
+ pStreamPA->pbPeekBuf = NULL;
+ }
+ else
+ {
+ /*
+ * We got a hole (drop needed). We will skip it as we leave it to
+ * the device's DMA engine to fill in buffer gaps with silence.
+ */
+ LogFunc(("pa_stream_peek returned a %#zx (%zu) byte hole - skipping.\n",
+ pStreamPA->cbPeekBuf, pStreamPA->cbPeekBuf));
+ }
+ pStreamPA->cbPeekBuf = 0;
+ pa_stream_drop(pStreamPA->pStream);
+ }
+ else
+ {
+ Assert(!pStreamPA->pbPeekBuf);
+ LogFunc(("pa_stream_peek returned empty buffer\n"));
+ break;
+ }
+ }
+ else
+ {
+ rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_peek failed on '%s' (%d)", pStreamPA->Cfg.szName, rcPa);
+ pStreamPA->pbPeekBuf = NULL;
+ pStreamPA->cbPeekBuf = 0;
+ break;
+ }
+ }
+ else
+ {
+ if (cbAvail == (size_t)-1)
+ rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
+ break;
+ }
+ }
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ *pcbRead = cbTotalRead;
+ if (RT_SUCCESS(rc) || cbTotalRead == 0)
+ { /* likely */ }
+ else
+ {
+ LogFunc(("Supressing %Rrc because we're returning %#x bytes\n", rc, cbTotalRead));
+ rc = VINF_SUCCESS;
+ }
+ Log3Func(("returns %Rrc *pcbRead=%#x (%#x left, peek %#x/%#x) @%#RX64\n",
+ rc, cbTotalRead, cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal));
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHstAudPaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ AssertPtrReturn(pInterface, NULL);
+ AssertPtrReturn(pszIID, NULL);
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG *
+*********************************************************************************************************************************/
+
+/**
+ * Destructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvHstAudPaDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
+ LogFlowFuncEnter();
+
+ if (pThis->pMainLoop)
+ pa_threaded_mainloop_stop(pThis->pMainLoop);
+
+ if (pThis->pContext)
+ {
+ pa_context_disconnect(pThis->pContext);
+ pa_context_unref(pThis->pContext);
+ pThis->pContext = NULL;
+ }
+
+ if (pThis->pMainLoop)
+ {
+ pa_threaded_mainloop_free(pThis->pMainLoop);
+ pThis->pMainLoop = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Pulse audio callback for context status changes, init variant.
+ *
+ * Signalls our event semaphore so we can do a timed wait from
+ * drvHstAudPaConstruct().
+ */
+static void drvHstAudPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser)
+{
+ AssertPtrReturnVoid(pCtx);
+ PDRVHSTAUDPASTATECHGCTX pStateChgCtx = (PDRVHSTAUDPASTATECHGCTX)pvUser;
+ pa_context_state_t enmCtxState = pa_context_get_state(pCtx);
+ switch (enmCtxState)
+ {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ AssertPtrReturnVoid(pStateChgCtx);
+ pStateChgCtx->enmCtxState = enmCtxState;
+ RTSemEventSignal(pStateChgCtx->hEvtInit);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Constructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHstAudPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogRel(("Audio: Initializing PulseAudio driver\n"));
+
+ /*
+ * Initialize instance data.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHstAudPaQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHstAudPaHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = drvHstAudPaHA_GetDevices;
+ pThis->IHostAudio.pfnSetDevice = drvHstAudPaHA_SetDevice;
+ pThis->IHostAudio.pfnGetStatus = drvHstAudPaHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
+ pThis->IHostAudio.pfnStreamConfigHint = NULL;
+ pThis->IHostAudio.pfnStreamCreate = drvHstAudPaHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = NULL;
+ pThis->IHostAudio.pfnStreamDestroy = drvHstAudPaHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
+ pThis->IHostAudio.pfnStreamEnable = drvHstAudPaHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHstAudPaHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHstAudPaHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHstAudPaHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHstAudPaHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvHstAudPaHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = NULL;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHstAudPaHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHstAudPaHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHstAudPaHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHstAudPaHA_StreamCapture;
+
+ /*
+ * Read configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|InputDeviceID|OutputDeviceID", "");
+ int rc = pHlp->pfnCFGMQueryString(pCfg, "VmName", pThis->szStreamName, sizeof(pThis->szStreamName));
+ AssertMsgRCReturn(rc, ("Confguration error: No/bad \"VmName\" value, rc=%Rrc\n", rc), rc);
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), "");
+ AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc);
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), "");
+ AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc);
+
+ /*
+ * Query the notification interface from the driver/device above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /*
+ * Load the pulse audio library.
+ */
+ LogRel2(("PulseAudio: Loading PulseAudio shared library ...\n"));
+ rc = audioLoadPulseLib();
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("PulseAudio: PulseAudio shared library loaded\n"));
+ LogRel(("PulseAudio: Using version %s\n", pa_get_library_version()));
+ }
+ else
+ {
+ LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
+ return rc;
+ }
+
+ LogRel2(("PulseAudio: Starting PulseAudio main loop ...\n"));
+
+ /*
+ * Set up the basic pulse audio bits (remember the destructore is always called).
+ */
+ //pThis->fAbortLoop = false;
+ pThis->pMainLoop = pa_threaded_mainloop_new();
+ if (!pThis->pMainLoop)
+ {
+ LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
+ return VERR_NO_MEMORY;
+ }
+
+ pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox");
+ if (!pThis->pContext)
+ {
+ LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
+ return VERR_NO_MEMORY;
+ }
+
+ if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
+ {
+ LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ LogRel2(("PulseAudio: Started PulseAudio main loop, connecting to server ...\n"));
+
+ /*
+ * Connect to the pulse audio server.
+ *
+ * We install an init state callback so we can do a timed wait in case
+ * connecting to the pulseaudio server should take too long.
+ */
+ pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
+ pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED;
+ rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit);
+ AssertLogRelRCReturn(rc, rc);
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx);
+ if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL))
+ {
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY)
+ {
+ /* Install the main state changed callback to know if something happens to our acquired context. */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChanged, pThis /* pvUserData */);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+ else
+ {
+ LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc));
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ }
+ else
+ {
+ LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc));
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ }
+ else
+ {
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */
+ }
+
+ RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit);
+ pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
+
+ /*
+ * Register statistics.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("PulseAudio: Connected to PulseAudio server\n"));
+
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatOverruns, STAMTYPE_COUNTER, "Overruns", STAMUNIT_OCCURENCES,
+ "Pulse-server side buffer overruns (all streams)");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatUnderruns, STAMTYPE_COUNTER, "Underruns", STAMUNIT_OCCURENCES,
+ "Pulse-server side buffer underruns (all streams)");
+ }
+
+ LogRel2(("PulseAudio: Initialization ended with %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Pulse audio driver registration record.
+ */
+const PDMDRVREG g_DrvHostPulseAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "PulseAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Pulse Audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHSTAUDPA),
+ /* pfnConstruct */
+ drvHstAudPaConstruct,
+ /* pfnDestruct */
+ drvHstAudPaDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp
new file mode 100644
index 00000000..bcaf5a18
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.cpp
@@ -0,0 +1,375 @@
+/* $Id: DrvHostAudioPulseAudioStubs.cpp $ */
+/** @file
+ * Stubs for libpulse.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+#include <VBox/log.h>
+#include <iprt/once.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "DrvHostAudioPulseAudioStubs.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBOX_PULSE_LIB "libpulse.so.0"
+
+#define PROXY_STUB(function, rettype, signature, shortsig) \
+ static rettype (*g_pfn_ ## function) signature; \
+ \
+ extern "C" rettype VBox_##function signature; \
+ rettype VBox_##function signature \
+ { \
+ return g_pfn_ ## function shortsig; \
+ }
+
+#define PROXY_STUB_VOID(function, signature, shortsig) \
+ static void (*g_pfn_ ## function) signature; \
+ \
+ extern "C" void VBox_##function signature; \
+ void VBox_##function signature \
+ { \
+ g_pfn_ ## function shortsig; \
+ }
+
+PROXY_STUB (pa_bytes_per_second, size_t,
+ (const pa_sample_spec *spec),
+ (spec))
+PROXY_STUB (pa_bytes_to_usec, pa_usec_t,
+ (uint64_t l, const pa_sample_spec *spec),
+ (l, spec))
+PROXY_STUB (pa_channel_map_init_auto, pa_channel_map*,
+ (pa_channel_map *m, unsigned channels, pa_channel_map_def_t def),
+ (m, channels, def))
+
+PROXY_STUB (pa_context_connect, int,
+ (pa_context *c, const char *server, pa_context_flags_t flags,
+ const pa_spawn_api *api),
+ (c, server, flags, api))
+PROXY_STUB_VOID(pa_context_disconnect,
+ (pa_context *c),
+ (c))
+PROXY_STUB (pa_context_get_server_info, pa_operation*,
+ (pa_context *c, pa_server_info_cb_t cb, void *userdata),
+ (c, cb, userdata))
+PROXY_STUB (pa_context_get_sink_info_by_name, pa_operation*,
+ (pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata),
+ (c, name, cb, userdata))
+PROXY_STUB (pa_context_get_sink_info_list, pa_operation *,
+ (pa_context *c, pa_sink_info_cb_t cb, void *userdata),
+ (c, cb, userdata))
+PROXY_STUB (pa_context_get_source_info_by_name, pa_operation*,
+ (pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata),
+ (c, name, cb, userdata))
+PROXY_STUB (pa_context_get_source_info_list, pa_operation *,
+ (pa_context *c, pa_source_info_cb_t cb, void *userdata),
+ (c, cb, userdata))
+PROXY_STUB (pa_context_get_state, pa_context_state_t,
+ (pa_context *c),
+ (c))
+PROXY_STUB_VOID(pa_context_unref,
+ (pa_context *c),
+ (c))
+PROXY_STUB (pa_context_errno, int,
+ (pa_context *c),
+ (c))
+PROXY_STUB (pa_context_new, pa_context*,
+ (pa_mainloop_api *mainloop, const char *name),
+ (mainloop, name))
+PROXY_STUB_VOID(pa_context_set_state_callback,
+ (pa_context *c, pa_context_notify_cb_t cb, void *userdata),
+ (c, cb, userdata))
+
+PROXY_STUB (pa_frame_size, size_t,
+ (const pa_sample_spec *spec),
+ (spec))
+PROXY_STUB (pa_get_library_version, const char *, (void), ())
+PROXY_STUB_VOID(pa_operation_unref,
+ (pa_operation *o),
+ (o))
+PROXY_STUB (pa_operation_get_state, pa_operation_state_t,
+ (pa_operation *o),
+ (o))
+PROXY_STUB_VOID(pa_operation_cancel,
+ (pa_operation *o),
+ (o))
+
+PROXY_STUB (pa_rtclock_now, pa_usec_t,
+ (void),
+ ())
+PROXY_STUB (pa_sample_format_to_string, const char*,
+ (pa_sample_format_t f),
+ (f))
+PROXY_STUB (pa_sample_spec_valid, int,
+ (const pa_sample_spec *spec),
+ (spec))
+PROXY_STUB (pa_strerror, const char*,
+ (int error),
+ (error))
+
+#if PA_PROTOCOL_VERSION >= 16
+PROXY_STUB (pa_stream_connect_playback, int,
+ (pa_stream *s, const char *dev, const pa_buffer_attr *attr,
+ pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream),
+ (s, dev, attr, flags, volume, sync_stream))
+#else
+PROXY_STUB (pa_stream_connect_playback, int,
+ (pa_stream *s, const char *dev, const pa_buffer_attr *attr,
+ pa_stream_flags_t flags, pa_cvolume *volume, pa_stream *sync_stream),
+ (s, dev, attr, flags, volume, sync_stream))
+#endif
+PROXY_STUB (pa_stream_connect_record, int,
+ (pa_stream *s, const char *dev, const pa_buffer_attr *attr,
+ pa_stream_flags_t flags),
+ (s, dev, attr, flags))
+PROXY_STUB (pa_stream_disconnect, int,
+ (pa_stream *s),
+ (s))
+PROXY_STUB (pa_stream_get_sample_spec, const pa_sample_spec*,
+ (pa_stream *s),
+ (s))
+PROXY_STUB_VOID(pa_stream_set_latency_update_callback,
+ (pa_stream *p, pa_stream_notify_cb_t cb, void *userdata),
+ (p, cb, userdata))
+PROXY_STUB (pa_stream_write, int,
+ (pa_stream *p, const void *data, size_t bytes, pa_free_cb_t free_cb,
+ int64_t offset, pa_seek_mode_t seek),
+ (p, data, bytes, free_cb, offset, seek))
+PROXY_STUB_VOID(pa_stream_unref,
+ (pa_stream *s),
+ (s))
+PROXY_STUB (pa_stream_get_state, pa_stream_state_t,
+ (pa_stream *p),
+ (p))
+PROXY_STUB (pa_stream_get_latency, int,
+ (pa_stream *s, pa_usec_t *r_usec, int *negative),
+ (s, r_usec, negative))
+PROXY_STUB (pa_stream_get_timing_info, pa_timing_info*,
+ (pa_stream *s),
+ (s))
+PROXY_STUB (pa_stream_readable_size, size_t,
+ (pa_stream *p),
+ (p))
+PROXY_STUB (pa_stream_set_buffer_attr, pa_operation *,
+ (pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata),
+ (s, attr, cb, userdata))
+PROXY_STUB_VOID(pa_stream_set_state_callback,
+ (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB_VOID(pa_stream_set_underflow_callback,
+ (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB_VOID(pa_stream_set_overflow_callback,
+ (pa_stream *s, pa_stream_notify_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB_VOID(pa_stream_set_write_callback,
+ (pa_stream *s, pa_stream_request_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB (pa_stream_flush, pa_operation*,
+ (pa_stream *s, pa_stream_success_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB (pa_stream_drain, pa_operation*,
+ (pa_stream *s, pa_stream_success_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB (pa_stream_trigger, pa_operation*,
+ (pa_stream *s, pa_stream_success_cb_t cb, void *userdata),
+ (s, cb, userdata))
+PROXY_STUB (pa_stream_new, pa_stream*,
+ (pa_context *c, const char *name, const pa_sample_spec *ss,
+ const pa_channel_map *map),
+ (c, name, ss, map))
+PROXY_STUB (pa_stream_get_buffer_attr, const pa_buffer_attr*,
+ (pa_stream *s),
+ (s))
+PROXY_STUB (pa_stream_peek, int,
+ (pa_stream *p, const void **data, size_t *bytes),
+ (p, data, bytes))
+PROXY_STUB (pa_stream_cork, pa_operation*,
+ (pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata),
+ (s, b, cb, userdata))
+PROXY_STUB (pa_stream_drop, int,
+ (pa_stream *p),
+ (p))
+PROXY_STUB (pa_stream_writable_size, size_t,
+ (pa_stream *p),
+ (p))
+
+PROXY_STUB_VOID(pa_threaded_mainloop_stop,
+ (pa_threaded_mainloop *m),
+ (m))
+PROXY_STUB (pa_threaded_mainloop_get_api, pa_mainloop_api*,
+ (pa_threaded_mainloop *m),
+ (m))
+PROXY_STUB_VOID(pa_threaded_mainloop_free,
+ (pa_threaded_mainloop* m),
+ (m))
+PROXY_STUB_VOID(pa_threaded_mainloop_signal,
+ (pa_threaded_mainloop *m, int wait_for_accept),
+ (m, wait_for_accept))
+PROXY_STUB_VOID(pa_threaded_mainloop_unlock,
+ (pa_threaded_mainloop *m),
+ (m))
+PROXY_STUB (pa_threaded_mainloop_new, pa_threaded_mainloop *,
+ (void),
+ ())
+PROXY_STUB_VOID(pa_threaded_mainloop_wait,
+ (pa_threaded_mainloop *m),
+ (m))
+PROXY_STUB (pa_threaded_mainloop_start, int,
+ (pa_threaded_mainloop *m),
+ (m))
+PROXY_STUB_VOID(pa_threaded_mainloop_lock,
+ (pa_threaded_mainloop *m),
+ (m))
+
+PROXY_STUB (pa_usec_to_bytes, size_t,
+ (pa_usec_t t, const pa_sample_spec *spec),
+ (t, spec))
+
+#define FUNC_ENTRY(function) { #function , (void (**)(void)) & g_pfn_ ## function }
+static struct
+{
+ const char *pszName;
+ void (**pfn)(void);
+} const g_aImportedFunctions[] =
+{
+ FUNC_ENTRY(pa_bytes_per_second),
+ FUNC_ENTRY(pa_bytes_to_usec),
+ FUNC_ENTRY(pa_channel_map_init_auto),
+
+ FUNC_ENTRY(pa_context_connect),
+ FUNC_ENTRY(pa_context_disconnect),
+ FUNC_ENTRY(pa_context_get_server_info),
+ FUNC_ENTRY(pa_context_get_sink_info_by_name),
+ FUNC_ENTRY(pa_context_get_sink_info_list),
+ FUNC_ENTRY(pa_context_get_source_info_by_name),
+ FUNC_ENTRY(pa_context_get_source_info_list),
+ FUNC_ENTRY(pa_context_get_state),
+ FUNC_ENTRY(pa_context_unref),
+ FUNC_ENTRY(pa_context_errno),
+ FUNC_ENTRY(pa_context_new),
+ FUNC_ENTRY(pa_context_set_state_callback),
+
+ FUNC_ENTRY(pa_frame_size),
+ FUNC_ENTRY(pa_get_library_version),
+ FUNC_ENTRY(pa_operation_unref),
+ FUNC_ENTRY(pa_operation_get_state),
+ FUNC_ENTRY(pa_operation_cancel),
+ FUNC_ENTRY(pa_rtclock_now),
+ FUNC_ENTRY(pa_sample_format_to_string),
+ FUNC_ENTRY(pa_sample_spec_valid),
+ FUNC_ENTRY(pa_strerror),
+
+ FUNC_ENTRY(pa_stream_connect_playback),
+ FUNC_ENTRY(pa_stream_connect_record),
+ FUNC_ENTRY(pa_stream_disconnect),
+ FUNC_ENTRY(pa_stream_get_sample_spec),
+ FUNC_ENTRY(pa_stream_set_latency_update_callback),
+ FUNC_ENTRY(pa_stream_write),
+ FUNC_ENTRY(pa_stream_unref),
+ FUNC_ENTRY(pa_stream_get_state),
+ FUNC_ENTRY(pa_stream_get_latency),
+ FUNC_ENTRY(pa_stream_get_timing_info),
+ FUNC_ENTRY(pa_stream_readable_size),
+ FUNC_ENTRY(pa_stream_set_buffer_attr),
+ FUNC_ENTRY(pa_stream_set_state_callback),
+ FUNC_ENTRY(pa_stream_set_underflow_callback),
+ FUNC_ENTRY(pa_stream_set_overflow_callback),
+ FUNC_ENTRY(pa_stream_set_write_callback),
+ FUNC_ENTRY(pa_stream_flush),
+ FUNC_ENTRY(pa_stream_drain),
+ FUNC_ENTRY(pa_stream_trigger),
+ FUNC_ENTRY(pa_stream_new),
+ FUNC_ENTRY(pa_stream_get_buffer_attr),
+ FUNC_ENTRY(pa_stream_peek),
+ FUNC_ENTRY(pa_stream_cork),
+ FUNC_ENTRY(pa_stream_drop),
+ FUNC_ENTRY(pa_stream_writable_size),
+
+ FUNC_ENTRY(pa_threaded_mainloop_stop),
+ FUNC_ENTRY(pa_threaded_mainloop_get_api),
+ FUNC_ENTRY(pa_threaded_mainloop_free),
+ FUNC_ENTRY(pa_threaded_mainloop_signal),
+ FUNC_ENTRY(pa_threaded_mainloop_unlock),
+ FUNC_ENTRY(pa_threaded_mainloop_new),
+ FUNC_ENTRY(pa_threaded_mainloop_wait),
+ FUNC_ENTRY(pa_threaded_mainloop_start),
+ FUNC_ENTRY(pa_threaded_mainloop_lock),
+
+ FUNC_ENTRY(pa_usec_to_bytes)
+};
+#undef FUNC_ENTRY
+
+/** Init once. */
+static RTONCE g_PulseAudioLibInitOnce = RTONCE_INITIALIZER;
+
+/** @callback_method_impl{FNRTONCE} */
+static DECLCALLBACK(int32_t) drvHostAudioPulseLibInitOnce(void *pvUser)
+{
+ RT_NOREF(pvUser);
+ LogFlowFunc(("\n"));
+
+ RTLDRMOD hMod = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystemEx(VBOX_PULSE_LIB, RTLDRLOAD_FLAGS_NO_UNLOAD, &hMod);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aImportedFunctions); i++)
+ {
+ rc = RTLdrGetSymbol(hMod, g_aImportedFunctions[i].pszName, (void **)g_aImportedFunctions[i].pfn);
+ if (RT_FAILURE(rc))
+ {
+ LogRelFunc(("Failed to resolve function #%u: '%s' (%Rrc)\n", i, g_aImportedFunctions[i].pszName, rc));
+ break;
+ }
+ }
+
+ RTLdrClose(hMod);
+ }
+ else
+ LogRelFunc(("Failed to load library %s: %Rrc\n", VBOX_PULSE_LIB, rc));
+ return rc;
+}
+
+/**
+ * Try to dynamically load the PulseAudio libraries.
+ *
+ * @returns VBox status code.
+ */
+int audioLoadPulseLib(void)
+{
+ LogFlowFunc(("\n"));
+ return RTOnce(&g_PulseAudioLibInitOnce, drvHostAudioPulseLibInitOnce, NULL);
+}
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h
new file mode 100644
index 00000000..03ebb93a
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubs.h
@@ -0,0 +1,41 @@
+/* $Id: DrvHostAudioPulseAudioStubs.h $ */
+/** @file
+ * Stubs for libpulse.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h
+#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/cdefs.h>
+
+RT_C_DECLS_BEGIN
+extern int audioLoadPulseLib(void);
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubs_h */
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h
new file mode 100644
index 00000000..4b6c52de
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioPulseAudioStubsMangling.h
@@ -0,0 +1,105 @@
+/* $Id: DrvHostAudioPulseAudioStubsMangling.h $ */
+/** @file
+ * Mangle libpulse symbols.
+ *
+ * This is necessary on hosts which don't support the -fvisibility gcc switch.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h
+#define VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define PULSE_MANGLER(symbol) VBox_##symbol
+
+#define pa_bytes_per_second PULSE_MANGLER(pa_bytes_per_second)
+#define pa_bytes_to_usec PULSE_MANGLER(pa_bytes_to_usec)
+#define pa_channel_map_init_auto PULSE_MANGLER(pa_channel_map_init_auto)
+
+#define pa_context_connect PULSE_MANGLER(pa_context_connect)
+#define pa_context_disconnect PULSE_MANGLER(pa_context_disconnect)
+#define pa_context_get_server_info PULSE_MANGLER(pa_context_get_server_info)
+#define pa_context_get_sink_info_by_name PULSE_MANGLER(pa_context_get_sink_info_by_name)
+#define pa_context_get_sink_info_list PULSE_MANGLER(pa_context_get_sink_info_list)
+#define pa_context_get_source_info_by_name PULSE_MANGLER(pa_context_get_source_info_by_name)
+#define pa_context_get_source_info_list PULSE_MANGLER(pa_context_get_source_info_list)
+#define pa_context_get_state PULSE_MANGLER(pa_context_get_state)
+#define pa_context_unref PULSE_MANGLER(pa_context_unref)
+#define pa_context_errno PULSE_MANGLER(pa_context_errno)
+#define pa_context_new PULSE_MANGLER(pa_context_new)
+#define pa_context_set_state_callback PULSE_MANGLER(pa_context_set_state_callback)
+
+#define pa_frame_size PULSE_MANGLER(pa_frame_size)
+#define pa_get_library_version PULSE_MANGLER(pa_get_library_version)
+#define pa_operation_unref PULSE_MANGLER(pa_operation_unref)
+#define pa_operation_get_state PULSE_MANGLER(pa_operation_get_state)
+#define pa_operation_cancel PULSE_MANGLER(pa_operation_cancel)
+#define pa_rtclock_now PULSE_MANGLER(pa_rtclock_now)
+#define pa_sample_format_to_string PULSE_MANGLER(pa_sample_format_to_string)
+#define pa_sample_spec_valid PULSE_MANGLER(pa_sample_spec_valid)
+
+#define pa_stream_connect_playback PULSE_MANGLER(pa_stream_connect_playback)
+#define pa_stream_connect_record PULSE_MANGLER(pa_stream_connect_record)
+#define pa_stream_cork PULSE_MANGLER(pa_stream_cork)
+#define pa_stream_disconnect PULSE_MANGLER(pa_stream_disconnect)
+#define pa_stream_drop PULSE_MANGLER(pa_stream_drop)
+#define pa_stream_get_sample_spec PULSE_MANGLER(pa_stream_get_sample_spec)
+#define pa_stream_set_latency_update_callback PULSE_MANGLER(pa_stream_set_latency_update_callback)
+#define pa_stream_write PULSE_MANGLER(pa_stream_write)
+#define pa_stream_unref PULSE_MANGLER(pa_stream_unref)
+#define pa_stream_get_state PULSE_MANGLER(pa_stream_get_state)
+#define pa_stream_get_latency PULSE_MANGLER(pa_stream_get_latency)
+#define pa_stream_get_timing_info PULSE_MANGLER(pa_stream_get_timing_info)
+#define pa_stream_set_buffer_attr PULSE_MANGLER(pa_stream_set_buffer_attr)
+#define pa_stream_set_state_callback PULSE_MANGLER(pa_stream_set_state_callback)
+#define pa_stream_set_underflow_callback PULSE_MANGLER(pa_stream_set_underflow_callback)
+#define pa_stream_set_overflow_callback PULSE_MANGLER(pa_stream_set_overflow_callback)
+#define pa_stream_set_write_callback PULSE_MANGLER(pa_stream_set_write_callback)
+#define pa_stream_flush PULSE_MANGLER(pa_stream_flush)
+#define pa_stream_drain PULSE_MANGLER(pa_stream_drain)
+#define pa_stream_trigger PULSE_MANGLER(pa_stream_trigger)
+#define pa_stream_new PULSE_MANGLER(pa_stream_new)
+#define pa_stream_get_buffer_attr PULSE_MANGLER(pa_stream_get_buffer_attr)
+#define pa_stream_peek PULSE_MANGLER(pa_stream_peek)
+#define pa_stream_readable_size PULSE_MANGLER(pa_stream_readable_size)
+#define pa_stream_writable_size PULSE_MANGLER(pa_stream_writable_size)
+
+#define pa_strerror PULSE_MANGLER(pa_strerror)
+
+#define pa_threaded_mainloop_stop PULSE_MANGLER(pa_threaded_mainloop_stop)
+#define pa_threaded_mainloop_get_api PULSE_MANGLER(pa_threaded_mainloop_get_api)
+#define pa_threaded_mainloop_free PULSE_MANGLER(pa_threaded_mainloop_free)
+#define pa_threaded_mainloop_signal PULSE_MANGLER(pa_threaded_mainloop_signal)
+#define pa_threaded_mainloop_unlock PULSE_MANGLER(pa_threaded_mainloop_unlock)
+#define pa_threaded_mainloop_new PULSE_MANGLER(pa_threaded_mainloop_new)
+#define pa_threaded_mainloop_wait PULSE_MANGLER(pa_threaded_mainloop_wait)
+#define pa_threaded_mainloop_start PULSE_MANGLER(pa_threaded_mainloop_start)
+#define pa_threaded_mainloop_lock PULSE_MANGLER(pa_threaded_mainloop_lock)
+
+#define pa_usec_to_bytes PULSE_MANGLER(pa_usec_to_bytes)
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvHostAudioPulseAudioStubsMangling_h */
+
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
+};
+
diff --git a/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp b/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp
new file mode 100644
index 00000000..d4468246
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp
@@ -0,0 +1,3376 @@
+/* $Id: DrvHostAudioWasApi.cpp $ */
+/** @file
+ * Host audio driver - Windows Audio Session API.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+/*#define INITGUID - defined in VBoxhostAudioDSound.cpp already */
+#include <VBox/log.h>
+#include <iprt/win/windows.h>
+#include <Mmdeviceapi.h>
+#include <iprt/win/audioclient.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <AudioSessionTypes.h>
+#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
+# define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY UINT32_C(0x08000000)
+#endif
+#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
+# define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM UINT32_C(0x80000000)
+#endif
+
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+#include <iprt/utf16.h>
+#include <iprt/uuid.h>
+
+#include <new> /* std::bad_alloc */
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Max GetCurrentPadding value we accept (to make sure it's safe to convert to bytes). */
+#define VBOX_WASAPI_MAX_PADDING UINT32_C(0x007fffff)
+
+/** Maximum number of cached device configs in each direction.
+ * The number 4 was picked at random. */
+#define VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES 4
+
+#if 0
+/** @name WM_DRVHOSTAUDIOWAS_XXX - Worker thread messages.
+ * @{ */
+#define WM_DRVHOSTAUDIOWAS_PURGE_CACHE (WM_APP + 3)
+/** @} */
+#endif
+
+
+/** @name DRVHOSTAUDIOWAS_DO_XXX - Worker thread operations.
+ * @{ */
+#define DRVHOSTAUDIOWAS_DO_PURGE_CACHE ((uintptr_t)0x49f37300 + 1)
+#define DRVHOSTAUDIOWAS_DO_PRUNE_CACHE ((uintptr_t)0x49f37300 + 2)
+#define DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH ((uintptr_t)0x49f37300 + 3)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+class DrvHostAudioWasMmNotifyClient;
+
+/** Pointer to the cache entry for a host audio device (+dir). */
+typedef struct DRVHOSTAUDIOWASCACHEDEV *PDRVHOSTAUDIOWASCACHEDEV;
+
+/**
+ * Cached pre-initialized audio client for a device.
+ *
+ * The activation and initialization of an IAudioClient has been observed to be
+ * very very slow (> 100 ms) and not suitable to be done on an EMT. So, we'll
+ * pre-initialize the device clients at construction time and when the default
+ * device changes to try avoid this problem.
+ *
+ * A client is returned to the cache after we're done with it, provided it still
+ * works fine.
+ */
+typedef struct DRVHOSTAUDIOWASCACHEDEVCFG
+{
+ /** Entry in DRVHOSTAUDIOWASCACHEDEV::ConfigList. */
+ RTLISTNODE ListEntry;
+ /** The device. */
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry;
+ /** The cached audio client. */
+ IAudioClient *pIAudioClient;
+ /** Output streams: The render client interface. */
+ IAudioRenderClient *pIAudioRenderClient;
+ /** Input streams: The capture client interface. */
+ IAudioCaptureClient *pIAudioCaptureClient;
+ /** The configuration. */
+ PDMAUDIOPCMPROPS Props;
+ /** The buffer size in frames. */
+ uint32_t cFramesBufferSize;
+ /** The device/whatever period in frames. */
+ uint32_t cFramesPeriod;
+ /** The setup status code.
+ * This is set to VERR_AUDIO_STREAM_INIT_IN_PROGRESS while the asynchronous
+ * initialization is still running. */
+ int volatile rcSetup;
+ /** Creation timestamp (just for reference). */
+ uint64_t nsCreated;
+ /** Init complete timestamp (just for reference). */
+ uint64_t nsInited;
+ /** When it was last used. */
+ uint64_t nsLastUsed;
+ /** The stringified properties. */
+ char szProps[32];
+} DRVHOSTAUDIOWASCACHEDEVCFG;
+/** Pointer to a pre-initialized audio client. */
+typedef DRVHOSTAUDIOWASCACHEDEVCFG *PDRVHOSTAUDIOWASCACHEDEVCFG;
+
+/**
+ * Per audio device (+ direction) cache entry.
+ */
+typedef struct DRVHOSTAUDIOWASCACHEDEV
+{
+ /** Entry in DRVHOSTAUDIOWAS::CacheHead. */
+ RTLISTNODE ListEntry;
+ /** The MM device associated with the stream. */
+ IMMDevice *pIDevice;
+ /** The direction of the device. */
+ PDMAUDIODIR enmDir;
+#if 0 /* According to https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a,
+ these were always support just missing from the SDK. */
+ /** Support for AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM: -1=unknown, 0=no, 1=yes. */
+ int8_t fSupportsAutoConvertPcm;
+ /** Support for AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY: -1=unknown, 0=no, 1=yes. */
+ int8_t fSupportsSrcDefaultQuality;
+#endif
+ /** List of cached configurations (DRVHOSTAUDIOWASCACHEDEVCFG). */
+ RTLISTANCHOR ConfigList;
+ /** The device ID length in RTUTF16 units. */
+ size_t cwcDevId;
+ /** The device ID. */
+ RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY];
+} DRVHOSTAUDIOWASCACHEDEV;
+
+
+/**
+ * Data for a WASABI stream.
+ */
+typedef struct DRVHOSTAUDIOWASSTREAM
+{
+ /** Common part. */
+ PDMAUDIOBACKENDSTREAM Core;
+
+ /** Entry in DRVHOSTAUDIOWAS::StreamHead. */
+ RTLISTNODE ListEntry;
+ /** The stream's acquired configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+ /** Cache entry to be relased when destroying the stream. */
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
+
+ /** Set if the stream is enabled. */
+ bool fEnabled;
+ /** Set if the stream is started (playing/capturing). */
+ bool fStarted;
+ /** Set if the stream is draining (output only). */
+ bool fDraining;
+ /** Set if we should restart the stream on resume (saved pause state). */
+ bool fRestartOnResume;
+ /** Set if we're switching to a new output/input device. */
+ bool fSwitchingDevice;
+
+ /** The RTTimeMilliTS() deadline for the draining of this stream (output). */
+ uint64_t msDrainDeadline;
+ /** Internal stream offset (bytes). */
+ uint64_t offInternal;
+ /** The RTTimeMilliTS() at the end of the last transfer. */
+ uint64_t msLastTransfer;
+
+ /** Input: Current capture buffer (advanced as we read). */
+ uint8_t *pbCapture;
+ /** Input: The number of bytes left in the current capture buffer. */
+ uint32_t cbCapture;
+ /** Input: The full size of what pbCapture is part of (for ReleaseBuffer). */
+ uint32_t cFramesCaptureToRelease;
+
+ /** Critical section protecting: . */
+ RTCRITSECT CritSect;
+ /** Buffer that drvHostWasStreamStatusString uses. */
+ char szStatus[128];
+} DRVHOSTAUDIOWASSTREAM;
+/** Pointer to a WASABI stream. */
+typedef DRVHOSTAUDIOWASSTREAM *PDRVHOSTAUDIOWASSTREAM;
+
+
+/**
+ * WASAPI-specific device entry.
+ */
+typedef struct DRVHOSTAUDIOWASDEV
+{
+ /** The core structure. */
+ PDMAUDIOHOSTDEV Core;
+ /** The device ID (flexible length). */
+ RTUTF16 wszDevId[RT_FLEXIBLE_ARRAY];
+} DRVHOSTAUDIOWASDEV;
+/** Pointer to a DirectSound device entry. */
+typedef DRVHOSTAUDIOWASDEV *PDRVHOSTAUDIOWASDEV;
+
+
+/**
+ * Data for a WASAPI host audio instance.
+ */
+typedef struct DRVHOSTAUDIOWAS
+{
+ /** The audio host audio interface we export. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Pointer to the PDM driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Audio device enumerator instance that we use for getting the default
+ * devices (or specific ones if overriden by config). Also used for
+ * implementing enumeration. */
+ IMMDeviceEnumerator *pIEnumerator;
+ /** The upwards interface. */
+ PPDMIHOSTAUDIOPORT pIHostAudioPort;
+ /** The output device ID, NULL for default.
+ * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */
+ PRTUTF16 pwszOutputDevId;
+ /** The input device ID, NULL for default.
+ * Protected by DrvHostAudioWasMmNotifyClient::m_CritSect. */
+ PRTUTF16 pwszInputDevId;
+
+ /** Pointer to the MM notification client instance. */
+ DrvHostAudioWasMmNotifyClient *pNotifyClient;
+ /** The input device to use. This can be NULL if there wasn't a suitable one
+ * around when we last looked or if it got removed/disabled/whatever.
+ * All access must be done inside the pNotifyClient critsect. */
+ IMMDevice *pIDeviceInput;
+ /** The output device to use. This can be NULL if there wasn't a suitable one
+ * around when we last looked or if it got removed/disabled/whatever.
+ * All access must be done inside the pNotifyClient critsect. */
+ IMMDevice *pIDeviceOutput;
+
+ /** List of streams (DRVHOSTAUDIOWASSTREAM).
+ * Requires CritSect ownership. */
+ RTLISTANCHOR StreamHead;
+ /** Serializing access to StreamHead. */
+ RTCRITSECTRW CritSectStreamList;
+
+ /** List of cached devices (DRVHOSTAUDIOWASCACHEDEV).
+ * Protected by CritSectCache */
+ RTLISTANCHOR CacheHead;
+ /** Serializing access to CacheHead. */
+ RTCRITSECT CritSectCache;
+ /** Semaphore for signalling that cache purge is done and that the destructor
+ * can do cleanups. */
+ RTSEMEVENTMULTI hEvtCachePurge;
+ /** Total number of device config entire for capturing.
+ * This includes in-use ones. */
+ uint32_t volatile cCacheEntriesIn;
+ /** Total number of device config entire for playback.
+ * This includes in-use ones. */
+ uint32_t volatile cCacheEntriesOut;
+
+#if 0
+ /** The worker thread. */
+ RTTHREAD hWorkerThread;
+ /** The TID of the worker thread (for posting messages to it). */
+ DWORD idWorkerThread;
+ /** The fixed wParam value for the worker thread. */
+ WPARAM uWorkerThreadFixedParam;
+#endif
+} DRVHOSTAUDIOWAS;
+/** Pointer to the data for a WASAPI host audio driver instance. */
+typedef DRVHOSTAUDIOWAS *PDRVHOSTAUDIOWAS;
+
+
+
+
+/**
+ * Gets the stream status.
+ *
+ * @returns Pointer to stream status string.
+ * @param pStreamWas The stream to get the status for.
+ */
+static const char *drvHostWasStreamStatusString(PDRVHOSTAUDIOWASSTREAM pStreamWas)
+{
+ static RTSTRTUPLE const s_aEnable[2] =
+ {
+ { RT_STR_TUPLE("DISABLED") },
+ { RT_STR_TUPLE("ENABLED ") },
+ };
+ PCRTSTRTUPLE pTuple = &s_aEnable[pStreamWas->fEnabled];
+ memcpy(pStreamWas->szStatus, pTuple->psz, pTuple->cch);
+ size_t off = pTuple->cch;
+
+ static RTSTRTUPLE const s_aStarted[2] =
+ {
+ { RT_STR_TUPLE(" STOPPED") },
+ { RT_STR_TUPLE(" STARTED") },
+ };
+ pTuple = &s_aStarted[pStreamWas->fStarted];
+ memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch);
+ off += pTuple->cch;
+
+ static RTSTRTUPLE const s_aDraining[2] =
+ {
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE(" DRAINING") },
+ };
+ pTuple = &s_aDraining[pStreamWas->fDraining];
+ memcpy(&pStreamWas->szStatus[off], pTuple->psz, pTuple->cch);
+ off += pTuple->cch;
+
+ Assert(off < sizeof(pStreamWas->szStatus));
+ pStreamWas->szStatus[off] = '\0';
+ return pStreamWas->szStatus;
+}
+
+
+/*********************************************************************************************************************************
+* IMMNotificationClient implementation
+*********************************************************************************************************************************/
+/**
+ * Multimedia notification client.
+ *
+ * We want to know when the default device changes so we can switch running
+ * streams to use the new one and so we can pre-activate it in preparation
+ * for new streams.
+ */
+class DrvHostAudioWasMmNotifyClient : public IMMNotificationClient
+{
+private:
+ /** Reference counter. */
+ uint32_t volatile m_cRefs;
+ /** The WASAPI host audio driver instance data.
+ * @note This can be NULL. Only access after entering critical section. */
+ PDRVHOSTAUDIOWAS m_pDrvWas;
+ /** Critical section serializing access to m_pDrvWas. */
+ RTCRITSECT m_CritSect;
+
+public:
+ /**
+ * @throws int on critical section init failure.
+ */
+ DrvHostAudioWasMmNotifyClient(PDRVHOSTAUDIOWAS a_pDrvWas)
+ : m_cRefs(1)
+ , m_pDrvWas(a_pDrvWas)
+ {
+ RT_ZERO(m_CritSect);
+ }
+
+ virtual ~DrvHostAudioWasMmNotifyClient() RT_NOEXCEPT
+ {
+ if (RTCritSectIsInitialized(&m_CritSect))
+ RTCritSectDelete(&m_CritSect);
+ }
+
+ /**
+ * Initializes the critical section.
+ * @note Must be buildable w/o exceptions enabled, so cannot do this from the
+ * constructor. */
+ int init(void) RT_NOEXCEPT
+ {
+ return RTCritSectInit(&m_CritSect);
+ }
+
+ /**
+ * Called by drvHostAudioWasDestruct to set m_pDrvWas to NULL.
+ */
+ void notifyDriverDestroyed() RT_NOEXCEPT
+ {
+ RTCritSectEnter(&m_CritSect);
+ m_pDrvWas = NULL;
+ RTCritSectLeave(&m_CritSect);
+ }
+
+ /**
+ * Enters the notification critsect for getting at the IMMDevice members in
+ * PDMHOSTAUDIOWAS.
+ */
+ void lockEnter() RT_NOEXCEPT
+ {
+ RTCritSectEnter(&m_CritSect);
+ }
+
+ /**
+ * Leaves the notification critsect.
+ */
+ void lockLeave() RT_NOEXCEPT
+ {
+ RTCritSectLeave(&m_CritSect);
+ }
+
+ /** @name IUnknown interface
+ * @{ */
+ IFACEMETHODIMP_(ULONG) AddRef()
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&m_cRefs);
+ AssertMsg(cRefs < 64, ("%#x\n", cRefs));
+ Log6Func(("returns %u\n", cRefs));
+ return cRefs;
+ }
+
+ IFACEMETHODIMP_(ULONG) Release()
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&m_cRefs);
+ AssertMsg(cRefs < 64, ("%#x\n", cRefs));
+ if (cRefs == 0)
+ delete this;
+ Log6Func(("returns %u\n", cRefs));
+ return cRefs;
+ }
+
+ IFACEMETHODIMP QueryInterface(const IID &rIID, void **ppvInterface)
+ {
+ if (IsEqualIID(rIID, IID_IUnknown))
+ *ppvInterface = static_cast<IUnknown *>(this);
+ else if (IsEqualIID(rIID, __uuidof(IMMNotificationClient)))
+ *ppvInterface = static_cast<IMMNotificationClient *>(this);
+ else
+ {
+ LogFunc(("Unknown rIID={%RTuuid}\n", &rIID));
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+ }
+ Log6Func(("returns S_OK + %p\n", *ppvInterface));
+ return S_OK;
+ }
+ /** @} */
+
+ /** @name IMMNotificationClient interface
+ * @{ */
+ IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR pwszDeviceId, DWORD dwNewState)
+ {
+ RT_NOREF(pwszDeviceId, dwNewState);
+ Log7Func(("pwszDeviceId=%ls dwNewState=%u (%#x)\n", pwszDeviceId, dwNewState, dwNewState));
+
+ /*
+ * Just trigger device re-enumeration.
+ */
+ notifyDeviceChanges();
+
+ /** @todo do we need to check for our devices here too? Not when using a
+ * default device. But when using a specific device, we could perhaps
+ * re-init the stream when dwNewState indicates precense. We might
+ * also take action when a devices ceases to be operating, but again
+ * only for non-default devices, probably... */
+
+ return S_OK;
+ }
+
+ IFACEMETHODIMP OnDeviceAdded(LPCWSTR pwszDeviceId)
+ {
+ RT_NOREF(pwszDeviceId);
+ Log7Func(("pwszDeviceId=%ls\n", pwszDeviceId));
+
+ /*
+ * Is this a device we're interested in? Grab the enumerator if it is.
+ */
+ bool fOutput = false;
+ IMMDeviceEnumerator *pIEnumerator = NULL;
+ RTCritSectEnter(&m_CritSect);
+ if ( m_pDrvWas != NULL
+ && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0)
+ || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0))
+ {
+ pIEnumerator = m_pDrvWas->pIEnumerator;
+ if (pIEnumerator /* paranoia */)
+ pIEnumerator->AddRef();
+ }
+ RTCritSectLeave(&m_CritSect);
+ if (pIEnumerator)
+ {
+ /*
+ * Get the device and update it.
+ */
+ IMMDevice *pIDevice = NULL;
+ HRESULT hrc = pIEnumerator->GetDevice(pwszDeviceId, &pIDevice);
+ if (SUCCEEDED(hrc))
+ setDevice(fOutput, pIDevice, pwszDeviceId, __PRETTY_FUNCTION__);
+ else
+ LogRelMax(64, ("WasAPI: Failed to get %s device '%ls' (OnDeviceAdded): %Rhrc\n",
+ fOutput ? "output" : "input", pwszDeviceId, hrc));
+ pIEnumerator->Release();
+
+ /*
+ * Trigger device re-enumeration.
+ */
+ notifyDeviceChanges();
+ }
+ return S_OK;
+ }
+
+ IFACEMETHODIMP OnDeviceRemoved(LPCWSTR pwszDeviceId)
+ {
+ RT_NOREF(pwszDeviceId);
+ Log7Func(("pwszDeviceId=%ls\n", pwszDeviceId));
+
+ /*
+ * Is this a device we're interested in? Then set it to NULL.
+ */
+ bool fOutput = false;
+ RTCritSectEnter(&m_CritSect);
+ if ( m_pDrvWas != NULL
+ && ( (fOutput = RTUtf16ICmp(m_pDrvWas->pwszOutputDevId, pwszDeviceId) == 0)
+ || RTUtf16ICmp(m_pDrvWas->pwszInputDevId, pwszDeviceId) == 0))
+ {
+ RTCritSectLeave(&m_CritSect);
+ setDevice(fOutput, NULL, pwszDeviceId, __PRETTY_FUNCTION__);
+ }
+ else
+ RTCritSectLeave(&m_CritSect);
+
+ /*
+ * Trigger device re-enumeration.
+ */
+ notifyDeviceChanges();
+ return S_OK;
+ }
+
+ IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow enmFlow, ERole enmRole, LPCWSTR pwszDefaultDeviceId)
+ {
+ /*
+ * Are we interested in this device? If so grab the enumerator.
+ */
+ IMMDeviceEnumerator *pIEnumerator = NULL;
+ RTCritSectEnter(&m_CritSect);
+ if ( m_pDrvWas != NULL
+ && ( (enmFlow == eRender && enmRole == eMultimedia && !m_pDrvWas->pwszOutputDevId)
+ || (enmFlow == eCapture && enmRole == eMultimedia && !m_pDrvWas->pwszInputDevId)))
+ {
+ pIEnumerator = m_pDrvWas->pIEnumerator;
+ if (pIEnumerator /* paranoia */)
+ pIEnumerator->AddRef();
+ }
+ RTCritSectLeave(&m_CritSect);
+ if (pIEnumerator)
+ {
+ /*
+ * Get the device and update it.
+ */
+ IMMDevice *pIDevice = NULL;
+ HRESULT hrc = pIEnumerator->GetDefaultAudioEndpoint(enmFlow, enmRole, &pIDevice);
+ if (SUCCEEDED(hrc))
+ setDevice(enmFlow == eRender, pIDevice, pwszDefaultDeviceId, __PRETTY_FUNCTION__);
+ else
+ LogRelMax(64, ("WasAPI: Failed to get default %s device (OnDefaultDeviceChange): %Rhrc\n",
+ enmFlow == eRender ? "output" : "input", hrc));
+ pIEnumerator->Release();
+
+ /*
+ * Trigger device re-enumeration.
+ */
+ notifyDeviceChanges();
+ }
+
+ Log7Func(("enmFlow=%d enmRole=%d pwszDefaultDeviceId=%ls\n", enmFlow, enmRole, pwszDefaultDeviceId));
+ return S_OK;
+ }
+
+ IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR pwszDeviceId, const PROPERTYKEY Key)
+ {
+ RT_NOREF(pwszDeviceId, Key);
+ Log7Func(("pwszDeviceId=%ls Key={%RTuuid, %u (%#x)}\n", pwszDeviceId, &Key.fmtid, Key.pid, Key.pid));
+ return S_OK;
+ }
+ /** @} */
+
+private:
+ /**
+ * Sets DRVHOSTAUDIOWAS::pIDeviceOutput or DRVHOSTAUDIOWAS::pIDeviceInput to @a pIDevice.
+ */
+ void setDevice(bool fOutput, IMMDevice *pIDevice, LPCWSTR pwszDeviceId, const char *pszCaller)
+ {
+ RT_NOREF(pszCaller, pwszDeviceId);
+
+ RTCritSectEnter(&m_CritSect);
+
+ /*
+ * Update our internal device reference.
+ */
+ if (m_pDrvWas)
+ {
+ if (fOutput)
+ {
+ Log7((LOG_FN_FMT ": Changing output device from %p to %p (%ls)\n",
+ pszCaller, m_pDrvWas->pIDeviceOutput, pIDevice, pwszDeviceId));
+ if (m_pDrvWas->pIDeviceOutput)
+ m_pDrvWas->pIDeviceOutput->Release();
+ m_pDrvWas->pIDeviceOutput = pIDevice;
+ }
+ else
+ {
+ Log7((LOG_FN_FMT ": Changing input device from %p to %p (%ls)\n",
+ pszCaller, m_pDrvWas->pIDeviceInput, pIDevice, pwszDeviceId));
+ if (m_pDrvWas->pIDeviceInput)
+ m_pDrvWas->pIDeviceInput->Release();
+ m_pDrvWas->pIDeviceInput = pIDevice;
+ }
+ }
+ else if (pIDevice)
+ pIDevice->Release();
+
+ /*
+ * Tell DrvAudio that the device has changed for one of the directions.
+ *
+ * We have to exit the critsect when doing so, or we'll create a locking
+ * order violation. So, try make sure the VM won't be destroyed while
+ * till DrvAudio have entered its critical section...
+ */
+ if (m_pDrvWas)
+ {
+ PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort;
+ if (pIHostAudioPort)
+ {
+ VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns);
+ if (enmVmState < VMSTATE_POWERING_OFF)
+ {
+ RTCritSectLeave(&m_CritSect);
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fOutput ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN, NULL);
+ return;
+ }
+ LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState));
+ }
+ }
+
+ RTCritSectLeave(&m_CritSect);
+ }
+
+ /**
+ * Tell DrvAudio to re-enumerate devices when it get a chance.
+ *
+ * We exit the critsect here too before calling DrvAudio just to be on the safe
+ * side (see setDevice()), even though the current DrvAudio code doesn't take
+ * any critsects.
+ */
+ void notifyDeviceChanges(void)
+ {
+ RTCritSectEnter(&m_CritSect);
+ if (m_pDrvWas)
+ {
+ PPDMIHOSTAUDIOPORT const pIHostAudioPort = m_pDrvWas->pIHostAudioPort;
+ if (pIHostAudioPort)
+ {
+ VMSTATE const enmVmState = PDMDrvHlpVMState(m_pDrvWas->pDrvIns);
+ if (enmVmState < VMSTATE_POWERING_OFF)
+ {
+ RTCritSectLeave(&m_CritSect);
+ pIHostAudioPort->pfnNotifyDevicesChanged(pIHostAudioPort);
+ return;
+ }
+ LogFlowFunc(("Ignoring change: enmVmState=%d\n", enmVmState));
+ }
+ }
+ RTCritSectLeave(&m_CritSect);
+ }
+};
+
+
+/*********************************************************************************************************************************
+* Pre-configured audio client cache. *
+*********************************************************************************************************************************/
+#define WAS_CACHE_MAX_ENTRIES_SAME_DEVICE 2
+
+/**
+ * Converts from PDM stream config to windows WAVEFORMATEXTENSIBLE struct.
+ *
+ * @param pProps The PDM audio PCM properties to convert from.
+ * @param pFmt The windows structure to initialize.
+ */
+static void drvHostAudioWasWaveFmtExtFromProps(PCPDMAUDIOPCMPROPS pProps, PWAVEFORMATEXTENSIBLE pFmt)
+{
+ RT_ZERO(*pFmt);
+ pFmt->Format.wFormatTag = WAVE_FORMAT_PCM;
+ pFmt->Format.nChannels = PDMAudioPropsChannels(pProps);
+ pFmt->Format.wBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ pFmt->Format.nSamplesPerSec = PDMAudioPropsHz(pProps);
+ pFmt->Format.nBlockAlign = PDMAudioPropsFrameSize(pProps);
+ pFmt->Format.nAvgBytesPerSec = PDMAudioPropsFramesToBytes(pProps, PDMAudioPropsHz(pProps));
+ pFmt->Format.cbSize = 0; /* No extra data specified. */
+
+ /*
+ * We need to use the extensible structure if there are more than two channels
+ * or if the channels have non-standard assignments.
+ */
+ if ( pFmt->Format.nChannels > 2
+ || ( pFmt->Format.nChannels == 1
+ ? pProps->aidChannels[0] != PDMAUDIOCHANNELID_MONO
+ : pProps->aidChannels[0] != PDMAUDIOCHANNELID_FRONT_LEFT
+ || pProps->aidChannels[1] != PDMAUDIOCHANNELID_FRONT_RIGHT))
+ {
+ pFmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ pFmt->Format.cbSize = sizeof(*pFmt) - sizeof(pFmt->Format);
+ pFmt->Samples.wValidBitsPerSample = PDMAudioPropsSampleBits(pProps);
+ pFmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+ pFmt->dwChannelMask = 0;
+ unsigned const cSrcChannels = pFmt->Format.nChannels;
+ for (unsigned i = 0; i < cSrcChannels; i++)
+ if ( pProps->aidChannels[i] >= PDMAUDIOCHANNELID_FIRST_STANDARD
+ && pProps->aidChannels[i] < PDMAUDIOCHANNELID_END_STANDARD)
+ pFmt->dwChannelMask |= RT_BIT_32(pProps->aidChannels[i] - PDMAUDIOCHANNELID_FIRST_STANDARD);
+ else
+ pFmt->Format.nChannels -= 1;
+ }
+}
+
+
+#if 0 /* unused */
+/**
+ * Converts from windows WAVEFORMATEX and stream props to PDM audio properties.
+ *
+ * @returns VINF_SUCCESS on success, VERR_AUDIO_STREAM_COULD_NOT_CREATE if not
+ * supported.
+ * @param pProps The output properties structure.
+ * @param pFmt The windows wave format structure.
+ * @param pszStream The stream name for error logging.
+ * @param pwszDevId The device ID for error logging.
+ */
+static int drvHostAudioWasCacheWaveFmtExToProps(PPDMAUDIOPCMPROPS pProps, WAVEFORMATEX const *pFmt,
+ const char *pszStream, PCRTUTF16 pwszDevId)
+{
+ if (pFmt->wFormatTag == WAVE_FORMAT_PCM)
+ {
+ if ( pFmt->wBitsPerSample == 8
+ || pFmt->wBitsPerSample == 16
+ || pFmt->wBitsPerSample == 32)
+ {
+ if (pFmt->nChannels > 0 && pFmt->nChannels < 16)
+ {
+ if (pFmt->nSamplesPerSec >= 4096 && pFmt->nSamplesPerSec <= 768000)
+ {
+ PDMAudioPropsInit(pProps, pFmt->wBitsPerSample / 8, true /*fSigned*/, pFmt->nChannels, pFmt->nSamplesPerSec);
+ if (PDMAudioPropsFrameSize(pProps) == pFmt->nBlockAlign)
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ LogRelMax(64, ("WasAPI: Error! Unsupported stream format for '%s' suggested by '%ls':\n"
+ "WasAPI: wFormatTag = %RU16 (expected %d)\n"
+ "WasAPI: nChannels = %RU16 (expected 1..15)\n"
+ "WasAPI: nSamplesPerSec = %RU32 (expected 4096..768000)\n"
+ "WasAPI: nAvgBytesPerSec = %RU32\n"
+ "WasAPI: nBlockAlign = %RU16\n"
+ "WasAPI: wBitsPerSample = %RU16 (expected 8, 16, or 32)\n"
+ "WasAPI: cbSize = %RU16\n",
+ pszStream, pwszDevId, pFmt->wFormatTag, WAVE_FORMAT_PCM, pFmt->nChannels, pFmt->nSamplesPerSec, pFmt->nAvgBytesPerSec,
+ pFmt->nBlockAlign, pFmt->wBitsPerSample, pFmt->cbSize));
+ return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+}
+#endif
+
+
+/**
+ * Destroys a devie config cache entry.
+ *
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pDevCfg Device config entry. Must not be in the list.
+ */
+static void drvHostAudioWasCacheDestroyDevConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+ if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
+ ASMAtomicDecU32(&pThis->cCacheEntriesIn);
+ else
+ ASMAtomicDecU32(&pThis->cCacheEntriesOut);
+
+ uint32_t cTypeClientRefs = 0;
+ if (pDevCfg->pIAudioCaptureClient)
+ {
+ cTypeClientRefs = pDevCfg->pIAudioCaptureClient->Release();
+ pDevCfg->pIAudioCaptureClient = NULL;
+ }
+
+ if (pDevCfg->pIAudioRenderClient)
+ {
+ cTypeClientRefs = pDevCfg->pIAudioRenderClient->Release();
+ pDevCfg->pIAudioRenderClient = NULL;
+ }
+
+ uint32_t cClientRefs = 0;
+ if (pDevCfg->pIAudioClient /* paranoia */)
+ {
+ cClientRefs = pDevCfg->pIAudioClient->Release();
+ pDevCfg->pIAudioClient = NULL;
+ }
+
+ Log8Func(("Destroying cache config entry: '%ls: %s' - cClientRefs=%u cTypeClientRefs=%u\n",
+ pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, cClientRefs, cTypeClientRefs));
+ RT_NOREF(cClientRefs, cTypeClientRefs);
+
+ pDevCfg->pDevEntry = NULL;
+ RTMemFree(pDevCfg);
+}
+
+
+/**
+ * Destroys a device cache entry.
+ *
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pDevEntry The device entry. Must not be in the cache!
+ */
+static void drvHostAudioWasCacheDestroyDevEntry(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry)
+{
+ Log8Func(("Destroying cache entry: %p - '%ls'\n", pDevEntry, pDevEntry->wszDevId));
+
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg, pDevCfgNext;
+ RTListForEachSafe(&pDevEntry->ConfigList, pDevCfg, pDevCfgNext, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry)
+ drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
+
+ uint32_t cDevRefs = 0;
+ if (pDevEntry->pIDevice /* paranoia */)
+ {
+ cDevRefs = pDevEntry->pIDevice->Release();
+ pDevEntry->pIDevice = NULL;
+ }
+
+ pDevEntry->cwcDevId = 0;
+ pDevEntry->wszDevId[0] = '\0';
+ RTMemFree(pDevEntry);
+ Log8Func(("Destroyed cache entry: %p cDevRefs=%u\n", pDevEntry, cDevRefs));
+}
+
+
+/**
+ * Prunes the cache.
+ */
+static void drvHostAudioWasCachePrune(PDRVHOSTAUDIOWAS pThis)
+{
+ /*
+ * Prune each direction separately.
+ */
+ struct
+ {
+ PDMAUDIODIR enmDir;
+ uint32_t volatile *pcEntries;
+ } aWork[] = { { PDMAUDIODIR_IN, &pThis->cCacheEntriesIn }, { PDMAUDIODIR_OUT, &pThis->cCacheEntriesOut }, };
+ for (uint32_t iWork = 0; iWork < RT_ELEMENTS(aWork); iWork++)
+ {
+ /*
+ * Remove the least recently used entry till we're below the threshold
+ * or there are no more inactive entries.
+ */
+ LogFlowFunc(("iWork=%u cEntries=%u\n", iWork, *aWork[iWork].pcEntries));
+ while (*aWork[iWork].pcEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+ {
+ RTCritSectEnter(&pThis->CritSectCache);
+ PDRVHOSTAUDIOWASCACHEDEVCFG pLeastRecentlyUsed = NULL;
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry;
+ RTListForEach(&pThis->CacheHead, pDevEntry, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
+ {
+ if (pDevEntry->enmDir == aWork[iWork].enmDir)
+ {
+ PDRVHOSTAUDIOWASCACHEDEVCFG pHeadCfg = RTListGetFirst(&pDevEntry->ConfigList,
+ DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry);
+ if ( pHeadCfg
+ && (!pLeastRecentlyUsed || pHeadCfg->nsLastUsed < pLeastRecentlyUsed->nsLastUsed))
+ pLeastRecentlyUsed = pHeadCfg;
+ }
+ }
+ if (pLeastRecentlyUsed)
+ RTListNodeRemove(&pLeastRecentlyUsed->ListEntry);
+ RTCritSectLeave(&pThis->CritSectCache);
+
+ if (!pLeastRecentlyUsed)
+ break;
+ drvHostAudioWasCacheDestroyDevConfig(pThis, pLeastRecentlyUsed);
+ }
+ }
+}
+
+
+/**
+ * Purges all the entries in the cache.
+ */
+static void drvHostAudioWasCachePurge(PDRVHOSTAUDIOWAS pThis, bool fOnWorker)
+{
+ for (;;)
+ {
+ RTCritSectEnter(&pThis->CritSectCache);
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry = RTListRemoveFirst(&pThis->CacheHead, DRVHOSTAUDIOWASCACHEDEV, ListEntry);
+ RTCritSectLeave(&pThis->CritSectCache);
+ if (!pDevEntry)
+ break;
+ drvHostAudioWasCacheDestroyDevEntry(pThis, pDevEntry);
+ }
+
+ if (fOnWorker)
+ {
+ int rc = RTSemEventMultiSignal(pThis->hEvtCachePurge);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Looks up a specific configuration.
+ *
+ * @returns Pointer to the device config (removed from cache) on success. NULL
+ * if no matching config found.
+ * @param pDevEntry Where to perform the lookup.
+ * @param pProps The config properties to match.
+ */
+static PDRVHOSTAUDIOWASCACHEDEVCFG
+drvHostAudioWasCacheLookupLocked(PDRVHOSTAUDIOWASCACHEDEV pDevEntry, PCPDMAUDIOPCMPROPS pProps)
+{
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg;
+ RTListForEach(&pDevEntry->ConfigList, pDevCfg, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry)
+ {
+ if (PDMAudioPropsAreEqual(&pDevCfg->Props, pProps))
+ {
+ RTListNodeRemove(&pDevCfg->ListEntry);
+ pDevCfg->nsLastUsed = RTTimeNanoTS();
+ return pDevCfg;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Initializes a device config entry.
+ *
+ * This is usually done on the worker thread.
+ *
+ * @returns VBox status code.
+ * @param pDevCfg The device configuration entry to initialize.
+ */
+static int drvHostAudioWasCacheInitConfig(PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+ /*
+ * Assert some sanity given that we migth be called on the worker thread
+ * and pDevCfg being a message parameter.
+ */
+ AssertPtrReturn(pDevCfg, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_2);
+ AssertReturn(PDMAudioPropsAreValid(&pDevCfg->Props), VERR_INTERNAL_ERROR_2);
+
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry = pDevCfg->pDevEntry;
+ AssertPtrReturn(pDevEntry, VERR_INTERNAL_ERROR_2);
+ AssertPtrReturn(pDevEntry->pIDevice, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pDevEntry->enmDir == PDMAUDIODIR_IN || pDevEntry->enmDir == PDMAUDIODIR_OUT, VERR_INTERNAL_ERROR_2);
+
+ /*
+ * First we need an IAudioClient interface for calling IsFormatSupported
+ * on so we can get guidance as to what to do next.
+ *
+ * Initially, I thought the AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM was not
+ * supported all the way back to Vista and that we'd had to try different
+ * things here to get the most optimal format. However, according to
+ * https://social.msdn.microsoft.com/Forums/en-US/1d974d90-6636-4121-bba3-a8861d9ab92a
+ * it is supported, just maybe missing from the SDK or something...
+ *
+ * I'll leave the IsFormatSupported call here as it gives us a clue as to
+ * what exactly the WAS needs to convert our audio stream into/from.
+ */
+ Log8Func(("Activating an IAudioClient for '%ls' ...\n", pDevEntry->wszDevId));
+ IAudioClient *pIAudioClient = NULL;
+ HRESULT hrc = pDevEntry->pIDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
+ NULL /*pActivationParams*/, (void **)&pIAudioClient);
+ Log8Func(("Activate('%ls', IAudioClient) -> %Rhrc\n", pDevEntry->wszDevId, hrc));
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("WasAPI: Activate(%ls, IAudioClient) failed: %Rhrc\n", pDevEntry->wszDevId, hrc));
+ pDevCfg->nsInited = RTTimeNanoTS();
+ pDevCfg->nsLastUsed = pDevCfg->nsInited;
+ return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ WAVEFORMATEXTENSIBLE WaveFmtExt;
+ drvHostAudioWasWaveFmtExtFromProps(&pDevCfg->Props, &WaveFmtExt);
+
+ PWAVEFORMATEX pClosestMatch = NULL;
+ hrc = pIAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &WaveFmtExt.Format, &pClosestMatch);
+
+ /*
+ * If the format is supported, go ahead and initialize the client instance.
+ *
+ * The docs talks about AUDCLNT_E_UNSUPPORTED_FORMAT being success too, but
+ * that doesn't seem to be the case (at least not for mixing up the
+ * WAVEFORMATEX::wFormatTag values). Seems that is the standard return code
+ * if there is anything it doesn't grok.
+ */
+ if (SUCCEEDED(hrc))
+ {
+ if (hrc == S_OK)
+ Log8Func(("IsFormatSupported(,%s,) -> S_OK + %p: requested format is supported\n", pDevCfg->szProps, pClosestMatch));
+ else
+ Log8Func(("IsFormatSupported(,%s,) -> %Rhrc + %p: %uch S%u %uHz\n", pDevCfg->szProps, hrc, pClosestMatch,
+ pClosestMatch ? pClosestMatch->nChannels : 0, pClosestMatch ? pClosestMatch->wBitsPerSample : 0,
+ pClosestMatch ? pClosestMatch->nSamplesPerSec : 0));
+
+ REFERENCE_TIME const cBufferSizeInNtTicks = PDMAudioPropsFramesToNtTicks(&pDevCfg->Props, pDevCfg->cFramesBufferSize);
+ uint32_t fInitFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
+ | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY;
+ hrc = pIAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, fInitFlags, cBufferSizeInNtTicks,
+ 0 /*cPeriodicityInNtTicks*/, &WaveFmtExt.Format, NULL /*pAudioSessionGuid*/);
+ Log8Func(("Initialize(,%x, %RI64, %s,) -> %Rhrc\n", fInitFlags, cBufferSizeInNtTicks, pDevCfg->szProps, hrc));
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * The direction specific client interface.
+ */
+ if (pDevEntry->enmDir == PDMAUDIODIR_IN)
+ hrc = pIAudioClient->GetService(__uuidof(IAudioCaptureClient), (void **)&pDevCfg->pIAudioCaptureClient);
+ else
+ hrc = pIAudioClient->GetService(__uuidof(IAudioRenderClient), (void **)&pDevCfg->pIAudioRenderClient);
+ Log8Func(("GetService -> %Rhrc + %p\n", hrc, pDevEntry->enmDir == PDMAUDIODIR_IN
+ ? (void *)pDevCfg->pIAudioCaptureClient : (void *)pDevCfg->pIAudioRenderClient));
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Obtain the actual stream format and buffer config.
+ */
+ UINT32 cFramesBufferSize = 0;
+ REFERENCE_TIME cDefaultPeriodInNtTicks = 0;
+ REFERENCE_TIME cMinimumPeriodInNtTicks = 0;
+ REFERENCE_TIME cLatencyinNtTicks = 0;
+ hrc = pIAudioClient->GetBufferSize(&cFramesBufferSize);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pIAudioClient->GetDevicePeriod(&cDefaultPeriodInNtTicks, &cMinimumPeriodInNtTicks);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = pIAudioClient->GetStreamLatency(&cLatencyinNtTicks);
+ if (SUCCEEDED(hrc))
+ {
+ LogRel2(("WasAPI: Aquired buffer parameters for %s:\n"
+ "WasAPI: cFramesBufferSize = %RU32\n"
+ "WasAPI: cDefaultPeriodInNtTicks = %RI64\n"
+ "WasAPI: cMinimumPeriodInNtTicks = %RI64\n"
+ "WasAPI: cLatencyinNtTicks = %RI64\n",
+ pDevCfg->szProps, cFramesBufferSize, cDefaultPeriodInNtTicks,
+ cMinimumPeriodInNtTicks, cLatencyinNtTicks));
+
+ pDevCfg->pIAudioClient = pIAudioClient;
+ pDevCfg->cFramesBufferSize = cFramesBufferSize;
+ pDevCfg->cFramesPeriod = PDMAudioPropsNanoToFrames(&pDevCfg->Props,
+ cDefaultPeriodInNtTicks * 100);
+ pDevCfg->nsInited = RTTimeNanoTS();
+ pDevCfg->nsLastUsed = pDevCfg->nsInited;
+ pDevCfg->rcSetup = VINF_SUCCESS;
+
+ if (pClosestMatch)
+ CoTaskMemFree(pClosestMatch);
+ Log8Func(("returns VINF_SUCCESS (%p (%s) inited in %'RU64 ns)\n",
+ pDevCfg, pDevCfg->szProps, pDevCfg->nsInited - pDevCfg->nsCreated));
+ return VINF_SUCCESS;
+ }
+ LogRelMax(64, ("WasAPI: GetStreamLatency failed: %Rhrc\n", hrc));
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetDevicePeriod failed: %Rhrc\n", hrc));
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetBufferSize failed: %Rhrc\n", hrc));
+
+ if (pDevCfg->pIAudioCaptureClient)
+ {
+ pDevCfg->pIAudioCaptureClient->Release();
+ pDevCfg->pIAudioCaptureClient = NULL;
+ }
+
+ if (pDevCfg->pIAudioRenderClient)
+ {
+ pDevCfg->pIAudioRenderClient->Release();
+ pDevCfg->pIAudioRenderClient = NULL;
+ }
+ }
+ else
+ LogRelMax(64, ("WasAPI: IAudioClient::GetService(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc));
+ }
+ else
+ LogRelMax(64, ("WasAPI: IAudioClient::Initialize(%s) failed: %Rhrc\n", pDevCfg->szProps, hrc));
+ }
+ else
+ LogRelMax(64,("WasAPI: IAudioClient::IsFormatSupported(,%s,) failed: %Rhrc\n", pDevCfg->szProps, hrc));
+
+ pIAudioClient->Release();
+ if (pClosestMatch)
+ CoTaskMemFree(pClosestMatch);
+ pDevCfg->nsInited = RTTimeNanoTS();
+ pDevCfg->nsLastUsed = 0;
+ Log8Func(("returns VERR_AUDIO_STREAM_COULD_NOT_CREATE (inited in %'RU64 ns)\n", pDevCfg->nsInited - pDevCfg->nsCreated));
+ return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+}
+
+
+/**
+ * Worker for drvHostAudioWasCacheLookupOrCreate.
+ *
+ * If lookup fails, a new entry will be created.
+ *
+ * @note Called holding the lock, returning without holding it!
+ */
+static int drvHostAudioWasCacheLookupOrCreateConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry,
+ PCPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker,
+ PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg)
+{
+ /*
+ * Check if we've got a matching config.
+ */
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupLocked(pDevEntry, &pCfgReq->Props);
+ if (pDevCfg)
+ {
+ *ppDevCfg = pDevCfg;
+ RTCritSectLeave(&pThis->CritSectCache);
+ Log8Func(("Config cache hit '%s' on '%ls': %p\n", pDevCfg->szProps, pDevEntry->wszDevId, pDevCfg));
+ return VINF_SUCCESS;
+ }
+
+ RTCritSectLeave(&pThis->CritSectCache);
+
+ /*
+ * Allocate an device config entry and hand the creation task over to the
+ * worker thread, unless we're already on it.
+ */
+ pDevCfg = (PDRVHOSTAUDIOWASCACHEDEVCFG)RTMemAllocZ(sizeof(*pDevCfg));
+ AssertReturn(pDevCfg, VERR_NO_MEMORY);
+ RTListInit(&pDevCfg->ListEntry);
+ pDevCfg->pDevEntry = pDevEntry;
+ pDevCfg->rcSetup = VERR_AUDIO_STREAM_INIT_IN_PROGRESS;
+ pDevCfg->Props = pCfgReq->Props;
+ pDevCfg->cFramesBufferSize = pCfgReq->Backend.cFramesBufferSize;
+ PDMAudioPropsToString(&pDevCfg->Props, pDevCfg->szProps, sizeof(pDevCfg->szProps));
+ pDevCfg->nsCreated = RTTimeNanoTS();
+ pDevCfg->nsLastUsed = pDevCfg->nsCreated;
+
+ uint32_t cCacheEntries;
+ if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
+ cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesIn);
+ else
+ cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesOut);
+ if (cCacheEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+ {
+ LogFlowFunc(("Trigger cache pruning.\n"));
+ int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
+ DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
+ AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
+ }
+
+ if (!fOnWorker)
+ {
+ *ppDevCfg = pDevCfg;
+ LogFlowFunc(("Doing the rest of the work on %p via pfnStreamInitAsync...\n", pDevCfg));
+ return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
+ }
+
+ /*
+ * Initialize the entry on the calling thread.
+ */
+ int rc = drvHostAudioWasCacheInitConfig(pDevCfg);
+ AssertRC(pDevCfg->rcSetup == rc);
+ if (RT_SUCCESS(rc))
+ rc = pDevCfg->rcSetup; /* paranoia */
+ if (RT_SUCCESS(rc))
+ {
+ *ppDevCfg = pDevCfg;
+ LogFlowFunc(("Returning %p\n", pDevCfg));
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pDevCfg);
+ *ppDevCfg = NULL;
+ return rc;
+}
+
+
+/**
+ * Looks up the given device + config combo in the cache, creating a new entry
+ * if missing.
+ *
+ * @returns VBox status code.
+ * @retval VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED if @a fOnWorker is @c false and
+ * we created a new entry that needs initalization by calling
+ * drvHostAudioWasCacheInitConfig() on it.
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pIDevice The device to look up.
+ * @param pCfgReq The configuration to look up.
+ * @param fOnWorker Set if we're on a worker thread, otherwise false. When
+ * set to @c true, VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED will
+ * not be returned and a new entry will be fully
+ * initialized before returning.
+ * @param ppDevCfg Where to return the requested device config.
+ */
+static int drvHostAudioWasCacheLookupOrCreate(PDRVHOSTAUDIOWAS pThis, IMMDevice *pIDevice, PCPDMAUDIOSTREAMCFG pCfgReq,
+ bool fOnWorker, PDRVHOSTAUDIOWASCACHEDEVCFG *ppDevCfg)
+{
+ *ppDevCfg = NULL;
+
+ /*
+ * Get the device ID so we can perform the lookup.
+ */
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ LPWSTR pwszDevId = NULL;
+ HRESULT hrc = pIDevice->GetId(&pwszDevId);
+ if (SUCCEEDED(hrc))
+ {
+ LogRel2(("WasAPI: Checking for cached device '%ls' ...\n", pwszDevId));
+
+ size_t cwcDevId = RTUtf16Len(pwszDevId);
+
+ /*
+ * The cache has two levels, so first the device entry.
+ */
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry, pDevEntryNext;
+ RTCritSectEnter(&pThis->CritSectCache);
+ RTListForEachSafe(&pThis->CacheHead, pDevEntry, pDevEntryNext, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
+ {
+ if ( pDevEntry->cwcDevId == cwcDevId
+ && pDevEntry->enmDir == pCfgReq->enmDir
+ && RTUtf16Cmp(pDevEntry->wszDevId, pwszDevId) == 0)
+ {
+ /*
+ * Cache hit -- here we now need to also check if the device interface we want to look up
+ * actually matches the one we have in the cache entry.
+ *
+ * If it doesn't, bail out and add a new device entry to the cache with the new interface below then.
+ *
+ * This is needed when switching audio interfaces and the device interface becomes invalid via
+ * AUDCLNT_E_DEVICE_INVALIDATED. See @bugref{10503}
+ */
+ if (pDevEntry->pIDevice != pIDevice)
+ {
+ LogRel2(("WasAPI: Cache hit for device '%ls': Stale interface (new: %p, old: %p)\n",
+ pDevEntry->wszDevId, pIDevice, pDevEntry->pIDevice));
+
+ LogRel(("WasAPI: Stale audio interface '%ls' detected!\n", pDevEntry->wszDevId));
+ break;
+ }
+
+ LogRel2(("WasAPI: Cache hit for device '%ls' (%p)\n", pwszDevId, pIDevice));
+
+ CoTaskMemFree(pwszDevId);
+ pwszDevId = NULL;
+
+ return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg);
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectCache);
+
+ LogRel2(("WasAPI: Cache miss for device '%ls' (%p)\n", pwszDevId, pIDevice));
+
+ /*
+ * Device not in the cache, add it.
+ */
+ pDevEntry = (PDRVHOSTAUDIOWASCACHEDEV)RTMemAllocZVar(RT_UOFFSETOF_DYN(DRVHOSTAUDIOWASCACHEDEV, wszDevId[cwcDevId + 1]));
+ if (pDevEntry)
+ {
+ pIDevice->AddRef();
+ pDevEntry->pIDevice = pIDevice;
+ pDevEntry->enmDir = pCfgReq->enmDir;
+ pDevEntry->cwcDevId = cwcDevId;
+#if 0
+ pDevEntry->fSupportsAutoConvertPcm = -1;
+ pDevEntry->fSupportsSrcDefaultQuality = -1;
+#endif
+ RTListInit(&pDevEntry->ConfigList);
+ memcpy(pDevEntry->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
+ pDevEntry->wszDevId[cwcDevId] = '\0';
+
+ CoTaskMemFree(pwszDevId);
+ pwszDevId = NULL;
+
+ /*
+ * Before adding the device, check that someone didn't race us adding it.
+ */
+ RTCritSectEnter(&pThis->CritSectCache);
+ PDRVHOSTAUDIOWASCACHEDEV pDevEntry2;
+ RTListForEach(&pThis->CacheHead, pDevEntry2, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
+ {
+ if ( pDevEntry2->cwcDevId == cwcDevId
+ /* Note: We have to compare the device interface here as well, as a cached device entry might
+ * have a stale audio interface for the same device. In such a case a new device entry will be created below. */
+ && pDevEntry2->pIDevice == pIDevice
+ && pDevEntry2->enmDir == pCfgReq->enmDir
+ && RTUtf16Cmp(pDevEntry2->wszDevId, pDevEntry->wszDevId) == 0)
+ {
+ pIDevice->Release();
+ RTMemFree(pDevEntry);
+ pDevEntry = NULL;
+
+ LogRel2(("WasAPI: Lost race adding device '%ls': %p\n", pDevEntry2->wszDevId, pDevEntry2));
+ return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry2, pCfgReq, fOnWorker, ppDevCfg);
+ }
+ }
+ RTListPrepend(&pThis->CacheHead, &pDevEntry->ListEntry);
+
+ LogRel2(("WasAPI: Added device '%ls' to cache: %p\n", pDevEntry->wszDevId, pDevEntry));
+ return drvHostAudioWasCacheLookupOrCreateConfig(pThis, pDevEntry, pCfgReq, fOnWorker, ppDevCfg);
+ }
+ CoTaskMemFree(pwszDevId);
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetId failed (lookup): %Rhrc\n", hrc));
+ return rc;
+}
+
+
+/**
+ * Return the given config to the cache.
+ *
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pDevCfg The device config to put back.
+ */
+static void drvHostAudioWasCachePutBack(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+ /*
+ * Reset the audio client to see that it works and to make sure it's in a sensible state.
+ */
+ HRESULT hrc = pDevCfg->pIAudioClient ? pDevCfg->pIAudioClient->Reset()
+ : pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS ? S_OK : E_FAIL;
+ if (SUCCEEDED(hrc))
+ {
+ Log8Func(("Putting %p/'%s' back\n", pDevCfg, pDevCfg->szProps));
+ RTCritSectEnter(&pThis->CritSectCache);
+ RTListAppend(&pDevCfg->pDevEntry->ConfigList, &pDevCfg->ListEntry);
+ uint32_t const cEntries = pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN ? pThis->cCacheEntriesIn : pThis->cCacheEntriesOut;
+ RTCritSectLeave(&pThis->CritSectCache);
+
+ /* Trigger pruning if we're over the threshold. */
+ if (cEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+ {
+ LogFlowFunc(("Trigger cache pruning.\n"));
+ int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
+ DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
+ AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
+ }
+ }
+ else
+ {
+ Log8Func(("IAudioClient::Reset failed (%Rhrc) on %p/'%s', destroying it.\n", hrc, pDevCfg, pDevCfg->szProps));
+ drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
+ }
+}
+
+
+static void drvHostWasCacheConfigHinting(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOSTREAMCFG pCfgReq, bool fOnWorker)
+{
+ /*
+ * Get the device.
+ */
+ pThis->pNotifyClient->lockEnter();
+ IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
+ if (pIDevice)
+ pIDevice->AddRef();
+ pThis->pNotifyClient->lockLeave();
+ if (pIDevice)
+ {
+ /*
+ * Look up the config and put it back.
+ */
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
+ int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgReq, fOnWorker, &pDevCfg);
+ LogFlowFunc(("pDevCfg=%p rc=%Rrc\n", pDevCfg, rc));
+ if (pDevCfg && RT_SUCCESS(rc))
+ drvHostAudioWasCachePutBack(pThis, pDevCfg);
+ pIDevice->Release();
+ }
+}
+
+
+/**
+ * Prefills the cache.
+ *
+ * @param pThis The WASAPI host audio driver instance data.
+ */
+static void drvHostAudioWasCacheFill(PDRVHOSTAUDIOWAS pThis)
+{
+#if 0 /* we don't have the buffer config nor do we really know which frequences to expect */
+ Log8Func(("enter\n"));
+ struct
+ {
+ PCRTUTF16 pwszDevId;
+ PDMAUDIODIR enmDir;
+ } aToCache[] =
+ {
+ { pThis->pwszInputDevId, PDMAUDIODIR_IN },
+ { pThis->pwszOutputDevId, PDMAUDIODIR_OUT }
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(aToCache); i++)
+ {
+ PCRTUTF16 pwszDevId = aToCache[i].pwszDevId;
+ IMMDevice *pIDevice = NULL;
+ HRESULT hrc;
+ if (pwszDevId)
+ hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
+ else
+ {
+ hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(aToCache[i].enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
+ eMultimedia, &pIDevice);
+ pwszDevId = aToCache[i].enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
+ }
+ if (SUCCEEDED(hrc))
+ {
+ PDMAUDIOSTREAMCFG Cfg = { aToCache[i].enmDir, { PDMAUDIOPLAYBACKDST_INVALID },
+ PDMAUDIOPCMPROPS_INITIALIZER(2, true, 2, 44100, false) };
+ Cfg.Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&Cfg.Props, 300);
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &Cfg);
+ if (pDevCfg)
+ drvHostAudioWasCachePutBack(pThis, pDevCfg);
+
+ pIDevice->Release();
+ }
+ else
+ LogRelMax(64, ("WasAPI: Failed to open audio device '%ls' (pre-caching): %Rhrc\n", pwszDevId, hrc));
+ }
+ Log8Func(("leave\n"));
+#else
+ RT_NOREF(pThis);
+#endif
+}
+
+
+/*********************************************************************************************************************************
+* Worker thread *
+*********************************************************************************************************************************/
+#if 0
+
+/**
+ * @callback_method_impl{FNRTTHREAD,
+ * Asynchronous thread for setting up audio client configs.}
+ */
+static DECLCALLBACK(int) drvHostWasWorkerThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PDRVHOSTAUDIOWAS pThis = (PDRVHOSTAUDIOWAS)pvUser;
+
+ /*
+ * We need to set the thread ID so others can post us thread messages.
+ * And before we signal that we're ready, make sure we've got a message queue.
+ */
+ pThis->idWorkerThread = GetCurrentThreadId();
+ LogFunc(("idWorkerThread=%#x (%u)\n", pThis->idWorkerThread, pThis->idWorkerThread));
+
+ MSG Msg;
+ PeekMessageW(&Msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+ int rc = RTThreadUserSignal(hThreadSelf);
+ AssertRC(rc);
+
+ /*
+ * Message loop.
+ */
+ BOOL fRet;
+ while ((fRet = GetMessageW(&Msg, NULL, 0, 0)) != FALSE)
+ {
+ if (fRet != -1)
+ {
+ TranslateMessage(&Msg);
+ Log9Func(("Msg: time=%u: msg=%#x l=%p w=%p for hwnd=%p\n", Msg.time, Msg.message, Msg.lParam, Msg.wParam, Msg.hwnd));
+ switch (Msg.message)
+ {
+ case WM_DRVHOSTAUDIOWAS_PURGE_CACHE:
+ {
+ AssertMsgBreak(Msg.wParam == pThis->uWorkerThreadFixedParam, ("%p\n", Msg.wParam));
+ AssertBreak(Msg.hwnd == NULL);
+ AssertBreak(Msg.lParam == 0);
+
+ drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
+ break;
+ }
+
+ default:
+ break;
+ }
+ DispatchMessageW(&Msg);
+ }
+ else
+ AssertMsgFailed(("GetLastError()=%u\n", GetLastError()));
+ }
+
+ LogFlowFunc(("Pre-quit cache purge...\n"));
+ drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
+
+ LogFunc(("Quits\n"));
+ return VINF_SUCCESS;
+}
+#endif
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIO *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+
+ /*
+ * Fill in the config structure.
+ */
+ RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "WasAPI");
+ pBackendCfg->cbStream = sizeof(DRVHOSTAUDIOWASSTREAM);
+ pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_HINT;
+ pBackendCfg->cMaxStreamsIn = UINT32_MAX;
+ pBackendCfg->cMaxStreamsOut = UINT32_MAX;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Queries information for @a pDevice and adds an entry to the enumeration.
+ *
+ * @returns VBox status code.
+ * @param pDevEnm The enumeration to add the device to.
+ * @param pIDevice The device.
+ * @param enmType The type of device.
+ * @param fDefault Whether it's the default device.
+ */
+static int drvHostWasEnumAddDev(PPDMAUDIOHOSTENUM pDevEnm, IMMDevice *pIDevice, EDataFlow enmType, bool fDefault)
+{
+ int rc = VINF_SUCCESS; /* ignore most errors */
+ RT_NOREF(fDefault); /** @todo default device marking/skipping. */
+
+ /*
+ * Gather the necessary properties.
+ */
+ IPropertyStore *pProperties = NULL;
+ HRESULT hrc = pIDevice->OpenPropertyStore(STGM_READ, &pProperties);
+ if (SUCCEEDED(hrc))
+ {
+ /* Get the friendly name (string). */
+ PROPVARIANT VarName;
+ PropVariantInit(&VarName);
+ hrc = pProperties->GetValue(PKEY_Device_FriendlyName, &VarName);
+ if (SUCCEEDED(hrc))
+ {
+ /* Get the device ID (string). */
+ LPWSTR pwszDevId = NULL;
+ hrc = pIDevice->GetId(&pwszDevId);
+ if (SUCCEEDED(hrc))
+ {
+ size_t const cwcDevId = RTUtf16Len(pwszDevId);
+
+ /* Get the device format (blob). */
+ PROPVARIANT VarFormat;
+ PropVariantInit(&VarFormat);
+ hrc = pProperties->GetValue(PKEY_AudioEngine_DeviceFormat, &VarFormat);
+ if (SUCCEEDED(hrc))
+ {
+ WAVEFORMATEX const * const pFormat = (WAVEFORMATEX const *)VarFormat.blob.pBlobData;
+ AssertPtr(pFormat); /* Observed sometimes being NULL on windows 7 sp1. */
+
+ /*
+ * Create a enumeration entry for it.
+ */
+ size_t const cbId = RTUtf16CalcUtf8Len(pwszDevId) + 1;
+ size_t const cbName = RTUtf16CalcUtf8Len(VarName.pwszVal) + 1;
+ size_t const cbDev = RT_ALIGN_Z( RT_OFFSETOF(DRVHOSTAUDIOWASDEV, wszDevId)
+ + (cwcDevId + 1) * sizeof(RTUTF16),
+ 64);
+ PDRVHOSTAUDIOWASDEV pDev = (PDRVHOSTAUDIOWASDEV)PDMAudioHostDevAlloc(cbDev, cbName, cbId);
+ if (pDev)
+ {
+ pDev->Core.enmType = PDMAUDIODEVICETYPE_BUILTIN;
+ pDev->Core.enmUsage = enmType == eRender ? PDMAUDIODIR_OUT : PDMAUDIODIR_IN;
+ if (fDefault)
+ pDev->Core.fFlags = enmType == eRender ? PDMAUDIOHOSTDEV_F_DEFAULT_OUT : PDMAUDIOHOSTDEV_F_DEFAULT_IN;
+ if (enmType == eRender)
+ pDev->Core.cMaxOutputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 2;
+ else
+ pDev->Core.cMaxInputChannels = RT_VALID_PTR(pFormat) ? pFormat->nChannels : 1;
+
+ memcpy(pDev->wszDevId, pwszDevId, cwcDevId * sizeof(RTUTF16));
+ pDev->wszDevId[cwcDevId] = '\0';
+
+ Assert(pDev->Core.pszName);
+ rc = RTUtf16ToUtf8Ex(VarName.pwszVal, RTSTR_MAX, &pDev->Core.pszName, cbName, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pDev->Core.pszId);
+ rc = RTUtf16ToUtf8Ex(pDev->wszDevId, RTSTR_MAX, &pDev->Core.pszId, cbId, NULL);
+ if (RT_SUCCESS(rc))
+ PDMAudioHostEnumAppend(pDevEnm, &pDev->Core);
+ else
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ else
+ PDMAudioHostDevFree(&pDev->Core);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ PropVariantClear(&VarFormat);
+ }
+ else
+ LogFunc(("Failed to get PKEY_AudioEngine_DeviceFormat: %Rhrc\n", hrc));
+ CoTaskMemFree(pwszDevId);
+ }
+ else
+ LogFunc(("Failed to get the device ID: %Rhrc\n", hrc));
+ PropVariantClear(&VarName);
+ }
+ else
+ LogFunc(("Failed to get PKEY_Device_FriendlyName: %Rhrc\n", hrc));
+ pProperties->Release();
+ }
+ else
+ LogFunc(("OpenPropertyStore failed: %Rhrc\n", hrc));
+
+ if (hrc == E_OUTOFMEMORY && RT_SUCCESS_NP(rc))
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Does a (Re-)enumeration of the host's playback + capturing devices.
+ *
+ * @return VBox status code.
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pDevEnm Where to store the enumerated devices.
+ */
+static int drvHostWasEnumerateDevices(PDRVHOSTAUDIOWAS pThis, PPDMAUDIOHOSTENUM pDevEnm)
+{
+ LogRel2(("WasAPI: Enumerating devices ...\n"));
+
+ int rc = VINF_SUCCESS;
+ for (unsigned idxPass = 0; idxPass < 2 && RT_SUCCESS(rc); idxPass++)
+ {
+ EDataFlow const enmType = idxPass == 0 ? EDataFlow::eRender : EDataFlow::eCapture;
+
+ /* Get the default device first. */
+ IMMDevice *pIDefaultDevice = NULL;
+ HRESULT hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmType, eMultimedia, &pIDefaultDevice);
+ if (SUCCEEDED(hrc))
+ rc = drvHostWasEnumAddDev(pDevEnm, pIDefaultDevice, enmType, true);
+ else
+ pIDefaultDevice = NULL;
+
+ /* Enumerate the devices. */
+ IMMDeviceCollection *pCollection = NULL;
+ hrc = pThis->pIEnumerator->EnumAudioEndpoints(enmType, DEVICE_STATE_ACTIVE /*| DEVICE_STATE_UNPLUGGED?*/, &pCollection);
+ if (SUCCEEDED(hrc) && pCollection != NULL)
+ {
+ UINT cDevices = 0;
+ hrc = pCollection->GetCount(&cDevices);
+ if (SUCCEEDED(hrc))
+ {
+ for (UINT idxDevice = 0; idxDevice < cDevices && RT_SUCCESS(rc); idxDevice++)
+ {
+ IMMDevice *pIDevice = NULL;
+ hrc = pCollection->Item(idxDevice, &pIDevice);
+ if (SUCCEEDED(hrc) && pIDevice)
+ {
+ if (pIDevice != pIDefaultDevice)
+ rc = drvHostWasEnumAddDev(pDevEnm, pIDevice, enmType, false);
+ pIDevice->Release();
+ }
+ }
+ }
+ pCollection->Release();
+ }
+ else
+ LogRelMax(10, ("EnumAudioEndpoints(%s) failed: %Rhrc\n", idxPass == 0 ? "output" : "input", hrc));
+
+ if (pIDefaultDevice)
+ pIDefaultDevice->Release();
+ }
+
+ LogRel2(("WasAPI: Enumerating devices done - %u device (%Rrc)\n", pDevEnm->cDevices, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
+
+ PDMAudioHostEnumInit(pDeviceEnum);
+ int rc = drvHostWasEnumerateDevices(pThis, pDeviceEnum);
+ if (RT_FAILURE(rc))
+ PDMAudioHostEnumDelete(pDeviceEnum);
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Worker for drvHostAudioWasHA_SetDevice.
+ */
+static int drvHostAudioWasSetDeviceWorker(PDRVHOSTAUDIOWAS pThis, const char *pszId, PRTUTF16 *ppwszDevId, IMMDevice **ppIDevice,
+ EDataFlow enmFlow, PDMAUDIODIR enmDir, const char *pszWhat)
+{
+ pThis->pNotifyClient->lockEnter();
+
+ /*
+ * Did anything actually change?
+ */
+ if ( (pszId == NULL) != (*ppwszDevId == NULL)
+ || ( pszId
+ && RTUtf16ICmpUtf8(*ppwszDevId, pszId) != 0))
+ {
+ /*
+ * Duplicate the ID.
+ */
+ PRTUTF16 pwszDevId = NULL;
+ if (pszId)
+ {
+ int rc = RTStrToUtf16(pszId, &pwszDevId);
+ AssertRCReturnStmt(rc, pThis->pNotifyClient->lockLeave(), rc);
+ }
+
+ /*
+ * Try get the device.
+ */
+ IMMDevice *pIDevice = NULL;
+ HRESULT hrc;
+ if (pwszDevId)
+ hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
+ else
+ hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(enmFlow, eMultimedia, &pIDevice);
+ LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
+ if (FAILED(hrc))
+ {
+ LogRel(("WasAPI: Failed to get IMMDevice for %s audio device '%s' (SetDevice): %Rhrc\n",
+ pszWhat, pszId ? pszId : "{default}", hrc));
+ pIDevice = NULL;
+ }
+
+ /*
+ * Make the switch.
+ */
+ LogRel(("PulseAudio: Changing %s device: '%ls' -> '%s'\n",
+ pszWhat, *ppwszDevId ? *ppwszDevId : L"{Default}", pszId ? pszId : "{Default}"));
+
+ if (*ppIDevice)
+ (*ppIDevice)->Release();
+ *ppIDevice = pIDevice;
+
+ RTUtf16Free(*ppwszDevId);
+ *ppwszDevId = pwszDevId;
+
+ /*
+ * Only notify the driver above us.
+ */
+ PPDMIHOSTAUDIOPORT const pIHostAudioPort = pThis->pIHostAudioPort;
+ pThis->pNotifyClient->lockLeave();
+
+ if (pIHostAudioPort)
+ {
+ LogFlowFunc(("Notifying parent driver about %s device change...\n", pszWhat));
+ pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, enmDir, NULL);
+ }
+ }
+ else
+ {
+ pThis->pNotifyClient->lockLeave();
+ LogFunc(("No %s device change\n", pszWhat));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+
+ /*
+ * Validate and normalize input.
+ */
+ AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
+ if (!pszId || !*pszId)
+ pszId = NULL;
+ else
+ AssertReturn(strlen(pszId) < 1024, VERR_INVALID_NAME);
+ LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
+
+ /*
+ * Do the updating.
+ */
+ if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszInputDevId, &pThis->pIDeviceInput,
+ eCapture, PDMAUDIODIR_IN, "input");
+ AssertRCReturn(rc, rc);
+ }
+
+ if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
+ {
+ int rc = drvHostAudioWasSetDeviceWorker(pThis, pszId, &pThis->pwszOutputDevId, &pThis->pIDeviceOutput,
+ eRender, PDMAUDIODIR_OUT, "output");
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioWasHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * Performs the actual switching of device config.
+ *
+ * Worker for drvHostAudioWasDoStreamDevSwitch() and
+ * drvHostAudioWasHA_StreamNotifyDeviceChanged().
+ */
+static void drvHostAudioWasCompleteStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ /* Do the switch. */
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfgOld = pStreamWas->pDevCfg;
+ pStreamWas->pDevCfg = pDevCfg;
+
+ /* The new stream is neither started nor draining. */
+ pStreamWas->fStarted = false;
+ pStreamWas->fDraining = false;
+
+ /* Device switching is done now. */
+ pStreamWas->fSwitchingDevice = false;
+
+ /* Stop the old stream or Reset() will fail when putting it back into the cache. */
+ if (pStreamWas->fEnabled && pDevCfgOld->pIAudioClient)
+ pDevCfgOld->pIAudioClient->Stop();
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ /* Notify DrvAudio. */
+ pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, false /*fReInit*/);
+
+ /* Put the old config back into the cache. */
+ drvHostAudioWasCachePutBack(pThis, pDevCfgOld);
+
+ LogFlowFunc(("returns with '%s' state: %s\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+}
+
+
+/**
+ * Called on a worker thread to initialize a new device config and switch the
+ * given stream to using it.
+ *
+ * @sa drvHostAudioWasHA_StreamNotifyDeviceChanged
+ */
+static void drvHostAudioWasDoStreamDevSwitch(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas,
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+ /*
+ * Do the initializing.
+ */
+ int rc = drvHostAudioWasCacheInitConfig(pDevCfg);
+ if (RT_SUCCESS(rc))
+ drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
+ else
+ {
+ LogRelMax(64, ("WasAPI: Failed to set up new device config '%ls:%s' for stream '%s': %Rrc\n",
+ pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
+ drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
+ pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnDoOnWorkerThread}
+ */
+static DECLCALLBACK(void) drvHostAudioWasHA_DoOnWorkerThread(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ uintptr_t uUser, void *pvUser)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ LogFlowFunc(("uUser=%#zx pStream=%p pvUser=%p\n", uUser, pStream, pvUser));
+
+ switch (uUser)
+ {
+ case DRVHOSTAUDIOWAS_DO_PURGE_CACHE:
+ Assert(pStream == NULL);
+ Assert(pvUser == NULL);
+ drvHostAudioWasCachePurge(pThis, true /*fOnWorker*/);
+ break;
+
+ case DRVHOSTAUDIOWAS_DO_PRUNE_CACHE:
+ Assert(pStream == NULL);
+ Assert(pvUser == NULL);
+ drvHostAudioWasCachePrune(pThis);
+ break;
+
+ case DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH:
+ AssertPtr(pStream);
+ AssertPtr(pvUser);
+ drvHostAudioWasDoStreamDevSwitch(pThis, (PDRVHOSTAUDIOWASSTREAM)pStream, (PDRVHOSTAUDIOWASCACHEDEVCFG)pvUser);
+ break;
+
+ default:
+ AssertMsgFailedBreak(("%#zx\n", uUser));
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamConfigHint}
+ *
+ * @note This is called on a DrvAudio worker thread.
+ */
+static DECLCALLBACK(void) drvHostAudioWasHA_StreamConfigHint(PPDMIHOSTAUDIO pInterface, PPDMAUDIOSTREAMCFG pCfg)
+{
+#if 0 /* disable to test async stream creation. */
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ LogFlowFunc(("pCfg=%p\n", pCfg));
+
+ drvHostWasCacheConfigHinting(pThis, pCfg);
+#else
+ RT_NOREF(pInterface, pCfg);
+#endif
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
+
+ const char * const pszStreamType = pCfgReq->enmDir == PDMAUDIODIR_IN ? "capture" : "playback"; RT_NOREF(pszStreamType);
+ LogFlowFunc(("enmPath=%s '%s'\n", PDMAudioPathGetName(pCfgReq->enmPath), pCfgReq->szName));
+#if defined(RTLOG_REL_ENABLED) || defined(LOG_ENABLED)
+ char szTmp[64];
+#endif
+ LogRel2(("WasAPI: Opening %s stream '%s' (%s)\n", pCfgReq->szName, pszStreamType,
+ PDMAudioPropsToString(&pCfgReq->Props, szTmp, sizeof(szTmp))));
+
+ RTListInit(&pStreamWas->ListEntry);
+
+ /*
+ * Do configuration conversion.
+ */
+ WAVEFORMATEXTENSIBLE WaveFmtExt;
+ drvHostAudioWasWaveFmtExtFromProps(&pCfgReq->Props, &WaveFmtExt);
+ LogRel2(("WasAPI: Requested %s format for '%s':\n"
+ "WasAPI: wFormatTag = %#RX16\n"
+ "WasAPI: nChannels = %RU16\n"
+ "WasAPI: nSamplesPerSec = %RU32\n"
+ "WasAPI: nAvgBytesPerSec = %RU32\n"
+ "WasAPI: nBlockAlign = %RU16\n"
+ "WasAPI: wBitsPerSample = %RU16\n"
+ "WasAPI: cbSize = %RU16\n"
+ "WasAPI: cBufferSizeInNtTicks = %RU64\n",
+ pszStreamType, pCfgReq->szName, WaveFmtExt.Format.wFormatTag, WaveFmtExt.Format.nChannels,
+ WaveFmtExt.Format.nSamplesPerSec, WaveFmtExt.Format.nAvgBytesPerSec, WaveFmtExt.Format.nBlockAlign,
+ WaveFmtExt.Format.wBitsPerSample, WaveFmtExt.Format.cbSize,
+ PDMAudioPropsFramesToNtTicks(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize) ));
+ if (WaveFmtExt.Format.cbSize != 0)
+ LogRel2(("WasAPI: dwChannelMask = %#RX32\n"
+ "WasAPI: wValidBitsPerSample = %RU16\n",
+ WaveFmtExt.dwChannelMask, WaveFmtExt.Samples.wValidBitsPerSample));
+
+ /* Set up the acquired format here as channel count + layout may have
+ changed and need to be communicated to caller and used in cache lookup. */
+ *pCfgAcq = *pCfgReq;
+ if (WaveFmtExt.Format.cbSize != 0)
+ {
+ PDMAudioPropsSetChannels(&pCfgAcq->Props, WaveFmtExt.Format.nChannels);
+ uint8_t idCh = 0;
+ for (unsigned iBit = 0; iBit < 32 && idCh < WaveFmtExt.Format.nChannels; iBit++)
+ if (WaveFmtExt.dwChannelMask & RT_BIT_32(iBit))
+ {
+ pCfgAcq->Props.aidChannels[idCh] = (unsigned)PDMAUDIOCHANNELID_FIRST_STANDARD + iBit;
+ idCh++;
+ }
+ Assert(idCh == WaveFmtExt.Format.nChannels);
+ }
+
+ /*
+ * Get the device we're supposed to use.
+ * (We cache this as it takes ~2ms to get the default device on a random W10 19042 system.)
+ */
+ pThis->pNotifyClient->lockEnter();
+ IMMDevice *pIDevice = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
+ if (pIDevice)
+ pIDevice->AddRef();
+ pThis->pNotifyClient->lockLeave();
+
+ PRTUTF16 pwszDevId = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->pwszInputDevId : pThis->pwszOutputDevId;
+ PRTUTF16 const pwszDevIdDesc = pwszDevId ? pwszDevId : pCfgReq->enmDir == PDMAUDIODIR_IN ? L"{Default-In}" : L"{Default-Out}";
+ if (!pIDevice)
+ {
+ /* This might not strictly be necessary anymore, however it shouldn't
+ hurt and may be useful when using specific devices. */
+ HRESULT hrc;
+ if (pwszDevId)
+ hrc = pThis->pIEnumerator->GetDevice(pwszDevId, &pIDevice);
+ else
+ hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(pCfgReq->enmDir == PDMAUDIODIR_IN ? eCapture : eRender,
+ eMultimedia, &pIDevice);
+ LogFlowFunc(("Got device %p (%Rhrc)\n", pIDevice, hrc));
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("WasAPI: Failed to open audio %s device '%ls': %Rhrc\n", pszStreamType, pwszDevIdDesc, hrc));
+ return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ }
+
+ /*
+ * Ask the cache to retrieve or instantiate the requested configuration.
+ */
+ /** @todo make it return a status code too and retry if the default device
+ * was invalidated/changed while we where working on it here. */
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
+ int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, pCfgAcq, false /*fOnWorker*/, &pDevCfg);
+
+ pIDevice->Release();
+ pIDevice = NULL;
+
+ if (pDevCfg && RT_SUCCESS(rc))
+ {
+ pStreamWas->pDevCfg = pDevCfg;
+
+ pCfgAcq->Props = pDevCfg->Props;
+ pCfgAcq->Backend.cFramesBufferSize = pDevCfg->cFramesBufferSize;
+ pCfgAcq->Backend.cFramesPeriod = pDevCfg->cFramesPeriod;
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pDevCfg->cFramesBufferSize
+ / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
+
+ PDMAudioStrmCfgCopy(&pStreamWas->Cfg, pCfgAcq);
+
+ /* Finally, the critical section. */
+ int rc2 = RTCritSectInit(&pStreamWas->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
+ RTListAppend(&pThis->StreamHead, &pStreamWas->ListEntry);
+ RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
+
+ if (pStreamWas->pDevCfg->pIAudioClient != NULL)
+ {
+ LogFlowFunc(("returns VINF_SUCCESS\n", rc));
+ return VINF_SUCCESS;
+ }
+ LogFlowFunc(("returns VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED\n", rc));
+ return VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
+ }
+
+ LogRelMax(64, ("WasAPI: Failed to create critical section for stream.\n"));
+ drvHostAudioWasCachePutBack(pThis, pDevCfg);
+ pStreamWas->pDevCfg = NULL;
+ }
+ else
+ LogRelMax(64, ("WasAPI: Failed to setup %s on audio device '%ls' (%Rrc).\n", pszStreamType, pwszDevIdDesc, rc));
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamInitAsync}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamInitAsync(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fDestroyed)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
+ LogFlowFunc(("Stream '%s'%s\n", pStreamWas->Cfg.szName, fDestroyed ? " - destroyed!" : ""));
+
+ /*
+ * Assert sane preconditions for this call.
+ */
+ AssertPtrReturn(pStreamWas->Core.pStream, VERR_INTERNAL_ERROR);
+ AssertPtrReturn(pStreamWas->pDevCfg, VERR_INTERNAL_ERROR_2);
+ AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry, VERR_INTERNAL_ERROR_3);
+ AssertPtrReturn(pStreamWas->pDevCfg->pDevEntry->pIDevice, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pStreamWas->pDevCfg->pDevEntry->enmDir == pStreamWas->Core.pStream->Cfg.enmDir, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pStreamWas->pDevCfg->pIAudioClient == NULL, VERR_INTERNAL_ERROR_5);
+ AssertReturn(pStreamWas->pDevCfg->pIAudioRenderClient == NULL, VERR_INTERNAL_ERROR_5);
+ AssertReturn(pStreamWas->pDevCfg->pIAudioCaptureClient == NULL, VERR_INTERNAL_ERROR_5);
+
+ /*
+ * Do the job.
+ */
+ int rc;
+ if (!fDestroyed)
+ rc = drvHostAudioWasCacheInitConfig(pStreamWas->pDevCfg);
+ else
+ {
+ AssertReturn(pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS, VERR_INTERNAL_ERROR_2);
+ pStreamWas->pDevCfg->rcSetup = VERR_WRONG_ORDER;
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFunc(("returns %Rrc (%s)\n", rc, pStreamWas->Cfg.szName));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ bool fImmediate)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
+ LogFlowFunc(("Stream '%s'\n", pStreamWas->Cfg.szName));
+ RT_NOREF(fImmediate);
+ HRESULT hrc;
+
+ if (RTCritSectIsInitialized(&pStreamWas->CritSect))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectStreamList);
+ RTListNodeRemove(&pStreamWas->ListEntry);
+ RTCritSectRwLeaveExcl(&pThis->CritSectStreamList);
+
+ RTCritSectDelete(&pStreamWas->CritSect);
+ }
+
+ if (pStreamWas->fStarted && pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioClient)
+ {
+ hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+ LogFunc(("Stop('%s') -> %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ pStreamWas->fStarted = false;
+ }
+
+ if (pStreamWas->cFramesCaptureToRelease)
+ {
+ hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(0);
+ Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
+ pStreamWas->cFramesCaptureToRelease = 0;
+ pStreamWas->pbCapture = NULL;
+ pStreamWas->cbCapture = 0;
+ }
+
+ if (pStreamWas->pDevCfg)
+ {
+ drvHostAudioWasCachePutBack(pThis, pStreamWas->pDevCfg);
+ pStreamWas->pDevCfg = NULL;
+ }
+
+ LogFlowFunc(("returns\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamNotifyDeviceChanged}
+ */
+static DECLCALLBACK(void) drvHostAudioWasHA_StreamNotifyDeviceChanged(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, void *pvUser)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ LogFlowFunc(("pStreamWas=%p (%s)\n", pStreamWas, pStreamWas->Cfg.szName));
+ RT_NOREF(pvUser);
+
+ /*
+ * See if we've got a cached config for the new device around.
+ * We ignore this entirely, for now at least, if the device was
+ * disconnected and there is no replacement.
+ */
+ pThis->pNotifyClient->lockEnter();
+ IMMDevice *pIDevice = pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN ? pThis->pIDeviceInput : pThis->pIDeviceOutput;
+ if (pIDevice)
+ pIDevice->AddRef();
+ pThis->pNotifyClient->lockLeave();
+ if (pIDevice)
+ {
+ PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg = NULL;
+ int rc = drvHostAudioWasCacheLookupOrCreate(pThis, pIDevice, &pStreamWas->Cfg, false /*fOnWorker*/, &pDevCfg);
+
+ pIDevice->Release();
+ pIDevice = NULL;
+
+ /*
+ * If we have a working audio client, just do the switch.
+ */
+ if (RT_SUCCESS(rc) && pDevCfg->pIAudioClient)
+ {
+ LogFlowFunc(("New device config is ready already!\n"));
+ Assert(rc == VINF_SUCCESS);
+ drvHostAudioWasCompleteStreamDevSwitch(pThis, pStreamWas, pDevCfg);
+ }
+ /*
+ * Otherwise create one asynchronously on a worker thread.
+ */
+ else if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("New device config needs async init ...\n"));
+ Assert(rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED);
+
+ RTCritSectEnter(&pStreamWas->CritSect);
+ pStreamWas->fSwitchingDevice = true;
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ pThis->pIHostAudioPort->pfnStreamNotifyPreparingDeviceSwitch(pThis->pIHostAudioPort, &pStreamWas->Core);
+
+ rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, &pStreamWas->Core,
+ DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH, pDevCfg);
+ AssertRCStmt(rc, drvHostAudioWasDoStreamDevSwitch(pThis, pStreamWas, pDevCfg));
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: Failed to create new device config '%ls:%s' for stream '%s': %Rrc\n",
+ pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
+
+ pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
+ }
+ }
+ else
+ LogFlowFunc(("no new device, leaving it as-is\n"));
+}
+
+
+/**
+ * Wrapper for starting a stream.
+ *
+ * @returns VBox status code.
+ * @param pThis The WASAPI host audio driver instance data.
+ * @param pStreamWas The stream.
+ * @param pszOperation The operation we're doing.
+ */
+static int drvHostAudioWasStreamStartWorker(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASSTREAM pStreamWas, const char *pszOperation)
+{
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Start();
+ LogFlow(("%s: Start(%s) returns %Rhrc\n", pszOperation, pStreamWas->Cfg.szName, hrc));
+ AssertStmt(hrc != AUDCLNT_E_NOT_STOPPED, hrc = S_OK);
+ if (SUCCEEDED(hrc))
+ {
+ pStreamWas->fStarted = true;
+ return VINF_SUCCESS;
+ }
+
+ /** @todo try re-setup the stuff on AUDCLNT_E_DEVICEINVALIDATED.
+ * Need some way of telling the caller (e.g. playback, capture) so they can
+ * retry what they're doing */
+ RT_NOREF(pThis);
+
+ pStreamWas->fStarted = false;
+ LogRelMax(64, ("WasAPI: Starting '%s' failed (%s): %Rhrc\n", pStreamWas->Cfg.szName, pszOperation, hrc));
+ return VERR_AUDIO_STREAM_NOT_READY;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ HRESULT hrc;
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ Assert(!pStreamWas->fEnabled);
+ Assert(!pStreamWas->fStarted);
+
+ /*
+ * We always reset the buffer before enabling the stream (normally never necessary).
+ */
+ if (pStreamWas->cFramesCaptureToRelease)
+ {
+ hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
+ Log4Func(("Releasing capture buffer (%#x frames): %Rhrc\n", pStreamWas->cFramesCaptureToRelease, hrc));
+ pStreamWas->cFramesCaptureToRelease = 0;
+ pStreamWas->pbCapture = NULL;
+ pStreamWas->cbCapture = 0;
+ }
+
+ hrc = pStreamWas->pDevCfg->pIAudioClient->Reset();
+ if (FAILED(hrc))
+ LogRelMax(64, ("WasAPI: Stream reset failed when enabling '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ pStreamWas->offInternal = 0;
+ pStreamWas->fDraining = false;
+ pStreamWas->fEnabled = true;
+ pStreamWas->fRestartOnResume = false;
+
+ /*
+ * Input streams will start capturing, while output streams will only start
+ * playing once we get some audio data to play.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN)
+ rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "enable");
+ else
+ Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
+ pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ /*
+ * Always try stop it (draining or no).
+ */
+ pStreamWas->fEnabled = false;
+ pStreamWas->fRestartOnResume = false;
+ Assert(!pStreamWas->fDraining || pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ int rc = VINF_SUCCESS;
+ if (pStreamWas->fStarted)
+ {
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+ LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+ pStreamWas->fStarted = false;
+ pStreamWas->fDraining = false;
+ }
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
+ *
+ * @note Basically the same as drvHostAudioWasHA_StreamDisable, just w/o the
+ * buffer resetting and fEnabled change.
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
+ pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ /*
+ * Unless we're draining the stream, stop it if it's started.
+ */
+ int rc = VINF_SUCCESS;
+ if (pStreamWas->fStarted && !pStreamWas->fDraining)
+ {
+ pStreamWas->fRestartOnResume = true;
+
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+ LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ if (FAILED(hrc))
+ {
+ LogRelMax(64, ("WasAPI: Stopping '%s' failed (pause): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+ pStreamWas->fStarted = false;
+ }
+ else
+ {
+ pStreamWas->fRestartOnResume = false;
+ if (pStreamWas->fDraining)
+ {
+ LogFunc(("Stream '%s' is draining\n", pStreamWas->Cfg.szName));
+ Assert(pStreamWas->fStarted);
+ }
+ }
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ /*
+ * Resume according to state saved by drvHostAudioWasHA_StreamPause.
+ */
+ int rc;
+ if (pStreamWas->fRestartOnResume)
+ rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "resume");
+ else
+ rc = VINF_SUCCESS;
+ pStreamWas->fRestartOnResume = false;
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
+ pStreamWas->msLastTransfer ? RTTimeMilliTS() - pStreamWas->msLastTransfer : -1,
+ pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+
+ /*
+ * If the stram was started, calculate when the buffered data has finished
+ * playing and switch to drain mode. DrvAudio will keep on calling
+ * pfnStreamPlay with an empty buffer while we're draining, so we'll use
+ * that for checking the deadline and finally stopping the stream.
+ */
+ RTCritSectEnter(&pStreamWas->CritSect);
+ int rc = VINF_SUCCESS;
+ if (pStreamWas->fStarted)
+ {
+ if (!pStreamWas->fDraining)
+ {
+ uint64_t const msNow = RTTimeMilliTS();
+ uint64_t msDrainDeadline = 0;
+ UINT32 cFramesPending = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
+ if (SUCCEEDED(hrc))
+ msDrainDeadline = msNow
+ + PDMAudioPropsFramesToMilli(&pStreamWas->Cfg.Props,
+ RT_MIN(cFramesPending,
+ pStreamWas->Cfg.Backend.cFramesBufferSize * 2))
+ + 1 /*fudge*/;
+ else
+ {
+ msDrainDeadline = msNow;
+ LogRelMax(64, ("WasAPI: GetCurrentPadding fail on '%s' when starting draining: %Rhrc\n",
+ pStreamWas->Cfg.szName, hrc));
+ }
+ pStreamWas->msDrainDeadline = msDrainDeadline;
+ pStreamWas->fDraining = true;
+ }
+ else
+ LogFlowFunc(("Already draining '%s' ...\n", pStreamWas->Cfg.szName));
+ }
+ else
+ {
+ LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamWas->Cfg.szName));
+ AssertStmt(!pStreamWas->fDraining, pStreamWas->fDraining = false);
+ }
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioWasHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+ PDMHOSTAUDIOSTREAMSTATE enmState;
+ AssertPtr(pStreamWas->pDevCfg);
+ if (pStreamWas->pDevCfg /*paranoia*/)
+ {
+ if (RT_SUCCESS(pStreamWas->pDevCfg->rcSetup))
+ {
+ if (!pStreamWas->fDraining)
+ enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+ else
+ {
+ Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
+ enmState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+ }
+ }
+ else if ( pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS
+ || pStreamWas->fSwitchingDevice )
+ enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
+ else
+ enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+ }
+ else if (pStreamWas->fSwitchingDevice)
+ enmState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
+ else
+ enmState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+
+ LogFlowFunc(("returns %d for '%s' {%s}\n", enmState, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ return enmState;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, 0);
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, 0);
+
+ uint32_t cbPending = 0;
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
+ && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
+ {
+ if (pStreamWas->fStarted)
+ {
+ UINT32 cFramesPending = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
+ if (SUCCEEDED(hrc))
+ {
+ AssertMsg(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
+ ("cFramesPending=%#x cFramesBufferSize=%#x\n",
+ cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
+ cbPending = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, RT_MIN(cFramesPending, VBOX_WASAPI_MAX_PADDING));
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ }
+ }
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ LogFlowFunc(("returns %#x (%u) {%s}\n", cbPending, cbPending, drvHostWasStreamStatusString(pStreamWas)));
+ return cbPending;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, 0);
+ LogFlowFunc(("Stream '%s' {%s}\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
+
+ uint32_t cbWritable = 0;
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ if ( pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT
+ && pStreamWas->pDevCfg->pIAudioClient /* paranoia */)
+ {
+ UINT32 cFramesPending = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
+ if (SUCCEEDED(hrc))
+ {
+ if (cFramesPending < pStreamWas->Cfg.Backend.cFramesBufferSize)
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
+ pStreamWas->Cfg.Backend.cFramesBufferSize - cFramesPending);
+ else if (cFramesPending > pStreamWas->Cfg.Backend.cFramesBufferSize)
+ {
+ LogRelMax(64, ("WasAPI: Warning! GetCurrentPadding('%s') return too high: cFramesPending=%#x > cFramesBufferSize=%#x\n",
+ pStreamWas->Cfg.szName, cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
+ AssertMsgFailed(("cFramesPending=%#x > cFramesBufferSize=%#x\n",
+ cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
+ }
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ }
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ LogFlowFunc(("returns %#x (%u) {%s}\n", cbWritable, cbWritable, drvHostWasStreamStatusString(pStreamWas)));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (cbBuf)
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
+
+ RTCritSectEnter(&pStreamWas->CritSect);
+ if (pStreamWas->fEnabled)
+ { /* likely */ }
+ else
+ {
+ RTCritSectLeave(&pStreamWas->CritSect);
+ *pcbWritten = 0;
+ LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
+ return VINF_SUCCESS;
+ }
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+
+ /*
+ * Transfer loop.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cReInits = 0;
+ uint32_t cbWritten = 0;
+ while (cbBuf > 0)
+ {
+ AssertBreakStmt(pStreamWas->pDevCfg && pStreamWas->pDevCfg->pIAudioRenderClient && pStreamWas->pDevCfg->pIAudioClient,
+ rc = VERR_AUDIO_STREAM_NOT_READY);
+
+ /*
+ * Figure out how much we can possibly write.
+ */
+ UINT32 cFramesPending = 0;
+ uint32_t cbWritable = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
+ if (SUCCEEDED(hrc))
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props,
+ pStreamWas->Cfg.Backend.cFramesBufferSize
+ - RT_MIN(cFramesPending, pStreamWas->Cfg.Backend.cFramesBufferSize));
+ else
+ {
+ LogRelMax(64, ("WasAPI: GetCurrentPadding(%s) failed during playback: %Rhrc (@%#RX64)\n",
+ pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+ if (cbWritable <= PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props))
+ break;
+
+ uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamWas->Cfg.Props, RT_MIN(cbWritable, cbBuf));
+ uint32_t const cFramesToWrite = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, cbToWrite);
+ Assert(PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesToWrite) == cbToWrite);
+ Log3Func(("@%#RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n",
+ pStreamWas->offInternal, cFramesPending, cbWritable, cbToWrite, cFramesToWrite,
+ drvHostWasStreamStatusString(pStreamWas) ));
+
+ /*
+ * Get the buffer, copy the data into it, and relase it back to the WAS machinery.
+ */
+ BYTE *pbData = NULL;
+ hrc = pStreamWas->pDevCfg->pIAudioRenderClient->GetBuffer(cFramesToWrite, &pbData);
+ if (SUCCEEDED(hrc))
+ {
+ memcpy(pbData, pvBuf, cbToWrite);
+ hrc = pStreamWas->pDevCfg->pIAudioRenderClient->ReleaseBuffer(cFramesToWrite, 0 /*fFlags*/);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Before we advance the buffer position (so we can resubmit it
+ * after re-init), make sure we've successfully started stream.
+ */
+ if (pStreamWas->fStarted)
+ { }
+ else
+ {
+ rc = drvHostAudioWasStreamStartWorker(pThis, pStreamWas, "play");
+ if (rc == VINF_SUCCESS)
+ { /* likely */ }
+ else if (RT_SUCCESS(rc) && ++cReInits < 5)
+ continue; /* re-submit buffer after re-init */
+ else
+ break;
+ }
+
+ /* advance. */
+ pvBuf = (uint8_t *)pvBuf + cbToWrite;
+ cbBuf -= cbToWrite;
+ cbWritten += cbToWrite;
+ pStreamWas->offInternal += cbToWrite;
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: ReleaseBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
+ cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: GetBuffer(%#x) failed on '%s' during playback: %Rhrc (@%#RX64)\n",
+ cFramesToWrite, pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+ }
+
+ /*
+ * Do draining deadline processing.
+ */
+ uint64_t const msNow = RTTimeMilliTS();
+ if ( !pStreamWas->fDraining
+ || msNow < pStreamWas->msDrainDeadline)
+ { /* likely */ }
+ else
+ {
+ LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+ if (FAILED(hrc))
+ LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ pStreamWas->fDraining = false;
+ pStreamWas->fStarted = false;
+ pStreamWas->fEnabled = false;
+ }
+
+ /*
+ * Done.
+ */
+ uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
+ if (cbWritten)
+ pStreamWas->msLastTransfer = msNow;
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ *pcbWritten = cbWritten;
+ if (RT_SUCCESS(rc) || !cbWritten)
+ { }
+ else
+ {
+ LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
+ rc = VINF_SUCCESS;
+ }
+ LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbWritten,
+ msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostAudioWasHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, 0);
+ Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_IN);
+
+ uint32_t cbReadable = 0;
+ RTCritSectEnter(&pStreamWas->CritSect);
+
+ if (pStreamWas->pDevCfg->pIAudioCaptureClient /* paranoia */)
+ {
+ UINT32 cFramesPending = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->GetCurrentPadding(&cFramesPending);
+ if (SUCCEEDED(hrc))
+ {
+ /* An unreleased buffer is included in the pending frame count, so subtract
+ whatever we've got hanging around since the previous pfnStreamCapture call. */
+ AssertMsgStmt(cFramesPending >= pStreamWas->cFramesCaptureToRelease,
+ ("%#x vs %#x\n", cFramesPending, pStreamWas->cFramesCaptureToRelease),
+ cFramesPending = pStreamWas->cFramesCaptureToRelease);
+ cFramesPending -= pStreamWas->cFramesCaptureToRelease;
+
+ /* Add what we've got left in said buffer. */
+ uint32_t cFramesCurPacket = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, pStreamWas->cbCapture);
+ cFramesPending += cFramesCurPacket;
+
+ /* Paranoia: Make sure we don't exceed the buffer size. */
+ AssertMsgStmt(cFramesPending <= pStreamWas->Cfg.Backend.cFramesBufferSize,
+ ("cFramesPending=%#x cFramesCaptureToRelease=%#x cFramesCurPacket=%#x cFramesBufferSize=%#x\n",
+ cFramesPending, pStreamWas->cFramesCaptureToRelease, cFramesCurPacket,
+ pStreamWas->Cfg.Backend.cFramesBufferSize),
+ cFramesPending = pStreamWas->Cfg.Backend.cFramesBufferSize);
+
+ cbReadable = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesPending);
+ }
+ else
+ LogRelMax(64, ("WasAPI: GetCurrentPadding failed on '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+ }
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ LogFlowFunc(("returns %#x (%u) {%s}\n", cbReadable, cbReadable, drvHostWasStreamStatusString(pStreamWas)));
+ return cbReadable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostAudioWasHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ RT_NOREF(pInterface); //PDRVHOSTAUDIOWAS pThis = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+ PDRVHOSTAUDIOWASSTREAM pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
+ AssertPtrReturn(pStreamWas, 0);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
+ Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
+
+ RTCritSectEnter(&pStreamWas->CritSect);
+ if (pStreamWas->fEnabled)
+ { /* likely */ }
+ else
+ {
+ RTCritSectLeave(&pStreamWas->CritSect);
+ *pcbRead = 0;
+ LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHostWasStreamStatusString(pStreamWas)));
+ return VINF_SUCCESS;
+ }
+ Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas) ));
+
+
+ /*
+ * Transfer loop.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbRead = 0;
+ uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamWas->Cfg.Props);
+ while (cbBuf >= cbFrame)
+ {
+ AssertBreakStmt(pStreamWas->pDevCfg->pIAudioCaptureClient && pStreamWas->pDevCfg->pIAudioClient, rc = VERR_AUDIO_STREAM_NOT_READY);
+
+ /*
+ * Anything pending from last call?
+ * (This is rather similar to the Pulse interface.)
+ */
+ if (pStreamWas->cFramesCaptureToRelease)
+ {
+ uint32_t const cbToCopy = RT_MIN(pStreamWas->cbCapture, cbBuf);
+ memcpy(pvBuf, pStreamWas->pbCapture, cbToCopy);
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbBuf -= cbToCopy;
+ cbRead += cbToCopy;
+ pStreamWas->offInternal += cbToCopy;
+ pStreamWas->pbCapture += cbToCopy;
+ pStreamWas->cbCapture -= cbToCopy;
+ if (!pStreamWas->cbCapture)
+ {
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->ReleaseBuffer(pStreamWas->cFramesCaptureToRelease);
+ Log4Func(("@%#RX64: Releasing capture buffer (%#x frames): %Rhrc\n",
+ pStreamWas->offInternal, pStreamWas->cFramesCaptureToRelease, hrc));
+ if (SUCCEEDED(hrc))
+ {
+ pStreamWas->cFramesCaptureToRelease = 0;
+ pStreamWas->pbCapture = NULL;
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: ReleaseBuffer(%s) failed during capture: %Rhrc (@%#RX64)\n",
+ pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+ }
+ if (cbBuf < cbFrame)
+ break;
+ }
+
+ /*
+ * Figure out if there is any data available to be read now. (Docs hint that we can not
+ * skip this and go straight for GetBuffer or we risk getting unwritten buffer space back).
+ */
+ UINT32 cFramesCaptured = 0;
+ HRESULT hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetNextPacketSize(&cFramesCaptured);
+ if (SUCCEEDED(hrc))
+ {
+ if (!cFramesCaptured)
+ break;
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: GetNextPacketSize(%s) failed during capture: %Rhrc (@%#RX64)\n",
+ pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Get the buffer.
+ */
+ cFramesCaptured = 0;
+ UINT64 uQpsNtTicks = 0;
+ UINT64 offDevice = 0;
+ DWORD fBufFlags = 0;
+ BYTE *pbData = NULL;
+ hrc = pStreamWas->pDevCfg->pIAudioCaptureClient->GetBuffer(&pbData, &cFramesCaptured, &fBufFlags, &offDevice, &uQpsNtTicks);
+ Log4Func(("@%#RX64: GetBuffer -> %Rhrc pbData=%p cFramesCaptured=%#x fBufFlags=%#x offDevice=%#RX64 uQpcNtTicks=%#RX64\n",
+ pStreamWas->offInternal, hrc, pbData, cFramesCaptured, fBufFlags, offDevice, uQpsNtTicks));
+ if (SUCCEEDED(hrc))
+ {
+ Assert(cFramesCaptured < VBOX_WASAPI_MAX_PADDING);
+ pStreamWas->pbCapture = pbData;
+ pStreamWas->cFramesCaptureToRelease = cFramesCaptured;
+ pStreamWas->cbCapture = PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesCaptured);
+ /* Just loop and re-use the copying code above. Can optimize later. */
+ }
+ else
+ {
+ LogRelMax(64, ("WasAPI: GetBuffer() failed on '%s' during capture: %Rhrc (@%#RX64)\n",
+ pStreamWas->Cfg.szName, hrc, pStreamWas->offInternal));
+ /** @todo reinit on AUDCLNT_E_DEVICEINVALIDATED? */
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+ }
+
+ /*
+ * Done.
+ */
+ uint64_t const msPrev = pStreamWas->msLastTransfer; RT_NOREF(msPrev);
+ uint64_t const msNow = RTTimeMilliTS();
+ if (cbRead)
+ pStreamWas->msLastTransfer = msNow;
+
+ RTCritSectLeave(&pStreamWas->CritSect);
+
+ *pcbRead = cbRead;
+ if (RT_SUCCESS(rc) || !cbRead)
+ { }
+ else
+ {
+ LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
+ rc = VINF_SUCCESS;
+ }
+ LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%#RX32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamWas->offInternal, rc, cbRead,
+ msPrev ? msNow - msPrev : 0, msPrev, pStreamWas->msLastTransfer, drvHostWasStreamStatusString(pStreamWas) ));
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVINS::IBase Interface *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostAudioWasQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG Interface *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
+ */
+static DECLCALLBACK(void) drvHostAudioWasPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
+
+ /*
+ * Start purging the cache asynchronously before we get to destruct.
+ * This might speed up VM shutdown a tiny fraction and also stress
+ * the shutting down of the thread pool a little.
+ */
+#if 0
+ if (pThis->hWorkerThread != NIL_RTTHREAD)
+ {
+ BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_DRVHOSTAUDIOWAS_PURGE_CACHE, pThis->uWorkerThreadFixedParam, 0);
+ LogFlowFunc(("Posted WM_DRVHOSTAUDIOWAS_PURGE_CACHE: %d\n", fRc));
+ Assert(fRc); RT_NOREF(fRc);
+ }
+#else
+ if (!RTListIsEmpty(&pThis->CacheHead) && pThis->pIHostAudioPort)
+ {
+ int rc = RTSemEventMultiCreate(&pThis->hEvtCachePurge);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL/*pStream*/,
+ DRVHOSTAUDIOWAS_DO_PURGE_CACHE, NULL /*pvUser*/);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("pfnDoOnWorkerThread/DRVHOSTAUDIOWAS_DO_PURGE_CACHE failed: %Rrc\n", rc));
+ RTSemEventMultiDestroy(pThis->hEvtCachePurge);
+ pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ }
+#endif
+
+ /*
+ * Deregister the notification client to reduce the risk of notifications
+ * comming in while we're being detatched or the VM is being destroyed.
+ */
+ if (pThis->pNotifyClient)
+ {
+ pThis->pNotifyClient->notifyDriverDestroyed();
+ pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
+ pThis->pNotifyClient->Release();
+ pThis->pNotifyClient = NULL;
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT, pfnDestruct}
+ */
+static DECLCALLBACK(void) drvHostAudioWasDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ LogFlowFuncEnter();
+
+ /*
+ * Release the notification client first.
+ */
+ if (pThis->pNotifyClient)
+ {
+ pThis->pNotifyClient->notifyDriverDestroyed();
+ pThis->pIEnumerator->UnregisterEndpointNotificationCallback(pThis->pNotifyClient);
+ pThis->pNotifyClient->Release();
+ pThis->pNotifyClient = NULL;
+ }
+
+#if 0
+ if (pThis->hWorkerThread != NIL_RTTHREAD)
+ {
+ BOOL fRc = PostThreadMessageW(pThis->idWorkerThread, WM_QUIT, 0, 0);
+ Assert(fRc); RT_NOREF(fRc);
+
+ int rc = RTThreadWait(pThis->hWorkerThread, RT_MS_15SEC, NULL);
+ AssertRC(rc);
+ }
+#endif
+
+ if (RTCritSectIsInitialized(&pThis->CritSectCache))
+ {
+ drvHostAudioWasCachePurge(pThis, false /*fOnWorker*/);
+ if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiWait(pThis->hEvtCachePurge, RT_MS_30SEC);
+ RTCritSectDelete(&pThis->CritSectCache);
+ }
+
+ if (pThis->hEvtCachePurge != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThis->hEvtCachePurge);
+ pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (pThis->pIEnumerator)
+ {
+ uint32_t cRefs = pThis->pIEnumerator->Release(); RT_NOREF(cRefs);
+ LogFlowFunc(("cRefs=%d\n", cRefs));
+ }
+
+ if (pThis->pIDeviceOutput)
+ {
+ pThis->pIDeviceOutput->Release();
+ pThis->pIDeviceOutput = NULL;
+ }
+
+ if (pThis->pIDeviceInput)
+ {
+ pThis->pIDeviceInput->Release();
+ pThis->pIDeviceInput = NULL;
+ }
+
+
+ if (RTCritSectRwIsInitialized(&pThis->CritSectStreamList))
+ RTCritSectRwDelete(&pThis->CritSectStreamList);
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT, pfnConstruct}
+ */
+static DECLCALLBACK(int) drvHostAudioWasConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ RT_NOREF(fFlags, pCfg);
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->hEvtCachePurge = NIL_RTSEMEVENTMULTI;
+#if 0
+ pThis->hWorkerThread = NIL_RTTHREAD;
+ pThis->idWorkerThread = 0;
+#endif
+ RTListInit(&pThis->StreamHead);
+ RTListInit(&pThis->CacheHead);
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostAudioWasQueryInterface;
+ /* IHostAudio */
+ pThis->IHostAudio.pfnGetConfig = drvHostAudioWasHA_GetConfig;
+ pThis->IHostAudio.pfnGetDevices = drvHostAudioWasHA_GetDevices;
+ pThis->IHostAudio.pfnSetDevice = drvHostAudioWasHA_SetDevice;
+ pThis->IHostAudio.pfnGetStatus = drvHostAudioWasHA_GetStatus;
+ pThis->IHostAudio.pfnDoOnWorkerThread = drvHostAudioWasHA_DoOnWorkerThread;
+ pThis->IHostAudio.pfnStreamConfigHint = drvHostAudioWasHA_StreamConfigHint;
+ pThis->IHostAudio.pfnStreamCreate = drvHostAudioWasHA_StreamCreate;
+ pThis->IHostAudio.pfnStreamInitAsync = drvHostAudioWasHA_StreamInitAsync;
+ pThis->IHostAudio.pfnStreamDestroy = drvHostAudioWasHA_StreamDestroy;
+ pThis->IHostAudio.pfnStreamNotifyDeviceChanged = drvHostAudioWasHA_StreamNotifyDeviceChanged;
+ pThis->IHostAudio.pfnStreamEnable = drvHostAudioWasHA_StreamEnable;
+ pThis->IHostAudio.pfnStreamDisable = drvHostAudioWasHA_StreamDisable;
+ pThis->IHostAudio.pfnStreamPause = drvHostAudioWasHA_StreamPause;
+ pThis->IHostAudio.pfnStreamResume = drvHostAudioWasHA_StreamResume;
+ pThis->IHostAudio.pfnStreamDrain = drvHostAudioWasHA_StreamDrain;
+ pThis->IHostAudio.pfnStreamGetState = drvHostAudioWasHA_StreamGetState;
+ pThis->IHostAudio.pfnStreamGetPending = drvHostAudioWasHA_StreamGetPending;
+ pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioWasHA_StreamGetWritable;
+ pThis->IHostAudio.pfnStreamPlay = drvHostAudioWasHA_StreamPlay;
+ pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioWasHA_StreamGetReadable;
+ pThis->IHostAudio.pfnStreamCapture = drvHostAudioWasHA_StreamCapture;
+
+ /*
+ * Validate and read the configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|VmUuid|InputDeviceID|OutputDeviceID", "");
+
+ char szTmp[1024];
+ int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "InputDeviceID", szTmp, sizeof(szTmp), "");
+ AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc);
+ if (szTmp[0])
+ {
+ rc = RTStrToUtf16(szTmp, &pThis->pwszInputDevId);
+ AssertRCReturn(rc, rc);
+ }
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "OutputDeviceID", szTmp, sizeof(szTmp), "");
+ AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc);
+ if (szTmp[0])
+ {
+ rc = RTStrToUtf16(szTmp, &pThis->pwszOutputDevId);
+ AssertRCReturn(rc, rc);
+ }
+
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Initialize the critical sections early.
+ */
+ rc = RTCritSectRwInit(&pThis->CritSectStreamList);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectInit(&pThis->CritSectCache);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create an enumerator instance that we can get the default devices from
+ * as well as do enumeration thru.
+ */
+ HRESULT hrc = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
+ (void **)&pThis->pIEnumerator);
+ if (FAILED(hrc))
+ {
+ pThis->pIEnumerator = NULL;
+ LogRel(("WasAPI: Failed to create an MMDeviceEnumerator object: %Rhrc\n", hrc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ AssertPtr(pThis->pIEnumerator);
+
+ /*
+ * Resolve the interface to the driver above us.
+ */
+ pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
+ AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /*
+ * Instantiate and register the notification client with the enumerator.
+ *
+ * Failure here isn't considered fatal at this time as we'll just miss
+ * default device changes.
+ */
+#ifdef RT_EXCEPTIONS_ENABLED
+ try { pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis); }
+ catch (std::bad_alloc &) { return VERR_NO_MEMORY; }
+#else
+ pThis->pNotifyClient = new DrvHostAudioWasMmNotifyClient(pThis);
+ AssertReturn(pThis->pNotifyClient, VERR_NO_MEMORY);
+#endif
+ rc = pThis->pNotifyClient->init();
+ AssertRCReturn(rc, rc);
+
+ hrc = pThis->pIEnumerator->RegisterEndpointNotificationCallback(pThis->pNotifyClient);
+ AssertMsg(SUCCEEDED(hrc), ("%Rhrc\n", hrc));
+ if (FAILED(hrc))
+ {
+ LogRel(("WasAPI: RegisterEndpointNotificationCallback failed: %Rhrc (ignored)\n"
+ "WasAPI: Warning! Will not be able to detect default device changes!\n"));
+ pThis->pNotifyClient->notifyDriverDestroyed();
+ pThis->pNotifyClient->Release();
+ pThis->pNotifyClient = NULL;
+ }
+
+ /*
+ * Retrieve the input and output device.
+ */
+ IMMDevice *pIDeviceInput = NULL;
+ if (pThis->pwszInputDevId)
+ hrc = pThis->pIEnumerator->GetDevice(pThis->pwszInputDevId, &pIDeviceInput);
+ else
+ hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, &pIDeviceInput);
+ if (SUCCEEDED(hrc))
+ LogFlowFunc(("pIDeviceInput=%p\n", pIDeviceInput));
+ else
+ {
+ LogRel(("WasAPI: Failed to get audio input device '%ls': %Rhrc\n",
+ pThis->pwszInputDevId ? pThis->pwszInputDevId : L"{Default}", hrc));
+ pIDeviceInput = NULL;
+ }
+
+ IMMDevice *pIDeviceOutput = NULL;
+ if (pThis->pwszOutputDevId)
+ hrc = pThis->pIEnumerator->GetDevice(pThis->pwszOutputDevId, &pIDeviceOutput);
+ else
+ hrc = pThis->pIEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pIDeviceOutput);
+ if (SUCCEEDED(hrc))
+ LogFlowFunc(("pIDeviceOutput=%p\n", pIDeviceOutput));
+ else
+ {
+ LogRel(("WasAPI: Failed to get audio output device '%ls': %Rhrc\n",
+ pThis->pwszOutputDevId ? pThis->pwszOutputDevId : L"{Default}", hrc));
+ pIDeviceOutput = NULL;
+ }
+
+ /* Carefully place them in the instance data: */
+ pThis->pNotifyClient->lockEnter();
+
+ if (pThis->pIDeviceInput)
+ pThis->pIDeviceInput->Release();
+ pThis->pIDeviceInput = pIDeviceInput;
+
+ if (pThis->pIDeviceOutput)
+ pThis->pIDeviceOutput->Release();
+ pThis->pIDeviceOutput = pIDeviceOutput;
+
+ pThis->pNotifyClient->lockLeave();
+
+#if 0
+ /*
+ * Create the worker thread. This thread has a message loop and will be
+ * signalled by DrvHostAudioWasMmNotifyClient while the VM is paused/whatever,
+ * so better make it a regular thread rather than PDM thread.
+ */
+ pThis->uWorkerThreadFixedParam = (WPARAM)RTRandU64();
+ rc = RTThreadCreateF(&pThis->hWorkerThread, drvHostWasWorkerThread, pThis, 0 /*cbStack*/, RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE | RTTHREADFLAGS_COM_MTA, "WasWork%u", pDrvIns->iInstance);
+ AssertRCReturn(rc, rc);
+
+ rc = RTThreadUserWait(pThis->hWorkerThread, RT_MS_10SEC);
+ AssertRC(rc);
+#endif
+
+ /*
+ * Prime the cache.
+ */
+ drvHostAudioWasCacheFill(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * PDM driver registration for WasAPI.
+ */
+const PDMDRVREG g_DrvHostAudioWas =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "HostAudioWas",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Windows Audio Session API (WASAPI) host audio driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTAUDIOWAS),
+ /* pfnConstruct */
+ drvHostAudioWasConstruct,
+ /* pfnDestruct */
+ drvHostAudioWasDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvHostAudioWasPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Audio/Makefile.kup b/src/VBox/Devices/Audio/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/Audio/Makefile.kup
diff --git a/src/VBox/Devices/Audio/testcase/Makefile.kmk b/src/VBox/Devices/Audio/testcase/Makefile.kmk
new file mode 100644
index 00000000..39cd4109
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/Makefile.kmk
@@ -0,0 +1,83 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the audio testcases.
+#
+
+#
+# Copyright (C) 2014-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
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+
+ PROGRAMS += tstAudioMixBuffer
+ TESTING += $(tstAudioMixBuffer_0_OUTDIR)/tstAudioMixBuffer.run
+
+ tstAudioMixBuffer_TEMPLATE = VBoxR3TstExe
+ tstAudioMixBuffer_DEFS = TESTCASE VBOX_AUDIO_MIX_BUFFER_TESTCASE
+ tstAudioMixBuffer_DEFS.debug = VBOX_WITH_EF_WRAPS
+ tstAudioMixBuffer_SOURCES = \
+ tstAudioMixBuffer.cpp \
+ ../AudioMixBuffer.cpp \
+ ../AudioHlp.cpp
+ tstAudioMixBuffer_LIBS = $(LIB_RUNTIME)
+
+ $$(tstAudioMixBuffer_0_OUTDIR)/tstAudioMixBuffer.run: $$(tstAudioMixBuffer_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstAudioMixBuffer_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ ifeq ($(KBUILD_TARGET),win.amd64) # Note: Only runs on Windows 8 or newer.
+ tstAudioClient3_TEMPLATE = VBoxR3TstExe
+ tstAudioClient3_DEFS = TESTCASE
+ tstAudioClient3_DEFS.debug = VBOX_WITH_EF_WRAPS
+ tstAudioClient3_SOURCES = tstAudioClient3.cpp
+ tstAudioClient3_LIBS = $(LIB_RUNTIME)
+ tstAudioClient3_INCS = \
+ $(KBUILD_DEVTOOLS)/win.x86/sdk/v10.0.17134.0/Include/10.0.17134.0/um \
+ $(KBUILD_DEVTOOLS)/win.x86/sdk/v10.0.17134.0/include/10.0.17134.0/shared
+
+ # Requires manual execution / verification.
+ PROGRAMS += tstAudioClient3
+ endif
+
+ PROGRAMS += tstAudioTestService
+ TESTING += $(tstAudioTestService_0_OUTDIR)/tstAudioTestService.run
+
+ tstAudioTestService_TEMPLATE = VBoxR3TstExe
+ tstAudioTestService_DEFS = TESTCASE
+ tstAudioTestService_DEFS.debug = VBOX_WITH_EF_WRAPS
+ tstAudioTestService_SOURCES = \
+ tstAudioTestService.cpp \
+ ../AudioTestService.cpp \
+ ../AudioTestServiceProtocol.cpp \
+ ../AudioTestServiceTcp.cpp \
+ ../AudioTestServiceClient.cpp
+ tstAudioTestService_LIBS = $(LIB_RUNTIME)
+
+ $$(tstAudioTestService_0_OUTDIR)/tstAudioTestService.run: $$(tstAudioTestService_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstAudioTestService_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Devices/Audio/testcase/tstAudioClient3.cpp b/src/VBox/Devices/Audio/testcase/tstAudioClient3.cpp
new file mode 100644
index 00000000..c259e42b
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/tstAudioClient3.cpp
@@ -0,0 +1,112 @@
+/* $Id: tstAudioClient3.cpp $ */
+/** @file
+ * Audio testcase - Tests for the IAudioClient3 interface.
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <iprt/win/windows.h>
+
+#include <Audioclient.h>
+#include <mmdeviceapi.h>
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ /*
+ * Initialize IPRT and create the test.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstAudioMixBuffer", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /* Note: IAudioClient3 is supported on Win8 or newer. */
+
+ /** @todo Very crude for now, lacks error checking and such. Later. */
+
+ HRESULT hr = CoInitialize(NULL);
+
+ IMMDeviceEnumerator* pEnumerator;
+ hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator),
+ reinterpret_cast<void**>(&pEnumerator));
+
+ IMMDevice* pDevice;
+ hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
+
+ IAudioClient3* pAudioClient;
+ hr = pDevice->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&pAudioClient));
+
+ WAVEFORMATEX* pFormat;
+ hr = pAudioClient->GetMixFormat(&pFormat);
+
+ UINT32 defaultPeriodInFrames;
+ UINT32 fundamentalPeriodInFrames;
+ UINT32 minPeriodInFrames;
+ UINT32 maxPeriodInFrames;
+ hr = pAudioClient->GetSharedModeEnginePeriod(pFormat,
+ &defaultPeriodInFrames,
+ &fundamentalPeriodInFrames,
+ &minPeriodInFrames,
+ &maxPeriodInFrames);
+
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "def=%RU32, fundamental=%RU32, min=%RU32, max=%RU32\n",
+ defaultPeriodInFrames, fundamentalPeriodInFrames, minPeriodInFrames, maxPeriodInFrames);
+
+ uint32_t cfDefault = defaultPeriodInFrames * 2;
+
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Trying to set %RU32 as default ...\n", cfDefault);
+
+ hr = pAudioClient->InitializeSharedAudioStream(0, cfDefault, pFormat, NULL);
+ if (hr != S_OK)
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Unable to set new period");
+ else
+ {
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "OK");
+
+ hr = pAudioClient->Start();
+
+ /** @todo Do some waiting / testing here. */
+ }
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
diff --git a/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp
new file mode 100644
index 00000000..38f4a9d7
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp
@@ -0,0 +1,916 @@
+/* $Id: tstAudioMixBuffer.cpp $ */
+/** @file
+ * Audio testcase - Mixing buffer.
+ */
+
+/*
+ * Copyright (C) 2014-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "../AudioMixBuffer.h"
+#include "../AudioHlp.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h> /* sin, M_PI */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef RT_LITTLE_ENDIAN
+bool const g_fLittleEndian = true;
+#else
+bool const g_fLittleEndian = false;
+#endif
+
+
+static void tstBasics(RTTEST hTest)
+{
+ RTTestSub(hTest, "Basics");
+
+ const PDMAUDIOPCMPROPS Cfg441StereoS16 = PDMAUDIOPCMPROPS_INITIALIZER(
+ /* a_cb: */ 2,
+ /* a_fSigned: */ true,
+ /* a_cChannels: */ 2,
+ /* a_uHz: */ 44100,
+ /* a_fSwapEndian: */ false
+ );
+ const PDMAUDIOPCMPROPS Cfg441StereoU16 = PDMAUDIOPCMPROPS_INITIALIZER(
+ /* a_cb: */ 2,
+ /* a_fSigned: */ false,
+ /* a_cChannels: */ 2,
+ /* a_uHz: */ 44100,
+ /* a_fSwapEndian: */ false
+ );
+ const PDMAUDIOPCMPROPS Cfg441StereoU32 = PDMAUDIOPCMPROPS_INITIALIZER(
+ /* a_cb: */ 4,
+ /* a_fSigned: */ false,
+ /* a_cChannels: */ 2,
+ /* a_uHz: */ 44100,
+ /* a_fSwapEndian: */ false
+ );
+
+ RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoS16) == 44100*4*8);
+ RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoU16) == 44100*4*8);
+ RTTESTI_CHECK(PDMAudioPropsGetBitrate(&Cfg441StereoU32) == 44100*8*8);
+
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&Cfg441StereoS16));
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&Cfg441StereoU16));
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&Cfg441StereoU32));
+
+
+ RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoS16, 1) == 4,
+ ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoS16, 1)));
+ RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU16, 1) == 4,
+ ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU16, 1)));
+ RTTESTI_CHECK_MSG(PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU32, 1) == 8,
+ ("got %x, expected 4\n", PDMAUDIOPCMPROPS_F2B(&Cfg441StereoU32, 1)));
+
+ RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoS16) == 4,
+ ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoS16)));
+ RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoU16) == 4,
+ ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoU16)));
+ RTTESTI_CHECK_MSG(PDMAudioPropsBytesPerFrame(&Cfg441StereoU32) == 8,
+ ("got %x, expected 4\n", PDMAudioPropsBytesPerFrame(&Cfg441StereoU32)));
+
+ uint32_t u32;
+ for (uint32_t i = 0; i < 256; i += 8)
+ {
+ RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoU32, i) == true);
+ for (uint32_t j = 1; j < 8; j++)
+ RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoU32, i + j) == false);
+ for (uint32_t j = 0; j < 8; j++)
+ RTTESTI_CHECK(PDMAudioPropsFloorBytesToFrame(&Cfg441StereoU32, i + j) == i);
+ }
+ for (uint32_t i = 0; i < 4096; i += 4)
+ {
+ RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoS16, i) == true);
+ for (uint32_t j = 1; j < 4; j++)
+ RTTESTI_CHECK(PDMAudioPropsIsSizeAligned(&Cfg441StereoS16, i + j) == false);
+ for (uint32_t j = 0; j < 4; j++)
+ RTTESTI_CHECK(PDMAudioPropsFloorBytesToFrame(&Cfg441StereoS16, i + j) == i);
+ }
+
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 44100)) == 44100 * 2 * 2,
+ ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 2)) == 2 * 2 * 2,
+ ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoS16, 1)) == 4,
+ ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoU16, 1)) == 4,
+ ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsFramesToBytes(&Cfg441StereoU32, 1)) == 8,
+ ("cb=%RU32\n", u32));
+
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoS16, 4)) == 1, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoU16, 4)) == 1, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsBytesToFrames(&Cfg441StereoU32, 8)) == 1, ("cb=%RU32\n", u32));
+
+ uint64_t u64;
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToNano(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_NS_1SEC,
+ ("ns=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToMicro(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_US_1SEC,
+ ("us=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsBytesToMilli(&Cfg441StereoS16, 44100 * 2 * 2)) == RT_MS_1SEC,
+ ("ms=%RU64\n", u64));
+
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 44100)) == RT_NS_1SEC, ("ns=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 1)) == 22675, ("ns=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 31)) == 702947, ("ns=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToNano(&Cfg441StereoS16, 255)) == 5782312, ("ns=%RU64\n", u64));
+ //RTTESTI_CHECK_MSG((u64 = DrvAudioHlpFramesToMicro(&Cfg441StereoS16, 44100)) == RT_US_1SEC,
+ // ("us=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToMilli(&Cfg441StereoS16, 44100)) == RT_MS_1SEC, ("ms=%RU64\n", u64));
+ RTTESTI_CHECK_MSG((u64 = PDMAudioPropsFramesToMilli(&Cfg441StereoS16, 255)) == 5, ("ms=%RU64\n", u64));
+
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToFrames(&Cfg441StereoS16, RT_NS_1SEC)) == 44100, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToFrames(&Cfg441StereoS16, 215876)) == 10, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToFrames(&Cfg441StereoS16, RT_MS_1SEC)) == 44100, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToFrames(&Cfg441StereoU32, 6)) == 265, ("cb=%RU32\n", u32));
+
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToBytes(&Cfg441StereoS16, RT_NS_1SEC)) == 44100*2*2, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsNanoToBytes(&Cfg441StereoS16, 702947)) == 31*2*2, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToBytes(&Cfg441StereoS16, RT_MS_1SEC)) == 44100*2*2, ("cb=%RU32\n", u32));
+ RTTESTI_CHECK_MSG((u32 = PDMAudioPropsMilliToBytes(&Cfg441StereoS16, 5)) == 884, ("cb=%RU32\n", u32));
+
+ /* DrvAudioHlpClearBuf: */
+ uint8_t *pbPage;
+ int rc = RTTestGuardedAlloc(hTest, HOST_PAGE_SIZE, 0, false /*fHead*/, (void **)&pbPage);
+ RTTESTI_CHECK_RC_OK_RETV(rc);
+
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoS16, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE / 4);
+ RTTESTI_CHECK(ASMMemIsZero(pbPage, HOST_PAGE_SIZE));
+
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoU16, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE / 4);
+ for (uint32_t off = 0; off < HOST_PAGE_SIZE; off += 2)
+ RTTESTI_CHECK_MSG(pbPage[off] == 0 && pbPage[off + 1] == 0x80, ("off=%#x: %#x %x\n", off, pbPage[off], pbPage[off + 1]));
+
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoU32, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE / 8);
+ for (uint32_t off = 0; off < HOST_PAGE_SIZE; off += 4)
+ RTTESTI_CHECK(pbPage[off] == 0 && pbPage[off + 1] == 0 && pbPage[off + 2] == 0 && pbPage[off + 3] == 0x80);
+
+
+ RTTestDisableAssertions(hTest);
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoS16, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE); /* should adjust down the frame count. */
+ RTTESTI_CHECK(ASMMemIsZero(pbPage, HOST_PAGE_SIZE));
+
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoU16, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE); /* should adjust down the frame count. */
+ for (uint32_t off = 0; off < HOST_PAGE_SIZE; off += 2)
+ RTTESTI_CHECK_MSG(pbPage[off] == 0 && pbPage[off + 1] == 0x80, ("off=%#x: %#x %x\n", off, pbPage[off], pbPage[off + 1]));
+
+ memset(pbPage, 0x42, HOST_PAGE_SIZE);
+ PDMAudioPropsClearBuffer(&Cfg441StereoU32, pbPage, HOST_PAGE_SIZE, HOST_PAGE_SIZE); /* should adjust down the frame count. */
+ for (uint32_t off = 0; off < HOST_PAGE_SIZE; off += 4)
+ RTTESTI_CHECK(pbPage[off] == 0 && pbPage[off + 1] == 0 && pbPage[off + 2] == 0 && pbPage[off + 3] == 0x80);
+ RTTestRestoreAssertions(hTest);
+
+ RTTestGuardedFree(hTest, pbPage);
+}
+
+
+static void tstSimple(RTTEST hTest)
+{
+ RTTestSub(hTest, "Simple");
+
+ /* 44100Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS config = PDMAUDIOPCMPROPS_INITIALIZER(
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 44100, /* Hz */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&config));
+
+ uint32_t cBufSize = _1K;
+
+ /*
+ * General stuff.
+ */
+ AUDIOMIXBUF mb;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&mb, "Single", &config, cBufSize));
+ RTTESTI_CHECK(AudioMixBufSize(&mb) == cBufSize);
+ RTTESTI_CHECK(AUDIOMIXBUF_B2F(&mb, AudioMixBufSizeBytes(&mb)) == cBufSize);
+ RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufSize(&mb)) == AudioMixBufSizeBytes(&mb));
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize);
+ RTTESTI_CHECK(AUDIOMIXBUF_F2B(&mb, AudioMixBufFree(&mb)) == AudioMixBufFreeBytes(&mb));
+
+ AUDIOMIXBUFWRITESTATE WriteState;
+ RTTESTI_CHECK_RC(AudioMixBufInitWriteState(&mb, &WriteState, &config), VINF_SUCCESS);
+
+ AUDIOMIXBUFPEEKSTATE PeekState;
+ RTTESTI_CHECK_RC(AudioMixBufInitPeekState(&mb, &PeekState, &config), VINF_SUCCESS);
+
+ /*
+ * A few writes (used to be the weird absolute writes).
+ */
+ uint32_t cFramesRead = 0, cFramesWritten = 0, cFramesWrittenAbs = 0;
+ int16_t aFrames16[2] = { 0xAA, 0xBB };
+ int32_t aFrames32[2] = { 0xCC, 0xDD };
+
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0);
+
+ AudioMixBufWrite(&mb, &WriteState, &aFrames16, sizeof(aFrames16), 0 /*offDstFrame*/, cBufSize / 4, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == 1 /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0);
+ AudioMixBufCommit(&mb, cFramesWritten);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 1);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 1);
+
+ AudioMixBufWrite(&mb, &WriteState, &aFrames32, sizeof(aFrames32), 0 /*offDstFrame*/, cBufSize / 4, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == 2 /* Frames */);
+ AudioMixBufCommit(&mb, cFramesWritten);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 3);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3);
+
+ /* Pretend we read the frames.*/
+ AudioMixBufAdvance(&mb, 3);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 3);
+ RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3);
+
+ /* Fill up the buffer completely and check wraps. */
+
+ uint32_t cbSamples = PDMAudioPropsFramesToBytes(&config, cBufSize);
+ uint16_t *paSamples = (uint16_t *)RTMemAlloc(cbSamples);
+ RTTESTI_CHECK_RETV(paSamples);
+ AudioMixBufWrite(&mb, &WriteState, paSamples, cbSamples, 0 /*offDstFrame*/, cBufSize, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == cBufSize);
+ AudioMixBufCommit(&mb, cFramesWritten);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 3);
+ RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 3);
+ RTMemFree(paSamples);
+ cbSamples = 0;
+
+ /*
+ * Writes and reads (used to be circular).
+ */
+ AudioMixBufDrop(&mb);
+
+ cFramesWrittenAbs = AudioMixBufUsed(&mb);
+
+ uint32_t cToWrite = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1; /* -1 as padding plus -2 frames for above. */
+ for (uint32_t i = 0; i < cToWrite; i++)
+ {
+ AudioMixBufWrite(&mb, &WriteState, &aFrames16[0], sizeof(aFrames16), 0 /*offDstFrame*/, 1, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == 1);
+ AudioMixBufCommit(&mb, cFramesWritten);
+ }
+ RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb));
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == 1);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 1U));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cToWrite + cFramesWrittenAbs /* + last absolute write */);
+
+ AudioMixBufWrite(&mb, &WriteState, &aFrames16[0], sizeof(aFrames16), 0 /*offDstFrame*/, 1, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == 1);
+ AudioMixBufCommit(&mb, cFramesWritten);
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 0U));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize);
+
+ /* Reads. */
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0);
+ uint32_t cbRead;
+ uint16_t aFrames16Buf[RT_ELEMENTS(aFrames16)];
+ uint32_t cToRead = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1;
+ for (uint32_t i = 0; i < cToRead; i++)
+ {
+ AudioMixBufPeek(&mb, 0 /*offSrcFrame*/, 1, &cFramesRead, &PeekState, aFrames16Buf, sizeof(aFrames16Buf), &cbRead);
+ RTTESTI_CHECK(cFramesRead == 1);
+ RTTESTI_CHECK(cbRead == sizeof(aFrames16Buf));
+ AudioMixBufAdvance(&mb, cFramesRead);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == i + 1);
+ }
+ RTTESTI_CHECK(!AudioMixBufIsEmpty(&mb));
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == AudioMixBufSize(&mb) - cFramesWrittenAbs - 1);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs - 1));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize - cToRead);
+
+ AudioMixBufPeek(&mb, 0 /*offSrcFrame*/, 1, &cFramesRead, &PeekState, aFrames16Buf, sizeof(aFrames16Buf), &cbRead);
+ RTTESTI_CHECK(cFramesRead == 1);
+ RTTESTI_CHECK(cbRead == sizeof(aFrames16Buf));
+ AudioMixBufAdvance(&mb, cFramesRead);
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize - cFramesWrittenAbs);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cFramesWrittenAbs);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0);
+
+ AudioMixBufTerm(&mb);
+}
+
+/** @name Eight test samples represented in all basic formats.
+ * @{ */
+static uint8_t const g_au8TestSamples[8] = { 0x1, 0x11, 0x32, 0x7f, 0x80, 0x81, 0xbe, 0xff };
+static int8_t const g_ai8TestSamples[8] = { -127, -111, -78, -1, 0, 1, 62, 127 };
+static uint16_t const g_au16TestSamples[8] = { 0x100, 0x1100, 0x3200, 0x7f00, 0x8000, 0x8100, 0xbe00, 0xff00 };
+static int16_t const g_ai16TestSamples[8] = { -32512, -28416, -19968, -256, 0, 256, 15872, 32512 };
+static uint32_t const g_au32TestSamples[8] = { 0x1000000, 0x11000000, 0x32000000, 0x7f000000, 0x80000000, 0x81000000, 0xbe000000, 0xff000000 };
+static int32_t const g_ai32TestSamples[8] = { -2130706432, -1862270976, -1308622848, -16777216, 0, 16777216, 1040187392, 2130706432 };
+static int64_t const g_ai64TestSamples[8] = { -2130706432, -1862270976, -1308622848, -16777216, 0, 16777216, 1040187392, 2130706432 };
+static struct { void const *apv[2]; uint32_t cb; } g_aTestSamples[] =
+{
+ /* 0/0: */ { { NULL, NULL }, 0 },
+ /* 1/8: */ { { g_au8TestSamples, g_ai8TestSamples }, sizeof( g_au8TestSamples) },
+ /* 2/16: */ { { g_au16TestSamples, g_ai16TestSamples }, sizeof(g_au16TestSamples) },
+ /* 3/24: */ { { NULL, NULL }, 0 },
+ /* 4/32: */ { { g_au32TestSamples, g_ai32TestSamples }, sizeof(g_au32TestSamples) },
+ /* 5: */ { { NULL, NULL }, 0 },
+ /* 6: */ { { NULL, NULL }, 0 },
+ /* 7: */ { { NULL, NULL }, 0 },
+ /* 8:64 */ { { NULL, g_ai64TestSamples }, sizeof(g_ai64TestSamples) }, /* raw */
+};
+/** @} */
+
+/** Fills a buffer with samples from an g_aTestSamples entry. */
+static uint32_t tstFillBuf(PCPDMAUDIOPCMPROPS pCfg, void const *pvTestSamples, uint32_t iTestSample,
+ uint8_t *pbBuf, uint32_t cFrames)
+{
+ uint8_t const cTestSamples = RT_ELEMENTS(g_au8TestSamples);
+
+ cFrames *= PDMAudioPropsChannels(pCfg);
+ switch (PDMAudioPropsSampleSize(pCfg))
+ {
+ case 1:
+ {
+ uint8_t const * const pau8TestSamples = (uint8_t const *)pvTestSamples;
+ uint8_t *pu8Dst = (uint8_t *)pbBuf;
+ while (cFrames-- > 0)
+ {
+ *pu8Dst++ = pau8TestSamples[iTestSample];
+ iTestSample = (iTestSample + 1) % cTestSamples;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ uint16_t const * const pau16TestSamples = (uint16_t const *)pvTestSamples;
+ uint16_t *pu16Dst = (uint16_t *)pbBuf;
+ while (cFrames-- > 0)
+ {
+ *pu16Dst++ = pau16TestSamples[iTestSample];
+ iTestSample = (iTestSample + 1) % cTestSamples;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ uint32_t const * const pau32TestSamples = (uint32_t const *)pvTestSamples;
+ uint32_t *pu32Dst = (uint32_t *)pbBuf;
+ while (cFrames-- > 0)
+ {
+ *pu32Dst++ = pau32TestSamples[iTestSample];
+ iTestSample = (iTestSample + 1) % cTestSamples;
+ }
+ break;
+ }
+
+ case 8:
+ {
+ uint64_t const * const pau64TestSamples = (uint64_t const *)pvTestSamples;
+ uint64_t *pu64Dst = (uint64_t *)pbBuf;
+ while (cFrames-- > 0)
+ {
+ *pu64Dst++ = pau64TestSamples[iTestSample];
+ iTestSample = (iTestSample + 1) % cTestSamples;
+ }
+ break;
+ }
+
+ default:
+ AssertFailedBreak();
+ }
+ return iTestSample;
+}
+
+
+static void tstConversion(RTTEST hTest, uint8_t cSrcBits, bool fSrcSigned, uint8_t cSrcChs,
+ uint8_t cDstBits, bool fDstSigned, uint8_t cDstChs)
+{
+ RTTestSubF(hTest, "Conv %uch %c%u to %uch %c%u", cSrcChs, fSrcSigned ? 'S' : 'U', cSrcBits,
+ cDstChs, fDstSigned ? 'S' : 'U', cDstBits);
+
+ PDMAUDIOPCMPROPS CfgSrc, CfgDst;
+ PDMAudioPropsInitEx(&CfgSrc, cSrcBits / 8, fSrcSigned, cSrcChs, 44100, g_fLittleEndian, cSrcBits == 64 /*fRaw*/);
+ PDMAudioPropsInitEx(&CfgDst, cDstBits / 8, fDstSigned, cDstChs, 44100, g_fLittleEndian, cDstBits == 64 /*fRaw*/);
+
+ void const * const pvSrcTestSamples = g_aTestSamples[cSrcBits / 8].apv[fSrcSigned];
+ void const * const pvDstTestSamples = g_aTestSamples[cDstBits / 8].apv[fDstSigned];
+ uint32_t const cMixBufFrames = RTRandU32Ex(128, 16384);
+ uint32_t const cIterations = RTRandU32Ex(256, 1536);
+ uint32_t const cbSrcBuf = PDMAudioPropsFramesToBytes(&CfgSrc, cMixBufFrames + 64);
+ uint8_t * const pbSrcBuf = (uint8_t *)RTMemAllocZ(cbSrcBuf);
+ uint32_t const cbDstBuf = PDMAudioPropsFramesToBytes(&CfgDst, cMixBufFrames + 64);
+ uint8_t * const pbDstBuf = (uint8_t *)RTMemAllocZ(cbDstBuf);
+ uint8_t * const pbDstExpect = (uint8_t *)RTMemAllocZ(cbDstBuf);
+ RTTESTI_CHECK_RETV(pbSrcBuf);
+ RTTESTI_CHECK_RETV(pbDstBuf);
+ RTTESTI_CHECK_RETV(pbDstExpect);
+
+ AUDIOMIXBUF MixBuf;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInit(&MixBuf, "FormatOutputConversion", &CfgSrc, cMixBufFrames), VINF_SUCCESS);
+ AUDIOMIXBUFWRITESTATE WriteState;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &CfgSrc), VINF_SUCCESS);
+ AUDIOMIXBUFWRITESTATE WriteStateIgnZero = WriteState; RT_NOREF(WriteStateIgnZero);
+ AUDIOMIXBUFPEEKSTATE PeekState;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &CfgDst), VINF_SUCCESS);
+
+ uint32_t iSrcTestSample = 0;
+ uint32_t iDstTestSample = 0;
+ //RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "cIterations=%u\n", cIterations);
+ for (uint32_t iIteration = 0; iIteration < cIterations; iIteration++)
+ {
+ /* Write some frames to the buffer. */
+ uint32_t const cSrcFramesToWrite = iIteration < 16 ? iIteration + 1
+ : AudioMixBufFree(&MixBuf) ? RTRandU32Ex(1, AudioMixBufFree(&MixBuf)) : 0;
+ if (cSrcFramesToWrite > 0)
+ {
+ uint32_t const cbSrcToWrite = PDMAudioPropsFramesToBytes(&CfgSrc, cSrcFramesToWrite);
+ uint32_t cFrames = RTRandU32();
+ switch (RTRandU32Ex(0, 3))
+ {
+ default:
+ iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite);
+ AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ break;
+
+ case 1: /* zero & blend */
+ AudioMixBufSilence(&MixBuf, &WriteStateIgnZero, 0 /*offFrame*/, cSrcFramesToWrite);
+ iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite);
+ AudioMixBufBlend(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ break;
+
+ case 2: /* blend same equal data twice */
+ {
+ AUDIOMIXBUFWRITESTATE WriteStateSame = WriteState;
+ iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite);
+ AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ AudioMixBufBlend(&MixBuf, &WriteStateSame, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ break;
+ }
+ case 3: /* write & blend with zero */
+ {
+ AUDIOMIXBUFWRITESTATE WriteStateSame = WriteState;
+ iSrcTestSample = tstFillBuf(&CfgSrc, pvSrcTestSamples, iSrcTestSample, pbSrcBuf, cSrcFramesToWrite);
+ AudioMixBufWrite(&MixBuf, &WriteState, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ PDMAudioPropsClearBuffer(&CfgSrc, pbSrcBuf, cbSrcToWrite, cSrcFramesToWrite);
+ AudioMixBufBlend(&MixBuf, &WriteStateSame, pbSrcBuf, cbSrcToWrite, 0 /*offDstFrame*/, cSrcFramesToWrite, &cFrames);
+ RTTESTI_CHECK(cFrames == cSrcFramesToWrite);
+ break;
+ }
+ }
+ AudioMixBufCommit(&MixBuf, cSrcFramesToWrite);
+ }
+
+ /* Read some frames back. */
+ uint32_t const cUsed = AudioMixBufUsed(&MixBuf);
+ uint32_t const cDstFramesToRead = iIteration < 16 ? iIteration + 1 : iIteration + 5 >= cIterations ? cUsed
+ : cUsed ? RTRandU32Ex(1, cUsed) : 0;
+ if (cDstFramesToRead > 0)
+ {
+ uint32_t const cbDstToRead = PDMAudioPropsFramesToBytes(&CfgDst, cDstFramesToRead);
+ uint32_t cbRead = RTRandU32();
+ uint32_t cFrames = RTRandU32();
+ RTRandBytes(pbDstBuf, cbDstToRead);
+ AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, (iIteration & 3) != 2 ? cDstFramesToRead : cUsed, &cFrames,
+ &PeekState, pbDstBuf, (iIteration & 3) != 3 ? cbDstToRead : cbDstBuf, &cbRead);
+ RTTESTI_CHECK(cFrames == cDstFramesToRead);
+ RTTESTI_CHECK(cbRead == cbDstToRead);
+ AudioMixBufAdvance(&MixBuf, cFrames);
+
+ /* Verify if we can. */
+ if (PDMAudioPropsChannels(&CfgSrc) == PDMAudioPropsChannels(&CfgDst))
+ {
+ iDstTestSample = tstFillBuf(&CfgDst, pvDstTestSamples, iDstTestSample, pbDstExpect, cFrames);
+ if (memcmp(pbDstExpect, pbDstBuf, cbRead) == 0)
+ { /* likely */ }
+ else
+ {
+ RTTestFailed(hTest,
+ "mismatch: %.*Rhxs\n"
+ "expected: %.*Rhxs\n"
+ "iIteration=%u cDstFramesToRead=%u cbRead=%#x\n",
+ RT_MIN(cbRead, 48), pbDstBuf,
+ RT_MIN(cbRead, 48), pbDstExpect,
+ iIteration, cDstFramesToRead, cbRead);
+ break;
+ }
+ }
+ }
+ }
+
+ AudioMixBufTerm(&MixBuf);
+ RTMemFree(pbSrcBuf);
+ RTMemFree(pbDstBuf);
+ RTMemFree(pbDstExpect);
+}
+
+
+#if 0 /** @todo rewrite to non-parent/child setup */
+static void tstDownsampling(RTTEST hTest, uint32_t uFromHz, uint32_t uToHz)
+{
+ RTTestSubF(hTest, "Downsampling %u to %u Hz (S16)", uFromHz, uToHz);
+
+ struct { int16_t l, r; }
+ aSrcFrames[4096],
+ aDstFrames[4096];
+
+ /* Parent (destination) buffer is xxxHz 2ch S16 */
+ uint32_t const cFramesParent = RTRandU32Ex(16, RT_ELEMENTS(aDstFrames));
+ PDMAUDIOPCMPROPS const CfgDst = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uToHz, false /*fSwap*/);
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgDst));
+ AUDIOMIXBUF Parent;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&Parent, "ParentDownsampling", &CfgDst, cFramesParent));
+
+ /* Child (source) buffer is yyykHz 2ch S16 */
+ PDMAUDIOPCMPROPS const CfgSrc = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uFromHz, false /*fSwap*/);
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValid(&CfgSrc));
+ uint32_t const cFramesChild = RTRandU32Ex(32, RT_ELEMENTS(aSrcFrames));
+ AUDIOMIXBUF Child;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&Child, "ChildDownsampling", &CfgSrc, cFramesChild));
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufLinkTo(&Child, &Parent));
+
+ /*
+ * Test parameters.
+ */
+ uint32_t const cMaxSrcFrames = RT_MIN(cFramesParent * uFromHz / uToHz - 1, cFramesChild);
+ uint32_t const cIterations = RTRandU32Ex(4, 128);
+ RTTestErrContext(hTest, "cFramesParent=%RU32 cFramesChild=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32",
+ cFramesParent, cFramesChild, cMaxSrcFrames, cIterations);
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "cFramesParent=%RU32 cFramesChild=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32\n",
+ cFramesParent, cFramesChild, cMaxSrcFrames, cIterations);
+
+ /*
+ * We generate a simple "A" sine wave as input.
+ */
+ uint32_t iSrcFrame = 0;
+ uint32_t iDstFrame = 0;
+ double rdFixed = 2.0 * M_PI * 440.0 /* A */ / PDMAudioPropsHz(&CfgSrc); /* Fixed sin() input. */
+ for (uint32_t i = 0; i < cIterations; i++)
+ {
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i);
+
+ /*
+ * Generate source frames and write them.
+ */
+ uint32_t const cSrcFrames = i < cIterations / 2
+ ? RTRandU32Ex(2, cMaxSrcFrames) & ~(uint32_t)1
+ : RTRandU32Ex(1, cMaxSrcFrames - 1) | 1;
+ for (uint32_t j = 0; j < cSrcFrames; j++, iSrcFrame++)
+ aSrcFrames[j].r = aSrcFrames[j].l = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame);
+
+ uint32_t cSrcFramesWritten = UINT32_MAX / 2;
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&Child, 0, &aSrcFrames, cSrcFrames * sizeof(aSrcFrames[0]),
+ &cSrcFramesWritten));
+ RTTESTI_CHECK_MSG_BREAK(cSrcFrames == cSrcFramesWritten,
+ ("cSrcFrames=%RU32 vs cSrcFramesWritten=%RU32\n", cSrcFrames, cSrcFramesWritten));
+
+ /*
+ * Mix them.
+ */
+ uint32_t cSrcFramesMixed = UINT32_MAX / 2;
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&Child, cSrcFramesWritten, &cSrcFramesMixed));
+ RTTESTI_CHECK_MSG(AudioMixBufUsed(&Child) == 0, ("%RU32\n", AudioMixBufUsed(&Child)));
+ RTTESTI_CHECK_MSG_BREAK(cSrcFramesWritten == cSrcFramesMixed,
+ ("cSrcFramesWritten=%RU32 cSrcFramesMixed=%RU32\n", cSrcFramesWritten, cSrcFramesMixed));
+ RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&Child) == 0, ("%RU32\n", AudioMixBufUsed(&Child)));
+
+ /*
+ * Read out the parent buffer.
+ */
+ uint32_t cDstFrames = AudioMixBufUsed(&Parent);
+ while (cDstFrames > 0)
+ {
+ uint32_t cFramesRead = UINT32_MAX / 2;
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&Parent, aDstFrames, sizeof(aDstFrames), &cFramesRead));
+ RTTESTI_CHECK_MSG(cFramesRead > 0 && cFramesRead <= cDstFrames,
+ ("cFramesRead=%RU32 cDstFrames=%RU32\n", cFramesRead, cDstFrames));
+
+ AudioMixBufReleaseReadBlock(&Parent, cFramesRead);
+ AudioMixBufFinish(&Parent, cFramesRead);
+
+ iDstFrame += cFramesRead;
+ cDstFrames -= cFramesRead;
+ RTTESTI_CHECK(AudioMixBufUsed(&Parent) == cDstFrames);
+ }
+ }
+
+ RTTESTI_CHECK(AudioMixBufUsed(&Parent) == 0);
+ RTTESTI_CHECK(AudioMixBufLive(&Child) == 0);
+ uint32_t const cDstMinExpect = (uint64_t)iSrcFrame * uToHz / uFromHz;
+ uint32_t const cDstMaxExpect = ((uint64_t)iSrcFrame * uToHz + uFromHz - 1) / uFromHz;
+ RTTESTI_CHECK_MSG(iDstFrame == cDstMinExpect || iDstFrame == cDstMaxExpect,
+ ("iSrcFrame=%#x -> %#x,%#x; iDstFrame=%#x\n", iSrcFrame, cDstMinExpect, cDstMaxExpect, iDstFrame));
+
+ AudioMixBufDestroy(&Parent);
+ AudioMixBufDestroy(&Child);
+}
+#endif
+
+
+static void tstNewPeek(RTTEST hTest, uint32_t uFromHz, uint32_t uToHz)
+{
+ RTTestSubF(hTest, "New peek %u to %u Hz (S16)", uFromHz, uToHz);
+
+ struct { int16_t l, r; }
+ aSrcFrames[4096],
+ aDstFrames[4096];
+
+ /* Mix buffer is uFromHz 2ch S16 */
+ uint32_t const cFrames = RTRandU32Ex(16, RT_ELEMENTS(aSrcFrames));
+ PDMAUDIOPCMPROPS const CfgSrc = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uFromHz, false /*fSwap*/);
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&CfgSrc));
+ AUDIOMIXBUF MixBuf;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInit(&MixBuf, "NewPeekMixBuf", &CfgSrc, cFrames));
+
+ /* Write state (source). */
+ AUDIOMIXBUFWRITESTATE WriteState;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &CfgSrc));
+
+ /* Peek state (destination) is uToHz 2ch S16 */
+ PDMAUDIOPCMPROPS const CfgDst = PDMAUDIOPCMPROPS_INITIALIZER(2 /*cbSample*/, true /*fSigned*/, 2 /*ch*/, uToHz, false /*fSwap*/);
+ RTTESTI_CHECK(AudioHlpPcmPropsAreValidAndSupported(&CfgDst));
+ AUDIOMIXBUFPEEKSTATE PeekState;
+ RTTESTI_CHECK_RC_OK_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &CfgDst));
+
+ /*
+ * Test parameters.
+ */
+ uint32_t const cMaxSrcFrames = RT_MIN(cFrames * uFromHz / uToHz - 1, cFrames);
+ uint32_t const cIterations = RTRandU32Ex(64, 1024);
+ RTTestErrContext(hTest, "cFrames=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32", cFrames, cMaxSrcFrames, cIterations);
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "cFrames=%RU32 cMaxSrcFrames=%RU32 cIterations=%RU32\n",
+ cFrames, cMaxSrcFrames, cIterations);
+
+ /*
+ * We generate a simple "A" sine wave as input.
+ */
+ uint32_t iSrcFrame = 0;
+ uint32_t iDstFrame = 0;
+ double rdFixed = 2.0 * M_PI * 440.0 /* A */ / PDMAudioPropsHz(&CfgSrc); /* Fixed sin() input. */
+ for (uint32_t i = 0; i < cIterations; i++)
+ {
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i);
+
+ /*
+ * Generate source frames and write them.
+ */
+ uint32_t const cSrcFrames = i < cIterations / 2
+ ? RTRandU32Ex(2, cMaxSrcFrames) & ~(uint32_t)1
+ : RTRandU32Ex(1, cMaxSrcFrames - 1) | 1;
+ for (uint32_t j = 0; j < cSrcFrames; j++, iSrcFrame++)
+ aSrcFrames[j].r = aSrcFrames[j].l = 32760 /*Amplitude*/ * sin(rdFixed * iSrcFrame);
+
+ uint32_t cSrcFramesWritten = UINT32_MAX / 2;
+ AudioMixBufWrite(&MixBuf, &WriteState, &aSrcFrames[0], cSrcFrames * sizeof(aSrcFrames[0]),
+ 0 /*offDstFrame*/, cSrcFrames, &cSrcFramesWritten);
+ RTTESTI_CHECK_MSG_BREAK(cSrcFrames == cSrcFramesWritten,
+ ("cSrcFrames=%RU32 vs cSrcFramesWritten=%RU32 cLiveFrames=%RU32\n",
+ cSrcFrames, cSrcFramesWritten, AudioMixBufUsed(&MixBuf)));
+ AudioMixBufCommit(&MixBuf, cSrcFrames);
+
+ /*
+ * Read out all the frames using the peek function.
+ */
+ uint32_t offSrcFrame = 0;
+ while (offSrcFrame < cSrcFramesWritten)
+ {
+ uint32_t cSrcFramesToRead = cSrcFramesWritten - offSrcFrame;
+ uint32_t cTmp = (uint64_t)cSrcFramesToRead * uToHz / uFromHz;
+ if (cTmp + 32 >= RT_ELEMENTS(aDstFrames))
+ cSrcFramesToRead = ((uint64_t)RT_ELEMENTS(aDstFrames) - 32) * uFromHz / uToHz; /* kludge */
+
+ uint32_t cSrcFramesPeeked = UINT32_MAX / 4;
+ uint32_t cbDstPeeked = UINT32_MAX / 2;
+ RTRandBytes(aDstFrames, sizeof(aDstFrames));
+ AudioMixBufPeek(&MixBuf, offSrcFrame, cSrcFramesToRead, &cSrcFramesPeeked,
+ &PeekState, aDstFrames, sizeof(aDstFrames), &cbDstPeeked);
+ uint32_t cDstFramesPeeked = PDMAudioPropsBytesToFrames(&CfgDst, cbDstPeeked);
+ RTTESTI_CHECK(cbDstPeeked > 0 || cSrcFramesPeeked > 0);
+
+ if (uFromHz == uToHz)
+ {
+ for (uint32_t iDst = 0; iDst < cDstFramesPeeked; iDst++)
+ if (memcmp(&aDstFrames[iDst], &aSrcFrames[offSrcFrame + iDst], sizeof(aSrcFrames[0])) != 0)
+ RTTestFailed(hTest, "Frame #%u differs: %#x / %#x, expected %#x / %#x\n", iDstFrame + iDst,
+ aDstFrames[iDst].l, aDstFrames[iDst].r,
+ aSrcFrames[iDst + offSrcFrame].l, aSrcFrames[iDst + offSrcFrame].r);
+ }
+
+ offSrcFrame += cSrcFramesPeeked;
+ iDstFrame += cDstFramesPeeked;
+ }
+
+ /*
+ * Then advance.
+ */
+ AudioMixBufAdvance(&MixBuf, cSrcFrames);
+ RTTESTI_CHECK(AudioMixBufUsed(&MixBuf) == 0);
+ }
+
+ /** @todo this is a bit lax... */
+ uint32_t const cDstMinExpect = ((uint64_t)iSrcFrame * uToHz - uFromHz - 1) / uFromHz;
+ uint32_t const cDstMaxExpect = ((uint64_t)iSrcFrame * uToHz + uFromHz - 1) / uFromHz;
+ RTTESTI_CHECK_MSG(iDstFrame >= cDstMinExpect && iDstFrame <= cDstMaxExpect,
+ ("iSrcFrame=%#x -> %#x..%#x; iDstFrame=%#x (delta %d)\n",
+ iSrcFrame, cDstMinExpect, cDstMaxExpect, iDstFrame, (cDstMinExpect + cDstMaxExpect) / 2 - iDstFrame));
+
+ AudioMixBufTerm(&MixBuf);
+}
+
+/* Test volume control. */
+static void tstVolume(RTTEST hTest)
+{
+ RTTestSub(hTest, "Volume control (44.1kHz S16 2ch)");
+ uint32_t const cBufSize = 256;
+
+ /*
+ * Configure a mixbuf where we read and write 44.1kHz S16 2ch.
+ */
+ PDMAUDIOPCMPROPS const Cfg = PDMAUDIOPCMPROPS_INITIALIZER(
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 44100, /* Hz */
+ false /* Swap Endian */
+ );
+ AUDIOMIXBUF MixBuf;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInit(&MixBuf, "Volume", &Cfg, cBufSize), VINF_SUCCESS);
+
+ AUDIOMIXBUFWRITESTATE WriteState;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInitWriteState(&MixBuf, &WriteState, &Cfg), VINF_SUCCESS);
+
+ AUDIOMIXBUFPEEKSTATE PeekState;
+ RTTESTI_CHECK_RC_RETV(AudioMixBufInitPeekState(&MixBuf, &PeekState, &Cfg), VINF_SUCCESS);
+
+ /*
+ * A few 16-bit signed test samples.
+ */
+ static int16_t const s_aFrames16S[16] =
+ {
+ INT16_MIN, INT16_MIN + 1, -128, -64, -4, -1, 0, 1,
+ 2, 255, 256, INT16_MAX / 2, INT16_MAX - 2, INT16_MAX - 1, INT16_MAX, 0,
+ };
+
+ /*
+ * 1) Full volume/0dB attenuation (255).
+ */
+ PDMAUDIOVOLUME Vol = PDMAUDIOVOLUME_INITIALIZER_MAX;
+ AudioMixBufSetVolume(&MixBuf, &Vol);
+
+ /* Write all the test frames to the mixer buffer: */
+ uint32_t cFramesWritten;
+ AudioMixBufWrite(&MixBuf, &WriteState, &s_aFrames16S[0], sizeof(s_aFrames16S), 0 /*offDstFrame*/, cBufSize, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == RT_ELEMENTS(s_aFrames16S) / 2);
+ AudioMixBufCommit(&MixBuf, cFramesWritten);
+
+ /* Read them back. We should get them back just like we wrote them. */
+ uint16_t au16Buf[cBufSize * 2];
+ uint32_t cFramesPeeked;
+ uint32_t cbPeeked;
+ AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, cFramesWritten, &cFramesPeeked, &PeekState, au16Buf, sizeof(au16Buf), &cbPeeked);
+ RTTESTI_CHECK(cFramesPeeked == cFramesWritten);
+ RTTESTI_CHECK(cbPeeked == PDMAudioPropsFramesToBytes(&Cfg, cFramesPeeked));
+ AudioMixBufAdvance(&MixBuf, cFramesPeeked);
+
+ /* Check that at 0dB the frames came out unharmed. */
+ if (memcmp(au16Buf, s_aFrames16S, sizeof(s_aFrames16S)) != 0)
+ RTTestFailed(hTest,
+ "0dB test failed\n"
+ "mismatch: %.*Rhxs\n"
+ "expected: %.*Rhxs\n",
+ sizeof(s_aFrames16S), au16Buf, sizeof(s_aFrames16S), s_aFrames16S);
+
+ /*
+ * 2) Half volume/-6dB attenuation (16 steps down).
+ */
+ PDMAudioVolumeInitFromStereo(&Vol, false, 255 - 16, 255 - 16);
+ AudioMixBufSetVolume(&MixBuf, &Vol);
+
+ /* Write all the test frames to the mixer buffer: */
+ AudioMixBufWrite(&MixBuf, &WriteState, &s_aFrames16S[0], sizeof(s_aFrames16S), 0 /*offDstFrame*/, cBufSize, &cFramesWritten);
+ RTTESTI_CHECK(cFramesWritten == RT_ELEMENTS(s_aFrames16S) / 2);
+ AudioMixBufCommit(&MixBuf, cFramesWritten);
+
+ /* Read them back. We should get them back just like we wrote them. */
+ AudioMixBufPeek(&MixBuf, 0 /*offSrcFrame*/, cFramesWritten, &cFramesPeeked, &PeekState, au16Buf, sizeof(au16Buf), &cbPeeked);
+ RTTESTI_CHECK(cFramesPeeked == cFramesWritten);
+ RTTESTI_CHECK(cbPeeked == PDMAudioPropsFramesToBytes(&Cfg, cFramesPeeked));
+ AudioMixBufAdvance(&MixBuf, cFramesPeeked);
+
+ /* Check that at -6dB the sample values are halved. */
+ int16_t ai16Expect[sizeof(s_aFrames16S) / 2];
+ memcpy(ai16Expect, s_aFrames16S, sizeof(ai16Expect));
+ for (uintptr_t i = 0; i < RT_ELEMENTS(ai16Expect); i++)
+ ai16Expect[i] >>= 1; /* /= 2 - not the same for signed numbers; */
+ if (memcmp(au16Buf, ai16Expect, sizeof(ai16Expect)) != 0)
+ RTTestFailed(hTest,
+ "-6dB test failed\n"
+ "mismatch: %.*Rhxs\n"
+ "expected: %.*Rhxs\n"
+ "wrote: %.*Rhxs\n",
+ sizeof(ai16Expect), au16Buf, sizeof(ai16Expect), ai16Expect, sizeof(s_aFrames16S), s_aFrames16S);
+
+ AudioMixBufTerm(&MixBuf);
+}
+
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ /*
+ * Initialize IPRT and create the test.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstAudioMixBuffer", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ tstBasics(hTest);
+ tstSimple(hTest);
+
+ /* Run tstConversion for all combinations we have test data. */
+ for (unsigned iSrc = 0; iSrc < RT_ELEMENTS(g_aTestSamples); iSrc++)
+ {
+ for (unsigned iSrcSigned = 0; iSrcSigned < RT_ELEMENTS(g_aTestSamples[0].apv); iSrcSigned++)
+ if (g_aTestSamples[iSrc].apv[iSrcSigned])
+ for (unsigned cSrcChs = 1; cSrcChs <= 2; cSrcChs++)
+ for (unsigned iDst = 0; iDst < RT_ELEMENTS(g_aTestSamples); iDst++)
+ for (unsigned iDstSigned = 0; iDstSigned < RT_ELEMENTS(g_aTestSamples[0].apv); iDstSigned++)
+ if (g_aTestSamples[iDst].apv[iDstSigned])
+ for (unsigned cDstChs = 1; cDstChs <= 2; cDstChs++)
+ tstConversion(hTest, iSrc * 8, iSrcSigned == 1, cSrcChs,
+ /*->*/ iDst * 8, iDstSigned == 1, cDstChs);
+ }
+
+#if 0 /** @todo rewrite to non-parent/child setup */
+ tstDownsampling(hTest, 44100, 22050);
+ tstDownsampling(hTest, 48000, 44100);
+ tstDownsampling(hTest, 48000, 22050);
+ tstDownsampling(hTest, 48000, 11000);
+#endif
+ tstNewPeek(hTest, 48000, 48000);
+ tstNewPeek(hTest, 48000, 11000);
+ tstNewPeek(hTest, 48000, 44100);
+ tstNewPeek(hTest, 44100, 22050);
+ tstNewPeek(hTest, 44100, 11000);
+ //tstNewPeek(hTest, 11000, 48000);
+ //tstNewPeek(hTest, 22050, 44100);
+
+ tstVolume(hTest);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
diff --git a/src/VBox/Devices/Audio/testcase/tstAudioTestService.cpp b/src/VBox/Devices/Audio/testcase/tstAudioTestService.cpp
new file mode 100644
index 00000000..9868218c
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/tstAudioTestService.cpp
@@ -0,0 +1,178 @@
+/* $Id: tstAudioTestService.cpp $ */
+/** @file
+ * Audio testcase - Tests for the Audio Test Service (ATS).
+ */
+
+/*
+ * Copyright (C) 2021-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+
+#include "../AudioTestService.h"
+#include "../AudioTestServiceClient.h"
+
+
+static size_t g_cToRead = _1M;
+static size_t g_cbRead = 0;
+
+
+/** @copydoc ATSCALLBACKS::pfnTestSetSendRead */
+static DECLCALLBACK(int) tstTestSetSendReadCallback(void const *pvUser,
+ const char *pszTag, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ RT_NOREF(pvUser, pszTag);
+
+ size_t cbToRead = RT_MIN(g_cToRead - g_cbRead, cbBuf);
+ if (cbToRead)
+ {
+ memset(pvBuf, 0x42, cbToRead);
+ g_cbRead += cbToRead;
+ }
+
+ *pcbRead = cbToRead;
+
+ return VINF_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+
+ /*
+ * Initialize IPRT and create the test.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstAudioTestService", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ ATSCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnTestSetSendRead = tstTestSetSendReadCallback;
+
+ ATSSERVER Srv;
+ rc = AudioTestSvcInit(&Srv, &Callbacks);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ RTGETOPTUNION Val;
+ RT_ZERO(Val);
+
+ Val.u32 = ATSCONNMODE_SERVER;
+ rc = AudioTestSvcHandleOption(&Srv, ATSTCPOPT_CONN_MODE, &Val);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ ATSCLIENT Client;
+
+ if (RT_SUCCESS(rc))
+ {
+ uint16_t uPort = ATS_TCP_DEF_BIND_PORT_HOST;
+
+ for (unsigned i = 0; i < 64; i++)
+ {
+ Val.u16 = uPort;
+ rc = AudioTestSvcHandleOption(&Srv, ATSTCPOPT_BIND_PORT, &Val);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ rc = AudioTestSvcStart(&Srv);
+ if (RT_SUCCESS(rc))
+ break;
+
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Port %RU32 already used\n", uPort);
+
+ /* Use a different port base in case VBox already is running
+ * with the same service using ATS_TCP_DEF_BIND_PORT_HOST. */
+ uPort = ATS_TCP_DEF_BIND_PORT_HOST + RTRandU32Ex(0, 4242);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Using port %RU32\n", uPort);
+
+ rc = AudioTestSvcClientCreate(&Client);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ Val.u32 = ATSCONNMODE_CLIENT;
+ rc = AudioTestSvcClientHandleOption(&Client, ATSTCPOPT_CONN_MODE, &Val);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ Val.psz = ATS_TCP_DEF_CONNECT_HOST_ADDR_STR;
+ rc = AudioTestSvcClientHandleOption(&Client, ATSTCPOPT_CONNECT_ADDRESS, &Val);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ Val.u16 = uPort;
+ rc = AudioTestSvcClientHandleOption(&Client, ATSTCPOPT_CONNECT_PORT, &Val);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ rc = AudioTestSvcClientConnect(&Client);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ char szTemp[RTPATH_MAX];
+ rc = RTPathTemp(szTemp, sizeof(szTemp));
+ RTTEST_CHECK_RC_OK(hTest, rc);
+ if (RT_SUCCESS(rc))
+ {
+ char szName[RTPATH_MAX];
+ RTTESTI_CHECK_RC(rc = RTStrCopy(szName, sizeof(szName), szTemp), VINF_SUCCESS);
+ RTTESTI_CHECK_RC(rc = RTPathAppend(szName, sizeof(szName), "tstAudioTestService-XXXXX"), VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioTestSvcClientTestSetDownload(&Client, "ignored", szName);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ /* ignore rc */ RTFileDelete(szName);
+ }
+ }
+ }
+
+ rc = AudioTestSvcClientDisconnect(&Client);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ AudioTestSvcClientDestroy(&Client);
+
+ rc = AudioTestSvcStop(&Srv);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ rc = AudioTestSvcDestroy(&Srv);
+ RTTEST_CHECK_RC_OK(hTest, rc);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}