summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/DrvAudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Audio/DrvAudio.cpp')
-rw-r--r--src/VBox/Devices/Audio/DrvAudio.cpp5045
1 files changed, 5045 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvAudio.cpp b/src/VBox/Devices/Audio/DrvAudio.cpp
new file mode 100644
index 00000000..2257033e
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvAudio.cpp
@@ -0,0 +1,5045 @@
+/* $Id: DrvAudio.cpp $ */
+/** @file
+ * Intermediate audio driver - Connects the audio device emulation with the host backend.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+#include <VBox/vmm/pdmaudiohostenuminline.h>
+
+#include <iprt/alloc.h>
+#include <iprt/asm-math.h>
+#include <iprt/assert.h>
+#include <iprt/circbuf.h>
+#include <iprt/req.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "AudioHlp.h"
+#include "AudioMixBuffer.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name PDMAUDIOSTREAM_STS_XXX - Used internally by DRVAUDIOSTREAM::fStatus.
+ * @{ */
+/** No flags being set. */
+#define PDMAUDIOSTREAM_STS_NONE UINT32_C(0)
+/** Set if the stream is enabled, clear if disabled. */
+#define PDMAUDIOSTREAM_STS_ENABLED RT_BIT_32(0)
+/** Set if the stream is paused.
+ * Requires the ENABLED status to be set when used. */
+#define PDMAUDIOSTREAM_STS_PAUSED RT_BIT_32(1)
+/** Output only: Set when the stream is draining.
+ * Requires the ENABLED status to be set when used. */
+#define PDMAUDIOSTREAM_STS_PENDING_DISABLE RT_BIT_32(2)
+
+/** Set if the backend for the stream has been created.
+ *
+ * This is generally always set after stream creation, but
+ * can be cleared if the re-initialization of the stream fails later on.
+ * Asynchronous init may still be incomplete, see
+ * PDMAUDIOSTREAM_STS_BACKEND_READY. */
+#define PDMAUDIOSTREAM_STS_BACKEND_CREATED RT_BIT_32(3)
+/** The backend is ready (PDMIHOSTAUDIO::pfnStreamInitAsync is done).
+ * Requires the BACKEND_CREATED status to be set. */
+#define PDMAUDIOSTREAM_STS_BACKEND_READY RT_BIT_32(4)
+/** Set if the stream needs to be re-initialized by the device (i.e. call
+ * PDMIAUDIOCONNECTOR::pfnStreamReInit). (The other status bits are preserved
+ * and are worked as normal while in this state, so that the stream can
+ * resume operation where it left off.) */
+#define PDMAUDIOSTREAM_STS_NEED_REINIT RT_BIT_32(5)
+/** Validation mask for PDMIAUDIOCONNECTOR. */
+#define PDMAUDIOSTREAM_STS_VALID_MASK UINT32_C(0x0000003f)
+/** Asserts the validity of the given stream status mask for PDMIAUDIOCONNECTOR. */
+#define PDMAUDIOSTREAM_STS_ASSERT_VALID(a_fStreamStatus) do { \
+ AssertMsg(!((a_fStreamStatus) & ~PDMAUDIOSTREAM_STS_VALID_MASK), ("%#x\n", (a_fStreamStatus))); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PAUSED) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_PENDING_DISABLE) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_ENABLED)); \
+ Assert(!((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_READY) || ((a_fStreamStatus) & PDMAUDIOSTREAM_STS_BACKEND_CREATED)); \
+ } while (0)
+
+/** @} */
+
+/**
+ * Experimental code for destroying all streams in a disabled direction rather
+ * than just disabling them.
+ *
+ * Cannot be enabled yet because the code isn't complete and DrvAudio will
+ * behave differently (incorrectly), see @bugref{9558#c5} for details.
+ */
+#if defined(DOXYGEN_RUNNING) || 0
+# define DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Audio stream context.
+ *
+ * Needed for separating data from the guest and host side (per stream).
+ */
+typedef struct DRVAUDIOSTREAMCTX
+{
+ /** The stream's audio configuration. */
+ PDMAUDIOSTREAMCFG Cfg;
+} DRVAUDIOSTREAMCTX;
+
+/**
+ * Capture state of a stream wrt backend.
+ */
+typedef enum DRVAUDIOCAPTURESTATE
+{
+ /** Invalid zero value. */
+ DRVAUDIOCAPTURESTATE_INVALID = 0,
+ /** No capturing or pre-buffering. */
+ DRVAUDIOCAPTURESTATE_NO_CAPTURE,
+ /** Regular capturing. */
+ DRVAUDIOCAPTURESTATE_CAPTURING,
+ /** Returning silence till the backend buffer has reched the configured
+ * pre-buffering level. */
+ DRVAUDIOCAPTURESTATE_PREBUF,
+ /** End of valid values. */
+ DRVAUDIOCAPTURESTATE_END
+} DRVAUDIOCAPTURESTATE;
+
+/**
+ * Play state of a stream wrt backend.
+ */
+typedef enum DRVAUDIOPLAYSTATE
+{
+ /** Invalid zero value. */
+ DRVAUDIOPLAYSTATE_INVALID = 0,
+ /** No playback or pre-buffering. */
+ DRVAUDIOPLAYSTATE_NOPLAY,
+ /** Playing w/o any prebuffering. */
+ DRVAUDIOPLAYSTATE_PLAY,
+ /** Parallel pre-buffering prior to a device switch (i.e. we're outputting to
+ * the old device and pre-buffering the same data in parallel). */
+ DRVAUDIOPLAYSTATE_PLAY_PREBUF,
+ /** Initial pre-buffering or the pre-buffering for a device switch (if it
+ * the device setup took less time than filling up the pre-buffer). */
+ DRVAUDIOPLAYSTATE_PREBUF,
+ /** The device initialization is taking too long, pre-buffering wraps around
+ * and drops samples. */
+ DRVAUDIOPLAYSTATE_PREBUF_OVERDUE,
+ /** Same as play-prebuf, but we don't have a working output device any more. */
+ DRVAUDIOPLAYSTATE_PREBUF_SWITCHING,
+ /** Working on committing the pre-buffered data.
+ * We'll typically leave this state immediately and go to PLAY, however if
+ * the backend cannot handle all the pre-buffered data at once, we'll stay
+ * here till it does. */
+ DRVAUDIOPLAYSTATE_PREBUF_COMMITTING,
+ /** End of valid values. */
+ DRVAUDIOPLAYSTATE_END
+} DRVAUDIOPLAYSTATE;
+
+
+/**
+ * Extended stream structure.
+ */
+typedef struct DRVAUDIOSTREAM
+{
+ /** The publicly visible bit. */
+ PDMAUDIOSTREAM Core;
+
+ /** Just an extra magic to verify that we allocated the stream rather than some
+ * faked up stuff from the device (DRVAUDIOSTREAM_MAGIC). */
+ uintptr_t uMagic;
+
+ /** List entry in DRVAUDIO::LstStreams. */
+ RTLISTNODE ListEntry;
+
+ /** Number of references to this stream.
+ * Only can be destroyed when the reference count reaches 0. */
+ uint32_t volatile cRefs;
+ /** Stream status - PDMAUDIOSTREAM_STS_XXX. */
+ uint32_t fStatus;
+
+ /** Data to backend-specific stream data.
+ * This data block will be casted by the backend to access its backend-dependent data.
+ *
+ * That way the backends do not have access to the audio connector's data. */
+ PPDMAUDIOBACKENDSTREAM pBackend;
+
+ /** Set if pfnStreamCreate returned VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED. */
+ bool fNeedAsyncInit;
+ /** The fImmediate parameter value for pfnStreamDestroy. */
+ bool fDestroyImmediate;
+ bool afPadding[2];
+
+ /** Number of (re-)tries while re-initializing the stream. */
+ uint32_t cTriesReInit;
+
+ /** The last backend state we saw.
+ * This is used to detect state changes (for what that is worth). */
+ PDMHOSTAUDIOSTREAMSTATE enmLastBackendState;
+
+ /** The pre-buffering threshold expressed in bytes. */
+ uint32_t cbPreBufThreshold;
+
+ /** The pfnStreamInitAsync request handle. */
+ PRTREQ hReqInitAsync;
+
+ /** The nanosecond timestamp when the stream was started. */
+ uint64_t nsStarted;
+ /** Internal stream position (as per pfnStreamPlay/pfnStreamCapture). */
+ uint64_t offInternal;
+
+ /** Timestamp (in ns) since last trying to re-initialize.
+ * Might be 0 if has not been tried yet. */
+ uint64_t nsLastReInit;
+ /** Timestamp (in ns) since last iteration. */
+ uint64_t nsLastIterated;
+ /** Timestamp (in ns) since last playback / capture. */
+ uint64_t nsLastPlayedCaptured;
+ /** Timestamp (in ns) since last read (input streams) or
+ * write (output streams). */
+ uint64_t nsLastReadWritten;
+
+
+ /** Union for input/output specifics depending on enmDir. */
+ union
+ {
+ /**
+ * The specifics for an audio input stream.
+ */
+ struct
+ {
+ /** The capture state. */
+ DRVAUDIOCAPTURESTATE enmCaptureState;
+
+ struct
+ {
+ /** File for writing non-interleaved captures. */
+ PAUDIOHLPFILE pFileCapture;
+ } Dbg;
+ struct
+ {
+ uint32_t cbBackendReadableBefore;
+ uint32_t cbBackendReadableAfter;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE ProfCapture;
+ STAMPROFILE ProfGetReadable;
+ STAMPROFILE ProfGetReadableBytes;
+#endif
+ } Stats;
+ } In;
+
+ /**
+ * The specifics for an audio output stream.
+ */
+ struct
+ {
+ /** Space for pre-buffering. */
+ uint8_t *pbPreBuf;
+ /** The size of the pre-buffer allocation (in bytes). */
+ uint32_t cbPreBufAlloc;
+ /** The current pre-buffering read offset. */
+ uint32_t offPreBuf;
+ /** Number of bytes we've pre-buffered. */
+ uint32_t cbPreBuffered;
+ /** The play state. */
+ DRVAUDIOPLAYSTATE enmPlayState;
+
+ struct
+ {
+ /** File for writing stream playback. */
+ PAUDIOHLPFILE pFilePlay;
+ } Dbg;
+ struct
+ {
+ uint32_t cbBackendWritableBefore;
+ uint32_t cbBackendWritableAfter;
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE ProfPlay;
+ STAMPROFILE ProfGetWritable;
+ STAMPROFILE ProfGetWritableBytes;
+#endif
+ } Stats;
+ } Out;
+ } RT_UNION_NM(u);
+#ifdef VBOX_WITH_STATISTICS
+ STAMPROFILE StatProfGetState;
+ STAMPROFILE StatXfer;
+#endif
+} DRVAUDIOSTREAM;
+/** Pointer to an extended stream structure. */
+typedef DRVAUDIOSTREAM *PDRVAUDIOSTREAM;
+
+/** Value for DRVAUDIOSTREAM::uMagic (Johann Sebastian Bach). */
+#define DRVAUDIOSTREAM_MAGIC UINT32_C(0x16850331)
+/** Value for DRVAUDIOSTREAM::uMagic after destruction */
+#define DRVAUDIOSTREAM_MAGIC_DEAD UINT32_C(0x17500728)
+
+
+/**
+ * Audio driver configuration data, tweakable via CFGM.
+ */
+typedef struct DRVAUDIOCFG
+{
+ /** PCM properties to use. */
+ PDMAUDIOPCMPROPS Props;
+ /** Whether using signed sample data or not.
+ * Needed in order to know whether there is a custom value set in CFGM or not.
+ * By default set to UINT8_MAX if not set to a custom value. */
+ uint8_t uSigned;
+ /** Whether swapping endianess of sample data or not.
+ * Needed in order to know whether there is a custom value set in CFGM or not.
+ * By default set to UINT8_MAX if not set to a custom value. */
+ uint8_t uSwapEndian;
+ /** Configures the period size (in ms).
+ * This value reflects the time in between each hardware interrupt on the
+ * backend (host) side. */
+ uint32_t uPeriodSizeMs;
+ /** Configures the (ring) buffer size (in ms). Often is a multiple of uPeriodMs. */
+ uint32_t uBufferSizeMs;
+ /** Configures the pre-buffering size (in ms).
+ * Time needed in buffer before the stream becomes active (pre buffering).
+ * The bigger this value is, the more latency for the stream will occur.
+ * Set to 0 to disable pre-buffering completely.
+ * By default set to UINT32_MAX if not set to a custom value. */
+ uint32_t uPreBufSizeMs;
+ /** The driver's debugging configuration. */
+ struct
+ {
+ /** Whether audio debugging is enabled or not. */
+ bool fEnabled;
+ /** Where to store the debugging files. */
+ char szPathOut[RTPATH_MAX];
+ } Dbg;
+} DRVAUDIOCFG;
+/** Pointer to tweakable audio configuration. */
+typedef DRVAUDIOCFG *PDRVAUDIOCFG;
+/** Pointer to const tweakable audio configuration. */
+typedef DRVAUDIOCFG const *PCDRVAUDIOCFG;
+
+
+/**
+ * Audio driver instance data.
+ *
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVAUDIO
+{
+ /** Read/Write critical section for guarding changes to pHostDrvAudio and
+ * BackendCfg during deteach/attach. Mostly taken in shared mode.
+ * @note Locking order: Must be entered after CritSectGlobals.
+ * @note Locking order: Must be entered after PDMAUDIOSTREAM::CritSect. */
+ RTCRITSECTRW CritSectHotPlug;
+ /** Critical section for protecting:
+ * - LstStreams
+ * - cStreams
+ * - In.fEnabled
+ * - In.cStreamsFree
+ * - Out.fEnabled
+ * - Out.cStreamsFree
+ * @note Locking order: Must be entered before PDMAUDIOSTREAM::CritSect.
+ * @note Locking order: Must be entered before CritSectHotPlug. */
+ RTCRITSECTRW CritSectGlobals;
+ /** List of audio streams (DRVAUDIOSTREAM). */
+ RTLISTANCHOR LstStreams;
+ /** Number of streams in the list. */
+ size_t cStreams;
+ struct
+ {
+ /** Whether this driver's input streams are enabled or not.
+ * This flag overrides all the attached stream statuses. */
+ bool fEnabled;
+ /** Max. number of free input streams.
+ * UINT32_MAX for unlimited streams. */
+ uint32_t cStreamsFree;
+ } In;
+ struct
+ {
+ /** Whether this driver's output streams are enabled or not.
+ * This flag overrides all the attached stream statuses. */
+ bool fEnabled;
+ /** Max. number of free output streams.
+ * UINT32_MAX for unlimited streams. */
+ uint32_t cStreamsFree;
+ } Out;
+
+ /** Audio configuration settings retrieved from the backend.
+ * The szName field is used for the DriverName config value till we get the
+ * authoritative name from the backend (only for logging). */
+ PDMAUDIOBACKENDCFG BackendCfg;
+ /** Our audio connector interface. */
+ PDMIAUDIOCONNECTOR IAudioConnector;
+ /** Interface used by the host backend. */
+ PDMIHOSTAUDIOPORT IHostAudioPort;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to audio driver below us. */
+ PPDMIHOSTAUDIO pHostDrvAudio;
+
+ /** Request pool if the backend needs it for async stream creation. */
+ RTREQPOOL hReqPool;
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /** Handle to the timer for delayed re-enumeration of backend devices. */
+ TMTIMERHANDLE hEnumTimer;
+ /** Unique name for the the disable-iteration timer. */
+ char szEnumTimerName[24];
+#endif
+
+ /** Input audio configuration values (static). */
+ DRVAUDIOCFG CfgIn;
+ /** Output audio configuration values (static). */
+ DRVAUDIOCFG CfgOut;
+
+ STAMCOUNTER StatTotalStreamsCreated;
+} DRVAUDIO;
+/** Pointer to the instance data of an audio driver. */
+typedef DRVAUDIO *PDRVAUDIO;
+/** Pointer to const instance data of an audio driver. */
+typedef DRVAUDIO const *PCDRVAUDIO;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd);
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx);
+#endif
+static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx);
+static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy);
+static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx);
+
+
+/** Buffer size for drvAudioStreamStatusToStr. */
+# define DRVAUDIO_STATUS_STR_MAX sizeof("BACKEND_CREATED BACKEND_READY ENABLED PAUSED PENDING_DISABLED NEED_REINIT 0x12345678")
+
+/**
+ * Converts an audio stream status to a string.
+ *
+ * @returns pszDst
+ * @param pszDst Buffer to convert into, at least minimum size is
+ * DRVAUDIO_STATUS_STR_MAX.
+ * @param fStatus Stream status flags to convert.
+ */
+static const char *drvAudioStreamStatusToStr(char pszDst[DRVAUDIO_STATUS_STR_MAX], uint32_t fStatus)
+{
+ static const struct
+ {
+ const char *pszMnemonic;
+ uint32_t cchMnemnonic;
+ uint32_t fFlag;
+ } s_aFlags[] =
+ {
+ { RT_STR_TUPLE("BACKEND_CREATED "), PDMAUDIOSTREAM_STS_BACKEND_CREATED },
+ { RT_STR_TUPLE("BACKEND_READY "), PDMAUDIOSTREAM_STS_BACKEND_READY },
+ { RT_STR_TUPLE("ENABLED "), PDMAUDIOSTREAM_STS_ENABLED },
+ { RT_STR_TUPLE("PAUSED "), PDMAUDIOSTREAM_STS_PAUSED },
+ { RT_STR_TUPLE("PENDING_DISABLE "), PDMAUDIOSTREAM_STS_PENDING_DISABLE },
+ { RT_STR_TUPLE("NEED_REINIT "), PDMAUDIOSTREAM_STS_NEED_REINIT },
+ };
+ if (!fStatus)
+ strcpy(pszDst, "NONE");
+ else
+ {
+ char *psz = pszDst;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
+ if (fStatus & s_aFlags[i].fFlag)
+ {
+ memcpy(psz, s_aFlags[i].pszMnemonic, s_aFlags[i].cchMnemnonic);
+ psz += s_aFlags[i].cchMnemnonic;
+ fStatus &= ~s_aFlags[i].fFlag;
+ if (!fStatus)
+ break;
+ }
+ if (fStatus == 0)
+ psz[-1] = '\0';
+ else
+ psz += RTStrPrintf(psz, DRVAUDIO_STATUS_STR_MAX - (psz - pszDst), "%#x", fStatus);
+ Assert((uintptr_t)(psz - pszDst) <= DRVAUDIO_STATUS_STR_MAX);
+ }
+ return pszDst;
+}
+
+
+/**
+ * Get play state name string.
+ */
+static const char *drvAudioPlayStateName(DRVAUDIOPLAYSTATE enmState)
+{
+ switch (enmState)
+ {
+ case DRVAUDIOPLAYSTATE_INVALID: return "INVALID";
+ case DRVAUDIOPLAYSTATE_NOPLAY: return "NOPLAY";
+ case DRVAUDIOPLAYSTATE_PLAY: return "PLAY";
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF: return "PLAY_PREBUF";
+ case DRVAUDIOPLAYSTATE_PREBUF: return "PREBUF";
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE: return "PREBUF_OVERDUE";
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING: return "PREBUF_SWITCHING";
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: return "PREBUF_COMMITTING";
+ case DRVAUDIOPLAYSTATE_END:
+ break;
+ }
+ return "BAD";
+}
+
+#ifdef LOG_ENABLED
+/**
+ * Get capture state name string.
+ */
+static const char *drvAudioCaptureStateName(DRVAUDIOCAPTURESTATE enmState)
+{
+ switch (enmState)
+ {
+ case DRVAUDIOCAPTURESTATE_INVALID: return "INVALID";
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE: return "NO_CAPTURE";
+ case DRVAUDIOCAPTURESTATE_CAPTURING: return "CAPTURING";
+ case DRVAUDIOCAPTURESTATE_PREBUF: return "PREBUF";
+ case DRVAUDIOCAPTURESTATE_END:
+ break;
+ }
+ return "BAD";
+}
+#endif
+
+/**
+ * Checks if the stream status is one that can be read from.
+ *
+ * @returns @c true if ready to be read from, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanRead)!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusCanRead(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_PAUSED
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+/**
+ * Checks if the stream status is one that can be written to.
+ *
+ * @returns @c true if ready to be written to, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses (use PDMAudioStrmStatusBackendCanWrite)!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusCanWrite(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_PAUSED
+ | PDMAUDIOSTREAM_STS_PENDING_DISABLE
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+/**
+ * Checks if the stream status is a ready-to-operate one.
+ *
+ * @returns @c true if ready to operate, @c false if not.
+ * @param fStatus Stream status to evaluate, PDMAUDIOSTREAM_STS_XXX.
+ * @note Not for backend statuses!
+ */
+DECLINLINE(bool) PDMAudioStrmStatusIsReady(uint32_t fStatus)
+{
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(fStatus);
+ AssertReturn(!(fStatus & ~PDMAUDIOSTREAM_STS_VALID_MASK), false);
+ return (fStatus & ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED
+ | PDMAUDIOSTREAM_STS_NEED_REINIT))
+ == ( PDMAUDIOSTREAM_STS_BACKEND_CREATED
+ | PDMAUDIOSTREAM_STS_ENABLED);
+}
+
+
+/**
+ * Wrapper around PDMIHOSTAUDIO::pfnStreamGetStatus and checks the result.
+ *
+ * @returns A PDMHOSTAUDIOSTREAMSTATE value.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to get the backend status for.
+ */
+DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendState(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ if (pThis->pHostDrvAudio)
+ {
+ /* Don't call if the backend wasn't created for this stream (disabled). */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ AssertPtrReturn(pThis->pHostDrvAudio->pfnStreamGetState, PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING);
+ PDMHOSTAUDIOSTREAMSTATE enmState = pThis->pHostDrvAudio->pfnStreamGetState(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ Log9Func(("%s: %s\n", pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmState) ));
+ Assert( enmState > PDMHOSTAUDIOSTREAMSTATE_INVALID
+ && enmState < PDMHOSTAUDIOSTREAMSTATE_END
+ && (enmState != PDMHOSTAUDIOSTREAMSTATE_DRAINING || pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT));
+ return enmState;
+ }
+ }
+ Log9Func(("%s: not-working\n", pStreamEx->Core.Cfg.szName));
+ return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+}
+
+
+/**
+ * Worker for drvAudioStreamProcessBackendStateChange that completes draining.
+ */
+DECLINLINE(void) drvAudioStreamProcessBackendStateChangeWasDraining(PDRVAUDIOSTREAM pStreamEx)
+{
+ Log(("drvAudioStreamProcessBackendStateChange: Stream '%s': Done draining - disabling stream.\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ drvAudioStreamResetInternal(pStreamEx);
+}
+
+
+/**
+ * Processes backend state change.
+ *
+ * @returns the new state value.
+ */
+static PDMHOSTAUDIOSTREAMSTATE drvAudioStreamProcessBackendStateChange(PDRVAUDIOSTREAM pStreamEx,
+ PDMHOSTAUDIOSTREAMSTATE enmNewState,
+ PDMHOSTAUDIOSTREAMSTATE enmOldState)
+{
+ PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir;
+#ifdef LOG_ENABLED
+ DRVAUDIOPLAYSTATE const enmPlayState = enmDir == PDMAUDIODIR_OUT
+ ? pStreamEx->Out.enmPlayState : DRVAUDIOPLAYSTATE_INVALID;
+ DRVAUDIOCAPTURESTATE const enmCaptureState = enmDir == PDMAUDIODIR_IN
+ ? pStreamEx->In.enmCaptureState : DRVAUDIOCAPTURESTATE_INVALID;
+#endif
+ Assert(enmNewState != enmOldState);
+ Assert(enmOldState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmOldState < PDMHOSTAUDIOSTREAMSTATE_END);
+ AssertReturn(enmNewState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmNewState < PDMHOSTAUDIOSTREAMSTATE_END, enmOldState);
+
+ /*
+ * Figure out what happend and how that reflects on the playback state and stuff.
+ */
+ switch (enmNewState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ /* Guess we're switching device. Nothing to do because the backend will tell us, right? */
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ /* The stream has stopped working or is inactive. Switch stop any draining & to noplay mode. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
+ if (enmDir == PDMAUDIODIR_OUT)
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ else
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE;
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ switch (enmOldState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ /* Should be taken care of elsewhere, so do nothing. */
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ /* Go back to pre-buffering/playing depending on whether it is enabled
+ or not, resetting the stream state. */
+ drvAudioStreamResetInternal(pStreamEx);
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ /* Complete the draining. May race the iterate code. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
+ break;
+
+ /* no default: */
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY: /* impossible */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ break;
+
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ /* We do all we need to do when issuing the DRAIN command. */
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ break;
+
+ /* no default: */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+
+ if (enmDir == PDMAUDIODIR_OUT)
+ LogFunc(("Output stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmOldState), drvAudioPlayStateName(enmPlayState),
+ PDMHostAudioStreamStateGetName(enmNewState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ else
+ LogFunc(("Input stream '%s': %s/%s -> %s/%s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmOldState), drvAudioCaptureStateName(enmCaptureState),
+ PDMHostAudioStreamStateGetName(enmNewState), drvAudioCaptureStateName(pStreamEx->In.enmCaptureState) ));
+
+ pStreamEx->enmLastBackendState = enmNewState;
+ return enmNewState;
+}
+
+
+/**
+ * This gets the backend state and handles changes compared to
+ * DRVAUDIOSTREAM::enmLastBackendState (updated).
+ *
+ * @returns A PDMHOSTAUDIOSTREAMSTATE value.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to get the backend status for.
+ */
+DECLINLINE(PDMHOSTAUDIOSTREAMSTATE) drvAudioStreamGetBackendStateAndProcessChanges(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if (pStreamEx->enmLastBackendState == enmBackendState)
+ return enmBackendState;
+ return drvAudioStreamProcessBackendStateChange(pStreamEx, enmBackendState, pStreamEx->enmLastBackendState);
+}
+
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+/**
+ * Enumerates all host audio devices.
+ *
+ * This functionality might not be implemented by all backends and will return
+ * VERR_NOT_SUPPORTED if not being supported.
+ *
+ * @note Must not hold the driver's critical section!
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance to be called.
+ * @param fLog Whether to print the enumerated device to the release log or not.
+ * @param pDevEnum Where to store the device enumeration.
+ *
+ * @remarks This is currently ONLY used for release logging.
+ */
+static DECLCALLBACK(int) drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum)
+{
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ int rc;
+
+ /*
+ * If the backend supports it, do a device enumeration.
+ */
+ if (pThis->pHostDrvAudio->pfnGetDevices)
+ {
+ PDMAUDIOHOSTENUM DevEnum;
+ rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
+ if (RT_SUCCESS(rc))
+ {
+ if (fLog)
+ {
+ LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->BackendCfg.szName));
+
+ PPDMAUDIOHOSTDEV pDev;
+ RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)
+ {
+ char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];
+ LogRel(("Audio: Device '%s':\n"
+ "Audio: ID = %s\n"
+ "Audio: Usage = %s\n"
+ "Audio: Flags = %s\n"
+ "Audio: Input channels = %RU8\n"
+ "Audio: Output channels = %RU8\n",
+ pDev->pszName, pDev->pszId ? pDev->pszId : "",
+ PDMAudioDirGetName(pDev->enmUsage), PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags),
+ pDev->cMaxInputChannels, pDev->cMaxOutputChannels));
+ }
+ }
+
+ if (pDevEnum)
+ rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);
+
+ PDMAudioHostEnumDelete(&DevEnum);
+ }
+ else
+ {
+ if (fLog)
+ LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc));
+ /* Not fatal. */
+ }
+ }
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+
+ if (fLog)
+ LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->BackendCfg.szName));
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+
+/*********************************************************************************************************************************
+* PDMIAUDIOCONNECTOR *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
+ */
+static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ LogFlowFunc(("enmDir=%s fEnable=%d\n", PDMAudioDirGetName(enmDir), fEnable));
+
+ /*
+ * Figure which status flag variable is being updated.
+ */
+ bool *pfEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ pfEnabled = &pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ pfEnabled = &pThis->Out.fEnabled;
+ else
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ /*
+ * Grab the driver wide lock and check it. Ignore call if no change.
+ */
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+
+ if (fEnable != *pfEnabled)
+ {
+ LogRel(("Audio: %s %s for driver '%s'\n",
+ fEnable ? "Enabling" : "Disabling", PDMAudioDirGetName(enmDir), pThis->BackendCfg.szName));
+
+ /*
+ * When enabling, we must update flag before calling drvAudioStreamControlInternalBackend.
+ */
+ if (fEnable)
+ *pfEnabled = true;
+
+ /*
+ * Update the backend status for the streams in the given direction.
+ *
+ * The pThis->Out.fEnable / pThis->In.fEnable status flags only reflect in the
+ * direction of the backend, drivers and devices above us in the chain does not
+ * know about this. When disabled playback goes to /dev/null and we capture
+ * only silence. This means pStreamEx->fStatus holds the nominal status
+ * and we'll use it to restore the operation. (See also @bugref{9882}.)
+ *
+ * The DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION build time define
+ * controls how this is implemented.
+ */
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ /** @todo duplex streams */
+ if (pStreamEx->Core.Cfg.enmDir == enmDir)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * When (re-)enabling a stream, clear the disabled warning bit again.
+ */
+ if (fEnable)
+ pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
+
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /*
+ * When enabling, we must make sure the stream has been created with the
+ * backend before enabling and maybe pausing it. When disabling we must
+ * destroy the stream. Paused includes enabled, as does draining, but we
+ * only want the former.
+ */
+#else
+ /*
+ * We don't need to do anything unless the stream is enabled.
+ * Paused includes enabled, as does draining, but we only want the former.
+ */
+#endif
+ uint32_t const fStatus = pStreamEx->fStatus;
+
+#ifndef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+#endif
+ {
+ const char *pszOperation;
+ int rc2;
+ if (fEnable)
+ {
+ if (!(fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /* The backend shouldn't have been created, so do that before enabling
+ and possibly pausing the stream. */
+ if (!(fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ rc2 = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ else
+ rc2 = VINF_SUCCESS;
+ pszOperation = "re-init";
+ if (RT_SUCCESS(rc2) && (fStatus & PDMAUDIOSTREAM_STS_ENABLED))
+#endif
+ {
+ /** @todo r=bird: We need to redo pre-buffering OR switch to
+ * DRVAUDIOPLAYSTATE_PREBUF_SWITCHING playback mode when disabling
+ * output streams. The former is preferred if associated with
+ * reporting the stream as INACTIVE. */
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ pszOperation = "enable";
+ if (RT_SUCCESS(rc2) && (fStatus & PDMAUDIOSTREAM_STS_PAUSED))
+ {
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ pszOperation = "pause";
+ }
+ }
+ }
+ else
+ {
+ rc2 = VINF_SUCCESS;
+ pszOperation = NULL;
+ }
+ }
+ else
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ rc2 = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+ else
+ rc2 = VINF_SUCCESS;
+ pszOperation = "destroy";
+#else
+ rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ pszOperation = "disable";
+#endif
+ }
+ if (RT_FAILURE(rc2))
+ {
+ LogRel(("Audio: Failed to %s %s stream '%s': %Rrc\n",
+ pszOperation, PDMAudioDirGetName(enmDir), pStreamEx->Core.Cfg.szName, rc2));
+ if (RT_SUCCESS(rc))
+ rc = rc2; /** @todo r=bird: This isn't entirely helpful to the caller since we'll update the status
+ * regardless of the status code we return. And anyway, there is nothing that can be done
+ * about individual stream by the caller... */
+ }
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ }
+
+ /*
+ * When disabling, we must update the status flag after the
+ * drvAudioStreamControlInternalBackend(DISABLE) calls.
+ */
+ *pfEnabled = fEnable;
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
+ */
+static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, false);
+
+ bool fEnabled;
+ if (enmDir == PDMAUDIODIR_IN)
+ fEnabled = pThis->In.fEnabled;
+ else if (enmDir == PDMAUDIODIR_OUT)
+ fEnabled = pThis->Out.fEnabled;
+ else
+ AssertFailedStmt(fEnabled = false);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ return fEnabled;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
+ else
+ rc = VERR_PDM_NO_ATTACHED_DRIVER;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ PDMAUDIOBACKENDSTS fBackendStatus;
+ if (pThis->pHostDrvAudio)
+ {
+ if (pThis->pHostDrvAudio->pfnGetStatus)
+ fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
+ else
+ fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN;
+ }
+ else
+ fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("LEAVE - %#x\n", fBackendStatus));
+ return fBackendStatus;
+}
+
+
+/**
+ * Frees an audio stream and its allocated resources.
+ *
+ * @param pStreamEx Audio stream to free. After this call the pointer will
+ * not be valid anymore.
+ */
+static void drvAudioStreamFree(PDRVAUDIOSTREAM pStreamEx)
+{
+ if (pStreamEx)
+ {
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+ Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ pStreamEx->Core.uMagic = ~PDMAUDIOSTREAM_MAGIC;
+ pStreamEx->pBackend = NULL;
+ pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC_DEAD;
+
+ RTCritSectDelete(&pStreamEx->Core.CritSect);
+
+ RTMemFree(pStreamEx);
+ }
+}
+
+
+/**
+ * Adjusts the request stream configuration, applying our settings.
+ *
+ * This also does some basic validations.
+ *
+ * Used by both the stream creation and stream configuration hinting code.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pCfg The configuration that should be adjusted.
+ * @param pszName Stream name to use when logging warnings and errors.
+ */
+static int drvAudioStreamAdjustConfig(PCDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg, const char *pszName)
+{
+ /* Get the right configuration for the stream to be created. */
+ PCDRVAUDIOCFG pDrvCfg = pCfg->enmDir == PDMAUDIODIR_IN ? &pThis->CfgIn: &pThis->CfgOut;
+
+ /* Fill in the tweakable parameters into the requested host configuration.
+ * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
+
+ /*
+ * PCM
+ */
+ if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
+ {
+ PDMAudioPropsSetSampleSize(&pCfg->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props));
+ LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n",
+ PDMAudioPropsSampleSize(&pCfg->Props), pszName));
+ }
+
+ if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.uHz = pDrvCfg->Props.uHz;
+ LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfg->Props.uHz, pszName));
+ }
+
+ if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.fSigned = RT_BOOL(pDrvCfg->uSigned);
+ LogRel2(("Audio: Using custom %s sample format for stream '%s'\n",
+ pCfg->Props.fSigned ? "signed" : "unsigned", pszName));
+ }
+
+ if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */
+ {
+ pCfg->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian);
+ LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n",
+ pCfg->Props.fSwapEndian ? "swapped" : "original", pszName));
+ }
+
+ if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
+ {
+ PDMAudioPropsSetChannels(&pCfg->Props, PDMAudioPropsChannels(&pDrvCfg->Props));
+ LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName));
+ }
+
+ /* Validate PCM properties. */
+ if (!AudioHlpPcmPropsAreValidAndSupported(&pCfg->Props))
+ {
+ LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Buffer size
+ */
+ const char *pszWhat = "device-specific";
+ if (pDrvCfg->uBufferSizeMs)
+ {
+ pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uBufferSizeMs);
+ pszWhat = "custom";
+ }
+
+ if (!pCfg->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */
+ {
+ pCfg->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfg->Props, 300 /*ms*/);
+ pszWhat = "default";
+ }
+
+ LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize),
+ pCfg->Backend.cFramesBufferSize, pszName));
+
+ /*
+ * Period size
+ */
+ pszWhat = "device-specific";
+ if (pDrvCfg->uPeriodSizeMs)
+ {
+ pCfg->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPeriodSizeMs);
+ pszWhat = "custom";
+ }
+
+ if (!pCfg->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */
+ {
+ pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 4;
+ pszWhat = "default";
+ }
+
+ if (pCfg->Backend.cFramesPeriod >= pCfg->Backend.cFramesBufferSize / 2)
+ {
+ LogRel(("Audio: Warning! Stream '%s': The stream period size (%RU64ms, %s) cannot be more than half the buffer size (%RU64ms)!\n",
+ pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod), pszWhat,
+ PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize)));
+ pCfg->Backend.cFramesPeriod = pCfg->Backend.cFramesBufferSize / 2;
+ }
+
+ LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPeriod),
+ pCfg->Backend.cFramesPeriod, pszName));
+
+ /*
+ * Pre-buffering size
+ */
+ pszWhat = "device-specific";
+ if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
+ {
+ pCfg->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfg->Props, pDrvCfg->uPreBufSizeMs);
+ pszWhat = "custom";
+ }
+ else /* No, then either use the default or device-specific settings (if any). */
+ {
+ if (pCfg->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
+ {
+ /* Pre-buffer 50% for both output & input. Capping both at 200ms.
+ The 50% reasoning being that we need to have sufficient slack space
+ in both directions as the guest DMA timer might be delayed by host
+ scheduling as well as sped up afterwards because of TM catch-up. */
+ uint32_t const cFramesMax = PDMAudioPropsMilliToFrames(&pCfg->Props, 200);
+ pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize / 2;
+ pCfg->Backend.cFramesPreBuffering = RT_MIN(pCfg->Backend.cFramesPreBuffering, cFramesMax);
+ pszWhat = "default";
+ }
+ }
+
+ if (pCfg->Backend.cFramesPreBuffering >= pCfg->Backend.cFramesBufferSize)
+ {
+ LogRel(("Audio: Warning! Stream '%s': Pre-buffering (%RU64ms, %s) cannot equal or exceed the buffer size (%RU64ms)!\n",
+ pszName, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesBufferSize), pszWhat,
+ PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering) ));
+ pCfg->Backend.cFramesPreBuffering = pCfg->Backend.cFramesBufferSize - 1;
+ }
+
+ LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n",
+ pszWhat, PDMAudioPropsFramesToMilli(&pCfg->Props, pCfg->Backend.cFramesPreBuffering),
+ pCfg->Backend.cFramesPreBuffering, pszName));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker thread function for drvAudioStreamConfigHint that's used when
+ * PDMAUDIOBACKEND_F_ASYNC_HINT is in effect.
+ */
+static DECLCALLBACK(void) drvAudioStreamConfigHintWorker(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg)
+{
+ LogFlowFunc(("pThis=%p pCfg=%p\n", pThis, pCfg));
+ AssertPtrReturnVoid(pCfg);
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ PPDMIHOSTAUDIO const pHostDrvAudio = pThis->pHostDrvAudio;
+ if (pHostDrvAudio)
+ {
+ AssertPtr(pHostDrvAudio->pfnStreamConfigHint);
+ if (pHostDrvAudio->pfnStreamConfigHint)
+ pHostDrvAudio->pfnStreamConfigHint(pHostDrvAudio, pCfg);
+ }
+ PDMAudioStrmCfgFree(pCfg);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Checks whether a given stream direction is enabled (permitted) or not.
+ *
+ * Currently there are only per-direction enabling/disabling of audio streams.
+ * This lets a user disabling input so it an untrusted VM cannot listen in
+ * without the user explicitly allowing it, or disable output so it won't
+ * disturb your and cannot communicate with other VMs or machines
+ *
+ * See @bugref{9882}.
+ *
+ * @retval true if the stream configuration is enabled/allowed.
+ * @retval false if not permitted.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param enmDir The stream direction to check.
+ */
+DECLINLINE(bool) drvAudioStreamIsDirectionEnabled(PDRVAUDIO pThis, PDMAUDIODIR enmDir)
+{
+ switch (enmDir)
+ {
+ case PDMAUDIODIR_IN:
+ return pThis->In.fEnabled;
+ case PDMAUDIODIR_OUT:
+ return pThis->Out.fEnabled;
+ case PDMAUDIODIR_DUPLEX:
+ return pThis->Out.fEnabled && pThis->In.fEnabled;
+ default:
+ AssertFailedReturn(false);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint}
+ */
+static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT);
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Don't do anything unless the backend has a pfnStreamConfigHint method
+ * and the direction is currently enabled.
+ */
+ if ( pThis->pHostDrvAudio
+ && pThis->pHostDrvAudio->pfnStreamConfigHint)
+ {
+ if (drvAudioStreamIsDirectionEnabled(pThis, pCfg->enmDir))
+ {
+ /*
+ * Adjust the configuration (applying out settings) then call the backend driver.
+ */
+ rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName);
+ AssertLogRelRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_CALLBACK_RETURN;
+ if (pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_HINT)
+ {
+ PPDMAUDIOSTREAMCFG pDupCfg = PDMAudioStrmCfgDup(pCfg);
+ if (pDupCfg)
+ {
+ rc = RTReqPoolCallVoidNoWait(pThis->hReqPool, (PFNRT)drvAudioStreamConfigHintWorker, 2, pThis, pDupCfg);
+ if (RT_SUCCESS(rc))
+ LogFlowFunc(("Asynchronous call running on worker thread.\n"));
+ else
+ PDMAudioStrmCfgFree(pDupCfg);
+ }
+ }
+ if (RT_FAILURE_NP(rc))
+ {
+ LogFlowFunc(("Doing synchronous call...\n"));
+ pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg);
+ }
+ }
+ }
+ else
+ LogFunc(("Ignoring hint because direction is not currently enabled\n"));
+ }
+ else
+ LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n"));
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+}
+
+
+/**
+ * Common worker for synchronizing the ENABLED and PAUSED status bits with the
+ * backend after it becomes ready.
+ *
+ * Used by async init and re-init.
+ *
+ * @note Is sometimes called w/o having entered DRVAUDIO::CritSectHotPlug.
+ * Caller must however own the stream critsect.
+ */
+static int drvAudioStreamUpdateBackendOnStatus(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, const char *pszWhen)
+{
+ int rc = VINF_SUCCESS;
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ if (RT_SUCCESS(rc))
+ {
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_FAILURE(rc))
+ LogRelMax(64, ("Audio: Failed to pause stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc));
+ }
+ }
+ else
+ LogRelMax(64, ("Audio: Failed to enable stream '%s' after %s: %Rrc\n", pStreamEx->Core.Cfg.szName, pszWhen, rc));
+ }
+ return rc;
+}
+
+
+/**
+ * For performing PDMIHOSTAUDIO::pfnStreamInitAsync on a worker thread.
+ *
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream. One reference for us to release.
+ */
+static DECLCALLBACK(void) drvAudioStreamInitAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ LogFlow(("pThis=%p pStreamEx=%p (%s)\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName));
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturnVoid(rc);
+
+ /*
+ * Do the init job.
+ */
+ bool fDestroyed;
+ PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtr(pIHostDrvAudio);
+ if (pIHostDrvAudio && pIHostDrvAudio->pfnStreamInitAsync)
+ {
+ fDestroyed = pStreamEx->cRefs <= 1;
+ rc = pIHostDrvAudio->pfnStreamInitAsync(pIHostDrvAudio, pStreamEx->pBackend, fDestroyed);
+ LogFlow(("pfnStreamInitAsync returns %Rrc (on %p, fDestroyed=%d)\n", rc, pStreamEx, fDestroyed));
+ }
+ else
+ {
+ fDestroyed = true;
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * On success, update the backend on the stream status and mark it ready for business.
+ */
+ if (RT_SUCCESS(rc) && !fDestroyed)
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Update the backend state.
+ */
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY; /* before the backend control call! */
+
+ rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "asynchronous initialization completed");
+
+ /*
+ * Modify the play state if output stream.
+ */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ switch (enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ break;
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ break;
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ break;
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ break; /* possible race here, so don't assert. */
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ AssertFailedBreak();
+ /* no default */
+ case DRVAUDIOPLAYSTATE_END:
+ case DRVAUDIOPLAYSTATE_INVALID:
+ break;
+ }
+ LogFunc(("enmPlayState: %s -> %s\n", drvAudioPlayStateName(enmPlayState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ }
+
+ /*
+ * Update the last backend state.
+ */
+ pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ /*
+ * Don't quite know what to do on failure...
+ */
+ else if (!fDestroyed)
+ {
+ LogRelMax(64, ("Audio: Failed to initialize stream '%s': %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ }
+
+ /*
+ * Release the request handle, must be done while inside the critical section.
+ */
+ if (pStreamEx->hReqInitAsync != NIL_RTREQ)
+ {
+ LogFlowFunc(("Releasing hReqInitAsync=%p\n", pStreamEx->hReqInitAsync));
+ RTReqRelease(pStreamEx->hReqInitAsync);
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Release our stream reference.
+ */
+ uint32_t cRefs = drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ LogFlowFunc(("returns (fDestroyed=%d, cRefs=%u)\n", fDestroyed, cRefs)); RT_NOREF(cRefs);
+}
+
+
+/**
+ * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that
+ * creates the backend (host driver) side of an audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to create backend for. The Core.Cfg field
+ * contains the requested configuration when we're called
+ * and the actual configuration when successfully
+ * returning.
+ *
+ * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):
+ * - per global extra-data
+ * - per-VM extra-data
+ * - requested configuration (by pCfgReq)
+ * - default value
+ */
+static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertMsg((pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED) == 0,
+ ("Stream '%s' already initialized in backend\n", pStreamEx->Core.Cfg.szName));
+
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /*
+ * Check if the stream direction is enabled (permitted).
+ */
+ if (!drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir))
+ {
+ LogFunc(("Stream direction is disbled, returning w/o doing anything\n"));
+ return VINF_SUCCESS;
+ }
+#endif
+
+ /*
+ * Adjust the stream config, applying defaults and any overriding settings.
+ */
+ int rc = drvAudioStreamAdjustConfig(pThis, &pStreamEx->Core.Cfg, pStreamEx->Core.Cfg.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ PDMAUDIOSTREAMCFG const CfgReq = pStreamEx->Core.Cfg;
+
+ /*
+ * Call the host driver to create the stream.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ AssertLogRelMsgStmt(RT_VALID_PTR(pThis->pHostDrvAudio),
+ ("Audio: %p\n", pThis->pHostDrvAudio), rc = VERR_PDM_NO_ATTACHED_DRIVER);
+ if (RT_SUCCESS(rc))
+ AssertLogRelMsgStmt(pStreamEx->Core.cbBackend == pThis->BackendCfg.cbStream,
+ ("Audio: Backend changed? cbBackend changed from %#x to %#x\n",
+ pStreamEx->Core.cbBackend, pThis->BackendCfg.cbStream),
+ rc = VERR_STATE_CHANGED);
+ if (RT_SUCCESS(rc))
+ rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, &CfgReq, &pStreamEx->Core.Cfg);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->enmLastBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ AssertLogRelReturn(pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INTERNAL_ERROR_3);
+ AssertLogRelReturn(pStreamEx->pBackend->pStream == &pStreamEx->Core, VERR_INTERNAL_ERROR_3);
+
+ /* Must set the backend-initialized flag now or the backend won't be
+ destroyed (this used to be done at the end of this function, with
+ several possible early return paths before it). */
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_CREATED;
+ }
+ else
+ {
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (rc == VERR_NOT_SUPPORTED)
+ LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.Cfg.szName));
+ else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
+ LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n",
+ pStreamEx->Core.Cfg.szName));
+ else
+ LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+ }
+
+ /* Remember if we need to call pfnStreamInitAsync. */
+ pStreamEx->fNeedAsyncInit = rc == VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED;
+ AssertStmt(rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED || pThis->pHostDrvAudio->pfnStreamInitAsync != NULL,
+ pStreamEx->fNeedAsyncInit = false);
+ AssertMsg( rc != VINF_AUDIO_STREAM_ASYNC_INIT_NEEDED
+ || pStreamEx->enmLastBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING,
+ ("rc=%Rrc %s\n", rc, PDMHostAudioStreamStateGetName(pStreamEx->enmLastBackendState)));
+
+ PPDMAUDIOSTREAMCFG const pCfgAcq = &pStreamEx->Core.Cfg;
+
+ /*
+ * Validate acquired configuration.
+ */
+ char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
+ LogFunc(("Backend returned: %s\n", PDMAudioStrmCfgToString(pCfgAcq, szTmp, sizeof(szTmp)) ));
+ AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq),
+ ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n",
+ pCfgAcq->szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))),
+ VERR_INVALID_PARAMETER);
+
+ /* Let the user know that the backend changed one of the values requested above. */
+ if (pCfgAcq->Backend.cFramesBufferSize != CfgReq.Backend.cFramesBufferSize)
+ LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesBufferSize), CfgReq.Backend.cFramesBufferSize,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
+
+ if (pCfgAcq->Backend.cFramesPeriod != CfgReq.Backend.cFramesPeriod)
+ LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPeriod), CfgReq.Backend.cFramesPeriod,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod));
+
+ /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
+ if (CfgReq.Backend.cFramesPreBuffering)
+ {
+ if (pCfgAcq->Backend.cFramesPreBuffering != CfgReq.Backend.cFramesPreBuffering)
+ LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
+ PDMAudioPropsFramesToMilli(&CfgReq.Props, CfgReq.Backend.cFramesPreBuffering), CfgReq.Backend.cFramesPreBuffering,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+
+ if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize)
+ {
+ pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize;
+ LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+ }
+ }
+ else if (CfgReq.Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
+ {
+ LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pCfgAcq->szName));
+ pCfgAcq->Backend.cFramesPreBuffering = 0;
+ }
+
+ /*
+ * Check if the backend did return sane values and correct if necessary.
+ */
+ uint32_t const cFramesPreBufferingMax = pCfgAcq->Backend.cFramesBufferSize - RT_MIN(16, pCfgAcq->Backend.cFramesBufferSize);
+ if (pCfgAcq->Backend.cFramesPreBuffering > cFramesPreBufferingMax)
+ {
+ LogRel2(("Audio: Warning! Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
+ pCfgAcq->Backend.cFramesPreBuffering, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, cFramesPreBufferingMax));
+ AssertMsgFailed(("cFramesPreBuffering=%#x vs cFramesPreBufferingMax=%#x\n", pCfgAcq->Backend.cFramesPreBuffering, cFramesPreBufferingMax));
+ pCfgAcq->Backend.cFramesPreBuffering = cFramesPreBufferingMax;
+ }
+
+ if (pCfgAcq->Backend.cFramesPeriod > pCfgAcq->Backend.cFramesBufferSize)
+ {
+ LogRel2(("Audio: Warning! Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",
+ pCfgAcq->Backend.cFramesPeriod, pCfgAcq->szName, pCfgAcq->Backend.cFramesBufferSize, pCfgAcq->Backend.cFramesBufferSize / 2));
+ AssertMsgFailed(("cFramesPeriod=%#x vs cFramesBufferSize=%#x\n", pCfgAcq->Backend.cFramesPeriod, pCfgAcq->Backend.cFramesBufferSize));
+ pCfgAcq->Backend.cFramesPeriod = pCfgAcq->Backend.cFramesBufferSize / 2;
+ }
+
+ LogRel2(("Audio: Buffer size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
+ LogRel2(("Audio: Pre-buffering size for stream '%s' is %RU64 ms / %RU32 frames\n", pCfgAcq->szName,
+ PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
+ LogRel2(("Audio: Scheduling hint for stream '%s' is %RU32ms / %RU32 frames\n", pCfgAcq->szName,
+ pCfgAcq->Device.cMsSchedulingHint, PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCfgAcq->Device.cMsSchedulingHint)));
+
+ /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
+ uint32_t const cMsPeriod = PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod);
+ LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n",
+ pCfgAcq->szName, cMsPeriod, pCfgAcq->Backend.cFramesPeriod));
+ /** @todo r=bird: This is probably a misleading/harmless warning as we'd just
+ * have to transfer more each time we move data. The period is generally
+ * pure irrelevant fiction anyway. A more relevant comparison would
+ * be to half the buffer size, i.e. making sure we get scheduled often
+ * enough to keep the buffer at least half full (probably more
+ * sensible if the buffer size was more than 2x scheduling periods). */
+ if ( CfgReq.Device.cMsSchedulingHint /* Any scheduling hint set? */
+ && CfgReq.Device.cMsSchedulingHint > cMsPeriod) /* This might lead to buffer underflows. */
+ LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
+ pCfgAcq->szName, CfgReq.Device.cMsSchedulingHint, cMsPeriod));
+
+ /*
+ * Done, just log the result:
+ */
+ LogFunc(("Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ LogRel2(("Audio: Acquired stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamCreate that initializes the audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to initialize. Caller already set a few fields.
+ * The Core.Cfg field contains the requested configuration
+ * when we're called and the actual configuration when
+ * successfully returning.
+ */
+static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ /*
+ * Init host stream.
+ */
+ pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
+
+ char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
+ LogFunc(("Requested stream config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ LogRel2(("Audio: Creating stream: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+
+ int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Configure host buffers.
+ */
+ Assert(pStreamEx->cbPreBufThreshold == 0);
+ if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0)
+ pStreamEx->cbPreBufThreshold = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ pStreamEx->Core.Cfg.Backend.cFramesPreBuffering);
+
+ /* Allocate space for pre-buffering of output stream w/o mixing buffers. */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ Assert(pStreamEx->Out.cbPreBufAlloc == 0);
+ Assert(pStreamEx->Out.cbPreBuffered == 0);
+ Assert(pStreamEx->Out.offPreBuf == 0);
+ if (pStreamEx->Core.Cfg.Backend.cFramesPreBuffering != 0)
+ {
+ uint32_t cbPreBufAlloc = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ pStreamEx->Core.Cfg.Backend.cFramesBufferSize);
+ cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->cbPreBufThreshold + _8K, _4K), cbPreBufAlloc);
+ cbPreBufAlloc = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbPreBufAlloc);
+ pStreamEx->Out.cbPreBufAlloc = cbPreBufAlloc;
+ pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(cbPreBufAlloc);
+ AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY);
+ }
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY; /* Changed upon enable. */
+ }
+
+ /*
+ * Register statistics.
+ */
+ PPDMDRVINS const pDrvIns = pThis->pDrvIns;
+ /** @todo expose config and more. */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPeriod, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "The size of the backend period (in frames)", "%s/0-HostBackendPeriodSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Backend.cFramesPreBuffering, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Pre-buffer size (in frames)", "%s/0-HostBackendPreBufferSize", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Device.cMsSchedulingHint, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Device DMA scheduling hint (in milliseconds)", "%s/0-DeviceSchedulingHint", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.uHz, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_HZ,
+ "Backend stream frequency", "%s/Hz", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Core.Cfg.Props.cbFrame, STAMTYPE_U8, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Backend frame size", "%s/Framesize", pStreamEx->Core.Cfg.szName);
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer before play", "%s/0-HostBackendBufReadableBefore", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.cbBackendReadableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer after play", "%s/0-HostBackendBufReadableAfter", pStreamEx->Core.Cfg.szName);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfCapture, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamCapture", "%s/ProfStreamCapture", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetReadable", "%s/ProfStreamGetReadable", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.ProfGetReadableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Readable byte stats", "%s/ProfStreamGetReadableBytes", pStreamEx->Core.Cfg.szName);
+#endif
+ }
+ else
+ {
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer before play", "%s/0-HostBackendBufWritableBefore", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Free space in backend buffer after play", "%s/0-HostBackendBufWritableAfter", pStreamEx->Core.Cfg.szName);
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfPlay, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamPlay", "%s/ProfStreamPlay", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritable, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetWritable", "%s/ProfStreamGetWritable", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.ProfGetWritableBytes, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Writeable byte stats", "%s/ProfStreamGetWritableBytes", pStreamEx->Core.Cfg.szName);
+#endif
+ }
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatProfGetState, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_OCCURENCES,
+ "Profiling time spent in StreamGetState", "%s/ProfStreamGetState", pStreamEx->Core.Cfg.szName);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->StatXfer, STAMTYPE_PROFILE, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Byte transfer stats (excluding pre-buffering)", "%s/Transfers", pStreamEx->Core.Cfg.szName);
+#endif
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->offInternal, STAMTYPE_U64, STAMVISIBILITY_USED, STAMUNIT_NONE,
+ "Internal stream offset", "%s/offInternal", pStreamEx->Core.Cfg.szName);
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PCPDMAUDIOSTREAMCFG pCfgReq,
+ PPDMAUDIOSTREAM *ppStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Assert sanity.
+ */
+ AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
+ *ppStream = NULL;
+ LogFlowFunc(("pCfgReq=%s\n", pCfgReq->szName));
+#ifdef LOG_ENABLED
+ PDMAudioStrmCfgLog(pCfgReq);
+#endif
+ AssertReturn(AudioHlpStreamCfgIsValid(pCfgReq), VERR_INVALID_PARAMETER);
+ AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED);
+
+ /*
+ * Grab a free stream count now.
+ */
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+
+ uint32_t * const pcFreeStreams = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.cStreamsFree : &pThis->Out.cStreamsFree;
+ if (*pcFreeStreams > 0)
+ *pcFreeStreams -= 1;
+ else
+ {
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ LogFlowFunc(("Maximum number of host %s streams reached\n", PDMAudioDirGetName(pCfgReq->enmDir) ));
+ return pCfgReq->enmDir == PDMAUDIODIR_IN ? VERR_AUDIO_NO_FREE_INPUT_STREAMS : VERR_AUDIO_NO_FREE_OUTPUT_STREAMS;
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ /*
+ * Get and check the backend size.
+ *
+ * Since we'll have to leave the hot-plug lock before we call the backend,
+ * we'll have revalidate the size at that time.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ size_t const cbHstStrm = pThis->BackendCfg.cbStream;
+ AssertStmt(cbHstStrm >= sizeof(PDMAUDIOBACKENDSTREAM), rc = VERR_OUT_OF_RANGE);
+ AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize common state.
+ */
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64));
+ if (pStreamEx)
+ {
+ rc = RTCritSectInit(&pStreamEx->Core.CritSect); /* (drvAudioStreamFree assumes it's initailized) */
+ if (RT_SUCCESS(rc))
+ {
+ PPDMAUDIOBACKENDSTREAM pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1);
+ pBackend->uMagic = PDMAUDIOBACKENDSTREAM_MAGIC;
+ pBackend->pStream = &pStreamEx->Core;
+
+ pStreamEx->pBackend = pBackend;
+ pStreamEx->Core.Cfg = *pCfgReq;
+ pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm;
+ pStreamEx->fDestroyImmediate = true;
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+ pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC;
+
+ /* Make a unqiue stream name including the host (backend) driver name. */
+ AssertPtr(pThis->pHostDrvAudio);
+ size_t cchName = RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:0",
+ pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : "<NoName>");
+ if (cchName < sizeof(pStreamEx->Core.Cfg.szName))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ bool fDone = true;
+ PDRVAUDIOSTREAM pIt;
+ RTListForEach(&pThis->LstStreams, pIt, DRVAUDIOSTREAM, ListEntry)
+ {
+ if (strcmp(pIt->Core.Cfg.szName, pStreamEx->Core.Cfg.szName) == 0)
+ {
+ RTStrPrintf(pStreamEx->Core.Cfg.szName, RT_ELEMENTS(pStreamEx->Core.Cfg.szName), "[%s] %s:%u",
+ pThis->BackendCfg.szName, pCfgReq->szName[0] != '\0' ? pCfgReq->szName : "<NoName>",
+ i);
+ fDone = false;
+ break;
+ }
+ }
+ if (fDone)
+ break;
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ }
+
+ /*
+ * Try to init the rest.
+ */
+ rc = drvAudioStreamInitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Set initial reference counts. */
+ pStreamEx->cRefs = pStreamEx->fNeedAsyncInit ? 2 : 1;
+
+ /* Add it to the list. */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ RTListAppend(&pThis->LstStreams, &pStreamEx->ListEntry);
+ pThis->cStreams++;
+ STAM_REL_COUNTER_INC(&pThis->StatTotalStreamsCreated);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ /*
+ * Init debug stuff if enabled (ignore failures).
+ */
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->CfgIn.Dbg.fEnabled)
+ AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCapture, pThis->CfgIn.Dbg.szPathOut,
+ "DrvAudioCapture", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props);
+ }
+ else /* Out */
+ {
+ if (pThis->CfgOut.Dbg.fEnabled)
+ AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlay, pThis->CfgOut.Dbg.szPathOut,
+ "DrvAudioPlay", pThis->pDrvIns->iInstance, &pStreamEx->Core.Cfg.Props);
+ }
+
+ /*
+ * Kick off the asynchronous init.
+ */
+ if (!pStreamEx->fNeedAsyncInit)
+ {
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ /* drvAudioStreamInitInternal returns success for disable stream directions w/o actually
+ creating a backend, so we need to check that before marking the backend ready.. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+#endif
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx));
+ }
+
+#ifdef VBOX_STRICT
+ /*
+ * Assert lock order to make sure the lock validator picks up on it.
+ */
+ RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+#endif
+
+ *ppStream = &pStreamEx->Core;
+ LogFlowFunc(("returns VINF_SUCCESS (pStreamEx=%p)\n", pStreamEx));
+ return VINF_SUCCESS;
+ }
+
+ LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc));
+ int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ AssertRC(rc2);
+ drvAudioStreamFree(pStreamEx);
+ }
+ else
+ RTMemFree(pStreamEx);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /*
+ * Give back the stream count, we couldn't use it after all.
+ */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ *pcFreeStreams += 1;
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Calls the backend to give it the chance to destroy its part of the audio stream.
+ *
+ * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and
+ * drvAudioStreamReInitInternal.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Audio stream destruct backend for.
+ */
+static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+
+ int rc = VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ AssertPtr(pStreamEx->pBackend);
+
+ /* Check if the pointer to the host audio driver is still valid.
+ * It can be NULL if we were called in drvAudioDestruct, for example. */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug); /** @todo needed? */
+ if (pThis->pHostDrvAudio)
+ rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend, pStreamEx->fDestroyImmediate);
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY);
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+
+ LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * Uninitializes an audio stream - worker for drvAudioStreamDestroy,
+ * drvAudioDestruct and drvAudioStreamCreate.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Pointer to audio stream to uninitialize.
+ */
+static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertMsgReturn(pStreamEx->cRefs <= 1,
+ ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs),
+ VERR_WRONG_ORDER);
+ LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.Cfg.szName, pStreamEx->cRefs));
+
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ /*
+ * ...
+ */
+ if (pStreamEx->fDestroyImmediate)
+ drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ int rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+
+ /* Free pre-buffer space. */
+ if ( pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT
+ && pStreamEx->Out.pbPreBuf)
+ {
+ RTMemFree(pStreamEx->Out.pbPreBuf);
+ pStreamEx->Out.pbPreBuf = NULL;
+ pStreamEx->Out.cbPreBufAlloc = 0;
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ if (pStreamEx->fStatus != PDMAUDIOSTREAM_STS_NONE)
+ {
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+ LogFunc(("[%s] Warning: Still has %s set when uninitializing\n",
+ pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+ }
+#endif
+ pStreamEx->fStatus = PDMAUDIOSTREAM_STS_NONE;
+ }
+
+ PPDMDRVINS const pDrvIns = pThis->pDrvIns;
+ PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.Cfg.szName);
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ if (pThis->CfgIn.Dbg.fEnabled)
+ {
+ AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCapture);
+ pStreamEx->In.Dbg.pFileCapture = NULL;
+ }
+ }
+ else
+ {
+ Assert(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT);
+ if (pThis->CfgOut.Dbg.fEnabled)
+ {
+ AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlay);
+ pStreamEx->Out.Dbg.pFilePlay = NULL;
+ }
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ LogFlowFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Internal release function.
+ *
+ * @returns New reference count, UINT32_MAX if bad stream.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to reference.
+ * @param fMayDestroy Whether the caller is allowed to implicitly destroy
+ * the stream or not.
+ */
+static uint32_t drvAudioStreamReleaseInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fMayDestroy)
+{
+ AssertPtrReturn(pStreamEx, UINT32_MAX);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
+ Assert(!RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ uint32_t cRefs = ASMAtomicDecU32(&pStreamEx->cRefs);
+ if (cRefs != 0)
+ Assert(cRefs < _1K);
+ else if (fMayDestroy)
+ {
+/** @todo r=bird: Caching one stream in each direction for some time,
+ * depending on the time it took to create it. drvAudioStreamCreate can use it
+ * if the configuration matches, otherwise it'll throw it away. This will
+ * provide a general speedup independ of device (HDA used to do this, but
+ * doesn't) and backend implementation. Ofc, the backend probably needs an
+ * opt-out here. */
+ int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ pThis->In.cStreamsFree++;
+ else /* Out */
+ pThis->Out.cStreamsFree++;
+ pThis->cStreams--;
+
+ RTListNodeRemove(&pStreamEx->ListEntry);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+
+ drvAudioStreamFree(pStreamEx);
+ }
+ else
+ {
+ LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ /** @todo r=bird: What's the plan now? */
+ }
+ }
+ else
+ {
+ cRefs = ASMAtomicIncU32(&pStreamEx->cRefs);
+ AssertFailed();
+ }
+
+ Log12Func(("returns %u (%s)\n", cRefs, cRefs > 0 ? pStreamEx->Core.Cfg.szName : "destroyed"));
+ return cRefs;
+}
+
+
+/**
+ * Asynchronous worker for drvAudioStreamDestroy.
+ *
+ * Does DISABLE and releases reference, possibly destroying the stream.
+ *
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream. One reference for us to release.
+ * @param fImmediate How to treat draining streams.
+ */
+static DECLCALLBACK(void) drvAudioStreamDestroyAsync(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, bool fImmediate)
+{
+ LogFlowFunc(("pThis=%p pStreamEx=%p (%s) fImmediate=%RTbool\n", pThis, pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate));
+#ifdef LOG_ENABLED
+ uint64_t const nsStart = RTTimeNanoTS();
+#endif
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+
+ pStreamEx->fDestroyImmediate = fImmediate; /* Do NOT adjust for draining status, just pass it as-is. CoreAudio needs this. */
+
+ if (!fImmediate && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ LogFlowFunc(("No DISABLE\n"));
+ else
+ {
+ int rc2 = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ LogFlowFunc(("DISABLE done: %Rrc\n", rc2));
+ AssertRC(rc2);
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+
+ LogFlowFunc(("returning (after %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, bool fImmediate)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /* Ignore NULL streams. */
+ if (!pStream)
+ return VINF_SUCCESS;
+
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ LogFlowFunc(("ENTER - %p (%s) fImmediate=%RTbool\n", pStreamEx, pStreamEx->Core.Cfg.szName, fImmediate));
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->pBackend && pStreamEx->pBackend->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * The main difference from a regular release is that this will disable
+ * (or drain if we could) the stream and we can cancel any pending
+ * pfnStreamInitAsync call.
+ */
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ if (pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC)
+ {
+ if (pStreamEx->cRefs > 0 && pStreamEx->cRefs < UINT32_MAX / 4)
+ {
+ char szStatus[DRVAUDIO_STATUS_STR_MAX];
+ LogRel2(("Audio: Destroying stream '%s': cRefs=%u; status: %s; backend: %s; hReqInitAsync=%p\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->cRefs, drvAudioStreamStatusToStr(szStatus, pStreamEx->fStatus),
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)),
+ pStreamEx->hReqInitAsync));
+
+ /* Try cancel pending async init request and release the it. */
+ if (pStreamEx->hReqInitAsync != NIL_RTREQ)
+ {
+ Assert(pStreamEx->cRefs >= 2);
+ int rc2 = RTReqCancel(pStreamEx->hReqInitAsync);
+
+ RTReqRelease(pStreamEx->hReqInitAsync);
+ pStreamEx->hReqInitAsync = NIL_RTREQ;
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /* (exit before releasing the stream to avoid assertion) */
+
+ if (RT_SUCCESS(rc2))
+ {
+ LogFlowFunc(("Successfully cancelled pending pfnStreamInitAsync call (hReqInitAsync=%p).\n",
+ pStreamEx->hReqInitAsync));
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ }
+ else
+ {
+ LogFlowFunc(("Failed to cancel pending pfnStreamInitAsync call (hReqInitAsync=%p): %Rrc\n",
+ pStreamEx->hReqInitAsync, rc2));
+ Assert(rc2 == VERR_RT_REQUEST_STATE);
+ }
+ }
+ else
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Now, if the backend requests asynchronous disabling and destruction
+ * push the disabling and destroying over to a worker thread.
+ *
+ * This is a general offloading feature that all backends should make use of,
+ * however it's rather precarious on macs where stopping an already draining
+ * stream may take 8-10ms which naturally isn't something we should be doing
+ * on an EMT.
+ */
+ if (!(pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY))
+ drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate);
+ else
+ {
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamDestroyAsync, 3, pThis, pStreamEx, fImmediate);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate));
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailedStmt(("%p cRefs=%#x\n", pStreamEx, pStreamEx->cRefs), rc = VERR_CALLER_NO_REFERENCE);
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
+ }
+ }
+ else
+ {
+ AssertLogRelMsgFailedStmt(("%p uMagic=%#x\n", pStreamEx, pStreamEx->uMagic), rc = VERR_INVALID_MAGIC);
+ RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Drops all audio data (and associated state) of a stream.
+ *
+ * Used by drvAudioStreamIterateInternal(), drvAudioStreamResetOnDisable(), and
+ * drvAudioStreamReInitInternal().
+ *
+ * @param pStreamEx Stream to drop data for.
+ */
+static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx)
+{
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ pStreamEx->nsLastIterated = 0;
+ pStreamEx->nsLastPlayedCaptured = 0;
+ pStreamEx->nsLastReadWritten = 0;
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
+ }
+ else
+ pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING;
+}
+
+
+/**
+ * Re-initializes an audio stream with its existing host and guest stream
+ * configuration.
+ *
+ * This might be the case if the backend told us we need to re-initialize
+ * because something on the host side has changed.
+ *
+ * @note Does not touch the stream's status flags.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to re-initialize.
+ */
+static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ char szTmp[RT_MAX(PDMAUDIOSTRMCFGTOSTRING_MAX, DRVAUDIO_STATUS_STR_MAX)];
+ LogFlowFunc(("[%s] status: %s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Destroy and re-create stream on backend side.
+ */
+ if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
+ == (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+
+ int rc = VERR_AUDIO_STREAM_NOT_READY;
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ drvAudioStreamResetInternal(pStreamEx);
+
+ RT_BZERO(pStreamEx->pBackend + 1, pStreamEx->Core.cbBackend - sizeof(*pStreamEx->pBackend));
+
+ rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("Acquired host config: %s\n", PDMAudioStrmCfgToString(&pStreamEx->Core.Cfg, szTmp, sizeof(szTmp)) ));
+ /** @todo Validate (re-)acquired configuration with pStreamEx->Core.Core.Cfg?
+ * drvAudioStreamInitInternal() does some setup and a bunch of
+ * validations + adjustments of the stream config, so this surely is quite
+ * optimistic. */
+ if (true)
+ {
+ /*
+ * Kick off the asynchronous init.
+ */
+ if (!pStreamEx->fNeedAsyncInit)
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ drvAudioStreamRetainInternal(pStreamEx);
+ int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync,
+ RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx);
+ LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
+ AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx));
+ }
+
+ /*
+ * Update the backend on the stream state if it's ready, otherwise
+ * let the worker thread do it after the async init has completed.
+ */
+ if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ == (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "re-initializing");
+ /** @todo not sure if we really need to care about this status code... */
+ }
+ else if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ Assert(pStreamEx->hReqInitAsync != NIL_RTREQ);
+ LogFunc(("Asynchronous stream init (%p) ...\n", pStreamEx->hReqInitAsync));
+ }
+ else
+ {
+ LogRel(("Audio: Re-initializing stream '%s' somehow failed, status: %s\n", pStreamEx->Core.Cfg.szName,
+ drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
+ AssertFailed();
+ rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+ }
+ }
+ }
+ else
+ LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ }
+ else
+ {
+ LogRel(("Audio: Re-initializing stream '%s' failed to destroy previous backend.\n", pStreamEx->Core.Cfg.szName));
+ AssertFailed();
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamReInit}
+ */
+static DECLCALLBACK(int) drvAudioStreamReInit(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT, VERR_INVALID_STATE);
+ LogFlowFunc(("\n"));
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT)
+ {
+ const unsigned cMaxTries = 5;
+ const uint64_t nsNow = RTTimeNanoTS();
+
+ /* Throttle re-initializing streams on failure. */
+ if ( pStreamEx->cTriesReInit < cMaxTries
+ && pStreamEx->hReqInitAsync == NIL_RTREQ
+ && ( pStreamEx->nsLastReInit == 0
+ || nsNow - pStreamEx->nsLastReInit >= RT_NS_1SEC * pStreamEx->cTriesReInit))
+ {
+ rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Remove the pending re-init flag on success. */
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ pStreamEx->nsLastReInit = nsNow;
+ pStreamEx->cTriesReInit++;
+
+ /* Did we exceed our tries re-initializing the stream?
+ * Then this one is dead-in-the-water, so disable it for further use. */
+ if (pStreamEx->cTriesReInit >= cMaxTries)
+ {
+ LogRel(("Audio: Re-initializing stream '%s' exceeded maximum retries (%u), leaving as disabled\n",
+ pStreamEx->Core.Cfg.szName, cMaxTries));
+
+ /* Don't try to re-initialize anymore and mark as disabled. */
+ /** @todo should mark it as not-initialized too, shouldn't we? */
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_NEED_REINIT | PDMAUDIOSTREAM_STS_ENABLED);
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+
+ /* Note: Further writes to this stream go to / will be read from the bit bucket (/dev/null) from now on. */
+ }
+ }
+ }
+ else
+ Log8Func(("cTriesReInit=%d hReqInitAsync=%p nsLast=%RU64 nsNow=%RU64 nsDelta=%RU64\n", pStreamEx->cTriesReInit,
+ pStreamEx->hReqInitAsync, pStreamEx->nsLastReInit, nsNow, nsNow - pStreamEx->nsLastReInit));
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+ }
+ else
+ {
+ AssertFailed();
+ rc = VERR_INVALID_STATE;
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Internal retain function.
+ *
+ * @returns New reference count, UINT32_MAX if bad stream.
+ * @param pStreamEx The stream to reference.
+ */
+static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pStreamEx, UINT32_MAX);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
+
+ uint32_t const cRefs = ASMAtomicIncU32(&pStreamEx->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _1K);
+
+ Log12Func(("returns %u (%s)\n", cRefs, pStreamEx->Core.Cfg.szName));
+ return cRefs;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+ return drvAudioStreamRetainInternal((PDRVAUDIOSTREAM)pStream);
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ return drvAudioStreamReleaseInternal(RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector),
+ (PDRVAUDIOSTREAM)pStream,
+ false /*fMayDestroy*/);
+}
+
+
+/**
+ * Controls a stream's backend.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to control.
+ * @param enmStreamCmd Control command.
+ *
+ * @note Caller has entered the critical section of the stream.
+ * @note Can be called w/o having entered DRVAUDIO::CritSectHotPlug.
+ */
+static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Whether to propagate commands down to the backend.
+ *
+ * 1. If the stream direction is disabled on the driver level, we should
+ * obviously not call the backend. Our stream status will reflect the
+ * actual state so drvAudioEnable() can tell the backend if the user
+ * re-enables the stream direction.
+ *
+ * 2. If the backend hasn't finished initializing yet, don't try call
+ * it to start/stop/pause/whatever the stream. (Better to do it here
+ * than to replicate this in the relevant backends.) When the backend
+ * finish initializing the stream, we'll update it about the stream state.
+ */
+ bool const fDirEnabled = drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir);
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ /* ^^^ (checks pThis->pHostDrvAudio != NULL too) */
+
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+ LogRel2(("Audio: %s stream '%s' backend (%s is %s; status: %s; backend-status: %s)\n",
+ PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir),
+ fDirEnabled ? "enabled" : "disabled", drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus),
+ PDMHostAudioStreamStateGetName(enmBackendState) ));
+
+ if (fDirEnabled)
+ {
+ if ( (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY /* don't really need this check, do we? */)
+ && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) )
+ {
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ rc = pThis->pHostDrvAudio->pfnStreamEnable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ rc = pThis->pHostDrvAudio->pfnStreamDisable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ rc = pThis->pHostDrvAudio->pfnStreamPause(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ rc = pThis->pHostDrvAudio->pfnStreamResume(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ if (pThis->pHostDrvAudio->pfnStreamDrain)
+ rc = pThis->pHostDrvAudio->pfnStreamDrain(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ default:
+ AssertMsgFailedBreakStmt(("Command %RU32 not implemented\n", enmStreamCmd), rc = VERR_INTERNAL_ERROR_2);
+ }
+ if (RT_SUCCESS(rc))
+ Log2Func(("[%s] %s succeeded (%Rrc)\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
+ else
+ {
+ LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
+ if ( rc != VERR_NOT_IMPLEMENTED
+ && rc != VERR_NOT_SUPPORTED
+ && rc != VERR_AUDIO_STREAM_NOT_READY)
+ LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.Cfg.szName, rc));
+ }
+ }
+ else
+ LogFlowFunc(("enmBackendStat(=%s) != OKAY || !(fStatus(=%#x) & BACKEND_READY)\n",
+ PDMHostAudioStreamStateGetName(enmBackendState), pStreamEx->fStatus));
+ }
+ else
+ LogFlowFunc(("fDirEnabled=false\n"));
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ return rc;
+}
+
+
+/**
+ * Resets the given audio stream.
+ *
+ * @param pStreamEx Stream to reset.
+ */
+static void drvAudioStreamResetOnDisable(PDRVAUDIOSTREAM pStreamEx)
+{
+ drvAudioStreamResetInternal(pStreamEx);
+
+ LogFunc(("[%s]\n", pStreamEx->Core.Cfg.szName));
+
+ pStreamEx->fStatus &= PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY;
+ pStreamEx->Core.fWarningsShown = PDMAUDIOSTREAM_WARN_FLAGS_NONE;
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Reset statistics.
+ */
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN)
+ {
+ }
+ else if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ }
+ else
+ AssertFailed();
+#endif
+}
+
+
+/**
+ * Controls an audio stream.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to control.
+ * @param enmStreamCmd Control command.
+ */
+static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtr(pThis);
+ AssertPtr(pStreamEx);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd),
+ drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+#ifdef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
+ {
+ rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
+ if (RT_FAILURE(rc))
+ break;
+ }
+#endif /* DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION */
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED))
+ {
+ /* Are we still draining this stream? Then we must disable it first. */
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+ {
+ LogFunc(("Stream '%s' is still draining - disabling...\n", pStreamEx->Core.Cfg.szName));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ if (drvAudioStreamGetBackendState(pThis, pStreamEx) != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+ drvAudioStreamResetInternal(pStreamEx);
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Reset the state before we try to start. */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ pStreamEx->enmLastBackendState = enmBackendState;
+ pStreamEx->offInternal = 0;
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ switch (enmBackendState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ if (pStreamEx->cbPreBufThreshold > 0)
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ AssertFailed();
+ RT_FALL_THROUGH();
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ pStreamEx->Out.enmPlayState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ break;
+ /* no default */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ LogFunc(("ENABLE: enmBackendState=%s enmPlayState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ }
+ else
+ {
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_NO_CAPTURE;
+ switch (enmBackendState)
+ {
+ case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_PREBUF;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+ AssertFailed();
+ RT_FALL_THROUGH();
+ case PDMHOSTAUDIOSTREAMSTATE_OKAY:
+ pStreamEx->In.enmCaptureState = pStreamEx->cbPreBufThreshold > 0
+ ? DRVAUDIOCAPTURESTATE_PREBUF : DRVAUDIOCAPTURESTATE_CAPTURING;
+ break;
+ case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
+ case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+ break;
+ /* no default */
+ case PDMHOSTAUDIOSTREAMSTATE_INVALID:
+ case PDMHOSTAUDIOSTREAMSTATE_END:
+ case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
+ break;
+ }
+ LogFunc(("ENABLE: enmBackendState=%s enmCaptureState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState),
+ drvAudioCaptureStateName(pStreamEx->In.enmCaptureState)));
+ }
+
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->nsStarted = RTTimeNanoTS();
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_ENABLED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+#ifndef DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ LogFunc(("DISABLE '%s': Backend DISABLE -> %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+ if (RT_SUCCESS(rc)) /** @todo ignore this and reset it anyway? */
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+#else
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
+#endif /* DRVAUDIO_WITH_STREAM_DESTRUCTION_IN_DISABLED_DIRECTION */
+ break;
+
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) == PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PAUSED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_RESUME:
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED)
+ {
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED);
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ }
+ break;
+
+ case PDMAUDIOSTREAMCMD_DRAIN:
+ /*
+ * Only for output streams and we don't want this command more than once.
+ */
+ AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_FUNCTION);
+ AssertBreak(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE));
+ if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ {
+ rc = VERR_INTERNAL_ERROR_2;
+ switch (pStreamEx->Out.enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ if (pStreamEx->Out.cbPreBuffered > 0)
+ {
+ LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data...\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ RT_FALL_THROUGH();
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ LogFunc(("DRAIN '%s': Nothing to drain (enmPlayState=%s)\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ LogFunc(("DRAIN '%s': Initiating backend draining (enmPlayState=%s -> NOPLAY) ...\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+ if (RT_SUCCESS(rc))
+ {
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ }
+ else
+ {
+ LogFunc(("DRAIN '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+ pStreamEx->Core.Cfg.szName, rc));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data (already committing)...\n",
+ pStreamEx->Core.Cfg.szName));
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ rc = VINF_SUCCESS;
+ break;
+
+ /* no default */
+ case DRVAUDIOPLAYSTATE_INVALID:
+ case DRVAUDIOPLAYSTATE_END:
+ AssertFailedBreak();
+ }
+ }
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.Cfg.szName, rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
+ PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /** @todo r=bird: why? It's not documented to ignore NULL streams. */
+ if (!pStream)
+ return VINF_SUCCESS;
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStreamEx->Core.Cfg.szName, PDMAudioStrmCmdGetName(enmStreamCmd)));
+
+ rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/**
+ * Copy data to the pre-buffer, ring-buffer style.
+ *
+ * The @a cbMax parameter is almost always set to the threshold size, the
+ * exception is when commiting the buffer and we want to top it off to reduce
+ * the number of transfers to the backend (the first transfer may start
+ * playback, so more data is better).
+ */
+static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax)
+{
+ uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
+ AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3);
+ AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4);
+ AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5);
+
+ uint32_t offRead = pStreamEx->Out.offPreBuf;
+ uint32_t cbCur = pStreamEx->Out.cbPreBuffered;
+ AssertStmt(offRead < cbAlloc, offRead %= cbAlloc);
+ AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax);
+
+ /*
+ * First chunk.
+ */
+ uint32_t offWrite = (offRead + cbCur) % cbAlloc;
+ uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf);
+ memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy);
+
+ /* Advance. */
+ offWrite = (offWrite + cbToCopy) % cbAlloc;
+ for (;;)
+ {
+ pbBuf += cbToCopy;
+ cbCur += cbToCopy;
+ if (cbCur > cbMax)
+ offRead = (offRead + cbCur - cbMax) % cbAlloc;
+ cbBuf -= cbToCopy;
+ if (!cbBuf)
+ break;
+
+ /*
+ * Second+ chunk, from the start of the buffer.
+ *
+ * Note! It is assumed very unlikely that we will ever see a cbBuf larger than
+ * cbMax, so we don't waste space on clipping cbBuf here (can happen with
+ * custom pre-buffer sizes).
+ */
+ Assert(offWrite == 0);
+ cbToCopy = RT_MIN(cbAlloc, cbBuf);
+ memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy);
+ }
+
+ /*
+ * Update the pre-buffering size and position.
+ */
+ pStreamEx->Out.cbPreBuffered = RT_MIN(cbCur, cbMax);
+ pStreamEx->Out.offPreBuf = offRead;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
+ *
+ * Caller owns the lock.
+ */
+static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf));
+
+ uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
+
+ uint32_t cbWritten = 0;
+ int rc = VINF_SUCCESS;
+ uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props);
+ while (cbBuf >= cbFrame && cbWritable >= cbFrame)
+ {
+ uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbWritable));
+ uint32_t cbWrittenNow = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbWrittenNow != cbToWrite)
+ Log3Func(("%s: @%#RX64: Wrote fewer bytes than requested: %#x, requested %#x\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
+#ifdef DEBUG_bird
+ Assert(cbWrittenNow == cbToWrite);
+#endif
+ AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
+ cbWritten += cbWrittenNow;
+ cbBuf -= cbWrittenNow;
+ pbBuf += cbWrittenNow;
+ pStreamEx->offInternal += cbWrittenNow;
+ }
+ else
+ {
+ *pcbWritten = cbWritten;
+ LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
+ return cbWritten ? VINF_SUCCESS : rc;
+ }
+
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ }
+
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbWritten);
+ *pcbWritten = cbWritten;
+ pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
+ if (cbWritten)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbWritten, cbBuf));
+ return rc;
+}
+
+
+/**
+ * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
+ */
+static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax,
+ uint32_t *pcbWritten)
+{
+ int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n",
+ pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered,
+ pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->cbPreBufThreshold, 1)));
+
+ }
+ else
+ *pcbWritten = 0;
+ return rc;
+}
+
+
+/**
+ * Used when we're committing (transfering) the pre-buffered bytes to the
+ * device.
+ *
+ * This is called both from drvAudioStreamPlay() and
+ * drvAudioStreamIterateInternal().
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the DrvAudio instance data.
+ * @param pStreamEx The stream to commit the pre-buffering for.
+ * @param pbBuf Buffer with new bytes to write. Can be NULL when called
+ * in the PENDING_DISABLE state from
+ * drvAudioStreamIterateInternal().
+ * @param cbBuf Number of new bytes. Can be zero.
+ * @param pcbWritten Where to return the number of bytes written.
+ *
+ * @note Locking: Stream critsect and hot-plug in shared mode.
+ */
+static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ /*
+ * First, top up the buffer with new data from pbBuf.
+ */
+ *pcbWritten = 0;
+ if (cbBuf > 0)
+ {
+ uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf);
+ if (cbToCopy > 0)
+ {
+ int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten);
+ AssertRCReturn(rc, rc);
+ pbBuf += cbToCopy;
+ cbBuf -= cbToCopy;
+ }
+ }
+
+ AssertReturn(pThis->pHostDrvAudio, VERR_AUDIO_BACKEND_NOT_ATTACHED);
+
+ /*
+ * Write the pre-buffered chunk.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
+ AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2);
+ uint32_t off = pStreamEx->Out.offPreBuf;
+ AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc);
+ uint32_t cbLeft = pStreamEx->Out.cbPreBuffered;
+ while (cbLeft > 0)
+ {
+ uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft);
+ Assert(cbToWrite > 0);
+
+ uint32_t cbPreBufWritten = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
+ cbToWrite, &cbPreBufWritten);
+ AssertRCBreak(rc);
+ if (!cbPreBufWritten)
+ break;
+ AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite);
+ off = (off + cbPreBufWritten) % cbAlloc;
+ cbLeft -= cbPreBufWritten;
+ }
+
+ if (cbLeft == 0)
+ {
+ LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal,
+ pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ pStreamEx->Out.cbPreBuffered = 0;
+ pStreamEx->Out.offPreBuf = 0;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY;
+
+ if (cbBuf > 0)
+ {
+ uint32_t cbWritten2 = 0;
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2);
+ if (RT_SUCCESS(rc))
+ *pcbWritten += cbWritten2;
+ }
+ else
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+ }
+ else
+ {
+ if (cbLeft != pStreamEx->Out.cbPreBuffered)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n",
+ pStreamEx->offInternal, pStreamEx->Core.Cfg.szName, pStreamEx->Out.cbPreBuffered - cbLeft,
+ pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ AssertMsg( pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING
+ || pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF
+ || RT_FAILURE(rc),
+ ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x enmPlayState=%s\n",
+ cbLeft, pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+
+ pStreamEx->Out.cbPreBuffered = cbLeft;
+ pStreamEx->Out.offPreBuf = off;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+ }
+
+ return *pcbWritten ? VINF_SUCCESS : rc;
+}
+
+
+/**
+ * Does one iteration of an audio stream.
+ *
+ * This function gives the backend the chance of iterating / altering data and
+ * does the actual mixing between the guest <-> host mixing buffers.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to driver instance.
+ * @param pStreamEx Stream to iterate.
+ */
+static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.Cfg.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
+
+ /* Not enabled or paused? Skip iteration. */
+ if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) != PDMAUDIOSTREAM_STS_ENABLED)
+ return VINF_SUCCESS;
+
+ /*
+ * Pending disable is really what we're here for.
+ *
+ * This only happens to output streams. We ASSUME the caller (MixerBuffer)
+ * implements a timeout on the draining, so we skip that here.
+ */
+ if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
+ { /* likely until we get to the end of the stream at least. */ }
+ else
+ {
+ AssertReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Move pre-buffered samples to the backend.
+ */
+ if (pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING)
+ {
+ if (pStreamEx->Out.cbPreBuffered > 0)
+ {
+ uint32_t cbIgnored = 0;
+ drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
+ Log3Func(("Stream '%s': Transferred %#x bytes\n", pStreamEx->Core.Cfg.szName, cbIgnored));
+ }
+ if (pStreamEx->Out.cbPreBuffered == 0)
+ {
+ Log3Func(("Stream '%s': No more pre-buffered data -> NOPLAY + backend DRAIN\n", pStreamEx->Core.Cfg.szName));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+
+ int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Stream '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+ pStreamEx->Core.Cfg.szName, rc));
+ rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ AssertRC(rc);
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+ }
+ }
+ else
+ Assert(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_NOPLAY);
+
+ /*
+ * Check the backend status to see if it's still draining and to
+ * update our status when it stops doing so.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ uint32_t cbIgnored = 0;
+ pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, NULL, 0, &cbIgnored);
+ }
+ else
+ {
+ LogFunc(("Stream '%s': Backend finished draining.\n", pStreamEx->Core.Cfg.szName));
+ drvAudioStreamResetOnDisable(pStreamEx);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+
+ /* Update timestamps. */
+ pStreamEx->nsLastIterated = RTTimeNanoTS();
+
+ return VINF_SUCCESS; /** @todo r=bird: What can the caller do with an error status here? */
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ rc = drvAudioStreamIterateInternal(pThis, pStreamEx);
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ if (RT_FAILURE(rc))
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetState}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTATE) drvAudioStreamGetState(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, PDMAUDIOSTREAMSTATE_INVALID);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
+ STAM_PROFILE_START(&pStreamEx->StatProfGetState, a);
+
+ /*
+ * Get the status mask.
+ */
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, PDMAUDIOSTREAMSTATE_INVALID);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+ uint32_t const fStrmStatus = pStreamEx->fStatus;
+ PDMAUDIODIR const enmDir = pStreamEx->Core.Cfg.enmDir;
+ Assert(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+
+ /*
+ * Translate it to state enum value.
+ */
+ PDMAUDIOSTREAMSTATE enmState;
+ if (!(fStrmStatus & PDMAUDIOSTREAM_STS_NEED_REINIT))
+ {
+ if (fStrmStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
+ {
+ if ( (fStrmStatus & PDMAUDIOSTREAM_STS_ENABLED)
+ && drvAudioStreamIsDirectionEnabled(pThis, pStreamEx->Core.Cfg.enmDir)
+ && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING
+ || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING ))
+ enmState = enmDir == PDMAUDIODIR_IN ? PDMAUDIOSTREAMSTATE_ENABLED_READABLE : PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE;
+ else
+ enmState = PDMAUDIOSTREAMSTATE_INACTIVE;
+ }
+ else
+ enmState = PDMAUDIOSTREAMSTATE_NOT_WORKING;
+ }
+ else
+ enmState = PDMAUDIOSTREAMSTATE_NEED_REINIT;
+
+ STAM_PROFILE_STOP(&pStreamEx->StatProfGetState, a);
+#ifdef LOG_ENABLED
+ char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
+#endif
+ Log3Func(("[%s] returns %s (status: %s)\n", pStreamEx->Core.Cfg.szName, PDMAudioStreamStateGetName(enmState),
+ drvAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
+ return enmState;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, 0);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0);
+ STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfGetWritable, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, 0);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Use the playback and backend states to determin how much can be written, if anything.
+ */
+ uint32_t cbWritable = 0;
+ DRVAUDIOPLAYSTATE const enmPlayMode = pStreamEx->Out.enmPlayState;
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+ if ( PDMAudioStrmStatusCanWrite(pStreamEx->fStatus)
+ && pThis->pHostDrvAudio != NULL
+ && enmBackendState != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+ {
+ switch (enmPlayMode)
+ {
+ /*
+ * Whatever the backend can hold.
+ */
+ case DRVAUDIOPLAYSTATE_PLAY:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ /*
+ * Whatever we've got of available space in the pre-buffer.
+ * Note! For the last round when we pass the pre-buffering threshold, we may
+ * report fewer bytes than what a DMA timer period for the guest device
+ * typically produces, however that should be transfered in the following
+ * round that goes directly to the backend buffer.
+ */
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
+ if (!cbWritable)
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 2);
+ break;
+
+ /*
+ * These are slightly more problematic and can go wrong if the pre-buffer is
+ * manually configured to be smaller than the output of a typeical DMA timer
+ * period for the guest device. So, to overcompensate, we just report back
+ * the backend buffer size (the pre-buffer is circular, so no overflow issue).
+ */
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props,
+ RT_MAX(pStreamEx->Core.Cfg.Backend.cFramesBufferSize,
+ pStreamEx->Core.Cfg.Backend.cFramesPreBuffering));
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ {
+ /* Buggy backend: We weren't able to copy all the pre-buffered data to it
+ when reaching the threshold. Try escape this situation, or at least
+ keep the extra buffering to a minimum. We must try write something
+ as long as there is space for it, as we need the pfnStreamWrite call
+ to move the data. */
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Cfg.Props, 8);
+ cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin)
+ cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2;
+ else
+ cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered);
+ AssertLogRel(cbWritable);
+ break;
+ }
+
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ break;
+ case DRVAUDIOPLAYSTATE_INVALID:
+ case DRVAUDIOPLAYSTATE_END:
+ AssertFailed();
+ break;
+ }
+
+ /* Make sure to align the writable size to the host's frame size. */
+ cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbWritable);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->Out.Stats.ProfGetWritableBytes, cbWritable);
+ STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfGetWritable, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ Log3Func(("[%s] cbWritable=%#RX32 (%RU64ms) enmPlayMode=%s enmBackendState=%s\n",
+ pStreamEx->Core.Cfg.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbWritable),
+ drvAudioPlayStateName(enmPlayMode), PDMHostAudioStreamStateGetName(enmBackendState) ));
+ return cbWritable;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Check input and sanity.
+ */
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ uint32_t uTmp;
+ if (pcbWritten)
+ AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);
+ else
+ pcbWritten = &uTmp;
+
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT,
+ ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
+ pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED);
+
+ AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf),
+ ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf));
+ STAM_PROFILE_START(&pStreamEx->Out.Stats.ProfPlay, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * First check that we can write to the stream, and if not,
+ * whether to just drop the input into the bit bucket.
+ */
+ if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if ( pThis->Out.fEnabled /* (see @bugref{9882}) */
+ && pThis->pHostDrvAudio != NULL)
+ {
+ /*
+ * Get the backend state and process changes to it since last time we checked.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+
+ /*
+ * Do the transfering.
+ */
+ switch (pStreamEx->Out.enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PLAY:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->cbPreBufThreshold);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->cbPreBufThreshold)
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ else if ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+ && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY))
+ {
+ Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n",
+ pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
+ cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold));
+ rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ }
+ else
+ {
+ Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n",
+ pStreamEx->Core.Cfg.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
+ cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->cbPreBufThreshold));
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE;
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ }
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ Assert( !(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)
+ || enmBackendState != PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ RT_FALL_THRU();
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->cbPreBufThreshold, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
+ break;
+
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(enmBackendState) ));
+ break;
+
+ default:
+ *pcbWritten = cbBuf;
+ AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf));
+ }
+
+ if (!pStreamEx->Out.Dbg.pFilePlay || RT_FAILURE(rc))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlay, pvBuf, *pcbWritten);
+ }
+ else
+ {
+ *pcbWritten = cbBuf;
+ pStreamEx->offInternal += cbBuf;
+ Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.Cfg.szName,
+ !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+
+ STAM_PROFILE_STOP(&pStreamEx->Out.Stats.ProfPlay, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, 0);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
+ AssertMsg(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
+ STAM_PROFILE_START(&pStreamEx->In.Stats.ProfGetReadable, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, 0);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ /*
+ * Use the capture state to determin how much can be written, if anything.
+ */
+ uint32_t cbReadable = 0;
+ DRVAUDIOCAPTURESTATE const enmCaptureState = pStreamEx->In.enmCaptureState;
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx); RT_NOREF(enmBackendState);
+ if ( PDMAudioStrmStatusCanRead(pStreamEx->fStatus)
+ && pThis->pHostDrvAudio != NULL)
+ {
+ switch (enmCaptureState)
+ {
+ /*
+ * Whatever the backend has to offer when in capture mode.
+ */
+ case DRVAUDIOCAPTURESTATE_CAPTURING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+ cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ break;
+
+ /*
+ * Same calculation as in drvAudioStreamCaptureSilence, only we cap it
+ * at the pre-buffering threshold so we don't get into trouble when we
+ * switch to capture mode between now and pfnStreamCapture.
+ */
+ case DRVAUDIOCAPTURESTATE_PREBUF:
+ {
+ uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted;
+ uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream);
+ if (offCur > pStreamEx->offInternal)
+ {
+ uint64_t const cbUnread = offCur - pStreamEx->offInternal;
+ cbReadable = (uint32_t)RT_MIN(pStreamEx->cbPreBufThreshold, cbUnread);
+ }
+ break;
+ }
+
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE:
+ break;
+
+ case DRVAUDIOCAPTURESTATE_INVALID:
+ case DRVAUDIOCAPTURESTATE_END:
+ AssertFailed();
+ break;
+ }
+
+ /* Make sure to align the readable size to the host's frame size. */
+ cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, cbReadable);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->In.Stats.ProfGetReadableBytes, cbReadable);
+ STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfGetReadable, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ Log3Func(("[%s] cbReadable=%#RX32 (%RU64ms) enmCaptureMode=%s enmBackendState=%s\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Core.Cfg.Props, cbReadable),
+ drvAudioCaptureStateName(enmCaptureState), PDMHostAudioStreamStateGetName(enmBackendState) ));
+ return cbReadable;
+}
+
+
+/**
+ * Worker for drvAudioStreamCapture that returns silence.
+ *
+ * The amount of silence returned is a function of how long the stream has been
+ * enabled.
+ *
+ * @returns VINF_SUCCESS
+ * @param pStreamEx The stream to commit the pre-buffering for.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The size of the output buffer.
+ * @param pcbRead Where to return the number of bytes actually read.
+ */
+static int drvAudioStreamCaptureSilence(PDRVAUDIOSTREAM pStreamEx, uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ /** @todo Does not take paused time into account... */
+ uint64_t const cNsStream = RTTimeNanoTS() - pStreamEx->nsStarted;
+ uint64_t const offCur = PDMAudioPropsNanoToBytes64(&pStreamEx->Core.Cfg.Props, cNsStream);
+ if (offCur > pStreamEx->offInternal)
+ {
+ uint64_t const cbUnread = offCur - pStreamEx->offInternal;
+ uint32_t const cbToClear = (uint32_t)RT_MIN(cbBuf, cbUnread);
+ *pcbRead = cbToClear;
+ pStreamEx->offInternal += cbToClear;
+ cbBuf -= cbToClear;
+ PDMAudioPropsClearBuffer(&pStreamEx->Core.Cfg.Props, pbBuf, cbToClear,
+ PDMAudioPropsBytesToFrames(&pStreamEx->Core.Cfg.Props, cbToClear));
+ }
+ else
+ *pcbRead = 0;
+ Log4Func(("%s: @%#RX64: Read %#x bytes of silence (%#x bytes left)\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, *pcbRead, cbBuf));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for drvAudioStreamCapture.
+ */
+static int drvAudioStreamCaptureLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ Log4Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbBuf));
+
+ uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable;
+
+ uint32_t cbRead = 0;
+ int rc = VINF_SUCCESS;
+ uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Cfg.Props);
+ while (cbBuf >= cbFrame && cbReadable >= cbFrame)
+ {
+ uint32_t const cbToRead = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Cfg.Props, RT_MIN(cbBuf, cbReadable));
+ uint32_t cbReadNow = 0;
+ rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToRead, &cbReadNow);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbReadNow != cbToRead)
+ Log4Func(("%s: @%#RX64: Read fewer bytes than requested: %#x, requested %#x\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbReadNow, cbToRead));
+#ifdef DEBUG_bird
+ Assert(cbReadNow == cbToRead);
+#endif
+ AssertStmt(cbReadNow <= cbToRead, cbReadNow = cbToRead);
+ cbRead += cbReadNow;
+ cbBuf -= cbReadNow;
+ pbBuf += cbReadNow;
+ pStreamEx->offInternal += cbReadNow;
+ }
+ else
+ {
+ *pcbRead = cbRead;
+ LogFunc(("%s: @%#RX64: pfnStreamCapture failed read %#x bytes (%#x previous read, %#x readable): %Rrc\n",
+ pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbToRead, cbRead, cbReadable, rc));
+ return cbRead ? VINF_SUCCESS : rc;
+ }
+
+ cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
+ }
+
+ STAM_PROFILE_ADD_PERIOD(&pStreamEx->StatXfer, cbRead);
+ *pcbRead = cbRead;
+ pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable;
+ if (cbRead)
+ pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
+
+ Log4Func(("%s: @%#RX64: Read %#x bytes (%#x bytes left)\n", pStreamEx->Core.Cfg.szName, pStreamEx->offInternal, cbRead, cbBuf));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
+ void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
+ AssertPtr(pThis);
+
+ /*
+ * Check input and sanity.
+ */
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ uint32_t uTmp;
+ if (pcbRead)
+ AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
+ else
+ pcbRead = &uTmp;
+
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertMsgReturn(pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_IN,
+ ("Stream '%s' is not an input stream and therefore cannot be read from (direction is '%s')\n",
+ pStreamEx->Core.Cfg.szName, PDMAudioDirGetName(pStreamEx->Core.Cfg.enmDir)), VERR_ACCESS_DENIED);
+
+ AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Core.Cfg.Props, cbBuf),
+ ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.Cfg.szName, cbBuf));
+ STAM_PROFILE_START(&pStreamEx->In.Stats.ProfCapture, a);
+
+ int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * First check that we can read from the stream, and if not,
+ * whether to just drop the input into the bit bucket.
+ */
+ if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus))
+ {
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if ( pThis->In.fEnabled /* (see @bugref{9882}) */
+ && pThis->pHostDrvAudio != NULL)
+ {
+ /*
+ * Get the backend state and process changes to it since last time we checked.
+ */
+ PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
+
+ /*
+ * Do the transfering.
+ */
+ switch (pStreamEx->In.enmCaptureState)
+ {
+ case DRVAUDIOCAPTURESTATE_CAPTURING:
+ Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
+ Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
+ rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+
+ case DRVAUDIOCAPTURESTATE_PREBUF:
+ if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
+ {
+ uint32_t const cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio,
+ pStreamEx->pBackend);
+ if (cbReadable >= pStreamEx->cbPreBufThreshold)
+ {
+ Log4Func(("[%s] Pre-buffering completed: cbReadable=%#x vs cbPreBufThreshold=%#x (cbBuf=%#x)\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold, cbBuf));
+ pStreamEx->In.enmCaptureState = DRVAUDIOCAPTURESTATE_CAPTURING;
+ rc = drvAudioStreamCaptureLocked(pThis, pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+ }
+ pStreamEx->In.Stats.cbBackendReadableBefore = cbReadable;
+ pStreamEx->In.Stats.cbBackendReadableAfter = cbReadable;
+ Log4Func(("[%s] Pre-buffering: Got %#x out of %#x\n",
+ pStreamEx->Core.Cfg.szName, cbReadable, pStreamEx->cbPreBufThreshold));
+ }
+ else
+ Log4Func(("[%s] Pre-buffering: Backend status %s\n",
+ pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) ));
+ drvAudioStreamCaptureSilence(pStreamEx, (uint8_t *)pvBuf, cbBuf, pcbRead);
+ break;
+
+ case DRVAUDIOCAPTURESTATE_NO_CAPTURE:
+ *pcbRead = 0;
+ Log4Func(("[%s] Not capturing - backend state: %s\n",
+ pStreamEx->Core.Cfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) ));
+ break;
+
+ default:
+ *pcbRead = 0;
+ AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->In.enmCaptureState, cbBuf));
+ }
+
+ if (!pStreamEx->In.Dbg.pFileCapture || RT_FAILURE(rc))
+ { /* likely */ }
+ else
+ AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCapture, pvBuf, *pcbRead);
+ }
+ else
+ {
+ *pcbRead = 0;
+ Log4Func(("[%s] Backend stream %s, returning no data\n", pStreamEx->Core.Cfg.szName,
+ !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ }
+ else
+ rc = VERR_AUDIO_STREAM_NOT_READY;
+
+ STAM_PROFILE_STOP(&pStreamEx->In.Stats.ProfCapture, a);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* PDMIHOSTAUDIOPORT interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for drvAudioHostPort_DoOnWorkerThread with stream argument, called on
+ * worker thread.
+ */
+static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadStreamWorker(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
+ uintptr_t uUser, void *pvUser)
+{
+ LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
+ AssertPtrReturnVoid(pThis);
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ /*
+ * The CritSectHotPlug lock should not be needed here as detach will destroy
+ * the thread pool. So, we'll leave taking the stream lock to the worker we're
+ * calling as there are no lock order concerns.
+ */
+ PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturnVoid(pIHostDrvAudio);
+ AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
+ pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, pStreamEx->pBackend, uUser, pvUser);
+
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * Worker for drvAudioHostPort_DoOnWorkerThread without stream argument, called
+ * on worker thread.
+ *
+ * This wrapper isn't technically required, but it helps with logging and a few
+ * extra sanity checks.
+ */
+static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadWorker(PDRVAUDIO pThis, uintptr_t uUser, void *pvUser)
+{
+ LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
+ AssertPtrReturnVoid(pThis);
+
+ /*
+ * The CritSectHotPlug lock should not be needed here as detach will destroy
+ * the thread pool.
+ */
+ PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturnVoid(pIHostDrvAudio);
+ AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
+
+ pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, NULL, uUser, pvUser);
+
+ LogFlowFunc(("returns\n"));
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnDoOnWorkerThread}
+ */
+static DECLCALLBACK(int) drvAudioHostPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ uintptr_t uUser, void *pvUser)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ LogFlowFunc(("pStream=%p uUser=%#zx pvUser=%p\n", pStream, uUser, pvUser));
+
+ /*
+ * Assert some sanity.
+ */
+ PDRVAUDIOSTREAM pStreamEx;
+ if (!pStream)
+ pStreamEx = NULL;
+ else
+ {
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertReturn(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
+ AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
+ }
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ Assert(pThis->hReqPool != NIL_RTREQPOOL);
+ AssertPtr(pThis->pHostDrvAudio);
+ if ( pThis->hReqPool != NIL_RTREQPOOL
+ && pThis->pHostDrvAudio != NULL)
+ {
+ AssertPtr(pThis->pHostDrvAudio->pfnDoOnWorkerThread);
+ if (pThis->pHostDrvAudio->pfnDoOnWorkerThread)
+ {
+ /*
+ * Try do the work.
+ */
+ if (!pStreamEx)
+ {
+ rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioHostPort_DoOnWorkerThreadWorker, 3, pThis, uUser, pvUser);
+ AssertRC(rc);
+ }
+ else
+ {
+ uint32_t cRefs = drvAudioStreamRetainInternal(pStreamEx);
+ if (cRefs != UINT32_MAX)
+ {
+ rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioHostPort_DoOnWorkerThreadStreamWorker,
+ 4, pThis, pStreamEx, uUser, pvUser);
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ {
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ rc = VERR_INVALID_FUNCTION;
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Marks a stream for re-init.
+ */
+static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller)
+{
+ LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pszCaller, pStreamEx->Core.Cfg.szName)); RT_NOREF(pszCaller);
+ Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
+
+ pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT;
+ PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+ pStreamEx->cTriesReInit = 0;
+ pStreamEx->nsLastReInit = 0;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDeviceChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
+ LogRel(("Audio: The %s device for %s is changing.\n", PDMAudioDirGetName(enmDir), pThis->BackendCfg.szName));
+
+ /*
+ * Grab the list lock in shared mode and do the work.
+ */
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ if (pStreamEx->Core.Cfg.enmDir == enmDir)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+
+ if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
+ {
+ LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend state: %s...\n", pStreamEx->Core.Cfg.szName,
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
+ pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser);
+ LogFlowFunc(("New stream backend state: %s\n",
+ PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
+ }
+ else
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyPreparingDeviceSwitch}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream)
+{
+ RT_NOREF(pInterface);
+
+ /*
+ * Backend stream to validated DrvAudio stream:
+ */
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+ LogFlowFunc(("pStreamEx=%p '%s'\n", pStreamEx, pStreamEx->Core.Cfg.szName));
+
+ /*
+ * Grab the lock and do switch the state (only needed for output streams for now).
+ */
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ if (pStreamEx->cbPreBufThreshold > 0)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ switch (enmPlayState)
+ {
+ case DRVAUDIOPLAYSTATE_PREBUF:
+ case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+ case DRVAUDIOPLAYSTATE_NOPLAY:
+ case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
+ break;
+ case DRVAUDIOPLAYSTATE_PLAY:
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF;
+ break;
+ case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+ case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+ break;
+ /* no default */
+ case DRVAUDIOPLAYSTATE_END:
+ case DRVAUDIOPLAYSTATE_INVALID:
+ break;
+ }
+ LogFunc(("%s -> %s\n", drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ }
+ else
+ LogFunc(("No pre-buffering configured.\n"));
+ }
+ else
+ LogFunc(("input stream, nothing to do.\n"));
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyDeviceChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+
+ /*
+ * Backend stream to validated DrvAudio stream:
+ */
+ AssertPtrReturnVoid(pStream);
+ AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
+ PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
+ AssertPtrReturnVoid(pStreamEx);
+ AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
+ AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
+
+ /*
+ * Grab the lock and do the requested work.
+ */
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
+
+ if (fReInit)
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+ else
+ {
+ /*
+ * Adjust the stream state now that the device has (perhaps finally) been switched.
+ *
+ * For enabled output streams, we must update the play state. We could try commit
+ * pre-buffered data here, but it's really not worth the hazzle and risk (don't
+ * know which thread we're on, do we now).
+ */
+ AssertStmt(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT),
+ pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT);
+
+
+ if (pStreamEx->Core.Cfg.enmDir == PDMAUDIODIR_OUT)
+ {
+ DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
+ pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
+ LogFunc(("%s: %s -> %s\n", pStreamEx->Core.Cfg.szName, drvAudioPlayStateName(enmPlayState),
+ drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
+ RT_NOREF(enmPlayState);
+ }
+
+ /* Disable and then fully resync. */
+ /** @todo This doesn't work quite reliably if we're in draining mode
+ * (PENDING_DISABLE, so the backend needs to take care of that prior to calling
+ * us. Sigh. The idea was to avoid extra state mess in the backend... */
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "device changed");
+ }
+
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+}
+
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+/**
+ * @callback_method_impl{FNTMTIMERDRV, Re-enumerate backend devices.}
+ *
+ * Used to do/trigger re-enumeration of backend devices with a delay after we
+ * got notification as there can be further notifications following shortly
+ * after the first one. Also good to get it of random COM/whatever threads.
+ */
+static DECLCALLBACK(void) drvAudioEnumerateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ RT_NOREF(hTimer, pvUser);
+
+ /* Try push the work over to the thread-pool if we've got one. */
+ RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
+ if (pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ int rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ (PFNRT)drvAudioDevicesEnumerateInternal,
+ 3, pThis, true /*fLog*/, (PPDMAUDIOHOSTENUM)NULL /*pDevEnum*/);
+ LogFunc(("RTReqPoolCallEx: %Rrc\n", rc));
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+ if (RT_SUCCESS(rc))
+ return;
+ }
+ else
+ RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
+
+ LogFunc(("Calling drvAudioDevicesEnumerateInternal...\n"));
+ drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+}
+#endif /* VBOX_WITH_AUDIO_ENUM */
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDevicesChanged}
+ */
+static DECLCALLBACK(void) drvAudioHostPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
+{
+ PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
+ LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->BackendCfg.szName));
+
+#ifdef RT_OS_DARWIN /** @todo Remove legacy behaviour: */
+ /* Mark all host streams to re-initialize. */
+ int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc2);
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+#endif
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /*
+ * Re-enumerate all host devices with a tiny delay to avoid re-doing this
+ * when a bunch of changes happens at once (they typically do on windows).
+ * We'll keep postponing it till it quiesces for a fraction of a second.
+ */
+ int rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hEnumTimer, RT_MS_1SEC / 3);
+ AssertRC(rc);
+#endif
+}
+
+
+/*********************************************************************************************************************************
+* PDMIBASE interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &pThis->IHostAudioPort);
+
+ return NULL;
+}
+
+
+/*********************************************************************************************************************************
+* PDMDRVREG interface implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Power Off notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ /** @todo locking? */
+ if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
+ {
+ /*
+ * Just destroy the host stream on the backend side.
+ * The rest will either be destructed by the device emulation or
+ * in drvAudioDestruct().
+ */
+ int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Detach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Detach flags.
+ */
+static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ RT_NOREF(fFlags);
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ AssertLogRelRCReturnVoid(rc);
+
+ LogFunc(("%s (detached %p, hReqPool=%p)\n", pThis->BackendCfg.szName, pThis->pHostDrvAudio, pThis->hReqPool));
+
+ /*
+ * Must first destroy the thread pool first so we are certain no threads
+ * are still using the instance being detached. Release lock while doing
+ * this as the thread functions may need to take it to complete.
+ */
+ if (pThis->pHostDrvAudio && pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ RTREQPOOL hReqPool = pThis->hReqPool;
+ pThis->hReqPool = NIL_RTREQPOOL;
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+
+ RTReqPoolRelease(hReqPool);
+
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ }
+
+ /*
+ * Now we can safely set pHostDrvAudio to NULL.
+ */
+ pThis->pHostDrvAudio = NULL;
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+}
+
+
+/**
+ * Initializes the host backend and queries its initial configuration.
+ *
+ * @returns VBox status code.
+ * @param pThis Driver instance to be called.
+ */
+static int drvAudioHostInit(PDRVAUDIO pThis)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Check the function pointers, make sure the ones we define as
+ * mandatory are present.
+ */
+ PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio;
+ AssertPtrReturn(pIHostDrvAudio, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnSetDevice, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnDoOnWorkerThread, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamInitAsync, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamEnable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamDisable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamPause, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamResume, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamDrain, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pIHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamGetState, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
+ AssertPtrReturn(pIHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
+
+ /*
+ * Get the backend configuration.
+ *
+ * Note! Limit the number of streams to max 128 in each direction to
+ * prevent wasting resources.
+ * Note! Take care not to wipe the DriverName config value on failure.
+ */
+ PDMAUDIOBACKENDCFG BackendCfg;
+ RT_ZERO(BackendCfg);
+ int rc = pIHostDrvAudio->pfnGetConfig(pIHostDrvAudio, &BackendCfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (LogIsEnabled() && strcmp(BackendCfg.szName, pThis->BackendCfg.szName) != 0)
+ LogFunc(("BackendCfg.szName: '%s' -> '%s'\n", pThis->BackendCfg.szName, BackendCfg.szName));
+ pThis->BackendCfg = BackendCfg;
+ pThis->In.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsIn, 128);
+ pThis->Out.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsOut, 128);
+
+ LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+ }
+ else
+ {
+ LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc));
+ return VERR_AUDIO_BACKEND_INIT_FAILED;
+ }
+
+ LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
+ pThis->BackendCfg.szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
+ if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
+ AssertRC(rc2);
+ /* Ignore rc2. */
+#endif
+
+ /*
+ * Create a thread pool if stream creation can be asynchronous.
+ *
+ * The pool employs no pushback as the caller is typically EMT and
+ * shouldn't be delayed.
+ *
+ * The number of threads limits and the device implementations use
+ * of pfnStreamDestroy limits the number of streams pending async
+ * init. We use RTReqCancel in drvAudioStreamDestroy to allow us
+ * to release extra reference held by the pfnStreamInitAsync call
+ * if successful. Cancellation will only be possible if the call
+ * hasn't been picked up by a worker thread yet, so the max number
+ * of threads in the pool defines how many destroyed streams that
+ * can be lingering. (We must keep this under control, otherwise
+ * an evil guest could just rapidly trigger stream creation and
+ * destruction to consume host heap and hog CPU resources for
+ * configuring audio backends.)
+ */
+ if ( pThis->hReqPool == NIL_RTREQPOOL
+ && ( pIHostDrvAudio->pfnStreamInitAsync
+ || pIHostDrvAudio->pfnDoOnWorkerThread
+ || (pThis->BackendCfg.fFlags & (PDMAUDIOBACKEND_F_ASYNC_HINT | PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY)) ))
+ {
+ char szName[16];
+ RTStrPrintf(szName, sizeof(szName), "Aud%uWr", pThis->pDrvIns->iInstance);
+ RTREQPOOL hReqPool = NIL_RTREQPOOL;
+ rc = RTReqPoolCreate(3 /*cMaxThreads*/, RT_MS_30SEC /*cMsMinIdle*/, UINT32_MAX /*cThreadsPushBackThreshold*/,
+ 1 /*cMsMaxPushBack*/, szName, &hReqPool);
+ LogFlowFunc(("Creating thread pool '%s': %Rrc, hReqPool=%p\n", szName, rc, hReqPool));
+ AssertRCReturn(rc, rc);
+
+ rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_THREAD_FLAGS, RTTHREADFLAGS_COM_MTA);
+ AssertRCReturnStmt(rc, RTReqPoolRelease(hReqPool), rc);
+
+ rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_MIN_THREADS, 1);
+ AssertRC(rc); /* harmless */
+
+ pThis->hReqPool = hReqPool;
+ }
+ else
+ LogFlowFunc(("No thread pool.\n"));
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does the actual backend driver attaching and queries the backend's interface.
+ *
+ * This is a worker for both drvAudioAttach and drvAudioConstruct.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pThis Pointer to driver instance.
+ * @param fFlags Attach flags; see PDMDrvHlpAttach().
+ */
+static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags)
+{
+ Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
+
+ /*
+ * Attach driver below and query its connector interface.
+ */
+ PPDMIBASE pDownBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
+ if (pThis->pHostDrvAudio)
+ {
+ /*
+ * If everything went well, initialize the lower driver.
+ */
+ rc = drvAudioHostInit(pThis);
+ if (RT_FAILURE(rc))
+ pThis->pHostDrvAudio = NULL;
+ }
+ else
+ {
+ LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->BackendCfg.szName));
+ rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
+ N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
+ }
+ }
+ /*
+ * If the host driver below us failed to construct for some beningn reason,
+ * we'll report it as a runtime error and replace it with the Null driver.
+ *
+ * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase
+ * will remain NULL in this case.
+ */
+ else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED
+ || rc == VERR_MODULE_NOT_FOUND
+ || rc == VERR_SYMBOL_NOT_FOUND
+ || rc == VERR_FILE_NOT_FOUND
+ || rc == VERR_PATH_NOT_FOUND)
+ {
+ /* Complain: */
+ LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n",
+ pThis->BackendCfg.szName, rc));
+ PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding",
+ N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"),
+ pThis->BackendCfg.szName);
+
+ /* Replace with null audio: */
+ pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull;
+ RTStrCopy(pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "NULL");
+ rc = drvAudioHostInit(pThis);
+ AssertRC(rc);
+ }
+
+ LogFunc(("[%s] rc=%Rrc\n", pThis->BackendCfg.szName, rc));
+ return rc;
+}
+
+
+/**
+ * Attach notification.
+ *
+ * @param pDrvIns The driver instance data.
+ * @param fFlags Attach flags.
+ */
+static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ LogFunc(("%s\n", pThis->BackendCfg.szName));
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+
+ rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ return rc;
+}
+
+
+/**
+ * Handles state changes for all audio streams.
+ *
+ * @param pDrvIns Pointer to driver instance.
+ * @param enmCmd Stream command to set for all streams.
+ */
+static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
+
+ int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
+ AssertRCReturnVoid(rc2);
+
+ PDRVAUDIOSTREAM pStreamEx;
+ RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
+ {
+ RTCritSectEnter(&pStreamEx->Core.CritSect);
+ drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
+ RTCritSectLeave(&pStreamEx->Core.CritSect);
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
+}
+
+
+/**
+ * Suspend notification.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
+{
+ drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
+}
+
+
+/**
+ * Destructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+
+ LogFlowFuncEnter();
+
+ /*
+ * We must start by setting pHostDrvAudio to NULL here as the anything below
+ * us has already been destroyed at this point.
+ */
+ if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ pThis->pHostDrvAudio = NULL;
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ }
+ else
+ {
+ Assert(pThis->pHostDrvAudio == NULL);
+ pThis->pHostDrvAudio = NULL;
+ }
+
+ /*
+ * Make sure the thread pool is out of the picture before we terminate all the streams.
+ */
+ if (pThis->hReqPool != NIL_RTREQPOOL)
+ {
+ uint32_t cRefs = RTReqPoolRelease(pThis->hReqPool);
+ Assert(cRefs == 0); RT_NOREF(cRefs);
+ pThis->hReqPool = NIL_RTREQPOOL;
+ }
+
+ /*
+ * Destroy all streams.
+ */
+ if (RTCritSectRwIsInitialized(&pThis->CritSectGlobals))
+ {
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+
+ PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
+ RTListForEachSafe(&pThis->LstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
+ {
+ int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pStreamEx->ListEntry);
+ drvAudioStreamFree(pStreamEx);
+ }
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+ RTCritSectRwDelete(&pThis->CritSectGlobals);
+ }
+
+
+ /* Sanity. */
+ Assert(RTListIsEmpty(&pThis->LstStreams));
+
+ if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
+ RTCritSectRwDelete(&pThis->CritSectHotPlug);
+
+ PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, "");
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * Constructs an audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+ LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
+
+ /*
+ * Basic instance init.
+ */
+ RTListInit(&pThis->LstStreams);
+ pThis->hReqPool = NIL_RTREQPOOL;
+
+ /*
+ * Read configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
+ "DriverName|"
+ "InputEnabled|"
+ "OutputEnabled|"
+ "DebugEnabled|"
+ "DebugPathOut|"
+ /* Deprecated: */
+ "PCMSampleBitIn|"
+ "PCMSampleBitOut|"
+ "PCMSampleHzIn|"
+ "PCMSampleHzOut|"
+ "PCMSampleSignedIn|"
+ "PCMSampleSignedOut|"
+ "PCMSampleSwapEndianIn|"
+ "PCMSampleSwapEndianOut|"
+ "PCMSampleChannelsIn|"
+ "PCMSampleChannelsOut|"
+ "PeriodSizeMsIn|"
+ "PeriodSizeMsOut|"
+ "BufferSizeMsIn|"
+ "BufferSizeMsOut|"
+ "PreBufferSizeMsIn|"
+ "PreBufferSizeMsOut",
+ "In|Out");
+
+ int rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DriverName", pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "Untitled");
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Neither input nor output by default for security reasons. */
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Debug stuff (same for both directions). */
+ rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "DebugEnabled", &pThis->CfgIn.Dbg.fEnabled, false);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "DebugPathOut", pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut), "");
+ AssertLogRelRCReturn(rc, rc);
+ if (pThis->CfgIn.Dbg.szPathOut[0] == '\0')
+ {
+ rc = RTPathTemp(pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
+ pThis->CfgIn.Dbg.szPathOut[0] = '\0';
+ pThis->CfgIn.Dbg.fEnabled = false;
+ }
+ }
+ if (pThis->CfgIn.Dbg.fEnabled)
+ LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n",
+ pThis->BackendCfg.szName, pThis->CfgIn.Dbg.szPathOut));
+
+ /* Copy debug setup to the output direction. */
+ pThis->CfgOut.Dbg = pThis->CfgIn.Dbg;
+
+ LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->BackendCfg.szName));
+ /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
+ LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
+ pThis->BackendCfg.szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
+
+ /*
+ * Per direction configuration. A bit complicated as
+ * these wasn't originally in sub-nodes.
+ */
+ for (unsigned iDir = 0; iDir < 2; iDir++)
+ {
+ char szNm[48];
+ PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->CfgIn : &pThis->CfgOut;
+ const char *pszDir = iDir == 0 ? "In" : "Out";
+
+#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
+ do { \
+ rc = RT_CONCAT(pHlp->pfnCFGMQueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
+ { \
+ rc = RT_CONCAT(pHlp->pfnCFGMQueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
+ { \
+ *(a_pValue) = a_uDefault; \
+ rc = VINF_SUCCESS; \
+ } \
+ else \
+ LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
+ } \
+ AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
+ N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
+ if (!(a_ExprValid)) \
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
+ N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
+ } while (0)
+
+ PCFGMNODE const pDirNode = pHlp->pfnCFGMGetChild(pCfg, pszDir);
+ rc = pHlp->pfnCFGMValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
+ "PCMSampleBit|"
+ "PCMSampleHz|"
+ "PCMSampleSigned|"
+ "PCMSampleSwapEndian|"
+ "PCMSampleChannels|"
+ "PeriodSizeMs|"
+ "BufferSizeMs|"
+ "PreBufferSizeMs",
+ "", pDrvIns->pReg->szName, pDrvIns->iInstance);
+ AssertRCReturn(rc, rc);
+
+ uint8_t cSampleBits = 0;
+ QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
+ cSampleBits == 0
+ || cSampleBits == 8
+ || cSampleBits == 16
+ || cSampleBits == 32
+ || cSampleBits == 64,
+ "Must be either 0, 8, 16, 32 or 64");
+ if (cSampleBits)
+ PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
+
+ uint8_t cChannels;
+ QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
+ if (cChannels)
+ PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
+
+ QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
+ pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
+ "In the range 6000 thru 768000, or 0");
+
+ QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
+ pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
+ "Must be either 0, 1, or 255");
+
+ QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
+ pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
+ "Must be either 0, 1, or 255");
+
+ QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
+ pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
+
+ QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
+ pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
+
+ QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
+ pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
+ "Max 1000, or 0xffffffff");
+#undef QUERY_VAL_RET
+ }
+
+ /*
+ * Init the rest of the driver instance data.
+ */
+ rc = RTCritSectRwInit(&pThis->CritSectHotPlug);
+ AssertRCReturn(rc, rc);
+ rc = RTCritSectRwInit(&pThis->CritSectGlobals);
+ AssertRCReturn(rc, rc);
+#ifdef VBOX_STRICT
+ /* Define locking order: */
+ RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
+ RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
+ RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
+#endif
+
+ pThis->pDrvIns = pDrvIns;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
+ /* IAudioConnector. */
+ pThis->IAudioConnector.pfnEnable = drvAudioEnable;
+ pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
+ pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
+ pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
+ pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint;
+ pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
+ pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
+ pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit;
+ pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
+ pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
+ pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
+ pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
+ pThis->IAudioConnector.pfnStreamGetState = drvAudioStreamGetState;
+ pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
+ pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
+ pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
+ pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
+ /* IHostAudioPort */
+ pThis->IHostAudioPort.pfnDoOnWorkerThread = drvAudioHostPort_DoOnWorkerThread;
+ pThis->IHostAudioPort.pfnNotifyDeviceChanged = drvAudioHostPort_NotifyDeviceChanged;
+ pThis->IHostAudioPort.pfnStreamNotifyPreparingDeviceSwitch = drvAudioHostPort_StreamNotifyPreparingDeviceSwitch;
+ pThis->IHostAudioPort.pfnStreamNotifyDeviceChanged = drvAudioHostPort_StreamNotifyDeviceChanged;
+ pThis->IHostAudioPort.pfnNotifyDevicesChanged = drvAudioHostPort_NotifyDevicesChanged;
+
+#ifdef VBOX_WITH_AUDIO_ENUM
+ /*
+ * Create a timer to trigger delayed device enumeration on device changes.
+ */
+ RTStrPrintf(pThis->szEnumTimerName, sizeof(pThis->szEnumTimerName), "AudioEnum-%u", pDrvIns->iInstance);
+ rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvAudioEnumerateTimer, NULL /*pvUser*/,
+ 0 /*fFlags*/, pThis->szEnumTimerName, &pThis->hEnumTimer);
+ AssertRCReturn(rc, rc);
+#endif
+
+ /*
+ * Attach the host driver, if present.
+ */
+ rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
+ if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ rc = VINF_SUCCESS;
+
+ /*
+ * Statistics (afte driver attach for name).
+ */
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->BackendCfg.fFlags, STAMTYPE_U32, "BackendFlags", STAMUNIT_COUNT, pThis->BackendCfg.szName); /* Mainly for the name. */
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->cStreams, STAMTYPE_U32, "Streams", STAMUNIT_COUNT, "Current streams count.");
+ PDMDrvHlpSTAMRegCounter(pDrvIns, &pThis->StatTotalStreamsCreated, "TotalStreamsCreated", "Number of stream ever created.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.fEnabled, STAMTYPE_BOOL, "InputEnabled", STAMUNIT_NONE, "Whether input is enabled or not.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->In.cStreamsFree, STAMTYPE_U32, "InputStreamFree", STAMUNIT_COUNT, "Number of free input stream slots");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.fEnabled, STAMTYPE_BOOL, "OutputEnabled", STAMUNIT_NONE, "Whether output is enabled or not.");
+ PDMDrvHlpSTAMRegister(pDrvIns, &pThis->Out.cStreamsFree, STAMTYPE_U32, "OutputStreamFree", STAMUNIT_COUNT, "Number of free output stream slots");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Audio driver registration record.
+ */
+const PDMDRVREG g_DrvAUDIO =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "AUDIO",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Audio connector driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ UINT32_MAX,
+ /* cbInstance */
+ sizeof(DRVAUDIO),
+ /* pfnConstruct */
+ drvAudioConstruct,
+ /* pfnDestruct */
+ drvAudioDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ drvAudioSuspend,
+ /* pfnResume */
+ drvAudioResume,
+ /* pfnAttach */
+ drvAudioAttach,
+ /* pfnDetach */
+ drvAudioDetach,
+ /* pfnPowerOff */
+ drvAudioPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+