summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Devices/Audio
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Audio')
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer.cpp2040
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer.h93
-rw-r--r--src/VBox/Devices/Audio/AudioMixer.cpp2236
-rw-r--r--src/VBox/Devices/Audio/AudioMixer.h263
-rw-r--r--src/VBox/Devices/Audio/DevHDA.cpp5414
-rw-r--r--src/VBox/Devices/Audio/DevHDA.h222
-rw-r--r--src/VBox/Devices/Audio/DevHDACommon.cpp719
-rw-r--r--src/VBox/Devices/Audio/DevHDACommon.h658
-rw-r--r--src/VBox/Devices/Audio/DevIchAc97.cpp4575
-rw-r--r--src/VBox/Devices/Audio/DevSB16.cpp2648
-rw-r--r--src/VBox/Devices/Audio/DrvAudio.cpp3676
-rw-r--r--src/VBox/Devices/Audio/DrvAudio.h286
-rw-r--r--src/VBox/Devices/Audio/DrvAudioCommon.cpp1927
-rw-r--r--src/VBox/Devices/Audio/DrvHostALSAAudio.cpp1530
-rw-r--r--src/VBox/Devices/Audio/DrvHostCoreAudio.cpp2715
-rw-r--r--src/VBox/Devices/Audio/DrvHostDSound.cpp2659
-rw-r--r--src/VBox/Devices/Audio/DrvHostDebugAudio.cpp428
-rw-r--r--src/VBox/Devices/Audio/DrvHostNullAudio.cpp415
-rw-r--r--src/VBox/Devices/Audio/DrvHostOSSAudio.cpp1169
-rw-r--r--src/VBox/Devices/Audio/DrvHostPulseAudio.cpp1744
-rw-r--r--src/VBox/Devices/Audio/DrvHostValidationKit.cpp482
-rw-r--r--src/VBox/Devices/Audio/HDACodec.cpp3301
-rw-r--r--src/VBox/Devices/Audio/HDACodec.h148
-rw-r--r--src/VBox/Devices/Audio/HDAStream.cpp2009
-rw-r--r--src/VBox/Devices/Audio/HDAStream.h309
-rw-r--r--src/VBox/Devices/Audio/HDAStreamChannel.cpp101
-rw-r--r--src/VBox/Devices/Audio/HDAStreamChannel.h30
-rw-r--r--src/VBox/Devices/Audio/HDAStreamMap.cpp174
-rw-r--r--src/VBox/Devices/Audio/HDAStreamMap.h61
-rw-r--r--src/VBox/Devices/Audio/HDAStreamPeriod.cpp431
-rw-r--r--src/VBox/Devices/Audio/HDAStreamPeriod.h114
-rw-r--r--src/VBox/Devices/Audio/Makefile.kup0
-rw-r--r--src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp246
-rw-r--r--src/VBox/Devices/Audio/VBoxMMNotificationClient.h91
-rw-r--r--src/VBox/Devices/Audio/alsa_mangling.h72
-rw-r--r--src/VBox/Devices/Audio/alsa_stubs.c243
-rw-r--r--src/VBox/Devices/Audio/alsa_stubs.h25
-rw-r--r--src/VBox/Devices/Audio/pulse_mangling.h93
-rw-r--r--src/VBox/Devices/Audio/pulse_stubs.c348
-rw-r--r--src/VBox/Devices/Audio/pulse_stubs.h25
-rw-r--r--src/VBox/Devices/Audio/testcase/Makefile.kmk42
-rw-r--r--src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp651
42 files changed, 44413 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.cpp b/src/VBox/Devices/Audio/AudioMixBuffer.cpp
new file mode 100644
index 00000000..2bd220b2
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixBuffer.cpp
@@ -0,0 +1,2040 @@
+/* $Id: AudioMixBuffer.cpp $ */
+/** @file
+ * Audio mixing buffer for converting reading/writing audio data.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_AUDIO_MIXER_BUFFER
+#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 <VBox/err.h>
+
+#include "AudioMixBuffer.h"
+
+#ifndef VBOX_AUDIO_TESTCASE
+# ifdef DEBUG
+# define AUDMIXBUF_LOG(x) LogFlowFunc(x)
+# else
+# define AUDMIXBUF_LOG(x) do {} while (0)
+# endif
+#else /* VBOX_AUDIO_TESTCASE */
+# define AUDMIXBUF_LOG(x) RTPrintf x
+#endif
+
+#ifdef DEBUG
+DECLINLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc);
+DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF pMixBuf);
+#endif
+
+/*
+ * 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.
+ */
+
+
+/** Logarithmic/exponential volume conversion table. */
+static uint32_t 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 */
+};
+
+/* Bit shift for fixed point conversion. */
+#define AUDIOMIXBUF_VOL_SHIFT 30
+
+/* Internal representation of 0dB volume (1.0 in fixed point). */
+#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. */
+
+
+/**
+ * Peeks for audio frames without any conversion done.
+ * This will get the raw frame data out of a mixing buffer.
+ *
+ * @return IPRT status code or VINF_AUDIO_MORE_DATA_AVAILABLE if more data is available to read.
+ *
+ * @param pMixBuf Mixing buffer to acquire audio frames from.
+ * @param cFramesToRead Number of audio frames to read.
+ * @param paFrameBuf Buffer where to store the returned audio frames.
+ * @param cFrameBuf Size (in frames) of the buffer to store audio frames into.
+ * @param pcFramesRead Returns number of read audio frames. Optional.
+ *
+ * @remark This function is not thread safe!
+ */
+int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead,
+ PPDMAUDIOFRAME paFrameBuf, uint32_t cFrameBuf, uint32_t *pcFramesRead)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(paFrameBuf, VERR_INVALID_POINTER);
+ AssertReturn(cFrameBuf, VERR_INVALID_PARAMETER);
+ /* pcRead is optional. */
+
+ int rc;
+
+ if (!cFramesToRead)
+ {
+ if (pcFramesRead)
+ *pcFramesRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cRead;
+ if (pMixBuf->offRead + cFramesToRead > pMixBuf->cFrames)
+ {
+ cRead = pMixBuf->cFrames - pMixBuf->offRead;
+ rc = VINF_AUDIO_MORE_DATA_AVAILABLE;
+ }
+ else
+ {
+ cRead = cFramesToRead;
+ rc = VINF_SUCCESS;
+ }
+
+ if (cRead > cFrameBuf)
+ {
+ cRead = cFrameBuf;
+ rc = VINF_AUDIO_MORE_DATA_AVAILABLE;
+ }
+
+ if (cRead)
+ {
+ memcpy(paFrameBuf, &pMixBuf->pFrames[pMixBuf->offRead], sizeof(PDMAUDIOFRAME) * cRead);
+
+ pMixBuf->offRead = (pMixBuf->offRead + cRead) % pMixBuf->cFrames;
+ Assert(pMixBuf->offRead <= pMixBuf->cFrames);
+ pMixBuf->cUsed -= RT_MIN(cRead, pMixBuf->cUsed);
+ }
+
+ if (pcFramesRead)
+ *pcFramesRead = cRead;
+
+ return rc;
+}
+
+/**
+ * Returns a mutable pointer to the mixing buffer's audio frame buffer for writing raw
+ * audio frames.
+ *
+ * @return IPRT status code. VINF_TRY_AGAIN for getting next pointer at beginning (circular).
+ * @param pMixBuf Mixing buffer to acquire audio frames from.
+ * @param cFrames Number of requested audio frames to write.
+ * @param ppvFrames Returns a mutable pointer to the buffer's audio frame data.
+ * @param pcFramesToWrite Number of available audio frames to write.
+ *
+ * @remark This function is not thread safe!
+ */
+int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames,
+ PPDMAUDIOFRAME *ppvFrames, uint32_t *pcFramesToWrite)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppvFrames, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcFramesToWrite, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (!cFrames)
+ {
+ *pcFramesToWrite = 0;
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cFramesToWrite;
+ if (pMixBuf->offWrite + cFrames > pMixBuf->cFrames)
+ {
+ cFramesToWrite = pMixBuf->cFrames - pMixBuf->offWrite;
+ rc = VINF_TRY_AGAIN;
+ }
+ else
+ {
+ cFramesToWrite = cFrames;
+ rc = VINF_SUCCESS;
+ }
+
+ *ppvFrames = &pMixBuf->pFrames[pMixBuf->offWrite];
+ AssertPtr(ppvFrames);
+
+ pMixBuf->offWrite = (pMixBuf->offWrite + cFramesToWrite) % pMixBuf->cFrames;
+ Assert(pMixBuf->offWrite <= pMixBuf->cFrames);
+ pMixBuf->cUsed += RT_MIN(cFramesToWrite, pMixBuf->cUsed);
+
+ *pcFramesToWrite = cFramesToWrite;
+
+ return rc;
+}
+
+/**
+ * Clears the entire frame buffer.
+ *
+ * @param pMixBuf Mixing buffer to clear.
+ *
+ */
+void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturnVoid(pMixBuf);
+
+ if (pMixBuf->cFrames)
+ RT_BZERO(pMixBuf->pFrames, pMixBuf->cFrames * sizeof(PDMAUDIOFRAME));
+}
+
+/**
+ * Clears (zeroes) the buffer by a certain amount of (used) frames and
+ * keeps track to eventually assigned children buffers.
+ *
+ * @param pMixBuf Mixing buffer to clear.
+ * @param cFramesToClear Number of audio frames to clear.
+ */
+void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear)
+{
+ AUDMIXBUF_LOG(("cFramesToClear=%RU32\n", cFramesToClear));
+ AUDMIXBUF_LOG(("%s: offRead=%RU32, cUsed=%RU32\n",
+ pMixBuf->pszName, pMixBuf->offRead, pMixBuf->cUsed));
+
+ AssertStmt(cFramesToClear <= pMixBuf->cFrames, cFramesToClear = pMixBuf->cFrames);
+
+ PPDMAUDIOMIXBUF pIter;
+ RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node)
+ {
+ AUDMIXBUF_LOG(("\t%s: cMixed=%RU32 -> %RU32\n",
+ pIter->pszName, pIter->cMixed, pIter->cMixed - cFramesToClear));
+
+ pIter->cMixed -= RT_MIN(pIter->cMixed, cFramesToClear);
+ /* Note: Do not increment pIter->cUsed here, as this gets done when reading from that buffer using AudioMixBufReadXXX. */
+ }
+
+ uint32_t cClearOff;
+ uint32_t cClearLen;
+
+ /* Clear end of buffer (wrap around). */
+ if (cFramesToClear > pMixBuf->offRead)
+ {
+ cClearOff = pMixBuf->cFrames - (cFramesToClear - pMixBuf->offRead);
+ cClearLen = pMixBuf->cFrames - cClearOff;
+
+ AUDMIXBUF_LOG(("Clearing1: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen));
+
+ RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME));
+
+ Assert(cFramesToClear >= cClearLen);
+ cFramesToClear -= cClearLen;
+ }
+
+ /* Clear beginning of buffer. */
+ if ( cFramesToClear
+ && pMixBuf->offRead)
+ {
+ Assert(pMixBuf->offRead >= cFramesToClear);
+
+ cClearOff = pMixBuf->offRead - cFramesToClear;
+ cClearLen = cFramesToClear;
+
+ Assert(cClearOff + cClearLen <= pMixBuf->cFrames);
+
+ AUDMIXBUF_LOG(("Clearing2: %RU32 - %RU32\n", cClearOff, cClearOff + cClearLen));
+
+ RT_BZERO(pMixBuf->pFrames + cClearOff, cClearLen * sizeof(PDMAUDIOFRAME));
+ }
+}
+
+/**
+ * Destroys (uninitializes) a mixing buffer.
+ *
+ * @param pMixBuf Mixing buffer to destroy.
+ */
+void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf)
+{
+ if (!pMixBuf)
+ return;
+
+ AudioMixBufUnlink(pMixBuf);
+
+ if (pMixBuf->pszName)
+ {
+ AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName));
+
+ RTStrFree(pMixBuf->pszName);
+ pMixBuf->pszName = NULL;
+ }
+
+ if (pMixBuf->pRate)
+ {
+ RTMemFree(pMixBuf->pRate);
+ pMixBuf->pRate = NULL;
+ }
+
+ if (pMixBuf->pFrames)
+ {
+ Assert(pMixBuf->cFrames);
+
+ RTMemFree(pMixBuf->pFrames);
+ pMixBuf->pFrames = NULL;
+ }
+
+ pMixBuf->cFrames = 0;
+}
+
+/**
+ * Returns the size (in audio frames) of free audio buffer space.
+ *
+ * @return uint32_t Size (in audio frames) of free audio buffer space.
+ * @param pMixBuf Mixing buffer to return free size for.
+ */
+uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+
+ uint32_t cFrames, cFramesFree;
+ if (pMixBuf->pParent)
+ {
+ /*
+ * As a linked child buffer we want to know how many frames
+ * already have been consumed by the parent.
+ */
+ cFrames = pMixBuf->pParent->cFrames;
+
+ Assert(pMixBuf->cMixed <= cFrames);
+ cFramesFree = cFrames - pMixBuf->cMixed;
+ }
+ else /* As a parent. */
+ {
+ cFrames = pMixBuf->cFrames;
+ Assert(cFrames >= pMixBuf->cUsed);
+ cFramesFree = pMixBuf->cFrames - pMixBuf->cUsed;
+ }
+
+ AUDMIXBUF_LOG(("%s: %RU32 of %RU32\n", pMixBuf->pszName, cFramesFree, cFrames));
+ return cFramesFree;
+}
+
+/**
+ * Returns the size (in bytes) of free audio buffer space.
+ *
+ * @return uint32_t Size (in bytes) of free audio buffer space.
+ * @param pMixBuf Mixing buffer to return free size for.
+ */
+uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf)
+{
+ return AUDIOMIXBUF_F2B(pMixBuf, AudioMixBufFree(pMixBuf));
+}
+
+/**
+ * Allocates the internal audio frame buffer.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to allocate frame buffer for.
+ * @param cFrames Number of audio frames to allocate.
+ */
+static int audioMixBufAlloc(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFrames)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertReturn(cFrames, VERR_INVALID_PARAMETER);
+
+ AUDMIXBUF_LOG(("%s: cFrames=%RU32\n", pMixBuf->pszName, cFrames));
+
+ size_t cbFrames = cFrames * sizeof(PDMAUDIOFRAME);
+ pMixBuf->pFrames = (PPDMAUDIOFRAME)RTMemAllocZ(cbFrames);
+ if (pMixBuf->pFrames)
+ {
+ pMixBuf->cFrames = cFrames;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+#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
+
+/**
+ * Macro for generating the conversion routines from/to different formats.
+ * Be careful what to pass in/out, as most of the macros are optimized for speed and
+ * thus don't do any bounds checking!
+ *
+ * Note: Currently does not handle any endianness conversion yet!
+ */
+#define AUDMIXBUF_CONVERT(_aName, _aType, _aMin, _aMax, _aSigned, _aShift) \
+ /* Clips a specific output value to a single sample value. */ \
+ DECLCALLBACK(int64_t) audioMixBufClipFrom##_aName(_aType aVal) \
+ { \
+ /* left shifting of signed values is not defined, therefore the intermediate uint64_t cast */ \
+ if (_aSigned) \
+ return (int64_t) (((uint64_t) ((int64_t) aVal )) << (32 - _aShift)); \
+ return (int64_t) (((uint64_t) ((int64_t) aVal - ((_aMax >> 1) + 1))) << (32 - _aShift)); \
+ } \
+ \
+ /* Clips a single sample value to a specific output value. */ \
+ DECLCALLBACK(_aType) audioMixBufClipTo##_aName(int64_t iVal) \
+ { \
+ if (iVal >= 0x7fffffff) \
+ return _aMax; \
+ if (iVal < -INT64_C(0x80000000)) \
+ return _aMin; \
+ \
+ if (_aSigned) \
+ return (_aType) (iVal >> (32 - _aShift)); \
+ return ((_aType) ((iVal >> (32 - _aShift)) + ((_aMax >> 1) + 1))); \
+ } \
+ \
+ DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Stereo(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \
+ PCPDMAUDMIXBUFCONVOPTS pOpts) \
+ { \
+ _aType const *pSrc = (_aType const *)pvSrc; \
+ uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \
+ AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \
+ pOpts->cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \
+ for (uint32_t i = 0; i < cFrames; i++) \
+ { \
+ paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uLeft ) >> AUDIOMIXBUF_VOL_SHIFT; \
+ paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc++), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \
+ paDst++; \
+ } \
+ \
+ return cFrames; \
+ } \
+ \
+ DECLCALLBACK(uint32_t) audioMixBufConvFrom##_aName##Mono(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, \
+ PCPDMAUDMIXBUFCONVOPTS pOpts) \
+ { \
+ _aType const *pSrc = (_aType const *)pvSrc; \
+ const uint32_t cFrames = RT_MIN(pOpts->cFrames, cbSrc / sizeof(_aType)); \
+ AUDMIXBUF_MACRO_LOG(("cFrames=%RU32, BpS=%zu, lVol=%RU32, rVol=%RU32\n", \
+ cFrames, sizeof(_aType), pOpts->From.Volume.uLeft, pOpts->From.Volume.uRight)); \
+ for (uint32_t i = 0; i < cFrames; i++) \
+ { \
+ paDst->i64LSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uLeft) >> AUDIOMIXBUF_VOL_SHIFT; \
+ paDst->i64RSample = ASMMult2xS32RetS64((int32_t)audioMixBufClipFrom##_aName(*pSrc), pOpts->From.Volume.uRight) >> AUDIOMIXBUF_VOL_SHIFT; \
+ pSrc++; \
+ paDst++; \
+ } \
+ \
+ return cFrames; \
+ } \
+ \
+ DECLCALLBACK(void) audioMixBufConvTo##_aName##Stereo(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \
+ { \
+ PCPDMAUDIOFRAME pSrc = paSrc; \
+ _aType *pDst = (_aType *)pvDst; \
+ _aType l, r; \
+ uint32_t cFrames = pOpts->cFrames; \
+ while (cFrames--) \
+ { \
+ AUDMIXBUF_MACRO_LOG(("%p: l=%RI64, r=%RI64\n", pSrc, pSrc->i64LSample, pSrc->i64RSample)); \
+ l = audioMixBufClipTo##_aName(pSrc->i64LSample); \
+ r = audioMixBufClipTo##_aName(pSrc->i64RSample); \
+ AUDMIXBUF_MACRO_LOG(("\t-> l=%RI16, r=%RI16\n", l, r)); \
+ *pDst++ = l; \
+ *pDst++ = r; \
+ pSrc++; \
+ } \
+ } \
+ \
+ DECLCALLBACK(void) audioMixBufConvTo##_aName##Mono(void *pvDst, PCPDMAUDIOFRAME paSrc, PCPDMAUDMIXBUFCONVOPTS pOpts) \
+ { \
+ PCPDMAUDIOFRAME pSrc = paSrc; \
+ _aType *pDst = (_aType *)pvDst; \
+ uint32_t cFrames = pOpts->cFrames; \
+ while (cFrames--) \
+ { \
+ *pDst++ = audioMixBufClipTo##_aName((pSrc->i64LSample + pSrc->i64RSample) / 2); \
+ pSrc++; \
+ } \
+ }
+
+/* audioMixBufConvXXXS8: 8 bit, signed. */
+AUDMIXBUF_CONVERT(S8 /* Name */, int8_t, INT8_MIN /* Min */, INT8_MAX /* Max */, true /* fSigned */, 8 /* cShift */)
+/* audioMixBufConvXXXU8: 8 bit, unsigned. */
+AUDMIXBUF_CONVERT(U8 /* Name */, uint8_t, 0 /* Min */, UINT8_MAX /* Max */, false /* fSigned */, 8 /* cShift */)
+/* audioMixBufConvXXXS16: 16 bit, signed. */
+AUDMIXBUF_CONVERT(S16 /* Name */, int16_t, INT16_MIN /* Min */, INT16_MAX /* Max */, true /* fSigned */, 16 /* cShift */)
+/* audioMixBufConvXXXU16: 16 bit, unsigned. */
+AUDMIXBUF_CONVERT(U16 /* Name */, uint16_t, 0 /* Min */, UINT16_MAX /* Max */, false /* fSigned */, 16 /* cShift */)
+/* audioMixBufConvXXXS32: 32 bit, signed. */
+AUDMIXBUF_CONVERT(S32 /* Name */, int32_t, INT32_MIN /* Min */, INT32_MAX /* Max */, true /* fSigned */, 32 /* cShift */)
+/* audioMixBufConvXXXU32: 32 bit, unsigned. */
+AUDMIXBUF_CONVERT(U32 /* Name */, uint32_t, 0 /* Min */, UINT32_MAX /* Max */, false /* fSigned */, 32 /* cShift */)
+
+#undef AUDMIXBUF_CONVERT
+
+#define AUDMIXBUF_MIXOP(_aName, _aOp) \
+ static void audioMixBufOp##_aName(PPDMAUDIOFRAME paDst, uint32_t cDstFrames, \
+ PPDMAUDIOFRAME paSrc, uint32_t cSrcFrames, \
+ PPDMAUDIOSTREAMRATE pRate, \
+ uint32_t *pcDstWritten, uint32_t *pcSrcRead) \
+ { \
+ AUDMIXBUF_MACRO_LOG(("cSrcFrames=%RU32, cDstFrames=%RU32\n", cSrcFrames, cDstFrames)); \
+ AUDMIXBUF_MACRO_LOG(("Rate: srcOffset=%RU32, dstOffset=%RU32, dstInc=%RU32\n", \
+ pRate->srcOffset, \
+ (uint32_t)(pRate->dstOffset >> 32), (uint32_t)(pRate->dstInc >> 32))); \
+ \
+ if (pRate->dstInc == (UINT64_C(1) + UINT32_MAX)) /* No conversion needed? */ \
+ { \
+ uint32_t cFrames = RT_MIN(cSrcFrames, cDstFrames); \
+ AUDMIXBUF_MACRO_LOG(("cFrames=%RU32\n", cFrames)); \
+ for (uint32_t i = 0; i < cFrames; i++) \
+ { \
+ paDst[i].i64LSample _aOp paSrc[i].i64LSample; \
+ paDst[i].i64RSample _aOp paSrc[i].i64RSample; \
+ } \
+ \
+ if (pcDstWritten) \
+ *pcDstWritten = cFrames; \
+ if (pcSrcRead) \
+ *pcSrcRead = cFrames; \
+ return; \
+ } \
+ \
+ PPDMAUDIOFRAME paSrcStart = paSrc; \
+ PPDMAUDIOFRAME paSrcEnd = paSrc + cSrcFrames; \
+ PPDMAUDIOFRAME paDstStart = paDst; \
+ PPDMAUDIOFRAME paDstEnd = paDst + cDstFrames; \
+ PDMAUDIOFRAME frameCur = { 0 }; \
+ PDMAUDIOFRAME frameOut; \
+ PDMAUDIOFRAME frameLast = pRate->srcFrameLast; \
+ \
+ while (paDst < paDstEnd) \
+ { \
+ Assert(paSrc <= paSrcEnd); \
+ Assert(paDst <= paDstEnd); \
+ if (paSrc >= paSrcEnd) \
+ break; \
+ \
+ while (pRate->srcOffset <= (pRate->dstOffset >> 32)) \
+ { \
+ Assert(paSrc <= paSrcEnd); \
+ frameLast = *paSrc++; \
+ pRate->srcOffset++; \
+ if (paSrc == paSrcEnd) \
+ break; \
+ } \
+ \
+ Assert(paSrc <= paSrcEnd); \
+ if (paSrc == paSrcEnd) \
+ break; \
+ \
+ frameCur = *paSrc; \
+ \
+ /* Interpolate. */ \
+ int64_t iDstOffInt = pRate->dstOffset & UINT32_MAX; \
+ \
+ frameOut.i64LSample = (frameLast.i64LSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64LSample * iDstOffInt) >> 32; \
+ frameOut.i64RSample = (frameLast.i64RSample * ((int64_t) (INT64_C(1) << 32) - iDstOffInt) + frameCur.i64RSample * iDstOffInt) >> 32; \
+ \
+ paDst->i64LSample _aOp frameOut.i64LSample; \
+ paDst->i64RSample _aOp frameOut.i64RSample; \
+ \
+ AUDMIXBUF_MACRO_LOG(("\tiDstOffInt=%RI64, l=%RI64, r=%RI64 (cur l=%RI64, r=%RI64)\n", \
+ iDstOffInt, \
+ paDst->i64LSample >> 32, paDst->i64RSample >> 32, \
+ frameCur.i64LSample >> 32, frameCur.i64RSample >> 32)); \
+ \
+ paDst++; \
+ pRate->dstOffset += pRate->dstInc; \
+ \
+ AUDMIXBUF_MACRO_LOG(("\t\tpRate->dstOffset=%RU32\n", pRate->dstOffset >> 32)); \
+ \
+ } \
+ \
+ AUDMIXBUF_MACRO_LOG(("%zu source frames -> %zu dest frames\n", paSrc - paSrcStart, paDst - paDstStart)); \
+ \
+ pRate->srcFrameLast = frameLast; \
+ \
+ AUDMIXBUF_MACRO_LOG(("pRate->srcSampleLast l=%RI64, r=%RI64\n", \
+ pRate->srcFrameLast.i64LSample, pRate->srcFrameLast.i64RSample)); \
+ \
+ if (pcDstWritten) \
+ *pcDstWritten = paDst - paDstStart; \
+ if (pcSrcRead) \
+ *pcSrcRead = paSrc - paSrcStart; \
+ }
+
+/* audioMixBufOpAssign: Assigns values from source buffer to destination bufffer, overwriting the destination. */
+AUDMIXBUF_MIXOP(Assign /* Name */, = /* Operation */)
+#if 0 /* unused */
+/* audioMixBufOpBlend: Blends together the values from both, the source and the destination buffer. */
+AUDMIXBUF_MIXOP(Blend /* Name */, += /* Operation */)
+#endif
+
+#undef AUDMIXBUF_MIXOP
+#undef AUDMIXBUF_MACRO_LOG
+
+/** Dummy conversion used when the source is muted. */
+static DECLCALLBACK(uint32_t)
+audioMixBufConvFromSilence(PPDMAUDIOFRAME paDst, const void *pvSrc, uint32_t cbSrc, PCPDMAUDMIXBUFCONVOPTS pOpts)
+{
+ RT_NOREF(cbSrc, pvSrc);
+
+ /* Internally zero always corresponds to silence. */
+ RT_BZERO(paDst, pOpts->cFrames * sizeof(paDst[0]));
+ return pOpts->cFrames;
+}
+
+/**
+ * Looks up the matching conversion (macro) routine for converting
+ * audio frames from a source format.
+ *
+ ** @todo Speed up the lookup by binding it to the actual stream state.
+ *
+ * @return PAUDMIXBUF_FN_CONVFROM Function pointer to conversion macro if found, NULL if not supported.
+ * @param enmFmt Audio format to lookup conversion macro for.
+ */
+static PFNPDMAUDIOMIXBUFCONVFROM audioMixBufConvFromLookup(PDMAUDIOMIXBUFFMT enmFmt)
+{
+ if (AUDMIXBUF_FMT_SIGNED(enmFmt))
+ {
+ if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2)
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvFromS8Stereo;
+ case 16: return audioMixBufConvFromS16Stereo;
+ case 32: return audioMixBufConvFromS32Stereo;
+ default: return NULL;
+ }
+ }
+ else
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvFromS8Mono;
+ case 16: return audioMixBufConvFromS16Mono;
+ case 32: return audioMixBufConvFromS32Mono;
+ default: return NULL;
+ }
+ }
+ }
+ else /* Unsigned */
+ {
+ if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2)
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvFromU8Stereo;
+ case 16: return audioMixBufConvFromU16Stereo;
+ case 32: return audioMixBufConvFromU32Stereo;
+ default: return NULL;
+ }
+ }
+ else
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvFromU8Mono;
+ case 16: return audioMixBufConvFromU16Mono;
+ case 32: return audioMixBufConvFromU32Mono;
+ default: return NULL;
+ }
+ }
+ }
+ /* not reached */
+}
+
+/**
+ * Looks up the matching conversion (macro) routine for converting
+ * audio frames to a destination format.
+ *
+ ** @todo Speed up the lookup by binding it to the actual stream state.
+ *
+ * @return PAUDMIXBUF_FN_CONVTO Function pointer to conversion macro if found, NULL if not supported.
+ * @param enmFmt Audio format to lookup conversion macro for.
+ */
+static PFNPDMAUDIOMIXBUFCONVTO audioMixBufConvToLookup(PDMAUDIOMIXBUFFMT enmFmt)
+{
+ if (AUDMIXBUF_FMT_SIGNED(enmFmt))
+ {
+ if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2)
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvToS8Stereo;
+ case 16: return audioMixBufConvToS16Stereo;
+ case 32: return audioMixBufConvToS32Stereo;
+ default: return NULL;
+ }
+ }
+ else
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvToS8Mono;
+ case 16: return audioMixBufConvToS16Mono;
+ case 32: return audioMixBufConvToS32Mono;
+ default: return NULL;
+ }
+ }
+ }
+ else /* Unsigned */
+ {
+ if (AUDMIXBUF_FMT_CHANNELS(enmFmt) == 2)
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvToU8Stereo;
+ case 16: return audioMixBufConvToU16Stereo;
+ case 32: return audioMixBufConvToU32Stereo;
+ default: return NULL;
+ }
+ }
+ else
+ {
+ switch (AUDMIXBUF_FMT_BITS_PER_SAMPLE(enmFmt))
+ {
+ case 8: return audioMixBufConvToU8Mono;
+ case 16: return audioMixBufConvToU16Mono;
+ case 32: return audioMixBufConvToU32Mono;
+ default: return NULL;
+ }
+ }
+ }
+ /* not reached */
+}
+
+/**
+ * Converts a PDM audio volume to an internal mixing buffer volume.
+ *
+ * @returns IPRT status code.
+ * @param pVolDst Where to store the converted mixing buffer volume.
+ * @param pVolSrc Volume to convert.
+ */
+static int audioMixBufConvVol(PPDMAUDMIXBUFVOL pVolDst, PPDMAUDIOVOLUME pVolSrc)
+{
+ if (!pVolSrc->fMuted) /* Only change/convert the volume value if we're not muted. */
+ {
+ uint8_t uVolL = pVolSrc->uLeft & 0xFF;
+ uint8_t uVolR = pVolSrc->uRight & 0xFF;
+
+ /** @todo Ensure that the input is in the correct range/initialized! */
+ pVolDst->uLeft = s_aVolumeConv[uVolL] * (AUDIOMIXBUF_VOL_0DB >> 16);
+ pVolDst->uRight = s_aVolumeConv[uVolR] * (AUDIOMIXBUF_VOL_0DB >> 16);
+ }
+
+ pVolDst->fMuted = pVolSrc->fMuted;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a mixing buffer.
+ *
+ * @return IPRT 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(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+
+ pMixBuf->pParent = NULL;
+
+ RTListInit(&pMixBuf->lstChildren);
+ pMixBuf->cChildren = 0;
+
+ pMixBuf->pFrames = NULL;
+ pMixBuf->cFrames = 0;
+
+ pMixBuf->offRead = 0;
+ pMixBuf->offWrite = 0;
+ pMixBuf->cMixed = 0;
+ pMixBuf->cUsed = 0;
+
+ /* Set initial volume to max. */
+ pMixBuf->Volume.fMuted = false;
+ pMixBuf->Volume.uLeft = AUDIOMIXBUF_VOL_0DB;
+ pMixBuf->Volume.uRight = AUDIOMIXBUF_VOL_0DB;
+
+ /* Prevent division by zero.
+ * Do a 1:1 conversion according to AUDIOMIXBUF_S2B_RATIO. */
+ pMixBuf->iFreqRatio = 1 << 20;
+
+ pMixBuf->pRate = NULL;
+
+ pMixBuf->AudioFmt = AUDMIXBUF_AUDIO_FMT_MAKE(pProps->uHz,
+ pProps->cChannels,
+ pProps->cBytes * 8 /* Bit */,
+ pProps->fSigned);
+
+ pMixBuf->pfnConvFrom = audioMixBufConvFromLookup(pMixBuf->AudioFmt);
+ pMixBuf->pfnConvTo = audioMixBufConvToLookup(pMixBuf->AudioFmt);
+
+ pMixBuf->cShift = pProps->cShift;
+ pMixBuf->pszName = RTStrDup(pszName);
+ if (!pMixBuf->pszName)
+ return VERR_NO_MEMORY;
+
+ AUDMIXBUF_LOG(("%s: uHz=%RU32, cChan=%RU8, cBits=%RU8, fSigned=%RTbool\n",
+ pMixBuf->pszName,
+ AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt),
+ AUDMIXBUF_FMT_CHANNELS(pMixBuf->AudioFmt),
+ AUDMIXBUF_FMT_BITS_PER_SAMPLE(pMixBuf->AudioFmt),
+ RT_BOOL(AUDMIXBUF_FMT_SIGNED(pMixBuf->AudioFmt))));
+
+ return audioMixBufAlloc(pMixBuf, cFrames);
+}
+
+/**
+ * Returns @c true if there are any audio frames available for processing,
+ * @c false if not.
+ *
+ * @return bool @c true if there are any audio frames available for processing, @c false if not.
+ * @param pMixBuf Mixing buffer to return value for.
+ */
+bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, true);
+
+ if (pMixBuf->pParent)
+ return (pMixBuf->cMixed == 0);
+ return (pMixBuf->cUsed == 0);
+}
+
+/**
+ * Calculates the frequency (sample rate) ratio of mixing buffer A in relation to mixing buffer B.
+ *
+ * @returns Calculated frequency ratio.
+ * @param pMixBufA First mixing buffer.
+ * @param pMixBufB Second mixing buffer.
+ */
+static int64_t audioMixBufCalcFreqRatio(PPDMAUDIOMIXBUF pMixBufA, PPDMAUDIOMIXBUF pMixBufB)
+{
+ int64_t iRatio = ((int64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufA->AudioFmt) << 32)
+ / AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBufB->AudioFmt);
+
+ if (iRatio == 0) /* Catch division by zero. */
+ iRatio = 1 << 20; /* Do a 1:1 conversion instead. */
+
+ return iRatio;
+}
+
+/**
+ * Links an audio mixing buffer to a parent mixing buffer. A parent mixing
+ * buffer can have multiple children mixing buffers [1:N], whereas a child only can
+ * have one parent mixing buffer [N:1].
+ *
+ * The mixing direction always goes from the child/children buffer(s) to the
+ * parent buffer.
+ *
+ * For guest audio output the host backend owns the parent mixing buffer, the
+ * device emulation owns the child/children.
+ *
+ * The audio format of each mixing buffer can vary; the internal mixing code
+ * then will automatically do the (needed) conversion.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to link parent to.
+ * @param pParent Parent mixing buffer to use for linking.
+ *
+ * @remark Circular linking is not allowed.
+ */
+int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pParent, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt),
+ ("Parent frame frequency (Hz) not set\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt),
+ ("Buffer sample frequency (Hz) not set\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pMixBuf != pParent,
+ ("Circular linking not allowed\n"), VERR_INVALID_PARAMETER);
+
+ if (pMixBuf->pParent) /* Already linked? */
+ {
+ AUDMIXBUF_LOG(("%s: Already linked to parent '%s'\n",
+ pMixBuf->pszName, pMixBuf->pParent->pszName));
+ return VERR_ACCESS_DENIED;
+ }
+
+ RTListAppend(&pParent->lstChildren, &pMixBuf->Node);
+ pParent->cChildren++;
+
+ /* Set the parent. */
+ pMixBuf->pParent = pParent;
+
+ /* Calculate the frequency ratios. */
+ pMixBuf->iFreqRatio = audioMixBufCalcFreqRatio(pParent, pMixBuf);
+
+ int rc = VINF_SUCCESS;
+#if 0
+ uint32_t cFrames = (uint32_t)RT_MIN( ((uint64_t)pParent->cFrames << 32)
+ / pMixBuf->iFreqRatio, _64K /* 64K frames max. */);
+ if (!cFrames)
+ cFrames = pParent->cFrames;
+
+ int rc = VINF_SUCCESS;
+
+ if (cFrames != pMixBuf->cFrames)
+ {
+ AUDMIXBUF_LOG(("%s: Reallocating frames %RU32 -> %RU32\n",
+ pMixBuf->pszName, pMixBuf->cFrames, cFrames));
+
+ uint32_t cbSamples = cFrames * sizeof(PDMAUDIOSAMPLE);
+ Assert(cbSamples);
+ pMixBuf->pSamples = (PPDMAUDIOSAMPLE)RTMemRealloc(pMixBuf->pSamples, cbSamples);
+ if (!pMixBuf->pSamples)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ pMixBuf->cFrames = cFrames;
+
+ /* Make sure to zero the reallocated buffer so that it can be
+ * used properly when blending with another buffer later. */
+ RT_BZERO(pMixBuf->pSamples, cbSamples);
+ }
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ if (!pMixBuf->pRate)
+ {
+ /* Create rate conversion. */
+ pMixBuf->pRate = (PPDMAUDIOSTREAMRATE)RTMemAllocZ(sizeof(PDMAUDIOSTREAMRATE));
+ if (!pMixBuf->pRate)
+ return VERR_NO_MEMORY;
+ }
+ else
+ RT_BZERO(pMixBuf->pRate, sizeof(PDMAUDIOSTREAMRATE));
+
+ pMixBuf->pRate->dstInc = ((uint64_t)AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt) << 32)
+ / AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt);
+
+ AUDMIXBUF_LOG(("uThisHz=%RU32, uParentHz=%RU32, iFreqRatio=0x%RX64 (%RI64), uRateInc=0x%RX64 (%RU64), cFrames=%RU32 (%RU32 parent)\n",
+ AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt),
+ AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt),
+ pMixBuf->iFreqRatio, pMixBuf->iFreqRatio,
+ pMixBuf->pRate->dstInc, pMixBuf->pRate->dstInc,
+ pMixBuf->cFrames,
+ pParent->cFrames));
+ AUDMIXBUF_LOG(("%s (%RU32Hz) -> %s (%RU32Hz)\n",
+ pMixBuf->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pMixBuf->AudioFmt),
+ pMixBuf->pParent->pszName, AUDMIXBUF_FMT_SAMPLE_FREQ(pParent->AudioFmt)));
+ }
+
+ return rc;
+}
+
+/**
+ * Returns number of available live frames, that is, frames that
+ * have been written into the mixing buffer but not have been processed yet.
+ *
+ * For a parent buffer, this simply returns the currently used number of frames
+ * in the buffer.
+ *
+ * For a child buffer, this returns the number of frames which have been mixed
+ * to the parent and were not processed by the parent yet.
+ *
+ * @return uint32_t Number of live frames available.
+ * @param pMixBuf Mixing buffer to return value for.
+ */
+uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+
+#ifdef RT_STRICT
+ uint32_t cFrames;
+#endif
+ uint32_t cAvail;
+ if (pMixBuf->pParent) /* Is this a child buffer? */
+ {
+#ifdef RT_STRICT
+ /* Use the frame count from the parent, as
+ * pMixBuf->cMixed specifies the frame count
+ * in parent frames. */
+ cFrames = pMixBuf->pParent->cFrames;
+#endif
+ cAvail = pMixBuf->cMixed;
+ }
+ else
+ {
+#ifdef RT_STRICT
+ cFrames = pMixBuf->cFrames;
+#endif
+ cAvail = pMixBuf->cUsed;
+ }
+
+ Assert(cAvail <= cFrames);
+ return cAvail;
+}
+
+/**
+ * Mixes audio frames from a source mixing buffer to a destination mixing buffer.
+ *
+ * @return IPRT status code.
+ * VERR_BUFFER_UNDERFLOW if the source did not have enough audio data.
+ * VERR_BUFFER_OVERFLOW if the destination did not have enough space to store the converted source audio data.
+ *
+ * @param pDst Destination mixing buffer.
+ * @param pSrc Source mixing buffer.
+ * @param cSrcOff Offset of source audio frames to mix.
+ * @param cSrcFrames Number of source audio frames to mix.
+ * @param pcSrcMixed Number of source audio frames successfully mixed. Optional.
+ */
+static int audioMixBufMixTo(PPDMAUDIOMIXBUF pDst, PPDMAUDIOMIXBUF pSrc, uint32_t cSrcOff, uint32_t cSrcFrames,
+ uint32_t *pcSrcMixed)
+{
+ AssertPtrReturn(pDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+ /* pcSrcMixed is optional. */
+
+ AssertMsgReturn(pDst == pSrc->pParent, ("Source buffer '%s' is not a child of destination '%s'\n",
+ pSrc->pszName, pDst->pszName), VERR_INVALID_PARAMETER);
+ uint32_t cReadTotal = 0;
+ uint32_t cWrittenTotal = 0;
+
+ Assert(pSrc->cMixed <= pDst->cFrames);
+
+ Assert(pSrc->cUsed >= pDst->cMixed);
+ Assert(pDst->cUsed <= pDst->cFrames);
+
+ uint32_t offSrcRead = cSrcOff;
+
+ uint32_t offDstWrite = pDst->offWrite;
+ uint32_t cDstMixed = pSrc->cMixed;
+
+ uint32_t cSrcAvail = RT_MIN(cSrcFrames, pSrc->cUsed);
+ uint32_t cDstAvail = pDst->cFrames - pDst->cUsed; /** @todo Use pDst->cMixed later? */
+
+ AUDMIXBUF_LOG(("%s (%RU32 available) -> %s (%RU32 available)\n",
+ pSrc->pszName, cSrcAvail, pDst->pszName, cDstAvail));
+#ifdef DEBUG
+ audioMixBufDbgPrintInternal(pDst, __FUNCTION__);
+#endif
+
+ if (!cSrcAvail)
+ return VERR_BUFFER_UNDERFLOW;
+
+ if (!cDstAvail)
+ return VERR_BUFFER_OVERFLOW;
+
+ uint32_t cSrcToRead = 0;
+ uint32_t cSrcRead;
+
+ uint32_t cDstToWrite;
+ uint32_t cDstWritten;
+
+ int rc = VINF_SUCCESS;
+
+ while (cSrcAvail && cDstAvail)
+ {
+ cSrcToRead = RT_MIN(cSrcAvail, pSrc->cFrames - offSrcRead);
+ cDstToWrite = RT_MIN(cDstAvail, pDst->cFrames - offDstWrite);
+
+ AUDMIXBUF_LOG(("\tSource: %RU32 @ %RU32 -> reading %RU32\n", cSrcAvail, offSrcRead, cSrcToRead));
+ AUDMIXBUF_LOG(("\tDest : %RU32 @ %RU32 -> writing %RU32\n", cDstAvail, offDstWrite, cDstToWrite));
+
+ if ( !cDstToWrite
+ || !cSrcToRead)
+ {
+ break;
+ }
+
+ cDstWritten = cSrcRead = 0;
+
+ Assert(offSrcRead < pSrc->cFrames);
+ Assert(offSrcRead + cSrcToRead <= pSrc->cFrames);
+
+ Assert(offDstWrite < pDst->cFrames);
+ Assert(offDstWrite + cDstToWrite <= pDst->cFrames);
+
+ audioMixBufOpAssign(pDst->pFrames + offDstWrite, cDstToWrite,
+ pSrc->pFrames + offSrcRead, cSrcToRead,
+ pSrc->pRate, &cDstWritten, &cSrcRead);
+
+ cReadTotal += cSrcRead;
+ cWrittenTotal += cDstWritten;
+
+ offSrcRead = (offSrcRead + cSrcRead) % pSrc->cFrames;
+ offDstWrite = (offDstWrite + cDstWritten) % pDst->cFrames;
+
+ cDstMixed += cDstWritten;
+
+ Assert(cSrcAvail >= cSrcRead);
+ cSrcAvail -= cSrcRead;
+
+ Assert(cDstAvail >= cDstWritten);
+ cDstAvail -= cDstWritten;
+
+ AUDMIXBUF_LOG(("\t%RU32 read (%RU32 left @ %RU32), %RU32 written (%RU32 left @ %RU32)\n",
+ cSrcRead, cSrcAvail, offSrcRead,
+ cDstWritten, cDstAvail, offDstWrite));
+ }
+
+ pSrc->offRead = offSrcRead;
+ Assert(pSrc->cUsed >= cReadTotal);
+ pSrc->cUsed -= RT_MIN(pSrc->cUsed, cReadTotal);
+
+ /* Note: Always count in parent frames, as the rate can differ! */
+ pSrc->cMixed = RT_MIN(cDstMixed, pDst->cFrames);
+
+ pDst->offWrite = offDstWrite;
+ Assert(pDst->offWrite <= pDst->cFrames);
+ Assert((pDst->cUsed + cWrittenTotal) <= pDst->cFrames);
+ pDst->cUsed += cWrittenTotal;
+
+ /* If there are more used frames than fitting in the destination buffer,
+ * adjust the values accordingly.
+ *
+ * This can happen if this routine has been called too often without
+ * actually processing the destination buffer in between. */
+ if (pDst->cUsed > pDst->cFrames)
+ {
+ LogFunc(("%s: Warning: Destination buffer used %RU32 / %RU32 frames\n", pDst->pszName, pDst->cUsed, pDst->cFrames));
+ pDst->offWrite = 0;
+ pDst->cUsed = pDst->cFrames;
+
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+
+#ifdef DEBUG
+ audioMixBufDbgValidate(pSrc);
+ audioMixBufDbgValidate(pDst);
+
+ Assert(pSrc->cMixed <= pDst->cFrames);
+#endif
+
+#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+ uint32_t offRead = pDst->offRead;
+
+ uint32_t cLeft = cWrittenTotal;
+ while (cLeft)
+ {
+ uint8_t auBuf[256];
+ RT_ZERO(auBuf);
+
+ Assert(sizeof(auBuf) >= 4);
+ Assert(sizeof(auBuf) % 4 == 0);
+
+ uint32_t cToRead = RT_MIN(AUDIOMIXBUF_B2F(pDst, sizeof(auBuf)), RT_MIN(cLeft, pDst->cFrames - offRead));
+ Assert(cToRead <= pDst->cUsed);
+
+ PDMAUDMIXBUFCONVOPTS convOpts;
+ RT_ZERO(convOpts);
+ convOpts.cFrames = cToRead;
+
+ pDst->pfnConvTo(auBuf, pDst->pFrames + offRead, &convOpts);
+
+ RTFILE fh;
+ int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_mixto.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ RTFileWrite(fh, auBuf, AUDIOMIXBUF_F2B(pDst, cToRead), NULL);
+ RTFileClose(fh);
+ }
+
+ offRead = (offRead + cToRead) % pDst->cFrames;
+ cLeft -= cToRead;
+ }
+#endif /* AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA */
+
+#ifdef DEBUG
+ audioMixBufDbgPrintInternal(pDst, __FUNCTION__);
+#endif
+
+ if (pcSrcMixed)
+ *pcSrcMixed = cReadTotal;
+
+ AUDMIXBUF_LOG(("cReadTotal=%RU32, cWrittenTotal=%RU32, cSrcMixed=%RU32, cDstUsed=%RU32, rc=%Rrc\n",
+ cReadTotal, cWrittenTotal, pSrc->cMixed, pDst->cUsed, rc));
+ return rc;
+}
+
+/**
+ * Mixes audio frames down to the parent mixing buffer, extended version.
+ *
+ * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation.
+ * @param pMixBuf Source mixing buffer to mix to its parent.
+ * @param cSrcOffset Offset (in frames) of source mixing buffer.
+ * @param cSrcFrames Number of source audio frames to mix to its parent.
+ * @param pcSrcMixed Number of source audio frames successfully mixed. Optional.
+ */
+int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed)
+{
+ AssertMsgReturn(VALID_PTR(pMixBuf->pParent),
+ ("Buffer is not linked to a parent buffer\n"),
+ VERR_INVALID_PARAMETER);
+
+ return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, cSrcOffset, cSrcFrames, pcSrcMixed);
+}
+
+/**
+ * Mixes audio frames down to the parent mixing buffer.
+ *
+ * @return IPRT status code. See audioMixBufMixTo() for a more detailed explanation.
+ * @param pMixBuf Source mixing buffer to mix to its parent.
+ * @param cSrcFrames Number of source audio frames to mix to its parent.
+ * @param pcSrcMixed Number of source audio frames successfully mixed. Optional.
+ */
+int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed)
+{
+ return audioMixBufMixTo(pMixBuf->pParent, pMixBuf, pMixBuf->offRead, cSrcFrames, pcSrcMixed);
+}
+
+#ifdef DEBUG
+/**
+ * Prints a single mixing buffer.
+ * Internal helper function for debugging. Do not use directly.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to print.
+ * @param pszFunc Function name to log this for.
+ * @param fIsParent Whether this is a parent buffer or not.
+ * @param uIdtLvl Indention level to use.
+ */
+DECL_FORCE_INLINE(void) audioMixBufDbgPrintSingle(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, bool fIsParent, uint16_t uIdtLvl)
+{
+ Log(("%s: %*s[%s] %s: offRead=%RU32, offWrite=%RU32, cMixed=%RU32 -> %RU32/%RU32\n",
+ pszFunc, uIdtLvl * 4, "", fIsParent ? "PARENT" : "CHILD",
+ pMixBuf->pszName, pMixBuf->offRead, pMixBuf->offWrite, pMixBuf->cMixed, pMixBuf->cUsed, pMixBuf->cFrames));
+}
+
+/**
+ * Validates a single mixing buffer.
+ *
+ * @return @true if the buffer state is valid or @false if not.
+ * @param pMixBuf Mixing buffer to validate.
+ */
+DECL_FORCE_INLINE(bool) audioMixBufDbgValidate(PPDMAUDIOMIXBUF 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;
+}
+
+/**
+ * Internal helper function for audioMixBufPrintChain().
+ * Do not use directly.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to print.
+ * @param pszFunc Function name to print the chain for.
+ * @param uIdtLvl Indention level to use.
+ * @param pcChildren Pointer to children counter.
+ */
+DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainHelper(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc, uint16_t uIdtLvl,
+ size_t *pcChildren)
+{
+ PPDMAUDIOMIXBUF pIter;
+ RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node)
+ {
+ audioMixBufDbgPrintSingle(pIter, pszFunc, false /* ifIsParent */, uIdtLvl + 1);
+ *pcChildren++;
+ }
+}
+
+DECL_FORCE_INLINE(void) audioMixBufDbgPrintChainInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc)
+{
+ PPDMAUDIOMIXBUF pParent = pMixBuf->pParent;
+ while (pParent)
+ {
+ if (!pParent->pParent)
+ break;
+
+ pParent = pParent->pParent;
+ }
+
+ if (!pParent)
+ pParent = pMixBuf;
+
+ audioMixBufDbgPrintSingle(pParent, pszFunc, true /* fIsParent */, 0 /* uIdtLvl */);
+
+ /* Recursively iterate children. */
+ size_t cChildren = 0;
+ audioMixBufDbgPrintChainHelper(pParent, pszFunc, 0 /* uIdtLvl */, &cChildren);
+
+ Log(("%s: Children: %zu\n", pszFunc, cChildren));
+}
+
+/**
+ * Prints statistics and status of the full chain of a mixing buffer to the logger,
+ * starting from the top root mixing buffer.
+ * For debug versions only.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to print.
+ */
+void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf)
+{
+ audioMixBufDbgPrintChainInternal(pMixBuf, __FUNCTION__);
+}
+
+DECL_FORCE_INLINE(void) audioMixBufDbgPrintInternal(PPDMAUDIOMIXBUF pMixBuf, const char *pszFunc)
+{
+ PPDMAUDIOMIXBUF pParent = pMixBuf;
+ if (pMixBuf->pParent)
+ pParent = pMixBuf->pParent;
+
+ audioMixBufDbgPrintSingle(pMixBuf, pszFunc, pParent == pMixBuf /* fIsParent */, 0 /* iIdtLevel */);
+
+ PPDMAUDIOMIXBUF pIter;
+ RTListForEach(&pMixBuf->lstChildren, pIter, PDMAUDIOMIXBUF, Node)
+ {
+ if (pIter == pMixBuf)
+ continue;
+ audioMixBufDbgPrintSingle(pIter, pszFunc, false /* fIsParent */, 1 /* iIdtLevel */);
+ }
+}
+
+/**
+ * Prints statistics and status of a mixing buffer to the logger.
+ * For debug versions only.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to print.
+ */
+void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf)
+{
+ audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__);
+}
+#endif /* DEBUG */
+
+/**
+ * Returns the total number of audio frames used.
+ *
+ * @return uint32_t
+ * @param pMixBuf
+ */
+uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ return pMixBuf->cUsed;
+}
+
+/**
+ * Returns the total number of bytes used.
+ *
+ * @return uint32_t
+ * @param pMixBuf
+ */
+uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cUsed);
+}
+
+/**
+ * Reads audio frames at a specific offset.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to read audio frames from.
+ * @param offFrames Offset (in audio frames) to start reading from.
+ * @param pvBuf Pointer to buffer to write output to.
+ * @param cbBuf Size (in bytes) of buffer to write to.
+ * @param pcbRead Size (in bytes) of data read. Optional.
+ */
+int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf,
+ uint32_t offFrames,
+ void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcbRead)
+{
+ return AudioMixBufReadAtEx(pMixBuf, pMixBuf->AudioFmt,
+ offFrames, pvBuf, cbBuf, pcbRead);
+}
+
+/**
+ * Reads audio frames at a specific offset.
+ * If the audio format of the mixing buffer and the requested audio format do
+ * not match the output will be converted accordingly.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to read audio frames from.
+ * @param enmFmt Audio format to use for output.
+ * @param offFrames Offset (in audio frames) to start reading from.
+ * @param pvBuf Pointer to buffer to write output to.
+ * @param cbBuf Size (in bytes) of buffer to write to.
+ * @param pcbRead Size (in bytes) of data read. Optional.
+ */
+int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt,
+ uint32_t offFrames,
+ void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcbRead)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ /* pcbRead is optional. */
+
+ uint32_t cDstFrames = pMixBuf->cFrames;
+ uint32_t cLive = pMixBuf->cUsed;
+
+ uint32_t cDead = cDstFrames - cLive;
+ uint32_t cToProcess = (uint32_t)AUDIOMIXBUF_F2F_RATIO(pMixBuf, cDead);
+ cToProcess = RT_MIN(cToProcess, AUDIOMIXBUF_B2F(pMixBuf, cbBuf));
+
+ AUDMIXBUF_LOG(("%s: offFrames=%RU32, cLive=%RU32, cDead=%RU32, cToProcess=%RU32\n",
+ pMixBuf->pszName, offFrames, cLive, cDead, cToProcess));
+
+ int rc;
+ if (cToProcess)
+ {
+ PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL;
+ if (pMixBuf->AudioFmt != enmFmt)
+ pfnConvTo = audioMixBufConvToLookup(enmFmt);
+ else
+ pfnConvTo = pMixBuf->pfnConvTo;
+
+ if (pfnConvTo)
+ {
+ PDMAUDMIXBUFCONVOPTS convOpts;
+ RT_ZERO(convOpts);
+ /* Note: No volume handling/conversion done in the conversion-to macros (yet). */
+
+ convOpts.cFrames = cToProcess;
+
+ pfnConvTo(pvBuf, pMixBuf->pFrames + offFrames, &convOpts);
+
+#ifdef DEBUG
+ AudioMixBufDbgPrint(pMixBuf);
+#endif
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = AUDIOMIXBUF_F2B(pMixBuf, cToProcess);
+ }
+
+ AUDMIXBUF_LOG(("cbRead=%RU32, rc=%Rrc\n", AUDIOMIXBUF_F2B(pMixBuf, cToProcess), rc));
+ return rc;
+}
+
+/**
+ * Reads audio frames. The audio format of the mixing buffer will be used.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to read audio frames from.
+ * @param pvBuf Pointer to buffer to write output to.
+ * @param cbBuf Size (in bytes) of buffer to write to.
+ * @param pcBlock Returns acquired block to read (in audio frames).
+ */
+int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock)
+{
+ return AudioMixBufAcquireReadBlockEx(pMixBuf, pMixBuf->AudioFmt, pvBuf, cbBuf, pcBlock);
+}
+
+/**
+ * Reads audio frames in a specific audio format.
+ * If the audio format of the mixing buffer and the requested audio format do
+ * not match the output will be converted accordingly.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to read audio frames from.
+ * @param enmFmt Audio format to use for output.
+ * @param pvBuf Pointer to buffer to write output to.
+ * @param cbBuf Size (in bytes) of buffer to write to.
+ * @param pcBlock Returns acquired block to read (in audio frames).
+ */
+int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcBlock)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcBlock, VERR_INVALID_POINTER);
+
+ /* Make sure that we at least have space for a full audio frame. */
+ AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER);
+
+ uint32_t cToRead = RT_MIN(pMixBuf->cUsed, AUDIOMIXBUF_B2F(pMixBuf, cbBuf));
+
+ AUDMIXBUF_LOG(("%s: cbBuf=%RU32 (%RU32 frames), cToRead=%RU32, fmtSrc=0x%x, fmtDst=0x%x\n",
+ pMixBuf->pszName, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cToRead, pMixBuf->AudioFmt, enmFmt));
+
+ if (!cToRead)
+ {
+#ifdef DEBUG
+ audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__);
+#endif
+ *pcBlock = 0;
+ return VINF_SUCCESS;
+ }
+
+ PFNPDMAUDIOMIXBUFCONVTO pfnConvTo = NULL;
+ if (pMixBuf->AudioFmt != enmFmt)
+ pfnConvTo = audioMixBufConvToLookup(enmFmt);
+ else
+ pfnConvTo = pMixBuf->pfnConvTo;
+
+ if (!pfnConvTo) /* Audio format not supported. */
+ {
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+
+ cToRead = RT_MIN(cToRead, pMixBuf->cFrames - pMixBuf->offRead);
+ if (cToRead)
+ {
+ PDMAUDMIXBUFCONVOPTS convOpts;
+ RT_ZERO(convOpts);
+ convOpts.cFrames = cToRead;
+
+ AUDMIXBUF_LOG(("cToRead=%RU32\n", cToRead));
+
+ pfnConvTo(pvBuf, pMixBuf->pFrames + pMixBuf->offRead, &convOpts);
+
+#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+ RTFILE fh;
+ int rc2 = RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_readcirc.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToRead), NULL);
+ RTFileClose(fh);
+ }
+#endif
+ }
+
+ *pcBlock = cToRead;
+
+#ifdef DEBUG
+ audioMixBufDbgValidate(pMixBuf);
+#endif
+
+ AUDMIXBUF_LOG(("cRead=%RU32 (%RU32 bytes)\n", cToRead, AUDIOMIXBUF_F2B(pMixBuf, cToRead)));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Releases a formerly acquired read block again.
+ *
+ * @param pMixBuf Mixing buffer to release acquired read block for.
+ * @param cBlock Size of the block to release (in audio frames).
+ */
+void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock)
+{
+ AssertPtrReturnVoid(pMixBuf);
+
+ if (!cBlock)
+ return;
+
+ pMixBuf->offRead = (pMixBuf->offRead + cBlock) % pMixBuf->cFrames;
+ Assert(pMixBuf->cUsed >= cBlock);
+ pMixBuf->cUsed -= RT_MIN(cBlock, pMixBuf->cUsed);
+}
+
+/**
+ * Returns the current read position of a mixing buffer.
+ *
+ * @returns IPRT status code.
+ * @param pMixBuf Mixing buffer to return position for.
+ */
+uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+
+ return pMixBuf->offRead;
+}
+
+/**
+ * Resets a mixing buffer.
+ *
+ * @param pMixBuf Mixing buffer to reset.
+ */
+void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturnVoid(pMixBuf);
+
+ AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName));
+
+ pMixBuf->offRead = 0;
+ pMixBuf->offWrite = 0;
+ pMixBuf->cMixed = 0;
+ pMixBuf->cUsed = 0;
+
+ AudioMixBufClear(pMixBuf);
+}
+
+/**
+ * Sets the overall (master) volume.
+ *
+ * @param pMixBuf Mixing buffer to set volume for.
+ * @param pVol Pointer to volume structure to set.
+ */
+void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturnVoid(pMixBuf);
+ AssertPtrReturnVoid(pVol);
+
+ LogFlowFunc(("%s: lVol=%RU8, rVol=%RU8, fMuted=%RTbool\n", pMixBuf->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted));
+
+ int rc2 = audioMixBufConvVol(&pMixBuf->Volume /* Dest */, pVol /* Source */);
+ AssertRC(rc2);
+}
+
+/**
+ * Returns the maximum amount of audio frames this buffer can hold.
+ *
+ * @return uint32_t Size (in audio frames) the mixing buffer can hold.
+ * @param pMixBuf Mixing buffer to retrieve maximum for.
+ */
+uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ return pMixBuf->cFrames;
+}
+
+/**
+ * Returns the maximum amount of bytes this buffer can hold.
+ *
+ * @return uint32_t Size (in bytes) the mixing buffer can hold.
+ * @param pMixBuf Mixing buffer to retrieve maximum for.
+ */
+uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+ return AUDIOMIXBUF_F2B(pMixBuf, pMixBuf->cFrames);
+}
+
+/**
+ * Unlinks a mixing buffer from its parent, if any.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Mixing buffer to unlink from parent.
+ */
+void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf)
+{
+ if (!pMixBuf || !pMixBuf->pszName)
+ return;
+
+ AUDMIXBUF_LOG(("%s\n", pMixBuf->pszName));
+
+ if (pMixBuf->pParent) /* IS this a children buffer? */
+ {
+ AUDMIXBUF_LOG(("%s: Unlinking from parent \"%s\"\n",
+ pMixBuf->pszName, pMixBuf->pParent->pszName));
+
+ RTListNodeRemove(&pMixBuf->Node);
+
+ /* Decrease the paren't children count. */
+ Assert(pMixBuf->pParent->cChildren);
+ pMixBuf->pParent->cChildren--;
+
+ /* Make sure to reset the parent mixing buffer each time it gets linked
+ * to a new child. */
+ AudioMixBufReset(pMixBuf->pParent);
+ pMixBuf->pParent = NULL;
+ }
+
+ PPDMAUDIOMIXBUF pChild, pChildNext;
+ RTListForEachSafe(&pMixBuf->lstChildren, pChild, pChildNext, PDMAUDIOMIXBUF, Node)
+ {
+ AUDMIXBUF_LOG(("\tUnlinking \"%s\"\n", pChild->pszName));
+
+ AudioMixBufReset(pChild);
+
+ Assert(pChild->pParent == pMixBuf);
+ pChild->pParent = NULL;
+
+ RTListNodeRemove(&pChild->Node);
+
+ /* Decrease the children count. */
+ Assert(pMixBuf->cChildren);
+ pMixBuf->cChildren--;
+ }
+
+ Assert(RTListIsEmpty(&pMixBuf->lstChildren));
+ Assert(pMixBuf->cChildren == 0);
+
+ AudioMixBufReset(pMixBuf);
+
+ if (pMixBuf->pRate)
+ {
+ pMixBuf->pRate->dstOffset = pMixBuf->pRate->srcOffset = 0;
+ pMixBuf->pRate->dstInc = 0;
+ }
+
+ pMixBuf->iFreqRatio = 1; /* Prevent division by zero. */
+}
+
+/**
+ * Writes audio frames at a specific offset.
+ * The sample format being written must match the format of the mixing buffer.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Pointer to mixing buffer to write to.
+ * @param offFrames Offset (in frames) starting to write at.
+ * @param pvBuf Pointer to audio buffer to be written.
+ * @param cbBuf Size (in bytes) of audio buffer.
+ * @param pcWritten Returns number of audio frames written. Optional.
+ */
+int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offFrames, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten)
+{
+ return AudioMixBufWriteAtEx(pMixBuf, pMixBuf->AudioFmt, offFrames, pvBuf, cbBuf, pcWritten);
+}
+
+/**
+ * Writes audio frames at a specific offset.
+ *
+ * Note that this operation also modifies the current read and write position
+ * to \a offFrames + written frames on success.
+ *
+ * The audio sample format to be written can be different from the audio format
+ * the mixing buffer operates on.
+ *
+ * @return IPRT status code.
+ * @param pMixBuf Pointer to mixing buffer to write to.
+ * @param enmFmt Audio format supplied in the buffer.
+ * @param offFrames Offset (in frames) starting to write at.
+ * @param pvBuf Pointer to audio buffer to be written.
+ * @param cbBuf Size (in bytes) of audio buffer.
+ * @param pcWritten Returns number of audio frames written. Optional.
+ */
+int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt,
+ uint32_t offFrames, const void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcWritten)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ /* pcbWritten is optional. */
+
+ if (offFrames >= pMixBuf->cFrames)
+ {
+ if (pcWritten)
+ *pcWritten = 0;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Adjust cToWrite so we don't overflow our buffers.
+ */
+ uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), pMixBuf->cFrames - offFrames);
+
+#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+ /*
+ * Now that we know how much we'll be converting we can log it.
+ */
+ RTFILE hFile;
+ int rc2 = RTFileOpen(&hFile, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writeat.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ RTFileWrite(hFile, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL);
+ RTFileClose(hFile);
+ }
+#endif
+
+ /*
+ * Pick the conversion function and do the conversion.
+ */
+ PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL;
+ if (!pMixBuf->Volume.fMuted)
+ {
+ if (pMixBuf->AudioFmt != enmFmt)
+ pfnConvFrom = audioMixBufConvFromLookup(enmFmt);
+ else
+ pfnConvFrom = pMixBuf->pfnConvFrom;
+ }
+ else
+ pfnConvFrom = &audioMixBufConvFromSilence;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cWritten;
+ if ( pfnConvFrom
+ && cToWrite)
+ {
+ PDMAUDMIXBUFCONVOPTS convOpts;
+
+ convOpts.cFrames = cToWrite;
+ convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted;
+ convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft;
+ convOpts.From.Volume.uRight = pMixBuf->Volume.uRight;
+
+ cWritten = pfnConvFrom(pMixBuf->pFrames + offFrames, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts);
+ }
+ else
+ {
+ cWritten = 0;
+ if (!pfnConvFrom)
+ {
+ AssertFailed();
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ AUDMIXBUF_LOG(("%s: offFrames=%RU32, cbBuf=%RU32, cToWrite=%RU32 (%zu bytes), cWritten=%RU32 (%zu bytes), rc=%Rrc\n",
+ pMixBuf->pszName, offFrames, cbBuf,
+ cToWrite, AUDIOMIXBUF_F2B(pMixBuf, cToWrite),
+ cWritten, AUDIOMIXBUF_F2B(pMixBuf, cWritten), rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ pMixBuf->offRead = offFrames % pMixBuf->cFrames;
+ pMixBuf->offWrite = (offFrames + cWritten) % pMixBuf->cFrames;
+ pMixBuf->cUsed = cWritten;
+ pMixBuf->cMixed = 0;
+
+#ifdef DEBUG
+ audioMixBufDbgValidate(pMixBuf);
+#endif
+ if (pcWritten)
+ *pcWritten = cWritten;
+ }
+ else
+ AUDMIXBUF_LOG(("%s: Failed with %Rrc\n", pMixBuf->pszName, rc));
+
+ return rc;
+}
+
+/**
+ * Writes audio frames.
+ *
+ * The sample format being written must match the format of the mixing buffer.
+ *
+ * @return IPRT status code, or VERR_BUFFER_OVERFLOW if frames which not have
+ * been processed yet have been overwritten (due to cyclic buffer).
+ * @param pMixBuf Pointer to mixing buffer to write to.
+ * @param pvBuf Pointer to audio buffer to be written.
+ * @param cbBuf Size (in bytes) of audio buffer.
+ * @param pcWritten Returns number of audio frames written. Optional.
+ */
+int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf,
+ const void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcWritten)
+{
+ return AudioMixBufWriteCircEx(pMixBuf, pMixBuf->AudioFmt, pvBuf, cbBuf, pcWritten);
+}
+
+/**
+ * Writes audio frames of a specific format.
+ * This function might write less data at once than requested.
+ *
+ * @return IPRT status code, or VERR_BUFFER_OVERFLOW no space is available for writing anymore.
+ * @param pMixBuf Pointer to mixing buffer to write to.
+ * @param enmFmt Audio format supplied in the buffer.
+ * @param pvBuf Pointer to audio buffer to be written.
+ * @param cbBuf Size (in bytes) of audio buffer.
+ * @param pcWritten Returns number of audio frames written. Optional.
+ */
+int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten)
+{
+ AssertPtrReturn(pMixBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ /* pcbWritten is optional. */
+
+ if (!cbBuf)
+ {
+ if (pcWritten)
+ *pcWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ /* Make sure that we at least write a full audio frame. */
+ AssertReturn(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), VERR_INVALID_PARAMETER);
+
+ Assert(pMixBuf->cFrames);
+ AssertPtr(pMixBuf->pFrames);
+
+ PFNPDMAUDIOMIXBUFCONVFROM pfnConvFrom = NULL;
+ if (!pMixBuf->Volume.fMuted)
+ {
+ if (pMixBuf->AudioFmt != enmFmt)
+ pfnConvFrom = audioMixBufConvFromLookup(enmFmt);
+ else
+ pfnConvFrom = pMixBuf->pfnConvFrom;
+ }
+ else
+ pfnConvFrom = &audioMixBufConvFromSilence;
+
+ if (!pfnConvFrom)
+ {
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cWritten = 0;
+
+ uint32_t cFree = pMixBuf->cFrames - pMixBuf->cUsed;
+ if (cFree)
+ {
+ if ((pMixBuf->cFrames - pMixBuf->offWrite) == 0)
+ pMixBuf->offWrite = 0;
+
+ uint32_t cToWrite = RT_MIN(AUDIOMIXBUF_B2F(pMixBuf, cbBuf), RT_MIN(pMixBuf->cFrames - pMixBuf->offWrite, cFree));
+ Assert(cToWrite);
+
+ PDMAUDMIXBUFCONVOPTS convOpts;
+ RT_ZERO(convOpts);
+
+ convOpts.From.Volume.fMuted = pMixBuf->Volume.fMuted;
+ convOpts.From.Volume.uLeft = pMixBuf->Volume.uLeft;
+ convOpts.From.Volume.uRight = pMixBuf->Volume.uRight;
+
+ convOpts.cFrames = cToWrite;
+
+ cWritten = pfnConvFrom(pMixBuf->pFrames + pMixBuf->offWrite,
+ pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), &convOpts);
+ Assert(cWritten == cToWrite);
+
+#ifdef AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA
+ RTFILE fh;
+ RTFileOpen(&fh, AUDIOMIXBUF_DEBUG_DUMP_PCM_DATA_PATH "mixbuf_writecirc_ex.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ RTFileWrite(fh, pvBuf, AUDIOMIXBUF_F2B(pMixBuf, cToWrite), NULL);
+ RTFileClose(fh);
+#endif
+ pMixBuf->cUsed += cWritten;
+ Assert(pMixBuf->cUsed <= pMixBuf->cFrames);
+
+ pMixBuf->offWrite = (pMixBuf->offWrite + cWritten) % pMixBuf->cFrames;
+ Assert(pMixBuf->offWrite <= pMixBuf->cFrames);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+#ifdef DEBUG
+ audioMixBufDbgPrintInternal(pMixBuf, __FUNCTION__);
+ audioMixBufDbgValidate(pMixBuf);
+#endif
+
+ if (pcWritten)
+ *pcWritten = cWritten;
+
+ AUDMIXBUF_LOG(("%s: enmFmt=0x%x, cbBuf=%RU32 (%RU32 frames), cWritten=%RU32, rc=%Rrc\n",
+ pMixBuf->pszName, enmFmt, cbBuf, AUDIOMIXBUF_B2F(pMixBuf, cbBuf), cWritten, rc));
+ return rc;
+}
+
+/**
+ * Returns the current write position of a mixing buffer.
+ *
+ * @returns IPRT status code.
+ * @param pMixBuf Mixing buffer to return position for.
+ */
+uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf)
+{
+ AssertPtrReturn(pMixBuf, 0);
+
+ return pMixBuf->offWrite;
+}
+
diff --git a/src/VBox/Devices/Audio/AudioMixBuffer.h b/src/VBox/Devices/Audio/AudioMixBuffer.h
new file mode 100644
index 00000000..f2c77688
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixBuffer.h
@@ -0,0 +1,93 @@
+/* $Id: AudioMixBuffer.h $ */
+/** @file
+ * Audio Mixing bufer convert audio samples to/from different rates / formats.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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>
+
+/** Constructs 32 bit value for given frequency, number of channels, bits per sample and signed bit.
+ * Note: This currently matches 1:1 the VRDE encoding -- this might change in the future, so better don't rely on this fact! */
+#define AUDMIXBUF_AUDIO_FMT_MAKE(freq, c, bps, s) ((((s) & 0x1) << 28) + (((bps) & 0xFF) << 20) + (((c) & 0xF) << 16) + ((freq) & 0xFFFF))
+
+/** Decodes frequency (Hz). */
+#define AUDMIXBUF_FMT_SAMPLE_FREQ(a) ((a) & 0xFFFF)
+/** Decodes number of channels. */
+#define AUDMIXBUF_FMT_CHANNELS(a) (((a) >> 16) & 0xF)
+/** Decodes signed bit. */
+#define AUDMIXBUF_FMT_SIGNED(a) (((a) >> 28) & 0x1)
+/** Decodes number of bits per sample. */
+#define AUDMIXBUF_FMT_BITS_PER_SAMPLE(a) (((a) >> 20) & 0xFF)
+/** Decodes number of bytes per sample. */
+#define AUDMIXBUF_FMT_BYTES_PER_SAMPLE(a) ((AUDMIXBUF_AUDIO_FMT_BITS_PER_SAMPLE(a) + 7) / 8)
+
+/** Converts frames to bytes. */
+#define AUDIOMIXBUF_F2B(pBuf, frames) ((frames) << (pBuf)->cShift)
+/** Converts frames to bytes, respecting the conversion ratio to
+ * a linked buffer. */
+#define AUDIOMIXBUF_F2B_RATIO(pBuf, frames) ((((int64_t) frames << 32) / (pBuf)->iFreqRatio) << (pBuf)->cShift)
+/** Converts bytes to frames, *not* taking the conversion ratio
+ * into account. */
+#define AUDIOMIXBUF_B2F(pBuf, cb) (cb >> (pBuf)->cShift)
+/** Converts number of frames according to the buffer's ratio. */
+#define AUDIOMIXBUF_F2F_RATIO(pBuf, frames) (((int64_t) frames << 32) / (pBuf)->iFreqRatio)
+
+
+inline uint32_t AudioMixBufBytesToSamples(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufClear(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufDestroy(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufFinish(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToClear);
+uint32_t AudioMixBufFree(PPDMAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufFreeBytes(PPDMAUDIOMIXBUF pMixBuf);
+int AudioMixBufInit(PPDMAUDIOMIXBUF pMixBuf, const char *pszName, PPDMAUDIOPCMPROPS pProps, uint32_t cFrames);
+bool AudioMixBufIsEmpty(PPDMAUDIOMIXBUF pMixBuf);
+int AudioMixBufLinkTo(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOMIXBUF pParent);
+uint32_t AudioMixBufLive(PPDMAUDIOMIXBUF pMixBuf);
+int AudioMixBufMixToParent(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcFrames, uint32_t *pcSrcMixed);
+int AudioMixBufMixToParentEx(PPDMAUDIOMIXBUF pMixBuf, uint32_t cSrcOffset, uint32_t cSrcFrames, uint32_t *pcSrcMixed);
+int AudioMixBufPeek(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME paSampleBuf, uint32_t cSampleBuf, uint32_t *pcFramesRead);
+int AudioMixBufPeekMutable(PPDMAUDIOMIXBUF pMixBuf, uint32_t cFramesToRead, PPDMAUDIOFRAME *ppvSamples, uint32_t *pcFramesRead);
+uint32_t AudioMixBufUsed(PPDMAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufUsedBytes(PPDMAUDIOMIXBUF pMixBuf);
+int AudioMixBufReadAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
+int AudioMixBufReadAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
+int AudioMixBufAcquireReadBlock(PPDMAUDIOMIXBUF pMixBuf, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock);
+int AudioMixBufAcquireReadBlockEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcBlock);
+void AudioMixBufReleaseReadBlock(PPDMAUDIOMIXBUF pMixBuf, uint32_t cBlock);
+uint32_t AudioMixBufReadPos(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufReset(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufSetVolume(PPDMAUDIOMIXBUF pMixBuf, PPDMAUDIOVOLUME pVol);
+uint32_t AudioMixBufSize(PPDMAUDIOMIXBUF pMixBuf);
+uint32_t AudioMixBufSizeBytes(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufUnlink(PPDMAUDIOMIXBUF pMixBuf);
+int AudioMixBufWriteAt(PPDMAUDIOMIXBUF pMixBuf, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten);
+int AudioMixBufWriteAtEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, uint32_t offSamples, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten);
+int AudioMixBufWriteCirc(PPDMAUDIOMIXBUF pMixBuf, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten);
+int AudioMixBufWriteCircEx(PPDMAUDIOMIXBUF pMixBuf, PDMAUDIOMIXBUFFMT enmFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcWritten);
+uint32_t AudioMixBufWritePos(PPDMAUDIOMIXBUF pMixBuf);
+
+#ifdef DEBUG
+void AudioMixBufDbgPrint(PPDMAUDIOMIXBUF pMixBuf);
+void AudioMixBufDbgPrintChain(PPDMAUDIOMIXBUF pMixBuf);
+#endif
+
+#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..a56099fb
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixer.cpp
@@ -0,0 +1,2236 @@
+/* $Id: AudioMixer.cpp $ */
+/** @file
+ * Audio mixing routines for multiplexing audio sources in device emulations.
+ *
+ * == Overview
+ *
+ * This mixer acts as a layer between the audio connector interface and
+ * the actual device emulation, providing mechanisms for audio sources (input)
+ * and audio sinks (output).
+ *
+ * Think of this mixer as kind of a high(er) level interface for the audio
+ * connector interface, abstracting common tasks such as creating and managing
+ * various audio sources and sinks. This mixer class is purely optional and can
+ * be left out when implementing a new device emulation, using only the audi
+ * connector interface instead. For example, the SB16 emulation does not use
+ * this mixer and does all its stream management on its own.
+ *
+ * As audio driver instances are handled as LUNs on the device level, this
+ * audio mixer then can take care of e.g. mixing various inputs/outputs to/from
+ * a specific source/sink.
+ *
+ * How and which audio streams are connected to sinks/sources depends on how
+ * the audio mixer has been set up.
+ *
+ * A sink can connect multiple output streams together, whereas a source
+ * does this with input streams. Each sink / source consists of one or more
+ * so-called mixer streams, which then in turn have pointers to the actual
+ * PDM audio input/output streams.
+ *
+ * == Playback
+ *
+ * For output sinks there can be one or more mixing stream attached.
+ * As the host sets the overall pace for the device emulation (virtual time
+ * in the guest OS vs. real time on the host OS), an output mixing sink
+ * needs to make sure that all connected output streams are able to accept
+ * all the same amount of data at a time.
+ *
+ * This is called synchronous multiplexing.
+ *
+ * A mixing sink employs an own audio mixing buffer, which in turn can convert
+ * the audio (output) data supplied from the device emulation into the sink's
+ * audio format. As all connected mixing streams in theory could have the same
+ * audio format as the mixing sink (parent), this can save processing time when
+ * it comes to serving a lot of mixing streams at once. That way only one
+ * conversion must be done, instead of each stream having to iterate over the
+ * data.
+ *
+ * == Recording
+ *
+ * For input sinks only one mixing stream at a time can be the recording
+ * source currently. A recording source is optional, e.g. it is possible to
+ * have no current recording source set. Switching to a different recording
+ * source at runtime is possible.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_AUDIO_MIXER
+#include <VBox/log.h>
+#include "AudioMixer.h"
+#include "AudioMixBuffer.h"
+#include "DrvAudio.h"
+
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
+
+static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink);
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster);
+static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink);
+static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+static void audioMixerSinkReset(PAUDMIXSINK pSink);
+static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink);
+static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWrittenMin);
+static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream);
+static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten);
+
+int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl);
+static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream);
+
+
+#ifdef LOG_ENABLED
+/**
+ * Converts a mixer sink status to a string.
+ *
+ * @returns Stringified mixer sink flags. Must be free'd with RTStrFree().
+ * "NONE" if no flags set.
+ * @param fFlags Mixer sink flags to convert.
+ */
+static char *dbgAudioMixerSinkStatusToStr(AUDMIXSINKSTS fStatus)
+{
+#define APPEND_FLAG_TO_STR(_aFlag) \
+ if (fStatus & AUDMIXSINK_STS_##_aFlag) \
+ { \
+ if (pszFlags) \
+ { \
+ rc2 = RTStrAAppend(&pszFlags, " "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+
+ char *pszFlags = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FLAG_TO_STR(NONE);
+ APPEND_FLAG_TO_STR(RUNNING);
+ APPEND_FLAG_TO_STR(PENDING_DISABLE);
+ APPEND_FLAG_TO_STR(DIRTY);
+
+ } while (0);
+
+ if ( RT_FAILURE(rc2)
+ && pszFlags)
+ {
+ RTStrFree(pszFlags);
+ pszFlags = NULL;
+ }
+
+#undef APPEND_FLAG_TO_STR
+
+ return pszFlags;
+}
+#endif /* DEBUG */
+
+/**
+ * Creates an audio sink and attaches it to the given mixer.
+ *
+ * @returns IPRT 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 ppSink Pointer which returns the created sink on success.
+ */
+int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ /* ppSink is optional. */
+
+ int rc = RTCritSectEnter(&pMixer->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK));
+ if (pSink)
+ {
+ pSink->pszName = RTStrDup(pszName);
+ if (!pSink->pszName)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pSink->CritSect);
+
+ if (RT_SUCCESS(rc))
+ {
+ pSink->pParent = pMixer;
+ pSink->enmDir = enmDir;
+ RTListInit(&pSink->lstStreams);
+
+ /* Set initial volume to max. */
+ pSink->Volume.fMuted = false;
+ pSink->Volume.uLeft = PDMAUDIO_VOLUME_MAX;
+ pSink->Volume.uRight = PDMAUDIO_VOLUME_MAX;
+
+ /* Ditto for the combined volume. */
+ pSink->VolumeCombined.fMuted = false;
+ pSink->VolumeCombined.uLeft = PDMAUDIO_VOLUME_MAX;
+ pSink->VolumeCombined.uRight = PDMAUDIO_VOLUME_MAX;
+
+ RTListAppend(&pMixer->lstSinks, &pSink->Node);
+ pMixer->cSinks++;
+
+ LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n",
+ pMixer, pSink, pMixer->cSinks));
+
+ if (ppSink)
+ *ppSink = pSink;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTCritSectDelete(&pSink->CritSect);
+
+ if (pSink)
+ {
+ RTMemFree(pSink);
+ pSink = NULL;
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ int rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Creates an audio mixer.
+ *
+ * @returns IPRT status code.
+ * @param pszName Name of the audio mixer.
+ * @param fFlags Creation flags. Not used at the moment and must be 0.
+ * @param ppMixer Pointer which returns the created mixer object.
+ */
+int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer)
+{
+ RT_NOREF(fFlags);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ /** @todo Add fFlags validation. */
+ AssertPtrReturn(ppMixer, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER));
+ if (pMixer)
+ {
+ pMixer->pszName = RTStrDup(pszName);
+ if (!pMixer->pszName)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pMixer->CritSect);
+
+ if (RT_SUCCESS(rc))
+ {
+ pMixer->cSinks = 0;
+ RTListInit(&pMixer->lstSinks);
+
+ /* Set master volume to the max. */
+ pMixer->VolMaster.fMuted = false;
+ pMixer->VolMaster.uLeft = PDMAUDIO_VOLUME_MAX;
+ pMixer->VolMaster.uRight = PDMAUDIO_VOLUME_MAX;
+
+ LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName));
+
+ *ppMixer = pMixer;
+ }
+ else
+ RTMemFree(pMixer);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * 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);
+ PAUDMIXSINK pSink;
+ unsigned iSink = 0;
+
+ int rc2 = RTCritSectEnter(&pMixer->CritSect);
+ if (RT_FAILURE(rc2))
+ return;
+
+ pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
+ pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
+
+ RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
+ {
+ pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName,
+ pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted);
+ ++iSink;
+ }
+
+ rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Destroys an audio mixer.
+ *
+ * @param pMixer Audio mixer to destroy.
+ */
+void AudioMixerDestroy(PAUDIOMIXER pMixer)
+{
+ if (!pMixer)
+ return;
+
+ int rc2 = RTCritSectEnter(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
+
+ PAUDMIXSINK pSink, pSinkNext;
+ RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node)
+ {
+ /* Save a pointer to the sink to remove, as pSink
+ * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */
+ PAUDMIXSINK pSinkToRemove = pSink;
+
+ audioMixerRemoveSinkInternal(pMixer, pSinkToRemove);
+ audioMixerSinkDestroyInternal(pSinkToRemove);
+ }
+
+ pMixer->cSinks = 0;
+
+ if (pMixer->pszName)
+ {
+ RTStrFree(pMixer->pszName);
+ pMixer->pszName = NULL;
+ }
+
+ rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ RTCritSectDelete(&pMixer->CritSect);
+
+ RTMemFree(pMixer);
+ pMixer = NULL;
+}
+
+/**
+ * Invalidates all internal data, internal version.
+ *
+ * @returns IPRT status code.
+ * @param pMixer Mixer to invalidate data for.
+ */
+int audioMixerInvalidateInternal(PAUDIOMIXER pMixer)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s]\n", pMixer->pszName));
+
+ /* Propagate new master volume to all connected sinks. */
+ PAUDMIXSINK pSink;
+ RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
+ {
+ int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster);
+ AssertRC(rc2);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Invalidates all internal data.
+ *
+ * @returns IPRT status code.
+ * @param pMixer Mixer to invalidate data for.
+ */
+void AudioMixerInvalidate(PAUDIOMIXER pMixer)
+{
+ AssertPtrReturnVoid(pMixer);
+
+ int rc2 = RTCritSectEnter(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFunc(("[%s]\n", pMixer->pszName));
+
+ rc2 = audioMixerInvalidateInternal(pMixer);
+ AssertRC(rc2);
+
+ rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Removes a formerly attached audio sink for an audio mixer, internal version.
+ *
+ * @returns IPRT status code.
+ * @param pMixer Mixer to remove sink from.
+ * @param pSink Sink to remove.
+ */
+static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+ if (!pSink)
+ return VERR_NOT_FOUND;
+
+ AssertMsgReturn(pSink->pParent == pMixer, ("%s: Is not part of mixer '%s'\n",
+ pSink->pszName, pMixer->pszName), VERR_NOT_FOUND);
+
+ LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n",
+ pMixer->pszName, pSink->pszName, pMixer->cSinks));
+
+ /* Remove sink from mixer. */
+ RTListNodeRemove(&pSink->Node);
+ Assert(pMixer->cSinks);
+
+ /* Set mixer to NULL so that we know we're not part of any mixer anymore. */
+ pSink->pParent = NULL;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes a formerly attached audio sink for an audio mixer.
+ *
+ * @returns IPRT status code.
+ * @param pMixer Mixer to remove sink from.
+ * @param pSink Sink to remove.
+ */
+void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
+{
+ int rc2 = RTCritSectEnter(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ audioMixerSinkRemoveAllStreamsInternal(pSink);
+ audioMixerRemoveSinkInternal(pMixer, pSink);
+
+ rc2 = RTCritSectLeave(&pMixer->CritSect);
+}
+
+/**
+ * Sets the mixer's master volume.
+ *
+ * @returns IPRT status code.
+ * @param pMixer Mixer to set master volume for.
+ * @param pVol Volume to set.
+ */
+int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pMixer->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME));
+
+ LogFlowFunc(("[%s] lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
+ pMixer->pszName, pVol->uLeft, pVol->uRight,
+ pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight));
+
+ rc = audioMixerInvalidateInternal(pMixer);
+
+ int rc2 = RTCritSectLeave(&pMixer->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/*********************************************************************************************************************************
+ * Mixer Sink implementation.
+ ********************************************************************************************************************************/
+
+/**
+ * Adds an audio stream to a specific audio sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to add audio stream to.
+ * @param pStream Stream to add.
+ */
+int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */
+ {
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return VERR_NO_MORE_HANDLES;
+ }
+
+ LogFlowFuncEnter();
+
+ /** @todo Check if stream already is assigned to (another) sink. */
+
+ /* If the sink is running and not in pending disable mode,
+ * make sure that the added stream also is enabled. */
+ if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+ {
+ rc = audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE, AUDMIXSTRMCTL_FLAG_NONE);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Apply the sink's combined volume to the stream. */
+ rc = pStream->pConn->pfnStreamSetVolume(pStream->pConn, pStream->pStream, &pSink->VolumeCombined);
+ AssertRC(rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* 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));
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Creates an audio mixer stream.
+ *
+ * @returns IPRT 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.
+ * @param fFlags Stream flags. Currently unused, set to 0.
+ * @param ppStream Pointer which receives the newly created audio stream.
+ */
+int AudioMixerSinkCreateStream(PAUDMIXSINK pSink,
+ PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pConn, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ /** @todo Validate fFlags. */
+ /* ppStream is optional. */
+
+ if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_ANY) == PDMAUDIOBACKENDSTS_NOT_ATTACHED)
+ return VERR_AUDIO_BACKEND_NOT_ATTACHED;
+
+ PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM));
+ if (!pMixStream)
+ return VERR_NO_MEMORY;
+
+ pMixStream->pszName = RTStrDup(pCfg->szName);
+ if (!pMixStream->pszName)
+ {
+ RTMemFree(pMixStream);
+ return VERR_NO_MEMORY;
+ }
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("[%s] fFlags=0x%x (enmDir=%ld, %RU8 bits, %RU8 channels, %RU32Hz)\n",
+ pSink->pszName, fFlags, pCfg->enmDir, pCfg->Props.cBytes * 8, pCfg->Props.cChannels, pCfg->Props.uHz));
+
+ /*
+ * Initialize the host-side configuration for the stream to be created.
+ * Always use the sink's PCM audio format as the host side when creating a stream for it.
+ */
+ AssertMsg(DrvAudioHlpPCMPropsAreValid(&pSink->PCMProps),
+ ("%s: Does not (yet) have a format set when it must\n", pSink->pszName));
+
+ PDMAUDIOSTREAMCFG CfgHost;
+ rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost);
+ AssertRCReturn(rc, rc);
+
+ /* Apply the sink's direction for the configuration to use to
+ * create the stream. */
+ if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
+ {
+ CfgHost.DestSource.Source = pCfg->DestSource.Source;
+ CfgHost.enmDir = PDMAUDIODIR_IN;
+ CfgHost.enmLayout = pCfg->enmLayout;
+ }
+ else
+ {
+ CfgHost.DestSource.Dest = pCfg->DestSource.Dest;
+ CfgHost.enmDir = PDMAUDIODIR_OUT;
+ CfgHost.enmLayout = pCfg->enmLayout;
+ }
+
+ RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName);
+
+ rc = RTCritSectInit(&pMixStream->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMAUDIOSTREAM pStream;
+ rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream);
+ 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 reyling on it to be around now. */
+ pConn->pfnStreamRetain(pConn, pStream);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCircBufCreate(&pMixStream->pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable. */
+ AssertRC(rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pMixStream->fFlags = fFlags;
+ pMixStream->pConn = pConn;
+
+ if (ppStream)
+ *ppStream = pMixStream;
+ }
+ else if (pMixStream)
+ {
+ int rc2 = RTCritSectDelete(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ if (pMixStream->pszName)
+ {
+ RTStrFree(pMixStream->pszName);
+ pMixStream->pszName = NULL;
+ }
+
+ RTMemFree(pMixStream);
+ pMixStream = NULL;
+ }
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Static helper function to translate a sink command
+ * to a PDM audio stream command.
+ *
+ * @returns PDM audio stream command, or PDMAUDIOSTREAMCMD_UNKNOWN if not found.
+ * @param enmCmd Mixer sink command to translate.
+ */
+static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd)
+{
+ switch (enmCmd)
+ {
+ case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE;
+ case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE;
+ case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE;
+ case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME;
+ case AUDMIXSINKCMD_DROP: return PDMAUDIOSTREAMCMD_DROP;
+ default: break;
+ }
+
+ AssertMsgFailed(("Unsupported sink command %d\n", enmCmd));
+ return PDMAUDIOSTREAMCMD_UNKNOWN;
+}
+
+/**
+ * Controls a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Mixer sink to control.
+ * @param enmSinkCmd Sink command to set.
+ */
+int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+
+ PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd);
+ if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN)
+ return VERR_NOT_SUPPORTED;
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Input sink and no recording source set? Bail out early. */
+ if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
+ && pSink->In.pStreamRecSource == NULL)
+ {
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+ }
+
+ PAUDMIXSTREAM pStream;
+ if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
+ && pSink->In.pStreamRecSource) /* Any recording source set? */
+ {
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ if (pStream == pSink->In.pStreamRecSource)
+ {
+ int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE);
+ if (rc2 == VERR_NOT_SUPPORTED)
+ rc2 = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. Flag? */
+ }
+ }
+ }
+ else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
+ {
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE);
+ if (rc2 == VERR_NOT_SUPPORTED)
+ rc2 = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. Flag? */
+ }
+ }
+
+ switch (enmSinkCmd)
+ {
+ case AUDMIXSINKCMD_ENABLE:
+ {
+ /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */
+ pSink->fStatus = AUDMIXSINK_STS_RUNNING;
+ break;
+ }
+
+ case AUDMIXSINKCMD_DISABLE:
+ {
+ if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ {
+ /* Set the sink in a pending disable state first.
+ * The final status (disabled) will be set in the sink's iteration. */
+ pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE;
+ }
+ break;
+ }
+
+ case AUDMIXSINKCMD_DROP:
+ {
+ AudioMixBufReset(&pSink->MixBuf);
+
+ /* Clear dirty bit, keep others. */
+ pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+#ifdef LOG_ENABLED
+ char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
+ LogFlowFunc(("[%s] enmCmd=%d, fStatus=%s, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pszStatus, rc));
+ RTStrFree(pszStatus);
+#endif
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Destroys a mixer sink and removes it from the attached mixer (if any).
+ *
+ * @param pSink Mixer sink to destroy.
+ */
+void AudioMixerSinkDestroy(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ AssertRC(rc2);
+
+ if (pSink->pParent)
+ {
+ /* Save mixer pointer, as after audioMixerRemoveSinkInternal() the
+ * pointer will be gone from the stream. */
+ PAUDIOMIXER pMixer = pSink->pParent;
+ AssertPtr(pMixer);
+
+ audioMixerRemoveSinkInternal(pMixer, pSink);
+
+ Assert(pMixer->cSinks);
+ pMixer->cSinks--;
+ }
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ audioMixerSinkDestroyInternal(pSink);
+}
+
+/**
+ * Destroys a mixer sink.
+ *
+ * @param pSink Mixer sink to destroy.
+ */
+static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink)
+{
+ AssertPtrReturnVoid(pSink);
+
+ LogFunc(("%s\n", pSink->pszName));
+
+ PAUDMIXSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
+ {
+ /* Save a pointer to the stream to remove, as pStream
+ * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */
+ PAUDMIXSTREAM pStreamToRemove = pStream;
+
+ audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove);
+ audioMixerStreamDestroyInternal(pStreamToRemove);
+ }
+
+#ifdef VBOX_AUDIO_MIXER_DEBUG
+ DrvAudioHlpFileDestroy(pSink->Dbg.pFile);
+ pSink->Dbg.pFile = NULL;
+#endif
+
+ if (pSink->pszName)
+ {
+ RTStrFree(pSink->pszName);
+ pSink->pszName = NULL;
+ }
+
+ RTCritSectDelete(&pSink->CritSect);
+
+ RTMemFree(pSink);
+ pSink = NULL;
+}
+
+/**
+ * Returns the amount of bytes ready to be read from a sink since the last call
+ * to AudioMixerSinkUpdate().
+ *
+ * @returns Amount of bytes ready to be read from the sink.
+ * @param pSink Sink to return number of available bytes for.
+ */
+uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, 0);
+
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("%s: Can't read from a non-input sink\n", pSink->pszName));
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return 0;
+
+ uint32_t cbReadable = 0;
+
+ if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ {
+#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN
+# error "Implement me!"
+#else
+ PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource;
+ if (!pStreamRecSource)
+ {
+ Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName));
+ }
+ else
+ {
+ AssertPtr(pStreamRecSource->pConn);
+ cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream);
+ }
+#endif
+ }
+
+ Log3Func(("[%s] cbReadable=%RU32\n", pSink->pszName, cbReadable));
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return cbReadable;
+}
+
+/**
+ * Returns the sink's current recording source.
+ *
+ * @return Mixer stream which currently is set as current recording source, NULL if none is set.
+ * @param pSink Audio mixer sink to return current recording source for.
+ */
+PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink)
+{
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n"));
+
+ PAUDMIXSTREAM pStream = pSink->In.pStreamRecSource;
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return pStream;
+}
+
+/**
+ * Returns the amount of bytes ready to be written to a sink since the last call
+ * to AudioMixerSinkUpdate().
+ *
+ * @returns Amount of bytes ready to be written to the sink.
+ * @param pSink Sink to return number of available bytes for.
+ */
+uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, 0);
+
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("%s: Can't write to a non-output sink\n", pSink->pszName));
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return 0;
+
+ uint32_t cbWritable = 0;
+
+ if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+ && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+ {
+ cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf);
+ }
+
+ Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
+ pSink->pszName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pSink->PCMProps)));
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return cbWritable;
+}
+
+/**
+ * Returns the sink's mixing direction.
+ *
+ * @returns Mixing direction.
+ * @param pSink Sink to return direction for.
+ */
+AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return AUDMIXSINKDIR_UNKNOWN;
+
+ AUDMIXSINKDIR enmDir = pSink->enmDir;
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return enmDir;
+}
+
+/**
+ * Returns the sink's (friendly) name.
+ *
+ * @returns The sink's (friendly) name.
+ */
+const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, "<Unknown>");
+
+ return pSink->pszName;
+}
+
+/**
+ * Returns a specific mixer stream from a sink, based on its index.
+ *
+ * @returns Mixer stream if found, or NULL if not found.
+ * @param pSink Sink to retrieve mixer stream from.
+ * @param uIndex Index of the mixer stream to return.
+ */
+PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex)
+{
+ AssertPtrReturn(pSink, NULL);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ AssertMsgReturn(uIndex < pSink->cStreams,
+ ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL);
+
+ /* Slow lookup, d'oh. */
+ PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node);
+ while (uIndex)
+ {
+ pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node);
+ uIndex--;
+ }
+
+ /** @todo Do we need to raise the stream's reference count here? */
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ AssertPtr(pStream);
+ return pStream;
+}
+
+/**
+ * Returns the current status of a mixer sink.
+ *
+ * @returns The sink's current status.
+ * @param pSink Mixer sink to return status for.
+ */
+AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return AUDMIXSINK_STS_NONE;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc2))
+ return AUDMIXSINK_STS_NONE;
+
+ /* If the dirty flag is set, there is unprocessed data in the sink. */
+ AUDMIXSINKSTS stsSink = pSink->fStatus;
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return stsSink;
+}
+
+/**
+ * Returns the number of attached mixer streams to a mixer sink.
+ *
+ * @returns The number of attached mixer streams.
+ * @param pSink Mixer sink to return number for.
+ */
+uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return 0;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc2))
+ return 0;
+
+ uint8_t cStreams = pSink->cStreams;
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return cStreams;
+}
+
+/**
+ * Returns whether the sink is in an active state or not.
+ * Note: The pending disable state also counts as active.
+ *
+ * @returns True if active, false if not.
+ * @param pSink Sink to return active state for.
+ */
+bool AudioMixerSinkIsActive(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return false;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc2))
+ return false;
+
+ bool fIsActive = pSink->fStatus & AUDMIXSINK_STS_RUNNING;
+ /* Note: AUDMIXSINK_STS_PENDING_DISABLE implies AUDMIXSINK_STS_RUNNING. */
+
+ Log3Func(("[%s] fActive=%RTbool\n", pSink->pszName, fIsActive));
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return fIsActive;
+}
+
+/**
+ * Reads audio data from a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Mixer sink to read data from.
+ * @param enmOp Mixer operation to use for reading the data.
+ * @param pvBuf Buffer where to store the read data.
+ * @param cbBuf Buffer size (in bytes) where to store the data.
+ * @param pcbRead Number of bytes read. Optional.
+ */
+int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ RT_NOREF(enmOp);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ /** @todo Handle mixing operation enmOp! */
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT,
+ ("Can't read from a sink which is not an input sink\n"));
+
+ uint32_t cbRead = 0;
+
+ /* Flag indicating whether this sink is in a 'clean' state,
+ * e.g. there is no more data to read from. */
+ bool fClean = true;
+
+ PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource;
+ if (!pStreamRecSource)
+ {
+ Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName));
+ }
+ else if (!DrvAudioHlpStreamStatusCanRead(
+ pStreamRecSource->pConn->pfnStreamGetStatus(pStreamRecSource->pConn, pStreamRecSource->pStream)))
+ {
+ Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pStreamRecSource->pszName));
+ }
+ else
+ {
+ uint32_t cbToRead = cbBuf;
+ while (cbToRead)
+ {
+ uint32_t cbReadStrm;
+ AssertPtr(pStreamRecSource->pConn);
+#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN
+# error "Implement me!"
+#else
+ rc = pStreamRecSource->pConn->pfnStreamRead(pStreamRecSource->pConn, pStreamRecSource->pStream,
+ (uint8_t *)pvBuf + cbRead, cbToRead, &cbReadStrm);
+#endif
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed reading from stream '%s': %Rrc\n", pSink->pszName, pStreamRecSource->pszName, rc));
+
+ Log3Func(("[%s] Stream '%s': Read %RU32 bytes\n", pSink->pszName, pStreamRecSource->pszName, cbReadStrm));
+
+ if ( RT_FAILURE(rc)
+ || !cbReadStrm)
+ break;
+
+ AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW);
+ cbToRead -= cbReadStrm;
+ cbRead += cbReadStrm;
+ Assert(cbRead <= cbBuf);
+ }
+
+ uint32_t cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream);
+
+ /* Still some data available? Then sink is not clean (yet). */
+ if (cbReadable)
+ fClean = false;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fClean)
+ pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
+
+ /* Update our last read time stamp. */
+ pSink->tsLastReadWrittenNs = RTTimeNanoTS();
+
+#ifdef VBOX_AUDIO_MIXER_DEBUG
+ int rc2 = DrvAudioHlpFileWrite(pSink->Dbg.pFile, pvBuf, cbRead, 0 /* fFlags */);
+ AssertRC(rc2);
+#endif
+ }
+ }
+
+#ifdef LOG_ENABLED
+ char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
+ Log2Func(("[%s] cbRead=%RU32, fClean=%RTbool, fStatus=%s, rc=%Rrc\n", pSink->pszName, cbRead, fClean, pszStatus, rc));
+ RTStrFree(pszStatus);
+#endif
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Removes a mixer stream from a mixer sink, internal version.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to remove mixer stream from.
+ * @param pStream Stream to remove.
+ */
+static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_PARAMETER);
+ if ( !pStream
+ || !pStream->pSink) /* Not part of a sink anymore? */
+ {
+ return VERR_NOT_FOUND;
+ }
+
+ AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n",
+ pStream->pszName, pSink->pszName), VERR_NOT_FOUND);
+
+ LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n",
+ pSink->pszName, pStream->pStream->szName, pSink->cStreams));
+
+ /* Remove stream from sink. */
+ RTListNodeRemove(&pStream->Node);
+
+ int rc = VINF_SUCCESS;
+
+ if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
+ {
+ /* Make sure to also un-set the recording source if this stream was set
+ * as the recording source before. */
+ if (pStream == pSink->In.pStreamRecSource)
+ rc = audioMixerSinkSetRecSourceInternal(pSink, NULL);
+ }
+
+ /* Set sink to NULL so that we know we're not part of any sink anymore. */
+ pStream->pSink = NULL;
+
+ return rc;
+}
+
+/**
+ * Removes a mixer stream from a mixer sink.
+ *
+ * @param pSink Sink to remove mixer stream from.
+ * @param pStream Stream to remove.
+ */
+void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ AssertRC(rc2);
+
+ rc2 = audioMixerSinkRemoveStreamInternal(pSink, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ Assert(pSink->cStreams);
+ pSink->cStreams--;
+ }
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Removes all attached streams from a given sink.
+ *
+ * @param pSink Sink to remove attached streams from.
+ */
+static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+
+ LogFunc(("%s\n", pSink->pszName));
+
+ PAUDMIXSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
+ audioMixerSinkRemoveStreamInternal(pSink, pStream);
+}
+
+/**
+ * Resets the sink's state.
+ *
+ * @param pSink Sink to reset.
+ */
+static void audioMixerSinkReset(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+
+ LogFunc(("[%s]\n", pSink->pszName));
+
+ AudioMixBufReset(&pSink->MixBuf);
+
+ /* Update last updated timestamp. */
+ pSink->tsLastUpdatedMs = 0;
+
+ /* Reset status. */
+ pSink->fStatus = AUDMIXSINK_STS_NONE;
+}
+
+/**
+ * Removes all attached streams from a given sink.
+ *
+ * @param pSink Sink to remove attached streams from.
+ */
+void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ AssertRC(rc2);
+
+ audioMixerSinkRemoveAllStreamsInternal(pSink);
+
+ pSink->cStreams = 0;
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Resets a sink. This will immediately stop all processing.
+ *
+ * @param pSink Sink to reset.
+ */
+void AudioMixerSinkReset(PAUDMIXSINK pSink)
+{
+ if (!pSink)
+ return;
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFunc(("[%s]\n", pSink->pszName));
+
+ audioMixerSinkReset(pSink);
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Returns the audio format of a mixer sink.
+ *
+ * @param pSink Sink to retrieve audio format for.
+ * @param pPCMProps Where to the returned audio format.
+ */
+void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps)
+{
+ AssertPtrReturnVoid(pSink);
+ AssertPtrReturnVoid(pPCMProps);
+
+ int rc2 = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc2))
+ return;
+
+ memcpy(pPCMProps, &pSink->PCMProps, sizeof(PDMAUDIOPCMPROPS));
+
+ rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Sets the audio format of a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to set audio format for.
+ * @param pPCMProps Audio format (PCM properties) to set.
+ */
+int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER);
+ AssertReturn(DrvAudioHlpPCMPropsAreValid(pPCMProps), VERR_INVALID_PARAMETER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */
+ {
+ rc = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc);
+
+ return rc;
+ }
+
+ if (pSink->PCMProps.uHz)
+ LogFlowFunc(("[%s] Old format: %RU8 bit, %RU8 channels, %RU32Hz\n",
+ pSink->pszName, pSink->PCMProps.cBytes * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
+
+ memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMAUDIOPCMPROPS));
+
+ LogFlowFunc(("[%s] New format %RU8 bit, %RU8 channels, %RU32Hz\n",
+ pSink->pszName, pSink->PCMProps.cBytes * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
+
+ /* Also update the sink's mixing buffer format. */
+ AudioMixBufDestroy(&pSink->MixBuf);
+ rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps,
+ DrvAudioHlpMilliToFrames(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable? */
+ if (RT_SUCCESS(rc))
+ {
+ PAUDMIXSTREAM pStream;
+ RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+ {
+ /** @todo Invalidate mix buffers! */
+ }
+ }
+
+#ifdef VBOX_AUDIO_MIXER_DEBUG
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpFileClose(pSink->Dbg.pFile);
+
+ char szTemp[RTPATH_MAX];
+ int rc2 = RTPathTemp(szTemp, sizeof(szTemp));
+ if (RT_SUCCESS(rc2))
+ {
+ /** @todo Sanitize sink name. */
+
+ char szName[64];
+ RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName);
+
+ char szFile[RTPATH_MAX + 1];
+ rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, szName,
+ 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pSink->Dbg.pFile);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pSink->Dbg.pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pSink->PCMProps);
+ }
+ }
+ }
+#endif
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Set the current recording source of an input mixer sink, internal version.
+ *
+ * @return IPRT status code.
+ * @param pSink Input mixer sink to set recording source for.
+ * @param pStream Mixer stream to set as current recording source. Must be an input stream.
+ * Specify NULL to un-set the current recording source.
+ */
+static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n"));
+
+ int rc;
+
+ if (pSink->In.pStreamRecSource) /* Disable old recording source, if any set. */
+ {
+ const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn;
+ AssertPtr(pConn);
+ rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, false /* Disable */);
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pStream) /* Can be NULL if un-setting. */
+ {
+ AssertPtr(pStream->pStream);
+ AssertMsg(pStream->pStream->enmDir == PDMAUDIODIR_IN, ("Specified stream is not an input stream\n"));
+ }
+
+ pSink->In.pStreamRecSource = pStream;
+
+ if (pSink->In.pStreamRecSource)
+ {
+ const PPDMIAUDIOCONNECTOR pConn = pSink->In.pStreamRecSource->pConn;
+ AssertPtr(pConn);
+ rc = pConn->pfnEnable(pConn, PDMAUDIODIR_IN, true /* Enable */);
+ }
+ }
+
+ LogFunc(("[%s] Recording source is now '%s', rc=%Rrc\n",
+ pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "<None>", rc));
+
+ return rc;
+}
+
+/**
+ * Set the current recording source of an input mixer sink.
+ *
+ * @return IPRT status code.
+ * @param pSink Input mixer sink to set recording source for.
+ * @param pStream Mixer stream to set as current recording source. Must be an input stream.
+ * Set to NULL to un-set the current recording source.
+ */
+int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = audioMixerSinkSetRecSourceInternal(pSink, pStream);
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Sets the volume of an individual sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to set volume for.
+ * @param pVol Volume to set.
+ */
+int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME));
+
+ LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n",
+ pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
+
+ LogRel2(("Mixer: Setting volume of sink '%s' to %RU8/%RU8 (%s)\n",
+ pSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted"));
+
+ AssertPtr(pSink->pParent);
+ rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster);
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Updates a mixer sink, internal version.
+ *
+ * @returns IPRT status code.
+ * @param pSink Mixer sink to update.
+ */
+static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
+ Log3Func(("[%s] fStatus=%s\n", pSink->pszName, pszStatus));
+ RTStrFree(pszStatus);
+#endif
+
+ /* Sink disabled? Take a shortcut. */
+ if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
+ return rc;
+
+ /* Input sink and no recording source set? Bail out early. */
+ if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
+ && pSink->In.pStreamRecSource == NULL)
+ return rc;
+
+ /* Number of disabled streams of this sink. */
+ uint8_t cStreamsDisabled = pSink->cStreams;
+
+ /* Next, try to write (multiplex) as much audio data as possible to all connected mixer streams. */
+ uint32_t cbToWriteToStreams = AudioMixBufUsedBytes(&pSink->MixBuf);
+
+ uint8_t arrChunkBuf[_1K]; /** @todo Hm ... some zero copy / shared buffers would be nice! */
+ while (cbToWriteToStreams)
+ {
+ uint32_t cfChunk;
+ rc = AudioMixBufAcquireReadBlock(&pSink->MixBuf, arrChunkBuf, RT_MIN(cbToWriteToStreams, sizeof(arrChunkBuf)), &cfChunk);
+ if (RT_FAILURE(rc))
+ break;
+
+ const uint32_t cbChunk = DrvAudioHlpFramesToBytes(cfChunk, &pSink->PCMProps);
+ Assert(cbChunk <= sizeof(arrChunkBuf));
+
+ /* Multiplex the current chunk in a synchronized fashion to all connected streams. */
+ uint32_t cbChunkWrittenMin = 0;
+ rc = audioMixerSinkMultiplexSync(pSink, AUDMIXOP_COPY, arrChunkBuf, cbChunk, &cbChunkWrittenMin);
+ if (RT_SUCCESS(rc))
+ {
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = audioMixerSinkWriteToStream(pSink, pMixStream);
+ AssertRC(rc2);
+ }
+ }
+
+ Log3Func(("[%s] cbChunk=%RU32, cbChunkWrittenMin=%RU32\n", pSink->pszName, cbChunk, cbChunkWrittenMin));
+
+ AudioMixBufReleaseReadBlock(&pSink->MixBuf, AUDIOMIXBUF_B2F(&pSink->MixBuf, cbChunkWrittenMin));
+
+ if ( RT_FAILURE(rc)
+ || cbChunkWrittenMin == 0)
+ break;
+
+ Assert(cbToWriteToStreams >= cbChunkWrittenMin);
+ cbToWriteToStreams -= cbChunkWrittenMin;
+ }
+
+ if ( !(pSink->fStatus & AUDMIXSINK_STS_DIRTY)
+ && AudioMixBufUsed(&pSink->MixBuf)) /* Still audio output data left? Consider the sink as being "dirty" then. */
+ {
+ /* Set dirty bit. */
+ pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
+ }
+
+ PAUDMIXSTREAM pMixStream, pMixStreamNext;
+ RTListForEachSafe(&pSink->lstStreams, pMixStream, pMixStreamNext, AUDMIXSTREAM, Node)
+ {
+ /* Input sink and not the recording source? Skip. */
+ if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
+ && pSink->In.pStreamRecSource != pMixStream)
+ continue;
+
+ PPDMAUDIOSTREAM pStream = pMixStream->pStream;
+ AssertPtr(pStream);
+
+ PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn;
+ AssertPtr(pConn);
+
+ uint32_t cfProc = 0;
+
+ if (!DrvAudioHlpStreamStatusIsReady(pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream)))
+ continue;
+
+ int rc2 = pConn->pfnStreamIterate(pConn, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
+ {
+ rc2 = pConn->pfnStreamCapture(pConn, pStream, &cfProc);
+ if (RT_FAILURE(rc2))
+ {
+ LogFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
+ continue;
+ }
+
+ if (cfProc)
+ pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
+ }
+ else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
+ {
+ rc2 = pConn->pfnStreamPlay(pConn, pStream, &cfProc);
+ if (RT_FAILURE(rc2))
+ {
+ LogFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
+ continue;
+ }
+ }
+ else
+ {
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ continue;
+ }
+ }
+
+ PDMAUDIOSTREAMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pStream);
+
+ /* Is the stream enabled or in pending disable state?
+ * Don't consider this stream as being disabled then. */
+ if ( (strmSts & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ || (strmSts & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE))
+ {
+ cStreamsDisabled--;
+ }
+
+ Log3Func(("\t%s: cPlayed/cCaptured=%RU32, rc2=%Rrc\n", pStream->szName, cfProc, rc2));
+ }
+
+ Log3Func(("[%s] fPendingDisable=%RTbool, %RU8/%RU8 streams disabled\n",
+ pSink->pszName, RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE), cStreamsDisabled, pSink->cStreams));
+
+ /* Update last updated timestamp. */
+ pSink->tsLastUpdatedMs = RTTimeMilliTS();
+
+ /* All streams disabled and the sink is in pending disable mode? */
+ if ( cStreamsDisabled == pSink->cStreams
+ && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+ {
+ audioMixerSinkReset(pSink);
+ }
+
+ return rc;
+}
+
+/**
+ * Updates (invalidates) a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Mixer sink to update.
+ */
+int AudioMixerSinkUpdate(PAUDMIXSINK pSink)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = audioMixerSinkUpdateInternal(pSink);
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/**
+ * Updates the (master) volume of a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Mixer sink to update volume for.
+ * @param pVolMaster Master volume to set.
+ */
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s] Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
+ pSink->pszName, pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight));
+ LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU32, rVol=%RU32 ",
+ pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
+
+ /** @todo Very crude implementation for now -- needs more work! */
+
+ pSink->VolumeCombined.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted;
+
+ pSink->VolumeCombined.uLeft = ( (pSink->Volume.uLeft ? pSink->Volume.uLeft : 1)
+ * (pVolMaster->uLeft ? pVolMaster->uLeft : 1)) / PDMAUDIO_VOLUME_MAX;
+
+ pSink->VolumeCombined.uRight = ( (pSink->Volume.uRight ? pSink->Volume.uRight : 1)
+ * (pVolMaster->uRight ? pVolMaster->uRight : 1)) / PDMAUDIO_VOLUME_MAX;
+
+ LogFlow(("-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
+ pSink->VolumeCombined.fMuted, pSink->VolumeCombined.uLeft, pSink->VolumeCombined.uRight));
+
+ /* Propagate new sink volume to all streams in the sink. */
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &pSink->VolumeCombined);
+ AssertRC(rc2);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes (buffered) output data of a sink's stream to the bound audio connector stream.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink of stream that contains the mixer stream.
+ * @param pMixStream Mixer stream to write output data for.
+ */
+static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream)
+{
+ if (!pMixStream->pCircBuf)
+ return VINF_SUCCESS;
+
+ return audioMixerSinkWriteToStreamEx(pSink, pMixStream, (uint32_t)RTCircBufUsed(pMixStream->pCircBuf), NULL /* pcbWritten */);
+}
+
+/**
+ * Writes (buffered) output data of a sink's stream to the bound audio connector stream, extended version.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink of stream that contains the mixer stream.
+ * @param pMixStream Mixer stream to write output data for.
+ * @param cbToWrite Size (in bytes) to write.
+ * @param pcbWritten Size (in bytes) written on success. Optional.
+ */
+static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten)
+{
+ /* pcbWritten is optional. */
+
+ if ( !cbToWrite
+ || !DrvAudioHlpStreamStatusCanWrite(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream)))
+ {
+ if (pcbWritten)
+ *pcbWritten = 0;
+
+ return VINF_SUCCESS;
+ }
+
+ PRTCIRCBUF pCircBuf = pMixStream->pCircBuf;
+
+ const uint32_t cbWritableStream = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream);
+ cbToWrite = RT_MIN(cbToWrite, RT_MIN((uint32_t)RTCircBufUsed(pCircBuf), cbWritableStream));
+
+ Log3Func(("[%s] cbWritableStream=%RU32, cbToWrite=%RU32\n",
+ pMixStream->pszName, cbWritableStream, cbToWrite));
+
+ uint32_t cbWritten = 0;
+
+ int rc = VINF_SUCCESS;
+
+ while (cbToWrite)
+ {
+ void *pvChunk;
+ size_t cbChunk;
+ RTCircBufAcquireReadBlock(pCircBuf, cbToWrite, &pvChunk, &cbChunk);
+
+ Log3Func(("[%s] cbChunk=%RU32\n", pMixStream->pszName, cbChunk));
+
+ uint32_t cbChunkWritten = 0;
+ if (cbChunk)
+ {
+ rc = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvChunk, (uint32_t)cbChunk,
+ &cbChunkWritten);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ LogRel2(("Mixer: Buffer overrun for mixer stream '%s' (sink '%s')\n", pMixStream->pszName, pSink->pszName));
+ break;
+ }
+ else if (rc == VERR_AUDIO_STREAM_NOT_READY)
+ {
+ /* Stream is not enabled, just skip. */
+ rc = VINF_SUCCESS;
+ }
+ else
+ LogRel2(("Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n",
+ pMixStream->pszName, pSink->pszName, rc));
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc));
+ }
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbChunkWritten);
+
+ if ( RT_FAILURE(rc)
+ || !cbChunkWritten)
+ break;
+
+ Assert(cbToWrite >= cbChunkWritten);
+ cbToWrite -= (uint32_t)cbChunkWritten;
+
+ cbWritten += (uint32_t)cbChunkWritten;
+ }
+
+ Log3Func(("[%s] cbWritten=%RU32\n", pMixStream->pszName, cbWritten));
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+
+#ifdef DEBUG_andy
+ AssertRC(rc);
+#endif
+
+ return rc;
+}
+
+/**
+ * Multiplexes audio output data to all connected mixer streams in a synchronized fashion, e.g.
+ * only multiplex as much data as all streams can handle at this time.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to write audio output to.
+ * @param enmOp What mixing operation to use. Currently not implemented.
+ * @param pvBuf Pointer to audio data to write.
+ * @param cbBuf Size (in bytes) of audio data to write.
+ * @param pcbWrittenMin Returns minimum size (in bytes) successfully written to all mixer streams. Optional.
+ */
+static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf,
+ uint32_t *pcbWrittenMin)
+{
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ RT_NOREF(enmOp);
+
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
+ ("%s: Can't multiplex to a sink which is not an output sink\n", pSink->pszName));
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbToWriteMin = UINT32_MAX;
+
+ Log3Func(("[%s] cbBuf=%RU32\n", pSink->pszName, cbBuf));
+
+ PAUDMIXSTREAM pMixStream;
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ if (!DrvAudioHlpStreamStatusCanWrite(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream)))
+ continue;
+
+ cbToWriteMin = RT_MIN(cbBuf, RT_MIN(cbToWriteMin, (uint32_t)RTCircBufFree(pMixStream->pCircBuf)));
+ }
+
+ if (cbToWriteMin == UINT32_MAX) /* No space at all? */
+ cbToWriteMin = 0;
+
+ if (cbToWriteMin)
+ {
+ RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+ {
+ PRTCIRCBUF pCircBuf = pMixStream->pCircBuf;
+ void *pvChunk;
+ size_t cbChunk;
+
+ uint32_t cbWrittenBuf = 0;
+ uint32_t cbToWriteBuf = cbToWriteMin;
+
+ while (cbToWriteBuf)
+ {
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToWriteBuf, &pvChunk, &cbChunk);
+
+ if (cbChunk)
+ memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenBuf, cbChunk);
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbChunk);
+
+ cbWrittenBuf += (uint32_t)cbChunk;
+ Assert(cbWrittenBuf <= cbBuf);
+
+ Assert(cbToWriteBuf >= cbChunk);
+ cbToWriteBuf -= (uint32_t)cbChunk;
+ }
+
+ if (cbWrittenBuf) /* Update the mixer stream's last written time stamp. */
+ pMixStream->tsLastReadWrittenNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] Mixer stream '%s' -> cbWrittenBuf=%RU32\n", pSink->pszName, pMixStream->pszName, cbWrittenBuf));
+ }
+ }
+
+ Log3Func(("[%s] cbBuf=%RU32, cbToWriteMin=%RU32\n", pSink->pszName, cbBuf, cbToWriteMin));
+
+ if (pcbWrittenMin)
+ *pcbWrittenMin = cbToWriteMin;
+
+ return rc;
+}
+
+/**
+ * Writes data to a mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pSink Sink to write data to.
+ * @param enmOp Mixer operation to use when writing data to the sink.
+ * @param pvBuf Buffer containing the audio data to write.
+ * @param cbBuf Size (in bytes) of the buffer containing the audio data.
+ * @param pcbWritten Number of bytes written. Optional.
+ */
+int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+ RT_NOREF(enmOp);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ int rc = RTCritSectEnter(&pSink->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pSink->fStatus & AUDMIXSINK_STS_RUNNING,
+ ("%s: Can't write to a sink which is not running (anymore) (status 0x%x)\n", pSink->pszName, pSink->fStatus));
+ AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
+ ("%s: Can't write to a sink which is not an output sink\n", pSink->pszName));
+
+ Assert(cbBuf <= AudioMixBufFreeBytes(&pSink->MixBuf));
+
+ uint32_t cbWritten = 0;
+ uint32_t cbToWrite = cbBuf;
+ while (cbToWrite)
+ {
+ /* First, write the data to the mixer sink's own mixing buffer.
+ * Here the audio data can be transformed into the mixer sink's format. */
+ uint32_t cfWritten = 0;
+ rc = AudioMixBufWriteCirc(&pSink->MixBuf, (uint8_t *)pvBuf + cbWritten, cbToWrite, &cfWritten);
+ if (RT_FAILURE(rc))
+ break;
+
+ const uint32_t cbWrittenChunk = DrvAudioHlpFramesToBytes(cfWritten, &pSink->PCMProps);
+
+ Assert(cbToWrite >= cbWrittenChunk);
+ cbToWrite -= cbWrittenChunk;
+ cbWritten += cbWrittenChunk;
+ }
+
+ Assert(cbWritten == cbBuf);
+
+ /* Update the sink's last written time stamp. */
+ pSink->tsLastReadWrittenNs = RTTimeNanoTS();
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+
+ int rc2 = RTCritSectLeave(&pSink->CritSect);
+ AssertRC(rc2);
+
+ return rc;
+}
+
+/*********************************************************************************************************************************
+ * Mixer Stream implementation.
+ ********************************************************************************************************************************/
+
+/**
+ * Controls a mixer stream, internal version.
+ *
+ * @returns IPRT status code.
+ * @param pMixStream Mixer stream to control.
+ * @param enmCmd Mixer stream command to use.
+ * @param fCtl Additional control flags. Pass 0.
+ */
+int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
+{
+ AssertPtr(pMixStream->pConn);
+ AssertPtr(pMixStream->pStream);
+
+ RT_NOREF(fCtl);
+
+ int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd);
+
+ LogFlowFunc(("[%s] enmCmd=%ld, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc));
+
+ return rc;
+}
+
+/**
+ * Controls a mixer stream.
+ *
+ * @returns IPRT status code.
+ * @param pMixStream Mixer stream to control.
+ * @param enmCmd Mixer stream command to use.
+ * @param fCtl Additional control flags. Pass 0.
+ */
+int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
+{
+ RT_NOREF(fCtl);
+ AssertPtrReturn(pMixStream, VERR_INVALID_POINTER);
+ /** @todo Validate fCtl. */
+
+ int rc = RTCritSectEnter(&pMixStream->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = audioMixerStreamCtlInternal(pMixStream, enmCmd, fCtl);
+
+ int rc2 = RTCritSectLeave(&pMixStream->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Destroys a mixer stream, internal version.
+ *
+ * @param pMixStream Mixer stream to destroy.
+ */
+static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream)
+{
+ AssertPtrReturnVoid(pMixStream);
+
+ LogFunc(("%s\n", pMixStream->pszName));
+
+ 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);
+
+ pMixStream->pStream = NULL;
+ }
+
+ pMixStream->pConn = NULL;
+ }
+
+ if (pMixStream->pszName)
+ {
+ RTStrFree(pMixStream->pszName);
+ pMixStream->pszName = NULL;
+ }
+
+ if (pMixStream->pCircBuf)
+ {
+ RTCircBufDestroy(pMixStream->pCircBuf);
+ pMixStream->pCircBuf = NULL;
+ }
+
+ int rc2 = RTCritSectDelete(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ RTMemFree(pMixStream);
+ pMixStream = NULL;
+}
+
+/**
+ * Destroys a mixer stream.
+ *
+ * @param pMixStream Mixer stream to destroy.
+ */
+void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream)
+{
+ if (!pMixStream)
+ return;
+
+ int rc2 = RTCritSectEnter(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ LogFunc(("%s\n", pMixStream->pszName));
+
+ if (pMixStream->pSink) /* Is the stream part of a sink? */
+ {
+ /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the
+ * pointer will be gone from the stream. */
+ PAUDMIXSINK pSink = pMixStream->pSink;
+
+ rc2 = audioMixerSinkRemoveStreamInternal(pSink, pMixStream);
+ if (RT_SUCCESS(rc2))
+ {
+ Assert(pSink->cStreams);
+ pSink->cStreams--;
+ }
+ }
+ else
+ rc2 = VINF_SUCCESS;
+
+ int rc3 = RTCritSectLeave(&pMixStream->CritSect);
+ AssertRC(rc3);
+
+ if (RT_SUCCESS(rc2))
+ {
+ audioMixerStreamDestroyInternal(pMixStream);
+ pMixStream = NULL;
+ }
+
+ LogFlowFunc(("Returning %Rrc\n", rc2));
+}
+
+/**
+ * Returns whether a mixer stream currently is active (playing/recording) or not.
+ *
+ * @returns @c true if playing/recording, @c false if not.
+ * @param pMixStream Mixer stream to return status for.
+ */
+bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream)
+{
+ int rc2 = RTCritSectEnter(&pMixStream->CritSect);
+ if (RT_FAILURE(rc2))
+ return false;
+
+ AssertPtr(pMixStream->pConn);
+ AssertPtr(pMixStream->pStream);
+
+ bool fIsActive;
+
+ if ( pMixStream->pConn
+ && pMixStream->pStream
+ && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ {
+ fIsActive = true;
+ }
+ else
+ fIsActive = false;
+
+ rc2 = RTCritSectLeave(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ return fIsActive;
+}
+
+/**
+ * Returns whether a mixer stream is valid (e.g. initialized and in a working state) or not.
+ *
+ * @returns @c true if valid, @c false if not.
+ * @param pMixStream Mixer stream to return status for.
+ */
+bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream)
+{
+ if (!pMixStream)
+ return false;
+
+ int rc2 = RTCritSectEnter(&pMixStream->CritSect);
+ if (RT_FAILURE(rc2))
+ return false;
+
+ bool fIsValid;
+
+ if ( pMixStream->pConn
+ && pMixStream->pStream
+ && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED))
+ {
+ fIsValid = true;
+ }
+ else
+ fIsValid = false;
+
+ rc2 = RTCritSectLeave(&pMixStream->CritSect);
+ AssertRC(rc2);
+
+ return fIsValid;
+}
+
diff --git a/src/VBox/Devices/Audio/AudioMixer.h b/src/VBox/Devices/Audio/AudioMixer.h
new file mode 100644
index 00000000..7f62d3c7
--- /dev/null
+++ b/src/VBox/Devices/Audio/AudioMixer.h
@@ -0,0 +1,263 @@
+/* $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-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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>
+
+/**
+ * Structure for maintaining an audio mixer instance.
+ */
+typedef struct AUDIOMIXER
+{
+ /** The mixer's name. */
+ char *pszName;
+ /** The mixer's critical section. */
+ RTCRITSECT CritSect;
+ /** The master volume of this mixer. */
+ PDMAUDIOVOLUME VolMaster;
+ /** List of audio mixer sinks. */
+ RTLISTANCHOR lstSinks;
+ /** Number of used audio sinks. */
+ uint8_t cSinks;
+} AUDIOMIXER, *PAUDIOMIXER;
+
+/** Defines an audio mixer stream's flags. */
+#define AUDMIXSTREAMFLAGS uint32_t
+
+/** No flags specified. */
+#define AUDMIXSTREAM_FLAG_NONE 0
+
+/** Prototype needed for AUDMIXSTREAM struct definition. */
+typedef struct AUDMIXSINK *PAUDMIXSINK;
+
+/**
+ * Structure for maintaining an audio mixer stream.
+ */
+typedef struct AUDMIXSTREAM
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Name of this stream. */
+ char *pszName;
+ /** The streams's critical section. */
+ RTCRITSECT CritSect;
+ /** Sink this stream is attached to. */
+ PAUDMIXSINK pSink;
+ /** Stream flags of type AUDMIXSTREAM_FLAG_. */
+ uint32_t fFlags;
+ /** Pointer to audio connector being used. */
+ PPDMIAUDIOCONNECTOR pConn;
+ /** Pointer to PDM audio stream this mixer stream handles. */
+ PPDMAUDIOSTREAM pStream;
+ /** Last read (recording) / written (playback) timestamp (in ns). */
+ uint64_t tsLastReadWrittenNs;
+ /** The stream's circular buffer for temporarily
+ * holding (raw) device audio data. */
+ PRTCIRCBUF pCircBuf;
+} AUDMIXSTREAM, *PAUDMIXSTREAM;
+
+/** Defines an audio sink's current status. */
+#define AUDMIXSINKSTS uint32_t
+
+/** No status specified. */
+#define AUDMIXSINK_STS_NONE 0
+/** The sink is active and running. */
+#define AUDMIXSINK_STS_RUNNING RT_BIT(0)
+/** The sink is in a pending disable state. */
+#define AUDMIXSINK_STS_PENDING_DISABLE RT_BIT(1)
+/** 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. */
+#define AUDMIXSINK_STS_DIRTY RT_BIT(2)
+
+/**
+ * Audio mixer sink direction.
+ */
+typedef enum AUDMIXSINKDIR
+{
+ /** Unknown direction. */
+ AUDMIXSINKDIR_UNKNOWN = 0,
+ /** Input (capturing from a device). */
+ AUDMIXSINKDIR_INPUT,
+ /** Output (playing to a device). */
+ AUDMIXSINKDIR_OUTPUT,
+ /** The usual 32-bit hack. */
+ AUDMIXSINKDIR_32BIT_HACK = 0x7fffffff
+} AUDMIXSINKDIR;
+
+/**
+ * Audio mixer sink command.
+ */
+typedef enum AUDMIXSINKCMD
+{
+ /** Unknown command, do not use. */
+ AUDMIXSINKCMD_UNKNOWN = 0,
+ /** Enables the sink. */
+ AUDMIXSINKCMD_ENABLE,
+ /** Disables the sink. */
+ AUDMIXSINKCMD_DISABLE,
+ /** Pauses the sink. */
+ AUDMIXSINKCMD_PAUSE,
+ /** Resumes the sink. */
+ AUDMIXSINKCMD_RESUME,
+ /** Tells the sink's streams to drop all (buffered) data immediately. */
+ AUDMIXSINKCMD_DROP,
+ /** Hack to blow the type up to 32-bit. */
+ AUDMIXSINKCMD_32BIT_HACK = 0x7fffffff
+} AUDMIXSINKCMD;
+
+/**
+ * Structure for keeping audio input sink specifics.
+ * Do not use directly. Instead, use AUDMIXSINK.
+ */
+typedef struct AUDMIXSINKIN
+{
+ /** The current recording source. Can be NULL if not set. */
+ PAUDMIXSTREAM pStreamRecSource;
+} AUDMIXSINKIN;
+
+/**
+ * Structure for keeping audio output sink specifics.
+ * Do not use directly. Instead, use AUDMIXSINK.
+ */
+typedef struct AUDMIXSINKOUT
+{
+} AUDMIXSINKOUT;
+
+/**
+ * Structure for maintaining an audio mixer sink.
+ */
+typedef struct AUDMIXSINK
+{
+ RTLISTNODE Node;
+ /** Pointer to mixer object this sink is bound to. */
+ PAUDIOMIXER pParent;
+ /** Name of this sink. */
+ char *pszName;
+ /** The sink direction, that is,
+ * if this sink handles input or output. */
+ AUDMIXSINKDIR enmDir;
+ /** The sink's critical section. */
+ RTCRITSECT CritSect;
+ /** This sink's mixing buffer, acting as
+ * a parent buffer for all streams this sink owns. */
+ PDMAUDIOMIXBUF MixBuf;
+ /** Union for input/output specifics. */
+ union
+ {
+ AUDMIXSINKIN In;
+ AUDMIXSINKOUT Out;
+ };
+ /** Sink status of type AUDMIXSINK_STS_XXX. */
+ AUDMIXSINKSTS fStatus;
+ /** The sink's PCM format. */
+ PDMAUDIOPCMPROPS PCMProps;
+ /** Number of streams assigned. */
+ uint8_t cStreams;
+ /** List of assigned streams.
+ * Note: All streams have the same PCM properties, so the
+ * mixer does not do any conversion. */
+ /** @todo Use something faster -- vector maybe? */
+ 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;
+#ifdef VBOX_AUDIO_MIXER_DEBUG
+ struct
+ {
+ PPDMAUDIOFILE pFile;
+ } Dbg;
+#endif
+} AUDMIXSINK, *PAUDMIXSINK;
+
+/**
+ * Audio mixer operation.
+ */
+typedef enum AUDMIXOP
+{
+ /** Invalid operation, do not use. */
+ AUDMIXOP_INVALID = 0,
+ /** Copy data from A to B, overwriting data in B. */
+ AUDMIXOP_COPY,
+ /** Blend data from A with (existing) data in B. */
+ AUDMIXOP_BLEND,
+ /** The usual 32-bit hack. */
+ AUDMIXOP_32BIT_HACK = 0x7fffffff
+} AUDMIXOP;
+
+/** No flags specified. */
+#define AUDMIXSTRMCTL_FLAG_NONE 0
+
+int AudioMixerCreate(const char *pszName, uint32_t uFlags, PAUDIOMIXER *ppMixer);
+int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink);
+void AudioMixerDestroy(PAUDIOMIXER pMixer);
+void AudioMixerInvalidate(PAUDIOMIXER pMixer);
+void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
+int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol);
+void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs);
+
+int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+int AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream);
+int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmCmd);
+void AudioMixerSinkDestroy(PAUDMIXSINK pSink);
+uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink);
+uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink);
+AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink);
+const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink);
+PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink);
+PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex);
+AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink);
+uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink);
+bool AudioMixerSinkIsActive(PAUDMIXSINK pSink);
+int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
+void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink);
+void AudioMixerSinkReset(PAUDMIXSINK pSink);
+void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps);
+int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps);
+int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
+int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol);
+int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
+int AudioMixerSinkUpdate(PAUDMIXSINK pSink);
+
+int AudioMixerStreamCtl(PAUDMIXSTREAM pStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl);
+void AudioMixerStreamDestroy(PAUDMIXSTREAM pStream);
+bool AudioMixerStreamIsActive(PAUDMIXSTREAM pStream);
+bool AudioMixerStreamIsValid(PAUDMIXSTREAM pStream);
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_AudioMixer_h */
+
diff --git a/src/VBox/Devices/Audio/DevHDA.cpp b/src/VBox/Devices/Audio/DevHDA.cpp
new file mode 100644
index 00000000..45679ac2
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHDA.cpp
@@ -0,0 +1,5414 @@
+/* $Id: DevHDA.cpp $ */
+/** @file
+ * DevHDA.cpp - VBox Intel HD Audio Controller.
+ *
+ * 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-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef DEBUG_bird
+# define RT_NO_STRICT /* I'm tried of this crap asserting on save and restore of Maverics guests. */
+#endif
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#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"
+
+#include "DevHDA.h"
+#include "DevHDACommon.h"
+
+#include "HDACodec.h"
+#include "HDAStream.h"
+#include "HDAStreamMap.h"
+#include "HDAStreamPeriod.h"
+
+#include "DrvAudio.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+//#define HDA_AS_PCI_EXPRESS
+
+/* Installs a DMA access handler (via PGM callback) to monitor
+ * HDA's DMA operations, that is, writing / reading audio stream data.
+ *
+ * !!! Note: Certain guests are *that* timing sensitive that when enabling !!!
+ * !!! such a handler will mess up audio completely (e.g. Windows 7). !!! */
+//#define HDA_USE_DMA_ACCESS_HANDLER
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+# include <VBox/vmm/pgm.h>
+#endif
+
+/* Uses the DMA access handler to read the written DMA audio (output) data.
+ * Only valid if HDA_USE_DMA_ACCESS_HANDLER is set.
+ *
+ * Also see the note / warning for HDA_USE_DMA_ACCESS_HANDLER. */
+//# define HDA_USE_DMA_ACCESS_HANDLER_WRITING
+
+/* Useful to debug the device' timing. */
+//#define HDA_DEBUG_TIMING
+
+/* To debug silence coming from the guest in form of audio gaps.
+ * Very crude implementation for now. */
+//#define HDA_DEBUG_SILENCE
+
+#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_pThis) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/**
+ * Acquires the HDA lock or returns.
+ */
+# define DEVHDA_LOCK_RETURN(a_pThis, a_rcBusy) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, a_rcBusy); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Acquires the HDA lock or returns.
+ */
+# define DEVHDA_LOCK_RETURN_VOID(a_pThis) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return; \
+ } \
+ } while (0)
+
+/**
+ * Releases the HDA lock.
+ */
+#define DEVHDA_UNLOCK(a_pThis) \
+ do { PDMCritSectLeave(&(a_pThis)->CritSect); } while (0)
+
+/**
+ * Acquires the TM lock and HDA lock, returns on failure.
+ */
+#define DEVHDA_LOCK_BOTH_RETURN_VOID(a_pThis, a_SD) \
+ do { \
+ int rcLock = TMTimerLock((a_pThis)->pTimer[a_SD], VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return; \
+ } \
+ rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ TMTimerUnlock((a_pThis)->pTimer[a_SD]); \
+ return; \
+ } \
+ } while (0)
+
+/**
+ * Acquires the TM lock and HDA lock, returns on failure.
+ */
+#define DEVHDA_LOCK_BOTH_RETURN(a_pThis, a_SD, a_rcBusy) \
+ do { \
+ int rcLock = TMTimerLock((a_pThis)->pTimer[a_SD], (a_rcBusy)); \
+ if (rcLock != VINF_SUCCESS) \
+ return rcLock; \
+ rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, (a_rcBusy)); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ TMTimerUnlock((a_pThis)->pTimer[a_SD]); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Releases the HDA lock and TM lock.
+ */
+#define DEVHDA_UNLOCK_BOTH(a_pThis, a_SD) \
+ do { \
+ PDMCritSectLeave(&(a_pThis)->CritSect); \
+ TMTimerUnlock((a_pThis)->pTimer[a_SD]); \
+ } 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;
+
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+/**
+ * Struct for keeping an HDA DMA access handler context.
+ */
+typedef struct HDADMAACCESSHANDLER
+{
+ /** Node for storing this handler in our list in HDASTREAMSTATE. */
+ RTLISTNODER3 Node;
+ /** Pointer to stream to which this access handler is assigned to. */
+ R3PTRTYPE(PHDASTREAM) pStream;
+ /** Access handler type handle. */
+ PGMPHYSHANDLERTYPE hAccessHandlerType;
+ /** First address this handler uses. */
+ RTGCPHYS GCPhysFirst;
+ /** Last address this handler uses. */
+ RTGCPHYS GCPhysLast;
+ /** Actual BDLE address to handle. */
+ RTGCPHYS BDLEAddr;
+ /** Actual BDLE buffer size to handle. */
+ RTGCPHYS BDLESize;
+ /** Whether the access handler has been registered or not. */
+ bool fRegistered;
+ uint8_t Padding[3];
+} HDADMAACCESSHANDLER, *PHDADMAACCESSHANDLER;
+#endif
+
+/**
+ * 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 HDA controller (state). */
+ R3PTRTYPE(PHDASTATE) pHDAState;
+ /** Driver flags. */
+ PDMAUDIODRVFLAGS fFlags;
+ uint8_t u32Padding0[2];
+ /** LUN to which this driver has been assigned. */
+ uint8_t uLUN;
+ /** Whether this driver is in an attached state or not. */
+ bool fAttached;
+ /** 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
+} HDADRIVER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+#ifdef IN_RING3
+static void hdaR3GCTLReset(PHDASTATE pThis);
+#endif
+
+/** @name Register read/write stubs.
+ * @{
+ */
+static int hdaRegReadUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t pu32Value);
+/** @} */
+
+/** @name Global register set read/write functions.
+ * @{
+ */
+static int hdaRegWriteGCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegReadLPIB(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegReadWALCLK(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteCORBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteCORBRP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteCORBCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteCORBSIZE(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteCORBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteRINTCNT(PHDASTATE pThis, uint32_t iReg, uint32_t pu32Value);
+static int hdaRegWriteRIRBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteRIRBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSTATESTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteIRS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegReadIRS(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteBase(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+/** @} */
+
+/** @name {IOB}SDn write functions.
+ * @{
+ */
+static int hdaRegWriteSDCBL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDLVI(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDFIFOW(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDFIFOS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDFMT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDBDPL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegWriteSDBDPU(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+/** @} */
+
+/** @name Generic register read/write functions.
+ * @{
+ */
+static int hdaRegReadU32(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteU32(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegReadU24(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+#ifdef IN_RING3
+static int hdaRegWriteU24(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+#endif
+static int hdaRegReadU16(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteU16(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+static int hdaRegReadU8(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+static int hdaRegWriteU8(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+/** @} */
+
+/** @name HDA device functions.
+ * @{
+ */
+#ifdef IN_RING3
+static int hdaR3AddStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg);
+static int hdaR3RemoveStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg);
+# ifdef HDA_USE_DMA_ACCESS_HANDLER
+static DECLCALLBACK(VBOXSTRICTRC) hdaR3DMAAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys,
+ void *pvBuf, size_t cbBuf,
+ PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser);
+# endif
+#endif /* IN_RING3 */
+/** @} */
+
+/** @name HDA mixer functions.
+ * @{
+ */
+#ifdef IN_RING3
+static int hdaR3MixerAddDrvStream(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv);
+#endif
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** No register description (RD) flags defined. */
+#define HDA_RD_FLAG_NONE 0
+/** Writes to SD are allowed while RUN bit is set. */
+#define HDA_RD_FLAG_SD_WRITE_RUN RT_BIT(0)
+
+/** 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) */ \
+ { offset, 0x00003, 0x00FF001F, 0x00F0001F, HDA_RD_FLAG_SD_WRITE_RUN, hdaRegReadU24 , hdaRegWriteSDCTL , HDA_REG_IDX_STRM(name, CTL) , #name " Stream Descriptor Control" }, \
+ /* Offset 0x83 (SD0) */ \
+ { offset + 0x3, 0x00001, 0x0000003C, 0x0000001C, HDA_RD_FLAG_SD_WRITE_RUN, hdaRegReadU8 , hdaRegWriteSDSTS , HDA_REG_IDX_STRM(name, STS) , #name " Status" }, \
+ /* Offset 0x84 (SD0) */ \
+ { offset + 0x4, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadLPIB, hdaRegWriteU32 , HDA_REG_IDX_STRM(name, LPIB) , #name " Link Position In Buffer" }, \
+ /* Offset 0x88 (SD0) */ \
+ { offset + 0x8, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDCBL , HDA_REG_IDX_STRM(name, CBL) , #name " Cyclic Buffer Length" }, \
+ /* Offset 0x8C (SD0) */ \
+ { offset + 0xC, 0x00002, 0x0000FFFF, 0x0000FFFF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDLVI , HDA_REG_IDX_STRM(name, LVI) , #name " Last Valid Index" }, \
+ /* Reserved: FIFO Watermark. ** @todo Document this! */ \
+ { offset + 0xE, 0x00002, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOW, HDA_REG_IDX_STRM(name, FIFOW), #name " FIFO Watermark" }, \
+ /* Offset 0x90 (SD0) */ \
+ { offset + 0x10, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFIFOS, HDA_REG_IDX_STRM(name, FIFOS), #name " FIFO Size" }, \
+ /* Offset 0x92 (SD0) */ \
+ { offset + 0x12, 0x00002, 0x00007F7F, 0x00007F7F, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteSDFMT , HDA_REG_IDX_STRM(name, FMT) , #name " Stream Format" }, \
+ /* Reserved: 0x94 - 0x98. */ \
+ /* Offset 0x98 (SD0) */ \
+ { offset + 0x18, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDBDPL , HDA_REG_IDX_STRM(name, BDPL) , #name " Buffer Descriptor List Pointer-Lower Base Address" }, \
+ /* Offset 0x9C (SD0) */ \
+ { offset + 0x1C, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteSDBDPU , HDA_REG_IDX_STRM(name, BDPU) , #name " 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)
+
+/* See 302349 p 6.2. */
+const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS] =
+{
+ /* offset size read mask write mask flags read callback write callback index + abbrev */
+ /*------- ------- ---------- ---------- ----------------- ---------------- ------------------- ------------------------ */
+ { 0x00000, 0x00002, 0x0000FFFB, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(GCAP) }, /* Global Capabilities */
+ { 0x00002, 0x00001, 0x000000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMIN) }, /* Minor Version */
+ { 0x00003, 0x00001, 0x000000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(VMAJ) }, /* Major Version */
+ { 0x00004, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTPAY) }, /* Output Payload Capabilities */
+ { 0x00006, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INPAY) }, /* Input Payload Capabilities */
+ { 0x00008, 0x00004, 0x00000103, 0x00000103, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteGCTL , HDA_REG_IDX(GCTL) }, /* Global Control */
+ { 0x0000c, 0x00002, 0x00007FFF, 0x00007FFF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(WAKEEN) }, /* Wake Enable */
+ { 0x0000e, 0x00002, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteSTATESTS, HDA_REG_IDX(STATESTS) }, /* State Change Status */
+ { 0x00010, 0x00002, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadUnimpl, hdaRegWriteUnimpl , HDA_REG_IDX(GSTS) }, /* Global Status */
+ { 0x00018, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteU16 , HDA_REG_IDX(OUTSTRMPAY) }, /* Output Stream Payload Capability */
+ { 0x0001A, 0x00002, 0x0000FFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteUnimpl , HDA_REG_IDX(INSTRMPAY) }, /* Input Stream Payload Capability */
+ { 0x00020, 0x00004, 0xC00000FF, 0xC00000FF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(INTCTL) }, /* Interrupt Control */
+ { 0x00024, 0x00004, 0xC00000FF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(INTSTS) }, /* Interrupt Status */
+ { 0x00030, 0x00004, 0xFFFFFFFF, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadWALCLK, hdaRegWriteUnimpl , HDA_REG_IDX_NOMEM(WALCLK) }, /* Wall Clock Counter */
+ { 0x00034, 0x00004, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(SSYNC) }, /* Stream Synchronization */
+ { 0x00040, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBLBASE) }, /* CORB Lower Base Address */
+ { 0x00044, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(CORBUBASE) }, /* CORB Upper Base Address */
+ { 0x00048, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteCORBWP , HDA_REG_IDX(CORBWP) }, /* CORB Write Pointer */
+ { 0x0004A, 0x00002, 0x000080FF, 0x00008000, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteCORBRP , HDA_REG_IDX(CORBRP) }, /* CORB Read Pointer */
+ { 0x0004C, 0x00001, 0x00000003, 0x00000003, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBCTL , HDA_REG_IDX(CORBCTL) }, /* CORB Control */
+ { 0x0004D, 0x00001, 0x00000001, 0x00000001, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBSTS , HDA_REG_IDX(CORBSTS) }, /* CORB Status */
+ { 0x0004E, 0x00001, 0x000000F3, 0x00000003, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteCORBSIZE, HDA_REG_IDX(CORBSIZE) }, /* CORB Size */
+ { 0x00050, 0x00004, 0xFFFFFF80, 0xFFFFFF80, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBLBASE) }, /* RIRB Lower Base Address */
+ { 0x00054, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(RIRBUBASE) }, /* RIRB Upper Base Address */
+ { 0x00058, 0x00002, 0x000000FF, 0x00008000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteRIRBWP , HDA_REG_IDX(RIRBWP) }, /* RIRB Write Pointer */
+ { 0x0005A, 0x00002, 0x000000FF, 0x000000FF, HDA_RD_FLAG_NONE, hdaRegReadU16 , hdaRegWriteRINTCNT , HDA_REG_IDX(RINTCNT) }, /* Response Interrupt Count */
+ { 0x0005C, 0x00001, 0x00000007, 0x00000007, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteU8 , HDA_REG_IDX(RIRBCTL) }, /* RIRB Control */
+ { 0x0005D, 0x00001, 0x00000005, 0x00000005, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteRIRBSTS , HDA_REG_IDX(RIRBSTS) }, /* RIRB Status */
+ { 0x0005E, 0x00001, 0x000000F3, 0x00000000, HDA_RD_FLAG_NONE, hdaRegReadU8 , hdaRegWriteUnimpl , HDA_REG_IDX(RIRBSIZE) }, /* RIRB Size */
+ { 0x00060, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteU32 , HDA_REG_IDX(IC) }, /* Immediate Command */
+ { 0x00064, 0x00004, 0x00000000, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteUnimpl , HDA_REG_IDX(IR) }, /* Immediate Response */
+ { 0x00068, 0x00002, 0x00000002, 0x00000002, HDA_RD_FLAG_NONE, hdaRegReadIRS , hdaRegWriteIRS , HDA_REG_IDX(IRS) }, /* Immediate Command Status */
+ { 0x00070, 0x00004, 0xFFFFFFFF, 0xFFFFFF81, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(DPLBASE) }, /* DMA Position Lower Base */
+ { 0x00074, 0x00004, 0xFFFFFFFF, 0xFFFFFFFF, HDA_RD_FLAG_NONE, hdaRegReadU32 , hdaRegWriteBase , HDA_REG_IDX(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)
+};
+
+const HDAREGALIAS g_aHdaRegAliases[] =
+{
+ { 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()
+};
+
+/** HDABDLESTATE field descriptors for the v6+ saved state. */
+static SSMFIELD const g_aSSMBDLEStateFields6[] =
+{
+ SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex),
+ SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW),
+ SSMFIELD_ENTRY_OLD(FIFO, HDA_FIFO_MAX), /* Deprecated; now is handled in the stream's circular buffer. */
+ SSMFIELD_ENTRY(HDABDLESTATE, u32BufOff),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDABDLESTATE field descriptors for the v7 saved state. */
+static SSMFIELD const g_aSSMBDLEStateFields7[] =
+{
+ SSMFIELD_ENTRY(HDABDLESTATE, u32BDLIndex),
+ SSMFIELD_ENTRY(HDABDLESTATE, cbBelowFIFOW),
+ SSMFIELD_ENTRY(HDABDLESTATE, 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(HDASTREAMSTATE, uCurBDLE),
+ 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, uCurBDLE),
+ SSMFIELD_ENTRY(HDASTREAMSTATE, fInReset),
+ SSMFIELD_ENTRY(HDASTREAMSTATE, tsTransferNext),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/** HDASTREAMPERIOD field descriptors for the v7 saved state. */
+static SSMFIELD const g_aSSMStreamPeriodFields7[] =
+{
+ SSMFIELD_ENTRY(HDASTREAMPERIOD, u64StartWalClk),
+ SSMFIELD_ENTRY(HDASTREAMPERIOD, u64ElapsedWalClk),
+ SSMFIELD_ENTRY(HDASTREAMPERIOD, framesTransferred),
+ SSMFIELD_ENTRY(HDASTREAMPERIOD, cIntPending),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/**
+ * 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)
+};
+
+#endif /* IN_RING3 */
+
+
+
+/**
+ * Retrieves the number of bytes of a FIFOW register.
+ *
+ * @return Number of bytes of a given FIFOW register.
+ */
+DECLINLINE(uint8_t) hdaSDFIFOWToBytes(uint32_t u32RegFIFOW)
+{
+ uint32_t cb;
+ switch (u32RegFIFOW)
+ {
+ case HDA_SDFIFOW_8B: cb = 8; break;
+ case HDA_SDFIFOW_16B: cb = 16; break;
+ case HDA_SDFIFOW_32B: cb = 32; break;
+ default: cb = 0; break;
+ }
+
+ Assert(RT_IS_POWER_OF_TWO(cb));
+ return cb;
+}
+
+#ifdef IN_RING3
+/**
+ * Reschedules pending interrupts for all audio streams which have complete
+ * audio periods but did not have the chance to issue their (pending) interrupts yet.
+ *
+ * @param pThis The HDA device state.
+ */
+static void hdaR3ReschedulePendingInterrupts(PHDASTATE pThis)
+{
+ bool fInterrupt = false;
+
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i)
+ {
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, i);
+ if (!pStream)
+ continue;
+
+ if ( hdaR3StreamPeriodIsComplete (&pStream->State.Period)
+ && hdaR3StreamPeriodNeedsInterrupt(&pStream->State.Period)
+ && hdaR3WalClkSet(pThis, hdaR3StreamPeriodGetAbsElapsedWalClk(&pStream->State.Period), false /* fForce */))
+ {
+ fInterrupt = true;
+ break;
+ }
+ }
+
+ LogFunc(("fInterrupt=%RTbool\n", fInterrupt));
+
+# ifndef LOG_ENABLED
+ hdaProcessInterrupt(pThis);
+# else
+ hdaProcessInterrupt(pThis, __FUNCTION__);
+# endif
+}
+#endif /* IN_RING3 */
+
+/**
+ * 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].offset < 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].offset)
+ {
+ if (idxLow == idxMiddle)
+ break;
+ idxEnd = idxMiddle;
+ }
+ else if (offReg > g_aHdaRegMap[idxMiddle].offset)
+ {
+ idxLow = idxMiddle + 1;
+ if (idxLow >= idxEnd)
+ break;
+ }
+ else
+ return idxMiddle;
+ }
+
+#ifdef RT_STRICT
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ Assert(g_aHdaRegMap[i].offset != 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.
+ */
+static int hdaR3RegLookupWithin(uint32_t offReg)
+{
+ /*
+ * Aliases.
+ */
+ if (offReg >= g_aHdaRegAliases[0].offReg)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegAliases); i++)
+ {
+ uint32_t off = offReg - g_aHdaRegAliases[i].offReg;
+ if (off < 4 && off < g_aHdaRegMap[g_aHdaRegAliases[i].idxAlias].size)
+ return g_aHdaRegAliases[i].idxAlias;
+ }
+ Assert(g_aHdaRegMap[RT_ELEMENTS(g_aHdaRegMap) - 1].offset < offReg);
+ 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].offset)
+ {
+ if (idxLow == idxMiddle)
+ break;
+ idxEnd = idxMiddle;
+ }
+ else if (offReg >= g_aHdaRegMap[idxMiddle].offset + g_aHdaRegMap[idxMiddle].size)
+ {
+ idxLow = idxMiddle + 1;
+ if (idxLow >= idxEnd)
+ break;
+ }
+ else
+ return idxMiddle;
+ }
+
+# ifdef RT_STRICT
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aHdaRegMap); i++)
+ Assert(offReg - g_aHdaRegMap[i].offset >= g_aHdaRegMap[i].size);
+# endif
+ return -1;
+}
+
+
+/**
+ * Synchronizes the CORB / RIRB buffers between internal <-> device state.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA 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(PHDASTATE pThis, bool fLocal)
+{
+ int rc = VINF_SUCCESS;
+ if (fLocal)
+ {
+ if (pThis->u64CORBBase)
+ {
+ AssertPtr(pThis->pu32CorbBuf);
+ Assert(pThis->cbCorbBuf);
+
+/** @todo r=bird: An explanation is required why PDMDevHlpPhysRead is used with
+ * the CORB and PDMDevHlpPCIPhysWrite with RIRB below. There are
+ * similar unexplained inconsistencies in DevHDACommon.cpp. */
+ rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pThis->u64CORBBase, pThis->pu32CorbBuf, pThis->cbCorbBuf);
+ Log(("hdaR3CmdSync/CORB: read %RGp LB %#x (%Rrc)\n", pThis->u64CORBBase, pThis->cbCorbBuf, rc));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ if (pThis->u64RIRBBase)
+ {
+ AssertPtr(pThis->pu64RirbBuf);
+ Assert(pThis->cbRirbBuf);
+
+ rc = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), pThis->u64RIRBBase, pThis->pu64RirbBuf, pThis->cbRirbBuf);
+ Log(("hdaR3CmdSync/RIRB: phys read %RGp LB %#x (%Rrc)\n", pThis->u64RIRBBase, pThis->pu64RirbBuf, 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 verb dispatcher.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA state.
+ */
+static int hdaR3CORBCmdProcess(PHDASTATE pThis)
+{
+ uint8_t corbRp = HDA_REG(pThis, CORBRP);
+ uint8_t corbWp = HDA_REG(pThis, CORBWP);
+ uint8_t rirbWp = HDA_REG(pThis, RIRBWP);
+
+ Log3Func(("CORB(RP:%x, WP:%x) RIRBWP:%x\n", corbRp, corbWp, 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(pThis, true /* Sync from guest */);
+ AssertRCReturn(rc, rc);
+
+ uint16_t cIntCnt = HDA_REG(pThis, RINTCNT) & 0xff;
+
+ if (!cIntCnt) /* 0 means 256 interrupts. */
+ cIntCnt = HDA_MAX_RINTCNT;
+
+ Log3Func(("START CORB(RP:%x, WP:%x) RIRBWP:%x, RINTCNT:%RU8/%RU8\n",
+ corbRp, corbWp, rirbWp, pThis->u16RespIntCnt, cIntCnt));
+
+ while (corbRp != corbWp)
+ {
+ corbRp = (corbRp + 1) % (pThis->cbCorbBuf / HDA_CORB_ELEMENT_SIZE); /* Advance +1 as the first command(s) are at CORBWP + 1. */
+
+ uint32_t uCmd = pThis->pu32CorbBuf[corbRp];
+ uint64_t uResp = 0;
+
+ rc = pThis->pCodec->pfnLookup(pThis->pCodec, HDA_CODEC_CMD(uCmd, 0 /* Codec index */), &uResp);
+ if (RT_FAILURE(rc))
+ LogFunc(("Codec lookup failed with rc=%Rrc\n", rc));
+
+ Log3Func(("Codec verb %08x -> response %016lx\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 CORB/RIRB syncing to guest required in that case? */
+ return rc;
+ }
+
+ rirbWp = (rirbWp + 1) % HDA_RIRB_SIZE;
+
+ pThis->pu64RirbBuf[rirbWp] = uResp;
+
+ pThis->u16RespIntCnt++;
+
+ bool fSendInterrupt = false;
+
+ 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;
+
+# ifndef LOG_ENABLED
+ rc = hdaProcessInterrupt(pThis);
+# else
+ rc = hdaProcessInterrupt(pThis, __FUNCTION__);
+# endif
+ }
+ }
+ }
+
+ 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;
+
+ rc = hdaR3CmdSync(pThis, false /* Sync to guest */);
+ AssertRCReturn(rc, rc);
+
+ if (RT_FAILURE(rc))
+ AssertRCReturn(rc, rc);
+
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+/* Register access handlers. */
+
+static int hdaRegReadUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(iReg);
+ *pu32Value = 0;
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteUnimpl(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value);
+ return VINF_SUCCESS;
+}
+
+/* U8 */
+static int hdaRegReadU8(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffffff00) == 0);
+ return hdaRegReadU32(pThis, iReg, pu32Value);
+}
+
+static int hdaRegWriteU8(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xffffff00) == 0);
+ return hdaRegWriteU32(pThis, iReg, u32Value);
+}
+
+/* U16 */
+static int hdaRegReadU16(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xffff0000) == 0);
+ return hdaRegReadU32(pThis, iReg, pu32Value);
+}
+
+static int hdaRegWriteU16(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xffff0000) == 0);
+ return hdaRegWriteU32(pThis, iReg, u32Value);
+}
+
+/* U24 */
+static int hdaRegReadU24(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Assert(((pThis->au32Regs[g_aHdaRegMap[iReg].mem_idx] & g_aHdaRegMap[iReg].readable) & 0xff000000) == 0);
+ return hdaRegReadU32(pThis, iReg, pu32Value);
+}
+
+#ifdef IN_RING3
+static int hdaRegWriteU24(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ Assert((u32Value & 0xff000000) == 0);
+ return hdaRegWriteU32(pThis, iReg, u32Value);
+}
+#endif
+
+/* U32 */
+static int hdaRegReadU32(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx;
+
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ);
+
+ *pu32Value = pThis->au32Regs[iRegMem] & g_aHdaRegMap[iReg].readable;
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteU32(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx;
+
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ pThis->au32Regs[iRegMem] = (u32Value & g_aHdaRegMap[iReg].writable)
+ | (pThis->au32Regs[iRegMem] & ~g_aHdaRegMap[iReg].writable);
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteGCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF_PV(iReg);
+#ifdef IN_RING3
+ DEVHDA_LOCK(pThis);
+#else
+ if (!(u32Value & HDA_GCTL_CRST))
+ return VINF_IOM_R3_MMIO_WRITE;
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+#endif
+
+ 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(pThis);
+#else
+ AssertFailedReturnStmt(DEVHDA_UNLOCK(pThis), 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). */
+ }
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteSTATESTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ 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. */
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegReadLPIB(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ);
+
+ const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LPIB, iReg);
+ uint32_t u32LPIB = HDA_STREAM_REG(pThis, LPIB, uSD);
+#ifdef LOG_ENABLED
+ const uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, uSD);
+ LogFlowFunc(("[SD%RU8] LPIB=%RU32, CBL=%RU32\n", uSD, u32LPIB, u32CBL));
+#endif
+
+ *pu32Value = u32LPIB;
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+/**
+ * Returns the current maximum value the wall clock counter can be set to.
+ * This maximum value depends on all currently handled HDA streams and their own current timing.
+ *
+ * @return Current maximum value the wall clock counter can be set to.
+ * @param pThis HDA state.
+ *
+ * @remark Does not actually set the wall clock counter.
+ */
+static uint64_t hdaR3WalClkGetMax(PHDASTATE pThis)
+{
+ const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk);
+ const uint64_t u64FrontAbsWalClk = pThis->SinkFront.pStream
+ ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkFront.pStream->State.Period) : 0;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+# error "Implement me!"
+# endif
+ const uint64_t u64LineInAbsWalClk = pThis->SinkLineIn.pStream
+ ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkLineIn.pStream->State.Period) : 0;
+# ifdef VBOX_WITH_HDA_MIC_IN
+ const uint64_t u64MicInAbsWalClk = pThis->SinkMicIn.pStream
+ ? hdaR3StreamPeriodGetAbsElapsedWalClk(&pThis->SinkMicIn.pStream->State.Period) : 0;
+# endif
+
+ uint64_t u64WalClkNew = RT_MAX(u64WalClkCur, u64FrontAbsWalClk);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+# error "Implement me!"
+# endif
+ u64WalClkNew = RT_MAX(u64WalClkNew, u64LineInAbsWalClk);
+# ifdef VBOX_WITH_HDA_MIC_IN
+ u64WalClkNew = RT_MAX(u64WalClkNew, u64MicInAbsWalClk);
+# endif
+
+ Log3Func(("%RU64 -> Front=%RU64, LineIn=%RU64 -> %RU64\n",
+ u64WalClkCur, u64FrontAbsWalClk, u64LineInAbsWalClk, u64WalClkNew));
+
+ return u64WalClkNew;
+}
+#endif /* IN_RING3 */
+
+static int hdaRegReadWALCLK(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+#ifdef IN_RING3
+ RT_NOREF(iReg);
+
+ DEVHDA_LOCK(pThis);
+
+ *pu32Value = RT_LO_U32(ASMAtomicReadU64(&pThis->u64WalClk));
+
+ Log3Func(("%RU32 (max @ %RU64)\n",*pu32Value, hdaR3WalClkGetMax(pThis)));
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+#else
+ RT_NOREF(pThis, iReg, pu32Value);
+ return VINF_IOM_R3_MMIO_READ;
+#endif
+}
+
+static int hdaRegWriteCORBRP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ if (u32Value & HDA_CORBRP_RST)
+ {
+ /* Do a CORB reset. */
+ if (pThis->cbCorbBuf)
+ {
+#ifdef IN_RING3
+ Assert(pThis->pu32CorbBuf);
+ RT_BZERO((void *)pThis->pu32CorbBuf, pThis->cbCorbBuf);
+#else
+ DEVHDA_UNLOCK(pThis);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+
+ 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. */
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteCORBCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ int rc = hdaRegWriteU8(pThis, iReg, u32Value);
+ AssertRC(rc);
+
+ if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Start DMA engine. */
+ {
+ rc = hdaR3CORBCmdProcess(pThis);
+ }
+ else
+ LogFunc(("CORB DMA not running, skipping\n"));
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+#else
+ RT_NOREF(pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+}
+
+static int hdaRegWriteCORBSIZE(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ RT_NOREF(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ 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"));
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+ }
+
+ u32Value = (u32Value & HDA_CORBSIZE_SZ);
+
+ uint16_t cEntries = HDA_CORB_SIZE; /* Set default. */
+
+ 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. */
+ /* Use default size. */
+ break;
+
+ default:
+ LogRel(("HDA: Guest tried to set an invalid CORB size (0x%x), keeping default\n", u32Value));
+ u32Value = 2;
+ /* Use default size. */
+ break;
+ }
+
+ uint32_t cbCorbBuf = cEntries * HDA_CORB_ELEMENT_SIZE;
+ Assert(cbCorbBuf <= HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE); /* Paranoia. */
+
+ if (cbCorbBuf != pThis->cbCorbBuf)
+ {
+ RT_BZERO(pThis->pu32CorbBuf, HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE); /* 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;
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+#else
+ RT_NOREF(pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+}
+
+static int hdaRegWriteCORBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF_PV(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ uint32_t v = HDA_REG(pThis, CORBSTS);
+ HDA_REG(pThis, CORBSTS) &= ~(v & u32Value);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteCORBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ int rc = hdaRegWriteU16(pThis, iReg, u32Value);
+ AssertRCSuccess(rc);
+
+ rc = hdaR3CORBCmdProcess(pThis);
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+#else
+ RT_NOREF(pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+}
+
+static int hdaRegWriteSDCBL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ int rc = hdaRegWriteU32(pThis, iReg, u32Value);
+ AssertRCSuccess(rc);
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+}
+
+static int hdaRegWriteSDCTL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ /* Get the stream descriptor. */
+ const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, CTL, iReg);
+
+ DEVHDA_LOCK_BOTH_RETURN(pThis, uSD, VINF_IOM_R3_MMIO_WRITE);
+
+ /*
+ * Some guests write too much (that is, 32-bit with the top 8 bit being junk)
+ * instead of 24-bit required for SDCTL. So just mask this here to be safe.
+ */
+ u32Value &= 0x00ffffff;
+
+ const bool fRun = RT_BOOL(u32Value & HDA_SDCTL_RUN);
+ const bool fInRun = RT_BOOL(HDA_REG_IND(pThis, iReg) & HDA_SDCTL_RUN);
+
+ const bool fReset = RT_BOOL(u32Value & HDA_SDCTL_SRST);
+ 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));*/
+
+ /*
+ * 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.
+ */
+ uint8_t uTag = (u32Value >> HDA_SDCTL_NUM_SHIFT) & HDA_SDCTL_NUM_MASK;
+ if (uTag > HDA_MAX_TAGS)
+ {
+ LogFunc(("[SD%RU8] Warning: Invalid stream tag %RU8 specified!\n", uSD, uTag));
+
+ int rc = hdaRegWriteU24(pThis, iReg, u32Value);
+ DEVHDA_UNLOCK_BOTH(pThis, uSD);
+ return rc;
+ }
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD);
+ AssertPtr(pStream);
+
+ if (fInReset)
+ {
+ Assert(!fReset);
+ Assert(!fInRun && !fRun);
+
+ /* Exit reset state. */
+ ASMAtomicXchgBool(&pStream->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(!fInRun && !fRun);
+
+ LogFunc(("[SD%RU8] Reset enter\n", uSD));
+
+ hdaR3StreamLock(pStream);
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ hdaR3StreamAsyncIOLock(pStream);
+# endif
+ /* Make sure to remove the run bit before doing the actual stream reset. */
+ HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_RUN;
+
+ hdaR3StreamReset(pThis, pStream, pStream->u8SD);
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ hdaR3StreamAsyncIOUnlock(pStream);
+# endif
+ hdaR3StreamUnlock(pStream);
+ }
+ else
+ {
+ /*
+ * We enter here to change DMA states only.
+ */
+ if (fInRun != fRun)
+ {
+ Assert(!fReset && !fInReset);
+ LogFunc(("[SD%RU8] State changed (fRun=%RTbool)\n", uSD, fRun));
+
+ hdaR3StreamLock(pStream);
+
+ int rc2;
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ if (fRun)
+ rc2 = hdaR3StreamAsyncIOCreate(pStream);
+
+ hdaR3StreamAsyncIOLock(pStream);
+# endif
+ 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));
+ }
+
+ PHDATAG pTag = &pThis->aTags[uTag];
+ AssertPtr(pTag);
+
+ LogFunc(("[SD%RU8] Using stream tag=%RU8\n", uSD, uTag));
+
+ /* Assign new values. */
+ pTag->uTag = uTag;
+ pTag->pStream = hdaGetStreamFromSD(pThis, uSD);
+
+# ifdef LOG_ENABLED
+ PDMAUDIOPCMPROPS Props;
+ rc2 = hdaR3SDFMTToPCMProps(HDA_STREAM_REG(pThis, FMT, pStream->u8SD), &Props);
+ AssertRC(rc2);
+ LogFunc(("[SD%RU8] %RU32Hz, %RU8bit, %RU8 channel(s)\n",
+ pStream->u8SD, Props.uHz, Props.cBytes * 8 /* Bit */, Props.cChannels));
+# endif
+ /* (Re-)initialize the stream with current values. */
+ rc2 = hdaR3StreamInit(pStream, pStream->u8SD);
+ 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. */
+ && rc2 != VINF_NO_CHANGE)
+ {
+ /* Remove the old stream from the device setup. */
+ rc2 = hdaR3RemoveStream(pThis, &pStream->State.Cfg);
+ AssertRC(rc2);
+
+ /* Add the stream to the device setup. */
+ rc2 = hdaR3AddStream(pThis, &pStream->State.Cfg);
+ AssertRC(rc2);
+ }
+ }
+
+ /* Enable/disable the stream. */
+ rc2 = hdaR3StreamEnable(pStream, fRun /* fEnable */);
+ AssertRC(rc2);
+
+ if (fRun)
+ {
+ /* Keep track of running streams. */
+ pThis->cStreamsActive++;
+
+ /* (Re-)init the stream's period. */
+ hdaR3StreamPeriodInit(&pStream->State.Period,
+ pStream->u8SD, pStream->u16LVI, pStream->u32CBL, &pStream->State.Cfg);
+
+ /* Begin a new period for this stream. */
+ rc2 = hdaR3StreamPeriodBegin(&pStream->State.Period, hdaWalClkGetCurrent(pThis)/* Use current wall clock time */);
+ AssertRC(rc2);
+
+ rc2 = hdaR3TimerSet(pThis, pStream, TMTimerGet(pThis->pTimer[pStream->u8SD]) + pStream->State.cTransferTicks, false /* fForce */);
+ AssertRC(rc2);
+ }
+ else
+ {
+ /* Keep track of running streams. */
+ Assert(pThis->cStreamsActive);
+ if (pThis->cStreamsActive)
+ pThis->cStreamsActive--;
+
+ /* Make sure to (re-)schedule outstanding (delayed) interrupts. */
+ hdaR3ReschedulePendingInterrupts(pThis);
+
+ /* Reset the period. */
+ hdaR3StreamPeriodReset(&pStream->State.Period);
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ hdaR3StreamAsyncIOUnlock(pStream);
+# endif
+ /* Make sure to leave the lock before (eventually) starting the timer. */
+ hdaR3StreamUnlock(pStream);
+ }
+ }
+
+ int rc2 = hdaRegWriteU24(pThis, iReg, u32Value);
+ AssertRC(rc2);
+
+ DEVHDA_UNLOCK_BOTH(pThis, uSD);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+#else /* !IN_RING3 */
+ RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* IN_RING3 */
+}
+
+static int hdaRegWriteSDSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+#ifdef IN_RING3
+ const uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, STS, iReg);
+
+ DEVHDA_LOCK_BOTH_RETURN(pThis, uSD, VINF_IOM_R3_MMIO_WRITE);
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD);
+ if (!pStream)
+ {
+ AssertMsgFailed(("[SD%RU8] Warning: Writing SDSTS on non-attached stream (0x%x)\n",
+ HDA_SD_NUM_FROM_REG(pThis, STS, iReg), u32Value));
+
+ int rc = hdaRegWriteU16(pThis, iReg, u32Value);
+ DEVHDA_UNLOCK_BOTH(pThis, uSD);
+ return rc;
+ }
+
+ hdaR3StreamLock(pStream);
+
+ 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);
+
+ /* Some guests tend to write SDnSTS even if the stream is not running.
+ * So make sure to check if the RUN bit is set first. */
+ const bool fRunning = pStream->State.fRunning;
+
+ Log3Func(("[SD%RU8] fRunning=%RTbool %R[sdsts]\n", pStream->u8SD, fRunning, v));
+
+ PHDASTREAMPERIOD pPeriod = &pStream->State.Period;
+
+ if (hdaR3StreamPeriodLock(pPeriod))
+ {
+ const bool fNeedsInterrupt = hdaR3StreamPeriodNeedsInterrupt(pPeriod);
+ if (fNeedsInterrupt)
+ hdaR3StreamPeriodReleaseInterrupt(pPeriod);
+
+ if (hdaR3StreamPeriodIsComplete(pPeriod))
+ {
+ /* Make sure to try to update the WALCLK register if a period is complete.
+ * Use the maximum WALCLK value all (active) streams agree to. */
+ const uint64_t uWalClkMax = hdaR3WalClkGetMax(pThis);
+ if (uWalClkMax > hdaWalClkGetCurrent(pThis))
+ hdaR3WalClkSet(pThis, uWalClkMax, false /* fForce */);
+
+ hdaR3StreamPeriodEnd(pPeriod);
+
+ if (fRunning)
+ hdaR3StreamPeriodBegin(pPeriod, hdaWalClkGetCurrent(pThis) /* Use current wall clock time */);
+ }
+
+ hdaR3StreamPeriodUnlock(pPeriod); /* Unlock before processing interrupt. */
+ }
+
+# ifndef LOG_ENABLED
+ hdaProcessInterrupt(pThis);
+# else
+ hdaProcessInterrupt(pThis, __FUNCTION__);
+# endif
+
+ const uint64_t tsNow = TMTimerGet(pThis->pTimer[uSD]);
+ Assert(tsNow >= pStream->State.tsTransferLast);
+
+ const uint64_t cTicksElapsed = tsNow - pStream->State.tsTransferLast;
+# ifdef LOG_ENABLED
+ const uint64_t cTicksTransferred = pStream->State.cbTransferProcessed * pStream->State.cTicksPerByte;
+# endif
+
+ uint64_t cTicksToNext = pStream->State.cTransferTicks;
+ if (cTicksToNext) /* Only do any calculations if the stream currently is set up for transfers. */
+ {
+ Log3Func(("[SD%RU8] cTicksElapsed=%RU64, cTicksTransferred=%RU64, cTicksToNext=%RU64\n",
+ pStream->u8SD, cTicksElapsed, cTicksTransferred, cTicksToNext));
+
+ Log3Func(("[SD%RU8] cbTransferProcessed=%RU32, cbTransferChunk=%RU32, cbTransferSize=%RU32\n",
+ pStream->u8SD, pStream->State.cbTransferProcessed, pStream->State.cbTransferChunk, pStream->State.cbTransferSize));
+
+ if (cTicksElapsed <= cTicksToNext)
+ {
+ cTicksToNext = cTicksToNext - cTicksElapsed;
+ }
+ else /* Catch up. */
+ {
+ Log3Func(("[SD%RU8] Warning: Lagging behind (%RU64 ticks elapsed, maximum allowed is %RU64)\n",
+ pStream->u8SD, cTicksElapsed, cTicksToNext));
+
+ LogRelMax2(64, ("HDA: Stream #%RU8 interrupt lagging behind (expected %uus, got %uus), trying to catch up ...\n",
+ pStream->u8SD,
+ (TMTimerGetFreq(pThis->pTimer[pStream->u8SD]) / pThis->uTimerHz) / 1000,(tsNow - pStream->State.tsTransferLast) / 1000));
+
+ cTicksToNext = 0;
+ }
+
+ Log3Func(("[SD%RU8] -> cTicksToNext=%RU64\n", pStream->u8SD, cTicksToNext));
+
+ /* Reset processed data counter. */
+ pStream->State.cbTransferProcessed = 0;
+ pStream->State.tsTransferNext = tsNow + cTicksToNext;
+
+ /* Only re-arm the timer if there were pending transfer interrupts left
+ * -- it could happen that we land in here if a guest writes to SDnSTS
+ * unconditionally. */
+ if (pStream->State.cTransferPendingInterrupts)
+ {
+ pStream->State.cTransferPendingInterrupts--;
+
+ /* Re-arm the timer. */
+ LogFunc(("Timer set SD%RU8\n", pStream->u8SD));
+ hdaR3TimerSet(pThis, pStream, tsNow + cTicksToNext, false /* fForce */);
+ }
+ }
+
+ hdaR3StreamUnlock(pStream);
+
+ DEVHDA_UNLOCK_BOTH(pThis, uSD);
+ return VINF_SUCCESS;
+#else /* IN_RING3 */
+ RT_NOREF(pThis, iReg, u32Value);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+static int hdaRegWriteSDLVI(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+ uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, LVI, iReg);
+
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT)
+ {
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD);
+
+ /* Try registering the DMA handlers.
+ * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */
+ if ( pStream
+ && hdaR3StreamRegisterDMAHandlers(pThis, pStream))
+ {
+ LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD));
+ }
+ }
+#endif
+
+ int rc2 = hdaRegWriteU16(pThis, iReg, u32Value);
+ AssertRC(rc2);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+}
+
+static int hdaRegWriteSDFIFOW(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg);
+
+ if (hdaGetDirFromSD(uSD) != PDMAUDIODIR_IN) /* FIFOW for input streams only. */
+ {
+#ifndef IN_RING0
+ LogRel(("HDA: Warning: Guest tried to write read-only FIFOW to output stream #%RU8, ignoring\n", uSD));
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+#else
+ DEVHDA_UNLOCK(pThis);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, HDA_SD_NUM_FROM_REG(pThis, FIFOW, iReg));
+ if (!pStream)
+ {
+ AssertMsgFailed(("[SD%RU8] Warning: Changing FIFOW on non-attached stream (0x%x)\n", uSD, u32Value));
+
+ int rc = hdaRegWriteU16(pThis, iReg, u32Value);
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+ }
+
+ uint32_t u32FIFOW = 0;
+
+ switch (u32Value)
+ {
+ case HDA_SDFIFOW_8B:
+ case HDA_SDFIFOW_16B:
+ case HDA_SDFIFOW_32B:
+ u32FIFOW = u32Value;
+ break;
+ default:
+ ASSERT_GUEST_LOGREL_MSG_FAILED(("Guest tried write unsupported FIFOW (0x%x) to stream #%RU8, defaulting to 32 bytes\n",
+ u32Value, uSD));
+ u32FIFOW = HDA_SDFIFOW_32B;
+ break;
+ }
+
+ if (u32FIFOW)
+ {
+ pStream->u16FIFOW = hdaSDFIFOWToBytes(u32FIFOW);
+ LogFunc(("[SD%RU8] Updating FIFOW to %RU32 bytes\n", uSD, pStream->u16FIFOW));
+
+ int rc2 = hdaRegWriteU16(pThis, iReg, u32FIFOW);
+ AssertRC(rc2);
+ }
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+}
+
+/**
+ * @note This method could be called for changing value on Output Streams only (ICH6 datasheet 18.2.39).
+ */
+static int hdaRegWriteSDFIFOS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ uint8_t uSD = HDA_SD_NUM_FROM_REG(pThis, FIFOS, iReg);
+
+ if (hdaGetDirFromSD(uSD) != PDMAUDIODIR_OUT) /* FIFOS for output streams only. */
+ {
+ LogRel(("HDA: Warning: Guest tried to write read-only FIFOS to input stream #%RU8, ignoring\n", uSD));
+
+ DEVHDA_UNLOCK(pThis);
+ return 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 write unsupported FIFOS (0x%x) to stream #%RU8, defaulting to 192 bytes\n",
+ u32Value, uSD));
+ u32FIFOS = HDA_SDOFIFO_192B;
+ break;
+ }
+
+ int rc2 = hdaRegWriteU16(pThis, iReg, u32FIFOS);
+ AssertRC(rc2);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+}
+
+#ifdef IN_RING3
+
+/**
+ * Adds an audio output stream to the device setup using the given configuration.
+ *
+ * @returns IPRT status code.
+ * @param pThis Device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStreamOut(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ 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 (pCfg->Props.cChannels)
+ {
+ 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", pCfg->Props.cChannels));
+
+ /* 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->DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT;
+ pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels);
+
+ rc = hdaCodecAddStream(pThis->pCodec, 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->DestSource.Dest = PDMAUDIOPLAYBACKDEST_CENTER_LFE;
+ pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ pCfg->Props.cChannels = (fUseCenter && fUseLFE) ? 2 : 1;
+ pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels);
+
+ rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_CENTER_LFE, pCfg);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fUseRear)
+ {
+ RTStrPrintf(pCfg->szName, RT_ELEMENTS(pCfg->szName), "Rear");
+
+ pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_REAR;
+ pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ pCfg->Props.cChannels = 2;
+ pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels);
+
+ rc = hdaCodecAddStream(pThis->pCodec, 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 IPRT status code.
+ * @param pThis Device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStreamIn(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ AssertReturn(pCfg->enmDir == PDMAUDIODIR_IN, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Source));
+
+ int rc;
+
+ switch (pCfg->DestSource.Source)
+ {
+ case PDMAUDIORECSOURCE_LINE:
+ {
+ rc = hdaCodecAddStream(pThis->pCodec, PDMAUDIOMIXERCTL_LINE_IN, pCfg);
+ break;
+ }
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIORECSOURCE_MIC:
+ {
+ rc = hdaCodecAddStream(pThis->pCodec, 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 IPRT status code.
+ * @param pThis Device state.
+ * @param pCfg Stream configuration to use for adding a stream.
+ */
+static int hdaR3AddStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFuncEnter();
+
+ switch (pCfg->enmDir)
+ {
+ case PDMAUDIODIR_OUT:
+ rc = hdaR3AddStreamOut(pThis, pCfg);
+ break;
+
+ case PDMAUDIODIR_IN:
+ rc = hdaR3AddStreamIn(pThis, 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.
+ *
+ * @returns IPRT status code.
+ * @param pThis Device state.
+ * @param pCfg Stream configuration to use for removing a stream.
+ */
+static int hdaR3RemoveStream(PHDASTATE pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ PDMAUDIOMIXERCTL enmMixerCtl = PDMAUDIOMIXERCTL_UNKNOWN;
+ switch (pCfg->enmDir)
+ {
+ case PDMAUDIODIR_IN:
+ {
+ LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Source));
+
+ switch (pCfg->DestSource.Source)
+ {
+ case PDMAUDIORECSOURCE_UNKNOWN: break;
+ case PDMAUDIORECSOURCE_LINE: enmMixerCtl = PDMAUDIOMIXERCTL_LINE_IN; break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIORECSOURCE_MIC: enmMixerCtl = PDMAUDIOMIXERCTL_MIC_IN; break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ break;
+ }
+
+ case PDMAUDIODIR_OUT:
+ {
+ LogFlowFunc(("Stream=%s, Source=%ld\n", pCfg->szName, pCfg->DestSource.Dest));
+
+ switch (pCfg->DestSource.Dest)
+ {
+ case PDMAUDIOPLAYBACKDEST_UNKNOWN: break;
+ case PDMAUDIOPLAYBACKDEST_FRONT: enmMixerCtl = PDMAUDIOMIXERCTL_FRONT; break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOPLAYBACKDEST_CENTER_LFE: enmMixerCtl = PDMAUDIOMIXERCTL_CENTER_LFE; break;
+ case PDMAUDIOPLAYBACKDEST_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 = hdaCodecRemoveStream(pThis->pCodec, enmMixerCtl);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* IN_RING3 */
+
+static int hdaRegWriteSDFMT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ /* 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. */
+ int rc = hdaRegWriteU16(pThis, iReg, u32Value);
+ AssertRC(rc);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+}
+
+/* Note: Will be called for both, BDPL and BDPU, registers. */
+DECLINLINE(int) hdaRegWriteSDBDPX(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value, uint8_t uSD)
+{
+#ifdef IN_RING3
+ DEVHDA_LOCK(pThis);
+
+# ifdef HDA_USE_DMA_ACCESS_HANDLER
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_OUT)
+ {
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD);
+
+ /* Try registering the DMA handlers.
+ * As we can't be sure in which order LVI + BDL base are set, try registering in both routines. */
+ if ( pStream
+ && hdaR3StreamRegisterDMAHandlers(pThis, pStream))
+ {
+ LogFunc(("[SD%RU8] DMA logging enabled\n", pStream->u8SD));
+ }
+ }
+# else
+ RT_NOREF(uSD);
+# endif
+
+ int rc2 = hdaRegWriteU32(pThis, iReg, u32Value);
+ AssertRC(rc2);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS; /* Always return success to the MMIO handler. */
+#else /* !IN_RING3 */
+ RT_NOREF_PV(pThis); RT_NOREF_PV(iReg); RT_NOREF_PV(u32Value); RT_NOREF_PV(uSD);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* IN_RING3 */
+}
+
+static int hdaRegWriteSDBDPL(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ return hdaRegWriteSDBDPX(pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPL, iReg));
+}
+
+static int hdaRegWriteSDBDPU(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ return hdaRegWriteSDBDPX(pThis, iReg, u32Value, HDA_SD_NUM_FROM_REG(pThis, BDPU, iReg));
+}
+
+static int hdaRegReadIRS(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ);
+
+ /* 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 */
+ }
+
+ int rc = hdaRegReadU32(pThis, iReg, pu32Value);
+ DEVHDA_UNLOCK(pThis);
+
+ return rc;
+}
+
+static int hdaRegWriteIRS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF_PV(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ /*
+ * 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))
+ {
+ DEVHDA_UNLOCK(pThis);
+
+ /*
+ * 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 */
+
+ uint64_t uResp;
+ int rc2 = pThis->pCodec->pfnLookup(pThis->pCodec,
+ 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 */
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ DEVHDA_UNLOCK(pThis);
+ 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);
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteRIRBWP(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ if (HDA_REG(pThis, CORBCTL) & HDA_CORBCTL_DMA) /* Ignore request if CORB DMA engine is (still) running. */
+ {
+ LogFunc(("CORB DMA (still) running, skipping\n"));
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+ }
+
+ if (u32Value & HDA_RIRBWP_RST)
+ {
+ /* Do a RIRB reset. */
+ if (pThis->cbRirbBuf)
+ {
+ Assert(pThis->pu64RirbBuf);
+ RT_BZERO((void *)pThis->pu64RirbBuf, pThis->cbRirbBuf);
+ }
+
+ LogRel2(("HDA: RIRB reset\n"));
+
+ HDA_REG(pThis, RIRBWP) = 0;
+ }
+
+ /* The remaining bits are O, see 6.2.22. */
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+}
+
+static int hdaRegWriteRINTCNT(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ 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"));
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+ }
+
+ int rc = hdaRegWriteU16(pThis, iReg, u32Value);
+ AssertRC(rc);
+
+ LogFunc(("Response interrupt count is now %RU8\n", HDA_REG(pThis, RINTCNT) & 0xFF));
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+}
+
+static int hdaRegWriteBase(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ uint32_t iRegMem = g_aHdaRegMap[iReg].mem_idx;
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ int rc = hdaRegWriteU32(pThis, iReg, u32Value);
+ AssertRCSuccess(rc);
+
+ 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);
+ LogRel(("HDA: %s DMA position buffer\n", pThis->fDMAPosition ? "Enabled" : "Disabled"));
+ break;
+ }
+ case HDA_REG_DPUBASE:
+ pThis->u64DPBase = RT_MAKE_U64(RT_LO_U32(pThis->u64DPBase) & DPBASE_ADDR_MASK, pThis->au32Regs[iRegMem]);
+ break;
+ default:
+ AssertMsgFailed(("Invalid index\n"));
+ break;
+ }
+
+ LogFunc(("CORB base:%llx RIRB base: %llx DP base: %llx\n",
+ pThis->u64CORBBase, pThis->u64RIRBBase, pThis->u64DPBase));
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+}
+
+static int hdaRegWriteRIRBSTS(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF_PV(iReg);
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ uint8_t v = HDA_REG(pThis, RIRBSTS);
+ HDA_REG(pThis, RIRBSTS) &= ~(v & u32Value);
+
+#ifndef LOG_ENABLED
+ int rc = hdaProcessInterrupt(pThis);
+#else
+ int rc = hdaProcessInterrupt(pThis, __FUNCTION__);
+#endif
+
+ DEVHDA_UNLOCK(pThis);
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Retrieves a corresponding sink for a given mixer control.
+ * Returns NULL if no sink is found.
+ *
+ * @return PHDAMIXERSINK
+ * @param pThis HDA state.
+ * @param enmMixerCtl Mixer control to get the corresponding sink for.
+ */
+static PHDAMIXERSINK hdaR3MixerControlToSink(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ PHDAMIXERSINK pSink;
+
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ /* Fall through is intentional. */
+ case PDMAUDIOMIXERCTL_FRONT:
+ pSink = &pThis->SinkFront;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOMIXERCTL_CENTER_LFE:
+ pSink = &pThis->SinkCenterLFE;
+ break;
+ case PDMAUDIOMIXERCTL_REAR:
+ pSink = &pThis->SinkRear;
+ break;
+# endif
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pSink = &pThis->SinkLineIn;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pSink = &pThis->SinkMicIn;
+ break;
+# endif
+ default:
+ pSink = NULL;
+ AssertMsgFailed(("Unhandled mixer control\n"));
+ break;
+ }
+
+ return pSink;
+}
+
+/**
+ * Adds a specific HDA driver to the driver chain.
+ *
+ * @return IPRT status code.
+ * @param pThis HDA state.
+ * @param pDrv HDA driver to add.
+ */
+static int hdaR3MixerAddDrv(PHDASTATE pThis, PHDADRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ PHDASTREAM pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn);
+ if ( pStream
+ && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkLineIn.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn);
+ if ( pStream
+ && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkMicIn.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+# endif
+
+ pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkFront);
+ if ( pStream
+ && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkFront.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkCenterLFE);
+ if ( pStream
+ && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->SinkCenterLFE.pMixSink, &pStream->State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ pStream = hdaR3GetStreamFromSink(pThis, &pThis->SinkRear);
+ if ( pStream
+ && DrvAudioHlpStreamCfgIsValid(&pStream->State.Cfg))
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, pThis->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 pThis HDA state.
+ * @param pDrv HDA driver to remove.
+ */
+static void hdaR3MixerRemoveDrv(PHDASTATE pThis, PHDADRIVER pDrv)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pDrv);
+
+ if (pDrv->LineIn.pMixStrm)
+ {
+ if (AudioMixerSinkGetRecordingSource(pThis->SinkLineIn.pMixSink) == pDrv->LineIn.pMixStrm)
+ AudioMixerSinkSetRecordingSource(pThis->SinkLineIn.pMixSink, NULL);
+
+ AudioMixerSinkRemoveStream(pThis->SinkLineIn.pMixSink, pDrv->LineIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm);
+ pDrv->LineIn.pMixStrm = NULL;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ if (pDrv->MicIn.pMixStrm)
+ {
+ if (AudioMixerSinkGetRecordingSource(pThis->SinkMicIn.pMixSink) == pDrv->MicIn.pMixStrm)
+ AudioMixerSinkSetRecordingSource(&pThis->SinkMicIn.pMixSink, NULL);
+
+ AudioMixerSinkRemoveStream(pThis->SinkMicIn.pMixSink, pDrv->MicIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm);
+ pDrv->MicIn.pMixStrm = NULL;
+ }
+# endif
+
+ if (pDrv->Front.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThis->SinkFront.pMixSink, pDrv->Front.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Front.pMixStrm);
+ pDrv->Front.pMixStrm = NULL;
+ }
+
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (pDrv->CenterLFE.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThis->SinkCenterLFE.pMixSink, pDrv->CenterLFE.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->CenterLFE.pMixStrm);
+ pDrv->CenterLFE.pMixStrm = NULL;
+ }
+
+ if (pDrv->Rear.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThis->SinkRear.pMixSink, pDrv->Rear.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Rear.pMixStrm);
+ pDrv->Rear.pMixStrm = NULL;
+ }
+# endif
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * @returns IPRT status code (ignored by caller).
+ * @param pThis HDA state.
+ * @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(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PHDADRIVER pDrv)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFunc(("szSink=%s, szStream=%s, cChannels=%RU8\n", pMixSink->pszName, pCfg->szName, pCfg->Props.cChannels));
+
+ PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg);
+ if (!pStreamCfg)
+ return VERR_NO_MEMORY;
+
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName));
+
+ int rc = VINF_SUCCESS;
+
+ PHDADRIVERSTREAM pDrvStream = NULL;
+
+ if (pStreamCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("enmRecSource=%d\n", pStreamCfg->DestSource.Source));
+
+ switch (pStreamCfg->DestSource.Source)
+ {
+ case PDMAUDIORECSOURCE_LINE:
+ pDrvStream = &pDrv->LineIn;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ case PDMAUDIORECSOURCE_MIC:
+ pDrvStream = &pDrv->MicIn;
+ break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else if (pStreamCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmPlaybackDest=%d\n", pStreamCfg->DestSource.Dest));
+
+ switch (pStreamCfg->DestSource.Dest)
+ {
+ case PDMAUDIOPLAYBACKDEST_FRONT:
+ pDrvStream = &pDrv->Front;
+ break;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ case PDMAUDIOPLAYBACKDEST_CENTER_LFE:
+ pDrvStream = &pDrv->CenterLFE;
+ break;
+ case PDMAUDIOPLAYBACKDEST_REAR:
+ pDrvStream = &pDrv->Rear;
+ break;
+# endif
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(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, pStreamCfg, 0 /* fFlags */, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ /* If this is an input stream, always set the latest (added) stream
+ * as the recording source.
+ * @todo Make the recording source dynamic (CFGM?). */
+ if (pStreamCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ PDMAUDIOBACKENDCFG Cfg;
+ rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (Cfg.cMaxStreamsIn) /* At least one input source available? */
+ {
+ rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n",
+ pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc));
+
+ if (RT_SUCCESS(rc))
+ LogRel(("HDA: Set recording source for '%s' to '%s'\n",
+ pStreamCfg->szName, Cfg.szName));
+ }
+ else
+ LogRel(("HDA: Backend '%s' currently is not offering any recording source for '%s'\n",
+ Cfg.szName, pStreamCfg->szName));
+ }
+ else if (RT_FAILURE(rc))
+ LogFunc(("LUN#%RU8: Unable to retrieve backend configuration for '%s', rc=%Rrc\n",
+ pDrv->uLUN, pStreamCfg->szName, rc));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+ }
+
+ DrvAudioHlpStreamCfgFree(pStreamCfg);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA 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(PHDASTATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFunc(("Sink=%s, Stream=%s\n", pMixSink->pszName, pCfg->szName));
+
+ if (!DrvAudioHlpStreamCfgIsValid(pCfg))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PHDADRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, HDADRIVER, Node)
+ {
+ int rc2 = hdaR3MixerAddDrvStream(pThis, 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. */
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{HDACODEC,pfnCbMixerAddStream}
+ *
+ * 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 IPRT status code.
+ * @param pThis HDA state.
+ * @param enmMixerCtl Mixer control to assign new stream to.
+ * @param pCfg Stream configuration for the new stream.
+ */
+static DECLCALLBACK(int) hdaR3MixerAddStream(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ int rc;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl);
+ if (pSink)
+ {
+ rc = hdaR3MixerAddDrvStreams(pThis, pSink->pMixSink, pCfg);
+
+ AssertPtr(pSink->pMixSink);
+ LogFlowFunc(("Sink=%s, Mixer control=%s\n", pSink->pMixSink->pszName, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl)));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{HDACODEC,pfnCbMixerRemoveStream}
+ *
+ * Removes a specified mixer control from the HDA's mixer.
+ *
+ * @return IPRT status code.
+ * @param pThis HDA state.
+ * @param enmMixerCtl Mixer control to remove.
+ *
+ * @remarks Can be called as a callback by the HDA codec.
+ */
+static DECLCALLBACK(int) hdaR3MixerRemoveStream(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl);
+ if (pSink)
+ {
+ PHDADRIVER pDrv;
+ RTListForEach(&pThis->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);
+
+ pMixStream = NULL;
+ }
+ }
+
+ AudioMixerSinkRemoveAllStreams(pSink->pMixSink);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFunc(("Mixer control=%s, rc=%Rrc\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{HDACODEC,pfnCbMixerControl}
+ *
+ * Controls an input / output converter widget, that is, which converter is connected
+ * to which stream (and channel).
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA State.
+ * @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.
+ *
+ * @remarks Can be called as a callback by the HDA codec.
+ */
+static DECLCALLBACK(int) hdaR3MixerControl(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel)
+{
+ LogFunc(("enmMixerCtl=%s, uSD=%RU8, uChannel=%RU8\n", DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), uSD, uChannel));
+
+ if (uSD == 0) /* Stream number 0 is reserved. */
+ {
+ Log2Func(("Invalid SDn (%RU8) number for mixer control '%s', ignoring\n", uSD, DrvAudioHlpAudMixerCtlToStr(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(pThis, enmMixerCtl);
+ if (pSink)
+ {
+ AssertPtr(pSink->pMixSink);
+
+ /* If this an output stream, determine the correct SD#. */
+ if ( (uSD < HDA_MAX_SDI)
+ && AudioMixerSinkGetDir(pSink->pMixSink) == AUDMIXSINKDIR_OUTPUT)
+ {
+ uSD += HDA_MAX_SDI;
+ }
+
+ /* Detach the existing stream from the sink. */
+ if ( pSink->pStream
+ && ( pSink->pStream->u8SD != uSD
+ || pSink->pStream->u8Channel != uChannel)
+ )
+ {
+ LogFunc(("Sink '%s' was assigned to stream #%RU8 (channel %RU8) before\n",
+ pSink->pMixSink->pszName, pSink->pStream->u8SD, pSink->pStream->u8Channel));
+
+ hdaR3StreamLock(pSink->pStream);
+
+ /* Only disable the stream if the stream descriptor # has changed. */
+ if (pSink->pStream->u8SD != uSD)
+ hdaR3StreamEnable(pSink->pStream, false);
+
+ pSink->pStream->pMixSink = NULL;
+
+ hdaR3StreamUnlock(pSink->pStream);
+
+ pSink->pStream = NULL;
+ }
+
+ Assert(uSD < HDA_MAX_STREAMS);
+
+ /* Attach the new stream to the sink.
+ * Enabling the stream will be done by the gust via a separate SDnCTL call then. */
+ if (pSink->pStream == NULL)
+ {
+ LogRel2(("HDA: Setting sink '%s' to stream #%RU8 (channel %RU8), mixer control=%s\n",
+ pSink->pMixSink->pszName, uSD, uChannel, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl)));
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uSD);
+ if (pStream)
+ {
+ hdaR3StreamLock(pStream);
+
+ pSink->pStream = pStream;
+
+ pStream->u8Channel = uChannel;
+ pStream->pMixSink = pSink;
+
+ hdaR3StreamUnlock(pStream);
+
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+ 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, DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{HDACODEC,pfnCbMixerSetVolume}
+ *
+ * Sets the volume of a specified mixer control.
+ *
+ * @return IPRT status code.
+ * @param pThis HDA State.
+ * @param enmMixerCtl Mixer control to set volume for.
+ * @param pVol Pointer to volume data to set.
+ *
+ * @remarks Can be called as a callback by the HDA codec.
+ */
+static DECLCALLBACK(int) hdaR3MixerSetVolume(PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol)
+{
+ int rc;
+
+ PHDAMIXERSINK pSink = hdaR3MixerControlToSink(pThis, enmMixerCtl);
+ if ( pSink
+ && pSink->pMixSink)
+ {
+ LogRel2(("HDA: Setting volume for mixer sink '%s' to %RU8/%RU8 (%s)\n",
+ pSink->pMixSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted"));
+
+ /* 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;
+}
+
+/**
+ * Main routine for the stream's timer.
+ *
+ * @param pDevIns Device instance.
+ * @param pTimer Timer this callback was called for.
+ * @param pvUser Pointer to associated HDASTREAM.
+ */
+static DECLCALLBACK(void) hdaR3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, pTimer);
+
+ PHDASTREAM pStream = (PHDASTREAM)pvUser;
+ AssertPtr(pStream);
+
+ PHDASTATE pThis = pStream->pHDAState;
+
+ DEVHDA_LOCK_BOTH_RETURN_VOID(pStream->pHDAState, pStream->u8SD);
+
+ hdaR3StreamUpdate(pStream, true /* fInTimer */);
+
+ /* Flag indicating whether to kick the timer again for a new data processing round. */
+ bool fSinkActive = false;
+ if (pStream->pMixSink)
+ fSinkActive = AudioMixerSinkIsActive(pStream->pMixSink->pMixSink);
+
+ if (fSinkActive)
+ {
+ const bool fTimerScheduled = hdaR3StreamTransferIsScheduled(pStream);
+ Log3Func(("fSinksActive=%RTbool, fTimerScheduled=%RTbool\n", fSinkActive, fTimerScheduled));
+ if (!fTimerScheduled)
+ hdaR3TimerSet(pThis, pStream,
+ TMTimerGet(pThis->pTimer[pStream->u8SD])
+ + TMTimerGetFreq(pThis->pTimer[pStream->u8SD]) / pStream->pHDAState->uTimerHz,
+ true /* fForce */);
+ }
+ else
+ Log3Func(("fSinksActive=%RTbool\n", fSinkActive));
+
+ DEVHDA_UNLOCK_BOTH(pThis, pStream->u8SD);
+}
+
+# ifdef HDA_USE_DMA_ACCESS_HANDLER
+/**
+ * HC access handler for the FIFO.
+ *
+ * @returns VINF_SUCCESS if the handler have carried out the operation.
+ * @returns VINF_PGM_HANDLER_DO_DEFAULT if the caller should carry out the access operation.
+ * @param pVM VM Handle.
+ * @param pVCpu The cross context CPU structure for the calling EMT.
+ * @param GCPhys The physical address the guest is writing to.
+ * @param pvPhys The HC mapping of that address.
+ * @param pvBuf What the guest is reading/writing.
+ * @param cbBuf How much it's reading/writing.
+ * @param enmAccessType The access type.
+ * @param enmOrigin Who is making the access.
+ * @param pvUser User argument.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) hdaR3DMAAccessHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys,
+ void *pvBuf, size_t cbBuf,
+ PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser)
+{
+ RT_NOREF(pVM, pVCpu, pvPhys, pvBuf, enmOrigin);
+
+ PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)pvUser;
+ AssertPtr(pHandler);
+
+ PHDASTREAM pStream = pHandler->pStream;
+ AssertPtr(pStream);
+
+ Assert(GCPhys >= pHandler->GCPhysFirst);
+ Assert(GCPhys <= pHandler->GCPhysLast);
+ Assert(enmAccessType == PGMACCESSTYPE_WRITE);
+
+ /* Not within BDLE range? Bail out. */
+ if ( (GCPhys < pHandler->BDLEAddr)
+ || (GCPhys + cbBuf > pHandler->BDLEAddr + pHandler->BDLESize))
+ {
+ return VINF_PGM_HANDLER_DO_DEFAULT;
+ }
+
+ switch(enmAccessType)
+ {
+ case PGMACCESSTYPE_WRITE:
+ {
+# ifdef DEBUG
+ PHDASTREAMDBGINFO pStreamDbg = &pStream->Dbg;
+
+ const uint64_t tsNowNs = RTTimeNanoTS();
+ const uint32_t tsElapsedMs = (tsNowNs - pStreamDbg->tsWriteSlotBegin) / 1000 / 1000;
+
+ uint64_t cWritesHz = ASMAtomicReadU64(&pStreamDbg->cWritesHz);
+ uint64_t cbWrittenHz = ASMAtomicReadU64(&pStreamDbg->cbWrittenHz);
+
+ if (tsElapsedMs >= (1000 / HDA_TIMER_HZ_DEFAULT))
+ {
+ LogFunc(("[SD%RU8] %RU32ms elapsed, cbWritten=%RU64, cWritten=%RU64 -- %RU32 bytes on average per time slot (%zums)\n",
+ pStream->u8SD, tsElapsedMs, cbWrittenHz, cWritesHz,
+ ASMDivU64ByU32RetU32(cbWrittenHz, cWritesHz ? cWritesHz : 1), 1000 / HDA_TIMER_HZ_DEFAULT));
+
+ pStreamDbg->tsWriteSlotBegin = tsNowNs;
+
+ cWritesHz = 0;
+ cbWrittenHz = 0;
+ }
+
+ cWritesHz += 1;
+ cbWrittenHz += cbBuf;
+
+ ASMAtomicIncU64(&pStreamDbg->cWritesTotal);
+ ASMAtomicAddU64(&pStreamDbg->cbWrittenTotal, cbBuf);
+
+ ASMAtomicWriteU64(&pStreamDbg->cWritesHz, cWritesHz);
+ ASMAtomicWriteU64(&pStreamDbg->cbWrittenHz, cbWrittenHz);
+
+ LogFunc(("[SD%RU8] Writing %3zu @ 0x%x (off %zu)\n",
+ pStream->u8SD, cbBuf, GCPhys, GCPhys - pHandler->BDLEAddr));
+
+ LogFunc(("[SD%RU8] cWrites=%RU64, cbWritten=%RU64 -> %RU32 bytes on average\n",
+ pStream->u8SD, pStreamDbg->cWritesTotal, pStreamDbg->cbWrittenTotal,
+ ASMDivU64ByU32RetU32(pStreamDbg->cbWrittenTotal, pStreamDbg->cWritesTotal)));
+# endif
+
+ if (pThis->fDebugEnabled)
+ {
+ RTFILE fh;
+ RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "hdaDMAAccessWrite.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ RTFileWrite(fh, pvBuf, cbBuf, NULL);
+ RTFileClose(fh);
+ }
+
+# ifdef HDA_USE_DMA_ACCESS_HANDLER_WRITING
+ PRTCIRCBUF pCircBuf = pStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+ while (cbBuf)
+ {
+ /* Make sure we only copy as much as the stream's FIFO can hold (SDFIFOS, 18.2.39). */
+ void *pvChunk;
+ size_t cbChunk;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbBuf, &pvChunk, &cbChunk);
+
+ if (cbChunk)
+ {
+ memcpy(pvChunk, pbBuf, cbChunk);
+
+ pbBuf += cbChunk;
+ Assert(cbBuf >= cbChunk);
+ cbBuf -= cbChunk;
+ }
+ else
+ {
+ //AssertMsg(RTCircBufFree(pCircBuf), ("No more space but still %zu bytes to write\n", cbBuf));
+ break;
+ }
+
+ LogFunc(("[SD%RU8] cbChunk=%zu\n", pStream->u8SD, cbChunk));
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbChunk);
+ }
+# endif /* HDA_USE_DMA_ACCESS_HANDLER_WRITING */
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Access type not implemented\n"));
+ break;
+ }
+
+ return VINF_PGM_HANDLER_DO_DEFAULT;
+}
+# endif /* HDA_USE_DMA_ACCESS_HANDLER */
+
+/**
+ * Soft reset of the device triggered via GCTL.
+ *
+ * @param pThis HDA state.
+ *
+ */
+static void hdaR3GCTLReset(PHDASTATE pThis)
+{
+ LogFlowFuncEnter();
+
+ pThis->cStreamsActive = 0;
+
+ 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;
+
+ /*
+ * Stop any audio currently playing and/or recording.
+ */
+ pThis->SinkFront.pStream = NULL;
+ if (pThis->SinkFront.pMixSink)
+ AudioMixerSinkReset(pThis->SinkFront.pMixSink);
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pThis->SinkMicIn.pStream = NULL;
+ if (pThis->SinkMicIn.pMixSink)
+ AudioMixerSinkReset(pThis->SinkMicIn.pMixSink);
+# endif
+ pThis->SinkLineIn.pStream = NULL;
+ if (pThis->SinkLineIn.pMixSink)
+ AudioMixerSinkReset(pThis->SinkLineIn.pMixSink);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ pThis->SinkCenterLFE = NULL;
+ if (pThis->SinkCenterLFE.pMixSink)
+ AudioMixerSinkReset(pThis->SinkCenterLFE.pMixSink);
+ pThis->SinkRear.pStream = NULL;
+ if (pThis->SinkRear.pMixSink)
+ AudioMixerSinkReset(pThis->SinkRear.pMixSink);
+# endif
+
+ /*
+ * Reset the codec.
+ */
+ if ( pThis->pCodec
+ && pThis->pCodec->pfnReset)
+ {
+ pThis->pCodec->pfnReset(pThis->pCodec);
+ }
+
+ /*
+ * 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.
+ */
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_MIC_IN , 1 /* SD0 */, 0 /* Channel */);
+# endif
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_LINE_IN , 1 /* SD0 */, 0 /* Channel */);
+
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_FRONT , 5 /* SD4 */, 0 /* Channel */);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_CENTER_LFE, 5 /* SD4 */, 0 /* Channel */);
+ hdaR3MixerControl(pThis, PDMAUDIOMIXERCTL_REAR , 5 /* SD4 */, 0 /* Channel */);
+# endif
+
+ /* Reset CORB. */
+ pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE;
+ RT_BZERO(pThis->pu32CorbBuf, pThis->cbCorbBuf);
+
+ /* Reset RIRB. */
+ pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE;
+ RT_BZERO(pThis->pu64RirbBuf, pThis->cbRirbBuf);
+
+ /* Clear our internal response interrupt counter. */
+ pThis->u16RespIntCnt = 0;
+
+ for (uint8_t uSD = 0; uSD < HDA_MAX_STREAMS; ++uSD)
+ {
+ int rc2 = hdaR3StreamEnable(&pThis->aStreams[uSD], false /* fEnable */);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Remove the RUN bit from SDnCTL in case the stream was in a running state before. */
+ HDA_STREAM_REG(pThis, CTL, uSD) &= ~HDA_SDCTL_RUN;
+ hdaR3StreamReset(pThis, &pThis->aStreams[uSD], uSD);
+ }
+ }
+
+ /* Clear stream tags <-> objects mapping table. */
+ RT_ZERO(pThis->aTags);
+
+ /* Emulation of codec "wake up" (HDA spec 5.5.1 and 6.5). */
+ HDA_REG(pThis, STATESTS) = 0x1;
+
+ LogFlowFuncLeave();
+ LogRel(("HDA: Reset\n"));
+}
+
+#endif /* IN_RING3 */
+
+/* MMIO callbacks */
+
+/**
+ * @callback_method_impl{FNIOMMMIOREAD, 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.
+ */
+PDMBOTHCBDECL(int) hdaMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ int rc;
+ RT_NOREF_PV(pvUser);
+ Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC);
+
+ /*
+ * Look up and log.
+ */
+ uint32_t offReg = GCPhysAddr - pThis->MMIOBaseAddr;
+ int idxRegDsc = hdaRegLookup(offReg); /* Register descriptor index. */
+#ifdef LOG_ENABLED
+ unsigned const cbLog = cb;
+ uint32_t offRegLog = offReg;
+#endif
+
+ Log3Func(("offReg=%#x cb=%#x\n", offReg, cb));
+ Assert(cb == 4); Assert((offReg & 3) == 0);
+
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_READ);
+
+ if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL)
+ LogFunc(("Access to registers except GCTL is blocked while reset\n"));
+
+ if (idxRegDsc == -1)
+ LogRel(("HDA: Invalid read access @0x%x (bytes=%u)\n", offReg, cb));
+
+ if (idxRegDsc != -1)
+ {
+ /* Leave lock before calling read function. */
+ DEVHDA_UNLOCK(pThis);
+
+ /* ASSUMES gapless DWORD at end of map. */
+ if (g_aHdaRegMap[idxRegDsc].size == 4)
+ {
+ /*
+ * Straight forward DWORD access.
+ */
+ rc = g_aHdaRegMap[idxRegDsc].pfnRead(pThis, idxRegDsc, (uint32_t *)pv);
+ Log3Func(("\tRead %s => %x (%Rrc)\n", g_aHdaRegMap[idxRegDsc].abbrev, *(uint32_t *)pv, rc));
+ }
+ else
+ {
+ /*
+ * Multi register read (unless there are trailing gaps).
+ * ASSUMES that only DWORD reads have sideeffects.
+ */
+#ifdef IN_RING3
+ uint32_t u32Value = 0;
+ unsigned cbLeft = 4;
+ do
+ {
+ uint32_t const cbReg = g_aHdaRegMap[idxRegDsc].size;
+ uint32_t u32Tmp = 0;
+
+ rc = g_aHdaRegMap[idxRegDsc].pfnRead(pThis, idxRegDsc, &u32Tmp);
+ Log3Func(("\tRead %s[%db] => %x (%Rrc)*\n", g_aHdaRegMap[idxRegDsc].abbrev, cbReg, u32Tmp, rc));
+ if (rc != VINF_SUCCESS)
+ break;
+ u32Value |= (u32Tmp & g_afMasks[cbReg]) << ((4 - cbLeft) * 8);
+
+ cbLeft -= cbReg;
+ offReg += cbReg;
+ idxRegDsc++;
+ } while (cbLeft > 0 && g_aHdaRegMap[idxRegDsc].offset == offReg);
+
+ if (rc == VINF_SUCCESS)
+ *(uint32_t *)pv = u32Value;
+ else
+ Assert(!IOM_SUCCESS(rc));
+#else /* !IN_RING3 */
+ /* Take the easy way out. */
+ rc = VINF_IOM_R3_MMIO_READ;
+#endif /* !IN_RING3 */
+ }
+ }
+ else
+ {
+ DEVHDA_UNLOCK(pThis);
+
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ Log3Func(("\tHole at %x is accessed for read\n", offReg));
+ }
+
+ /*
+ * Log the outcome.
+ */
+#ifdef LOG_ENABLED
+ if (cbLog == 4)
+ Log3Func(("\tReturning @%#05x -> %#010x %Rrc\n", offRegLog, *(uint32_t *)pv, rc));
+ else if (cbLog == 2)
+ Log3Func(("\tReturning @%#05x -> %#06x %Rrc\n", offRegLog, *(uint16_t *)pv, rc));
+ else if (cbLog == 1)
+ Log3Func(("\tReturning @%#05x -> %#04x %Rrc\n", offRegLog, *(uint8_t *)pv, rc));
+#endif
+ return rc;
+}
+
+
+DECLINLINE(int) hdaWriteReg(PHDASTATE pThis, int idxRegDsc, uint32_t u32Value, char const *pszLog)
+{
+ DEVHDA_LOCK_RETURN(pThis, VINF_IOM_R3_MMIO_WRITE);
+
+ if (!(HDA_REG(pThis, GCTL) & HDA_GCTL_CRST) && idxRegDsc != HDA_REG_GCTL)
+ {
+ Log(("hdaWriteReg: Warning: Access to %s is blocked while controller is in reset mode\n", g_aHdaRegMap[idxRegDsc].abbrev));
+ LogRel2(("HDA: Warning: Access to register %s is blocked while controller is in reset mode\n",
+ g_aHdaRegMap[idxRegDsc].abbrev));
+
+ DEVHDA_UNLOCK(pThis);
+ 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)
+ {
+ const uint32_t uSDCTL = HDA_STREAM_REG(pThis, CTL, HDA_SD_NUM_FROM_REG(pThis, CTL, idxRegDsc));
+
+ /*
+ * 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.
+ */
+
+ /* Is the RUN bit currently set? */
+ if ( RT_BOOL(uSDCTL & HDA_SDCTL_RUN)
+ /* Are writes to the register denied if RUN bit is set? */
+ && !(g_aHdaRegMap[idxRegDsc].fFlags & HDA_RD_FLAG_SD_WRITE_RUN))
+ {
+ Log(("hdaWriteReg: Warning: Access to %s is blocked! %R[sdctl]\n", g_aHdaRegMap[idxRegDsc].abbrev, uSDCTL));
+ LogRel2(("HDA: Warning: Access to register %s is blocked while the stream's RUN bit is set\n",
+ g_aHdaRegMap[idxRegDsc].abbrev));
+
+ DEVHDA_UNLOCK(pThis);
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Leave the lock before calling write function. */
+ /** @todo r=bird: Why do we need to do that?? There is no
+ * explanation why this is necessary here...
+ *
+ * More or less all write functions retake the lock, so why not let
+ * those who need to drop the lock or take additional locks release
+ * it? See, releasing a lock you already got always runs the risk
+ * of someone else grabbing it and forcing you to wait, better to
+ * do the two-three things a write handle needs to do than enter
+ * and exit the lock all the time. */
+ DEVHDA_UNLOCK(pThis);
+
+#ifdef LOG_ENABLED
+ uint32_t const idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx;
+ uint32_t const u32OldValue = pThis->au32Regs[idxRegMem];
+#endif
+ int rc = g_aHdaRegMap[idxRegDsc].pfnWrite(pThis, idxRegDsc, u32Value);
+ Log3Func(("Written value %#x to %s[%d byte]; %x => %x%s, rc=%d\n", u32Value, g_aHdaRegMap[idxRegDsc].abbrev,
+ g_aHdaRegMap[idxRegDsc].size, u32OldValue, pThis->au32Regs[idxRegMem], pszLog, rc));
+ RT_NOREF(pszLog);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIOWRITE, Looks up and calls the appropriate handler.}
+ */
+PDMBOTHCBDECL(int) hdaMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ int rc;
+ RT_NOREF_PV(pvUser);
+ Assert(pThis->uAlignmentCheckMagic == HDASTATE_ALIGNMENT_CHECK_MAGIC);
+
+ /*
+ * The behavior of accesses that aren't aligned on natural boundraries is
+ * undefined. Just reject them outright.
+ */
+ /** @todo IOM could check this, it could also split the 8 byte accesses for us. */
+ Assert(cb == 1 || cb == 2 || cb == 4 || cb == 8);
+ if (GCPhysAddr & (cb - 1))
+ return PDMDevHlpDBGFStop(pDevIns, RT_SRC_POS, "misaligned write access: GCPhysAddr=%RGp cb=%u\n", GCPhysAddr, cb);
+
+ /*
+ * Look up and log the access.
+ */
+ uint32_t offReg = GCPhysAddr - pThis->MMIOBaseAddr;
+ int idxRegDsc = hdaRegLookup(offReg);
+#if defined(IN_RING3) || defined(LOG_ENABLED)
+ uint32_t idxRegMem = idxRegDsc != -1 ? g_aHdaRegMap[idxRegDsc].mem_idx : 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
+ {
+ u64Value = 0; /* shut up gcc. */
+ AssertReleaseMsgFailed(("%u\n", cb));
+ }
+
+#ifdef LOG_ENABLED
+ uint32_t const u32LogOldValue = idxRegDsc >= 0 ? pThis->au32Regs[idxRegMem] : UINT32_MAX;
+ if (idxRegDsc == -1)
+ Log3Func(("@%#05x u32=%#010x cb=%d\n", offReg, *(uint32_t const *)pv, cb));
+ else if (cb == 4)
+ Log3Func(("@%#05x u32=%#010x %s\n", offReg, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev));
+ else if (cb == 2)
+ Log3Func(("@%#05x u16=%#06x (%#010x) %s\n", offReg, *(uint16_t *)pv, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev));
+ else if (cb == 1)
+ Log3Func(("@%#05x u8=%#04x (%#010x) %s\n", offReg, *(uint8_t *)pv, *(uint32_t *)pv, g_aHdaRegMap[idxRegDsc].abbrev));
+
+ if (idxRegDsc >= 0 && g_aHdaRegMap[idxRegDsc].size != cb)
+ Log3Func(("\tsize=%RU32 != cb=%u!!\n", g_aHdaRegMap[idxRegDsc].size, cb));
+#endif
+
+ /*
+ * Try for a direct hit first.
+ */
+ if (idxRegDsc != -1 && g_aHdaRegMap[idxRegDsc].size == cb)
+ {
+ rc = hdaWriteReg(pThis, idxRegDsc, u64Value, "");
+ Log3Func(("\t%#x -> %#x\n", u32LogOldValue, idxRegMem != UINT32_MAX ? pThis->au32Regs[idxRegMem] : UINT32_MAX));
+ }
+ /*
+ * Partial or multiple register access, loop thru the requested memory.
+ */
+ else
+ {
+#ifdef IN_RING3
+ /*
+ * 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 == -1 && (idxRegDsc = hdaR3RegLookupWithin(offReg)) != -1)
+ {
+ uint32_t const cbBefore = offReg - g_aHdaRegMap[idxRegDsc].offset; Assert(cbBefore > 0 && cbBefore < 4);
+ offReg -= cbBefore;
+ idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx;
+ u64Value <<= cbBefore * 8;
+ u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbBefore];
+ Log3Func(("\tWithin register, supplied %u leading bits: %#llx -> %#llx ...\n",
+ cbBefore * 8, ~g_afMasks[cbBefore] & u64Value, u64Value));
+ }
+
+ /* Loop thru the write area, it may cover multiple registers. */
+ rc = VINF_SUCCESS;
+ for (;;)
+ {
+ uint32_t cbReg;
+ if (idxRegDsc != -1)
+ {
+ idxRegMem = g_aHdaRegMap[idxRegDsc].mem_idx;
+ cbReg = g_aHdaRegMap[idxRegDsc].size;
+ if (cb < cbReg)
+ {
+ u64Value |= pThis->au32Regs[idxRegMem] & g_afMasks[cbReg] & ~g_afMasks[cb];
+ Log3Func(("\tSupplying 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(pThis, idxRegDsc, u64Value, "*");
+ Log3Func(("\t%#x -> %#x\n", uLogOldVal, pThis->au32Regs[idxRegMem]));
+ }
+ else
+ {
+ LogRel(("HDA: Invalid write access @0x%x\n", offReg));
+ cbReg = 1;
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ if (cbReg >= cb)
+ break;
+
+ /* Advance. */
+ offReg += cbReg;
+ cb -= cbReg;
+ u64Value >>= cbReg * 8;
+ if (idxRegDsc == -1)
+ idxRegDsc = hdaRegLookup(offReg);
+ else
+ {
+ idxRegDsc++;
+ if ( (unsigned)idxRegDsc >= RT_ELEMENTS(g_aHdaRegMap)
+ || g_aHdaRegMap[idxRegDsc].offset != offReg)
+ {
+ idxRegDsc = -1;
+ }
+ }
+ }
+
+#else /* !IN_RING3 */
+ /* Take the simple way out. */
+ rc = VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+ }
+
+ return rc;
+}
+
+
+/* PCI callback. */
+
+#ifdef IN_RING3
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) hdaR3PciIoRegionMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, enmType);
+ PHDASTATE pThis = RT_FROM_MEMBER(pPciDev, HDASTATE, PciDev);
+
+ /*
+ * 18.2 of the ICH6 datasheet defines the valid access widths as byte, word, and double word.
+ *
+ * Let IOM talk DWORDs when reading, saves a lot of complications. On
+ * writing though, we have to do it all ourselves because of sideeffects.
+ */
+ Assert(enmType == PCI_ADDRESS_SPACE_MEM);
+ int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD
+ | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ hdaMMIOWrite, hdaMMIORead, "HDA");
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fRZEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/,
+ "hdaMMIOWrite", "hdaMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/,
+ "hdaMMIOWrite", "hdaMMIORead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->MMIOBaseAddr = GCPhysAddress;
+ return VINF_SUCCESS;
+}
+
+
+/* Saved state workers and callbacks. */
+
+static int hdaR3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PHDASTREAM pStream)
+{
+ RT_NOREF(pDevIns);
+#if defined(LOG_ENABLED)
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+#endif
+
+ Log2Func(("[SD%RU8]\n", pStream->u8SD));
+
+ /* Save stream ID. */
+ int rc = SSMR3PutU8(pSSM, pStream->u8SD);
+ AssertRCReturn(rc, rc);
+ Assert(pStream->u8SD < HDA_MAX_STREAMS);
+
+ rc = SSMR3PutStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE), 0 /*fFlags*/, g_aSSMStreamStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ rc = SSMR3PutStructEx(pSSM, &pStream->State.BDLE.Desc, sizeof(HDABDLEDESC),
+ 0 /*fFlags*/, g_aSSMBDLEDescFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ rc = SSMR3PutStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE),
+ 0 /*fFlags*/, g_aSSMBDLEStateFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ rc = SSMR3PutStructEx(pSSM, &pStream->State.Period, sizeof(HDASTREAMPERIOD),
+ 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL);
+ AssertRCReturn(rc, rc);
+
+ uint32_t cbCircBufSize = 0;
+ uint32_t cbCircBufUsed = 0;
+
+ if (pStream->State.pCircBuf)
+ {
+ cbCircBufSize = (uint32_t)RTCircBufSize(pStream->State.pCircBuf);
+ cbCircBufUsed = (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
+ }
+
+ rc = SSMR3PutU32(pSSM, cbCircBufSize);
+ AssertRCReturn(rc, rc);
+
+ rc = SSMR3PutU32(pSSM, cbCircBufUsed);
+ AssertRCReturn(rc, rc);
+
+ if (cbCircBufUsed)
+ {
+ /*
+ * We now need to get the circular buffer's data without actually modifying
+ * the internal read / used offsets -- otherwise we would end up with broken audio
+ * data after saving the state.
+ *
+ * So get the current read offset and serialize the buffer data manually based on that.
+ */
+ size_t const offBuf = RTCircBufOffsetRead(pStream->State.pCircBuf);
+ void *pvBuf;
+ size_t cbBuf;
+ RTCircBufAcquireReadBlock(pStream->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf);
+ Assert(cbBuf);
+ if (cbBuf)
+ {
+ rc = SSMR3PutMem(pSSM, pvBuf, cbBuf);
+ AssertRC(rc);
+ if ( RT_SUCCESS(rc)
+ && cbBuf < cbCircBufUsed)
+ {
+ rc = SSMR3PutMem(pSSM, (uint8_t *)pvBuf - offBuf, cbCircBufUsed - cbBuf);
+ }
+ }
+ RTCircBufReleaseReadBlock(pStream->State.pCircBuf, 0 /* Don't advance read pointer -- see comment above */);
+ }
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n",
+ pStream->u8SD,
+ HDA_STREAM_REG(pThis, LPIB, pStream->u8SD), HDA_STREAM_REG(pThis, CBL, pStream->u8SD), HDA_STREAM_REG(pThis, LVI, pStream->u8SD)));
+
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1);
+#endif
+
+ return rc;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) hdaR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ /* Save Codec nodes states. */
+ hdaCodecSaveState(pThis->pCodec, pSSM);
+
+ /* Save MMIO registers. */
+ SSMR3PutU32(pSSM, RT_ELEMENTS(pThis->au32Regs));
+ SSMR3PutMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+
+ /* Save controller-specifc internals. */
+ SSMR3PutU64(pSSM, pThis->u64WalClk);
+ SSMR3PutU8(pSSM, pThis->u8IRQL);
+
+ /* Save number of streams. */
+ SSMR3PutU32(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]);
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Does required post processing when loading a saved state.
+ *
+ * @param pThis Pointer to HDA state.
+ */
+static int hdaR3LoadExecPost(PHDASTATE pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Enable all previously active streams.
+ */
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++)
+ {
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, i);
+ if (pStream)
+ {
+ int rc2;
+
+ bool fActive = RT_BOOL(HDA_STREAM_REG(pThis, CTL, i) & HDA_SDCTL_RUN);
+ if (fActive)
+ {
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ /* Make sure to also create the async I/O thread before actually enabling the stream. */
+ rc2 = hdaR3StreamAsyncIOCreate(pStream);
+ AssertRC(rc2);
+
+ /* ... and enabling it. */
+ hdaR3StreamAsyncIOEnable(pStream, true /* fEnable */);
+#endif
+ /* Resume the stream's period. */
+ hdaR3StreamPeriodResume(&pStream->State.Period);
+
+ /* (Re-)enable the stream. */
+ rc2 = hdaR3StreamEnable(pStream, true /* fEnable */);
+ AssertRC(rc2);
+
+ /* Add the stream to the device setup. */
+ rc2 = hdaR3AddStream(pThis, &pStream->State.Cfg);
+ AssertRC(rc2);
+
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+ /* (Re-)install the DMA handler. */
+ hdaR3StreamRegisterDMAHandlers(pThis, pStream);
+#endif
+ if (hdaR3StreamTransferIsScheduled(pStream))
+ hdaR3TimerSet(pThis, pStream, hdaR3StreamTransferGetNext(pStream), true /* fForce */);
+
+ /* Also keep track of the currently active streams. */
+ pThis->cStreamsActive++;
+ }
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Handles loading of all saved state versions older than the current one.
+ *
+ * @param pThis Pointer to HDA state.
+ * @param pSSM Pointer to SSM handle.
+ * @param uVersion Saved state version to load.
+ * @param uPass Loading stage to handle.
+ */
+static int hdaR3LoadExecLegacy(PHDASTATE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Load MMIO registers.
+ */
+ uint32_t cRegs;
+ switch (uVersion)
+ {
+ case HDA_SSM_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 (SSMR3HandleRevision(pSSM) >= 71199)
+ {
+ uint32_t uVer = SSMR3HandleVersion(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_SSM_VERSION_2:
+ case HDA_SSM_VERSION_3:
+ cRegs = 112;
+ AssertCompile(RT_ELEMENTS(pThis->au32Regs) >= 112);
+ break;
+
+ /* Since version 4 we store the register count to stay flexible. */
+ case HDA_SSM_VERSION_4:
+ case HDA_SSM_VERSION_5:
+ case HDA_SSM_VERSION_6:
+ rc = SSMR3GetU32(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:
+ LogRel(("HDA: Warning: Unsupported / too new saved state version (%RU32)\n", uVersion));
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ }
+
+ if (cRegs >= RT_ELEMENTS(pThis->au32Regs))
+ {
+ SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+ SSMR3Skip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs)));
+ }
+ else
+ SSMR3GetMem(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));
+
+ /*
+ * 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).
+ */
+#define HDA_SSM_LOAD_BDLE_STATE_PRE_V5(v, x) \
+ { \
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3GetU64(pSSM, &x.Desc.u64BufAddr); /* u64BdleCviAddr */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* u32BdleMaxCvi */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3GetU32(pSSM, &x.State.u32BDLIndex); /* u32BdleCvi */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3GetU32(pSSM, &x.Desc.u32BufSize); /* u32BdleCviLen */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3GetU32(pSSM, &x.State.u32BufOff); /* u32BdleCviPos */ \
+ AssertRCReturn(rc, rc); \
+ bool fIOC; \
+ rc = SSMR3GetBool(pSSM, &fIOC); /* fBdleCviIoc */ \
+ AssertRCReturn(rc, rc); \
+ x.Desc.fFlags = fIOC ? HDA_BDLE_FLAG_IOC : 0; \
+ rc = SSMR3GetU32(pSSM, &x.State.cbBelowFIFOW); /* cbUnderFifoW */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3Skip(pSSM, sizeof(uint8_t) * 256); /* FIFO */ \
+ AssertRCReturn(rc, rc); \
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */ \
+ AssertRCReturn(rc, rc); \
+ }
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ */
+ switch (uVersion)
+ {
+ case HDA_SSM_VERSION_1:
+ case HDA_SSM_VERSION_2:
+ case HDA_SSM_VERSION_3:
+ case HDA_SSM_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! */
+
+ /* Output */
+ PHDASTREAM pStream = &pThis->aStreams[4];
+ rc = hdaR3StreamInit(pStream, 4 /* Stream descriptor, hardcoded */);
+ if (RT_FAILURE(rc))
+ break;
+ HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE);
+ pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex;
+
+ /* Microphone-In */
+ pStream = &pThis->aStreams[2];
+ rc = hdaR3StreamInit(pStream, 2 /* Stream descriptor, hardcoded */);
+ if (RT_FAILURE(rc))
+ break;
+ HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE);
+ pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex;
+
+ /* Line-In */
+ pStream = &pThis->aStreams[0];
+ rc = hdaR3StreamInit(pStream, 0 /* Stream descriptor, hardcoded */);
+ if (RT_FAILURE(rc))
+ break;
+ HDA_SSM_LOAD_BDLE_STATE_PRE_V5(uVersion, pStream->State.BDLE);
+ pStream->State.uCurBDLE = pStream->State.BDLE.State.u32BDLIndex;
+ break;
+ }
+
+#undef HDA_SSM_LOAD_BDLE_STATE_PRE_V5
+
+ default: /* Since v5 we support flexible stream and BDLE counts. */
+ {
+ uint32_t cStreams;
+ rc = SSMR3GetU32(pSSM, &cStreams);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (cStreams > HDA_MAX_STREAMS)
+ cStreams = HDA_MAX_STREAMS; /* Sanity. */
+
+ /* Load stream states. */
+ for (uint32_t i = 0; i < cStreams; i++)
+ {
+ uint8_t uStreamID;
+ rc = SSMR3GetU8(pSSM, &uStreamID);
+ if (RT_FAILURE(rc))
+ break;
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uStreamID);
+ HDASTREAM StreamDummy;
+
+ if (!pStream)
+ {
+ pStream = &StreamDummy;
+ LogRel2(("HDA: Warning: Stream ID=%RU32 not supported, skipping to load ...\n", uStreamID));
+ }
+
+ rc = hdaR3StreamInit(pStream, uStreamID);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Stream #%RU32: Initialization of stream %RU8 failed, rc=%Rrc\n", i, uStreamID, rc));
+ break;
+ }
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ */
+
+ if (uVersion == HDA_SSM_VERSION_5)
+ {
+ /* Get the current BDLE entry and skip the rest. */
+ uint16_t cBDLE;
+
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */
+ AssertRC(rc);
+ rc = SSMR3GetU16(pSSM, &cBDLE); /* cBDLE */
+ AssertRC(rc);
+ rc = SSMR3GetU16(pSSM, &pStream->State.uCurBDLE); /* uCurBDLE */
+ AssertRC(rc);
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */
+ AssertRC(rc);
+
+ uint32_t u32BDLEIndex;
+ for (uint16_t a = 0; a < cBDLE; a++)
+ {
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* Begin marker */
+ AssertRC(rc);
+ rc = SSMR3GetU32(pSSM, &u32BDLEIndex); /* u32BDLIndex */
+ AssertRC(rc);
+
+ /* Does the current BDLE index match the current BDLE to process? */
+ if (u32BDLEIndex == pStream->State.uCurBDLE)
+ {
+ rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.State.cbBelowFIFOW); /* cbBelowFIFOW */
+ AssertRC(rc);
+ rc = SSMR3Skip(pSSM, sizeof(uint8_t) * 256); /* FIFO, deprecated */
+ AssertRC(rc);
+ rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.State.u32BufOff); /* u32BufOff */
+ AssertRC(rc);
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t)); /* End marker */
+ AssertRC(rc);
+ }
+ else /* Skip not current BDLEs. */
+ {
+ rc = SSMR3Skip(pSSM, sizeof(uint32_t) /* cbBelowFIFOW */
+ + sizeof(uint8_t) * 256 /* au8FIFO */
+ + sizeof(uint32_t) /* u32BufOff */
+ + sizeof(uint32_t)); /* End marker */
+ AssertRC(rc);
+ }
+ }
+ }
+ else
+ {
+ rc = SSMR3GetStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE),
+ 0 /* fFlags */, g_aSSMStreamStateFields6, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Get HDABDLEDESC. */
+ uint32_t uMarker;
+ rc = SSMR3GetU32(pSSM, &uMarker); /* Begin marker. */
+ AssertRC(rc);
+ Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */);
+ rc = SSMR3GetU64(pSSM, &pStream->State.BDLE.Desc.u64BufAddr);
+ AssertRC(rc);
+ rc = SSMR3GetU32(pSSM, &pStream->State.BDLE.Desc.u32BufSize);
+ AssertRC(rc);
+ bool fFlags = false;
+ rc = SSMR3GetBool(pSSM, &fFlags); /* Saved states < v7 only stored the IOC as boolean flag. */
+ AssertRC(rc);
+ pStream->State.BDLE.Desc.fFlags = fFlags ? HDA_BDLE_FLAG_IOC : 0;
+ rc = SSMR3GetU32(pSSM, &uMarker); /* End marker. */
+ AssertRC(rc);
+ Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */);
+
+ rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE),
+ 0 /* fFlags */, g_aSSMBDLEStateFields6, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n",
+ uStreamID,
+ HDA_STREAM_REG(pThis, LPIB, uStreamID), HDA_STREAM_REG(pThis, CBL, uStreamID), HDA_STREAM_REG(pThis, LVI, uStreamID)));
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->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 = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ LogRel2(("hdaR3LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass));
+
+ /*
+ * Load Codec nodes states.
+ */
+ int rc = hdaCodecLoadState(pThis->pCodec, 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_SSM_VERSION) /* Handle older saved states? */
+ {
+ rc = hdaR3LoadExecLegacy(pThis, pSSM, uVersion, uPass);
+ if (RT_SUCCESS(rc))
+ rc = hdaR3LoadExecPost(pThis);
+
+ return rc;
+ }
+
+ /*
+ * Load MMIO registers.
+ */
+ uint32_t cRegs;
+ rc = SSMR3GetU32(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)));
+
+ if (cRegs >= RT_ELEMENTS(pThis->au32Regs))
+ {
+ SSMR3GetMem(pSSM, pThis->au32Regs, sizeof(pThis->au32Regs));
+ SSMR3Skip(pSSM, sizeof(uint32_t) * (cRegs - RT_ELEMENTS(pThis->au32Regs)));
+ }
+ else
+ SSMR3GetMem(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 controller-specifc internals.
+ * Don't annoy other team mates (forgot this for state v7).
+ */
+ if ( SSMR3HandleRevision(pSSM) >= 116273
+ || SSMR3HandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 2, 0))
+ {
+ rc = SSMR3GetU64(pSSM, &pThis->u64WalClk);
+ AssertRC(rc);
+
+ rc = SSMR3GetU8(pSSM, &pThis->u8IRQL);
+ AssertRC(rc);
+ }
+
+ /*
+ * Load streams.
+ */
+ uint32_t cStreams;
+ rc = SSMR3GetU32(pSSM, &cStreams);
+ AssertRC(rc);
+
+ if (cStreams > HDA_MAX_STREAMS)
+ cStreams = HDA_MAX_STREAMS; /* Sanity. */
+
+ Log2Func(("cStreams=%RU32\n", cStreams));
+
+ /* Load stream states. */
+ for (uint32_t i = 0; i < cStreams; i++)
+ {
+ uint8_t uStreamID;
+ rc = SSMR3GetU8(pSSM, &uStreamID);
+ AssertRC(rc);
+
+ PHDASTREAM pStream = hdaGetStreamFromSD(pThis, uStreamID);
+ HDASTREAM StreamDummy;
+
+ if (!pStream)
+ {
+ pStream = &StreamDummy;
+ LogRel2(("HDA: Warning: Loading of stream #%RU8 not supported, skipping to load ...\n", uStreamID));
+ }
+
+ rc = hdaR3StreamInit(pStream, uStreamID);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Stream #%RU8: Loading initialization failed, rc=%Rrc\n", uStreamID, rc));
+ /* Continue. */
+ }
+
+ rc = SSMR3GetStructEx(pSSM, &pStream->State, sizeof(HDASTREAMSTATE),
+ 0 /* fFlags */, g_aSSMStreamStateFields7, NULL);
+ AssertRC(rc);
+
+ /*
+ * Load BDLEs (Buffer Descriptor List Entries) and DMA counters.
+ */
+ rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.Desc, sizeof(HDABDLEDESC),
+ 0 /* fFlags */, g_aSSMBDLEDescFields7, NULL);
+ AssertRC(rc);
+
+ rc = SSMR3GetStructEx(pSSM, &pStream->State.BDLE.State, sizeof(HDABDLESTATE),
+ 0 /* fFlags */, g_aSSMBDLEStateFields7, NULL);
+ AssertRC(rc);
+
+ Log2Func(("[SD%RU8] %R[bdle]\n", pStream->u8SD, &pStream->State.BDLE));
+
+ /*
+ * Load period state.
+ */
+ hdaR3StreamPeriodInit(&pStream->State.Period,
+ pStream->u8SD, pStream->u16LVI, pStream->u32CBL, &pStream->State.Cfg);
+
+ rc = SSMR3GetStructEx(pSSM, &pStream->State.Period, sizeof(HDASTREAMPERIOD),
+ 0 /* fFlags */, g_aSSMStreamPeriodFields7, NULL);
+ AssertRC(rc);
+
+ /*
+ * Load internal (FIFO) buffer.
+ */
+ uint32_t cbCircBufSize = 0;
+ rc = SSMR3GetU32(pSSM, &cbCircBufSize); /* cbCircBuf */
+ AssertRC(rc);
+
+ uint32_t cbCircBufUsed = 0;
+ rc = SSMR3GetU32(pSSM, &cbCircBufUsed); /* cbCircBuf */
+ AssertRC(rc);
+
+ if (cbCircBufSize) /* If 0, skip the buffer. */
+ {
+ /* Paranoia. */
+ AssertReleaseMsg(cbCircBufSize <= _1M,
+ ("HDA: Saved state contains bogus DMA buffer size (%RU32) for stream #%RU8",
+ cbCircBufSize, uStreamID));
+ AssertReleaseMsg(cbCircBufUsed <= cbCircBufSize,
+ ("HDA: Saved state contains invalid DMA buffer usage (%RU32/%RU32) for stream #%RU8",
+ cbCircBufUsed, cbCircBufSize, uStreamID));
+
+ /* Do we need to cre-create the circular buffer do fit the data size? */
+ if ( pStream->State.pCircBuf
+ && cbCircBufSize != (uint32_t)RTCircBufSize(pStream->State.pCircBuf))
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ rc = RTCircBufCreate(&pStream->State.pCircBuf, cbCircBufSize);
+ AssertRC(rc);
+
+ if ( RT_SUCCESS(rc)
+ && cbCircBufUsed)
+ {
+ void *pvBuf;
+ size_t cbBuf;
+
+ RTCircBufAcquireWriteBlock(pStream->State.pCircBuf, cbCircBufUsed, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ {
+ rc = SSMR3GetMem(pSSM, pvBuf, cbBuf);
+ AssertRC(rc);
+ }
+
+ RTCircBufReleaseWriteBlock(pStream->State.pCircBuf, cbBuf);
+
+ Assert(cbBuf == cbCircBufUsed);
+ }
+ }
+
+ Log2Func(("[SD%RU8] LPIB=%RU32, CBL=%RU32, LVI=%RU32\n",
+ uStreamID,
+ HDA_STREAM_REG(pThis, LPIB, uStreamID), HDA_STREAM_REG(pThis, CBL, uStreamID), HDA_STREAM_REG(pThis, LVI, uStreamID)));
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1);
+#endif
+ /** @todo (Re-)initialize active periods? */
+
+ } /* for cStreams */
+
+ rc = hdaR3LoadExecPost(pThis);
+ AssertRC(rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/* IPRT format type handlers. */
+
+/**
+ * @callback_method_impl{FNRTSTRFORMATTYPE}
+ */
+static DECLCALLBACK(size_t) hdaR3StrFmtBDLE(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);
+ PHDABDLE pBDLE = (PHDABDLE)pvValue;
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "BDLE(idx:%RU32, off:%RU32, fifow:%RU32, IOC:%RTbool, DMA[%RU32 bytes @ 0x%x])",
+ pBDLE->State.u32BDLIndex, pBDLE->State.u32BufOff, pBDLE->State.cbBelowFIFOW,
+ pBDLE->Desc.fFlags & HDA_BDLE_FLAG_IOC, pBDLE->Desc.u32BufSize, pBDLE->Desc.u64BufAddr);
+}
+
+/**
+ * @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 dumpers */
+
+static int hdaR3DbgLookupRegByName(const char *pszArgs)
+{
+ int iReg = 0;
+ for (; iReg < HDA_NUM_REGS; ++iReg)
+ if (!RTStrICmp(g_aHdaRegMap[iReg].abbrev, pszArgs))
+ return iReg;
+ return -1;
+}
+
+
+static void hdaR3DbgPrintRegister(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iHdaIndex)
+{
+ Assert( pThis
+ && iHdaIndex >= 0
+ && iHdaIndex < HDA_NUM_REGS);
+ pHlp->pfnPrintf(pHlp, "%s: 0x%x\n", g_aHdaRegMap[iHdaIndex].abbrev, pThis->au32Regs[g_aHdaRegMap[iHdaIndex].mem_idx]);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfo(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ int iHdaRegisterIndex = hdaR3DbgLookupRegByName(pszArgs);
+ if (iHdaRegisterIndex != -1)
+ hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex);
+ else
+ {
+ for(iHdaRegisterIndex = 0; (unsigned int)iHdaRegisterIndex < HDA_NUM_REGS; ++iHdaRegisterIndex)
+ hdaR3DbgPrintRegister(pThis, pHlp, iHdaRegisterIndex);
+ }
+}
+
+static void hdaR3DbgPrintStream(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx)
+{
+ Assert( pThis
+ && iIdx >= 0
+ && iIdx < HDA_MAX_STREAMS);
+
+ const PHDASTREAM pStream = &pThis->aStreams[iIdx];
+
+ pHlp->pfnPrintf(pHlp, "Stream #%d:\n", iIdx);
+ pHlp->pfnPrintf(pHlp, "\tSD%dCTL : %R[sdctl]\n", iIdx, HDA_STREAM_REG(pThis, CTL, iIdx));
+ pHlp->pfnPrintf(pHlp, "\tSD%dCTS : %R[sdsts]\n", iIdx, HDA_STREAM_REG(pThis, STS, iIdx));
+ pHlp->pfnPrintf(pHlp, "\tSD%dFIFOS: %R[sdfifos]\n", iIdx, HDA_STREAM_REG(pThis, FIFOS, iIdx));
+ pHlp->pfnPrintf(pHlp, "\tSD%dFIFOW: %R[sdfifow]\n", iIdx, HDA_STREAM_REG(pThis, FIFOW, iIdx));
+ pHlp->pfnPrintf(pHlp, "\tBDLE : %R[bdle]\n", &pStream->State.BDLE);
+}
+
+static void hdaR3DbgPrintBDLE(PHDASTATE pThis, PCDBGFINFOHLP pHlp, int iIdx)
+{
+ Assert( pThis
+ && iIdx >= 0
+ && iIdx < HDA_MAX_STREAMS);
+
+ const PHDASTREAM pStream = &pThis->aStreams[iIdx];
+ const PHDABDLE pBDLE = &pStream->State.BDLE;
+
+ pHlp->pfnPrintf(pHlp, "Stream #%d BDLE:\n", iIdx);
+
+ uint64_t u64BaseDMA = RT_MAKE_U64(HDA_STREAM_REG(pThis, BDPL, iIdx),
+ HDA_STREAM_REG(pThis, BDPU, iIdx));
+ uint16_t u16LVI = HDA_STREAM_REG(pThis, LVI, iIdx);
+ uint32_t u32CBL = HDA_STREAM_REG(pThis, CBL, iIdx);
+
+ if (!u64BaseDMA)
+ return;
+
+ pHlp->pfnPrintf(pHlp, "\tCurrent: %R[bdle]\n\n", pBDLE);
+
+ pHlp->pfnPrintf(pHlp, "\tMemory:\n");
+
+ uint32_t cbBDLE = 0;
+ for (uint16_t i = 0; i < u16LVI + 1; i++)
+ {
+ HDABDLEDESC bd;
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BaseDMA + i * sizeof(HDABDLEDESC), &bd, sizeof(bd));
+
+ pHlp->pfnPrintf(pHlp, "\t\t%s #%03d BDLE(adr:0x%llx, size:%RU32, ioc:%RTbool)\n",
+ pBDLE->State.u32BDLIndex == i ? "*" : " ", i, bd.u64BufAddr, bd.u32BufSize, bd.fFlags & HDA_BDLE_FLAG_IOC);
+
+ cbBDLE += bd.u32BufSize;
+ }
+
+ pHlp->pfnPrintf(pHlp, "Total: %RU32 bytes\n", cbBDLE);
+
+ if (cbBDLE != u32CBL)
+ pHlp->pfnPrintf(pHlp, "Warning: %RU32 bytes does not match CBL (%RU32)!\n", cbBDLE, u32CBL);
+
+ pHlp->pfnPrintf(pHlp, "DMA counters (base @ 0x%llx):\n", u64BaseDMA);
+ if (!u64BaseDMA) /* No DMA base given? Bail out. */
+ {
+ pHlp->pfnPrintf(pHlp, "\tNo counters found\n");
+ return;
+ }
+
+ for (int i = 0; i < u16LVI + 1; i++)
+ {
+ uint32_t uDMACnt;
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), (pThis->u64DPBase & DPBASE_ADDR_MASK) + (i * 2 * sizeof(uint32_t)),
+ &uDMACnt, sizeof(uDMACnt));
+
+ pHlp->pfnPrintf(pHlp, "\t#%03d DMA @ 0x%x\n", i , uDMACnt);
+ }
+}
+
+static int hdaR3DbgLookupStrmIdx(PHDASTATE pThis, const char *pszArgs)
+{
+ RT_NOREF(pThis, pszArgs);
+ /** @todo Add args parsing. */
+ return -1;
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoStream(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs);
+ if (iHdaStreamdex != -1)
+ hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex);
+ else
+ for(iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex)
+ hdaR3DbgPrintStream(pThis, pHlp, iHdaStreamdex);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoBDLE(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ int iHdaStreamdex = hdaR3DbgLookupStrmIdx(pThis, pszArgs);
+ if (iHdaStreamdex != -1)
+ hdaR3DbgPrintBDLE(pThis, pHlp, iHdaStreamdex);
+ else
+ for (iHdaStreamdex = 0; iHdaStreamdex < HDA_MAX_STREAMS; ++iHdaStreamdex)
+ hdaR3DbgPrintBDLE(pThis, pHlp, iHdaStreamdex);
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoCodecNodes(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ if (pThis->pCodec->pfnDbgListNodes)
+ pThis->pCodec->pfnDbgListNodes(pThis->pCodec, pHlp, pszArgs);
+ else
+ pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n");
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoCodecSelector(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ if (pThis->pCodec->pfnDbgSelector)
+ pThis->pCodec->pfnDbgSelector(pThis->pCodec, pHlp, pszArgs);
+ else
+ pHlp->pfnPrintf(pHlp, "Codec implementation doesn't provide corresponding callback\n");
+}
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV}
+ */
+static DECLCALLBACK(void) hdaR3DbgInfoMixer(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ if (pThis->pMixer)
+ AudioMixerDebug(pThis->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)
+{
+ PHDASTATE pThis = RT_FROM_MEMBER(pInterface, HDASTATE, IBase);
+ Assert(&pThis->IBase == pInterface);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ return NULL;
+}
+
+
+/* PDMDEVREG */
+
+/**
+ * Attach command, internal version.
+ *
+ * This is called to let the device attach to a driver for a specified LUN
+ * during runtime. This is not called during VM construction, the device
+ * constructor has to attach to all the available drivers.
+ *
+ * @returns VBox status code.
+ * @param pThis HDA state.
+ * @param uLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int hdaR3AttachInternal(PHDASTATE pThis, unsigned uLUN, uint32_t fFlags, PHDADRIVER *ppDrv)
+{
+ RT_NOREF(fFlags);
+
+ /*
+ * Attach driver.
+ */
+ char *pszDesc;
+ if (RTStrAPrintf(&pszDesc, "Audio driver port (HDA) for LUN#%u", uLUN) <= 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN,
+ &pThis->IBase, &pDrvBase, pszDesc);
+ if (RT_SUCCESS(rc))
+ {
+ PHDADRIVER pDrv = (PHDADRIVER)RTMemAllocZ(sizeof(HDADRIVER));
+ if (pDrv)
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN#%u has no host audio interface, rc=%Rrc\n", uLUN, rc));
+ pDrv->pHDAState = pThis;
+ pDrv->uLUN = uLUN;
+
+ /*
+ * For now we always set the driver at LUN 0 as our primary
+ * host backend. This might change in the future.
+ */
+ if (pDrv->uLUN == 0)
+ pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY;
+
+ LogFunc(("LUN#%u: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags));
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThis->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+
+ if (RT_FAILURE(rc))
+ {
+ /* Only free this string on failure;
+ * must remain valid for the live of the driver instance. */
+ RTStrFree(pszDesc);
+ }
+
+ LogFunc(("uLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc));
+ return rc;
+}
+
+/**
+ * Detach command, internal version.
+ *
+ * This is called to let the device detach from a driver for a specified LUN
+ * during runtime.
+ *
+ * @returns VBox status code.
+ * @param pThis HDA state.
+ * @param pDrv Driver to detach from device.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static int hdaR3DetachInternal(PHDASTATE pThis, PHDADRIVER pDrv, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ /* First, 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(pThis, pDrv);
+
+ /* Next, search backwards for a capable (attached) driver which now will be the
+ * new recording source. */
+ PHDADRIVER pDrvCur;
+ RTListForEachReverse(&pThis->lstDrv, pDrvCur, HDADRIVER, Node)
+ {
+ if (!pDrvCur->pConnector)
+ continue;
+
+ PDMAUDIOBACKENDCFG Cfg;
+ int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg);
+ if (RT_FAILURE(rc2))
+ continue;
+
+ PHDADRIVERSTREAM pDrvStrm;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pDrvStrm = &pDrvCur->MicIn;
+ if ( pDrvStrm
+ && pDrvStrm->pMixStrm)
+ {
+ rc2 = AudioMixerSinkSetRecordingSource(pThis->SinkMicIn.pMixSink, pDrvStrm->pMixStrm);
+ if (RT_SUCCESS(rc2))
+ LogRel2(("HDA: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName));
+ }
+# endif
+ pDrvStrm = &pDrvCur->LineIn;
+ if ( pDrvStrm
+ && pDrvStrm->pMixStrm)
+ {
+ rc2 = AudioMixerSinkSetRecordingSource(pThis->SinkLineIn.pMixSink, pDrvStrm->pMixStrm);
+ if (RT_SUCCESS(rc2))
+ LogRel2(("HDA: Set new recording source for 'Line In' to '%s'\n", Cfg.szName));
+ }
+ }
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) hdaR3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ DEVHDA_LOCK_RETURN(pThis, VERR_IGNORED);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ PHDADRIVER pDrv;
+ int rc2 = hdaR3AttachInternal(pThis, uLUN, fFlags, &pDrv);
+ if (RT_SUCCESS(rc2))
+ rc2 = hdaR3MixerAddDrv(pThis, pDrv);
+
+ if (RT_FAILURE(rc2))
+ LogFunc(("Failed with %Rrc\n", rc2));
+
+ DEVHDA_UNLOCK(pThis);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) hdaR3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ DEVHDA_LOCK(pThis);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ PHDADRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, HDADRIVER, Node)
+ {
+ if (pDrv->uLUN == uLUN)
+ {
+ int rc2 = hdaR3DetachInternal(pThis, pDrv, fFlags);
+ if (RT_SUCCESS(rc2))
+ {
+ RTMemFree(pDrv);
+ pDrv = NULL;
+ }
+
+ break;
+ }
+ }
+
+ DEVHDA_UNLOCK(pThis);
+}
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) hdaR3PowerOff(PPDMDEVINS pDevIns)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ DEVHDA_LOCK_RETURN_VOID(pThis);
+
+ LogRel2(("HDA: Powering off ...\n"));
+
+ /* Ditto goes for the codec, which in turn uses the mixer. */
+ hdaCodecPowerOff(pThis->pCodec);
+
+ /*
+ * 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 (pThis->pMixer)
+ {
+ AudioMixerDestroy(pThis->pMixer);
+ pThis->pMixer = NULL;
+ }
+
+ DEVHDA_UNLOCK(pThis);
+}
+
+
+/**
+ * Re-attaches (replaces) a driver with a new driver.
+ *
+ * This is only used by to attach the Null driver when it failed to attach the
+ * one that was configured.
+ *
+ * @returns VBox status code.
+ * @param pThis Device instance to re-attach driver to.
+ * @param pDrv Driver instance used for attaching to.
+ * If NULL is specified, a new driver will be created and appended
+ * to the driver list.
+ * @param uLUN The logical unit which is being re-detached.
+ * @param pszDriver New driver name to attach.
+ */
+static int hdaR3ReattachInternal(PHDASTATE pThis, PHDADRIVER pDrv, uint8_t uLUN, const char *pszDriver)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDriver, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pDrv)
+ {
+ rc = hdaR3DetachInternal(pThis, pDrv, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pDrv = NULL;
+ }
+
+ PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3);
+ PCFGMNODE pRoot = CFGMR3GetRoot(pVM);
+ PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/hda/0/");
+
+ /* Remove LUN branch. */
+ CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN));
+
+#define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; }
+
+ do
+ {
+ PCFGMNODE pLunL0;
+ rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK();
+ rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK();
+
+ PCFGMNODE pLunL1, pLunL2;
+ rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK();
+ rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK();
+
+ rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK();
+
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ rc = hdaR3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+
+ LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc));
+
+#undef RC_CHECK
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) hdaR3Reset(PPDMDEVINS pDevIns)
+{
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+
+ LogFlowFuncEnter();
+
+ DEVHDA_LOCK_RETURN_VOID(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(pThis);
+
+ /* 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(pThis);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnRelocate}
+ */
+static DECLCALLBACK(void) hdaR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ NOREF(offDelta);
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) hdaR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PHDASTATE pThis = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ DEVHDA_LOCK(pThis); /** @todo r=bird: this will fail on early constructor failure. */
+
+ PHDADRIVER pDrv;
+ while (!RTListIsEmpty(&pThis->lstDrv))
+ {
+ pDrv = RTListGetFirst(&pThis->lstDrv, HDADRIVER, Node);
+
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ if (pThis->pCodec)
+ {
+ hdaCodecDestruct(pThis->pCodec);
+
+ RTMemFree(pThis->pCodec);
+ pThis->pCodec = NULL;
+ }
+
+ RTMemFree(pThis->pu32CorbBuf);
+ pThis->pu32CorbBuf = NULL;
+
+ RTMemFree(pThis->pu64RirbBuf);
+ pThis->pu64RirbBuf = NULL;
+
+ for (uint8_t i = 0; i < HDA_MAX_STREAMS; i++)
+ hdaR3StreamDestroy(&pThis->aStreams[i]);
+
+ DEVHDA_UNLOCK(pThis);
+ 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 = PDMINS_2_DATA(pDevIns, PHDASTATE);
+ Assert(iInstance == 0); RT_NOREF(iInstance);
+
+ /*
+ * Initialize the state sufficently to make the destructor work.
+ */
+ pThis->uAlignmentCheckMagic = HDASTATE_ALIGNMENT_CHECK_MAGIC;
+ RTListInit(&pThis->lstDrv);
+ /** @todo r=bird: There are probably other things which should be
+ * initialized here before we start failing. */
+
+ /*
+ * Validations.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "RZEnabled\0"
+ "TimerHz\0"
+ "PosAdjustEnabled\0"
+ "PosAdjustFrames\0"
+ "DebugEnabled\0"
+ "DebugPathOut\0"))
+ {
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_ ("Invalid configuration for the Intel HDA device"));
+ }
+
+ int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read RCEnabled as boolean"));
+
+
+ rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, HDA_TIMER_HZ_DEFAULT /* Default value, if not set. */);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read Hertz (Hz) rate as unsigned integer"));
+
+ if (pThis->uTimerHz != HDA_TIMER_HZ_DEFAULT)
+ LogRel(("HDA: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "PosAdjustEnabled", &pThis->fPosAdjustEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read position adjustment enabled as boolean"));
+
+ if (!pThis->fPosAdjustEnabled)
+ LogRel(("HDA: Position adjustment is disabled\n"));
+
+ rc = CFGMR3QueryU16Def(pCfg, "PosAdjustFrames", &pThis->cPosAdjustFrames, HDA_POS_ADJUST_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read position adjustment frames as unsigned integer"));
+
+ if (pThis->cPosAdjustFrames)
+ LogRel(("HDA: Using custom position adjustment (%RU16 audio frames)\n", pThis->cPosAdjustFrames));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->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 = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath),
+ VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("HDA configuration error: failed to read debugging output path flag as string"));
+
+ if (!strlen(pThis->Dbg.szOutPath))
+ RTStrPrintf(pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+
+ if (pThis->Dbg.fEnabled)
+ LogRel2(("HDA: Debug output will be saved to '%s'\n", pThis->Dbg.szOutPath));
+
+ /*
+ * 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, "HDA");
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Initialize data (most of it anyway).
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ /* IBase */
+ pThis->IBase.pfnQueryInterface = hdaR3QueryInterface;
+
+ /* PCI Device */
+ PCIDevSetVendorId (&pThis->PciDev, HDA_PCI_VENDOR_ID); /* nVidia */
+ PCIDevSetDeviceId (&pThis->PciDev, HDA_PCI_DEVICE_ID); /* HDA */
+
+ PCIDevSetCommand (&pThis->PciDev, 0x0000); /* 04 rw,ro - pcicmd. */
+ PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST); /* 06 rwc?,ro? - pcists. */
+ PCIDevSetRevisionId (&pThis->PciDev, 0x01); /* 08 ro - rid. */
+ PCIDevSetClassProg (&pThis->PciDev, 0x00); /* 09 ro - pi. */
+ PCIDevSetClassSub (&pThis->PciDev, 0x03); /* 0a ro - scc; 03 == HDA. */
+ PCIDevSetClassBase (&pThis->PciDev, 0x04); /* 0b ro - bcc; 04 == multimedia. */
+ PCIDevSetHeaderType (&pThis->PciDev, 0x00); /* 0e ro - headtyp. */
+ PCIDevSetBaseAddress (&pThis->PciDev, 0, /* 10 rw - MMIO */
+ false /* fIoSpace */, false /* fPrefetchable */, true /* f64Bit */, 0x00000000);
+ PCIDevSetInterruptLine (&pThis->PciDev, 0x00); /* 3c rw. */
+ PCIDevSetInterruptPin (&pThis->PciDev, 0x01); /* 3d ro - INTA#. */
+
+#if defined(HDA_AS_PCI_EXPRESS)
+ PCIDevSetCapabilityList (&pThis->PciDev, 0x80);
+#elif defined(VBOX_WITH_MSI_DEVICES)
+ PCIDevSetCapabilityList (&pThis->PciDev, 0x60);
+#else
+ PCIDevSetCapabilityList (&pThis->PciDev, 0x50); /* ICH6 datasheet 18.1.16 */
+#endif
+
+ /// @todo r=michaln: If there are really no PCIDevSetXx 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 */
+ PCIDevSetByte(&pThis->PciDev, 0x40, 0x01);
+
+ /* Power Management */
+ PCIDevSetByte(&pThis->PciDev, 0x50 + 0, VBOX_PCI_CAP_ID_PM);
+ PCIDevSetByte(&pThis->PciDev, 0x50 + 1, 0x0); /* next */
+ PCIDevSetWord(&pThis->PciDev, 0x50 + 2, VBOX_PCI_PM_CAP_DSI | 0x02 /* version, PM1.1 */ );
+
+#ifdef HDA_AS_PCI_EXPRESS
+ /* PCI Express */
+ PCIDevSetByte(&pThis->PciDev, 0x80 + 0, VBOX_PCI_CAP_ID_EXP); /* PCI_Express */
+ PCIDevSetByte(&pThis->PciDev, 0x80 + 1, 0x60); /* next */
+ /* Device flags */
+ PCIDevSetWord(&pThis->PciDev, 0x80 + 2,
+ /* version */ 0x1 |
+ /* Root Complex Integrated Endpoint */ (VBOX_PCI_EXP_TYPE_ROOT_INT_EP << 4) |
+ /* MSI */ (100) << 9 );
+ /* Device capabilities */
+ PCIDevSetDWord(&pThis->PciDev, 0x80 + 4, VBOX_PCI_EXP_DEVCAP_FLRESET);
+ /* Device control */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 8, 0);
+ /* Device status */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 10, 0);
+ /* Link caps */
+ PCIDevSetDWord(&pThis->PciDev, 0x80 + 12, 0);
+ /* Link control */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 16, 0);
+ /* Link status */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 18, 0);
+ /* Slot capabilities */
+ PCIDevSetDWord(&pThis->PciDev, 0x80 + 20, 0);
+ /* Slot control */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 24, 0);
+ /* Slot status */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 26, 0);
+ /* Root control */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 28, 0);
+ /* Root capabilities */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 30, 0);
+ /* Root status */
+ PCIDevSetDWord(&pThis->PciDev, 0x80 + 32, 0);
+ /* Device capabilities 2 */
+ PCIDevSetDWord(&pThis->PciDev, 0x80 + 36, 0);
+ /* Device control 2 */
+ PCIDevSetQWord(&pThis->PciDev, 0x80 + 40, 0);
+ /* Link control 2 */
+ PCIDevSetQWord(&pThis->PciDev, 0x80 + 48, 0);
+ /* Slot control 2 */
+ PCIDevSetWord( &pThis->PciDev, 0x80 + 56, 0);
+#endif
+
+ /*
+ * Register the PCI device.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 0x4000, PCI_ADDRESS_SPACE_MEM, hdaR3PciIoRegionMap);
+ if (RT_FAILURE(rc))
+ return 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 */
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x50);
+ }
+#endif
+
+ rc = PDMDevHlpSSMRegister(pDevIns, HDA_SSM_VERSION, sizeof(*pThis), hdaR3SaveExec, hdaR3LoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ LogRel(("HDA: Asynchronous I/O enabled\n"));
+#endif
+
+ uint8_t uLUN;
+ for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN)
+ {
+ LogFunc(("Trying to attach driver for LUN #%RU32 ...\n", uLUN));
+ rc = hdaR3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED)
+ {
+ hdaR3ReattachInternal(pThis, NULL /* pDrv */, uLUN, "NullAudio");
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Host audio backend initialization has failed. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ /* Attaching to the NULL audio backend will never fail. */
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+
+ LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerCreate("HDA Mixer", 0 /* uFlags */, &pThis->pMixer);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add mixer output sinks.
+ */
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Front",
+ AUDMIXSINKDIR_OUTPUT, &pThis->SinkFront.pMixSink);
+ AssertRC(rc);
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Center / Subwoofer",
+ AUDMIXSINKDIR_OUTPUT, &pThis->SinkCenterLFE.pMixSink);
+ AssertRC(rc);
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] Rear",
+ AUDMIXSINKDIR_OUTPUT, &pThis->SinkRear.pMixSink);
+ AssertRC(rc);
+#else
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] PCM Output",
+ AUDMIXSINKDIR_OUTPUT, &pThis->SinkFront.pMixSink);
+ AssertRC(rc);
+#endif
+ /*
+ * Add mixer input sinks.
+ */
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Line In",
+ AUDMIXSINKDIR_INPUT, &pThis->SinkLineIn.pMixSink);
+ AssertRC(rc);
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Microphone In",
+ AUDMIXSINKDIR_INPUT, &pThis->SinkMicIn.pMixSink);
+ AssertRC(rc);
+#endif
+ /* There is no master volume control. Set the master to max. */
+ PDMAUDIOVOLUME vol = { false, 255, 255 };
+ rc = AudioMixerSetMasterVolume(pThis->pMixer, &vol);
+ AssertRC(rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Allocate CORB buffer. */
+ pThis->cbCorbBuf = HDA_CORB_SIZE * HDA_CORB_ELEMENT_SIZE;
+ pThis->pu32CorbBuf = (uint32_t *)RTMemAllocZ(pThis->cbCorbBuf);
+ if (pThis->pu32CorbBuf)
+ {
+ /* Allocate RIRB buffer. */
+ pThis->cbRirbBuf = HDA_RIRB_SIZE * HDA_RIRB_ELEMENT_SIZE;
+ pThis->pu64RirbBuf = (uint64_t *)RTMemAllocZ(pThis->cbRirbBuf);
+ if (pThis->pu64RirbBuf)
+ {
+ /* Allocate codec. */
+ pThis->pCodec = (PHDACODEC)RTMemAllocZ(sizeof(HDACODEC));
+ if (!pThis->pCodec)
+ rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating HDA codec state"));
+ }
+ else
+ rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating RIRB"));
+ }
+ else
+ rc = PDMDEV_SET_ERROR(pDevIns, VERR_NO_MEMORY, N_("Out of memory allocating CORB"));
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Set codec callbacks to this controller. */
+ pThis->pCodec->pfnCbMixerAddStream = hdaR3MixerAddStream;
+ pThis->pCodec->pfnCbMixerRemoveStream = hdaR3MixerRemoveStream;
+ pThis->pCodec->pfnCbMixerControl = hdaR3MixerControl;
+ pThis->pCodec->pfnCbMixerSetVolume = hdaR3MixerSetVolume;
+
+ pThis->pCodec->pHDAState = pThis; /* Assign HDA controller state to codec. */
+
+ /* Construct the codec. */
+ rc = hdaCodecConstruct(pDevIns, pThis->pCodec, 0 /* Codec index */, pCfg);
+ if (RT_FAILURE(rc))
+ 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(pThis->pCodec->u16VendorId);
+ Assert(pThis->pCodec->u16DeviceId);
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, pThis->pCodec->u16VendorId); /* 2c ro - intel.) */
+ PCIDevSetSubSystemId( &pThis->PciDev, pThis->pCodec->u16DeviceId); /* 2e ro. */
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create all hardware streams.
+ */
+ 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 (uint8_t i = 0; i < HDA_MAX_STREAMS; ++i)
+ {
+ /* Create the emulation timer (per stream).
+ *
+ * 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.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, hdaR3Timer, &pThis->aStreams[i],
+ TMTIMER_FLAGS_NO_CRIT_SECT, s_apszNames[i], &pThis->pTimer[i]);
+ AssertRCReturn(rc, rc);
+
+ /* Use our own critcal section for the device timer.
+ * That way we can control more fine-grained when to lock what. */
+ rc = TMR3TimerSetCritSect(pThis->pTimer[i], &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = hdaR3StreamCreate(&pThis->aStreams[i], pThis, i /* u8SD */);
+ AssertRC(rc);
+ }
+
+#ifdef VBOX_WITH_AUDIO_HDA_ONETIME_INIT
+ /*
+ * Initialize the driver chain.
+ */
+ PHDADRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, HDADRIVER, Node)
+ {
+ /*
+ * Only primary drivers are critical for the VM to run. Everything else
+ * might not worth showing an own error message box in the GUI.
+ */
+ if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY))
+ continue;
+
+ PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector;
+ AssertPtr(pCon);
+
+ bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm);
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm);
+# endif
+ bool fValidOut = AudioMixerStreamIsValid(pDrv->Front.pMixStrm);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ /** @todo Anything to do here? */
+# endif
+
+ if ( !fValidLineIn
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ && !fValidMicIn
+# endif
+ && !fValidOut)
+ {
+ LogRel(("HDA: Falling back to NULL backend (no sound audible)\n"));
+
+ hdaR3Reset(pDevIns);
+ hdaR3ReattachInternal(pThis, pDrv, pDrv->uLUN, "NullAudio");
+
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("No audio devices could be opened. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ }
+ else
+ {
+ bool fWarn = false;
+
+ PDMAUDIOBACKENDCFG backendCfg;
+ int rc2 = pCon->pfnGetConfig(pCon, &backendCfg);
+ if (RT_SUCCESS(rc2))
+ {
+ if (backendCfg.cMaxStreamsIn)
+ {
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ /* If the audio backend supports two or more input streams at once,
+ * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */
+ if (backendCfg.cMaxStreamsIn >= 2)
+ fWarn = !fValidLineIn || !fValidMicIn;
+ /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and
+ * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize.
+ * One of the two simply is not in use then. */
+ else if (backendCfg.cMaxStreamsIn == 1)
+ fWarn = !fValidLineIn && !fValidMicIn;
+ /* Don't warn if our backend is not able of supporting any input streams at all. */
+# else /* !VBOX_WITH_AUDIO_HDA_MIC_IN */
+ /* We only have line-in as input source. */
+ fWarn = !fValidLineIn;
+# endif /* VBOX_WITH_AUDIO_HDA_MIC_IN */
+ }
+
+ if ( !fWarn
+ && backendCfg.cMaxStreamsOut)
+ {
+ fWarn = !fValidOut;
+ }
+ }
+ else
+ {
+ LogRel(("HDA: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2));
+ fWarn = true;
+ }
+
+ if (fWarn)
+ {
+ char szMissingStreams[255];
+ size_t len = 0;
+ if (!fValidLineIn)
+ {
+ LogRel(("HDA: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN));
+ len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input");
+ }
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ if (!fValidMicIn)
+ {
+ LogRel(("HDA: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN));
+ len += RTStrPrintf(szMissingStreams + len,
+ sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone");
+ }
+# endif /* VBOX_WITH_AUDIO_HDA_MIC_IN */
+ if (!fValidOut)
+ {
+ LogRel(("HDA: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN));
+ len += RTStrPrintf(szMissingStreams + len,
+ sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output");
+ }
+
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Some HDA audio streams (%s) could not be opened. Guest applications generating audio "
+ "output or depending on audio input may hang. Make sure your host audio device "
+ "is working properly. Check the logfile for error messages of the audio "
+ "subsystem"), szMissingStreams);
+ }
+ }
+ }
+#endif /* VBOX_WITH_AUDIO_HDA_ONETIME_INIT */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ hdaR3Reset(pDevIns);
+
+ /*
+ * Debug and string formatter types.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hda", "HDA info. (hda [register case-insensitive])", hdaR3DbgInfo);
+ PDMDevHlpDBGFInfoRegister(pDevIns, "hdabdle", "HDA stream BDLE info. (hdabdle [stream number])", hdaR3DbgInfoBDLE);
+ 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("bdle", hdaR3StrFmtBDLE, NULL);
+ AssertRC(rc);
+ rc = RTStrFormatTypeRegister("sdctl", hdaR3StrFmtSDCTL, NULL);
+ AssertRC(rc);
+ rc = RTStrFormatTypeRegister("sdsts", hdaR3StrFmtSDSTS, NULL);
+ AssertRC(rc);
+ rc = RTStrFormatTypeRegister("sdfifos", hdaR3StrFmtSDFIFOS, NULL);
+ AssertRC(rc);
+ rc = RTStrFormatTypeRegister("sdfifow", hdaR3StrFmtSDFIFOW, NULL);
+ AssertRC(rc);
+
+ /*
+ * Some debug assertions.
+ */
+ 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->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));
+
+ /* alignment. */
+ AssertReleaseMsg( pReg->size == 1
+ || (pReg->size == 2 && (pReg->offset & 1) == 0)
+ || (pReg->size == 3 && (pReg->offset & 3) == 0)
+ || (pReg->size == 4 && (pReg->offset & 3) == 0),
+ ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size));
+
+ /* registers are packed into dwords - with 3 exceptions with gaps at the end of the dword. */
+ AssertRelease(((pReg->offset + pReg->size) & 3) == 0 || pNextReg);
+ if (pReg->offset & 3)
+ {
+ struct HDAREGDESC const *pPrevReg = i > 0 ? &g_aHdaRegMap[i - 1] : NULL;
+ AssertReleaseMsg(pPrevReg, ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size));
+ if (pPrevReg)
+ AssertReleaseMsg(pPrevReg->offset + pPrevReg->size == pReg->offset,
+ ("[%#x] = {%#x LB %#x} vs. [%#x] = {%#x LB %#x}\n",
+ i - 1, pPrevReg->offset, pPrevReg->size, i + 1, pReg->offset, pReg->size));
+ }
+#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->offset + pReg->size) & 3) == 0,
+ ("[%#x] = {%#x LB %#x}\n", i, pReg->offset, pReg->size));
+ }
+ }
+
+# ifdef VBOX_WITH_STATISTICS
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "/Devices/HDA/Timer", STAMUNIT_TICKS_PER_CALL, "Profiling hdaR3Timer.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "/Devices/HDA/Input", STAMUNIT_TICKS_PER_CALL, "Profiling input.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "/Devices/HDA/Output", STAMUNIT_TICKS_PER_CALL, "Profiling output.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "/Devices/HDA/BytesRead" , STAMUNIT_BYTES, "Bytes read from HDA emulation.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "/Devices/HDA/BytesWritten", STAMUNIT_BYTES, "Bytes written to HDA emulation.");
+ }
+# endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceHDA =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "hda",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "Intel HD Audio Controller",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(HDASTATE),
+ /* pfnConstruct */
+ hdaR3Construct,
+ /* pfnDestruct */
+ hdaR3Destruct,
+ /* pfnRelocate */
+ hdaR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ hdaR3Reset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ hdaR3Attach,
+ /* pfnDetach */
+ hdaR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ hdaR3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#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..1b03f60c
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHDA.h
@@ -0,0 +1,222 @@
+/* $Id: DevHDA.h $ */
+/** @file
+ * DevHDA.h - VBox Intel HD Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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"
+
+#include "HDACodec.h"
+#include "HDAStream.h"
+#include "HDAStreamMap.h"
+#include "HDAStreamPeriod.h"
+
+
+
+/**
+ * Structure defining an HDA mixer sink.
+ * 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) pStream;
+ /** Pointer to the actual audio mixer sink. */
+ R3PTRTYPE(PAUDMIXSINK) pMixSink;
+} HDAMIXERSINK, *PHDAMIXERSINK;
+
+/**
+ * Structure for mapping a stream tag to an HDA stream.
+ */
+typedef struct HDATAG
+{
+ /** Own stream tag. */
+ uint8_t uTag;
+ uint8_t Padding[7];
+ /** Pointer to associated stream. */
+ R3PTRTYPE(PHDASTREAM) pStream;
+} HDATAG, *PHDATAG;
+
+/** @todo Make STAM values out of this? */
+typedef struct HDASTATEDBGINFO
+{
+#ifdef DEBUG
+ /** Timestamp (in ns) of the last timer callback (hdaTimer).
+ * Used to calculate the time actually elapsed between two timer callbacks. */
+ uint64_t tsTimerLastCalledNs;
+ /** 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;
+ /** 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;
+ } IRQ;
+#endif
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ /** Path where to dump the debug output to.
+ * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */
+ char szOutPath[RTPATH_MAX + 1];
+} HDASTATEDBGINFO, *PHDASTATEDBGINFO;
+
+/**
+ * ICH Intel HD Audio Controller state.
+ */
+typedef struct HDASTATE
+{
+ /** The PCI device structure. */
+ PDMPCIDEV PciDev;
+ /** R3 Pointer to the device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** R0 Pointer to the device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** R0 Pointer to the device instance. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Padding for alignment. */
+ uint32_t u32Padding;
+ /** Critical section protecting the HDA state. */
+ PDMCRITSECT CritSect;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ RTGCPHYS MMIOBaseAddr;
+ /** The HDA's register set. */
+ uint32_t au32Regs[HDA_NUM_REGS];
+ /** Internal stream states. */
+ HDASTREAM aStreams[HDA_MAX_STREAMS];
+ /** Mapping table between stream tags and stream states. */
+ HDATAG aTags[HDA_MAX_TAGS];
+ /** 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;
+ /** Pointer to CORB buffer. */
+ R3PTRTYPE(uint32_t *) pu32CorbBuf;
+ /** Size in bytes of CORB buffer. */
+ uint32_t cbCorbBuf;
+ /** Padding for alignment. */
+ uint32_t u32Padding1;
+ /** Pointer to RIRB buffer. */
+ R3PTRTYPE(uint64_t *) pu64RirbBuf;
+ /** Size in bytes of RIRB buffer. */
+ uint32_t cbRirbBuf;
+ /** DMA position buffer enable bit. */
+ bool fDMAPosition;
+ /** Flag whether the R0 and RC parts are enabled. */
+ bool fRZEnabled;
+ /** Reserved. */
+ bool fPadding1b;
+ /** Number of active (running) SDn streams. */
+ uint8_t cStreamsActive;
+ /** The stream timers for pumping data thru the attached LUN drivers. */
+ PTMTIMERR3 pTimer[HDA_MAX_STREAMS];
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatTimer;
+ STAMPROFILE StatIn;
+ STAMPROFILE StatOut;
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+#endif
+ /** Pointer to HDA codec to use. */
+ R3PTRTYPE(PHDACODEC) pCodec;
+ /** 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
+ /** Last updated wall clock (WALCLK) counter. */
+ uint64_t u64WalClk;
+ /** Response Interrupt Count (RINTCNT). */
+ uint16_t u16RespIntCnt;
+ /** Position adjustment (in audio frames).
+ *
+ * This is not an official feature of the HDA specs, but used by
+ * certain OS drivers (e.g. snd_hda_intel) to work around certain
+ * quirks by "real" HDA hardware implementations.
+ *
+ * The position adjustment specifies how many audio frames
+ * a stream is ahead from its actual reading/writing position when
+ * starting a stream.
+ */
+ uint16_t cPosAdjustFrames;
+ /** Whether the position adjustment is enabled or not. */
+ bool fPosAdjustEnabled;
+#ifdef VBOX_STRICT
+ /** Wall clock (WALCLK) stale count.
+ * This indicates the number of set wall clock
+ * values which did not actually move the counter forward (stale). */
+ uint8_t u8WalClkStaleCnt;
+ uint8_t Padding1[2];
+#else
+ uint8_t Padding1[3];
+#endif
+ /** Current IRQ level. */
+ uint8_t u8IRQL;
+ /** The device timer Hz rate. Defaults to HDA_TIMER_HZ_DEFAULT. */
+ uint16_t uTimerHz;
+ /** Padding for alignment. */
+ uint8_t au8Padding3[3];
+ HDASTATEDBGINFO Dbg;
+ /** 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, *PHDASTATE;
+
+/** Value for HDASTATE:uAlignmentCheckMagic. */
+#define HDASTATE_ALIGNMENT_CHECK_MAGIC UINT64_C(0x1298afb75893e059)
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDA_h */
+
diff --git a/src/VBox/Devices/Audio/DevHDACommon.cpp b/src/VBox/Devices/Audio/DevHDACommon.cpp
new file mode 100644
index 00000000..d32dcbc8
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHDACommon.cpp
@@ -0,0 +1,719 @@
+/* $Id: DevHDACommon.cpp $ */
+/** @file
+ * DevHDACommon.cpp - Shared HDA device functions.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include "DrvAudio.h"
+
+#include "DevHDA.h"
+#include "DevHDACommon.h"
+
+#include "HDAStream.h"
+
+
+#ifndef LOG_ENABLED
+/**
+ * Processes (de/asserts) the interrupt according to the HDA's current state.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA state.
+ */
+int hdaProcessInterrupt(PHDASTATE pThis)
+#else
+/**
+ * Processes (de/asserts) the interrupt according to the HDA's current state.
+ * Debug version.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA state.
+ * @param pszSource Caller information.
+ */
+int hdaProcessInterrupt(PHDASTATE pThis, const char *pszSource)
+#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(pThis->CTX_SUFF(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(pThis->CTX_SUFF(pDevIns), 0, 0 /* Deassert */);
+ pThis->u8IRQL = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves the currently set value for the wall clock.
+ *
+ * @return IPRT status code.
+ * @return Currently set wall clock value.
+ * @param pThis HDA state.
+ *
+ * @remark Operation is atomic.
+ */
+uint64_t hdaWalClkGetCurrent(PHDASTATE pThis)
+{
+ return ASMAtomicReadU64(&pThis->u64WalClk);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Sets the actual WALCLK register to the specified wall clock value.
+ * The specified wall clock value only will be set (unless fForce is set to true) if all
+ * handled HDA streams have passed (in time) that value. This guarantees that the WALCLK
+ * register stays in sync with all handled HDA streams.
+ *
+ * @return true if the WALCLK register has been updated, false if not.
+ * @param pThis HDA state.
+ * @param u64WalClk Wall clock value to set WALCLK register to.
+ * @param fForce Whether to force setting the wall clock value or not.
+ */
+bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce)
+{
+ const bool fFrontPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkFront)->State.Period,
+ u64WalClk);
+ const uint64_t u64FrontAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkFront)->State.Period);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+# error "Implement me!"
+# endif
+
+ const bool fLineInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn)->State.Period, u64WalClk);
+ const uint64_t u64LineInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkLineIn)->State.Period);
+# ifdef VBOX_WITH_HDA_MIC_IN
+ const bool fMicInPassed = hdaR3StreamPeriodHasPassedAbsWalClk (&hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn)->State.Period, u64WalClk);
+ const uint64_t u64MicInAbsWalClk = hdaR3StreamPeriodGetAbsElapsedWalClk(&hdaR3GetStreamFromSink(pThis, &pThis->SinkMicIn)->State.Period);
+# endif
+
+# ifdef VBOX_STRICT
+ const uint64_t u64WalClkCur = ASMAtomicReadU64(&pThis->u64WalClk);
+# endif
+
+ /* Only drive the WALCLK register forward if all (active) stream periods have passed
+ * the specified point in time given by u64WalClk. */
+ if ( ( fFrontPassed
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+# error "Implement me!"
+# endif
+ && fLineInPassed
+# ifdef VBOX_WITH_HDA_MIC_IN
+ && fMicInPassed
+# endif
+ )
+ || fForce)
+ {
+ if (!fForce)
+ {
+ /* Get the maximum value of all periods we need to handle.
+ * Not the most elegant solution, but works for now ... */
+ u64WalClk = RT_MAX(u64WalClk, u64FrontAbsWalClk);
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+# error "Implement me!"
+# endif
+ u64WalClk = RT_MAX(u64WalClk, u64LineInAbsWalClk);
+# ifdef VBOX_WITH_HDA_MIC_IN
+ u64WalClk = RT_MAX(u64WalClk, u64MicInAbsWalClk);
+# endif
+
+# ifdef VBOX_STRICT
+ AssertMsg(u64WalClk >= u64WalClkCur,
+ ("Setting WALCLK to a value going backwards does not make any sense (old %RU64 vs. new %RU64)\n",
+ u64WalClkCur, u64WalClk));
+ if (u64WalClk == u64WalClkCur) /* Setting a stale value? */
+ {
+ if (pThis->u8WalClkStaleCnt++ > 3)
+ AssertMsgFailed(("Setting WALCLK to a stale value (%RU64) too often isn't a good idea really. "
+ "Good luck with stuck audio stuff.\n", u64WalClk));
+ }
+ else
+ pThis->u8WalClkStaleCnt = 0;
+# endif
+ }
+
+ /* Set the new WALCLK value. */
+ ASMAtomicWriteU64(&pThis->u64WalClk, u64WalClk);
+ }
+
+ const uint64_t u64WalClkNew = hdaWalClkGetCurrent(pThis);
+
+ Log3Func(("Cur: %RU64, New: %RU64 (force %RTbool) -> %RU64 %s\n",
+ u64WalClkCur, u64WalClk, fForce,
+ u64WalClkNew, u64WalClkNew == u64WalClk ? "[OK]" : "[DELAYED]"));
+
+ return (u64WalClkNew == u64WalClk);
+}
+
+/**
+ * Returns the default (mixer) sink from a given SD#.
+ * Returns NULL if no sink is found.
+ *
+ * @return PHDAMIXERSINK
+ * @param pThis HDA state.
+ * @param uSD SD# to return mixer sink for.
+ * NULL if not found / handled.
+ */
+PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATE pThis, uint8_t uSD)
+{
+ if (hdaGetDirFromSD(uSD) == PDMAUDIODIR_IN)
+ {
+ const uint8_t uFirstSDI = 0;
+
+ if (uSD == uFirstSDI) /* First SDI. */
+ return &pThis->SinkLineIn;
+# ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ if (uSD == uFirstSDI + 1)
+ return &pThis->SinkMicIn;
+# else
+ /* If we don't have a dedicated Mic-In sink, use the always present Line-In sink. */
+ return &pThis->SinkLineIn;
+# endif
+ }
+ else
+ {
+ const uint8_t uFirstSDO = HDA_MAX_SDI;
+
+ if (uSD == uFirstSDO)
+ return &pThis->SinkFront;
+# ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (uSD == uFirstSDO + 1)
+ return &pThis->SinkCenterLFE;
+ if (uSD == uFirstSDO + 2)
+ return &pThis->SinkRear;
+# endif
+ }
+
+ return NULL;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * 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.
+ */
+PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD)
+{
+ AssertReturn(uSD < HDA_MAX_STREAMS, PDMAUDIODIR_UNKNOWN);
+
+ if (uSD < HDA_MAX_SDI)
+ return PDMAUDIODIR_IN;
+
+ return PDMAUDIODIR_OUT;
+}
+
+/**
+ * Returns the HDA stream of specified stream descriptor number.
+ *
+ * @return Pointer to HDA stream, or NULL if none found.
+ */
+PHDASTREAM hdaGetStreamFromSD(PHDASTATE pThis, uint8_t uSD)
+{
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(uSD < HDA_MAX_STREAMS, NULL);
+
+ if (uSD >= HDA_MAX_STREAMS)
+ {
+ AssertMsgFailed(("Invalid / non-handled SD%RU8\n", uSD));
+ return NULL;
+ }
+
+ return &pThis->aStreams[uSD];
+}
+
+#ifdef IN_RING3
+
+/**
+ * Returns the HDA stream of specified HDA sink.
+ *
+ * @return Pointer to HDA stream, or NULL if none found.
+ */
+PHDASTREAM hdaR3GetStreamFromSink(PHDASTATE pThis, PHDAMIXERSINK pSink)
+{
+ AssertPtrReturn(pThis, NULL);
+ AssertPtrReturn(pSink, NULL);
+
+ /** @todo Do something with the channel mapping here? */
+ return pSink->pStream;
+}
+
+/**
+ * Reads DMA data from a given HDA output stream.
+ *
+ * @return IPRT status code.
+ * @param pThis HDA state.
+ * @param pStream HDA output stream to read DMA data from.
+ * @param pvBuf Where to store the read data.
+ * @param cbBuf How much to read in bytes.
+ * @param pcbRead Returns read bytes from DMA. Optional.
+ */
+int hdaR3DMARead(PHDASTATE pThis, PHDASTREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcbRead is optional. */
+
+ PHDABDLE pBDLE = &pStream->State.BDLE;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbReadTotal = 0;
+ uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
+
+# ifdef HDA_DEBUG_SILENCE
+ uint64_t csSilence = 0;
+
+ pStream->Dbg.cSilenceThreshold = 100;
+ pStream->Dbg.cbSilenceReadMin = _1M;
+# endif
+
+ RTGCPHYS addrChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
+
+ while (cbLeft)
+ {
+ uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS);
+
+ rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), addrChunk, (uint8_t *)pvBuf + cbReadTotal, cbChunk);
+ if (RT_FAILURE(rc))
+ break;
+
+# ifdef HDA_DEBUG_SILENCE
+ uint16_t *pu16Buf = (uint16_t *)pvBuf;
+ for (size_t i = 0; i < cbChunk / sizeof(uint16_t); i++)
+ {
+ if (*pu16Buf == 0)
+ csSilence++;
+ else
+ break;
+ pu16Buf++;
+ }
+# endif
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbReadTotal, cbChunk, 0 /* fFlags */);
+
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, cbChunk);
+ addrChunk = (addrChunk + cbChunk) % pBDLE->Desc.u32BufSize;
+
+ Assert(cbLeft >= cbChunk);
+ cbLeft -= cbChunk;
+
+ cbReadTotal += cbChunk;
+ }
+
+# ifdef HDA_DEBUG_SILENCE
+ if (csSilence)
+ pStream->Dbg.csSilence += csSilence;
+
+ if ( csSilence == 0
+ && pStream->Dbg.csSilence > pStream->Dbg.cSilenceThreshold
+ && pStream->Dbg.cbReadTotal >= pStream->Dbg.cbSilenceReadMin)
+ {
+ LogFunc(("Silent block detected: %RU64 audio samples\n", pStream->Dbg.csSilence));
+ pStream->Dbg.csSilence = 0;
+ }
+# endif
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * Writes audio data from an HDA input stream's FIFO to its associated DMA area.
+ *
+ * @return IPRT status code.
+ * @param pThis HDA state.
+ * @param pStream HDA input stream to write audio data to.
+ * @param pvBuf Data to write.
+ * @param cbBuf How much (in bytes) to write.
+ * @param pcbWritten Returns written bytes on success. Optional.
+ */
+int hdaR3DMAWrite(PHDASTATE pThis, PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcbWritten is optional. */
+
+ PHDABDLE pBDLE = &pStream->State.BDLE;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+ uint32_t cbLeft = RT_MIN(cbBuf, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
+
+ RTGCPHYS addrChunk = pBDLE->Desc.u64BufAddr + pBDLE->State.u32BufOff;
+
+ while (cbLeft)
+ {
+ uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS);
+
+ /* Sanity checks. */
+ Assert(cbChunk <= pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMARaw, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, 0 /* fFlags */);
+
+ rc = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns),
+ addrChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
+ if (RT_FAILURE(rc))
+ break;
+
+ STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbChunk);
+ addrChunk = (addrChunk + cbChunk) % pBDLE->Desc.u32BufSize;
+
+ Assert(cbLeft >= cbChunk);
+ cbLeft -= (uint32_t)cbChunk;
+
+ cbWrittenTotal += (uint32_t)cbChunk;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbWritten)
+ *pcbWritten = cbWrittenTotal;
+ }
+ else
+ LogFunc(("Failed with %Rrc\n", rc));
+
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Returns a new INTSTS value based on the current device state.
+ *
+ * @returns Determined INTSTS register value.
+ * @param pThis HDA state.
+ *
+ * @remark This function does *not* set INTSTS!
+ */
+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;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Converts an HDA stream's SDFMT register into a given PCM properties structure.
+ *
+ * @return IPRT 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 cBytes = 0;
+ switch (EXTRACT_VALUE(u16SDFMT, HDA_SDFMT_BITS_MASK, HDA_SDFMT_BITS_SHIFT))
+ {
+ case 0:
+ cBytes = 1;
+ break;
+ case 1:
+ cBytes = 2;
+ break;
+ case 4:
+ cBytes = 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))
+ {
+ RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS));
+
+ pProps->cBytes = cBytes;
+ pProps->fSigned = true;
+ pProps->cChannels = (u16SDFMT & 0xf) + 1;
+ pProps->uHz = u32Hz * u32HzMult / u32HzDiv;
+ pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels);
+ }
+
+# undef EXTRACT_VALUE
+ return rc;
+}
+
+# ifdef LOG_ENABLED
+void hdaR3BDLEDumpAll(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(pThis->CTX_SUFF(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_FLAG_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(pThis->CTX_SUFF(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 */
+
+/**
+ * Fetches a Bundle Descriptor List Entry (BDLE) from the DMA engine.
+ *
+ * @param pThis Pointer to HDA state.
+ * @param pBDLE Where to store the fetched result.
+ * @param u64BaseDMA Address base of DMA engine to use.
+ * @param u16Entry BDLE entry to fetch.
+ */
+int hdaR3BDLEFetch(PHDASTATE pThis, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBDLE, VERR_INVALID_POINTER);
+ AssertReturn(u64BaseDMA, VERR_INVALID_PARAMETER);
+
+ if (!u64BaseDMA)
+ {
+ LogRel2(("HDA: Unable to fetch BDLE #%RU16 - no base DMA address set (yet)\n", u16Entry));
+ return VERR_NOT_FOUND;
+ }
+ /** @todo Compare u16Entry with LVI. */
+
+ int rc = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)),
+ &pBDLE->Desc, sizeof(pBDLE->Desc));
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Reset internal state. */
+ RT_ZERO(pBDLE->State);
+ pBDLE->State.u32BDLIndex = u16Entry;
+ }
+
+ Log3Func(("Entry #%d @ 0x%x: %R[bdle], rc=%Rrc\n", u16Entry, u64BaseDMA + (u16Entry * sizeof(HDABDLEDESC)), pBDLE, rc));
+
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Tells whether a given BDLE is complete or not.
+ *
+ * @return true if BDLE is complete, false if not.
+ * @param pBDLE BDLE to retrieve status for.
+ */
+bool hdaR3BDLEIsComplete(PHDABDLE pBDLE)
+{
+ bool fIsComplete = false;
+
+ if ( !pBDLE->Desc.u32BufSize /* There can be BDLEs with 0 size. */
+ || (pBDLE->State.u32BufOff >= pBDLE->Desc.u32BufSize))
+ {
+ Assert(pBDLE->State.u32BufOff == pBDLE->Desc.u32BufSize);
+ fIsComplete = true;
+ }
+
+ Log3Func(("%R[bdle] => %s\n", pBDLE, fIsComplete ? "COMPLETE" : "INCOMPLETE"));
+
+ return fIsComplete;
+}
+
+/**
+ * Tells whether a given BDLE needs an interrupt or not.
+ *
+ * @return true if BDLE needs an interrupt, false if not.
+ * @param pBDLE BDLE to retrieve status for.
+ */
+bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE)
+{
+ return (pBDLE->Desc.fFlags & HDA_BDLE_FLAG_IOC);
+}
+
+/**
+ * Sets the virtual device timer to a new expiration time.
+ *
+ * @returns Whether the new expiration time was set or not.
+ * @param pThis HDA state.
+ * @param pStream HDA stream to set timer for.
+ * @param tsExpire New (virtual) expiration time to set.
+ * @param fForce Whether to force setting the expiration time or not.
+ *
+ * @remark This function takes all active HDA streams and their
+ * current timing into account. This is needed to make sure
+ * that all streams can match their needed timing.
+ *
+ * To achieve this, the earliest (lowest) timestamp of all
+ * active streams found will be used for the next scheduling slot.
+ *
+ * Forcing a new expiration time will override the above mechanism.
+ */
+bool hdaR3TimerSet(PHDASTATE pThis, PHDASTREAM pStream, uint64_t tsExpire, bool fForce)
+{
+ AssertPtrReturn(pThis, false);
+ AssertPtrReturn(pStream, false);
+
+ uint64_t tsExpireMin = tsExpire;
+
+ if (!fForce)
+ {
+ if (hdaR3StreamTransferIsScheduled(pStream))
+ tsExpireMin = RT_MIN(tsExpireMin, hdaR3StreamTransferGetNext(pStream));
+ }
+
+ AssertPtr(pThis->pTimer[pStream->u8SD]);
+
+ const uint64_t tsNow = TMTimerGet(pThis->pTimer[pStream->u8SD]);
+
+ /*
+ * Make sure to not go backwards in time, as this will assert in TMTimerSet().
+ * This in theory could happen in hdaR3StreamTransferGetNext() from above.
+ */
+ if (tsExpireMin < tsNow)
+ tsExpireMin = tsNow;
+
+ int rc = TMTimerSet(pThis->pTimer[pStream->u8SD], tsExpireMin);
+ AssertRC(rc);
+
+ return RT_SUCCESS(rc);
+}
+
+#endif /* IN_RING3 */
diff --git a/src/VBox/Devices/Audio/DevHDACommon.h b/src/VBox/Devices/Audio/DevHDACommon.h
new file mode 100644
index 00000000..c667d57a
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevHDACommon.h
@@ -0,0 +1,658 @@
+/* $Id: DevHDACommon.h $ */
+/** @file
+ * DevHDACommon.h - Shared HDA device defines / functions.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DevHDACommon_h
+#define VBOX_INCLUDED_SRC_Audio_DevHDACommon_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "AudioMixer.h"
+#include <VBox/log.h> /* LOG_ENABLED */
+
+/** See 302349 p 6.2. */
+typedef struct HDAREGDESC
+{
+ /** Register offset in the register space. */
+ uint32_t offset;
+ /** Size in bytes. Registers of size > 4 are in fact tables. */
+ uint32_t size;
+ /** Readable bits. */
+ uint32_t readable;
+ /** Writable bits. */
+ uint32_t writable;
+ /** Register descriptor (RD) flags of type HDA_RD_FLAG_.
+ * These are used to specify the handling (read/write)
+ * policy of the register. */
+ uint32_t fFlags;
+ /** Read callback. */
+ int (*pfnRead)(PHDASTATE pThis, uint32_t iReg, uint32_t *pu32Value);
+ /** Write callback. */
+ int (*pfnWrite)(PHDASTATE pThis, uint32_t iReg, uint32_t u32Value);
+ /** Index into the register storage array. */
+ uint32_t mem_idx;
+ /** Abbreviated name. */
+ const char *abbrev;
+ /** Descripton. */
+ const char *desc;
+} HDAREGDESC, *PHDAREGDESC;
+
+/**
+ * HDA register aliases (HDA spec 3.3.45).
+ * @remarks Sorted by offReg.
+ */
+typedef struct HDAREGALIAS
+{
+ /** The alias register offset. */
+ uint32_t offReg;
+ /** The register index. */
+ int idxAlias;
+} HDAREGALIAS, *PHDAREGALIAS;
+
+/**
+ * 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);
+
+/** Number of general registers. */
+#define HDA_NUM_GENERAL_REGS 34
+/** Number of total registers in the HDA's register map. */
+#define HDA_NUM_REGS (HDA_NUM_GENERAL_REGS + (HDA_MAX_STREAMS * 10 /* Each stream descriptor has 10 registers */))
+/** Total number of stream tags (channels). Index 0 is reserved / invalid. */
+#define HDA_MAX_TAGS 16
+
+/**
+ * ICH6 datasheet defines limits for FIFOS registers (18.2.39).
+ * Formula: size - 1
+ * Other values not listed are not supported.
+ */
+/** Maximum FIFO size (in bytes). */
+#define HDA_FIFO_MAX 256
+
+/** Default timer frequency (in Hz).
+ *
+ * Lowering this value can ask for trouble, as backends then can run
+ * into data underruns.
+ *
+ * Note: For handling surround setups (e.g. 5.1 speaker setups) we need
+ * a higher Hz rate, as the device emulation otherwise will come into
+ * timing trouble, making the output (DMA reads) crackling. */
+#define HDA_TIMER_HZ_DEFAULT 100
+
+/** Default position adjustment (in audio samples).
+ *
+ * For snd_hda_intel (Linux guests), the first BDL entry always is being used as
+ * so-called BDL adjustment, which can vary, and is being used for chipsets which
+ * misbehave and/or are incorrectly implemented.
+ *
+ * The BDL adjustment entry *always* has the IOC (Interrupt on Completion) bit set.
+ *
+ * For Intel Baytrail / Braswell implementations the BDL default adjustment is 32 frames, whereas
+ * for ICH / PCH it's only one (1) frame.
+ *
+ * See default_bdl_pos_adj() and snd_hdac_stream_setup_periods() for more information.
+ *
+ * By default we apply some simple heuristics in hdaStreamInit().
+ */
+#define HDA_POS_ADJUST_DEFAULT 0
+
+/** HDA's (fixed) audio frame size in bytes.
+ * We only support 16-bit stereo frames at the moment. */
+#define HDA_FRAME_SIZE_DEFAULT 4
+
+/** Offset of the SD0 register map. */
+#define HDA_REG_DESC_SD0_BASE 0x80
+
+/** Turn a short global register name into an memory index and a stringized name. */
+#define HDA_REG_IDX(abbrev) HDA_MEM_IND_NAME(abbrev), #abbrev
+
+/** Turns a short stream register name into an memory index and a stringized name. */
+#define HDA_REG_IDX_STRM(reg, suff) HDA_MEM_IND_NAME(reg ## suff), #reg #suff
+
+/** Same as above for a register *not* stored in memory. */
+#define HDA_REG_IDX_NOMEM(abbrev) 0, #abbrev
+
+extern const HDAREGDESC g_aHdaRegMap[HDA_NUM_REGS];
+
+/**
+ * NB: Register values stored in memory (au32Regs[]) are indexed through
+ * the HDA_RMX_xxx macros (also HDA_MEM_IND_NAME()). On the other hand, the
+ * register descriptors in g_aHdaRegMap[] are indexed through the
+ * HDA_REG_xxx macros (also HDA_REG_IND_NAME()).
+ *
+ * The au32Regs[] layout is kept unchanged for saved state
+ * compatibility.
+ */
+
+/* Registers */
+#define HDA_REG_IND_NAME(x) HDA_REG_##x
+#define HDA_MEM_IND_NAME(x) HDA_RMX_##x
+#define HDA_REG_IND(pThis, x) ((pThis)->au32Regs[g_aHdaRegMap[x].mem_idx])
+#define HDA_REG(pThis, x) (HDA_REG_IND((pThis), HDA_REG_IND_NAME(x)))
+
+
+#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_OUTSTRMPAY 9 /* 0x18 */
+#define HDA_RMX_OUTSTRMPAY 112
+
+#define HDA_REG_INSTRMPAY 10 /* 0x1a */
+#define HDA_RMX_INSTRMPAY 113
+
+#define HDA_REG_INTCTL 11 /* 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 12 /* 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 13 /* 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.
+ */
+#define HDA_REG_SSYNC 14 /* 0x34 */
+#define HDA_RMX_SSYNC 12
+
+#define HDA_REG_CORBLBASE 15 /* 0x40 */
+#define HDA_RMX_CORBLBASE 13
+
+#define HDA_REG_CORBUBASE 16 /* 0x44 */
+#define HDA_RMX_CORBUBASE 14
+
+#define HDA_REG_CORBWP 17 /* 0x48 */
+#define HDA_RMX_CORBWP 15
+
+#define HDA_REG_CORBRP 18 /* 0x4A */
+#define HDA_RMX_CORBRP 16
+#define HDA_CORBRP_RST RT_BIT(15) /* CORB Read Pointer Reset */
+
+#define HDA_REG_CORBCTL 19 /* 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 20 /* 0x4D */
+#define HDA_RMX_CORBSTS 18
+
+#define HDA_REG_CORBSIZE 21 /* 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 22 /* 0x50 */
+#define HDA_RMX_RIRBLBASE 20
+
+#define HDA_REG_RIRBUBASE 23 /* 0x54 */
+#define HDA_RMX_RIRBUBASE 21
+
+#define HDA_REG_RIRBWP 24 /* 0x58 */
+#define HDA_RMX_RIRBWP 22
+#define HDA_RIRBWP_RST RT_BIT(15) /* RIRB Write Pointer Reset */
+
+#define HDA_REG_RINTCNT 25 /* 0x5A */
+#define HDA_RMX_RINTCNT 23
+
+/** Maximum number of Response Interrupts. */
+#define HDA_MAX_RINTCNT 256
+
+#define HDA_REG_RIRBCTL 26 /* 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 27 /* 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 28 /* 0x5E */
+#define HDA_RMX_RIRBSIZE 26
+
+#define HDA_REG_IC 29 /* 0x60 */
+#define HDA_RMX_IC 27
+
+#define HDA_REG_IR 30 /* 0x64 */
+#define HDA_RMX_IR 28
+
+#define HDA_REG_IRS 31 /* 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 32 /* 0x70 */
+#define HDA_RMX_DPLBASE 30
+
+#define HDA_REG_DPUBASE 33 /* 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]. */
+#define HDA_STREAM_REG(pThis, name, sdnum) (HDA_REG_IND((pThis), HDA_REG_SD0##name + (sdnum) * 10))
+
+#define HDA_SD_NUM_FROM_REG(pThis, func, reg) ((reg - HDA_STREAM_REG_DEF(func, 0)) / 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 35 /* 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 36 /* 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 37 /* 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 38 /* 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 39 /* 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 40 /* 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)
+
+#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 41 /* 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 42 /* 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 43 /* 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))
+
+/** Interrupt on completion (IOC) flag. */
+#define HDA_BDLE_FLAG_IOC RT_BIT(0)
+
+
+
+/** The HDA controller. */
+typedef struct HDASTATE *PHDASTATE;
+/** The HDA stream. */
+typedef struct HDASTREAM *PHDASTREAM;
+
+typedef struct HDAMIXERSINK *PHDAMIXERSINK;
+
+
+/**
+ * Internal state of a Buffer Descriptor List Entry (BDLE),
+ * needed to keep track of the data needed for the actual device
+ * emulation.
+ */
+typedef struct HDABDLESTATE
+{
+ /** 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;
+} HDABDLESTATE, *PHDABDLESTATE;
+
+/**
+ * BDL description structure.
+ * Do not touch this, as this must match to the HDA specs.
+ */
+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;
+ /** Bit 0: Interrupt on completion; the controller will generate
+ * an interrupt when the last byte of the buffer has been
+ * fetched by the DMA engine.
+ *
+ * Rest is 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. */
+
+/**
+ * Buffer Descriptor List Entry (BDLE) (3.6.3).
+ */
+typedef struct HDABDLE
+{
+ /** The actual BDL description. */
+ HDABDLEDESC Desc;
+ /** Internal state of this BDLE.
+ * Not part of the actual BDLE registers. */
+ HDABDLESTATE State;
+} HDABDLE;
+AssertCompileSizeAlignment(HDABDLE, 8);
+/** Pointer to a buffer descriptor list entry (BDLE). */
+typedef HDABDLE *PHDABDLE;
+
+/** @name Object lookup functions.
+ * @{
+ */
+#ifdef IN_RING3
+PHDAMIXERSINK hdaR3GetDefaultSink(PHDASTATE pThis, uint8_t uSD);
+#endif
+PDMAUDIODIR hdaGetDirFromSD(uint8_t uSD);
+PHDASTREAM hdaGetStreamFromSD(PHDASTATE pThis, uint8_t uSD);
+#ifdef IN_RING3
+PHDASTREAM hdaR3GetStreamFromSink(PHDASTATE pThis, PHDAMIXERSINK pSink);
+#endif
+/** @} */
+
+/** @name Interrupt functions.
+ * @{
+ */
+#ifdef LOG_ENABLED
+int hdaProcessInterrupt(PHDASTATE pThis, const char *pszSource);
+#else
+int hdaProcessInterrupt(PHDASTATE pThis);
+#endif
+/** @} */
+
+/** @name Wall clock (WALCLK) functions.
+ * @{
+ */
+uint64_t hdaWalClkGetCurrent(PHDASTATE pThis);
+#ifdef IN_RING3
+bool hdaR3WalClkSet(PHDASTATE pThis, uint64_t u64WalClk, bool fForce);
+#endif
+/** @} */
+
+/** @name DMA utility functions.
+ * @{
+ */
+#ifdef IN_RING3
+int hdaR3DMARead(PHDASTATE pThis, PHDASTREAM pStream, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead);
+int hdaR3DMAWrite(PHDASTATE pThis, PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
+#endif
+/** @} */
+
+/** @name Register functions.
+ * @{
+ */
+uint32_t hdaGetINTSTS(PHDASTATE pThis);
+#ifdef IN_RING3
+int hdaR3SDFMTToPCMProps(uint16_t u16SDFMT, PPDMAUDIOPCMPROPS pProps);
+#endif /* IN_RING3 */
+/** @} */
+
+/** @name BDLE (Buffer Descriptor List Entry) functions.
+ * @{
+ */
+#ifdef IN_RING3
+# ifdef LOG_ENABLED
+void hdaR3BDLEDumpAll(PHDASTATE pThis, uint64_t u64BDLBase, uint16_t cBDLE);
+# endif
+int hdaR3BDLEFetch(PHDASTATE pThis, PHDABDLE pBDLE, uint64_t u64BaseDMA, uint16_t u16Entry);
+bool hdaR3BDLEIsComplete(PHDABDLE pBDLE);
+bool hdaR3BDLENeedsInterrupt(PHDABDLE pBDLE);
+#endif /* IN_RING3 */
+/** @} */
+
+/** @name Device timer functions.
+ * @{
+ */
+#ifdef IN_RING3
+bool hdaR3TimerSet(PHDASTATE pThis, PHDASTREAM pStream, uint64_t u64Expire, bool fForce);
+#endif /* IN_RING3 */
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DevHDACommon_h */
+
diff --git a/src/VBox/Devices/Audio/DevIchAc97.cpp b/src/VBox/Devices/Audio/DevIchAc97.cpp
new file mode 100644
index 00000000..6eee6f7a
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevIchAc97.cpp
@@ -0,0 +1,4575 @@
+/* $Id: DevIchAc97.cpp $ */
+/** @file
+ * DevIchAc97 - VBox ICH AC97 Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_AC97
+#include <VBox/log.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.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>
+#endif
+
+#include "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+#include "DrvAudio.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Current saved state version. */
+#define AC97_SSM_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). */
+#define AC97_FIFO_MAX 256
+
+#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)
+
+#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)
+
+#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)
+
+#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 (BD).
+ * @{ */
+#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_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
+
+#define AC97_REC_MASK 7
+enum
+{
+ AC97_REC_MIC = 0,
+ AC97_REC_CD,
+ AC97_REC_VIDEO,
+ AC97_REC_AUX,
+ AC97_REC_LINE_IN,
+ AC97_REC_STEREO_MIX,
+ AC97_REC_MONO_MIX,
+ AC97_REC_PHONE
+};
+
+enum
+{
+ AC97_Reset = 0x00,
+ AC97_Master_Volume_Mute = 0x02,
+ AC97_Headphone_Volume_Mute = 0x04, /** Also known as AUX, see table 16, section 5.7. */
+ AC97_Master_Volume_Mono_Mute = 0x06,
+ AC97_Master_Tone_RL = 0x08,
+ AC97_PC_BEEP_Volume_Mute = 0x0A,
+ AC97_Phone_Volume_Mute = 0x0C,
+ AC97_Mic_Volume_Mute = 0x0E,
+ AC97_Line_In_Volume_Mute = 0x10,
+ AC97_CD_Volume_Mute = 0x12,
+ AC97_Video_Volume_Mute = 0x14,
+ AC97_Aux_Volume_Mute = 0x16,
+ AC97_PCM_Out_Volume_Mute = 0x18,
+ AC97_Record_Select = 0x1A,
+ AC97_Record_Gain_Mute = 0x1C,
+ AC97_Record_Gain_Mic_Mute = 0x1E,
+ AC97_General_Purpose = 0x20,
+ AC97_3D_Control = 0x22,
+ AC97_AC_97_RESERVED = 0x24,
+ AC97_Powerdown_Ctrl_Stat = 0x26,
+ AC97_Extended_Audio_ID = 0x28,
+ AC97_Extended_Audio_Ctrl_Stat = 0x2A,
+ AC97_PCM_Front_DAC_Rate = 0x2C,
+ AC97_PCM_Surround_DAC_Rate = 0x2E,
+ AC97_PCM_LFE_DAC_Rate = 0x30,
+ AC97_PCM_LR_ADC_Rate = 0x32,
+ AC97_MIC_ADC_Rate = 0x34,
+ AC97_6Ch_Vol_C_LFE_Mute = 0x36,
+ AC97_6Ch_Vol_L_R_Surround_Mute = 0x38,
+ AC97_Vendor_Reserved = 0x58,
+ AC97_AD_Misc = 0x76,
+ AC97_Vendor_ID1 = 0x7c,
+ AC97_Vendor_ID2 = 0x7e
+};
+
+/* Codec models. */
+typedef enum
+{
+ AC97_CODEC_STAC9700 = 0, /**< SigmaTel STAC9700 */
+ AC97_CODEC_AD1980, /**< Analog Devices AD1980 */
+ AC97_CODEC_AD1981B /**< Analog Devices AD1981B */
+} AC97CODEC;
+
+/* 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. */
+
+#define ICHAC97STATE_2_DEVINS(a_pAC97) ((a_pAC97)->CTX_SUFF(pDevIns))
+
+enum
+{
+ BUP_SET = RT_BIT(0),
+ BUP_LAST = RT_BIT(1)
+};
+
+/** Emits registers for a specific (Native Audio Bus Master BAR) NABMBAR.
+ * @todo This totally messes with grepping for identifiers and tagging. */
+#define AC97_NABMBAR_REGS(prefix, off) \
+ enum { \
+ prefix ## _BDBAR = off, /* Buffer Descriptor Base Address */ \
+ prefix ## _CIV = off + 4, /* Current Index Value */ \
+ prefix ## _LVI = off + 5, /* Last Valid Index */ \
+ prefix ## _SR = off + 6, /* Status Register */ \
+ prefix ## _PICB = off + 8, /* Position in Current Buffer */ \
+ prefix ## _PIV = off + 10, /* Prefetched Index Value */ \
+ prefix ## _CR = off + 11 /* Control Register */ \
+ }
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * Enumeration of AC'97 source indices.
+ *
+ * Note: The order of this indices is fixed (also applies for saved states) for the moment.
+ * So make sure you know what you're done when altering this.
+ */
+typedef enum
+{
+ AC97SOUNDSOURCE_PI_INDEX = 0, /**< PCM in */
+ AC97SOUNDSOURCE_PO_INDEX, /**< PCM out */
+ AC97SOUNDSOURCE_MC_INDEX, /**< Mic in */
+ AC97SOUNDSOURCE_END_INDEX
+} AC97SOUNDSOURCE;
+
+AC97_NABMBAR_REGS(PI, AC97SOUNDSOURCE_PI_INDEX * 16);
+AC97_NABMBAR_REGS(PO, AC97SOUNDSOURCE_PO_INDEX * 16);
+AC97_NABMBAR_REGS(MC, AC97SOUNDSOURCE_MC_INDEX * 16);
+#endif
+
+enum
+{
+ /** NABMBAR: Global Control Register. */
+ AC97_GLOB_CNT = 0x2c,
+ /** NABMBAR Global Status. */
+ AC97_GLOB_STA = 0x30,
+ /** Codec Access Semaphore Register. */
+ AC97_CAS = 0x34
+};
+
+#define AC97_PORT2IDX(a_idx) ( ((a_idx) >> 4) & 3 )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Buffer Descriptor List Entry (BDLE).
+ */
+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). */
+ uint32_t ctl_len;
+} AC97BDLE;
+AssertCompileSize(AC97BDLE, 8);
+/** Pointer to BDLE. */
+typedef AC97BDLE *PAC97BDLE;
+
+/**
+ * Bus master register set for an audio stream.
+ */
+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 (in samples). */
+ 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;
+
+#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+/**
+ * Structure keeping the AC'97 stream's state for asynchronous I/O.
+ */
+typedef struct AC97STREAMSTATEAIO
+{
+ /** Thread handle for the actual I/O thread. */
+ RTTHREAD Thread;
+ /** Event for letting the thread know there is some data to process. */
+ RTSEMEVENT Event;
+ /** Critical section for synchronizing access. */
+ RTCRITSECT CritSect;
+ /** Started indicator. */
+ volatile bool fStarted;
+ /** Shutdown indicator. */
+ volatile bool fShutdown;
+ /** Whether the thread should do any data processing or not. */
+ volatile bool fEnabled;
+ uint32_t Padding1;
+} AC97STREAMSTATEAIO, *PAC97STREAMSTATEAIO;
+#endif
+
+/** The ICH AC'97 (Intel) controller. */
+typedef struct AC97STATE *PAC97STATE;
+
+/**
+ * Structure for keeping the internal state of an AC'97 stream.
+ */
+typedef struct AC97STREAMSTATE
+{
+ /** Criticial 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
+ /** The stream's current configuration. */
+ PDMAUDIOSTREAMCFG Cfg; //+104
+ uint32_t Padding2;
+#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ /** Asynchronous I/O state members. */
+ AC97STREAMSTATEAIO AIO;
+#endif
+ /** 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;
+ /** Transfer chunk size (in bytes) of a transfer period. */
+ uint32_t cbTransferChunk;
+ /** 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;
+ uint8_t Padding3[2];
+ /** (Virtual) clock ticks per transfer. */
+ uint64_t cTransferTicks;
+ /** Timestamp (in ns) of last stream update. */
+ uint64_t tsLastUpdateNs;
+} AC97STREAMSTATE;
+AssertCompileSizeAlignment(AC97STREAMSTATE, 8);
+/** Pointer to internal state of an AC'97 stream. */
+typedef AC97STREAMSTATE *PAC97STREAMSTATE;
+
+/**
+ * Structure containing AC'97 stream debug stuff, configurable at runtime.
+ */
+typedef struct AC97STREAMDBGINFORT
+{
+ /** 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(PPDMAUDIOFILE) 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(PPDMAUDIOFILE) pFileDMA;
+} AC97STREAMDBGINFORT, *PAC97STREAMDBGINFORT;
+
+/**
+ * Structure containing AC'97 stream debug information.
+ */
+typedef struct AC97STREAMDBGINFO
+{
+ /** Runtime debug info. */
+ AC97STREAMDBGINFORT Runtime;
+} AC97STREAMDBGINFO ,*PAC97STREAMDBGINFO;
+
+/**
+ * Structure for an AC'97 stream.
+ */
+typedef struct AC97STREAM
+{
+ /** Stream number (SDn). */
+ uint8_t u8SD;
+ uint8_t abPadding0[7];
+ /** Bus master registers of this stream. */
+ AC97BMREGS Regs;
+ /** Internal state of this stream. */
+ AC97STREAMSTATE State;
+ /** Pointer to parent (AC'97 state). */
+ R3PTRTYPE(PAC97STATE) pAC97State;
+#if HC_ARCH_BITS == 32
+ uint32_t Padding1;
+#endif
+ /** Debug information. */
+ AC97STREAMDBGINFO Dbg;
+} AC97STREAM, *PAC97STREAM;
+AssertCompileSizeAlignment(AC97STREAM, 8);
+/** Pointer to an AC'97 stream (registers + state). */
+typedef AC97STREAM *PAC97STREAM;
+
+typedef struct AC97STATE *PAC97STATE;
+#ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+/**
+ * Structure for the async I/O thread context.
+ */
+typedef struct AC97STREAMTHREADCTX
+{
+ PAC97STATE pThis;
+ PAC97STREAM pStream;
+} AC97STREAMTHREADCTX, *PAC97STREAMTHREADCTX;
+#endif
+
+/**
+ * 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 AC97DRIVERSTREAM
+{
+ /** Associated mixer stream handle. */
+ R3PTRTYPE(PAUDMIXSTREAM) pMixStrm;
+} AC97DRIVERSTREAM, *PAC97DRIVERSTREAM;
+
+/**
+ * Struct for maintaining a host backend driver.
+ */
+typedef struct AC97DRIVER
+{
+ /** Node for storing this driver in our device driver list of AC97STATE. */
+ RTLISTNODER3 Node;
+ /** Pointer to AC97 controller (state). */
+ R3PTRTYPE(PAC97STATE) pAC97State;
+ /** Driver flags. */
+ PDMAUDIODRVFLAGS fFlags;
+ uint32_t PaddingFlags;
+ /** 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 Padding[4];
+ /** 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;
+} AC97DRIVER, *PAC97DRIVER;
+
+typedef struct AC97STATEDBGINFO
+{
+ /** Whether debugging is enabled or not. */
+ bool fEnabled;
+ /** Path where to dump the debug output to.
+ * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH. */
+ char szOutPath[RTPATH_MAX + 1];
+} AC97STATEDBGINFO, *PAC97STATEDBGINFO;
+
+/**
+ * Structure for maintaining an AC'97 device state.
+ */
+typedef struct AC97STATE
+{
+ /** The PCI device state. */
+ PDMPCIDEV PciDev;
+ /** Critical section protecting the AC'97 state. */
+ PDMCRITSECT CritSect;
+ /** R3 pointer to the device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** R0 pointer to the device instance. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** RC pointer to the device instance. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Set if R0/RC is enabled. */
+ bool fRZEnabled;
+ bool afPadding0[3];
+ /** 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. */
+ AC97STREAM aStreams[AC97_MAX_STREAMS];
+ /** The device timer Hz rate. Defaults to AC97_TIMER_HZ_DEFAULT_DEFAULT. */
+ uint16_t uTimerHz;
+ /** The timer for pumping data thru the attached LUN drivers - RCPtr. */
+ PTMTIMERRC pTimerRC[AC97_MAX_STREAMS];
+ /** The timer for pumping data thru the attached LUN drivers - R3Ptr. */
+ PTMTIMERR3 pTimerR3[AC97_MAX_STREAMS];
+ /** The timer for pumping data thru the attached LUN drivers - R0Ptr. */
+ PTMTIMERR0 pTimerR0[AC97_MAX_STREAMS];
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatTimer;
+ STAMPROFILE StatIn;
+ STAMPROFILE StatOut;
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+#endif
+ /** 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;
+ uint8_t silence[128];
+ int32_t bup_flag;
+ /** Base port of the I/O space region. */
+ RTIOPORT IOPortBase[2];
+ /** Codec model. */
+ uint32_t uCodecModel;
+#if HC_ARCH_BITS == 64
+ uint32_t uPadding2;
+#endif
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ AC97STATEDBGINFO Dbg;
+} AC97STATE;
+AssertCompileMemberAlignment(AC97STATE, aStreams, 8);
+/** Pointer to a AC'97 state. */
+typedef AC97STATE *PAC97STATE;
+
+/**
+ * Acquires the AC'97 lock.
+ */
+#define DEVAC97_LOCK(a_pThis) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/**
+ * Acquires the AC'97 lock or returns.
+ */
+# define DEVAC97_LOCK_RETURN(a_pThis, a_rcBusy) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, a_rcBusy); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Acquires the AC'97 lock or returns.
+ */
+# define DEVAC97_LOCK_RETURN_VOID(a_pThis) \
+ do { \
+ int rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return; \
+ } \
+ } while (0)
+
+#ifdef IN_RC
+/** Retrieves an attribute from a specific audio stream in RC. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##RC[a_SD]
+#elif defined(IN_RING0)
+/** Retrieves an attribute from a specific audio stream in R0. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##R0[a_SD]
+#else
+/** Retrieves an attribute from a specific audio stream in R3. */
+# define DEVAC97_CTX_SUFF_SD(a_Var, a_SD) a_Var##R3[a_SD]
+#endif
+
+/**
+ * Releases the AC'97 lock.
+ */
+#define DEVAC97_UNLOCK(a_pThis) \
+ do { PDMCritSectLeave(&(a_pThis)->CritSect); } while (0)
+
+/**
+ * Acquires the TM lock and AC'97 lock, returns on failure.
+ */
+#define DEVAC97_LOCK_BOTH_RETURN_VOID(a_pThis, a_SD) \
+ do { \
+ int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ return; \
+ } \
+ rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, VERR_IGNORED); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
+ return; \
+ } \
+ } while (0)
+
+/**
+ * Acquires the TM lock and AC'97 lock, returns on failure.
+ */
+#define DEVAC97_LOCK_BOTH_RETURN(a_pThis, a_SD, a_rcBusy) \
+ do { \
+ int rcLock = TMTimerLock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD), (a_rcBusy)); \
+ if (rcLock != VINF_SUCCESS) \
+ return rcLock; \
+ rcLock = PDMCritSectEnter(&(a_pThis)->CritSect, (a_rcBusy)); \
+ if (rcLock != VINF_SUCCESS) \
+ { \
+ AssertRC(rcLock); \
+ TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
+ return rcLock; \
+ } \
+ } while (0)
+
+/**
+ * Releases the AC'97 lock and TM lock.
+ */
+#define DEVAC97_UNLOCK_BOTH(a_pThis, a_SD) \
+ do { \
+ PDMCritSectLeave(&(a_pThis)->CritSect); \
+ TMTimerUnlock((a_pThis)->DEVAC97_CTX_SUFF_SD(pTimer, a_SD)); \
+ } while (0)
+
+#ifdef VBOX_WITH_STATISTICS
+AssertCompileMemberAlignment(AC97STATE, StatTimer, 8);
+AssertCompileMemberAlignment(AC97STATE, StatBytesRead, 8);
+AssertCompileMemberAlignment(AC97STATE, StatBytesWritten, 8);
+#endif
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef IN_RING3
+static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8Strm);
+static void ichac97R3StreamDestroy(PAC97STATE pThis, PAC97STREAM pStream);
+static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STREAM pStream);
+static int ichac97R3StreamReOpen(PAC97STATE pThis, PAC97STREAM pStream);
+static int ichac97R3StreamClose(PAC97STATE pThis, PAC97STREAM pStream);
+static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream);
+static void ichac97R3StreamLock(PAC97STREAM pStream);
+static void ichac97R3StreamUnlock(PAC97STREAM pStream);
+static uint32_t ichac97R3StreamGetUsed(PAC97STREAM pStream);
+static uint32_t ichac97R3StreamGetFree(PAC97STREAM pStream);
+static int ichac97R3StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax);
+static void ichac97R3StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream, bool fInTimer);
+
+static DECLCALLBACK(void) ichac97R3Reset(PPDMDEVINS pDevIns);
+
+static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
+
+static int ichac97R3MixerAddDrv(PAC97STATE pThis, PAC97DRIVER pDrv);
+static int ichac97R3MixerAddDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv);
+static int ichac97R3MixerAddDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg);
+static void ichac97R3MixerRemoveDrv(PAC97STATE pThis, PAC97DRIVER pDrv);
+static void ichac97R3MixerRemoveDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc, PAC97DRIVER pDrv);
+static void ichac97R3MixerRemoveDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc);
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+static DECLCALLBACK(int) ichac97R3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser);
+static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream);
+static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream);
+static int ichac97R3StreamAsyncIONotify(PAC97STATE pThis, PAC97STREAM pStream);
+static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream);
+static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream);
+/*static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable); Unused */
+# endif
+
+DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(uint8_t uSD);
+
+# ifdef LOG_ENABLED
+static void ichac97R3BDLEDumpAll(PAC97STATE pThis, uint64_t u64BDLBase, uint16_t cBDLE);
+# endif
+#endif /* IN_RING3 */
+bool ichac97TimerSet(PAC97STATE pThis, PAC97STREAM pStream, uint64_t tsExpire, bool fForce);
+
+static void ichac97WarmReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+static void ichac97ColdReset(PAC97STATE pThis)
+{
+ NOREF(pThis);
+}
+
+#ifdef IN_RING3
+
+/**
+ * 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 pThis AC'97 state.
+ * @param uIndex Stream index to get audio mixer sink for.
+ */
+DECLINLINE(PAUDMIXSINK) ichac97R3IndexToSink(PAC97STATE pThis, uint8_t uIndex)
+{
+ AssertPtrReturn(pThis, NULL);
+
+ switch (uIndex)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX: return pThis->pSinkLineIn;
+ case AC97SOUNDSOURCE_PO_INDEX: return pThis->pSinkOut;
+ case AC97SOUNDSOURCE_MC_INDEX: return pThis->pSinkMicIn;
+ default: break;
+ }
+
+ AssertMsgFailed(("Wrong index %RU8\n", uIndex));
+ return NULL;
+}
+
+/**
+ * Fetches the current BDLE (Buffer Descriptor List Entry) of an AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to fetch BDLE for.
+ *
+ * @remark Uses CIV as BDLE index.
+ */
+static void ichac97R3StreamFetchBDLE(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ PPDMDEVINS pDevIns = ICHAC97STATE_2_DEVINS(pThis);
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ AC97BDLE BDLE;
+ PDMDevHlpPhysRead(pDevIns, pRegs->bdbar + pRegs->civ * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE));
+ pRegs->bd_valid = 1;
+# ifndef RT_LITTLE_ENDIAN
+# error "Please adapt the code (audio buffers are little endian)!"
+# else
+ pRegs->bd.addr = RT_H2LE_U32(BDLE.addr & ~3);
+ pRegs->bd.ctl_len = RT_H2LE_U32(BDLE.ctl_len);
+# endif
+ pRegs->picb = pRegs->bd.ctl_len & AC97_BD_LEN_MASK;
+ LogFlowFunc(("bd %2d addr=%#x ctl=%#06x len=%#x(%d bytes), bup=%RTbool, ioc=%RTbool\n",
+ pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len >> 16,
+ pRegs->bd.ctl_len & AC97_BD_LEN_MASK,
+ (pRegs->bd.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */
+ RT_BOOL(pRegs->bd.ctl_len & AC97_BD_BUP),
+ RT_BOOL(pRegs->bd.ctl_len & AC97_BD_IOC)));
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Updates the status register (SR) of an AC'97 audio stream.
+ *
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to update SR for.
+ * @param new_sr New value for status register (SR).
+ */
+static void ichac97StreamUpdateSR(PAC97STATE pThis, PAC97STREAM pStream, uint32_t new_sr)
+{
+ PPDMDEVINS pDevIns = ICHAC97STATE_2_DEVINS(pThis);
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ bool fSignal = false;
+ int iIRQL = 0;
+
+ uint32_t new_mask = new_sr & AC97_SR_INT_MASK;
+ uint32_t old_mask = pRegs->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) && (pRegs->cr & AC97_CR_LVBIE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ else if ((new_mask & AC97_SR_BCIS) && (pRegs->cr & AC97_CR_IOCE))
+ {
+ fSignal = true;
+ iIRQL = 1;
+ }
+ }
+
+ pRegs->sr = new_sr;
+
+ LogFlowFunc(("IOC%d, LVB%d, sr=%#x, fSignal=%RTbool, IRQL=%d\n",
+ pRegs->sr & AC97_SR_BCIS, pRegs->sr & AC97_SR_LVBCI, pRegs->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 pThis 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(PAC97STATE pThis, PAC97STREAM pStream, uint32_t u32Val)
+{
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ Log3Func(("[SD%RU8] SR <- %#x (sr %#x)\n", pStream->u8SD, u32Val, pRegs->sr));
+
+ pRegs->sr |= u32Val & ~(AC97_SR_RO_MASK | AC97_SR_WCLEAR_MASK);
+ ichac97StreamUpdateSR(pThis, pStream, pRegs->sr & ~(u32Val & AC97_SR_WCLEAR_MASK));
+}
+
+#ifdef IN_RING3
+
+/**
+ * Returns whether an AC'97 stream is enabled or not.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 device state.
+ * @param pStream Stream to return status for.
+ */
+static bool ichac97R3StreamIsEnabled(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ AssertPtrReturn(pThis, false);
+ AssertPtrReturn(pStream, false);
+
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD);
+ bool fIsEnabled = RT_BOOL(AudioMixerSinkGetStatus(pSink) & AUDMIXSINK_STS_RUNNING);
+
+ LogFunc(("[SD%RU8] fIsEnabled=%RTbool\n", pStream->u8SD, fIsEnabled));
+ return fIsEnabled;
+}
+
+/**
+ * Enables or disables an AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to enable or disable.
+ * @param fEnable Whether to enable or disable the stream.
+ *
+ */
+static int ichac97R3StreamEnable(PAC97STATE pThis, PAC97STREAM pStream, bool fEnable)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ ichac97R3StreamLock(pStream);
+
+ int rc = VINF_SUCCESS;
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ if (fEnable)
+ rc = ichac97R3StreamAsyncIOCreate(pThis, pStream);
+ if (RT_SUCCESS(rc))
+ ichac97R3StreamAsyncIOLock(pStream);
+# endif
+
+ if (fEnable)
+ {
+ if (pStream->State.pCircBuf)
+ RTCircBufReset(pStream->State.pCircBuf);
+
+ rc = ichac97R3StreamOpen(pThis, pStream);
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ {
+ if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileStream))
+ {
+ int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+
+ if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMA))
+ {
+ int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMA, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+ }
+ }
+ else
+ rc = ichac97R3StreamClose(pThis, pStream);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* First, enable or disable the stream and the stream's sink, if any. */
+ rc = AudioMixerSinkCtl(ichac97R3IndexToSink(pThis, pStream->u8SD),
+ fEnable ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE);
+ }
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ ichac97R3StreamAsyncIOUnlock(pStream);
+# endif
+
+ /* Make sure to leave the lock before (eventually) starting the timer. */
+ ichac97R3StreamUnlock(pStream);
+
+ LogFunc(("[SD%RU8] fEnable=%RTbool, rc=%Rrc\n", pStream->u8SD, fEnable, rc));
+ return rc;
+}
+
+/**
+ * Resets an AC'97 stream.
+ *
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to reset.
+ *
+ */
+static void ichac97R3StreamReset(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pStream);
+
+ ichac97R3StreamLock(pStream);
+
+ LogFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ if (pStream->State.pCircBuf)
+ RTCircBufReset(pStream->State.pCircBuf);
+
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ pRegs->bdbar = 0;
+ pRegs->civ = 0;
+ pRegs->lvi = 0;
+
+ pRegs->picb = 0;
+ pRegs->piv = 0;
+ pRegs->cr = pRegs->cr & AC97_CR_DONT_CLEAR_MASK;
+ pRegs->bd_valid = 0;
+
+ RT_ZERO(pThis->silence);
+
+ ichac97R3StreamUnlock(pStream);
+}
+
+/**
+ * Creates an AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to create.
+ * @param u8SD Stream descriptor number to assign.
+ */
+static int ichac97R3StreamCreate(PAC97STATE pThis, PAC97STREAM pStream, uint8_t u8SD)
+{
+ RT_NOREF(pThis);
+ AssertPtrReturn(pStream, VERR_INVALID_PARAMETER);
+ /** @todo Validate u8Strm. */
+
+ LogFunc(("[SD%RU8] pStream=%p\n", u8SD, pStream));
+
+ AssertReturn(u8SD < AC97_MAX_STREAMS, VERR_INVALID_PARAMETER);
+ pStream->u8SD = u8SD;
+ pStream->pAC97State = pThis;
+
+ int rc = RTCritSectInit(&pStream->State.CritSect);
+
+ pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled;
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ {
+ char szFile[64];
+
+ if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN)
+ RTStrPrintf(szFile, sizeof(szFile), "ac97StreamWriteSD%RU8", pStream->u8SD);
+ else
+ RTStrPrintf(szFile, sizeof(szFile), "ac97StreamReadSD%RU8", pStream->u8SD);
+
+ char szPath[RTPATH_MAX + 1];
+ int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile,
+ 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ AssertRC(rc2);
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileStream);
+ AssertRC(rc2);
+
+ if (ichac97GetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN)
+ RTStrPrintf(szFile, sizeof(szFile), "ac97DMAWriteSD%RU8", pStream->u8SD);
+ else
+ RTStrPrintf(szFile, sizeof(szFile), "ac97DMAReadSD%RU8", pStream->u8SD);
+
+ rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile,
+ 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ AssertRC(rc2);
+
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMA);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileStream);
+ DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMA);
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys an AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to destroy.
+ */
+static void ichac97R3StreamDestroy(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ ichac97R3StreamClose(pThis, pStream);
+
+ int rc2 = RTCritSectDelete(&pStream->State.CritSect);
+ AssertRC(rc2);
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ rc2 = ichac97R3StreamAsyncIODestroy(pThis, pStream);
+ AssertRC(rc2);
+# else
+ RT_NOREF(pThis);
+# endif
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileStream);
+ pStream->Dbg.Runtime.pFileStream = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMA);
+ pStream->Dbg.Runtime.pFileDMA = NULL;
+ }
+
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Destroys all AC'97 audio streams of the device.
+ *
+ * @param pThis AC'97 state.
+ */
+static void ichac97R3StreamsDestroy(PAC97STATE pThis)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Destroy all AC'97 streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ ichac97R3StreamDestroy(pThis, &pThis->aStreams[i]);
+
+ /*
+ * Destroy all sinks.
+ */
+
+ PDMAUDIODESTSOURCE dstSrc;
+ if (pThis->pSinkLineIn)
+ {
+ dstSrc.Source = PDMAUDIORECSOURCE_LINE;
+ ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkLineIn, PDMAUDIODIR_IN, dstSrc);
+
+ AudioMixerSinkDestroy(pThis->pSinkLineIn);
+ pThis->pSinkLineIn = NULL;
+ }
+
+ if (pThis->pSinkMicIn)
+ {
+ dstSrc.Source = PDMAUDIORECSOURCE_MIC;
+ ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkMicIn, PDMAUDIODIR_IN, dstSrc);
+
+ AudioMixerSinkDestroy(pThis->pSinkMicIn);
+ pThis->pSinkMicIn = NULL;
+ }
+
+ if (pThis->pSinkOut)
+ {
+ dstSrc.Dest = PDMAUDIOPLAYBACKDEST_FRONT;
+ ichac97R3MixerRemoveDrvStreams(pThis, pThis->pSinkOut, PDMAUDIODIR_OUT, dstSrc);
+
+ AudioMixerSinkDestroy(pThis->pSinkOut);
+ pThis->pSinkOut = NULL;
+ }
+}
+
+/**
+ * Writes audio data from a mixer sink into an AC'97 stream's DMA buffer.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pDstStream AC'97 stream to write to.
+ * @param pSrcMixSink Mixer sink to get audio data to write from.
+ * @param cbToWrite Number of bytes to write.
+ * @param pcbWritten Number of bytes written. Optional.
+ */
+static int ichac97R3StreamWrite(PAC97STATE pThis, PAC97STREAM pDstStream, PAUDMIXSINK pSrcMixSink, uint32_t cbToWrite,
+ uint32_t *pcbWritten)
+{
+ RT_NOREF(pThis);
+ AssertPtrReturn(pDstStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrcMixSink, VERR_INVALID_POINTER);
+ AssertReturn(cbToWrite, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ PRTCIRCBUF pCircBuf = pDstStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ void *pvDst;
+ size_t cbDst;
+
+ uint32_t cbRead = 0;
+
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvDst, &cbDst);
+
+ if (cbDst)
+ {
+ int rc2 = AudioMixerSinkRead(pSrcMixSink, AUDMIXOP_COPY, pvDst, (uint32_t)cbDst, &cbRead);
+ AssertRC(rc2);
+
+ if (pDstStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pDstStream->Dbg.Runtime.pFileStream, pvDst, cbRead, 0 /* fFlags */);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbRead);
+
+ if (pcbWritten)
+ *pcbWritten = cbRead;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads audio data from an AC'97 stream's DMA buffer and writes into a specified mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pSrcStream AC'97 stream to read audio data from.
+ * @param pDstMixSink Mixer sink to write audio data to.
+ * @param cbToRead Number of bytes to read.
+ * @param pcbRead Number of bytes read. Optional.
+ */
+static int ichac97R3StreamRead(PAC97STATE pThis, PAC97STREAM pSrcStream, PAUDMIXSINK pDstMixSink, uint32_t cbToRead,
+ uint32_t *pcbRead)
+{
+ RT_NOREF(pThis);
+ AssertPtrReturn(pSrcStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDstMixSink, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ PRTCIRCBUF pCircBuf = pSrcStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ void *pvSrc;
+ size_t cbSrc;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbReadTotal = 0;
+ uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf));
+
+ while (cbLeft)
+ {
+ uint32_t cbWritten = 0;
+
+ RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc);
+
+ if (cbSrc)
+ {
+ if (pSrcStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pSrcStream->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */);
+
+ rc = AudioMixerSinkWrite(pDstMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten);
+ AssertRC(rc);
+
+ Assert(cbSrc >= cbWritten);
+ Log3Func(("[SD%RU8] %RU32/%zu bytes read\n", pSrcStream->u8SD, cbWritten, cbSrc));
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbWritten);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ Assert(cbLeft >= cbWritten);
+ cbLeft -= cbWritten;
+
+ cbReadTotal += cbWritten;
+ }
+
+ if (pcbRead)
+ *pcbRead = cbReadTotal;
+
+ return rc;
+}
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+
+/**
+ * Asynchronous I/O thread for an AC'97 stream.
+ * This will do the heavy lifting work for us as soon as it's getting notified by another thread.
+ *
+ * @returns IPRT status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser User argument. Must be of type PAC97STREAMTHREADCTX.
+ */
+static DECLCALLBACK(int) ichac97R3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PAC97STREAMTHREADCTX pCtx = (PAC97STREAMTHREADCTX)pvUser;
+ AssertPtr(pCtx);
+
+ PAC97STATE pThis = pCtx->pThis;
+ AssertPtr(pThis);
+
+ PAC97STREAM pStream = pCtx->pStream;
+ AssertPtr(pStream);
+
+ PAC97STREAMSTATEAIO pAIO = &pCtx->pStream->State.AIO;
+
+ ASMAtomicXchgBool(&pAIO->fStarted, true);
+
+ RTThreadUserSignal(hThreadSelf);
+
+ LogFunc(("[SD%RU8] Started\n", pStream->u8SD));
+
+ for (;;)
+ {
+ Log2Func(("[SD%RU8] Waiting ...\n", pStream->u8SD));
+
+ int rc2 = RTSemEventWait(pAIO->Event, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc2))
+ break;
+
+ if (ASMAtomicReadBool(&pAIO->fShutdown))
+ break;
+
+ rc2 = RTCritSectEnter(&pAIO->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ if (!pAIO->fEnabled)
+ {
+ RTCritSectLeave(&pAIO->CritSect);
+ continue;
+ }
+
+ ichac97R3StreamUpdate(pThis, pStream, false /* fInTimer */);
+
+ int rc3 = RTCritSectLeave(&pAIO->CritSect);
+ AssertRC(rc3);
+ }
+
+ AssertRC(rc2);
+ }
+
+ LogFunc(("[SD%RU8] Ended\n", pStream->u8SD));
+
+ ASMAtomicXchgBool(&pAIO->fStarted, false);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the async I/O thread for a specific AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 audio stream to create the async I/O thread for.
+ */
+static int ichac97R3StreamAsyncIOCreate(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ int rc;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ {
+ pAIO->fShutdown = false;
+ pAIO->fEnabled = true; /* Enabled by default. */
+
+ rc = RTSemEventCreate(&pAIO->Event);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pAIO->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ AC97STREAMTHREADCTX Ctx = { pThis, pStream };
+
+ char szThreadName[64];
+ RTStrPrintf2(szThreadName, sizeof(szThreadName), "ac97AIO%RU8", pStream->u8SD);
+
+ rc = RTThreadCreate(&pAIO->Thread, ichac97R3StreamAsyncIOThread, &Ctx,
+ 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, szThreadName);
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(pAIO->Thread, 10 * 1000 /* 10s timeout */);
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Destroys the async I/O thread of a specific AC'97 audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 audio stream to destroy the async I/O thread for.
+ */
+static int ichac97R3StreamAsyncIODestroy(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return VINF_SUCCESS;
+
+ ASMAtomicWriteBool(&pAIO->fShutdown, true);
+
+ int rc = ichac97R3StreamAsyncIONotify(pThis, pStream);
+ AssertRC(rc);
+
+ int rcThread;
+ rc = RTThreadWait(pAIO->Thread, 30 * 1000 /* 30s timeout */, &rcThread);
+ LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectDelete(&pAIO->CritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventDestroy(pAIO->Event);
+ AssertRC(rc);
+
+ pAIO->fStarted = false;
+ pAIO->fShutdown = false;
+ pAIO->fEnabled = false;
+ }
+
+ LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Lets the stream's async I/O thread know that there is some data to process.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to notify async I/O thread for.
+ */
+static int ichac97R3StreamAsyncIONotify(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ RT_NOREF(pThis);
+
+ LogFunc(("[SD%RU8]\n", pStream->u8SD));
+ return RTSemEventSignal(pStream->State.AIO.Event);
+}
+
+/**
+ * Locks the async I/O thread of a specific AC'97 audio stream.
+ *
+ * @param pStream AC'97 stream to lock async I/O thread for.
+ */
+static void ichac97R3StreamAsyncIOLock(PAC97STREAM pStream)
+{
+ PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return;
+
+ int rc2 = RTCritSectEnter(&pAIO->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks the async I/O thread of a specific AC'97 audio stream.
+ *
+ * @param pStream AC'97 stream to unlock async I/O thread for.
+ */
+static void ichac97R3StreamAsyncIOUnlock(PAC97STREAM pStream)
+{
+ PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return;
+
+ int rc2 = RTCritSectLeave(&pAIO->CritSect);
+ AssertRC(rc2);
+}
+
+#if 0 /* Unused */
+/**
+ * Enables (resumes) or disables (pauses) the async I/O thread.
+ *
+ * @param pStream AC'97 stream to enable/disable async I/O thread for.
+ * @param fEnable Whether to enable or disable the I/O thread.
+ *
+ * @remarks Does not do locking.
+ */
+static void ichac97R3StreamAsyncIOEnable(PAC97STREAM pStream, bool fEnable)
+{
+ PAC97STREAMSTATEAIO pAIO = &pStream->State.AIO;
+ ASMAtomicXchgBool(&pAIO->fEnabled, fEnable);
+}
+#endif
+# endif /* VBOX_WITH_AUDIO_AC97_ASYNC_IO */
+
+# ifdef LOG_ENABLED
+static void ichac97R3BDLEDumpAll(PAC97STATE 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++)
+ {
+ AC97BDLE BDLE;
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), u64BDLBase + i * sizeof(AC97BDLE), &BDLE, sizeof(AC97BDLE));
+
+# ifndef RT_LITTLE_ENDIAN
+# error "Please adapt the code (audio buffers are little endian)!"
+# else
+ BDLE.addr = RT_H2LE_U32(BDLE.addr & ~3);
+ BDLE.ctl_len = RT_H2LE_U32(BDLE.ctl_len);
+#endif
+ LogFunc(("\t#%03d BDLE(adr:0x%llx, size:%RU32 [%RU32 bytes], bup:%RTbool, ioc:%RTbool)\n",
+ i, BDLE.addr,
+ BDLE.ctl_len & AC97_BD_LEN_MASK,
+ (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1, /** @todo r=andy Assumes 16bit samples. */
+ RT_BOOL(BDLE.ctl_len & AC97_BD_BUP),
+ RT_BOOL(BDLE.ctl_len & AC97_BD_IOC)));
+
+ cbBDLE += (BDLE.ctl_len & AC97_BD_LEN_MASK) << 1; /** @todo r=andy Ditto. */
+ }
+
+ LogFlowFunc(("Total: %RU32 bytes\n", cbBDLE));
+}
+# endif /* LOG_ENABLED */
+
+/**
+ * Updates an AC'97 stream by doing its required data transfers.
+ * The host sink(s) set the overall pace.
+ *
+ * This routine is called by both, the synchronous and the asynchronous
+ * (VBOX_WITH_AUDIO_AC97_ASYNC_IO), implementations.
+ *
+ * When running synchronously, the device DMA transfers *and* the mixer sink
+ * processing is within the device timer.
+ *
+ * When running asynchronously, only the device DMA transfers are done in the
+ * device timer, whereas the mixer sink processing then is done in the stream's
+ * own async I/O thread. This thread also will call this function
+ * (with fInTimer set to @c false).
+ *
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to update.
+ * @param fInTimer Whether to this function was called from the timer
+ * context or an asynchronous I/O stream thread (if supported).
+ */
+static void ichac97R3StreamUpdate(PAC97STATE pThis, PAC97STREAM pStream, bool fInTimer)
+{
+ RT_NOREF(fInTimer);
+
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD);
+ AssertPtr(pSink);
+
+ if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */
+ return;
+
+ int rc2;
+
+ if (pStream->State.Cfg.enmDir == PDMAUDIODIR_OUT) /* Output (SDO). */
+ {
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ if (fInTimer)
+# endif
+ {
+ const uint32_t cbStreamFree = ichac97R3StreamGetFree(pStream);
+ if (cbStreamFree)
+ {
+ Log3Func(("[SD%RU8] PICB=%zu (%RU64ms), cbFree=%zu (%RU64ms), cbTransferChunk=%zu (%RU64ms)\n",
+ pStream->u8SD,
+ (pStream->Regs.picb << 1), DrvAudioHlpBytesToMilli((pStream->Regs.picb << 1), &pStream->State.Cfg.Props),
+ cbStreamFree, DrvAudioHlpBytesToMilli(cbStreamFree, &pStream->State.Cfg.Props),
+ pStream->State.cbTransferChunk, DrvAudioHlpBytesToMilli(pStream->State.cbTransferChunk, &pStream->State.Cfg.Props)));
+
+ /* Do the DMA transfer. */
+ rc2 = ichac97R3StreamTransfer(pThis, pStream, RT_MIN(pStream->State.cbTransferChunk, cbStreamFree));
+ AssertRC(rc2);
+
+ pStream->State.tsLastUpdateNs = RTTimeNanoTS();
+ }
+ }
+
+ Log3Func(("[SD%RU8] fInTimer=%RTbool\n", pStream->u8SD, fInTimer));
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ rc2 = ichac97R3StreamAsyncIONotify(pThis, pStream);
+ AssertRC(rc2);
+# endif
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ if (!fInTimer) /* In async I/O thread */
+ {
+# endif
+ const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink);
+ const uint32_t cbStreamReadable = ichac97R3StreamGetUsed(pStream);
+ const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable);
+
+ Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStream->u8SD, cbSinkWritable, cbStreamReadable));
+
+ if (cbToReadFromStream)
+ {
+ /* Read (guest output) data and write it to the stream's sink. */
+ rc2 = ichac97R3StreamRead(pThis, pStream, pSink, cbToReadFromStream, NULL);
+ AssertRC(rc2);
+ }
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ }
+#endif
+ /* When running synchronously, update the associated sink here.
+ * Otherwise this will be done in the async I/O thread. */
+ rc2 = AudioMixerSinkUpdate(pSink);
+ AssertRC(rc2);
+ }
+ else /* Input (SDI). */
+ {
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ if (!fInTimer)
+ {
+# endif
+ rc2 = AudioMixerSinkUpdate(pSink);
+ AssertRC(rc2);
+
+ /* Is the sink ready to be read (host input data) from? If so, by how much? */
+ uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink);
+
+ /* How much (guest input) data is available for writing at the moment for the AC'97 stream? */
+ uint32_t cbStreamFree = ichac97R3StreamGetFree(pStream);
+
+ Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStream->u8SD, cbSinkReadable, cbStreamFree));
+
+ /* Do not read more than the sink can provide at the moment.
+ * The host sets the overall pace. */
+ if (cbSinkReadable > cbStreamFree)
+ cbSinkReadable = cbStreamFree;
+
+ if (cbSinkReadable)
+ {
+ /* Write (guest input) data to the stream which was read from stream's sink before. */
+ rc2 = ichac97R3StreamWrite(pThis, pStream, pSink, cbSinkReadable, NULL /* pcbWritten */);
+ AssertRC(rc2);
+ }
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ }
+ else /* fInTimer */
+ {
+# endif
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ const uint64_t tsNowNs = RTTimeNanoTS();
+ if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS)
+ {
+ rc2 = ichac97R3StreamAsyncIONotify(pThis, pStream);
+ AssertRC(rc2);
+
+ pStream->State.tsLastUpdateNs = tsNowNs;
+ }
+# endif
+
+ const uint32_t cbStreamUsed = ichac97R3StreamGetUsed(pStream);
+ if (cbStreamUsed)
+ {
+ /* When running synchronously, do the DMA data transfers here.
+ * Otherwise this will be done in the stream's async I/O thread. */
+ rc2 = ichac97R3StreamTransfer(pThis, pStream, cbStreamUsed);
+ AssertRC(rc2);
+ }
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ }
+# endif
+ }
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Sets a AC'97 mixer control to a specific value.
+ *
+ * @returns IPRT status code.
+ * @param pThis 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)));
+ 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 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
+
+/**
+ * Retrieves a specific driver stream of a AC'97 driver.
+ *
+ * @returns Pointer to driver stream if found, or NULL if not found.
+ * @param pThis AC'97 state.
+ * @param pDrv Driver to retrieve driver stream for.
+ * @param enmDir Stream direction to retrieve.
+ * @param dstSrc Stream destination / source to retrieve.
+ */
+static PAC97DRIVERSTREAM ichac97R3MixerGetDrvStream(PAC97STATE pThis, PAC97DRIVER pDrv,
+ PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc)
+{
+ RT_NOREF(pThis);
+
+ PAC97DRIVERSTREAM pDrvStream = NULL;
+
+ if (enmDir == PDMAUDIODIR_IN)
+ {
+ LogFunc(("enmRecSource=%d\n", dstSrc.Source));
+
+ switch (dstSrc.Source)
+ {
+ case PDMAUDIORECSOURCE_LINE:
+ pDrvStream = &pDrv->LineIn;
+ break;
+ case PDMAUDIORECSOURCE_MIC:
+ pDrvStream = &pDrv->MicIn;
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ else if (enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("enmPlaybackDest=%d\n", dstSrc.Dest));
+
+ switch (dstSrc.Dest)
+ {
+ case PDMAUDIOPLAYBACKDEST_FRONT:
+ pDrvStream = &pDrv->Out;
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ else
+ AssertFailed();
+
+ return pDrvStream;
+}
+
+/**
+ * Adds a driver stream to a specific mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pMixSink Mixer sink to add driver stream to.
+ * @param pCfg Stream configuration to use.
+ * @param pDrv Driver stream to add.
+ */
+static int ichac97R3MixerAddDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg, PAC97DRIVER pDrv)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ PPDMAUDIOSTREAMCFG pStreamCfg = DrvAudioHlpStreamCfgDup(pCfg);
+ if (!pStreamCfg)
+ return VERR_NO_MEMORY;
+
+ if (!RTStrPrintf(pStreamCfg->szName, sizeof(pStreamCfg->szName), "%s", pCfg->szName))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamCfg);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pStreamCfg->szName));
+
+ int rc;
+
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pThis, pDrv, pStreamCfg->enmDir, pStreamCfg->DestSource);
+ 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, pStreamCfg, 0 /* fFlags */, &pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Created stream \"%s\" for sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerSinkAddStream(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Added stream \"%s\" to sink, rc=%Rrc\n", pDrv->uLUN, pStreamCfg->szName, rc));
+ if (RT_SUCCESS(rc))
+ {
+ /* If this is an input stream, always set the latest (added) stream
+ * as the recording source.
+ * @todo Make the recording source dynamic (CFGM?). */
+ if (pStreamCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ PDMAUDIOBACKENDCFG Cfg;
+ rc = pDrv->pConnector->pfnGetConfig(pDrv->pConnector, &Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (Cfg.cMaxStreamsIn) /* At least one input source available? */
+ {
+ rc = AudioMixerSinkSetRecordingSource(pMixSink, pMixStrm);
+ LogFlowFunc(("LUN#%RU8: Recording source for '%s' -> '%s', rc=%Rrc\n",
+ pDrv->uLUN, pStreamCfg->szName, Cfg.szName, rc));
+
+ if (RT_SUCCESS(rc))
+ LogRel2(("AC97: Set recording source for '%s' to '%s'\n", pStreamCfg->szName, Cfg.szName));
+ }
+ else
+ LogRel(("AC97: Backend '%s' currently is not offering any recording source for '%s'\n",
+ Cfg.szName, pStreamCfg->szName));
+ }
+ else if (RT_FAILURE(rc))
+ LogFunc(("LUN#%RU8: Unable to retrieve backend configuratio for '%s', rc=%Rrc\n",
+ pDrv->uLUN, pStreamCfg->szName, rc));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ pDrvStream->pMixStrm = pMixStrm;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ DrvAudioHlpStreamCfgFree(pStreamCfg);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds all current driver streams to a specific mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pMixSink Mixer sink to add stream to.
+ * @param pCfg Stream configuration to use.
+ */
+static int ichac97R3MixerAddDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMixSink, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ if (!DrvAudioHlpStreamCfgIsValid(pCfg))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = AudioMixerSinkSetFormat(pMixSink, &pCfg->Props);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pThis, 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. */
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds a specific AC'97 driver to the driver chain.
+ *
+ * @return IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pDrv AC'97 driver to add.
+ */
+static int ichac97R3MixerAddDrv(PAC97STATE pThis, PAC97DRIVER pDrv)
+{
+ int rc = VINF_SUCCESS;
+
+ if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkLineIn,
+ &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkOut,
+ &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (DrvAudioHlpStreamCfgIsValid(&pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg))
+ {
+ int rc2 = ichac97R3MixerAddDrvStream(pThis, pThis->pSinkMicIn,
+ &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX].State.Cfg, pDrv);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+/**
+ * Removes a specific AC'97 driver from the driver chain and destroys its
+ * associated streams.
+ *
+ * @param pThis AC'97 state.
+ * @param pDrv AC'97 driver to remove.
+ */
+static void ichac97R3MixerRemoveDrv(PAC97STATE pThis, PAC97DRIVER pDrv)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pDrv);
+
+ if (pDrv->MicIn.pMixStrm)
+ {
+ if (AudioMixerSinkGetRecordingSource(pThis->pSinkMicIn) == pDrv->MicIn.pMixStrm)
+ AudioMixerSinkSetRecordingSource(pThis->pSinkMicIn, NULL);
+
+ AudioMixerSinkRemoveStream(pThis->pSinkMicIn, pDrv->MicIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->MicIn.pMixStrm);
+ pDrv->MicIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->LineIn.pMixStrm)
+ {
+ if (AudioMixerSinkGetRecordingSource(pThis->pSinkLineIn) == pDrv->LineIn.pMixStrm)
+ AudioMixerSinkSetRecordingSource(pThis->pSinkLineIn, NULL);
+
+ AudioMixerSinkRemoveStream(pThis->pSinkLineIn, pDrv->LineIn.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->LineIn.pMixStrm);
+ pDrv->LineIn.pMixStrm = NULL;
+ }
+
+ if (pDrv->Out.pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pThis->pSinkOut, pDrv->Out.pMixStrm);
+ AudioMixerStreamDestroy(pDrv->Out.pMixStrm);
+ pDrv->Out.pMixStrm = NULL;
+ }
+
+ RTListNodeRemove(&pDrv->Node);
+}
+
+/**
+ * Removes a driver stream from a specific mixer sink.
+ *
+ * @param pThis AC'97 state.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param dstSrc Stream destination / source to remove.
+ * @param pDrv Driver stream to remove.
+ */
+static void ichac97R3MixerRemoveDrvStream(PAC97STATE pThis, PAUDMIXSINK pMixSink,
+ PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc, PAC97DRIVER pDrv)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pMixSink);
+
+ PAC97DRIVERSTREAM pDrvStream = ichac97R3MixerGetDrvStream(pThis, pDrv, enmDir, dstSrc);
+ if (pDrvStream)
+ {
+ if (pDrvStream->pMixStrm)
+ {
+ AudioMixerSinkRemoveStream(pMixSink, pDrvStream->pMixStrm);
+
+ AudioMixerStreamDestroy(pDrvStream->pMixStrm);
+ pDrvStream->pMixStrm = NULL;
+ }
+ }
+}
+
+/**
+ * Removes all driver streams from a specific mixer sink.
+ *
+ * @param pThis AC'97 state.
+ * @param pMixSink Mixer sink to remove audio streams from.
+ * @param enmDir Stream direction to remove.
+ * @param dstSrc Stream destination / source to remove.
+ */
+static void ichac97R3MixerRemoveDrvStreams(PAC97STATE pThis, PAUDMIXSINK pMixSink,
+ PDMAUDIODIR enmDir, PDMAUDIODESTSOURCE dstSrc)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pMixSink);
+
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ ichac97R3MixerRemoveDrvStream(pThis, pMixSink, enmDir, dstSrc, pDrv);
+ }
+}
+
+/**
+ * Calculates and returns the ticks for a specified amount of bytes.
+ *
+ * @returns Calculated ticks
+ * @param pThis AC'97 device state.
+ * @param pStream AC'97 stream to calculate ticks for.
+ * @param cbBytes Bytes to calculate ticks for.
+ */
+static uint64_t ichac97R3StreamTransferCalcNext(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbBytes)
+{
+ if (!cbBytes)
+ return 0;
+
+ const uint64_t usBytes = DrvAudioHlpBytesToMicro(cbBytes, &pStream->State.Cfg.Props);
+ const uint64_t cTransferTicks = TMTimerFromMicro((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD), usBytes);
+
+ Log3Func(("[SD%RU8] Timer %uHz, cbBytes=%RU32 -> usBytes=%RU64, cTransferTicks=%RU64\n",
+ pStream->u8SD, pStream->State.uTimerHz, cbBytes, usBytes, cTransferTicks));
+
+ return cTransferTicks;
+}
+
+/**
+ * Updates the next transfer based on a specific amount of bytes.
+ *
+ * @param pThis AC'97 device state.
+ * @param pStream AC'97 stream to update.
+ * @param cbBytes Bytes to update next transfer for.
+ */
+static void ichac97R3StreamTransferUpdate(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbBytes)
+{
+ if (!cbBytes)
+ return;
+
+ /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration.
+ * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */
+ pStream->State.cbTransferChunk = cbBytes;
+
+ /* Update the transfer ticks. */
+ pStream->State.cTransferTicks = ichac97R3StreamTransferCalcNext(pThis, pStream, pStream->State.cbTransferChunk);
+ Assert(pStream->State.cTransferTicks); /* Paranoia. */
+}
+
+/**
+ * Opens an AC'97 stream with its current mixer settings.
+ *
+ * This will open 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 IPRT status code.
+ * @param pThis AC'97 device state.
+ * @param pStream AC'97 stream to open.
+ */
+static int ichac97R3StreamOpen(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ int rc = VINF_SUCCESS;
+
+ PDMAUDIOSTREAMCFG Cfg;
+ RT_ZERO(Cfg);
+
+ PAUDMIXSINK pMixSink = NULL;
+
+ Cfg.Props.cChannels = 2;
+ Cfg.Props.cBytes = 2 /* 16-bit */;
+ Cfg.Props.fSigned = true;
+ Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cBytes, Cfg.Props.cChannels);
+
+ switch (pStream->u8SD)
+ {
+ case AC97SOUNDSOURCE_PI_INDEX:
+ {
+ Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_LR_ADC_Rate);
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.DestSource.Source = PDMAUDIORECSOURCE_LINE;
+ Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Line-In");
+
+ pMixSink = pThis->pSinkLineIn;
+ break;
+ }
+
+ case AC97SOUNDSOURCE_MC_INDEX:
+ {
+ Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_MIC_ADC_Rate);
+ Cfg.enmDir = PDMAUDIODIR_IN;
+ Cfg.DestSource.Source = PDMAUDIORECSOURCE_MIC;
+ Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Mic-In");
+
+ pMixSink = pThis->pSinkMicIn;
+ break;
+ }
+
+ case AC97SOUNDSOURCE_PO_INDEX:
+ {
+ Cfg.Props.uHz = ichac97MixerGet(pThis, AC97_PCM_Front_DAC_Rate);
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT;
+ Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+ RTStrCopy(Cfg.szName, sizeof(Cfg.szName), "Output");
+
+ pMixSink = pThis->pSinkOut;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Only (re-)create the stream (and driver chain) if we really have to.
+ * Otherwise avoid this and just reuse it, as this costs performance. */
+ if (!DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pStream->State.Cfg.Props))
+ {
+ LogFlowFunc(("[SD%RU8] uHz=%RU32\n", pStream->u8SD, Cfg.Props.uHz));
+
+ if (Cfg.Props.uHz)
+ {
+ Assert(Cfg.enmDir != PDMAUDIODIR_UNKNOWN);
+
+ /*
+ * Set the stream's timer Hz rate, based on the PCM properties Hz rate.
+ */
+ 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. */
+ pStream->State.uTimerHz = 200;
+ else /* Just take the global Hz rate otherwise. */
+ pStream->State.uTimerHz = pThis->uTimerHz;
+ }
+ else
+ pStream->State.uTimerHz = pThis->uTimerHz;
+
+ /* Set scheduling hint (if available). */
+ if (pStream->State.uTimerHz)
+ Cfg.Device.uSchedulingHintMs = 1000 /* ms */ / pStream->State.uTimerHz;
+
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ rc = RTCircBufCreate(&pStream->State.pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &Cfg.Props)); /** @todo Make this configurable. */
+ if (RT_SUCCESS(rc))
+ {
+ ichac97R3MixerRemoveDrvStreams(pThis, pMixSink, Cfg.enmDir, Cfg.DestSource);
+
+ rc = ichac97R3MixerAddDrvStreams(pThis, pMixSink, &Cfg);
+ if (RT_SUCCESS(rc))
+ rc = DrvAudioHlpStreamCfgCopy(&pStream->State.Cfg, &Cfg);
+ }
+ }
+ }
+ else
+ LogFlowFunc(("[SD%RU8] Skipping (re-)creation\n", pStream->u8SD));
+ }
+
+ LogFlowFunc(("[SD%RU8] rc=%Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Closes an AC'97 stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to close.
+ */
+static int ichac97R3StreamClose(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ RT_NOREF(pThis, pStream);
+
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Re-opens (that is, closes and opens again) an AC'97 stream on the backend
+ * side with the current AC'97 mixer settings for this stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 device state.
+ * @param pStream AC'97 stream to re-open.
+ */
+static int ichac97R3StreamReOpen(PAC97STATE pThis, PAC97STREAM pStream)
+{
+ LogFlowFunc(("[SD%RU8]\n", pStream->u8SD));
+
+ int rc = ichac97R3StreamClose(pThis, pStream);
+ if (RT_SUCCESS(rc))
+ rc = ichac97R3StreamOpen(pThis, pStream);
+
+ return rc;
+}
+
+/**
+ * Locks an AC'97 stream for serialized access.
+ *
+ * @returns IPRT status code.
+ * @param pStream AC'97 stream to lock.
+ */
+static void ichac97R3StreamLock(PAC97STREAM pStream)
+{
+ AssertPtrReturnVoid(pStream);
+ int rc2 = RTCritSectEnter(&pStream->State.CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks a formerly locked AC'97 stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream AC'97 stream to unlock.
+ */
+static void ichac97R3StreamUnlock(PAC97STREAM pStream)
+{
+ AssertPtrReturnVoid(pStream);
+ int rc2 = RTCritSectLeave(&pStream->State.CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Retrieves the available size of (buffered) audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Available data (in bytes).
+ * @param pStream AC'97 stream to retrieve size for.
+ */
+static uint32_t ichac97R3StreamGetUsed(PAC97STREAM pStream)
+{
+ AssertPtrReturn(pStream, 0);
+
+ if (!pStream->State.pCircBuf)
+ return 0;
+
+ return (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
+}
+
+/**
+ * Retrieves the free size of audio data (in bytes) of a given AC'97 stream.
+ *
+ * @returns Free data (in bytes).
+ * @param pStream AC'97 stream to retrieve size for.
+ */
+static uint32_t ichac97R3StreamGetFree(PAC97STREAM pStream)
+{
+ AssertPtrReturn(pStream, 0);
+
+ if (!pStream->State.pCircBuf)
+ return 0;
+
+ return (uint32_t)RTCircBufFree(pStream->State.pCircBuf);
+}
+
+/**
+ * Sets the volume of a specific AC'97 mixer control.
+ *
+ * This currently only supports attenuation -- gain support is currently not implemented.
+ *
+ * @returns IPRT status code.
+ * @param pThis 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, 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 (pThis->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol };
+ PAUDMIXSINK pSink = NULL;
+
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER:
+ rc = AudioMixerSetMasterVolume(pThis->pMixer, &Vol);
+ break;
+
+ case PDMAUDIOMIXERCTL_FRONT:
+ pSink = pThis->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.
+ *
+ * NB: gain support is currently not implemented in PDM audio.
+ *
+ * @returns IPRT status code.
+ * @param pThis 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, 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.
+ */
+ const bool 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 (pThis->pMixer) /* Device can be in reset state, so no mixer available. */
+ {
+ PDMAUDIOVOLUME Vol = { fCtlMuted, lVol, rVol };
+ PAUDMIXSINK pSink = NULL;
+
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_MIC_IN:
+ pSink = pThis->pSinkMicIn;
+ break;
+
+ case PDMAUDIOMIXERCTL_LINE_IN:
+ pSink = pThis->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 == pThis->pSinkLineIn) && pThis->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 PDMAUDIORECSOURCE ichac97R3IdxToRecSource(uint8_t uIdx)
+{
+ switch (uIdx)
+ {
+ case AC97_REC_MIC: return PDMAUDIORECSOURCE_MIC;
+ case AC97_REC_CD: return PDMAUDIORECSOURCE_CD;
+ case AC97_REC_VIDEO: return PDMAUDIORECSOURCE_VIDEO;
+ case AC97_REC_AUX: return PDMAUDIORECSOURCE_AUX;
+ case AC97_REC_LINE_IN: return PDMAUDIORECSOURCE_LINE;
+ case AC97_REC_PHONE: return PDMAUDIORECSOURCE_PHONE;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("Unknown record source %d, using MIC\n", uIdx));
+ return PDMAUDIORECSOURCE_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(PDMAUDIORECSOURCE enmRecSrc)
+{
+ switch (enmRecSrc)
+ {
+ case PDMAUDIORECSOURCE_MIC: return AC97_REC_MIC;
+ case PDMAUDIORECSOURCE_CD: return AC97_REC_CD;
+ case PDMAUDIORECSOURCE_VIDEO: return AC97_REC_VIDEO;
+ case PDMAUDIORECSOURCE_AUX: return AC97_REC_AUX;
+ case PDMAUDIORECSOURCE_LINE: return AC97_REC_LINE_IN;
+ case PDMAUDIORECSOURCE_PHONE: return AC97_REC_PHONE;
+ default:
+ break;
+ }
+
+ LogFlowFunc(("Unknown audio recording source %d using MIC\n", enmRecSrc));
+ return AC97_REC_MIC;
+}
+
+/**
+ * Returns the audio direction of a specified stream descriptor.
+ *
+ * @return Audio direction.
+ */
+DECLINLINE(PDMAUDIODIR) ichac97GetDirFromSD(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;
+}
+
+#endif /* IN_RING3 */
+
+#ifdef IN_RING3
+
+/**
+ * Performs an AC'97 mixer record select to switch to a different recording
+ * source.
+ *
+ * @param pThis 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;
+ PDMAUDIORECSOURCE ars = ichac97R3IdxToRecSource(rs);
+ PDMAUDIORECSOURCE als = ichac97R3IdxToRecSource(ls);
+ rs = ichac97R3RecSourceToIdx(ars);
+ ls = ichac97R3RecSourceToIdx(als);
+ ichac97MixerSet(pThis, AC97_Record_Select, rs | (ls << 8));
+}
+
+/**
+ * Resets the AC'97 mixer.
+ *
+ * @returns IPRT status code.
+ * @param pThis AC'97 state.
+ */
+static int ichac97R3MixerReset(PAC97STATE pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+
+ 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. */
+ uint16_t fEAID = AC97_EAID_REV1; /* Our hardware is AC'97 rev2.3 compliant. */
+ uint16_t fEACS = 0;
+#ifdef VBOX_WITH_AC97_VRA
+ fEAID |= AC97_EAID_VRA; /* Variable Rate PCM Audio capable. */
+ fEACS |= AC97_EACS_VRA; /* Ditto. */
+#endif
+#ifdef VBOX_WITH_AC97_VRM
+ fEAID |= AC97_EAID_VRM; /* Variable Rate Mic-In Audio capable. */
+ fEACS |= AC97_EACS_VRM; /* Ditto. */
+#endif
+
+ ichac97MixerSet(pThis, AC97_Extended_Audio_ID, fEAID);
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, fEACS);
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate , 0xbb80);
+ ichac97MixerSet(pThis, AC97_PCM_Surround_DAC_Rate , 0xbb80);
+ ichac97MixerSet(pThis, AC97_PCM_LFE_DAC_Rate , 0xbb80);
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate , 0xbb80);
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate , 0xbb80);
+
+ if (pThis->uCodecModel == AC97_CODEC_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->uCodecModel == AC97_CODEC_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, 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, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, 0x8808);
+ ichac97R3MixerSetVolume(pThis, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8808);
+ ichac97R3MixerSetVolume(pThis, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8008);
+
+ /* The default for record controls is 0 dB gain with mute on. */
+ ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, 0x8000);
+ ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, 0x8000);
+
+ return VINF_SUCCESS;
+}
+
+# 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(pThis->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 */
+
+/**
+ * Timer callback which handles the audio data transfers on a periodic basis.
+ *
+ * @param pDevIns Device instance.
+ * @param pTimer Timer which was used when calling this.
+ * @param pvUser User argument as PAC97STATE.
+ */
+static DECLCALLBACK(void) ichac97R3Timer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, pTimer);
+
+ PAC97STREAM pStream = (PAC97STREAM)pvUser;
+ AssertPtr(pStream);
+
+ PAC97STATE pThis = pStream->pAC97State;
+ AssertPtr(pThis);
+
+ STAM_PROFILE_START(&pThis->StatTimer, a);
+
+ DEVAC97_LOCK_BOTH_RETURN_VOID(pThis, pStream->u8SD);
+
+ ichac97R3StreamUpdate(pThis, pStream, true /* fInTimer */);
+
+ PAUDMIXSINK pSink = ichac97R3IndexToSink(pThis, pStream->u8SD);
+
+ bool fSinkActive = false;
+ if (pSink)
+ fSinkActive = AudioMixerSinkIsActive(pSink);
+
+ if (fSinkActive)
+ {
+ ichac97R3StreamTransferUpdate(pThis, pStream, pStream->Regs.picb << 1); /** @todo r=andy Assumes 16-bit samples. */
+
+ ichac97TimerSet(pThis,pStream,
+ TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks,
+ false /* fForce */);
+ }
+
+ DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD);
+
+ STAM_PROFILE_STOP(&pThis->StatTimer, a);
+}
+#endif /* IN_RING3 */
+
+/**
+ * Sets the virtual device timer to a new expiration time.
+ *
+ * @returns Whether the new expiration time was set or not.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to set timer for.
+ * @param tsExpire New (virtual) expiration time to set.
+ * @param fForce Whether to force setting the expiration time or not.
+ *
+ * @remark This function takes all active AC'97 streams and their
+ * current timing into account. This is needed to make sure
+ * that all streams can match their needed timing.
+ *
+ * To achieve this, the earliest (lowest) timestamp of all
+ * active streams found will be used for the next scheduling slot.
+ *
+ * Forcing a new expiration time will override the above mechanism.
+ */
+bool ichac97TimerSet(PAC97STATE pThis, PAC97STREAM pStream, uint64_t tsExpire, bool fForce)
+{
+ AssertPtrReturn(pThis, false);
+ AssertPtrReturn(pStream, false);
+
+ RT_NOREF(fForce);
+
+ uint64_t tsExpireMin = tsExpire;
+
+ AssertPtr((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD));
+
+ const uint64_t tsNow = TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD));
+
+ /* Make sure to not go backwards in time, as this will assert in TMTimerSet(). */
+ if (tsExpireMin < tsNow)
+ tsExpireMin = tsNow;
+
+ int rc = TMTimerSet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD), tsExpireMin);
+ AssertRC(rc);
+
+ return RT_SUCCESS(rc);
+}
+
+#ifdef IN_RING3
+
+/**
+ * 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 IPRT status code.
+ * @param pThis AC'97 state.
+ * @param pStream AC'97 stream to update.
+ * @param cbToProcessMax Maximum of data (in bytes) to process.
+ */
+static int ichac97R3StreamTransfer(PAC97STATE pThis, PAC97STREAM pStream, uint32_t cbToProcessMax)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ if (!cbToProcessMax)
+ return VINF_SUCCESS;
+
+#ifdef VBOX_STRICT
+ const unsigned cbFrame = DrvAudioHlpPCMPropsBytesPerFrame(&pStream->State.Cfg.Props);
+#endif
+
+ /* Make sure to only process an integer number of audio frames. */
+ Assert(cbToProcessMax % cbFrame == 0);
+
+ ichac97R3StreamLock(pStream);
+
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ if (pRegs->sr & AC97_SR_DCH) /* Controller halted? */
+ {
+ if (pRegs->cr & AC97_CR_RPBM) /* Bus master operation starts. */
+ {
+ switch (pStream->u8SD)
+ {
+ case AC97SOUNDSOURCE_PO_INDEX:
+ /*ichac97R3WriteBUP(pThis, cbToProcess);*/
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ ichac97R3StreamUnlock(pStream);
+ return VINF_SUCCESS;
+ }
+
+ /* BCIS flag still set? Skip iteration. */
+ if (pRegs->sr & AC97_SR_BCIS)
+ {
+ Log3Func(("[SD%RU8] BCIS set\n", pStream->u8SD));
+
+ ichac97R3StreamUnlock(pStream);
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cbLeft = RT_MIN((uint32_t)(pRegs->picb << 1), cbToProcessMax); /** @todo r=andy Assumes 16bit samples. */
+ uint32_t cbProcessedTotal = 0;
+
+ PRTCIRCBUF pCircBuf = pStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ int rc = VINF_SUCCESS;
+
+ Log3Func(("[SD%RU8] cbToProcessMax=%RU32, cbLeft=%RU32\n", pStream->u8SD, cbToProcessMax, cbLeft));
+
+ while (cbLeft)
+ {
+ if (!pRegs->picb) /* Got a new buffer descriptor, that is, the position is 0? */
+ {
+ Log3Func(("Fresh buffer descriptor %RU8 is empty, addr=%#x, len=%#x, skipping\n",
+ pRegs->civ, pRegs->bd.addr, pRegs->bd.ctl_len));
+ if (pRegs->civ == pRegs->lvi)
+ {
+ pRegs->sr |= AC97_SR_DCH; /** @todo r=andy Also set CELV? */
+ pThis->bup_flag = 0;
+
+ rc = VINF_EOF;
+ break;
+ }
+
+ pRegs->sr &= ~AC97_SR_CELV;
+ pRegs->civ = pRegs->piv;
+ pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE;
+
+ ichac97R3StreamFetchBDLE(pThis, pStream);
+ continue;
+ }
+
+ uint32_t cbChunk = cbLeft;
+
+ switch (pStream->u8SD)
+ {
+ case AC97SOUNDSOURCE_PO_INDEX: /* Output */
+ {
+ void *pvDst;
+ size_t cbDst;
+
+ RTCircBufAcquireWriteBlock(pCircBuf, cbChunk, &pvDst, &cbDst);
+
+ if (cbDst)
+ {
+ int rc2 = PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvDst, cbDst);
+ AssertRC(rc2);
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pvDst, cbDst, 0 /* fFlags */);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDst);
+
+ cbChunk = (uint32_t)cbDst; /* Update the current chunk size to what really has been written. */
+ break;
+ }
+
+ case AC97SOUNDSOURCE_PI_INDEX: /* Input */
+ case AC97SOUNDSOURCE_MC_INDEX: /* Input */
+ {
+ void *pvSrc;
+ size_t cbSrc;
+
+ RTCircBufAcquireReadBlock(pCircBuf, cbChunk, &pvSrc, &cbSrc);
+
+ if (cbSrc)
+ {
+/** @todo r=bird: Just curious, DevHDA uses PDMDevHlpPCIPhysWrite here. So,
+ * is AC97 not subject to PCI busmaster enable/disable? */
+ int rc2 = PDMDevHlpPhysWrite(pThis->CTX_SUFF(pDevIns), pRegs->bd.addr, (uint8_t *)pvSrc, cbSrc);
+ AssertRC(rc2);
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMA, pvSrc, cbSrc, 0 /* fFlags */);
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbSrc);
+
+ cbChunk = (uint32_t)cbSrc; /* Update the current chunk size to what really has been read. */
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Stream #%RU8 not supported\n", pStream->u8SD));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if (cbChunk)
+ {
+ cbProcessedTotal += cbChunk;
+ Assert(cbProcessedTotal <= cbToProcessMax);
+ Assert(cbLeft >= cbChunk);
+ cbLeft -= cbChunk;
+ Assert((cbChunk & 1) == 0); /* Else the following shift won't work */
+
+ pRegs->picb -= (cbChunk >> 1); /** @todo r=andy Assumes 16bit samples. */
+ pRegs->bd.addr += cbChunk;
+ }
+
+ LogFlowFunc(("[SD%RU8] cbChunk=%RU32, cbLeft=%RU32, cbTotal=%RU32, rc=%Rrc\n",
+ pStream->u8SD, cbChunk, cbLeft, cbProcessedTotal, rc));
+
+ if (!pRegs->picb)
+ {
+ uint32_t new_sr = pRegs->sr & ~AC97_SR_CELV;
+
+ if (pRegs->bd.ctl_len & AC97_BD_IOC)
+ {
+ new_sr |= AC97_SR_BCIS;
+ }
+
+ if (pRegs->civ == pRegs->lvi)
+ {
+ /* Did we run out of data? */
+ LogFunc(("Underrun CIV (%RU8) == LVI (%RU8)\n", pRegs->civ, pRegs->lvi));
+
+ new_sr |= AC97_SR_LVBCI | AC97_SR_DCH | AC97_SR_CELV;
+ pThis->bup_flag = (pRegs->bd.ctl_len & AC97_BD_BUP) ? BUP_LAST : 0;
+
+ rc = VINF_EOF;
+ }
+ else
+ {
+ pRegs->civ = pRegs->piv;
+ pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE;
+ ichac97R3StreamFetchBDLE(pThis, pStream);
+ }
+
+ ichac97StreamUpdateSR(pThis, pStream, new_sr);
+ }
+
+ if (/* All data processed? */
+ rc == VINF_EOF
+ /* ... or an error occurred? */
+ || RT_FAILURE(rc))
+ {
+ break;
+ }
+ }
+
+ ichac97R3StreamUnlock(pStream);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+
+/**
+ * Port I/O Handler for IN operations.
+ *
+ * @returns VINF_SUCCESS or VINF_EM_*.
+ * @returns VERR_IOM_IOPORT_UNUSED if the port is really unused and a ~0 value should be returned.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param pu32Val Where to store the result. This is always a 32-bit
+ * variable regardless of what @a cbVal might say.
+ * @param cbVal Number of bytes read.
+ */
+PDMBOTHCBDECL(int) ichac97IOPortNABMRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32Val, unsigned cbVal)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_READ);
+
+ /* Get the index of the NABMBAR port. */
+ const uint32_t uPortIdx = uPort - pThis->IOPortBase[1];
+
+ PAC97STREAM pStream = NULL;
+ PAC97BMREGS pRegs = NULL;
+
+ if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS)
+ {
+ pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)];
+ AssertPtr(pStream);
+ pRegs = &pStream->Regs;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ switch (cbVal)
+ {
+ case 1:
+ {
+ switch (uPortIdx)
+ {
+ case AC97_CAS:
+ /* Codec Access Semaphore Register */
+ Log3Func(("CAS %d\n", pThis->cas));
+ *pu32Val = pThis->cas;
+ pThis->cas = 1;
+ break;
+ case PI_CIV:
+ case PO_CIV:
+ case MC_CIV:
+ /* Current Index Value Register */
+ *pu32Val = pRegs->civ;
+ Log3Func(("CIV[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_LVI:
+ case PO_LVI:
+ case MC_LVI:
+ /* Last Valid Index Register */
+ *pu32Val = pRegs->lvi;
+ Log3Func(("LVI[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_PIV:
+ case PO_PIV:
+ case MC_PIV:
+ /* Prefetched Index Value Register */
+ *pu32Val = pRegs->piv;
+ Log3Func(("PIV[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_CR:
+ case PO_CR:
+ case MC_CR:
+ /* Control Register */
+ *pu32Val = pRegs->cr;
+ Log3Func(("CR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ /* Status Register (lower part) */
+ *pu32Val = RT_LO_U8(pRegs->sr);
+ Log3Func(("SRb[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ default:
+ *pu32Val = UINT32_MAX;
+ LogFunc(("U nabm readb %#x -> %#x\n", uPort, *pu32Val));
+ break;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ switch (uPortIdx)
+ {
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ /* Status Register */
+ *pu32Val = pRegs->sr;
+ Log3Func(("SR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_PICB:
+ case PO_PICB:
+ case MC_PICB:
+ /* Position in Current Buffer */
+ *pu32Val = pRegs->picb;
+ Log3Func(("PICB[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ default:
+ *pu32Val = UINT32_MAX;
+ LogFunc(("U nabm readw %#x -> %#x\n", uPort, *pu32Val));
+ break;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ switch (uPortIdx)
+ {
+ case PI_BDBAR:
+ case PO_BDBAR:
+ case MC_BDBAR:
+ /* Buffer Descriptor Base Address Register */
+ *pu32Val = pRegs->bdbar;
+ Log3Func(("BMADDR[%d] -> %#x\n", AC97_PORT2IDX(uPortIdx), *pu32Val));
+ break;
+ case PI_CIV:
+ case PO_CIV:
+ case MC_CIV:
+ /* 32-bit access: Current Index Value Register +
+ * Last Valid Index Register +
+ * Status Register */
+ *pu32Val = pRegs->civ | (pRegs->lvi << 8) | (pRegs->sr << 16); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */
+ Log3Func(("CIV LVI SR[%d] -> %#x, %#x, %#x\n",
+ AC97_PORT2IDX(uPortIdx), pRegs->civ, pRegs->lvi, pRegs->sr));
+ break;
+ case PI_PICB:
+ case PO_PICB:
+ case MC_PICB:
+ /* 32-bit access: Position in Current Buffer Register +
+ * Prefetched Index Value Register +
+ * Control Register */
+ *pu32Val = pRegs->picb | (pRegs->piv << 16) | (pRegs->cr << 24); /** @todo r=andy Use RT_MAKE_U32_FROM_U8. */
+ Log3Func(("PICB PIV CR[%d] -> %#x %#x %#x %#x\n",
+ AC97_PORT2IDX(uPortIdx), *pu32Val, pRegs->picb, pRegs->piv, pRegs->cr));
+ break;
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ *pu32Val = pThis->glob_cnt;
+ Log3Func(("glob_cnt -> %#x\n", *pu32Val));
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ *pu32Val = pThis->glob_sta | AC97_GS_S0CR;
+ Log3Func(("glob_sta -> %#x\n", *pu32Val));
+ break;
+ default:
+ *pu32Val = UINT32_MAX;
+ LogFunc(("U nabm readl %#x -> %#x\n", uPort, *pu32Val));
+ break;
+ }
+ break;
+ }
+
+ default:
+ {
+ AssertFailed();
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+
+ DEVAC97_UNLOCK(pThis);
+
+ return rc;
+}
+
+/**
+ * Port I/O Handler for OUT operations.
+ *
+ * @returns VINF_SUCCESS or VINF_EM_*.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the OUT operation.
+ * @param u32Val The value to output.
+ * @param cbVal The value size in bytes.
+ */
+PDMBOTHCBDECL(int) ichac97IOPortNABMWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32Val, unsigned cbVal)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ /* Get the index of the NABMBAR register. */
+ const uint32_t uPortIdx = uPort - pThis->IOPortBase[1];
+
+ PAC97STREAM pStream = NULL;
+ PAC97BMREGS pRegs = NULL;
+
+ if (AC97_PORT2IDX(uPortIdx) < AC97_MAX_STREAMS)
+ {
+ pStream = &pThis->aStreams[AC97_PORT2IDX(uPortIdx)];
+ AssertPtr(pStream);
+ pRegs = &pStream->Regs;
+
+ DEVAC97_LOCK_BOTH_RETURN(pThis, pStream->u8SD, VINF_IOM_R3_IOPORT_WRITE);
+ }
+
+ int rc = VINF_SUCCESS;
+ switch (cbVal)
+ {
+ case 1:
+ {
+ switch (uPortIdx)
+ {
+ /*
+ * Last Valid Index.
+ */
+ case PI_LVI:
+ case PO_LVI:
+ case MC_LVI:
+ {
+ AssertPtr(pStream);
+ AssertPtr(pRegs);
+ if ( (pRegs->cr & AC97_CR_RPBM)
+ && (pRegs->sr & AC97_SR_DCH))
+ {
+#ifdef IN_RING3
+ pRegs->sr &= ~(AC97_SR_DCH | AC97_SR_CELV);
+ pRegs->civ = pRegs->piv;
+ pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE;
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ pRegs->lvi = u32Val % AC97_MAX_BDLE;
+ Log3Func(("[SD%RU8] LVI <- %#x\n", pStream->u8SD, u32Val));
+ break;
+ }
+
+ /*
+ * Control Registers.
+ */
+ case PI_CR:
+ case PO_CR:
+ case MC_CR:
+ {
+ AssertPtr(pStream);
+ AssertPtr(pRegs);
+#ifdef IN_RING3
+ Log3Func(("[SD%RU8] CR <- %#x (cr %#x)\n", pStream->u8SD, u32Val, pRegs->cr));
+ if (u32Val & AC97_CR_RR) /* Busmaster reset. */
+ {
+ Log3Func(("[SD%RU8] Reset\n", pStream->u8SD));
+
+ /* Make sure that Run/Pause Bus Master bit (RPBM) is cleared (0). */
+ Assert((pRegs->cr & AC97_CR_RPBM) == 0);
+
+ ichac97R3StreamEnable(pThis, pStream, false /* fEnable */);
+ ichac97R3StreamReset(pThis, pStream);
+
+ ichac97StreamUpdateSR(pThis, pStream, AC97_SR_DCH); /** @todo Do we need to do that? */
+ }
+ else
+ {
+ pRegs->cr = u32Val & AC97_CR_VALID_MASK;
+
+ if (!(pRegs->cr & AC97_CR_RPBM))
+ {
+ Log3Func(("[SD%RU8] Disable\n", pStream->u8SD));
+
+ ichac97R3StreamEnable(pThis, pStream, false /* fEnable */);
+
+ pRegs->sr |= AC97_SR_DCH;
+ }
+ else
+ {
+ Log3Func(("[SD%RU8] Enable\n", pStream->u8SD));
+
+ pRegs->civ = pRegs->piv;
+ pRegs->piv = (pRegs->piv + 1) % AC97_MAX_BDLE;
+
+ pRegs->sr &= ~AC97_SR_DCH;
+
+ /* Fetch the initial BDLE descriptor. */
+ ichac97R3StreamFetchBDLE(pThis, pStream);
+# ifdef LOG_ENABLED
+ ichac97R3BDLEDumpAll(pThis, pStream->Regs.bdbar, pStream->Regs.lvi + 1);
+# endif
+ ichac97R3StreamEnable(pThis, pStream, true /* fEnable */);
+
+ /* Arm the timer for this stream. */
+ int rc2 = ichac97TimerSet(pThis, pStream,
+ TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks,
+ false /* fForce */);
+ AssertRC(rc2);
+ }
+ }
+#else /* !IN_RING3 */
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ }
+
+ /*
+ * Status Registers.
+ */
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ {
+ ichac97StreamWriteSR(pThis, pStream, u32Val);
+ break;
+ }
+
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val));
+ break;
+ }
+ break;
+ }
+
+ case 2:
+ {
+ switch (uPortIdx)
+ {
+ case PI_SR:
+ case PO_SR:
+ case MC_SR:
+ ichac97StreamWriteSR(pThis, pStream, u32Val);
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val));
+ break;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ switch (uPortIdx)
+ {
+ case PI_BDBAR:
+ case PO_BDBAR:
+ case MC_BDBAR:
+ AssertPtr(pStream);
+ AssertPtr(pRegs);
+ /* Buffer Descriptor list Base Address Register */
+ pRegs->bdbar = u32Val & ~3;
+ Log3Func(("[SD%RU8] BDBAR <- %#x (bdbar %#x)\n", AC97_PORT2IDX(uPortIdx), u32Val, pRegs->bdbar));
+ break;
+ case AC97_GLOB_CNT:
+ /* Global Control */
+ if (u32Val & AC97_GC_WR)
+ ichac97WarmReset(pThis);
+ if (u32Val & AC97_GC_CR)
+ ichac97ColdReset(pThis);
+ if (!(u32Val & (AC97_GC_WR | AC97_GC_CR)))
+ pThis->glob_cnt = u32Val & AC97_GC_VALID_MASK;
+ Log3Func(("glob_cnt <- %#x (glob_cnt %#x)\n", u32Val, pThis->glob_cnt));
+ break;
+ case AC97_GLOB_STA:
+ /* Global Status */
+ pThis->glob_sta &= ~(u32Val & AC97_GS_WCLEAR_MASK);
+ pThis->glob_sta |= (u32Val & ~(AC97_GS_WCLEAR_MASK | AC97_GS_RO_MASK)) & AC97_GS_VALID_MASK;
+ Log3Func(("glob_sta <- %#x (glob_sta %#x)\n", u32Val, pThis->glob_sta));
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val));
+ break;
+ }
+ break;
+ }
+
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NABMWrite (%u byte) portIdx=%#x <- %#x\n", cbVal, uPortIdx, u32Val));
+ break;
+ }
+
+ if (pStream)
+ DEVAC97_UNLOCK_BOTH(pThis, pStream->u8SD);
+
+ return rc;
+}
+
+/**
+ * Port I/O Handler for IN operations.
+ *
+ * @returns VINF_SUCCESS or VINF_EM_*.
+ * @returns VERR_IOM_IOPORT_UNUSED if the port is really unused and a ~0 value should be returned.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the IN operation.
+ * @param pu32Val Where to store the result. This is always a 32-bit
+ * variable regardless of what @a cbVal might say.
+ * @param cbVal Number of bytes read.
+ */
+PDMBOTHCBDECL(int) ichac97IOPortNAMRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32Val, unsigned cbVal)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_READ);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t index = uPort - pThis->IOPortBase[0];
+ Assert(index < 256);
+
+ switch (cbVal)
+ {
+ case 1:
+ {
+ LogRel2(("AC97: Warning: Unimplemented read (%u byte) port=%#x, idx=%RU32\n", cbVal, uPort, index));
+ pThis->cas = 0;
+ *pu32Val = UINT32_MAX;
+ break;
+ }
+
+ case 2:
+ {
+ pThis->cas = 0;
+ *pu32Val = ichac97MixerGet(pThis, index);
+ break;
+ }
+
+ case 4:
+ {
+ LogRel2(("AC97: Warning: Unimplemented read (%u byte) port=%#x, idx=%RU32\n", cbVal, uPort, index));
+ pThis->cas = 0;
+ *pu32Val = UINT32_MAX;
+ break;
+ }
+
+ default:
+ {
+ AssertFailed();
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ }
+
+ DEVAC97_UNLOCK(pThis);
+
+ return rc;
+}
+
+/**
+ * Port I/O Handler for OUT operations.
+ *
+ * @returns VINF_SUCCESS or VINF_EM_*.
+ *
+ * @param pDevIns The device instance.
+ * @param pvUser User argument.
+ * @param uPort Port number used for the OUT operation.
+ * @param u32Val The value to output.
+ * @param cbVal The value size in bytes.
+ * @remarks Caller enters the device critical section.
+ */
+PDMBOTHCBDECL(int) ichac97IOPortNAMWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32Val, unsigned cbVal)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ RT_NOREF(pvUser);
+
+ DEVAC97_LOCK_RETURN(pThis, VINF_IOM_R3_IOPORT_WRITE);
+
+ uint32_t uPortIdx = uPort - pThis->IOPortBase[0];
+
+ int rc = VINF_SUCCESS;
+ switch (cbVal)
+ {
+ case 1:
+ {
+ LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val));
+ pThis->cas = 0;
+ break;
+ }
+
+ case 2:
+ {
+ pThis->cas = 0;
+ switch (uPortIdx)
+ {
+ case AC97_Reset:
+#ifdef IN_RING3
+ ichac97R3Reset(pThis->CTX_SUFF(pDevIns));
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Powerdown_Ctrl_Stat:
+ u32Val &= ~0xf;
+ u32Val |= ichac97MixerGet(pThis, uPortIdx) & 0xf;
+ ichac97MixerSet(pThis, uPortIdx, u32Val);
+ break;
+ case AC97_Master_Volume_Mute:
+ if (pThis->uCodecModel == AC97_CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_LOSEL)
+ break; /* Register controls surround (rear), do nothing. */
+ }
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Headphone_Volume_Mute:
+ if (pThis->uCodecModel == AC97_CODEC_AD1980)
+ {
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ {
+ /* Register controls PCM (front) outputs. */
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_VOLUME_MASTER, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ }
+ break;
+ case AC97_PCM_Out_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_FRONT, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Line_In_Volume_Mute:
+#ifdef IN_RING3
+ ichac97R3MixerSetVolume(pThis, uPortIdx, PDMAUDIOMIXERCTL_LINE_IN, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Select:
+#ifdef IN_RING3
+ ichac97R3MixerRecordSelect(pThis, u32Val);
+#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, uPortIdx, PDMAUDIOMIXERCTL_LINE_IN, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_Record_Gain_Mic_Mute:
+#ifdef IN_RING3
+ /* Ditto; see note above. */
+ ichac97R3MixerSetGain(pThis, uPortIdx, PDMAUDIOMIXERCTL_MIC_IN, u32Val);
+#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", u32Val));
+ break;
+ case AC97_Extended_Audio_ID:
+ LogFunc(("Attempt to write extended audio ID to %#x\n", u32Val));
+ break;
+ case AC97_Extended_Audio_Ctrl_Stat:
+#ifdef IN_RING3
+ if (!(u32Val & AC97_EACS_VRA))
+ {
+ ichac97MixerSet(pThis, AC97_PCM_Front_DAC_Rate, 48000);
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
+
+ ichac97MixerSet(pThis, AC97_PCM_LR_ADC_Rate, 48000);
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
+ }
+ else
+ LogRel2(("AC97: Variable rate audio (VRA) is not supported\n"));
+
+ if (!(u32Val & AC97_EACS_VRM))
+ {
+ ichac97MixerSet(pThis, AC97_MIC_ADC_Rate, 48000);
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
+ }
+ else
+ LogRel2(("AC97: Variable rate microphone audio (VRM) is not supported\n"));
+
+ LogFunc(("Setting extended audio control to %#x\n", u32Val));
+ ichac97MixerSet(pThis, AC97_Extended_Audio_Ctrl_Stat, u32Val);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ break;
+ case AC97_PCM_Front_DAC_Rate:
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+#ifdef IN_RING3
+ ichac97MixerSet(pThis, uPortIdx, u32Val);
+ LogFunc(("Set front DAC rate to %RU32\n", u32Val));
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ else
+ LogRel2(("AC97: Setting Front DAC rate when VRA is not set is forbidden, ignoring\n"));
+ break;
+ case AC97_MIC_ADC_Rate:
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRM)
+ {
+#ifdef IN_RING3
+ ichac97MixerSet(pThis, uPortIdx, u32Val);
+ LogFunc(("Set MIC ADC rate to %RU32\n", u32Val));
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ else
+ LogRel2(("AC97: Setting MIC ADC rate when VRM is not set is forbidden, ignoring\n"));
+ break;
+ case AC97_PCM_LR_ADC_Rate:
+ if (ichac97MixerGet(pThis, AC97_Extended_Audio_Ctrl_Stat) & AC97_EACS_VRA)
+ {
+#ifdef IN_RING3
+ ichac97MixerSet(pThis, uPortIdx, u32Val);
+ LogFunc(("Set front LR ADC rate to %RU32\n", u32Val));
+ ichac97R3StreamReOpen(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]);
+#else
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#endif
+ }
+ else
+ LogRel2(("AC97: Setting LR ADC rate when VRA is not set is forbidden, ignoring\n"));
+ break;
+ default:
+ LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val));
+ ichac97MixerSet(pThis, uPortIdx, u32Val);
+ break;
+ }
+ break;
+ }
+
+ case 4:
+ {
+ LogRel2(("AC97: Warning: Unimplemented NAMWrite (%u byte) port=%#x, idx=0x%x <- %#x\n", cbVal, uPort, uPortIdx, u32Val));
+ pThis->cas = 0;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled NAMWrite port=%#x, cbVal=%u u32Val=%#x\n", uPort, cbVal, u32Val));
+ break;
+ }
+
+ DEVAC97_UNLOCK(pThis);
+
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) ichac97R3IOPortMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(cb, enmType);
+
+ Assert(enmType == PCI_ADDRESS_SPACE_IO);
+ Assert(cb >= 0x20);
+
+ if (iRegion > 1) /* We support 2 regions max. at the moment. */
+ return VERR_INVALID_PARAMETER;
+
+ PAC97STATE pThis = RT_FROM_MEMBER(pPciDev, AC97STATE, PciDev);
+ RTIOPORT Port = (RTIOPORT)GCPhysAddress;
+
+ int rc;
+ if (iRegion == 0)
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, Port, 256, NULL, ichac97IOPortNAMWrite, ichac97IOPortNAMRead,
+ NULL, NULL, "ICHAC97 NAM");
+ AssertRCReturn(rc, rc);
+ if (pThis->fRZEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, Port, 256, NIL_RTR0PTR, "ichac97IOPortNAMWrite", "ichac97IOPortNAMRead",
+ NULL, NULL, "ICHAC97 NAM");
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, Port, 256, NIL_RTRCPTR, "ichac97IOPortNAMWrite", "ichac97IOPortNAMRead",
+ NULL, NULL, "ICHAC97 NAM");
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ rc = PDMDevHlpIOPortRegister(pDevIns, Port, 64, NULL, ichac97IOPortNABMWrite, ichac97IOPortNABMRead,
+ NULL, NULL, "ICHAC97 NABM");
+ AssertRCReturn(rc, rc);
+ if (pThis->fRZEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, Port, 64, NIL_RTR0PTR, "ichac97IOPortNABMWrite", "ichac97IOPortNABMRead",
+ NULL, NULL, "ICHAC97 NABM");
+ AssertRCReturn(rc, rc);
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, Port, 64, NIL_RTRCPTR, "ichac97IOPortNABMWrite", "ichac97IOPortNABMRead",
+ NULL, NULL, "ICHAC97 NABM");
+ AssertRCReturn(rc, rc);
+
+ }
+ }
+
+ pThis->IOPortBase[iRegion] = Port;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Saves (serializes) an AC'97 stream using SSM.
+ *
+ * @returns IPRT status code.
+ * @param pDevIns Device instance.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to save.
+ */
+static int ichac97R3SaveStream(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ RT_NOREF(pDevIns);
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ SSMR3PutU32(pSSM, pRegs->bdbar);
+ SSMR3PutU8( pSSM, pRegs->civ);
+ SSMR3PutU8( pSSM, pRegs->lvi);
+ SSMR3PutU16(pSSM, pRegs->sr);
+ SSMR3PutU16(pSSM, pRegs->picb);
+ SSMR3PutU8( pSSM, pRegs->piv);
+ SSMR3PutU8( pSSM, pRegs->cr);
+ SSMR3PutS32(pSSM, pRegs->bd_valid);
+ SSMR3PutU32(pSSM, pRegs->bd.addr);
+ SSMR3PutU32(pSSM, pRegs->bd.ctl_len);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogFlowFuncEnter();
+
+ SSMR3PutU32(pSSM, pThis->glob_cnt);
+ SSMR3PutU32(pSSM, pThis->glob_sta);
+ SSMR3PutU32(pSSM, pThis->cas);
+
+ /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
+ /* Note: The order the streams are loaded here is critical, so don't touch. */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ int rc2 = ichac97R3SaveStream(pDevIns, pSSM, &pThis->aStreams[i]);
+ AssertRC(rc2);
+ }
+
+ SSMR3PutMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ uint8_t active[AC97SOUNDSOURCE_END_INDEX];
+
+ active[AC97SOUNDSOURCE_PI_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PI_INDEX]) ? 1 : 0;
+ active[AC97SOUNDSOURCE_PO_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_PO_INDEX]) ? 1 : 0;
+ active[AC97SOUNDSOURCE_MC_INDEX] = ichac97R3StreamIsEnabled(pThis, &pThis->aStreams[AC97SOUNDSOURCE_MC_INDEX]) ? 1 : 0;
+
+ SSMR3PutMem(pSSM, active, sizeof(active));
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Loads an AC'97 stream from SSM.
+ *
+ * @returns IPRT status code.
+ * @param pSSM Saved state manager (SSM) handle to use.
+ * @param pStream AC'97 stream to load.
+ */
+static int ichac97R3LoadStream(PSSMHANDLE pSSM, PAC97STREAM pStream)
+{
+ PAC97BMREGS pRegs = &pStream->Regs;
+
+ SSMR3GetU32(pSSM, &pRegs->bdbar);
+ SSMR3GetU8( pSSM, &pRegs->civ);
+ SSMR3GetU8( pSSM, &pRegs->lvi);
+ SSMR3GetU16(pSSM, &pRegs->sr);
+ SSMR3GetU16(pSSM, &pRegs->picb);
+ SSMR3GetU8( pSSM, &pRegs->piv);
+ SSMR3GetU8( pSSM, &pRegs->cr);
+ SSMR3GetS32(pSSM, &pRegs->bd_valid);
+ SSMR3GetU32(pSSM, &pRegs->bd.addr);
+ return SSMR3GetU32(pSSM, &pRegs->bd.ctl_len);
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ichac97R3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogRel2(("ichac97LoadExec: uVersion=%RU32, uPass=0x%x\n", uVersion, uPass));
+
+ AssertMsgReturn (uVersion == AC97_SSM_VERSION, ("%RU32\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ SSMR3GetU32(pSSM, &pThis->glob_cnt);
+ SSMR3GetU32(pSSM, &pThis->glob_sta);
+ SSMR3GetU32(pSSM, &pThis->cas);
+
+ /** @todo r=andy For the next saved state version, add unique stream identifiers and a stream count. */
+ /* Note: The order the streams are loaded here is critical, so don't touch. */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ int rc2 = ichac97R3LoadStream(pSSM, &pThis->aStreams[i]);
+ AssertRCReturn(rc2, rc2);
+ }
+
+ SSMR3GetMem(pSSM, pThis->mixer_data, sizeof(pThis->mixer_data));
+
+ /** @todo r=andy Stream IDs are hardcoded to certain streams. */
+ uint8_t uaStrmsActive[AC97SOUNDSOURCE_END_INDEX];
+ int rc2 = SSMR3GetMem(pSSM, uaStrmsActive, sizeof(uaStrmsActive));
+ AssertRCReturn(rc2, rc2);
+
+ ichac97R3MixerRecordSelect(pThis, ichac97MixerGet(pThis, AC97_Record_Select));
+ ichac97R3MixerSetVolume(pThis, AC97_Master_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER, ichac97MixerGet(pThis, AC97_Master_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, AC97_PCM_Out_Volume_Mute, PDMAUDIOMIXERCTL_FRONT, ichac97MixerGet(pThis, AC97_PCM_Out_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, AC97_Line_In_Volume_Mute, PDMAUDIOMIXERCTL_LINE_IN, ichac97MixerGet(pThis, AC97_Line_In_Volume_Mute));
+ ichac97R3MixerSetVolume(pThis, AC97_Mic_Volume_Mute, PDMAUDIOMIXERCTL_MIC_IN, ichac97MixerGet(pThis, AC97_Mic_Volume_Mute));
+ ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mic_Mute, PDMAUDIOMIXERCTL_MIC_IN, ichac97MixerGet(pThis, AC97_Record_Gain_Mic_Mute));
+ ichac97R3MixerSetGain(pThis, AC97_Record_Gain_Mute, PDMAUDIOMIXERCTL_LINE_IN, ichac97MixerGet(pThis, AC97_Record_Gain_Mute));
+ if (pThis->uCodecModel == AC97_CODEC_AD1980)
+ if (ichac97MixerGet(pThis, AC97_AD_Misc) & AC97_AD_MISC_HPSEL)
+ ichac97R3MixerSetVolume(pThis, AC97_Headphone_Volume_Mute, PDMAUDIOMIXERCTL_VOLUME_MASTER,
+ ichac97MixerGet(pThis, AC97_Headphone_Volume_Mute));
+
+ /** @todo r=andy Stream IDs are hardcoded to certain streams. */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ const bool fEnable = RT_BOOL(uaStrmsActive[i]);
+ const PAC97STREAM pStream = &pThis->aStreams[i];
+
+ rc2 = ichac97R3StreamEnable(pThis, pStream, fEnable);
+ if ( fEnable
+ && RT_SUCCESS(rc2))
+ {
+ /* Re-arm the timer for this stream. */
+ rc2 = ichac97TimerSet(pThis, pStream,
+ TMTimerGet((pThis)->DEVAC97_CTX_SUFF_SD(pTimer, pStream->u8SD)) + pStream->State.cTransferTicks,
+ false /* fForce */);
+ }
+
+ AssertRC(rc2);
+ /* Keep going. */
+ }
+
+ pThis->bup_flag = 0;
+ pThis->last_samp = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ichac97R3QueryInterface(struct PDMIBASE *pInterface, const char *pszIID)
+{
+ PAC97STATE pThis = RT_FROM_MEMBER(pInterface, AC97STATE, IBase);
+ Assert(&pThis->IBase == pInterface);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ return NULL;
+}
+
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) ichac97R3PowerOff(PPDMDEVINS pDevIns)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogRel2(("AC97: Powering off ...\n"));
+
+ /* Note: Involves mixer stream / sink destruction, so also do this here
+ * instead of in ichac97R3Destruct(). */
+ ichac97R3StreamsDestroy(pThis);
+
+ /**
+ * 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 (pThis->pMixer)
+ {
+ AudioMixerDestroy(pThis->pMixer);
+ pThis->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 = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ 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);
+
+ /*
+ * Reset all streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ ichac97R3StreamEnable(pThis, &pThis->aStreams[i], false /* fEnable */);
+ ichac97R3StreamReset(pThis, &pThis->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(pThis->pSinkLineIn);
+ AudioMixerSinkReset(pThis->pSinkMicIn);
+ AudioMixerSinkReset(pThis->pSinkOut);
+}
+
+
+/**
+ * Attach command, internal version.
+ *
+ * This is called to let the device attach to a driver for a specified LUN
+ * during runtime. This is not called during VM construction, the device
+ * constructor has to attach to all the available drivers.
+ *
+ * @returns VBox status code.
+ * @param pThis AC'97 state.
+ * @param uLUN The logical unit which is being attached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int ichac97R3AttachInternal(PAC97STATE pThis, unsigned uLUN, uint32_t fFlags, PAC97DRIVER *ppDrv)
+{
+ RT_NOREF(fFlags);
+
+ /*
+ * Attach driver.
+ */
+ char *pszDesc;
+ if (RTStrAPrintf(&pszDesc, "Audio driver port (AC'97) for LUN #%u", uLUN) <= 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN,
+ &pThis->IBase, &pDrvBase, pszDesc);
+ if (RT_SUCCESS(rc))
+ {
+ PAC97DRIVER pDrv = (PAC97DRIVER)RTMemAllocZ(sizeof(AC97DRIVER));
+ if (pDrv)
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", uLUN, rc));
+ pDrv->pAC97State = pThis;
+ pDrv->uLUN = uLUN;
+
+ /*
+ * For now we always set the driver at LUN 0 as our primary
+ * host backend. This might change in the future.
+ */
+ if (pDrv->uLUN == 0)
+ pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY;
+
+ LogFunc(("LUN#%RU8: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags));
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThis->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+
+ if (RT_FAILURE(rc))
+ {
+ /* Only free this string on failure;
+ * must remain valid for the live of the driver instance. */
+ RTStrFree(pszDesc);
+ }
+
+ LogFunc(("uLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc));
+ return rc;
+}
+
+/**
+ * Detach command, internal version.
+ *
+ * This is called to let the device detach from a driver for a specified LUN
+ * during runtime.
+ *
+ * @returns VBox status code.
+ * @param pThis AC'97 state.
+ * @param pDrv Driver to detach from device.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static int ichac97R3DetachInternal(PAC97STATE pThis, PAC97DRIVER pDrv, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ /* First, 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(pThis, pDrv);
+
+ /* Next, search backwards for a capable (attached) driver which now will be the
+ * new recording source. */
+ PDMAUDIODESTSOURCE dstSrc;
+ PAC97DRIVER pDrvCur;
+ RTListForEachReverse(&pThis->lstDrv, pDrvCur, AC97DRIVER, Node)
+ {
+ if (!pDrvCur->pConnector)
+ continue;
+
+ PDMAUDIOBACKENDCFG Cfg;
+ int rc2 = pDrvCur->pConnector->pfnGetConfig(pDrvCur->pConnector, &Cfg);
+ if (RT_FAILURE(rc2))
+ continue;
+
+ dstSrc.Source = PDMAUDIORECSOURCE_MIC;
+ PAC97DRIVERSTREAM pDrvStrm = ichac97R3MixerGetDrvStream(pThis, pDrvCur, PDMAUDIODIR_IN, dstSrc);
+ if ( pDrvStrm
+ && pDrvStrm->pMixStrm)
+ {
+ rc2 = AudioMixerSinkSetRecordingSource(pThis->pSinkMicIn, pDrvStrm->pMixStrm);
+ if (RT_SUCCESS(rc2))
+ LogRel2(("AC97: Set new recording source for 'Mic In' to '%s'\n", Cfg.szName));
+ }
+
+ dstSrc.Source = PDMAUDIORECSOURCE_LINE;
+ pDrvStrm = ichac97R3MixerGetDrvStream(pThis, pDrvCur, PDMAUDIODIR_IN, dstSrc);
+ if ( pDrvStrm
+ && pDrvStrm->pMixStrm)
+ {
+ rc2 = AudioMixerSinkSetRecordingSource(pThis->pSinkLineIn, pDrvStrm->pMixStrm);
+ if (RT_SUCCESS(rc2))
+ LogRel2(("AC97: Set new recording source for 'Line In' to '%s'\n", Cfg.szName));
+ }
+ }
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) ichac97R3Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ DEVAC97_LOCK(pThis);
+
+ PAC97DRIVER pDrv;
+ int rc2 = ichac97R3AttachInternal(pThis, uLUN, fFlags, &pDrv);
+ if (RT_SUCCESS(rc2))
+ rc2 = ichac97R3MixerAddDrv(pThis, pDrv);
+
+ if (RT_FAILURE(rc2))
+ LogFunc(("Failed with %Rrc\n", rc2));
+
+ DEVAC97_UNLOCK(pThis);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) ichac97R3Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ DEVAC97_LOCK(pThis);
+
+ PAC97DRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node)
+ {
+ if (pDrv->uLUN == uLUN)
+ {
+ int rc2 = ichac97R3DetachInternal(pThis, pDrv, fFlags);
+ if (RT_SUCCESS(rc2))
+ {
+ RTMemFree(pDrv);
+ pDrv = NULL;
+ }
+
+ break;
+ }
+ }
+
+ DEVAC97_UNLOCK(pThis);
+}
+
+/**
+ * Re-attaches (replaces) a driver with a new driver.
+ *
+ * @returns VBox status code.
+ * @param pThis Device instance.
+ * @param pDrv Driver instance used for attaching to.
+ * If NULL is specified, a new driver will be created and appended
+ * to the driver list.
+ * @param uLUN The logical unit which is being re-detached.
+ * @param pszDriver New driver name to attach.
+ */
+static int ichac97R3ReattachInternal(PAC97STATE pThis, PAC97DRIVER pDrv, uint8_t uLUN, const char *pszDriver)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDriver, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pDrv)
+ {
+ rc = ichac97R3DetachInternal(pThis, pDrv, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pDrv = NULL;
+ }
+
+ PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3);
+ PCFGMNODE pRoot = CFGMR3GetRoot(pVM);
+ PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/ichac97/0/");
+
+ /* Remove LUN branch. */
+ CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN));
+
+# define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; }
+
+ do
+ {
+ PCFGMNODE pLunL0;
+ rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK();
+ rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK();
+
+ PCFGMNODE pLunL1, pLunL2;
+ rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK();
+ rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK();
+
+ rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK();
+
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ rc = ichac97R3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+
+ LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc));
+
+# undef RC_CHECK
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnRelocate}
+ */
+static DECLCALLBACK(void) ichac97R3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ NOREF(offDelta);
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) ichac97R3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PAC97STATE pThis = PDMINS_2_DATA(pDevIns, PAC97STATE);
+
+ LogFlowFuncEnter();
+
+ PAC97DRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, AC97DRIVER, Node)
+ {
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThis->lstDrv));
+
+ 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 = PDMINS_2_DATA(pDevIns, PAC97STATE);
+ Assert(iInstance == 0); RT_NOREF(iInstance);
+
+ /*
+ * Initialize data so we can run the destructor without scewing up.
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->IBase.pfnQueryInterface = ichac97R3QueryInterface;
+ RTListInit(&pThis->lstDrv);
+
+ /*
+ * Validations.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "RZEnabled\0"
+ "Codec\0"
+ "TimerHz\0"
+ "DebugEnabled\0"
+ "DebugPathOut\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("Invalid configuration for the AC'97 device"));
+
+ /*
+ * Read config data.
+ */
+ int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC'97 configuration error: failed to read RCEnabled as boolean"));
+
+ char szCodec[20];
+ rc = CFGMR3QueryStringDef(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"));
+
+ rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &pThis->uTimerHz, AC97_TIMER_HZ_DEFAULT /* Default value, if not set. */);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC'97 configuration error: failed to read Hertz (Hz) rate as unsigned integer"));
+
+ if (pThis->uTimerHz != AC97_TIMER_HZ_DEFAULT)
+ LogRel(("AC97: Using custom device timer rate (%RU16Hz)\n", pThis->uTimerHz));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->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 = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath),
+ VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("AC97 configuration error: failed to read debugging output path flag as string"));
+
+ if (!strlen(pThis->Dbg.szOutPath))
+ RTStrPrintf(pThis->Dbg.szOutPath, sizeof(pThis->Dbg.szOutPath), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+
+ if (pThis->Dbg.fEnabled)
+ LogRel2(("AC97: Debug output will be saved to '%s'\n", pThis->Dbg.szOutPath));
+
+ /*
+ * 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.
+ */
+ if (!strcmp(szCodec, "STAC9700"))
+ pThis->uCodecModel = AC97_CODEC_STAC9700;
+ else if (!strcmp(szCodec, "AD1980"))
+ pThis->uCodecModel = AC97_CODEC_AD1980;
+ else if (!strcmp(szCodec, "AD1981B"))
+ pThis->uCodecModel = AC97_CODEC_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 */
+ PCIDevSetVendorId (&pThis->PciDev, 0x8086); /* 00 ro - intel. */ Assert(pThis->PciDev.abConfig[0x00] == 0x86); Assert(pThis->PciDev.abConfig[0x01] == 0x80);
+ PCIDevSetDeviceId (&pThis->PciDev, 0x2415); /* 02 ro - 82801 / 82801aa(?). */ Assert(pThis->PciDev.abConfig[0x02] == 0x15); Assert(pThis->PciDev.abConfig[0x03] == 0x24);
+ PCIDevSetCommand (&pThis->PciDev, 0x0000); /* 04 rw,ro - pcicmd. */ Assert(pThis->PciDev.abConfig[0x04] == 0x00); Assert(pThis->PciDev.abConfig[0x05] == 0x00);
+ PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_DEVSEL_MEDIUM | VBOX_PCI_STATUS_FAST_BACK); /* 06 rwc?,ro? - pcists. */ Assert(pThis->PciDev.abConfig[0x06] == 0x80); Assert(pThis->PciDev.abConfig[0x07] == 0x02);
+ PCIDevSetRevisionId (&pThis->PciDev, 0x01); /* 08 ro - rid. */ Assert(pThis->PciDev.abConfig[0x08] == 0x01);
+ PCIDevSetClassProg (&pThis->PciDev, 0x00); /* 09 ro - pi. */ Assert(pThis->PciDev.abConfig[0x09] == 0x00);
+ PCIDevSetClassSub (&pThis->PciDev, 0x01); /* 0a ro - scc; 01 == Audio. */ Assert(pThis->PciDev.abConfig[0x0a] == 0x01);
+ PCIDevSetClassBase (&pThis->PciDev, 0x04); /* 0b ro - bcc; 04 == multimedia.*/Assert(pThis->PciDev.abConfig[0x0b] == 0x04);
+ PCIDevSetHeaderType (&pThis->PciDev, 0x00); /* 0e ro - headtyp. */ Assert(pThis->PciDev.abConfig[0x0e] == 0x00);
+ PCIDevSetBaseAddress (&pThis->PciDev, 0, /* 10 rw - nambar - native audio mixer base. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pThis->PciDev.abConfig[0x10] == 0x01); Assert(pThis->PciDev.abConfig[0x11] == 0x00); Assert(pThis->PciDev.abConfig[0x12] == 0x00); Assert(pThis->PciDev.abConfig[0x13] == 0x00);
+ PCIDevSetBaseAddress (&pThis->PciDev, 1, /* 14 rw - nabmbar - native audio bus mastering. */
+ true /* fIoSpace */, false /* fPrefetchable */, false /* f64Bit */, 0x00000000); Assert(pThis->PciDev.abConfig[0x14] == 0x01); Assert(pThis->PciDev.abConfig[0x15] == 0x00); Assert(pThis->PciDev.abConfig[0x16] == 0x00); Assert(pThis->PciDev.abConfig[0x17] == 0x00);
+ PCIDevSetInterruptLine(&pThis->PciDev, 0x00); /* 3c rw. */ Assert(pThis->PciDev.abConfig[0x3c] == 0x00);
+ PCIDevSetInterruptPin (&pThis->PciDev, 0x01); /* 3d ro - INTA#. */ Assert(pThis->PciDev.abConfig[0x3d] == 0x01);
+
+ if (pThis->uCodecModel == AC97_CODEC_AD1980)
+ {
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId (&pThis->PciDev, 0x0177); /* 2e ro. */
+ }
+ else if (pThis->uCodecModel == AC97_CODEC_AD1981B)
+ {
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x1028); /* 2c ro - Dell.) */
+ PCIDevSetSubSystemId (&pThis->PciDev, 0x01ad); /* 2e ro. */
+ }
+ else
+ {
+ PCIDevSetSubSystemVendorId(&pThis->PciDev, 0x8086); /* 2c ro - Intel.) */
+ PCIDevSetSubSystemId (&pThis->PciDev, 0x0000); /* 2e ro. */
+ }
+
+ /*
+ * Register the PCI device, it's I/O regions, the timer and the
+ * saved state item.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 256, PCI_ADDRESS_SPACE_IO, ichac97R3IOPortMap);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 1, 64, PCI_ADDRESS_SPACE_IO, ichac97R3IOPortMap);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpSSMRegister(pDevIns, AC97_SSM_VERSION, sizeof(*pThis), ichac97R3SaveExec, ichac97R3LoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+# ifdef VBOX_WITH_AUDIO_AC97_ASYNC_IO
+ LogRel(("AC97: Asynchronous I/O enabled\n"));
+# endif
+
+ /*
+ * Attach driver.
+ */
+ uint8_t uLUN;
+ for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN)
+ {
+ LogFunc(("Trying to attach driver for LUN #%RU8 ...\n", uLUN));
+ rc = ichac97R3AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED)
+ {
+ ichac97R3ReattachInternal(pThis, NULL /* pDrv */, uLUN, "NullAudio");
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Host audio backend initialization has failed. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ /* Attaching to the NULL audio backend will never fail. */
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+
+ LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerCreate("AC'97 Mixer", 0 /* uFlags */, &pThis->pMixer);
+ if (RT_SUCCESS(rc))
+ {
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Line In", AUDMIXSINKDIR_INPUT, &pThis->pSinkLineIn);
+ AssertRC(rc);
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Recording] Microphone In", AUDMIXSINKDIR_INPUT, &pThis->pSinkMicIn);
+ AssertRC(rc);
+ rc = AudioMixerCreateSink(pThis->pMixer, "[Playback] PCM Output", AUDMIXSINKDIR_OUTPUT, &pThis->pSinkOut);
+ AssertRC(rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create all hardware streams.
+ */
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ int rc2 = ichac97R3StreamCreate(pThis, &pThis->aStreams[i], i /* SD# */);
+ AssertRC(rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# ifdef VBOX_WITH_AUDIO_AC97_ONETIME_INIT
+ PAC97DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, AC97DRIVER, Node)
+ {
+ /*
+ * Only primary drivers are critical for the VM to run. Everything else
+ * might not worth showing an own error message box in the GUI.
+ */
+ if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY))
+ continue;
+
+ PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector;
+ AssertPtr(pCon);
+
+ bool fValidLineIn = AudioMixerStreamIsValid(pDrv->LineIn.pMixStrm);
+ bool fValidMicIn = AudioMixerStreamIsValid(pDrv->MicIn.pMixStrm);
+ bool fValidOut = AudioMixerStreamIsValid(pDrv->Out.pMixStrm);
+
+ if ( !fValidLineIn
+ && !fValidMicIn
+ && !fValidOut)
+ {
+ LogRel(("AC97: Falling back to NULL backend (no sound audible)\n"));
+
+ ichac97R3Reset(pDevIns);
+ ichac97R3ReattachInternal(pThis, pDrv, pDrv->uLUN, "NullAudio");
+
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("No audio devices could be opened. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ }
+ else
+ {
+ bool fWarn = false;
+
+ PDMAUDIOBACKENDCFG backendCfg;
+ int rc2 = pCon->pfnGetConfig(pCon, &backendCfg);
+ if (RT_SUCCESS(rc2))
+ {
+ if (backendCfg.cMaxStreamsIn)
+ {
+ /* If the audio backend supports two or more input streams at once,
+ * warn if one of our two inputs (microphone-in and line-in) failed to initialize. */
+ if (backendCfg.cMaxStreamsIn >= 2)
+ fWarn = !fValidLineIn || !fValidMicIn;
+ /* If the audio backend only supports one input stream at once (e.g. pure ALSA, and
+ * *not* ALSA via PulseAudio plugin!), only warn if both of our inputs failed to initialize.
+ * One of the two simply is not in use then. */
+ else if (backendCfg.cMaxStreamsIn == 1)
+ fWarn = !fValidLineIn && !fValidMicIn;
+ /* Don't warn if our backend is not able of supporting any input streams at all. */
+ }
+
+ if ( !fWarn
+ && backendCfg.cMaxStreamsOut)
+ {
+ fWarn = !fValidOut;
+ }
+ }
+ else
+ {
+ LogRel(("AC97: Unable to retrieve audio backend configuration for LUN #%RU8, rc=%Rrc\n", pDrv->uLUN, rc2));
+ fWarn = true;
+ }
+
+ if (fWarn)
+ {
+ char szMissingStreams[255] = "";
+ size_t len = 0;
+ if (!fValidLineIn)
+ {
+ LogRel(("AC97: WARNING: Unable to open PCM line input for LUN #%RU8!\n", pDrv->uLUN));
+ len = RTStrPrintf(szMissingStreams, sizeof(szMissingStreams), "PCM Input");
+ }
+ if (!fValidMicIn)
+ {
+ LogRel(("AC97: WARNING: Unable to open PCM microphone input for LUN #%RU8!\n", pDrv->uLUN));
+ len += RTStrPrintf(szMissingStreams + len,
+ sizeof(szMissingStreams) - len, len ? ", PCM Microphone" : "PCM Microphone");
+ }
+ if (!fValidOut)
+ {
+ LogRel(("AC97: WARNING: Unable to open PCM output for LUN #%RU8!\n", pDrv->uLUN));
+ len += RTStrPrintf(szMissingStreams + len,
+ sizeof(szMissingStreams) - len, len ? ", PCM Output" : "PCM Output");
+ }
+
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Some AC'97 audio streams (%s) could not be opened. Guest applications generating audio "
+ "output or depending on audio input may hang. Make sure your host audio device "
+ "is working properly. Check the logfile for error messages of the audio "
+ "subsystem"), szMissingStreams);
+ }
+ }
+ }
+# endif /* VBOX_WITH_AUDIO_AC97_ONETIME_INIT */
+ }
+
+ if (RT_SUCCESS(rc))
+ ichac97R3Reset(pDevIns);
+
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < AC97_MAX_STREAMS; i++)
+ {
+ /* Create the emulation timer (per stream).
+ *
+ * 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.
+ */
+ char szTimer[16];
+ RTStrPrintf2(szTimer, sizeof(szTimer), "AC97SD%i", i);
+
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, ichac97R3Timer, &pThis->aStreams[i],
+ TMTIMER_FLAGS_NO_CRIT_SECT, szTimer, &pThis->pTimerR3[i]);
+ AssertRCReturn(rc, rc);
+ pThis->pTimerR0[i] = TMTimerR0Ptr(pThis->pTimerR3[i]);
+ pThis->pTimerRC[i] = TMTimerRCPtr(pThis->pTimerR3[i]);
+
+ /* Use our own critcal section for the device timer.
+ * That way we can control more fine-grained when to lock what. */
+ rc = TMR3TimerSetCritSect(pThis->pTimerR3[i], &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+# ifdef VBOX_WITH_STATISTICS
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTimer, STAMTYPE_PROFILE, "/Devices/AC97/Timer", STAMUNIT_TICKS_PER_CALL, "Profiling ichac97Timer.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIn, STAMTYPE_PROFILE, "/Devices/AC97/Input", STAMUNIT_TICKS_PER_CALL, "Profiling input.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatOut, STAMTYPE_PROFILE, "/Devices/AC97/Output", STAMUNIT_TICKS_PER_CALL, "Profiling output.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, "/Devices/AC97/BytesRead" , STAMUNIT_BYTES, "Bytes read from AC97 emulation.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, "/Devices/AC97/BytesWritten", STAMUNIT_BYTES, "Bytes written to AC97 emulation.");
+ }
+# endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceICHAC97 =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "ichac97",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "ICH AC'97 Audio Controller",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(AC97STATE),
+ /* pfnConstruct */
+ ichac97R3Construct,
+ /* pfnDestruct */
+ ichac97R3Destruct,
+ /* pfnRelocate */
+ ichac97R3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ichac97R3Reset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ ichac97R3Attach,
+ /* pfnDetach */
+ ichac97R3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ ichac97R3PowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* !IN_RING3 */
+#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..b2e997b5
--- /dev/null
+++ b/src/VBox/Devices/Audio/DevSB16.cpp
@@ -0,0 +1,2648 @@
+/* $Id: DevSB16.cpp $ */
+/** @file
+ * DevSB16 - VBox SB16 Audio Controller.
+ */
+
+/*
+ * Copyright (C) 2015-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ * --------------------------------------------------------------------
+ *
+ * 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 "VBoxDD.h"
+
+#include "AudioMixBuffer.h"
+#include "AudioMixer.h"
+#include "DrvAudio.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** 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 *
+*********************************************************************************************************************************/
+/**
+ * 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 PDM audio stream. */
+ R3PTRTYPE(PPDMAUDIOSTREAM) pStream;
+ /** The stream's current configuration. */
+} SB16DRIVERSTREAM, *PSB16DRIVERSTREAM;
+
+/**
+ * Struct for maintaining a host backend driver.
+ */
+typedef struct SB16STATE *PSB16STATE;
+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;
+ /** Driver flags. */
+ PDMAUDIODRVFLAGS fFlags;
+ uint32_t PaddingFlags;
+ /** 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 Padding[4];
+ /** 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;
+} SB16DRIVER, *PSB16DRIVER;
+
+/**
+ * Structure for a SB16 stream.
+ */
+typedef struct SB16STREAM
+{
+ /** The stream's current configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+} SB16STREAM, *PSB16STREAM;
+
+typedef struct SB16STATE
+{
+#ifdef VBOX
+ /** Pointer to the device instance. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the connector of the attached audio driver. */
+ PPDMIAUDIOCONNECTOR pDrv;
+ int irqCfg;
+ int dmaCfg;
+ int hdmaCfg;
+ int portCfg;
+ int verCfg;
+#endif
+ int irq;
+ int dma;
+ int hdma;
+ int port;
+ int ver;
+
+ int in_index;
+ int out_data_len;
+ int fmt_stereo;
+ int fmt_signed;
+ int fmt_bits;
+ PDMAUDIOFMT fmt;
+ int dma_auto;
+ int block_size;
+ int fifo;
+ int freq;
+ int time_const;
+ int speaker;
+ int needed_bytes;
+ int cmd;
+ int use_hdma;
+ int highspeed;
+ int can_write; /** @todo Value never gets 0? */
+
+ int v2x6;
+
+ uint8_t csp_param;
+ uint8_t csp_value;
+ uint8_t csp_mode;
+ uint8_t csp_regs[256];
+ uint8_t csp_index;
+ uint8_t csp_reg83[4];
+ int csp_reg83r;
+ int csp_reg83w;
+
+ uint8_t in2_data[10];
+ uint8_t out_data[50];
+ uint8_t test_reg;
+ uint8_t last_read_byte;
+ int nzero;
+
+ int left_till_irq; /** Note: Can be < 0. */
+
+ int dma_running;
+ int bytes_per_second;
+ int align;
+
+ RTLISTANCHOR lstDrv;
+ /** Number of active (running) SDn streams. */
+ uint8_t cStreamsActive;
+ /** The timer for pumping data thru the attached LUN drivers. */
+ PTMTIMERR3 pTimerIO;
+ /** Flag indicating whether the timer is active or not. */
+ bool fTimerActive;
+ uint8_t u8Padding1[7];
+ /** The timer interval for pumping data thru the LUN drivers in timer ticks. */
+ uint64_t cTimerTicksIO;
+ /** Timestamp of the last timer callback (sb16TimerIO).
+ * Used to calculate the time actually elapsed between two timer callbacks. */
+ uint64_t uTimerTSIO;
+ PTMTIMER pTimerIRQ;
+ /** The base interface for LUN\#0. */
+ PDMIBASE IBase;
+ /** Output stream. */
+ SB16STREAM Out;
+
+ /* mixer state */
+ uint8_t mixer_nreg;
+ uint8_t mixer_regs[256];
+} SB16STATE, *PSB16STATE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int sb16CheckAndReOpenOut(PSB16STATE pThis);
+static int sb16OpenOut(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg);
+static void sb16CloseOut(PSB16STATE pThis);
+static void sb16TimerMaybeStart(PSB16STATE pThis);
+static void sb16TimerMaybeStop(PSB16STATE pThis);
+
+
+
+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;
+}
+
+#if 0 // unused // def DEBUG
+DECLINLINE(void) log_dsp(PSB16STATE pThis)
+{
+ LogFlowFunc(("%s:%s:%d:%s:dmasize=%d:freq=%d:const=%d:speaker=%d\n",
+ pThis->fmt_stereo ? "Stereo" : "Mono",
+ pThis->fmt_signed ? "Signed" : "Unsigned",
+ pThis->fmt_bits,
+ pThis->dma_auto ? "Auto" : "Single",
+ pThis->block_size,
+ pThis->freq,
+ pThis->time_const,
+ pThis->speaker));
+}
+#endif
+
+static void sb16SpeakerControl(PSB16STATE pThis, int on)
+{
+ pThis->speaker = on;
+ /* AUD_enable (pThis->voice, on); */
+}
+
+static void sb16Control(PSB16STATE pThis, int hold)
+{
+ int dma = pThis->use_hdma ? pThis->hdma : pThis->dma;
+ pThis->dma_running = hold;
+
+ LogFlowFunc(("hold %d high %d dma %d\n", hold, pThis->use_hdma, dma));
+
+ PDMDevHlpDMASetDREQ(pThis->pDevInsR3, dma, hold);
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ if (!pDrv->Out.pStream)
+ continue;
+
+ int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream,
+ hold == 1 ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
+ LogFlowFunc(("%s: rc=%Rrc\n", pDrv->Out.pStream->szName, rc2)); NOREF(rc2);
+ }
+
+ if (hold)
+ {
+ pThis->cStreamsActive++;
+ sb16TimerMaybeStart(pThis);
+ PDMDevHlpDMASchedule(pThis->pDevInsR3);
+ }
+ else
+ {
+ if (pThis->cStreamsActive)
+ pThis->cStreamsActive--;
+ sb16TimerMaybeStop(pThis);
+ }
+}
+
+/**
+ * @callback_method_impl{PFNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) sb16TimerIRQ(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvThis)
+{
+ RT_NOREF(pDevIns, pTimer);
+ PSB16STATE pThis = (PSB16STATE)pvThis;
+ pThis->can_write = 1;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1);
+}
+
+#define DMA8_AUTO 1
+#define DMA8_HIGH 2
+
+static void continue_dma8(PSB16STATE pThis)
+{
+ sb16CheckAndReOpenOut(pThis);
+ sb16Control(pThis, 1);
+}
+
+static void dma_cmd8(PSB16STATE pThis, int mask, int dma_len)
+{
+ pThis->fmt = PDMAUDIOFMT_U8;
+ pThis->use_hdma = 0;
+ pThis->fmt_bits = 8;
+ pThis->fmt_signed = 0;
+ pThis->fmt_stereo = (pThis->mixer_regs[0x0e] & 2) != 0;
+
+ if (-1 == pThis->time_const)
+ {
+ if (pThis->freq <= 0)
+ pThis->freq = 11025;
+ }
+ else
+ {
+ int tmp = (256 - pThis->time_const);
+ pThis->freq = (1000000 + (tmp / 2)) / tmp;
+ }
+
+ if (dma_len != -1)
+ {
+ pThis->block_size = dma_len << pThis->fmt_stereo;
+ }
+ 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 */
+ pThis->block_size &= ~pThis->fmt_stereo;
+ }
+
+ pThis->freq >>= pThis->fmt_stereo;
+ pThis->left_till_irq = pThis->block_size;
+ pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo);
+ /* pThis->highspeed = (mask & DMA8_HIGH) != 0; */
+ pThis->dma_auto = (mask & DMA8_AUTO) != 0;
+ pThis->align = (1 << pThis->fmt_stereo) - 1;
+
+ if (pThis->block_size & pThis->align)
+ LogFlowFunc(("warning: misaligned block size %d, alignment %d\n",
+ pThis->block_size, pThis->align + 1));
+
+ LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n",
+ pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits,
+ pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed));
+
+ continue_dma8(pThis);
+ sb16SpeakerControl(pThis, 1);
+}
+
+static void dma_cmd(PSB16STATE pThis, uint8_t cmd, uint8_t d0, int dma_len)
+{
+ pThis->use_hdma = cmd < 0xc0;
+ pThis->fifo = (cmd >> 1) & 1;
+ pThis->dma_auto = (cmd >> 2) & 1;
+ pThis->fmt_signed = (d0 >> 4) & 1;
+ pThis->fmt_stereo = (d0 >> 5) & 1;
+
+ switch (cmd >> 4)
+ {
+ case 11:
+ pThis->fmt_bits = 16;
+ break;
+
+ case 12:
+ pThis->fmt_bits = 8;
+ break;
+ }
+
+ if (-1 != pThis->time_const)
+ {
+#if 1
+ int tmp = 256 - pThis->time_const;
+ pThis->freq = (1000000 + (tmp / 2)) / tmp;
+#else
+ /* pThis->freq = 1000000 / ((255 - pThis->time_const) << pThis->fmt_stereo); */
+ pThis->freq = 1000000 / ((255 - pThis->time_const));
+#endif
+ pThis->time_const = -1;
+ }
+
+ pThis->block_size = dma_len + 1;
+ pThis->block_size <<= ((pThis->fmt_bits == 16) ? 1 : 0);
+ if (!pThis->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.
+ */
+ pThis->block_size <<= pThis->fmt_stereo;
+ }
+
+ LogFlowFunc(("freq %d, stereo %d, sign %d, bits %d, dma %d, auto %d, fifo %d, high %d\n",
+ pThis->freq, pThis->fmt_stereo, pThis->fmt_signed, pThis->fmt_bits,
+ pThis->block_size, pThis->dma_auto, pThis->fifo, pThis->highspeed));
+
+ if (16 == pThis->fmt_bits)
+ pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S16 : PDMAUDIOFMT_U16;
+ else
+ pThis->fmt = pThis->fmt_signed ? PDMAUDIOFMT_S8 : PDMAUDIOFMT_U8;
+
+ pThis->left_till_irq = pThis->block_size;
+
+ pThis->bytes_per_second = (pThis->freq << pThis->fmt_stereo) << ((pThis->fmt_bits == 16) ? 1 : 0);
+ pThis->highspeed = 0;
+ pThis->align = (1 << (pThis->fmt_stereo + (pThis->fmt_bits == 16))) - 1;
+ if (pThis->block_size & pThis->align)
+ {
+ LogFlowFunc(("warning: misaligned block size %d, alignment %d\n",
+ pThis->block_size, pThis->align + 1));
+ }
+
+ sb16CheckAndReOpenOut(pThis);
+ sb16Control(pThis, 1);
+ sb16SpeakerControl(pThis, 1);
+}
+
+static inline void dsp_out_data (PSB16STATE pThis, uint8_t val)
+{
+ LogFlowFunc(("outdata %#x\n", val));
+ if ((size_t) pThis->out_data_len < sizeof (pThis->out_data)) {
+ pThis->out_data[pThis->out_data_len++] = val;
+ }
+}
+
+static inline uint8_t dsp_get_data (PSB16STATE pThis)
+{
+ if (pThis->in_index) {
+ return pThis->in2_data[--pThis->in_index];
+ }
+ else {
+ LogFlowFunc(("buffer underflow\n"));
+ return 0;
+ }
+}
+
+static void sb16HandleCommand(PSB16STATE pThis, 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->needed_bytes = 3;
+ }
+ else
+ {
+ pThis->needed_bytes = 0;
+
+ switch (cmd)
+ {
+ case 0x03:
+ dsp_out_data(pThis, 0x10); /* pThis->csp_param); */
+ goto warn;
+
+ case 0x04:
+ pThis->needed_bytes = 1;
+ goto warn;
+
+ case 0x05:
+ pThis->needed_bytes = 2;
+ goto warn;
+
+ case 0x08:
+ /* __asm__ ("int3"); */
+ goto warn;
+
+ case 0x0e:
+ pThis->needed_bytes = 2;
+ goto warn;
+
+ case 0x09:
+ dsp_out_data(pThis, 0xf8);
+ goto warn;
+
+ case 0x0f:
+ pThis->needed_bytes = 1;
+ goto warn;
+
+ case 0x10:
+ pThis->needed_bytes = 1;
+ goto warn;
+
+ case 0x14:
+ pThis->needed_bytes = 2;
+ pThis->block_size = 0;
+ break;
+
+ case 0x1c: /* Auto-Initialize DMA DAC, 8-bit */
+ dma_cmd8(pThis, DMA8_AUTO, -1);
+ break;
+
+ case 0x20: /* Direct ADC, Juice/PL */
+ dsp_out_data(pThis, 0xff);
+ goto warn;
+
+ case 0x35:
+ LogFlowFunc(("0x35 - MIDI command not implemented\n"));
+ break;
+
+ case 0x40:
+ pThis->freq = -1;
+ pThis->time_const = -1;
+ pThis->needed_bytes = 1;
+ break;
+
+ case 0x41:
+ pThis->freq = -1;
+ pThis->time_const = -1;
+ pThis->needed_bytes = 2;
+ break;
+
+ case 0x42:
+ pThis->freq = -1;
+ pThis->time_const = -1;
+ pThis->needed_bytes = 2;
+ goto warn;
+
+ case 0x45:
+ dsp_out_data(pThis, 0xaa);
+ goto warn;
+
+ case 0x47: /* Continue Auto-Initialize DMA 16bit */
+ break;
+
+ case 0x48:
+ pThis->needed_bytes = 2;
+ break;
+
+ case 0x74:
+ pThis->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */
+ LogFlowFunc(("0x75 - DMA DAC, 4-bit ADPCM not implemented\n"));
+ break;
+
+ case 0x75: /* DMA DAC, 4-bit ADPCM Reference */
+ pThis->needed_bytes = 2;
+ LogFlowFunc(("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n"));
+ break;
+
+ case 0x76: /* DMA DAC, 2.6-bit ADPCM */
+ pThis->needed_bytes = 2;
+ LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n"));
+ break;
+
+ case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */
+ pThis->needed_bytes = 2;
+ LogFlowFunc(("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n"));
+ break;
+
+ case 0x7d:
+ LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n"));
+ LogFlowFunc(("not implemented\n"));
+ break;
+
+ case 0x7f:
+ LogFlowFunc(("0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n"));
+ LogFlowFunc(("not implemented\n"));
+ break;
+
+ case 0x80:
+ pThis->needed_bytes = 2;
+ break;
+
+ case 0x90:
+ case 0x91:
+ dma_cmd8(pThis, (((cmd & 1) == 0) ? 1 : 0) | DMA8_HIGH, -1);
+ break;
+
+ case 0xd0: /* halt DMA operation. 8bit */
+ sb16Control(pThis, 0);
+ break;
+
+ case 0xd1: /* speaker on */
+ sb16SpeakerControl(pThis, 1);
+ break;
+
+ case 0xd3: /* speaker off */
+ sb16SpeakerControl(pThis, 0);
+ break;
+
+ case 0xd4: /* continue DMA operation. 8bit */
+ /* KQ6 (or maybe Sierras audblst.drv in general) resets
+ the frequency between halt/continue */
+ continue_dma8(pThis);
+ break;
+
+ case 0xd5: /* halt DMA operation. 16bit */
+ sb16Control(pThis, 0);
+ break;
+
+ case 0xd6: /* continue DMA operation. 16bit */
+ sb16Control(pThis, 1);
+ break;
+
+ case 0xd9: /* exit auto-init DMA after this block. 16bit */
+ pThis->dma_auto = 0;
+ break;
+
+ case 0xda: /* exit auto-init DMA after this block. 8bit */
+ pThis->dma_auto = 0;
+ break;
+
+ case 0xe0: /* DSP identification */
+ pThis->needed_bytes = 1;
+ break;
+
+ case 0xe1:
+ dsp_out_data(pThis, pThis->ver & 0xff);
+ dsp_out_data(pThis, pThis->ver >> 8);
+ break;
+
+ case 0xe2:
+ pThis->needed_bytes = 1;
+ goto warn;
+
+ case 0xe3:
+ {
+ for (int i = sizeof (e3) - 1; i >= 0; --i)
+ dsp_out_data(pThis, e3[i]);
+
+ break;
+ }
+
+ case 0xe4: /* write test reg */
+ pThis->needed_bytes = 1;
+ break;
+
+ case 0xe7:
+ LogFlowFunc(("Attempt to probe for ESS (0xe7)?\n"));
+ break;
+
+ case 0xe8: /* read test reg */
+ dsp_out_data(pThis, pThis->test_reg);
+ break;
+
+ case 0xf2:
+ case 0xf3:
+ dsp_out_data(pThis, 0xaa);
+ pThis->mixer_regs[0x82] |= (cmd == 0xf2) ? 1 : 2;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1);
+ break;
+
+ case 0xf8:
+ /* Undocumented, used by old Creative diagnostic programs. */
+ dsp_out_data (pThis, 0);
+ goto warn;
+
+ case 0xf9:
+ pThis->needed_bytes = 1;
+ goto warn;
+
+ case 0xfa:
+ dsp_out_data (pThis, 0);
+ goto warn;
+
+ case 0xfc: /* FIXME */
+ dsp_out_data (pThis, 0);
+ goto warn;
+
+ default:
+ LogFlowFunc(("Unrecognized command %#x\n", cmd));
+ break;
+ }
+ }
+
+ if (!pThis->needed_bytes)
+ LogFlow(("\n"));
+
+exit:
+
+ if (!pThis->needed_bytes)
+ pThis->cmd = -1;
+ else
+ pThis->cmd = cmd;
+
+ return;
+
+warn:
+ LogFlowFunc(("warning: command %#x,%d is not truly understood yet\n",
+ cmd, pThis->needed_bytes));
+ goto exit;
+}
+
+static uint16_t dsp_get_lohi (PSB16STATE pThis)
+{
+ uint8_t hi = dsp_get_data (pThis);
+ uint8_t lo = dsp_get_data (pThis);
+ return (hi << 8) | lo;
+}
+
+static uint16_t dsp_get_hilo (PSB16STATE pThis)
+{
+ uint8_t lo = dsp_get_data (pThis);
+ uint8_t hi = dsp_get_data (pThis);
+ return (hi << 8) | lo;
+}
+
+static void complete(PSB16STATE pThis)
+{
+ int d0, d1, d2;
+ LogFlowFunc(("complete command %#x, in_index %d, needed_bytes %d\n",
+ pThis->cmd, pThis->in_index, pThis->needed_bytes));
+
+ if (pThis->cmd > 0xaf && pThis->cmd < 0xd0)
+ {
+ d2 = dsp_get_data (pThis);
+ d1 = dsp_get_data (pThis);
+ d0 = dsp_get_data (pThis);
+
+ if (pThis->cmd & 8)
+ LogFlowFunc(("ADC params cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2));
+ else
+ {
+ LogFlowFunc(("cmd = %#x d0 = %d, d1 = %d, d2 = %d\n", pThis->cmd, d0, d1, d2));
+ dma_cmd(pThis, pThis->cmd, d0, d1 + (d2 << 8));
+ }
+ }
+ else
+ {
+ switch (pThis->cmd)
+ {
+ case 0x04:
+ pThis->csp_mode = dsp_get_data (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 = dsp_get_data (pThis);
+ pThis->csp_value = dsp_get_data (pThis);
+ LogFlowFunc(("CSP command 0x05: param=%#x value=%#x\n",
+ pThis->csp_param,
+ pThis->csp_value));
+ break;
+
+ case 0x0e:
+ {
+ d0 = dsp_get_data(pThis);
+ d1 = dsp_get_data(pThis);
+ LogFlowFunc(("write CSP register %d <- %#x\n", d1, d0));
+ if (d1 == 0x83)
+ {
+ LogFlowFunc(("0x83[%d] <- %#x\n", pThis->csp_reg83r, d0));
+ pThis->csp_reg83[pThis->csp_reg83r % 4] = d0;
+ pThis->csp_reg83r += 1;
+ }
+ else
+ pThis->csp_regs[d1] = d0;
+ break;
+ }
+
+ case 0x0f:
+ d0 = dsp_get_data(pThis);
+ LogFlowFunc(("read CSP register %#x -> %#x, mode=%#x\n", d0, pThis->csp_regs[d0], pThis->csp_mode));
+ if (d0 == 0x83)
+ {
+ LogFlowFunc(("0x83[%d] -> %#x\n",
+ pThis->csp_reg83w,
+ pThis->csp_reg83[pThis->csp_reg83w % 4]));
+ dsp_out_data (pThis, pThis->csp_reg83[pThis->csp_reg83w % 4]);
+ pThis->csp_reg83w += 1;
+ }
+ else
+ dsp_out_data(pThis, pThis->csp_regs[d0]);
+ break;
+
+ case 0x10:
+ d0 = dsp_get_data(pThis);
+ LogFlowFunc(("cmd 0x10 d0=%#x\n", d0));
+ break;
+
+ case 0x14:
+ dma_cmd8(pThis, 0, dsp_get_lohi (pThis) + 1);
+ break;
+
+ case 0x40:
+ pThis->time_const = dsp_get_data(pThis);
+ LogFlowFunc(("set time const %d\n", pThis->time_const));
+ break;
+
+ case 0x42: /* FT2 sets output freq with this, go figure */
+#if 0
+ LogFlowFunc(("cmd 0x42 might not do what it think it should\n"));
+#endif
+ case 0x41:
+ pThis->freq = dsp_get_hilo(pThis);
+ LogFlowFunc(("set freq %d\n", pThis->freq));
+ break;
+
+ case 0x48:
+ pThis->block_size = dsp_get_lohi(pThis) + 1;
+ LogFlowFunc(("set dma block len %d\n", pThis->block_size));
+ break;
+
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ /* ADPCM stuff, ignore */
+ break;
+
+ case 0x80:
+ {
+ int freq, samples, bytes;
+ uint64_t ticks;
+
+ freq = pThis->freq > 0 ? pThis->freq : 11025;
+ samples = dsp_get_lohi (pThis) + 1;
+ bytes = samples << pThis->fmt_stereo << ((pThis->fmt_bits == 16) ? 1 : 0);
+ ticks = (bytes * TMTimerGetFreq(pThis->pTimerIRQ)) / freq;
+ if (ticks < TMTimerGetFreq(pThis->pTimerIRQ) / 1024)
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1);
+ else
+ TMTimerSet(pThis->pTimerIRQ, TMTimerGet(pThis->pTimerIRQ) + ticks);
+ LogFlowFunc(("mix silence: %d samples, %d bytes, %RU64 ticks\n", samples, bytes, ticks));
+ break;
+ }
+
+ case 0xe0:
+ d0 = dsp_get_data(pThis);
+ pThis->out_data_len = 0;
+ LogFlowFunc(("E0 data = %#x\n", d0));
+ dsp_out_data(pThis, ~d0);
+ break;
+
+ case 0xe2:
+ d0 = dsp_get_data(pThis);
+ LogFlow(("SB16:E2 = %#x\n", d0));
+ break;
+
+ case 0xe4:
+ pThis->test_reg = dsp_get_data(pThis);
+ break;
+
+ case 0xf9:
+ d0 = dsp_get_data(pThis);
+ LogFlowFunc(("command 0xf9 with %#x\n", d0));
+ switch (d0) {
+ case 0x0e:
+ dsp_out_data(pThis, 0xff);
+ break;
+
+ case 0x0f:
+ dsp_out_data(pThis, 0x07);
+ break;
+
+ case 0x37:
+ dsp_out_data(pThis, 0x38);
+ break;
+
+ default:
+ dsp_out_data(pThis, 0x00);
+ break;
+ }
+ break;
+
+ default:
+ LogFlowFunc(("complete: unrecognized command %#x\n", pThis->cmd));
+ return;
+ }
+ }
+
+ LogFlow(("\n"));
+ pThis->cmd = -1;
+ return;
+}
+
+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.
+ */
+static void sb16GetMasterVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
+{
+ /* There's no mute switch, only volume controls. */
+ uint8_t lvol = sb16MixRegToVol(pThis, 0x30);
+ uint8_t rvol = sb16MixRegToVol(pThis, 0x31);
+
+ pVol->fMuted = false;
+ pVol->uLeft = lvol;
+ pVol->uRight = rvol;
+}
+
+/**
+ * Returns the device's current output stream volume.
+ *
+ * @param pThis SB16 state.
+ * @param pVol Where to store the output stream volume information.
+ */
+static void sb16GetPcmOutVolume(PSB16STATE pThis, PPDMAUDIOVOLUME pVol)
+{
+ /* There's no mute switch, only volume controls. */
+ uint8_t lvol = sb16MixRegToVol(pThis, 0x32);
+ uint8_t rvol = sb16MixRegToVol(pThis, 0x33);
+
+ pVol->fMuted = false;
+ pVol->uLeft = lvol;
+ pVol->uRight = rvol;
+}
+
+static void sb16UpdateVolume(PSB16STATE pThis)
+{
+ PDMAUDIOVOLUME VolMaster;
+ sb16GetMasterVolume(pThis, &VolMaster);
+
+ PDMAUDIOVOLUME VolOut;
+ sb16GetPcmOutVolume(pThis, &VolOut);
+
+ /* Combine the master + output stream volume. */
+ PDMAUDIOVOLUME VolCombined;
+ RT_ZERO(VolCombined);
+
+ VolCombined.fMuted = VolMaster.fMuted || VolOut.fMuted;
+ if (!VolCombined.fMuted)
+ {
+ VolCombined.uLeft = ( (VolOut.uLeft ? VolOut.uLeft : 1)
+ * (VolMaster.uLeft ? VolMaster.uLeft : 1)) / PDMAUDIO_VOLUME_MAX;
+
+ VolCombined.uRight = ( (VolOut.uRight ? VolOut.uRight : 1)
+ * (VolMaster.uRight ? VolMaster.uRight : 1)) / PDMAUDIO_VOLUME_MAX;
+ }
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ if (!pDrv->Out.pStream)
+ continue;
+
+ int rc2 = pDrv->pConnector->pfnStreamSetVolume(pDrv->pConnector, pDrv->Out.pStream, &VolCombined);
+ AssertRC(rc2);
+ }
+}
+
+static void sb16CmdResetLegacy(PSB16STATE pThis)
+{
+ LogFlowFuncEnter();
+
+ pThis->freq = 11025;
+ pThis->fmt_signed = 0;
+ pThis->fmt_bits = 8;
+ pThis->fmt_stereo = 0;
+
+ /* At the moment we only have one stream, the output stream. */
+ PPDMAUDIOSTREAMCFG pCfg = &pThis->Out.Cfg;
+
+ pCfg->enmDir = PDMAUDIODIR_OUT;
+ pCfg->DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT;
+ pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ pCfg->Props.uHz = pThis->freq;
+ pCfg->Props.cChannels = 1; /* Mono */
+ pCfg->Props.cBytes = 1 /* 8-bit */;
+ pCfg->Props.fSigned = false;
+ pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels);
+
+ AssertCompile(sizeof(pCfg->szName) > sizeof("Output"));
+ strcpy(pCfg->szName, "Output");
+
+ sb16CloseOut(pThis);
+}
+
+static void sb16CmdReset(PSB16STATE pThis)
+{
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+ if (pThis->dma_auto)
+ {
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1);
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+ }
+
+ pThis->mixer_regs[0x82] = 0;
+ pThis->dma_auto = 0;
+ pThis->in_index = 0;
+ pThis->out_data_len = 0;
+ pThis->left_till_irq = 0;
+ pThis->needed_bytes = 0;
+ pThis->block_size = -1;
+ pThis->nzero = 0;
+ pThis->highspeed = 0;
+ pThis->v2x6 = 0;
+ pThis->cmd = -1;
+
+ dsp_out_data(pThis, 0xaa);
+ sb16SpeakerControl(pThis, 0);
+
+ sb16Control(pThis, 0);
+ sb16CmdResetLegacy(pThis);
+}
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTOUT}
+ */
+static DECLCALLBACK(int) dsp_write(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t val, unsigned cb)
+{
+ RT_NOREF(pDevIns, cb);
+ PSB16STATE pThis = (PSB16STATE)opaque;
+ int iport = nport - pThis->port;
+
+ LogFlowFunc(("write %#x <- %#x\n", nport, val));
+ switch (iport)
+ {
+ case 0x06:
+ switch (val)
+ {
+ case 0x00:
+ {
+ if (pThis->v2x6 == 1)
+ {
+ if (0 && pThis->highspeed)
+ {
+ pThis->highspeed = 0;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+ sb16Control(pThis, 0);
+ }
+ else
+ sb16CmdReset(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 */
+ sb16CmdReset(pThis);
+ break;
+
+ case 0x39:
+ dsp_out_data(pThis, 0x38);
+ sb16CmdReset(pThis);
+ pThis->v2x6 = 0x39;
+ break;
+
+ default:
+ pThis->v2x6 = val;
+ break;
+ }
+ break;
+
+ case 0x0c: /* Write data or command | write status */
+#if 0
+ if (pThis->highspeed)
+ break;
+#endif
+ if (0 == pThis->needed_bytes)
+ {
+ sb16HandleCommand(pThis, val);
+#if 0
+ if (0 == pThis->needed_bytes) {
+ log_dsp (pThis);
+ }
+#endif
+ }
+ else
+ {
+ if (pThis->in_index == sizeof (pThis->in2_data))
+ {
+ LogFlowFunc(("in data overrun\n"));
+ }
+ else
+ {
+ pThis->in2_data[pThis->in_index++] = val;
+ if (pThis->in_index == pThis->needed_bytes)
+ {
+ pThis->needed_bytes = 0;
+ complete (pThis);
+#if 0
+ log_dsp (pThis);
+#endif
+ }
+ }
+ }
+ break;
+
+ default:
+ LogFlowFunc(("nport=%#x, val=%#x)\n", nport, val));
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTIN}
+ */
+static DECLCALLBACK(int) dsp_read(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pDevIns, cb);
+ PSB16STATE pThis = (PSB16STATE)opaque;
+ int iport, retval, ack = 0;
+
+ iport = nport - pThis->port;
+
+ /** @todo reject non-byte access?
+ * The spec does not mention a non-byte access so we should check how real hardware behaves. */
+
+ switch (iport)
+ {
+ case 0x06: /* reset */
+ retval = 0xff;
+ break;
+
+ case 0x0a: /* read data */
+ if (pThis->out_data_len)
+ {
+ retval = pThis->out_data[--pThis->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 0x0c: /* 0 can write */
+ retval = pThis->can_write ? 0 : 0x80;
+ break;
+
+ case 0x0d: /* timer interrupt clear */
+ /* LogFlowFunc(("timer interrupt clear\n")); */
+ retval = 0;
+ break;
+
+ case 0x0e: /* data available status | irq 8 ack */
+ retval = (!pThis->out_data_len || pThis->highspeed) ? 0 : 0x80;
+ if (pThis->mixer_regs[0x82] & 1)
+ {
+ ack = 1;
+ pThis->mixer_regs[0x82] &= ~1;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+ }
+ break;
+
+ case 0x0f: /* irq 16 ack */
+ retval = 0xff;
+ if (pThis->mixer_regs[0x82] & 2)
+ {
+ ack = 1;
+ pThis->mixer_regs[0x82] &= ~2;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+ }
+ break;
+
+ default:
+ goto error;
+ }
+
+ if (!ack)
+ LogFlowFunc(("read %#x -> %#x\n", nport, retval));
+
+ *pu32 = retval;
+ return VINF_SUCCESS;
+
+ error:
+ LogFlowFunc(("warning: dsp_read %#x error\n", nport));
+ return VERR_IOM_IOPORT_UNUSED;
+}
+
+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);
+}
+
+static int mixer_write_indexb(PSB16STATE pThis, uint8_t val)
+{
+ pThis->mixer_nreg = val;
+ return VINF_SUCCESS;
+}
+
+uint32_t popcount(uint32_t u) /** @todo r=andy WTF? */
+{
+ 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;
+}
+
+uint32_t lsbindex(uint32_t u)
+{
+ return popcount((u & -(int32_t)u) - 1);
+}
+
+/* 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 mixer_write_datab(PSB16STATE pThis, uint8_t val)
+{
+ bool fUpdateMaster = false;
+ bool fUpdateStream = false;
+
+ LogFlowFunc(("mixer_write [%#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);
+ LogFlowFunc(("setting irq to %d (val=%#x)\n", irq, val));
+ if (irq > 0)
+ pThis->irq = irq;
+ break;
+ }
+
+ case 0x81:
+ {
+ int dma, hdma;
+
+ dma = lsbindex (val & 0xf);
+ hdma = lsbindex (val & 0xf0);
+ if (dma != pThis->dma || hdma != pThis->hdma)
+ LogFlow(("SB16: attempt to change DMA 8bit %d(%d), 16bit %d(%d) (val=%#x)\n",
+ dma, pThis->dma, hdma, pThis->hdma, val));
+#if 0
+ pThis->dma = dma;
+ pThis->hdma = hdma;
+#endif
+ break;
+ }
+
+ case 0x82:
+ LogFlowFunc(("attempt to write into IRQ status register (val=%#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{PFNIOMIOPORTOUT}
+ */
+static DECLCALLBACK(int) mixer_write(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t val, unsigned cb)
+{
+ RT_NOREF(pDevIns);
+ PSB16STATE pThis = (PSB16STATE)opaque;
+ int iport = nport - pThis->port;
+ switch (cb)
+ {
+ case 1:
+ switch (iport)
+ {
+ case 4:
+ mixer_write_indexb(pThis, val);
+ break;
+ case 5:
+ mixer_write_datab(pThis, val);
+ break;
+ }
+ break;
+ case 2:
+ mixer_write_indexb(pThis, val & 0xff);
+ mixer_write_datab(pThis, (val >> 8) & 0xff);
+ break;
+ default:
+ AssertMsgFailed(("Port=%#x cb=%d u32=%#x\n", nport, cb, val));
+ break;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{PFNIOMIOPORTIN}
+ */
+static DECLCALLBACK(int) mixer_read(PPDMDEVINS pDevIns, void *opaque, RTIOPORT nport, uint32_t *pu32, unsigned cb)
+{
+ RT_NOREF(pDevIns, cb, nport);
+ PSB16STATE pThis = (PSB16STATE)opaque;
+
+#ifndef DEBUG_SB16_MOST
+ if (pThis->mixer_nreg != 0x82)
+ LogFlowFunc(("mixer_read[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
+#else
+ LogFlowFunc(("mixer_read[%#x] -> %#x\n", pThis->mixer_nreg, pThis->mixer_regs[pThis->mixer_nreg]));
+#endif
+ *pu32 = pThis->mixer_regs[pThis->mixer_nreg];
+ return VINF_SUCCESS;
+}
+
+/**
+ * Called by sb16DMARead.
+ */
+static int sb16WriteAudio(PSB16STATE pThis, int nchan, uint32_t dma_pos, uint32_t dma_len, int len)
+{
+ uint8_t tmpbuf[_4K]; /** @todo Have a buffer on the heap. */
+ uint32_t cbToWrite = len;
+ uint32_t cbWrittenTotal = 0;
+
+ while (cbToWrite)
+ {
+ uint32_t cbToRead = RT_MIN(dma_len - dma_pos, cbToWrite);
+ if (cbToRead > sizeof(tmpbuf))
+ cbToRead = sizeof(tmpbuf);
+
+ uint32_t cbRead = 0;
+ int rc2 = PDMDevHlpDMAReadMemory(pThis->pDevInsR3, nchan, tmpbuf, dma_pos, cbToRead, &cbRead);
+ AssertMsgRC(rc2, (" from DMA failed: %Rrc\n", rc2));
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ if (cbRead)
+ {
+ RTFILE fh;
+ RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ RTFileWrite(fh, tmpbuf, cbRead, NULL);
+ RTFileClose(fh);
+ }
+#endif
+ /*
+ * Write data to the backends.
+ */
+ uint32_t cbWritten = 0;
+
+ /* Just multiplex the output to the connected backends.
+ * No need to utilize the virtual mixer here (yet). */
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ if (!pDrv->Out.pStream)
+ continue;
+
+ if (!DrvAudioHlpStreamStatusCanWrite(pDrv->pConnector->pfnStreamGetStatus(pDrv->pConnector, pDrv->Out.pStream)))
+ continue;
+
+ uint32_t cbWrittenToStream = 0;
+ rc2 = pDrv->pConnector->pfnStreamWrite(pDrv->pConnector, pDrv->Out.pStream, tmpbuf, cbRead, &cbWrittenToStream);
+
+ LogFlowFunc(("\tLUN#%RU8: rc=%Rrc, cbWrittenToStream=%RU32\n", pDrv->uLUN, rc2, cbWrittenToStream));
+ }
+
+ cbWritten = cbRead; /* Always report everything written, as the backends need to keep up themselves. */
+
+ LogFlowFunc(("\tcbToRead=%RU32, cbToWrite=%RU32, cbWritten=%RU32, cbLeft=%RU32\n",
+ cbToRead, cbToWrite, cbWritten, cbToWrite - cbWrittenTotal));
+
+ Assert(cbToWrite >= cbWritten);
+ cbToWrite -= cbWritten;
+ dma_pos = (dma_pos + cbWritten) % dma_len;
+ cbWrittenTotal += cbWritten;
+
+ if (!cbWritten)
+ break;
+ }
+
+ return cbWrittenTotal;
+}
+
+/**
+ * @callback_method_impl{FNDMATRANSFERHANDLER,
+ * Worker callback for both DMA channels.}
+ */
+static DECLCALLBACK(uint32_t) sb16DMARead(PPDMDEVINS pDevIns, void *opaque, unsigned nchan, uint32_t dma_pos, uint32_t dma_len)
+{
+ RT_NOREF(pDevIns);
+ PSB16STATE pThis = (PSB16STATE)opaque;
+ int till, copy, written, free;
+
+ if (pThis->block_size <= 0)
+ {
+ LogFlowFunc(("invalid block size=%d nchan=%d dma_pos=%d dma_len=%d\n",
+ pThis->block_size, nchan, dma_pos, dma_len));
+ return dma_pos;
+ }
+
+ if (pThis->left_till_irq < 0)
+ pThis->left_till_irq = pThis->block_size;
+
+ free = dma_len;
+
+ copy = free;
+ till = pThis->left_till_irq;
+
+#ifdef DEBUG_SB16_MOST
+ LogFlowFunc(("pos:%06d %d till:%d len:%d\n", dma_pos, free, till, dma_len));
+#endif
+
+ if (copy >= till)
+ {
+ if (0 == pThis->dma_auto)
+ {
+ copy = till;
+ }
+ else
+ {
+ if (copy >= till + pThis->block_size)
+ copy = till; /* Make sure we won't skip IRQs. */
+ }
+ }
+
+ written = sb16WriteAudio(pThis, nchan, dma_pos, dma_len, copy);
+ dma_pos = (dma_pos + written) % dma_len;
+ pThis->left_till_irq -= written;
+
+ if (pThis->left_till_irq <= 0)
+ {
+ pThis->mixer_regs[0x82] |= (nchan & 4) ? 2 : 1;
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 1);
+ if (0 == pThis->dma_auto)
+ {
+ sb16Control(pThis, 0);
+ sb16SpeakerControl(pThis, 0);
+ }
+ }
+
+ Log3Func(("pos %d/%d free %5d till %5d copy %5d written %5d block_size %5d\n",
+ dma_pos, dma_len, free, pThis->left_till_irq, copy, written,
+ pThis->block_size));
+
+ while (pThis->left_till_irq <= 0)
+ pThis->left_till_irq += pThis->block_size;
+
+ return dma_pos;
+}
+
+static void sb16TimerMaybeStart(PSB16STATE pThis)
+{
+ LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive));
+
+ if (pThis->cStreamsActive == 0) /* Only start the timer if there are no active streams. */
+ return;
+
+ if (!pThis->pTimerIO)
+ return;
+
+ /* Set timer flag. */
+ ASMAtomicXchgBool(&pThis->fTimerActive, true);
+
+ /* Update current time timestamp. */
+ pThis->uTimerTSIO = TMTimerGet(pThis->pTimerIO);
+
+ /* Fire off timer. */
+ TMTimerSet(pThis->pTimerIO, TMTimerGet(pThis->pTimerIO) + pThis->cTimerTicksIO);
+}
+
+static void sb16TimerMaybeStop(PSB16STATE pThis)
+{
+ LogFlowFunc(("cStreamsActive=%RU8\n", pThis->cStreamsActive));
+
+ if (pThis->cStreamsActive) /* Some streams still active? Bail out. */
+ return;
+
+ if (!pThis->pTimerIO)
+ return;
+
+ /* Set timer flag. */
+ ASMAtomicXchgBool(&pThis->fTimerActive, false);
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV}
+ */
+static DECLCALLBACK(void) sb16TimerIO(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns);
+ PSB16STATE pThis = (PSB16STATE)pvUser;
+ Assert(pThis == PDMINS_2_DATA(pDevIns, PSB16STATE));
+ AssertPtr(pThis);
+
+ uint64_t cTicksNow = TMTimerGet(pTimer);
+ bool fIsPlaying = false; /* Whether one or more streams are still playing. */
+ bool fDoTransfer = false;
+
+ pThis->uTimerTSIO = cTicksNow;
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ PPDMAUDIOSTREAM pStream = pDrv->Out.pStream;
+ if (!pStream)
+ continue;
+
+ PPDMIAUDIOCONNECTOR pConn = pDrv->pConnector;
+ if (!pConn)
+ continue;
+
+ int rc2 = pConn->pfnStreamIterate(pConn, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = pConn->pfnStreamPlay(pConn, pStream, NULL /* cPlayed */);
+ if (RT_FAILURE(rc2))
+ {
+ LogFlowFunc(("%s: Failed playing stream, rc=%Rrc\n", pStream->szName, rc2));
+ continue;
+ }
+
+ /* Only do the next DMA transfer if we're able to write the remaining data block. */
+ fDoTransfer = pConn->pfnStreamGetWritable(pConn, pStream) > (unsigned)pThis->left_till_irq;
+ }
+
+ PDMAUDIOSTREAMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pStream);
+ fIsPlaying |= ( (strmSts & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ || (strmSts & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE));
+ }
+
+ bool fTimerActive = ASMAtomicReadBool(&pThis->fTimerActive);
+ bool fKickTimer = fTimerActive || fIsPlaying;
+
+ LogFlowFunc(("fTimerActive=%RTbool, fIsPlaying=%RTbool\n", fTimerActive, fIsPlaying));
+
+ if (fDoTransfer)
+ {
+ /* Schedule the next transfer. */
+ PDMDevHlpDMASchedule(pThis->pDevInsR3);
+
+ /* Kick the timer at least one more time. */
+ fKickTimer = true;
+ }
+
+ /*
+ * Recording.
+ */
+ /** @todo Implement recording. */
+
+ if (fKickTimer)
+ {
+ /* Kick the timer again. */
+ uint64_t cTicks = pThis->cTimerTicksIO;
+ /** @todo adjust cTicks down by now much cbOutMin represents. */
+ TMTimerSet(pThis->pTimerIO, cTicksNow + cTicks);
+ }
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) sb16LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ SSMR3PutS32(pSSM, pThis->irqCfg);
+ SSMR3PutS32(pSSM, pThis->dmaCfg);
+ SSMR3PutS32(pSSM, pThis->hdmaCfg);
+ SSMR3PutS32(pSSM, pThis->portCfg);
+ SSMR3PutS32(pSSM, pThis->verCfg);
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+/**
+ * Worker for sb16SaveExec.
+ */
+static int sb16Save(PSSMHANDLE pSSM, PSB16STATE pThis)
+{
+ SSMR3PutS32(pSSM, pThis->irq);
+ SSMR3PutS32(pSSM, pThis->dma);
+ SSMR3PutS32(pSSM, pThis->hdma);
+ SSMR3PutS32(pSSM, pThis->port);
+ SSMR3PutS32(pSSM, pThis->ver);
+ SSMR3PutS32(pSSM, pThis->in_index);
+ SSMR3PutS32(pSSM, pThis->out_data_len);
+ SSMR3PutS32(pSSM, pThis->fmt_stereo);
+ SSMR3PutS32(pSSM, pThis->fmt_signed);
+ SSMR3PutS32(pSSM, pThis->fmt_bits);
+
+ SSMR3PutU32(pSSM, pThis->fmt);
+
+ SSMR3PutS32(pSSM, pThis->dma_auto);
+ SSMR3PutS32(pSSM, pThis->block_size);
+ SSMR3PutS32(pSSM, pThis->fifo);
+ SSMR3PutS32(pSSM, pThis->freq);
+ SSMR3PutS32(pSSM, pThis->time_const);
+ SSMR3PutS32(pSSM, pThis->speaker);
+ SSMR3PutS32(pSSM, pThis->needed_bytes);
+ SSMR3PutS32(pSSM, pThis->cmd);
+ SSMR3PutS32(pSSM, pThis->use_hdma);
+ SSMR3PutS32(pSSM, pThis->highspeed);
+ SSMR3PutS32(pSSM, pThis->can_write);
+ SSMR3PutS32(pSSM, pThis->v2x6);
+
+ SSMR3PutU8 (pSSM, pThis->csp_param);
+ SSMR3PutU8 (pSSM, pThis->csp_value);
+ SSMR3PutU8 (pSSM, pThis->csp_mode);
+ SSMR3PutU8 (pSSM, pThis->csp_param); /* Bug compatible! */
+ SSMR3PutMem(pSSM, pThis->csp_regs, 256);
+ SSMR3PutU8 (pSSM, pThis->csp_index);
+ SSMR3PutMem(pSSM, pThis->csp_reg83, 4);
+ SSMR3PutS32(pSSM, pThis->csp_reg83r);
+ SSMR3PutS32(pSSM, pThis->csp_reg83w);
+
+ SSMR3PutMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data));
+ SSMR3PutMem(pSSM, pThis->out_data, sizeof(pThis->out_data));
+ SSMR3PutU8 (pSSM, pThis->test_reg);
+ SSMR3PutU8 (pSSM, pThis->last_read_byte);
+
+ SSMR3PutS32(pSSM, pThis->nzero);
+ SSMR3PutS32(pSSM, pThis->left_till_irq);
+ SSMR3PutS32(pSSM, pThis->dma_running);
+ SSMR3PutS32(pSSM, pThis->bytes_per_second);
+ SSMR3PutS32(pSSM, pThis->align);
+
+ SSMR3PutS32(pSSM, pThis->mixer_nreg);
+ return SSMR3PutMem(pSSM, pThis->mixer_regs, 256);
+}
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) sb16SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ sb16LiveExec(pDevIns, pSSM, 0);
+ return sb16Save(pSSM, pThis);
+}
+
+/**
+ * Worker for sb16LoadExec.
+ */
+static int sb16Load(PSSMHANDLE pSSM, PSB16STATE pThis)
+{
+ SSMR3GetS32(pSSM, &pThis->irq);
+ SSMR3GetS32(pSSM, &pThis->dma);
+ SSMR3GetS32(pSSM, &pThis->hdma);
+ SSMR3GetS32(pSSM, &pThis->port);
+ SSMR3GetS32(pSSM, &pThis->ver);
+ SSMR3GetS32(pSSM, &pThis->in_index);
+ SSMR3GetS32(pSSM, &pThis->out_data_len);
+ SSMR3GetS32(pSSM, &pThis->fmt_stereo);
+ SSMR3GetS32(pSSM, &pThis->fmt_signed);
+ SSMR3GetS32(pSSM, &pThis->fmt_bits);
+
+ SSMR3GetU32(pSSM, (uint32_t *)&pThis->fmt);
+
+ SSMR3GetS32(pSSM, &pThis->dma_auto);
+ SSMR3GetS32(pSSM, &pThis->block_size);
+ SSMR3GetS32(pSSM, &pThis->fifo);
+ SSMR3GetS32(pSSM, &pThis->freq);
+ SSMR3GetS32(pSSM, &pThis->time_const);
+ SSMR3GetS32(pSSM, &pThis->speaker);
+ SSMR3GetS32(pSSM, &pThis->needed_bytes);
+ SSMR3GetS32(pSSM, &pThis->cmd);
+ SSMR3GetS32(pSSM, &pThis->use_hdma);
+ SSMR3GetS32(pSSM, &pThis->highspeed);
+ SSMR3GetS32(pSSM, &pThis->can_write);
+ SSMR3GetS32(pSSM, &pThis->v2x6);
+
+ SSMR3GetU8 (pSSM, &pThis->csp_param);
+ SSMR3GetU8 (pSSM, &pThis->csp_value);
+ SSMR3GetU8 (pSSM, &pThis->csp_mode);
+ SSMR3GetU8 (pSSM, &pThis->csp_param); /* Bug compatible! */
+ SSMR3GetMem(pSSM, pThis->csp_regs, 256);
+ SSMR3GetU8 (pSSM, &pThis->csp_index);
+ SSMR3GetMem(pSSM, pThis->csp_reg83, 4);
+ SSMR3GetS32(pSSM, &pThis->csp_reg83r);
+ SSMR3GetS32(pSSM, &pThis->csp_reg83w);
+
+ SSMR3GetMem(pSSM, pThis->in2_data, sizeof(pThis->in2_data));
+ SSMR3GetMem(pSSM, pThis->out_data, sizeof(pThis->out_data));
+ SSMR3GetU8 (pSSM, &pThis->test_reg);
+ SSMR3GetU8 (pSSM, &pThis->last_read_byte);
+
+ SSMR3GetS32(pSSM, &pThis->nzero);
+ SSMR3GetS32(pSSM, &pThis->left_till_irq);
+ SSMR3GetS32(pSSM, &pThis->dma_running);
+ SSMR3GetS32(pSSM, &pThis->bytes_per_second);
+ SSMR3GetS32(pSSM, &pThis->align);
+
+ int32_t mixer_nreg = 0;
+ int rc = SSMR3GetS32(pSSM, &mixer_nreg);
+ AssertRCReturn(rc, rc);
+ pThis->mixer_nreg = (uint8_t)mixer_nreg;
+ rc = SSMR3GetMem(pSSM, pThis->mixer_regs, 256);
+ AssertRCReturn(rc, rc);
+
+ if (pThis->dma_running)
+ {
+ sb16CheckAndReOpenOut(pThis);
+ sb16Control(pThis, 1);
+ sb16SpeakerControl(pThis, pThis->speaker);
+ }
+
+ /* 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 = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ 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)
+ {
+ int32_t irq;
+ SSMR3GetS32 (pSSM, &irq);
+ int32_t dma;
+ SSMR3GetS32 (pSSM, &dma);
+ int32_t hdma;
+ SSMR3GetS32 (pSSM, &hdma);
+ int32_t port;
+ SSMR3GetS32 (pSSM, &port);
+ int32_t ver;
+ int rc = SSMR3GetS32 (pSSM, &ver);
+ AssertRCReturn (rc, rc);
+
+ if ( irq != pThis->irqCfg
+ || dma != pThis->dmaCfg
+ || hdma != pThis->hdmaCfg
+ || port != pThis->portCfg
+ || ver != pThis->verCfg)
+ {
+ return SSMR3SetCfgError(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, pThis->irqCfg,
+ dma, pThis->dmaCfg,
+ hdma, pThis->hdmaCfg,
+ port, pThis->portCfg,
+ ver, pThis->verCfg);
+ }
+ }
+
+ if (uPass != SSM_PASS_FINAL)
+ return VINF_SUCCESS;
+
+ return sb16Load(pSSM, pThis);
+}
+
+/**
+ * Creates a PDM audio stream for a specific driver.
+ *
+ * @returns IPRT status code.
+ * @param pThis SB16 state.
+ * @param pCfg Stream configuration to use.
+ * @param pDrv Driver stream to create PDM stream for.
+ */
+static int sb16CreateDrvStream(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg, PSB16DRIVER pDrv)
+{
+ RT_NOREF(pThis);
+
+ AssertReturn(pCfg->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
+ Assert(DrvAudioHlpStreamCfgIsValid(pCfg));
+
+ PPDMAUDIOSTREAMCFG pCfgHost = DrvAudioHlpStreamCfgDup(pCfg);
+ if (!pCfgHost)
+ return VERR_NO_MEMORY;
+
+ LogFunc(("[LUN#%RU8] %s\n", pDrv->uLUN, pCfgHost->szName));
+
+ AssertMsg(pDrv->Out.pStream == NULL, ("[LUN#%RU8] Driver stream already present when it must not\n", pDrv->uLUN));
+
+ /* Disable pre-buffering for SB16; not needed for that bit of data. */
+ pCfgHost->Backend.cfPreBuf = 0;
+
+ int rc = pDrv->pConnector->pfnStreamCreate(pDrv->pConnector, pCfgHost, pCfg /* pCfgGuest */, &pDrv->Out.pStream);
+ if (RT_SUCCESS(rc))
+ {
+ pDrv->pConnector->pfnStreamRetain(pDrv->pConnector, pDrv->Out.pStream);
+ LogFlowFunc(("LUN#%RU8: Created output \"%s\", rc=%Rrc\n", pDrv->uLUN, pCfg->szName, rc));
+ }
+
+ DrvAudioHlpStreamCfgFree(pCfgHost);
+
+ return rc;
+}
+
+/**
+ * Destroys a PDM audio stream of a specific driver.
+ *
+ * @param pThis SB16 state.
+ * @param pDrv Driver stream to destroy PDM stream for.
+ */
+static void sb16DestroyDrvStream(PSB16STATE pThis, PSB16DRIVER pDrv)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pDrv);
+
+ if (pDrv->Out.pStream)
+ {
+ pDrv->pConnector->pfnStreamRelease(pDrv->pConnector, pDrv->Out.pStream);
+
+ int rc2 = pDrv->pConnector->pfnStreamControl(pDrv->pConnector, pDrv->Out.pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc2);
+
+ rc2 = pDrv->pConnector->pfnStreamDestroy(pDrv->pConnector, pDrv->Out.pStream);
+ AssertRC(rc2);
+
+ pDrv->Out.pStream = NULL;
+ }
+}
+
+/**
+ * Checks if the output stream needs to be (re-)created and does so if needed.
+ *
+ * @return VBox status code.
+ * @param pThis SB16 state.
+ */
+static int sb16CheckAndReOpenOut(PSB16STATE pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pThis->freq > 0)
+ {
+ /* At the moment we only have one stream, the output stream. */
+ PDMAUDIOSTREAMCFG Cfg;
+ RT_ZERO(Cfg);
+
+ Cfg.Props.uHz = pThis->freq;
+ Cfg.Props.cChannels = 1 << pThis->fmt_stereo;
+ Cfg.Props.cBytes = pThis->fmt_bits / 8;
+ Cfg.Props.fSigned = RT_BOOL(pThis->fmt_signed);
+ Cfg.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Cfg.Props.cBytes, Cfg.Props.cChannels);
+
+ if (!DrvAudioHlpPCMPropsAreEqual(&Cfg.Props, &pThis->Out.Cfg.Props))
+ {
+ Cfg.enmDir = PDMAUDIODIR_OUT;
+ Cfg.DestSource.Dest = PDMAUDIOPLAYBACKDEST_FRONT;
+ Cfg.enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ strcpy(Cfg.szName, "Output");
+
+ sb16CloseOut(pThis);
+
+ rc = sb16OpenOut(pThis, &Cfg);
+ AssertRC(rc);
+ }
+ }
+ else
+ sb16CloseOut(pThis);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static int sb16OpenOut(PSB16STATE pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ if (!DrvAudioHlpStreamCfgIsValid(pCfg))
+ return VERR_INVALID_PARAMETER;
+
+ int rc = DrvAudioHlpStreamCfgCopy(&pThis->Out.Cfg, pCfg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Set scheduling hint (if available). */
+ if (pThis->cTimerTicksIO)
+ pThis->Out.Cfg.Device.uSchedulingHintMs = 1000 /* ms */ / (TMTimerGetFreq(pThis->pTimerIO) / pThis->cTimerTicksIO);
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ int rc2 = sb16CreateDrvStream(pThis, &pThis->Out.Cfg, 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. */
+ }
+
+ sb16UpdateVolume(pThis);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static void sb16CloseOut(PSB16STATE pThis)
+{
+ AssertPtrReturnVoid(pThis);
+
+ LogFlowFuncEnter();
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ sb16DestroyDrvStream(pThis, pDrv);
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * @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;
+}
+
+
+/**
+ * Attach command, internal version.
+ *
+ * This is called to let the device attach to a driver for a specified LUN
+ * during runtime. This is not called during VM construction, the device
+ * constructor has to attach to all the available drivers.
+ *
+ * @returns VBox status code.
+ * @param pThis SB16 state.
+ * @param uLUN The logical unit which is being detached.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ * @param ppDrv Attached driver instance on success. Optional.
+ */
+static int sb16AttachInternal(PSB16STATE pThis, unsigned uLUN, uint32_t fFlags, PSB16DRIVER *ppDrv)
+{
+ RT_NOREF(fFlags);
+
+ /*
+ * Attach driver.
+ */
+ char *pszDesc;
+ if (RTStrAPrintf(&pszDesc, "Audio driver port (SB16) for LUN #%u", uLUN) <= 0)
+ AssertLogRelFailedReturn(VERR_NO_MEMORY);
+
+ PPDMIBASE pDrvBase;
+ int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, uLUN,
+ &pThis->IBase, &pDrvBase, pszDesc);
+ if (RT_SUCCESS(rc))
+ {
+ PSB16DRIVER pDrv = (PSB16DRIVER)RTMemAllocZ(sizeof(SB16DRIVER));
+ if (pDrv)
+ {
+ pDrv->pDrvBase = pDrvBase;
+ pDrv->pConnector = PDMIBASE_QUERY_INTERFACE(pDrvBase, PDMIAUDIOCONNECTOR);
+ AssertMsg(pDrv->pConnector != NULL, ("Configuration error: LUN #%u has no host audio interface, rc=%Rrc\n", uLUN, rc));
+ pDrv->pSB16State = pThis;
+ pDrv->uLUN = uLUN;
+
+ /*
+ * For now we always set the driver at LUN 0 as our primary
+ * host backend. This might change in the future.
+ */
+ if (pDrv->uLUN == 0)
+ pDrv->fFlags |= PDMAUDIODRVFLAGS_PRIMARY;
+
+ LogFunc(("LUN#%RU8: pCon=%p, drvFlags=0x%x\n", uLUN, pDrv->pConnector, pDrv->fFlags));
+
+ /* Attach to driver list if not attached yet. */
+ if (!pDrv->fAttached)
+ {
+ RTListAppend(&pThis->lstDrv, &pDrv->Node);
+ pDrv->fAttached = true;
+ }
+
+ if (ppDrv)
+ *ppDrv = pDrv;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ LogFunc(("No attached driver for LUN #%u\n", uLUN));
+
+ if (RT_FAILURE(rc))
+ {
+ /* Only free this string on failure;
+ * must remain valid for the live of the driver instance. */
+ RTStrFree(pszDesc);
+ }
+
+ LogFunc(("iLUN=%u, fFlags=0x%x, rc=%Rrc\n", uLUN, fFlags, rc));
+ return rc;
+}
+
+/**
+ * Detach command, internal version.
+ *
+ * This is called to let the device detach from a driver for a specified LUN
+ * during runtime.
+ *
+ * @returns VBox status code.
+ * @param pThis SB16 state.
+ * @param pDrv Driver to detach device from.
+ * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
+ */
+static int sb16DetachInternal(PSB16STATE pThis, PSB16DRIVER pDrv, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ sb16DestroyDrvStream(pThis, pDrv);
+
+ RTListNodeRemove(&pDrv->Node);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", pDrv->uLUN, fFlags));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) sb16Attach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ PSB16DRIVER pDrv;
+ int rc2 = sb16AttachInternal(pThis, uLUN, fFlags, &pDrv);
+ if (RT_SUCCESS(rc2))
+ rc2 = sb16CreateDrvStream(pThis, &pThis->Out.Cfg, pDrv);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) sb16Detach(PPDMDEVINS pDevIns, unsigned uLUN, uint32_t fFlags)
+{
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogFunc(("uLUN=%u, fFlags=0x%x\n", uLUN, fFlags));
+
+ PSB16DRIVER pDrv, pDrvNext;
+ RTListForEachSafe(&pThis->lstDrv, pDrv, pDrvNext, SB16DRIVER, Node)
+ {
+ if (pDrv->uLUN == uLUN)
+ {
+ int rc2 = sb16DetachInternal(pThis, pDrv, fFlags);
+ if (RT_SUCCESS(rc2))
+ {
+ RTMemFree(pDrv);
+ pDrv = NULL;
+ }
+
+ break;
+ }
+ }
+}
+
+/**
+ * Re-attaches (replaces) a driver with a new driver.
+ *
+ * @returns VBox status code.
+ * @param pThis Device instance.
+ * @param pDrv Driver instance used for attaching to.
+ * If NULL is specified, a new driver will be created and appended
+ * to the driver list.
+ * @param uLUN The logical unit which is being re-detached.
+ * @param pszDriver New driver name to attach.
+ */
+static int sb16Reattach(PSB16STATE pThis, PSB16DRIVER pDrv, uint8_t uLUN, const char *pszDriver)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDriver, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pDrv)
+ {
+ rc = sb16DetachInternal(pThis, pDrv, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pDrv = NULL;
+ }
+
+ PVM pVM = PDMDevHlpGetVM(pThis->pDevInsR3);
+ PCFGMNODE pRoot = CFGMR3GetRoot(pVM);
+ PCFGMNODE pDev0 = CFGMR3GetChild(pRoot, "Devices/sb16/0/");
+
+ /* Remove LUN branch. */
+ CFGMR3RemoveNode(CFGMR3GetChildF(pDev0, "LUN#%u/", uLUN));
+
+ if (pDrv)
+ {
+ /* Re-use the driver instance so detach it before. */
+ rc = PDMDevHlpDriverDetach(pThis->pDevInsR3, PDMIBASE_2_PDMDRV(pDrv->pDrvBase), 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+#define RC_CHECK() if (RT_FAILURE(rc)) { AssertReleaseRC(rc); break; }
+
+ do
+ {
+ PCFGMNODE pLunL0;
+ rc = CFGMR3InsertNodeF(pDev0, &pLunL0, "LUN#%u/", uLUN); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL0, "Driver", "AUDIO"); RC_CHECK();
+ rc = CFGMR3InsertNode(pLunL0, "Config/", NULL); RC_CHECK();
+
+ PCFGMNODE pLunL1, pLunL2;
+ rc = CFGMR3InsertNode (pLunL0, "AttachedDriver/", &pLunL1); RC_CHECK();
+ rc = CFGMR3InsertNode (pLunL1, "Config/", &pLunL2); RC_CHECK();
+ rc = CFGMR3InsertString(pLunL1, "Driver", pszDriver); RC_CHECK();
+
+ rc = CFGMR3InsertString(pLunL2, "AudioDriver", pszDriver); RC_CHECK();
+
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ rc = sb16AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+
+ LogFunc(("pThis=%p, uLUN=%u, pszDriver=%s, rc=%Rrc\n", pThis, uLUN, pszDriver, rc));
+
+#undef RC_CHECK
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) sb16DevReset(PPDMDEVINS pDevIns)
+{
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ /* Bring back the device to initial state, and especially make
+ * sure there's no interrupt or DMA activity.
+ */
+ PDMDevHlpISASetIrq(pThis->pDevInsR3, pThis->irq, 0);
+
+ pThis->mixer_regs[0x82] = 0;
+ pThis->csp_regs[5] = 1;
+ pThis->csp_regs[9] = 0xf8;
+
+ pThis->dma_auto = 0;
+ pThis->in_index = 0;
+ pThis->out_data_len = 0;
+ pThis->left_till_irq = 0;
+ pThis->needed_bytes = 0;
+ pThis->block_size = -1;
+ pThis->nzero = 0;
+ pThis->highspeed = 0;
+ pThis->v2x6 = 0;
+ pThis->cmd = -1;
+
+ sb16MixerReset(pThis);
+ sb16SpeakerControl(pThis, 0);
+ sb16Control(pThis, 0);
+ sb16CmdResetLegacy(pThis);
+}
+
+/**
+ * Powers off the device.
+ *
+ * @param pDevIns Device instance to power off.
+ */
+static DECLCALLBACK(void) sb16PowerOff(PPDMDEVINS pDevIns)
+{
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogRel2(("SB16: Powering off ...\n"));
+
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ sb16DestroyDrvStream(pThis, pDrv);
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) sb16Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* this shall come first */
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ LogFlowFuncEnter();
+
+ PSB16DRIVER pDrv;
+ while (!RTListIsEmpty(&pThis->lstDrv))
+ {
+ pDrv = RTListGetFirst(&pThis->lstDrv, SB16DRIVER, Node);
+
+ RTListNodeRemove(&pDrv->Node);
+ RTMemFree(pDrv);
+ }
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) sb16Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ RT_NOREF(iInstance);
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* this shall come first */
+ PSB16STATE pThis = PDMINS_2_DATA(pDevIns, PSB16STATE);
+
+ /*
+ * 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);
+
+ /*
+ * Validations.
+ */
+ Assert(iInstance == 0);
+ if (!CFGMR3AreValuesValid(pCfg,
+ "IRQ\0"
+ "DMA\0"
+ "DMA16\0"
+ "Port\0"
+ "Version\0"
+ "TimerHz\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("Invalid configuration for SB16 device"));
+
+ /*
+ * Read config data.
+ */
+ int rc = CFGMR3QuerySIntDef(pCfg, "IRQ", &pThis->irq, 5);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: Failed to get the \"IRQ\" value"));
+ pThis->irqCfg = pThis->irq;
+
+ rc = CFGMR3QuerySIntDef(pCfg, "DMA", &pThis->dma, 1);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: Failed to get the \"DMA\" value"));
+ pThis->dmaCfg = pThis->dma;
+
+ rc = CFGMR3QuerySIntDef(pCfg, "DMA16", &pThis->hdma, 5);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: Failed to get the \"DMA16\" value"));
+ pThis->hdmaCfg = pThis->hdma;
+
+ RTIOPORT Port;
+ rc = CFGMR3QueryPortDef(pCfg, "Port", &Port, 0x220);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: Failed to get the \"Port\" value"));
+ pThis->port = Port;
+ pThis->portCfg = Port;
+
+ uint16_t u16Version;
+ rc = CFGMR3QueryU16Def(pCfg, "Version", &u16Version, 0x0405);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: Failed to get the \"Version\" value"));
+ pThis->ver = u16Version;
+ pThis->verCfg = u16Version;
+
+ uint16_t uTimerHz;
+ rc = CFGMR3QueryU16Def(pCfg, "TimerHz", &uTimerHz, 100 /* Hz */);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("SB16 configuration error: failed to read Hertz (Hz) rate as unsigned integer"));
+ /*
+ * Setup the mixer now that we've got the irq and dma channel numbers.
+ */
+ pThis->mixer_regs[0x80] = magic_of_irq(pThis->irq);
+ pThis->mixer_regs[0x81] = (1 << pThis->dma) | (1 << pThis->hdma);
+ pThis->mixer_regs[0x82] = 2 << 5;
+
+ sb16MixerReset(pThis);
+
+ /*
+ * Create timer(s), register & attach stuff.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIRQ, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IRQ timer", &pThis->pTimerIRQ);
+ if (RT_FAILURE(rc))
+ AssertMsgFailedReturn(("Error creating IRQ timer, rc=%Rrc\n", rc), rc);
+
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->port + 0x04, 2, pThis, mixer_write, mixer_read, NULL, NULL, "SB16");
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = PDMDevHlpIOPortRegister(pDevIns, pThis->port + 0x06, 10, pThis, dsp_write, dsp_read, NULL, NULL, "SB16");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpDMARegister(pDevIns, pThis->hdma, sb16DMARead, pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = PDMDevHlpDMARegister(pDevIns, pThis->dma, sb16DMARead, pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->can_write = 1;
+
+ rc = PDMDevHlpSSMRegister3(pDevIns, SB16_SAVE_STATE_VERSION, sizeof(SB16STATE), sb16LiveExec, sb16SaveExec, sb16LoadExec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach driver.
+ */
+ uint8_t uLUN;
+ for (uLUN = 0; uLUN < UINT8_MAX; ++uLUN)
+ {
+ LogFunc(("Trying to attach driver for LUN #%RU8 ...\n", uLUN));
+ rc = sb16AttachInternal(pThis, uLUN, 0 /* fFlags */, NULL /* ppDrv */);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_AUDIO_BACKEND_INIT_FAILED)
+ {
+ sb16Reattach(pThis, NULL /* pDrv */, uLUN, "NullAudio");
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Host audio backend initialization has failed. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ /* Attaching to the NULL audio backend will never fail. */
+ rc = VINF_SUCCESS;
+ }
+ break;
+ }
+ }
+
+ LogFunc(("cLUNs=%RU8, rc=%Rrc\n", uLUN, rc));
+
+ sb16CmdResetLegacy(pThis);
+
+#ifdef VBOX_WITH_AUDIO_SB16_ONETIME_INIT
+ PSB16DRIVER pDrv;
+ RTListForEach(&pThis->lstDrv, pDrv, SB16DRIVER, Node)
+ {
+ /*
+ * Only primary drivers are critical for the VM to run. Everything else
+ * might not worth showing an own error message box in the GUI.
+ */
+ if (!(pDrv->fFlags & PDMAUDIODRVFLAGS_PRIMARY))
+ continue;
+
+ PPDMIAUDIOCONNECTOR pCon = pDrv->pConnector;
+ AssertPtr(pCon);
+
+ /** @todo No input streams available for SB16 yet. */
+
+ if (!pDrv->Out.pStream)
+ continue;
+
+ bool fValidOut = pCon->pfnStreamGetStatus(pCon, pDrv->Out.pStream) & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+ if (!fValidOut)
+ {
+ LogRel(("SB16: Falling back to NULL backend (no sound audible)\n"));
+
+ sb16CmdResetLegacy(pThis);
+ sb16Reattach(pThis, pDrv, pDrv->uLUN, "NullAudio");
+
+ PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("No audio devices could be opened. Selecting the NULL audio backend "
+ "with the consequence that no sound is audible"));
+ }
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, sb16TimerIO, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "SB16 IO timer", &pThis->pTimerIO);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cTimerTicksIO = TMTimerGetFreq(pThis->pTimerIO) / uTimerHz;
+ pThis->uTimerTSIO = TMTimerGet(pThis->pTimerIO);
+ LogFunc(("Timer ticks=%RU64 (%RU16 Hz)\n", pThis->cTimerTicksIO, uTimerHz));
+ }
+ else
+ AssertMsgFailedReturn(("Error creating I/O timer, rc=%Rrc\n", rc), rc);
+ }
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "sb16WriteAudio.pcm");
+#endif
+
+ return VINF_SUCCESS;
+}
+
+const PDMDEVREG g_DeviceSB16 =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "sb16",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Sound Blaster 16 Controller",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS,
+ /* fClass */
+ PDM_DEVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ 1,
+ /* cbInstance */
+ sizeof(SB16STATE),
+ /* 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,
+ /* 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..c11fe1e6
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudio.cpp
@@ -0,0 +1,3676 @@
+/* $Id: DrvAudio.cpp $ */
+/** @file
+ * Intermediate audio driver header.
+ *
+ * @remarks Intermediate audio driver for connecting the audio device emulation
+ * with the host backend.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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 <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/circbuf.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DrvAudio.h"
+#include "AudioMixBuffer.h"
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum);
+#endif
+
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest);
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
+
+#ifndef VBOX_AUDIO_TESTCASE
+
+# if 0 /* unused */
+
+static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey,
+ PDMAUDIOFMT enmDefault, bool *pfDefault)
+{
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ char *pszValue = NULL;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue);
+ if (fmt == PDMAUDIOFMT_INVALID)
+ {
+ *pfDefault = true;
+ return enmDefault;
+ }
+
+ *pfDefault = false;
+ return fmt;
+}
+
+static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey,
+ int iDefault, bool *pfDefault)
+{
+
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return iDefault;
+ }
+
+ uint64_t u64Data = 0;
+ int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return iDefault;
+
+ }
+
+ *pfDefault = false;
+ return u64Data;
+}
+
+static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey,
+ const char *pszDefault, bool *pfDefault)
+{
+ if ( pCfgHandle == NULL
+ || pszKey == NULL)
+ {
+ *pfDefault = true;
+ return pszDefault;
+ }
+
+ char *pszValue = NULL;
+ int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
+ if (RT_FAILURE(rc))
+ {
+ *pfDefault = true;
+ return pszDefault;
+ }
+
+ *pfDefault = false;
+ return pszValue;
+}
+
+# endif /* unused */
+
+#ifdef LOG_ENABLED
+/**
+ * Converts an audio stream status to a string.
+ *
+ * @returns Stringified stream status flags. Must be free'd with RTStrFree().
+ * "NONE" if no flags set.
+ * @param fStatus Stream status flags to convert.
+ */
+static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus)
+{
+#define APPEND_FLAG_TO_STR(_aFlag) \
+ if (fStatus & PDMAUDIOSTREAMSTS_FLAG_##_aFlag) \
+ { \
+ if (pszFlags) \
+ { \
+ rc2 = RTStrAAppend(&pszFlags, " "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+
+ char *pszFlags = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FLAG_TO_STR(INITIALIZED );
+ APPEND_FLAG_TO_STR(ENABLED );
+ APPEND_FLAG_TO_STR(PAUSED );
+ APPEND_FLAG_TO_STR(PENDING_DISABLE);
+ APPEND_FLAG_TO_STR(PENDING_REINIT );
+ } while (0);
+
+ if (!pszFlags)
+ rc2 = RTStrAAppend(&pszFlags, "NONE");
+
+ if ( RT_FAILURE(rc2)
+ && pszFlags)
+ {
+ RTStrFree(pszFlags);
+ pszFlags = NULL;
+ }
+
+#undef APPEND_FLAG_TO_STR
+
+ return pszFlags;
+}
+#endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */
+
+# if 0 /* unused */
+static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts)
+{
+ AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER);
+ /* oaOpts and cOpts are optional. */
+
+ PCFGMNODE pCfgChildHandle = NULL;
+ PCFGMNODE pCfgChildChildHandle = NULL;
+
+ /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
+ * The getter function will return default values.
+ */
+ if (pCfgHandle != NULL)
+ {
+ /* If its audio general setting, need to traverse to one child node.
+ * /Devices/ichac97/0/LUN#0/Config/Audio
+ */
+ if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */
+ {
+ pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
+ if(pCfgChildHandle)
+ pCfgHandle = pCfgChildHandle;
+ }
+ else
+ {
+ /* If its driver specific configuration , then need to traverse two level deep child
+ * child nodes. for eg. in case of DirectSoundConfiguration item
+ * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
+ */
+ pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
+ if (pCfgChildHandle)
+ {
+ pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle);
+ if (pCfgChildChildHandle)
+ pCfgHandle = pCfgChildChildHandle;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < cOpts; i++)
+ {
+ audio_option *pOpt = &paOpts[i];
+ if (!pOpt->valp)
+ {
+ LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name));
+ continue;
+ }
+
+ bool fUseDefault;
+
+ switch (pOpt->tag)
+ {
+ case AUD_OPT_BOOL:
+ case AUD_OPT_INT:
+ {
+ int *intp = (int *)pOpt->valp;
+ *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault);
+
+ break;
+ }
+
+ case AUD_OPT_FMT:
+ {
+ PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp;
+ *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault);
+
+ break;
+ }
+
+ case AUD_OPT_STR:
+ {
+ const char **strp = (const char **)pOpt->valp;
+ *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault);
+
+ break;
+ }
+
+ default:
+ LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag));
+ fUseDefault = false;
+ break;
+ }
+
+ if (!pOpt->overridenp)
+ pOpt->overridenp = &pOpt->overriden;
+
+ *pOpt->overridenp = !fUseDefault;
+ }
+
+ return VINF_SUCCESS;
+}
+# endif /* unused */
+#endif /* !VBOX_AUDIO_TESTCASE */
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
+
+ rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd);
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Controls an audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFlowFunc(("fStatus=%s\n", pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ {
+ /* Is a pending disable outstanding? Then disable first. */
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+
+ if (RT_SUCCESS(rc))
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
+
+ if (RT_SUCCESS(rc))
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ {
+ /*
+ * For playback (output) streams first mark the host stream as pending disable,
+ * so that the rest of the remaining audio data will be played first before
+ * closing the stream.
+ */
+ if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ LogFunc(("[%s] Pending disable/pause\n", pStream->szName));
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE;
+ }
+
+ /* Can we close the host stream as well (not in pending disable mode)? */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE))
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ drvAudioStreamResetInternal(pThis, pStream);
+ }
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_SUCCESS(rc))
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_RESUME);
+ if (RT_SUCCESS(rc))
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED;
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DROP:
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DROP);
+ if (RT_SUCCESS(rc))
+ {
+ drvAudioStreamDropInternal(pThis, pStream);
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Controls a stream's backend.
+ * If the stream has no backend available, VERR_NOT_FOUND is returned.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ return VINF_SUCCESS;
+
+ LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName));
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ {
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_ENABLE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DISABLE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ /* Only pause if the stream is enabled. */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ break;
+
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_PAUSE);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ /* Only need to resume if the stream is enabled. */
+ if (!(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
+ break;
+
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_RESUME);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DRAIN);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DROP:
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStream->pvBackend, PDMAUDIOSTREAMCMD_DROP);
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if ( rc != VERR_NOT_IMPLEMENTED
+ && rc != VERR_NOT_SUPPORTED)
+ LogRel(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pStream->szName, rc));
+
+ LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Initializes an audio stream with a given host and guest stream configuration.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to initialize.
+ * @param pCfgHost Stream configuration to use for the host side (backend).
+ * @param pCfgGuest Stream configuration to use for the guest side.
+ */
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
+
+ /*
+ * Init host stream.
+ */
+
+ /* Set the host's default audio data layout. */
+ pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+#ifdef DEBUG
+ LogFunc(("[%s] Requested host format:\n", pStream->szName));
+ DrvAudioHlpStreamCfgPrint(pCfgHost);
+#endif
+
+ LogRel2(("Audio: Creating stream '%s'\n", pStream->szName));
+ LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ pCfgGuest->Props.uHz, pCfgGuest->Props.cBytes * 8, pCfgGuest->Props.fSigned ? "S" : "U",
+ pCfgGuest->Props.cChannels, pCfgGuest->Props.cChannels == 1 ? "Channel" : "Channels"));
+ LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ pCfgHost->Props.uHz, pCfgHost->Props.cBytes * 8, pCfgHost->Props.fSigned ? "S" : "U",
+ pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 1 ? "Channel" : "Channels"));
+
+ PDMAUDIOSTREAMCFG CfgHostAcq;
+ int rc = drvAudioStreamCreateInternalBackend(pThis, pStream, pCfgHost, &CfgHostAcq);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef DEBUG
+ LogFunc(("[%s] Acquired host format:\n", pStream->szName));
+ DrvAudioHlpStreamCfgPrint(&CfgHostAcq);
+#endif
+
+ LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
+ CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
+ CfgHostAcq.Props.uHz, CfgHostAcq.Props.cBytes * 8, CfgHostAcq.Props.fSigned ? "S" : "U",
+ CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 1 ? "Channel" : "Channels"));
+
+ /* Let the user know if the backend changed some of the tweakable values. */
+ if (CfgHostAcq.Backend.cfBufferSize != pCfgHost->Backend.cfBufferSize)
+ LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfBufferSize, &pCfgHost->Props), pCfgHost->Backend.cfBufferSize,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props), CfgHostAcq.Backend.cfBufferSize));
+
+ if (CfgHostAcq.Backend.cfPeriod != pCfgHost->Backend.cfPeriod)
+ LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPeriod, &pCfgHost->Props), pCfgHost->Backend.cfPeriod,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPeriod));
+
+ if (CfgHostAcq.Backend.cfPreBuf != pCfgHost->Backend.cfPreBuf)
+ LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ DrvAudioHlpFramesToMilli(pCfgHost->Backend.cfPreBuf, &pCfgHost->Props), pCfgHost->Backend.cfPreBuf,
+ DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props), CfgHostAcq.Backend.cfPreBuf));
+ /*
+ * Configure host buffers.
+ */
+
+ /* Check if the backend did return sane values and correct if necessary.
+ * Should never happen with our own backends, but you never know ... */
+ if (CfgHostAcq.Backend.cfBufferSize < CfgHostAcq.Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), "
+ "setting pre-buffering size to %RU32 frames\n",
+ CfgHostAcq.Backend.cfPreBuf, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
+ CfgHostAcq.Backend.cfPreBuf = CfgHostAcq.Backend.cfBufferSize;
+ }
+
+ if (CfgHostAcq.Backend.cfPeriod > CfgHostAcq.Backend.cfBufferSize)
+ {
+ LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n",
+ CfgHostAcq.Backend.cfPeriod, pStream->szName, CfgHostAcq.Backend.cfBufferSize, CfgHostAcq.Backend.cfBufferSize));
+ CfgHostAcq.Backend.cfPeriod = CfgHostAcq.Backend.cfBufferSize;
+ }
+
+ uint64_t msBufferSize = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfBufferSize, &CfgHostAcq.Props);
+
+ LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msBufferSize, CfgHostAcq.Backend.cfBufferSize));
+
+ /* If no own pre-buffer is set, let the backend choose. */
+ uint64_t msPreBuf = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPreBuf, &CfgHostAcq.Props);
+ LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msPreBuf, CfgHostAcq.Backend.cfPreBuf));
+
+ /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
+ const uint32_t msPeriod = DrvAudioHlpFramesToMilli(CfgHostAcq.Backend.cfPeriod, &CfgHostAcq.Props);
+
+ LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n",
+ pStream->szName, msPeriod, CfgHostAcq.Backend.cfPeriod));
+
+ if ( pCfgGuest->Device.uSchedulingHintMs /* Any scheduling hint set? */
+ && pCfgGuest->Device.uSchedulingHintMs > msPeriod) /* This might lead to buffer underflows. */
+ {
+ LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
+ pStream->szName, pCfgGuest->Device.uSchedulingHintMs, msPeriod));
+ }
+
+ /* Destroy any former mixing buffer. */
+ AudioMixBufDestroy(&pStream->Host.MixBuf);
+
+ /* Make sure to (re-)set the host buffer's shift size. */
+ CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cBytes, CfgHostAcq.Props.cChannels);
+
+ rc = AudioMixBufInit(&pStream->Host.MixBuf, pStream->szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cfBufferSize);
+ AssertRC(rc);
+
+ /* Make a copy of the acquired host stream configuration. */
+ rc = DrvAudioHlpStreamCfgCopy(&pStream->Host.Cfg, &CfgHostAcq);
+ AssertRC(rc);
+
+ /*
+ * Init guest stream.
+ */
+
+ if (pCfgGuest->Device.uSchedulingHintMs)
+ LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",
+ pStream->szName, pCfgGuest->Device.uSchedulingHintMs,
+ DrvAudioHlpMilliToBytes(pCfgGuest->Device.uSchedulingHintMs, &pCfgGuest->Props)));
+
+ /* Destroy any former mixing buffer. */
+ AudioMixBufDestroy(&pStream->Guest.MixBuf);
+
+ /* Set the guests's default audio data layout. */
+ pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+
+ /* Make sure to (re-)set the guest buffer's shift size. */
+ pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cBytes, pCfgGuest->Props.cChannels);
+
+ rc = AudioMixBufInit(&pStream->Guest.MixBuf, pStream->szName, &pCfgGuest->Props, CfgHostAcq.Backend.cfBufferSize);
+ AssertRC(rc);
+
+ /* Make a copy of the guest stream configuration. */
+ rc = DrvAudioHlpStreamCfgCopy(&pStream->Guest.Cfg, pCfgGuest);
+ AssertRC(rc);
+
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc));
+
+ if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
+ {
+ /* Host (Parent) -> Guest (Child). */
+ rc = AudioMixBufLinkTo(&pStream->Host.MixBuf, &pStream->Guest.MixBuf);
+ AssertRC(rc);
+ }
+ else
+ {
+ /* Guest (Parent) -> Host (Child). */
+ rc = AudioMixBufLinkTo(&pStream->Guest.MixBuf, &pStream->Host.MixBuf);
+ AssertRC(rc);
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ char szStatName[255];
+
+ if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
+ {
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesCaptured", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured,
+ szStatName, STAMUNIT_COUNT, "Total frames played.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesCaptured", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured,
+ szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesRead", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead,
+ szStatName, STAMUNIT_COUNT, "Total frames read.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesRead", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead,
+ szStatName, STAMUNIT_COUNT, "Total number of reads.");
+ }
+ else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT)
+ {
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesPlayed", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed,
+ szStatName, STAMUNIT_COUNT, "Total frames played.");
+
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesPlayed", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed,
+ szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesWritten", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten,
+ szStatName, STAMUNIT_COUNT, "Total frames written.");
+
+ RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesWritten", pStream->szName);
+ PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten,
+ szStatName, STAMUNIT_COUNT, "Total number of writes.");
+ }
+ else
+ AssertFailed();
+#endif
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Frees an audio stream and its allocated resources.
+ *
+ * @param pStream Audio stream to free.
+ * After this call the pointer will not be valid anymore.
+ */
+static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream)
+{
+ if (!pStream)
+ return;
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ if (pStream->pvBackend)
+ {
+ Assert(pStream->cbBackend);
+ RTMemFree(pStream->pvBackend);
+ pStream->pvBackend = NULL;
+ }
+
+ RTMemFree(pStream);
+ pStream = NULL;
+}
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Schedules a re-initialization of all current audio streams.
+ * The actual re-initialization will happen at some later point in time.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ */
+static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFunc(("\n"));
+
+ /* Mark all host streams to re-initialize. */
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
+
+# ifdef VBOX_WITH_AUDIO_ENUM
+ /* Re-enumerate all host devices as soon as possible. */
+ pThis->fEnumerateDevices = true;
+# endif
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+/**
+ * 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.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to re-initialize.
+ */
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s]\n", pStream->szName));
+
+ /*
+ * Gather current stream status.
+ */
+ bool fIsEnabled = RT_BOOL(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED); /* Stream is enabled? */
+
+ /*
+ * Destroy and re-create stream on backend side.
+ */
+ int rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamCreateInternalBackend(pThis, pStream, &pStream->Host.Cfg, NULL /* pCfgAcq */);
+ /** @todo Validate (re-)acquired configuration with pStream->Host.Cfg? */
+ }
+ }
+
+ /* Drop all old data. */
+ drvAudioStreamDropInternal(pThis, pStream);
+
+ /*
+ * Restore previous stream state.
+ */
+ if (fIsEnabled)
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_ENABLE);
+
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+
+ LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Drops all audio data (and associated state) of a stream.
+ *
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to drop data for.
+ */
+static void drvAudioStreamDropInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ RT_NOREF(pThis);
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ AudioMixBufReset(&pStream->Guest.MixBuf);
+ AudioMixBufReset(&pStream->Host.MixBuf);
+
+ pStream->tsLastIteratedNs = 0;
+ pStream->tsLastPlayedCapturedNs = 0;
+ pStream->tsLastReadWrittenNs = 0;
+
+ pStream->fThresholdReached = false;
+}
+
+/**
+ * Resets a given audio stream.
+ *
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to reset.
+ */
+static void drvAudioStreamResetInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ drvAudioStreamDropInternal(pThis, pStream);
+
+ LogFunc(("[%s]\n", pStream->szName));
+
+ pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Reset statistics.
+ */
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesCaptured);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalFramesRead);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesCaptured);
+ STAM_COUNTER_RESET(&pStream->In.Stats.TotalTimesRead);
+ }
+ else if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesPlayed);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalFramesWritten);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesPlayed);
+ STAM_COUNTER_RESET(&pStream->Out.Stats.TotalTimesWritten);
+ }
+ else
+ AssertFailed();
+#endif
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite}
+ */
+static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
+ pStream->szName, DrvAudioHlpAudDirToStr(pStream->enmDir)));
+
+ AssertMsg(DrvAudioHlpBytesIsAligned(cbBuf, &pStream->Guest.Cfg.Props),
+ ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStream->szName, cbBuf));
+
+ uint32_t cbWrittenTotal = 0;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out);
+#endif
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ AssertPtr(pszStreamSts);
+#endif
+
+ /* Whether to discard the incoming data or not. */
+ bool fToBitBucket = false;
+
+ do
+ {
+ if ( !pThis->Out.fEnabled
+ || !DrvAudioHlpStreamStatusIsReady(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ if (pThis->pHostDrvAudio)
+ {
+ /* If the backend's stream is not writable, all written data goes to /dev/null. */
+ if (!DrvAudioHlpStreamStatusCanWrite(
+ pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
+ {
+ fToBitBucket = true;
+ break;
+ }
+ }
+
+ const uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
+ if (cbFree < cbBuf)
+ LogRel2(("Audio: Lost audio output (%RU64ms, %RU32 free but needs %RU32) due to full host stream buffer '%s'\n",
+ DrvAudioHlpBytesToMilli(cbBuf - cbFree, &pStream->Host.Cfg.Props), cbFree, cbBuf, pStream->szName));
+
+ uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
+ if (!cbToWrite)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* We use the guest side mixing buffer as an intermediate buffer to do some
+ * (first) processing (if needed), so always write the incoming data at offset 0. */
+ uint32_t cfGstWritten = 0;
+ rc = AudioMixBufWriteAt(&pStream->Guest.MixBuf, 0 /* offFrames */, pvBuf, cbToWrite, &cfGstWritten);
+ if ( RT_FAILURE(rc)
+ || !cfGstWritten)
+ {
+ AssertMsgFailed(("[%s] Write failed: cbToWrite=%RU32, cfWritten=%RU32, rc=%Rrc\n",
+ pStream->szName, cbToWrite, cfGstWritten, rc));
+ break;
+ }
+
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Out.Dbg.pFileStreamWrite, pvBuf, cbToWrite, 0 /* fFlags */);
+
+ uint32_t cfGstMixed = 0;
+ if (cfGstWritten)
+ {
+ int rc2 = AudioMixBufMixToParentEx(&pStream->Guest.MixBuf, 0 /* cSrcOffset */, cfGstWritten /* cSrcFrames */,
+ &cfGstMixed /* pcSrcMixed */);
+ if (RT_FAILURE(rc2))
+ {
+ AssertMsgFailed(("[%s] Mixing failed: cbToWrite=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
+ pStream->szName, cbToWrite, cfGstWritten, cfGstMixed, rc2));
+ }
+ else
+ {
+ const uint64_t tsNowNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] Writing %RU32 frames (%RU64ms)\n",
+ pStream->szName, cfGstWritten, DrvAudioHlpFramesToMilli(cfGstWritten, &pStream->Guest.Cfg.Props)));
+
+ Log3Func(("[%s] Last written %RU64ns (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
+ pStream->szName, tsNowNs - pStream->tsLastReadWrittenNs,
+ (tsNowNs - pStream->tsLastReadWrittenNs) / RT_NS_1MS,
+ DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
+ AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
+
+ pStream->tsLastReadWrittenNs = tsNowNs;
+ /* Keep going. */
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ cbWrittenTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfGstWritten);
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfGstWritten);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfGstMixed);
+ Assert(cfGstWritten >= cfGstMixed);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfGstWritten - cfGstMixed);
+ STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal);
+
+ STAM_COUNTER_ADD(&pStream->Out.Stats.TotalFramesWritten, cfGstWritten);
+ STAM_COUNTER_INC(&pStream->Out.Stats.TotalTimesWritten);
+#endif
+ }
+
+ Log3Func(("[%s] Dbg: cbBuf=%RU32, cbToWrite=%RU32, cfHstUsed=%RU32, cfHstfLive=%RU32, cfGstWritten=%RU32, "
+ "cfGstMixed=%RU32, cbWrittenTotal=%RU32, rc=%Rrc\n",
+ pStream->szName, cbBuf, cbToWrite, AudioMixBufUsed(&pStream->Host.MixBuf),
+ AudioMixBufLive(&pStream->Host.MixBuf), cfGstWritten, cfGstMixed, cbWrittenTotal, rc));
+
+ } while (0);
+
+#ifdef LOG_ENABLED
+ RTStrFree(pszStreamSts);
+#endif
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fToBitBucket)
+ {
+ Log3Func(("[%s] Backend stream not ready (yet), discarding written data\n", pStream->szName));
+ cbWrittenTotal = cbBuf; /* Report all data as being written to the caller. */
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, UINT32_MAX);
+ AssertPtrReturn(pStream, UINT32_MAX);
+
+ NOREF(pInterface);
+
+ return ++pStream->cRefs;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, UINT32_MAX);
+ AssertPtrReturn(pStream, UINT32_MAX);
+
+ NOREF(pInterface);
+
+ if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */
+ pStream->cRefs--;
+
+ return pStream->cRefs;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = drvAudioStreamIterateInternal(pThis, pStream);
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+
+ return 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 IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to iterate.
+ */
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!pThis->pHostDrvAudio)
+ return VINF_SUCCESS;
+
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ int rc;
+
+ /* Is the stream scheduled for re-initialization? Do so now. */
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT)
+ {
+#ifdef VBOX_WITH_AUDIO_ENUM
+ if (pThis->fEnumerateDevices)
+ {
+ /* Re-enumerate all host devices. */
+ drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+
+ pThis->fEnumerateDevices = false;
+ }
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+ /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded
+ * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
+
+ rc = drvAudioStreamReInitInternal(pThis, pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ /* Not enabled or paused? Skip iteration. */
+ if ( !(pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
+ || (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
+ {
+ return VINF_SUCCESS;
+ }
+
+ /* Whether to try closing a pending to close stream. */
+ bool fTryClosePending = false;
+
+ do
+ {
+ rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+ /* No audio frames to transfer from guest to host (anymore)?
+ * Then try closing this stream if marked so in the next block. */
+ const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+ fTryClosePending = cfLive == 0;
+ Log3Func(("[%s] fTryClosePending=%RTbool, cfLive=%RU32\n", pStream->szName, fTryClosePending, cfLive));
+ }
+
+ /* Has the host stream marked as pending to disable?
+ * Try disabling the stream then. */
+ if ( pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE
+ && fTryClosePending)
+ {
+ /* Tell the backend to drain the stream, that is, play the remaining (buffered) data
+ * on the backend side. */
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DRAIN);
+ if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining. */
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */
+ {
+ const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStream->pvBackend);
+ Log3Func(("[%s] cxPending=%RU32\n", pStream->szName, cxPending));
+
+ /* Only try close pending if no audio data is pending on the backend-side anymore. */
+ fTryClosePending = cxPending == 0;
+ }
+
+ if (fTryClosePending)
+ {
+ LogFunc(("[%s] Closing pending stream\n", pStream->szName));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ {
+ pStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAG_ENABLED | PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE);
+ drvAudioStreamDropInternal(pThis, pStream);
+ }
+ else
+ LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStream->szName, rc));
+ }
+ }
+ }
+
+ } while (0);
+
+ /* Update timestamps. */
+ pStream->tsLastIteratedNs = RTTimeNanoTS();
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Plays an audio host output stream which has been configured for non-interleaved (layout) data.
+ *
+ * @return IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to play.
+ * @param cfToPlay Number of audio frames to play.
+ * @param pcfPlayed Returns number of audio frames played. Optional.
+ */
+static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfPlayed is optional. */
+
+ if (!cfToPlay)
+ {
+ if (pcfPlayed)
+ *pcfPlayed = 0;
+ return VINF_SUCCESS;
+ }
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_OUT);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfPlayedTotal = 0;
+
+ uint8_t auBuf[256]; /** @todo Get rid of this here. */
+
+ uint32_t cfLeft = cfToPlay;
+ uint32_t cbChunk = sizeof(auBuf);
+
+ while (cfLeft)
+ {
+ uint32_t cfRead = 0;
+ rc = AudioMixBufAcquireReadBlock(&pStream->Host.MixBuf,
+ auBuf, RT_MIN(cbChunk, AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfLeft)),
+ &cfRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Host.MixBuf, cfRead);
+ Assert(cbRead <= cbChunk);
+
+ uint32_t cfPlayed = 0;
+ uint32_t cbPlayed = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
+ auBuf, cbRead, &cbPlayed);
+ if ( RT_SUCCESS(rc)
+ && cbPlayed)
+ {
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Out.Dbg.pFilePlayNonInterleaved, auBuf, cbPlayed, 0 /* fFlags */);
+
+ if (cbRead != cbPlayed)
+ LogRel2(("Audio: Host stream '%s' played wrong amount (%RU32 bytes read but played %RU32)\n",
+ pStream->szName, cbRead, cbPlayed));
+
+ cfPlayed = AUDIOMIXBUF_B2F(&pStream->Host.MixBuf, cbPlayed);
+ cfPlayedTotal += cfPlayed;
+
+ Assert(cfLeft >= cfPlayed);
+ cfLeft -= cfPlayed;
+ }
+
+ AudioMixBufReleaseReadBlock(&pStream->Host.MixBuf, cfPlayed);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcfPlayed)
+ *pcfPlayed = cfPlayedTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * Plays an audio host output stream which has been configured for raw audio (layout) data.
+ *
+ * @return IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Stream to play.
+ * @param cfToPlay Number of audio frames to play.
+ * @param pcfPlayed Returns number of audio frames played. Optional.
+ */
+static int drvAudioStreamPlayRaw(PDRVAUDIO pThis,
+ PPDMAUDIOSTREAM pStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfPlayed is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_OUT);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
+
+ if (!cfToPlay)
+ {
+ if (pcfPlayed)
+ *pcfPlayed = 0;
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfPlayedTotal = 0;
+
+ PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */
+
+ uint32_t cfLeft = cfToPlay;
+ while (cfLeft)
+ {
+ uint32_t cfRead = 0;
+ rc = AudioMixBufPeek(&pStream->Host.MixBuf, cfLeft, aFrameBuf,
+ RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cfRead)
+ {
+ uint32_t cfPlayed;
+
+ /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames
+ * rather on bytes. */
+ Assert(cfRead <= RT_ELEMENTS(aFrameBuf));
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStream->pvBackend,
+ aFrameBuf, cfRead, &cfPlayed);
+ if ( RT_FAILURE(rc)
+ || !cfPlayed)
+ {
+ break;
+ }
+
+ cfPlayedTotal += cfPlayed;
+ Assert(cfPlayedTotal <= cfToPlay);
+
+ Assert(cfLeft >= cfRead);
+ cfLeft -= cfRead;
+ }
+ else
+ {
+ if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */
+ continue;
+
+ break;
+ }
+ }
+ else if (RT_FAILURE(rc))
+ break;
+ }
+
+ Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pStream->szName, cfPlayedTotal, cfToPlay, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcfPlayed)
+ *pcfPlayed = cfPlayedTotal;
+
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcFramesPlayed is optional. */
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cfPlayedTotal = 0;
+
+ PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] Start fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ do
+ {
+ if (!pThis->pHostDrvAudio)
+ {
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+ break;
+ }
+
+ if ( !pThis->Out.fEnabled
+ || !DrvAudioHlpStreamStatusIsReady(stsStream))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ const uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+#ifdef LOG_ENABLED
+ const uint8_t uLivePercent = (100 * cfLive) / AudioMixBufSize(&pStream->Host.MixBuf);
+#endif
+ const uint64_t tsDeltaPlayedCapturedNs = RTTimeNanoTS() - pStream->tsLastPlayedCapturedNs;
+ const uint32_t cfPassedReal = DrvAudioHlpNanoToFrames(tsDeltaPlayedCapturedNs, &pStream->Host.Cfg.Props);
+
+ const uint32_t cfPeriod = pStream->Host.Cfg.Backend.cfPeriod;
+
+ Log3Func(("[%s] Last played %RU64ns (%RU64ms), filled with %RU64ms (%RU8%%) total (fThresholdReached=%RTbool)\n",
+ pStream->szName, tsDeltaPlayedCapturedNs, tsDeltaPlayedCapturedNs / RT_NS_1MS_64,
+ DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props), uLivePercent, pStream->fThresholdReached));
+
+ if ( pStream->fThresholdReached /* Has the treshold been reached (e.g. are we in playing stage) ... */
+ && cfLive == 0) /* ... and we now have no live samples to process? */
+ {
+ LogRel2(("Audio: Buffer underrun for stream '%s' occurred (%RU64ms passed)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props)));
+
+ if (pStream->Host.Cfg.Backend.cfPreBuf) /* Any pre-buffering configured? */
+ {
+ /* Enter pre-buffering stage again. */
+ pStream->fThresholdReached = false;
+ }
+ }
+
+ bool fDoPlay = pStream->fThresholdReached;
+ bool fJustStarted = false;
+ if (!fDoPlay)
+ {
+ /* Did we reach the backend's playback (pre-buffering) threshold? Can be 0 if no threshold set. */
+ if (cfLive >= pStream->Host.Cfg.Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Stream '%s' buffering complete\n", pStream->szName));
+ Log3Func(("[%s] Dbg: Buffering complete\n", pStream->szName));
+ fDoPlay = true;
+ }
+ /* Some audio files are shorter than the pre-buffering level (e.g. the "click" Explorer sounds on some Windows guests),
+ * so make sure that we also play those by checking if the stream already is pending disable mode, even if we didn't
+ * hit the pre-buffering watermark yet.
+ *
+ * To reproduce, use "Windows Navigation Start.wav" on Windows 7 (2824 samples). */
+ else if ( cfLive
+ && pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
+ {
+ LogRel2(("Audio: Stream '%s' buffering complete (short sound)\n", pStream->szName));
+ Log3Func(("[%s] Dbg: Buffering complete (short)\n", pStream->szName));
+ fDoPlay = true;
+ }
+
+ if (fDoPlay)
+ {
+ pStream->fThresholdReached = true;
+ fJustStarted = true;
+ LogRel2(("Audio: Stream '%s' started playing\n", pStream->szName));
+ Log3Func(("[%s] Dbg: started playing\n", pStream->szName));
+ }
+ else /* Not yet, so still buffering audio data. */
+ LogRel2(("Audio: Stream '%s' is buffering (%RU8%% complete)\n",
+ pStream->szName, (100 * cfLive) / pStream->Host.Cfg.Backend.cfPreBuf));
+ }
+
+ if (fDoPlay)
+ {
+ uint32_t cfWritable = PDMAUDIOPCMPROPS_B2F(&pStream->Host.Cfg.Props,
+ pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream->pvBackend));
+
+ uint32_t cfToPlay = 0;
+ if (fJustStarted)
+ cfToPlay = RT_MIN(cfWritable, cfPeriod);
+
+ if (!cfToPlay)
+ {
+ /* Did we reach/pass (in real time) the device scheduling slot?
+ * Play as much as we can write to the backend then. */
+ if (cfPassedReal >= DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props))
+ cfToPlay = cfWritable;
+ }
+
+ if (cfToPlay > cfLive) /* Don't try to play more than available. */
+ cfToPlay = cfLive;
+#ifdef DEBUG
+ Log3Func(("[%s] Playing %RU32 frames (%RU64ms), now filled with %RU64ms -- %RU8%%\n",
+ pStream->szName, cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
+ DrvAudioHlpFramesToMilli(AudioMixBufUsed(&pStream->Host.MixBuf), &pStream->Host.Cfg.Props),
+ AudioMixBufUsed(&pStream->Host.MixBuf) * 100 / AudioMixBufSize(&pStream->Host.MixBuf)));
+#endif
+ if (cfToPlay)
+ {
+ if (pThis->pHostDrvAudio->pfnStreamPlayBegin)
+ pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
+ {
+ rc = drvAudioStreamPlayNonInterleaved(pThis, pStream, cfToPlay, &cfPlayedTotal);
+ }
+ else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
+ {
+ rc = drvAudioStreamPlayRaw(pThis, pStream, cfToPlay, &cfPlayedTotal);
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (pThis->pHostDrvAudio->pfnStreamPlayEnd)
+ pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ pStream->tsLastPlayedCapturedNs = RTTimeNanoTS();
+ }
+
+ Log3Func(("[%s] Dbg: fJustStarted=%RTbool, cfSched=%RU32 (%RU64ms), cfPassedReal=%RU32 (%RU64ms), "
+ "cfLive=%RU32 (%RU64ms), cfPeriod=%RU32 (%RU64ms), cfWritable=%RU32 (%RU64ms), "
+ "-> cfToPlay=%RU32 (%RU64ms), cfPlayed=%RU32 (%RU64ms)\n",
+ pStream->szName, fJustStarted,
+ DrvAudioHlpMilliToFrames(pStream->Guest.Cfg.Device.uSchedulingHintMs, &pStream->Host.Cfg.Props),
+ pStream->Guest.Cfg.Device.uSchedulingHintMs,
+ cfPassedReal, DrvAudioHlpFramesToMilli(cfPassedReal, &pStream->Host.Cfg.Props),
+ cfLive, DrvAudioHlpFramesToMilli(cfLive, &pStream->Host.Cfg.Props),
+ cfPeriod, DrvAudioHlpFramesToMilli(cfPeriod, &pStream->Host.Cfg.Props),
+ cfWritable, DrvAudioHlpFramesToMilli(cfWritable, &pStream->Host.Cfg.Props),
+ cfToPlay, DrvAudioHlpFramesToMilli(cfToPlay, &pStream->Host.Cfg.Props),
+ cfPlayedTotal, DrvAudioHlpFramesToMilli(cfPlayedTotal, &pStream->Host.Cfg.Props)));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ AudioMixBufFinish(&pStream->Host.MixBuf, cfPlayedTotal);
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal);
+ STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out);
+
+ STAM_COUNTER_ADD (&pStream->Out.Stats.TotalFramesPlayed, cfPlayedTotal);
+ STAM_COUNTER_INC (&pStream->Out.Stats.TotalTimesPlayed);
+#endif
+ }
+
+ } while (0);
+
+#ifdef LOG_ENABLED
+ uint32_t cfLive = AudioMixBufLive(&pStream->Host.MixBuf);
+ pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] End fStatus=%s, cfLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n",
+ pStream->szName, pszStreamSts, cfLive, cfPlayedTotal, rc));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcFramesPlayed)
+ *pcFramesPlayed = cfPlayedTotal;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Captures non-interleaved input from a host stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Driver instance.
+ * @param pStream Stream to capture from.
+ * @param pcfCaptured Number of (host) audio frames captured. Optional.
+ */
+static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfCaptured is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_IN);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfCapturedTotal = 0;
+
+ AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
+
+ uint8_t auBuf[_1K]; /** @todo Get rid of this. */
+ uint32_t cbBuf = sizeof(auBuf);
+
+ uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (!cbReadable)
+ Log2Func(("[%s] No readable data available\n", pStream->szName));
+
+ uint32_t cbFree = AudioMixBufFreeBytes(&pStream->Guest.MixBuf); /* Parent */
+ if (!cbFree)
+ Log2Func(("[%s] Buffer full\n", pStream->szName));
+
+ if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
+ cbReadable = cbFree;
+
+ while (cbReadable)
+ {
+ uint32_t cbCaptured;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
+ auBuf, RT_MIN(cbReadable, cbBuf), &cbCaptured);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc2);
+
+ break;
+ }
+
+ Assert(cbCaptured <= cbBuf);
+ if (cbCaptured > cbBuf) /* Paranoia. */
+ cbCaptured = cbBuf;
+
+ if (!cbCaptured) /* Nothing captured? Take a shortcut. */
+ break;
+
+ /* We use the host side mixing buffer as an intermediate buffer to do some
+ * (first) processing (if needed), so always write the incoming data at offset 0. */
+ uint32_t cfHstWritten = 0;
+ rc = AudioMixBufWriteAt(&pStream->Host.MixBuf, 0 /* offFrames */, auBuf, cbCaptured, &cfHstWritten);
+ if ( RT_FAILURE(rc)
+ || !cfHstWritten)
+ {
+ AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
+ pStream->szName, cbCaptured, cfHstWritten, rc));
+ break;
+ }
+
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->In.Dbg.pFileCaptureNonInterleaved, auBuf, cbCaptured, 0 /* fFlags */);
+
+ uint32_t cfHstMixed = 0;
+ if (cfHstWritten)
+ {
+ int rc2 = AudioMixBufMixToParentEx(&pStream->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
+ &cfHstMixed /* pcSrcMixed */);
+ Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
+ pStream->szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
+ AssertRC(rc2);
+ }
+
+ Assert(cbReadable >= cbCaptured);
+ cbReadable -= cbCaptured;
+ cfCapturedTotal += cfHstMixed;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cfCapturedTotal)
+ Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
+ }
+ else
+ LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStream->szName, rc));
+
+ if (pcfCaptured)
+ *pcfCaptured = cfCapturedTotal;
+
+ return rc;
+}
+
+/**
+ * Captures raw input from a host stream.
+ * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
+ * no data layout processing done in between.
+ *
+ * Needed for e.g. the VRDP audio backend (in Main).
+ *
+ * @returns IPRT status code.
+ * @param pThis Driver instance.
+ * @param pStream Stream to capture from.
+ * @param pcfCaptured Number of (host) audio frames captured. Optional.
+ */
+static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, uint32_t *pcfCaptured)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcfCaptured is optional. */
+
+ /* Sanity. */
+ Assert(pStream->enmDir == PDMAUDIODIR_IN);
+ Assert(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cfCapturedTotal = 0;
+
+ AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
+
+ /* Note: Raw means *audio frames*, not bytes! */
+ uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream->pvBackend);
+ if (!cfReadable)
+ Log2Func(("[%s] No readable data available\n", pStream->szName));
+
+ const uint32_t cfFree = AudioMixBufFree(&pStream->Guest.MixBuf); /* Parent */
+ if (!cfFree)
+ Log2Func(("[%s] Buffer full\n", pStream->szName));
+
+ if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
+ cfReadable = cfFree;
+
+ while (cfReadable)
+ {
+ PPDMAUDIOFRAME paFrames;
+ uint32_t cfWritable;
+ rc = AudioMixBufPeekMutable(&pStream->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
+ if ( RT_FAILURE(rc)
+ || !cfWritable)
+ {
+ break;
+ }
+
+ uint32_t cfCaptured;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStream->pvBackend,
+ paFrames, cfWritable, &cfCaptured);
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc2);
+
+ break;
+ }
+
+ Assert(cfCaptured <= cfWritable);
+ if (cfCaptured > cfWritable) /* Paranoia. */
+ cfCaptured = cfWritable;
+
+ Assert(cfReadable >= cfCaptured);
+ cfReadable -= cfCaptured;
+ cfCapturedTotal += cfCaptured;
+ }
+
+ Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCapturedTotal, rc));
+
+ if (pcfCaptured)
+ *pcfCaptured = cfCapturedTotal;
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
+{
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cfCaptured = 0;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ Log3Func(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif /* LOG_ENABLED */
+
+ do
+ {
+ if (!pThis->pHostDrvAudio)
+ {
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+ break;
+ }
+
+ if ( !pThis->In.fEnabled
+ || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Do the actual capturing.
+ */
+
+ if (pThis->pHostDrvAudio->pfnStreamCaptureBegin)
+ pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_LIKELY(pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
+ {
+ rc = drvAudioStreamCaptureNonInterleaved(pThis, pStream, &cfCaptured);
+ }
+ else if (pStream->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
+ {
+ rc = drvAudioStreamCaptureRaw(pThis, pStream, &cfCaptured);
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (pThis->pHostDrvAudio->pfnStreamCaptureEnd)
+ pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ if (RT_SUCCESS(rc))
+ {
+ Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStream->szName, cfCaptured, rc));
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
+
+ STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesCaptured, cfCaptured);
+#endif
+ }
+ else if (RT_UNLIKELY(RT_FAILURE(rc)))
+ {
+ LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+ }
+
+ } while (0);
+
+ if (pcFramesCaptured)
+ *pcFramesCaptured = cfCaptured;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+
+ return rc;
+}
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Duplicates an audio callback.
+ *
+ * @returns Pointer to duplicated callback, or NULL on failure.
+ * @param pCB Callback to duplicate.
+ */
+static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB)
+{
+ AssertPtrReturn(pCB, NULL);
+
+ PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD));
+ if (!pCBCopy)
+ return NULL;
+
+ if (pCB->pvCtx)
+ {
+ pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx);
+ if (!pCBCopy->pvCtx)
+ {
+ RTMemFree(pCBCopy);
+ return NULL;
+ }
+
+ pCBCopy->cbCtx = pCB->cbCtx;
+ }
+
+ return pCBCopy;
+}
+
+/**
+ * Destroys a given callback.
+ *
+ * @param pCB Callback to destroy.
+ */
+static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB)
+{
+ if (!pCB)
+ return;
+
+ RTListNodeRemove(&pCB->Node);
+ if (pCB->pvCtx)
+ {
+ Assert(pCB->cbCtx);
+ RTMemFree(pCB->pvCtx);
+ }
+ RTMemFree(pCB);
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks}
+ */
+static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
+ AssertReturn(cCallbacks, VERR_INVALID_PARAMETER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ for (size_t i = 0; i < cCallbacks; i++)
+ {
+ PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]);
+ if (!pCB)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ switch (pCB->enmSource)
+ {
+ case PDMAUDIOCBSOURCE_DEVICE:
+ {
+ switch (pCB->Device.enmType)
+ {
+ case PDMAUDIODEVICECBTYPE_DATA_INPUT:
+ RTListAppend(&pThis->In.lstCB, &pCB->Node);
+ break;
+
+ case PDMAUDIODEVICECBTYPE_DATA_OUTPUT:
+ RTListAppend(&pThis->Out.lstCB, &pCB->Node);
+ break;
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+ }
+
+ /** @todo Undo allocations on error. */
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Backend callback implementation.
+ *
+ * Important: No calls back to the backend within this function, as the backend
+ * might hold any locks / critical sections while executing this callback.
+ * Will result in some ugly deadlocks (or at least locking order violations) then.
+ *
+ * @copydoc FNPDMHOSTAUDIOCALLBACK
+ */
+static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns,
+ PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser)
+{
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+ RT_NOREF(pvUser, cbUser);
+ /* pvUser and cbUser are optional. */
+
+ /* Get the upper driver (PDMIAUDIOCONNECTOR). */
+ AssertPtr(pDrvIns->pUpBase);
+ PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
+ AssertPtr(pInterface);
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser));
+
+ switch (enmType)
+ {
+ case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED:
+ LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
+ rc = drvAudioScheduleReInitInternal(pThis);
+ break;
+
+ default:
+ AssertMsgFailed(("Not supported\n"));
+ break;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+
+#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.
+ *
+ * @returns IPRT 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.
+ */
+static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum)
+{
+ int rc;
+
+ /*
+ * If the backend supports it, do a device enumeration.
+ */
+ if (pThis->pHostDrvAudio->pfnGetDevices)
+ {
+ PDMAUDIODEVICEENUM 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->szName));
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ if (fLog)
+ {
+ char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags);
+
+ LogRel(("Audio: Device '%s':\n", pDev->szName));
+ LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage)));
+ LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "<NONE>"));
+ LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels));
+ LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels));
+
+ if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ }
+
+ if (pDevEnum)
+ rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum);
+
+ DrvAudioHlpDeviceEnumFree(&DevEnum);
+ }
+ else
+ {
+ if (fLog)
+ LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->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->szName));
+ }
+
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+/**
+ * Initializes the host backend and queries its initial configuration.
+ * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned.
+ *
+ * Note: As this routine is called when attaching to the device LUN in the
+ * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED.
+ * Everything else is considered as fatal and must be handled separately in
+ * the device emulation!
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance to be called.
+ * @param pCfgHandle CFGM configuration handle to use for this driver.
+ */
+static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle)
+{
+ /* pCfgHandle is optional. */
+ NOREF(pCfgHandle);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ AssertPtr(pThis->pHostDrvAudio);
+ int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ /*
+ * Get the backend configuration.
+ */
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
+ pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
+
+ LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+
+ LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once\n",
+ pThis->szName,
+ /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */
+ RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, 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);
+
+ RT_NOREF(rc2);
+ /* Ignore rc. */
+#endif
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /*
+ * If the backend supports it, offer a callback to this connector.
+ */
+ if (pThis->pHostDrvAudio->pfnSetCallback)
+ {
+ rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback);
+ if (RT_FAILURE(rc2))
+ LogRel(("Audio: Error registering callback for host driver '%s', rc=%Rrc\n", pThis->szName, rc2));
+ /* Not fatal. */
+ }
+#endif
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * 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", DrvAudioHlpStreamCmdToStr(enmCmd)));
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ if (pThis->pHostDrvAudio)
+ {
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ drvAudioStreamControlInternal(pThis, pStream, enmCmd);
+ }
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Retrieves an audio configuration from the specified CFGM node.
+ *
+ * @return VBox status code.
+ * @param pThis Driver instance to be called.
+ * @param pCfg Where to store the retrieved audio configuration to.
+ * @param pNode Where to get the audio configuration from.
+ */
+static int drvAudioGetCfgFromCFGM(PDRVAUDIO pThis, PDRVAUDIOCFG pCfg, PCFGMNODE pNode)
+{
+ RT_NOREF(pThis);
+
+ /* Debug stuff. */
+ CFGMR3QueryBoolDef(pNode, "DebugEnabled", &pCfg->Dbg.fEnabled, false);
+ int rc2 = CFGMR3QueryString(pNode, "DebugPathOut", pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut));
+ if ( RT_FAILURE(rc2)
+ || !strlen(pCfg->Dbg.szPathOut))
+ {
+ RTStrPrintf(pCfg->Dbg.szPathOut, sizeof(pCfg->Dbg.szPathOut), VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH);
+ }
+
+ if (pCfg->Dbg.fEnabled)
+ LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pCfg->Dbg.szPathOut));
+
+ /* Buffering stuff. */
+ CFGMR3QueryU32Def(pNode, "PeriodSizeMs", &pCfg->uPeriodSizeMs, 0);
+ CFGMR3QueryU32Def(pNode, "BufferSizeMs", &pCfg->uBufferSizeMs, 0);
+ CFGMR3QueryU32Def(pNode, "PreBufferSizeMs", &pCfg->uPreBufSizeMs, UINT32_MAX /* No custom value set */);
+
+ LogFunc(("pCfg=%p, uPeriodSizeMs=%RU32, uBufferSizeMs=%RU32, uPreBufSizeMs=%RU32\n",
+ pCfg, pCfg->uPeriodSizeMs, pCfg->uBufferSizeMs, pCfg->uPreBufSizeMs));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Intializes an audio driver instance.
+ *
+ * @returns IPRT status code.
+ * @param pDrvIns Pointer to driver instance.
+ * @param pCfgHandle CFGM handle to use for configuration.
+ */
+static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
+{
+ AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Configure driver from CFGM.
+ */
+#ifdef DEBUG
+ CFGMR3Dump(pCfgHandle);
+#endif
+
+ pThis->fTerminate = false;
+ pThis->pCFGMNode = pCfgHandle;
+
+ int rc2 = CFGMR3QueryString(pThis->pCFGMNode, "DriverName", pThis->szName, sizeof(pThis->szName));
+ if (RT_FAILURE(rc2))
+ RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled");
+
+ /* By default we don't enable anything if wrongly / not set-up. */
+ CFGMR3QueryBoolDef(pThis->pCFGMNode, "InputEnabled", &pThis->In.fEnabled, false);
+ CFGMR3QueryBoolDef(pThis->pCFGMNode, "OutputEnabled", &pThis->Out.fEnabled, false);
+
+ LogRel2(("Audio: Verbose logging for driver '%s' enabled\n", pThis->szName));
+
+ LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
+ pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
+
+ /*
+ * Load configurations.
+ */
+ rc = drvAudioGetCfgFromCFGM(pThis, &pThis->In.Cfg, pThis->pCFGMNode);
+ if (RT_SUCCESS(rc))
+ rc = drvAudioGetCfgFromCFGM(pThis, &pThis->Out.Cfg, pThis->pCFGMNode);
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
+ */
+static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
+ pStream->szName, pStream->enmDir));
+
+ uint32_t cbReadTotal = 0;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ do
+ {
+ if ( !pThis->In.fEnabled
+ || !DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+ break;
+ }
+
+ /*
+ * Read from the parent buffer (that is, the guest buffer) which
+ * should have the audio data in the format the guest needs.
+ */
+ uint32_t cfReadTotal = 0;
+
+ const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStream->Guest.MixBuf, cbBuf);
+
+ uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStream->Guest.MixBuf));
+ while (cfToRead)
+ {
+ uint32_t cfRead;
+ rc = AudioMixBufAcquireReadBlock(&pStream->Guest.MixBuf,
+ (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
+ AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfToRead), &cfRead);
+ if (RT_FAILURE(rc))
+ break;
+
+#ifdef VBOX_WITH_STATISTICS
+ const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfRead);
+
+ STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
+
+ STAM_COUNTER_ADD(&pStream->In.Stats.TotalFramesRead, cfRead);
+ STAM_COUNTER_INC(&pStream->In.Stats.TotalTimesRead);
+#endif
+ Assert(cfToRead >= cfRead);
+ cfToRead -= cfRead;
+
+ cfReadTotal += cfRead;
+
+ AudioMixBufReleaseReadBlock(&pStream->Guest.MixBuf, cfRead);
+ }
+
+ if (cfReadTotal)
+ {
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ DrvAudioHlpFileWrite(pStream->In.Dbg.pFileStreamRead,
+ pvBuf, AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
+
+ AudioMixBufFinish(&pStream->Guest.MixBuf, cfReadTotal);
+ }
+
+ /* If we were not able to read as much data as requested, fill up the returned
+ * data with silence.
+ *
+ * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
+ if (cfReadTotal < cfBuf)
+ {
+ Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
+ DrvAudioHlpFramesToMilli(cfBuf - cfReadTotal, &pStream->Guest.Cfg.Props),
+ DrvAudioHlpFramesToMilli(cfBuf, &pStream->Guest.Cfg.Props)));
+
+ DrvAudioHlpClearBuf(&pStream->Guest.Cfg.Props,
+ (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal),
+ AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfBuf - cfReadTotal),
+ cfBuf - cfReadTotal);
+
+ cfReadTotal = cfBuf;
+ }
+
+ cbReadTotal = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadTotal);
+
+ pStream->tsLastReadWrittenNs = RTTimeNanoTS();
+
+ Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
+
+ } while (0);
+
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest,
+ PPDMAUDIOSTREAM *ppStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
+#ifdef DEBUG
+ DrvAudioHlpStreamCfgPrint(pCfgHost);
+ DrvAudioHlpStreamCfgPrint(pCfgGuest);
+#endif
+
+ PPDMAUDIOSTREAM pStream = NULL;
+
+#define RC_BREAK(x) { rc = x; break; }
+
+ do
+ {
+ if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost)
+ || !DrvAudioHlpStreamCfgIsValid(pCfgGuest))
+ {
+ RC_BREAK(VERR_INVALID_PARAMETER);
+ }
+
+ /* Make sure that both configurations actually intend the same thing. */
+ if (pCfgHost->enmDir != pCfgGuest->enmDir)
+ {
+ AssertMsgFailed(("Stream configuration directions do not match\n"));
+ RC_BREAK(VERR_INVALID_PARAMETER);
+ }
+
+ /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */
+ size_t cbHstStrm;
+ if (pCfgHost->enmDir == PDMAUDIODIR_IN)
+ {
+ if (!pThis->In.cStreamsFree)
+ {
+ LogFlowFunc(("Maximum number of host input streams reached\n"));
+ RC_BREAK(VERR_AUDIO_NO_FREE_INPUT_STREAMS);
+ }
+
+ cbHstStrm = pThis->BackendCfg.cbStreamIn;
+ }
+ else /* Out */
+ {
+ if (!pThis->Out.cStreamsFree)
+ {
+ LogFlowFunc(("Maximum number of host output streams reached\n"));
+ RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS);
+ }
+
+ cbHstStrm = pThis->BackendCfg.cbStreamOut;
+ }
+
+ /*
+ * Allocate and initialize common state.
+ */
+
+ pStream = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM));
+ AssertPtrBreakStmt(pStream, rc = VERR_NO_MEMORY);
+
+ /* Retrieve host driver name for easier identification. */
+ AssertPtr(pThis->pHostDrvAudio);
+ PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase);
+ AssertPtr(pDrvAudioInst);
+ AssertPtr(pDrvAudioInst->pReg);
+
+ Assert(pDrvAudioInst->pReg->szName[0] != '\0');
+ RTStrPrintf(pStream->szName, RT_ELEMENTS(pStream->szName), "[%s] %s",
+ pDrvAudioInst->pReg->szName[0] != '\0' ? pDrvAudioInst->pReg->szName : "Untitled",
+ pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");
+
+ pStream->enmDir = pCfgHost->enmDir;
+
+ /*
+ * Allocate and init backend-specific data.
+ */
+
+ if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */
+ {
+ pStream->pvBackend = RTMemAllocZ(cbHstStrm);
+ AssertPtrBreakStmt(pStream->pvBackend, rc = VERR_NO_MEMORY);
+
+ pStream->cbBackend = cbHstStrm;
+ }
+
+ /*
+ * Try to init the rest.
+ */
+
+ rc = drvAudioStreamInitInternal(pThis, pStream, pCfgHost, pCfgGuest);
+ if (RT_FAILURE(rc))
+ break;
+
+ } while (0);
+
+#undef RC_BREAK
+
+ if (RT_FAILURE(rc))
+ {
+ if (pStream)
+ {
+ int rc2 = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+ }
+ }
+ else
+ {
+ /* Append the stream to our stream list. */
+ RTListAppend(&pThis->lstStreams, &pStream->Node);
+
+ /* Set initial reference counts. */
+ pStream->cRefs = 1;
+
+ if (pCfgHost->enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ {
+ char szFile[RTPATH_MAX + 1];
+
+ int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "CaptureNonInterleaved",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->In.Dbg.pFileCaptureNonInterleaved);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileCaptureNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->In.Cfg.Dbg.szPathOut, "StreamRead",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->In.Dbg.pFileStreamRead);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->In.Dbg.pFileStreamRead, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+ }
+ }
+
+ if (pThis->In.cStreamsFree)
+ pThis->In.cStreamsFree--;
+ }
+ else /* Out */
+ {
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ {
+ char szFile[RTPATH_MAX + 1];
+
+ int rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "PlayNonInterleaved",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->Out.Dbg.pFilePlayNonInterleaved);
+ if (RT_SUCCESS(rc2))
+ rc = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFilePlayNonInterleaved, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), pThis->Out.Cfg.Dbg.szPathOut, "StreamWrite",
+ pThis->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE,
+ &pStream->Out.Dbg.pFileStreamWrite);
+ if (RT_SUCCESS(rc2))
+ rc2 = DrvAudioHlpFileOpen(pStream->Out.Dbg.pFileStreamWrite, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->Host.Cfg.Props);
+ }
+ }
+ }
+
+ if (pThis->Out.cStreamsFree)
+ pThis->Out.cStreamsFree--;
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1);
+#endif
+ *ppStream = pStream;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
+ */
+static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ if (fEnable != *pfEnabled)
+ {
+ LogRel(("Audio: %s %s for driver '%s'\n",
+ fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
+
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ {
+ if (pStream->enmDir != enmDir) /* Skip unwanted streams. */
+ continue;
+
+ int rc2 = drvAudioStreamControlInternal(pThis, pStream,
+ fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_FAILURE(rc2))
+ LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n",
+ fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2));
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ /* Keep going. */
+ }
+
+ *pfEnabled = fEnable;
+ }
+
+ int rc3 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc3;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
+ */
+static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, false);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc2))
+ return false;
+
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(false);
+
+ const bool fIsEnabled = *pfEnabled;
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return fIsEnabled;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetConfig)
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN;
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetStatus)
+ backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
+ }
+ else
+ backendSts = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return backendSts;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, 0);
+ AssertPtrReturn(pStream, 0);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
+
+ uint32_t cbReadable = 0;
+
+ if ( pThis->pHostDrvAudio
+ && DrvAudioHlpStreamStatusCanRead(pStream->fStatus))
+ {
+ const uint32_t cfReadable = AudioMixBufLive(&pStream->Guest.MixBuf);
+
+ cbReadable = AUDIOMIXBUF_F2B(&pStream->Guest.MixBuf, cfReadable);
+
+ if (!cbReadable)
+ {
+ /*
+ * If nothing is readable, check if the stream on the backend side is ready to be read from.
+ * If it isn't, return the number of bytes readable since the last read from this stream.
+ *
+ * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
+ * situations, but the device emulation needs input data to keep the DMA transfers moving.
+ * Reading the actual data from a stream then will return silence then.
+ */
+ if (!DrvAudioHlpStreamStatusCanRead(
+ pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStream->pvBackend)))
+ {
+ cbReadable = DrvAudioHlpNanoToBytes(RTTimeNanoTS() - pStream->tsLastReadWrittenNs,
+ &pStream->Host.Cfg.Props);
+ Log3Func(("[%s] Backend stream not ready, returning silence\n", pStream->szName));
+ }
+ }
+
+ /* Make sure to align the readable size to the guest's frame size. */
+ cbReadable = DrvAudioHlpBytesAlign(cbReadable, &pStream->Guest.Cfg.Props);
+ }
+
+ Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
+ pStream->szName, cbReadable, DrvAudioHlpBytesToMilli(cbReadable, &pStream->Host.Cfg.Props)));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ /* Return bytes instead of audio frames. */
+ return cbReadable;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, 0);
+ AssertPtrReturn(pStream, 0);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"));
+
+ uint32_t cbWritable = 0;
+
+ /* Note: We don't propage the backend stream's status to the outside -- it's the job of this
+ * audio connector to make sense of it. */
+ if (DrvAudioHlpStreamStatusCanWrite(pStream->fStatus))
+ {
+ cbWritable = AudioMixBufFreeBytes(&pStream->Host.MixBuf);
+
+ /* Make sure to align the writable size to the host's frame size. */
+ cbWritable = DrvAudioHlpBytesAlign(cbWritable, &pStream->Host.Cfg.Props);
+ }
+
+ Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
+ pStream->szName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pStream->Host.Cfg.Props)));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return cbWritable;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, false);
+
+ if (!pStream)
+ return PDMAUDIOSTREAMSTS_FLAG_NONE;
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ PDMAUDIOSTREAMSTS stsStream = pStream->fStatus;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(stsStream);
+ Log3Func(("[%s] %s\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+#endif
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return stsStream;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
+ */
+static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVol, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
+
+ AudioMixBufSetVolume(&pStream->Guest.MixBuf, pVol);
+ AudioMixBufSetVolume(&pStream->Host.MixBuf, pVol);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc);
+
+ LogRel2(("Audio: Destroying stream '%s'\n", pStream->szName));
+
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
+ if (pStream->cRefs > 1)
+ rc = VERR_WRONG_ORDER;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_FAILURE(rc))
+ LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+ pThis->In.cStreamsFree++;
+ }
+ else /* Out */
+ {
+ pThis->Out.cStreamsFree++;
+ }
+
+ RTListNodeRemove(&pStream->Node);
+
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates an audio stream on the backend side.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Audio stream to create the backend side for.
+ * @param pCfgReq Requested audio stream configuration to use for stream creation.
+ * @param pCfgAcq Acquired audio stream configuration returned by the backend.
+ *
+ * @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,
+ PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ AssertMsg((pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) == 0,
+ ("Stream '%s' already initialized in backend\n", pStream->szName));
+
+ /* Get the right configuration for the stream to be created. */
+ PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;
+
+ /* 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. */
+
+ char szWhat[64]; /* Log where a value came from. */
+
+ /*
+ * Period size
+ */
+ if (pDrvCfg->uPeriodSizeMs)
+ {
+ pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(pDrvCfg->uPeriodSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+
+ if (!pCfgReq->Backend.cfPeriod) /* Set default period size if nothing explicitly is set. */
+ {
+ pCfgReq->Backend.cfPeriod = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+
+ LogRel2(("Audio: Using %s period size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props), pCfgReq->Backend.cfPeriod, pStream->szName));
+
+ /*
+ * Buffer size
+ */
+ if (pDrvCfg->uBufferSizeMs)
+ {
+ pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(pDrvCfg->uBufferSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+
+ if (!pCfgReq->Backend.cfBufferSize) /* Set default buffer size if nothing explicitly is set. */
+ {
+ pCfgReq->Backend.cfBufferSize = DrvAudioHlpMilliToFrames(250 /* ms */, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+
+ LogRel2(("Audio: Using %s buffer size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props), pCfgReq->Backend.cfBufferSize, pStream->szName));
+
+ /*
+ * Pre-buffering size
+ */
+ if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
+ {
+ pCfgReq->Backend.cfPreBuf = DrvAudioHlpMilliToFrames(pDrvCfg->uPreBufSizeMs, &pCfgReq->Props);
+ RTStrPrintf(szWhat, sizeof(szWhat), "global / per-VM");
+ }
+ else /* No, then either use the default or device-specific settings (if any). */
+ {
+ if (pCfgReq->Backend.cfPreBuf == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
+ {
+ /* For pre-buffering to finish the buffer at least must be full one time. */
+ pCfgReq->Backend.cfPreBuf = pCfgReq->Backend.cfBufferSize;
+ RTStrPrintf(szWhat, sizeof(szWhat), "default");
+ }
+ else
+ RTStrPrintf(szWhat, sizeof(szWhat), "device-specific");
+ }
+
+ LogRel2(("Audio: Using %s pre-buffering size (%RU64ms, %RU32 frames) for stream '%s'\n",
+ szWhat,
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props), pCfgReq->Backend.cfPreBuf, pStream->szName));
+
+ /*
+ * Validate input.
+ */
+ if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPeriod)
+ {
+ LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props),
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPeriod, &pCfgReq->Props)));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ( pCfgReq->Backend.cfPreBuf != UINT32_MAX /* Custom pre-buffering set? */
+ && pCfgReq->Backend.cfPreBuf)
+ {
+ if (pCfgReq->Backend.cfBufferSize < pCfgReq->Backend.cfPreBuf)
+ {
+ LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfPreBuf, &pCfgReq->Props),
+ DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props)));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ /* Make the acquired host configuration the requested host configuration initially,
+ * in case the backend does not report back an acquired configuration. */
+ int rc = DrvAudioHlpStreamCfgCopy(pCfgAcq, pCfgReq);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
+ pStream->szName));
+ return rc;
+ }
+
+ rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStream->pvBackend, pCfgReq, pCfgAcq);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_SUPPORTED)
+ LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStream->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", pStream->szName));
+ else
+ LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStream->szName, rc));
+
+ return rc;
+ }
+
+ /* Validate acquired configuration. */
+ if (!DrvAudioHlpStreamCfgIsValid(pCfgAcq))
+ {
+ LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pStream->szName));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Let the user know that the backend changed one of the values requested above. */
+ if (pCfgAcq->Backend.cfBufferSize != pCfgReq->Backend.cfBufferSize)
+ LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfBufferSize, &pCfgAcq->Props), pCfgAcq->Backend.cfBufferSize));
+
+ if (pCfgAcq->Backend.cfPeriod != pCfgReq->Backend.cfPeriod)
+ LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPeriod, &pCfgAcq->Props), pCfgAcq->Backend.cfPeriod));
+
+ /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
+ if ( pCfgReq->Backend.cfPreBuf
+ && pCfgAcq->Backend.cfPreBuf != pCfgReq->Backend.cfPreBuf)
+ {
+ LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
+ pStream->szName, DrvAudioHlpFramesToMilli(pCfgAcq->Backend.cfPreBuf, &pCfgAcq->Props), pCfgAcq->Backend.cfPreBuf));
+ }
+ else if (pCfgReq->Backend.cfPreBuf == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
+ {
+ LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStream->szName));
+ pCfgAcq->Backend.cfPreBuf = 0;
+ }
+
+ /* Sanity for detecting buggy backends. */
+ AssertMsgReturn(pCfgAcq->Backend.cfPeriod < pCfgAcq->Backend.cfBufferSize,
+ ("Acquired period size must be smaller than buffer size\n"),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pCfgAcq->Backend.cfPreBuf <= pCfgAcq->Backend.cfBufferSize,
+ ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"),
+ VERR_INVALID_PARAMETER);
+
+ pStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Calls the backend to give it the chance to destroy its part of the audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Audio stream destruct backend for.
+ */
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFunc(("[%s] fStatus=%s\n", pStream->szName, pszStreamSts));
+#endif
+
+ if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)
+ {
+ AssertPtr(pStream->pvBackend);
+
+ /* Check if the pointer to the host audio driver is still valid.
+ * It can be NULL if we were called in drvAudioDestruct, for example. */
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStream->pvBackend);
+
+ pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+ }
+
+#ifdef LOG_ENABLED
+ RTStrFree(pszStreamSts);
+#endif
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
+ return rc;
+}
+
+/**
+ * Uninitializes an audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStream Pointer to audio stream to uninitialize.
+ */
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
+
+ if (pStream->cRefs > 1)
+ return VERR_WRONG_ORDER;
+
+ int rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
+
+ /* Destroy mixing buffers. */
+ AudioMixBufDestroy(&pStream->Guest.MixBuf);
+ AudioMixBufDestroy(&pStream->Host.MixBuf);
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ if (pStream->fStatus != PDMAUDIOSTREAMSTS_FLAG_NONE)
+ {
+ char *pszStreamSts = dbgAudioStreamStatusToStr(pStream->fStatus);
+ LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", pStream->szName, pszStreamSts));
+ RTStrFree(pszStreamSts);
+ }
+#endif
+ pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_NONE;
+ }
+
+ if (pStream->enmDir == PDMAUDIODIR_IN)
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesCaptured);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesCaptured);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalFramesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->In.Stats.TotalTimesRead);
+#endif
+ if (pThis->In.Cfg.Dbg.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileCaptureNonInterleaved);
+ pStream->In.Dbg.pFileCaptureNonInterleaved = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->In.Dbg.pFileStreamRead);
+ pStream->In.Dbg.pFileStreamRead = NULL;
+ }
+ }
+ else if (pStream->enmDir == PDMAUDIODIR_OUT)
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesPlayed);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesPlayed);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalFramesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pStream->Out.Stats.TotalTimesWritten);
+#endif
+ if (pThis->Out.Cfg.Dbg.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFilePlayNonInterleaved);
+ pStream->Out.Dbg.pFilePlayNonInterleaved = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->Out.Dbg.pFileStreamWrite);
+ pStream->Out.Dbg.pFileStreamWrite = NULL;
+ }
+ }
+ else
+ AssertFailed();
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Does the actual backend driver attaching and queries the backend's interface.
+ *
+ * @return VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param fFlags Attach flags; see PDMDrvHlpAttach().
+ */
+static int drvAudioDoAttachInternal(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(pThis->pDrvIns, fFlags, &pDownBase);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
+ if (!pThis->pHostDrvAudio)
+ {
+ LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
+ rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("Host audio backend missing or invalid"));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If everything went well, initialize the lower driver.
+ */
+ AssertPtr(pThis->pCFGMNode);
+ rc = drvAudioHostInit(pThis, pThis->pCFGMNode);
+ }
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
+ return rc;
+}
+
+
+/********************************************************************/
+
+/**
+ * @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);
+
+ return NULL;
+}
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ return;
+
+ /* Just destroy the host stream on the backend side.
+ * The rest will either be destructed by the device emulation or
+ * in drvAudioDestruct(). */
+ PPDMAUDIOSTREAM pStream;
+ RTListForEach(&pThis->lstStreams, pStream, PDMAUDIOSTREAM, Node)
+ {
+ drvAudioStreamControlInternalBackend(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
+ drvAudioStreamDestroyInternalBackend(pThis, pStream);
+ }
+
+ /*
+ * Last call for the driver below us.
+ * Let it know that we reached end of life.
+ */
+ if (pThis->pHostDrvAudio->pfnShutdown)
+ pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);
+
+ pThis->pHostDrvAudio = NULL;
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Constructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
+
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ RTListInit(&pThis->lstStreams);
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ RTListInit(&pThis->In.lstCB);
+ RTListInit(&pThis->Out.lstCB);
+#endif
+
+ /*
+ * Init the static parts.
+ */
+ 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.pfnStreamCreate = drvAudioStreamCreate;
+ pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
+ pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
+ pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
+ pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
+ pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
+ pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite;
+ pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
+ pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
+ pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
+ pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
+ pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
+ pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
+ pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks;
+#endif
+
+ int rc = drvAudioInit(pDrvIns, pCfg);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
+ STAMUNIT_COUNT, "Total active audio streams.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
+ STAMUNIT_COUNT, "Total created audio streams.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
+ STAMUNIT_COUNT, "Total frames read by device emulation.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten",
+ STAMUNIT_COUNT, "Total frames written by device emulation ");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn",
+ STAMUNIT_COUNT, "Total input frames mixed.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut",
+ STAMUNIT_COUNT, "Total output frames mixed.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn",
+ STAMUNIT_COUNT, "Total input frames lost.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut",
+ STAMUNIT_COUNT, "Total output frames lost.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut",
+ STAMUNIT_COUNT, "Total frames played by backend.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
+ STAMUNIT_COUNT, "Total frames captured by backend.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
+ STAMUNIT_BYTES, "Total bytes read.");
+ PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten",
+ STAMUNIT_BYTES, "Total bytes written.");
+
+ PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn",
+ STAMUNIT_NS_PER_CALL, "Profiling of input data processing.");
+ PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut",
+ STAMUNIT_NS_PER_CALL, "Profiling of output data processing.");
+#endif
+ }
+
+ rc = drvAudioDoAttachInternal(pThis, fFlags);
+ if (RT_FAILURE(rc))
+ {
+ /* No lower attached driver (yet)? Not a failure, might get attached later at runtime, just skip. */
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * 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();
+
+ int rc2;
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ /*
+ * Note: No calls here to the driver below us anymore,
+ * as PDM already has destroyed it.
+ * If you need to call something from the host driver,
+ * do this in drvAudioPowerOff() instead.
+ */
+
+ /* Thus, NULL the pointer to the host audio driver first,
+ * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
+ pThis->pHostDrvAudio = NULL;
+
+ PPDMAUDIOSTREAM pStream, pStreamNext;
+ RTListForEachSafe(&pThis->lstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node)
+ {
+ rc2 = drvAudioStreamUninitInternal(pThis, pStream);
+ if (RT_SUCCESS(rc2))
+ {
+ RTListNodeRemove(&pStream->Node);
+
+ drvAudioStreamFree(pStream);
+ pStream = NULL;
+ }
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThis->lstStreams));
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /*
+ * Destroy callbacks, if any.
+ */
+ PPDMAUDIOCBRECORD pCB, pCBNext;
+ RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
+ drvAudioCallbackDestroy(pCB);
+
+ RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
+ drvAudioCallbackDestroy(pCB);
+#endif
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ {
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn);
+ PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut);
+#endif
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Suspend notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
+}
+
+/**
+ * Resume notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
+}
+
+/**
+ * Attach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Attach flags.
+ */
+static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ LogFunc(("%s\n", pThis->szName));
+
+ int rc = drvAudioDoAttachInternal(pThis, fFlags);
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ return rc;
+}
+
+/**
+ * Detach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Detach flags.
+ */
+static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ pThis->pHostDrvAudio = NULL;
+
+ LogFunc(("%s\n", pThis->szName));
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * 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/DrvAudio.h b/src/VBox/Devices/Audio/DrvAudio.h
new file mode 100644
index 00000000..cdabbdcb
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudio.h
@@ -0,0 +1,286 @@
+/* $Id: DrvAudio.h $ */
+/** @file
+ * Intermediate audio driver header.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_DrvAudio_h
+#define VBOX_INCLUDED_SRC_Audio_DrvAudio_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/pdmdev.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+typedef enum
+{
+ AUD_OPT_INT,
+ AUD_OPT_FMT,
+ AUD_OPT_STR,
+ AUD_OPT_BOOL
+} audio_option_tag_e;
+
+typedef struct audio_option
+{
+ const char *name;
+ audio_option_tag_e tag;
+ void *valp;
+ const char *descr;
+ int *overridenp;
+ int overriden;
+} audio_option;
+
+#ifdef VBOX_WITH_STATISTICS
+/**
+ * Structure for keeping stream statistics for the
+ * statistic manager (STAM).
+ */
+typedef struct DRVAUDIOSTATS
+{
+ STAMCOUNTER TotalStreamsActive;
+ STAMCOUNTER TotalStreamsCreated;
+ STAMCOUNTER TotalFramesRead;
+ STAMCOUNTER TotalFramesWritten;
+ STAMCOUNTER TotalFramesMixedIn;
+ STAMCOUNTER TotalFramesMixedOut;
+ STAMCOUNTER TotalFramesLostIn;
+ STAMCOUNTER TotalFramesLostOut;
+ STAMCOUNTER TotalFramesOut;
+ STAMCOUNTER TotalFramesIn;
+ STAMCOUNTER TotalBytesRead;
+ STAMCOUNTER TotalBytesWritten;
+ /** How much delay (in ms) for input processing. */
+ STAMPROFILEADV DelayIn;
+ /** How much delay (in ms) for output processing. */
+ STAMPROFILEADV DelayOut;
+} DRVAUDIOSTATS, *PDRVAUDIOSTATS;
+#endif
+
+/**
+ * Audio driver configuration data, tweakable via CFGM.
+ */
+typedef struct DRVAUDIOCFG
+{
+ /** 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.
+ * Defaults to VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH if not set. */
+ char szPathOut[RTPATH_MAX + 1];
+ } Dbg;
+} DRVAUDIOCFG, *PDRVAUDIOCFG;
+
+/**
+ * Audio driver instance data.
+ *
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVAUDIO
+{
+ /** Friendly name of the driver. */
+ char szName[64];
+ /** Critical section for serializing access. */
+ RTCRITSECT CritSect;
+ /** Shutdown indicator. */
+ bool fTerminate;
+ /** Our audio connector interface. */
+ PDMIAUDIOCONNECTOR IAudioConnector;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to audio driver below us. */
+ PPDMIHOSTAUDIO pHostDrvAudio;
+ /** Pointer to CFGM configuration node of this driver. */
+ PCFGMNODE pCFGMNode;
+ /** List of audio streams. */
+ RTLISTANCHOR lstStreams;
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /** Flag indicating to perform an (re-)enumeration of the host audio devices. */
+ bool fEnumerateDevices;
+#endif
+ /** Audio configuration settings retrieved from the backend. */
+ PDMAUDIOBACKENDCFG BackendCfg;
+#ifdef VBOX_WITH_STATISTICS
+ /** Statistics for the statistics manager (STAM). */
+ DRVAUDIOSTATS Stats;
+#endif
+ 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;
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ RTLISTANCHOR lstCB;
+#endif
+ /** The driver's input confguration (tweakable via CFGM). */
+ DRVAUDIOCFG Cfg;
+ } 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;
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ RTLISTANCHOR lstCB;
+#endif
+ /** The driver's output confguration (tweakable via CFGM). */
+ DRVAUDIOCFG Cfg;
+ } Out;
+} DRVAUDIO, *PDRVAUDIO;
+
+/** Makes a PDRVAUDIO out of a PPDMIAUDIOCONNECTOR. */
+#define PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface) \
+ ( (PDRVAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVAUDIO, IAudioConnector)) )
+
+/** @name Audio format helper methods.
+ * @{ */
+const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir);
+const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt);
+bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt);
+uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt);
+/** @} */
+
+/** @name Audio calculation helper methods.
+ * @{ */
+void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames);
+uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels);
+uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps);
+bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps);
+uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps);
+uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps);
+uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps);
+uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps);
+uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps);
+/** @} */
+
+/** @name Audio PCM properties helper methods.
+ * @{ */
+bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps1, const PPDMAUDIOPCMPROPS pPCMProps2);
+bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pPCMProps, const PPDMAUDIOSTREAMCFG pCfg);
+bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps);
+uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps);
+void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps);
+int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pPCMProps, PPDMAUDIOSTREAMCFG pCfg);
+/** @} */
+
+/** @name Audio configuration helper methods.
+ * @{ */
+void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg);
+bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg);
+int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg);
+PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg);
+void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg);
+void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg);
+/** @} */
+
+/** @name Audio stream command helper methods.
+ * @{ */
+const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd);
+/** @} */
+
+/** @name Audio stream status helper methods.
+ * @{ */
+bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS enmStatus);
+bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS enmStatus);
+bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS enmStatus);
+/** @} */
+
+/** @name Audio file (name) helper methods.
+ * @{ */
+int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath);
+int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName, uint32_t uInstance, PDMAUDIOFILETYPE enmType, PDMAUDIOFILENAMEFLAGS fFlags);
+/** @} */
+
+/** @name Audio device methods.
+ * @{ */
+PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData);
+void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev);
+PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData);
+/** @} */
+
+/** @name Audio device enumartion methods.
+ * @{ */
+int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm);
+void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm);
+int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev);
+int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage);
+int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm);
+PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm);
+int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm);
+int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm, PDMAUDIODIR enmUsage, bool fCopyUserData);
+PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmDir);
+uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage);
+void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm);
+/** @} */
+
+/** @name Audio string-ify methods.
+ * @{ */
+const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl);
+const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDEST enmPlaybackDst);
+const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSOURCE enmRecSource);
+PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt);
+char *DrvAudioHlpAudDevFlagsToStrA(PDMAUDIODEVFLAG fFlags);
+/** @} */
+
+/** @name Audio file methods.
+ * @{ */
+int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, PDMAUDIOFILEFLAGS fFlags, PPDMAUDIOFILE *ppFile);
+void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile);
+int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps);
+int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile);
+int DrvAudioHlpFileDelete(PPDMAUDIOFILE pFile);
+size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile);
+bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile);
+int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags);
+/** @} */
+
+#define AUDIO_MAKE_FOURCC(c0, c1, c2, c3) RT_H2LE_U32_C(RT_MAKE_U32_FROM_U8(c0, c1, c2, c3))
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_DrvAudio_h */
+
diff --git a/src/VBox/Devices/Audio/DrvAudioCommon.cpp b/src/VBox/Devices/Audio/DrvAudioCommon.cpp
new file mode 100644
index 00000000..7f2a1c78
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudioCommon.cpp
@@ -0,0 +1,1927 @@
+/* $Id: DrvAudioCommon.cpp $ */
+/** @file
+ * Intermedia audio driver, common routines.
+ *
+ * These are also used in the drivers which are bound to Main, e.g. the VRDE
+ * or the video audio recording drivers.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/uuid.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/mm.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DrvAudio.h"
+#include "AudioMixBuffer.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Structure for building up a .WAV file header.
+ */
+typedef struct AUDIOWAVFILEHDR
+{
+ uint32_t u32RIFF;
+ uint32_t u32Size;
+ uint32_t u32WAVE;
+
+ uint32_t u32Fmt;
+ uint32_t u32Size1;
+ uint16_t u16AudioFormat;
+ uint16_t u16NumChannels;
+ uint32_t u32SampleRate;
+ uint32_t u32ByteRate;
+ uint16_t u16BlockAlign;
+ uint16_t u16BitsPerSample;
+
+ uint32_t u32ID2;
+ uint32_t u32Size2;
+} AUDIOWAVFILEHDR, *PAUDIOWAVFILEHDR;
+AssertCompileSize(AUDIOWAVFILEHDR, 11*4);
+
+/**
+ * Structure for keeeping the internal .WAV file data
+ */
+typedef struct AUDIOWAVFILEDATA
+{
+ /** The file header/footer. */
+ AUDIOWAVFILEHDR Hdr;
+} AUDIOWAVFILEDATA, *PAUDIOWAVFILEDATA;
+
+
+
+
+/**
+ * Retrieves the matching PDMAUDIOFMT for given bits + signing flag.
+ *
+ * @return IPRT status code.
+ * @return PDMAUDIOFMT Resulting audio format or PDMAUDIOFMT_INVALID if invalid.
+ * @param cBits Bits to retrieve audio format for.
+ * @param fSigned Signed flag for bits to retrieve audio format for.
+ */
+PDMAUDIOFMT DrvAudioAudFmtBitsToAudFmt(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: break;
+ }
+ }
+ else
+ {
+ switch (cBits)
+ {
+ case 8: return PDMAUDIOFMT_U8;
+ case 16: return PDMAUDIOFMT_U16;
+ case 32: return PDMAUDIOFMT_U32;
+ default: break;
+ }
+ }
+
+ AssertMsgFailed(("Bogus audio bits %RU8\n", cBits));
+ return PDMAUDIOFMT_INVALID;
+}
+
+/**
+ * Clears a sample buffer by the given amount of audio frames with silence (according to the format
+ * given by the PCM properties).
+ *
+ * @param pPCMProps PCM properties to use for the buffer to clear.
+ * @param pvBuf Buffer to clear.
+ * @param cbBuf Size (in bytes) of the buffer.
+ * @param cFrames Number of audio frames to clear in the buffer.
+ */
+void DrvAudioHlpClearBuf(const PPDMAUDIOPCMPROPS pPCMProps, void *pvBuf, size_t cbBuf, uint32_t cFrames)
+{
+ AssertPtrReturnVoid(pPCMProps);
+ AssertPtrReturnVoid(pvBuf);
+
+ if (!cbBuf || !cFrames)
+ return;
+
+ Assert(pPCMProps->cBytes);
+ size_t cbToClear = DrvAudioHlpFramesToBytes(cFrames, pPCMProps);
+ Assert(cbBuf >= cbToClear);
+
+ if (cbBuf < cbToClear)
+ cbToClear = cbBuf;
+
+ Log2Func(("pPCMProps=%p, pvBuf=%p, cFrames=%RU32, fSigned=%RTbool, cBytes=%RU8\n",
+ pPCMProps, pvBuf, cFrames, pPCMProps->fSigned, pPCMProps->cBytes));
+
+ Assert(pPCMProps->fSwapEndian == false); /** @todo Swapping Endianness is not supported yet. */
+
+ if (pPCMProps->fSigned)
+ {
+ RT_BZERO(pvBuf, cbToClear);
+ }
+ else /* Unsigned formats. */
+ {
+ switch (pPCMProps->cBytes)
+ {
+ case 1: /* 8 bit */
+ {
+ memset(pvBuf, 0x80, cbToClear);
+ break;
+ }
+
+ case 2: /* 16 bit */
+ {
+ uint16_t *p = (uint16_t *)pvBuf;
+ uint16_t s = 0x0080;
+
+ for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++)
+ p[i] = s;
+
+ break;
+ }
+
+ /** @todo Add 24 bit? */
+
+ case 4: /* 32 bit */
+ {
+ uint32_t *p = (uint32_t *)pvBuf;
+ uint32_t s = 0x00000080;
+
+ for (uint32_t i = 0; i < DrvAudioHlpBytesToFrames((uint32_t)cbToClear, pPCMProps); i++)
+ p[i] = s;
+
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailed(("Invalid bytes per sample: %RU8\n", pPCMProps->cBytes));
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * 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);
+}
+
+/**
+ * Allocates an audio device.
+ *
+ * @returns Newly allocated audio device, or NULL if failed.
+ * @param cbData How much additional data (in bytes) should be allocated to provide
+ * a (backend) specific area to store additional data.
+ * Optional, can be 0.
+ */
+PPDMAUDIODEVICE DrvAudioHlpDeviceAlloc(size_t cbData)
+{
+ PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)RTMemAllocZ(sizeof(PDMAUDIODEVICE));
+ if (!pDev)
+ return NULL;
+
+ if (cbData)
+ {
+ pDev->pvData = RTMemAllocZ(cbData);
+ if (!pDev->pvData)
+ {
+ RTMemFree(pDev);
+ return NULL;
+ }
+ }
+
+ pDev->cbData = cbData;
+
+ pDev->cMaxInputChannels = 0;
+ pDev->cMaxOutputChannels = 0;
+
+ return pDev;
+}
+
+/**
+ * Frees an audio device.
+ *
+ * @param pDev Device to free.
+ */
+void DrvAudioHlpDeviceFree(PPDMAUDIODEVICE pDev)
+{
+ if (!pDev)
+ return;
+
+ Assert(pDev->cRefCount == 0);
+
+ if (pDev->pvData)
+ {
+ Assert(pDev->cbData);
+
+ RTMemFree(pDev->pvData);
+ pDev->pvData = NULL;
+ }
+
+ RTMemFree(pDev);
+ pDev = NULL;
+}
+
+/**
+ * Duplicates an audio device entry.
+ *
+ * @returns Duplicated audio device entry on success, or NULL on failure.
+ * @param pDev Audio device entry to duplicate.
+ * @param fCopyUserData Whether to also copy the user data portion or not.
+ */
+PPDMAUDIODEVICE DrvAudioHlpDeviceDup(const PPDMAUDIODEVICE pDev, bool fCopyUserData)
+{
+ AssertPtrReturn(pDev, NULL);
+
+ PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceAlloc(fCopyUserData ? pDev->cbData : 0);
+ if (pDevDup)
+ {
+ memcpy(pDevDup, pDev, sizeof(PDMAUDIODEVICE));
+
+ if ( fCopyUserData
+ && pDevDup->cbData)
+ {
+ memcpy(pDevDup->pvData, pDev->pvData, pDevDup->cbData);
+ }
+ else
+ {
+ pDevDup->cbData = 0;
+ pDevDup->pvData = NULL;
+ }
+ }
+
+ return pDevDup;
+}
+
+/**
+ * Initializes an audio device enumeration structure.
+ *
+ * @returns IPRT status code.
+ * @param pDevEnm Device enumeration to initialize.
+ */
+int DrvAudioHlpDeviceEnumInit(PPDMAUDIODEVICEENUM pDevEnm)
+{
+ AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
+
+ RTListInit(&pDevEnm->lstDevices);
+ pDevEnm->cDevices = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Frees audio device enumeration data.
+ *
+ * @param pDevEnm Device enumeration to destroy.
+ */
+void DrvAudioHlpDeviceEnumFree(PPDMAUDIODEVICEENUM pDevEnm)
+{
+ if (!pDevEnm)
+ return;
+
+ PPDMAUDIODEVICE pDev, pDevNext;
+ RTListForEachSafe(&pDevEnm->lstDevices, pDev, pDevNext, PDMAUDIODEVICE, Node)
+ {
+ RTListNodeRemove(&pDev->Node);
+
+ DrvAudioHlpDeviceFree(pDev);
+
+ pDevEnm->cDevices--;
+ }
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pDevEnm->lstDevices));
+ Assert(pDevEnm->cDevices == 0);
+}
+
+/**
+ * Adds an audio device to a device enumeration.
+ *
+ * @return IPRT status code.
+ * @param pDevEnm Device enumeration to add device to.
+ * @param pDev Device to add. The pointer will be owned by the device enumeration then.
+ */
+int DrvAudioHlpDeviceEnumAdd(PPDMAUDIODEVICEENUM pDevEnm, PPDMAUDIODEVICE pDev)
+{
+ AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDev, VERR_INVALID_POINTER);
+
+ RTListAppend(&pDevEnm->lstDevices, &pDev->Node);
+ pDevEnm->cDevices++;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Duplicates a device enumeration.
+ *
+ * @returns Duplicated device enumeration, or NULL on failure.
+ * Must be free'd with DrvAudioHlpDeviceEnumFree().
+ * @param pDevEnm Device enumeration to duplicate.
+ */
+PPDMAUDIODEVICEENUM DrvAudioHlpDeviceEnumDup(const PPDMAUDIODEVICEENUM pDevEnm)
+{
+ AssertPtrReturn(pDevEnm, NULL);
+
+ PPDMAUDIODEVICEENUM pDevEnmDup = (PPDMAUDIODEVICEENUM)RTMemAlloc(sizeof(PDMAUDIODEVICEENUM));
+ if (!pDevEnmDup)
+ return NULL;
+
+ int rc2 = DrvAudioHlpDeviceEnumInit(pDevEnmDup);
+ AssertRC(rc2);
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ PPDMAUDIODEVICE pDevDup = DrvAudioHlpDeviceDup(pDev, true /* fCopyUserData */);
+ if (!pDevDup)
+ {
+ rc2 = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc2 = DrvAudioHlpDeviceEnumAdd(pDevEnmDup, pDevDup);
+ if (RT_FAILURE(rc2))
+ {
+ DrvAudioHlpDeviceFree(pDevDup);
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc2))
+ {
+ DrvAudioHlpDeviceEnumFree(pDevEnmDup);
+ pDevEnmDup = NULL;
+ }
+
+ return pDevEnmDup;
+}
+
+/**
+ * Copies device enumeration entries from the source to the destination enumeration.
+ *
+ * @returns IPRT status code.
+ * @param pDstDevEnm Destination enumeration to store enumeration entries into.
+ * @param pSrcDevEnm Source enumeration to use.
+ * @param enmUsage Which entries to copy. Specify PDMAUDIODIR_ANY to copy all entries.
+ * @param fCopyUserData Whether to also copy the user data portion or not.
+ */
+int DrvAudioHlpDeviceEnumCopyEx(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm,
+ PDMAUDIODIR enmUsage, bool fCopyUserData)
+{
+ AssertPtrReturn(pDstDevEnm, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrcDevEnm, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ PPDMAUDIODEVICE pSrcDev;
+ RTListForEach(&pSrcDevEnm->lstDevices, pSrcDev, PDMAUDIODEVICE, Node)
+ {
+ if ( enmUsage != PDMAUDIODIR_ANY
+ && enmUsage != pSrcDev->enmUsage)
+ {
+ continue;
+ }
+
+ PPDMAUDIODEVICE pDstDev = DrvAudioHlpDeviceDup(pSrcDev, fCopyUserData);
+ if (!pDstDev)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ rc = DrvAudioHlpDeviceEnumAdd(pDstDevEnm, pDstDev);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Copies all device enumeration entries from the source to the destination enumeration.
+ *
+ * Note: Does *not* copy the user-specific data assigned to a device enumeration entry.
+ * To do so, use DrvAudioHlpDeviceEnumCopyEx().
+ *
+ * @returns IPRT status code.
+ * @param pDstDevEnm Destination enumeration to store enumeration entries into.
+ * @param pSrcDevEnm Source enumeration to use.
+ */
+int DrvAudioHlpDeviceEnumCopy(PPDMAUDIODEVICEENUM pDstDevEnm, const PPDMAUDIODEVICEENUM pSrcDevEnm)
+{
+ return DrvAudioHlpDeviceEnumCopyEx(pDstDevEnm, pSrcDevEnm, PDMAUDIODIR_ANY, false /* fCopyUserData */);
+}
+
+/**
+ * Returns the default device of a given device enumeration.
+ * This assumes that only one default device per usage is set.
+ *
+ * @returns Default device if found, or NULL if none found.
+ * @param pDevEnm Device enumeration to get default device for.
+ * @param enmUsage Usage to get default device for.
+ */
+PPDMAUDIODEVICE DrvAudioHlpDeviceEnumGetDefaultDevice(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage)
+{
+ AssertPtrReturn(pDevEnm, NULL);
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ if (enmUsage != PDMAUDIODIR_ANY)
+ {
+ if (enmUsage != pDev->enmUsage) /* Wrong usage? Skip. */
+ continue;
+ }
+
+ if (pDev->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
+ return pDev;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns the number of enumerated devices of a given device enumeration.
+ *
+ * @returns Number of devices if found, or 0 if none found.
+ * @param pDevEnm Device enumeration to get default device for.
+ * @param enmUsage Usage to get default device for.
+ */
+uint16_t DrvAudioHlpDeviceEnumGetDeviceCount(const PPDMAUDIODEVICEENUM pDevEnm, PDMAUDIODIR enmUsage)
+{
+ AssertPtrReturn(pDevEnm, 0);
+
+ if (enmUsage == PDMAUDIODIR_ANY)
+ return pDevEnm->cDevices;
+
+ uint32_t cDevs = 0;
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ if (enmUsage == pDev->enmUsage)
+ cDevs++;
+ }
+
+ return cDevs;
+}
+
+/**
+ * Logs an audio device enumeration.
+ *
+ * @param pszDesc Logging description.
+ * @param pDevEnm Device enumeration to log.
+ */
+void DrvAudioHlpDeviceEnumPrint(const char *pszDesc, const PPDMAUDIODEVICEENUM pDevEnm)
+{
+ AssertPtrReturnVoid(pszDesc);
+ AssertPtrReturnVoid(pDevEnm);
+
+ LogFunc(("%s: %RU16 devices\n", pszDesc, pDevEnm->cDevices));
+
+ PPDMAUDIODEVICE pDev;
+ RTListForEach(&pDevEnm->lstDevices, pDev, PDMAUDIODEVICE, Node)
+ {
+ char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags);
+
+ LogFunc(("Device '%s':\n", pDev->szName));
+ LogFunc(("\tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage)));
+ LogFunc(("\tFlags = %s\n", pszFlags ? pszFlags : "<NONE>"));
+ LogFunc(("\tInput channels = %RU8\n", pDev->cMaxInputChannels));
+ LogFunc(("\tOutput channels = %RU8\n", pDev->cMaxOutputChannels));
+ LogFunc(("\tData = %p (%zu bytes)\n", pDev->pvData, pDev->cbData));
+
+ if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+}
+
+/**
+ * Converts an audio direction to a string.
+ *
+ * @returns Stringified audio direction, or "Unknown", if not found.
+ * @param enmDir Audio direction to convert.
+ */
+const char *DrvAudioHlpAudDirToStr(PDMAUDIODIR enmDir)
+{
+ switch (enmDir)
+ {
+ case PDMAUDIODIR_UNKNOWN: return "Unknown";
+ case PDMAUDIODIR_IN: return "Input";
+ case PDMAUDIODIR_OUT: return "Output";
+ case PDMAUDIODIR_ANY: return "Duplex";
+ default: break;
+ }
+
+ AssertMsgFailed(("Invalid audio direction %ld\n", enmDir));
+ return "Unknown";
+}
+
+/**
+ * Converts an audio mixer control to a string.
+ *
+ * @returns Stringified audio mixer control or "Unknown", if not found.
+ * @param enmMixerCtl Audio mixer control to convert.
+ */
+const char *DrvAudioHlpAudMixerCtlToStr(PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ switch (enmMixerCtl)
+ {
+ case PDMAUDIOMIXERCTL_VOLUME_MASTER: return "Master Volume";
+ case PDMAUDIOMIXERCTL_FRONT: return "Front";
+ case PDMAUDIOMIXERCTL_CENTER_LFE: return "Center / LFE";
+ case PDMAUDIOMIXERCTL_REAR: return "Rear";
+ case PDMAUDIOMIXERCTL_LINE_IN: return "Line-In";
+ case PDMAUDIOMIXERCTL_MIC_IN: return "Microphone-In";
+ default: break;
+ }
+
+ AssertMsgFailed(("Invalid mixer control %ld\n", enmMixerCtl));
+ return "Unknown";
+}
+
+/**
+ * Converts an audio device flags to a string.
+ *
+ * @returns Stringified audio flags. Must be free'd with RTStrFree().
+ * NULL if no flags set.
+ * @param fFlags Audio flags to convert.
+ */
+char *DrvAudioHlpAudDevFlagsToStrA(PDMAUDIODEVFLAG fFlags)
+{
+#define APPEND_FLAG_TO_STR(_aFlag) \
+ if (fFlags & PDMAUDIODEV_FLAGS_##_aFlag) \
+ { \
+ if (pszFlags) \
+ { \
+ rc2 = RTStrAAppend(&pszFlags, " "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+
+ char *pszFlags = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FLAG_TO_STR(DEFAULT);
+ APPEND_FLAG_TO_STR(HOTPLUG);
+ APPEND_FLAG_TO_STR(BUGGY);
+ APPEND_FLAG_TO_STR(IGNORE);
+ APPEND_FLAG_TO_STR(LOCKED);
+ APPEND_FLAG_TO_STR(DEAD);
+
+ } while (0);
+
+ if (!pszFlags)
+ rc2 = RTStrAAppend(&pszFlags, "NONE");
+
+ if ( RT_FAILURE(rc2)
+ && pszFlags)
+ {
+ RTStrFree(pszFlags);
+ pszFlags = NULL;
+ }
+
+#undef APPEND_FLAG_TO_STR
+
+ return pszFlags;
+}
+
+/**
+ * Converts a playback destination enumeration to a string.
+ *
+ * @returns Stringified playback destination, or "Unknown", if not found.
+ * @param enmPlaybackDst Playback destination to convert.
+ */
+const char *DrvAudioHlpPlaybackDstToStr(const PDMAUDIOPLAYBACKDEST enmPlaybackDst)
+{
+ switch (enmPlaybackDst)
+ {
+ case PDMAUDIOPLAYBACKDEST_UNKNOWN: return "Unknown";
+ case PDMAUDIOPLAYBACKDEST_FRONT: return "Front";
+ case PDMAUDIOPLAYBACKDEST_CENTER_LFE: return "Center / LFE";
+ case PDMAUDIOPLAYBACKDEST_REAR: return "Rear";
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Invalid playback destination %ld\n", enmPlaybackDst));
+ return "Unknown";
+}
+
+/**
+ * Converts a recording source enumeration to a string.
+ *
+ * @returns Stringified recording source, or "Unknown", if not found.
+ * @param enmRecSrc Recording source to convert.
+ */
+const char *DrvAudioHlpRecSrcToStr(const PDMAUDIORECSOURCE enmRecSrc)
+{
+ switch (enmRecSrc)
+ {
+ case PDMAUDIORECSOURCE_UNKNOWN: return "Unknown";
+ case PDMAUDIORECSOURCE_MIC: return "Microphone In";
+ case PDMAUDIORECSOURCE_CD: return "CD";
+ case PDMAUDIORECSOURCE_VIDEO: return "Video";
+ case PDMAUDIORECSOURCE_AUX: return "AUX";
+ case PDMAUDIORECSOURCE_LINE: return "Line In";
+ case PDMAUDIORECSOURCE_PHONE: return "Phone";
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Invalid recording source %ld\n", enmRecSrc));
+ return "Unknown";
+}
+
+/**
+ * Returns wether the given audio format has signed bits or not.
+ *
+ * @return IPRT status code.
+ * @return bool @c true for signed bits, @c false for unsigned.
+ * @param enmFmt Audio format to retrieve value for.
+ */
+bool DrvAudioHlpAudFmtIsSigned(PDMAUDIOFMT enmFmt)
+{
+ switch (enmFmt)
+ {
+ case PDMAUDIOFMT_S8:
+ case PDMAUDIOFMT_S16:
+ case PDMAUDIOFMT_S32:
+ return true;
+
+ case PDMAUDIOFMT_U8:
+ case PDMAUDIOFMT_U16:
+ case PDMAUDIOFMT_U32:
+ return false;
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Bogus audio format %ld\n", enmFmt));
+ return false;
+}
+
+/**
+ * Returns the bits of a given audio format.
+ *
+ * @return IPRT status code.
+ * @return uint8_t Bits of audio format.
+ * @param enmFmt Audio format to retrieve value for.
+ */
+uint8_t DrvAudioHlpAudFmtToBits(PDMAUDIOFMT enmFmt)
+{
+ switch (enmFmt)
+ {
+ case PDMAUDIOFMT_S8:
+ case PDMAUDIOFMT_U8:
+ return 8;
+
+ case PDMAUDIOFMT_U16:
+ case PDMAUDIOFMT_S16:
+ return 16;
+
+ case PDMAUDIOFMT_U32:
+ case PDMAUDIOFMT_S32:
+ return 32;
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Bogus audio format %ld\n", enmFmt));
+ return 0;
+}
+
+/**
+ * Converts an audio format to a string.
+ *
+ * @returns Stringified audio format, or "Unknown", if not found.
+ * @param enmFmt Audio format to convert.
+ */
+const char *DrvAudioHlpAudFmtToStr(PDMAUDIOFMT enmFmt)
+{
+ switch (enmFmt)
+ {
+ case PDMAUDIOFMT_U8:
+ return "U8";
+
+ case PDMAUDIOFMT_U16:
+ return "U16";
+
+ case PDMAUDIOFMT_U32:
+ return "U32";
+
+ case PDMAUDIOFMT_S8:
+ return "S8";
+
+ case PDMAUDIOFMT_S16:
+ return "S16";
+
+ case PDMAUDIOFMT_S32:
+ return "S32";
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("Bogus audio format %ld\n", enmFmt));
+ return "Unknown";
+}
+
+/**
+ * Converts a given string to an audio format.
+ *
+ * @returns Audio format for the given string, or PDMAUDIOFMT_INVALID if not found.
+ * @param pszFmt String to convert to an audio format.
+ */
+PDMAUDIOFMT DrvAudioHlpStrToAudFmt(const char *pszFmt)
+{
+ AssertPtrReturn(pszFmt, PDMAUDIOFMT_INVALID);
+
+ if (!RTStrICmp(pszFmt, "u8"))
+ return PDMAUDIOFMT_U8;
+ else if (!RTStrICmp(pszFmt, "u16"))
+ return PDMAUDIOFMT_U16;
+ else if (!RTStrICmp(pszFmt, "u32"))
+ return PDMAUDIOFMT_U32;
+ else if (!RTStrICmp(pszFmt, "s8"))
+ return PDMAUDIOFMT_S8;
+ else if (!RTStrICmp(pszFmt, "s16"))
+ return PDMAUDIOFMT_S16;
+ else if (!RTStrICmp(pszFmt, "s32"))
+ return PDMAUDIOFMT_S32;
+
+ AssertMsgFailed(("Invalid audio format '%s'\n", pszFmt));
+ return PDMAUDIOFMT_INVALID;
+}
+
+/**
+ * Checks whether two given PCM properties are equal.
+ *
+ * @returns @c true if equal, @c false if not.
+ * @param pProps1 First properties to compare.
+ * @param pProps2 Second properties to compare.
+ */
+bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps1, const PPDMAUDIOPCMPROPS pProps2)
+{
+ AssertPtrReturn(pProps1, false);
+ AssertPtrReturn(pProps2, false);
+
+ if (pProps1 == pProps2) /* If the pointers match, take a shortcut. */
+ return true;
+
+ return pProps1->uHz == pProps2->uHz
+ && pProps1->cChannels == pProps2->cChannels
+ && pProps1->cBytes == pProps2->cBytes
+ && pProps1->fSigned == pProps2->fSigned
+ && pProps1->fSwapEndian == pProps2->fSwapEndian;
+}
+
+/**
+ * Checks whether given PCM properties are valid or not.
+ *
+ * Returns @c true if properties are valid, @c false if not.
+ * @param pProps PCM properties to check.
+ */
+bool DrvAudioHlpPCMPropsAreValid(const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, false);
+
+ /* Minimum 1 channel (mono), maximum 7.1 (= 8) channels. */
+ bool fValid = ( pProps->cChannels >= 1
+ && pProps->cChannels <= 8);
+
+ if (fValid)
+ {
+ switch (pProps->cBytes)
+ {
+ case 1: /* 8 bit */
+ if (pProps->fSigned)
+ fValid = false;
+ break;
+ case 2: /* 16 bit */
+ if (!pProps->fSigned)
+ fValid = false;
+ break;
+ /** @todo Do we need support for 24 bit samples? */
+ case 4: /* 32 bit */
+ if (!pProps->fSigned)
+ fValid = false;
+ break;
+ default:
+ fValid = false;
+ break;
+ }
+ }
+
+ if (!fValid)
+ return false;
+
+ fValid &= pProps->uHz > 0;
+ fValid &= pProps->cShift == PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels);
+ fValid &= pProps->fSwapEndian == false; /** @todo Handling Big Endian audio data is not supported yet. */
+
+ return fValid;
+}
+
+/**
+ * Checks whether the given PCM properties are equal with the given
+ * stream configuration.
+ *
+ * @returns @c true if equal, @c false if not.
+ * @param pProps PCM properties to compare.
+ * @param pCfg Stream configuration to compare.
+ */
+bool DrvAudioHlpPCMPropsAreEqual(const PPDMAUDIOPCMPROPS pProps, const PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pProps, false);
+ AssertPtrReturn(pCfg, false);
+
+ return DrvAudioHlpPCMPropsAreEqual(pProps, &pCfg->Props);
+}
+
+/**
+ * Returns the bytes per frame for given PCM properties.
+ *
+ * @return Bytes per (audio) frame.
+ * @param pProps PCM properties to retrieve bytes per frame for.
+ */
+uint32_t DrvAudioHlpPCMPropsBytesPerFrame(const PPDMAUDIOPCMPROPS pProps)
+{
+ return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+}
+
+/**
+ * Prints PCM properties to the debug log.
+ *
+ * @param pProps Stream configuration to log.
+ */
+void DrvAudioHlpPCMPropsPrint(const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturnVoid(pProps);
+
+ Log(("uHz=%RU32, cChannels=%RU8, cBits=%RU8%s",
+ pProps->uHz, pProps->cChannels, pProps->cBytes * 8, pProps->fSigned ? "S" : "U"));
+}
+
+/**
+ * Converts PCM properties to a audio stream configuration.
+ *
+ * @return IPRT status code.
+ * @param pProps PCM properties to convert.
+ * @param pCfg Stream configuration to store result into.
+ */
+int DrvAudioHlpPCMPropsToStreamCfg(const PPDMAUDIOPCMPROPS pProps, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ DrvAudioHlpStreamCfgInit(pCfg);
+
+ memcpy(&pCfg->Props, pProps, sizeof(PDMAUDIOPCMPROPS));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a stream configuration with its default values.
+ *
+ * @param pCfg Stream configuration to initialize.
+ */
+void DrvAudioHlpStreamCfgInit(PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturnVoid(pCfg);
+
+ RT_BZERO(pCfg, sizeof(PDMAUDIOSTREAMCFG));
+
+ pCfg->Backend.cfPreBuf = UINT32_MAX; /* Explicitly set to "undefined". */
+}
+
+/**
+ * Checks whether a given stream configuration is valid or not.
+ *
+ * Returns @c true if configuration is valid, @c false if not.
+ * @param pCfg Stream configuration to check.
+ */
+bool DrvAudioHlpStreamCfgIsValid(const PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, false);
+
+ bool fValid = ( pCfg->enmDir == PDMAUDIODIR_IN
+ || pCfg->enmDir == PDMAUDIODIR_OUT);
+
+ fValid &= ( pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED
+ || pCfg->enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
+
+ if (fValid)
+ fValid = DrvAudioHlpPCMPropsAreValid(&pCfg->Props);
+
+ return fValid;
+}
+
+/**
+ * Frees an allocated audio stream configuration.
+ *
+ * @param pCfg Audio stream configuration to free.
+ */
+void DrvAudioHlpStreamCfgFree(PPDMAUDIOSTREAMCFG pCfg)
+{
+ if (pCfg)
+ {
+ RTMemFree(pCfg);
+ pCfg = NULL;
+ }
+}
+
+/**
+ * Copies a source stream configuration to a destination stream configuration.
+ *
+ * @returns IPRT status code.
+ * @param pDstCfg Destination stream configuration to copy source to.
+ * @param pSrcCfg Source stream configuration to copy to destination.
+ */
+int DrvAudioHlpStreamCfgCopy(PPDMAUDIOSTREAMCFG pDstCfg, const PPDMAUDIOSTREAMCFG pSrcCfg)
+{
+ AssertPtrReturn(pDstCfg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrcCfg, VERR_INVALID_POINTER);
+
+#ifdef VBOX_STRICT
+ if (!DrvAudioHlpStreamCfgIsValid(pSrcCfg))
+ {
+ AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pSrcCfg->szName, pSrcCfg));
+ return VERR_INVALID_PARAMETER;
+ }
+#endif
+
+ memcpy(pDstCfg, pSrcCfg, sizeof(PDMAUDIOSTREAMCFG));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Duplicates an audio stream configuration.
+ * Must be free'd with DrvAudioHlpStreamCfgFree().
+ *
+ * @return Duplicates audio stream configuration on success, or NULL on failure.
+ * @param pCfg Audio stream configuration to duplicate.
+ */
+PPDMAUDIOSTREAMCFG DrvAudioHlpStreamCfgDup(const PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pCfg, NULL);
+
+#ifdef VBOX_STRICT
+ if (!DrvAudioHlpStreamCfgIsValid(pCfg))
+ {
+ AssertMsgFailed(("Stream config '%s' (%p) is invalid\n", pCfg->szName, pCfg));
+ return NULL;
+ }
+#endif
+
+ PPDMAUDIOSTREAMCFG pDst = (PPDMAUDIOSTREAMCFG)RTMemAllocZ(sizeof(PDMAUDIOSTREAMCFG));
+ if (!pDst)
+ return NULL;
+
+ int rc2 = DrvAudioHlpStreamCfgCopy(pDst, pCfg);
+ if (RT_FAILURE(rc2))
+ {
+ DrvAudioHlpStreamCfgFree(pDst);
+ pDst = NULL;
+ }
+
+ AssertPtr(pDst);
+ return pDst;
+}
+
+/**
+ * Prints an audio stream configuration to the debug log.
+ *
+ * @param pCfg Stream configuration to log.
+ */
+void DrvAudioHlpStreamCfgPrint(const PPDMAUDIOSTREAMCFG pCfg)
+{
+ if (!pCfg)
+ return;
+
+ LogFunc(("szName=%s, enmDir=%RU32 (uHz=%RU32, cBits=%RU8%s, cChannels=%RU8)\n",
+ pCfg->szName, pCfg->enmDir,
+ pCfg->Props.uHz, pCfg->Props.cBytes * 8, pCfg->Props.fSigned ? "S" : "U", pCfg->Props.cChannels));
+}
+
+/**
+ * Converts a stream command to a string.
+ *
+ * @returns Stringified stream command, or "Unknown", if not found.
+ * @param enmCmd Stream command to convert.
+ */
+const char *DrvAudioHlpStreamCmdToStr(PDMAUDIOSTREAMCMD enmCmd)
+{
+ switch (enmCmd)
+ {
+ case PDMAUDIOSTREAMCMD_UNKNOWN: return "Unknown";
+ case PDMAUDIOSTREAMCMD_ENABLE: return "Enable";
+ case PDMAUDIOSTREAMCMD_DISABLE: return "Disable";
+ case PDMAUDIOSTREAMCMD_PAUSE: return "Pause";
+ case PDMAUDIOSTREAMCMD_RESUME: return "Resume";
+ case PDMAUDIOSTREAMCMD_DRAIN: return "Drain";
+ case PDMAUDIOSTREAMCMD_DROP: return "Drop";
+ default: break;
+ }
+
+ AssertMsgFailed(("Invalid stream command %ld\n", enmCmd));
+ return "Unknown";
+}
+
+/**
+ * Returns @c true if the given stream status indicates a can-be-read-from stream,
+ * @c false if not.
+ *
+ * @returns @c true if ready to be read from, @c if not.
+ * @param enmStatus Stream status to evaluate.
+ */
+bool DrvAudioHlpStreamStatusCanRead(PDMAUDIOSTREAMSTS enmStatus)
+{
+ AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false);
+
+ return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED
+ && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT);
+}
+
+/**
+ * Returns @c true if the given stream status indicates a can-be-written-to stream,
+ * @c false if not.
+ *
+ * @returns @c true if ready to be written to, @c if not.
+ * @param enmStatus Stream status to evaluate.
+ */
+bool DrvAudioHlpStreamStatusCanWrite(PDMAUDIOSTREAMSTS enmStatus)
+{
+ AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false);
+
+ return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED
+ && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT);
+}
+
+/**
+ * Returns @c true if the given stream status indicates a ready-to-operate stream,
+ * @c false if not.
+ *
+ * @returns @c true if ready to operate, @c if not.
+ * @param enmStatus Stream status to evaluate.
+ */
+bool DrvAudioHlpStreamStatusIsReady(PDMAUDIOSTREAMSTS enmStatus)
+{
+ AssertReturn(enmStatus & PDMAUDIOSTREAMSTS_VALID_MASK, false);
+
+ return enmStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED
+ && enmStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED
+ && !(enmStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT);
+}
+
+/**
+ * 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 The calculated bit rate.
+ * @param cBits Number of bits per sample.
+ * @param uHz Hz (Hertz) rate.
+ * @param cChannels Number of audio channels.
+ */
+uint32_t DrvAudioHlpCalcBitrate(uint8_t cBits, uint32_t uHz, uint8_t cChannels)
+{
+ return (cBits * uHz * cChannels);
+}
+
+/**
+ * Calculates the audio bit rate out of a given audio stream configuration.
+ *
+ * Divide the result by 8 to get the byte rate.
+ *
+ * @returns The calculated bit rate.
+ * @param pProps PCM properties to calculate bitrate for.
+ *
+ * @remark
+ */
+uint32_t DrvAudioHlpCalcBitrate(const PPDMAUDIOPCMPROPS pProps)
+{
+ return DrvAudioHlpCalcBitrate(pProps->cBytes * 8, pProps->uHz, pProps->cChannels);
+}
+
+/**
+ * Aligns the given byte amount to the given PCM properties and returns the aligned
+ * size.
+ *
+ * @return Aligned size (in bytes).
+ * @param cbSize Size (in bytes) to align.
+ * @param pProps PCM properties to align size to.
+ */
+uint32_t DrvAudioHlpBytesAlign(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!cbSize)
+ return 0;
+
+ return PDMAUDIOPCMPROPS_B2F(pProps, cbSize) * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+}
+
+/**
+ * Returns if the the given size is properly aligned to the given PCM properties.
+ *
+ * @return @c true if properly aligned, @c false if not.
+ * @param cbSize Size (in bytes) to check alignment for.
+ * @param pProps PCM properties to use for checking the alignment.
+ */
+bool DrvAudioHlpBytesIsAligned(uint32_t cbSize, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!cbSize)
+ return true;
+
+ return (cbSize % PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) == 0);
+}
+
+/**
+ * Returns the bytes per second for given PCM properties.
+ *
+ * @returns Bytes per second.
+ * @param pProps PCM properties to retrieve size for.
+ */
+DECLINLINE(uint64_t) drvAudioHlpBytesPerSec(const PPDMAUDIOPCMPROPS pProps)
+{
+ return PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */) * pProps->uHz;
+}
+
+/**
+ * Returns the number of audio frames for a given amount of bytes.
+ *
+ * @return Calculated audio frames for given bytes.
+ * @param cbBytes Bytes to convert to audio frames.
+ * @param pProps PCM properties to calulate frames for.
+ */
+uint32_t DrvAudioHlpBytesToFrames(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ return PDMAUDIOPCMPROPS_B2F(pProps, cbBytes);
+}
+
+/**
+ * Returns the time (in ms) for given byte amount and PCM properties.
+ *
+ * @return uint64_t Calculated time (in ms).
+ * @param cbBytes Amount of bytes to calculate time for.
+ * @param pProps PCM properties to calculate amount of bytes for.
+ *
+ * @note Does rounding up the result.
+ */
+uint64_t DrvAudioHlpBytesToMilli(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!pProps->uHz) /* Prevent division by zero. */
+ return 0;
+
+ const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+
+ if (!cbFrame) /* Prevent division by zero. */
+ return 0;
+
+ uint64_t uTimeMs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_MS_1SEC;
+
+ return (uTimeMs + pProps->uHz - 1) / pProps->uHz;
+}
+
+/**
+ * Returns the time (in us) for given byte amount and PCM properties.
+ *
+ * @return uint64_t Calculated time (in us).
+ * @param cbBytes Amount of bytes to calculate time for.
+ * @param pProps PCM properties to calculate amount of bytes for.
+ *
+ * @note Does rounding up the result.
+ */
+uint64_t DrvAudioHlpBytesToMicro(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!pProps->uHz) /* Prevent division by zero. */
+ return 0;
+
+ const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+
+ if (!cbFrame) /* Prevent division by zero. */
+ return 0;
+
+ uint64_t uTimeUs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_US_1SEC;
+
+ return (uTimeUs + pProps->uHz - 1) / pProps->uHz;
+}
+
+/**
+ * Returns the time (in ns) for given byte amount and PCM properties.
+ *
+ * @return uint64_t Calculated time (in ns).
+ * @param cbBytes Amount of bytes to calculate time for.
+ * @param pProps PCM properties to calculate amount of bytes for.
+ *
+ * @note Does rounding up the result.
+ */
+uint64_t DrvAudioHlpBytesToNano(uint32_t cbBytes, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!pProps->uHz) /* Prevent division by zero. */
+ return 0;
+
+ const unsigned cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+
+ if (!cbFrame) /* Prevent division by zero. */
+ return 0;
+
+ uint64_t uTimeNs = ((cbBytes + cbFrame - 1) / cbFrame) * RT_NS_1SEC;
+
+ return (uTimeNs + pProps->uHz - 1) / pProps->uHz;
+}
+
+/**
+ * Returns the bytes for a given audio frames amount and PCM properties.
+ *
+ * @return Calculated bytes for given audio frames.
+ * @param cFrames Amount of audio frames to calculate bytes for.
+ * @param pProps PCM properties to calculate bytes for.
+ */
+uint32_t DrvAudioHlpFramesToBytes(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!cFrames)
+ return 0;
+
+ return cFrames * PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+}
+
+/**
+ * Returns the time (in ms) for given audio frames amount and PCM properties.
+ *
+ * @return uint64_t Calculated time (in ms).
+ * @param cFrames Amount of audio frames to calculate time for.
+ * @param pProps PCM properties to calculate time (in ms) for.
+ */
+uint64_t DrvAudioHlpFramesToMilli(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!cFrames)
+ return 0;
+
+ if (!pProps->uHz) /* Prevent division by zero. */
+ return 0;
+
+ return cFrames / ((double)pProps->uHz / (double)RT_MS_1SEC);
+}
+
+/**
+ * Returns the time (in ns) for given audio frames amount and PCM properties.
+ *
+ * @return uint64_t Calculated time (in ns).
+ * @param cFrames Amount of audio frames to calculate time for.
+ * @param pProps PCM properties to calculate time (in ns) for.
+ */
+uint64_t DrvAudioHlpFramesToNano(uint32_t cFrames, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!cFrames)
+ return 0;
+
+ if (!pProps->uHz) /* Prevent division by zero. */
+ return 0;
+
+ return cFrames / ((double)pProps->uHz / (double)RT_NS_1SEC);
+}
+
+/**
+ * Returns the amount of bytes for a given time (in ms) and PCM properties.
+ *
+ * Note: The result will return an amount of bytes which is aligned to the audio frame size.
+ *
+ * @return uint32_t Calculated amount of bytes.
+ * @param uMs Time (in ms) to calculate amount of bytes for.
+ * @param pProps PCM properties to calculate amount of bytes for.
+ */
+uint32_t DrvAudioHlpMilliToBytes(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!uMs)
+ return 0;
+
+ const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps);
+
+ uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_MS_1SEC) * uMs;
+ if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */
+ uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame);
+
+ Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */
+
+ return uBytes;
+}
+
+/**
+ * Returns the amount of bytes for a given time (in ns) and PCM properties.
+ *
+ * Note: The result will return an amount of bytes which is aligned to the audio frame size.
+ *
+ * @return uint32_t Calculated amount of bytes.
+ * @param uNs Time (in ns) to calculate amount of bytes for.
+ * @param pProps PCM properties to calculate amount of bytes for.
+ */
+uint32_t DrvAudioHlpNanoToBytes(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ if (!uNs)
+ return 0;
+
+ const uint32_t uBytesPerFrame = DrvAudioHlpPCMPropsBytesPerFrame(pProps);
+
+ uint32_t uBytes = ((double)drvAudioHlpBytesPerSec(pProps) / (double)RT_NS_1SEC) * uNs;
+ if (uBytes % uBytesPerFrame) /* Any remainder? Make the returned bytes an integral number to the given frames. */
+ uBytes = uBytes + (uBytesPerFrame - uBytes % uBytesPerFrame);
+
+ Assert(uBytes % uBytesPerFrame == 0); /* Paranoia. */
+
+ return uBytes;
+}
+
+/**
+ * Returns the amount of audio frames for a given time (in ms) and PCM properties.
+ *
+ * @return uint32_t Calculated amount of audio frames.
+ * @param uMs Time (in ms) to calculate amount of frames for.
+ * @param pProps PCM properties to calculate amount of frames for.
+ */
+uint32_t DrvAudioHlpMilliToFrames(uint64_t uMs, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+ if (!cbFrame) /* Prevent division by zero. */
+ return 0;
+
+ return DrvAudioHlpMilliToBytes(uMs, pProps) / cbFrame;
+}
+
+/**
+ * Returns the amount of audio frames for a given time (in ns) and PCM properties.
+ *
+ * @return uint32_t Calculated amount of audio frames.
+ * @param uNs Time (in ns) to calculate amount of frames for.
+ * @param pProps PCM properties to calculate amount of frames for.
+ */
+uint32_t DrvAudioHlpNanoToFrames(uint64_t uNs, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pProps, 0);
+
+ const uint32_t cbFrame = PDMAUDIOPCMPROPS_F2B(pProps, 1 /* Frame */);
+ if (!cbFrame) /* Prevent division by zero. */
+ return 0;
+
+ return DrvAudioHlpNanoToBytes(uNs, pProps) / cbFrame;
+}
+
+/**
+ * Sanitizes the file name component so that unsupported characters
+ * will be replaced by an underscore ("_").
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to sanitize.
+ * @param cbPath Size (in bytes) of path to sanitize.
+ */
+int DrvAudioHlpFileNameSanitize(char *pszPath, size_t cbPath)
+{
+ RT_NOREF(cbPath);
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ /* Filter out characters not allowed on Windows platforms, put in by
+ RTTimeSpecToString(). */
+ /** @todo Use something like RTPathSanitize() if available later some time. */
+ static RTUNICP const s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* Replacement */);
+ if (cReplaced < 0)
+ rc = VERR_INVALID_UTF8_ENCODING;
+#else
+ RT_NOREF(pszPath);
+#endif
+ return rc;
+}
+
+/**
+ * Constructs an unique file name, based on the given path and the audio file type.
+ *
+ * @returns IPRT status code.
+ * @param pszFile Where to store the constructed file name.
+ * @param cchFile Size (in characters) of the file name buffer.
+ * @param pszPath Base path to use.
+ * @param pszName A name for better identifying the file.
+ * @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.
+ */
+int DrvAudioHlpFileNameGet(char *pszFile, size_t cchFile, const char *pszPath, const char *pszName,
+ uint32_t uInstance, PDMAUDIOFILETYPE enmType, PDMAUDIOFILENAMEFLAGS fFlags)
+{
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertReturn(cchFile, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ /** @todo Validate fFlags. */
+
+ int rc;
+
+ do
+ {
+ char szFilePath[RTPATH_MAX + 1];
+ RTStrPrintf2(szFilePath, sizeof(szFilePath), "%s", pszPath);
+
+ /* Create it when necessary. */
+ if (!RTDirExists(szFilePath))
+ {
+ rc = RTDirCreateFullPath(szFilePath, RTFS_UNIX_IRWXU);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ char szFileName[RTPATH_MAX + 1];
+ szFileName[0] = '\0';
+
+ if (fFlags & PDMAUDIOFILENAME_FLAG_TS)
+ {
+ RTTIMESPEC time;
+ if (!RTTimeSpecToString(RTTimeNow(&time), szFileName, sizeof(szFileName)))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ rc = DrvAudioHlpFileNameSanitize(szFileName, sizeof(szFileName));
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTStrCat(szFileName, sizeof(szFileName), "-");
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ rc = RTStrCat(szFileName, sizeof(szFileName), pszName);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTStrCat(szFileName, sizeof(szFileName), "-");
+ if (RT_FAILURE(rc))
+ break;
+
+ char szInst[16];
+ RTStrPrintf2(szInst, sizeof(szInst), "%RU32", uInstance);
+ rc = RTStrCat(szFileName, sizeof(szFileName), szInst);
+ if (RT_FAILURE(rc))
+ break;
+
+ switch (enmType)
+ {
+ case PDMAUDIOFILETYPE_RAW:
+ rc = RTStrCat(szFileName, sizeof(szFileName), ".pcm");
+ break;
+
+ case PDMAUDIOFILETYPE_WAV:
+ rc = RTStrCat(szFileName, sizeof(szFileName), ".wav");
+ break;
+
+ default:
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName);
+ if (RT_FAILURE(rc))
+ break;
+
+ RTStrPrintf2(pszFile, cchFile, "%s", szFilePath);
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates an audio file.
+ *
+ * @returns IPRT status code.
+ * @param enmType Audio file type to open / create.
+ * @param pszFile File path of file to open or create.
+ * @param fFlags Audio file flags.
+ * @param ppFile Where to store the created audio file handle.
+ * Needs to be destroyed with DrvAudioHlpFileDestroy().
+ */
+int DrvAudioHlpFileCreate(PDMAUDIOFILETYPE enmType, const char *pszFile, PDMAUDIOFILEFLAGS fFlags, PPDMAUDIOFILE *ppFile)
+{
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ /** @todo Validate fFlags. */
+
+ PPDMAUDIOFILE pFile = (PPDMAUDIOFILE)RTMemAlloc(sizeof(PDMAUDIOFILE));
+ if (!pFile)
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmType)
+ {
+ case PDMAUDIOFILETYPE_RAW:
+ case PDMAUDIOFILETYPE_WAV:
+ pFile->enmType = enmType;
+ break;
+
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTStrPrintf(pFile->szName, RT_ELEMENTS(pFile->szName), "%s", pszFile);
+ pFile->hFile = NIL_RTFILE;
+ pFile->fFlags = fFlags;
+ pFile->pvData = NULL;
+ pFile->cbData = 0;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pFile);
+ pFile = NULL;
+ }
+ else
+ *ppFile = pFile;
+
+ return rc;
+}
+
+/**
+ * Destroys a formerly created audio file.
+ *
+ * @param pFile Audio file (object) to destroy.
+ */
+void DrvAudioHlpFileDestroy(PPDMAUDIOFILE pFile)
+{
+ if (!pFile)
+ return;
+
+ DrvAudioHlpFileClose(pFile);
+
+ RTMemFree(pFile);
+ pFile = NULL;
+}
+
+/**
+ * Opens or creates an audio file.
+ *
+ * @returns IPRT status code.
+ * @param pFile Pointer to audio file handle to use.
+ * @param fOpen Open flags.
+ * Use PDMAUDIOFILE_DEFAULT_OPEN_FLAGS for the default open flags.
+ * @param pProps PCM properties to use.
+ */
+int DrvAudioHlpFileOpen(PPDMAUDIOFILE pFile, uint32_t fOpen, const PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+ /** @todo Validate fOpen flags. */
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pFile->enmType == PDMAUDIOFILETYPE_RAW)
+ {
+ rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen);
+ }
+ else if (pFile->enmType == PDMAUDIOFILETYPE_WAV)
+ {
+ Assert(pProps->cChannels);
+ Assert(pProps->uHz);
+ Assert(pProps->cBytes);
+
+ pFile->pvData = (PAUDIOWAVFILEDATA)RTMemAllocZ(sizeof(AUDIOWAVFILEDATA));
+ if (pFile->pvData)
+ {
+ pFile->cbData = sizeof(PAUDIOWAVFILEDATA);
+
+ PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData;
+ AssertPtr(pData);
+
+ /* Header. */
+ pData->Hdr.u32RIFF = AUDIO_MAKE_FOURCC('R','I','F','F');
+ pData->Hdr.u32Size = 36;
+ pData->Hdr.u32WAVE = AUDIO_MAKE_FOURCC('W','A','V','E');
+
+ pData->Hdr.u32Fmt = AUDIO_MAKE_FOURCC('f','m','t',' ');
+ pData->Hdr.u32Size1 = 16; /* Means PCM. */
+ pData->Hdr.u16AudioFormat = 1; /* PCM, linear quantization. */
+ pData->Hdr.u16NumChannels = pProps->cChannels;
+ pData->Hdr.u32SampleRate = pProps->uHz;
+ pData->Hdr.u32ByteRate = DrvAudioHlpCalcBitrate(pProps) / 8;
+ pData->Hdr.u16BlockAlign = pProps->cChannels * pProps->cBytes;
+ pData->Hdr.u16BitsPerSample = pProps->cBytes * 8;
+
+ /* Data chunk. */
+ pData->Hdr.u32ID2 = AUDIO_MAKE_FOURCC('d','a','t','a');
+ pData->Hdr.u32Size2 = 0;
+
+ rc = RTFileOpen(&pFile->hFile, pFile->szName, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(pFile->hFile, &pData->Hdr, sizeof(pData->Hdr), NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(pFile->hFile);
+ pFile->hFile = NIL_RTFILE;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pFile->pvData);
+ pFile->pvData = NULL;
+ pFile->cbData = 0;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("Audio: Opened file '%s'\n", pFile->szName));
+ }
+ else
+ LogRel(("Audio: Failed opening file '%s', rc=%Rrc\n", pFile->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Closes an audio file.
+ *
+ * @returns IPRT status code.
+ * @param pFile Audio file handle to close.
+ */
+int DrvAudioHlpFileClose(PPDMAUDIOFILE pFile)
+{
+ if (!pFile)
+ return VINF_SUCCESS;
+
+ size_t cbSize = DrvAudioHlpFileGetDataSize(pFile);
+
+ int rc = VINF_SUCCESS;
+
+ if (pFile->enmType == PDMAUDIOFILETYPE_RAW)
+ {
+ if (RTFileIsValid(pFile->hFile))
+ rc = RTFileClose(pFile->hFile);
+ }
+ else if (pFile->enmType == PDMAUDIOFILETYPE_WAV)
+ {
+ if (RTFileIsValid(pFile->hFile))
+ {
+ PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData;
+ if (pData) /* The .WAV file data only is valid when a file actually has been created. */
+ {
+ /* Update the header with the current data size. */
+ RTFileWriteAt(pFile->hFile, 0, &pData->Hdr, sizeof(pData->Hdr), NULL);
+ }
+
+ rc = RTFileClose(pFile->hFile);
+ }
+
+ if (pFile->pvData)
+ {
+ RTMemFree(pFile->pvData);
+ pFile->pvData = NULL;
+ }
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if ( RT_SUCCESS(rc)
+ && !cbSize
+ && !(pFile->fFlags & PDMAUDIOFILE_FLAG_KEEP_IF_EMPTY))
+ {
+ rc = DrvAudioHlpFileDelete(pFile);
+ }
+
+ pFile->cbData = 0;
+
+ if (RT_SUCCESS(rc))
+ {
+ pFile->hFile = NIL_RTFILE;
+ LogRel2(("Audio: Closed file '%s' (%zu bytes)\n", pFile->szName, cbSize));
+ }
+ else
+ LogRel(("Audio: Failed closing file '%s', rc=%Rrc\n", pFile->szName, rc));
+
+ return rc;
+}
+
+/**
+ * Deletes an audio file.
+ *
+ * @returns IPRT status code.
+ * @param pFile Audio file handle to delete.
+ */
+int DrvAudioHlpFileDelete(PPDMAUDIOFILE 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 the raw audio data size of an audio file.
+ *
+ * Note: This does *not* include file headers and other data which does
+ * not belong to the actual PCM audio data.
+ *
+ * @returns Size (in bytes) of the raw PCM audio data.
+ * @param pFile Audio file handle to retrieve the audio data size for.
+ */
+size_t DrvAudioHlpFileGetDataSize(PPDMAUDIOFILE pFile)
+{
+ AssertPtrReturn(pFile, 0);
+
+ size_t cbSize = 0;
+
+ if (pFile->enmType == PDMAUDIOFILETYPE_RAW)
+ {
+ cbSize = RTFileTell(pFile->hFile);
+ }
+ else if (pFile->enmType == PDMAUDIOFILETYPE_WAV)
+ {
+ PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData;
+ if (pData) /* The .WAV file data only is valid when a file actually has been created. */
+ cbSize = pData->Hdr.u32Size2;
+ }
+
+ return cbSize;
+}
+
+/**
+ * Returns whether the given audio file is open and in use or not.
+ *
+ * @return bool True if open, false if not.
+ * @param pFile Audio file handle to check open status for.
+ */
+bool DrvAudioHlpFileIsOpen(PPDMAUDIOFILE pFile)
+{
+ if (!pFile)
+ return false;
+
+ return RTFileIsValid(pFile->hFile);
+}
+
+/**
+ * Write PCM data to a wave (.WAV) file.
+ *
+ * @returns IPRT status code.
+ * @param pFile Audio file handle to write PCM data to.
+ * @param pvBuf Audio data to write.
+ * @param cbBuf Size (in bytes) of audio data to write.
+ * @param fFlags Additional write flags. Not being used at the moment and must be 0.
+ */
+int DrvAudioHlpFileWrite(PPDMAUDIOFILE pFile, const void *pvBuf, size_t cbBuf, uint32_t fFlags)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ AssertReturn(fFlags == 0, VERR_INVALID_PARAMETER); /** @todo fFlags are currently not implemented. */
+
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ AssertReturn(RTFileIsValid(pFile->hFile), VERR_WRONG_ORDER);
+
+ int rc;
+
+ if (pFile->enmType == PDMAUDIOFILETYPE_RAW)
+ {
+ rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL);
+ }
+ else if (pFile->enmType == PDMAUDIOFILETYPE_WAV)
+ {
+ PAUDIOWAVFILEDATA pData = (PAUDIOWAVFILEDATA)pFile->pvData;
+ AssertPtr(pData);
+
+ rc = RTFileWrite(pFile->hFile, pvBuf, cbBuf, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pData->Hdr.u32Size += (uint32_t)cbBuf;
+ pData->Hdr.u32Size2 += (uint32_t)cbBuf;
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp b/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp
new file mode 100644
index 00000000..bffde104
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostALSAAudio.cpp
@@ -0,0 +1,1530 @@
+/* $Id: DrvHostALSAAudio.cpp $ */
+/** @file
+ * ALSA audio driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ * --------------------------------------------------------------------
+ *
+ * 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>
+
+RT_C_DECLS_BEGIN
+ #include "alsa_stubs.h"
+ #include "alsa_mangling.h"
+RT_C_DECLS_END
+
+#include <alsa/asoundlib.h>
+#include <alsa/control.h> /* For device enumeration. */
+
+#include "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+
+/** Makes DRVHOSTALSAAUDIO out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTALSAAUDIO(pInterface) \
+ ( (PDRVHOSTALSAAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTALSAAUDIO, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+
+typedef struct ALSAAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ union
+ {
+ struct
+ {
+ } In;
+ struct
+ {
+ } Out;
+ };
+ snd_pcm_t *phPCM;
+ void *pvBuf;
+ size_t cbBuf;
+} ALSAAUDIOSTREAM, *PALSAAUDIOSTREAM;
+
+/* latency = period_size * periods / (rate * bytes_per_frame) */
+
+static int alsaStreamRecover(snd_pcm_t *phPCM);
+
+/**
+ * Host Alsa audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTALSAAUDIO
+{
+ /** 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;
+} DRVHOSTALSAAUDIO, *PDRVHOSTALSAAUDIO;
+
+/** Maximum number of tries to recover a broken pipe. */
+#define ALSA_RECOVERY_TRIES_MAX 5
+
+typedef struct ALSAAUDIOSTREAMCFG
+{
+ unsigned int freq;
+ /** PCM sound format. */
+ snd_pcm_format_t fmt;
+ /** PCM data access type. */
+ snd_pcm_access_t access;
+ /** Whether resampling should be performed by alsalib or not. */
+ int resample;
+ int nchannels;
+ /** Buffer size (in audio frames). */
+ unsigned long buffer_size;
+ /** Periods (in audio frames). */
+ unsigned long period_size;
+ /** For playback: Starting to play threshold (in audio frames).
+ * For Capturing: Starting to capture threshold (in audio frames). */
+ unsigned long threshold;
+} ALSAAUDIOSTREAMCFG, *PALSAAUDIOSTREAMCFG;
+
+
+
+static snd_pcm_format_t alsaAudioPropsToALSA(PPDMAUDIOPCMPROPS pProps)
+{
+ switch (pProps->cBytes)
+ {
+ case 1:
+ return pProps->fSigned ? SND_PCM_FORMAT_S8 : SND_PCM_FORMAT_U8;
+
+ case 2:
+ return pProps->fSigned ? SND_PCM_FORMAT_S16_LE : SND_PCM_FORMAT_U16_LE;
+
+ case 4:
+ return pProps->fSigned ? SND_PCM_FORMAT_S32_LE : SND_PCM_FORMAT_U32_LE;
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("%RU8 bytes not supported\n", pProps->cBytes));
+ return SND_PCM_FORMAT_U8;
+}
+
+
+static int alsaALSAToAudioProps(snd_pcm_format_t fmt, PPDMAUDIOPCMPROPS pProps)
+{
+ switch (fmt)
+ {
+ case SND_PCM_FORMAT_S8:
+ pProps->cBytes = 1;
+ pProps->fSigned = true;
+ break;
+
+ case SND_PCM_FORMAT_U8:
+ pProps->cBytes = 1;
+ pProps->fSigned = false;
+ break;
+
+ case SND_PCM_FORMAT_S16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ break;
+
+ case SND_PCM_FORMAT_U16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+ break;
+
+ case SND_PCM_FORMAT_S16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ case SND_PCM_FORMAT_U16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ case SND_PCM_FORMAT_S32_LE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+ break;
+
+ case SND_PCM_FORMAT_U32_LE:
+ pProps->cBytes = 4;
+ pProps->fSigned = false;
+ break;
+
+ case SND_PCM_FORMAT_S32_BE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ case SND_PCM_FORMAT_U32_BE:
+ pProps->cBytes = 4;
+ pProps->fSigned = false;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Format %ld not supported\n", fmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ Assert(pProps->cBytes);
+ Assert(pProps->cChannels);
+ pProps->cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pProps->cBytes, pProps->cChannels);
+
+ return VINF_SUCCESS;
+}
+
+
+static int alsaStreamSetSWParams(snd_pcm_t *phPCM, bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt)
+{
+ if (fIn) /* For input streams there's nothing to do in here right now. */
+ return VINF_SUCCESS;
+
+ snd_pcm_sw_params_t *pSWParms = NULL;
+ snd_pcm_sw_params_alloca(&pSWParms);
+ if (!pSWParms)
+ return VERR_NO_MEMORY;
+
+ int rc;
+ do
+ {
+ int err = snd_pcm_sw_params_current(phPCM, pSWParms);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to get current software parameters: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ err = snd_pcm_sw_params_set_start_threshold(phPCM, pSWParms, pCfgReq->threshold);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set software threshold to %ld: %s\n", pCfgReq->threshold, snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ err = snd_pcm_sw_params_set_avail_min(phPCM, pSWParms, pCfgReq->period_size);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set available minimum to %ld: %s\n", pCfgReq->threshold, snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ err = snd_pcm_sw_params(phPCM, pSWParms);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set new software parameters: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ err = snd_pcm_sw_params_get_start_threshold(pSWParms, &pCfgObt->threshold);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to get start threshold\n"));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ LogFunc(("Setting threshold to %RU32 frames\n", pCfgObt->threshold));
+ rc = VINF_SUCCESS;
+ }
+ while (0);
+
+ return rc;
+}
+
+
+static int alsaStreamClose(snd_pcm_t **pphPCM)
+{
+ if (!pphPCM || !*pphPCM)
+ return VINF_SUCCESS;
+
+ int rc;
+ int rc2 = snd_pcm_close(*pphPCM);
+ if (rc2)
+ {
+ LogRel(("ALSA: Closing PCM descriptor failed: %s\n", snd_strerror(rc2)));
+ rc = VERR_GENERAL_FAILURE; /** @todo */
+ }
+ else
+ {
+ *pphPCM = NULL;
+ rc = VINF_SUCCESS;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int alsaStreamOpen(bool fIn, PALSAAUDIOSTREAMCFG pCfgReq, PALSAAUDIOSTREAMCFG pCfgObt, snd_pcm_t **pphPCM)
+{
+ snd_pcm_t *phPCM = NULL;
+
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ unsigned int cChannels = pCfgReq->nchannels;
+ unsigned int uFreq = pCfgReq->freq;
+ snd_pcm_uframes_t obt_buffer_size;
+
+ do
+ {
+ const char *pszDev = "default"; /** @todo Make this configurable through PALSAAUDIOSTREAMCFG. */
+ if (!pszDev)
+ {
+ LogRel(("ALSA: Invalid or no %s device name set\n", fIn ? "input" : "output"));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ LogRel(("ALSA: Using %s device \"%s\"\n", fIn ? "input" : "output", pszDev));
+
+ int err = snd_pcm_open(&phPCM, pszDev,
+ fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to open \"%s\" as %s device: %s\n", pszDev, fIn ? "input" : "output", snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_nonblock(phPCM, 1);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error setting output non-blocking mode: %s\n", snd_strerror(err)));
+ break;
+ }
+
+ snd_pcm_hw_params_t *pHWParms;
+ snd_pcm_hw_params_alloca(&pHWParms); /** @todo Check for successful allocation? */
+ err = snd_pcm_hw_params_any(phPCM, pHWParms);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to initialize hardware parameters: %s\n", snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_hw_params_set_access(phPCM, pHWParms, SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set access type: %s\n", snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_hw_params_set_format(phPCM, pHWParms, pCfgReq->fmt);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set audio format to %d: %s\n", pCfgReq->fmt, snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_hw_params_set_rate_near(phPCM, pHWParms, &uFreq, 0);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set frequency to %uHz: %s\n", pCfgReq->freq, snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_hw_params_set_channels_near(phPCM, pHWParms, &cChannels);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set number of channels to %d\n", pCfgReq->nchannels));
+ break;
+ }
+
+ if ( cChannels != 1
+ && cChannels != 2)
+ {
+ LogRel(("ALSA: Number of audio channels (%u) not supported\n", cChannels));
+ break;
+ }
+
+ snd_pcm_uframes_t period_size_f = pCfgReq->period_size;
+ snd_pcm_uframes_t buffer_size_f = pCfgReq->buffer_size;
+
+ snd_pcm_uframes_t minval = period_size_f;
+
+ int dir = 0;
+ err = snd_pcm_hw_params_get_period_size_min(pHWParms, &minval, &dir);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Could not determine minimal period size\n"));
+ break;
+ }
+ else
+ {
+ LogFunc(("Minimal period size is: %ld\n", minval));
+ if (period_size_f < minval)
+ period_size_f = minval;
+ }
+
+ err = snd_pcm_hw_params_set_period_size_near(phPCM, pHWParms, &period_size_f, 0);
+ LogFunc(("Period size is: %RU32\n", period_size_f));
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set period size %d (%s)\n", period_size_f, snd_strerror(err)));
+ break;
+ }
+
+ minval = buffer_size_f;
+ err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Could not retrieve minimal buffer size\n"));
+ break;
+ }
+ else
+ LogFunc(("Minimal buffer size is: %RU32\n", minval));
+
+ err = snd_pcm_hw_params_set_buffer_size_near(phPCM, pHWParms, &buffer_size_f);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to set near buffer size %RU32: %s\n", buffer_size_f, snd_strerror(err)));
+ break;
+ }
+
+ err = snd_pcm_hw_params(phPCM, pHWParms);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to apply audio parameters\n"));
+ break;
+ }
+
+ err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to get buffer size\n"));
+ break;
+ }
+
+ snd_pcm_uframes_t obt_period_size;
+ err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Failed to get period size\n"));
+ break;
+ }
+
+ LogRel2(("ALSA: Frequency is %dHz, period size is %RU32 frames, buffer size is %RU32 frames\n",
+ pCfgReq->freq, obt_period_size, obt_buffer_size));
+
+ err = snd_pcm_prepare(phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Could not prepare hPCM %p\n", (void *)phPCM));
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED;
+ break;
+ }
+
+ rc = alsaStreamSetSWParams(phPCM, fIn, pCfgReq, pCfgObt);
+ if (RT_FAILURE(rc))
+ break;
+
+ pCfgObt->fmt = pCfgReq->fmt;
+ pCfgObt->nchannels = cChannels;
+ pCfgObt->freq = uFreq;
+ pCfgObt->period_size = obt_period_size;
+ pCfgObt->buffer_size = obt_buffer_size;
+
+ rc = VINF_SUCCESS;
+ }
+ while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+ *pphPCM = phPCM;
+ }
+ else
+ alsaStreamClose(&phPCM);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+#ifdef DEBUG
+static void alsaDbgErrorHandler(const char *file, int line, const char *function,
+ int err, const char *fmt, ...)
+{
+ /** @todo Implement me! */
+ RT_NOREF(file, line, function, err, fmt);
+}
+#endif
+
+
+static int alsaStreamGetAvail(snd_pcm_t *phPCM, snd_pcm_sframes_t *pFramesAvail)
+{
+ AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
+ /* pFramesAvail is optional. */
+
+ int rc;
+
+ snd_pcm_sframes_t framesAvail = snd_pcm_avail_update(phPCM);
+ if (framesAvail < 0)
+ {
+ if (framesAvail == -EPIPE)
+ {
+ rc = alsaStreamRecover(phPCM);
+ if (RT_SUCCESS(rc))
+ framesAvail = snd_pcm_avail_update(phPCM);
+ }
+ else
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pFramesAvail)
+ *pFramesAvail = framesAvail;
+ }
+
+ LogFunc(("cFrames=%ld, rc=%Rrc\n", framesAvail, rc));
+ return rc;
+}
+
+
+static int alsaStreamRecover(snd_pcm_t *phPCM)
+{
+ AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
+
+ int err = snd_pcm_prepare(phPCM);
+ if (err < 0)
+ {
+ LogFunc(("Failed to recover stream %p: %s\n", phPCM, snd_strerror(err)));
+ return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int alsaStreamResume(snd_pcm_t *phPCM)
+{
+ AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
+
+ int err = snd_pcm_resume(phPCM);
+ if (err < 0)
+ {
+ LogFunc(("Failed to resume stream %p: %s\n", phPCM, snd_strerror(err)));
+ return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+
+ LogFlowFuncEnter();
+
+ int rc = audioLoadAlsaLib();
+ if (RT_FAILURE(rc))
+ LogRel(("ALSA: Failed to load the ALSA shared library, rc=%Rrc\n", rc));
+ else
+ {
+#ifdef DEBUG
+ snd_lib_error_set_handler(alsaDbgErrorHandler);
+#endif
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ snd_pcm_sframes_t cAvail;
+ int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cAvail);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg;
+ AssertPtr(pCfg);
+
+ if (!cAvail) /* No data yet? */
+ {
+ snd_pcm_state_t state = snd_pcm_state(pStreamALSA->phPCM);
+ switch (state)
+ {
+ case SND_PCM_STATE_PREPARED:
+ cAvail = PDMAUDIOSTREAMCFG_B2F(pCfg, cxBuf);
+ break;
+
+ case SND_PCM_STATE_SUSPENDED:
+ {
+ rc = alsaStreamResume(pStreamALSA->phPCM);
+ if (RT_FAILURE(rc))
+ break;
+
+ LogFlow(("Resuming suspended input stream\n"));
+ break;
+ }
+
+ default:
+ LogFlow(("No frames available, state=%d\n", state));
+ break;
+ }
+
+ if (!cAvail)
+ {
+ if (pcxRead)
+ *pcxRead = 0;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Check how much we can read from the capture device without overflowing
+ * the mixer buffer.
+ */
+ size_t cbToRead = RT_MIN((size_t)PDMAUDIOSTREAMCFG_F2B(pCfg, cAvail), cxBuf);
+
+ LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
+
+ uint32_t cbReadTotal = 0;
+
+ snd_pcm_uframes_t cToRead;
+ snd_pcm_sframes_t cRead;
+
+ while ( cbToRead
+ && RT_SUCCESS(rc))
+ {
+ cToRead = RT_MIN(PDMAUDIOSTREAMCFG_B2F(pCfg, cbToRead),
+ PDMAUDIOSTREAMCFG_B2F(pCfg, pStreamALSA->cbBuf));
+ AssertBreakStmt(cToRead, rc = VERR_NO_DATA);
+ cRead = snd_pcm_readi(pStreamALSA->phPCM, pStreamALSA->pvBuf, cToRead);
+ if (cRead <= 0)
+ {
+ switch (cRead)
+ {
+ case 0:
+ {
+ LogFunc(("No input frames available\n"));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ case -EAGAIN:
+ {
+ /*
+ * Don't set error here because EAGAIN means there are no further frames
+ * available at the moment, try later. As we might have read some frames
+ * already these need to be processed instead.
+ */
+ cbToRead = 0;
+ break;
+ }
+
+ case -EPIPE:
+ {
+ rc = alsaStreamRecover(pStreamALSA->phPCM);
+ if (RT_FAILURE(rc))
+ break;
+
+ LogFlowFunc(("Recovered from capturing\n"));
+ continue;
+ }
+
+ default:
+ {
+ LogFunc(("Failed to read input frames: %s\n", snd_strerror(cRead)));
+ rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * We should not run into a full mixer buffer or we loose samples and
+ * run into an endless loop if ALSA keeps producing samples ("null"
+ * capture device for example).
+ */
+ uint32_t cbRead = PDMAUDIOSTREAMCFG_F2B(pCfg, cRead);
+
+ memcpy(pvBuf, pStreamALSA->pvBuf, cbRead);
+
+ Assert(cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ cbReadTotal += cbRead;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcxWritten is optional. */
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ PPDMAUDIOSTREAMCFG pCfg = pStreamALSA->pCfg;
+ AssertPtr(pCfg);
+
+ int rc;
+
+ uint32_t cbWrittenTotal = 0;
+
+ do
+ {
+ snd_pcm_sframes_t csAvail;
+ rc = alsaStreamGetAvail(pStreamALSA->phPCM, &csAvail);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
+ break;
+ }
+
+ if (!csAvail)
+ break;
+
+ size_t cbToWrite = RT_MIN((unsigned)PDMAUDIOSTREAMCFG_F2B(pCfg, csAvail), pStreamALSA->cbBuf);
+ if (!cbToWrite)
+ break;
+
+ /* Do not write more than available. */
+ if (cbToWrite > cxBuf)
+ cbToWrite = cxBuf;
+
+ memcpy(pStreamALSA->pvBuf, pvBuf, cbToWrite);
+
+ snd_pcm_sframes_t csWritten = 0;
+
+ /* Don't try infinitely on recoverable errors. */
+ unsigned iTry;
+ for (iTry = 0; iTry < ALSA_RECOVERY_TRIES_MAX; iTry++)
+ {
+ csWritten = snd_pcm_writei(pStreamALSA->phPCM, pStreamALSA->pvBuf,
+ PDMAUDIOSTREAMCFG_B2F(pCfg, cbToWrite));
+ if (csWritten <= 0)
+ {
+ switch (csWritten)
+ {
+ case 0:
+ {
+ LogFunc(("Failed to write %zu bytes\n", cbToWrite));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ case -EPIPE:
+ {
+ rc = alsaStreamRecover(pStreamALSA->phPCM);
+ if (RT_FAILURE(rc))
+ break;
+
+ LogFlowFunc(("Recovered from playback\n"));
+ continue;
+ }
+
+ case -ESTRPIPE:
+ {
+ /* Stream was suspended and waiting for a recovery. */
+ rc = alsaStreamResume(pStreamALSA->phPCM);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("ALSA: Failed to resume output stream\n"));
+ break;
+ }
+
+ LogFlowFunc(("Resumed suspended output stream\n"));
+ continue;
+ }
+
+ default:
+ LogFlowFunc(("Failed to write %RU32 bytes, error unknown\n", cbToWrite));
+ rc = VERR_GENERAL_FAILURE; /** @todo */
+ break;
+ }
+ }
+ else
+ break;
+ } /* For number of tries. */
+
+ if ( iTry == ALSA_RECOVERY_TRIES_MAX
+ && csWritten <= 0)
+ rc = VERR_BROKEN_PIPE;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ cbWrittenTotal = PDMAUDIOSTREAMCFG_F2B(pCfg, csWritten);
+
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+
+static int alsaDestroyStreamIn(PALSAAUDIOSTREAM pStreamALSA)
+{
+ alsaStreamClose(&pStreamALSA->phPCM);
+
+ if (pStreamALSA->pvBuf)
+ {
+ RTMemFree(pStreamALSA->pvBuf);
+ pStreamALSA->pvBuf = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int alsaDestroyStreamOut(PALSAAUDIOSTREAM pStreamALSA)
+{
+ alsaStreamClose(&pStreamALSA->phPCM);
+
+ if (pStreamALSA->pvBuf)
+ {
+ RTMemFree(pStreamALSA->pvBuf);
+ pStreamALSA->pvBuf = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int alsaCreateStreamOut(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ snd_pcm_t *phPCM = NULL;
+
+ int rc;
+
+ do
+ {
+ ALSAAUDIOSTREAMCFG req;
+ req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
+ req.freq = pCfgReq->Props.uHz;
+ req.nchannels = pCfgReq->Props.cChannels;
+ req.period_size = pCfgReq->Backend.cfPeriod;
+ req.buffer_size = pCfgReq->Backend.cfBufferSize;
+ req.threshold = pCfgReq->Backend.cfPreBuf;
+
+ ALSAAUDIOSTREAMCFG obt;
+ rc = alsaStreamOpen(false /* fIn */, &req, &obt, &phPCM);
+ if (RT_FAILURE(rc))
+ break;
+
+ pCfgAcq->Props.uHz = obt.freq;
+ pCfgAcq->Props.cChannels = obt.nchannels;
+
+ rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ break;
+
+ pCfgAcq->Backend.cfPeriod = obt.period_size;
+ pCfgAcq->Backend.cfBufferSize = obt.buffer_size;
+ pCfgAcq->Backend.cfPreBuf = obt.threshold;
+
+ pStreamALSA->cbBuf = pCfgAcq->Backend.cfBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props);
+ pStreamALSA->pvBuf = RTMemAllocZ(pStreamALSA->cbBuf);
+ if (!pStreamALSA->pvBuf)
+ {
+ LogRel(("ALSA: Not enough memory for output DAC buffer (%zu frames)\n", pCfgAcq->Backend.cfBufferSize));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pStreamALSA->phPCM = phPCM;
+ }
+ while (0);
+
+ if (RT_FAILURE(rc))
+ alsaStreamClose(&phPCM);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int alsaCreateStreamIn(PALSAAUDIOSTREAM pStreamALSA, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ int rc;
+
+ snd_pcm_t *phPCM = NULL;
+
+ do
+ {
+ ALSAAUDIOSTREAMCFG req;
+ req.fmt = alsaAudioPropsToALSA(&pCfgReq->Props);
+ req.freq = pCfgReq->Props.uHz;
+ req.nchannels = pCfgReq->Props.cChannels;
+ req.period_size = DrvAudioHlpMilliToFrames(50 /* ms */, &pCfgReq->Props); /** @todo Make this configurable. */
+ req.buffer_size = req.period_size * 2; /** @todo Make this configurable. */
+ req.threshold = req.period_size;
+
+ ALSAAUDIOSTREAMCFG obt;
+ rc = alsaStreamOpen(true /* fIn */, &req, &obt, &phPCM);
+ if (RT_FAILURE(rc))
+ break;
+
+ pCfgAcq->Props.uHz = obt.freq;
+ pCfgAcq->Props.cChannels = obt.nchannels;
+
+ rc = alsaALSAToAudioProps(obt.fmt, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ break;
+
+ pCfgAcq->Backend.cfPeriod = obt.period_size;
+ pCfgAcq->Backend.cfBufferSize = obt.buffer_size;
+ /* No pre-buffering. */
+
+ pStreamALSA->cbBuf = pCfgAcq->Backend.cfBufferSize * DrvAudioHlpPCMPropsBytesPerFrame(&pCfgAcq->Props);
+ pStreamALSA->pvBuf = RTMemAlloc(pStreamALSA->cbBuf);
+ if (!pStreamALSA->pvBuf)
+ {
+ LogRel(("ALSA: Not enough memory for input ADC buffer (%zu frames)\n", pCfgAcq->Backend.cfBufferSize));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pStreamALSA->phPCM = phPCM;
+ }
+ while (0);
+
+ if (RT_FAILURE(rc))
+ alsaStreamClose(&phPCM);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int alsaControlStreamIn(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ int err;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ err = snd_pcm_prepare(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error preparing input stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ else
+ {
+ Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED);
+
+ /* Only start the PCM stream for input streams. */
+ err = snd_pcm_start(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error starting input stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ }
+
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ err = snd_pcm_drop(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error disabling input stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ err = snd_pcm_drop(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error pausing input stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int alsaControlStreamOut(PALSAAUDIOSTREAM pStreamALSA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ int err;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ err = snd_pcm_prepare(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error preparing output stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ else
+ {
+ Assert(snd_pcm_state(pStreamALSA->phPCM) == SND_PCM_STATE_PREPARED);
+ }
+
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ err = snd_pcm_drop(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error disabling output stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ err = snd_pcm_drop(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error pausing output stream: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ {
+ snd_pcm_state_t streamState = snd_pcm_state(pStreamALSA->phPCM);
+ Log2Func(("Stream state is: %d\n", streamState));
+
+ if ( streamState == SND_PCM_STATE_PREPARED
+ || streamState == SND_PCM_STATE_RUNNING)
+ {
+ err = snd_pcm_nonblock(pStreamALSA->phPCM, 0);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error disabling output non-blocking mode: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ break;
+ }
+
+ err = snd_pcm_drain(pStreamALSA->phPCM);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error draining output: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ break;
+ }
+
+ err = snd_pcm_nonblock(pStreamALSA->phPCM, 1);
+ if (err < 0)
+ {
+ LogRel(("ALSA: Error re-enabling output non-blocking mode: %s\n", snd_strerror(err)));
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "ALSA audio driver");
+
+ pBackendCfg->cbStreamIn = sizeof(ALSAAUDIOSTREAM);
+ pBackendCfg->cbStreamOut = sizeof(ALSAAUDIOSTREAM);
+
+ /* Enumerate sound devices. */
+ char **pszHints;
+ int err = snd_device_name_hint(-1 /* All cards */, "pcm", (void***)&pszHints);
+ if (err == 0)
+ {
+ char** pszHintCur = pszHints;
+ while (*pszHintCur != NULL)
+ {
+ char *pszDev = snd_device_name_get_hint(*pszHintCur, "NAME");
+ bool fSkip = !pszDev
+ || !RTStrICmp("null", pszDev);
+ if (fSkip)
+ {
+ if (pszDev)
+ free(pszDev);
+ pszHintCur++;
+ continue;
+ }
+
+ char *pszIOID = snd_device_name_get_hint(*pszHintCur, "IOID");
+ if (pszIOID)
+ {
+#if 0
+ if (!RTStrICmp("input", pszIOID))
+
+ else if (!RTStrICmp("output", pszIOID))
+#endif
+ }
+ else /* NULL means bidirectional, input + output. */
+ {
+ }
+
+ LogRel2(("ALSA: Found %s device: %s\n", pszIOID ? RTStrToLower(pszIOID) : "bidirectional", pszDev));
+
+ /* Special case for ALSAAudio. */
+ if ( pszDev
+ && RTStrIStr("pulse", pszDev) != NULL)
+ LogRel2(("ALSA: ALSAAudio plugin in use\n"));
+
+ if (pszIOID)
+ free(pszIOID);
+
+ if (pszDev)
+ free(pszDev);
+
+ pszHintCur++;
+ }
+
+ snd_device_name_free_hint((void **)pszHints);
+ pszHints = NULL;
+ }
+ else
+ LogRel2(("ALSA: Error enumerating PCM devices: %Rrc (%d)\n", RTErrConvertFromErrno(err), err));
+
+ /* 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,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostALSAAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostALSAAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = alsaCreateStreamIn (pStreamALSA, pCfgReq, pCfgAcq);
+ else
+ rc = alsaCreateStreamOut(pStreamALSA, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamALSA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamALSA->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = alsaDestroyStreamIn(pStreamALSA);
+ else
+ rc = alsaDestroyStreamOut(pStreamALSA);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamALSA->pCfg);
+ pStreamALSA->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ if (!pStreamALSA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamALSA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = alsaControlStreamIn (pStreamALSA, enmStreamCmd);
+ else
+ rc = alsaControlStreamOut(pStreamALSA, enmStreamCmd);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostALSAAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ uint32_t cbAvail = 0;
+
+ snd_pcm_sframes_t cFramesAvail;
+ int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail);
+ if (RT_SUCCESS(rc))
+ cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostALSAAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ uint32_t cbAvail = 0;
+
+ snd_pcm_sframes_t cFramesAvail;
+ int rc = alsaStreamGetAvail(pStreamALSA->phPCM, &cFramesAvail);
+ if (RT_SUCCESS(rc))
+ cbAvail = PDMAUDIOSTREAMCFG_F2B(pStreamALSA->pCfg, cFramesAvail);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHostALSAStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, 0);
+
+ PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+
+ snd_pcm_sframes_t cfDelay = 0;
+ snd_pcm_state_t enmState = snd_pcm_state(pStreamALSA->phPCM);
+
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pStreamALSA->pCfg);
+ if (pStreamALSA->pCfg->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. */
+ int rc2 = snd_pcm_delay(pStreamALSA->phPCM, &cfDelay);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ /* Make sure to check the stream's status.
+ * If it's anything but SND_PCM_STATE_RUNNING, the delay is meaningless and therefore 0. */
+ if (enmState != SND_PCM_STATE_RUNNING)
+ cfDelay = 0;
+ }
+
+ /* Note: For input streams we never have pending data left. */
+
+ Log2Func(("cfDelay=%RI32, enmState=%d, rc=%d\n", cfDelay, enmState, rc));
+
+ return DrvAudioHlpFramesToBytes(cfDelay, &pStreamALSA->pCfg->Props);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostALSAAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostALSAAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do here for ALSA. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostALSAAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/**
+ * Construct a DirectSound Audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
+ LogRel(("Audio: Initializing ALSA driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostALSAAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostALSAAudio);
+ pThis->IHostAudio.pfnStreamGetPending = drvHostALSAStreamGetPending;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Char 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(DRVHOSTALSAAUDIO),
+ /* pfnConstruct */
+ drvHostAlsaAudioConstruct,
+ /* 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/DrvHostCoreAudio.cpp b/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp
new file mode 100644
index 00000000..45da4242
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp
@@ -0,0 +1,2715 @@
+/* $Id: DrvHostCoreAudio.cpp $ */
+/** @file
+ * VBox audio devices - Mac OS X CoreAudio audio driver.
+ */
+
+/*
+ * Copyright (C) 2010-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+
+#include "DrvAudio.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 <CoreAudio/CoreAudio.h>
+#include <CoreServices/CoreServices.h>
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioConverter.h>
+#include <AudioToolbox/AudioToolbox.h>
+
+
+
+/* Enables utilizing the Core Audio converter unit for converting
+ * input / output from/to our requested formats. That might be more
+ * performant than using our own routines later down the road. */
+/** @todo Needs more investigation and testing first before enabling. */
+//# define VBOX_WITH_AUDIO_CA_CONVERTER
+
+/** @todo
+ * - Maybe make sure the threads are immediately stopped if playing/recording stops.
+ */
+
+/*
+ * Most of this is based on:
+ * http://developer.apple.com/mac/library/technotes/tn2004/tn2097.html
+ * http://developer.apple.com/mac/library/technotes/tn2002/tn2091.html
+ * http://developer.apple.com/mac/library/qa/qa2007/qa1533.html
+ * http://developer.apple.com/mac/library/qa/qa2001/qa1317.html
+ * http://developer.apple.com/mac/library/documentation/AudioUnit/Reference/AUComponentServicesReference/Reference/reference.html
+ */
+
+/* Prototypes needed for COREAUDIODEVICE. */
+struct DRVHOSTCOREAUDIO;
+typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
+
+/**
+ * Structure for holding Core Audio-specific device data.
+ * This data then lives in the pvData part of the PDMAUDIODEVICE struct.
+ */
+typedef struct COREAUDIODEVICEDATA
+{
+ /** Pointer to driver instance this device is bound to. */
+ PDRVHOSTCOREAUDIO pDrv;
+ /** The audio device ID of the currently used device (UInt32 typedef). */
+ AudioDeviceID deviceID;
+ /** The device' UUID. */
+ CFStringRef UUID;
+ /** List of attached (native) Core Audio streams attached to this device. */
+ RTLISTANCHOR lstStreams;
+} COREAUDIODEVICEDATA, *PCOREAUDIODEVICEDATA;
+
+/**
+ * Host Coreaudio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTCOREAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Critical section to serialize access. */
+ RTCRITSECT CritSect;
+ /** Current (last reported) device enumeration. */
+ PDMAUDIODEVICEENUM Devices;
+ /** Pointer to the currently used input device in the device enumeration.
+ * Can be NULL if none assigned. */
+ PPDMAUDIODEVICE pDefaultDevIn;
+ /** Pointer to the currently used output device in the device enumeration.
+ * Can be NULL if none assigned. */
+ PPDMAUDIODEVICE pDefaultDevOut;
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /** Callback function to the upper driver.
+ * Can be NULL if not being used / registered. */
+ PFNPDMHOSTAUDIOCALLBACK pfnCallback;
+#endif
+} DRVHOSTCOREAUDIO, *PDRVHOSTCOREAUDIO;
+
+/** Converts a pointer to DRVHOSTCOREAUDIO::IHostAudio to a PDRVHOSTCOREAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface) RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio)
+
+/**
+ * Structure for holding a Core Audio unit
+ * and its data.
+ */
+typedef struct COREAUDIOUNIT
+{
+ /** Pointer to the device this audio unit is bound to.
+ * Can be NULL if not bound to a device (anymore). */
+ PPDMAUDIODEVICE pDevice;
+ /** The actual audio unit object. */
+ AudioUnit audioUnit;
+ /** Stream description for using with VBox:
+ * - When using this audio unit for input (capturing), this format states
+ * the unit's output format.
+ * - When using this audio unit for output (playback), this format states
+ * the unit's input format. */
+ AudioStreamBasicDescription streamFmt;
+} COREAUDIOUNIT, *PCOREAUDIOUNIT;
+
+/*******************************************************************************
+ *
+ * Helper function section
+ *
+ ******************************************************************************/
+
+/* Move these down below the internal function prototypes... */
+
+static void coreAudioPrintASBD(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
+{
+ char pszSampleRate[32];
+ LogRel2(("CoreAudio: %s description:\n", pszDesc));
+ LogRel2(("CoreAudio:\tFormat ID: %RU32 (%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:\tFlags: %RU32", 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"));
+ snprintf(pszSampleRate, 32, "%.2f", (float)pASBD->mSampleRate); /** @todo r=andy Use RTStrPrint*. */
+ LogRel2(("CoreAudio:\tSampleRate : %s\n", pszSampleRate));
+ LogRel2(("CoreAudio:\tChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
+ LogRel2(("CoreAudio:\tFramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
+ LogRel2(("CoreAudio:\tBitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
+ LogRel2(("CoreAudio:\tBytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
+ LogRel2(("CoreAudio:\tBytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
+}
+
+static void coreAudioPCMPropsToASBD(PDMAUDIOPCMPROPS *pPCMProps, AudioStreamBasicDescription *pASBD)
+{
+ AssertPtrReturnVoid(pPCMProps);
+ AssertPtrReturnVoid(pASBD);
+
+ RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
+
+ pASBD->mFormatID = kAudioFormatLinearPCM;
+ pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
+ pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
+ pASBD->mSampleRate = (Float64)pPCMProps->uHz;
+ pASBD->mChannelsPerFrame = pPCMProps->cChannels;
+ pASBD->mBitsPerChannel = pPCMProps->cBytes * 8;
+ if (pPCMProps->fSigned)
+ pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
+ pASBD->mBytesPerFrame = pASBD->mChannelsPerFrame * (pASBD->mBitsPerChannel / 8);
+ pASBD->mBytesPerPacket = pASBD->mFramesPerPacket * pASBD->mBytesPerFrame;
+}
+
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+static int coreAudioASBDToStreamCfg(AudioStreamBasicDescription *pASBD, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pASBD, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pCfg, VERR_INVALID_PARAMETER);
+
+ pCfg->Props.cChannels = pASBD->mChannelsPerFrame;
+ pCfg->Props.uHz = (uint32_t)pASBD->mSampleRate;
+ pCfg->Props.cBytes = pASBD->mBitsPerChannel / 8;
+ pCfg->Props.fSigned = RT_BOOL(pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger);
+ pCfg->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfg->Props.cBytes, pCfg->Props.cChannels);
+
+ return VINF_SUCCESS;
+}
+#endif /* !VBOX_WITH_AUDIO_CALLBACKS */
+
+#if 0 /* unused */
+static int coreAudioCFStringToCString(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 coreAudioDeviceUIDtoID(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 propAdr = { kAudioHardwarePropertyDeviceForUID, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ UInt32 uSize = sizeof(AudioValueTranslation);
+ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 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 */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** @name Initialization status indicator used for the recreation of the AudioUnits.
+ *
+ * Global structures section
+ *
+ ******************************************************************************/
+
+/**
+ * Enumeration for a Core Audio stream status.
+ */
+typedef enum COREAUDIOSTATUS
+{
+ /** The device is uninitialized. */
+ COREAUDIOSTATUS_UNINIT = 0,
+ /** The device is currently initializing. */
+ COREAUDIOSTATUS_IN_INIT,
+ /** The device is initialized. */
+ COREAUDIOSTATUS_INIT,
+ /** The device is currently uninitializing. */
+ COREAUDIOSTATUS_IN_UNINIT,
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ /** The device has to be reinitialized.
+ * Note: Only needed if VBOX_WITH_AUDIO_CALLBACKS is not defined, as otherwise
+ * the Audio Connector will take care of this as soon as this backend
+ * tells it to do so via the provided audio callback. */
+ COREAUDIOSTATUS_REINIT,
+#endif
+ /** The usual 32-bit hack. */
+ COREAUDIOSTATUS_32BIT_HACK = 0x7fffffff
+} COREAUDIOSTATUS, *PCOREAUDIOSTATUS;
+
+#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
+ /* Error code which indicates "End of data" */
+ static const OSStatus caConverterEOFDErr = 0x656F6664; /* 'eofd' */
+#endif
+
+/* Prototypes needed for COREAUDIOSTREAMCBCTX. */
+struct COREAUDIOSTREAM;
+typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
+
+/**
+ * Structure for keeping a conversion callback context.
+ * This is needed when using an audio converter during input/output processing.
+ */
+typedef struct COREAUDIOCONVCBCTX
+{
+ /** Pointer to the stream this context is bound to. */
+ PCOREAUDIOSTREAM pStream;
+ /** Source stream description. */
+ AudioStreamBasicDescription asbdSrc;
+ /** Destination stream description. */
+ AudioStreamBasicDescription asbdDst;
+ /** Pointer to native buffer list used for rendering the source audio data into. */
+ AudioBufferList *pBufLstSrc;
+ /** Total packet conversion count. */
+ UInt32 uPacketCnt;
+ /** Current packet conversion index. */
+ UInt32 uPacketIdx;
+ /** Error count, for limiting the logging. */
+ UInt32 cErrors;
+} COREAUDIOCONVCBCTX, *PCOREAUDIOCONVCBCTX;
+
+/**
+ * Structure for keeping the input stream specifics.
+ */
+typedef struct COREAUDIOSTREAMIN
+{
+#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
+ /** The audio converter if necessary. NULL if no converter is being used. */
+ AudioConverterRef ConverterRef;
+ /** Callback context for the audio converter. */
+ COREAUDIOCONVCBCTX convCbCtx;
+#endif
+ /** The ratio between the device & the stream sample rate. */
+ Float64 sampleRatio;
+} COREAUDIOSTREAMIN, *PCOREAUDIOSTREAMIN;
+
+/**
+ * Structure for keeping the output stream specifics.
+ */
+typedef struct COREAUDIOSTREAMOUT
+{
+ /** Nothing here yet. */
+} COREAUDIOSTREAMOUT, *PCOREAUDIOSTREAMOUT;
+
+/**
+ * Structure for maintaining a Core Audio stream.
+ */
+typedef struct COREAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Stream-specific data, depending on the stream type. */
+ union
+ {
+ COREAUDIOSTREAMIN In;
+ COREAUDIOSTREAMOUT Out;
+ };
+ /** List node for the device's stream list. */
+ RTLISTNODE Node;
+ /** Pointer to driver instance this stream is bound to. */
+ PDRVHOSTCOREAUDIO pDrv;
+ /** The stream's thread handle for maintaining the audio queue. */
+ RTTHREAD hThread;
+ /** Flag indicating to start a stream's data processing. */
+ bool fRun;
+ /** Whether the stream is in a running (active) state or not.
+ * For playback streams this means that audio data can be (or is being) played,
+ * for capturing streams this means that audio data is being captured (if available). */
+ bool fIsRunning;
+ /** Thread shutdown indicator. */
+ bool fShutdown;
+ /** Critical section for serializing access between thread + callbacks. */
+ RTCRITSECT CritSect;
+ /** The actual audio queue being used. */
+ AudioQueueRef audioQueue;
+ /** The audio buffers which are used with the above audio queue. */
+ AudioQueueBufferRef audioBuffer[2];
+ /** The acquired (final) audio format for this stream. */
+ AudioStreamBasicDescription asbdStream;
+ /** The audio unit for this stream. */
+ COREAUDIOUNIT Unit;
+ /** Initialization status tracker. Used when some of the device parameters
+ * or the device itself is changed during the runtime. */
+ volatile uint32_t enmStatus;
+ /** An internal ring buffer for transferring data from/to the rendering callbacks. */
+ PRTCIRCBUF pCircBuf;
+} COREAUDIOSTREAM, *PCOREAUDIOSTREAM;
+
+static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev);
+#endif
+static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream);
+
+static int coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd);
+
+static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
+static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev);
+static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv);
+
+static OSStatus coreAudioDevPropChgCb(AudioObjectID propertyID, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void *pvUser);
+
+static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
+static void coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer, const AudioTimeStamp *pAudioTS, UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc);
+static void coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer);
+
+#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
+/**
+ * Initializes a conversion callback context.
+ *
+ * @return IPRT status code.
+ * @param pConvCbCtx Conversion callback context to initialize.
+ * @param pStream Pointer to stream to use.
+ * @param pASBDSrc Input (source) stream description to use.
+ * @param pASBDDst Output (destination) stream description to use.
+ */
+static int coreAudioInitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx, PCOREAUDIOSTREAM pStream,
+ AudioStreamBasicDescription *pASBDSrc, AudioStreamBasicDescription *pASBDDst)
+{
+ AssertPtrReturn(pConvCbCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pASBDSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pASBDDst, VERR_INVALID_POINTER);
+
+#ifdef DEBUG
+ coreAudioPrintASBD("CbCtx: Src", pASBDSrc);
+ coreAudioPrintASBD("CbCtx: Dst", pASBDDst);
+#endif
+
+ pConvCbCtx->pStream = pStream;
+
+ memcpy(&pConvCbCtx->asbdSrc, pASBDSrc, sizeof(AudioStreamBasicDescription));
+ memcpy(&pConvCbCtx->asbdDst, pASBDDst, sizeof(AudioStreamBasicDescription));
+
+ pConvCbCtx->pBufLstSrc = NULL;
+ pConvCbCtx->cErrors = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Uninitializes a conversion callback context.
+ *
+ * @return IPRT status code.
+ * @param pConvCbCtx Conversion callback context to uninitialize.
+ */
+static void coreAudioUninitConvCbCtx(PCOREAUDIOCONVCBCTX pConvCbCtx)
+{
+ AssertPtrReturnVoid(pConvCbCtx);
+
+ pConvCbCtx->pStream = NULL;
+
+ RT_ZERO(pConvCbCtx->asbdSrc);
+ RT_ZERO(pConvCbCtx->asbdDst);
+
+ pConvCbCtx->pBufLstSrc = NULL;
+ pConvCbCtx->cErrors = 0;
+}
+#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
+
+
+/**
+ * Does a (re-)enumeration of the host's playback + recording devices.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param enmUsage Which devices to enumerate.
+ * @param pDevEnm Where to store the enumerated devices.
+ */
+static int coreAudioDevicesEnumerate(PDRVHOSTCOREAUDIO pThis, PDMAUDIODIR enmUsage, PPDMAUDIODEVICEENUM pDevEnm)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDevEnm, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ do
+ {
+ AudioDeviceID defaultDeviceID = kAudioDeviceUnknown;
+
+ /* Fetch the default audio device currently in use. */
+ AudioObjectPropertyAddress propAdrDefaultDev = { enmUsage == PDMAUDIODIR_IN
+ ? kAudioHardwarePropertyDefaultInputDevice
+ : kAudioHardwarePropertyDefaultOutputDevice,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+ UInt32 uSize = sizeof(defaultDeviceID);
+ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDefaultDev, 0, NULL, &uSize, &defaultDeviceID);
+ if (err != noErr)
+ {
+ LogRel(("CoreAudio: Unable to determine default %s device (%RI32)\n",
+ enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback", err));
+ return VERR_NOT_FOUND;
+ }
+
+ if (defaultDeviceID == kAudioDeviceUnknown)
+ {
+ LogFunc(("No default %s device found\n", enmUsage == PDMAUDIODIR_IN ? "capturing" : "playback"));
+ /* Keep going. */
+ }
+
+ AudioObjectPropertyAddress propAdrDevList = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize);
+ if (err != kAudioHardwareNoError)
+ break;
+
+ AudioDeviceID *pDevIDs = (AudioDeviceID *)alloca(uSize);
+ if (pDevIDs == NULL)
+ break;
+
+ err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdrDevList, 0, NULL, &uSize, pDevIDs);
+ if (err != kAudioHardwareNoError)
+ break;
+
+ rc = DrvAudioHlpDeviceEnumInit(pDevEnm);
+ if (RT_FAILURE(rc))
+ break;
+
+ UInt16 cDevices = uSize / sizeof(AudioDeviceID);
+
+ PPDMAUDIODEVICE pDev = NULL;
+ for (UInt16 i = 0; i < cDevices; i++)
+ {
+ if (pDev) /* Some (skipped) device to clean up first? */
+ DrvAudioHlpDeviceFree(pDev);
+
+ pDev = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
+ if (!pDev)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Set usage. */
+ pDev->enmUsage = enmUsage;
+
+ /* Init backend-specific device data. */
+ PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ AssertPtr(pDevData);
+ coreAudioDeviceDataInit(pDevData, pDevIDs[i], enmUsage == PDMAUDIODIR_IN, pThis);
+
+ /* Check if the device is valid. */
+ AudioDeviceID curDevID = pDevData->deviceID;
+
+ /* Is the device the default device? */
+ if (curDevID == defaultDeviceID)
+ pDev->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
+
+ AudioObjectPropertyAddress propAddrCfg = { kAudioDevicePropertyStreamConfiguration,
+ enmUsage == PDMAUDIODIR_IN
+ ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster };
+
+ err = AudioObjectGetPropertyDataSize(curDevID, &propAddrCfg, 0, NULL, &uSize);
+ if (err != noErr)
+ continue;
+
+ AudioBufferList *pBufList = (AudioBufferList *)RTMemAlloc(uSize);
+ if (!pBufList)
+ continue;
+
+ err = AudioObjectGetPropertyData(curDevID, &propAddrCfg, 0, NULL, &uSize, pBufList);
+ if (err == noErr)
+ {
+ for (UInt32 a = 0; a < pBufList->mNumberBuffers; a++)
+ {
+ if (enmUsage == PDMAUDIODIR_IN)
+ pDev->cMaxInputChannels += pBufList->mBuffers[a].mNumberChannels;
+ else if (enmUsage == PDMAUDIODIR_OUT)
+ pDev->cMaxOutputChannels += pBufList->mBuffers[a].mNumberChannels;
+ }
+ }
+
+ if (pBufList)
+ {
+ RTMemFree(pBufList);
+ pBufList = NULL;
+ }
+
+ /* Check if the device is valid, e.g. has any input/output channels according to its usage. */
+ if ( enmUsage == PDMAUDIODIR_IN
+ && !pDev->cMaxInputChannels)
+ {
+ continue;
+ }
+ else if ( enmUsage == PDMAUDIODIR_OUT
+ && !pDev->cMaxOutputChannels)
+ {
+ continue;
+ }
+
+ /* Resolve the device's name. */
+ AudioObjectPropertyAddress propAddrName = { kAudioObjectPropertyName,
+ enmUsage == PDMAUDIODIR_IN
+ ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster };
+ uSize = sizeof(CFStringRef);
+ CFStringRef pcfstrName = NULL;
+
+ err = AudioObjectGetPropertyData(curDevID, &propAddrName, 0, NULL, &uSize, &pcfstrName);
+ if (err != kAudioHardwareNoError)
+ continue;
+
+ CFIndex uMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(pcfstrName), kCFStringEncodingUTF8) + 1;
+ if (uMax)
+ {
+ char *pszName = (char *)RTStrAlloc(uMax);
+ if ( pszName
+ && CFStringGetCString(pcfstrName, pszName, uMax, kCFStringEncodingUTF8))
+ {
+ RTStrPrintf(pDev->szName, sizeof(pDev->szName), "%s", pszName);
+ }
+
+ LogFunc(("Device '%s': %RU32\n", pszName, curDevID));
+
+ if (pszName)
+ {
+ RTStrFree(pszName);
+ pszName = NULL;
+ }
+ }
+
+ CFRelease(pcfstrName);
+
+ /* Check if the device is alive for the intended usage. */
+ AudioObjectPropertyAddress propAddrAlive = { kAudioDevicePropertyDeviceIsAlive,
+ enmUsage == PDMAUDIODIR_IN
+ ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster };
+
+ UInt32 uAlive = 0;
+ uSize = sizeof(uAlive);
+
+ err = AudioObjectGetPropertyData(curDevID, &propAddrAlive, 0, NULL, &uSize, &uAlive);
+ if ( (err == noErr)
+ && !uAlive)
+ {
+ pDev->fFlags |= PDMAUDIODEV_FLAGS_DEAD;
+ }
+
+ /* Check if the device is being hogged by someone else. */
+ AudioObjectPropertyAddress propAddrHogged = { kAudioDevicePropertyHogMode,
+ kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
+
+ pid_t pid = 0;
+ uSize = sizeof(pid);
+
+ err = AudioObjectGetPropertyData(curDevID, &propAddrHogged, 0, NULL, &uSize, &pid);
+ if ( (err == noErr)
+ && (pid != -1))
+ {
+ pDev->fFlags |= PDMAUDIODEV_FLAGS_LOCKED;
+ }
+
+ /* Add the device to the enumeration. */
+ rc = DrvAudioHlpDeviceEnumAdd(pDevEnm, pDev);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* NULL device pointer because it's now part of the device enumeration. */
+ pDev = NULL;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ DrvAudioHlpDeviceFree(pDev);
+ pDev = NULL;
+ }
+
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef DEBUG
+ LogFunc(("Devices for pDevEnm=%p, enmUsage=%RU32:\n", pDevEnm, enmUsage));
+ DrvAudioHlpDeviceEnumPrint("Core Audio", pDevEnm);
+#endif
+ }
+ else
+ DrvAudioHlpDeviceEnumFree(pDevEnm);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Checks if an audio device with a specific device ID is in the given device
+ * enumeration or not.
+ *
+ * @retval true if the node is the last element in the list.
+ * @retval false otherwise.
+ *
+ * @param pEnmSrc Device enumeration to search device ID in.
+ * @param deviceID Device ID to search.
+ */
+bool coreAudioDevicesHasDevice(PPDMAUDIODEVICEENUM pEnmSrc, AudioDeviceID deviceID)
+{
+ PPDMAUDIODEVICE pDevSrc;
+ RTListForEach(&pEnmSrc->lstDevices, pDevSrc, PDMAUDIODEVICE, Node)
+ {
+ PCOREAUDIODEVICEDATA pDevSrcData = (PCOREAUDIODEVICEDATA)pDevSrc->pvData;
+ AssertPtr(pDevSrcData);
+
+ if (pDevSrcData->deviceID == deviceID)
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Enumerates all host devices and builds a final device enumeration list, consisting
+ * of (duplex) input and output devices.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pEnmDst Where to store the device enumeration list.
+ */
+int coreAudioDevicesEnumerateAll(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICEENUM pEnmDst)
+{
+ PDMAUDIODEVICEENUM devEnmIn;
+ int rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_IN, &devEnmIn);
+ if (RT_SUCCESS(rc))
+ {
+ PDMAUDIODEVICEENUM devEnmOut;
+ rc = coreAudioDevicesEnumerate(pThis, PDMAUDIODIR_OUT, &devEnmOut);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Build up the final device enumeration, based on the input and output device lists
+ * just enumerated.
+ *
+ * Also make sure to handle duplex devices, that is, devices which act as input and output
+ * at the same time.
+ */
+
+ rc = DrvAudioHlpDeviceEnumInit(pEnmDst);
+ if (RT_SUCCESS(rc))
+ {
+ PPDMAUDIODEVICE pDevSrcIn;
+ RTListForEach(&devEnmIn.lstDevices, pDevSrcIn, PDMAUDIODEVICE, Node)
+ {
+ PCOREAUDIODEVICEDATA pDevSrcInData = (PCOREAUDIODEVICEDATA)pDevSrcIn->pvData;
+ AssertPtr(pDevSrcInData);
+
+ PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
+ if (!pDevDst)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData;
+ AssertPtr(pDevDstData);
+ coreAudioDeviceDataInit(pDevDstData, pDevSrcInData->deviceID, true /* fIsInput */, pThis);
+
+ RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcIn->szName);
+
+ pDevDst->enmUsage = PDMAUDIODIR_IN; /* Input device by default (simplex). */
+ pDevDst->cMaxInputChannels = pDevSrcIn->cMaxInputChannels;
+
+ /* Handle flags. */
+ if (pDevSrcIn->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
+ pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
+ /** @todo Handle hot plugging? */
+
+ /*
+ * Now search through the list of all found output devices and check if we found
+ * an output device with the same device ID as the currently handled input device.
+ *
+ * If found, this means we have to treat that device as a duplex device then.
+ */
+ PPDMAUDIODEVICE pDevSrcOut;
+ RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node)
+ {
+ PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData;
+ AssertPtr(pDevSrcOutData);
+
+ if (pDevSrcInData->deviceID == pDevSrcOutData->deviceID)
+ {
+ pDevDst->enmUsage = PDMAUDIODIR_ANY;
+ pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst);
+ }
+ else
+ {
+ DrvAudioHlpDeviceFree(pDevDst);
+ pDevDst = NULL;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * As a last step, add all remaining output devices which have not been handled in the loop above,
+ * that is, all output devices which operate in simplex mode.
+ */
+ PPDMAUDIODEVICE pDevSrcOut;
+ RTListForEach(&devEnmOut.lstDevices, pDevSrcOut, PDMAUDIODEVICE, Node)
+ {
+ PCOREAUDIODEVICEDATA pDevSrcOutData = (PCOREAUDIODEVICEDATA)pDevSrcOut->pvData;
+ AssertPtr(pDevSrcOutData);
+
+ if (coreAudioDevicesHasDevice(pEnmDst, pDevSrcOutData->deviceID))
+ continue; /* Already in our list, skip. */
+
+ PPDMAUDIODEVICE pDevDst = DrvAudioHlpDeviceAlloc(sizeof(COREAUDIODEVICEDATA));
+ if (!pDevDst)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ PCOREAUDIODEVICEDATA pDevDstData = (PCOREAUDIODEVICEDATA)pDevDst->pvData;
+ AssertPtr(pDevDstData);
+ coreAudioDeviceDataInit(pDevDstData, pDevSrcOutData->deviceID, false /* fIsInput */, pThis);
+
+ RTStrCopy(pDevDst->szName, sizeof(pDevDst->szName), pDevSrcOut->szName);
+
+ pDevDst->enmUsage = PDMAUDIODIR_OUT;
+ pDevDst->cMaxOutputChannels = pDevSrcOut->cMaxOutputChannels;
+
+ pDevDstData->deviceID = pDevSrcOutData->deviceID;
+
+ /* Handle flags. */
+ if (pDevSrcOut->fFlags & PDMAUDIODEV_FLAGS_DEFAULT)
+ pDevDst->fFlags |= PDMAUDIODEV_FLAGS_DEFAULT;
+ /** @todo Handle hot plugging? */
+
+ rc = DrvAudioHlpDeviceEnumAdd(pEnmDst, pDevDst);
+ if (RT_FAILURE(rc))
+ {
+ DrvAudioHlpDeviceFree(pDevDst);
+ break;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ DrvAudioHlpDeviceEnumFree(pEnmDst);
+ }
+
+ DrvAudioHlpDeviceEnumFree(&devEnmOut);
+ }
+
+ DrvAudioHlpDeviceEnumFree(&devEnmIn);
+ }
+
+#ifdef DEBUG
+ if (RT_SUCCESS(rc))
+ DrvAudioHlpDeviceEnumPrint("Core Audio (Final)", pEnmDst);
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Initializes a Core Audio-specific device data structure.
+ *
+ * @returns IPRT status code.
+ * @param pDevData Device data structure to initialize.
+ * @param deviceID Core Audio device ID to assign this structure to.
+ * @param fIsInput Whether this is an input device or not.
+ * @param pDrv Driver instance to use.
+ */
+static void coreAudioDeviceDataInit(PCOREAUDIODEVICEDATA pDevData, AudioDeviceID deviceID, bool fIsInput, PDRVHOSTCOREAUDIO pDrv)
+{
+ AssertPtrReturnVoid(pDevData);
+ AssertPtrReturnVoid(pDrv);
+
+ pDevData->deviceID = deviceID;
+ pDevData->pDrv = pDrv;
+
+ /* Get the device UUID. */
+ AudioObjectPropertyAddress propAdrDevUUID = { kAudioDevicePropertyDeviceUID,
+ fIsInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster };
+ UInt32 uSize = sizeof(pDevData->UUID);
+ OSStatus err = AudioObjectGetPropertyData(pDevData->deviceID, &propAdrDevUUID, 0, NULL, &uSize, &pDevData->UUID);
+ if (err != noErr)
+ LogRel(("CoreAudio: Failed to retrieve device UUID for device %RU32 (%RI32)\n", deviceID, err));
+
+ RTListInit(&pDevData->lstStreams);
+}
+
+
+/**
+ * Propagates an audio device status to all its connected Core Audio streams.
+ *
+ * @return IPRT status code.
+ * @param pDev Audio device to propagate status for.
+ * @param enmSts Status to propagate.
+ */
+static int coreAudioDevicePropagateStatus(PPDMAUDIODEVICE pDev, COREAUDIOSTATUS enmSts)
+{
+ AssertPtrReturn(pDev, VERR_INVALID_POINTER);
+
+ PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ AssertPtrReturn(pDevData, VERR_INVALID_POINTER);
+
+ /* Sanity. */
+ AssertPtr(pDevData->pDrv);
+
+ LogFlowFunc(("pDev=%p, pDevData=%p, enmSts=%RU32\n", pDev, pDevData, enmSts));
+
+ PCOREAUDIOSTREAM pCAStream;
+ RTListForEach(&pDevData->lstStreams, pCAStream, COREAUDIOSTREAM, Node)
+ {
+ LogFlowFunc(("pCAStream=%p\n", pCAStream));
+
+ /* We move the reinitialization to the next output event.
+ * This make sure this thread isn't blocked and the
+ * reinitialization is done when necessary only. */
+ ASMAtomicXchgU32(&pCAStream->enmStatus, enmSts);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(OSStatus) coreAudioDeviceStateChangedCb(AudioObjectID propertyID,
+ UInt32 nAddresses,
+ const AudioObjectPropertyAddress properties[],
+ void *pvUser)
+{
+ RT_NOREF(propertyID, nAddresses, properties);
+
+ LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser));
+
+ PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser;
+ AssertPtr(pDev);
+
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ AssertPtrReturn(pData, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = pData->pDrv;
+ AssertPtr(pThis);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ UInt32 uAlive = 1;
+ UInt32 uSize = sizeof(UInt32);
+
+ AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ AudioDeviceID deviceID = pData->deviceID;
+
+ OSStatus err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &uSize, &uAlive);
+
+ bool fIsDead = false;
+
+ if (err == kAudioHardwareBadDeviceError)
+ fIsDead = true; /* Unplugged. */
+ else if ((err == kAudioHardwareNoError) && (!RT_BOOL(uAlive)))
+ fIsDead = true; /* Something else happened. */
+
+ if (fIsDead)
+ {
+ LogRel2(("CoreAudio: Device '%s' stopped functioning\n", pDev->szName));
+
+ /* Mark device as dead. */
+ rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_UNINIT);
+ AssertRC(rc2);
+ }
+
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+ return noErr;
+}
+
+/* Callback for getting notified when the default recording/playback device has been changed. */
+static DECLCALLBACK(OSStatus) coreAudioDefaultDeviceChangedCb(AudioObjectID propertyID,
+ UInt32 nAddresses,
+ const AudioObjectPropertyAddress properties[],
+ void *pvUser)
+{
+ RT_NOREF(propertyID, nAddresses);
+
+ LogFlowFunc(("propertyID=%u, nAddresses=%u, pvUser=%p\n", propertyID, nAddresses, pvUser));
+
+ PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
+ AssertPtr(pThis);
+
+ int rc2 = RTCritSectEnter(&pThis->CritSect);
+ AssertRC(rc2);
+
+ for (UInt32 idxAddress = 0; idxAddress < nAddresses; idxAddress++)
+ {
+ PPDMAUDIODEVICE pDev = NULL;
+
+ /*
+ * Check if the default input / output device has been changed.
+ */
+ const AudioObjectPropertyAddress *pProperty = &properties[idxAddress];
+ switch (pProperty->mSelector)
+ {
+ case kAudioHardwarePropertyDefaultInputDevice:
+ LogFlowFunc(("kAudioHardwarePropertyDefaultInputDevice\n"));
+ pDev = pThis->pDefaultDevIn;
+ break;
+
+ case kAudioHardwarePropertyDefaultOutputDevice:
+ LogFlowFunc(("kAudioHardwarePropertyDefaultOutputDevice\n"));
+ pDev = pThis->pDefaultDevOut;
+ break;
+
+ default:
+ /* Skip others. */
+ break;
+ }
+
+ LogFlowFunc(("pDev=%p\n", pDev));
+
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ if (pDev)
+ {
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ AssertPtr(pData);
+
+ /* This listener is called on every change of the hardware
+ * device. So check if the default device has really changed. */
+ UInt32 uSize = sizeof(AudioDeviceID);
+ UInt32 uResp = 0;
+
+ OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, pProperty, 0, NULL, &uSize, &uResp);
+ if (err == noErr)
+ {
+ if (pData->deviceID != uResp) /* Has the device ID changed? */
+ {
+ rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT);
+ AssertRC(rc2);
+ }
+ }
+ }
+#endif /* VBOX_WITH_AUDIO_CALLBACKS */
+ }
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ PFNPDMHOSTAUDIOCALLBACK pfnCallback = pThis->pfnCallback;
+#endif
+
+ /* Make sure to leave the critical section before calling the callback. */
+ rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ if (pfnCallback)
+ /* Ignore rc */ pfnCallback(pThis->pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0);
+#endif
+
+ return noErr;
+}
+
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * Re-initializes a Core Audio stream with a specific audio device and stream configuration.
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance.
+ * @param pCAStream Audio stream to re-initialize.
+ * @param pDev Audio device to use for re-initialization.
+ * @param pCfg Stream configuration to use for re-initialization.
+ */
+static int coreAudioStreamReinitEx(PDRVHOSTCOREAUDIO pThis,
+ PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev, PPDMAUDIOSTREAMCFG pCfg)
+{
+ LogFunc(("pCAStream=%p\n", pCAStream));
+
+ int rc = coreAudioStreamUninit(pCAStream);
+ if (RT_SUCCESS(rc))
+ {
+ rc = coreAudioStreamInit(pCAStream, pThis, pDev);
+ if (RT_SUCCESS(rc))
+ {
+ rc = coreAudioStreamInitQueue(pCAStream, pCfg /* pCfgReq */, NULL /* pCfgAcq */);
+ if (RT_SUCCESS(rc))
+ rc = coreAudioStreamControl(pCAStream->pDrv, pCAStream, PDMAUDIOSTREAMCMD_ENABLE);
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = coreAudioStreamUninit(pCAStream);
+ AssertRC(rc2);
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("CoreAudio: Unable to re-init stream: %Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Re-initializes a Core Audio stream with a specific audio device.
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance.
+ * @param pCAStream Audio stream to re-initialize.
+ * @param pDev Audio device to use for re-initialization.
+ */
+static int coreAudioStreamReinit(PDRVHOSTCOREAUDIO pThis, PCOREAUDIOSTREAM pCAStream, PPDMAUDIODEVICE pDev)
+{
+ int rc = coreAudioStreamUninit(pCAStream);
+ if (RT_SUCCESS(rc))
+ {
+ /* Use the acquired stream configuration from the former initialization to
+ * re-initialize the stream. */
+ PDMAUDIOSTREAMCFG CfgAcq;
+ rc = coreAudioASBDToStreamCfg(&pCAStream->Unit.streamFmt, &CfgAcq);
+ if (RT_SUCCESS(rc))
+ rc = coreAudioStreamReinitEx(pThis, pCAStream, pDev, &CfgAcq);
+ }
+
+ return rc;
+}
+#endif /* !VBOX_WITH_AUDIO_CALLBACKS */
+
+#ifdef VBOX_WITH_AUDIO_CA_CONVERTER
+/* Callback to convert audio input data from one format to another. */
+static DECLCALLBACK(OSStatus) coreAudioConverterCb(AudioConverterRef inAudioConverter,
+ UInt32 *ioNumberDataPackets,
+ AudioBufferList *ioData,
+ AudioStreamPacketDescription **ppASPD,
+ void *pvUser)
+{
+ RT_NOREF(inAudioConverter);
+
+ AssertPtrReturn(ioNumberDataPackets, caConverterEOFDErr);
+ AssertPtrReturn(ioData, caConverterEOFDErr);
+
+ PCOREAUDIOCONVCBCTX pConvCbCtx = (PCOREAUDIOCONVCBCTX)pvUser;
+ AssertPtr(pConvCbCtx);
+
+ /* Initialize values. */
+ ioData->mBuffers[0].mNumberChannels = 0;
+ ioData->mBuffers[0].mDataByteSize = 0;
+ ioData->mBuffers[0].mData = NULL;
+
+ if (ppASPD)
+ {
+ Log3Func(("Handling packet description not implemented\n"));
+ }
+ else
+ {
+ /** @todo Check converter ID? */
+
+ /** @todo Handled non-interleaved data by going through the full buffer list,
+ * not only through the first buffer like we do now. */
+ Log3Func(("ioNumberDataPackets=%RU32\n", *ioNumberDataPackets));
+
+ UInt32 cNumberDataPackets = *ioNumberDataPackets;
+ Assert(pConvCbCtx->uPacketIdx + cNumberDataPackets <= pConvCbCtx->uPacketCnt);
+
+ if (cNumberDataPackets)
+ {
+ AssertPtr(pConvCbCtx->pBufLstSrc);
+ Assert(pConvCbCtx->pBufLstSrc->mNumberBuffers == 1); /* Only one buffer for the source supported atm. */
+
+ AudioStreamBasicDescription *pSrcASBD = &pConvCbCtx->asbdSrc;
+ AudioBuffer *pSrcBuf = &pConvCbCtx->pBufLstSrc->mBuffers[0];
+
+ size_t cbOff = pConvCbCtx->uPacketIdx * pSrcASBD->mBytesPerPacket;
+
+ cNumberDataPackets = RT_MIN((pSrcBuf->mDataByteSize - cbOff) / pSrcASBD->mBytesPerPacket,
+ cNumberDataPackets);
+
+ void *pvAvail = (uint8_t *)pSrcBuf->mData + cbOff;
+ size_t cbAvail = RT_MIN(pSrcBuf->mDataByteSize - cbOff, cNumberDataPackets * pSrcASBD->mBytesPerPacket);
+
+ Log3Func(("cNumberDataPackets=%RU32, cbOff=%zu, cbAvail=%zu\n", cNumberDataPackets, cbOff, cbAvail));
+
+ /* Set input data for the converter to use.
+ * Note: For VBR (Variable Bit Rates) or interleaved data handling we need multiple buffers here. */
+ ioData->mNumberBuffers = 1;
+
+ ioData->mBuffers[0].mNumberChannels = pSrcBuf->mNumberChannels;
+ ioData->mBuffers[0].mDataByteSize = cbAvail;
+ ioData->mBuffers[0].mData = pvAvail;
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ RTFILE fh;
+ int rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileWrite(fh, pvAvail, cbAvail, NULL);
+ RTFileClose(fh);
+ }
+ else
+ AssertFailed();
+#endif
+ pConvCbCtx->uPacketIdx += cNumberDataPackets;
+ Assert(pConvCbCtx->uPacketIdx <= pConvCbCtx->uPacketCnt);
+
+ *ioNumberDataPackets = cNumberDataPackets;
+ }
+ }
+
+ Log3Func(("%RU32 / %RU32 -> ioNumberDataPackets=%RU32\n",
+ pConvCbCtx->uPacketIdx, pConvCbCtx->uPacketCnt, *ioNumberDataPackets));
+
+ return noErr;
+}
+#endif /* VBOX_WITH_AUDIO_CA_CONVERTER */
+
+
+/**
+ * Initializes a Core Audio stream.
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance.
+ * @param pCAStream Stream to initialize.
+ * @param pDev Audio device to use for this stream.
+ */
+static int coreAudioStreamInit(PCOREAUDIOSTREAM pCAStream, PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
+{
+ AssertPtrReturn(pCAStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDev, VERR_INVALID_POINTER);
+
+ Assert(pCAStream->Unit.pDevice == NULL); /* Make sure no device is assigned yet. */
+ AssertPtr(pDev->pvData);
+ Assert(pDev->cbData == sizeof(COREAUDIODEVICEDATA));
+
+#ifdef DEBUG
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ LogFunc(("pCAStream=%p, pDev=%p ('%s', ID=%RU32)\n", pCAStream, pDev, pDev->szName, pData->deviceID));
+#endif
+
+ pCAStream->Unit.pDevice = pDev;
+ pCAStream->pDrv = pThis;
+
+ return VINF_SUCCESS;
+}
+
+# define CA_BREAK_STMT(stmt) \
+ stmt; \
+ break;
+
+/**
+ * Thread for a Core Audio stream's audio queue handling.
+ * This thread is required per audio queue to pump data to/from the Core Audio stream and
+ * handling its callbacks.
+ *
+ * @returns IPRT status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser User argument.
+ */
+static DECLCALLBACK(int) coreAudioQueueThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
+ AssertPtr(pCAStream);
+ AssertPtr(pCAStream->pCfg);
+
+ const bool fIn = pCAStream->pCfg->enmDir == PDMAUDIODIR_IN;
+
+ LogFunc(("Thread started for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn));
+
+ /*
+ * Create audio queue.
+ */
+ OSStatus err;
+ if (fIn)
+ err = AudioQueueNewInput(&pCAStream->asbdStream, coreAudioInputQueueCb, pCAStream /* pvData */,
+ CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue);
+ else
+ err = AudioQueueNewOutput(&pCAStream->asbdStream, coreAudioOutputQueueCb, pCAStream /* pvData */,
+ CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &pCAStream->audioQueue);
+
+ if (err != noErr)
+ return VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ /*
+ * Assign device to queue.
+ */
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pCAStream->Unit.pDevice->pvData;
+ AssertPtr(pData);
+
+ UInt32 uSize = sizeof(pData->UUID);
+ err = AudioQueueSetProperty(pCAStream->audioQueue, kAudioQueueProperty_CurrentDevice, &pData->UUID, uSize);
+ if (err != noErr)
+ return VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ const size_t cbBufSize = DrvAudioHlpFramesToBytes(pCAStream->pCfg->Backend.cfPeriod, &pCAStream->pCfg->Props);
+
+ /*
+ * Allocate audio buffers.
+ */
+ for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
+ {
+ err = AudioQueueAllocateBuffer(pCAStream->audioQueue, cbBufSize, &pCAStream->audioBuffer[i]);
+ if (err != noErr)
+ break;
+ }
+
+ if (err != noErr)
+ return VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ /* Signal the main thread before entering the main loop. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Enter the main loop.
+ */
+ while (!ASMAtomicReadBool(&pCAStream->fShutdown))
+ {
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (fIn)
+ {
+ AudioQueueStop(pCAStream->audioQueue, 1);
+ }
+ else
+ {
+ AudioQueueStop(pCAStream->audioQueue, 0);
+ }
+
+ for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
+ {
+ if (pCAStream->audioBuffer[i])
+ AudioQueueFreeBuffer(pCAStream->audioQueue, pCAStream->audioBuffer[i]);
+ }
+
+ AudioQueueDispose(pCAStream->audioQueue, 1);
+
+ LogFunc(("Thread ended for pCAStream=%p, fIn=%RTbool\n", pCAStream, fIn));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Processes input data of an audio queue buffer and stores it into a Core Audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to store input data into.
+ * @param audioBuffer Audio buffer to process input data from.
+ */
+int coreAudioInputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer)
+{
+ PRTCIRCBUF pCircBuf = pCAStream->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ UInt8 *pvSrc = (UInt8 *)audioBuffer->mAudioData;
+ UInt8 *pvDst = NULL;
+
+ size_t cbWritten = 0;
+
+ size_t cbToWrite = audioBuffer->mAudioDataByteSize;
+ size_t cbLeft = RT_MIN(cbToWrite, RTCircBufFree(pCircBuf));
+
+ while (cbLeft)
+ {
+ /* Try to acquire the necessary block from the ring buffer. */
+ RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, (void **)&pvDst, &cbToWrite);
+
+ if (!cbToWrite)
+ break;
+
+ /* Copy the data from our ring buffer to the core audio buffer. */
+ memcpy((UInt8 *)pvDst, pvSrc + cbWritten, cbToWrite);
+
+ /* Release the read buffer, so it could be used for new data. */
+ RTCircBufReleaseWriteBlock(pCircBuf, cbToWrite);
+
+ cbWritten += cbToWrite;
+
+ Assert(cbLeft >= cbToWrite);
+ cbLeft -= cbToWrite;
+ }
+
+ Log3Func(("pCAStream=%p, cbBuffer=%RU32/%zu, cbWritten=%zu\n",
+ pCAStream, audioBuffer->mAudioDataByteSize, audioBuffer->mAudioDataBytesCapacity, cbWritten));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Input audio queue callback. Called whenever input data from the audio queue becomes available.
+ *
+ * @param pvUser User argument.
+ * @param audioQueue Audio queue to process input data from.
+ * @param audioBuffer Audio buffer to process input data from. Must be part of audio queue.
+ * @param pAudioTS Audio timestamp.
+ * @param cPacketDesc Number of packet descriptors.
+ * @param paPacketDesc Array of packet descriptors.
+ */
+static DECLCALLBACK(void) coreAudioInputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer,
+ const AudioTimeStamp *pAudioTS,
+ UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
+{
+ RT_NOREF(audioQueue, pAudioTS, cPacketDesc, paPacketDesc);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
+ AssertPtr(pCAStream);
+
+ int rc = RTCritSectEnter(&pCAStream->CritSect);
+ AssertRC(rc);
+
+ rc = coreAudioInputQueueProcBuffer(pCAStream, audioBuffer);
+ if (RT_SUCCESS(rc))
+ AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL);
+
+ rc = RTCritSectLeave(&pCAStream->CritSect);
+ AssertRC(rc);
+}
+
+/**
+ * Processes output data of a Core Audio stream into an audio queue buffer.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to process output data for.
+ * @param audioBuffer Audio buffer to store data into.
+ */
+int coreAudioOutputQueueProcBuffer(PCOREAUDIOSTREAM pCAStream, AudioQueueBufferRef audioBuffer)
+{
+ AssertPtr(pCAStream);
+
+ PRTCIRCBUF pCircBuf = pCAStream->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ size_t cbRead = 0;
+
+ UInt8 *pvSrc = NULL;
+ UInt8 *pvDst = (UInt8 *)audioBuffer->mAudioData;
+
+ size_t cbToRead = RT_MIN(RTCircBufUsed(pCircBuf), audioBuffer->mAudioDataBytesCapacity);
+ size_t cbLeft = cbToRead;
+
+ while (cbLeft)
+ {
+ /* Try to acquire the necessary block from the ring buffer. */
+ RTCircBufAcquireReadBlock(pCircBuf, cbLeft, (void **)&pvSrc, &cbToRead);
+
+ if (cbToRead)
+ {
+ /* Copy the data from our ring buffer to the core audio buffer. */
+ memcpy((UInt8 *)pvDst + cbRead, pvSrc, cbToRead);
+ }
+
+ /* Release the read buffer, so it could be used for new data. */
+ RTCircBufReleaseReadBlock(pCircBuf, cbToRead);
+
+ if (!cbToRead)
+ break;
+
+ /* Move offset. */
+ cbRead += cbToRead;
+ Assert(cbRead <= audioBuffer->mAudioDataBytesCapacity);
+
+ Assert(cbToRead <= cbLeft);
+ cbLeft -= cbToRead;
+ }
+
+ audioBuffer->mAudioDataByteSize = cbRead;
+
+ if (audioBuffer->mAudioDataByteSize < audioBuffer->mAudioDataBytesCapacity)
+ {
+ RT_BZERO((UInt8 *)audioBuffer->mAudioData + audioBuffer->mAudioDataByteSize,
+ audioBuffer->mAudioDataBytesCapacity - audioBuffer->mAudioDataByteSize);
+
+ audioBuffer->mAudioDataByteSize = audioBuffer->mAudioDataBytesCapacity;
+ }
+
+ Log3Func(("pCAStream=%p, cbCapacity=%RU32, cbRead=%zu\n",
+ pCAStream, audioBuffer->mAudioDataBytesCapacity, cbRead));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Output audio queue callback. Called whenever an audio queue is ready to process more output data.
+ *
+ * @param pvUser User argument.
+ * @param audioQueue Audio queue to process output data for.
+ * @param audioBuffer Audio buffer to store output data in. Must be part of audio queue.
+ */
+static DECLCALLBACK(void) coreAudioOutputQueueCb(void *pvUser, AudioQueueRef audioQueue, AudioQueueBufferRef audioBuffer)
+{
+ RT_NOREF(audioQueue);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pvUser;
+ AssertPtr(pCAStream);
+
+ int rc = RTCritSectEnter(&pCAStream->CritSect);
+ AssertRC(rc);
+
+ rc = coreAudioOutputQueueProcBuffer(pCAStream, audioBuffer);
+ if (RT_SUCCESS(rc))
+ AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL);
+
+ rc = RTCritSectLeave(&pCAStream->CritSect);
+ AssertRC(rc);
+}
+
+/**
+ * Invalidates a Core Audio stream's audio queue.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to invalidate its queue for.
+ */
+static int coreAudioStreamInvalidateQueue(PCOREAUDIOSTREAM pCAStream)
+{
+ int rc = VINF_SUCCESS;
+
+ Log3Func(("pCAStream=%p\n", pCAStream));
+
+ for (size_t i = 0; i < RT_ELEMENTS(pCAStream->audioBuffer); i++)
+ {
+ AudioQueueBufferRef pBuf = pCAStream->audioBuffer[i];
+
+ if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ int rc2 = coreAudioInputQueueProcBuffer(pCAStream, pBuf);
+ if (RT_SUCCESS(rc2))
+ {
+ AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL);
+ }
+ }
+ else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ int rc2 = coreAudioOutputQueueProcBuffer(pCAStream, pBuf);
+ if ( RT_SUCCESS(rc2)
+ && pBuf->mAudioDataByteSize)
+ {
+ AudioQueueEnqueueBuffer(pCAStream->audioQueue, pBuf, 0, NULL);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ else
+ AssertFailed();
+ }
+
+ return rc;
+}
+
+/**
+ * Initializes a Core Audio stream's audio queue.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to initialize audio queue for.
+ * @param pCfgReq Requested stream configuration.
+ * @param pCfgAcq Acquired stream configuration on success.
+ */
+static int coreAudioStreamInitQueue(PCOREAUDIOSTREAM pCAStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pCfgAcq);
+
+ LogFunc(("pCAStream=%p, pCfgReq=%p, pCfgAcq=%p\n", pCAStream, pCfgReq, pCfgAcq));
+
+ /* No device assigned? Bail out early. */
+ if (pCAStream->Unit.pDevice == NULL)
+ return VERR_NOT_AVAILABLE;
+
+ const bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
+
+ int rc = VINF_SUCCESS;
+
+ /* Create the recording device's out format based on our required audio settings. */
+ Assert(pCAStream->pCfg == NULL);
+ pCAStream->pCfg = DrvAudioHlpStreamCfgDup(pCfgReq);
+ if (!pCAStream->pCfg)
+ rc = VERR_NO_MEMORY;
+
+ coreAudioPCMPropsToASBD(&pCfgReq->Props, &pCAStream->asbdStream);
+ /** @todo Do some validation? */
+
+ coreAudioPrintASBD( fIn
+ ? "Capturing queue format"
+ : "Playback queue format", &pCAStream->asbdStream);
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("CoreAudio: Failed to convert requested %s format to native format (%Rrc)\n", fIn ? "input" : "output", rc));
+ return rc;
+ }
+
+ rc = RTCircBufCreate(&pCAStream->pCircBuf, PDMAUDIOSTREAMCFG_F2B(pCfgReq, pCfgReq->Backend.cfBufferSize));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Start the thread.
+ */
+ rc = RTThreadCreate(&pCAStream->hThread, coreAudioQueueThread,
+ pCAStream /* pvUser */, 0 /* Default stack size */,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CAQUEUE");
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(pCAStream->hThread, 10 * 1000 /* 10s timeout */);
+
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Unitializes a Core Audio stream's audio queue.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to unitialize audio queue for.
+ */
+static int coreAudioStreamUninitQueue(PCOREAUDIOSTREAM pCAStream)
+{
+ LogFunc(("pCAStream=%p\n", pCAStream));
+
+ if (pCAStream->hThread != NIL_RTTHREAD)
+ {
+ LogFunc(("Waiting for thread ...\n"));
+
+ ASMAtomicXchgBool(&pCAStream->fShutdown, true);
+
+ int rcThread;
+ int rc = RTThreadWait(pCAStream->hThread, 30 * 1000, &rcThread);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RT_NOREF(rcThread);
+ LogFunc(("Thread stopped with %Rrc\n", rcThread));
+
+ pCAStream->hThread = NIL_RTTHREAD;
+ }
+
+ if (pCAStream->pCfg)
+ {
+ DrvAudioHlpStreamCfgFree(pCAStream->pCfg);
+ pCAStream->pCfg = NULL;
+ }
+
+ if (pCAStream->pCircBuf)
+ {
+ RTCircBufDestroy(pCAStream->pCircBuf);
+ pCAStream->pCircBuf = NULL;
+ }
+
+ LogFunc(("Returning\n"));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unitializes a Core Audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pCAStream Core Audio stream to uninitialize.
+ */
+static int coreAudioStreamUninit(PCOREAUDIOSTREAM pCAStream)
+{
+ LogFunc(("pCAStream=%p\n", pCAStream));
+
+ int rc = coreAudioStreamUninitQueue(pCAStream);
+ if (RT_SUCCESS(rc))
+ {
+ pCAStream->Unit.pDevice = NULL;
+ pCAStream->pDrv = NULL;
+ }
+
+ return rc;
+}
+
+/**
+ * Registers callbacks for a specific Core Audio device.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pDev Audio device to use for the registered callbacks.
+ */
+static int coreAudioDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
+{
+ RT_NOREF(pThis);
+
+ AudioDeviceID deviceID = kAudioDeviceUnknown;
+
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ if (pData)
+ deviceID = pData->deviceID;
+
+ if (deviceID != kAudioDeviceUnknown)
+ {
+ LogFunc(("deviceID=%RU32\n", deviceID));
+
+ /*
+ * Register device callbacks.
+ */
+ AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ OSStatus err = AudioObjectAddPropertyListener(deviceID, &propAdr,
+ coreAudioDeviceStateChangedCb, pDev /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareIllegalOperationError)
+ {
+ LogRel(("CoreAudio: Failed to add the recording device state changed listener (%RI32)\n", err));
+ }
+
+ propAdr.mSelector = kAudioDeviceProcessorOverload;
+ propAdr.mScope = kAudioUnitScope_Global;
+ err = AudioObjectAddPropertyListener(deviceID, &propAdr,
+ coreAudioDevPropChgCb, pDev /* pvUser */);
+ if (err != noErr)
+ LogRel(("CoreAudio: Failed to register processor overload listener (%RI32)\n", err));
+
+ propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ propAdr.mScope = kAudioUnitScope_Global;
+ err = AudioObjectAddPropertyListener(deviceID, &propAdr,
+ coreAudioDevPropChgCb, pDev /* pvUser */);
+ if (err != noErr)
+ LogRel(("CoreAudio: Failed to register sample rate changed listener (%RI32)\n", err));
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregisters all formerly registered callbacks of a Core Audio device again.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pDev Audio device to use for the registered callbacks.
+ */
+static int coreAudioDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, PPDMAUDIODEVICE pDev)
+{
+ RT_NOREF(pThis);
+
+ AudioDeviceID deviceID = kAudioDeviceUnknown;
+
+ if (pDev)
+ {
+ PCOREAUDIODEVICEDATA pData = (PCOREAUDIODEVICEDATA)pDev->pvData;
+ if (pData)
+ deviceID = pData->deviceID;
+ }
+
+ if (deviceID != kAudioDeviceUnknown)
+ {
+ LogFunc(("deviceID=%RU32\n", deviceID));
+
+ /*
+ * Unregister per-device callbacks.
+ */
+ AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+ OSStatus err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
+ coreAudioDevPropChgCb, pDev /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareBadObjectError)
+ {
+ LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%RI32)\n", err));
+ }
+
+ propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
+ err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
+ coreAudioDevPropChgCb, pDev /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareBadObjectError)
+ {
+ LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
+ }
+
+ propAdr.mSelector = kAudioDevicePropertyDeviceIsAlive;
+ err = AudioObjectRemovePropertyListener(deviceID, &propAdr,
+ coreAudioDeviceStateChangedCb, pDev /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareBadObjectError)
+ {
+ LogRel(("CoreAudio: Failed to remove the device alive listener (%RI32)\n", err));
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* Callback for getting notified when some of the properties of an audio device have changed. */
+static DECLCALLBACK(OSStatus) coreAudioDevPropChgCb(AudioObjectID propertyID,
+ UInt32 cAddresses,
+ const AudioObjectPropertyAddress properties[],
+ void *pvUser)
+{
+ RT_NOREF(cAddresses, properties, pvUser);
+
+ PPDMAUDIODEVICE pDev = (PPDMAUDIODEVICE)pvUser;
+ AssertPtr(pDev);
+
+ LogFlowFunc(("propertyID=%u, nAddresses=%u, pDev=%p\n", propertyID, cAddresses, pDev));
+
+ switch (propertyID)
+ {
+#ifdef DEBUG
+ case kAudioDeviceProcessorOverload:
+ {
+ LogFunc(("Processor overload detected!\n"));
+ break;
+ }
+#endif /* DEBUG */
+ case kAudioDevicePropertyNominalSampleRate:
+ {
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ int rc2 = coreAudioDevicePropagateStatus(pDev, COREAUDIOSTATUS_REINIT);
+ AssertRC(rc2);
+#else
+ RT_NOREF(pDev);
+#endif
+ break;
+ }
+
+ default:
+ /* Just skip. */
+ break;
+ }
+
+ return noErr;
+}
+
+/**
+ * Enumerates all available host audio devices internally.
+ *
+ * @returns IPRT status code.
+ * @param pThis Host audio driver instance.
+ */
+static int coreAudioEnumerateDevices(PDRVHOSTCOREAUDIO pThis)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Unregister old default devices, if any.
+ */
+ if (pThis->pDefaultDevIn)
+ {
+ coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevIn);
+ pThis->pDefaultDevIn = NULL;
+ }
+
+ if (pThis->pDefaultDevOut)
+ {
+ coreAudioDeviceUnregisterCallbacks(pThis, pThis->pDefaultDevOut);
+ pThis->pDefaultDevOut = NULL;
+ }
+
+ /* Remove old / stale device entries. */
+ DrvAudioHlpDeviceEnumFree(&pThis->Devices);
+
+ /* Enumerate all devices internally. */
+ int rc = coreAudioDevicesEnumerateAll(pThis, &pThis->Devices);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Default input device.
+ */
+ pThis->pDefaultDevIn = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_IN);
+ if (pThis->pDefaultDevIn)
+ {
+ LogRel2(("CoreAudio: Default capturing device is '%s'\n", pThis->pDefaultDevIn->szName));
+
+#ifdef DEBUG
+ PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevIn->pvData;
+ AssertPtr(pDevData);
+ LogFunc(("pDefaultDevIn=%p, ID=%RU32\n", pThis->pDefaultDevIn, pDevData->deviceID));
+#endif
+ rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevIn);
+ }
+ else
+ LogRel2(("CoreAudio: No default capturing device found\n"));
+
+ /*
+ * Default output device.
+ */
+ pThis->pDefaultDevOut = DrvAudioHlpDeviceEnumGetDefaultDevice(&pThis->Devices, PDMAUDIODIR_OUT);
+ if (pThis->pDefaultDevOut)
+ {
+ LogRel2(("CoreAudio: Default playback device is '%s'\n", pThis->pDefaultDevOut->szName));
+
+#ifdef DEBUG
+ PCOREAUDIODEVICEDATA pDevData = (PCOREAUDIODEVICEDATA)pThis->pDefaultDevOut->pvData;
+ AssertPtr(pDevData);
+ LogFunc(("pDefaultDevOut=%p, ID=%RU32\n", pThis->pDefaultDevOut, pDevData->deviceID));
+#endif
+ rc = coreAudioDeviceRegisterCallbacks(pThis, pThis->pDefaultDevOut);
+ }
+ else
+ LogRel2(("CoreAudio: No default playback device found\n"));
+ }
+
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pcxRead is optional. */
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ /* Check if the audio device should be reinitialized. If so do it. */
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
+ {
+ /* For now re just re-initialize with the current input device. */
+ if (pThis->pDefaultDevIn)
+ {
+ int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevIn);
+ if (RT_FAILURE(rc2))
+ return VERR_NOT_AVAILABLE;
+ }
+ else
+ return VERR_NOT_AVAILABLE;
+ }
+#else
+ RT_NOREF(pThis);
+#endif
+
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
+ {
+ if (pcxRead)
+ *pcxRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbReadTotal = 0;
+
+ rc = RTCritSectEnter(&pCAStream->CritSect);
+ AssertRC(rc);
+
+ do
+ {
+ size_t cbToWrite = RT_MIN(cxBuf, RTCircBufUsed(pCAStream->pCircBuf));
+
+ uint8_t *pvChunk;
+ size_t cbChunk;
+
+ Log3Func(("cbToWrite=%zu/%zu\n", cbToWrite, RTCircBufSize(pCAStream->pCircBuf)));
+
+ while (cbToWrite)
+ {
+ /* Try to acquire the necessary block from the ring buffer. */
+ RTCircBufAcquireReadBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
+ if (cbChunk)
+ memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
+
+ /* Release the read buffer, so it could be used for new data. */
+ RTCircBufReleaseReadBlock(pCAStream->pCircBuf, cbChunk);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ Assert(cbToWrite >= cbChunk);
+ cbToWrite -= cbChunk;
+
+ cbReadTotal += cbChunk;
+ }
+ }
+ while (0);
+
+ int rc2 = RTCritSectLeave(&pCAStream->CritSect);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ /* Check if the audio device should be reinitialized. If so do it. */
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_REINIT)
+ {
+ if (pThis->pDefaultDevOut)
+ {
+ /* For now re just re-initialize with the current output device. */
+ int rc2 = coreAudioStreamReinit(pThis, pCAStream, pThis->pDefaultDevOut);
+ if (RT_FAILURE(rc2))
+ return VERR_NOT_AVAILABLE;
+ }
+ else
+ return VERR_NOT_AVAILABLE;
+ }
+#else
+ RT_NOREF(pThis);
+#endif
+
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
+ {
+ if (pcxWritten)
+ *pcxWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cbWrittenTotal = 0;
+
+ int rc = VINF_SUCCESS;
+
+ rc = RTCritSectEnter(&pCAStream->CritSect);
+ AssertRC(rc);
+
+ size_t cbToWrite = RT_MIN(cxBuf, RTCircBufFree(pCAStream->pCircBuf));
+ Log3Func(("cbToWrite=%zu\n", cbToWrite));
+
+ uint8_t *pvChunk;
+ size_t cbChunk;
+
+ while (cbToWrite)
+ {
+ /* Try to acquire the necessary space from the ring buffer. */
+ RTCircBufAcquireWriteBlock(pCAStream->pCircBuf, cbToWrite, (void **)&pvChunk, &cbChunk);
+ if (!cbChunk)
+ {
+ RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
+ break;
+ }
+
+ Assert(cbChunk <= cbToWrite);
+ Assert(cbWrittenTotal + cbChunk <= cxBuf);
+
+ memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk);
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ RTFILE fh;
+ rc = RTFileOpen(&fh,VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ RTFileWrite(fh, pvChunk, cbChunk, NULL);
+ RTFileClose(fh);
+ }
+ else
+ AssertFailed();
+#endif
+
+ /* Release the ring buffer, so the read thread could start reading this data. */
+ RTCircBufReleaseWriteBlock(pCAStream->pCircBuf, cbChunk);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ Assert(cbToWrite >= cbChunk);
+ cbToWrite -= cbChunk;
+
+ cbWrittenTotal += cbChunk;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pCAStream->fRun
+ && !pCAStream->fIsRunning)
+ {
+ rc = coreAudioStreamInvalidateQueue(pCAStream);
+ if (RT_SUCCESS(rc))
+ {
+ AudioQueueStart(pCAStream->audioQueue, NULL);
+ pCAStream->fRun = false;
+ pCAStream->fIsRunning = true;
+ }
+ }
+
+ int rc2 = RTCritSectLeave(&pCAStream->CritSect);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) coreAudioStreamControl(PDRVHOSTCOREAUDIO pThis,
+ PCOREAUDIOSTREAM pCAStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ RT_NOREF(pThis);
+
+ uint32_t enmStatus = ASMAtomicReadU32(&pCAStream->enmStatus);
+
+ LogFlowFunc(("enmStreamCmd=%RU32, enmStatus=%RU32\n", enmStreamCmd, enmStatus));
+
+ if (!( enmStatus == COREAUDIOSTATUS_INIT
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ || enmStatus == COREAUDIOSTATUS_REINIT
+#endif
+ ))
+ {
+ return VINF_SUCCESS;
+ }
+
+ if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ LogFunc(("Queue enable\n"));
+ if (pCAStream->pCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ rc = coreAudioStreamInvalidateQueue(pCAStream);
+ if (RT_SUCCESS(rc))
+ {
+ /* Start the audio queue immediately. */
+ AudioQueueStart(pCAStream->audioQueue, NULL);
+ }
+ }
+ else if (pCAStream->pCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ /* Touch the run flag to start the audio queue as soon as
+ * we have anough data to actually play something. */
+ ASMAtomicXchgBool(&pCAStream->fRun, true);
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ LogFunc(("Queue disable\n"));
+ AudioQueueStop(pCAStream->audioQueue, 1 /* Immediately */);
+ ASMAtomicXchgBool(&pCAStream->fRun, false);
+ ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
+ break;
+ }
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ LogFunc(("Queue pause\n"));
+ AudioQueuePause(pCAStream->audioQueue);
+ ASMAtomicXchgBool(&pCAStream->fIsRunning, false);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ RT_BZERO(pBackendCfg, sizeof(PDMAUDIOBACKENDCFG));
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio driver");
+
+ pBackendCfg->cbStreamIn = sizeof(COREAUDIOSTREAM);
+ pBackendCfg->cbStreamOut = sizeof(COREAUDIOSTREAM);
+
+ /* For Core Audio we provide one stream per device for now. */
+ pBackendCfg->cMaxStreamsIn = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_IN);
+ pBackendCfg->cMaxStreamsOut = DrvAudioHlpDeviceEnumGetDeviceCount(&pThis->Devices, PDMAUDIODIR_OUT);
+
+ LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioGetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIODEVICEENUM pDeviceEnum)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = coreAudioEnumerateDevices(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (pDeviceEnum)
+ {
+ rc = DrvAudioHlpDeviceEnumInit(pDeviceEnum);
+ if (RT_SUCCESS(rc))
+ rc = DrvAudioHlpDeviceEnumCopy(pDeviceEnum, &pThis->Devices);
+
+ if (RT_FAILURE(rc))
+ DrvAudioHlpDeviceEnumFree(pDeviceEnum);
+ }
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioSetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ /* pfnCallback will be handled below. */
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("pfnCallback=%p\n", pfnCallback));
+
+ if (pfnCallback) /* Register. */
+ {
+ Assert(pThis->pfnCallback == NULL);
+ pThis->pfnCallback = pfnCallback;
+ }
+ else /* Unregister. */
+ {
+ if (pThis->pfnCallback)
+ pThis->pfnCallback = NULL;
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ return rc;
+}
+#endif
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostCoreAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(pInterface, enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ int rc = RTCritSectInit(&pCAStream->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pCAStream->hThread = NIL_RTTHREAD;
+ pCAStream->fRun = false;
+ pCAStream->fIsRunning = false;
+ pCAStream->fShutdown = false;
+
+ /* Input or output device? */
+ bool fIn = pCfgReq->enmDir == PDMAUDIODIR_IN;
+
+ /* For now, just use the default device available. */
+ PPDMAUDIODEVICE pDev = fIn ? pThis->pDefaultDevIn : pThis->pDefaultDevOut;
+
+ LogFunc(("pStream=%p, pCfgReq=%p, pCfgAcq=%p, fIn=%RTbool, pDev=%p\n", pStream, pCfgReq, pCfgAcq, fIn, pDev));
+
+ if (pDev) /* (Default) device available? */
+ {
+ /* Sanity. */
+ AssertPtr(pDev->pvData);
+ Assert(pDev->cbData);
+
+ /* Init the Core Audio stream. */
+ rc = coreAudioStreamInit(pCAStream, pThis, pDev);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_INIT);
+
+ rc = coreAudioStreamInitQueue(pCAStream, pCfgReq, pCfgAcq);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_INIT);
+ }
+ else
+ {
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
+
+ int rc2 = coreAudioStreamUninit(pCAStream);
+ AssertRC(rc2);
+
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
+ }
+ }
+ }
+ else
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ uint32_t status = ASMAtomicReadU32(&pCAStream->enmStatus);
+ if (!( status == COREAUDIOSTATUS_INIT
+#ifndef VBOX_WITH_AUDIO_CALLBACKS
+ || status == COREAUDIOSTATUS_REINIT
+#endif
+ ))
+ {
+ return VINF_SUCCESS;
+ }
+
+ if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc = coreAudioStreamControl(pThis, pCAStream, PDMAUDIOSTREAMCMD_DISABLE);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_IN_UNINIT);
+
+ rc = coreAudioStreamUninit(pCAStream);
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicXchgU32(&pCAStream->enmStatus, COREAUDIOSTATUS_UNINIT);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCritSectIsInitialized(&pCAStream->CritSect))
+ RTCritSectDelete(&pCAStream->CritSect);
+ }
+
+ LogFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ return coreAudioStreamControl(pThis, pCAStream, enmStreamCmd);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostCoreAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) != COREAUDIOSTATUS_INIT)
+ return 0;
+
+ AssertPtr(pCAStream->pCfg);
+ AssertPtr(pCAStream->pCircBuf);
+
+ switch (pCAStream->pCfg->enmDir)
+ {
+ case PDMAUDIODIR_IN:
+ return (uint32_t)RTCircBufUsed(pCAStream->pCircBuf);
+
+ case PDMAUDIODIR_OUT:
+ default:
+ AssertFailed();
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostCoreAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ uint32_t cbWritable = 0;
+
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
+ {
+ AssertPtr(pCAStream->pCfg);
+ AssertPtr(pCAStream->pCircBuf);
+
+ switch (pCAStream->pCfg->enmDir)
+ {
+ case PDMAUDIODIR_OUT:
+ cbWritable = (uint32_t)RTCircBufFree(pCAStream->pCircBuf);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ LogFlowFunc(("cbWritable=%RU32\n", cbWritable));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostCoreAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PCOREAUDIOSTREAM pCAStream = (PCOREAUDIOSTREAM)pStream;
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
+
+ if (!pCAStream->pCfg) /* Not (yet) configured? Skip. */
+ return strmSts;
+
+ if (ASMAtomicReadU32(&pCAStream->enmStatus) == COREAUDIOSTATUS_INIT)
+ strmSts |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+
+ return strmSts;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ RT_NOREF(pInterface, pStream);
+
+ /* Nothing to do here for Core Audio. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ int rc = DrvAudioHlpDeviceEnumInit(&pThis->Devices);
+ if (RT_SUCCESS(rc))
+ {
+ /* Do the first (initial) internal device enumeration. */
+ rc = coreAudioEnumerateDevices(pThis);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Register system callbacks. */
+ AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
+ coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareIllegalOperationError)
+ {
+ LogRel(("CoreAudio: Failed to add the input default device changed listener (%RI32)\n", err));
+ }
+
+ propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
+ coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareIllegalOperationError)
+ {
+ LogRel(("CoreAudio: Failed to add the output default device changed listener (%RI32)\n", err));
+ }
+ }
+
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostCoreAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ PDRVHOSTCOREAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTCOREAUDIO(pInterface);
+
+ /*
+ * Unregister system callbacks.
+ */
+ AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster };
+
+ OSStatus err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
+ coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareBadObjectError)
+ {
+ LogRel(("CoreAudio: Failed to remove the default input device changed listener (%RI32)\n", err));
+ }
+
+ propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+ err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
+ coreAudioDefaultDeviceChangedCb, pThis /* pvUser */);
+ if ( err != noErr
+ && err != kAudioHardwareBadObjectError)
+ {
+ LogRel(("CoreAudio: Failed to remove the default output device changed listener (%RI32)\n", err));
+ }
+
+ LogFlowFuncEnter();
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(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;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT,
+ * Construct a Core Audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+ LogRel(("Audio: Initializing Core Audio driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostCoreAudio);
+
+ /* This backend supports device enumeration. */
+ pThis->IHostAudio.pfnGetDevices = drvHostCoreAudioGetDevices;
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /* This backend supports host audio callbacks. */
+ pThis->IHostAudio.pfnSetCallback = drvHostCoreAudioSetCallback;
+ pThis->pfnCallback = NULL;
+#endif
+
+ int rc = RTCritSectInit(&pThis->CritSect);
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caConverterCbInput.pcm");
+ RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "caPlayback.pcm");
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVDESTRUCT}
+ */
+static DECLCALLBACK(void) drvHostCoreAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
+
+ int rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFuncLeaveRC(rc2);
+}
+
+
+/**
+ * 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 */
+ drvHostCoreAudioConstruct,
+ /* pfnDestruct */
+ drvHostCoreAudioDestruct,
+ /* 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/DrvHostDSound.cpp b/src/VBox/Devices/Audio/DrvHostDSound.cpp
new file mode 100644
index 00000000..a98fd7a0
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostDSound.cpp
@@ -0,0 +1,2659 @@
+/* $Id: DrvHostDSound.cpp $ */
+/** @file
+ * Windows host backend driver using DirectSound.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <iprt/win/windows.h>
+#include <dsound.h>
+
+#include <iprt/alloc.h>
+#include <iprt/system.h>
+#include <iprt/uuid.h>
+#include <iprt/utf16.h>
+
+#include "DrvAudio.h"
+#include "VBoxDD.h"
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+# include <new> /* For bad_alloc. */
+# include "VBoxMMNotificationClient.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * IDirectSound* interface uses HRESULT status codes and the driver callbacks use
+ * the IPRT status codes. To minimize HRESULT->IPRT conversion most internal functions
+ * in the driver return HRESULT and conversion is done in the driver callbacks.
+ *
+ * Naming convention:
+ * 'dsound*' functions return IPRT status code;
+ * 'directSound*' - return HRESULT.
+ */
+
+/*
+ * Optional release logging, which a user can turn on with the
+ * 'VBoxManage debugvm' command.
+ * Debug logging still uses the common Log* macros from IPRT.
+ * Messages which always should go to the release log use LogRel.
+ */
+/* 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
+/** 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
+
+/** Makes DRVHOSTDSOUND out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface) \
+ ( (PDRVHOSTDSOUND)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTDSOUND, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* 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
+{
+ /** 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;
+ /** The stream's critical section for synchronizing access. */
+ RTCRITSECT CritSect;
+ /** The internal playback / capturing buffer. */
+ PRTCIRCBUF pCircBuf;
+ /** Size (in bytes) of the DirectSound buffer.
+ * Note: This in *not* the size of the circular buffer above! */
+ 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. */
+ 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;
+ /** Timestamp (in ms) of the last transfer from the internal buffer
+ * to the DirectSound buffer. */
+ uint64_t tsLastTransferredMs;
+ /** Number of buffer underruns happened. Used for logging. */
+ uint8_t cUnderruns;
+ } Out;
+ };
+#ifdef LOG_ENABLED
+ struct
+ {
+ uint64_t tsLastTransferredMs;
+ } Dbg;
+#endif
+} DSOUNDSTREAM, *PDSOUNDSTREAM;
+
+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;
+ /** List of found host input devices. */
+ RTLISTANCHOR lstDevInput;
+ /** List of found host output devices. */
+ RTLISTANCHOR lstDevOutput;
+ /** DirectSound configuration options. */
+ DSOUNDHOSTCFG Cfg;
+ /** Whether this backend supports any audio input. */
+ bool fEnabledIn;
+ /** Whether this backend supports any audio output. */
+ bool fEnabledOut;
+ /** The Direct Sound playback interface. */
+ LPDIRECTSOUND8 pDS;
+ /** The Direct Sound capturing interface. */
+ LPDIRECTSOUNDCAPTURE8 pDSC;
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ VBoxMMNotificationClient *m_pNotificationClient;
+#endif
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /** Callback function to the upper driver.
+ * Can be NULL if not being used / registered. */
+ PFNPDMHOSTAUDIOCALLBACK pfnCallback;
+#endif
+ /** Pointer to the input stream. */
+ PDSOUNDSTREAM pDSStrmIn;
+ /** Pointer to the output stream. */
+ PDSOUNDSTREAM pDSStrmOut;
+} DRVHOSTDSOUND, *PDRVHOSTDSOUND;
+
+/** No flags specified. */
+#define DSOUNDENUMCBFLAGS_NONE 0
+/** (Release) log found devices. */
+#define DSOUNDENUMCBFLAGS_LOG RT_BIT(0)
+
+/**
+ * Callback context for enumeration callbacks
+ */
+typedef struct DSOUNDENUMCBCTX
+{
+ /** Pointer to host backend driver. */
+ PDRVHOSTDSOUND pDrv;
+ /** Enumeration flags. */
+ uint32_t fFlags;
+ /** Number of found input devices. */
+ uint8_t cDevIn;
+ /** Number of found output devices. */
+ uint8_t cDevOut;
+} DSOUNDENUMCBCTX, *PDSOUNDENUMCBCTX;
+
+typedef struct DSOUNDDEV
+{
+ RTLISTNODE Node;
+ char *pszName;
+ GUID Guid;
+} DSOUNDDEV, *PDSOUNDDEV;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static HRESULT directSoundPlayRestore(PDRVHOSTDSOUND pThis, LPDIRECTSOUNDBUFFER8 pDSB);
+static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS);
+static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush);
+static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush);
+
+static void dsoundDeviceRemove(PDSOUNDDEV pDev);
+static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg);
+
+static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable);
+static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS);
+static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis);
+static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum);
+
+
+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 int dsoundWaveFmtFromCfg(PPDMAUDIOSTREAMCFG pCfg, PWAVEFORMATEX pFmt)
+{
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pFmt, VERR_INVALID_POINTER);
+
+ RT_BZERO(pFmt, sizeof(WAVEFORMATEX));
+
+ pFmt->wFormatTag = WAVE_FORMAT_PCM;
+ pFmt->nChannels = pCfg->Props.cChannels;
+ pFmt->wBitsPerSample = pCfg->Props.cBytes * 8;
+ pFmt->nSamplesPerSec = pCfg->Props.uHz;
+ pFmt->nBlockAlign = pFmt->nChannels * pFmt->wBitsPerSample / 8;
+ pFmt->nAvgBytesPerSec = pFmt->nSamplesPerSec * pFmt->nBlockAlign;
+ pFmt->cbSize = 0; /* No extra data specified. */
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves the number of free bytes available for writing to a DirectSound output stream.
+ *
+ * @return IPRT 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.
+ */
+static int dsoundGetFreeOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, DWORD *pdwFree)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamDS, VERR_INVALID_POINTER);
+ AssertPtrReturn(pdwFree, VERR_INVALID_POINTER);
+
+ Assert(pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT); /* Paranoia. */
+
+ LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
+ if (!pDSB)
+ {
+ AssertPtr(pDSB);
+ return 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 cbPlayCursor, cbWriteCursor;
+ hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &cbPlayCursor, &cbWriteCursor);
+ if (SUCCEEDED(hr))
+ {
+ int32_t cbDiff = cbWriteCursor - cbPlayCursor;
+ if (cbDiff < 0)
+ cbDiff += pStreamDS->cbBufSize;
+
+ int32_t cbFree = cbPlayCursor - pStreamDS->Out.offWritePos;
+ if (cbFree < 0)
+ cbFree += pStreamDS->cbBufSize;
+
+ if (cbFree > (int32_t)pStreamDS->cbBufSize - cbDiff)
+ {
+ pStreamDS->Out.offWritePos = cbWriteCursor;
+ cbFree = pStreamDS->cbBufSize - cbDiff;
+ }
+
+ /* When starting to use a DirectSound buffer, cbPlayCursor and cbWriteCursor
+ * 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;
+
+ DSLOGREL(("DSound: cbPlayCursor=%RU32, cbWriteCursor=%RU32, offWritePos=%RU32 -> cbFree=%RI32\n",
+ cbPlayCursor, cbWriteCursor, pStreamDS->Out.offWritePos, cbFree));
+
+ *pdwFree = cbFree;
+
+ 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));
+
+ return VERR_NOT_AVAILABLE;
+}
+
+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}");
+}
+
+/**
+ * Clears the list of the host's playback + capturing devices.
+ *
+ * @param pThis Host audio driver instance.
+ */
+static void dsoundDevicesClear(PDRVHOSTDSOUND pThis)
+{
+ AssertPtrReturnVoid(pThis);
+
+ PDSOUNDDEV pDev, pDevNext;
+ RTListForEachSafe(&pThis->lstDevInput, pDev, pDevNext, DSOUNDDEV, Node)
+ dsoundDeviceRemove(pDev);
+
+ Assert(RTListIsEmpty(&pThis->lstDevInput));
+
+ RTListForEachSafe(&pThis->lstDevOutput, pDev, pDevNext, DSOUNDDEV, Node)
+ dsoundDeviceRemove(pDev);
+
+ Assert(RTListIsEmpty(&pThis->lstDevOutput));
+}
+
+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 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 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 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
+ */
+
+static void directSoundPlayInterfaceDestroy(PDRVHOSTDSOUND pThis)
+{
+ if (pThis->pDS)
+ {
+ LogFlowFuncEnter();
+
+ IDirectSound8_Release(pThis->pDS);
+ pThis->pDS = NULL;
+ }
+}
+
+static HRESULT directSoundPlayInterfaceCreate(PDRVHOSTDSOUND pThis)
+{
+ if (pThis->pDS != NULL)
+ return S_OK;
+
+ LogFlowFuncEnter();
+
+ HRESULT hr = CoCreateInstance(CLSID_DirectSound8, NULL, CLSCTX_ALL,
+ IID_IDirectSound8, (void **)&pThis->pDS);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Creating playback instance failed with %Rhrc\n", hr));
+ }
+ else
+ {
+ hr = IDirectSound8_Initialize(pThis->pDS, pThis->Cfg.pGuidPlay);
+ if (SUCCEEDED(hr))
+ {
+ HWND hWnd = GetDesktopWindow();
+ hr = IDirectSound8_SetCooperativeLevel(pThis->pDS, hWnd, DSSCL_PRIORITY);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Setting cooperative level for window %p failed with %Rhrc\n", hWnd, hr));
+ }
+
+ if (FAILED(hr))
+ {
+ if (hr == DSERR_NODRIVER) /* Usually means that no playback devices are attached. */
+ DSLOGREL(("DSound: DirectSound playback is currently unavailable\n"));
+ else
+ DSLOGREL(("DSound: DirectSound playback initialization failed with %Rhrc\n", hr));
+
+ directSoundPlayInterfaceDestroy(pThis);
+ }
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+static HRESULT directSoundPlayClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+
+ LogFlowFuncEnter();
+
+ HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
+ if (FAILED(hr))
+ return hr;
+
+ DSLOG(("DSound: Closing playback stream\n"));
+
+ if (pStreamDS->pCircBuf)
+ Assert(RTCircBufUsed(pStreamDS->pCircBuf) == 0);
+
+ if (SUCCEEDED(hr))
+ {
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (pStreamDS->pCircBuf)
+ {
+ RTCircBufDestroy(pStreamDS->pCircBuf);
+ pStreamDS->pCircBuf = NULL;
+ }
+
+ if (pStreamDS->Out.pDSB)
+ {
+ IDirectSoundBuffer8_Release(pStreamDS->Out.pDSB);
+ pStreamDS->Out.pDSB = NULL;
+ }
+
+ pThis->pDSStrmOut = NULL;
+
+ RTCritSectLeave(&pThis->CritSect);
+ }
+
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Stopping playback stream %p failed with %Rhrc\n", pStreamDS, hr));
+
+ return hr;
+}
+
+static HRESULT directSoundPlayOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+ AssertPtrReturn(pCfgReq, E_POINTER);
+ AssertPtrReturn(pCfgAcq, E_POINTER);
+
+ LogFlowFuncEnter();
+
+ Assert(pStreamDS->Out.pDSB == NULL);
+
+ DSLOG(("DSound: Opening playback stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n",
+ pCfgReq->Props.uHz,
+ pCfgReq->Props.cChannels,
+ pCfgReq->Props.cBytes * 8,
+ pCfgReq->Props.fSigned));
+
+ WAVEFORMATEX wfx;
+ int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx);
+ if (RT_FAILURE(rc))
+ return E_INVALIDARG;
+
+ DSLOG(("DSound: Requested playback format:\n"
+ " wFormatTag = %RI16\n"
+ " nChannels = %RI16\n"
+ " nSamplesPerSec = %RU32\n"
+ " nAvgBytesPerSec = %RU32\n"
+ " nBlockAlign = %RI16\n"
+ " wBitsPerSample = %RI16\n"
+ " cbSize = %RI16\n",
+ wfx.wFormatTag,
+ wfx.nChannels,
+ wfx.nSamplesPerSec,
+ wfx.nAvgBytesPerSec,
+ wfx.nBlockAlign,
+ wfx.wBitsPerSample,
+ wfx.cbSize));
+
+ dsoundUpdateStatusInternal(pThis);
+
+ HRESULT hr = directSoundPlayInterfaceCreate(pThis);
+ if (FAILED(hr))
+ return hr;
+
+ do /* To use breaks. */
+ {
+ LPDIRECTSOUNDBUFFER pDSB = NULL;
+
+ DSBUFFERDESC bd;
+ RT_ZERO(bd);
+ bd.dwSize = sizeof(bd);
+ bd.lpwfxFormat = &wfx;
+
+ /*
+ * 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.
+ */
+ bd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE;
+ bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props);
+
+ DSLOG(("DSound: Requested playback buffer is %RU64ms (%ld bytes)\n",
+ DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes));
+
+ hr = IDirectSound8_CreateSoundBuffer(pThis->pDS, &bd, &pDSB, NULL);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Creating playback sound buffer failed with %Rhrc\n", hr));
+ break;
+ }
+
+ /* "Upgrade" to IDirectSoundBuffer8 interface. */
+ hr = IDirectSoundBuffer_QueryInterface(pDSB, IID_IDirectSoundBuffer8, (PVOID *)&pStreamDS->Out.pDSB);
+ IDirectSoundBuffer_Release(pDSB);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Querying playback sound buffer interface failed with %Rhrc\n", hr));
+ break;
+ }
+
+ /*
+ * Query the actual parameters set for this stream.
+ * Those might be different than the initially requested parameters.
+ */
+ RT_ZERO(wfx);
+ hr = IDirectSoundBuffer8_GetFormat(pStreamDS->Out.pDSB, &wfx, sizeof(wfx), NULL);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Getting playback format failed with %Rhrc\n", hr));
+ break;
+ }
+
+ DSBCAPS bc;
+ RT_ZERO(bc);
+ bc.dwSize = sizeof(bc);
+
+ hr = IDirectSoundBuffer8_GetCaps(pStreamDS->Out.pDSB, &bc);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Getting playback capabilities failed with %Rhrc\n", hr));
+ break;
+ }
+
+ DSLOG(("DSound: Acquired playback buffer is %RU64ms (%ld bytes)\n",
+ DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes));
+
+ DSLOG(("DSound: Acquired playback format:\n"
+ " dwBufferBytes = %RI32\n"
+ " dwFlags = 0x%x\n"
+ " wFormatTag = %RI16\n"
+ " nChannels = %RI16\n"
+ " nSamplesPerSec = %RU32\n"
+ " nAvgBytesPerSec = %RU32\n"
+ " nBlockAlign = %RI16\n"
+ " wBitsPerSample = %RI16\n"
+ " cbSize = %RI16\n",
+ bc.dwBufferBytes,
+ bc.dwFlags,
+ wfx.wFormatTag,
+ wfx.nChannels,
+ wfx.nSamplesPerSec,
+ wfx.nAvgBytesPerSec,
+ wfx.nBlockAlign,
+ wfx.wBitsPerSample,
+ wfx.cbSize));
+
+ if (bc.dwBufferBytes & pStreamDS->uAlign)
+ DSLOGREL(("DSound: Playback capabilities returned misaligned buffer: size %RU32, alignment %RU32\n",
+ bc.dwBufferBytes, pStreamDS->uAlign + 1));
+
+ /*
+ * Initial state.
+ * dsoundPlayStart initializes part of it to make sure that Stop/Start continues with a correct
+ * playback buffer position.
+ */
+ pStreamDS->cbBufSize = bc.dwBufferBytes;
+
+ rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering" */
+ AssertRC(rc);
+
+ pThis->pDSStrmOut = pStreamDS;
+
+ const uint32_t cfBufSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize);
+
+ pCfgAcq->Backend.cfBufferSize = cfBufSize;
+ pCfgAcq->Backend.cfPeriod = cfBufSize / 4;
+ pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod * 2;
+
+ } while (0);
+
+ if (FAILED(hr))
+ directSoundPlayClose(pThis, pStreamDS);
+
+ return hr;
+}
+
+static void dsoundPlayClearBuffer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ AssertPtrReturnVoid(pStreamDS);
+
+ PPDMAUDIOPCMPROPS pProps = &pStreamDS->Cfg.Props;
+
+ HRESULT hr = IDirectSoundBuffer_SetCurrentPosition(pStreamDS->Out.pDSB, 0 /* Position */);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Setting current position to 0 when clearing buffer failed with %Rhrc\n", hr));
+
+ PVOID pv1;
+ hr = directSoundPlayLock(pThis, pStreamDS,
+ 0 /* dwOffset */, pStreamDS->cbBufSize,
+ &pv1, NULL, 0, 0, DSBLOCK_ENTIREBUFFER);
+ if (SUCCEEDED(hr))
+ {
+ DrvAudioHlpClearBuf(pProps, pv1, pStreamDS->cbBufSize, PDMAUDIOPCMPROPS_B2F(pProps, pStreamDS->cbBufSize));
+
+ directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, NULL, 0, 0);
+
+ /* Make sure to get the last playback position and current write position from DirectSound again.
+ * Those positions in theory could have changed, re-fetch them to be sure. */
+ hr = IDirectSoundBuffer_GetCurrentPosition(pStreamDS->Out.pDSB,
+ &pStreamDS->Out.offPlayCursorLastPlayed, &pStreamDS->Out.offWritePos);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Re-fetching current position when clearing buffer failed with %Rhrc\n", hr));
+ }
+}
+
+/**
+ * Transfers audio data from the internal buffer to the DirectSound playback instance.
+ * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to transfer playback data for.
+ */
+static int dsoundPlayTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ if (!pStreamDS->fEnabled)
+ {
+ Log2Func(("Stream disabled, skipping\n"));
+ return VINF_SUCCESS;
+ }
+
+ uint32_t cbTransferred = 0;
+
+ PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ LPDIRECTSOUNDBUFFER8 pDSB = pStreamDS->Out.pDSB;
+ AssertPtr(pDSB);
+
+ int rc = VINF_SUCCESS;
+
+ DWORD offPlayCursor, offWriteCursor;
+ HRESULT hr = IDirectSoundBuffer8_GetCurrentPosition(pDSB, &offPlayCursor, &offWriteCursor);
+ if (FAILED(hr))
+ {
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ return rc;
+ }
+
+ DWORD cbFree, cbRemaining;
+ if (pStreamDS->Out.fFirstTransfer)
+ {
+ cbRemaining = 0;
+ cbFree = pStreamDS->cbBufSize;
+ }
+ else
+ {
+ cbFree = dsoundRingDistance(offPlayCursor, pStreamDS->Out.offWritePos, pStreamDS->cbBufSize);
+ cbRemaining = dsoundRingDistance(pStreamDS->Out.offWritePos, offPlayCursor, pStreamDS->cbBufSize);
+ }
+
+ uint32_t cbAvail = (uint32_t)RTCircBufUsed(pCircBuf);
+ uint32_t cbToTransfer = RT_MIN(cbFree, cbAvail);
+
+#ifdef LOG_ENABLED
+ if (!pStreamDS->Dbg.tsLastTransferredMs)
+ pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS();
+ Log3Func(("offPlay=%RU32, offWrite=%RU32, tsLastTransferredMs=%RU64ms, cbAvail=%RU32, cbFree=%RU32 -> cbToTransfer=%RU32 "
+ "(fFirst=%RTbool, fDrain=%RTbool)\n",
+ offPlayCursor, offWriteCursor, RTTimeMilliTS() - pStreamDS->Dbg.tsLastTransferredMs, cbAvail, cbFree, cbToTransfer,
+ pStreamDS->Out.fFirstTransfer, pStreamDS->Out.fDrain));
+ pStreamDS->Dbg.tsLastTransferredMs = RTTimeMilliTS();
+#endif
+
+ while (cbToTransfer)
+ {
+ DWORD cb1 = 0;
+ DWORD cb2 = 0;
+
+ void *pvBuf;
+ size_t cbBuf;
+ RTCircBufAcquireReadBlock(pCircBuf, cbToTransfer, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ {
+ PVOID pv1, pv2;
+ hr = directSoundPlayLock(pThis, pStreamDS, pStreamDS->Out.offWritePos, (DWORD)cbBuf,
+ &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */);
+ if (FAILED(hr))
+ {
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ AssertPtr(pv1);
+ Assert(cb1);
+
+ memcpy(pv1, pvBuf, cb1);
+
+ if (pv2 && cb2) /* Buffer wrap-around? Write second part. */
+ memcpy(pv2, (uint8_t *)pvBuf + cb1, cb2);
+
+ directSoundPlayUnlock(pThis, pDSB, pv1, pv2, cb1, cb2);
+
+ pStreamDS->Out.offWritePos = (pStreamDS->Out.offWritePos + cb1 + cb2) % pStreamDS->cbBufSize;
+
+ Assert(cbToTransfer >= cbBuf);
+ cbToTransfer -= (uint32_t)cbBuf;
+
+ cbTransferred += cb1 + cb2;
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cb1 + cb2);
+ }
+
+ pStreamDS->Out.cbTransferred += cbTransferred;
+
+ if ( pStreamDS->Out.fFirstTransfer
+ && pStreamDS->Out.cbTransferred >= DrvAudioHlpFramesToBytes(pStreamDS->Cfg.Backend.cfPreBuf, &pStreamDS->Cfg.Props))
+ {
+ hr = directSoundPlayStart(pThis, pStreamDS);
+ if (SUCCEEDED(hr))
+ {
+ pStreamDS->Out.fFirstTransfer = false;
+ }
+ else
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+
+ cbAvail = (uint32_t)RTCircBufUsed(pCircBuf);
+ if ( !cbAvail
+ && cbTransferred)
+ {
+ pStreamDS->Out.cbLastTransferred = cbTransferred;
+ pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS();
+
+ LogFlowFunc(("cbLastTransferred=%RU32, tsLastTransferredMs=%RU64\n",
+ pStreamDS->Out.cbLastTransferred, pStreamDS->Out.tsLastTransferredMs));
+ }
+
+ LogFlowFunc(("cbTransferred=%RU32, cbAvail=%RU32, rc=%Rrc\n", cbTransferred, cbAvail, rc));
+ return rc;
+}
+
+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;
+}
+
+static HRESULT directSoundPlayStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+
+ HRESULT hr = S_OK;
+
+ if (pStreamDS->Out.pDSB)
+ {
+ DSLOG(("DSound: Stopping playback\n"));
+ hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ if (FAILED(hr))
+ {
+ hr = directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+ if (FAILED(hr))
+ hr = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+ }
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (fFlush)
+ dsoundStreamReset(pThis, pStreamDS);
+ }
+
+ if (FAILED(hr))
+ DSLOGREL(("DSound: %s playback failed with %Rhrc\n", fFlush ? "Stopping" : "Pausing", hr));
+
+ return hr;
+}
+
+/**
+ * Enables or disables a stream.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to enable / disable.
+ * @param fEnable Whether to enable or disable the stream.
+ */
+static int dsoundStreamEnable(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fEnable)
+{
+ RT_NOREF(pThis);
+
+ LogFunc(("%s %s\n",
+ fEnable ? "Enabling" : "Disabling",
+ pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
+
+ if (fEnable)
+ dsoundStreamReset(pThis, pStreamDS);
+
+ pStreamDS->fEnabled = fEnable;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Resets the state of a DirectSound stream.
+ *
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to reset state for.
+ */
+static void dsoundStreamReset(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ RT_NOREF(pThis);
+
+ LogFunc(("Resetting %s\n",
+ pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN ? "capture" : "playback"));
+
+ if (pStreamDS->pCircBuf)
+ RTCircBufReset(pStreamDS->pCircBuf);
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ pStreamDS->In.offReadPos = 0;
+ pStreamDS->In.cOverruns = 0;
+
+ /* Also reset the DirectSound Capture Buffer (DSCB) by clearing all data to make sure
+ * not stale audio data is left. */
+ if (pStreamDS->In.pDSCB)
+ {
+ PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2;
+ HRESULT hr = directSoundCaptureLock(pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2,
+ 0 /* Flags */);
+ if (SUCCEEDED(hr))
+ {
+ DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
+ if (pv2 && cb2)
+ DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
+ directSoundCaptureUnlock(pStreamDS->In.pDSCB, pv1, pv2, cb1, cb2);
+ }
+ }
+ }
+ else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pStreamDS->Out.fFirstTransfer = true;
+ pStreamDS->Out.fDrain = false;
+ pStreamDS->Out.cUnderruns = 0;
+
+ pStreamDS->Out.cbLastTransferred = 0;
+ pStreamDS->Out.tsLastTransferredMs = 0;
+
+ pStreamDS->Out.cbTransferred = 0;
+ pStreamDS->Out.cbWritten = 0;
+
+ pStreamDS->Out.offWritePos = 0;
+ pStreamDS->Out.offPlayCursorLastPending = 0;
+ pStreamDS->Out.offPlayCursorLastPlayed = 0;
+
+ /* Also reset the DirectSound Buffer (DSB) by setting the position to 0 and clear all data to make sure
+ * not stale audio data is left. */
+ if (pStreamDS->Out.pDSB)
+ {
+ HRESULT hr = IDirectSoundBuffer8_SetCurrentPosition(pStreamDS->Out.pDSB, 0);
+ if (SUCCEEDED(hr))
+ {
+ PVOID pv1; PVOID pv2; DWORD cb1; DWORD cb2;
+ hr = directSoundPlayLock(pThis, pStreamDS, 0 /* Offset */, pStreamDS->cbBufSize, &pv1, &pv2, &cb1, &cb2,
+ 0 /* Flags */);
+ if (SUCCEEDED(hr))
+ {
+ DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv1, cb1, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb1));
+ if (pv2 && cb2)
+ DrvAudioHlpClearBuf(&pStreamDS->Cfg.Props, pv2, cb2, PDMAUDIOPCMPROPS_B2F(&pStreamDS->Cfg.Props, cb2));
+ directSoundPlayUnlock(pThis, pStreamDS->Out.pDSB, pv1, pv2, cb1, cb2);
+ }
+ }
+ }
+ }
+
+#ifdef LOG_ENABLED
+ pStreamDS->Dbg.tsLastTransferredMs = 0;
+#endif
+}
+
+
+/**
+ * Starts playing a DirectSound stream.
+ *
+ * @return HRESULT
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to start playing.
+ */
+static HRESULT directSoundPlayStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ HRESULT hr = S_OK;
+
+ DWORD fFlags = DSCBSTART_LOOPING;
+
+ for (unsigned i = 0; i < DRV_DSOUND_RESTORE_ATTEMPTS_MAX; i++)
+ {
+ DSLOG(("DSound: Starting playback\n"));
+ hr = IDirectSoundBuffer8_Play(pStreamDS->Out.pDSB, 0, 0, fFlags);
+ if ( SUCCEEDED(hr)
+ || hr != DSERR_BUFFERLOST)
+ break;
+ else
+ {
+ LogFunc(("Restarting playback failed due to lost buffer, restoring ...\n"));
+ directSoundPlayRestore(pThis, pStreamDS->Out.pDSB);
+ }
+ }
+
+ return hr;
+}
+
+
+/*
+ * DirectSoundCapture
+ */
+
+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->DestSource.Source)
+ {
+ case PDMAUDIORECSOURCE_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 PDMAUDIORECSOURCE_MIC:
+ {
+ RTListForEach(&pThis->lstDevInput, pDev, DSOUNDDEV, Node)
+ {
+ if (RTStrIStr(pDev->pszName, "Mic")) /** @todo What is with non en_us windows versions? */
+ break;
+ }
+
+ if (RTListNodeIsDummy(&pThis->lstDevInput, pDev, DSOUNDDEV, Node))
+ pDev = NULL; /* Found nothing. */
+
+ 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",
+ DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pDev->pszName));
+
+ pGUID = &pDev->Guid;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DSound: Selecting recording device failed with %Rrc\n", rc));
+ return NULL;
+ }
+
+ char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
+
+ /* This always has to be in the release log. */
+ LogRel(("DSound: Guest source '%s' is using host recording device with GUID '%s'\n",
+ DrvAudioHlpRecSrcToStr(pCfg->DestSource.Source), pszGUID ? pszGUID: "{?}"));
+
+ if (pszGUID)
+ {
+ RTStrFree(pszGUID);
+ pszGUID = NULL;
+ }
+
+ return pGUID;
+}
+
+/**
+ * Transfers audio data from the DirectSound capture instance to the internal buffer.
+ * Due to internal accounting and querying DirectSound, this function knows how much it can transfer at once.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pStreamDS Stream to capture audio data for.
+ */
+static int dsoundCaptureTransfer(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ RT_NOREF(pThis);
+
+ LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB = pStreamDS->In.pDSCB;
+ AssertPtr(pDSCB);
+
+ DWORD offCaptureCursor;
+ HRESULT hr = IDirectSoundCaptureBuffer_GetCurrentPosition(pDSCB, NULL, &offCaptureCursor);
+ if (FAILED(hr))
+ {
+ AssertFailed();
+ return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+
+ DWORD cbUsed = dsoundRingDistance(offCaptureCursor, pStreamDS->In.offReadPos, pStreamDS->cbBufSize);
+
+ PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
+ AssertPtr(pCircBuf);
+
+ uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf);
+ if ( !cbFree
+ || pStreamDS->In.cOverruns < 32) /** @todo Make this configurable. */
+ {
+ DSLOG(("DSound: Warning: Capture buffer full, skipping to record data (%RU32 bytes)\n", cbUsed));
+ pStreamDS->In.cOverruns++;
+ }
+
+ DWORD cbToCapture = RT_MIN(cbUsed, cbFree);
+
+ Log3Func(("cbUsed=%ld, cbToCapture=%ld\n", cbUsed, cbToCapture));
+
+ while (cbToCapture)
+ {
+ void *pvBuf;
+ size_t cbBuf;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToCapture, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ {
+ PVOID pv1, pv2;
+ DWORD cb1, cb2;
+ hr = directSoundCaptureLock(pStreamDS, pStreamDS->In.offReadPos, (DWORD)cbBuf,
+ &pv1, &pv2, &cb1, &cb2, 0 /* dwFlags */);
+ if (FAILED(hr))
+ break;
+
+ AssertPtr(pv1);
+ Assert(cb1);
+
+ memcpy(pvBuf, pv1, cb1);
+
+ if (pv2 && cb2) /* Buffer wrap-around? Write second part. */
+ memcpy((uint8_t *)pvBuf + cb1, pv2, cb2);
+
+ directSoundCaptureUnlock(pDSCB, pv1, pv2, cb1, cb2);
+
+ pStreamDS->In.offReadPos = (pStreamDS->In.offReadPos + cb1 + cb2) % pStreamDS->cbBufSize;
+
+ Assert(cbToCapture >= cbBuf);
+ cbToCapture -= (uint32_t)cbBuf;
+ }
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ if (cbBuf)
+ {
+ RTFILE fh;
+ int rc2 = RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "dsoundCapture.pcm",
+ RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc2))
+ {
+ RTFileWrite(fh, pvBuf, cbBuf, NULL);
+ RTFileClose(fh);
+ }
+ }
+#endif
+ RTCircBufReleaseWriteBlock(pCircBuf, cbBuf);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys the DirectSound capturing interface.
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance to destroy capturing interface for.
+ */
+static void directSoundCaptureInterfaceDestroy(PDRVHOSTDSOUND pThis)
+{
+ if (pThis->pDSC)
+ {
+ LogFlowFuncEnter();
+
+ IDirectSoundCapture_Release(pThis->pDSC);
+ pThis->pDSC = NULL;
+ }
+}
+
+/**
+ * Creates the DirectSound capturing interface.
+ *
+ * @return IPRT status code.
+ * @param pThis Driver instance to create the capturing interface for.
+ * @param pCfg Audio stream to use for creating the capturing interface.
+ */
+static HRESULT directSoundCaptureInterfaceCreate(PDRVHOSTDSOUND pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pCfg, E_POINTER);
+
+ if (pThis->pDSC)
+ return S_OK;
+
+ LogFlowFuncEnter();
+
+ HRESULT hr = CoCreateInstance(CLSID_DirectSoundCapture8, NULL, CLSCTX_ALL,
+ IID_IDirectSoundCapture8, (void **)&pThis->pDSC);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Creating capture instance failed with %Rhrc\n", hr));
+ }
+ else
+ {
+ LPCGUID pGUID = dsoundCaptureSelectDevice(pThis, pCfg);
+ /* pGUID can be NULL when using the default device. */
+
+ hr = IDirectSoundCapture_Initialize(pThis->pDSC, pGUID);
+ if (FAILED(hr))
+ {
+ if (hr == DSERR_NODRIVER) /* Usually means that no capture devices are attached. */
+ DSLOGREL(("DSound: Capture device currently is unavailable\n"));
+ else
+ DSLOGREL(("DSound: Initializing capturing device failed with %Rhrc\n", hr));
+
+ directSoundCaptureInterfaceDestroy(pThis);
+ }
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+static HRESULT directSoundCaptureClose(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+
+ LogFlowFuncEnter();
+
+ HRESULT hr = directSoundCaptureStop(pThis, pStreamDS, true /* fFlush */);
+ if (FAILED(hr))
+ return hr;
+
+ if ( pStreamDS
+ && pStreamDS->In.pDSCB)
+ {
+ DSLOG(("DSound: Closing capturing stream\n"));
+
+ IDirectSoundCaptureBuffer8_Release(pStreamDS->In.pDSCB);
+ pStreamDS->In.pDSCB = NULL;
+ }
+
+ LogFlowFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+static HRESULT directSoundCaptureOpen(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+ AssertPtrReturn(pCfgReq, E_POINTER);
+ AssertPtrReturn(pCfgAcq, E_POINTER);
+
+ LogFlowFuncEnter();
+
+ Assert(pStreamDS->In.pDSCB == NULL);
+
+ DSLOG(("DSound: Opening capturing stream (uHz=%RU32, cChannels=%RU8, cBits=%RU8, fSigned=%RTbool)\n",
+ pCfgReq->Props.uHz,
+ pCfgReq->Props.cChannels,
+ pCfgReq->Props.cBytes * 8,
+ pCfgReq->Props.fSigned));
+
+ WAVEFORMATEX wfx;
+ int rc = dsoundWaveFmtFromCfg(pCfgReq, &wfx);
+ if (RT_FAILURE(rc))
+ return E_INVALIDARG;
+
+ dsoundUpdateStatusInternalEx(pThis, NULL /* Cfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */);
+
+ HRESULT hr = directSoundCaptureInterfaceCreate(pThis, pCfgReq);
+ if (FAILED(hr))
+ return hr;
+
+ do /* To use breaks. */
+ {
+ DSCBUFFERDESC bd;
+ RT_ZERO(bd);
+
+ bd.dwSize = sizeof(bd);
+ bd.lpwfxFormat = &wfx;
+ bd.dwBufferBytes = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props);
+
+ DSLOG(("DSound: Requested capture buffer is %RU64ms (%ld bytes)\n",
+ DrvAudioHlpBytesToMilli(bd.dwBufferBytes, &pCfgReq->Props), bd.dwBufferBytes));
+
+ LPDIRECTSOUNDCAPTUREBUFFER pDSCB;
+ hr = IDirectSoundCapture_CreateCaptureBuffer(pThis->pDSC, &bd, &pDSCB, NULL);
+ if (FAILED(hr))
+ {
+ if (hr == E_ACCESSDENIED)
+ {
+ DSLOGREL(("DSound: Capturing input from host not possible, access denied\n"));
+ }
+ else
+ DSLOGREL(("DSound: Creating capture buffer failed with %Rhrc\n", hr));
+ break;
+ }
+
+ hr = IDirectSoundCaptureBuffer_QueryInterface(pDSCB, IID_IDirectSoundCaptureBuffer8, (void **)&pStreamDS->In.pDSCB);
+ IDirectSoundCaptureBuffer_Release(pDSCB);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Querying interface for capture buffer failed with %Rhrc\n", hr));
+ break;
+ }
+
+ /*
+ * Query the actual parameters.
+ */
+ DWORD offByteReadPos = 0;
+ hr = IDirectSoundCaptureBuffer8_GetCurrentPosition(pStreamDS->In.pDSCB, NULL, &offByteReadPos);
+ if (FAILED(hr))
+ {
+ offByteReadPos = 0;
+ DSLOGREL(("DSound: Getting capture position failed with %Rhrc\n", hr));
+ }
+
+ RT_ZERO(wfx);
+ hr = IDirectSoundCaptureBuffer8_GetFormat(pStreamDS->In.pDSCB, &wfx, sizeof(wfx), NULL);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Getting capture format failed with %Rhrc\n", hr));
+ break;
+ }
+
+ DSCBCAPS bc;
+ RT_ZERO(bc);
+ bc.dwSize = sizeof(bc);
+ hr = IDirectSoundCaptureBuffer8_GetCaps(pStreamDS->In.pDSCB, &bc);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Getting capture capabilities failed with %Rhrc\n", hr));
+ break;
+ }
+
+ DSLOG(("DSound: Acquired capture buffer is %RU64ms (%ld bytes)\n",
+ DrvAudioHlpBytesToMilli(bc.dwBufferBytes, &pCfgReq->Props), bc.dwBufferBytes));
+
+ DSLOG(("DSound: Capture format:\n"
+ " dwBufferBytes = %RI32\n"
+ " dwFlags = 0x%x\n"
+ " wFormatTag = %RI16\n"
+ " nChannels = %RI16\n"
+ " nSamplesPerSec = %RU32\n"
+ " nAvgBytesPerSec = %RU32\n"
+ " nBlockAlign = %RI16\n"
+ " wBitsPerSample = %RI16\n"
+ " cbSize = %RI16\n",
+ bc.dwBufferBytes,
+ bc.dwFlags,
+ wfx.wFormatTag,
+ wfx.nChannels,
+ wfx.nSamplesPerSec,
+ wfx.nAvgBytesPerSec,
+ wfx.nBlockAlign,
+ wfx.wBitsPerSample,
+ wfx.cbSize));
+
+ if (bc.dwBufferBytes & pStreamDS->uAlign)
+ DSLOGREL(("DSound: Capture GetCaps returned misaligned buffer: size %RU32, alignment %RU32\n",
+ bc.dwBufferBytes, pStreamDS->uAlign + 1));
+
+ /* Initial state: reading at the initial capture position, no error. */
+ pStreamDS->In.offReadPos = 0;
+ pStreamDS->cbBufSize = bc.dwBufferBytes;
+
+ rc = RTCircBufCreate(&pStreamDS->pCircBuf, pStreamDS->cbBufSize) * 2; /* Use "double buffering". */
+ AssertRC(rc);
+
+ pThis->pDSStrmIn = pStreamDS;
+
+ pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamDS->cbBufSize);
+
+ } while (0);
+
+ if (FAILED(hr))
+ directSoundCaptureClose(pThis, pStreamDS);
+
+ LogFlowFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+static HRESULT directSoundCaptureStop(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, bool fFlush)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+
+ RT_NOREF(pThis);
+
+ HRESULT hr = S_OK;
+
+ if (pStreamDS->In.pDSCB)
+ {
+ DSLOG(("DSound: Stopping capture\n"));
+ hr = IDirectSoundCaptureBuffer_Stop(pStreamDS->In.pDSCB);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ if (fFlush)
+ dsoundStreamReset(pThis, pStreamDS);
+ }
+
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Stopping capture buffer failed with %Rhrc\n", hr));
+
+ return hr;
+}
+
+static HRESULT directSoundCaptureStart(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ AssertPtrReturn(pThis, E_POINTER);
+ AssertPtrReturn(pStreamDS, E_POINTER);
+
+ HRESULT hr;
+ if (pStreamDS->In.pDSCB)
+ {
+ DWORD dwStatus;
+ hr = IDirectSoundCaptureBuffer8_GetStatus(pStreamDS->In.pDSCB, &dwStatus);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: Retrieving capture status failed with %Rhrc\n", hr));
+ }
+ else
+ {
+ if (dwStatus & DSCBSTATUS_CAPTURING)
+ {
+ DSLOG(("DSound: Already capturing\n"));
+ }
+ else
+ {
+ const DWORD fFlags = DSCBSTART_LOOPING;
+
+ DSLOG(("DSound: Starting to capture\n"));
+ hr = IDirectSoundCaptureBuffer8_Start(pStreamDS->In.pDSCB, fFlags);
+ if (FAILED(hr))
+ DSLOGREL(("DSound: Starting to capture failed with %Rhrc\n", hr));
+ }
+ }
+ }
+ else
+ hr = E_UNEXPECTED;
+
+ LogFlowFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+static int dsoundDevAdd(PRTLISTANCHOR pList, LPGUID pGUID, LPCWSTR pwszDescription, PDSOUNDDEV *ppDev)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pGUID, VERR_INVALID_POINTER);
+ AssertPtrReturn(pwszDescription, VERR_INVALID_POINTER);
+
+ PDSOUNDDEV pDev = (PDSOUNDDEV)RTMemAlloc(sizeof(DSOUNDDEV));
+ if (!pDev)
+ return VERR_NO_MEMORY;
+
+ int rc = RTUtf16ToUtf8(pwszDescription, &pDev->pszName);
+ if ( RT_SUCCESS(rc)
+ && pGUID)
+ {
+ memcpy(&pDev->Guid, pGUID, sizeof(GUID));
+ }
+
+ if (RT_SUCCESS(rc))
+ RTListAppend(pList, &pDev->Node);
+
+ if (ppDev)
+ *ppDev = pDev;
+
+ return rc;
+}
+
+static void dsoundDeviceRemove(PDSOUNDDEV pDev)
+{
+ if (pDev)
+ {
+ if (pDev->pszName)
+ {
+ RTStrFree(pDev->pszName);
+ pDev->pszName = NULL;
+ }
+
+ RTListNodeRemove(&pDev->Node);
+
+ RTMemFree(pDev);
+ pDev = NULL;
+ }
+}
+
+static void dsoundLogDevice(const char *pszType, LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule)
+{
+ char *pszGUID = dsoundGUIDToUtf8StrA(pGUID);
+ /* This always has to be in the release log.
+ * Only print this when we're running in verbose (audio debug) mode, as this can generate a lot of content. */
+ LogRel2(("DSound: %s: GUID: %s [%ls] (Module: %ls)\n", pszType, pszGUID ? pszGUID : "{?}", pwszDescription, pwszModule));
+ RTStrFree(pszGUID);
+}
+
+static BOOL CALLBACK dsoundDevicesEnumCbPlayback(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext)
+{
+ PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext;
+ AssertPtrReturn(pCtx, FALSE);
+ AssertPtrReturn(pCtx->pDrv, FALSE);
+
+ if (!pGUID)
+ return TRUE;
+
+ AssertPtrReturn(pwszDescription, FALSE);
+ /* Do not care about pwszModule. */
+
+ if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG)
+ dsoundLogDevice("Output", pGUID, pwszDescription, pwszModule);
+
+ int rc = dsoundDevAdd(&pCtx->pDrv->lstDevOutput,
+ pGUID, pwszDescription, NULL /* ppDev */);
+ if (RT_FAILURE(rc))
+ return FALSE; /* Abort enumeration. */
+
+ pCtx->cDevOut++;
+
+ return TRUE;
+}
+
+static BOOL CALLBACK dsoundDevicesEnumCbCapture(LPGUID pGUID, LPCWSTR pwszDescription, LPCWSTR pwszModule, PVOID lpContext)
+{
+ PDSOUNDENUMCBCTX pCtx = (PDSOUNDENUMCBCTX)lpContext;
+ AssertPtrReturn(pCtx, FALSE);
+ AssertPtrReturn(pCtx->pDrv, FALSE);
+
+ if (!pGUID)
+ return TRUE;
+
+ if (pCtx->fFlags & DSOUNDENUMCBFLAGS_LOG)
+ dsoundLogDevice("Input", pGUID, pwszDescription, pwszModule);
+
+ int rc = dsoundDevAdd(&pCtx->pDrv->lstDevInput,
+ pGUID, pwszDescription, NULL /* ppDev */);
+ if (RT_FAILURE(rc))
+ return FALSE; /* Abort enumeration. */
+
+ pCtx->cDevIn++;
+
+ return TRUE;
+}
+
+/**
+ * Does a (Re-)enumeration of the host's playback + capturing devices.
+ *
+ * @return IPRT status code.
+ * @param pThis Host audio driver instance.
+ * @param pEnmCtx Enumeration context to use.
+ * @param fEnum Enumeration flags.
+ */
+static int dsoundDevicesEnumerate(PDRVHOSTDSOUND pThis, PDSOUNDENUMCBCTX pEnmCtx, uint32_t fEnum)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEnmCtx, VERR_INVALID_POINTER);
+
+ dsoundDevicesClear(pThis);
+
+ RTLDRMOD hDSound = NULL;
+ int rc = RTLdrLoadSystem("dsound.dll", true /*fNoUnload*/, &hDSound);
+ if (RT_SUCCESS(rc))
+ {
+ PFNDIRECTSOUNDENUMERATEW pfnDirectSoundEnumerateW = NULL;
+ PFNDIRECTSOUNDCAPTUREENUMERATEW pfnDirectSoundCaptureEnumerateW = NULL;
+
+ rc = RTLdrGetSymbol(hDSound, "DirectSoundEnumerateW", (void**)&pfnDirectSoundEnumerateW);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hDSound, "DirectSoundCaptureEnumerateW", (void**)&pfnDirectSoundCaptureEnumerateW);
+
+ if (RT_SUCCESS(rc))
+ {
+ HRESULT hr = pfnDirectSoundEnumerateW(&dsoundDevicesEnumCbPlayback, pEnmCtx);
+ if (FAILED(hr))
+ LogRel2(("DSound: Error enumerating host playback devices: %Rhrc\n", hr));
+
+ hr = pfnDirectSoundCaptureEnumerateW(&dsoundDevicesEnumCbCapture, pEnmCtx);
+ if (FAILED(hr))
+ LogRel2(("DSound: Error enumerating host capturing devices: %Rhrc\n", hr));
+
+ if (fEnum & DSOUNDENUMCBFLAGS_LOG)
+ {
+ LogRel2(("DSound: Found %RU8 host playback devices\n", pEnmCtx->cDevOut));
+ LogRel2(("DSound: Found %RU8 host capturing devices\n", pEnmCtx->cDevIn));
+ }
+ }
+
+ RTLdrClose(hDSound);
+ }
+ else
+ {
+ /* No dsound.dll on this system. */
+ LogRel2(("DSound: Could not load dsound.dll: %Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+/**
+ * Updates this host driver's internal status, according to the global, overall input/output
+ * state and all connected (native) audio streams.
+ *
+ * @param pThis Host audio driver instance.
+ * @param pCfg Where to store the backend configuration. Optional.
+ * @param fEnum Enumeration flags.
+ */
+static void dsoundUpdateStatusInternalEx(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
+{
+ AssertPtrReturnVoid(pThis);
+ /* pCfg is optional. */
+
+ PDMAUDIOBACKENDCFG Cfg;
+ RT_ZERO(Cfg);
+
+ Cfg.cbStreamOut = sizeof(DSOUNDSTREAM);
+ Cfg.cbStreamIn = sizeof(DSOUNDSTREAM);
+
+ DSOUNDENUMCBCTX cbCtx = { pThis, fEnum, 0, 0 };
+
+ int rc = dsoundDevicesEnumerate(pThis, &cbCtx, fEnum);
+ 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->fEnabledOut = RT_BOOL(cbCtx.cDevOut);
+ pThis->fEnabledIn = RT_BOOL(cbCtx.cDevIn);
+
+ RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "DirectSound audio driver");
+
+ Cfg.cMaxStreamsIn = UINT32_MAX;
+ Cfg.cMaxStreamsOut = UINT32_MAX;
+
+ if (pCfg)
+ memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+}
+
+static void dsoundUpdateStatusInternal(PDRVHOSTDSOUND pThis)
+{
+ dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, 0 /* fEnum */);
+}
+
+static int dsoundCreateStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ LogFlowFunc(("pStreamDS=%p, pCfgReq=%p\n", pStreamDS, pCfgReq));
+
+ int rc = VINF_SUCCESS;
+
+ /* Try to open playback in case the device is already there. */
+ HRESULT hr = directSoundPlayOpen(pThis, pStreamDS, pCfgReq, pCfgAcq);
+ if (SUCCEEDED(hr))
+ {
+ rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
+ if (RT_SUCCESS(rc))
+ dsoundStreamReset(pThis, pStreamDS);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static int dsoundControlStreamOut(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ LogFlowFunc(("pStreamDS=%p, cmd=%d\n", pStreamDS, enmStreamCmd));
+
+ int rc = VINF_SUCCESS;
+
+ HRESULT hr;
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ {
+ dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ hr = directSoundPlayStart(pThis, pStreamDS);
+ if (FAILED(hr))
+ rc = VERR_NOT_SUPPORTED; /** @todo Fix this. */
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ {
+ dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */);
+ hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
+ if (FAILED(hr))
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ hr = directSoundPlayStop(pThis, pStreamDS, false /* fFlush */);
+ if (FAILED(hr))
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ {
+ /* Make sure we transferred everything. */
+ pStreamDS->fEnabled = true;
+ pStreamDS->Out.fDrain = true;
+ rc = dsoundPlayTransfer(pThis, pStreamDS);
+ if ( RT_SUCCESS(rc)
+ && pStreamDS->Out.fFirstTransfer)
+ {
+ /* If this was the first transfer ever for this stream, make sure to also play the (short) audio data. */
+ DSLOG(("DSound: Started playing output (short sound)\n"));
+
+ pStreamDS->Out.fFirstTransfer = false;
+ pStreamDS->Out.cbLastTransferred = pStreamDS->Out.cbTransferred; /* All transferred audio data must be played. */
+ pStreamDS->Out.tsLastTransferredMs = RTTimeMilliTS();
+
+ hr = directSoundPlayStart(pThis, pStreamDS);
+ if (FAILED(hr))
+ rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
+ }
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DROP:
+ {
+ pStreamDS->Out.cbLastTransferred = 0;
+ pStreamDS->Out.tsLastTransferredMs = 0;
+ RTCircBufReset(pStreamDS->pCircBuf);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+int drvHostDSoundStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcxWritten is optional. */
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+ PRTCIRCBUF pCircBuf = pStreamDS->pCircBuf;
+
+ uint32_t cbToPlay = RT_MIN(cxBuf, (uint32_t)RTCircBufFree(pCircBuf));
+ while (cbToPlay)
+ {
+ void *pvChunk;
+ size_t cbChunk;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbToPlay, &pvChunk, &cbChunk);
+
+ if (cbChunk)
+ {
+ memcpy(pvChunk, pbBuf, cbChunk);
+
+ pbBuf += cbChunk;
+ Assert(cbToPlay >= cbChunk);
+ cbToPlay -= (uint32_t)cbChunk;
+
+ cbWrittenTotal += (uint32_t)cbChunk;
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbChunk);
+ }
+
+ Assert(cbWrittenTotal <= cxBuf);
+ Assert(cbWrittenTotal == cxBuf);
+
+ pStreamDS->Out.cbWritten += cbWrittenTotal;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+ else
+ dsoundUpdateStatusInternal(pThis);
+
+ return rc;
+}
+
+static int dsoundDestroyStreamOut(PDRVHOSTDSOUND pThis, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ LogFlowFuncEnter();
+
+ HRESULT hr = directSoundPlayStop(pThis, pStreamDS, true /* fFlush */);
+ if (SUCCEEDED(hr))
+ {
+ hr = directSoundPlayClose(pThis, pStreamDS);
+ if (FAILED(hr))
+ return VERR_GENERAL_FAILURE; /** @todo Fix. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+static int dsoundCreateStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ LogFunc(("pStreamDS=%p, pCfgReq=%p, enmRecSource=%s\n",
+ pStreamDS, pCfgReq, DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source)));
+
+ int rc = VINF_SUCCESS;
+
+ /* Try to open capture in case the device is already there. */
+ HRESULT hr = directSoundCaptureOpen(pThis, pStreamDS, pCfgReq, pCfgAcq);
+ if (SUCCEEDED(hr))
+ {
+ rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
+ if (RT_SUCCESS(rc))
+ dsoundStreamReset(pThis, pStreamDS);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ return rc;
+}
+
+static int dsoundControlStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ LogFlowFunc(("pStreamDS=%p, enmStreamCmd=%ld\n", pStreamDS, enmStreamCmd));
+
+ int rc = VINF_SUCCESS;
+
+ HRESULT hr;
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ dsoundStreamEnable(pThis, pStreamDS, true /* fEnable */);
+ RT_FALL_THROUGH();
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ /* Try to start capture. If it fails, then reopen and try again. */
+ hr = directSoundCaptureStart(pThis, pStreamDS);
+ if (FAILED(hr))
+ {
+ hr = directSoundCaptureClose(pThis, pStreamDS);
+ if (SUCCEEDED(hr))
+ {
+ PDMAUDIOSTREAMCFG CfgAcq;
+ hr = directSoundCaptureOpen(pThis, pStreamDS, &pStreamDS->Cfg /* pCfgReq */, &CfgAcq);
+ if (SUCCEEDED(hr))
+ {
+ rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, &CfgAcq);
+ if (RT_FAILURE(rc))
+ break;
+
+ /** @todo What to do if the format has changed? */
+
+ hr = directSoundCaptureStart(pThis, pStreamDS);
+ }
+ }
+ }
+
+ if (FAILED(hr))
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ dsoundStreamEnable(pThis, pStreamDS, false /* fEnable */);
+ RT_FALL_THROUGH();
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ directSoundCaptureStop(pThis, pStreamDS,
+ enmStreamCmd == PDMAUDIOSTREAMCMD_DISABLE /* fFlush */);
+
+ /* Return success in any case, as stopping the capture can fail if
+ * the capture buffer is not around anymore.
+ *
+ * This can happen if the host's capturing device has been changed suddenly. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+int drvHostDSoundStreamCapture(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbReadTotal = 0;
+
+ uint32_t cbToRead = RT_MIN((uint32_t)RTCircBufUsed(pStreamDS->pCircBuf), cxBuf);
+ while (cbToRead)
+ {
+ void *pvChunk;
+ size_t cbChunk;
+ RTCircBufAcquireReadBlock(pStreamDS->pCircBuf, cbToRead, &pvChunk, &cbChunk);
+
+ if (cbChunk)
+ {
+ memcpy((uint8_t *)pvBuf + cbReadTotal, pvChunk, cbChunk);
+ cbReadTotal += (uint32_t)cbChunk;
+ Assert(cbToRead >= cbChunk);
+ cbToRead -= (uint32_t)cbChunk;
+ }
+
+ RTCircBufReleaseReadBlock(pStreamDS->pCircBuf, cbChunk);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+ else
+ dsoundUpdateStatusInternal(pThis);
+
+ return rc;
+}
+
+static int dsoundDestroyStreamIn(PDRVHOSTDSOUND pThis, PDSOUNDSTREAM pStreamDS)
+{
+ LogFlowFuncEnter();
+
+ directSoundCaptureClose(pThis, pStreamDS);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+int drvHostDSoundGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+
+ dsoundUpdateStatusInternalEx(pThis, pBackendCfg, 0 /* fEnum */);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+void drvHostDSoundShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+
+ LogFlowFuncEnter();
+
+ RT_NOREF(pThis);
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostDSoundInit(PPDMIHOSTAUDIO pInterface)
+{
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ LogFlowFuncEnter();
+
+ int rc;
+
+ /* Verify that IDirectSound is available. */
+ LPDIRECTSOUND pDirectSound = NULL;
+ HRESULT hr = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_ALL, IID_IDirectSound, (void **)&pDirectSound);
+ if (SUCCEEDED(hr))
+ {
+ IDirectSound_Release(pDirectSound);
+
+ rc = VINF_SUCCESS;
+
+ dsoundUpdateStatusInternalEx(pThis, NULL /* pCfg */, DSOUNDENUMCBFLAGS_LOG /* fEnum */);
+ }
+ else
+ {
+ DSLOGREL(("DSound: DirectSound not available: %Rhrc\n", hr));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static LPCGUID dsoundConfigQueryGUID(PCFGMNODE pCfg, const char *pszName, RTUUID *pUuid)
+{
+ LPCGUID pGuid = NULL;
+
+ char *pszGuid = NULL;
+ int rc = CFGMR3QueryStringAlloc(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 int dsoundConfigInit(PDRVHOSTDSOUND pThis, PCFGMNODE pCfg)
+{
+ pThis->Cfg.pGuidPlay = dsoundConfigQueryGUID(pCfg, "DeviceGuidOut", &pThis->Cfg.uuidPlay);
+ pThis->Cfg.pGuidCapture = dsoundConfigQueryGUID(pCfg, "DeviceGuidIn", &pThis->Cfg.uuidCapture);
+
+ DSLOG(("DSound: Configuration: DeviceGuidOut {%RTuuid}, DeviceGuidIn {%RTuuid}\n",
+ &pThis->Cfg.uuidPlay,
+ &pThis->Cfg.uuidCapture));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDSoundGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostDSoundStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = dsoundCreateStreamIn(pThis, pStreamDS, pCfgReq, pCfgAcq);
+ else
+ rc = dsoundCreateStreamOut(pThis, pStreamDS, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = DrvAudioHlpStreamCfgCopy(&pStreamDS->Cfg, pCfgAcq);
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pStreamDS->CritSect);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostDSoundStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ int rc;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ rc = dsoundDestroyStreamIn(pThis, pStreamDS);
+ else
+ rc = dsoundDestroyStreamOut(pThis, pStreamDS);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCritSectIsInitialized(&pStreamDS->CritSect))
+ rc = RTCritSectDelete(&pStreamDS->CritSect);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostDSoundStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ int rc;
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ rc = dsoundControlStreamIn(pThis, pStreamDS, enmStreamCmd);
+ else
+ rc = dsoundControlStreamOut(pThis, pStreamDS, enmStreamCmd);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
+
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ if ( pStreamDS->fEnabled
+ && pStreamDS->pCircBuf)
+ {
+ return (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf);
+ }
+
+ return 0;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOSTREAMSTS_FLAG_NONE);
+ AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
+
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ if (pStreamDS->fEnabled)
+ return (uint32_t)RTCircBufFree(pStreamDS->pCircBuf);
+
+ return 0;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetPending}
+ */
+static DECLCALLBACK(uint32_t) drvHostDSoundStreamGetPending(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, 0);
+
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ uint32_t cbPending = 0;
+
+ /* Any uncommitted data left? */
+ if (pStreamDS->pCircBuf)
+ cbPending = (uint32_t)RTCircBufUsed(pStreamDS->pCircBuf);
+
+ /* Check if we have committed data which still needs to be played by
+ * by DirectSound's streaming buffer. */
+ if (!cbPending)
+ {
+ const uint64_t diffLastTransferredMs = RTTimeMilliTS() - pStreamDS->Out.tsLastTransferredMs;
+ const uint64_t uLastTranserredChunkMs = DrvAudioHlpBytesToMilli(pStreamDS->Out.cbLastTransferred, &pStreamDS->Cfg.Props);
+ if ( uLastTranserredChunkMs
+ && diffLastTransferredMs < uLastTranserredChunkMs)
+ cbPending = 1;
+
+ Log3Func(("diffLastTransferredMs=%RU64ms, uLastTranserredChunkMs=%RU64ms (%RU32 bytes) -> cbPending=%RU32\n",
+ diffLastTransferredMs, uLastTranserredChunkMs, pStreamDS->Out.cbLastTransferred, cbPending));
+ }
+ else
+ Log3Func(("cbPending=%RU32\n", cbPending));
+
+ return cbPending;
+ }
+ /* Note: For input streams we never have pending data left. */
+
+ return 0;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDSoundStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, PDMAUDIOSTREAMSTS_FLAG_NONE);
+
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
+
+ if (pStreamDS->fEnabled)
+ strmSts |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+
+ return strmSts;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostDSoundStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+ PDSOUNDSTREAM pStreamDS = (PDSOUNDSTREAM)pStream;
+
+ if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ return dsoundCaptureTransfer(pThis, pStreamDS);
+ }
+ else if (pStreamDS->Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ return dsoundPlayTransfer(pThis, pStreamDS);
+ }
+
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnSetCallback}
+ */
+static DECLCALLBACK(int) drvHostDSoundSetCallback(PPDMIHOSTAUDIO pInterface, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ /* pfnCallback will be handled below. */
+
+ PDRVHOSTDSOUND pThis = PDMIHOSTAUDIO_2_DRVHOSTDSOUND(pInterface);
+
+ int rc = RTCritSectEnter(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("pfnCallback=%p\n", pfnCallback));
+
+ if (pfnCallback) /* Register. */
+ {
+ Assert(pThis->pfnCallback == NULL);
+ pThis->pfnCallback = pfnCallback;
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ if (pThis->m_pNotificationClient)
+ pThis->m_pNotificationClient->RegisterCallback(pThis->pDrvIns, pfnCallback);
+#endif
+ }
+ else /* Unregister. */
+ {
+ if (pThis->pfnCallback)
+ pThis->pfnCallback = NULL;
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ if (pThis->m_pNotificationClient)
+ pThis->m_pNotificationClient->UnregisterCallback();
+#endif
+ }
+
+ int rc2 = RTCritSectLeave(&pThis->CritSect);
+ AssertRC(rc2);
+ }
+
+ return rc;
+}
+#endif
+
+
+/*********************************************************************************************************************************
+* 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->Dispose();
+ pThis->m_pNotificationClient->Release();
+
+ pThis->m_pNotificationClient = NULL;
+ }
+#endif
+
+ if (pThis->pDrvIns)
+ CoUninitialize();
+
+ int rc2 = RTCritSectDelete(&pThis->CritSect);
+ AssertRC(rc2);
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * @callback_method_impl{FNPDMDRVCONSTRUCT,
+ * Construct a DirectSound Audio driver instance.}
+ */
+static DECLCALLBACK(int) drvHostDSoundConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
+
+ LogRel(("Audio: Initializing DirectSound audio driver\n"));
+
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (FAILED(hr))
+ {
+ DSLOGREL(("DSound: CoInitializeEx failed with %Rhrc\n", hr));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostDSound);
+ pThis->IHostAudio.pfnStreamGetPending = drvHostDSoundStreamGetPending;
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ /* This backend supports host audio callbacks. */
+ pThis->IHostAudio.pfnSetCallback = drvHostDSoundSetCallback;
+ pThis->pfnCallback = NULL;
+#endif
+
+ /*
+ * Init the static parts.
+ */
+ RTListInit(&pThis->lstDevInput);
+ RTListInit(&pThis->lstDevOutput);
+
+ pThis->fEnabledIn = false;
+ pThis->fEnabledOut = false;
+
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_AUDIO_MMNOTIFICATION_CLIENT
+ bool fUseNotificationClient = false;
+
+ char szOSVersion[32];
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion));
+ if (RT_SUCCESS(rc))
+ {
+ /* IMMNotificationClient is available starting at Windows Vista. */
+ if (RTStrVersionCompare(szOSVersion, "6.0") >= 0)
+ fUseNotificationClient = true;
+ }
+
+ if (fUseNotificationClient)
+ {
+ try
+ {
+ pThis->m_pNotificationClient = new VBoxMMNotificationClient();
+
+ HRESULT hr = pThis->m_pNotificationClient->Initialize();
+ if (FAILED(hr))
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+ catch (std::bad_alloc &ex)
+ {
+ NOREF(ex);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ LogRel2(("DSound: Notification client is %s\n", fUseNotificationClient ? "enabled" : "disabled"));
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize configuration values.
+ */
+ rc = dsoundConfigInit(pThis, pCfg);
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+
+/**
+ * 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/DrvHostDebugAudio.cpp b/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp
new file mode 100644
index 00000000..04af08d4
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostDebugAudio.cpp
@@ -0,0 +1,428 @@
+/* $Id: DrvHostDebugAudio.cpp $ */
+/** @file
+ * Debug audio driver.
+ *
+ * Host backend for dumping and injecting audio data from/to the device emulation.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <iprt/alloc.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 "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/**
+ * Structure for keeping a debug input/output stream.
+ */
+typedef struct DEBUGAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Audio file to dump output to or read input from. */
+ PPDMAUDIOFILE pFile;
+ union
+ {
+ struct
+ {
+ /** Timestamp of last captured samples. */
+ uint64_t tsLastCaptured;
+ } In;
+ };
+} DEBUGAUDIOSTREAM, *PDEBUGAUDIOSTREAM;
+
+/**
+ * Debug audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTDEBUGAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVHOSTDEBUGAUDIO, *PDRVHOSTDEBUGAUDIO;
+
+/*******************************************PDM_AUDIO_DRIVER******************************/
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Debug audio driver");
+
+ pBackendCfg->cbStreamOut = sizeof(DEBUGAUDIOSTREAM);
+ pBackendCfg->cbStreamIn = sizeof(DEBUGAUDIOSTREAM);
+
+ pBackendCfg->cMaxStreamsOut = 1; /* Output; writing to a file. */
+ pBackendCfg->cMaxStreamsIn = 0; /** @todo Right now we don't support any input (capturing, injecting from a file). */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostDebugAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostDebugAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+static int debugCreateStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+
+ return VINF_SUCCESS;
+}
+
+
+static int debugCreateStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pDrv, pCfgAcq);
+
+ char szTemp[RTPATH_MAX];
+ int rc = RTPathTemp(szTemp, sizeof(szTemp));
+ if (RT_SUCCESS(rc))
+ {
+ char szFile[RTPATH_MAX];
+ rc = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, "DebugAudioOut",
+ pDrv->pDrvIns->iInstance, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, &pStreamDbg->pFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &pCfgReq->Props);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("DebugAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc));
+ }
+ else
+ LogRel(("DebugAudio: Unable to build file name for temp dir '%s': %Rrc\n", szTemp, rc));
+ }
+ else
+ LogRel(("DebugAudio: Unable to retrieve temp dir: %Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamCreate(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio);
+ PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = debugCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+ else
+ rc = debugCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamDbg->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ RT_NOREF(pInterface);
+ PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream;
+
+ int rc = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cxBuf, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DebugAudio: Writing output failed with %Rrc\n", rc));
+ return rc;
+ }
+
+ if (pcxWritten)
+ *pcxWritten = cxBuf;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pInterface, pStream, pvBuf, cxBuf);
+
+ /* Never capture anything. */
+ if (pcxRead)
+ *pcxRead = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+static int debugDestroyStreamIn(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg)
+{
+ RT_NOREF(pDrv, pStreamDbg);
+ return VINF_SUCCESS;
+}
+
+
+static int debugDestroyStreamOut(PDRVHOSTDEBUGAUDIO pDrv, PDEBUGAUDIOSTREAM pStreamDbg)
+{
+ RT_NOREF(pDrv);
+
+ DrvAudioHlpFileDestroy(pStreamDbg->pFile);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVHOSTDEBUGAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTDEBUGAUDIO, IHostAudio);
+ PDEBUGAUDIOSTREAM pStreamDbg = (PDEBUGAUDIOSTREAM)pStream;
+
+ if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = debugDestroyStreamIn (pDrv, pStreamDbg);
+ else
+ rc = debugDestroyStreamOut(pDrv, pStreamDbg);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg);
+ pStreamDbg->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ RT_NOREF(enmStreamCmd);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDebugAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return 0; /* Never capture anything. */
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostDebugAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostDebugAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostDebugAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostDebugAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO);
+
+ 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) drvHostDebugAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTDEBUGAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDEBUGAUDIO);
+ LogRel(("Audio: Initializing DEBUG driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostDebugAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostDebugAudio);
+
+#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
+ RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "AudioDebugOutput.pcm");
+#endif
+
+ 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(DRVHOSTDEBUGAUDIO),
+ /* pfnConstruct */
+ drvHostDebugAudioConstruct,
+ /* 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/DrvHostNullAudio.cpp b/src/VBox/Devices/Audio/DrvHostNullAudio.cpp
new file mode 100644
index 00000000..aec4e022
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostNullAudio.cpp
@@ -0,0 +1,415 @@
+/* $Id: DrvHostNullAudio.cpp $ */
+/** @file
+ * NULL audio driver.
+ *
+ * This also acts as a fallback if no other backend is available.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ * --------------------------------------------------------------------
+ *
+ * This code is based on: noaudio.c QEMU based code.
+ *
+ * QEMU Timer based audio emulation
+ *
+ * Copyright (c) 2004-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 *
+*********************************************************************************************************************************/
+#include <iprt/mem.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 "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct NULLAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+} NULLAUDIOSTREAM, *PNULLAUDIOSTREAM;
+
+/**
+ * NULL audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTNULLAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVHOSTNULLAUDIO, *PDRVHOSTNULLAUDIO;
+
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostNullAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "NULL audio driver");
+
+ pBackendCfg->cbStreamOut = sizeof(NULLAUDIOSTREAM);
+ pBackendCfg->cbStreamIn = sizeof(NULLAUDIOSTREAM);
+
+ pBackendCfg->cMaxStreamsOut = 1; /* Output */
+ pBackendCfg->cMaxStreamsIn = 2; /* Line input + microphone input. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostNullAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ NOREF(pInterface);
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostNullAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostNullAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+
+ RT_NOREF(pInterface, pStream, pvBuf);
+
+ /* Note: No copying of samples needed here, as this a NULL backend. */
+
+ if (pcxWritten)
+ *pcxWritten = cxBuf; /* Return all bytes as written. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pInterface, pStream);
+
+ PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream;
+
+ /* Return silence. */
+ Assert(pStreamNull->pCfg);
+ DrvAudioHlpClearBuf(&pStreamNull->pCfg->Props, pvBuf, cxBuf, PDMAUDIOPCMPROPS_B2F(&pStreamNull->pCfg->Props, cxBuf));
+
+ if (pcxRead)
+ *pcxRead = cxBuf;
+
+ return VINF_SUCCESS;
+}
+
+
+static int nullCreateStreamIn(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pStreamNull, pCfgReq, pCfgAcq);
+
+ return VINF_SUCCESS;
+}
+
+
+static int nullCreateStreamOut(PNULLAUDIOSTREAM pStreamNull, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pStreamNull, pCfgReq, pCfgAcq);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = nullCreateStreamIn( pStreamNull, pCfgReq, pCfgAcq);
+ else
+ rc = nullCreateStreamOut(pStreamNull, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamNull->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamNull->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static int nullDestroyStreamIn(void)
+{
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+static int nullDestroyStreamOut(PNULLAUDIOSTREAM pStreamNull)
+{
+ RT_NOREF(pStreamNull);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PNULLAUDIOSTREAM pStreamNull = (PNULLAUDIOSTREAM)pStream;
+
+ if (!pStreamNull->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamNull->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = nullDestroyStreamIn();
+ else
+ rc = nullDestroyStreamOut(pStreamNull);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamNull->pCfg);
+ pStreamNull->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ RT_NOREF(pInterface, pStream, enmStreamCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostNullAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostNullAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostNullAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostNullAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ NOREF(pInterface);
+ NOREF(pStream);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostNullAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO);
+
+ 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) drvHostNullAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+ /* pCfg is optional. */
+
+ PDRVHOSTNULLAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTNULLAUDIO);
+ LogRel(("Audio: Initializing NULL driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostNullAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostNullAudio);
+
+ 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(DRVHOSTNULLAUDIO),
+ /* pfnConstruct */
+ drvHostNullAudioConstruct,
+ /* 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/DrvHostOSSAudio.cpp b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp
new file mode 100644
index 00000000..4b1d7905
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostOSSAudio.cpp
@@ -0,0 +1,1169 @@
+/* $Id: DrvHostOSSAudio.cpp $ */
+/** @file
+ * OSS (Open Sound System) host audio backend.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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/uuid.h> /* For PDMIBASE_2_PDMDRV. */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "DrvAudio.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
+
+/** Makes DRVHOSTOSSAUDIO out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTOSSAUDIO(pInterface) \
+ ( (PDRVHOSTOSSAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTOSSAUDIO, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+
+/**
+ * OSS host audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTOSSAUDIO
+{
+ /** 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;
+} DRVHOSTOSSAUDIO, *PDRVHOSTOSSAUDIO;
+
+typedef struct OSSAUDIOSTREAMCFG
+{
+ PDMAUDIOPCMPROPS Props;
+ uint16_t cFragments;
+ uint32_t cbFragmentSize;
+} OSSAUDIOSTREAMCFG, *POSSAUDIOSTREAMCFG;
+
+typedef struct OSSAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Buffer alignment. */
+ uint8_t uAlign;
+ union
+ {
+ struct
+ {
+
+ } In;
+ struct
+ {
+#ifndef RT_OS_L4
+ /** Whether we use a memory mapped file instead of our
+ * own allocated PCM buffer below. */
+ /** @todo The memory mapped code seems to be utterly broken.
+ * Needs investigation! */
+ bool fMMIO;
+#endif
+ } Out;
+ };
+ int hFile;
+ int cFragments;
+ int cbFragmentSize;
+ /** Own PCM buffer. */
+ void *pvBuf;
+ /** Size (in bytes) of own PCM buffer. */
+ size_t cbBuf;
+ int old_optr;
+} OSSAUDIOSTREAM, *POSSAUDIOSTREAM;
+
+typedef struct OSSAUDIOCFG
+{
+#ifndef RT_OS_L4
+ bool try_mmap;
+#endif
+ int nfrags;
+ int fragsize;
+ const char *devpath_out;
+ const char *devpath_in;
+ int debug;
+} OSSAUDIOCFG, *POSSAUDIOCFG;
+
+static OSSAUDIOCFG s_OSSConf =
+{
+#ifndef RT_OS_L4
+ false,
+#endif
+ 4,
+ 4096,
+ "/dev/dsp",
+ "/dev/dsp",
+ 0
+};
+
+
+/* http://www.df.lth.se/~john_e/gems/gem002d.html */
+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;
+}
+
+
+static uint32_t lsbindex(uint32_t u)
+{
+ return popcount ((u&-u)-1);
+}
+
+
+static int ossOSSToAudioProps(int fmt, PPDMAUDIOPCMPROPS pProps)
+{
+ RT_BZERO(pProps, sizeof(PDMAUDIOPCMPROPS));
+
+ switch (fmt)
+ {
+ case AFMT_S8:
+ pProps->cBytes = 1;
+ pProps->fSigned = true;
+ break;
+
+ case AFMT_U8:
+ pProps->cBytes = 1;
+ pProps->fSigned = false;
+ break;
+
+ case AFMT_S16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ break;
+
+ case AFMT_U16_LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+ break;
+
+ case AFMT_S16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ case AFMT_U16_BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = false;
+#ifdef RT_LITTLE_ENDIAN
+ pProps->fSwapEndian = true;
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Format %ld not supported\n", fmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossStreamClose(int *phFile)
+{
+ if (!phFile || !*phFile || *phFile == -1)
+ return VINF_SUCCESS;
+
+ int rc;
+ if (close(*phFile))
+ {
+ LogRel(("OSS: Closing stream failed: %s\n", strerror(errno)));
+ rc = VERR_GENERAL_FAILURE; /** @todo */
+ }
+ else
+ {
+ *phFile = -1;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+static int ossStreamOpen(const char *pszDev, int fOpen, POSSAUDIOSTREAMCFG pOSSReq, POSSAUDIOSTREAMCFG pOSSAcq, int *phFile)
+{
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ int hFile = -1;
+ do
+ {
+ hFile = open(pszDev, fOpen);
+ if (hFile == -1)
+ {
+ LogRel(("OSS: Failed to open %s: %s (%d)\n", pszDev, strerror(errno), errno));
+ break;
+ }
+
+ int iFormat;
+ switch (pOSSReq->Props.cBytes)
+ {
+ case 1:
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S8 : AFMT_U8;
+ break;
+
+ case 2:
+ iFormat = pOSSReq->Props.fSigned ? AFMT_S16_LE : AFMT_U16_LE;
+ break;
+
+ default:
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ioctl(hFile, SNDCTL_DSP_SAMPLESIZE, &iFormat))
+ {
+ LogRel(("OSS: Failed to set audio format to %ld: %s (%d)\n", iFormat, strerror(errno), errno));
+ break;
+ }
+
+ int cChannels = pOSSReq->Props.cChannels;
+ if (ioctl(hFile, SNDCTL_DSP_CHANNELS, &cChannels))
+ {
+ LogRel(("OSS: Failed to set number of audio channels (%RU8): %s (%d)\n",
+ pOSSReq->Props.cChannels, strerror(errno), errno));
+ break;
+ }
+
+ int freq = pOSSReq->Props.uHz;
+ if (ioctl(hFile, SNDCTL_DSP_SPEED, &freq))
+ {
+ LogRel(("OSS: Failed to set audio frequency (%dHZ): %s (%d)\n", pOSSReq->Props.uHz, strerror(errno), errno));
+ break;
+ }
+
+ /* Obsolete on Solaris (using O_NONBLOCK is sufficient). */
+#if !(defined(VBOX) && defined(RT_OS_SOLARIS))
+ if (ioctl(hFile, SNDCTL_DSP_NONBLOCK))
+ {
+ LogRel(("OSS: Failed to set non-blocking mode: %s (%d)\n", strerror(errno), errno));
+ break;
+ }
+#endif
+
+ /* Check access mode (input or output). */
+ bool fIn = ((fOpen & O_ACCMODE) == O_RDONLY);
+
+ LogRel2(("OSS: Requested %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSReq->cFragments, fIn ? "input" : "output", pOSSReq->cbFragmentSize));
+
+ int mmmmssss = (pOSSReq->cFragments << 16) | lsbindex(pOSSReq->cbFragmentSize);
+ if (ioctl(hFile, SNDCTL_DSP_SETFRAGMENT, &mmmmssss))
+ {
+ LogRel(("OSS: Failed to set %RU16 fragments to %RU32 bytes each: %s (%d)\n",
+ pOSSReq->cFragments, pOSSReq->cbFragmentSize, strerror(errno), errno));
+ break;
+ }
+
+ audio_buf_info abinfo;
+ if (ioctl(hFile, fIn ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo))
+ {
+ LogRel(("OSS: Failed to retrieve %s buffer length: %s (%d)\n", fIn ? "input" : "output", strerror(errno), errno));
+ break;
+ }
+
+ rc = ossOSSToAudioProps(iFormat, &pOSSAcq->Props);
+ if (RT_SUCCESS(rc))
+ {
+ pOSSAcq->Props.cChannels = cChannels;
+ pOSSAcq->Props.uHz = freq;
+ pOSSAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pOSSAcq->Props.cBytes, pOSSAcq->Props.cChannels);
+
+ pOSSAcq->cFragments = abinfo.fragstotal;
+ pOSSAcq->cbFragmentSize = abinfo.fragsize;
+
+ LogRel2(("OSS: Got %RU16 %s fragments, %RU32 bytes each\n",
+ pOSSAcq->cFragments, fIn ? "input" : "output", pOSSAcq->cbFragmentSize));
+
+ *phFile = hFile;
+ }
+ }
+ while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int ossControlStreamIn(/*PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd*/ void)
+{
+ /** @todo Nothing to do here right now!? */
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossControlStreamOut(PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ DrvAudioHlpClearBuf(&pStreamOSS->pCfg->Props, pStreamOSS->pvBuf, pStreamOSS->cbBuf,
+ PDMAUDIOPCMPROPS_B2F(&pStreamOSS->pCfg->Props, pStreamOSS->cbBuf));
+
+ int mask = PCM_ENABLE_OUTPUT;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to enable output stream: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ int mask = 0;
+ if (ioctl(pStreamOSS->hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to disable output stream: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ }
+
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbToRead = RT_MIN(pStreamOSS->cbBuf, cxBuf);
+
+ LogFlowFunc(("cbToRead=%zi\n", cbToRead));
+
+ uint32_t cbReadTotal = 0;
+ uint32_t cbTemp;
+ ssize_t cbRead;
+ size_t offWrite = 0;
+
+ while (cbToRead)
+ {
+ cbTemp = RT_MIN(cbToRead, pStreamOSS->cbBuf);
+ AssertBreakStmt(cbTemp, rc = VERR_NO_DATA);
+ cbRead = read(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf, cbTemp);
+
+ LogFlowFunc(("cbRead=%zi, cbTemp=%RU32, cbToRead=%zu\n", cbRead, cbTemp, cbToRead));
+
+ if (cbRead < 0)
+ {
+ switch (errno)
+ {
+ case 0:
+ {
+ LogFunc(("Failed to read %z frames\n", cbRead));
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+
+ case EINTR:
+ case EAGAIN:
+ rc = VERR_NO_DATA;
+ break;
+
+ default:
+ LogFlowFunc(("Failed to read %zu input frames, rc=%Rrc\n", cbTemp, rc));
+ rc = VERR_GENERAL_FAILURE; /** @todo Fix this. */
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (cbRead)
+ {
+ memcpy((uint8_t *)pvBuf + offWrite, pStreamOSS->pvBuf, cbRead);
+
+ Assert((ssize_t)cbToRead >= cbRead);
+ cbToRead -= cbRead;
+ offWrite += cbRead;
+ cbReadTotal += cbRead;
+ }
+ else /* No more data, try next round. */
+ break;
+ }
+
+ if (rc == VERR_NO_DATA)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+
+static int ossDestroyStreamIn(PPDMAUDIOBACKENDSTREAM pStream)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ LogFlowFuncEnter();
+
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ RTMemFree(pStreamOSS->pvBuf);
+ pStreamOSS->pvBuf = NULL;
+ }
+
+ pStreamOSS->cbBuf = 0;
+
+ ossStreamClose(&pStreamOSS->hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossDestroyStreamOut(PPDMAUDIOBACKENDSTREAM pStream)
+{
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+#ifndef RT_OS_L4
+ if (pStreamOSS->Out.fMMIO)
+ {
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ int rc2 = munmap(pStreamOSS->pvBuf, pStreamOSS->cbBuf);
+ if (rc2 == 0)
+ {
+ pStreamOSS->pvBuf = NULL;
+ pStreamOSS->cbBuf = 0;
+
+ pStreamOSS->Out.fMMIO = false;
+ }
+ else
+ LogRel(("OSS: Failed to memory unmap playback buffer on close: %s\n", strerror(errno)));
+ }
+ }
+ else
+ {
+#endif
+ if (pStreamOSS->pvBuf)
+ {
+ Assert(pStreamOSS->cbBuf);
+
+ RTMemFree(pStreamOSS->pvBuf);
+ pStreamOSS->pvBuf = NULL;
+ }
+
+ pStreamOSS->cbBuf = 0;
+#ifndef RT_OS_L4
+ }
+#endif
+
+ ossStreamClose(&pStreamOSS->hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "OSS audio driver");
+
+ pBackendCfg->cbStreamIn = sizeof(OSSAUDIOSTREAM);
+ pBackendCfg->cbStreamOut = sizeof(OSSAUDIOSTREAM);
+
+ 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);
+ }
+
+ int ossVer = -1;
+
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ oss_sysinfo ossInfo;
+ RT_ZERO(ossInfo);
+#endif
+
+ if (hFile != -1)
+ {
+ int err = ioctl(hFile, OSS_GETVERSION, &ossVer);
+ if (err == 0)
+ {
+ LogRel2(("OSS: Using version: %d\n", ossVer));
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ 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;
+#ifdef VBOX_WITH_AUDIO_OSS_SYSINFO
+ }
+#endif
+ }
+ else
+ LogRel(("OSS: Unable to determine installed version: %s (%d)\n", strerror(err), err));
+ }
+ else
+ LogRel(("OSS: No devices found, audio is not available\n"));
+
+ if (hFile != -1)
+ close(hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+static int ossCreateStreamIn(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ int rc;
+
+ int hFile = -1;
+
+ do
+ {
+ OSSAUDIOSTREAMCFG ossReq;
+ memcpy(&ossReq.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
+
+ ossReq.cFragments = s_OSSConf.nfrags;
+ ossReq.cbFragmentSize = s_OSSConf.fragsize;
+
+ OSSAUDIOSTREAMCFG ossAcq;
+ RT_ZERO(ossAcq);
+
+ rc = ossStreamOpen(s_OSSConf.devpath_in, O_RDONLY | O_NONBLOCK, &ossReq, &ossAcq, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pCfgAcq->Props, &ossAcq.Props, sizeof(PDMAUDIOPCMPROPS));
+
+ if (ossAcq.cFragments * ossAcq.cbFragmentSize & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Warning: Misaligned capturing buffer: Size = %zu, Alignment = %u\n",
+ ossAcq.cFragments * ossAcq.cbFragmentSize, pStreamOSS->uAlign + 1));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, ossAcq.cFragments * ossAcq.cbFragmentSize);
+ void *pvBuf = RTMemAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ LogRel(("OSS: Failed allocating capturing buffer with (%zu bytes)\n", cbBuf));
+ rc = VERR_NO_MEMORY;
+ }
+
+ pStreamOSS->hFile = hFile;
+ pStreamOSS->pvBuf = pvBuf;
+ pStreamOSS->cbBuf = cbBuf;
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, ossAcq.cbFragmentSize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering". */
+ /** @todo Pre-buffering required? */
+ }
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int ossCreateStreamOut(POSSAUDIOSTREAM pStreamOSS, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ int rc;
+ int hFile = -1;
+
+ do
+ {
+ OSSAUDIOSTREAMCFG reqStream;
+ RT_ZERO(reqStream);
+
+ OSSAUDIOSTREAMCFG obtStream;
+ RT_ZERO(obtStream);
+
+ memcpy(&reqStream.Props, &pCfgReq->Props, sizeof(PDMAUDIOPCMPROPS));
+
+ reqStream.cFragments = s_OSSConf.nfrags;
+ reqStream.cbFragmentSize = s_OSSConf.fragsize;
+
+ rc = ossStreamOpen(s_OSSConf.devpath_out, O_WRONLY, &reqStream, &obtStream, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pCfgAcq->Props, &obtStream.Props, sizeof(PDMAUDIOPCMPROPS));
+
+ if (obtStream.cFragments * obtStream.cbFragmentSize & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Warning: Misaligned playback buffer: Size = %zu, Alignment = %u\n",
+ obtStream.cFragments * obtStream.cbFragmentSize, pStreamOSS->uAlign + 1));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->Out.fMMIO = false;
+
+ size_t cbBuf = PDMAUDIOSTREAMCFG_F2B(pCfgAcq, obtStream.cFragments * obtStream.cbFragmentSize);
+ Assert(cbBuf);
+
+#ifndef RT_OS_L4
+ if (s_OSSConf.try_mmap)
+ {
+ pStreamOSS->pvBuf = mmap(0, cbBuf, PROT_READ | PROT_WRITE, MAP_SHARED, hFile, 0);
+ if (pStreamOSS->pvBuf == MAP_FAILED)
+ {
+ LogRel(("OSS: Failed to memory map %zu bytes of playback buffer: %s\n", cbBuf, strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+ else
+ {
+ int mask = 0;
+ if (ioctl(hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to retrieve initial trigger mask for playback buffer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ /* Note: No break here, need to unmap file first! */
+ }
+ else
+ {
+ mask = PCM_ENABLE_OUTPUT;
+ if (ioctl (hFile, SNDCTL_DSP_SETTRIGGER, &mask) < 0)
+ {
+ LogRel(("OSS: Failed to retrieve PCM_ENABLE_OUTPUT mask: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ /* Note: No break here, need to unmap file first! */
+ }
+ else
+ {
+ pStreamOSS->Out.fMMIO = true;
+ LogRel(("OSS: Using MMIO\n"));
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = munmap(pStreamOSS->pvBuf, cbBuf);
+ if (rc2)
+ LogRel(("OSS: Failed to memory unmap playback buffer: %s\n", strerror(errno)));
+ break;
+ }
+ }
+ }
+#endif /* !RT_OS_L4 */
+
+ /* Memory mapping failed above? Try allocating an own buffer. */
+#ifndef RT_OS_L4
+ if (!pStreamOSS->Out.fMMIO)
+ {
+#endif
+ void *pvBuf = RTMemAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ LogRel(("OSS: Failed allocating playback buffer with %zu bytes\n", cbBuf));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pStreamOSS->hFile = hFile;
+ pStreamOSS->pvBuf = pvBuf;
+ pStreamOSS->cbBuf = cbBuf;
+#ifndef RT_OS_L4
+ }
+#endif
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, obtStream.cbFragmentSize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use "double buffering" */
+ }
+
+ } while (0);
+
+ if (RT_FAILURE(rc))
+ ossStreamClose(&hFile);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+ uint32_t cbWrittenTotal = 0;
+
+#ifndef RT_OS_L4
+ count_info cntinfo;
+#endif
+
+ do
+ {
+ uint32_t cbAvail = cxBuf;
+ uint32_t cbToWrite;
+
+#ifndef RT_OS_L4
+ if (pStreamOSS->Out.fMMIO)
+ {
+ /* Get current playback pointer. */
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOPTR, &cntinfo);
+ if (!rc2)
+ {
+ LogRel(("OSS: Failed to retrieve current playback pointer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ /* Nothing to play? */
+ if (cntinfo.ptr == pStreamOSS->old_optr)
+ break;
+
+ int cbData;
+ if (cntinfo.ptr > pStreamOSS->old_optr)
+ cbData = cntinfo.ptr - pStreamOSS->old_optr;
+ else
+ cbData = cxBuf + cntinfo.ptr - pStreamOSS->old_optr;
+ Assert(cbData >= 0);
+
+ cbToWrite = RT_MIN((unsigned)cbData, cbAvail);
+ }
+ else
+ {
+#endif
+ audio_buf_info abinfo;
+ int rc2 = ioctl(pStreamOSS->hFile, SNDCTL_DSP_GETOSPACE, &abinfo);
+ if (rc2 < 0)
+ {
+ LogRel(("OSS: Failed to retrieve current playback buffer: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ if ((size_t)abinfo.bytes > cxBuf)
+ {
+ LogRel2(("OSS: Warning: Too big output size (%d > %RU32), limiting to %RU32\n", abinfo.bytes, cxBuf, cxBuf));
+ abinfo.bytes = cxBuf;
+ /* Keep going. */
+ }
+
+ if (abinfo.bytes < 0)
+ {
+ LogRel2(("OSS: Warning: Invalid available size (%d vs. %RU32)\n", abinfo.bytes, cxBuf));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ cbToWrite = RT_MIN(unsigned(abinfo.fragments * abinfo.fragsize), cbAvail);
+#ifndef RT_OS_L4
+ }
+#endif
+ cbToWrite = RT_MIN(cbToWrite, pStreamOSS->cbBuf);
+
+ while (cbToWrite)
+ {
+ uint32_t cbWritten = cbToWrite;
+
+ memcpy(pStreamOSS->pvBuf, pvBuf, cbWritten);
+
+ uint32_t cbChunk = cbWritten;
+ uint32_t cbChunkOff = 0;
+ while (cbChunk)
+ {
+ ssize_t cbChunkWritten = write(pStreamOSS->hFile, (uint8_t *)pStreamOSS->pvBuf + cbChunkOff,
+ RT_MIN(cbChunk, (unsigned)s_OSSConf.fragsize));
+ if (cbChunkWritten < 0)
+ {
+ LogRel(("OSS: Failed writing output data: %s\n", strerror(errno)));
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ if (cbChunkWritten & pStreamOSS->uAlign)
+ {
+ LogRel(("OSS: Misaligned write (written %z, expected %RU32)\n", cbChunkWritten, cbChunk));
+ break;
+ }
+
+ cbChunkOff += (uint32_t)cbChunkWritten;
+ Assert(cbChunkOff <= cbWritten);
+ Assert(cbChunk >= (uint32_t)cbChunkWritten);
+ cbChunk -= (uint32_t)cbChunkWritten;
+ }
+
+ Assert(cbToWrite >= cbWritten);
+ cbToWrite -= cbWritten;
+ cbWrittenTotal += cbWritten;
+ }
+
+#ifndef RT_OS_L4
+ /* Update read pointer. */
+ if (pStreamOSS->Out.fMMIO)
+ pStreamOSS->old_optr = cntinfo.ptr;
+#endif
+
+ } while(0);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostOSSAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostOSSAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+ RT_NOREF(enmDir);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = ossCreateStreamIn (pStreamOSS, pCfgReq, pCfgAcq);
+ else
+ rc = ossCreateStreamOut(pStreamOSS, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamOSS->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamOSS->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = ossDestroyStreamIn(pStream);
+ else
+ rc = ossDestroyStreamOut(pStream);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamOSS->pCfg);
+ pStreamOSS->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamControl(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+
+ if (!pStreamOSS->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamOSS->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = ossControlStreamIn(/*pInterface, pStream, enmStreamCmd*/);
+ else
+ rc = ossControlStreamOut(pStreamOSS, enmStreamCmd);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostOSSAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do here for OSS. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostOSSAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostOSSAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED
+ | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ return strmSts;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostOSSAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+/**
+ * Constructs an OSS audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostOSSAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTOSSAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTOSSAUDIO);
+ LogRel(("Audio: Initializing OSS driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostOSSAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostOSSAudio);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Char 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(DRVHOSTOSSAUDIO),
+ /* pfnConstruct */
+ drvHostOSSAudioConstruct,
+ /* 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/DrvHostPulseAudio.cpp b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp
new file mode 100644
index 00000000..82de1ffb
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp
@@ -0,0 +1,1744 @@
+/* $Id: DrvHostPulseAudio.cpp $ */
+/** @file
+ * VBox audio devices: Pulse Audio audio driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include <stdio.h>
+
+#include <iprt/alloc.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+
+RT_C_DECLS_BEGIN
+ #include "pulse_mangling.h"
+ #include "pulse_stubs.h"
+RT_C_DECLS_END
+
+#include <pulse/pulseaudio.h>
+
+#include "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */
+
+#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
+
+/** No flags specified. */
+#define PULSEAUDIOENUMCBFLAGS_NONE 0
+/** (Release) log found devices. */
+#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0)
+
+/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \
+ ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+
+/**
+ * Host Pulse audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTPULSEAUDIO
+{
+ /** 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;
+ /** Enumeration operation successful? */
+ volatile bool fEnumOpSuccess;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Error count for not flooding the release log.
+ * Specify UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+ /** The stream (base) name; needed for distinguishing
+ * streams in the PulseAudio mixer controls if multiple
+ * VMs are running at the same time. */
+ char szStreamName[64];
+} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO;
+
+typedef struct PULSEAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Pointer to driver instance. */
+ PDRVHOSTPULSEAUDIO pDrv;
+ /** Pointer to opaque PulseAudio stream. */
+ pa_stream *pStream;
+ /** Pulse sample format and attribute specification. */
+ pa_sample_spec SampleSpec;
+ /** Pulse playback and buffer metrics. */
+ pa_buffer_attr BufAttr;
+ int fOpSuccess;
+ /** Pointer to Pulse sample peeking buffer. */
+ const uint8_t *pu8PeekBuf;
+ /** Current size (in bytes) of peeking data in
+ * buffer. */
+ size_t cbPeekBuf;
+ /** Our offset (in bytes) in peeking buffer. */
+ size_t offPeekBuf;
+ pa_operation *pDrainOp;
+ /** Number of occurred audio data underflows. */
+ uint32_t cUnderflows;
+ /** Current latency (in us). */
+ uint64_t curLatencyUs;
+#ifdef LOG_ENABLED
+ /** Start time stamp (in us) of stream playback / recording. */
+ pa_usec_t tsStartUs;
+ /** Time stamp (in us) when last read from / written to the stream. */
+ pa_usec_t tsLastReadWrittenUs;
+#endif
+} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM;
+
+/**
+ * Callback context for server enumeration callbacks.
+ */
+typedef struct PULSEAUDIOENUMCBCTX
+{
+ /** Pointer to host backend driver. */
+ PDRVHOSTPULSEAUDIO pDrv;
+ /** Enumeration flags. */
+ uint32_t fFlags;
+ /** Number of found input devices. */
+ uint8_t cDevIn;
+ /** Number of found output devices. */
+ uint8_t cDevOut;
+ /** 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;
+} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX;
+
+#ifndef PA_CONTEXT_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
+static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
+ return
+ x == PA_CONTEXT_CONNECTING ||
+ x == PA_CONTEXT_AUTHORIZING ||
+ x == PA_CONTEXT_SETTING_NAME ||
+ x == PA_CONTEXT_READY;
+}
+#endif /* !PA_CONTEXT_IS_GOOD */
+
+#ifndef PA_STREAM_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
+static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
+ return
+ x == PA_STREAM_CREATING ||
+ x == PA_STREAM_READY;
+}
+#endif /* !PA_STREAM_IS_GOOD */
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+
+static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum);
+static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg);
+#ifdef DEBUG
+static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext);
+static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext);
+#endif
+static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext);
+
+
+/**
+ * Signal the main loop to abort. Just signalling isn't sufficient as the
+ * mainloop might not have been entered yet.
+ */
+static void paSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
+{
+ if (!pThis)
+ return;
+
+ pThis->fAbortLoop = true;
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
+}
+
+
+static pa_sample_format_t paAudioPropsToPulse(PPDMAUDIOPCMPROPS pProps)
+{
+ switch (pProps->cBytes)
+ {
+ case 1:
+ if (!pProps->fSigned)
+ return PA_SAMPLE_U8;
+ break;
+
+ case 2:
+ if (pProps->fSigned)
+ return PA_SAMPLE_S16LE;
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case 4:
+ if (pProps->fSigned)
+ return PA_SAMPLE_S32LE;
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("%RU8%s not supported\n", pProps->cBytes, pProps->fSigned ? "S" : "U"));
+ return PA_SAMPLE_INVALID;
+}
+
+
+static int paPulseToAudioProps(pa_sample_format_t pulsefmt, PPDMAUDIOPCMPROPS pProps)
+{
+ switch (pulsefmt)
+ {
+ case PA_SAMPLE_U8:
+ pProps->cBytes = 1;
+ pProps->fSigned = false;
+ break;
+
+ case PA_SAMPLE_S16LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ break;
+
+ case PA_SAMPLE_S16BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ /** @todo Handle Endianess. */
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case PA_SAMPLE_S32LE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+ break;
+#endif
+
+#ifdef PA_SAMPLE_S32BE
+ case PA_SAMPLE_S32BE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+ /** @todo Handle Endianess. */
+ break;
+#endif
+
+ default:
+ AssertLogRelMsgFailed(("PulseAudio: Format (%ld) not supported\n", pulsefmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Synchronously wait until an operation completed.
+ */
+static int paWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOP, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ uint64_t u64StartMs = RTTimeMilliTS();
+ while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING)
+ {
+ if (!pThis->fAbortLoop)
+ {
+ AssertPtr(pThis->pMainLoop);
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ if ( !pThis->pContext
+ || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
+ {
+ LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
+ break;
+ }
+ }
+ pThis->fAbortLoop = false;
+
+ uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
+ if (u64ElapsedMs >= cMsTimeout)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ }
+
+ pa_operation_unref(pOP);
+
+ return rc;
+}
+
+
+static int paWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
+{
+ return paWaitForEx(pThis, pOP, 10 * 1000 /* 10s timeout */);
+}
+
+
+/**
+ * Context status changed.
+ */
+static void paContextCbStateChanged(pa_context *pCtx, void *pvUser)
+{
+ AssertPtrReturnVoid(pCtx);
+
+ PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_context_get_state(pCtx))
+ {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ paSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Callback called when our pa_stream_drain operation was completed.
+ */
+static void paStreamCbDrain(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
+ AssertPtrReturnVoid(pStreamPA);
+
+ pStreamPA->fOpSuccess = fSuccess;
+ if (fSuccess)
+ {
+ pa_operation_unref(pa_stream_cork(pStream, 1,
+ paStreamCbSuccess, pvUser));
+ }
+ else
+ paError(pStreamPA->pDrv, "Failed to drain stream");
+
+ if (pStreamPA->pDrainOp)
+ {
+ pa_operation_unref(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+}
+
+
+/**
+ * Stream status changed.
+ */
+static void paStreamCbStateChanged(pa_stream *pStream, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_stream_get_state(pStream))
+ {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ paSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+#ifdef DEBUG
+static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext)
+{
+ RT_NOREF(cbLen, pvContext);
+
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
+ AssertPtrReturnVoid(pStrm);
+
+ pa_usec_t usec = 0;
+ int neg = 0;
+ pa_stream_get_latency(pStream, &usec, &neg);
+
+ Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000));
+}
+
+
+static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext)
+{
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
+ AssertPtrReturnVoid(pStrm);
+
+ pStrm->cUnderflows++;
+
+ LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
+
+ if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
+ && pStrm->curLatencyUs < 2000000 /* 2s */)
+ {
+ pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2;
+
+ LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */));
+
+ pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
+ pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
+
+ pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
+
+ pStrm->cUnderflows = 0;
+ }
+
+ pa_usec_t curLatencyUs = 0;
+ pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */);
+
+ LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */));
+
+# ifdef LOG_ENABLED
+ const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
+ const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
+
+ pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
+ pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec);
+ pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
+
+ Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n",
+ curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64,
+ curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels));
+# endif
+}
+
+
+static void paStreamCbOverflow(pa_stream *pStream, void *pvContext)
+{
+ RT_NOREF(pStream, pvContext);
+
+ Log2Func(("Warning: Hit overflow\n"));
+}
+#endif /* DEBUG */
+
+
+static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
+ AssertPtrReturnVoid(pStrm);
+
+ pStrm->fOpSuccess = fSuccess;
+
+ if (fSuccess)
+ paSignalWaiter(pStrm->pDrv);
+ else
+ paError(pStrm->pDrv, "Failed to finish stream operation");
+}
+
+
+static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ pa_stream *pStream = NULL;
+ uint32_t flags = PA_STREAM_NOFLAGS;
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ do
+ {
+ pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec;
+
+ LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n",
+ pszName, pSampleSpec->rate, pSampleSpec->channels,
+ pa_sample_format_to_string(pSampleSpec->format)));
+
+ if (!pa_sample_spec_valid(pSampleSpec))
+ {
+ LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName));
+ break;
+ }
+
+ pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr;
+
+ /** @todo r=andy Use pa_stream_new_with_proplist instead. */
+ if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */)))
+ {
+ LogRel(("PulseAudio: Could not create stream '%s'\n", pszName));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+#ifdef DEBUG
+ pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA);
+ pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA);
+ if (!fIn) /* Only for output streams. */
+ pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA);
+#endif
+ pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis);
+
+#if PA_API_VERSION >= 12
+ /* XXX */
+ flags |= PA_STREAM_ADJUST_LATENCY;
+#endif
+ /* For using pa_stream_get_latency() and pa_stream_get_time(). */
+ flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
+
+ /* No input/output right away after the stream was started. */
+ flags |= PA_STREAM_START_CORKED;
+
+ if (fIn)
+ {
+ LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
+ pBufAttr->maxlength, pBufAttr->fragsize));
+
+ if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
+ {
+ LogRel(("PulseAudio: Could not connect input stream '%s': %s\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+ }
+ else
+ {
+ LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
+ pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
+
+ if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
+ /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
+ {
+ LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+ }
+
+ /* Wait until the stream is ready. */
+ for (;;)
+ {
+ if (!pThis->fAbortLoop)
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ pThis->fAbortLoop = false;
+
+ pa_stream_state_t streamSt = pa_stream_get_state(pStream);
+ if (streamSt == PA_STREAM_READY)
+ break;
+ else if ( streamSt == PA_STREAM_FAILED
+ || streamSt == PA_STREAM_TERMINATED)
+ {
+ LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt));
+ break;
+ }
+ }
+
+#ifdef LOG_ENABLED
+ pStreamPA->tsStartUs = pa_rtclock_now();
+#endif
+ const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
+ AssertPtr(pBufAttrObtained);
+ memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
+
+ LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n",
+ fIn ? "capture" : "playback",
+ pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf));
+
+ pStreamPA->pStream = pStream;
+
+ rc = VINF_SUCCESS;
+
+ } while (0);
+
+ if ( RT_FAILURE(rc)
+ && pStream)
+ pa_stream_disconnect(pStream);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pStream)
+ pa_stream_unref(pStream);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ LogFlowFuncEnter();
+
+ int rc = audioLoadPulseLib();
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
+ return rc;
+ }
+
+ LogRel(("PulseAudio: Using v%s\n", pa_get_library_version()));
+
+ pThis->fAbortLoop = false;
+ pThis->pMainLoop = NULL;
+
+ bool fLocked = false;
+
+ do
+ {
+ if (!(pThis->pMainLoop = pa_threaded_mainloop_new()))
+ {
+ LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox")))
+ {
+ LogRel(("PulseAudio: Failed to allocate context: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
+ {
+ LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+
+ /* Install a global callback to known if something happens to our acquired context. */
+ pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */);
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ fLocked = true;
+
+ if (pa_context_connect(pThis->pContext, NULL /* pszServer */,
+ PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ LogRel(("PulseAudio: Failed to connect to server: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+
+ /* Wait until the pThis->pContext is ready. */
+ for (;;)
+ {
+ if (!pThis->fAbortLoop)
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ pThis->fAbortLoop = false;
+
+ pa_context_state_t cstate = pa_context_get_state(pThis->pContext);
+ if (cstate == PA_CONTEXT_READY)
+ break;
+ else if ( cstate == PA_CONTEXT_TERMINATED
+ || cstate == PA_CONTEXT_FAILED)
+ {
+ LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate));
+ break;
+ }
+ }
+ }
+ while (0);
+
+ if (fLocked)
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_FAILURE(rc))
+ {
+ 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;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ pStreamPA->pDrainOp = NULL;
+
+ pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
+ pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
+ pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
+
+ pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props) * RT_US_1MS;
+
+ const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec);
+
+ LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency));
+
+ pStreamPA->BufAttr.tlength = cbLatency;
+ pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
+ pStreamPA->BufAttr.prebuf = cbLatency;
+ pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
+
+ LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT);
+
+ char szName[256];
+ RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
+ DrvAudioHlpPlaybackDstToStr(pCfgReq->DestSource.Dest), pThis->szStreamName);
+
+ /* Note that the struct BufAttr is updated to the obtained values after this call! */
+ int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format));
+ return rc;
+ }
+
+ pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
+ pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
+ pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels);
+
+ LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq);
+ pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength);
+ pCfgAcq->Backend.cfPreBuf = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf);
+
+ pStreamPA->pDrv = pThis;
+
+ return rc;
+}
+
+
+static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
+ pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
+ pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
+
+ pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
+ pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
+
+ Assert(pCfgReq->enmDir == PDMAUDIODIR_IN);
+
+ char szName[256];
+ RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
+ DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source), pThis->szStreamName);
+
+ /* Note: Other members of BufAttr are ignored for record streams. */
+ int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format));
+ return rc;
+ }
+
+ pStreamPA->pDrv = pThis;
+ pStreamPA->pu8PeekBuf = NULL;
+
+ pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
+ pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfBufferSize;
+ pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pvBuf, cxBuf);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ /* We should only call pa_stream_readable_size() once and trust the first value. */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (cbAvail == (size_t)-1)
+ return paError(pStreamPA->pDrv, "Failed to determine input data size");
+
+ /* If the buffer was not dropped last call, add what remains. */
+ if (pStreamPA->pu8PeekBuf)
+ {
+ Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
+ cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
+ }
+
+ Log3Func(("cbAvail=%zu\n", cbAvail));
+
+ if (!cbAvail) /* No data? Bail out. */
+ {
+ if (pcxRead)
+ *pcxRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbToRead = RT_MIN(cbAvail, cxBuf);
+
+ Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
+ cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
+
+ uint32_t cbReadTotal = 0;
+
+ while (cbToRead)
+ {
+ /* If there is no data, do another peek. */
+ if (!pStreamPA->pu8PeekBuf)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_stream_peek(pStreamPA->pStream,
+ (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ pStreamPA->offPeekBuf = 0;
+
+ /* No data anymore?
+ * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
+ * we need to drop the stream lateron. */
+ if ( !pStreamPA->pu8PeekBuf
+ && !pStreamPA->cbPeekBuf)
+ {
+ break;
+ }
+ }
+
+ Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
+ size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
+
+ Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
+ cbToRead, cbToWrite,
+ pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
+
+ if ( cbToWrite
+ /* Only copy data if it's not a data hole (see above). */
+ && pStreamPA->pu8PeekBuf
+ && pStreamPA->cbPeekBuf)
+ {
+ memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
+
+ Assert(cbToRead >= cbToWrite);
+ cbToRead -= cbToWrite;
+ cbReadTotal += cbToWrite;
+
+ pStreamPA->offPeekBuf += cbToWrite;
+ Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
+ }
+
+ if (/* Nothing to write anymore? Drop the buffer. */
+ !cbToWrite
+ /* Was there a hole in the peeking buffer? Drop it. */
+ || !pStreamPA->pu8PeekBuf
+ /* If the buffer is done, drop it. */
+ || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_stream_drop(pStreamPA->pStream);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ pStreamPA->pu8PeekBuf = NULL;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcxWritten is optional. */
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+#ifdef LOG_ENABLED
+ const pa_usec_t tsNowUs = pa_rtclock_now();
+ const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs;
+
+ Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */));
+
+ pPAStream->tsLastReadWrittenUs = tsNowUs;
+#endif
+
+ do
+ {
+ size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream);
+ if (cbWriteable == (size_t)-1)
+ {
+ rc = paError(pPAStream->pDrv, "Failed to determine output data size");
+ break;
+ }
+
+ size_t cbLeft = RT_MIN(cbWriteable, cxBuf);
+ Assert(cbLeft); /* At this point we better have *something* to write. */
+
+ while (cbLeft)
+ {
+ uint32_t cbChunk = cbLeft; /* Write all at once for now. */
+
+ if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */,
+ 0, PA_SEEK_RELATIVE) < 0)
+ {
+ rc = paError(pPAStream->pDrv, "Failed to write to output stream");
+ break;
+ }
+
+ Assert(cbLeft >= cbChunk);
+ cbLeft -= cbChunk;
+ cbWrittenTotal += cbChunk;
+ }
+
+ } while (0);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+
+/** @todo Implement va handling. */
+static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
+
+ if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
+ {
+ int rc2 = pa_context_errno(pThis->pContext);
+ LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
+ }
+
+ /** @todo Implement some PulseAudio -> IPRT mapping here. */
+ return VERR_GENERAL_FAILURE;
+}
+
+
+static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
+{
+ if (eol > 0)
+ return;
+
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+ if (eol < 0)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(pInfo);
+
+ LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name));
+
+ /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */
+ pCbCtx->cDevOut++;
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+}
+
+
+static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
+{
+ if (eol > 0)
+ return;
+
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+ if (eol < 0)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(pInfo);
+
+ LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name));
+
+ /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */
+ pCbCtx->cDevIn++;
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+}
+
+
+static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
+{
+ AssertPtrReturnVoid(pCtx);
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+
+ if (!pInfo)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ if (pInfo->default_sink_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
+ pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
+ }
+
+ if (pInfo->default_sink_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_source_name));
+ pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
+ }
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
+}
+
+
+static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ PDMAUDIOBACKENDCFG Cfg;
+ RT_ZERO(Cfg);
+
+ RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio driver");
+
+ Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM);
+ Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM);
+ Cfg.cMaxStreamsOut = UINT32_MAX;
+ Cfg.cMaxStreamsIn = UINT32_MAX;
+
+ PULSEAUDIOENUMCBCTX CbCtx;
+ RT_ZERO(CbCtx);
+
+ CbCtx.pDrv = pThis;
+ CbCtx.fFlags = fEnum;
+
+ bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pThis->fEnumOpSuccess = false;
+
+ LogRel(("PulseAudio: Retrieving server information ...\n"));
+
+ /* Check if server information is available and bail out early if it isn't. */
+ pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx);
+ if (!paOpServerInfo)
+ {
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ LogRel(("PulseAudio: Server information not available, skipping enumeration\n"));
+ return VINF_SUCCESS;
+ }
+
+ int rc = paWaitFor(pThis, paOpServerInfo);
+ if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
+ if (RT_SUCCESS(rc))
+ {
+ if (CbCtx.pszDefaultSink)
+ {
+ if (fLog)
+ LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
+
+ pThis->fEnumOpSuccess = false;
+ rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
+ paEnumSinkCb, &CbCtx));
+ if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
+ if ( RT_FAILURE(rc)
+ && fLog)
+ {
+ LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink));
+ }
+ }
+ else if (fLog)
+ LogRel2(("PulseAudio: No default output sink found\n"));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (CbCtx.pszDefaultSource)
+ {
+ if (fLog)
+ LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
+
+ pThis->fEnumOpSuccess = false;
+ rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
+ paEnumSourceCb, &CbCtx));
+ if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess)
+ && fLog)
+ {
+ LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource));
+ }
+ }
+ else if (fLog)
+ LogRel2(("PulseAudio: No default input source found\n"));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fLog)
+ {
+ LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut));
+ LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn));
+ }
+
+ if (pCfg)
+ memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
+ }
+
+ if (CbCtx.pszDefaultSink)
+ {
+ RTStrFree(CbCtx.pszDefaultSink);
+ CbCtx.pszDefaultSink = NULL;
+ }
+
+ if (CbCtx.pszDefaultSource)
+ {
+ RTStrFree(CbCtx.pszDefaultSource);
+ CbCtx.pszDefaultSource = NULL;
+ }
+ }
+ else if (fLog)
+ LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n"));
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ LogFlowFuncEnter();
+
+ if (pStreamPA->pStream)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pa_stream_disconnect(pStreamPA->pStream);
+ pa_stream_unref(pStreamPA->pStream);
+
+ pStreamPA->pStream = NULL;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ if (pStreamPA->pStream)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ /* Make sure to cancel a pending draining operation, if any. */
+ if (pStreamPA->pDrainOp)
+ {
+ pa_operation_cancel(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+
+ pa_stream_disconnect(pStreamPA->pStream);
+ pa_stream_unref(pStreamPA->pStream);
+
+ pStreamPA->pStream = NULL;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ if ( pStreamPA->pDrainOp
+ && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
+ {
+ pa_operation_cancel(pStreamPA->pDrainOp);
+ pa_operation_unref(pStreamPA->pDrainOp);
+
+ pStreamPA->pDrainOp = NULL;
+ }
+ else
+ {
+ /* Uncork (resume) stream. */
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA));
+ }
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
+ * Note that we must return immediately from here! */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (!pStreamPA->pDrainOp)
+ {
+ rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA));
+ if (RT_SUCCESS(rc))
+ pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA);
+ }
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA));
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
+ {
+ pa_stream_drop(pStreamPA->pStream);
+ pStreamPA->pu8PeekBuf = NULL;
+ }
+
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA));
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ AssertPtrReturnVoid(pInterface);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ 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();
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq);
+ else if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
+ rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq);
+ else
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamPA->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = paDestroyStreamIn (pThis, pStreamPA);
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ rc = paDestroyStreamOut(pThis, pStreamPA);
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamPA->pCfg);
+ pStreamPA->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd);
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd);
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ return rc;
+}
+
+
+static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ uint32_t cbAvail = 0;
+
+ if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
+ {
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
+ Log3Func(("cbReadable=%RU32\n", cbAvail));
+ }
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
+
+ Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
+ cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ /* Don't report more writable than the PA server can handle. */
+ if (cbWritable > pStreamPA->BufAttr.maxlength)
+ cbWritable = pStreamPA->BufAttr.maxlength;
+
+ cbAvail = (uint32_t)cbWritable;
+ }
+ else
+ AssertFailed();
+ }
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ return paStreamGetAvail(pThis, pStreamPA);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ return paStreamGetAvail(pThis, pStreamPA);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ RT_NOREF(pStream);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
+
+ /* Check PulseAudio's general status. */
+ if ( pThis->pContext
+ && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
+ {
+ strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ }
+
+ return strmSts;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do here for PulseAudio. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ AssertPtrReturn(pInterface, NULL);
+ AssertPtrReturn(pszIID, NULL);
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/**
+ * Destructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ LogFlowFuncEnter();
+}
+
+
+/**
+ * Constructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
+ LogRel(("Audio: Initializing PulseAudio driver\n"));
+
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio);
+
+ int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
+ AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * 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(DRVHOSTPULSEAUDIO),
+ /* pfnConstruct */
+ drvHostPulseAudioConstruct,
+ /* pfnDestruct */
+ drvHostPulseAudioDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+#if 0 /* unused */
+static struct audio_option pulse_options[] =
+{
+ {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out,
+ "DAC period size in milliseconds", NULL, 0},
+ {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in,
+ "ADC period size in milliseconds", NULL, 0}
+};
+#endif
+
diff --git a/src/VBox/Devices/Audio/DrvHostValidationKit.cpp b/src/VBox/Devices/Audio/DrvHostValidationKit.cpp
new file mode 100644
index 00000000..3344e001
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostValidationKit.cpp
@@ -0,0 +1,482 @@
+/* $Id: DrvHostValidationKit.cpp $ */
+/** @file
+ * ValidationKit audio driver - host backend for dumping and injecting audio data
+ * from/to the device emulation.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include <iprt/alloc.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 "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/**
+ * Structure for keeping a VAKIT input/output stream.
+ */
+typedef struct VAKITAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Audio file to dump output to or read input from. */
+ PPDMAUDIOFILE pFile;
+ /** Text file to store timing of audio buffers submittions**/
+ RTFILE hFileTiming;
+ /** Timestamp of the first play or record request**/
+ uint64_t tsStarted;
+ /** Total number of samples played or recorded so far**/
+ uint32_t uSamplesSinceStarted;
+ union
+ {
+ struct
+ {
+ /** Timestamp of last captured samples. */
+ uint64_t tsLastCaptured;
+ } In;
+ struct
+ {
+ /** Timestamp of last played samples. */
+ uint64_t tsLastPlayed;
+ uint8_t *pu8PlayBuffer;
+ uint32_t cbPlayBuffer;
+ } Out;
+ };
+} VAKITAUDIOSTREAM, *PVAKITAUDIOSTREAM;
+
+/**
+ * VAKIT audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTVAKITAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+} DRVHOSTVAKITAUDIO, *PDRVHOSTVAKITAUDIO;
+
+/*******************************************PDM_AUDIO_DRIVER******************************/
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostVaKitAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ RT_NOREF(pInterface);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Validation Kit audio driver");
+
+ pBackendCfg->cbStreamOut = sizeof(VAKITAUDIOSTREAM);
+ pBackendCfg->cbStreamIn = sizeof(VAKITAUDIOSTREAM);
+
+ pBackendCfg->cMaxStreamsOut = 1; /* Output */
+ pBackendCfg->cMaxStreamsIn = 0; /* No input supported yet. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostVaKitAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostVaKitAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ RT_NOREF(pInterface);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostVaKitAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+static int vakitCreateStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+
+ return VINF_SUCCESS;
+}
+
+
+static int vakitCreateStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ RT_NOREF(pDrv, pCfgAcq);
+
+ int rc = VINF_SUCCESS;
+
+ pStreamDbg->tsStarted = 0;
+ pStreamDbg->uSamplesSinceStarted = 0;
+ pStreamDbg->Out.tsLastPlayed = 0;
+ pStreamDbg->Out.cbPlayBuffer = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props);
+ pStreamDbg->Out.pu8PlayBuffer = (uint8_t *)RTMemAlloc(pStreamDbg->Out.cbPlayBuffer);
+ if (!pStreamDbg->Out.pu8PlayBuffer)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ char szTemp[RTPATH_MAX];
+ rc = RTPathTemp(szTemp, sizeof(szTemp));
+
+ RTPathAppend(szTemp, sizeof(szTemp), "VBoxTestTmp\\VBoxAudioValKit");
+
+ if (RT_SUCCESS(rc))
+ {
+ char szFile[RTPATH_MAX + 1];
+
+ rc = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, "VaKit",
+ 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAG_NONE, &pStreamDbg->pFile);
+ if (RT_SUCCESS(rc))
+ rc = DrvAudioHlpFileOpen(pStreamDbg->pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pCfgReq->Props);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VaKitAudio: Creating output file '%s' failed with %Rrc\n", szFile, rc));
+ }
+ else
+ {
+ size_t cch;
+ char szTimingInfo[128];
+ cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "# %dHz %dch %dbit\n",
+ pCfgReq->Props.uHz, pCfgReq->Props.cChannels, pCfgReq->Props.cBytes * 8);
+
+ RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL);
+ }
+ }
+ else
+ LogRel(("VaKitAudio: Unable to retrieve temp dir: %Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostVaKitAudioStreamCreate(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio);
+ PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = vakitCreateStreamIn( pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+ else
+ rc = vakitCreateStreamOut(pDrv, pStreamDbg, pCfgReq, pCfgAcq);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamDbg->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamDbg->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostVaKitAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio);
+ PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream;
+ RT_NOREF(pDrv);
+
+ uint64_t tsSinceStart;
+ size_t cch;
+ char szTimingInfo[128];
+
+ if (pStreamDbg->tsStarted == 0)
+ {
+ pStreamDbg->tsStarted = RTTimeNanoTS();
+ tsSinceStart = 0;
+ }
+ else
+ {
+ tsSinceStart = RTTimeNanoTS() - pStreamDbg->tsStarted;
+ }
+
+ // Microseconds are used everythere below
+ uint32_t sBuf = cxBuf >> pStreamDbg->pCfg->Props.cShift;
+ cch = RTStrPrintf(szTimingInfo, sizeof(szTimingInfo), "%d %d %d %d\n",
+ (uint32_t)(tsSinceStart / 1000), // Host time elapsed since Guest submitted the first buffer for playback
+ (uint32_t)(pStreamDbg->uSamplesSinceStarted * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long all the samples submitted previously were played
+ (uint32_t)(sBuf * 1.0E6 / pStreamDbg->pCfg->Props.uHz), // how long a new uSamplesReady samples should\will be played
+ sBuf);
+ RTFileWrite(pStreamDbg->hFileTiming, szTimingInfo, cch, NULL);
+ pStreamDbg->uSamplesSinceStarted += sBuf;
+
+ /* Remember when samples were consumed. */
+ // pStreamDbg->Out.tsLastPlayed = PDMDrvHlpTMGetVirtualTime(pDrv->pDrvIns);;
+
+ int rc2 = DrvAudioHlpFileWrite(pStreamDbg->pFile, pvBuf, cxBuf, 0 /* fFlags */);
+ if (RT_FAILURE(rc2))
+ LogRel(("VaKitAudio: Writing output failed with %Rrc\n", rc2));
+
+ *pcxWritten = cxBuf;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostVaKitAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxRead)
+{
+ RT_NOREF(pInterface, pStream, pvBuf, cxBuf);
+
+ /* Never capture anything. */
+ if (pcxRead)
+ *pcxRead = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+static int vakitDestroyStreamIn(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg)
+{
+ RT_NOREF(pDrv, pStreamDbg);
+ return VINF_SUCCESS;
+}
+
+
+static int vakitDestroyStreamOut(PDRVHOSTVAKITAUDIO pDrv, PVAKITAUDIOSTREAM pStreamDbg)
+{
+ RT_NOREF(pDrv);
+
+ if (pStreamDbg->Out.pu8PlayBuffer)
+ {
+ RTMemFree(pStreamDbg->Out.pu8PlayBuffer);
+ pStreamDbg->Out.pu8PlayBuffer = NULL;
+ }
+
+ if (pStreamDbg->pFile)
+ {
+ size_t cbDataSize = DrvAudioHlpFileGetDataSize(pStreamDbg->pFile);
+ if (cbDataSize)
+ LogRel(("VaKitAudio: Created output file '%s' (%zu bytes)\n", pStreamDbg->pFile->szName, cbDataSize));
+
+ DrvAudioHlpFileDestroy(pStreamDbg->pFile);
+ pStreamDbg->pFile = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) drvHostVaKitAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVHOSTVAKITAUDIO pDrv = RT_FROM_MEMBER(pInterface, DRVHOSTVAKITAUDIO, IHostAudio);
+ PVAKITAUDIOSTREAM pStreamDbg = (PVAKITAUDIOSTREAM)pStream;
+
+ if (!pStreamDbg->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamDbg->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = vakitDestroyStreamIn (pDrv, pStreamDbg);
+ else
+ rc = vakitDestroyStreamOut(pDrv, pStreamDbg);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamDbg->pCfg);
+ pStreamDbg->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) drvHostVaKitAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ RT_NOREF(enmStreamCmd);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostVaKitAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostVaKitAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return UINT32_MAX;
+}
+
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostVaKitAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+
+ return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
+}
+
+static DECLCALLBACK(int) drvHostVaKitAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface, pStream);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostVaKitAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO);
+
+ 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) drvHostVaKitAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTVAKITAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTVAKITAUDIO);
+ LogRel(("Audio: Initializing VAKIT driver\n"));
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostVaKitAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostVaKitAudio);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * 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(DRVHOSTVAKITAUDIO),
+ /* pfnConstruct */
+ drvHostVaKitAudioConstruct,
+ /* 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/HDACodec.cpp b/src/VBox/Devices/Audio/HDACodec.cpp
new file mode 100644
index 00000000..a13ce526
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDACodec.cpp
@@ -0,0 +1,3301 @@
+/* $Id: HDACodec.cpp $ */
+/** @file
+ * HDACodec - VBox HD Audio Codec.
+ *
+ * Implemented based on the Intel HD Audio specification and the
+ * Sigmatel/IDT STAC9220 datasheet.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA_CODEC
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.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 "DrvAudio.h"
+#include "HDACodec.h"
+#include "DevHDACommon.h"
+#include "AudioMixer.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* 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
+
+/**
+ * Common (or core) codec node structure.
+ */
+typedef struct CODECCOMMONNODE
+{
+ /** The node's ID. */
+ uint8_t uID;
+ /** The node's name. */
+ char const *pszName;
+ /** 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;
+typedef CODECCOMMONNODE *PCODECCOMMONNODE;
+AssertCompile(CODECNODE_F00_PARAM_LENGTH == 20); /* saved state */
+AssertCompile(CODECNODE_F02_PARAM_LENGTH == 16); /* saved state */
+
+/**
+ * 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) + sizeof(void *) - 1) & ~(sizeof(void *) - 1)) )
+
+typedef struct ROOTCODECNODE
+{
+ CODECCOMMONNODE node;
+} ROOTCODECNODE, *PROOTCODECNODE;
+AssertNodeSize(ROOTCODECNODE, 0);
+
+#define AMPLIFIER_SIZE 60
+typedef uint32_t AMPLIFIER[AMPLIFIER_SIZE];
+#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)])
+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);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/* 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
+
+/* STAC9220 - 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 };
+/* STAC 9221. */
+/** @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 a CODECNODE. */
+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 the CODECNODE. */
+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()
+};
+
+
+
+#if 0 /* unused */
+static DECLCALLBACK(void) stac9220DbgNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ for (uint8_t i = 1; i < pThis->cTotalNodes; i++)
+ {
+ PCODECNODE pNode = &pThis->paNodes[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
+
+/**
+ * Resets the codec with all its connected nodes.
+ *
+ * @param pThis HDA codec to reset.
+ */
+static DECLCALLBACK(void) stac9220Reset(PHDACODEC pThis)
+{
+ AssertPtrReturnVoid(pThis->paNodes);
+ AssertPtrReturnVoid(pThis->pfnNodeReset);
+
+ LogRel(("HDA: Codec reset\n"));
+
+ pThis->fInReset = true;
+
+ for (uint8_t i = 0; i < pThis->cTotalNodes; i++)
+ pThis->pfnNodeReset(pThis, i, &pThis->paNodes[i]);
+
+ pThis->fInReset = false;
+}
+
+/**
+ * Resets a single node of the codec.
+ *
+ * @returns IPRT status code.
+ * @param pThis HDA codec of node to reset.
+ * @param uNID Node ID to set node to.
+ * @param pNode Node to reset.
+ */
+static DECLCALLBACK(int) stac9220ResetNode(PHDACODEC pThis, uint8_t uNID, PCODECNODE pNode)
+{
+ LogFlowFunc(("NID=0x%x (%RU8)\n", uNID, uNID));
+
+ if ( !pThis->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 44.1kHz, 16-bit signed. */
+ pNode->afg.node.au32F00_param[0x0A] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT;
+ 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_1X, 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_1X, 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_1X, 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->paNodes[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_1X, 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->paNodes[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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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 (!pThis->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;
+ }
+
+ return VINF_SUCCESS;
+}
+
+static int stac9220Construct(PHDACODEC pThis)
+{
+ unconst(pThis->cTotalNodes) = STAC9221_NUM_NODES;
+
+ pThis->pfnReset = stac9220Reset;
+ pThis->pfnNodeReset = stac9220ResetNode;
+
+ pThis->u16VendorId = 0x8384; /* SigmaTel */
+ /*
+ * 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.
+ */
+ pThis->u16DeviceId = 0x7680; /* STAC9221 A1 */
+ pThis->u8BSKU = 0x76;
+ pThis->u8AssemblyId = 0x80;
+
+ pThis->paNodes = (PCODECNODE)RTMemAllocZ(sizeof(CODECNODE) * pThis->cTotalNodes);
+ if (!pThis->paNodes)
+ return VERR_NO_MEMORY;
+
+ pThis->fInReset = false;
+
+#define STAC9220WIDGET(type) pThis->au8##type##s = g_abStac9220##type##s
+ 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
+
+ unconst(pThis->u8AdcVolsLineIn) = STAC9220_NID_AMP_ADC0;
+ unconst(pThis->u8DacLineOut) = STAC9220_NID_DAC1;
+
+ /*
+ * 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!
+ */
+ AssertPtr(pThis->paNodes);
+ AssertPtr(pThis->pfnNodeReset);
+
+ for (uint8_t i = 0; i < pThis->cTotalNodes; i++)
+ {
+ int rc2 = stac9220ResetNode(pThis, i, &pThis->paNodes[i]);
+ AssertRC(rc2);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Some generic predicate functions.
+ */
+
+#define DECLISNODEOFTYPE(type) \
+ DECLINLINE(bool) hdaCodecIs##type##Node(PHDACODEC pThis, uint8_t cNode) \
+ { \
+ Assert(pThis->au8##type##s); \
+ for (int i = 0; pThis->au8##type##s[i] != 0; ++i) \
+ if (pThis->au8##type##s[i] == cNode) \
+ return true; \
+ return false; \
+ }
+/* hdaCodecIsPortNode */
+DECLISNODEOFTYPE(Port)
+/* hdaCodecIsDacNode */
+DECLISNODEOFTYPE(Dac)
+/* hdaCodecIsAdcVolNode */
+DECLISNODEOFTYPE(AdcVol)
+/* hdaCodecIsAdcNode */
+DECLISNODEOFTYPE(Adc)
+/* hdaCodecIsAdcMuxNode */
+DECLISNODEOFTYPE(AdcMux)
+/* hdaCodecIsPcbeepNode */
+DECLISNODEOFTYPE(Pcbeep)
+/* hdaCodecIsSpdifOutNode */
+DECLISNODEOFTYPE(SpdifOut)
+/* hdaCodecIsSpdifInNode */
+DECLISNODEOFTYPE(SpdifIn)
+/* hdaCodecIsDigInPinNode */
+DECLISNODEOFTYPE(DigInPin)
+/* hdaCodecIsDigOutPinNode */
+DECLISNODEOFTYPE(DigOutPin)
+/* hdaCodecIsCdNode */
+DECLISNODEOFTYPE(Cd)
+/* hdaCodecIsVolKnobNode */
+DECLISNODEOFTYPE(VolKnob)
+/* hdaCodecIsReservedNode */
+DECLISNODEOFTYPE(Reserved)
+
+
+/*
+ * Misc helpers.
+ */
+static int hdaCodecToAudVolume(PHDACODEC 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 lVol = AMPLIFIER_REGISTER(*pAmp, iDir, AMPLIFIER_LEFT, 0) & 0x7f;
+ uint8_t rVol = 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.
+ */
+ lVol = (lVol + 1) * (2 * 255) / 256;
+ rVol = (rVol + 1) * (2 * 255) / 256;
+
+ PDMAUDIOVOLUME Vol = { RT_BOOL(iMute), lVol, rVol };
+
+ LogFunc(("[NID0x%02x] %RU8/%RU8 (%s)\n",
+ pNode->node.uID, lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted"));
+
+ LogRel2(("HDA: Setting volume for mixer control '%s' to %RU8/%RU8 (%s)\n",
+ DrvAudioHlpAudMixerCtlToStr(enmMixerCtl), lVol, rVol, RT_BOOL(iMute) ? "Muted" : "Unmuted"));
+
+ return pThis->pfnCbMixerSetVolume(pThis->pHDAState, 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 */
+
+static DECLCALLBACK(int) vrbProcUnimplemented(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ RT_NOREF(pThis, cmd);
+ LogFlowFunc(("cmd(raw:%x: cad:%x, d:%c, nid:%x, verb:%x)\n", cmd,
+ CODEC_CAD(cmd), CODEC_DIRECT(cmd) ? 'N' : 'Y', CODEC_NID(cmd), CODEC_VERBDATA(cmd)));
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vrbProcBreak(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ int rc;
+ rc = vrbProcUnimplemented(pThis, cmd, pResp);
+ *pResp |= CODEC_RESPONSE_UNSOLICITED;
+ return rc;
+}
+
+#endif /* unused */
+
+/* B-- */
+static DECLCALLBACK(int) vrbProcGetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 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(cmd) == AMPLIFIER_OUT ? 0 : CODEC_GET_AMP_INDEX(cmd);
+
+ PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)];
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->dac.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->adcvol.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->adcmux.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->pcbeep.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->port.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = AMPLIFIER_REGISTER(pNode->adc.B_params,
+ CODEC_GET_AMP_DIRECTION(cmd),
+ CODEC_GET_AMP_SIDE(cmd),
+ u8Index);
+ else
+ LogRel2(("HDA: Warning: Unhandled get amplifier command: 0x%x (NID=0x%x [%RU8])\n", cmd, CODEC_NID(cmd), CODEC_NID(cmd)));
+
+ return VINF_SUCCESS;
+}
+
+/* 3-- */
+static DECLCALLBACK(int) vrbProcSetAmplifier(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ PCODECNODE pNode = &pThis->paNodes[CODEC_NID(cmd)];
+ AMPLIFIER *pAmplifier = NULL;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->dac.B_params;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->adcvol.B_params;
+ else if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->adcmux.B_params;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->pcbeep.B_params;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->port.B_params;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ pAmplifier = &pNode->adc.B_params;
+ else
+ LogRel2(("HDA: Warning: Unhandled set amplifier command: 0x%x (Payload=%RU16, NID=0x%x [%RU8])\n",
+ cmd, CODEC_VERB_PAYLOAD16(cmd), CODEC_NID(cmd), CODEC_NID(cmd)));
+
+ if (!pAmplifier)
+ return VINF_SUCCESS;
+
+ bool fIsOut = CODEC_SET_AMP_IS_OUT_DIRECTION(cmd);
+ bool fIsIn = CODEC_SET_AMP_IS_IN_DIRECTION(cmd);
+ bool fIsLeft = CODEC_SET_AMP_IS_LEFT_SIDE(cmd);
+ bool fIsRight = CODEC_SET_AMP_IS_RIGHT_SIDE(cmd);
+ uint8_t u8Index = CODEC_SET_AMP_INDEX(cmd);
+
+ if ( (!fIsLeft && !fIsRight)
+ || (!fIsOut && !fIsIn))
+ return VINF_SUCCESS;
+
+ LogFunc(("[NID0x%02x] fIsOut=%RTbool, fIsIn=%RTbool, fIsLeft=%RTbool, fIsRight=%RTbool, Idx=%RU8\n",
+ CODEC_NID(cmd), fIsOut, fIsIn, fIsLeft, fIsRight, u8Index));
+
+ if (fIsIn)
+ {
+ if (fIsLeft)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_LEFT, u8Index), cmd, 0);
+ if (fIsRight)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_IN, AMPLIFIER_RIGHT, u8Index), cmd, 0);
+
+ // if (CODEC_NID(cmd) == pThis->u8AdcVolsLineIn)
+ // {
+ hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_LINE_IN);
+ // }
+ }
+ if (fIsOut)
+ {
+ if (fIsLeft)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_LEFT, u8Index), cmd, 0);
+ if (fIsRight)
+ hdaCodecSetRegisterU8(&AMPLIFIER_REGISTER(*pAmplifier, AMPLIFIER_OUT, AMPLIFIER_RIGHT, u8Index), cmd, 0);
+
+ if (CODEC_NID(cmd) == pThis->u8DacLineOut)
+ hdaCodecToAudVolume(pThis, pNode, pAmplifier, PDMAUDIOMIXERCTL_FRONT);
+ }
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vrbProcGetParameter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F00_PARAM_LENGTH);
+ if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F00_PARAM_LENGTH)
+ {
+ *pResp = 0;
+
+ LogFlowFunc(("invalid F00 parameter %d\n", (cmd & CODEC_VERB_8BIT_DATA)));
+ return VINF_SUCCESS;
+ }
+
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F00_param[cmd & CODEC_VERB_8BIT_DATA];
+ return VINF_SUCCESS;
+}
+
+/* F01 */
+static DECLCALLBACK(int) vrbProcGetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 701 */
+static DECLCALLBACK(int) vrbProcSetConSelectCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsAdcMuxNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcmux.u32F01_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F01_param;
+ else if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F01_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F01_param;
+ else if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F01_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set connection select control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F07 */
+static DECLCALLBACK(int) vrbProcGetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 707 */
+static DECLCALLBACK(int) vrbProcSetPinCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F07_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F07_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F07_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F07_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F07_param;
+ else if ( hdaCodecIsReservedNode(pThis, CODEC_NID(cmd))
+ && CODEC_NID(cmd) == 0x1b)
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F07_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set pin control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F08 */
+static DECLCALLBACK(int) vrbProcGetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param;
+ else if ((cmd) == STAC9220_NID_AFG)
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param;
+ else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 708 */
+static DECLCALLBACK(int) vrbProcSetUnsolicitedEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param;
+ else if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F08_param;
+ else if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F08_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F08_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F08_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set unsolicited enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F09 */
+static DECLCALLBACK(int) vrbProcGetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param;
+ else
+ {
+ AssertFailed();
+ LogRel2(("HDA: Warning: Unhandled get pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* 709 */
+static DECLCALLBACK(int) vrbProcSetPinSense(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F09_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F09_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set pin sense command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) vrbProcGetConnectionListEntry(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ Assert((cmd & CODEC_VERB_8BIT_DATA) < CODECNODE_F02_PARAM_LENGTH);
+ if ((cmd & CODEC_VERB_8BIT_DATA) >= CODECNODE_F02_PARAM_LENGTH)
+ {
+ LogFlowFunc(("access to invalid F02 index %d\n", (cmd & CODEC_VERB_8BIT_DATA)));
+ return VINF_SUCCESS;
+ }
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].node.au32F02_param[cmd & CODEC_VERB_8BIT_DATA];
+ return VINF_SUCCESS;
+}
+
+/* F03 */
+static DECLCALLBACK(int) vrbProcGetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param;
+
+ return VINF_SUCCESS;
+}
+
+/* 703 */
+static DECLCALLBACK(int) vrbProcSetProcessingState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].adc.u32F03_param, cmd, 0);
+ return VINF_SUCCESS;
+}
+
+/* F0D */
+static DECLCALLBACK(int) vrbProcGetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param;
+
+ return VINF_SUCCESS;
+}
+
+static int codecSetDigitalConverter(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F0d_param, cmd, u8Offset);
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU8(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F0d_param, cmd, u8Offset);
+ return VINF_SUCCESS;
+}
+
+/* 70D */
+static DECLCALLBACK(int) vrbProcSetDigitalConverter1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ return codecSetDigitalConverter(pThis, cmd, 0, pResp);
+}
+
+/* 70E */
+static DECLCALLBACK(int) vrbProcSetDigitalConverter2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ return codecSetDigitalConverter(pThis, cmd, 8, pResp);
+}
+
+/* F20 */
+static DECLCALLBACK(int) vrbProcGetSubId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ Assert(CODEC_CAD(cmd) == pThis->id);
+ Assert(CODEC_NID(cmd) < pThis->cTotalNodes);
+ if (CODEC_NID(cmd) >= pThis->cTotalNodes)
+ {
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd)));
+ return VINF_SUCCESS;
+ }
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param;
+ else
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+static int codecSetSubIdX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset)
+{
+ Assert(CODEC_CAD(cmd) == pThis->id);
+ Assert(CODEC_NID(cmd) < pThis->cTotalNodes);
+ if (CODEC_NID(cmd) >= pThis->cTotalNodes)
+ {
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd)));
+ return VINF_SUCCESS;
+ }
+ uint32_t *pu32Reg;
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F20_param;
+ else
+ AssertFailedReturn(VINF_SUCCESS);
+ hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset);
+ return VINF_SUCCESS;
+}
+
+/* 720 */
+static DECLCALLBACK(int) vrbProcSetSubId0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetSubIdX(pThis, cmd, 0);
+}
+
+/* 721 */
+static DECLCALLBACK(int) vrbProcSetSubId1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetSubIdX(pThis, cmd, 8);
+}
+
+/* 722 */
+static DECLCALLBACK(int) vrbProcSetSubId2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetSubIdX(pThis, cmd, 16);
+}
+
+/* 723 */
+static DECLCALLBACK(int) vrbProcSetSubId3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetSubIdX(pThis, cmd, 24);
+}
+
+static DECLCALLBACK(int) vrbProcReset(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ Assert(CODEC_CAD(cmd) == pThis->id);
+ Assert(CODEC_NID(cmd) == STAC9220_NID_AFG);
+
+ if ( CODEC_NID(cmd) == STAC9220_NID_AFG
+ && pThis->pfnReset)
+ {
+ pThis->pfnReset(pThis);
+ }
+
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+/* F05 */
+static DECLCALLBACK(int) vrbProcGetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ LogFunc(("[NID0x%02x]: fReset=%RTbool, fStopOk=%RTbool, Act=D%RU8, Set=D%RU8\n",
+ CODEC_NID(cmd), CODEC_F05_IS_RESET(*pResp), CODEC_F05_IS_STOPOK(*pResp), CODEC_F05_ACT(*pResp), CODEC_F05_SET(*pResp)));
+ return VINF_SUCCESS;
+}
+
+/* 705 */
+#if 1
+static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param;
+ else
+ {
+ LogRel2(("HDA: Warning: Unhandled set power state command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+ }
+
+ if (!pu32Reg)
+ return VINF_SUCCESS;
+
+ uint8_t uPwrCmd = CODEC_F05_SET (cmd);
+ 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(cmd), uPwrCmd, fReset, fStopOk, fError, uPwrAct, uPwrSet));
+ LogFunc(("AFG: Act=D%RU8, Set=D%RU8\n",
+ CODEC_F05_ACT(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param),
+ CODEC_F05_SET(pThis->paNodes[STAC9220_NID_AFG].afg.u32F05_param)));
+#endif
+
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, uPwrCmd /* PS-Act */, uPwrCmd /* PS-Set */);
+
+ const uint8_t uAFGPwrAct = CODEC_F05_ACT(pThis->paNodes[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(_aList, _aMember) \
+ { \
+ const uint8_t *pu8NodeIndex = &_aList[0]; \
+ while (*(++pu8NodeIndex)) \
+ { \
+ pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param = \
+ CODEC_MAKE_F05(fReset, fStopOk, 0, uAFGPwrAct, uPwrCmd); \
+ LogFunc(("\t[NID0x%02x]: Act=D%RU8, Set=D%RU8\n", *pu8NodeIndex, \
+ CODEC_F05_ACT(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param), \
+ CODEC_F05_SET(pThis->paNodes[*pu8NodeIndex]._aMember.u32F05_param))); \
+ } \
+ }
+
+ PROPAGATE_PWR_STATE(pThis->au8Dacs, dac);
+ PROPAGATE_PWR_STATE(pThis->au8Adcs, adc);
+ PROPAGATE_PWR_STATE(pThis->au8DigInPins, digin);
+ PROPAGATE_PWR_STATE(pThis->au8DigOutPins, digout);
+ PROPAGATE_PWR_STATE(pThis->au8SpdifIns, spdifin);
+ PROPAGATE_PWR_STATE(pThis->au8SpdifOuts, spdifout);
+ PROPAGATE_PWR_STATE(pThis->au8Reserveds, 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(cmd),
+ 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);
+}
+
+static DECLCALLBACK(int) vrbProcSetPowerState(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ Assert(CODEC_CAD(cmd) == pThis->id);
+ Assert(CODEC_NID(cmd) < pThis->cTotalNodes);
+ if (CODEC_NID(cmd) >= pThis->cTotalNodes)
+ {
+ LogFlowFunc(("invalid node address %d\n", CODEC_NID(cmd)));
+ return VINF_SUCCESS;
+ }
+ *pResp = 0;
+ uint32_t *pu32Reg;
+ if (CODEC_NID(cmd) == 1 /* AFG */)
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].afg.u32F05_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F05_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F05_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F05_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F05_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F05_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F05_param;
+ else
+ AssertFailedReturn(VINF_SUCCESS);
+
+ bool fReset = CODEC_F05_IS_RESET(*pu32Reg);
+ bool fStopOk = CODEC_F05_IS_STOPOK(*pu32Reg);
+
+ if (CODEC_NID(cmd) != 1 /* AFG */)
+ {
+ /*
+ * We shouldn't propogate actual power state, which actual for AFG
+ */
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0,
+ CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param),
+ CODEC_F05_SET(cmd));
+ }
+
+ /* Propagate next power state only if AFG is on or verb modifies AFG power state */
+ if ( CODEC_NID(cmd) == 1 /* AFG */
+ || !CODEC_F05_ACT(pThis->paNodes[1].afg.u32F05_param))
+ {
+ *pu32Reg = CODEC_MAKE_F05(fReset, fStopOk, 0, CODEC_F05_SET(cmd), CODEC_F05_SET(cmd));
+ if ( CODEC_NID(cmd) == 1 /* AFG */
+ && (CODEC_F05_SET(cmd)) == CODEC_F05_D0)
+ {
+ /* now we're powered on AFG and may propogate power states on nodes */
+ const uint8_t *pu8NodeIndex = &pThis->au8Dacs[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].dac.u32F05_param);
+
+ pu8NodeIndex = &pThis->au8Adcs[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].adc.u32F05_param);
+
+ pu8NodeIndex = &pThis->au8DigInPins[0];
+ while (*(++pu8NodeIndex))
+ codecPropogatePowerState(&pThis->paNodes[*pu8NodeIndex].digin.u32F05_param);
+ }
+ }
+ return VINF_SUCCESS;
+}
+#endif
+
+/* F06 */
+static DECLCALLBACK(int) vrbProcGetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32F06_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param;
+ else if (CODEC_NID(cmd) == STAC9221_NID_I2S_OUT)
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F06_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get stream ID command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ LogFlowFunc(("[NID0x%02x] Stream ID=%RU8, channel=%RU8\n",
+ CODEC_NID(cmd), CODEC_F00_06_GET_STREAM_ID(cmd), CODEC_F00_06_GET_CHANNEL_ID(cmd)));
+
+ return VINF_SUCCESS;
+}
+
+/* 706 */
+static DECLCALLBACK(int) vrbProcSetStreamId(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint8_t uSD = CODEC_F00_06_GET_STREAM_ID(cmd);
+ uint8_t uChannel = CODEC_F00_06_GET_CHANNEL_ID(cmd);
+
+ LogFlowFunc(("[NID0x%02x] Setting to stream ID=%RU8, channel=%RU8\n",
+ CODEC_NID(cmd), uSD, uChannel));
+
+ PDMAUDIODIR enmDir;
+ uint32_t *pu32Addr = NULL;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ {
+ pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F06_param;
+ enmDir = PDMAUDIODIR_OUT;
+ }
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ {
+ pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].adc.u32F06_param;
+ enmDir = PDMAUDIODIR_IN;
+ }
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ {
+ pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].spdifout.u32F06_param;
+ enmDir = PDMAUDIODIR_OUT;
+ }
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ {
+ pu32Addr = &pThis->paNodes[CODEC_NID(cmd)].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(cmd), cmd));
+ }
+
+ /* Do we (re-)assign our input/output SDn (SDI/SDO) IDs? */
+ if (enmDir != PDMAUDIODIR_UNKNOWN)
+ {
+ pThis->paNodes[CODEC_NID(cmd)].node.uSD = uSD;
+ pThis->paNodes[CODEC_NID(cmd)].node.uChannel = uChannel;
+
+ if (enmDir == PDMAUDIODIR_OUT)
+ {
+ /** @todo Check if non-interleaved streams need a different channel / SDn? */
+
+ /* Propagate to the controller. */
+ pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_FRONT, uSD, uChannel);
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_CENTER_LFE, uSD, uChannel);
+ pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_REAR, uSD, uChannel);
+#endif
+ }
+ else if (enmDir == PDMAUDIODIR_IN)
+ {
+ pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_LINE_IN, uSD, uChannel);
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ pThis->pfnCbMixerControl(pThis->pHDAState, PDMAUDIOMIXERCTL_MIC_IN, uSD, uChannel);
+#endif
+ }
+ }
+
+ if (pu32Addr)
+ hdaCodecSetRegisterU8(pu32Addr, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* A0 */
+static DECLCALLBACK(int) vrbProcGetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param;
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param;
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param;
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32A_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* Also see section 3.7.1. */
+static DECLCALLBACK(int) vrbProcSetConverterFormat(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].dac.u32A_param, cmd, 0);
+ else if (hdaCodecIsAdcNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].adc.u32A_param, cmd, 0);
+ else if (hdaCodecIsSpdifOutNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifout.u32A_param, cmd, 0);
+ else if (hdaCodecIsSpdifInNode(pThis, CODEC_NID(cmd)))
+ hdaCodecSetRegisterU16(&pThis->paNodes[CODEC_NID(cmd)].spdifin.u32A_param, cmd, 0);
+ else
+ LogRel2(("HDA: Warning: Unhandled set converter format command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* F0C */
+static DECLCALLBACK(int) vrbProcGetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 70C */
+static DECLCALLBACK(int) vrbProcSetEAPD_BTLEnabled(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsAdcVolNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].adcvol.u32F0c_param;
+ else if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F0c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F0c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set EAPD/BTL enabled command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F0F */
+static DECLCALLBACK(int) vrbProcGetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 70F */
+static DECLCALLBACK(int) vrbProcSetVolumeKnobCtrl(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsVolKnobNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].volumeKnob.u32F0f_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set volume knob control command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F15 */
+static DECLCALLBACK(int) vrbProcGetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ RT_NOREF(pThis, cmd);
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+/* 715 */
+static DECLCALLBACK(int) vrbProcSetGPIOData(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ RT_NOREF(pThis, cmd);
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+/* F16 */
+static DECLCALLBACK(int) vrbProcGetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ RT_NOREF(pThis, cmd);
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+/* 716 */
+static DECLCALLBACK(int) vrbProcSetGPIOEnableMask(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ RT_NOREF(pThis, cmd);
+ *pResp = 0;
+ return VINF_SUCCESS;
+}
+
+/* F17 */
+static DECLCALLBACK(int) vrbProcGetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ /* Note: this is true for ALC885. */
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ *pResp = pThis->paNodes[1].afg.u32F17_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 717 */
+static DECLCALLBACK(int) vrbProcSetGPIODirection(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (CODEC_NID(cmd) == STAC9220_NID_AFG)
+ pu32Reg = &pThis->paNodes[1].afg.u32F17_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set GPIO direction command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/* F1C */
+static DECLCALLBACK(int) vrbProcGetConfig(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get config command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+static int codecSetConfigX(PHDACODEC pThis, uint32_t cmd, uint8_t u8Offset)
+{
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsPortNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].port.u32F1c_param;
+ else if (hdaCodecIsDigInPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digin.u32F1c_param;
+ else if (hdaCodecIsDigOutPinNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].digout.u32F1c_param;
+ else if (hdaCodecIsCdNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].cdnode.u32F1c_param;
+ else if (hdaCodecIsPcbeepNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].pcbeep.u32F1c_param;
+ else if (hdaCodecIsReservedNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].reserved.u32F1c_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set config command (%RU8) for NID0x%02x: 0x%x\n", u8Offset, CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, u8Offset);
+
+ return VINF_SUCCESS;
+}
+
+/* 71C */
+static DECLCALLBACK(int) vrbProcSetConfig0(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetConfigX(pThis, cmd, 0);
+}
+
+/* 71D */
+static DECLCALLBACK(int) vrbProcSetConfig1(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetConfigX(pThis, cmd, 8);
+}
+
+/* 71E */
+static DECLCALLBACK(int) vrbProcSetConfig2(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetConfigX(pThis, cmd, 16);
+}
+
+/* 71E */
+static DECLCALLBACK(int) vrbProcSetConfig3(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+ return codecSetConfigX(pThis, cmd, 24);
+}
+
+/* F04 */
+static DECLCALLBACK(int) vrbProcGetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ *pResp = pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled get SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ return VINF_SUCCESS;
+}
+
+/* 704 */
+static DECLCALLBACK(int) vrbProcSetSDISelect(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp)
+{
+ *pResp = 0;
+
+ uint32_t *pu32Reg = NULL;
+ if (hdaCodecIsDacNode(pThis, CODEC_NID(cmd)))
+ pu32Reg = &pThis->paNodes[CODEC_NID(cmd)].dac.u32F04_param;
+ else
+ LogRel2(("HDA: Warning: Unhandled set SDI select command for NID0x%02x: 0x%x\n", CODEC_NID(cmd), cmd));
+
+ if (pu32Reg)
+ hdaCodecSetRegisterU8(pu32Reg, cmd, 0);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * HDA codec verb map.
+ * @todo Any reason not to use binary search here?
+ */
+static const CODECVERB g_aCodecVerbs[] =
+{
+ /* Verb Verb mask Callback Name
+ * ---------- --------------------- ----------------------------------------------------------
+ */
+ { 0x000F0000, CODEC_VERB_8BIT_CMD , vrbProcGetParameter , "GetParameter " },
+ { 0x000F0100, CODEC_VERB_8BIT_CMD , vrbProcGetConSelectCtrl , "GetConSelectCtrl " },
+ { 0x00070100, CODEC_VERB_8BIT_CMD , vrbProcSetConSelectCtrl , "SetConSelectCtrl " },
+ { 0x000F0600, CODEC_VERB_8BIT_CMD , vrbProcGetStreamId , "GetStreamId " },
+ { 0x00070600, CODEC_VERB_8BIT_CMD , vrbProcSetStreamId , "SetStreamId " },
+ { 0x000F0700, CODEC_VERB_8BIT_CMD , vrbProcGetPinCtrl , "GetPinCtrl " },
+ { 0x00070700, CODEC_VERB_8BIT_CMD , vrbProcSetPinCtrl , "SetPinCtrl " },
+ { 0x000F0800, CODEC_VERB_8BIT_CMD , vrbProcGetUnsolicitedEnabled , "GetUnsolicitedEnabled " },
+ { 0x00070800, CODEC_VERB_8BIT_CMD , vrbProcSetUnsolicitedEnabled , "SetUnsolicitedEnabled " },
+ { 0x000F0900, CODEC_VERB_8BIT_CMD , vrbProcGetPinSense , "GetPinSense " },
+ { 0x00070900, CODEC_VERB_8BIT_CMD , vrbProcSetPinSense , "SetPinSense " },
+ { 0x000F0200, CODEC_VERB_8BIT_CMD , vrbProcGetConnectionListEntry , "GetConnectionListEntry" },
+ { 0x000F0300, CODEC_VERB_8BIT_CMD , vrbProcGetProcessingState , "GetProcessingState " },
+ { 0x00070300, CODEC_VERB_8BIT_CMD , vrbProcSetProcessingState , "SetProcessingState " },
+ { 0x000F0D00, CODEC_VERB_8BIT_CMD , vrbProcGetDigitalConverter , "GetDigitalConverter " },
+ { 0x00070D00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter1 , "SetDigitalConverter1 " },
+ { 0x00070E00, CODEC_VERB_8BIT_CMD , vrbProcSetDigitalConverter2 , "SetDigitalConverter2 " },
+ { 0x000F2000, CODEC_VERB_8BIT_CMD , vrbProcGetSubId , "GetSubId " },
+ { 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 " },
+ { 0x000F0500, CODEC_VERB_8BIT_CMD , vrbProcGetPowerState , "GetPowerState " },
+ { 0x00070500, CODEC_VERB_8BIT_CMD , vrbProcSetPowerState , "SetPowerState " },
+ { 0x000F0C00, CODEC_VERB_8BIT_CMD , vrbProcGetEAPD_BTLEnabled , "GetEAPD_BTLEnabled " },
+ { 0x00070C00, CODEC_VERB_8BIT_CMD , vrbProcSetEAPD_BTLEnabled , "SetEAPD_BTLEnabled " },
+ { 0x000F0F00, CODEC_VERB_8BIT_CMD , vrbProcGetVolumeKnobCtrl , "GetVolumeKnobCtrl " },
+ { 0x00070F00, CODEC_VERB_8BIT_CMD , vrbProcSetVolumeKnobCtrl , "SetVolumeKnobCtrl " },
+ { 0x000F1500, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOData , "GetGPIOData " },
+ { 0x00071500, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOData , "SetGPIOData " },
+ { 0x000F1600, CODEC_VERB_8BIT_CMD , vrbProcGetGPIOEnableMask , "GetGPIOEnableMask " },
+ { 0x00071600, CODEC_VERB_8BIT_CMD , vrbProcSetGPIOEnableMask , "SetGPIOEnableMask " },
+ { 0x000F1700, CODEC_VERB_8BIT_CMD , vrbProcGetGPIODirection , "GetGPIODirection " },
+ { 0x00071700, CODEC_VERB_8BIT_CMD , vrbProcSetGPIODirection , "SetGPIODirection " },
+ { 0x000F1C00, CODEC_VERB_8BIT_CMD , vrbProcGetConfig , "GetConfig " },
+ { 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 " },
+ { 0x000A0000, CODEC_VERB_16BIT_CMD, vrbProcGetConverterFormat , "GetConverterFormat " },
+ { 0x00020000, CODEC_VERB_16BIT_CMD, vrbProcSetConverterFormat , "SetConverterFormat " },
+ { 0x000B0000, CODEC_VERB_16BIT_CMD, vrbProcGetAmplifier , "GetAmplifier " },
+ { 0x00030000, CODEC_VERB_16BIT_CMD, vrbProcSetAmplifier , "SetAmplifier " },
+ { 0x000F0400, CODEC_VERB_8BIT_CMD , vrbProcGetSDISelect , "GetSDISelect " },
+ { 0x00070400, CODEC_VERB_8BIT_CMD , vrbProcSetSDISelect , "SetSDISelect " }
+ /** @todo Implement 0x7e7: IDT Set GPIO (STAC922x only). */
+};
+
+#ifdef DEBUG
+typedef struct CODECDBGINFO
+{
+ /** DBGF info helpers. */
+ PCDBGFINFOHLP pHlp;
+ /** Current recursion level. */
+ uint8_t uLevel;
+ /** Pointer to codec state. */
+ PHDACODEC pThis;
+
+} CODECDBGINFO, *PCODECDBGINFO;
+
+#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__)
+
+static void codecDbgPrintfIndentV(PCODECDBGINFO pInfo, uint16_t uIndent, const char *pszFormat, va_list va)
+{
+ char *pszValueFormat;
+ if (RTStrAPrintfV(&pszValueFormat, pszFormat, va))
+ {
+ pInfo->pHlp->pfnPrintf(pInfo->pHlp, "%*s%s", uIndent, "", pszValueFormat);
+ RTStrFree(pszValueFormat);
+ }
+}
+
+static void codecDbgPrintf(PCODECDBGINFO pInfo, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ codecDbgPrintfIndentV(pInfo, pInfo->uLevel * 4, pszFormat, va);
+ va_end(va);
+}
+
+/* Power state */
+static void codecDbgPrintNodeRegF05(PCODECDBGINFO 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(PCODECDBGINFO pInfo, uint32_t u32Reg)
+{
+ codecDbgPrintf(pInfo, "RegA: %x\n", u32Reg);
+}
+
+static void codecDbgPrintNodeRegF00(PCODECDBGINFO 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(PCODECDBGINFO 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(PCODECDBGINFO 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(PCODECDBGINFO 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->cTotalNodes; i++)
+ {
+ const PCODECNODE pSubNode = &pInfo->pThis->paNodes[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
+ }
+}
+
+static DECLCALLBACK(void) codecDbgListNodes(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ pHlp->pfnPrintf(pHlp, "HDA LINK / INPUTS\n");
+
+ CODECDBGINFO dbgInfo;
+ dbgInfo.pHlp = pHlp;
+ dbgInfo.pThis = pThis;
+ dbgInfo.uLevel = 0;
+
+ PCODECDBGINFO pInfo = &dbgInfo;
+
+ CODECDBG_INDENT
+ for (uint8_t i = 0; i < pThis->cTotalNodes; i++)
+ {
+ PCODECNODE pNode = &pThis->paNodes[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
+}
+
+static DECLCALLBACK(void) codecDbgSelector(PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pThis, pHlp, pszArgs);
+}
+#endif
+
+static DECLCALLBACK(int) codecLookup(PHDACODEC pThis, uint32_t cmd, uint64_t *puResp)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(puResp, VERR_INVALID_POINTER);
+
+ if (CODEC_CAD(cmd) != pThis->id)
+ {
+ *puResp = 0;
+ AssertMsgFailed(("Unknown codec address 0x%x\n", CODEC_CAD(cmd)));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ( CODEC_VERBDATA(cmd) == 0
+ || CODEC_NID(cmd) >= pThis->cTotalNodes)
+ {
+ *puResp = 0;
+ AssertMsgFailed(("[NID0x%02x] Unknown / invalid node or data (0x%x)\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd)));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /** @todo r=andy Implement a binary search here. */
+ for (size_t i = 0; i < pThis->cVerbs; i++)
+ {
+ if ((CODEC_VERBDATA(cmd) & pThis->paVerbs[i].mask) == pThis->paVerbs[i].verb)
+ {
+ int rc2 = pThis->paVerbs[i].pfn(pThis, cmd, puResp);
+ AssertRC(rc2);
+ Log3Func(("[NID0x%02x] (0x%x) %s: 0x%x -> 0x%x\n",
+ CODEC_NID(cmd), pThis->paVerbs[i].verb, pThis->paVerbs[i].pszName, CODEC_VERB_PAYLOAD8(cmd), *puResp));
+ return rc2;
+ }
+ }
+
+ *puResp = 0;
+ LogFunc(("[NID0x%02x] Callback for %x not found\n", CODEC_NID(cmd), CODEC_VERBDATA(cmd)));
+ return VERR_NOT_FOUND;
+}
+
+/*
+ * APIs exposed to DevHDA.
+ */
+
+int hdaCodecAddStream(PHDACODEC 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 %d not implemented\n", enmMixerCtl));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = pThis->pfnCbMixerAddStream(pThis->pHDAState, enmMixerCtl, pCfg);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = pThis->pfnCbMixerRemoveStream(pThis->pHDAState, enmMixerCtl);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int hdaCodecSaveState(PHDACODEC pThis, PSSMHANDLE pSSM)
+{
+ AssertLogRelMsgReturn(pThis->cTotalNodes == STAC9221_NUM_NODES, ("cTotalNodes=%#x, should be 0x1c", pThis->cTotalNodes),
+ VERR_INTERNAL_ERROR);
+ SSMR3PutU32(pSSM, pThis->cTotalNodes);
+ for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode)
+ SSMR3PutStructEx(pSSM, &pThis->paNodes[idxNode].SavedState, sizeof(pThis->paNodes[idxNode].SavedState),
+ 0 /*fFlags*/, g_aCodecNodeFields, NULL /*pvUser*/);
+ return VINF_SUCCESS;
+}
+
+int hdaCodecLoadState(PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+ int rc = VINF_SUCCESS;
+
+ PCSSMFIELD pFields = NULL;
+ uint32_t fFlags = 0;
+ switch (uVersion)
+ {
+ case HDA_SSM_VERSION_1:
+ AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+ pFields = g_aCodecNodeFieldsV1;
+ fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED;
+ break;
+
+ case HDA_SSM_VERSION_2:
+ case HDA_SSM_VERSION_3:
+ AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+ pFields = g_aCodecNodeFields;
+ fFlags = SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED;
+ break;
+
+ /* Since version 4 a flexible node count is supported. */
+ case HDA_SSM_VERSION_4:
+ case HDA_SSM_VERSION_5:
+ case HDA_SSM_VERSION:
+ {
+ uint32_t cNodes;
+ int rc2 = SSMR3GetU32(pSSM, &cNodes);
+ AssertRCReturn(rc2, rc2);
+ if (cNodes != 0x1c)
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ AssertReturn(pThis->cTotalNodes == 0x1c, VERR_INTERNAL_ERROR);
+
+ pFields = g_aCodecNodeFields;
+ fFlags = 0;
+ break;
+ }
+
+ default:
+ rc = VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned idxNode = 0; idxNode < pThis->cTotalNodes; ++idxNode)
+ {
+ uint8_t idOld = pThis->paNodes[idxNode].SavedState.Core.uID;
+ int rc2 = SSMR3GetStructEx(pSSM, &pThis->paNodes[idxNode].SavedState,
+ sizeof(pThis->paNodes[idxNode].SavedState),
+ fFlags, pFields, NULL);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ AssertLogRelMsgReturn(idOld == pThis->paNodes[idxNode].SavedState.Core.uID,
+ ("loaded %#x, expected %#x\n", pThis->paNodes[idxNode].SavedState.Core.uID, idOld),
+ VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update stuff after changing the state.
+ */
+ PCODECNODE pNode;
+ if (hdaCodecIsDacNode(pThis, pThis->u8DacLineOut))
+ {
+ pNode = &pThis->paNodes[pThis->u8DacLineOut];
+ hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT);
+ }
+ else if (hdaCodecIsSpdifOutNode(pThis, pThis->u8DacLineOut))
+ {
+ pNode = &pThis->paNodes[pThis->u8DacLineOut];
+ hdaCodecToAudVolume(pThis, pNode, &pNode->spdifout.B_params, PDMAUDIOMIXERCTL_FRONT);
+ }
+
+ pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn];
+ hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Powers off the codec.
+ *
+ * @param pThis Codec to power off.
+ */
+void hdaCodecPowerOff(PHDACODEC pThis)
+{
+ if (!pThis)
+ return;
+
+ LogFlowFuncEnter();
+
+ LogRel2(("HDA: Powering off codec ...\n"));
+
+ int rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_FRONT);
+ AssertRC(rc2);
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_CENTER_LFE);
+ AssertRC(rc2);
+ rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_REAR);
+ AssertRC(rc2);
+#endif
+
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+ rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_MIC_IN);
+ AssertRC(rc2);
+#endif
+ rc2 = hdaCodecRemoveStream(pThis, PDMAUDIOMIXERCTL_LINE_IN);
+ AssertRC(rc2);
+}
+
+void hdaCodecDestruct(PHDACODEC pThis)
+{
+ if (!pThis)
+ return;
+
+ LogFlowFuncEnter();
+
+ if (pThis->paNodes)
+ {
+ RTMemFree(pThis->paNodes);
+ pThis->paNodes = NULL;
+ }
+}
+
+int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis,
+ uint16_t uLUN, PCFGMNODE pCfg)
+{
+ AssertPtrReturn(pDevIns, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ pThis->id = uLUN;
+ pThis->paVerbs = &g_aCodecVerbs[0];
+ pThis->cVerbs = RT_ELEMENTS(g_aCodecVerbs);
+
+#ifdef DEBUG
+ pThis->pfnDbgSelector = codecDbgSelector;
+ pThis->pfnDbgListNodes = codecDbgListNodes;
+#endif
+ pThis->pfnLookup = codecLookup;
+
+ int rc = stac9220Construct(pThis);
+ AssertRCReturn(rc, rc);
+
+ /* Common root node initializers. */
+ pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[0] = CODEC_MAKE_F00_00(pThis->u16VendorId, pThis->u16DeviceId);
+ pThis->paNodes[STAC9220_NID_ROOT].root.node.au32F00_param[4] = CODEC_MAKE_F00_04(0x1, 0x1);
+
+ /* Common AFG node initializers. */
+ pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x4] = CODEC_MAKE_F00_04(0x2, pThis->cTotalNodes - 2);
+ pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0x5] = CODEC_MAKE_F00_05(1, CODEC_F00_05_AFG);
+ pThis->paNodes[STAC9220_NID_AFG].afg.node.au32F00_param[0xA] = CODEC_F00_0A_44_1KHZ | CODEC_F00_0A_16_BIT;
+ pThis->paNodes[STAC9220_NID_AFG].afg.u32F20_param = CODEC_MAKE_F20(pThis->u16VendorId, pThis->u8BSKU, pThis->u8AssemblyId);
+
+ /*
+ * Set initial volume.
+ */
+ PCODECNODE pNode = &pThis->paNodes[pThis->u8DacLineOut];
+ hdaCodecToAudVolume(pThis, pNode, &pNode->dac.B_params, PDMAUDIOMIXERCTL_FRONT);
+
+ pNode = &pThis->paNodes[pThis->u8AdcVolsLineIn];
+ hdaCodecToAudVolume(pThis, pNode, &pNode->adcvol.B_params, PDMAUDIOMIXERCTL_LINE_IN);
+#ifdef VBOX_WITH_AUDIO_HDA_MIC_IN
+# error "Implement mic-in support!"
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Audio/HDACodec.h b/src/VBox/Devices/Audio/HDACodec.h
new file mode 100644
index 00000000..56aaf756
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDACodec.h
@@ -0,0 +1,148 @@
+/* $Id: HDACodec.h $ */
+/** @file
+ * HDACodec - VBox HD Audio Codec.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_HDACodec_h
+#define VBOX_INCLUDED_SRC_Audio_HDACodec_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/list.h>
+
+#include "AudioMixer.h"
+
+/** The ICH HDA (Intel) controller. */
+typedef struct HDASTATE *PHDASTATE;
+/** The ICH HDA (Intel) codec state. */
+typedef struct HDACODEC *PHDACODEC;
+/** The HDA host driver backend. */
+typedef struct HDADRIVER *PHDADRIVER;
+typedef struct PDMIAUDIOCONNECTOR *PPDMIAUDIOCONNECTOR;
+typedef struct PDMAUDIOGSTSTRMOUT *PPDMAUDIOGSTSTRMOUT;
+typedef struct PDMAUDIOGSTSTRMIN *PPDMAUDIOGSTSTRMIN;
+
+/**
+ * Verb processor method.
+ */
+typedef DECLCALLBACK(int) FNHDACODECVERBPROCESSOR(PHDACODEC pThis, uint32_t cmd, uint64_t *pResp);
+typedef FNHDACODECVERBPROCESSOR *PFNHDACODECVERBPROCESSOR;
+typedef FNHDACODECVERBPROCESSOR **PPFNHDACODECVERBPROCESSOR;
+
+/* PRM 5.3.1 */
+#define CODEC_RESPONSE_UNSOLICITED RT_BIT_64(34)
+
+typedef struct CODECVERB
+{
+ /** Verb. */
+ uint32_t verb;
+ /** Verb mask. */
+ uint32_t mask;
+ /** Function pointer for implementation callback. */
+ PFNHDACODECVERBPROCESSOR pfn;
+ /** Friendly name, for debugging. */
+ const char *pszName;
+} CODECVERB;
+
+union CODECNODE;
+typedef union CODECNODE CODECNODE, *PCODECNODE;
+
+/**
+ * Structure for keeping a HDA codec state.
+ */
+typedef struct HDACODEC
+{
+ uint16_t id;
+ uint16_t u16VendorId;
+ uint16_t u16DeviceId;
+ uint8_t u8BSKU;
+ uint8_t u8AssemblyId;
+ /** List of assigned HDA drivers to this codec.
+ * A driver only can be assigned to one codec at a time. */
+ RTLISTANCHOR lstDrv;
+
+ CODECVERB const *paVerbs;
+ size_t cVerbs;
+
+ PCODECNODE paNodes;
+ /** Pointer to HDA state (controller) this
+ * codec is assigned to. */
+ PHDASTATE pHDAState;
+ bool fInReset;
+
+ const uint8_t cTotalNodes;
+ const uint8_t *au8Ports;
+ const uint8_t *au8Dacs;
+ const uint8_t *au8AdcVols;
+ const uint8_t *au8Adcs;
+ const uint8_t *au8AdcMuxs;
+ const uint8_t *au8Pcbeeps;
+ const uint8_t *au8SpdifIns;
+ const uint8_t *au8SpdifOuts;
+ const uint8_t *au8DigInPins;
+ const uint8_t *au8DigOutPins;
+ const uint8_t *au8Cds;
+ const uint8_t *au8VolKnobs;
+ const uint8_t *au8Reserveds;
+ const uint8_t u8AdcVolsLineIn;
+ const uint8_t u8DacLineOut;
+
+ /** Public codec functions. */
+ DECLR3CALLBACKMEMBER(int, pfnLookup, (PHDACODEC pThis, uint32_t uVerb, uint64_t *puResp));
+ DECLR3CALLBACKMEMBER(void, pfnReset, (PHDACODEC pThis));
+ DECLR3CALLBACKMEMBER(int, pfnNodeReset, (PHDACODEC pThis, uint8_t, PCODECNODE));
+
+ /** Callbacks to the HDA controller, mostly used for multiplexing to the various host backends. */
+ DECLR3CALLBACKMEMBER(int, pfnCbMixerAddStream, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg));
+ DECLR3CALLBACKMEMBER(int, pfnCbMixerRemoveStream, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl));
+ DECLR3CALLBACKMEMBER(int, pfnCbMixerControl, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, uint8_t uSD, uint8_t uChannel));
+ DECLR3CALLBACKMEMBER(int, pfnCbMixerSetVolume, (PHDASTATE pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOVOLUME pVol));
+
+ /** These callbacks are set by codec implementation to answer debugger requests. */
+ DECLR3CALLBACKMEMBER(void, pfnDbgListNodes, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs));
+ DECLR3CALLBACKMEMBER(void, pfnDbgSelector, (PHDACODEC pThis, PCDBGFINFOHLP pHlp, const char *pszArgs));
+} HDACODEC;
+
+int hdaCodecConstruct(PPDMDEVINS pDevIns, PHDACODEC pThis, uint16_t uLUN, PCFGMNODE pCfg);
+void hdaCodecDestruct(PHDACODEC pThis);
+void hdaCodecPowerOff(PHDACODEC pThis);
+int hdaCodecSaveState(PHDACODEC pThis, PSSMHANDLE pSSM);
+int hdaCodecLoadState(PHDACODEC pThis, PSSMHANDLE pSSM, uint32_t uVersion);
+int hdaCodecAddStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl, PPDMAUDIOSTREAMCFG pCfg);
+int hdaCodecRemoveStream(PHDACODEC pThis, PDMAUDIOMIXERCTL enmMixerCtl);
+
+/** 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_SSM_VERSION 7
+/** Saves the current BDLE state. */
+#define HDA_SSM_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. */
+#define HDA_SSM_VERSION_5 5
+/** Since this version the number of MMIO registers can be flexible. */
+#define HDA_SSM_VERSION_4 4
+#define HDA_SSM_VERSION_3 3
+#define HDA_SSM_VERSION_2 2
+#define HDA_SSM_VERSION_1 1
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_HDACodec_h */
+
diff --git a/src/VBox/Devices/Audio/HDAStream.cpp b/src/VBox/Devices/Audio/HDAStream.cpp
new file mode 100644
index 00000000..d4e14b6e
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStream.cpp
@@ -0,0 +1,2009 @@
+/* $Id: HDAStream.cpp $ */
+/** @file
+ * HDAStream.cpp - Stream functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "DrvAudio.h"
+
+#include "DevHDA.h"
+#include "HDAStream.h"
+
+
+#ifdef IN_RING3
+
+/**
+ * Creates an HDA stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to create.
+ * @param pThis HDA state to assign the HDA stream to.
+ * @param u8SD Stream descriptor number to assign.
+ */
+int hdaR3StreamCreate(PHDASTREAM pStream, PHDASTATE pThis, uint8_t u8SD)
+{
+ RT_NOREF(pThis);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ pStream->u8SD = u8SD;
+ pStream->pMixSink = NULL;
+ pStream->pHDAState = pThis;
+ pStream->pTimer = pThis->pTimer[u8SD];
+ AssertPtr(pStream->pTimer);
+
+ pStream->State.fInReset = false;
+ pStream->State.fRunning = false;
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+ RTListInit(&pStream->State.lstDMAHandlers);
+#endif
+
+ int rc = RTCritSectInit(&pStream->CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = hdaR3StreamPeriodCreate(&pStream->State.Period);
+ AssertRCReturn(rc, rc);
+
+ pStream->State.tsLastUpdateNs = 0;
+
+#ifdef DEBUG
+ rc = RTCritSectInit(&pStream->Dbg.CritSect);
+ AssertRCReturn(rc, rc);
+#endif
+
+ pStream->Dbg.Runtime.fEnabled = pThis->Dbg.fEnabled;
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ {
+ char szFile[64];
+
+ if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN)
+ RTStrPrintf(szFile, sizeof(szFile), "hdaStreamWriteSD%RU8", pStream->u8SD);
+ else
+ RTStrPrintf(szFile, sizeof(szFile), "hdaStreamReadSD%RU8", pStream->u8SD);
+
+ char szPath[RTPATH_MAX + 1];
+ int rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile,
+ 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ AssertRC(rc2);
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileStream);
+ AssertRC(rc2);
+
+ if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN)
+ RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawWriteSD%RU8", pStream->u8SD);
+ else
+ RTStrPrintf(szFile, sizeof(szFile), "hdaDMARawReadSD%RU8", pStream->u8SD);
+
+ rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile,
+ 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ AssertRC(rc2);
+
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMARaw);
+ AssertRC(rc2);
+
+ if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN)
+ RTStrPrintf(szFile, sizeof(szFile), "hdaDMAWriteMappedSD%RU8", pStream->u8SD);
+ else
+ RTStrPrintf(szFile, sizeof(szFile), "hdaDMAReadMappedSD%RU8", pStream->u8SD);
+
+ rc2 = DrvAudioHlpFileNameGet(szPath, sizeof(szPath), pThis->Dbg.szOutPath, szFile,
+ 0 /* uInst */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAG_NONE);
+ AssertRC(rc2);
+
+ rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szPath, PDMAUDIOFILE_FLAG_NONE, &pStream->Dbg.Runtime.pFileDMAMapped);
+ AssertRC(rc2);
+
+ /* Delete stale debugging files from a former run. */
+ DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileStream);
+ DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMARaw);
+ DrvAudioHlpFileDelete(pStream->Dbg.Runtime.pFileDMAMapped);
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys an HDA stream.
+ *
+ * @param pStream HDA stream to destroy.
+ */
+void hdaR3StreamDestroy(PHDASTREAM pStream)
+{
+ AssertPtrReturnVoid(pStream);
+
+ LogFlowFunc(("[SD%RU8] Destroying ...\n", pStream->u8SD));
+
+ hdaR3StreamMapDestroy(&pStream->State.Mapping);
+
+ int rc2;
+
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ rc2 = hdaR3StreamAsyncIODestroy(pStream);
+ AssertRC(rc2);
+#endif
+
+ if (RTCritSectIsInitialized(&pStream->CritSect))
+ {
+ rc2 = RTCritSectDelete(&pStream->CritSect);
+ AssertRC(rc2);
+ }
+
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ hdaR3StreamPeriodDestroy(&pStream->State.Period);
+
+#ifdef DEBUG
+ if (RTCritSectIsInitialized(&pStream->Dbg.CritSect))
+ {
+ rc2 = RTCritSectDelete(&pStream->Dbg.CritSect);
+ AssertRC(rc2);
+ }
+#endif
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ {
+ DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileStream);
+ pStream->Dbg.Runtime.pFileStream = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMARaw);
+ pStream->Dbg.Runtime.pFileDMARaw = NULL;
+
+ DrvAudioHlpFileDestroy(pStream->Dbg.Runtime.pFileDMAMapped);
+ pStream->Dbg.Runtime.pFileDMAMapped = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Initializes an HDA stream.
+ *
+ * @returns IPRT status code. VINF_NO_CHANGE if the stream does not need (re-)initialization because the stream's (hardware)
+ * parameters did not change.
+ * @param pStream HDA stream to initialize.
+ * @param uSD SD (stream descriptor) number to assign the HDA stream to.
+ */
+int hdaR3StreamInit(PHDASTREAM pStream, uint8_t uSD)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PHDASTATE pThis = pStream->pHDAState;
+ AssertPtr(pThis);
+
+ 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 uint16_t u16FIFOS = HDA_STREAM_REG(pThis, FIFOS, uSD) + 1;
+ 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
+ || !u16FIFOS
+ || !u16FMT)
+ {
+ LogFunc(("[SD%RU8] Registers not set up yet, skipping (re-)initialization\n", uSD));
+ return VINF_SUCCESS;
+ }
+
+ PDMAUDIOPCMPROPS Props;
+ int rc = hdaR3SDFMTToPCMProps(u16FMT, &Props);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("HDA: Warning: Format 0x%x for stream #%RU8 not supported\n", HDA_STREAM_REG(pThis, FMT, uSD), uSD));
+ return rc;
+ }
+
+ /* Reset (any former) stream map. */
+ hdaR3StreamMapReset(&pStream->State.Mapping);
+
+ /*
+ * Initialize the stream mapping in any case, regardless if
+ * we support surround audio or not. This is needed to handle
+ * the supported channels within a single audio stream, e.g. mono/stereo.
+ *
+ * In other words, the stream mapping *always* knows the real
+ * number of channels in a single audio stream.
+ */
+ rc = hdaR3StreamMapInit(&pStream->State.Mapping, &Props);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Set the stream's timer Hz rate, based on the stream channel count.
+ * Currently this is just a rough guess and we might want to optimize this further.
+ *
+ * In any case, more channels per SDI/SDO means that we have to drive data more frequently.
+ */
+ if (pThis->uTimerHz == HDA_TIMER_HZ_DEFAULT) /* Make sure that we don't have any custom Hz rate set we want to enforce */
+ {
+ if (Props.cChannels >= 5)
+ pStream->State.uTimerHz = 300;
+ else if (Props.cChannels == 4)
+ pStream->State.uTimerHz = 150;
+ else
+ pStream->State.uTimerHz = 100;
+ }
+ else
+ pStream->State.uTimerHz = pThis->uTimerHz;
+
+#ifndef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (Props.cChannels > 2)
+ {
+ /*
+ * When not running with surround support enabled, override the audio channel count
+ * with stereo (2) channels so that we at least can properly work with those.
+ *
+ * Note: This also involves dealing with surround setups the guest might has set up for us.
+ */
+ LogRel2(("HDA: More than stereo (2) channels are not supported (%RU8 requested), "
+ "falling back to stereo channels for stream #%RU8\n", Props.cChannels, uSD));
+ Props.cChannels = 2;
+ Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(Props.cBytes, Props.cChannels);
+ }
+#endif
+
+ /* Did some of the vital / critical parameters change?
+ * If not, we can skip a lot of the (re-)initialization and just (re-)use the existing stuff.
+ * Also, tell the caller so that further actions can be taken. */
+ if ( uSD == pStream->u8SD
+ && u64BDLBase == pStream->u64BDLBase
+ && u16LVI == pStream->u16LVI
+ && u32CBL == pStream->u32CBL
+ && u16FIFOS == pStream->u16FIFOS
+ && u16FMT == pStream->u16FMT)
+ {
+ LogFunc(("[SD%RU8] No format change, skipping (re-)initialization\n", uSD));
+ return VINF_NO_CHANGE;
+ }
+
+ pStream->u8SD = uSD;
+
+ /* Update all register copies so that we later know that something has changed. */
+ pStream->u64BDLBase = u64BDLBase;
+ pStream->u16LVI = u16LVI;
+ pStream->u32CBL = u32CBL;
+ pStream->u16FIFOS = u16FIFOS;
+ pStream->u16FMT = u16FMT;
+
+ PPDMAUDIOSTREAMCFG pCfg = &pStream->State.Cfg;
+ pCfg->Props = Props;
+
+ /* (Re-)Allocate the stream's internal DMA buffer, based on the PCM properties we just got above. */
+ if (pStream->State.pCircBuf)
+ {
+ RTCircBufDestroy(pStream->State.pCircBuf);
+ pStream->State.pCircBuf = NULL;
+ }
+
+ /* By default we allocate an internal buffer of 100ms. */
+ rc = RTCircBufCreate(&pStream->State.pCircBuf,
+ DrvAudioHlpMilliToBytes(100 /* ms */, &pCfg->Props)); /** @todo Make this configurable. */
+ AssertRCReturn(rc, rc);
+
+ /* Set the stream's direction. */
+ pCfg->enmDir = hdaGetDirFromSD(pStream->u8SD);
+
+ /* 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->DestSource.Source = PDMAUDIORECSOURCE_LINE;
+ pCfg->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
+ RTStrCopy(pCfg->szName, sizeof(pCfg->szName), "Line In");
+# endif
+ break;
+
+ case PDMAUDIODIR_OUT:
+ /* Destination(s) will be set in hdaAddStreamOut(),
+ * based on the channels / stream layout. */
+ break;
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ /* Set scheduling hint (if available). */
+ if (pStream->State.uTimerHz)
+ pCfg->Device.uSchedulingHintMs = 1000 /* ms */ / pStream->State.uTimerHz;
+
+ LogFunc(("[SD%RU8] DMA @ 0x%x (%RU32 bytes), LVI=%RU16, FIFOS=%RU16\n",
+ pStream->u8SD, pStream->u64BDLBase, pStream->u32CBL, pStream->u16LVI, pStream->u16FIFOS));
+
+ /* Make sure that mandatory parameters are set up correctly. */
+ AssertStmt(pStream->u32CBL % pStream->State.Mapping.cbFrameSize == 0, rc = VERR_INVALID_PARAMETER);
+ AssertStmt(pStream->u16LVI >= 1, rc = VERR_INVALID_PARAMETER);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure that the chosen Hz rate dividable by the stream's rate. */
+ if (pStream->State.Cfg.Props.uHz % pStream->State.uTimerHz != 0)
+ LogRel(("HDA: Stream timer Hz rate (%RU32) does not fit to stream #%RU8 timing (%RU32)\n",
+ pStream->State.uTimerHz, pStream->u8SD, pStream->State.Cfg.Props.uHz));
+
+ /* Figure out how many transfer fragments we're going to use for this stream. */
+ /** @todo Use a more dynamic fragment size? */
+ Assert(pStream->u16LVI <= UINT8_MAX - 1);
+ uint8_t cFragments = pStream->u16LVI + 1;
+ if (cFragments <= 1)
+ cFragments = 2; /* At least two fragments (BDLEs) must be present. */
+
+ /*
+ * Handle the stream's position adjustment.
+ */
+ uint32_t cfPosAdjust = 0;
+
+ LogFunc(("[SD%RU8] fPosAdjustEnabled=%RTbool, cPosAdjustFrames=%RU16\n",
+ pStream->u8SD, pThis->fPosAdjustEnabled, pThis->cPosAdjustFrames));
+
+ if (pThis->fPosAdjustEnabled) /* Is the position adjustment enabled at all? */
+ {
+ HDABDLE BDLE;
+ RT_ZERO(BDLE);
+
+ int rc2 = hdaR3BDLEFetch(pThis, &BDLE, pStream->u64BDLBase, 0 /* Entry */);
+ AssertRC(rc2);
+
+ /* Note: Do *not* check if this BDLE aligns to the stream's frame size.
+ * It can happen that this isn't the case on some guests, e.g.
+ * on Windows with a 5.1 speaker setup.
+ *
+ * The only thing which counts is that the stream's CBL value
+ * properly aligns to the stream's frame size.
+ */
+
+ /* If no custom set position adjustment is set, apply some
+ * simple heuristics to detect the appropriate position adjustment. */
+ if ( !pThis->cPosAdjustFrames
+ /* Position adjustmenet buffer *must* have the IOC bit set! */
+ && hdaR3BDLENeedsInterrupt(&BDLE))
+ {
+ /** @todo Implement / use a (dynamic) table once this gets more complicated. */
+#ifdef VBOX_WITH_INTEL_HDA
+ /* Intel ICH / PCH: 1 frame. */
+ if (BDLE.Desc.u32BufSize == (uint32_t)(1 * pStream->State.Mapping.cbFrameSize))
+ {
+ cfPosAdjust = 1;
+ }
+ /* Intel Baytrail / Braswell: 32 frames. */
+ else if (BDLE.Desc.u32BufSize == (uint32_t)(32 * pStream->State.Mapping.cbFrameSize))
+ {
+ cfPosAdjust = 32;
+ }
+#endif
+ }
+ else /* Go with the set default. */
+ cfPosAdjust = pThis->cPosAdjustFrames;
+
+ if (cfPosAdjust)
+ {
+ /* Also adjust the number of fragments, as the position adjustment buffer
+ * does not count as an own fragment as such.
+ *
+ * This e.g. can happen on (newer) Ubuntu guests which use
+ * 4 (IOC) + 4408 (IOC) + 4408 (IOC) + 4408 (IOC) + 4404 (= 17632) bytes,
+ * where the first buffer (4) is used as position adjustment.
+ *
+ * Only skip a fragment if the whole buffer fragment is used for
+ * position adjustment.
+ */
+ if ( (cfPosAdjust * pStream->State.Mapping.cbFrameSize) == BDLE.Desc.u32BufSize
+ && cFragments)
+ {
+ cFragments--;
+ }
+
+ /* Initialize position adjustment counter. */
+ pStream->State.cfPosAdjustDefault = cfPosAdjust;
+ pStream->State.cfPosAdjustLeft = pStream->State.cfPosAdjustDefault;
+
+ LogRel2(("HDA: Position adjustment for stream #%RU8 active (%RU32 frames)\n",
+ pStream->u8SD, pStream->State.cfPosAdjustDefault));
+ }
+ }
+
+ LogFunc(("[SD%RU8] cfPosAdjust=%RU32, cFragments=%RU8\n", pStream->u8SD, cfPosAdjust, cFragments));
+
+ /*
+ * Set up data transfer stuff.
+ */
+
+ /* Calculate the fragment size the guest OS expects interrupt delivery at. */
+ pStream->State.cbTransferSize = pStream->u32CBL / cFragments;
+ Assert(pStream->State.cbTransferSize);
+ Assert(pStream->State.cbTransferSize % pStream->State.Mapping.cbFrameSize == 0);
+
+ /* Calculate the bytes we need to transfer to / from the stream's DMA per iteration.
+ * This is bound to the device's Hz rate and thus to the (virtual) timing the device expects. */
+ pStream->State.cbTransferChunk = (pStream->State.Cfg.Props.uHz / pStream->State.uTimerHz) * pStream->State.Mapping.cbFrameSize;
+ Assert(pStream->State.cbTransferChunk);
+ Assert(pStream->State.cbTransferChunk % pStream->State.Mapping.cbFrameSize == 0);
+
+ /* Make sure that the transfer chunk does not exceed the overall transfer size. */
+ if (pStream->State.cbTransferChunk > pStream->State.cbTransferSize)
+ pStream->State.cbTransferChunk = pStream->State.cbTransferSize;
+
+ const uint64_t cTicksPerHz = TMTimerGetFreq(pStream->pTimer) / pStream->State.uTimerHz;
+
+ /* Calculate the timer ticks per byte for this stream. */
+ pStream->State.cTicksPerByte = cTicksPerHz / pStream->State.cbTransferChunk;
+ Assert(pStream->State.cTicksPerByte);
+
+ /* Calculate timer ticks per transfer. */
+ pStream->State.cTransferTicks = pStream->State.cbTransferChunk * pStream->State.cTicksPerByte;
+ Assert(pStream->State.cTransferTicks);
+
+ LogFunc(("[SD%RU8] Timer %uHz (%RU64 ticks per Hz), cTicksPerByte=%RU64, cbTransferChunk=%RU32, cTransferTicks=%RU64, " \
+ "cbTransferSize=%RU32\n",
+ pStream->u8SD, pStream->State.uTimerHz, cTicksPerHz, pStream->State.cTicksPerByte,
+ pStream->State.cbTransferChunk, pStream->State.cTransferTicks, pStream->State.cbTransferSize));
+
+ /* Make sure to also update the stream's DMA counter (based on its current LPIB value). */
+ hdaR3StreamSetPosition(pStream, HDA_STREAM_REG(pThis, LPIB, pStream->u8SD));
+
+#ifdef LOG_ENABLED
+ hdaR3BDLEDumpAll(pThis, pStream->u64BDLBase, pStream->u16LVI + 1);
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("HDA: Initializing stream #%RU8 failed with %Rrc\n", pStream->u8SD, rc));
+
+ return rc;
+}
+
+/**
+ * Resets an HDA stream.
+ *
+ * @param pThis HDA state.
+ * @param pStream HDA stream to reset.
+ * @param uSD Stream descriptor (SD) number to use for this stream.
+ */
+void hdaR3StreamReset(PHDASTATE pThis, PHDASTREAM pStream, uint8_t uSD)
+{
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(uSD < HDA_MAX_STREAMS);
+
+# ifdef VBOX_STRICT
+ AssertReleaseMsg(!pStream->State.fRunning, ("[SD%RU8] Cannot reset stream while in running state\n", uSD));
+# endif
+
+ LogFunc(("[SD%RU8] Reset\n", uSD));
+
+ /*
+ * Set reset state.
+ */
+ Assert(ASMAtomicReadBool(&pStream->State.fInReset) == false); /* No nested calls. */
+ ASMAtomicXchgBool(&pStream->State.fInReset, true);
+
+ /*
+ * Second, initialize the registers.
+ */
+ HDA_STREAM_REG(pThis, STS, uSD) = HDA_SDSTS_FIFORDY;
+ /* 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) = 0x40000 | (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;
+
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+ hdaR3StreamUnregisterDMAHandlers(pThis, pStream);
+#endif
+
+ /* Assign the default mixer sink to the stream. */
+ pStream->pMixSink = hdaR3GetDefaultSink(pThis, uSD);
+
+ /* Reset position adjustment counter. */
+ pStream->State.cfPosAdjustLeft = pStream->State.cfPosAdjustDefault;
+
+ /* Reset transfer stuff. */
+ pStream->State.cbTransferProcessed = 0;
+ pStream->State.cTransferPendingInterrupts = 0;
+ pStream->State.tsTransferLast = 0;
+ pStream->State.tsTransferNext = 0;
+
+ /* Initialize other timestamps. */
+ pStream->State.tsLastUpdateNs = 0;
+
+ RT_ZERO(pStream->State.BDLE);
+ pStream->State.uCurBDLE = 0;
+
+ if (pStream->State.pCircBuf)
+ RTCircBufReset(pStream->State.pCircBuf);
+
+ /* Reset the stream's period. */
+ hdaR3StreamPeriodReset(&pStream->State.Period);
+
+#ifdef DEBUG
+ pStream->Dbg.cReadsTotal = 0;
+ pStream->Dbg.cbReadTotal = 0;
+ pStream->Dbg.tsLastReadNs = 0;
+ pStream->Dbg.cWritesTotal = 0;
+ pStream->Dbg.cbWrittenTotal = 0;
+ pStream->Dbg.cWritesHz = 0;
+ pStream->Dbg.cbWrittenHz = 0;
+ pStream->Dbg.tsWriteSlotBegin = 0;
+#endif
+
+ /* Report that we're done resetting this stream. */
+ HDA_STREAM_REG(pThis, CTL, uSD) = 0;
+
+ LogFunc(("[SD%RU8] Reset\n", uSD));
+
+ /* Exit reset mode. */
+ ASMAtomicXchgBool(&pStream->State.fInReset, false);
+}
+
+/**
+ * Enables or disables an HDA audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to enable or disable.
+ * @param fEnable Whether to enable or disble the stream.
+ */
+int hdaR3StreamEnable(PHDASTREAM pStream, bool fEnable)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFunc(("[SD%RU8] fEnable=%RTbool, pMixSink=%p\n", pStream->u8SD, fEnable, pStream->pMixSink));
+
+ int rc = VINF_SUCCESS;
+
+ AUDMIXSINKCMD enmCmd = fEnable
+ ? AUDMIXSINKCMD_ENABLE : AUDMIXSINKCMD_DISABLE;
+
+ /* First, enable or disable the stream and the stream's sink, if any. */
+ if ( pStream->pMixSink
+ && pStream->pMixSink->pMixSink)
+ rc = AudioMixerSinkCtl(pStream->pMixSink->pMixSink, enmCmd);
+
+ if ( RT_SUCCESS(rc)
+ && fEnable
+ && pStream->Dbg.Runtime.fEnabled)
+ {
+ Assert(DrvAudioHlpPCMPropsAreValid(&pStream->State.Cfg.Props));
+
+ if (fEnable)
+ {
+ if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileStream))
+ {
+ int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileStream, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+
+ if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMARaw))
+ {
+ int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMARaw, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+
+ if (!DrvAudioHlpFileIsOpen(pStream->Dbg.Runtime.pFileDMAMapped))
+ {
+ int rc2 = DrvAudioHlpFileOpen(pStream->Dbg.Runtime.pFileDMAMapped, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS,
+ &pStream->State.Cfg.Props);
+ AssertRC(rc2);
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pStream->State.fRunning = fEnable;
+ }
+
+ LogFunc(("[SD%RU8] rc=%Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAM pStream)
+{
+ return HDA_STREAM_REG(pThis, LPIB, pStream->u8SD);
+}
+
+/**
+ * Updates an HDA stream's current read or write buffer position (depending on the stream type) by
+ * updating its associated LPIB register and DMA position buffer (if enabled).
+ *
+ * @param pStream HDA stream to update read / write position for.
+ * @param u32LPIB Absolute position (in bytes) to set current read / write position to.
+ */
+void hdaR3StreamSetPosition(PHDASTREAM pStream, uint32_t u32LPIB)
+{
+ AssertPtrReturnVoid(pStream);
+
+ Log3Func(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n",
+ pStream->u8SD, u32LPIB, pStream->pHDAState->fDMAPosition));
+
+ /* Update LPIB in any case. */
+ HDA_STREAM_REG(pStream->pHDAState, LPIB, pStream->u8SD) = u32LPIB;
+
+ /* Do we need to tell the current DMA position? */
+ if (pStream->pHDAState->fDMAPosition)
+ {
+ int rc2 = PDMDevHlpPCIPhysWrite(pStream->pHDAState->CTX_SUFF(pDevIns),
+ pStream->pHDAState->u64DPBase + (pStream->u8SD * 2 * sizeof(uint32_t)),
+ (void *)&u32LPIB, sizeof(uint32_t));
+ AssertRC(rc2);
+ }
+}
+
+/**
+ * Retrieves the available size of (buffered) audio data (in bytes) of a given HDA stream.
+ *
+ * @returns Available data (in bytes).
+ * @param pStream HDA stream to retrieve size for.
+ */
+uint32_t hdaR3StreamGetUsed(PHDASTREAM pStream)
+{
+ AssertPtrReturn(pStream, 0);
+
+ if (!pStream->State.pCircBuf)
+ return 0;
+
+ return (uint32_t)RTCircBufUsed(pStream->State.pCircBuf);
+}
+
+/**
+ * Retrieves the free size of audio data (in bytes) of a given HDA stream.
+ *
+ * @returns Free data (in bytes).
+ * @param pStream HDA stream to retrieve size for.
+ */
+uint32_t hdaR3StreamGetFree(PHDASTREAM pStream)
+{
+ AssertPtrReturn(pStream, 0);
+
+ if (!pStream->State.pCircBuf)
+ return 0;
+
+ return (uint32_t)RTCircBufFree(pStream->State.pCircBuf);
+}
+
+/**
+ * Returns whether a next transfer for a given stream is scheduled or not.
+ * This takes pending stream interrupts into account as well as the next scheduled
+ * transfer timestamp.
+ *
+ * @returns True if a next transfer is scheduled, false if not.
+ * @param pStream HDA stream to retrieve schedule status for.
+ */
+bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStream)
+{
+ if (pStream)
+ {
+ AssertPtrReturn(pStream->pHDAState, false);
+
+ if (pStream->State.fRunning)
+ {
+ if (pStream->State.cTransferPendingInterrupts)
+ {
+ Log3Func(("[SD%RU8] Scheduled (%RU8 IRQs pending)\n", pStream->u8SD, pStream->State.cTransferPendingInterrupts));
+ return true;
+ }
+
+ const uint64_t tsNow = TMTimerGet(pStream->pTimer);
+ if (pStream->State.tsTransferNext > tsNow)
+ {
+ Log3Func(("[SD%RU8] Scheduled in %RU64\n", pStream->u8SD, pStream->State.tsTransferNext - tsNow));
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns the (virtual) clock timestamp of the next transfer, if any.
+ * Will return 0 if no new transfer is scheduled.
+ *
+ * @returns The (virtual) clock timestamp of the next transfer.
+ * @param pStream HDA stream to retrieve timestamp for.
+ */
+uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStream)
+{
+ return pStream->State.tsTransferNext;
+}
+
+/**
+ * Writes audio data from a mixer sink into an HDA stream's DMA buffer.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to write to.
+ * @param pvBuf Data buffer to write.
+ * If NULL, silence will be written.
+ * @param cbBuf Number of bytes of data buffer to write.
+ * @param pcbWritten Number of bytes written. Optional.
+ */
+int hdaR3StreamWrite(PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ /* pvBuf is optional. */
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ PRTCIRCBUF pCircBuf = pStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+ uint32_t cbLeft = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf));
+
+ while (cbLeft)
+ {
+ void *pvDst;
+ size_t cbDst;
+
+ RTCircBufAcquireWriteBlock(pCircBuf, cbLeft, &pvDst, &cbDst);
+
+ if (cbDst)
+ {
+ if (pvBuf)
+ {
+ memcpy(pvDst, (uint8_t *)pvBuf + cbWrittenTotal, cbDst);
+ }
+ else /* Send silence. */
+ {
+ /** @todo Use a sample spec for "silence" based on the PCM parameters.
+ * For now we ASSUME that silence equals NULLing the data. */
+ RT_BZERO(pvDst, cbDst);
+ }
+
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileStream, pvDst, cbDst, 0 /* fFlags */);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDst);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ Assert(cbLeft >= (uint32_t)cbDst);
+ cbLeft -= (uint32_t)cbDst;
+
+ cbWrittenTotal += (uint32_t)cbDst;
+ }
+
+ Log3Func(("cbWrittenTotal=%RU32\n", cbWrittenTotal));
+
+ if (pcbWritten)
+ *pcbWritten = cbWrittenTotal;
+
+ return rc;
+}
+
+
+/**
+ * Reads audio data from an HDA stream's DMA buffer and writes into a specified mixer sink.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to read audio data from.
+ * @param cbToRead Number of bytes to read.
+ * @param pcbRead Number of bytes read. Optional.
+ */
+int hdaR3StreamRead(PHDASTREAM pStream, uint32_t cbToRead, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertReturn(cbToRead, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ PHDAMIXERSINK pSink = pStream->pMixSink;
+ if (!pSink)
+ {
+ AssertMsgFailed(("[SD%RU8] Can't read from a stream with no sink attached\n", pStream->u8SD));
+
+ if (pcbRead)
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ PRTCIRCBUF pCircBuf = pStream->State.pCircBuf;
+ AssertPtr(pCircBuf);
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbReadTotal = 0;
+ uint32_t cbLeft = RT_MIN(cbToRead, (uint32_t)RTCircBufUsed(pCircBuf));
+
+ while (cbLeft)
+ {
+ void *pvSrc;
+ size_t cbSrc;
+
+ uint32_t cbWritten = 0;
+
+ RTCircBufAcquireReadBlock(pCircBuf, cbLeft, &pvSrc, &cbSrc);
+
+ if (cbSrc)
+ {
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileStream, pvSrc, cbSrc, 0 /* fFlags */);
+
+ rc = AudioMixerSinkWrite(pSink->pMixSink, AUDMIXOP_COPY, pvSrc, (uint32_t)cbSrc, &cbWritten);
+ AssertRC(rc);
+
+ Assert(cbSrc >= cbWritten);
+ Log2Func(("[SD%RU8] %RU32/%zu bytes read\n", pStream->u8SD, cbWritten, cbSrc));
+ }
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbWritten);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ Assert(cbLeft >= cbWritten);
+ cbLeft -= cbWritten;
+
+ cbReadTotal += cbWritten;
+ }
+
+ if (pcbRead)
+ *pcbRead = cbReadTotal;
+
+ return rc;
+}
+
+/**
+ * Transfers data of an HDA stream according to its usage (input / output).
+ *
+ * For an SDO (output) stream this means reading DMA data from the device to
+ * the HDA stream's internal FIFO buffer.
+ *
+ * For an SDI (input) stream this is reading audio data from the HDA stream's
+ * internal FIFO buffer and writing it as DMA data to the device.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to update.
+ * @param cbToProcessMax How much data (in bytes) to process as maximum.
+ */
+int hdaR3StreamTransfer(PHDASTREAM pStream, uint32_t cbToProcessMax)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ hdaR3StreamLock(pStream);
+
+ PHDASTATE pThis = pStream->pHDAState;
+ AssertPtr(pThis);
+
+ PHDASTREAMPERIOD pPeriod = &pStream->State.Period;
+ if (!hdaR3StreamPeriodLock(pPeriod))
+ return VERR_ACCESS_DENIED;
+
+ bool fProceed = true;
+
+ /* Stream not running? */
+ if (!pStream->State.fRunning)
+ {
+ Log3Func(("[SD%RU8] Not running\n", pStream->u8SD));
+ fProceed = false;
+ }
+ else if (HDA_STREAM_REG(pThis, STS, pStream->u8SD) & HDA_SDSTS_BCIS)
+ {
+ Log3Func(("[SD%RU8] BCIS bit set\n", pStream->u8SD));
+ fProceed = false;
+ }
+
+ if (!fProceed)
+ {
+ hdaR3StreamPeriodUnlock(pPeriod);
+ hdaR3StreamUnlock(pStream);
+ return VINF_SUCCESS;
+ }
+
+ const uint64_t tsNow = TMTimerGet(pStream->pTimer);
+
+ if (!pStream->State.tsTransferLast)
+ pStream->State.tsTransferLast = tsNow;
+
+#ifdef DEBUG
+ const int64_t iTimerDelta = tsNow - pStream->State.tsTransferLast;
+ Log3Func(("[SD%RU8] Time now=%RU64, last=%RU64 -> %RI64 ticks delta\n",
+ pStream->u8SD, tsNow, pStream->State.tsTransferLast, iTimerDelta));
+#endif
+
+ pStream->State.tsTransferLast = tsNow;
+
+ /* Sanity checks. */
+ Assert(pStream->u8SD < HDA_MAX_STREAMS);
+ Assert(pStream->u64BDLBase);
+ Assert(pStream->u32CBL);
+ Assert(pStream->u16FIFOS);
+
+ /* State sanity checks. */
+ Assert(ASMAtomicReadBool(&pStream->State.fInReset) == false);
+
+ int rc = VINF_SUCCESS;
+
+ /* Fetch first / next BDL entry. */
+ PHDABDLE pBDLE = &pStream->State.BDLE;
+ if (hdaR3BDLEIsComplete(pBDLE))
+ {
+ rc = hdaR3BDLEFetch(pThis, pBDLE, pStream->u64BDLBase, pStream->State.uCurBDLE);
+ AssertRC(rc);
+ }
+
+ uint32_t cbToProcess = RT_MIN(pStream->State.cbTransferSize - pStream->State.cbTransferProcessed,
+ pStream->State.cbTransferChunk);
+
+ Log3Func(("[SD%RU8] cbToProcess=%RU32, cbToProcessMax=%RU32\n", pStream->u8SD, cbToProcess, cbToProcessMax));
+
+ if (cbToProcess > cbToProcessMax)
+ {
+ LogFunc(("[SD%RU8] Limiting transfer (cbToProcess=%RU32, cbToProcessMax=%RU32)\n",
+ pStream->u8SD, cbToProcess, cbToProcessMax));
+
+ /* Never process more than a stream currently can handle. */
+ cbToProcess = cbToProcessMax;
+ }
+
+ uint32_t cbProcessed = 0;
+ uint32_t cbLeft = cbToProcess;
+
+ uint8_t abChunk[HDA_FIFO_MAX + 1];
+ while (cbLeft)
+ {
+ /* Limit the chunk to the stream's FIFO size and what's left to process. */
+ uint32_t cbChunk = RT_MIN(cbLeft, pStream->u16FIFOS);
+
+ /* Limit the chunk to the remaining data of the current BDLE. */
+ cbChunk = RT_MIN(cbChunk, pBDLE->Desc.u32BufSize - pBDLE->State.u32BufOff);
+
+ /* If there are position adjustment frames left to be processed,
+ * make sure that we process them first as a whole. */
+ if (pStream->State.cfPosAdjustLeft)
+ cbChunk = RT_MIN(cbChunk, uint32_t(pStream->State.cfPosAdjustLeft * pStream->State.Mapping.cbFrameSize));
+
+ Log3Func(("[SD%RU8] cbChunk=%RU32, cPosAdjustFramesLeft=%RU16\n",
+ pStream->u8SD, cbChunk, pStream->State.cfPosAdjustLeft));
+
+ if (!cbChunk)
+ break;
+
+ uint32_t cbDMA = 0;
+ PRTCIRCBUF pCircBuf = pStream->State.pCircBuf;
+
+ if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_IN) /* Input (SDI). */
+ {
+ STAM_PROFILE_START(&pThis->StatIn, a);
+
+ uint32_t cbDMAWritten = 0;
+ uint32_t cbDMAToWrite = cbChunk;
+
+ /** @todo Do we need interleaving streams support here as well?
+ * Never saw anything else besides mono/stereo mics (yet). */
+ while (cbDMAToWrite)
+ {
+ void *pvBuf; size_t cbBuf;
+ RTCircBufAcquireReadBlock(pCircBuf, cbDMAToWrite, &pvBuf, &cbBuf);
+
+ if ( !cbBuf
+ && !RTCircBufUsed(pCircBuf))
+ break;
+
+ memcpy(abChunk + cbDMAWritten, pvBuf, cbBuf);
+
+ RTCircBufReleaseReadBlock(pCircBuf, cbBuf);
+
+ Assert(cbDMAToWrite >= cbBuf);
+ cbDMAToWrite -= (uint32_t)cbBuf;
+ cbDMAWritten += (uint32_t)cbBuf;
+ Assert(cbDMAWritten <= cbChunk);
+ }
+
+ if (cbDMAToWrite)
+ {
+ LogRel2(("HDA: FIFO underflow for stream #%RU8 (%RU32 bytes outstanding)\n", pStream->u8SD, cbDMAToWrite));
+
+ Assert(cbChunk == cbDMAWritten + cbDMAToWrite);
+ memset((uint8_t *)abChunk + cbDMAWritten, 0, cbDMAToWrite);
+ cbDMAWritten = cbChunk;
+ }
+
+ rc = hdaR3DMAWrite(pThis, pStream, abChunk, cbDMAWritten, &cbDMA /* pcbWritten */);
+ if (RT_FAILURE(rc))
+ LogRel(("HDA: Writing to stream #%RU8 DMA failed with %Rrc\n", pStream->u8SD, rc));
+
+ STAM_PROFILE_STOP(&pThis->StatIn, a);
+ }
+ else if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */
+ {
+ STAM_PROFILE_START(&pThis->StatOut, a);
+
+ rc = hdaR3DMARead(pThis, pStream, abChunk, cbChunk, &cbDMA /* pcbRead */);
+ if (RT_SUCCESS(rc))
+ {
+ const uint32_t cbFree = (uint32_t)RTCircBufFree(pCircBuf);
+
+ /*
+ * Most guests don't use different stream frame sizes than
+ * the default one, so save a bit of CPU time and don't go into
+ * the frame extraction code below.
+ *
+ * Only macOS guests need the frame extraction branch below at the moment AFAIK.
+ */
+ if (pStream->State.Mapping.cbFrameSize == HDA_FRAME_SIZE_DEFAULT)
+ {
+ uint32_t cbDMARead = 0;
+ uint32_t cbDMALeft = RT_MIN(cbDMA, cbFree);
+
+ while (cbDMALeft)
+ {
+ void *pvBuf; size_t cbBuf;
+ RTCircBufAcquireWriteBlock(pCircBuf, cbDMALeft, &pvBuf, &cbBuf);
+
+ if (cbBuf)
+ {
+ memcpy(pvBuf, abChunk + cbDMARead, cbBuf);
+ cbDMARead += (uint32_t)cbBuf;
+ cbDMALeft -= (uint32_t)cbBuf;
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbBuf);
+ }
+ }
+ else
+ {
+ /*
+ * The following code extracts the required audio stream (channel) data
+ * of non-interleaved *and* interleaved audio streams.
+ *
+ * We by default only support 2 channels with 16-bit samples (HDA_FRAME_SIZE),
+ * but an HDA audio stream can have interleaved audio data of multiple audio
+ * channels in such a single stream ("AA,AA,AA vs. AA,BB,AA,BB").
+ *
+ * So take this into account by just handling the first channel in such a stream ("A")
+ * and just discard the other channel's data.
+ *
+ * I know, the following code is horribly slow, but seems to work for now.
+ ** @todo Optimize channel data extraction! Use some SSE(3) / intrinsics?
+ */
+ for (unsigned m = 0; m < pStream->State.Mapping.cMappings; m++)
+ {
+ const uint32_t cbFrame = pStream->State.Mapping.cbFrameSize;
+
+ Assert(cbFree >= cbDMA);
+
+ PPDMAUDIOSTREAMMAP pMap = &pStream->State.Mapping.paMappings[m];
+ AssertPtr(pMap);
+
+ Log3Func(("Mapping #%u: Start (cbDMA=%RU32, cbFrame=%RU32, cbOff=%RU32)\n",
+ m, cbDMA, cbFrame, pMap->cbOff));
+
+ uint8_t *pbSrcBuf = abChunk;
+ size_t cbSrcOff = pMap->cbOff;
+ Assert(cbChunk >= cbSrcOff);
+
+ for (unsigned i = 0; i < cbDMA / cbFrame; i++)
+ {
+ void *pvDstBuf; size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbSize, &pvDstBuf, &cbDstBuf);
+
+ Assert(cbDstBuf >= pMap->cbSize);
+
+ if (cbDstBuf)
+ {
+ Log3Func(("Mapping #%u: Frame #%02u: cbSize=%zu, cbFirst=%zu, cbOff=%zu, cbDstBuf=%zu, cbSrcOff=%zu\n",
+ m, i, pMap->cbSize, pMap->cbFirst, pMap->cbOff, cbDstBuf, cbSrcOff));
+
+ memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf);
+
+#if 0 /* Too slow, even for release builds, so disabled it. */
+ if (pStream->Dbg.Runtime.fEnabled)
+ DrvAudioHlpFileWrite(pStream->Dbg.Runtime.pFileDMAMapped, pvDstBuf, cbDstBuf,
+ 0 /* fFlags */);
+#endif
+ Assert(cbSrcOff <= cbDMA);
+ if (cbSrcOff + cbFrame + pMap->cbFirst <= cbDMA)
+ cbSrcOff += cbFrame + pMap->cbFirst;
+
+ Log3Func(("Mapping #%u: Frame #%02u: -> cbSrcOff=%zu\n", m, i, cbSrcOff));
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf);
+ }
+
+ Log3Func(("Mapping #%u: End cbSize=%zu, cbDMA=%RU32, cbSrcOff=%zu\n",
+ m, pMap->cbSize, cbDMA, cbSrcOff));
+
+ Assert(cbSrcOff <= cbDMA);
+
+ const uint32_t cbSrcLeft = cbDMA - (uint32_t)cbSrcOff;
+ if (cbSrcLeft)
+ {
+ Log3Func(("Mapping #%u: cbSrcLeft=%RU32\n", m, cbSrcLeft));
+
+ if (cbSrcLeft >= pMap->cbSize)
+ {
+ void *pvDstBuf; size_t cbDstBuf;
+ RTCircBufAcquireWriteBlock(pCircBuf, pMap->cbSize, &pvDstBuf, &cbDstBuf);
+
+ Assert(cbDstBuf >= pMap->cbSize);
+
+ if (cbDstBuf)
+ {
+ memcpy(pvDstBuf, pbSrcBuf + cbSrcOff, cbDstBuf);
+ }
+
+ RTCircBufReleaseWriteBlock(pCircBuf, cbDstBuf);
+ }
+
+ Assert(pMap->cbFrame >= cbSrcLeft);
+ pMap->cbOff = pMap->cbFrame - cbSrcLeft;
+ }
+ else
+ pMap->cbOff = 0;
+
+ Log3Func(("Mapping #%u finish (cbSrcOff=%zu, cbOff=%zu)\n", m, cbSrcOff, pMap->cbOff));
+ }
+ }
+ }
+ else
+ LogRel(("HDA: Reading from stream #%RU8 DMA failed with %Rrc\n", pStream->u8SD, rc));
+
+ STAM_PROFILE_STOP(&pThis->StatOut, a);
+ }
+
+ else /** @todo Handle duplex streams? */
+ AssertFailed();
+
+ if (cbDMA)
+ {
+ /* We always increment the position of DMA buffer counter because we're always reading
+ * into an intermediate DMA buffer. */
+ pBDLE->State.u32BufOff += (uint32_t)cbDMA;
+ Assert(pBDLE->State.u32BufOff <= pBDLE->Desc.u32BufSize);
+
+ /* Are we done doing the position adjustment?
+ * Only then do the transfer accounting .*/
+ if (pStream->State.cfPosAdjustLeft == 0)
+ {
+ Assert(cbLeft >= cbDMA);
+ cbLeft -= cbDMA;
+
+ cbProcessed += cbDMA;
+ }
+
+ /*
+ * Update the stream's current position.
+ * Do this as accurate and close to the actual data transfer as possible.
+ * All guetsts rely on this, depending on the mechanism they use (LPIB register or DMA counters).
+ */
+ uint32_t cbStreamPos = hdaR3StreamGetPosition(pThis, pStream);
+ if (cbStreamPos == pStream->u32CBL)
+ cbStreamPos = 0;
+
+ hdaR3StreamSetPosition(pStream, cbStreamPos + cbDMA);
+ }
+
+ if (hdaR3BDLEIsComplete(pBDLE))
+ {
+ Log3Func(("[SD%RU8] Complete: %R[bdle]\n", pStream->u8SD, pBDLE));
+
+ /* Does the current BDLE require an interrupt to be sent? */
+ if ( hdaR3BDLENeedsInterrupt(pBDLE)
+ /* Are we done doing the position adjustment?
+ * It can happen that a BDLE which is handled while doing the
+ * position adjustment requires an interrupt on completion (IOC) being set.
+ *
+ * In such a case we need to skip such an interrupt and just move on. */
+ && pStream->State.cfPosAdjustLeft == 0)
+ {
+ /* 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, pStream->u8SD) & HDA_SDCTL_IOCE)
+ {
+ pStream->State.cTransferPendingInterrupts++;
+
+ AssertMsg(pStream->State.cTransferPendingInterrupts <= 32,
+ ("Too many pending interrupts (%RU8) for stream #%RU8\n",
+ pStream->State.cTransferPendingInterrupts, pStream->u8SD));
+ }
+ }
+
+ if (pStream->State.uCurBDLE == pStream->u16LVI)
+ {
+ pStream->State.uCurBDLE = 0;
+ }
+ else
+ pStream->State.uCurBDLE++;
+
+ /* Fetch the next BDLE entry. */
+ hdaR3BDLEFetch(pThis, pBDLE, pStream->u64BDLBase, pStream->State.uCurBDLE);
+ }
+
+ /* Do the position adjustment accounting. */
+ pStream->State.cfPosAdjustLeft -=
+ RT_MIN(pStream->State.cfPosAdjustLeft, cbDMA / pStream->State.Mapping.cbFrameSize);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ Log3Func(("[SD%RU8] cbToProcess=%RU32, cbProcessed=%RU32, cbLeft=%RU32, %R[bdle], rc=%Rrc\n",
+ pStream->u8SD, cbToProcess, cbProcessed, cbLeft, pBDLE, rc));
+
+ /* Sanity. */
+ Assert(cbProcessed == cbToProcess);
+ Assert(cbLeft == 0);
+
+ /* Only do the data accounting if we don't have to do any position
+ * adjustment anymore. */
+ if (pStream->State.cfPosAdjustLeft == 0)
+ {
+ hdaR3StreamPeriodInc(pPeriod, RT_MIN(cbProcessed / pStream->State.Mapping.cbFrameSize,
+ hdaR3StreamPeriodGetRemainingFrames(pPeriod)));
+
+ pStream->State.cbTransferProcessed += cbProcessed;
+ }
+
+ /* Make sure that we never report more stuff processed than initially announced. */
+ if (pStream->State.cbTransferProcessed > pStream->State.cbTransferSize)
+ pStream->State.cbTransferProcessed = pStream->State.cbTransferSize;
+
+ uint32_t cbTransferLeft = pStream->State.cbTransferSize - pStream->State.cbTransferProcessed;
+ bool fTransferComplete = !cbTransferLeft;
+ uint64_t tsTransferNext = 0;
+
+ if (fTransferComplete)
+ {
+ /*
+ * Try updating the wall clock.
+ *
+ * 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!
+ */
+ const bool fWalClkSet = hdaR3WalClkSet(pThis,
+ hdaWalClkGetCurrent(pThis)
+ + hdaR3StreamPeriodFramesToWalClk(pPeriod,
+ pStream->State.cbTransferProcessed
+ / pStream->State.Mapping.cbFrameSize),
+ false /* fForce */);
+ RT_NOREF(fWalClkSet);
+ }
+
+ /* Does the period have any interrupts outstanding? */
+ if (pStream->State.cTransferPendingInterrupts)
+ {
+ Log3Func(("[SD%RU8] Scheduling interrupt\n", pStream->u8SD));
+
+ /*
+ * Set the stream's BCIS bit.
+ *
+ * Note: This only must be done if the whole period is complete, and not if only
+ * one specific BDL entry is complete (if it has the IOC bit set).
+ *
+ * This will otherwise confuses the guest when it 1) deasserts the interrupt,
+ * 2) reads SDSTS (with BCIS set) and then 3) too early reads a (wrong) WALCLK value.
+ *
+ * snd_hda_intel on Linux will tell.
+ */
+ HDA_STREAM_REG(pThis, STS, pStream->u8SD) |= HDA_SDSTS_BCIS;
+
+ /* Trigger an interrupt first and let hdaRegWriteSDSTS() deal with
+ * ending / beginning a period. */
+#ifndef LOG_ENABLED
+ hdaProcessInterrupt(pThis);
+#else
+ hdaProcessInterrupt(pThis, __FUNCTION__);
+#endif
+ }
+ else /* Transfer still in-flight -- schedule the next timing slot. */
+ {
+ uint32_t cbTransferNext = cbTransferLeft;
+
+ /* No data left to transfer anymore or do we have more data left
+ * than we can transfer per timing slot? Clamp. */
+ if ( !cbTransferNext
+ || cbTransferNext > pStream->State.cbTransferChunk)
+ {
+ cbTransferNext = pStream->State.cbTransferChunk;
+ }
+
+ tsTransferNext = tsNow + (cbTransferNext * pStream->State.cTicksPerByte);
+
+ /*
+ * If the current transfer is complete, reset our counter.
+ *
+ * This can happen for examlpe if the guest OS (like macOS) sets up
+ * big BDLEs without IOC bits set (but for the last one) and the
+ * transfer is complete before we reach such a BDL entry.
+ */
+ if (fTransferComplete)
+ pStream->State.cbTransferProcessed = 0;
+ }
+
+ /* If we need to do another transfer, (re-)arm the device timer. */
+ if (tsTransferNext) /* Can be 0 if no next transfer is needed. */
+ {
+ Log3Func(("[SD%RU8] Scheduling timer\n", pStream->u8SD));
+
+ TMTimerUnlock(pStream->pTimer);
+
+ LogFunc(("Timer set SD%RU8\n", pStream->u8SD));
+ hdaR3TimerSet(pStream->pHDAState, pStream, tsTransferNext, false /* fForce */);
+
+ TMTimerLock(pStream->pTimer, VINF_SUCCESS);
+
+ pStream->State.tsTransferNext = tsTransferNext;
+ }
+
+ pStream->State.tsTransferLast = tsNow;
+
+ Log3Func(("[SD%RU8] cbTransferLeft=%RU32 -- %RU32/%RU32\n",
+ pStream->u8SD, cbTransferLeft, pStream->State.cbTransferProcessed, pStream->State.cbTransferSize));
+ Log3Func(("[SD%RU8] fTransferComplete=%RTbool, cTransferPendingInterrupts=%RU8\n",
+ pStream->u8SD, fTransferComplete, pStream->State.cTransferPendingInterrupts));
+ Log3Func(("[SD%RU8] tsNow=%RU64, tsTransferNext=%RU64 (in %RU64 ticks)\n",
+ pStream->u8SD, tsNow, tsTransferNext, tsTransferNext - tsNow));
+
+ hdaR3StreamPeriodUnlock(pPeriod);
+ hdaR3StreamUnlock(pStream);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Updates a HDA stream by doing its required data transfers.
+ * The host sink(s) set the overall pace.
+ *
+ * This routine is called by both, the synchronous and the asynchronous, implementations.
+ *
+ * This routine is called by both, the synchronous and the asynchronous
+ * (VBOX_WITH_AUDIO_HDA_ASYNC_IO), implementations.
+ *
+ * When running synchronously, the device DMA transfers *and* the mixer sink
+ * processing is within the device timer.
+ *
+ * When running asynchronously, only the device DMA transfers are done in the
+ * device timer, whereas the mixer sink processing then is done in the stream's
+ * own async I/O thread. This thread also will call this function
+ * (with fInTimer set to @c false).
+ *
+ * @param pStream HDA stream to update.
+ * @param fInTimer Whether to this function was called from the timer
+ * context or an asynchronous I/O stream thread (if supported).
+ */
+void hdaR3StreamUpdate(PHDASTREAM pStream, bool fInTimer)
+{
+ if (!pStream)
+ return;
+
+ PAUDMIXSINK pSink = NULL;
+ if ( pStream->pMixSink
+ && pStream->pMixSink->pMixSink)
+ {
+ pSink = pStream->pMixSink->pMixSink;
+ }
+
+ if (!AudioMixerSinkIsActive(pSink)) /* No sink available? Bail out. */
+ return;
+
+ int rc2;
+
+ if (hdaGetDirFromSD(pStream->u8SD) == PDMAUDIODIR_OUT) /* Output (SDO). */
+ {
+ bool fDoRead = false; /* Whether to read from the HDA stream or not. */
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ if (fInTimer)
+# endif
+ {
+ const uint32_t cbStreamFree = hdaR3StreamGetFree(pStream);
+ if (cbStreamFree)
+ {
+ /* Do the DMA transfer. */
+ rc2 = hdaR3StreamTransfer(pStream, cbStreamFree);
+ AssertRC(rc2);
+ }
+
+ /* Only read from the HDA stream at the given scheduling rate. */
+ const uint64_t tsNowNs = RTTimeNanoTS();
+ if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS)
+ {
+ fDoRead = true;
+ pStream->State.tsLastUpdateNs = tsNowNs;
+ }
+ }
+
+ Log3Func(("[SD%RU8] fInTimer=%RTbool, fDoRead=%RTbool\n", pStream->u8SD, fInTimer, fDoRead));
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ if (fDoRead)
+ {
+ rc2 = hdaR3StreamAsyncIONotify(pStream);
+ AssertRC(rc2);
+ }
+# endif
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ if (!fInTimer) /* In async I/O thread */
+ {
+# else
+ if (fDoRead)
+ {
+# endif
+ const uint32_t cbSinkWritable = AudioMixerSinkGetWritable(pSink);
+ const uint32_t cbStreamReadable = hdaR3StreamGetUsed(pStream);
+ const uint32_t cbToReadFromStream = RT_MIN(cbStreamReadable, cbSinkWritable);
+
+ Log3Func(("[SD%RU8] cbSinkWritable=%RU32, cbStreamReadable=%RU32\n", pStream->u8SD, cbSinkWritable, cbStreamReadable));
+
+ if (cbToReadFromStream)
+ {
+ /* Read (guest output) data and write it to the stream's sink. */
+ rc2 = hdaR3StreamRead(pStream, cbToReadFromStream, NULL);
+ AssertRC(rc2);
+ }
+
+ /* When running synchronously, update the associated sink here.
+ * Otherwise this will be done in the async I/O thread. */
+ rc2 = AudioMixerSinkUpdate(pSink);
+ AssertRC(rc2);
+ }
+ }
+ else /* Input (SDI). */
+ {
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ if (!fInTimer)
+ {
+# endif
+ rc2 = AudioMixerSinkUpdate(pSink);
+ AssertRC(rc2);
+
+ /* Is the sink ready to be read (host input data) from? If so, by how much? */
+ uint32_t cbSinkReadable = AudioMixerSinkGetReadable(pSink);
+
+ /* How much (guest input) data is available for writing at the moment for the HDA stream? */
+ const uint32_t cbStreamFree = hdaR3StreamGetFree(pStream);
+
+ Log3Func(("[SD%RU8] cbSinkReadable=%RU32, cbStreamFree=%RU32\n", pStream->u8SD, cbSinkReadable, cbStreamFree));
+
+ /* Do not read more than the HDA stream can hold at the moment.
+ * The host sets the overall pace. */
+ if (cbSinkReadable > cbStreamFree)
+ cbSinkReadable = cbStreamFree;
+
+ if (cbSinkReadable)
+ {
+ uint8_t abFIFO[HDA_FIFO_MAX + 1];
+ while (cbSinkReadable)
+ {
+ uint32_t cbRead;
+ rc2 = AudioMixerSinkRead(pSink, AUDMIXOP_COPY,
+ abFIFO, RT_MIN(cbSinkReadable, (uint32_t)sizeof(abFIFO)), &cbRead);
+ AssertRCBreak(rc2);
+
+ if (!cbRead)
+ {
+ AssertMsgFailed(("Nothing read from sink, even if %RU32 bytes were (still) announced\n", cbSinkReadable));
+ break;
+ }
+
+ /* Write (guest input) data to the stream which was read from stream's sink before. */
+ uint32_t cbWritten;
+ rc2 = hdaR3StreamWrite(pStream, abFIFO, cbRead, &cbWritten);
+ AssertRCBreak(rc2);
+
+ if (!cbWritten)
+ {
+ AssertFailed(); /* Should never happen, as we know how much we can write. */
+ break;
+ }
+
+ Assert(cbSinkReadable >= cbRead);
+ cbSinkReadable -= cbRead;
+ }
+ }
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ }
+ else /* fInTimer */
+ {
+# endif
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ const uint64_t tsNowNs = RTTimeNanoTS();
+ if (tsNowNs - pStream->State.tsLastUpdateNs >= pStream->State.Cfg.Device.uSchedulingHintMs * RT_NS_1MS)
+ {
+ rc2 = hdaR3StreamAsyncIONotify(pStream);
+ AssertRC(rc2);
+
+ pStream->State.tsLastUpdateNs = tsNowNs;
+ }
+# endif
+ const uint32_t cbStreamUsed = hdaR3StreamGetUsed(pStream);
+ if (cbStreamUsed)
+ {
+ rc2 = hdaR3StreamTransfer(pStream, cbStreamUsed);
+ AssertRC(rc2);
+ }
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ }
+# endif
+ }
+}
+
+/**
+ * Locks an HDA stream for serialized access.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to lock.
+ */
+void hdaR3StreamLock(PHDASTREAM pStream)
+{
+ AssertPtrReturnVoid(pStream);
+ int rc2 = RTCritSectEnter(&pStream->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks a formerly locked HDA stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to unlock.
+ */
+void hdaR3StreamUnlock(PHDASTREAM pStream)
+{
+ AssertPtrReturnVoid(pStream);
+ int rc2 = RTCritSectLeave(&pStream->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Updates an HDA stream's current read or write buffer position (depending on the stream type) by
+ * updating its associated LPIB register and DMA position buffer (if enabled).
+ *
+ * @returns Set LPIB value.
+ * @param pStream HDA stream to update read / write position for.
+ * @param u32LPIB New LPIB (position) value to set.
+ */
+uint32_t hdaR3StreamUpdateLPIB(PHDASTREAM pStream, uint32_t u32LPIB)
+{
+ AssertPtrReturn(pStream, 0);
+
+ AssertMsg(u32LPIB <= pStream->u32CBL,
+ ("[SD%RU8] New LPIB (%RU32) exceeds CBL (%RU32)\n", pStream->u8SD, u32LPIB, pStream->u32CBL));
+
+ const PHDASTATE pThis = pStream->pHDAState;
+
+ u32LPIB = RT_MIN(u32LPIB, pStream->u32CBL);
+
+ LogFlowFunc(("[SD%RU8] LPIB=%RU32 (DMA Position Buffer Enabled: %RTbool)\n",
+ pStream->u8SD, u32LPIB, pThis->fDMAPosition));
+
+ /* Update LPIB in any case. */
+ HDA_STREAM_REG(pThis, LPIB, pStream->u8SD) = u32LPIB;
+
+ /* Do we need to tell the current DMA position? */
+ if (pThis->fDMAPosition)
+ {
+ int rc2 = PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns),
+ pThis->u64DPBase + (pStream->u8SD * 2 * sizeof(uint32_t)),
+ (void *)&u32LPIB, sizeof(uint32_t));
+ AssertRC(rc2);
+ }
+
+ return u32LPIB;
+}
+
+# ifdef HDA_USE_DMA_ACCESS_HANDLER
+/**
+ * Registers access handlers for a stream's BDLE DMA accesses.
+ *
+ * @returns true if registration was successful, false if not.
+ * @param pStream HDA stream to register BDLE access handlers for.
+ */
+bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream)
+{
+ /* At least LVI and the BDL base must be set. */
+ if ( !pStream->u16LVI
+ || !pStream->u64BDLBase)
+ {
+ return false;
+ }
+
+ hdaR3StreamUnregisterDMAHandlers(pStream);
+
+ LogFunc(("Registering ...\n"));
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Create BDLE ranges.
+ */
+
+ struct BDLERANGE
+ {
+ RTGCPHYS uAddr;
+ uint32_t uSize;
+ } arrRanges[16]; /** @todo Use a define. */
+
+ size_t cRanges = 0;
+
+ for (uint16_t i = 0; i < pStream->u16LVI + 1; i++)
+ {
+ HDABDLE BDLE;
+ rc = hdaR3BDLEFetch(pThis, &BDLE, pStream->u64BDLBase, i /* Index */);
+ if (RT_FAILURE(rc))
+ break;
+
+ bool fAddRange = true;
+ BDLERANGE *pRange;
+
+ if (cRanges)
+ {
+ pRange = &arrRanges[cRanges - 1];
+
+ /* Is the current range a direct neighbor of the current BLDE? */
+ if ((pRange->uAddr + pRange->uSize) == BDLE.Desc.u64BufAddr)
+ {
+ /* Expand the current range by the current BDLE's size. */
+ pRange->uSize += BDLE.Desc.u32BufSize;
+
+ /* Adding a new range in this case is not needed anymore. */
+ fAddRange = false;
+
+ LogFunc(("Expanding range %zu by %RU32 (%RU32 total now)\n", cRanges - 1, BDLE.Desc.u32BufSize, pRange->uSize));
+ }
+ }
+
+ /* Do we need to add a new range? */
+ if ( fAddRange
+ && cRanges < RT_ELEMENTS(arrRanges))
+ {
+ pRange = &arrRanges[cRanges];
+
+ pRange->uAddr = BDLE.Desc.u64BufAddr;
+ pRange->uSize = BDLE.Desc.u32BufSize;
+
+ LogFunc(("Adding range %zu - 0x%x (%RU32)\n", cRanges, pRange->uAddr, pRange->uSize));
+
+ cRanges++;
+ }
+ }
+
+ LogFunc(("%zu ranges total\n", cRanges));
+
+ /*
+ * Register all ranges as DMA access handlers.
+ */
+
+ for (size_t i = 0; i < cRanges; i++)
+ {
+ BDLERANGE *pRange = &arrRanges[i];
+
+ PHDADMAACCESSHANDLER pHandler = (PHDADMAACCESSHANDLER)RTMemAllocZ(sizeof(HDADMAACCESSHANDLER));
+ if (!pHandler)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ RTListAppend(&pStream->State.lstDMAHandlers, &pHandler->Node);
+
+ pHandler->pStream = pStream; /* Save a back reference to the owner. */
+
+ char szDesc[32];
+ RTStrPrintf(szDesc, sizeof(szDesc), "HDA[SD%RU8 - RANGE%02zu]", pStream->u8SD, i);
+
+ int rc2 = PGMR3HandlerPhysicalTypeRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3), PGMPHYSHANDLERKIND_WRITE,
+ hdaDMAAccessHandler,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ szDesc, &pHandler->hAccessHandlerType);
+ AssertRCBreak(rc2);
+
+ pHandler->BDLEAddr = pRange->uAddr;
+ pHandler->BDLESize = pRange->uSize;
+
+ /* Get first and last pages of the BDLE range. */
+ RTGCPHYS pgFirst = pRange->uAddr & ~PAGE_OFFSET_MASK;
+ RTGCPHYS pgLast = RT_ALIGN(pgFirst + pRange->uSize, PAGE_SIZE);
+
+ /* Calculate the region size (in pages). */
+ RTGCPHYS regionSize = RT_ALIGN(pgLast - pgFirst, PAGE_SIZE);
+
+ pHandler->GCPhysFirst = pgFirst;
+ pHandler->GCPhysLast = pHandler->GCPhysFirst + (regionSize - 1);
+
+ LogFunc(("\tRegistering region '%s': 0x%x - 0x%x (region size: %zu)\n",
+ szDesc, pHandler->GCPhysFirst, pHandler->GCPhysLast, regionSize));
+ LogFunc(("\tBDLE @ 0x%x - 0x%x (%RU32)\n",
+ pHandler->BDLEAddr, pHandler->BDLEAddr + pHandler->BDLESize, pHandler->BDLESize));
+
+ rc2 = PGMHandlerPhysicalRegister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3),
+ pHandler->GCPhysFirst, pHandler->GCPhysLast,
+ pHandler->hAccessHandlerType, pHandler, NIL_RTR0PTR, NIL_RTRCPTR,
+ szDesc);
+ AssertRCBreak(rc2);
+
+ pHandler->fRegistered = true;
+ }
+
+ LogFunc(("Registration ended with rc=%Rrc\n", rc));
+
+ return RT_SUCCESS(rc);
+}
+
+/**
+ * Unregisters access handlers of a stream's BDLEs.
+ *
+ * @param pStream HDA stream to unregister BDLE access handlers for.
+ */
+void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream)
+{
+ LogFunc(("\n"));
+
+ PHDADMAACCESSHANDLER pHandler, pHandlerNext;
+ RTListForEachSafe(&pStream->State.lstDMAHandlers, pHandler, pHandlerNext, HDADMAACCESSHANDLER, Node)
+ {
+ if (!pHandler->fRegistered) /* Handler not registered? Skip. */
+ continue;
+
+ LogFunc(("Unregistering 0x%x - 0x%x (%zu)\n",
+ pHandler->GCPhysFirst, pHandler->GCPhysLast, pHandler->GCPhysLast - pHandler->GCPhysFirst));
+
+ int rc2 = PGMHandlerPhysicalDeregister(PDMDevHlpGetVM(pStream->pHDAState->pDevInsR3),
+ pHandler->GCPhysFirst);
+ AssertRC(rc2);
+
+ RTListNodeRemove(&pHandler->Node);
+
+ RTMemFree(pHandler);
+ pHandler = NULL;
+ }
+
+ Assert(RTListIsEmpty(&pStream->State.lstDMAHandlers));
+}
+# endif /* HDA_USE_DMA_ACCESS_HANDLER */
+
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+/**
+ * Asynchronous I/O thread for a HDA stream.
+ * This will do the heavy lifting work for us as soon as it's getting notified by another thread.
+ *
+ * @returns IPRT status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser User argument. Must be of type PHDASTREAMTHREADCTX.
+ */
+DECLCALLBACK(int) hdaR3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PHDASTREAMTHREADCTX pCtx = (PHDASTREAMTHREADCTX)pvUser;
+ AssertPtr(pCtx);
+
+ PHDASTREAM pStream = pCtx->pStream;
+ AssertPtr(pStream);
+
+ PHDASTREAMSTATEAIO pAIO = &pCtx->pStream->State.AIO;
+
+ ASMAtomicXchgBool(&pAIO->fStarted, true);
+
+ RTThreadUserSignal(hThreadSelf);
+
+ LogFunc(("[SD%RU8] Started\n", pStream->u8SD));
+
+ for (;;)
+ {
+ int rc2 = RTSemEventWait(pAIO->Event, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc2))
+ break;
+
+ if (ASMAtomicReadBool(&pAIO->fShutdown))
+ break;
+
+ rc2 = RTCritSectEnter(&pAIO->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ if (!pAIO->fEnabled)
+ {
+ RTCritSectLeave(&pAIO->CritSect);
+ continue;
+ }
+
+ hdaR3StreamUpdate(pStream, false /* fInTimer */);
+
+ int rc3 = RTCritSectLeave(&pAIO->CritSect);
+ AssertRC(rc3);
+ }
+
+ AssertRC(rc2);
+ }
+
+ LogFunc(("[SD%RU8] Ended\n", pStream->u8SD));
+
+ ASMAtomicXchgBool(&pAIO->fStarted, false);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates the async I/O thread for a specific HDA audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA audio stream to create the async I/O thread for.
+ */
+int hdaR3StreamAsyncIOCreate(PHDASTREAM pStream)
+{
+ PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ int rc;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ {
+ pAIO->fShutdown = false;
+ pAIO->fEnabled = true; /* Enabled by default. */
+
+ rc = RTSemEventCreate(&pAIO->Event);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pAIO->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ HDASTREAMTHREADCTX Ctx = { pStream->pHDAState, pStream };
+
+ char szThreadName[64];
+ RTStrPrintf2(szThreadName, sizeof(szThreadName), "hdaAIO%RU8", pStream->u8SD);
+
+ rc = RTThreadCreate(&pAIO->Thread, hdaR3StreamAsyncIOThread, &Ctx,
+ 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, szThreadName);
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(pAIO->Thread, 10 * 1000 /* 10s timeout */);
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Destroys the async I/O thread of a specific HDA audio stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA audio stream to destroy the async I/O thread for.
+ */
+int hdaR3StreamAsyncIODestroy(PHDASTREAM pStream)
+{
+ PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return VINF_SUCCESS;
+
+ ASMAtomicWriteBool(&pAIO->fShutdown, true);
+
+ int rc = hdaR3StreamAsyncIONotify(pStream);
+ AssertRC(rc);
+
+ int rcThread;
+ rc = RTThreadWait(pAIO->Thread, 30 * 1000 /* 30s timeout */, &rcThread);
+ LogFunc(("Async I/O thread ended with %Rrc (%Rrc)\n", rc, rcThread));
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectDelete(&pAIO->CritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventDestroy(pAIO->Event);
+ AssertRC(rc);
+
+ pAIO->fStarted = false;
+ pAIO->fShutdown = false;
+ pAIO->fEnabled = false;
+ }
+
+ LogFunc(("[SD%RU8] Returning %Rrc\n", pStream->u8SD, rc));
+ return rc;
+}
+
+/**
+ * Lets the stream's async I/O thread know that there is some data to process.
+ *
+ * @returns IPRT status code.
+ * @param pStream HDA stream to notify async I/O thread for.
+ */
+int hdaR3StreamAsyncIONotify(PHDASTREAM pStream)
+{
+ return RTSemEventSignal(pStream->State.AIO.Event);
+}
+
+/**
+ * Locks the async I/O thread of a specific HDA audio stream.
+ *
+ * @param pStream HDA stream to lock async I/O thread for.
+ */
+void hdaR3StreamAsyncIOLock(PHDASTREAM pStream)
+{
+ PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return;
+
+ int rc2 = RTCritSectEnter(&pAIO->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks the async I/O thread of a specific HDA audio stream.
+ *
+ * @param pStream HDA stream to unlock async I/O thread for.
+ */
+void hdaR3StreamAsyncIOUnlock(PHDASTREAM pStream)
+{
+ PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO;
+
+ if (!ASMAtomicReadBool(&pAIO->fStarted))
+ return;
+
+ int rc2 = RTCritSectLeave(&pAIO->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Enables (resumes) or disables (pauses) the async I/O thread.
+ *
+ * @param pStream HDA stream to enable/disable async I/O thread for.
+ * @param fEnable Whether to enable or disable the I/O thread.
+ *
+ * @remarks Does not do locking.
+ */
+void hdaR3StreamAsyncIOEnable(PHDASTREAM pStream, bool fEnable)
+{
+ PHDASTREAMSTATEAIO pAIO = &pStream->State.AIO;
+ ASMAtomicXchgBool(&pAIO->fEnabled, fEnable);
+}
+# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */
+
+#endif /* IN_RING3 */
+
diff --git a/src/VBox/Devices/Audio/HDAStream.h b/src/VBox/Devices/Audio/HDAStream.h
new file mode 100644
index 00000000..92cefcdf
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStream.h
@@ -0,0 +1,309 @@
+/* $Id: HDAStream.h $ */
+/** @file
+ * HDAStream.h - Stream functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_HDAStream_h
+#define VBOX_INCLUDED_SRC_Audio_HDAStream_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "DevHDACommon.h"
+
+#include "HDAStreamMap.h"
+#include "HDAStreamPeriod.h"
+
+
+typedef struct HDAMIXERSINK *PHDAMIXERSINK;
+
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+/**
+ * Structure keeping the HDA stream's state for asynchronous I/O.
+ */
+typedef struct HDASTREAMSTATEAIO
+{
+ /** Thread handle for the actual I/O thread. */
+ RTTHREAD Thread;
+ /** Event for letting the thread know there is some data to process. */
+ RTSEMEVENT Event;
+ /** Critical section for synchronizing access. */
+ RTCRITSECT CritSect;
+ /** Started indicator. */
+ volatile bool fStarted;
+ /** Shutdown indicator. */
+ volatile bool fShutdown;
+ /** Whether the thread should do any data processing or not. */
+ volatile bool fEnabled;
+ uint32_t Padding1;
+} HDASTREAMSTATEAIO, *PHDASTREAMSTATEAIO;
+#endif
+
+/**
+ * Structure containing HDA stream debug stuff, configurable at runtime.
+ */
+typedef struct HDASTREAMDBGINFORT
+{
+ /** 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(PPDMAUDIOFILE) 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(PPDMAUDIOFILE) pFileDMARaw;
+ /** File for dumping mapped (that is, extracted) DMA reads / writes. */
+ R3PTRTYPE(PPDMAUDIOFILE) pFileDMAMapped;
+} HDASTREAMDBGINFORT, *PHDASTREAMDBGINFORT;
+
+/**
+ * Structure containing HDA stream debug information.
+ */
+typedef struct HDASTREAMDBGINFO
+{
+#ifdef DEBUG
+ /** Critical section to serialize access if needed. */
+ RTCRITSECT CritSect;
+ uint32_t Padding0[2];
+ /** Number of total read accesses. */
+ uint64_t cReadsTotal;
+ /** Number of total DMA bytes read. */
+ uint64_t cbReadTotal;
+ /** Timestamp (in ns) of last read access. */
+ uint64_t tsLastReadNs;
+ /** Number of total write accesses. */
+ uint64_t cWritesTotal;
+ /** Number of total DMA bytes written. */
+ uint64_t cbWrittenTotal;
+ /** Number of total write accesses since last iteration (Hz). */
+ uint64_t cWritesHz;
+ /** Number of total DMA bytes written since last iteration (Hz). */
+ uint64_t cbWrittenHz;
+ /** Timestamp (in ns) of beginning a new write slot. */
+ uint64_t tsWriteSlotBegin;
+ /** Number of current silence samples in a (consecutive) row. */
+ uint64_t csSilence;
+ /** Number of silent samples in a row to consider an audio block as audio gap (silence). */
+ uint64_t cSilenceThreshold;
+ /** How many bytes to skip in an audio stream before detecting silence.
+ * (useful for intros and silence at the beginning of a song). */
+ uint64_t cbSilenceReadMin;
+#endif
+ /** Runtime debug info. */
+ HDASTREAMDBGINFORT Runtime;
+} HDASTREAMDBGINFO ,*PHDASTREAMDBGINFO;
+
+/**
+ * Internal state of a HDA stream.
+ */
+typedef struct HDASTREAMSTATE
+{
+ /** Current BDLE to use. Wraps around to 0 if
+ * maximum (cBDLE) is reached. */
+ uint16_t uCurBDLE;
+ /** 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;
+ /** Unused, padding. */
+ uint8_t Padding0[4];
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+ /** Asynchronous I/O state members. */
+ HDASTREAMSTATEAIO AIO;
+#endif
+ /** This stream's data mapping. */
+ HDASTREAMMAP Mapping;
+ /** Current BDLE (Buffer Descriptor List Entry). */
+ HDABDLE BDLE;
+ /** Circular buffer (FIFO) for holding DMA'ed data. */
+ R3PTRTYPE(PRTCIRCBUF) pCircBuf;
+#if HC_ARCH_BITS == 32
+ RTR3PTR Padding1;
+#endif
+ /** 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;
+ /** Total transfer size (in bytes) of a transfer period. */
+ uint32_t cbTransferSize;
+ /** Transfer chunk size (in bytes) of a transfer period. */
+ uint32_t cbTransferChunk;
+ /** How many bytes already have been processed in within
+ * the current transfer period. */
+ uint32_t cbTransferProcessed;
+ /** How many interrupts are pending due to
+ * BDLE interrupt-on-completion (IOC) bits set. */
+ uint8_t cTransferPendingInterrupts;
+ uint8_t Padding2[2];
+ /** 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 hdaR3StreamInit(). */
+ uint16_t uTimerHz;
+ /** Number of audio data frames for the position adjustment.
+ * 0 if no position adjustment is needed. */
+ uint16_t cfPosAdjustDefault;
+ /** How many audio data frames are left to be processed
+ * for the position adjustment handling.
+ *
+ * 0 if position adjustment handling is done or inactive. */
+ uint16_t cfPosAdjustLeft;
+ /** (Virtual) clock ticks per byte. */
+ uint64_t cTicksPerByte;
+ /** (Virtual) clock ticks per transfer. */
+ uint64_t cTransferTicks;
+ /** The stream's period. Need for timing. */
+ HDASTREAMPERIOD Period;
+ /** The stream's current configuration.
+ * Should match SDFMT. */
+ PDMAUDIOSTREAMCFG Cfg;
+ uint32_t Padding4;
+#ifdef HDA_USE_DMA_ACCESS_HANDLER
+ /** List of DMA handlers. */
+ RTLISTANCHORR3 lstDMAHandlers;
+#endif
+ /** Timestamp (in ns) of last stream update. */
+ uint64_t tsLastUpdateNs;
+} HDASTREAMSTATE;
+AssertCompileSizeAlignment(HDASTREAMSTATE, 8);
+typedef HDASTREAMSTATE *PHDASTREAMSTATE;
+
+/**
+ * Structure for keeping a HDA stream (SDI / SDO).
+ *
+ * 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).
+ *
+ * How a specific SDI/SDO is mapped to our internal audio streams relies on the
+ * stream channel mappings.
+ *
+ * Contains only register values which do *not* change until a
+ * stream reset occurs.
+ */
+typedef struct HDASTREAM
+{
+ /** Stream descriptor number (SDn). */
+ uint8_t u8SD;
+ /** Current channel index.
+ * For a stereo stream, this is u8Channel + 1. */
+ uint8_t u8Channel;
+ uint8_t Padding0[6];
+ /** DMA base address (SDnBDPU - SDnBDPL).
+ * Will be updated in hdaR3StreamInit(). */
+ uint64_t u64BDLBase;
+ /** Cyclic Buffer Length (SDnCBL).
+ * Represents the size of the ring buffer.
+ * Will be updated in hdaR3StreamInit(). */
+ uint32_t u32CBL;
+ /** Format (SDnFMT).
+ * Will be updated in hdaR3StreamInit(). */
+ uint16_t u16FMT;
+ /** FIFO Size (FIFOS).
+ * Maximum number of bytes that may have been DMA'd into
+ * memory but not yet transmitted on the link.
+ *
+ * Will be updated in hdaR3StreamInit(). */
+ uint16_t u16FIFOS;
+ /** FIFO Watermark. */
+ uint16_t u16FIFOW;
+ /** Last Valid Index (SDnLVI).
+ * Will be updated in hdaR3StreamInit(). */
+ uint16_t u16LVI;
+ uint16_t Padding1[2];
+ /** Pointer to the HDA state this stream is attached to. */
+ R3PTRTYPE(PHDASTATE) pHDAState;
+ /** Pointer to HDA sink this stream is attached to. */
+ R3PTRTYPE(PHDAMIXERSINK) pMixSink;
+ /** The stream'S critical section to serialize access. */
+ RTCRITSECT CritSect;
+ /** Pointer to the stream's timer. */
+ PTMTIMERR3 pTimer;
+ /** Internal state of this stream. */
+ HDASTREAMSTATE State;
+ /** Debug information. */
+ HDASTREAMDBGINFO Dbg;
+} HDASTREAM, *PHDASTREAM;
+
+#ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+/**
+ * Structure for keeping a HDA stream thread context.
+ */
+typedef struct HDASTREAMTHREADCTX
+{
+ PHDASTATE pThis;
+ PHDASTREAM pStream;
+} HDASTREAMTHREADCTX, *PHDASTREAMTHREADCTX;
+#endif
+
+#ifdef IN_RING3
+
+/** @name Stream functions.
+ * @{
+ */
+int hdaR3StreamCreate(PHDASTREAM pStream, PHDASTATE pThis, uint8_t u8SD);
+void hdaR3StreamDestroy(PHDASTREAM pStream);
+int hdaR3StreamInit(PHDASTREAM pStream, uint8_t uSD);
+void hdaR3StreamReset(PHDASTATE pThis, PHDASTREAM pStream, uint8_t uSD);
+int hdaR3StreamEnable(PHDASTREAM pStream, bool fEnable);
+uint32_t hdaR3StreamGetPosition(PHDASTATE pThis, PHDASTREAM pStream);
+void hdaR3StreamSetPosition(PHDASTREAM pStream, uint32_t u32LPIB);
+uint32_t hdaR3StreamGetFree(PHDASTREAM pStream);
+uint32_t hdaR3StreamGetUsed(PHDASTREAM pStream);
+bool hdaR3StreamTransferIsScheduled(PHDASTREAM pStream);
+uint64_t hdaR3StreamTransferGetNext(PHDASTREAM pStream);
+int hdaR3StreamTransfer(PHDASTREAM pStream, uint32_t cbToProcessMax);
+void hdaR3StreamLock(PHDASTREAM pStream);
+void hdaR3StreamUnlock(PHDASTREAM pStream);
+int hdaR3StreamRead(PHDASTREAM pStream, uint32_t cbToRead, uint32_t *pcbRead);
+int hdaR3StreamWrite(PHDASTREAM pStream, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten);
+void hdaR3StreamUpdate(PHDASTREAM pStream, bool fAsync);
+# ifdef HDA_USE_DMA_ACCESS_HANDLER
+bool hdaR3StreamRegisterDMAHandlers(PHDASTREAM pStream);
+void hdaR3StreamUnregisterDMAHandlers(PHDASTREAM pStream);
+# endif /* HDA_USE_DMA_ACCESS_HANDLER */
+/** @} */
+
+/** @name Timer functions.
+ * @{
+ */
+DECLCALLBACK(void) hdaR3StreamTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
+/** @} */
+
+
+/** @name Async I/O stream functions.
+ * @{
+ */
+# ifdef VBOX_WITH_AUDIO_HDA_ASYNC_IO
+DECLCALLBACK(int) hdaR3StreamAsyncIOThread(RTTHREAD hThreadSelf, void *pvUser);
+int hdaR3StreamAsyncIOCreate(PHDASTREAM pStream);
+int hdaR3StreamAsyncIODestroy(PHDASTREAM pStream);
+int hdaR3StreamAsyncIONotify(PHDASTREAM pStream);
+void hdaR3StreamAsyncIOLock(PHDASTREAM pStream);
+void hdaR3StreamAsyncIOUnlock(PHDASTREAM pStream);
+void hdaR3StreamAsyncIOEnable(PHDASTREAM pStream, bool fEnable);
+# endif /* VBOX_WITH_AUDIO_HDA_ASYNC_IO */
+/** @} */
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStream_h */
+
diff --git a/src/VBox/Devices/Audio/HDAStreamChannel.cpp b/src/VBox/Devices/Audio/HDAStreamChannel.cpp
new file mode 100644
index 00000000..3e2d254e
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamChannel.cpp
@@ -0,0 +1,101 @@
+/* $Id: HDAStreamChannel.cpp $ */
+/** @file
+ * HDAStreamChannel.cpp - Stream channel functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "HDAStreamChannel.h"
+
+/**
+ * Initializes a stream channel data structure.
+ *
+ * @returns IPRT status code.
+ * @param pChanData Channel data to initialize.
+ * @param fFlags
+ */
+int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags)
+{
+ int rc = RTCircBufCreate(&pChanData->pCircBuf, 256); /** @todo Make this configurable? */
+ if (RT_SUCCESS(rc))
+ {
+ pChanData->fFlags = fFlags;
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys a stream channel data structure.
+ *
+ * @param pChanData Channel data to destroy.
+ */
+void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData)
+{
+ if (!pChanData)
+ return;
+
+ if (pChanData->pCircBuf)
+ {
+ RTCircBufDestroy(pChanData->pCircBuf);
+ pChanData->pCircBuf = NULL;
+ }
+
+ pChanData->fFlags = PDMAUDIOSTREAMCHANNELDATA_FLAG_NONE;
+}
+
+/**
+ * Acquires (reads) audio channel data.
+ * Must be released when done with hdaR3StreamChannelReleaseData().
+ *
+ * @returns IPRT status code.
+ * @param pChanData Channel data to acquire audio channel data from.
+ * @param ppvData Where to store the pointer to the acquired data.
+ * @param pcbData Size (in bytes) of acquired data.
+ */
+int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void **ppvData, size_t *pcbData)
+{
+ AssertPtrReturn(pChanData, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppvData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbData, VERR_INVALID_POINTER);
+
+ RTCircBufAcquireReadBlock(pChanData->pCircBuf, 256 /** @todo Make this configurarble? */, ppvData, &pChanData->cbAcq);
+
+ *pcbData = pChanData->cbAcq;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Releases formerly acquired data by hdaR3StreamChannelAcquireData().
+ *
+ * @returns IPRT status code.
+ * @param pChanData Channel data to release formerly acquired data for.
+ */
+int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData)
+{
+ AssertPtrReturn(pChanData, VERR_INVALID_POINTER);
+ RTCircBufReleaseReadBlock(pChanData->pCircBuf, pChanData->cbAcq);
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Devices/Audio/HDAStreamChannel.h b/src/VBox/Devices/Audio/HDAStreamChannel.h
new file mode 100644
index 00000000..3b19a789
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamChannel.h
@@ -0,0 +1,30 @@
+/* $Id: HDAStreamChannel.h $ */
+/** @file
+ * HDAStreamChannel.h - Stream channel functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h
+#define VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+int hdaR3StreamChannelDataInit(PPDMAUDIOSTREAMCHANNELDATA pChanData, uint32_t fFlags);
+void hdaR3StreamChannelDataDestroy(PPDMAUDIOSTREAMCHANNELDATA pChanData);
+int hdaR3StreamChannelAcquireData(PPDMAUDIOSTREAMCHANNELDATA pChanData, void *ppvData, size_t *pcbData);
+int hdaR3StreamChannelReleaseData(PPDMAUDIOSTREAMCHANNELDATA pChanData);
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamChannel_h */
+
diff --git a/src/VBox/Devices/Audio/HDAStreamMap.cpp b/src/VBox/Devices/Audio/HDAStreamMap.cpp
new file mode 100644
index 00000000..27845e39
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamMap.cpp
@@ -0,0 +1,174 @@
+/* $Id: HDAStreamMap.cpp $ */
+/** @file
+ * HDAStreamMap.cpp - Stream mapping functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "DrvAudio.h"
+
+#include "HDAStreamChannel.h"
+#include "HDAStreamMap.h"
+
+#ifdef IN_RING3
+
+static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps);
+
+/**
+ * Initializes a stream mapping structure according to the given PCM properties.
+ *
+ * @return IPRT status code.
+ * @param pMap Pointer to mapping to initialize.
+ * @param pProps Pointer to PCM properties to use for initialization.
+ */
+int hdaR3StreamMapInit(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps)
+{
+ AssertPtrReturn(pMap, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProps, VERR_INVALID_POINTER);
+
+ if (!DrvAudioHlpPCMPropsAreValid(pProps))
+ return VERR_INVALID_PARAMETER;
+
+ hdaR3StreamMapReset(pMap);
+
+ int rc = hdaR3StreamMapSetup(pMap, pProps);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if ( RT_SUCCESS(rc)
+ /* Create circular buffer if not created yet. */
+ && !pMap->pCircBuf)
+ {
+ rc = RTCircBufCreate(&pMap->pCircBuf, _4K); /** @todo Make size configurable? */
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ pMap->cbFrameSize = pProps->cChannels * pProps->cBytes;
+
+ LogFunc(("cChannels=%RU8, cBytes=%RU8 -> cbFrameSize=%RU32\n",
+ pProps->cChannels, pProps->cBytes, pMap->cbFrameSize));
+
+ Assert(pMap->cbFrameSize); /* Frame size must not be 0. */
+
+ pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_INTERLEAVED;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destroys a given stream mapping.
+ *
+ * @param pMap Pointer to mapping to destroy.
+ */
+void hdaR3StreamMapDestroy(PHDASTREAMMAP pMap)
+{
+ hdaR3StreamMapReset(pMap);
+
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ if (pMap->pCircBuf)
+ {
+ RTCircBufDestroy(pMap->pCircBuf);
+ pMap->pCircBuf = NULL;
+ }
+#endif
+}
+
+
+/**
+ * Resets a given stream mapping.
+ *
+ * @param pMap Pointer to mapping to reset.
+ */
+void hdaR3StreamMapReset(PHDASTREAMMAP pMap)
+{
+ AssertPtrReturnVoid(pMap);
+
+ pMap->enmLayout = PDMAUDIOSTREAMLAYOUT_UNKNOWN;
+
+ if (pMap->paMappings)
+ {
+ for (uint8_t i = 0; i < pMap->cMappings; i++)
+ hdaR3StreamChannelDataDestroy(&pMap->paMappings[i].Data);
+
+ RTMemFree(pMap->paMappings);
+ pMap->paMappings = NULL;
+
+ pMap->cMappings = 0;
+ }
+}
+
+
+/**
+ * Sets up a stream mapping according to the given properties / configuration.
+ *
+ * @return VBox status code, or VERR_NOT_SUPPORTED if the channel setup is not supported (yet).
+ * @param pMap Pointer to mapping to set up.
+ * @param pProps PCM audio properties to use for lookup.
+ */
+static int hdaR3StreamMapSetup(PHDASTREAMMAP pMap, PPDMAUDIOPCMPROPS pProps)
+{
+ int rc;
+
+ /** @todo We ASSUME that all channels in a stream ...
+ * - have the same format
+ * - are in a consecutive order with no gaps in between
+ * - have a simple (raw) data layout
+ * - work in a non-striped fashion, e.g. interleaved (only on one SDn, not spread over multiple SDns) */
+ if ( pProps->cChannels == 1 /* Mono */
+ || pProps->cChannels == 2 /* Stereo */
+ || pProps->cChannels == 4 /* Quadrophonic */
+ || pProps->cChannels == 6) /* Surround (5.1) */
+ {
+ /* For now we don't have anything other as mono / stereo channels being covered by the backends.
+ * So just set up one channel covering those and skipping the rest (like configured rear or center/LFE outputs). */
+ pMap->cMappings = 1;
+ pMap->paMappings = (PPDMAUDIOSTREAMMAP)RTMemAlloc(sizeof(PDMAUDIOSTREAMMAP) * pMap->cMappings);
+ if (!pMap->paMappings)
+ return VERR_NO_MEMORY;
+
+ PPDMAUDIOSTREAMMAP pMapLR = &pMap->paMappings[0];
+
+ pMapLR->aID[0] = PDMAUDIOSTREAMCHANNELID_FRONT_LEFT;
+ pMapLR->aID[1] = PDMAUDIOSTREAMCHANNELID_FRONT_RIGHT;
+ pMapLR->cbFrame = pProps->cBytes * pProps->cChannels;
+ pMapLR->cbSize = pProps->cBytes * 2 /* Front left + Front right channels */;
+ pMapLR->cbFirst = 0;
+ pMapLR->cbOff = pMapLR->cbFirst;
+
+ rc = hdaR3StreamChannelDataInit(&pMapLR->Data, PDMAUDIOSTREAMCHANNELDATA_FLAG_NONE);
+ AssertRC(rc);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED; /** @todo r=andy Support more setups. */
+
+ return rc;
+}
+#endif /* IN_RING3 */
+
diff --git a/src/VBox/Devices/Audio/HDAStreamMap.h b/src/VBox/Devices/Audio/HDAStreamMap.h
new file mode 100644
index 00000000..f0e6e62e
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamMap.h
@@ -0,0 +1,61 @@
+/* $Id: HDAStreamMap.h $ */
+/** @file
+ * HDAStreamMap.h - Stream map functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h
+#define VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Structure for keeping an audio stream data mapping.
+ */
+typedef struct HDASTREAMMAP
+{
+ /** The stream's layout. */
+ PDMAUDIOSTREAMLAYOUT enmLayout;
+ uint8_t cbFrameSize;
+ /** Number of mappings in paMappings. */
+ uint8_t cMappings;
+ uint8_t aPadding[2];
+ /** Array of stream mappings.
+ * Note: The mappings *must* be layed out in an increasing order, e.g.
+ * how the data appears in the given data block. */
+ R3PTRTYPE(PPDMAUDIOSTREAMMAP) paMappings;
+#if HC_ARCH_BITS == 32
+ RTR3PTR Padding1;
+#endif
+#ifdef VBOX_WITH_AUDIO_HDA_51_SURROUND
+ /** Circular buffer holding for holding audio data for this mapping. */
+ R3PTRTYPE(PRTCIRCBUF) pCircBuf;
+#endif
+} HDASTREAMMAP;
+AssertCompileSizeAlignment(HDASTREAMMAP, 8);
+typedef HDASTREAMMAP *PHDASTREAMMAP;
+
+/** @name Stream mapping functions.
+ * @{
+ */
+#ifdef IN_RING3
+int hdaR3StreamMapInit(PHDASTREAMMAP pMapping, PPDMAUDIOPCMPROPS pProps);
+void hdaR3StreamMapDestroy(PHDASTREAMMAP pMapping);
+void hdaR3StreamMapReset(PHDASTREAMMAP pMapping);
+#endif /* IN_RING3 */
+/** @} */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamMap_h */
+
diff --git a/src/VBox/Devices/Audio/HDAStreamPeriod.cpp b/src/VBox/Devices/Audio/HDAStreamPeriod.cpp
new file mode 100644
index 00000000..558e8a99
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamPeriod.cpp
@@ -0,0 +1,431 @@
+/* $Id: HDAStreamPeriod.cpp $ */
+/** @file
+ * HDAStreamPeriod.cpp - Stream period functions for HD Audio.
+ *
+ * Utility functions for handling HDA audio stream periods. Stream period
+ * handling is needed in order to keep track of a stream's timing
+ * and processed audio data.
+ *
+ * As the HDA device only has one bit clock (WALCLK) but audio streams can be
+ * processed at certain points in time, these functions can be used to estimate
+ * and schedule the wall clock (WALCLK) for all streams accordingly.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_HDA
+#include <VBox/log.h>
+
+#include <iprt/asm-math.h> /* For ASMMultU64ByU32DivByU32(). */
+
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include "DrvAudio.h"
+#include "HDAStreamPeriod.h"
+
+
+#ifdef IN_RING3 /* entire file currently */
+
+/**
+ * Creates a stream period.
+ *
+ * @return IPRT status code.
+ * @param pPeriod Stream period to initialize.
+ */
+int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod)
+{
+ Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID));
+
+ int rc = RTCritSectInit(&pPeriod->CritSect);
+ AssertRCReturnStmt(rc, pPeriod->fStatus = 0, rc);
+ pPeriod->fStatus = HDASTREAMPERIOD_FLAG_VALID;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a formerly created stream period.
+ *
+ * @param pPeriod Stream period to destroy.
+ */
+void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod)
+{
+ if (pPeriod->fStatus & HDASTREAMPERIOD_FLAG_VALID)
+ {
+ RTCritSectDelete(&pPeriod->CritSect);
+
+ pPeriod->fStatus = HDASTREAMPERIOD_FLAG_NONE;
+ }
+}
+
+/**
+ * Initializes a given stream period with needed parameters.
+ *
+ * @return VBox status code.
+ * @param pPeriod Stream period to (re-)initialize. Must be created with hdaR3StreamPeriodCreate() first.
+ * @param u8SD Stream descriptor (serial data #) number to assign this stream period to.
+ * @param u16LVI The HDA stream's LVI value to use for the period calculation.
+ * @param u32CBL The HDA stream's CBL value to use for the period calculation.
+ * @param pStreamCfg Audio stream configuration to use for this period.
+ */
+int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod,
+ uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg)
+{
+ if ( !u16LVI
+ || !u32CBL
+ || !DrvAudioHlpPCMPropsAreValid(&pStreamCfg->Props))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Linux guests (at least Ubuntu):
+ * 17632 bytes (CBL) / 4 (frame size) = 4408 frames / 4 (LVI) = 1102 frames per period
+ *
+ * Windows guests (Win10 AU):
+ * 3584 bytes (CBL) / 4 (frame size) = 896 frames / 2 (LVI) = 448 frames per period
+ */
+ unsigned cTotalPeriods = u16LVI + 1;
+
+ if (cTotalPeriods <= 1)
+ cTotalPeriods = 2; /* At least two periods *must* be present (LVI >= 1). */
+
+ uint32_t framesToTransfer = (u32CBL / 4 /** @todo Define frame size? */) / cTotalPeriods;
+
+ pPeriod->u8SD = u8SD;
+ pPeriod->u64StartWalClk = 0;
+ pPeriod->u32Hz = pStreamCfg->Props.uHz;
+ pPeriod->u64DurationWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, framesToTransfer);
+ pPeriod->u64ElapsedWalClk = 0;
+ pPeriod->i64DelayWalClk = 0;
+ pPeriod->framesToTransfer = framesToTransfer;
+ pPeriod->framesTransferred = 0;
+ pPeriod->cIntPending = 0;
+
+ Log3Func(("[SD%RU8] %RU64 long, Hz=%RU32, CBL=%RU32, LVI=%RU16 -> %u periods, %RU32 frames each\n",
+ pPeriod->u8SD, pPeriod->u64DurationWalClk, pPeriod->u32Hz, u32CBL, u16LVI,
+ cTotalPeriods, pPeriod->framesToTransfer));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Resets a stream period to its initial state.
+ *
+ * @param pPeriod Stream period to reset.
+ */
+void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod)
+{
+ Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
+
+ if (pPeriod->cIntPending)
+ LogRelMax(50, ("HDA: Warning: %RU8 interrupts for stream #%RU8 still pending -- so a period reset might trigger audio hangs\n",
+ pPeriod->cIntPending, pPeriod->u8SD));
+
+ pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE;
+ pPeriod->u64StartWalClk = 0;
+ pPeriod->u64ElapsedWalClk = 0;
+ pPeriod->framesTransferred = 0;
+ pPeriod->cIntPending = 0;
+# ifdef LOG_ENABLED
+ pPeriod->Dbg.tsStartNs = 0;
+# endif
+}
+
+/**
+ * Begins a new period life span of a given period.
+ *
+ * @return IPRT status code.
+ * @param pPeriod Stream period to begin new life span for.
+ * @param u64WalClk Wall clock (WALCLK) value to set for the period's starting point.
+ */
+int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
+{
+ Assert(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE)); /* No nested calls. */
+
+ pPeriod->fStatus |= HDASTREAMPERIOD_FLAG_ACTIVE;
+ pPeriod->u64StartWalClk = u64WalClk;
+ pPeriod->u64ElapsedWalClk = 0;
+ pPeriod->framesTransferred = 0;
+ pPeriod->cIntPending = 0;
+# ifdef LOG_ENABLED
+ pPeriod->Dbg.tsStartNs = RTTimeNanoTS();
+# endif
+
+ Log3Func(("[SD%RU8] Starting @ %RU64 (%RU64 long)\n", pPeriod->u8SD, pPeriod->u64StartWalClk, pPeriod->u64DurationWalClk));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Ends a formerly begun period life span.
+ *
+ * @param pPeriod Stream period to end life span for.
+ */
+void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod)
+{
+ Log3Func(("[SD%RU8] Took %zuus\n", pPeriod->u8SD, (RTTimeNanoTS() - pPeriod->Dbg.tsStartNs) / 1000));
+
+ if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE))
+ return;
+
+ /* Sanity. */
+ AssertMsg(pPeriod->cIntPending == 0,
+ ("%RU8 interrupts for stream #%RU8 still pending -- so ending a period might trigger audio hangs\n",
+ pPeriod->cIntPending, pPeriod->u8SD));
+ Assert(hdaR3StreamPeriodIsComplete(pPeriod));
+
+ pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE;
+}
+
+/**
+ * Pauses a period. All values remain intact.
+ *
+ * @param pPeriod Stream period to pause.
+ */
+void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod)
+{
+ AssertMsg((pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE), ("Period %p already in inactive state\n", pPeriod));
+
+ pPeriod->fStatus &= ~HDASTREAMPERIOD_FLAG_ACTIVE;
+
+ Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
+}
+
+/**
+ * Resumes a formerly paused period.
+ *
+ * @param pPeriod Stream period to resume.
+ */
+void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod)
+{
+ AssertMsg(!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE), ("Period %p already in active state\n", pPeriod));
+
+ pPeriod->fStatus |= HDASTREAMPERIOD_FLAG_ACTIVE;
+
+ Log3Func(("[SD%RU8]\n", pPeriod->u8SD));
+}
+
+/**
+ * Locks a stream period for serializing access.
+ *
+ * @return true if locking was successful, false if not.
+ * @param pPeriod Stream period to lock.
+ */
+bool hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod)
+{
+ return RT_SUCCESS(RTCritSectEnter(&pPeriod->CritSect));
+}
+
+/**
+ * Unlocks a formerly locked stream period.
+ *
+ * @param pPeriod Stream period to unlock.
+ */
+void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod)
+{
+ int rc2 = RTCritSectLeave(&pPeriod->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Returns the wall clock (WALCLK) value for a given amount of stream period audio frames.
+ *
+ * @return Calculated wall clock value.
+ * @param pPeriod Stream period to calculate wall clock value for.
+ * @param uFrames Number of audio frames to calculate wall clock value for.
+ *
+ * @remark Calculation depends on the given stream period and assumes a 24 MHz wall clock counter (WALCLK).
+ */
+uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames)
+{
+ /* Prevent division by zero. */
+ const uint32_t uHz = pPeriod->u32Hz ? pPeriod->u32Hz : 1;
+
+ /* 24 MHz wall clock (WALCLK): 42ns resolution. */
+ return ASMMultU64ByU32DivByU32(uFrames, 24000000, uHz);
+}
+
+/**
+ * Returns the absolute wall clock (WALCLK) value for the already elapsed time of
+ * a given stream period.
+ *
+ * @return Absolute elapsed time as wall clock (WALCLK) value.
+ * @param pPeriod Stream period to use.
+ */
+uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod)
+{
+ return pPeriod->u64StartWalClk
+ + pPeriod->u64ElapsedWalClk
+ + pPeriod->i64DelayWalClk;
+}
+
+/**
+ * Returns the absolute wall clock (WALCLK) value for the calculated end time of
+ * a given stream period.
+ *
+ * @return Absolute end time as wall clock (WALCLK) value.
+ * @param pPeriod Stream period to use.
+ */
+uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod)
+{
+ return pPeriod->u64StartWalClk + pPeriod->u64DurationWalClk;
+}
+
+/**
+ * Returns the remaining audio frames to process for a given stream period.
+ *
+ * @return Number of remaining audio frames to process. 0 if all were processed.
+ * @param pPeriod Stream period to return value for.
+ */
+uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod)
+{
+ Assert(pPeriod->framesToTransfer >= pPeriod->framesTransferred);
+ return pPeriod->framesToTransfer - pPeriod->framesTransferred;
+}
+
+/**
+ * Tells whether a given stream period has elapsed (time-wise) or not.
+ *
+ * @return true if the stream period has elapsed, false if not.
+ * @param pPeriod Stream period to get status for.
+ */
+bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod)
+{
+ return (pPeriod->u64ElapsedWalClk >= pPeriod->u64DurationWalClk);
+}
+
+/**
+ * Tells whether a given stream period has passed the given absolute wall clock (WALCLK)
+ * time or not
+ *
+ * @return true if the stream period has passed the given time, false if not.
+ * @param pPeriod Stream period to get status for.
+ * @param u64WalClk Absolute wall clock (WALCLK) time to check for.
+ */
+bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk)
+{
+ /* Period not in use? */
+ if (!(pPeriod->fStatus & HDASTREAMPERIOD_FLAG_ACTIVE))
+ return true; /* ... implies that it has passed. */
+
+ if (hdaR3StreamPeriodHasElapsed(pPeriod))
+ return true; /* Period already has elapsed. */
+
+ return (pPeriod->u64StartWalClk + pPeriod->u64ElapsedWalClk) >= u64WalClk;
+}
+
+/**
+ * Tells whether a given stream period has some required interrupts pending or not.
+ *
+ * @return true if period has interrupts pending, false if not.
+ * @param pPeriod Stream period to get status for.
+ */
+bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod)
+{
+ return pPeriod->cIntPending > 0;
+}
+
+/**
+ * Acquires (references) an (pending) interrupt for a given stream period.
+ *
+ * @param pPeriod Stream period to acquire interrupt for.
+ *
+ * @remark This routine does not do any actual interrupt processing; it only
+ * keeps track of the required (pending) interrupts for a stream period.
+ */
+void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod)
+{
+ uint32_t cIntPending = pPeriod->cIntPending;
+ if (cIntPending)
+ {
+ Log3Func(("[SD%RU8] Already pending\n", pPeriod->u8SD));
+ return;
+ }
+
+ pPeriod->cIntPending++;
+
+ Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
+}
+
+/**
+ * Releases (dereferences) a pending interrupt.
+ *
+ * @param pPeriod Stream period to release pending interrupt for.
+ */
+void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod)
+{
+ Assert(pPeriod->cIntPending);
+ pPeriod->cIntPending--;
+
+ Log3Func(("[SD%RU8] %RU32\n", pPeriod->u8SD, pPeriod->cIntPending));
+}
+
+/**
+ * Adds an amount of (processed) audio frames to a given stream period.
+ *
+ * @return IPRT status code.
+ * @param pPeriod Stream period to add audio frames to.
+ * @param framesInc Audio frames to add.
+ */
+void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc)
+{
+ pPeriod->framesTransferred += framesInc;
+ Assert(pPeriod->framesTransferred <= pPeriod->framesToTransfer);
+
+ pPeriod->u64ElapsedWalClk = hdaR3StreamPeriodFramesToWalClk(pPeriod, pPeriod->framesTransferred);
+ Assert(pPeriod->u64ElapsedWalClk <= pPeriod->u64DurationWalClk);
+
+ Log3Func(("[SD%RU8] cbTransferred=%RU32, u64ElapsedWalClk=%RU64\n",
+ pPeriod->u8SD, pPeriod->framesTransferred, pPeriod->u64ElapsedWalClk));
+}
+
+/**
+ * Tells whether a given stream period is considered as complete or not.
+ *
+ * @return true if stream period is complete, false if not.
+ * @param pPeriod Stream period to report status for.
+ *
+ * @remark A stream period is considered complete if it has 1) passed (elapsed) its calculated period time
+ * and 2) processed all required audio frames.
+ */
+bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod)
+{
+ const bool fIsComplete = /* Has the period elapsed time-wise? */
+ hdaR3StreamPeriodHasElapsed(pPeriod)
+ /* All frames transferred? */
+ && pPeriod->framesTransferred >= pPeriod->framesToTransfer;
+# ifdef VBOX_STRICT
+ if (fIsComplete)
+ {
+ Assert(pPeriod->framesTransferred == pPeriod->framesToTransfer);
+ Assert(pPeriod->u64ElapsedWalClk == pPeriod->u64DurationWalClk);
+ }
+# endif
+
+ Log3Func(("[SD%RU8] Period %s - runtime %RU64 / %RU64 (abs @ %RU64, starts @ %RU64, ends @ %RU64), %RU8 IRQs pending\n",
+ pPeriod->u8SD,
+ fIsComplete ? "COMPLETE" : "NOT COMPLETE YET",
+ pPeriod->u64ElapsedWalClk, pPeriod->u64DurationWalClk,
+ hdaR3StreamPeriodGetAbsElapsedWalClk(pPeriod), pPeriod->u64StartWalClk,
+ hdaR3StreamPeriodGetAbsEndWalClk(pPeriod), pPeriod->cIntPending));
+
+ return fIsComplete;
+}
+
+#endif /* IN_RING3 */
+
diff --git a/src/VBox/Devices/Audio/HDAStreamPeriod.h b/src/VBox/Devices/Audio/HDAStreamPeriod.h
new file mode 100644
index 00000000..a782a3bc
--- /dev/null
+++ b/src/VBox/Devices/Audio/HDAStreamPeriod.h
@@ -0,0 +1,114 @@
+/* $Id: HDAStreamPeriod.h $ */
+/** @file
+ * HDAStreamPeriod.h - Stream period functions for HD Audio.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h
+#define VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/critsect.h>
+#ifdef DEBUG
+# include <iprt/time.h>
+#endif
+#include <VBox/log.h> /* LOG_ENABLED */
+
+struct HDASTREAM;
+typedef HDASTREAM *PHDASTREAM;
+
+#ifdef LOG_ENABLED
+/**
+ * Structure for debug information of an HDA stream's period.
+ */
+typedef struct HDASTREAMPERIODDBGINFO
+{
+ /** Host start time (in ns) of the period. */
+ uint64_t tsStartNs;
+} HDASTREAMPERIODDBGINFO, *PHDASTREAMPERIODDBGINFO;
+#endif
+
+/** No flags set. */
+#define HDASTREAMPERIOD_FLAG_NONE 0
+/** The stream period has been initialized and is in a valid state. */
+#define HDASTREAMPERIOD_FLAG_VALID RT_BIT(0)
+/** The stream period is active. */
+#define HDASTREAMPERIOD_FLAG_ACTIVE RT_BIT(1)
+
+/**
+ * Structure for keeping an HDA stream's (time) period.
+ * This is needed in order to keep track of stream timing and interrupt delivery.
+ */
+typedef struct HDASTREAMPERIOD
+{
+ /** Critical section for serializing access. */
+ RTCRITSECT CritSect;
+ /** Associated HDA stream descriptor (SD) number. */
+ uint8_t u8SD;
+ /** The period's status flags. */
+ uint8_t fStatus;
+ /** Number of pending interrupts required for this period. */
+ uint8_t cIntPending;
+ uint8_t bPadding0;
+ /** Hertz (Hz) rate this period runs with. */
+ uint32_t u32Hz;
+ /** Period start time (in wall clock counts). */
+ uint64_t u64StartWalClk;
+ /** Period duration (in wall clock counts). */
+ uint64_t u64DurationWalClk;
+ /** The period's (relative) elapsed time (in wall clock counts). */
+ uint64_t u64ElapsedWalClk;
+ /** Delay (in wall clock counts) for tweaking the period timing. Optional. */
+ int64_t i64DelayWalClk;
+ /** Number of audio frames to transfer for this period. */
+ uint32_t framesToTransfer;
+ /** Number of audio frames already transfered. */
+ uint32_t framesTransferred;
+#ifdef LOG_ENABLED
+ /** Debugging information. */
+ HDASTREAMPERIODDBGINFO Dbg;
+#endif
+} HDASTREAMPERIOD;
+AssertCompileSizeAlignment(HDASTREAMPERIOD, 8);
+/** Pointer to a HDA stream's time period keeper. */
+typedef HDASTREAMPERIOD *PHDASTREAMPERIOD;
+
+#ifdef IN_RING3
+int hdaR3StreamPeriodCreate(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodDestroy(PHDASTREAMPERIOD pPeriod);
+int hdaR3StreamPeriodInit(PHDASTREAMPERIOD pPeriod, uint8_t u8SD, uint16_t u16LVI, uint32_t u32CBL, PPDMAUDIOSTREAMCFG pStreamCfg);
+void hdaR3StreamPeriodReset(PHDASTREAMPERIOD pPeriod);
+int hdaR3StreamPeriodBegin(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk);
+void hdaR3StreamPeriodEnd(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodPause(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodResume(PHDASTREAMPERIOD pPeriod);
+bool hdaR3StreamPeriodLock(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodUnlock(PHDASTREAMPERIOD pPeriod);
+uint64_t hdaR3StreamPeriodFramesToWalClk(PHDASTREAMPERIOD pPeriod, uint32_t uFrames);
+uint64_t hdaR3StreamPeriodGetAbsEndWalClk(PHDASTREAMPERIOD pPeriod);
+uint64_t hdaR3StreamPeriodGetAbsElapsedWalClk(PHDASTREAMPERIOD pPeriod);
+uint32_t hdaR3StreamPeriodGetRemainingFrames(PHDASTREAMPERIOD pPeriod);
+bool hdaR3StreamPeriodHasElapsed(PHDASTREAMPERIOD pPeriod);
+bool hdaR3StreamPeriodHasPassedAbsWalClk(PHDASTREAMPERIOD pPeriod, uint64_t u64WalClk);
+bool hdaR3StreamPeriodNeedsInterrupt(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodAcquireInterrupt(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodReleaseInterrupt(PHDASTREAMPERIOD pPeriod);
+void hdaR3StreamPeriodInc(PHDASTREAMPERIOD pPeriod, uint32_t framesInc);
+bool hdaR3StreamPeriodIsComplete(PHDASTREAMPERIOD pPeriod);
+#endif /* IN_RING3 */
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_HDAStreamPeriod_h */
+
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/VBoxMMNotificationClient.cpp b/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp
new file mode 100644
index 00000000..92ae2119
--- /dev/null
+++ b/src/VBox/Devices/Audio/VBoxMMNotificationClient.cpp
@@ -0,0 +1,246 @@
+/* $Id: VBoxMMNotificationClient.cpp $ */
+/** @file
+ * VBoxMMNotificationClient.cpp - Implementation of the IMMNotificationClient interface
+ * to detect audio endpoint changes.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#include "VBoxMMNotificationClient.h"
+
+#include <iprt/win/windows.h>
+
+#pragma warning(push)
+#pragma warning(disable: 4201)
+#include <mmdeviceapi.h>
+#include <endpointvolume.h>
+#pragma warning(pop)
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+
+VBoxMMNotificationClient::VBoxMMNotificationClient(void)
+ : m_fRegisteredClient(false)
+ , m_cRef(1)
+{
+}
+
+VBoxMMNotificationClient::~VBoxMMNotificationClient(void)
+{
+}
+
+/**
+ * Uninitializes the mulitmedia notification client implementation.
+ */
+void VBoxMMNotificationClient::Dispose(void)
+{
+ DetachFromEndpoint();
+
+ if (m_fRegisteredClient)
+ {
+ m_pEnum->UnregisterEndpointNotificationCallback(this);
+
+ m_fRegisteredClient = false;
+ }
+}
+
+/**
+ * Initializes the mulitmedia notification client implementation.
+ *
+ * @return HRESULT
+ */
+HRESULT VBoxMMNotificationClient::Initialize(void)
+{
+ HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
+ (void **)&m_pEnum);
+ if (SUCCEEDED(hr))
+ {
+ hr = m_pEnum->RegisterEndpointNotificationCallback(this);
+ if (SUCCEEDED(hr))
+ {
+ m_fRegisteredClient = true;
+
+ hr = AttachToDefaultEndpoint();
+ }
+ }
+
+ LogFunc(("Returning %Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Registration callback implementation for storing our (required) contexts.
+ *
+ * @return IPRT status code.
+ * @param pDrvIns Driver instance to register the notification client to.
+ * @param pfnCallback Audio callback to call by the notification client in case of new events.
+ */
+int VBoxMMNotificationClient::RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback)
+{
+ this->m_pDrvIns = pDrvIns;
+ this->m_pfnCallback = pfnCallback;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unregistration callback implementation for cleaning up our mess when we're done handling
+ * with notifications.
+ */
+void VBoxMMNotificationClient::UnregisterCallback(void)
+{
+ this->m_pDrvIns = NULL;
+ this->m_pfnCallback = NULL;
+}
+
+/**
+ * Stub being called when attaching to the default audio endpoint.
+ * Does nothing at the moment.
+ */
+HRESULT VBoxMMNotificationClient::AttachToDefaultEndpoint(void)
+{
+ return S_OK;
+}
+
+/**
+ * Stub being called when detaching from the default audio endpoint.
+ * Does nothing at the moment.
+ */
+void VBoxMMNotificationClient::DetachFromEndpoint(void)
+{
+
+}
+
+/**
+ * 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 VBoxMMNotificationClient::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;
+ }
+
+ LogRel2(("Audio: Device '%ls' has changed state to '%s'\n", pwstrDeviceId, pszState));
+
+#ifdef VBOX_WITH_AUDIO_CALLBACKS
+ AssertPtr(this->m_pDrvIns);
+ AssertPtr(this->m_pfnCallback);
+
+ if (this->m_pfnCallback)
+ /* Ignore rc */ this->m_pfnCallback(this->m_pDrvIns, PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED, NULL, 0);
+#endif
+
+ 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 VBoxMMNotificationClient::OnDeviceAdded(LPCWSTR pwstrDeviceId)
+{
+ RT_NOREF(pwstrDeviceId);
+ LogFunc(("%ls\n", pwstrDeviceId));
+ 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 VBoxMMNotificationClient::OnDeviceRemoved(LPCWSTR pwstrDeviceId)
+{
+ RT_NOREF(pwstrDeviceId);
+ LogFunc(("%ls\n", pwstrDeviceId));
+ 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 VBoxMMNotificationClient::OnDefaultDeviceChanged(EDataFlow eFlow, ERole eRole, LPCWSTR pwstrDefaultDeviceId)
+{
+ RT_NOREF(eFlow, eRole, pwstrDefaultDeviceId);
+
+ if (eFlow == eRender)
+ {
+
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP VBoxMMNotificationClient::QueryInterface(REFIID interfaceID, void **ppvInterface)
+{
+ const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient);
+
+ if ( IsEqualIID(interfaceID, IID_IUnknown)
+ || IsEqualIID(interfaceID, IID_IMMNotificationClient))
+ {
+ *ppvInterface = static_cast<IMMNotificationClient*>(this);
+ AddRef();
+ return S_OK;
+ }
+
+ *ppvInterface = NULL;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) VBoxMMNotificationClient::AddRef(void)
+{
+ return InterlockedIncrement(&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) VBoxMMNotificationClient::Release(void)
+{
+ long lRef = InterlockedDecrement(&m_cRef);
+ if (lRef == 0)
+ delete this;
+
+ return lRef;
+}
+
diff --git a/src/VBox/Devices/Audio/VBoxMMNotificationClient.h b/src/VBox/Devices/Audio/VBoxMMNotificationClient.h
new file mode 100644
index 00000000..6d63174c
--- /dev/null
+++ b/src/VBox/Devices/Audio/VBoxMMNotificationClient.h
@@ -0,0 +1,91 @@
+/* $Id: VBoxMMNotificationClient.h $ */
+/** @file
+ * VBoxMMNotificationClient.h - Implementation of the IMMNotificationClient interface
+ * to detect audio endpoint changes.
+ */
+
+/*
+ * Copyright (C) 2017-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h
+#define VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/critsect.h>
+#include <iprt/win/windows.h>
+
+/* Should fix warning in include\ks.h. */
+#ifndef _WIN64
+# ifdef RT_ARCH_X86
+# define _WIN64 1
+# else
+# define _WIN64 0
+# endif
+#endif
+
+#include <Mmdeviceapi.h>
+
+#include "DrvAudio.h"
+
+class VBoxMMNotificationClient : IMMNotificationClient
+{
+public:
+
+ VBoxMMNotificationClient();
+ virtual ~VBoxMMNotificationClient();
+
+ HRESULT Initialize();
+ int RegisterCallback(PPDMDRVINS pDrvIns, PFNPDMHOSTAUDIOCALLBACK pfnCallback);
+ void UnregisterCallback(void);
+ void Dispose();
+
+ /** @name IUnknown interface
+ * @{ */
+ IFACEMETHODIMP_(ULONG) Release();
+ /** @} */
+
+private:
+
+ bool m_fRegisteredClient;
+ IMMDeviceEnumerator *m_pEnum;
+ IMMDevice *m_pEndpoint;
+
+ long m_cRef;
+
+ PPDMDRVINS m_pDrvIns;
+ PFNPDMHOSTAUDIOCALLBACK m_pfnCallback;
+
+ HRESULT AttachToDefaultEndpoint();
+ void DetachFromEndpoint();
+
+ /** @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; }
+ IFACEMETHODIMP OnDeviceQueryRemove() { return S_OK; }
+ IFACEMETHODIMP OnDeviceQueryRemoveFailed() { return S_OK; }
+ IFACEMETHODIMP OnDeviceRemovePending() { return S_OK; }
+ /** @} */
+
+ /** @name IUnknown interface
+ * @{ */
+ IFACEMETHODIMP QueryInterface(const IID& iid, void** ppUnk);
+ IFACEMETHODIMP_(ULONG) AddRef();
+ /** @} */
+};
+#endif /* !VBOX_INCLUDED_SRC_Audio_VBoxMMNotificationClient_h */
+
diff --git a/src/VBox/Devices/Audio/alsa_mangling.h b/src/VBox/Devices/Audio/alsa_mangling.h
new file mode 100644
index 00000000..118fea8f
--- /dev/null
+++ b/src/VBox/Devices/Audio/alsa_mangling.h
@@ -0,0 +1,72 @@
+/* $Id: alsa_mangling.h $ */
+/** @file
+ * Mangle libasound symbols.
+ *
+ * This is necessary on hosts which don't support the -fvisibility gcc switch.
+ */
+
+/*
+ * Copyright (C) 2013-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_alsa_mangling_h
+#define VBOX_INCLUDED_SRC_Audio_alsa_mangling_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_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_start ALSA_MANGLER(snd_pcm_start)
+#define snd_pcm_state ALSA_MANGLER(snd_pcm_state)
+#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)
+
+#endif /* !VBOX_INCLUDED_SRC_Audio_alsa_mangling_h */
diff --git a/src/VBox/Devices/Audio/alsa_stubs.c b/src/VBox/Devices/Audio/alsa_stubs.c
new file mode 100644
index 00000000..db40bc45
--- /dev/null
+++ b/src/VBox/Devices/Audio/alsa_stubs.c
@@ -0,0 +1,243 @@
+/* $Id: alsa_stubs.c $ */
+/** @file
+ * Stubs for libasound.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <iprt/assert.h>
+#include <iprt/ldr.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+
+#include <alsa/asoundlib.h>
+
+#include "alsa_stubs.h"
+
+#define VBOX_ALSA_LIB "libasound.so.2"
+
+#define PROXY_STUB(function, rettype, signature, shortsig) \
+ static rettype (*pfn_ ## function) signature; \
+ \
+ 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))
+
+/*
+ * PCM
+ */
+
+PROXY_STUB(snd_pcm_avail_update, snd_pcm_sframes_t, (snd_pcm_t *pcm),
+ (pcm))
+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 *frames),
+ (pcm, frames))
+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_state, snd_pcm_state_t, (snd_pcm_t *pcm), (pcm))
+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))
+
+/*
+ * 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), ())
+
+typedef struct
+{
+ const char *name;
+ void (**fn)(void);
+} SHARED_FUNC;
+
+#define ELEMENT(function) { #function , (void (**)(void)) & pfn_ ## function }
+static SHARED_FUNC SharedFuncs[] =
+{
+ ELEMENT(snd_lib_error_set_handler),
+ ELEMENT(snd_strerror),
+
+ ELEMENT(snd_device_name_hint),
+ ELEMENT(snd_device_name_get_hint),
+ ELEMENT(snd_device_name_free_hint),
+
+ ELEMENT(snd_pcm_avail_update),
+ 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(snd_pcm_state),
+
+ 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),
+};
+#undef ELEMENT
+
+/**
+ * Try to dynamically load the ALSA libraries. This function is not
+ * thread-safe, and should be called before attempting to use any of the
+ * ALSA functions.
+ *
+ * @returns iprt status code
+ */
+int audioLoadAlsaLib(void)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+ static enum { NO = 0, YES, FAIL } isLibLoaded = NO;
+ RTLDRMOD hLib;
+
+ LogFlowFunc(("\n"));
+ /* If this is not NO then the function has obviously been called twice,
+ which is likely to be a bug. */
+ if (NO != isLibLoaded)
+ {
+ AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO"));
+ return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED;
+ }
+ isLibLoaded = FAIL;
+ rc = RTLdrLoad(VBOX_ALSA_LIB, &hLib);
+ if (RT_FAILURE(rc))
+ {
+ LogRelFunc(("Failed to load library %s\n", VBOX_ALSA_LIB));
+ return rc;
+ }
+ for (i=0; i<RT_ELEMENTS(SharedFuncs); i++)
+ {
+ rc = RTLdrGetSymbol(hLib, SharedFuncs[i].name, (void**)SharedFuncs[i].fn);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ isLibLoaded = YES;
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Audio/alsa_stubs.h b/src/VBox/Devices/Audio/alsa_stubs.h
new file mode 100644
index 00000000..cf8fc47e
--- /dev/null
+++ b/src/VBox/Devices/Audio/alsa_stubs.h
@@ -0,0 +1,25 @@
+/* $Id: alsa_stubs.h $ */
+/** @file
+ * Stubs for libasound.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_alsa_stubs_h
+#define VBOX_INCLUDED_SRC_Audio_alsa_stubs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+extern int audioLoadAlsaLib(void);
+#endif /* !VBOX_INCLUDED_SRC_Audio_alsa_stubs_h */
+
diff --git a/src/VBox/Devices/Audio/pulse_mangling.h b/src/VBox/Devices/Audio/pulse_mangling.h
new file mode 100644
index 00000000..7d2bf60d
--- /dev/null
+++ b/src/VBox/Devices/Audio/pulse_mangling.h
@@ -0,0 +1,93 @@
+/* $Id: pulse_mangling.h $ */
+/** @file
+ * Mangle libpulse symbols.
+ *
+ * This is necessary on hosts which don't support the -fvisibility gcc switch.
+ */
+
+/*
+ * Copyright (C) 2013-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_pulse_mangling_h
+#define VBOX_INCLUDED_SRC_Audio_pulse_mangling_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_source_info_by_name PULSE_MANGLER(pa_context_get_source_info_by_name)
+#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_pulse_mangling_h */
+
diff --git a/src/VBox/Devices/Audio/pulse_stubs.c b/src/VBox/Devices/Audio/pulse_stubs.c
new file mode 100644
index 00000000..0427c3c5
--- /dev/null
+++ b/src/VBox/Devices/Audio/pulse_stubs.c
@@ -0,0 +1,348 @@
+/* $Id: pulse_stubs.c $ */
+/** @file
+ * Stubs for libpulse.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <iprt/assert.h>
+#include <iprt/ldr.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "pulse_stubs.h"
+
+#define VBOX_PULSE_LIB "libpulse.so.0"
+
+#define PROXY_STUB(function, rettype, signature, shortsig) \
+ static rettype (*g_pfn_ ## function) signature; \
+ \
+ 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; \
+ \
+ 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_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_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))
+
+typedef struct
+{
+ const char *name;
+ void (**fn)(void);
+} SHARED_FUNC;
+
+#define ELEMENT(function) { #function , (void (**)(void)) & g_pfn_ ## function }
+static SHARED_FUNC SharedFuncs[] =
+{
+ ELEMENT(pa_bytes_per_second),
+ ELEMENT(pa_bytes_to_usec),
+ ELEMENT(pa_channel_map_init_auto),
+
+ ELEMENT(pa_context_connect),
+ ELEMENT(pa_context_disconnect),
+ ELEMENT(pa_context_get_server_info),
+ ELEMENT(pa_context_get_sink_info_by_name),
+ ELEMENT(pa_context_get_source_info_by_name),
+ ELEMENT(pa_context_get_state),
+ ELEMENT(pa_context_unref),
+ ELEMENT(pa_context_errno),
+ ELEMENT(pa_context_new),
+ ELEMENT(pa_context_set_state_callback),
+
+ ELEMENT(pa_frame_size),
+ ELEMENT(pa_get_library_version),
+ ELEMENT(pa_operation_unref),
+ ELEMENT(pa_operation_get_state),
+ ELEMENT(pa_operation_cancel),
+ ELEMENT(pa_rtclock_now),
+ ELEMENT(pa_sample_format_to_string),
+ ELEMENT(pa_sample_spec_valid),
+ ELEMENT(pa_strerror),
+
+ ELEMENT(pa_stream_connect_playback),
+ ELEMENT(pa_stream_connect_record),
+ ELEMENT(pa_stream_disconnect),
+ ELEMENT(pa_stream_get_sample_spec),
+ ELEMENT(pa_stream_set_latency_update_callback),
+ ELEMENT(pa_stream_write),
+ ELEMENT(pa_stream_unref),
+ ELEMENT(pa_stream_get_state),
+ ELEMENT(pa_stream_get_latency),
+ ELEMENT(pa_stream_get_timing_info),
+ ELEMENT(pa_stream_readable_size),
+ ELEMENT(pa_stream_set_buffer_attr),
+ ELEMENT(pa_stream_set_state_callback),
+ ELEMENT(pa_stream_set_underflow_callback),
+ ELEMENT(pa_stream_set_overflow_callback),
+ ELEMENT(pa_stream_set_write_callback),
+ ELEMENT(pa_stream_flush),
+ ELEMENT(pa_stream_drain),
+ ELEMENT(pa_stream_trigger),
+ ELEMENT(pa_stream_new),
+ ELEMENT(pa_stream_get_buffer_attr),
+ ELEMENT(pa_stream_peek),
+ ELEMENT(pa_stream_cork),
+ ELEMENT(pa_stream_drop),
+ ELEMENT(pa_stream_writable_size),
+
+ ELEMENT(pa_threaded_mainloop_stop),
+ ELEMENT(pa_threaded_mainloop_get_api),
+ ELEMENT(pa_threaded_mainloop_free),
+ ELEMENT(pa_threaded_mainloop_signal),
+ ELEMENT(pa_threaded_mainloop_unlock),
+ ELEMENT(pa_threaded_mainloop_new),
+ ELEMENT(pa_threaded_mainloop_wait),
+ ELEMENT(pa_threaded_mainloop_start),
+ ELEMENT(pa_threaded_mainloop_lock),
+
+ ELEMENT(pa_usec_to_bytes)
+};
+#undef ELEMENT
+
+/**
+ * Try to dynamically load the PulseAudio libraries. This function is not
+ * thread-safe, and should be called before attempting to use any of the
+ * PulseAudio functions.
+ *
+ * @returns iprt status code
+ */
+int audioLoadPulseLib(void)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+ static enum { NO = 0, YES, FAIL } isLibLoaded = NO;
+ RTLDRMOD hLib;
+
+ LogFlowFunc(("\n"));
+ /* If this is not NO then the function has obviously been called twice,
+ which is likely to be a bug. */
+ if (NO != isLibLoaded)
+ {
+ AssertMsgFailed(("isLibLoaded == %s\n", YES == isLibLoaded ? "YES" : "NO"));
+ return YES == isLibLoaded ? VINF_SUCCESS : VERR_NOT_SUPPORTED;
+ }
+ isLibLoaded = FAIL;
+ rc = RTLdrLoad(VBOX_PULSE_LIB, &hLib);
+ if (RT_FAILURE(rc))
+ {
+ LogRelFunc(("Failed to load library %s\n", VBOX_PULSE_LIB));
+ return rc;
+ }
+ for (i=0; i<RT_ELEMENTS(SharedFuncs); i++)
+ {
+ rc = RTLdrGetSymbol(hLib, SharedFuncs[i].name, (void**)SharedFuncs[i].fn);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ isLibLoaded = YES;
+ return rc;
+}
+
diff --git a/src/VBox/Devices/Audio/pulse_stubs.h b/src/VBox/Devices/Audio/pulse_stubs.h
new file mode 100644
index 00000000..abc59bdc
--- /dev/null
+++ b/src/VBox/Devices/Audio/pulse_stubs.h
@@ -0,0 +1,25 @@
+/* $Id: pulse_stubs.h $ */
+/** @file
+ * Stubs for libpulse.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Audio_pulse_stubs_h
+#define VBOX_INCLUDED_SRC_Audio_pulse_stubs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+extern int audioLoadPulseLib(void);
+#endif /* !VBOX_INCLUDED_SRC_Audio_pulse_stubs_h */
+
diff --git a/src/VBox/Devices/Audio/testcase/Makefile.kmk b/src/VBox/Devices/Audio/testcase/Makefile.kmk
new file mode 100644
index 00000000..1cdbcbea
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/Makefile.kmk
@@ -0,0 +1,42 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the audio testcases.
+#
+
+#
+# Copyright (C) 2014-2019 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+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
+ tstAudioMixBuffer_DEFS.debug = VBOX_WITH_EF_WRAPS
+ tstAudioMixBuffer_SOURCES = \
+ tstAudioMixBuffer.cpp \
+ ../AudioMixBuffer.cpp \
+ ../DrvAudioCommon.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"
+
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp
new file mode 100644
index 00000000..a8566678
--- /dev/null
+++ b/src/VBox/Devices/Audio/testcase/tstAudioMixBuffer.cpp
@@ -0,0 +1,651 @@
+/* $Id: tstAudioMixBuffer.cpp $ */
+/** @file
+ * Audio testcase - Mixing buffer.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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 "../AudioMixBuffer.h"
+#include "../DrvAudio.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+static int tstSingle(RTTEST hTest)
+{
+ RTTestSubF(hTest, "Single buffer");
+
+ /* 44100Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS config = PDMAUDIOPCMPROPS_INITIALIZOR(
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 44100, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&config));
+
+ uint32_t cBufSize = _1K;
+
+ /*
+ * General stuff.
+ */
+ PDMAUDIOMIXBUF mb;
+ RTTESTI_CHECK_RC_OK(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));
+
+ /*
+ * Absolute writes.
+ */
+ uint32_t cFramesRead = 0, cFramesWritten = 0, cFramesWrittenAbs = 0;
+ int8_t aFrames8 [2] = { 0x12, 0x34 };
+ int16_t aFrames16[2] = { 0xAA, 0xBB };
+ int32_t aFrames32[2] = { 0xCC, 0xDD };
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames8, sizeof(aFrames8), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 0 /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 0);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, &aFrames16, sizeof(aFrames16), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 1 /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 1);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 2 /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2);
+
+ /* Beyond buffer. */
+ RTTESTI_CHECK_RC(AudioMixBufWriteAt(&mb, AudioMixBufSize(&mb) + 1, &aFrames16, sizeof(aFrames16),
+ &cFramesWritten), VERR_BUFFER_OVERFLOW);
+
+ /* Offset wrap-around: When writing as much (or more) frames the mixing buffer can hold. */
+ uint32_t cbSamples = cBufSize * sizeof(int16_t) * 2 /* Channels */;
+ RTTESTI_CHECK(cbSamples);
+ uint16_t *paSamples = (uint16_t *)RTMemAlloc(cbSamples);
+ RTTESTI_CHECK(paSamples);
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 0 /* Offset */, paSamples, cbSamples, &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == cBufSize /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize);
+ RTTESTI_CHECK(AudioMixBufReadPos(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufWritePos(&mb) == 0);
+ RTMemFree(paSamples);
+ cbSamples = 0;
+
+ /*
+ * Circular writes.
+ */
+ AudioMixBufReset(&mb);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteAt(&mb, 2 /* Offset */, &aFrames32, sizeof(aFrames32), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 2 /* Frames */);
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == 2);
+
+ 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++)
+ {
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 1);
+ }
+ 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 */);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&mb, &aFrames16, sizeof(aFrames16), &cFramesWritten));
+ RTTESTI_CHECK(cFramesWritten == 1);
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == 0);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, 0U));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cBufSize);
+
+ /* Circular reads. */
+ uint32_t cToRead = AudioMixBufSize(&mb) - cFramesWrittenAbs - 1;
+ for (uint32_t i = 0; i < cToRead; i++)
+ {
+ RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead));
+ RTTESTI_CHECK(cFramesRead == 1);
+ AudioMixBufReleaseReadBlock(&mb, cFramesRead);
+ AudioMixBufFinish(&mb, cFramesRead);
+ }
+ 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);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufAcquireReadBlock(&mb, &aFrames16, sizeof(aFrames16), &cFramesRead));
+ RTTESTI_CHECK(cFramesRead == 1);
+ AudioMixBufReleaseReadBlock(&mb, cFramesRead);
+ AudioMixBufFinish(&mb, cFramesRead);
+ RTTESTI_CHECK(AudioMixBufFree(&mb) == cBufSize - cFramesWrittenAbs);
+ RTTESTI_CHECK(AudioMixBufFreeBytes(&mb) == AUDIOMIXBUF_F2B(&mb, cBufSize - cFramesWrittenAbs));
+ RTTESTI_CHECK(AudioMixBufUsed(&mb) == cFramesWrittenAbs);
+
+ AudioMixBufDestroy(&mb);
+
+ return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS;
+}
+
+static int tstParentChild(RTTEST hTest)
+{
+ uint32_t cParentBufSize = RTRandU32Ex(_1K /* Min */, _16K /* Max */); /* Enough room for random sizes */
+
+ /* 44100Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR(
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 44100, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p));
+
+ PDMAUDIOMIXBUF parent;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cParentBufSize));
+
+ /* 22050Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS cfg_c1 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Upmixing to parent */
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 22050, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c1));
+
+ uint32_t cFrames = 16;
+ uint32_t cChildBufSize = RTRandU32Ex(cFrames /* Min */, 64 /* Max */);
+
+ PDMAUDIOMIXBUF child1;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child1, "Child1", &cfg_c1, cChildBufSize));
+ RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child1, &parent));
+
+ /* 48000Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS cfg_c2 = PDMAUDIOPCMPROPS_INITIALIZOR(/* Downmixing to parent */
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 48000, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c2));
+
+ PDMAUDIOMIXBUF child2;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child2, "Child2", &cfg_c2, cChildBufSize));
+ RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child2, &parent));
+
+ /*
+ * Writing + mixing from child/children -> parent, sequential.
+ */
+ uint32_t cbBuf = _1K;
+ char pvBuf[_1K];
+ int16_t aFrames16[32] = { 0xAA, 0xBB };
+ uint32_t cFramesRead, cFramesWritten, cFramesMixed;
+
+ uint32_t cFramesChild1 = cFrames;
+ uint32_t cFramesChild2 = cFrames;
+
+ uint32_t t = RTRandU32() % 32;
+
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG,
+ "cParentBufSize=%RU32, cChildBufSize=%RU32, %RU32 frames -> %RU32 iterations total\n",
+ cParentBufSize, cChildBufSize, cFrames, t);
+
+ /*
+ * Using AudioMixBufWriteAt for writing to children.
+ */
+ RTTestSubF(hTest, "2 Children -> Parent (AudioMixBufWriteAt)");
+
+ uint32_t cChildrenSamplesMixedTotal = 0;
+
+ for (uint32_t i = 0; i < t; i++)
+ {
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "i=%RU32\n", i);
+
+ uint32_t cChild1Writes = RTRandU32() % 8;
+
+ for (uint32_t c1 = 0; c1 < cChild1Writes; c1++)
+ {
+ /* Child 1. */
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child1, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten));
+ RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild1, ("Child1: Expected %RU32 written frames, got %RU32\n", cFramesChild1, cFramesWritten));
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child1, cFramesWritten, &cFramesMixed));
+
+ cChildrenSamplesMixedTotal += cFramesMixed;
+
+ RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child1: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed));
+ RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child1) == 0, ("Child1: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child1)));
+ }
+
+ uint32_t cChild2Writes = RTRandU32() % 8;
+
+ for (uint32_t c2 = 0; c2 < cChild2Writes; c2++)
+ {
+ /* Child 2. */
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufWriteAt(&child2, 0, &aFrames16, sizeof(aFrames16), &cFramesWritten));
+ RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesChild2, ("Child2: Expected %RU32 written frames, got %RU32\n", cFramesChild2, cFramesWritten));
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufMixToParent(&child2, cFramesWritten, &cFramesMixed));
+
+ cChildrenSamplesMixedTotal += cFramesMixed;
+
+ RTTESTI_CHECK_MSG_BREAK(cFramesWritten == cFramesMixed, ("Child2: Expected %RU32 mixed frames, got %RU32\n", cFramesWritten, cFramesMixed));
+ RTTESTI_CHECK_MSG_BREAK(AudioMixBufUsed(&child2) == 0, ("Child2: Expected %RU32 used frames, got %RU32\n", 0, AudioMixBufUsed(&child2)));
+ }
+
+ /*
+ * Read out all frames from the parent buffer and also mark the just-read frames as finished
+ * so that both connected children buffers can keep track of their stuff.
+ */
+ uint32_t cParentSamples = AudioMixBufUsed(&parent);
+ while (cParentSamples)
+ {
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, pvBuf, cbBuf, &cFramesRead));
+ if (!cFramesRead)
+ break;
+
+ AudioMixBufReleaseReadBlock(&parent, cFramesRead);
+ AudioMixBufFinish(&parent, cFramesRead);
+
+ RTTESTI_CHECK(cParentSamples >= cFramesRead);
+ cParentSamples -= cFramesRead;
+ }
+
+ RTTESTI_CHECK(cParentSamples == 0);
+ }
+
+ RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0);
+ RTTESTI_CHECK(AudioMixBufLive(&child1) == 0);
+ RTTESTI_CHECK(AudioMixBufLive(&child2) == 0);
+
+ AudioMixBufDestroy(&parent);
+ AudioMixBufDestroy(&child1);
+ AudioMixBufDestroy(&child2);
+
+ return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS;
+}
+
+/* Test 8-bit sample conversion (8-bit -> internal -> 8-bit). */
+static int tstConversion8(RTTEST hTest)
+{
+ unsigned i;
+ uint32_t cBufSize = 256;
+
+ RTTestSubF(hTest, "Sample conversion (U8)");
+
+ /* 44100Hz, 1 Channel, U8 */
+ PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR(
+ 1, /* Bytes */
+ false, /* Signed */
+ 1, /* Channels */
+ 44100, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p));
+
+ PDMAUDIOMIXBUF parent;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize));
+
+ /* Child uses half the sample rate; that ensures the mixing engine can't
+ * take shortcuts and performs conversion. Because conversion to double
+ * the sample rate effectively inserts one additional sample between every
+ * two source frames, N source frames will be converted to N * 2 - 1
+ * frames. However, the last source sample will be saved for later
+ * interpolation and not immediately output.
+ */
+
+ /* 22050Hz, 1 Channel, U8 */
+ PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */
+ 1, /* Bytes */
+ false, /* Signed */
+ 1, /* Channels */
+ 22050, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(1 /* Bytes */, 1 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c));
+
+ PDMAUDIOMIXBUF child;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize));
+ RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent));
+
+ /* 8-bit unsigned frames. Often used with SB16 device. */
+ uint8_t aFrames8U[16] = { 0xAA, 0xBB, 0, 1, 43, 125, 126, 127,
+ 128, 129, 130, 131, 132, UINT8_MAX - 1, UINT8_MAX, 0 };
+
+ /*
+ * Writing + mixing from child -> parent, sequential.
+ */
+ uint32_t cbBuf = 256;
+ char achBuf[256];
+ uint32_t cFramesRead, cFramesWritten, cFramesMixed;
+
+ uint32_t cFramesChild = 16;
+ uint32_t cFramesParent = cFramesChild * 2 - 2;
+ uint32_t cFramesTotalRead = 0;
+
+ /**** 8-bit unsigned samples ****/
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 8-bit\n", cfg_c.uHz, cfg_c.cChannels);
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames8U, sizeof(aFrames8U), &cFramesWritten));
+ RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten));
+ RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed));
+ uint32_t cFrames = AudioMixBufUsed(&parent);
+ RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames));
+
+ RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child));
+
+ for (;;)
+ {
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead));
+ if (!cFramesRead)
+ break;
+ cFramesTotalRead += cFramesRead;
+ AudioMixBufReleaseReadBlock(&parent, cFramesRead);
+ AudioMixBufFinish(&parent, cFramesRead);
+ }
+
+ RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead));
+
+ /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */
+ /* NB: This also checks that the default volume setting is 0dB attenuation. */
+ uint8_t *pSrc8 = &aFrames8U[0];
+ uint8_t *pDst8 = (uint8_t *)achBuf;
+
+ for (i = 0; i < cFramesChild - 1; ++i)
+ {
+ RTTESTI_CHECK_MSG(*pSrc8 == *pDst8, ("index %u: Dst=%d, Src=%d\n", i, *pDst8, *pSrc8));
+ pSrc8 += 1;
+ pDst8 += 2;
+ }
+
+ RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0);
+ RTTESTI_CHECK(AudioMixBufLive(&child) == 0);
+
+ AudioMixBufDestroy(&parent);
+ AudioMixBufDestroy(&child);
+
+ return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS;
+}
+
+/* Test 16-bit sample conversion (16-bit -> internal -> 16-bit). */
+static int tstConversion16(RTTEST hTest)
+{
+ unsigned i;
+ uint32_t cBufSize = 256;
+
+ RTTestSubF(hTest, "Sample conversion (S16)");
+
+ /* 44100Hz, 1 Channel, S16 */
+ PDMAUDIOPCMPROPS cfg_p = PDMAUDIOPCMPROPS_INITIALIZOR(
+ 2, /* Bytes */
+ true, /* Signed */
+ 1, /* Channels */
+ 44100, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_p));
+
+ PDMAUDIOMIXBUF parent;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg_p, cBufSize));
+
+ /* 22050Hz, 1 Channel, S16 */
+ PDMAUDIOPCMPROPS cfg_c = PDMAUDIOPCMPROPS_INITIALIZOR( /* Upmixing to parent */
+ 2, /* Bytes */
+ true, /* Signed */
+ 1, /* Channels */
+ 22050, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 1 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg_c));
+
+ PDMAUDIOMIXBUF child;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg_c, cBufSize));
+ RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent));
+
+ /* 16-bit signed. More or less exclusively used as output, and usually as input, too. */
+ int16_t aFrames16S[16] = { 0xAA, 0xBB, INT16_MIN, INT16_MIN + 1, INT16_MIN / 2, -3, -2, -1,
+ 0, 1, 2, 3, INT16_MAX / 2, INT16_MAX - 1, INT16_MAX, 0 };
+
+ /*
+ * Writing + mixing from child -> parent, sequential.
+ */
+ uint32_t cbBuf = 256;
+ char achBuf[256];
+ uint32_t cFramesRead, cFramesWritten, cFramesMixed;
+
+ uint32_t cFramesChild = 16;
+ uint32_t cFramesParent = cFramesChild * 2 - 2;
+ uint32_t cFramesTotalRead = 0;
+
+ /**** 16-bit signed samples ****/
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Conversion test %uHz %uch 16-bit\n", cfg_c.uHz, cfg_c.cChannels);
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten));
+ RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten));
+ RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed));
+ uint32_t cFrames = AudioMixBufUsed(&parent);
+ RTTESTI_CHECK_MSG(AudioMixBufLive(&child) == cFrames, ("Child: Expected %RU32 mixed frames, got %RU32\n", AudioMixBufLive(&child), cFrames));
+
+ RTTESTI_CHECK(AudioMixBufUsed(&parent) == AudioMixBufLive(&child));
+
+ for (;;)
+ {
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead));
+ if (!cFramesRead)
+ break;
+ cFramesTotalRead += cFramesRead;
+ AudioMixBufReleaseReadBlock(&parent, cFramesRead);
+ AudioMixBufFinish(&parent, cFramesRead);
+ }
+ RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead));
+
+ /* Check that the frames came out unharmed. Every other sample is interpolated and we ignore it. */
+ /* NB: This also checks that the default volume setting is 0dB attenuation. */
+ int16_t *pSrc16 = &aFrames16S[0];
+ int16_t *pDst16 = (int16_t *)achBuf;
+
+ for (i = 0; i < cFramesChild - 1; ++i)
+ {
+ RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16));
+ pSrc16 += 1;
+ pDst16 += 2;
+ }
+
+ RTTESTI_CHECK(AudioMixBufUsed(&parent) == 0);
+ RTTESTI_CHECK(AudioMixBufLive(&child) == 0);
+
+ AudioMixBufDestroy(&parent);
+ AudioMixBufDestroy(&child);
+
+ return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS;
+}
+
+/* Test volume control. */
+static int tstVolume(RTTEST hTest)
+{
+ unsigned i;
+ uint32_t cBufSize = 256;
+
+ RTTestSubF(hTest, "Volume control");
+
+ /* Same for parent/child. */
+ /* 44100Hz, 2 Channels, S16 */
+ PDMAUDIOPCMPROPS cfg = PDMAUDIOPCMPROPS_INITIALIZOR(
+ 2, /* Bytes */
+ true, /* Signed */
+ 2, /* Channels */
+ 44100, /* Hz */
+ PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(2 /* Bytes */, 2 /* Channels */), /* Shift */
+ false /* Swap Endian */
+ );
+
+ RTTESTI_CHECK(DrvAudioHlpPCMPropsAreValid(&cfg));
+
+ PDMAUDIOVOLUME vol = { false, 0, 0 }; /* Not muted. */
+ PDMAUDIOMIXBUF parent;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&parent, "Parent", &cfg, cBufSize));
+
+ PDMAUDIOMIXBUF child;
+ RTTESTI_CHECK_RC_OK(AudioMixBufInit(&child, "Child", &cfg, cBufSize));
+ RTTESTI_CHECK_RC_OK(AudioMixBufLinkTo(&child, &parent));
+
+ /* A few 16-bit signed samples. */
+ int16_t 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 };
+
+ /*
+ * Writing + mixing from child -> parent.
+ */
+ uint32_t cbBuf = 256;
+ char achBuf[256];
+ uint32_t cFramesRead, cFramesWritten, cFramesMixed;
+
+ uint32_t cFramesChild = 8;
+ uint32_t cFramesParent = cFramesChild;
+ uint32_t cFramesTotalRead;
+ int16_t *pSrc16;
+ int16_t *pDst16;
+
+ /**** Volume control test ****/
+ RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Volume control test %uHz %uch \n", cfg.uHz, cfg.cChannels);
+
+ /* 1) Full volume/0dB attenuation (255). */
+ vol.uLeft = vol.uRight = 255;
+ AudioMixBufSetVolume(&child, &vol);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten));
+ RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten));
+ RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed));
+
+ cFramesTotalRead = 0;
+ for (;;)
+ {
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead));
+ if (!cFramesRead)
+ break;
+ cFramesTotalRead += cFramesRead;
+ AudioMixBufReleaseReadBlock(&parent, cFramesRead);
+ AudioMixBufFinish(&parent, cFramesRead);
+ }
+ RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead));
+
+ /* Check that at 0dB the frames came out unharmed. */
+ pSrc16 = &aFrames16S[0];
+ pDst16 = (int16_t *)achBuf;
+
+ for (i = 0; i < cFramesParent * 2 /* stereo */; ++i)
+ {
+ RTTESTI_CHECK_MSG(*pSrc16 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16));
+ ++pSrc16;
+ ++pDst16;
+ }
+ AudioMixBufReset(&child);
+
+ /* 2) Half volume/-6dB attenuation (16 steps down). */
+ vol.uLeft = vol.uRight = 255 - 16;
+ AudioMixBufSetVolume(&child, &vol);
+
+ RTTESTI_CHECK_RC_OK(AudioMixBufWriteCirc(&child, &aFrames16S, sizeof(aFrames16S), &cFramesWritten));
+ RTTESTI_CHECK_MSG(cFramesWritten == cFramesChild, ("Child: Expected %RU32 written frames, got %RU32\n", cFramesChild, cFramesWritten));
+ RTTESTI_CHECK_RC_OK(AudioMixBufMixToParent(&child, cFramesWritten, &cFramesMixed));
+
+ cFramesTotalRead = 0;
+ for (;;)
+ {
+ RTTESTI_CHECK_RC_OK_BREAK(AudioMixBufAcquireReadBlock(&parent, achBuf, cbBuf, &cFramesRead));
+ if (!cFramesRead)
+ break;
+ cFramesTotalRead += cFramesRead;
+ AudioMixBufReleaseReadBlock(&parent, cFramesRead);
+ AudioMixBufFinish(&parent, cFramesRead);
+ }
+ RTTESTI_CHECK_MSG(cFramesTotalRead == cFramesParent, ("Parent: Expected %RU32 mixed frames, got %RU32\n", cFramesParent, cFramesTotalRead));
+
+ /* Check that at -6dB the sample values are halved. */
+ pSrc16 = &aFrames16S[0];
+ pDst16 = (int16_t *)achBuf;
+
+ for (i = 0; i < cFramesParent * 2 /* stereo */; ++i)
+ {
+ /* Watch out! For negative values, x >> 1 is not the same as x / 2. */
+ RTTESTI_CHECK_MSG(*pSrc16 >> 1 == *pDst16, ("index %u: Dst=%d, Src=%d\n", i, *pDst16, *pSrc16));
+ ++pSrc16;
+ ++pDst16;
+ }
+
+ AudioMixBufDestroy(&parent);
+ AudioMixBufDestroy(&child);
+
+ return RTTestSubErrorCount(hTest) ? VERR_GENERAL_FAILURE : VINF_SUCCESS;
+}
+
+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);
+
+ rc = tstSingle(hTest);
+ if (RT_SUCCESS(rc))
+ rc = tstParentChild(hTest);
+ if (RT_SUCCESS(rc))
+ rc = tstConversion8(hTest);
+ if (RT_SUCCESS(rc))
+ rc = tstConversion16(hTest);
+ if (RT_SUCCESS(rc))
+ rc = tstVolume(hTest);
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}