summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Audio/DrvHostPulseAudio.cpp')
-rw-r--r--src/VBox/Devices/Audio/DrvHostPulseAudio.cpp1744
1 files changed, 1744 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp
new file mode 100644
index 00000000..82de1ffb
--- /dev/null
+++ b/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp
@@ -0,0 +1,1744 @@
+/* $Id: DrvHostPulseAudio.cpp $ */
+/** @file
+ * VBox audio devices: Pulse Audio audio driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
+#include <VBox/log.h>
+#include <VBox/vmm/pdmaudioifs.h>
+
+#include <stdio.h>
+
+#include <iprt/alloc.h>
+#include <iprt/mem.h>
+#include <iprt/uuid.h>
+
+RT_C_DECLS_BEGIN
+ #include "pulse_mangling.h"
+ #include "pulse_stubs.h"
+RT_C_DECLS_END
+
+#include <pulse/pulseaudio.h>
+
+#include "DrvAudio.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */
+
+#ifndef PA_STREAM_NOFLAGS
+# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
+#endif
+
+#ifndef PA_CONTEXT_NOFLAGS
+# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
+#endif
+
+/** No flags specified. */
+#define PULSEAUDIOENUMCBFLAGS_NONE 0
+/** (Release) log found devices. */
+#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0)
+
+/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */
+#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \
+ ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) )
+
+
+/*********************************************************************************************************************************
+* Structures *
+*********************************************************************************************************************************/
+
+/**
+ * Host Pulse audio driver instance data.
+ * @implements PDMIAUDIOCONNECTOR
+ */
+typedef struct DRVHOSTPULSEAUDIO
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to PulseAudio's threaded main loop. */
+ pa_threaded_mainloop *pMainLoop;
+ /**
+ * Pointer to our PulseAudio context.
+ * Note: We use a pMainLoop in a separate thread (pContext).
+ * So either use callback functions or protect these functions
+ * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
+ */
+ pa_context *pContext;
+ /** Shutdown indicator. */
+ volatile bool fAbortLoop;
+ /** Enumeration operation successful? */
+ volatile bool fEnumOpSuccess;
+ /** Pointer to host audio interface. */
+ PDMIHOSTAUDIO IHostAudio;
+ /** Error count for not flooding the release log.
+ * Specify UINT32_MAX for unlimited logging. */
+ uint32_t cLogErrors;
+ /** The stream (base) name; needed for distinguishing
+ * streams in the PulseAudio mixer controls if multiple
+ * VMs are running at the same time. */
+ char szStreamName[64];
+} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO;
+
+typedef struct PULSEAUDIOSTREAM
+{
+ /** The stream's acquired configuration. */
+ PPDMAUDIOSTREAMCFG pCfg;
+ /** Pointer to driver instance. */
+ PDRVHOSTPULSEAUDIO pDrv;
+ /** Pointer to opaque PulseAudio stream. */
+ pa_stream *pStream;
+ /** Pulse sample format and attribute specification. */
+ pa_sample_spec SampleSpec;
+ /** Pulse playback and buffer metrics. */
+ pa_buffer_attr BufAttr;
+ int fOpSuccess;
+ /** Pointer to Pulse sample peeking buffer. */
+ const uint8_t *pu8PeekBuf;
+ /** Current size (in bytes) of peeking data in
+ * buffer. */
+ size_t cbPeekBuf;
+ /** Our offset (in bytes) in peeking buffer. */
+ size_t offPeekBuf;
+ pa_operation *pDrainOp;
+ /** Number of occurred audio data underflows. */
+ uint32_t cUnderflows;
+ /** Current latency (in us). */
+ uint64_t curLatencyUs;
+#ifdef LOG_ENABLED
+ /** Start time stamp (in us) of stream playback / recording. */
+ pa_usec_t tsStartUs;
+ /** Time stamp (in us) when last read from / written to the stream. */
+ pa_usec_t tsLastReadWrittenUs;
+#endif
+} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM;
+
+/**
+ * Callback context for server enumeration callbacks.
+ */
+typedef struct PULSEAUDIOENUMCBCTX
+{
+ /** Pointer to host backend driver. */
+ PDRVHOSTPULSEAUDIO pDrv;
+ /** Enumeration flags. */
+ uint32_t fFlags;
+ /** Number of found input devices. */
+ uint8_t cDevIn;
+ /** Number of found output devices. */
+ uint8_t cDevOut;
+ /** Name of default sink being used. Must be free'd using RTStrFree(). */
+ char *pszDefaultSink;
+ /** Name of default source being used. Must be free'd using RTStrFree(). */
+ char *pszDefaultSource;
+} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX;
+
+#ifndef PA_CONTEXT_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
+static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
+ return
+ x == PA_CONTEXT_CONNECTING ||
+ x == PA_CONTEXT_AUTHORIZING ||
+ x == PA_CONTEXT_SETTING_NAME ||
+ x == PA_CONTEXT_READY;
+}
+#endif /* !PA_CONTEXT_IS_GOOD */
+
+#ifndef PA_STREAM_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
+static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
+ return
+ x == PA_STREAM_CREATING ||
+ x == PA_STREAM_READY;
+}
+#endif /* !PA_STREAM_IS_GOOD */
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+
+static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum);
+static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg);
+#ifdef DEBUG
+static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext);
+static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext);
+#endif
+static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext);
+
+
+/**
+ * Signal the main loop to abort. Just signalling isn't sufficient as the
+ * mainloop might not have been entered yet.
+ */
+static void paSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
+{
+ if (!pThis)
+ return;
+
+ pThis->fAbortLoop = true;
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
+}
+
+
+static pa_sample_format_t paAudioPropsToPulse(PPDMAUDIOPCMPROPS pProps)
+{
+ switch (pProps->cBytes)
+ {
+ case 1:
+ if (!pProps->fSigned)
+ return PA_SAMPLE_U8;
+ break;
+
+ case 2:
+ if (pProps->fSigned)
+ return PA_SAMPLE_S16LE;
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case 4:
+ if (pProps->fSigned)
+ return PA_SAMPLE_S32LE;
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ AssertMsgFailed(("%RU8%s not supported\n", pProps->cBytes, pProps->fSigned ? "S" : "U"));
+ return PA_SAMPLE_INVALID;
+}
+
+
+static int paPulseToAudioProps(pa_sample_format_t pulsefmt, PPDMAUDIOPCMPROPS pProps)
+{
+ switch (pulsefmt)
+ {
+ case PA_SAMPLE_U8:
+ pProps->cBytes = 1;
+ pProps->fSigned = false;
+ break;
+
+ case PA_SAMPLE_S16LE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ break;
+
+ case PA_SAMPLE_S16BE:
+ pProps->cBytes = 2;
+ pProps->fSigned = true;
+ /** @todo Handle Endianess. */
+ break;
+
+#ifdef PA_SAMPLE_S32LE
+ case PA_SAMPLE_S32LE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+ break;
+#endif
+
+#ifdef PA_SAMPLE_S32BE
+ case PA_SAMPLE_S32BE:
+ pProps->cBytes = 4;
+ pProps->fSigned = true;
+ /** @todo Handle Endianess. */
+ break;
+#endif
+
+ default:
+ AssertLogRelMsgFailed(("PulseAudio: Format (%ld) not supported\n", pulsefmt));
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Synchronously wait until an operation completed.
+ */
+static int paWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOP, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ uint64_t u64StartMs = RTTimeMilliTS();
+ while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING)
+ {
+ if (!pThis->fAbortLoop)
+ {
+ AssertPtr(pThis->pMainLoop);
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ if ( !pThis->pContext
+ || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
+ {
+ LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
+ break;
+ }
+ }
+ pThis->fAbortLoop = false;
+
+ uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
+ if (u64ElapsedMs >= cMsTimeout)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ }
+
+ pa_operation_unref(pOP);
+
+ return rc;
+}
+
+
+static int paWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
+{
+ return paWaitForEx(pThis, pOP, 10 * 1000 /* 10s timeout */);
+}
+
+
+/**
+ * Context status changed.
+ */
+static void paContextCbStateChanged(pa_context *pCtx, void *pvUser)
+{
+ AssertPtrReturnVoid(pCtx);
+
+ PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_context_get_state(pCtx))
+ {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ paSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/**
+ * Callback called when our pa_stream_drain operation was completed.
+ */
+static void paStreamCbDrain(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
+ AssertPtrReturnVoid(pStreamPA);
+
+ pStreamPA->fOpSuccess = fSuccess;
+ if (fSuccess)
+ {
+ pa_operation_unref(pa_stream_cork(pStream, 1,
+ paStreamCbSuccess, pvUser));
+ }
+ else
+ paError(pStreamPA->pDrv, "Failed to drain stream");
+
+ if (pStreamPA->pDrainOp)
+ {
+ pa_operation_unref(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+}
+
+
+/**
+ * Stream status changed.
+ */
+static void paStreamCbStateChanged(pa_stream *pStream, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
+ AssertPtrReturnVoid(pThis);
+
+ switch (pa_stream_get_state(pStream))
+ {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ paSignalWaiter(pThis);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+#ifdef DEBUG
+static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext)
+{
+ RT_NOREF(cbLen, pvContext);
+
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
+ AssertPtrReturnVoid(pStrm);
+
+ pa_usec_t usec = 0;
+ int neg = 0;
+ pa_stream_get_latency(pStream, &usec, &neg);
+
+ Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000));
+}
+
+
+static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext)
+{
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
+ AssertPtrReturnVoid(pStrm);
+
+ pStrm->cUnderflows++;
+
+ LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
+
+ if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
+ && pStrm->curLatencyUs < 2000000 /* 2s */)
+ {
+ pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2;
+
+ LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */));
+
+ pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
+ pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
+
+ pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
+
+ pStrm->cUnderflows = 0;
+ }
+
+ pa_usec_t curLatencyUs = 0;
+ pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */);
+
+ LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */));
+
+# ifdef LOG_ENABLED
+ const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
+ const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
+
+ pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
+ pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec);
+ pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
+
+ Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n",
+ curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64,
+ curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels));
+# endif
+}
+
+
+static void paStreamCbOverflow(pa_stream *pStream, void *pvContext)
+{
+ RT_NOREF(pStream, pvContext);
+
+ Log2Func(("Warning: Hit overflow\n"));
+}
+#endif /* DEBUG */
+
+
+static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser)
+{
+ AssertPtrReturnVoid(pStream);
+
+ PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
+ AssertPtrReturnVoid(pStrm);
+
+ pStrm->fOpSuccess = fSuccess;
+
+ if (fSuccess)
+ paSignalWaiter(pStrm->pDrv);
+ else
+ paError(pStrm->pDrv, "Failed to finish stream operation");
+}
+
+
+static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
+
+ pa_stream *pStream = NULL;
+ uint32_t flags = PA_STREAM_NOFLAGS;
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ do
+ {
+ pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec;
+
+ LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n",
+ pszName, pSampleSpec->rate, pSampleSpec->channels,
+ pa_sample_format_to_string(pSampleSpec->format)));
+
+ if (!pa_sample_spec_valid(pSampleSpec))
+ {
+ LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName));
+ break;
+ }
+
+ pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr;
+
+ /** @todo r=andy Use pa_stream_new_with_proplist instead. */
+ if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */)))
+ {
+ LogRel(("PulseAudio: Could not create stream '%s'\n", pszName));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+#ifdef DEBUG
+ pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA);
+ pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA);
+ if (!fIn) /* Only for output streams. */
+ pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA);
+#endif
+ pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis);
+
+#if PA_API_VERSION >= 12
+ /* XXX */
+ flags |= PA_STREAM_ADJUST_LATENCY;
+#endif
+ /* For using pa_stream_get_latency() and pa_stream_get_time(). */
+ flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
+
+ /* No input/output right away after the stream was started. */
+ flags |= PA_STREAM_START_CORKED;
+
+ if (fIn)
+ {
+ LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
+ pBufAttr->maxlength, pBufAttr->fragsize));
+
+ if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
+ {
+ LogRel(("PulseAudio: Could not connect input stream '%s': %s\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+ }
+ else
+ {
+ LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
+ pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
+
+ if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
+ /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
+ {
+ LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n",
+ pszName, pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+ }
+
+ /* Wait until the stream is ready. */
+ for (;;)
+ {
+ if (!pThis->fAbortLoop)
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ pThis->fAbortLoop = false;
+
+ pa_stream_state_t streamSt = pa_stream_get_state(pStream);
+ if (streamSt == PA_STREAM_READY)
+ break;
+ else if ( streamSt == PA_STREAM_FAILED
+ || streamSt == PA_STREAM_TERMINATED)
+ {
+ LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt));
+ break;
+ }
+ }
+
+#ifdef LOG_ENABLED
+ pStreamPA->tsStartUs = pa_rtclock_now();
+#endif
+ const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
+ AssertPtr(pBufAttrObtained);
+ memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
+
+ LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n",
+ fIn ? "capture" : "playback",
+ pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf));
+
+ pStreamPA->pStream = pStream;
+
+ rc = VINF_SUCCESS;
+
+ } while (0);
+
+ if ( RT_FAILURE(rc)
+ && pStream)
+ pa_stream_disconnect(pStream);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pStream)
+ pa_stream_unref(pStream);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ LogFlowFuncEnter();
+
+ int rc = audioLoadPulseLib();
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
+ return rc;
+ }
+
+ LogRel(("PulseAudio: Using v%s\n", pa_get_library_version()));
+
+ pThis->fAbortLoop = false;
+ pThis->pMainLoop = NULL;
+
+ bool fLocked = false;
+
+ do
+ {
+ if (!(pThis->pMainLoop = pa_threaded_mainloop_new()))
+ {
+ LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox")))
+ {
+ LogRel(("PulseAudio: Failed to allocate context: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
+ {
+ LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+
+ /* Install a global callback to known if something happens to our acquired context. */
+ pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */);
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ fLocked = true;
+
+ if (pa_context_connect(pThis->pContext, NULL /* pszServer */,
+ PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ LogRel(("PulseAudio: Failed to connect to server: %s\n",
+ pa_strerror(pa_context_errno(pThis->pContext))));
+ break;
+ }
+
+ /* Wait until the pThis->pContext is ready. */
+ for (;;)
+ {
+ if (!pThis->fAbortLoop)
+ pa_threaded_mainloop_wait(pThis->pMainLoop);
+ pThis->fAbortLoop = false;
+
+ pa_context_state_t cstate = pa_context_get_state(pThis->pContext);
+ if (cstate == PA_CONTEXT_READY)
+ break;
+ else if ( cstate == PA_CONTEXT_TERMINATED
+ || cstate == PA_CONTEXT_FAILED)
+ {
+ LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate));
+ break;
+ }
+ }
+ }
+ while (0);
+
+ if (fLocked)
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_FAILURE(rc))
+ {
+ if (pThis->pMainLoop)
+ pa_threaded_mainloop_stop(pThis->pMainLoop);
+
+ if (pThis->pContext)
+ {
+ pa_context_disconnect(pThis->pContext);
+ pa_context_unref(pThis->pContext);
+ pThis->pContext = NULL;
+ }
+
+ if (pThis->pMainLoop)
+ {
+ pa_threaded_mainloop_free(pThis->pMainLoop);
+ pThis->pMainLoop = NULL;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ pStreamPA->pDrainOp = NULL;
+
+ pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
+ pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
+ pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
+
+ pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props) * RT_US_1MS;
+
+ const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec);
+
+ LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency));
+
+ pStreamPA->BufAttr.tlength = cbLatency;
+ pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
+ pStreamPA->BufAttr.prebuf = cbLatency;
+ pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
+
+ LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT);
+
+ char szName[256];
+ RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
+ DrvAudioHlpPlaybackDstToStr(pCfgReq->DestSource.Dest), pThis->szStreamName);
+
+ /* Note that the struct BufAttr is updated to the obtained values after this call! */
+ int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format));
+ return rc;
+ }
+
+ pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
+ pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
+ pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels);
+
+ LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
+ pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq);
+ pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength);
+ pCfgAcq->Backend.cfPreBuf = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf);
+
+ pStreamPA->pDrv = pThis;
+
+ return rc;
+}
+
+
+static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
+ pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
+ pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
+
+ pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
+ pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
+
+ Assert(pCfgReq->enmDir == PDMAUDIODIR_IN);
+
+ char szName[256];
+ RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
+ DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source), pThis->szStreamName);
+
+ /* Note: Other members of BufAttr are ignored for record streams. */
+ int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format));
+ return rc;
+ }
+
+ pStreamPA->pDrv = pThis;
+ pStreamPA->pu8PeekBuf = NULL;
+
+ pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
+ pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
+
+ pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize);
+ pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfBufferSize;
+ pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
+{
+ RT_NOREF(pvBuf, cxBuf);
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ /* We should only call pa_stream_readable_size() once and trust the first value. */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (cbAvail == (size_t)-1)
+ return paError(pStreamPA->pDrv, "Failed to determine input data size");
+
+ /* If the buffer was not dropped last call, add what remains. */
+ if (pStreamPA->pu8PeekBuf)
+ {
+ Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
+ cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
+ }
+
+ Log3Func(("cbAvail=%zu\n", cbAvail));
+
+ if (!cbAvail) /* No data? Bail out. */
+ {
+ if (pcxRead)
+ *pcxRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbToRead = RT_MIN(cbAvail, cxBuf);
+
+ Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
+ cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
+
+ uint32_t cbReadTotal = 0;
+
+ while (cbToRead)
+ {
+ /* If there is no data, do another peek. */
+ if (!pStreamPA->pu8PeekBuf)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_stream_peek(pStreamPA->pStream,
+ (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ pStreamPA->offPeekBuf = 0;
+
+ /* No data anymore?
+ * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
+ * we need to drop the stream lateron. */
+ if ( !pStreamPA->pu8PeekBuf
+ && !pStreamPA->cbPeekBuf)
+ {
+ break;
+ }
+ }
+
+ Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
+ size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
+
+ Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
+ cbToRead, cbToWrite,
+ pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
+
+ if ( cbToWrite
+ /* Only copy data if it's not a data hole (see above). */
+ && pStreamPA->pu8PeekBuf
+ && pStreamPA->cbPeekBuf)
+ {
+ memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
+
+ Assert(cbToRead >= cbToWrite);
+ cbToRead -= cbToWrite;
+ cbReadTotal += cbToWrite;
+
+ pStreamPA->offPeekBuf += cbToWrite;
+ Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
+ }
+
+ if (/* Nothing to write anymore? Drop the buffer. */
+ !cbToWrite
+ /* Was there a hole in the peeking buffer? Drop it. */
+ || !pStreamPA->pu8PeekBuf
+ /* If the buffer is done, drop it. */
+ || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ pa_stream_drop(pStreamPA->pStream);
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ pStreamPA->pu8PeekBuf = NULL;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxRead)
+ *pcxRead = cbReadTotal;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
+ uint32_t *pcxWritten)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
+ /* pcxWritten is optional. */
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream;
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbWrittenTotal = 0;
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+#ifdef LOG_ENABLED
+ const pa_usec_t tsNowUs = pa_rtclock_now();
+ const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs;
+
+ Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */));
+
+ pPAStream->tsLastReadWrittenUs = tsNowUs;
+#endif
+
+ do
+ {
+ size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream);
+ if (cbWriteable == (size_t)-1)
+ {
+ rc = paError(pPAStream->pDrv, "Failed to determine output data size");
+ break;
+ }
+
+ size_t cbLeft = RT_MIN(cbWriteable, cxBuf);
+ Assert(cbLeft); /* At this point we better have *something* to write. */
+
+ while (cbLeft)
+ {
+ uint32_t cbChunk = cbLeft; /* Write all at once for now. */
+
+ if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */,
+ 0, PA_SEEK_RELATIVE) < 0)
+ {
+ rc = paError(pPAStream->pDrv, "Failed to write to output stream");
+ break;
+ }
+
+ Assert(cbLeft >= cbChunk);
+ cbLeft -= cbChunk;
+ cbWrittenTotal += cbChunk;
+ }
+
+ } while (0);
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pcxWritten)
+ *pcxWritten = cbWrittenTotal;
+ }
+
+ return rc;
+}
+
+
+/** @todo Implement va handling. */
+static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
+
+ if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
+ {
+ int rc2 = pa_context_errno(pThis->pContext);
+ LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
+ }
+
+ /** @todo Implement some PulseAudio -> IPRT mapping here. */
+ return VERR_GENERAL_FAILURE;
+}
+
+
+static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
+{
+ if (eol > 0)
+ return;
+
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+ if (eol < 0)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(pInfo);
+
+ LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name));
+
+ /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */
+ pCbCtx->cDevOut++;
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+}
+
+
+static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
+{
+ if (eol > 0)
+ return;
+
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+ if (eol < 0)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(pInfo);
+
+ LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name));
+
+ /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */
+ pCbCtx->cDevIn++;
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+}
+
+
+static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
+{
+ AssertPtrReturnVoid(pCtx);
+ PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
+ AssertPtrReturnVoid(pCbCtx);
+ PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
+ AssertPtrReturnVoid(pThis);
+
+ if (!pInfo)
+ {
+ pThis->fEnumOpSuccess = false;
+ pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
+ return;
+ }
+
+ if (pInfo->default_sink_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
+ pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
+ }
+
+ if (pInfo->default_sink_name)
+ {
+ Assert(RTStrIsValidEncoding(pInfo->default_source_name));
+ pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
+ }
+
+ pThis->fEnumOpSuccess = true;
+ pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
+}
+
+
+static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
+
+ PDMAUDIOBACKENDCFG Cfg;
+ RT_ZERO(Cfg);
+
+ RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio driver");
+
+ Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM);
+ Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM);
+ Cfg.cMaxStreamsOut = UINT32_MAX;
+ Cfg.cMaxStreamsIn = UINT32_MAX;
+
+ PULSEAUDIOENUMCBCTX CbCtx;
+ RT_ZERO(CbCtx);
+
+ CbCtx.pDrv = pThis;
+ CbCtx.fFlags = fEnum;
+
+ bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
+
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pThis->fEnumOpSuccess = false;
+
+ LogRel(("PulseAudio: Retrieving server information ...\n"));
+
+ /* Check if server information is available and bail out early if it isn't. */
+ pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx);
+ if (!paOpServerInfo)
+ {
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ LogRel(("PulseAudio: Server information not available, skipping enumeration\n"));
+ return VINF_SUCCESS;
+ }
+
+ int rc = paWaitFor(pThis, paOpServerInfo);
+ if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
+ if (RT_SUCCESS(rc))
+ {
+ if (CbCtx.pszDefaultSink)
+ {
+ if (fLog)
+ LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
+
+ pThis->fEnumOpSuccess = false;
+ rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
+ paEnumSinkCb, &CbCtx));
+ if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
+ rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
+ if ( RT_FAILURE(rc)
+ && fLog)
+ {
+ LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink));
+ }
+ }
+ else if (fLog)
+ LogRel2(("PulseAudio: No default output sink found\n"));
+
+ if (RT_SUCCESS(rc))
+ {
+ if (CbCtx.pszDefaultSource)
+ {
+ if (fLog)
+ LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
+
+ pThis->fEnumOpSuccess = false;
+ rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
+ paEnumSourceCb, &CbCtx));
+ if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess)
+ && fLog)
+ {
+ LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource));
+ }
+ }
+ else if (fLog)
+ LogRel2(("PulseAudio: No default input source found\n"));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fLog)
+ {
+ LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut));
+ LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn));
+ }
+
+ if (pCfg)
+ memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
+ }
+
+ if (CbCtx.pszDefaultSink)
+ {
+ RTStrFree(CbCtx.pszDefaultSink);
+ CbCtx.pszDefaultSink = NULL;
+ }
+
+ if (CbCtx.pszDefaultSource)
+ {
+ RTStrFree(CbCtx.pszDefaultSource);
+ CbCtx.pszDefaultSource = NULL;
+ }
+ }
+ else if (fLog)
+ LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n"));
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ LogFlowFuncEnter();
+
+ if (pStreamPA->pStream)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ pa_stream_disconnect(pStreamPA->pStream);
+ pa_stream_unref(pStreamPA->pStream);
+
+ pStreamPA->pStream = NULL;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ if (pStreamPA->pStream)
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ /* Make sure to cancel a pending draining operation, if any. */
+ if (pStreamPA->pDrainOp)
+ {
+ pa_operation_cancel(pStreamPA->pDrainOp);
+ pStreamPA->pDrainOp = NULL;
+ }
+
+ pa_stream_disconnect(pStreamPA->pStream);
+ pa_stream_unref(pStreamPA->pStream);
+
+ pStreamPA->pStream = NULL;
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ if ( pStreamPA->pDrainOp
+ && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
+ {
+ pa_operation_cancel(pStreamPA->pDrainOp);
+ pa_operation_unref(pStreamPA->pDrainOp);
+
+ pStreamPA->pDrainOp = NULL;
+ }
+ else
+ {
+ /* Uncork (resume) stream. */
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA));
+ }
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
+ * Note that we must return immediately from here! */
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (!pStreamPA->pDrainOp)
+ {
+ rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA));
+ if (RT_SUCCESS(rc))
+ pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA);
+ }
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
+
+ switch (enmStreamCmd)
+ {
+ case PDMAUDIOSTREAMCMD_ENABLE:
+ case PDMAUDIOSTREAMCMD_RESUME:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA));
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ case PDMAUDIOSTREAMCMD_DISABLE:
+ case PDMAUDIOSTREAMCMD_PAUSE:
+ {
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+ if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
+ {
+ pa_stream_drop(pStreamPA->pStream);
+ pStreamPA->pu8PeekBuf = NULL;
+ }
+
+ rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA));
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
+ */
+static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface)
+{
+ AssertPtrReturnVoid(pInterface);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ LogFlowFuncEnter();
+
+ if (pThis->pMainLoop)
+ pa_threaded_mainloop_stop(pThis->pMainLoop);
+
+ if (pThis->pContext)
+ {
+ pa_context_disconnect(pThis->pContext);
+ pa_context_unref(pThis->pContext);
+ pThis->pContext = NULL;
+ }
+
+ if (pThis->pMainLoop)
+ {
+ pa_threaded_mainloop_free(pThis->pMainLoop);
+ pThis->pMainLoop = NULL;
+ }
+
+ LogFlowFuncLeave();
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
+{
+ RT_NOREF(enmDir);
+ AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
+
+ return PDMAUDIOBACKENDSTS_RUNNING;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
+ PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ int rc;
+ if (pCfgReq->enmDir == PDMAUDIODIR_IN)
+ rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq);
+ else if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
+ rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq);
+ else
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ if (RT_SUCCESS(rc))
+ {
+ pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
+ if (!pStreamPA->pCfg)
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = paDestroyStreamIn (pThis, pStreamPA);
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ rc = paDestroyStreamOut(pThis, pStreamPA);
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ if (RT_SUCCESS(rc))
+ {
+ DrvAudioHlpStreamCfgFree(pStreamPA->pCfg);
+ pStreamPA->pCfg = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamControl(PPDMIHOSTAUDIO pInterface,
+ PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
+ return VINF_SUCCESS;
+
+ int rc;
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd);
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd);
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ return rc;
+}
+
+
+static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
+{
+ pa_threaded_mainloop_lock(pThis->pMainLoop);
+
+ uint32_t cbAvail = 0;
+
+ if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
+ {
+ if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
+ {
+ cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
+ Log3Func(("cbReadable=%RU32\n", cbAvail));
+ }
+ else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
+ {
+ size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
+
+ Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
+ cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
+
+ /* Don't report more writable than the PA server can handle. */
+ if (cbWritable > pStreamPA->BufAttr.maxlength)
+ cbWritable = pStreamPA->BufAttr.maxlength;
+
+ cbAvail = (uint32_t)cbWritable;
+ }
+ else
+ AssertFailed();
+ }
+
+ pa_threaded_mainloop_unlock(pThis->pMainLoop);
+
+ return cbAvail;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
+ */
+static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ return paStreamGetAvail(pThis, pStreamPA);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
+ */
+static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+ PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+
+ return paStreamGetAvail(pThis, pStreamPA);
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
+ */
+static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ RT_NOREF(pStream);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
+
+ PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
+
+ /* Check PulseAudio's general status. */
+ if ( pThis->pContext
+ && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
+ {
+ strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
+ }
+
+ return strmSts;
+}
+
+
+/**
+ * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
+ */
+static DECLCALLBACK(int) drvHostPulseAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
+{
+ AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Nothing to do here for PulseAudio. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ AssertPtrReturn(pInterface, NULL);
+ AssertPtrReturn(pszIID, NULL);
+
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
+
+ return NULL;
+}
+
+
+/**
+ * Destructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVDESTRUCT
+ */
+static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ LogFlowFuncEnter();
+}
+
+
+/**
+ * Constructs a PulseAudio Audio driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg, fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
+
+ PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
+ LogRel(("Audio: Initializing PulseAudio driver\n"));
+
+ pThis->pDrvIns = pDrvIns;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface;
+ /* IHostAudio */
+ PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio);
+
+ int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
+ AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Pulse audio driver registration record.
+ */
+const PDMDRVREG g_DrvHostPulseAudio =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "PulseAudio",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Pulse Audio host driver",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_AUDIO,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTPULSEAUDIO),
+ /* pfnConstruct */
+ drvHostPulseAudioConstruct,
+ /* pfnDestruct */
+ drvHostPulseAudioDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+#if 0 /* unused */
+static struct audio_option pulse_options[] =
+{
+ {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out,
+ "DAC period size in milliseconds", NULL, 0},
+ {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in,
+ "ADC period size in milliseconds", NULL, 0}
+};
+#endif
+