DrvHostALSAAudio.cpp revision 7b2cd67e9d38011fddd62967b7e9d26d88a28133
/* $Id$ */
/** @file
* VBox audio devices: ALSA 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.
* --------------------------------------------------------------------
*
* This code is based on: alsaaudio.c
*
* QEMU ALSA audio driver
*
* Copyright (c) 2005 Vassili Karpov (malc)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include "alsa_stubs.h"
#include "alsa_mangling.h"
#include <alsa/asoundlib.h>
#include "DrvAudio.h"
#include "AudioMixBuffer.h"
#include "VBoxDD.h"
#include "vl_vbox.h"
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
typedef struct ALSAAUDIOSTREAMIN
{
void *pvBuf;
typedef struct ALSAAUDIOSTREAMOUT
{
void *pvBuf;
/* latency = period_size * periods / (rate * bytes_per_frame) */
typedef struct ALSAAUDIOCFG
{
int size_in_usec_in;
int size_in_usec_out;
const char *pcm_name_in;
const char *pcm_name_out;
unsigned int buffer_size_in;
unsigned int period_size_in;
unsigned int buffer_size_out;
unsigned int period_size_out;
unsigned int threshold;
static ALSAAUDIOCFG s_ALSAConf =
{
#ifdef HIGH_LATENCY
1,
1,
#else
0,
0,
#endif
"default",
"default",
#ifdef HIGH_LATENCY
400000,
400000 / 4,
400000,
400000 / 4,
#else
# define DEFAULT_BUFFER_SIZE 1024
# define DEFAULT_PERIOD_SIZE 256
DEFAULT_BUFFER_SIZE * 4,
DEFAULT_PERIOD_SIZE * 4,
#endif
0,
0,
0,
0,
0
};
/**
* Host Alsa audio driver instance data.
* @implements PDMIAUDIOCONNECTOR
*/
typedef struct DRVHOSTALSAAUDIO
{
/** 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 ALSAAUDIOSTREAMCFG
{
unsigned int freq;
int nchannels;
unsigned long buffer_size;
unsigned long period_size;
{
return VINF_SUCCESS;
int rc;
if (rc2)
{
LogRel(("ALSA: Closing PCM descriptor failed: %s\n",
snd_strerror(rc2)));
}
else
{
rc = VINF_SUCCESS;
}
return rc;
}
{
switch (fmt)
{
case AUD_FMT_S8:
return SND_PCM_FORMAT_S8;
case AUD_FMT_U8:
return SND_PCM_FORMAT_U8;
case AUD_FMT_S16:
return SND_PCM_FORMAT_S16_LE;
case AUD_FMT_U16:
return SND_PCM_FORMAT_U16_LE;
case AUD_FMT_S32:
return SND_PCM_FORMAT_S32_LE;
case AUD_FMT_U32:
return SND_PCM_FORMAT_U32_LE;
default:
break;
}
return SND_PCM_FORMAT_U8;
}
{
/* pEndianness is optional. */
switch (fmt)
{
case SND_PCM_FORMAT_S8:
*pFmt = AUD_FMT_S8;
if (pEndianness)
break;
case SND_PCM_FORMAT_U8:
*pFmt = AUD_FMT_U8;
if (pEndianness)
break;
case SND_PCM_FORMAT_S16_LE:
*pFmt = AUD_FMT_S16;
if (pEndianness)
break;
case SND_PCM_FORMAT_U16_LE:
*pFmt = AUD_FMT_U16;
if (pEndianness)
break;
case SND_PCM_FORMAT_S16_BE:
*pFmt = AUD_FMT_S16;
if (pEndianness)
break;
case SND_PCM_FORMAT_U16_BE:
*pFmt = AUD_FMT_U16;
if (pEndianness)
break;
case SND_PCM_FORMAT_S32_LE:
*pFmt = AUD_FMT_S32;
if (pEndianness)
break;
case SND_PCM_FORMAT_U32_LE:
*pFmt = AUD_FMT_U32;
if (pEndianness)
break;
case SND_PCM_FORMAT_S32_BE:
*pFmt = AUD_FMT_S32;
if (pEndianness)
break;
case SND_PCM_FORMAT_U32_BE:
*pFmt = AUD_FMT_U32;
if (pEndianness)
break;
default:
return VERR_NOT_SUPPORTED;
}
return VINF_SUCCESS;
}
{
switch (fmt)
{
case SND_PCM_FORMAT_S8:
case SND_PCM_FORMAT_U8:
*puShift = 0;
break;
case SND_PCM_FORMAT_S16_LE:
case SND_PCM_FORMAT_U16_LE:
case SND_PCM_FORMAT_S16_BE:
case SND_PCM_FORMAT_U16_BE:
*puShift = 1;
break;
case SND_PCM_FORMAT_S32_LE:
case SND_PCM_FORMAT_U32_LE:
case SND_PCM_FORMAT_S32_BE:
case SND_PCM_FORMAT_U32_BE:
*puShift = 2;
break;
default:
return VERR_NOT_SUPPORTED;
}
return VINF_SUCCESS;
}
{
if (!pSWParms)
return VERR_NO_MEMORY;
int rc;
do
{
if (err < 0)
{
LogRel(("ALSA: Failed to get current software parameters for threshold: %s\n",
snd_strerror(err)));
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to set software threshold to %ld: %s\n",
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to set new software parameters for threshold: %s\n",
snd_strerror(err)));
break;
}
rc = VINF_SUCCESS;
}
while (0);
return rc;
}
static int drvHostALSAAudioOpen(bool fIn,
{
int rc;
do
{
if (!pszDev)
{
LogRel(("ALSA: Invalid or no %s device name set\n",
break;
}
if (err < 0)
{
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to initialize hardware parameters: %s\n",
snd_strerror(err)));
break;
}
if (err < 0)
{
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to set audio format to %d: %s\n",
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to set frequency to %dHz: %s\n",
break;
}
if (err < 0)
{
break;
}
if ( cChannels != 1
&& cChannels != 2)
{
break;
}
{
if (!buffer_size)
{
}
}
if (buffer_size)
{
{
if (period_size)
{
&period_size, 0);
if (err < 0)
{
break;
}
}
&buffer_size, 0);
if (err < 0)
{
break;
}
}
else
{
if (period_size_f)
{
int dir = 0;
if (err < 0)
{
LogRel(("ALSA: Could not determine minimal period size\n"));
break;
}
else
{
if (period_size_f < minval)
{
{
LogFunc(("Period size %RU32 is less than minimal period size %RU32\n",
period_size_f, minval));
}
}
}
&period_size_f, 0);
if (err < 0)
{
LogRel(("ALSA: Failed to set period size %d (%s)\n",
break;
}
}
/* Calculate default buffer size here since it might have been changed
* in the _near functions */
if (err < 0)
{
LogRel(("ALSA: Could not retrieve minimal buffer size\n"));
break;
}
else
{
if (buffer_size_f < minval)
{
{
LogFunc(("Buffer size %RU32 is less than minimal buffer size %RU32\n",
buffer_size_f, minval));
}
}
}
if (err < 0)
{
LogRel(("ALSA: Failed to set buffer size %d: %s\n",
break;
}
}
}
else
LogFunc(("Warning: Buffer size is not set\n"));
if (err < 0)
{
LogRel(("ALSA: Failed to apply audio parameters\n"));
break;
}
if (err < 0)
{
LogRel(("ALSA: Failed to get buffer size\n"));
break;
}
int dir = 0;
if (err < 0)
{
LogRel(("ALSA: Failed to get period size\n"));
break;
}
LogFunc(("Freq=%dHz, period size=%RU32, buffer size=%RU32\n",
if (err < 0)
{
break;
}
if ( !fIn
&& s_ALSAConf.threshold)
{
unsigned uShift;
if (RT_SUCCESS(rc))
{
int bytes_per_sec = uFreq
<< (cChannels == 2)
<< uShift;
}
}
else
rc = VINF_SUCCESS;
}
while (0);
if (RT_SUCCESS(rc))
{
}
else
return rc;
}
#ifdef DEBUG
{
/** @todo Implement me! */
}
#endif
{
int rc;
if (framesAvail < 0)
{
if (framesAvail == -EPIPE)
{
if (RT_SUCCESS(rc))
}
else
}
else
rc = VINF_SUCCESS;
if (framesAvail >= 0)
return rc;
}
{
if (err < 0)
{
LogFunc(("Failed to recover stream %p: %s\n",
return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
}
return VINF_SUCCESS;
}
{
if (err < 0)
{
LogFunc(("Failed to resume stream %p: %s\n",
return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
}
return VINF_SUCCESS;
}
{
int err;
if (fPause)
{
if (err < 0)
{
LogFlow(("Error stopping stream %p: %s\n",
return VERR_ACCESS_DENIED;
}
}
else
{
if (err < 0)
{
LogFlow(("Error preparing stream %p: %s\n",
return VERR_ACCESS_DENIED;
}
}
return VINF_SUCCESS;
}
{
int rc = audioLoadAlsaLib();
if (RT_FAILURE(rc))
else
{
#ifdef DEBUG
#endif
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioCaptureIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
{
if (RT_FAILURE(rc))
{
return rc;
}
if (!cAvail) /* No data yet? */
{
switch (state)
{
case SND_PCM_STATE_PREPARED:
break;
case SND_PCM_STATE_SUSPENDED:
{
if (RT_FAILURE(rc))
break;
LogFlow(("Resuming suspended input stream\n"));
break;
}
default:
break;
}
if (!cAvail)
{
if (pcSamplesCaptured)
*pcSamplesCaptured = 0;
return VINF_SUCCESS;
}
}
uint32_t cWrittenTotal = 0;
while ( cbToRead
&& RT_SUCCESS(rc))
{
if (cRead <= 0)
{
switch (cRead)
{
case 0:
{
LogFunc(("No input frames available\n"));
break;
}
case -EAGAIN:
/*
* Don't set error here because EAGAIN means there are no further frames
* available at the moment, try later. As we might have read some frames
* already these need to be processed instead.
*/
cbToRead = 0;
break;
case -EPIPE:
{
if (RT_FAILURE(rc))
break;
LogFlowFunc(("Recovered from capturing\n"));
continue;
}
default:
break;
}
}
else
{
&cWritten);
if (RT_FAILURE(rc))
break;
}
}
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) drvHostALSAAudioPlayOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
{
int rc = VINF_SUCCESS;
uint32_t cbReadTotal = 0;
do
{
if (RT_FAILURE(rc))
{
break;
}
cAvail),
LogFlowFunc(("cbToRead=%zu, cbAvail=%zu\n",
while (cbToRead)
{
if (RT_FAILURE(rc))
break;
if (cWritten <= 0)
{
switch (cWritten)
{
case 0:
{
break;
}
case -EPIPE:
{
if (RT_FAILURE(rc))
break;
LogFlowFunc(("Recovered from playback\n"));
continue;
}
case -ESTRPIPE:
{
/* Stream was suspended and waiting for a recovery. */
if (RT_FAILURE(rc))
{
LogRel(("ALSA: Failed to resume output stream\n"));
break;
}
LogFlowFunc(("Resumed suspended output stream\n"));
continue;
}
default:
LogFlowFunc(("Failed to write %RI32 output frames, rc=%Rrc\n",
break;
}
}
if (RT_FAILURE(rc))
break;
cbReadTotal += cbRead;
}
}
while (0);
if (RT_SUCCESS(rc))
{
if (cReadTotal)
if (pcSamplesPlayed)
LogFlowFunc(("cReadTotal=%RU32 (%RU32 bytes), rc=%Rrc\n",
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
if (pThisStrmIn->pvBuf)
{
}
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvHostALSAAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
{
if (pThisStrmOut->pvBuf)
{
}
return VINF_SUCCESS;
}
{
int rc;
do
{
if (RT_FAILURE(rc))
break;
if (RT_FAILURE(rc))
break;
if (RT_FAILURE(rc))
break;
if (!pThisStrmOut->pvBuf)
{
LogRel(("ALSA: Not enough memory for output DAC buffer (%RU32 samples, each %d bytes)\n",
rc = VERR_NO_MEMORY;
break;
}
if (pcSamples)
}
while (0);
if (RT_FAILURE(rc))
return rc;
}
{
int rc;
do
{
if (RT_FAILURE(rc))
break;
if (RT_FAILURE(rc))
break;
if (RT_FAILURE(rc))
break;
if (!pThisStrmIn->pvBuf)
{
LogRel(("ALSA: Not enough memory for input ADC buffer (%RU32 samples, each %d bytes)\n",
rc = VERR_NO_MEMORY;
break;
}
if (pcSamples)
}
while (0);
if (RT_FAILURE(rc))
return rc;
}
{
return true; /* Always all enabled. */
}
static DECLCALLBACK(int) drvHostALSAAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
{
int rc;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
break;
break;
default:
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioControlOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
{
int rc;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
break;
break;
default:
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioGetConf(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pCfg)
{
return VINF_SUCCESS;
}
{
}
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
static DECLCALLBACK(void *) drvHostALSAAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
return NULL;
}
/**
* Construct a DirectSound Audio driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
LogRel(("Audio: Initializing ALSA driver\n"));
/*
* Init the static parts.
*/
/* IBase */
/* IHostAudio */
return VINF_SUCCESS;
}
/**
* Char driver registration record.
*/
const PDMDRVREG g_DrvHostALSAAudio =
{
/* u32Version */
/* szName */
"ALSAAudio",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"ALSA host audio driver",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVHOSTALSAAUDIO),
/* pfnConstruct */
/* pfnDestruct */
NULL,
/* 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 alsa_options[] =
{
"(undocumented)", NULL, 0},
"DAC device name (for instance dmix)", NULL, 0},
"ADC device name", NULL, 0},
};