/* $Id$ */
/** @file
* VBox audio devices: filter driver, which sits between the host audio driver
* and the virtual audio device and intercept all host driver operations.
*
* The filter is used mostly for remote audio input.
*/
/*
* Copyright (C) 2010-2011 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.
*/
#include "vl_vbox.h"
#include "audio.h"
#include "audio_int.h"
#define FILTER_EXTENSIVE_LOGGING
/*******************************************************************************
*
* IO Ring Buffer section
*
******************************************************************************/
/* Implementation of a lock free ring buffer which could be used in a multi
* threaded environment. Note that only the acquire, release and getter
* functions are threading aware. So don't use reset if the ring buffer is
* still in use. */
typedef struct IORINGBUFFER
{
/* The current read position in the buffer */
/* The current write position in the buffer */
/* How much space of the buffer is currently in use */
/* How big is the buffer */
/* The buffer itself */
char *pBuffer;
} IORINGBUFFER;
/* Pointer to an ring buffer structure */
{
if (pTmpBuffer)
{
if(pTmpBuffer->pBuffer)
{
*ppBuffer = pTmpBuffer;
}
else
}
}
{
if (pBuffer)
{
}
}
{
pBuffer->cBufferUsed = 0;
}
{
}
{
}
{
}
static void IORingBufferAquireReadBlock(PIORINGBUFFER pBuffer, uint32_t cReqSize, char **ppStart, uint32_t *pcSize)
{
*ppStart = 0;
*pcSize = 0;
/* How much is in use? */
if (uUsed > 0)
{
/* Get the size out of the requested size, the read block till the end
* of the buffer & the currently used size. */
if (uSize > 0)
{
/* Return the pointer address which point to the current read
* position. */
}
}
}
{
/* Split at the end of the buffer. */
}
static void IORingBufferAquireWriteBlock(PIORINGBUFFER pBuffer, uint32_t cReqSize, char **ppStart, uint32_t *pcSize)
{
*ppStart = 0;
*pcSize = 0;
/* How much is free? */
if (uFree > 0)
{
/* Get the size out of the requested size, the write block till the end
* of the buffer & the currently free size. */
if (uSize > 0)
{
/* Return the pointer address which point to the current write
* position. */
}
}
}
{
/* Split at the end of the buffer. */
}
/*******************************************************************************
*
* Global structures section
*
******************************************************************************/
/* Initialization status indicator used for the recreation of the AudioUnits. */
struct
{
void *pDrvOpaque;
} filter_conf =
{
};
/*
* filterVoiceOut and filterVoiceIn are allocated at the end of the original driver HWVoice structure:
* {
* HWVoiceOut;
* OriginalDriverHWVoiceData;
* filterVoiceOut;
* }
*/
typedef struct filterVoiceOut
{
/* HW voice input structure, which prepends the filterVoiceOut. */
/* A ring buffer for transferring data to the playback thread */
/* Initialization status tracker. Used when some of the device parameters
* or the device itself is changed during the runtime. */
/* Whether the output stream is used by the filter. */
bool fIntercepted;
/* Whether this stream is active. */
bool fIsRunning;
/* Sniffer level context for this audio output stream. */
void *pvOutputCtx;
typedef struct filterVoiceIn
{
/* HW voice input structure, which prepends the filterVoiceIn. */
/* A temporary position value. */
/* A ring buffer for transferring data from the recording thread */
/* Initialization status tracker. Used when some of the device parameters
* or the device itself is changed during the runtime. */
/* the stream has been successfully initialized by host. */
bool fHostOK;
/* Whether the input stream is used by the filter. */
bool fIntercepted;
/* Whether this stream is active. */
bool fIsRunning;
/* Sniffer level context for this audio input stream. */
void *pvInputCtx;
#ifdef FILTER_EXTENSIVE_LOGGING
#else
# define CA_EXT_DEBUG_LOG(a) do {} while(0)
#endif
/*******************************************************************************
*
* CoreAudio output section
*
******************************************************************************/
/* We need some forward declarations */
{
cFrames = 2048;
/* Create the internal ring buffer. */
{
LogRel(("FilterAudio: [Output] Failed to create internal ring buffer\n"));
return -1;
}
LogRel(("FilterAudio: [Output] Warning! After recreation, the CoreAudio ring buffer doesn't has the same size as the device buffer (%RU32 vs. %RU32).\n", cSamples, (uint32_t)pVoice->phw->samples));
return 0;
}
{
if (!pVoice->fIntercepted)
{
}
/* We return the live count in the case we are not initialized. This should
* prevent any under runs. */
/* Make sure the device is running */
/* How much space is available in the ring buffer */
/* How much data is available. Use the smaller size of the too. */
CA_EXT_DEBUG_LOG(("FilterAudio: [Output] Start writing buffer with %RU32 samples (%RU32 bytes)\n", csAvail, csAvail << pVoice->phw->info.shift));
/* Iterate as long as data is available */
{
/* How much is left? Split request at the end of our samples buffer. */
CA_EXT_DEBUG_LOG(("FilterAudio: [Output] Try writing %RU32 samples (%RU32 bytes)\n", csToWrite, cbToWrite));
/* Try to acquire the necessary space from the ring buffer. */
/* How much to we get? */
CA_EXT_DEBUG_LOG(("FilterAudio: [Output] There is space for %RU32 samples (%RU32 bytes) available\n", csToWrite, cbToWrite));
/* Break if nothing is free anymore. */
if (RT_UNLIKELY(cbToWrite == 0))
break;
/* Copy the data from our mix buffer to the ring buffer. */
/* Release the ring buffer, so the read thread could start reading this data. */
/* How much have we written so far. */
}
CA_EXT_DEBUG_LOG(("FilterAudio: [Output] Finished writing buffer with %RU32 samples (%RU32 bytes)\n", csWritten, csWritten << pVoice->phw->info.shift));
/* Return the count of samples we have processed. */
return csWritten;
}
{
/* Every host backend just calls the generic function, so no need to forward. */
}
{
if (!pVoice->fIntercepted)
{
/* Note: audio.c does not use variable parameters '...', so ok to forward only 'phw' and 'cmd'. */
}
if (!(status == CA_STATUS_INIT))
return 0;
switch (cmd)
{
case VOICE_ENABLE:
{
/* Only start the device if it is actually stopped */
if (!pVoice->fIsRunning)
{
}
break;
}
case VOICE_DISABLE:
{
/* Only stop the device if it is actually running */
if (pVoice->fIsRunning)
{
}
break;
}
}
return 0;
}
{
int rc = 0;
if (!pVoice->fIntercepted)
{
return;
}
if (!(status == CA_STATUS_INIT))
return;
{
}
else
}
{
int rc = 0;
if (!filter_output_intercepted())
{
pVoice->fIntercepted = false;
}
/* Output is not tested and is not used currently */
AssertFailed();
return -1;
pVoice->fIntercepted = true;
/* Initialize the hardware info section with the audio settings */
if (RT_UNLIKELY(rc != 0))
return rc;
/* The samples have to correspond to the internal ring buffer size. */
pVoice->phw->samples = (IORingBufferSize(pVoice->pBuf) >> pVoice->phw->info.shift) / pVoice->phw->info.nchannels;
return 0;
}
/*******************************************************************************
*
* FilterAudio input section
*
******************************************************************************/
/*
* Callback to feed audio input buffer. Samples format is be the same as
* in the voice. The caller prepares st_sample_t.
*
* @param cbSamples Size of pvSamples array in bytes.
* @param pvSamples Points to an array of samples.
*
* @return IPRT status code.
*/
const void *pvSamples)
{
if (!pVoice->fIsRunning)
return VINF_SUCCESS;
/* If nothing is pending return immediately. */
if (cbSamples == 0)
return VINF_SUCCESS;
/* How much space is free in the ring buffer? */
/* How much space is used in the audio buffer. Use the smaller size of the too. */
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Start writing buffer with %RU32 samples (%RU32 bytes)\n", csAvail, csAvail * sizeof(st_sample_t)));
/* Iterate as long as data is available */
{
/* How much is left? */
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Try writing %RU32 samples (%RU32 bytes)\n", csToWrite, cbToWrite));
/* Try to acquire the necessary space from the ring buffer. */
/* How much do we get? */
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] There is space for %RU32 samples (%RU32 bytes) available\n", csToWrite, cbToWrite));
/* Break if nothing is free anymore. */
if (RT_UNLIKELY(csToWrite == 0))
break;
/* Copy the data from the audio buffer to the ring buffer. */
/* Release the ring buffer, so the main thread could start reading this data. */
}
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Finished writing buffer with %RU32 samples (%RU32 bytes)\n", csWritten, csWritten * sizeof(st_sample_t)));
return rc;
}
{
char *pcSrc;
if (!filter_conf.pDrv)
{
AssertFailed();
return -1;
}
if (!pVoice->fIntercepted)
{
{
/* Host did not initialize the voice. */
Log(("FilterAudio: [Input]: run_in voice %p (hw %p) not available on host\n", pVoice, pVoice->phw));
return -1;
}
}
if (!pVoice->fIsRunning)
return 0;
/* How much space is used in the ring buffer? */
/* How much space is available in the mix buffer. Use the smaller size of the too. */
csAvail = RT_MIN(csAvail, (uint32_t)(pVoice->phw->samples - audio_pcm_hw_get_live_in (pVoice->phw)));
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Start reading buffer with %RU32 samples (%RU32 bytes)\n", csAvail, csAvail * sizeof(st_sample_t)));
/* Iterate as long as data is available */
{
/* How much is left? Split request at the end of our samples buffer. */
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Try reading %RU32 samples (%RU32 bytes)\n", csToRead, cbToRead));
/* Try to acquire the necessary block from the ring buffer. */
/* How much to we get? */
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] There are %RU32 samples (%RU32 bytes) available\n", csToRead, cbToRead));
/* Break if nothing is used anymore. */
if (csToRead == 0)
break;
/* Copy the data from our ring buffer to the mix buffer. */
/* Release the read buffer, so it could be used for new data. */
/* How much have we reads so far. */
}
CA_EXT_DEBUG_LOG(("FilterAudio: [Input] Finished reading buffer with %RU32 samples (%RU32 bytes)\n", csReads, csReads * sizeof(st_sample_t)));
return csReads;
}
{
/* Every host backend just calls the generic function, so no need to forward. */
}
{
if (!filter_conf.pDrv)
{
AssertFailed();
return -1;
}
if (cmd == VOICE_ENABLE)
{
/* Decide who will provide input audio: filter or host driver. */
if (!filter_input_intercepted())
{
{
/* Host did not initialize the voice. */
Log(("FilterAudio: [Input]: ctl_in ENABLE voice %p (hw %p) not available on host\n", pVoice, pVoice->phw));
return -1;
}
/* Note: audio.c does not use variable parameters '...', so ok to forward only 'phw' and 'cmd'. */
Log(("FilterAudio: [Input]: forwarding ctl_in ENABLE for voice %p (hw %p)\n", pVoice, pVoice->phw));
}
/* The filter will use this voice. */
Log(("FilterAudio: [Input]: ctl_in ENABLE for voice %p (hw %p), cmd %d\n", pVoice, pVoice->phw, cmd));
return -1;
/* Only start the device if it is actually stopped */
if (!pVoice->fIsRunning)
{
/* Sniffer will inform us on a second thread for new incoming audio data.
* Therefore register an callback function, which will process the new data.
* */
rc = filter_input_begin(&pVoice->pvInputCtx, fltRecordingCallback, pVoice, pVoice->phw, pVoice->phw->samples);
if (RT_SUCCESS(rc))
{
pVoice->fIsRunning = true;
/* Remember that this voice is used by the filter. */
pVoice->fIntercepted = true;
}
}
if (RT_FAILURE(rc))
{
return -1;
}
}
else if (cmd == VOICE_DISABLE)
{
return -1;
/* Check if the voice has been intercepted. */
if (!pVoice->fIntercepted)
{
{
/* Host did not initialize the voice. Theoretically should not happen, because
* audio.c should not disable a voice which has not been enabled at all.
*/
Log(("FilterAudio: [Input]: ctl_in DISABLE voice %p (hw %p) not available on host\n", pVoice, pVoice->phw));
return -1;
}
/* Note: audio.c does not use variable parameters '...', so ok to forward only 'phw' and 'cmd'. */
Log(("FilterAudio: [Input]: forwarding ctl_in DISABLE for voice %p (hw %p)\n", pVoice, pVoice->phw));
}
/* The filter used this voice. */
Log(("FilterAudio: [Input]: ctl_in DISABLE for voice %p (hw %p), cmd %d\n", pVoice, pVoice->phw, cmd));
/* Only stop the device if it is actually running */
if (pVoice->fIsRunning)
{
pVoice->fIsRunning = false;
/* Tell the sniffer to not to use this context anymore. */
}
/* This voice is no longer used by the filter. */
pVoice->fIntercepted = false;
}
else
{
return -1; /* Unknown command. */
}
return 0;
}
{
if (!filter_conf.pDrv)
{
AssertFailed();
return;
}
/* Uninitialize both host and filter parts of the voice. */
{
/* Uninit host part only if it was initialized by host. */
}
return;
/* If this voice is intercepted by filter, try to stop it. */
if (pVoice->fIntercepted)
{
}
else
{
ret = 0;
}
{
}
else
}
{
if (!filter_conf.pDrv)
{
AssertFailed();
return -1;
}
/* Initialize both host and filter parts of the voice. */
Log(("FilterAudio: [Input]: init_in for voice %p (hw %p), hostret = %d\n", pVoice, pVoice->phw, hostret));
pVoice->fIntercepted = false;
pVoice->fIsRunning = false;
{
/* Initialize required fields of the common part of the voice. */
/* Initialize the hardware info section with the audio settings */
}
/* Create the internal ring buffer. */
{
LogRel(("FilterAudio: [Input] Failed to create internal ring buffer\n"));
return -1;
}
return 0;
}
/*******************************************************************************
*
* FilterAudio global section
*
******************************************************************************/
static void *filteraudio_audio_init(void)
{
/* This is not supposed to be called. */
Log(("FilterAudio: Init\n"));
AssertFailed();
return NULL;
}
{
/* Forward to the host driver. */
if (filter_conf.pDrv)
{
}
}
{
};
{
INIT_FIELD(descr =)
"FilterAudio: filter driver between host audio and virtual device",
};
{
/* Modify the audio driver structure to be like the original driver. */
return &filteraudio_audio_driver;
}
{
if (pDrv != &filteraudio_audio_driver)
{
/* This is not the driver for which the filter was installed.
* The filter has no idea and assumes that if the voice
* is not NULL then it is a valid host voice.
*/
}
if (!filter_conf.pDrv)
{
AssertFailed();
}
}
{
/* Output is not yet implemented and there are no filter voices.
* The filter has no idea and assumes that if the voice
* is not NULL then it is a valid host voice.
*
* @todo: similar to filteraudio_is_host_voice_in_ok
*/
}