pulseaudio.c revision e64031e20c39650a7bc902a3e1aba613b9415dee
/** @file
*
* VBox PulseAudio backend
*/
/*
* Copyright (C) 2006-2007 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 *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
#include <pulse/pulseaudio.h>
#include "pulse_stubs.h"
#include "../../vl_vbox.h"
#include "audio.h"
#define AUDIO_CAP "pulse"
#include "audio_int.h"
#include <stdio.h>
#define MAX_LOG_REL_ERRORS 32
/*
* 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;
typedef struct PulseVoice
{
/** not accessed from within this context */
union
{
} hw;
/** DAC buffer */
void *pPCMBuf;
/** Pulse stream */
/** Pulse sample format and attribute specification */
/** Pulse playback and buffer metrics */
int fOpSuccess;
/** number of logged errors */
unsigned cErrors;
/** Pulse record peek buffer */
const uint8_t *pu8PeekBuf;
} PulseVoice;
/* 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.)
*/
static struct
{
int buffer_msecs_out;
int buffer_msecs_in;
} conf
=
{
};
{
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:
return PA_SAMPLE_U8;
}
}
{
switch (pulsefmt)
{
case PA_SAMPLE_U8:
*endianess = 0;
*fmt = AUD_FMT_U8;
break;
case PA_SAMPLE_S16LE:
*fmt = AUD_FMT_S16;
*endianess = 0;
break;
case PA_SAMPLE_S16BE:
*fmt = AUD_FMT_S16;
*endianess = 1;
break;
#ifdef PA_SAMPLE_S32LE
case PA_SAMPLE_S32LE:
*fmt = AUD_FMT_S32;
*endianess = 0;
break;
#endif
#ifdef PA_SAMPLE_S32BE
case PA_SAMPLE_S32BE:
*fmt = AUD_FMT_S32;
*endianess = 1;
break;
#endif
default:
return -1;
}
return 0;
}
{
if (!fSuccess)
{
{
}
}
}
/**
* Synchronously wait until an operation completed.
*/
{
if (op)
{
}
return 1;
}
/**
* Context status changed.
*/
{
switch (pa_context_get_state(pContext))
{
case PA_CONTEXT_READY:
case PA_CONTEXT_TERMINATED:
break;
case PA_CONTEXT_FAILED:
if (pPulse)
break;
default:
break;
}
}
/**
* Stream status changed.
*/
{
switch (pa_stream_get_state(pStream))
{
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
break;
default:
break;
}
}
/**
* Callback called when our pa_stream_drain operation was completed.
*/
{
if (!fSuccess)
{
{
}
}
else
}
{
const pa_buffer_attr *pBufAttrObtained;
char achPCMName[64];
pa_stream_flags_t flags = 0;
const char *stream_name = audio_get_stream_name();
LogRel(("Pulse: open %s rate=%dHz channels=%d format=%s\n",
if (!pa_sample_spec_valid(pSampleSpec))
{
LogRel(("Pulse: Unsupported sample specification\n"));
goto fail;
}
{
goto unlock_and_fail;
}
#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)
{
LogRel(("Pulse: Requested record buffer attributes: maxlength=%d fragsize=%d\n",
{
LogRel(("Pulse: Cannot connect record stream: %s\n",
}
}
else
{
LogRel(("Pulse: Requested playback buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
{
LogRel(("Pulse: Cannot connect playback stream: %s\n",
}
}
/* Wait until the stream is ready */
for (;;)
{
if (sstate == PA_STREAM_READY)
break;
{
}
}
if (fIn)
{
LogRel(("Pulse: Obtained record buffer attributes: maxlength=%d fragsize=%d\n",
}
else
{
LogRel(("Pulse: Obtained playback buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
}
return 0;
fail:
if (pStream)
return -1;
}
{
int cbBuf;
/* 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 */
/* Notice that the struct BufAttr is updated to the obtained values after this call */
return -1;
{
return -1;
}
{
return -1;
}
/* Convert from bytes to frames (aka samples) */
return 0;
}
{
{
}
{
}
}
{
int cFramesLive;
int cFramesWritten = 0;
int csSamples;
int cFramesToWrite;
int cFramesAvail;
if (!cFramesLive)
return 0;
{
{
LogRel(("Pulse: Failed to determine the writable size: %s\n",
pa_strerror(rc)));
}
goto unlock_and_exit;
}
while (csSamples)
{
/* split request at the end of our samples buffer */
{
LogRel(("Pulse: Failed to write %d samples: %s\n",
break;
}
}
return cFramesWritten;
}
{
}
{
switch (cmd)
{
case VOICE_ENABLE:
/* Start audio output. */
{
}
else
{
/* should return immediately */
}
break;
case VOICE_DISABLE:
/* Pause audio output (the Pause bit of the AC97 x_CR register is set).
* Note that we must return immediately from here! */
{
/* should return immediately */
}
break;
default:
return -1;
}
return 0;
}
{
/* XXX check these values */
/* Other memebers of pa_buffer_attr are ignored for record streams */
return -1;
{
return -1;
}
return 0;
}
{
{
}
}
{
int cFramesRead = 0; /* total frames which have been read this call */
int cFramesAvail; /* total frames available from pulse at start of call */
int cFramesToRead; /* the largest amount we want/can get this call */
int cFramesToPeek; /* the largest amount we want/can get this peek */
/* We should only call pa_stream_readable_size() once and trust the first value */
if (cFramesAvail == -1)
{
{
LogRel(("Pulse: Failed to determine the readable size: %s\n",
pa_strerror(rc)));
}
return 0;
}
/* If the buffer was not dropped last call, add what remains */
if (pPulse->pu8PeekBuf)
{
/* If there is no data, do another peek */
if (!pPulse->pu8PeekBuf)
{
pPulse->offPeekBuf = 0;
if ( !pPulse->pu8PeekBuf
break;
}
/* Check for wrapping around the buffer end */
{
}
else
{
}
/* If the buffer is done, drop it */
{
}
}
return cFramesRead;
}
{
}
{
switch (cmd)
{
case VOICE_ENABLE:
/* should return immediately */
break;
case VOICE_DISABLE:
if (pPulse->pu8PeekBuf)
{
}
/* should return immediately */
break;
default:
return -1;
}
return 0;
}
static void *pulse_audio_init (void)
{
int rc;
rc = audioLoadPulseLib();
if (RT_FAILURE(rc))
{
return NULL;
}
if (!(g_pMainLoop = pa_threaded_mainloop_new()))
{
LogRel(("Pulse: Failed to allocate main loop: %s\n",
goto fail;
}
{
LogRel(("Pulse: Failed to allocate context: %s\n",
goto fail;
}
if (pa_threaded_mainloop_start(g_pMainLoop) < 0)
{
LogRel(("Pulse: Failed to start threaded mainloop: %s\n",
goto fail;
}
{
LogRel(("Pulse: Failed to connect to server: %s\n",
goto unlock_and_fail;
}
/* Wait until the g_pContext is ready */
for (;;)
{
if (cstate == PA_CONTEXT_READY)
break;
{
goto unlock_and_fail;
}
}
return &conf;
if (g_pMainLoop)
fail:
if (g_pMainLoop)
if (g_pContext)
{
g_pContext = NULL;
}
if (g_pMainLoop)
{
g_pMainLoop = NULL;
}
return NULL;
}
static void pulse_audio_fini (void *opaque)
{
if (g_pMainLoop)
if (g_pContext)
{
g_pContext = NULL;
}
if (g_pMainLoop)
{
g_pMainLoop = NULL;
}
(void) opaque;
}
static struct audio_option pulse_options[] =
{
"DAC period size in milliseconds", NULL, 0},
"ADC period size in milliseconds", NULL, 0},
};
static struct audio_pcm_ops pulse_pcm_ops =
{
};
struct audio_driver pulse_audio_driver =
{
};