summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/AudioMixer.cpp
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/AudioMixer.cpp
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/AudioMixer.cpp')
-rw-r--r--src/VBox/Devices/Audio/AudioMixer.cpp2236
1 files changed, 2236 insertions, 0 deletions
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;
+}
+