summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/AudioMixBuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Audio/AudioMixBuffer.cpp')
-rw-r--r--src/VBox/Devices/Audio/AudioMixBuffer.cpp2040
1 files changed, 2040 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;
+}
+