DrvHostALSAAudio.cpp revision 7da9e7e719adde3baba3f6fa1d0bcfb170cf9911
/* $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;
* 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.
* --------------------------------------------------------------------
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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 <iprt/alloc.h>
#include <iprt/uuid.h> /* For PDMIBASE_2_PDMDRV. */
#include <VBox/vmm/pdmaudioifs.h>
RT_C_DECLS_BEGIN
#include "alsa_stubs.h"
#include "alsa_mangling.h"
RT_C_DECLS_END
#include <alsa/asoundlib.h>
#include "DrvAudio.h"
#include "AudioMixBuffer.h"
#include "VBoxDD.h"
#include "vl_vbox.h"
#ifdef LOG_GROUP
# undef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
#include <VBox/log.h>
typedef struct ALSAAUDIOSTREAMIN
{
PDMAUDIOHSTSTRMIN pStreamIn;
snd_pcm_t *phPCM;
void *pvBuf;
size_t cbBuf;
} ALSAAUDIOSTREAMIN, *PALSAAUDIOSTREAMIN;
typedef struct ALSAAUDIOSTREAMOUT
{
PDMAUDIOHSTSTRMOUT pStreamOut;
snd_pcm_t *phPCM;
void *pvBuf;
size_t cbBuf;
} ALSAAUDIOSTREAMOUT, *PALSAAUDIOSTREAMOUT;
/* 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;
int buffer_size_in_overriden;
int period_size_in_overriden;
int buffer_size_out_overriden;
int period_size_out_overriden;
} ALSAAUDIOCFG, *PALSAAUDIOCFG;
static int drvHostALSAAudioRecover(snd_pcm_t *phPCM);
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,
DEFAULT_BUFFER_SIZE,
DEFAULT_PERIOD_SIZE,
#endif
0,
0,
0,
0,
0
};
/**
* Host Alsa audio driver instance data.
* @implements PDMIAUDIOCONNECTOR
*/
typedef struct DRVHOSTALSAAUDIO
{
/** Pointer to the driver instance structure. */
PPDMDRVINS pDrvIns;
/** Pointer to host audio interface. */
PDMIHOSTAUDIO IHostAudio;
/** Error count for not flooding the release log.
* UINT32_MAX for unlimited logging. */
uint32_t cLogErrors;
} DRVHOSTALSAAUDIO, *PDRVHOSTALSAAUDIO;
typedef struct ALSAAUDIOSTREAMCFG
{
unsigned int freq;
snd_pcm_format_t fmt;
int nchannels;
unsigned long buffer_size;
unsigned long period_size;
snd_pcm_uframes_t samples;
} ALSAAUDIOSTREAMCFG, *PALSAAUDIOSTREAMCFG;
static int drvHostALSAAudioClose(snd_pcm_t **pphPCM)
{
if (!pphPCM || !*pphPCM)
return VINF_SUCCESS;
int rc;
int rc2 = snd_pcm_close(*pphPCM);
if (rc2)
{
LogRel(("ALSA: Closing PCM descriptor failed: %s\n",
snd_strerror(rc2)));
rc = VERR_GENERAL_FAILURE; /** @todo */
}
else
{
*pphPCM = NULL;
rc = VINF_SUCCESS;
}
return rc;
}
static snd_pcm_format_t drvHostALSAAudioFmtToALSA(PDMAUDIOFMT fmt)
{
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;
}
AssertMsgFailed(("Format %ld not supported\n", fmt));
return SND_PCM_FORMAT_U8;
}
static int drvHostALSAAudioALSAToFmt(snd_pcm_format_t fmt,
PDMAUDIOFMT *pFmt, PDMAUDIOENDIANESS *pEndianness)
{
AssertPtrReturn(pFmt, VERR_INVALID_POINTER);
/* pEndianness is optional. */
switch (fmt)
{
case SND_PCM_FORMAT_S8:
*pFmt = AUD_FMT_S8;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_U8:
*pFmt = AUD_FMT_U8;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_S16_LE:
*pFmt = AUD_FMT_S16;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_U16_LE:
*pFmt = AUD_FMT_U16;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_S16_BE:
*pFmt = AUD_FMT_S16;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_BIG;
break;
case SND_PCM_FORMAT_U16_BE:
*pFmt = AUD_FMT_U16;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_BIG;
break;
case SND_PCM_FORMAT_S32_LE:
*pFmt = AUD_FMT_S32;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_U32_LE:
*pFmt = AUD_FMT_U32;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_LITTLE;
break;
case SND_PCM_FORMAT_S32_BE:
*pFmt = AUD_FMT_S32;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_BIG;
break;
case SND_PCM_FORMAT_U32_BE:
*pFmt = AUD_FMT_U32;
if (pEndianness)
*pEndianness = PDMAUDIOENDIANESS_BIG;
break;
default:
AssertMsgFailed(("Format %ld not supported\n", fmt));
return VERR_NOT_SUPPORTED;
}
return VINF_SUCCESS;
}
static int drvHostALSAAudioALSAGetShift(snd_pcm_format_t fmt, unsigned *puShift)
{
AssertPtrReturn(puShift, VERR_INVALID_POINTER);
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:
AssertMsgFailed(("Format %ld not supported\n", fmt));
return VERR_NOT_SUPPORTED;
}
return VINF_SUCCESS;
}
static int drvHostALSAAudioSetThreshold(snd_pcm_t *phPCM,
snd_pcm_uframes_t threshold)
{
snd_pcm_sw_params_t *pSWParms = NULL;
snd_pcm_sw_params_alloca(&pSWParms);
if (!pSWParms)
return VERR_NO_MEMORY;
int rc;
do
{
int err = snd_pcm_sw_params_current(phPCM, pSWParms);
if (err < 0)
{
LogRel(("ALSA: Failed to get current software parameters for threshold: %s\n",
snd_strerror(err)));
rc = VERR_ACCESS_DENIED;
break;
}
err = snd_pcm_sw_params_set_start_threshold(phPCM, pSWParms, threshold);
if (err < 0)
{
LogRel(("ALSA: Failed to set software threshold to %ld: %s\n",
threshold, snd_strerror(err)));
rc = VERR_ACCESS_DENIED;
break;
}
err = snd_pcm_sw_params(phPCM, pSWParms);
if (err < 0)
{
LogRel(("ALSA: Failed to set new software parameters for threshold: %s\n",
snd_strerror(err)));
rc = VERR_ACCESS_DENIED;
break;
}
LogFlowFunc(("Setting threshold to %RU32\n", threshold));
rc = VINF_SUCCESS;
}
while (0);
return rc;
}
static int drvHostALSAAudioOpen(bool fIn,
PALSAAUDIOSTREAMCFG pCfgReq,
PALSAAUDIOSTREAMCFG pCfgObt,
snd_pcm_t **pphPCM)
{
snd_pcm_t *phPCM = NULL;
int rc;
unsigned int cChannels = pCfgReq->nchannels;
unsigned int uFreq = pCfgReq->freq;
snd_pcm_uframes_t obt_buffer_size;
do
{
const char *pszDev = fIn ? s_ALSAConf.pcm_name_in : s_ALSAConf.pcm_name_out;
if (!pszDev)
{
LogRel(("ALSA: Invalid or no %s device name set\n",
fIn ? "input" : "output"));
rc = VERR_INVALID_PARAMETER;
break;
}
int err = snd_pcm_open(&phPCM, pszDev,
fIn ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK);
if (err < 0)
{
LogRel(("ALSA: Failed to open \"%s\" as %s: %s\n", pszDev,
fIn ? "ADC" : "DAC", snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
snd_pcm_hw_params_t *pHWParms;
snd_pcm_hw_params_alloca(&pHWParms); /** @todo Check for successful allocation? */
err = snd_pcm_hw_params_any(phPCM, pHWParms);
if (err < 0)
{
LogRel(("ALSA: Failed to initialize hardware parameters: %s\n",
snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
err = snd_pcm_hw_params_set_access(phPCM, pHWParms,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0)
{
LogRel(("ALSA: Failed to set access type: %s\n", snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
err = snd_pcm_hw_params_set_format(phPCM, pHWParms, pCfgReq->fmt);
if (err < 0)
{
LogRel(("ALSA: Failed to set audio format to %d: %s\n",
pCfgReq->fmt, snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
err = snd_pcm_hw_params_set_rate_near(phPCM, pHWParms, &uFreq, 0);
if (err < 0)
{
LogRel(("ALSA: Failed to set frequency to %dHz: %s\n",
pCfgReq->freq, snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
err = snd_pcm_hw_params_set_channels_near(phPCM, pHWParms, &cChannels);
if (err < 0)
{
LogRel(("ALSA: Failed to set number of channels to %d\n", pCfgReq->nchannels));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
if ( cChannels != 1
&& cChannels != 2)
{
LogRel(("ALSA: Number of audio channels (%u) not supported\n", cChannels));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
unsigned int period_size = pCfgReq->period_size;
unsigned int buffer_size = pCfgReq->buffer_size;
if ( !((fIn && s_ALSAConf.size_in_usec_in)
|| (!fIn && s_ALSAConf.size_in_usec_out)))
{
if (!buffer_size)
{
buffer_size = DEFAULT_BUFFER_SIZE;
period_size = DEFAULT_PERIOD_SIZE;
}
}
if (buffer_size)
{
if ( ( fIn && s_ALSAConf.size_in_usec_in)
|| (!fIn && s_ALSAConf.size_in_usec_out))
{
if (period_size)
{
err = snd_pcm_hw_params_set_period_time_near(phPCM, pHWParms,
&period_size, 0);
if (err < 0)
{
LogRel(("ALSA: Failed to set period time %d\n", pCfgReq->period_size));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
}
err = snd_pcm_hw_params_set_buffer_time_near(phPCM, pHWParms,
&buffer_size, 0);
if (err < 0)
{
LogRel(("ALSA: Failed to set buffer time %d\n", pCfgReq->buffer_size));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
}
else
{
snd_pcm_uframes_t period_size_f = (snd_pcm_uframes_t)period_size;
snd_pcm_uframes_t buffer_size_f = (snd_pcm_uframes_t)buffer_size;
snd_pcm_uframes_t minval;
if (period_size_f)
{
minval = period_size_f;
int dir = 0;
err = snd_pcm_hw_params_get_period_size_min(pHWParms,
&minval, &dir);
if (err < 0)
{
LogRel(("ALSA: Could not determine minimal period size\n"));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
else
{
LogFunc(("Minimal period size is: %ld\n", minval));
if (period_size_f < minval)
{
if ( ( fIn && s_ALSAConf.period_size_in_overriden)
|| (!fIn && s_ALSAConf.period_size_out_overriden))
{
LogFunc(("Period size %RU32 is less than minimal period size %RU32\n",
period_size_f, minval));
}
period_size_f = minval;
}
}
err = snd_pcm_hw_params_set_period_size_near(phPCM, pHWParms,
&period_size_f, 0);
LogFunc(("Period size is: %RU32\n", period_size_f));
if (err < 0)
{
LogRel(("ALSA: Failed to set period size %d (%s)\n",
period_size_f, snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
}
/* Calculate default buffer size here since it might have been changed
* in the _near functions */
buffer_size_f = 4 * period_size_f;
minval = buffer_size_f;
err = snd_pcm_hw_params_get_buffer_size_min(pHWParms, &minval);
if (err < 0)
{
LogRel(("ALSA: Could not retrieve minimal buffer size\n"));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
else
{
LogFunc(("Minimal buffer size is: %RU32\n", minval));
if (buffer_size_f < minval)
{
if ( ( fIn && s_ALSAConf.buffer_size_in_overriden)
|| (!fIn && s_ALSAConf.buffer_size_out_overriden))
{
LogFunc(("Buffer size %RU32 is less than minimal buffer size %RU32\n",
buffer_size_f, minval));
}
buffer_size_f = minval;
}
}
err = snd_pcm_hw_params_set_buffer_size_near(phPCM,
pHWParms, &buffer_size_f);
LogFunc(("Buffer size is: %RU32\n", buffer_size_f));
if (err < 0)
{
LogRel(("ALSA: Failed to set buffer size %d: %s\n",
buffer_size_f, snd_strerror(err)));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
}
}
else
LogFunc(("Warning: Buffer size is not set\n"));
err = snd_pcm_hw_params(phPCM, pHWParms);
if (err < 0)
{
LogRel(("ALSA: Failed to apply audio parameters\n"));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
err = snd_pcm_hw_params_get_buffer_size(pHWParms, &obt_buffer_size);
if (err < 0)
{
LogRel(("ALSA: Failed to get buffer size\n"));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
snd_pcm_uframes_t obt_period_size;
int dir = 0;
err = snd_pcm_hw_params_get_period_size(pHWParms, &obt_period_size, &dir);
if (err < 0)
{
LogRel(("ALSA: Failed to get period size\n"));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
LogFunc(("Freq=%dHz, period size=%RU32, buffer size=%RU32\n",
pCfgReq->freq, obt_period_size, obt_buffer_size));
err = snd_pcm_prepare(phPCM);
if (err < 0)
{
LogRel(("ALSA: Could not prepare hPCM %p\n", (void *)phPCM));
rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
break;
}
if ( !fIn
&& s_ALSAConf.threshold)
{
unsigned uShift;
rc = drvHostALSAAudioALSAGetShift(pCfgReq->fmt, &uShift);
if (RT_SUCCESS(rc))
{
int bytes_per_sec = uFreq
<< (cChannels == 2)
<< uShift;
snd_pcm_uframes_t threshold
= (s_ALSAConf.threshold * bytes_per_sec) / 1000;
rc = drvHostALSAAudioSetThreshold(phPCM, threshold);
}
}
else
rc = VINF_SUCCESS;
}
while (0);
if (RT_SUCCESS(rc))
{
pCfgObt->fmt = pCfgReq->fmt;
pCfgObt->nchannels = cChannels;
pCfgObt->freq = uFreq;
pCfgObt->samples = obt_buffer_size;
*pphPCM = phPCM;
}
else
drvHostALSAAudioClose(&phPCM);
LogFlowFuncLeaveRC(rc);
return rc;
}
#ifdef DEBUG
static void drvHostALSAAudioErrorHandler(const char *file, int line, const char *function,
int err, const char *fmt, ...)
{
/** @todo Implement me! */
}
#endif
static int drvHostALSAAudioGetAvail(snd_pcm_t *phPCM, snd_pcm_sframes_t *pFramesAvail)
{
AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
AssertPtrReturn(pFramesAvail, VERR_INVALID_POINTER);
int rc;
snd_pcm_sframes_t framesAvail;
framesAvail = snd_pcm_avail_update(phPCM);
if (framesAvail < 0)
{
if (framesAvail == -EPIPE)
{
rc = drvHostALSAAudioRecover(phPCM);
if (RT_SUCCESS(rc))
framesAvail = snd_pcm_avail_update(phPCM);
}
else
rc = VERR_ACCESS_DENIED; /** @todo Find a better rc. */
}
else
rc = VINF_SUCCESS;
if (framesAvail >= 0)
*pFramesAvail = framesAvail;
return rc;
}
static int drvHostALSAAudioRecover(snd_pcm_t *phPCM)
{
AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
int err = snd_pcm_prepare(phPCM);
if (err < 0)
{
LogFunc(("Failed to recover stream %p: %s\n",
phPCM, snd_strerror(err)));
return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
}
return VINF_SUCCESS;
}
static int drvHostALSAAudioResume(snd_pcm_t *phPCM)
{
AssertPtrReturn(phPCM, VERR_INVALID_POINTER);
int err = snd_pcm_resume(phPCM);
if (err < 0)
{
LogFunc(("Failed to resume stream %p: %s\n",
phPCM, snd_strerror(err)));
return VERR_ACCESS_DENIED; /** @todo Find a better rc. */
}
return VINF_SUCCESS;
}
static int drvHostALSAAudioStreamCtl(snd_pcm_t *phPCM, bool fPause)
{
int err;
if (fPause)
{
err = snd_pcm_drop(phPCM);
if (err < 0)
{
LogFlow(("Error stopping stream %p: %s\n",
phPCM, snd_strerror(err)));
return VERR_ACCESS_DENIED;
}
}
else
{
err = snd_pcm_prepare (phPCM);
if (err < 0)
{
LogFlow(("Error preparing stream %p: %s\n",
phPCM, snd_strerror(err)));
return VERR_ACCESS_DENIED;
}
}
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvHostALSAAudioInit(PPDMIHOSTAUDIO pInterface)
{
NOREF(pInterface);
LogFlowFuncEnter();
int rc = audioLoadAlsaLib();
if (RT_FAILURE(rc))
LogRel(("ALSA: Failed to load the ALSA shared library, rc=%Rrc\n", rc));
else
{
#ifdef DEBUG
snd_lib_error_set_handler(drvHostALSAAudioErrorHandler);
#endif
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioCaptureIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
uint32_t *pcSamplesCaptured)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMIN pThisStrmIn = (PALSAAUDIOSTREAMIN)pHstStrmIn;
snd_pcm_sframes_t cAvail;
int rc = drvHostALSAAudioGetAvail(pThisStrmIn->phPCM, &cAvail);
if (RT_FAILURE(rc))
{
LogFunc(("Error getting number of captured frames, rc=%Rrc\n", rc));
return rc;
}
if (!cAvail) /* No data yet? */
{
snd_pcm_state_t state = snd_pcm_state(pThisStrmIn->phPCM);
switch (state)
{
case SND_PCM_STATE_PREPARED:
cAvail = audioMixBufFree(&pHstStrmIn->MixBuf);
break;
case SND_PCM_STATE_SUSPENDED:
{
rc = drvHostALSAAudioResume(pThisStrmIn->phPCM);
if (RT_FAILURE(rc))
break;
LogFlow(("Resuming suspended input stream\n"));
break;
}
default:
LogFlow(("No frames available, state=%d\n", state));
break;
}
if (!cAvail)
{
if (pcSamplesCaptured)
*pcSamplesCaptured = 0;
return VINF_SUCCESS;
}
}
Assert(cAvail);
size_t cbToRead = AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf, cAvail);
LogFlowFunc(("cbToRead=%zu, cAvail=%RI32\n", cbToRead, cAvail));
uint32_t cWrittenTotal = 0;
snd_pcm_uframes_t cToRead;
snd_pcm_sframes_t cRead;
while (cbToRead)
{
cToRead = RT_MIN(AUDIOMIXBUF_B2S(&pHstStrmIn->MixBuf, cbToRead),
AUDIOMIXBUF_B2S(&pHstStrmIn->MixBuf, pThisStrmIn->cbBuf));
AssertBreakStmt(cToRead, rc = VERR_NO_DATA);
cRead = snd_pcm_readi(pThisStrmIn->phPCM, pThisStrmIn->pvBuf, cToRead);
if (cRead <= 0)
{
switch (cRead)
{
case 0:
{
LogFunc(("Failed to read %RI32 frames\n", cRead));
rc = VERR_ACCESS_DENIED;
break;
}
case -EAGAIN:
break;
case -EPIPE:
{
rc = drvHostALSAAudioRecover(pThisStrmIn->phPCM);
if (RT_FAILURE(rc))
break;
LogFlowFunc(("Recovered from capturing\n"));
continue;
}
default:
LogFlowFunc(("Failed to read %RI32 input frames, rc=%Rrc\n",
cRead, rc));
rc = VERR_GENERAL_FAILURE; /** @todo */
break;
}
}
else
{
uint32_t cWritten;
rc = audioMixBufWriteCirc(&pHstStrmIn->MixBuf,
pThisStrmIn->pvBuf, AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf, cRead),
&cWritten);
if (RT_FAILURE(rc))
break;
uint32_t cbWritten = AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf, cWritten);
Assert(cbToRead >= cbWritten);
cbToRead -= cbWritten;
cWrittenTotal += cWritten;
}
}
if (RT_SUCCESS(rc))
{
uint32_t cProcessed = 0;
if (cWrittenTotal)
rc = audioMixBufMixToParent(&pHstStrmIn->MixBuf, cWrittenTotal,
&cProcessed);
if (pcSamplesCaptured)
*pcSamplesCaptured = cWrittenTotal;
LogFlowFunc(("cWrittenTotal=%RU32 (%RU32 processed), rc=%Rrc\n",
cWrittenTotal, cProcessed, rc));
}
LogFlowFuncLeaveRC(rc);
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioPlayOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
uint32_t *pcSamplesPlayed)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMOUT pThisStrmOut = (PALSAAUDIOSTREAMOUT)pHstStrmOut;
int rc = VINF_SUCCESS;
uint32_t cbReadTotal = 0;
do
{
snd_pcm_sframes_t cAvail;
rc = drvHostALSAAudioGetAvail(pThisStrmOut->phPCM, &cAvail);
if (RT_FAILURE(rc))
{
LogFunc(("Error getting number of playback frames, rc=%Rrc\n", rc));
break;
}
size_t cbToRead = RT_MIN(AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf,
cAvail),
AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf,
drvAudioHstOutSamplesLive(pHstStrmOut, NULL /* pcStreamsLive */)));
LogFlowFunc(("cbToRead=%zu, cbAvail=%zu\n",
cbToRead, AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cAvail)));
uint32_t cRead, cbRead;
snd_pcm_sframes_t cWritten;
while (cbToRead)
{
rc = audioMixBufReadCirc(&pHstStrmOut->MixBuf, pThisStrmOut->pvBuf, cbToRead, &cRead);
if (RT_FAILURE(rc))
break;
cbRead = AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cRead);
AssertBreak(cbRead);
cWritten = snd_pcm_writei(pThisStrmOut->phPCM, pThisStrmOut->pvBuf, cRead);
if (cWritten <= 0)
{
switch (cWritten)
{
case 0:
{
LogFunc(("Failed to write %RI32 frames\n", cRead));
rc = VERR_ACCESS_DENIED;
break;
}
case -EPIPE:
{
rc = drvHostALSAAudioRecover(pThisStrmOut->phPCM);
if (RT_FAILURE(rc))
break;
LogFlowFunc(("Recovered from playback\n"));
continue;
}
case -ESTRPIPE:
{
/* Stream was suspended and waiting for a recovery. */
rc = drvHostALSAAudioResume(pThisStrmOut->phPCM);
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",
cRead, rc));
rc = VERR_GENERAL_FAILURE; /** @todo */
break;
}
}
if (RT_FAILURE(rc))
break;
Assert(cbToRead >= cRead);
cbToRead -= cbRead;
cbReadTotal += cbRead;
}
}
while (0);
if (RT_SUCCESS(rc))
{
uint32_t cReadTotal = AUDIOMIXBUF_B2S(&pHstStrmOut->MixBuf, cbReadTotal);
if (cReadTotal)
audioMixBufFinish(&pHstStrmOut->MixBuf, cReadTotal);
if (pcSamplesPlayed)
*pcSamplesPlayed = cReadTotal;
LogFlowFunc(("cReadTotal=%RU32 (%RU32 bytes), rc=%Rrc\n",
cReadTotal, cbReadTotal, rc));
}
LogFlowFuncLeaveRC(rc);
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMIN pThisStrmIn = (PALSAAUDIOSTREAMIN)pHstStrmIn;
drvHostALSAAudioClose(&pThisStrmIn->phPCM);
if (pThisStrmIn->pvBuf)
{
RTMemFree(pThisStrmIn->pvBuf);
pThisStrmIn->pvBuf = NULL;
}
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvHostALSAAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMOUT pThisStrmOut = (PALSAAUDIOSTREAMOUT)pHstStrmOut;
drvHostALSAAudioClose(&pThisStrmOut->phPCM);
if (pThisStrmOut->pvBuf)
{
RTMemFree(pThisStrmOut->pvBuf);
pThisStrmOut->pvBuf = NULL;
}
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvHostALSAAudioInitOut(PPDMIHOSTAUDIO pInterface,
PPDMAUDIOHSTSTRMOUT pHstStrmOut, PPDMAUDIOSTREAMCFG pCfg,
uint32_t *pcSamples)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMOUT pThisStrmOut = (PALSAAUDIOSTREAMOUT)pHstStrmOut;
snd_pcm_t *phPCM = NULL;
int rc;
do
{
ALSAAUDIOSTREAMCFG req;
req.fmt = drvHostALSAAudioFmtToALSA(pCfg->enmFormat);
req.freq = pCfg->uHz;
req.nchannels = pCfg->cChannels;
req.period_size = s_ALSAConf.period_size_out;
req.buffer_size = s_ALSAConf.buffer_size_out;
ALSAAUDIOSTREAMCFG obt;
rc = drvHostALSAAudioOpen(false /* false */, &req, &obt, &phPCM);
if (RT_FAILURE(rc))
break;
PDMAUDIOFMT enmFormat;
PDMAUDIOENDIANESS enmEnd;
rc = drvHostALSAAudioALSAToFmt(obt.fmt, &enmFormat, &enmEnd);
if (RT_FAILURE(rc))
break;
PDMAUDIOSTREAMCFG streamCfg;
streamCfg.uHz = obt.freq;
streamCfg.cChannels = obt.nchannels;
streamCfg.enmFormat = enmFormat;
streamCfg.enmEndianness = enmEnd;
rc = drvAudioStreamCfgToProps(&streamCfg, &pHstStrmOut->Props);
if (RT_FAILURE(rc))
break;
AssertBreakStmt(obt.samples, rc = VERR_INVALID_PARAMETER);
size_t cbBuf = obt.samples * (1 << pHstStrmOut->Props.cShift);
AssertBreakStmt(cbBuf, rc = VERR_INVALID_PARAMETER);
pThisStrmOut->pvBuf = RTMemAlloc(cbBuf);
if (!pThisStrmOut->pvBuf)
{
LogRel(("ALSA: Not enough memory for output DAC buffer (%RU32 samples, each %d bytes)\n",
obt.samples, 1 << pHstStrmOut->Props.cShift));
rc = VERR_NO_MEMORY;
break;
}
pThisStrmOut->cbBuf = cbBuf;
pThisStrmOut->phPCM = phPCM;
if (pcSamples)
*pcSamples = obt.samples;
}
while (0);
if (RT_FAILURE(rc))
drvHostALSAAudioClose(&phPCM);
LogFlowFuncLeaveRC(rc);
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioInitIn(PPDMIHOSTAUDIO pInterface,
PPDMAUDIOHSTSTRMIN pHstStrmIn, PPDMAUDIOSTREAMCFG pCfg,
PDMAUDIORECSOURCE enmRecSource,
uint32_t *pcSamples)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
int rc;
PALSAAUDIOSTREAMIN pThisStrmIn = (PALSAAUDIOSTREAMIN)pHstStrmIn;
snd_pcm_t *phPCM = NULL;
do
{
ALSAAUDIOSTREAMCFG req;
req.fmt = drvHostALSAAudioFmtToALSA(pCfg->enmFormat);
req.freq = pCfg->uHz;
req.nchannels = pCfg->cChannels;
req.period_size = s_ALSAConf.period_size_in;
req.buffer_size = s_ALSAConf.buffer_size_in;
ALSAAUDIOSTREAMCFG obt;
rc = drvHostALSAAudioOpen(true /* fIn */, &req, &obt, &phPCM);
if (RT_FAILURE(rc))
break;
PDMAUDIOFMT enmFormat;
PDMAUDIOENDIANESS enmEnd;
rc = drvHostALSAAudioALSAToFmt(obt.fmt, &enmFormat, &enmEnd);
if (RT_FAILURE(rc))
break;
PDMAUDIOSTREAMCFG streamCfg;
streamCfg.uHz = obt.freq;
streamCfg.cChannels = obt.nchannels;
streamCfg.enmFormat = enmFormat;
streamCfg.enmEndianness = enmEnd;
rc = drvAudioStreamCfgToProps(&streamCfg, &pHstStrmIn->Props);
if (RT_FAILURE(rc))
break;
AssertBreakStmt(obt.samples, rc = VERR_INVALID_PARAMETER);
size_t cbBuf = obt.samples * (1 << pHstStrmIn->Props.cShift);
AssertBreakStmt(cbBuf, rc = VERR_INVALID_PARAMETER);
pThisStrmIn->pvBuf = RTMemAlloc(cbBuf);
if (!pThisStrmIn->pvBuf)
{
LogRel(("ALSA: Not enough memory for input ADC buffer (%RU32 samples, each %d bytes)\n",
obt.samples, 1 << pHstStrmIn->Props.cShift));
rc = VERR_NO_MEMORY;
break;
}
pThisStrmIn->cbBuf = cbBuf;
pThisStrmIn->phPCM = phPCM;
if (pcSamples)
*pcSamples = obt.samples;
}
while (0);
if (RT_FAILURE(rc))
drvHostALSAAudioClose(&phPCM);
LogFlowFuncLeaveRC(rc);
return rc;
}
static DECLCALLBACK(bool) drvHostALSAAudioIsEnabled(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
{
NOREF(pInterface);
NOREF(enmDir);
return true; /* Always all enabled. */
}
static DECLCALLBACK(int) drvHostALSAAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
PDMAUDIOSTREAMCMD enmStreamCmd)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMIN pThisStrmIn = (PALSAAUDIOSTREAMIN)pHstStrmIn;
LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
int rc;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
rc = drvHostALSAAudioStreamCtl(pThisStrmIn->phPCM, false /* fStop */);
break;
case PDMAUDIOSTREAMCMD_DISABLE:
rc = drvHostALSAAudioStreamCtl(pThisStrmIn->phPCM, true /* fStop */);
break;
default:
AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
rc = VERR_INVALID_PARAMETER;
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioControlOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
PDMAUDIOSTREAMCMD enmStreamCmd)
{
NOREF(pInterface);
AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
PALSAAUDIOSTREAMOUT pThisStrmOut = (PALSAAUDIOSTREAMOUT)pHstStrmOut;
LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
int rc;
switch (enmStreamCmd)
{
case PDMAUDIOSTREAMCMD_ENABLE:
rc = drvHostALSAAudioStreamCtl(pThisStrmOut->phPCM, false /* fStop */);
break;
case PDMAUDIOSTREAMCMD_DISABLE:
rc = drvHostALSAAudioStreamCtl(pThisStrmOut->phPCM, true /* fStop */);
break;
default:
AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
rc = VERR_INVALID_PARAMETER;
break;
}
return rc;
}
static DECLCALLBACK(int) drvHostALSAAudioGetConf(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pCfg)
{
NOREF(pInterface);
AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
pCfg->cbStreamOut = sizeof(ALSAAUDIOSTREAMOUT);
pCfg->cbStreamIn = sizeof(ALSAAUDIOSTREAMIN);
pCfg->cMaxHstStrmsOut = INT_MAX;
pCfg->cMaxHstStrmsIn = INT_MAX;
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
static DECLCALLBACK(void *) drvHostALSAAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
return NULL;
}
static DECLCALLBACK(void) drvHostAlsaAudioDestruct(PPDMDRVINS pDrvIns)
{
}
/**
* Construct a DirectSound Audio driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
static DECLCALLBACK(int) drvHostAlsaAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
{
PDRVHOSTALSAAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTALSAAUDIO);
LogRel(("Audio: Initializing ALSA driver\n"));
/*
* Init the static parts.
*/
pThis->pDrvIns = pDrvIns;
/* IBase */
pDrvIns->IBase.pfnQueryInterface = drvHostALSAAudioQueryInterface;
/* IHostAudio */
PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostALSAAudio);
return VINF_SUCCESS;
}
/**
* Char driver registration record.
*/
const PDMDRVREG g_DrvHostALSAAudio =
{
/* u32Version */
PDM_DRVREG_VERSION,
/* szName */
"ALSAAudio",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"ALSA host audio driver",
/* fFlags */
PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
/* fClass. */
PDM_DRVREG_CLASS_AUDIO,
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVHOSTALSAAUDIO),
/* pfnConstruct */
drvHostAlsaAudioConstruct,
/* pfnDestruct */
drvHostAlsaAudioDestruct,
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32EndVersion */
PDM_DRVREG_VERSION
};
static struct audio_option alsa_options[] =
{
{"DACSizeInUsec", AUD_OPT_BOOL, &s_ALSAConf.size_in_usec_out,
"DAC period/buffer size in microseconds (otherwise in frames)", NULL, 0},
{"DACPeriodSize", AUD_OPT_INT, &s_ALSAConf.period_size_out,
"DAC period size", &s_ALSAConf.period_size_out_overriden, 0},
{"DACBufferSize", AUD_OPT_INT, &s_ALSAConf.buffer_size_out,
"DAC buffer size", &s_ALSAConf.buffer_size_out_overriden, 0},
{"ADCSizeInUsec", AUD_OPT_BOOL, &s_ALSAConf.size_in_usec_in,
"ADC period/buffer size in microseconds (otherwise in frames)", NULL, 0},
{"ADCPeriodSize", AUD_OPT_INT, &s_ALSAConf.period_size_in,
"ADC period size", &s_ALSAConf.period_size_in_overriden, 0},
{"ADCBufferSize", AUD_OPT_INT, &s_ALSAConf.buffer_size_in,
"ADC buffer size", &s_ALSAConf.buffer_size_in_overriden, 0},
{"Threshold", AUD_OPT_INT, &s_ALSAConf.threshold,
"(undocumented)", NULL, 0},
{"DACDev", AUD_OPT_STR, &s_ALSAConf.pcm_name_out,
"DAC device name (for instance dmix)", NULL, 0},
{"ADCDev", AUD_OPT_STR, &s_ALSAConf.pcm_name_in,
"ADC device name", NULL, 0},
NULL
};