DrvHostPulseAudio.cpp revision 605036d491298181444650ae12453c9207a7cf01
/* $Id$ */
/** @file
* VBox audio devices: Pulse Audio audio driver.
*/
/*
* Copyright (C) 2006-2015 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <stdio.h>
#include "pulse_mangling.h"
#include "pulse_stubs.h"
#include <pulse/pulseaudio.h>
#include "vl_vbox.h"
#include "DrvAudio.h"
#include "AudioMixBuffer.h"
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */
#ifndef PA_STREAM_NOFLAGS
#endif
#ifndef PA_CONTEXT_NOFLAGS
#endif
/*
* We use a g_pMainLoop in a separate thread g_pContext. We have to call functions for
* manipulating objects either from callback functions or we have to protect
* these functions by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
*/
static struct pa_threaded_mainloop *g_pMainLoop;
static struct pa_context *g_pContext;
/**
* Host Pulse audio driver instance data.
* @implements PDMIAUDIOCONNECTOR
*/
typedef struct DRVHOSTPULSEAUDIO
{
/** Pointer to the driver instance structure. */
/** Pointer to host audio interface. */
/** Error count for not flooding the release log.
* UINT32_MAX for unlimited logging. */
typedef struct PULSEAUDIOSTREAM
{
/** Must come first, as this struct might be
* casted to one of these structs. */
union
{
} hw;
/** Pointer to driver instance. */
void *pvPCMBuf;
/** Pointer to opaque PulseAudio stream. */
/** Pulse sample format and attribute specification. */
/** Pulse playback and buffer metrics. */
int fOpSuccess;
/** Pointer to Pulse sample peeking buffer. */
const uint8_t *pu8PeekBuf;
/** Current size (in bytes) of peeking data in
* buffer. */
/** Our offset (in bytes) in peeking buffer. */
/* The desired buffer length in milliseconds. Will be the target total stream
* latency on newer version of pulse. Apparent latency can be less (or more.)
*/
typedef struct PULSEAUDIOCFG
{
static PULSEAUDIOCFG s_pulseCfg =
{
100, /* buffer_msecs_out */
100 /* buffer_msecs_in */
};
/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */
#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \
{
switch (fmt)
{
case AUD_FMT_U8:
return PA_SAMPLE_U8;
case AUD_FMT_S16:
return PA_SAMPLE_S16LE;
#ifdef PA_SAMPLE_S32LE
case AUD_FMT_S32:
return PA_SAMPLE_S32LE;
#endif
default:
break;
}
return PA_SAMPLE_U8;
}
{
switch (pulsefmt)
{
case PA_SAMPLE_U8:
*pFmt = AUD_FMT_U8;
break;
case PA_SAMPLE_S16LE:
*pFmt = AUD_FMT_S16;
break;
case PA_SAMPLE_S16BE:
*pFmt = AUD_FMT_S16;
break;
#ifdef PA_SAMPLE_S32LE
case PA_SAMPLE_S32LE:
*pFmt = AUD_FMT_S32;
break;
#endif
#ifdef PA_SAMPLE_S32BE
case PA_SAMPLE_S32BE:
*pFmt = AUD_FMT_S32;
break;
#endif
default:
return VERR_NOT_SUPPORTED;
}
return VINF_SUCCESS;
}
/**
* Synchronously wait until an operation completed.
*/
{
int rc = VINF_SUCCESS;
if (pOP)
{
{
if (u64ElapsedMs >= cMsTimeout)
{
rc = VERR_TIMEOUT;
break;
}
}
}
return rc;
}
/**
* Context status changed.
*/
{
switch (pa_context_get_state(pContext))
{
case PA_CONTEXT_READY:
case PA_CONTEXT_TERMINATED:
break;
case PA_CONTEXT_FAILED:
break;
default:
break;
}
}
/**
* Callback called when our pa_stream_drain operation was completed.
*/
{
if (fSuccess)
{
}
else
}
/**
* Stream status changed.
*/
{
switch (pa_stream_get_state(pStream))
{
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
break;
default:
break;
}
}
{
if (fSuccess)
{
}
else
}
{
if (!pa_sample_spec_valid(pSampleSpec))
{
LogRel(("PulseAudio: Unsupported sample specification for stream \"%s\"\n",
pszName));
return VERR_NOT_SUPPORTED;
}
int rc = VINF_SUCCESS;
LogFunc(("Opening \"%s\", rate=%dHz, channels=%d, format=%s\n",
do
{
NULL /* pa_channel_map */)))
{
rc = VERR_NO_MEMORY;
break;
}
#if PA_API_VERSION >= 12
/* XXX */
#endif
#if 0
/* Not applicable as we don't use pa_stream_get_latency() and pa_stream_get_time(). */
#endif
if (fIn)
{
LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
{
LogRel(("PulseAudio: Could not connect input stream \"%s\": %s\n",
break;
}
}
else
{
LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
{
LogRel(("PulseAudio: Could not connect playback stream \"%s\": %s\n",
break;
}
}
/* Wait until the stream is ready. */
for (;;)
{
if (sstate == PA_STREAM_READY)
break;
else if ( sstate == PA_STREAM_FAILED
|| sstate == PA_STREAM_TERMINATED)
{
LogRel(("PulseAudio: Failed to initialize stream \"%s\" (state %ld)\n",
break;
}
}
if (RT_FAILURE(rc))
break;
if (fIn)
LogFunc(("Obtained record buffer attributes: maxlength=%RU32, fragsize=%RU32\n",
else
LogFunc(("Obtained playback buffer attributes: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d\n",
}
while (0);
if (RT_FAILURE(rc))
{
if (pStream)
if (pStream)
}
else
return rc;
}
{
int rc = audioLoadPulseLib();
if (RT_FAILURE(rc))
{
return rc;
}
bool fLocked = false;
do
{
if (!(g_pMainLoop = pa_threaded_mainloop_new()))
{
LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
rc = VERR_NO_MEMORY;
break;
}
{
LogRel(("PulseAudio: Failed to allocate context: %s\n",
rc = VERR_NO_MEMORY;
break;
}
if (pa_threaded_mainloop_start(g_pMainLoop) < 0)
{
LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
break;
}
fLocked = true;
PA_CONTEXT_NOFLAGS, NULL) < 0)
{
LogRel(("PulseAudio: Failed to connect to server: %s\n",
break;
}
/* Wait until the g_pContext is ready */
for (;;)
{
if (cstate == PA_CONTEXT_READY)
break;
else if ( cstate == PA_CONTEXT_TERMINATED
|| cstate == PA_CONTEXT_FAILED)
{
break;
}
}
}
while (0);
if (RT_FAILURE(rc))
{
if (g_pMainLoop)
{
if (fLocked)
if (g_pMainLoop)
}
if (g_pContext)
{
g_pContext = NULL;
}
if (g_pMainLoop)
{
g_pMainLoop = NULL;
}
}
return rc;
}
{
/* pcSamples is optional. */
/* Note that setting maxlength to -1 does not work on PulseAudio servers
* older than 0.9.10. So use the suggested value of 3/2 of tlength */
/* Note that the struct BufAttr is updated to the obtained values after this call! */
int rc = drvHostPulseAudioOpen(false /* fIn */, "pa.out", &pThisStrmOut->SampleSpec, &pThisStrmOut->BufAttr,
&pThisStrmOut->pStream);
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
{
return rc;
}
if (RT_SUCCESS(rc))
{
if (cbBuf)
{
if (pThisStrmOut->pvPCMBuf)
{
if (pcSamples)
}
else
rc = VERR_NO_MEMORY;
}
else
}
return rc;
}
{
return true; /* Always all enabled. */
}
{
/* pcSamples is optional. */
/* XXX check these values */
/* Note: Other members of pa_buffer_attr are ignored for record streams. */
int rc = drvHostPulseAudioOpen(true /* fIn */, "pa.in", &pThisStrmIn->SampleSpec, &pThisStrmIn->BufAttr,
&pThisStrmIn->pStream);
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
{
return rc;
}
if (RT_SUCCESS(rc))
{
if (pcSamples)
}
return rc;
}
static DECLCALLBACK(int) drvHostPulseAudioCaptureIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
{
/* pcSamplesPlayed is optional. */
/* We should only call pa_stream_readable_size() once and trust the first value. */
/* If the buffer was not dropped last call, add what remains. */
if (pThisStrmIn->pu8PeekBuf)
{
}
if (!cbAvail) /* No data? Bail out. */
{
if (pcSamplesCaptured)
*pcSamplesCaptured = 0;
return VINF_SUCCESS;
}
int rc = VINF_SUCCESS;
LogFlowFunc(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
uint32_t cWrittenTotal = 0;
while (cbToRead)
{
/* If there is no data, do another peek. */
if (!pThisStrmIn->pu8PeekBuf)
{
pThisStrmIn->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 ( !pThisStrmIn->pu8PeekBuf
&& !pThisStrmIn->cbPeekBuf)
{
break;
}
}
LogFlowFunc(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
if (cbToWrite)
{
if (RT_FAILURE(rc))
break;
}
if (/* Nothing to write anymore? Drop the buffer. */
/* Was there a hole in the peeking buffer? Drop it. */
|| !pThisStrmIn->pu8PeekBuf
/* If the buffer is done, drop it. */
{
}
}
if (RT_SUCCESS(rc))
{
uint32_t cProcessed = 0;
if (cWrittenTotal)
&cProcessed);
if (pcSamplesCaptured)
LogFlowFunc(("cWrittenTotal=%RU32 (%RU32 processed), rc=%Rrc\n",
}
return rc;
}
static DECLCALLBACK(int) drvHostPulseAudioPlayOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
{
/* pcSamplesPlayed is optional. */
int rc = VINF_SUCCESS;
uint32_t cbReadTotal = 0;
if (!cLive)
{
if (pcSamplesPlayed)
*pcSamplesPlayed = 0;
return VINF_SUCCESS;
}
do
{
{
break;
}
LogFlowFunc(("cbToRead=%zu, cbWriteable=%zu, cbLive=%zu\n",
while (cbToRead)
{
if ( !cRead
|| RT_FAILURE(rc))
{
break;
}
if (pa_stream_write(pThisStrmOut->pStream, pThisStrmOut->pvPCMBuf, cbRead, NULL /* Cleanup callback */,
0, PA_SEEK_RELATIVE) < 0)
{
break;
}
cbReadTotal += cbRead;
LogFlowFunc(("\tcRead=%RU32 (%zu bytes) cbReadTotal=%RU32, cbToRead=%RU32\n",
}
} while (0);
if (RT_SUCCESS(rc))
{
if (cReadTotal)
if (pcSamplesPlayed)
}
return rc;
}
/** @todo Implement va handling. */
{
{
}
/** @todo Implement some PulseAudio -> IPRT mapping here. */
return VERR_GENERAL_FAILURE;
}
static DECLCALLBACK(int) drvHostPulseAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
if (pThisStrmIn->pStream)
{
}
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvHostPulseAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
{
if (pThisStrmOut->pStream)
{
}
if (pThisStrmOut->pvPCMBuf)
{
pThisStrmOut->cbPCMBuf = 0;
}
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
{
if ( pThisStrmOut->pDrainOp
{
}
else
{
/* This should return immediately. */
15 * 1000 /* 15s timeout */);
}
break;
}
{
/* Pause audio output (the Pause bit of the AC97 x_CR register is set).
* Note that we must return immediately from here! */
if (!pThisStrmOut->pDrainOp)
{
/* This should return immediately. */
15 * 1000 /* 15s timeout */);
}
break;
}
default:
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostPulseAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
{
int rc = VINF_SUCCESS;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
{
/* This should return immediately. */
15 * 1000 /* 15s timeout */);
break;
}
{
{
}
/* This should return immediately. */
15 * 1000 /* 15s timeout */);
break;
}
default:
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostPulseAudioGetConf(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pCfg)
{
return VINF_SUCCESS;
}
{
if (g_pMainLoop)
if (g_pContext)
{
g_pContext = NULL;
}
if (g_pMainLoop)
{
g_pMainLoop = NULL;
}
}
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
return NULL;
}
/**
* Constructs a PulseAudio Audio driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
/* pCfg is optional. */
LogRel(("Audio: Initializing PulseAudio driver\n"));
/* IBase */
/* IHostAudio */
return VINF_SUCCESS;
}
/**
* Destructs a PulseAudio Audio driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
{
}
/**
* Char driver registration record.
*/
const PDMDRVREG g_DrvHostPulseAudio =
{
/* u32Version */
/* szName */
"PulseAudio",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"Pulse Audio host driver",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVHOSTPULSEAUDIO),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};
static struct audio_option pulse_options[] =
{
"DAC period size in milliseconds", NULL, 0},
"ADC period size in milliseconds", NULL, 0},
};