DrvAudio.cpp revision f9de02a9df6b89c81f9cfa3fecf268f188085e1b
/* $Id$ */
/** @file
* Intermediate audio driver header.
*
* @remarks Intermediate audio driver having audio device as one of the sink and
* host backend as other.
*/
/*
* Copyright (C) 2006-2014 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 be1 useful, but WITHOUT ANY WARRANTY of any kind.
* --------------------------------------------------------------------
*
* This code is based on: audio.c from QEMU AUDIO subsystem.
*
* QEMU Audio subsystem
*
* Copyright (c) 2003-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.
*/
#include <iprt/asm-math.h>
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_DEV_AUDIO
#include "VBoxDD.h"
#include "vl_vbox.h"
#include <ctype.h>
#include <stdlib.h>
#include "DrvAudio.h"
#include "AudioMixBuffer.h"
static int drvAudioAllocHstIn(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg, PDMAUDIORECSOURCE enmRecSource, PPDMAUDIOHSTSTRMIN *ppHstStrmIn);
{
0,
};
{
0,
};
{
0,
};
{
int rc;
{
}
else
rc = VERR_NOT_FOUND;
if (RT_FAILURE(rc))
{
if (!pHstStrmOut)
{
if (RT_FAILURE(rc))
}
}
if (RT_SUCCESS(rc))
return rc;
}
{
if ( pCfgHandle == NULL
{
*pfDefault = true;
return enmDefault;
}
if (RT_FAILURE(rc))
{
*pfDefault = true;
return enmDefault;
}
if (fmt == AUD_FMT_INVALID)
{
*pfDefault = true;
return enmDefault;
}
*pfDefault = false;
return fmt;
}
{
if ( pCfgHandle == NULL
{
*pfDefault = true;
return iDefault;
}
if (RT_FAILURE(rc))
{
*pfDefault = true;
return iDefault;
}
*pfDefault = false;
return u64Data;
}
const char *pszDefault, bool *pfDefault)
{
if ( pCfgHandle == NULL
{
*pfDefault = true;
return pszDefault;
}
if (RT_FAILURE(rc))
{
*pfDefault = true;
return pszDefault;
}
*pfDefault = false;
return pszValue;
}
static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, struct audio_option *opt)
{
/* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
* The getter function will return default values.
*/
if (pCfgHandle != NULL)
{
/* If its audio general setting, need to traverse to one child node.
*/
{
if(pCfgChildHandle)
}
else
{
/* If its driver specific configuration , then need to traverse two level deep child
* child nodes. for eg. in case of DirectSoundConfiguration item
* /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
*/
if (pCfgChildHandle)
{
if (pCfgChildChildHandle)
}
}
}
{
LogFlowFunc(("Option value pointer for `%s' is not set\n",
LogFlowFunc(("Option value pointer for `%s' is not set\n",
continue;
}
bool fUseDefault;
{
case AUD_OPT_BOOL:
case AUD_OPT_INT:
{
break;
}
case AUD_OPT_FMT:
{
break;
}
case AUD_OPT_STR:
{
break;
}
default:
LogFlowFunc(("Bad value tag for option `%s' - %d\n",
break;
}
if (!opt->overridenp)
}
return VINF_SUCCESS;
}
{
if (fValid)
{
{
case AUD_FMT_S8:
case AUD_FMT_U8:
case AUD_FMT_S16:
case AUD_FMT_U16:
case AUD_FMT_S32:
case AUD_FMT_U32:
break;
default:
fValid = false;
break;
}
}
/** @todo Check for defined frequencies supported. */
#ifdef DEBUG
#endif
return fValid;
}
{
if (!len)
return;
{
}
else
{
{
case 8:
break;
case 16:
{
int i;
short s = INT16_MAX;
if (pPCMInfo->fSwapEndian)
s = bswap16(s);
p[i] = s;
}
break;
case 32:
{
int i;
if (pPCMInfo->fSwapEndian)
s = bswap32(s);
p[i] = s;
}
break;
default:
break;
}
}
}
/*
* Guest input stream handling (capturing from the host).
*/
/*
* Host output handling (playing output from the guest).
*/
{
int rc;
{
if (RT_SUCCESS(rc))
{
/* Remove from driver instance list. */
return VINF_SUCCESS;
}
}
else
LogFlowFunc(("Host output stream %p still being used, rc=%Rrc\n",
pHstStrmOut, rc));
return rc;
}
{
if (pGstStrmOut)
{
if (pGstStrmOut->pHstStrmOut)
{
/* Unregister from parent first. */
/* Try destroying the associated host output stream. This could
* be skipped if there are other guest output streams with this
* host stream. */
}
}
return VINF_SUCCESS;
}
{
if (pHstStrmIn)
{
return NULL;
}
}
{
if (pHstStrmIn->fEnabled)
return pHstStrmIn;
return NULL;
}
{
return pHstStrmIn;
return NULL;
}
static int drvAudioHstInAdd(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfg, PDMAUDIORECSOURCE enmRecSource,
{
if (RT_SUCCESS(rc))
return rc;
}
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
return VERR_NO_MEMORY;
}
}
return rc;
}
{
if (!pThis->cFreeOutputStreams)
{
LogFlowFunc(("Maximum number of host output streams reached\n"));
return VERR_NO_MORE_HANDLES;
}
/* Validate backend configuration. */
{
LogFlowFunc(("Backend output configuration not valid, bailing out\n"));
return VERR_INVALID_PARAMETER;
}
if (!pHstStrmOut)
{
LogFlowFunc(("Error allocating host output stream with %zu bytes\n",
return VERR_NO_MEMORY;
}
int rc;
bool fInitialized = false;
do
{
&cSamples);
if (RT_FAILURE(rc))
{
break;
}
fInitialized = true;
if (RT_SUCCESS(rc))
{
}
} while (0);
if (RT_FAILURE(rc))
{
if (fInitialized)
{
}
}
else
return rc;
}
{
else
LogFlowFunc(("Using fixed audio output settings: %RTbool\n",
if (!pGstStrmOut)
{
LogFlowFunc(("Failed to allocate memory for guest output stream \"%s\"\n",
pszName));
return VERR_NO_MEMORY;
}
if (RT_FAILURE(rc))
{
LogFlowFunc(("Error adding host output stream \"%s\", rc=%Rrc\n",
return rc;
}
if (RT_SUCCESS(rc))
{
if (ppGstStrmOut)
}
if (RT_FAILURE(rc))
return rc;
}
static int drvAudioCreateStreamPairIn(PDRVAUDIO pThis, const char *pszName, PDMAUDIORECSOURCE enmRecSource,
{
{
LogFlowFunc(("Using fixed audio settings\n"));
}
else
if (!pGstStrmIn)
return VERR_NO_MEMORY;
if (RT_FAILURE(rc))
{
LogFunc(("Failed to add host audio input stream \"%s\", rc=%Rrc\n",
return rc;
}
if (RT_SUCCESS(rc))
{
if (ppGstStrmIn)
}
else
return rc;
}
/**
* Initializes a guest input stream.
*
* @return IPRT status code.
* @param pGstStrmIn Pointer to guest stream to initialize.
* @param pHstStrmIn Pointer to host input stream to associate this guest
* stream with.
* @param pszName Pointer to stream name to use for this stream.
* @param pCfg Pointer to stream configuration to use.
*/
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
#ifdef DEBUG
#endif
return VERR_NO_MEMORY;
}
}
return rc;
}
{
if (!pThis->cFreeInputStreams)
{
LogFlowFunc(("No more input streams free to use, bailing out\n"));
return VERR_NO_MORE_HANDLES;
}
/* Validate backend configuration. */
{
LogFlowFunc(("Backend input configuration not valid, bailing out\n"));
return VERR_INVALID_PARAMETER;
}
if (!pHstStrmIn)
{
LogFlowFunc(("Error allocating host innput stream with %RU32 bytes\n",
return VERR_NO_MEMORY;
}
int rc;
bool fInitialized = false;
do
{
if (RT_FAILURE(rc))
{
break;
}
fInitialized = true;
cSamples);
if (RT_SUCCESS(rc))
{
}
} while (0);
if (RT_FAILURE(rc))
{
if (fInitialized)
{
}
}
else
return rc;
}
/**
* Writes VM audio output data from the guest stream into the host stream.
* The attached host driver backend then will play out the audio in a
* later step then.
*
* @return IPRT status code.
* @return int
* @param pThis
* @param pGstStrmOut
* @param pvBuf
* @param cbBuf
* @param pcbWritten
*/
{
/* pcbWritten is optional. */
("Writing to disabled host output stream %p not possible\n",
/*
* First, write data from the device emulation into our
* guest mixing buffer.
*/
0 /* Offset in samples */,
&cWritten);
/*
* Second, mix the guest mixing buffer with the host mixing
* buffer so that the host backend can play the data lateron.
*/
if ( RT_SUCCESS(rc)
&& cWritten)
{
}
else
cMixed = 0;
if (RT_SUCCESS(rc))
{
/* Return the number of samples which actually have been mixed
* down to the parent, regardless how much samples were written
* into the children buffer. */
if (pcbWritten)
}
LogFlowFunc(("Written pvBuf=%p, cbBuf=%zu, cWritten=%RU32 (%RU32 bytes), cMixed=%RU32, rc=%Rrc\n",
return rc;
}
{
if (pHstStrmOut)
{
return NULL;
}
}
{
{
if (pHostStrmOut->fEnabled)
return pHostStrmOut;
}
return NULL;
}
{
{
return pHstStrmOut;
}
return NULL;
}
{
int rc;
{
if (RT_SUCCESS(rc))
{
/* Remove from driver instance list. */
}
}
else
LogFlowFunc(("Host input stream %p still being used, rc=%Rrc\n",
pHstStrmIn, rc));
return rc;
}
{
if (pGstStrmIn)
{
if (pGstStrmIn->pHstStrmIn)
{
/* Unlink child. */
/* Try destroying the associated host input stream. This could
* be skipped if there are other guest input streams with this
* host stream. */
}
}
return VINF_SUCCESS;
}
{
int rc = VINF_SUCCESS;
uint32_t cSamplesLive = 0;
/*
* Playback.
*/
{
if (!cStreamsLive)
cSamplesLive = 0;
/* Has this stream marked as disabled but there still were guest streams relying
* on it? Check if this stream now can be closed and do so, if possible. */
if ( pHstStrmOut->fPendingDisable
&& !cStreamsLive)
{
/* Stop playing the current (pending) stream. */
if (RT_SUCCESS(rc2))
{
pHstStrmOut->fEnabled = false;
pHstStrmOut->fPendingDisable = false;
}
else
LogFunc(("%p: Backend vetoed against closing output stream, rc=%Rrc\n",
pHstStrmOut, rc2));
continue;
}
/*
* No live samples to play at the moment?
*
* Tell the device emulation for each connected guest stream how many
* bytes are free so that the device emulation can continue writing data to
* these streams.
*/
if (!cSamplesLive)
{
{
{
/* Tell the sound device emulation how many samples are free
* so that it can start writing PCM data to us. */
}
}
continue;
}
}
/*
* Recording.
*/
{
/* Call the host backend to capture the audio input data. */
if (RT_FAILURE(rc2))
continue;
{
}
}
if (RT_SUCCESS(rc))
{
if (cbFreeOut == UINT32_MAX)
cbFreeOut = 0;
if (pcbAvailIn)
*pcbAvailIn = cbAvailIn;
if (pcbFreeOut)
*pcbFreeOut = cbFreeOut;
if (pcSamplesLive)
}
return rc;
}
{
/* pcbFree is optional. */
int rc = VINF_SUCCESS;
/*
* Process all enabled host output streams.
*/
{
if (!cStreamsLive)
cSamplesLive = 0;
/* Has this stream marked as disabled but there still were guest streams relying
* on it? Check if this stream now can be closed and do so, if possible. */
if ( pHstStrmOut->fPendingDisable
&& !cStreamsLive)
{
/* Stop playing the current (pending) stream. */
if (RT_SUCCESS(rc2))
{
pHstStrmOut->fEnabled = false;
pHstStrmOut->fPendingDisable = false;
}
else
LogFunc(("\t%p: Backend vetoed against closing output stream, rc=%Rrc\n",
rc2, pHstStrmOut));
continue;
}
LogFlowFunc(("\t%p: cStreamsLive=%RU32, cSamplesLive=%RU32, cSamplesPlayed=%RU32, rc=%Rrc\n",
bool fNeedsCleanup = false;
{
continue;
{
}
}
if (fNeedsCleanup)
{
{
}
}
}
return rc;
}
{
/* pCfgHandle is optional. */
if (RT_FAILURE(rc))
{
return rc;
}
if (cbHstStrmsOut)
{
{
LogRel(("Audio: Warning: %RU32 output streams were requested, host driver only supports %RU32\n",
}
}
else
pThis->cFreeOutputStreams = 0;
if (cbHstStrmIn)
{
/*
* Note:
* - Our AC'97 emulation has two inputs, line (P.IN) and microphone (P.MIC).
** @todo Document HDA.
*/
{
LogRel(("Audio: Warning: %RU32 input streams were requested, host driver only supports %RU32\n",
}
}
else
pThis->cFreeInputStreams = 0;
LogFlowFunc(("cMaxHstStrmsOut=%RU32 (cb=%RU32), cMaxHstStrmsIn=%RU32 (cb=%RU32)\n",
LogFlowFunc(("cFreeInputStreams=%RU8, cFreeOutputStreams=%RU8\n",
return VINF_SUCCESS;
}
{
if (!pThis->pHostDrvAudio)
return;
}
{
/* Tear down all host output streams. */
{
}
/* Tear down all host input streams. */
{
}
}
static struct audio_option audio_options[] =
{
/* DAC */
"Use fixed settings for host DAC", NULL, 0},
"Frequency for fixed host DAC", NULL, 0},
"Format for fixed host DAC", NULL, 0},
"Number of channels for fixed DAC (1 - mono, 2 - stereo)", NULL, 0},
"Number of streams for DAC", NULL, 0},
/* ADC */
"Use fixed settings for host ADC", NULL, 0},
"Frequency for fixed host ADC", NULL, 0},
"Format for fixed host ADC", NULL, 0},
"Number of channels for fixed ADC (1 - mono, 2 - stereo)", NULL, 0},
"Number of streams for ADC", NULL, 0},
/* Misc */
"Timer frequency in Hz (0 - use lowest possible)", NULL, 0},
};
{
int rc = VINF_SUCCESS;
/* Get the configuration data from the selected backend (if available). */
if (RT_SUCCESS(rc))
{
/** @todo Check for invalid options? */
if (!pThis->cFreeOutputStreams)
if (!pThis->cFreeInputStreams)
}
/*
* If everything went well, initialize the lower driver.
*/
if (RT_SUCCESS(rc))
return rc;
}
{
LogRel(("Audio: Using NULL driver; no sound will be audible\n"));
/* Nothing to do here yet. */
return VINF_SUCCESS;
}
static DECLCALLBACK(int) drvAudioRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn,
{
/* pcbWritten is optional. */
("Reading from disabled host input stream %p not possible\n",
pGstStrmIn->pHstStrmIn));
/*
* Read from the parent buffer (that is, the guest buffer) which
* should have the audio data in the format the guest needs.
*/
if (RT_SUCCESS(rc))
{
if (pcbRead)
}
LogFlowFunc(("cRead=%RU32 (%RU32 bytes), rc=%Rrc\n",
return rc;
}
static DECLCALLBACK(int) drvAudioIsSetOutVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut,
{
LogFlowFunc(("pGstStrmOut=%p, fMute=%RTbool, uVolLeft=%RU8, uVolRight=%RU8\n",
if (pGstStrmOut)
{
pGstStrmOut->State.uVolumeLeft = (uint32_t)uVolLeft * 0x808080; /* maximum is INT_MAX = 0x7fffffff */
pGstStrmOut->State.uVolumeRight = (uint32_t)uVolRight * 0x808080; /* maximum is INT_MAX = 0x7fffffff */
}
return VINF_SUCCESS;
}
/** @todo Handle more channels than stereo ... */
{
LogFlowFunc(("fMute=%RTbool, uVolLeft=%RU8, uVolRight=%RU8\n",
/* 0x00..0xff => 0x01..0x100 */
if (u32VolumeLeft)
if (u32VolumeRight)
sum_out_volume.mute = 0;
return VINF_SUCCESS;
}
{
/* pGstVoiceOut is optional. */
if (pGstVoiceOut)
{
{
if (fEnable)
{
pHstStrmOut->fPendingDisable = false;
if (!pHstStrmOut->fEnabled)
{
pHstStrmOut->fEnabled = true;
/** @todo Check rc. */
}
}
else
{
if (pHstStrmOut->fEnabled)
{
uint32_t cGstStrmsActive = 0;
{
}
}
}
}
}
return VINF_SUCCESS;
}
{
/* pGstStrmIn is optional. */
if (pGstStrmIn)
{
LogFlowFunc(("pHstStrmIn=%p, pGstStrmIn=%p, fEnable=%RTbool\n",
{
if (fEnable)
{
if (!pHstStrmIn->fEnabled)
{
pHstStrmIn->fEnabled = true;
else
}
}
else
{
if (pHstStrmIn->fEnabled)
{
pHstStrmIn->fEnabled = false;
else
}
}
}
}
return VINF_SUCCESS;
}
{
return (pGstStrmIn != NULL);
}
{
return (pGstStrmOut != NULL);
}
{
if (!drvAudioStreamCfgIsValid(pCfg))
{
LogFunc(("Input stream configuration is not valid, bailing out\n"));
return VERR_INVALID_PARAMETER;
}
if ( pGstStrmIn
{
LogFunc(("Input stream %p exists and matches required configuration, skipping creation\n",
pGstStrmIn));
return VWRN_ALREADY_EXISTS;
}
&& pGstStrmIn)
{
pGstStrmIn = NULL;
}
int rc;
if (pGstStrmIn)
{
}
else
if (pGstStrmIn)
{
}
return rc;
}
{
if (!drvAudioStreamCfgIsValid(pCfg))
{
LogFunc(("Output stream configuration is not valid, bailing out\n"));
return VERR_INVALID_PARAMETER;
}
if ( pGstStrmOut
{
LogFunc(("Output stream %p exists and matches required configuration, skipping creation\n",
pGstStrmOut));
return VWRN_ALREADY_EXISTS;
}
#if 0
/* Any live samples that need to be updated after
* we set the new parameters? */
uint32_t cLiveSamples = 0;
&& pGstStrmOut
{
if (cLiveSamples)
{
pGstStrmOut = NULL;
}
}
#endif
if ( pGstStrmOut
{
pGstStrmOut = NULL;
}
int rc;
if (pGstStrmOut)
{
}
else
{
if (RT_FAILURE(rc))
}
if (RT_SUCCESS(rc))
{
#if 0
/* Update remaining live samples with new rate. */
if (cLiveSamples)
{
}
#endif
}
return rc;
}
static DECLCALLBACK(bool) drvAudioIsActiveIn(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn)
{
}
static DECLCALLBACK(bool) drvAudioIsActiveOut(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
}
static DECLCALLBACK(void) drvAudioCloseIn(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn)
{
if (pGstStrmIn)
}
DECLCALLBACK(void) drvAudioCloseOut(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
if (pGstStrmOut)
}
/********************************************************************/
/**
* @interface_method_impl{PDMIBASE,pfnQueryInterface}
*/
{
return NULL;
}
/**
* Power Off notification.
*
* @param pDrvIns The driver instance data.
*/
{
}
/**
* Destructs an audio driver instance.
*
* Most VM resources are freed by the VM. This callback is provided so that any non-VM
* resources can be freed correctly.
*
* @param pDrvIns The driver instance data.
*/
{
}
/**
* Constructs an audio driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, uint32_t fFlags)
{
/*
* Init the static parts.
*/
/* IBase. */
/* IAudio. */
/*
* Attach driver below and query its connector interface.
*/
if (RT_FAILURE(rc))
{
LogRel(("Audio: Failed to attach to driver %p below (flags=0x%x), rc=%Rrc\n",
return rc;
}
if (!pThis->pHostDrvAudio)
{
LogRel(("Audio: Failed to query interface for underlying host driver\n"));
N_("No audio interface below"));
}
#ifdef DEBUG_andy
#endif
return rc;
}
/**
* Suspend notification.
*
* @param pDrvIns The driver instance data.
*/
{
}
/**
* Resume notification.
*
* @param pDrvIns The driver instance data.
*/
{
}
/**
* Audio driver registration record.
*/
const PDMDRVREG g_DrvAUDIO =
{
/* u32Version */
/* szName */
"AUDIO",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"Audio connector driver",
/* fFlags */
/* fClass. */
/* cMaxInstances */
2,
/* cbInstance */
sizeof(DRVAUDIO),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
/* pfnResume */
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};