/* $Id: audiosniffer.c 45025 2013-03-13 16:45:15Z vboxsync $ */ /** @file * VBox audio device: Audio sniffer device */ /* * Copyright (C) 2006-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; * 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. */ #define LOG_GROUP LOG_GROUP_DEV_AUDIO #define AUDIO_CAP "sniffer" #include #include #include #include #include #include #include #include #include "VBoxDD.h" #include "vl_vbox.h" #include "audio.h" #include "audio_int.h" typedef struct _AUDIOSNIFFERSTATE { /** If the device is enabled. */ bool fEnabled; /** Whether audio should reach the host driver too. */ bool fKeepHostAudio; /** Whether audio input operations should be forwarded to the connector. */ bool fInterceptAudioInput; /** Pointer to device instance. */ PPDMDEVINS pDevIns; /** Audio Sniffer port base interface. */ PDMIBASE IBase; /** Audio Sniffer port interface. */ PDMIAUDIOSNIFFERPORT IPort; /** Pointer to base interface of the driver. */ PPDMIBASE pDrvBase; /** Audio Sniffer connector interface */ PPDMIAUDIOSNIFFERCONNECTOR pDrv; } AUDIOSNIFFERSTATE; static AUDIOSNIFFERSTATE *g_pData = NULL; /* * Public sniffer callbacks to be called from audio driver. */ /* *** Subject to change *** * Process audio output. The function is called when an audio output * driver is about to play audio samples. * * It is expected that there is only one audio data flow, * i.e. one voice. * * @param hw Audio samples information. * @param pvSamples Pointer to audio samples. * @param cSamples Number of audio samples in the buffer. * @returns 'true' if audio also to be played back by the output driver. * 'false' if audio should not be played. */ DECLCALLBACK(bool) sniffer_run_out (HWVoiceOut *hw, void *pvSamples, unsigned cSamples) { int samplesPerSec; int nChannels; int bitsPerSample; bool fUnsigned; if (!g_pData || !g_pData->pDrv || !g_pData->fEnabled) { return true; } samplesPerSec = hw->info.freq; nChannels = hw->info.nchannels; bitsPerSample = hw->info.bits; fUnsigned = (hw->info.sign == 0); g_pData->pDrv->pfnAudioSamplesOut (g_pData->pDrv, pvSamples, cSamples, samplesPerSec, nChannels, bitsPerSample, fUnsigned); return g_pData->fKeepHostAudio; } /* * Filter interface. */ /* Internal audio input context, which makes sure that: * - the filter audio input callback is not called after the filter has issued filter_input_end; * - maintains internal information and state of the audio stream. */ typedef struct SnifferInputCtx { /* Whether the context is still in use by the filter or I'll check. */ int32_t volatile cRefs; /* The filter callback for incoming audio data. */ PFNAUDIOINPUTCALLBACK pfnFilterCallback; void *pvFilterCallback; /* Whether the stream has been ended by the filter. */ bool fEndedByFilter; /* Context pointer returned by pfnAudioInputBegin. */ void *pvUserCtx; /* Audio format used for recording. */ HWVoiceIn *phw; /* Number of bytes per frame (bitsPerSample * channels) of the actual input format. */ uint32_t cBytesPerFrame; /* Frequency of the actual audio format. */ int iFreq; /* Convertion from the actual input format to st_sample_t. */ t_sample *conv; /* If the actual format frequence differs from the requested format, this is not NULL. */ void *rate; /* Temporary buffer for st_sample_t representation of the input audio data. */ void *pvSamplesBuffer; uint32_t cbSamplesBufferAllocated; /* Temporary buffer for frequency conversion. */ void *pvRateBuffer; uint32_t cbRateBufferAllocated; } SnifferInputCtx; static void ictxDelete(SnifferInputCtx *pCtx) { /* The caller will not use this context anymore. */ if (pCtx->rate) { st_rate_stop (pCtx->rate); } RTMemFree(pCtx->pvSamplesBuffer); RTMemFree(pCtx->pvRateBuffer); memset(pCtx, 0, sizeof(*pCtx)); RTMemFree(pCtx); } static void ictxReallocSamplesBuffer(SnifferInputCtx *pCtx, uint32_t cs) { uint32_t cbBuffer = cs * sizeof(st_sample_t); if (cbBuffer > pCtx->cbSamplesBufferAllocated) { RTMemFree(pCtx->pvSamplesBuffer); pCtx->pvSamplesBuffer = RTMemAlloc(cbBuffer); if (pCtx->pvSamplesBuffer) { pCtx->cbSamplesBufferAllocated = cbBuffer; } else { pCtx->cbSamplesBufferAllocated = 0; } } } static void ictxReallocRateBuffer(SnifferInputCtx *pCtx, uint32_t cs) { uint32_t cbBuffer = cs * sizeof(st_sample_t); if (cbBuffer > pCtx->cbRateBufferAllocated) { RTMemFree(pCtx->pvRateBuffer); pCtx->pvRateBuffer = RTMemAlloc(cbBuffer); if (pCtx->pvRateBuffer) { pCtx->cbRateBufferAllocated = cbBuffer; } else { pCtx->cbRateBufferAllocated = 0; } } } /* * Filter audio output. */ /* Whether the filter should intercept audio output. */ int filter_output_intercepted(void) { return 0; /* @todo Not implemented yet.*/ } /* Filter informs that an audio output is starting. */ int filter_output_begin(void **ppvOutputCtx, struct audio_pcm_info *pinfo, int samples) { return VERR_NOT_SUPPORTED; /* @todo Not implemented yet.*/ } /* Filter informs that the audio output has been stopped. */ void filter_output_end(void *pvOutputCtx) { return; /* @todo Not implemented yet.*/ } /* * Filter audio input. */ /* Whether the filter should intercept audio input. */ int filter_input_intercepted(void) { if (!g_pData || !g_pData->pDrv) { return 0; } return g_pData->fInterceptAudioInput; } /* Filter informs that an audio input is starting. */ int filter_input_begin (void **ppvInputCtx, PFNAUDIOINPUTCALLBACK pfnCallback, void *pvCallback, HWVoiceIn *phw, int cSamples) { int rc = VINF_SUCCESS; SnifferInputCtx *pCtx = NULL; if (!g_pData || !g_pData->pDrv) { return VERR_NOT_SUPPORTED; } pCtx = (SnifferInputCtx *)RTMemAlloc(sizeof(SnifferInputCtx)); if (!pCtx) { return VERR_NO_MEMORY; } pCtx->cRefs = 2; /* Context is used by both the filter and the user. */ pCtx->pfnFilterCallback = pfnCallback; pCtx->pvFilterCallback = pvCallback; pCtx->fEndedByFilter = false; pCtx->pvUserCtx = NULL; pCtx->phw = phw; pCtx->cBytesPerFrame = 1; pCtx->iFreq = 0; pCtx->conv = NULL; pCtx->rate = NULL; pCtx->pvSamplesBuffer = NULL; pCtx->cbSamplesBufferAllocated = 0; pCtx->pvRateBuffer = NULL; pCtx->cbRateBufferAllocated = 0; rc = g_pData->pDrv->pfnAudioInputBegin (g_pData->pDrv, &pCtx->pvUserCtx, /* Returned by the pDrv. */ pCtx, cSamples, /* How many samples in one block is preferred. */ phw->info.freq, /* Required frequency. */ phw->info.nchannels, /* Number of audio channels. */ phw->info.bits); /* A sample size in one channel, samples are signed. */ if (RT_SUCCESS(rc)) { *ppvInputCtx = pCtx; } else { RTMemFree(pCtx); } Log(("input_begin rc = %Rrc\n", rc)); return rc; } /* Filter informs that the audio input must be stopped. */ void filter_input_end(void *pvCtx) { int32_t c; SnifferInputCtx *pCtx = (SnifferInputCtx *)pvCtx; void *pvUserCtx = pCtx->pvUserCtx; pCtx->fEndedByFilter = true; c = ASMAtomicDecS32(&pCtx->cRefs); if (c == 0) { ictxDelete(pCtx); pCtx = NULL; } if (!g_pData || !g_pData->pDrv) { AssertFailed(); return; } g_pData->pDrv->pfnAudioInputEnd (g_pData->pDrv, pvUserCtx); Log(("input_end\n")); } /* * Audio PDM device. */ static DECLCALLBACK(int) iface_AudioInputIntercept (PPDMIAUDIOSNIFFERPORT pInterface, bool fIntercept) { AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort); Assert(g_pData == pThis); pThis->fInterceptAudioInput = fIntercept; return VINF_SUCCESS; } static DECLCALLBACK(int) iface_AudioInputEventBegin (PPDMIAUDIOSNIFFERPORT pInterface, void *pvContext, int iSampleHz, int cChannels, int cBits, bool fUnsigned) { int bitIdx; AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort); int rc = VINF_SUCCESS; SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext; Log(("FilterAudio: AudioInputEventBegin: %dHz,%dch,%dbits,%d ended %d\n", iSampleHz, cChannels, cBits, fUnsigned, pCtx->fEndedByFilter)); Assert(g_pData == pThis); /* Prepare a format convertion for the actually used format. */ pCtx->cBytesPerFrame = ((cBits + 7) / 8) * cChannels; if (cBits == 16) { bitIdx = 1; } else if (cBits == 32) { bitIdx = 2; } else { bitIdx = 0; } pCtx->conv = mixeng_conv[(cChannels == 2)? 1: 0] /* stereo */ [!fUnsigned] /* sign */ [0] /* big endian */ [bitIdx]; /* bits */ if (iSampleHz && iSampleHz != pCtx->phw->info.freq) { pCtx->rate = st_rate_start (iSampleHz, pCtx->phw->info.freq); pCtx->iFreq = iSampleHz; } return rc; } static DECLCALLBACK(int) iface_AudioInputEventData (PPDMIAUDIOSNIFFERPORT pInterface, void *pvContext, const void *pvData, uint32_t cbData) { AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort); int rc = VINF_SUCCESS; SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext; Log(("FilterAudio: AudioInputEventData: pvData %p. cbData %d, ended %d\n", pvData, cbData, pCtx->fEndedByFilter)); Assert(g_pData == pThis); if ( !pCtx->fEndedByFilter && pCtx->conv) { /* Convert PCM samples to st_sample_t. * And then apply rate conversion if necessary. */ /* Optimization: allocate 'ps' buffer once per context and reallocate if cbData changes. * Usually size of packets is constant. */ st_sample_t *ps; uint32_t cs = cbData / pCtx->cBytesPerFrame; /* How many samples. */ ictxReallocSamplesBuffer(pCtx, cs); ps = (st_sample_t *)pCtx->pvSamplesBuffer; if (ps) { void *pvSamples = NULL; uint32_t cbSamples = 0; Assert(pCtx->cbSamplesBufferAllocated >= cs * sizeof(st_sample_t)); pCtx->conv(ps, pvData, cs, &nominal_volume); if (pCtx->rate) { st_sample_t *psConverted; uint32_t csConverted = (cs * pCtx->phw->info.freq) / pCtx->iFreq; ictxReallocRateBuffer(pCtx, csConverted); psConverted = (st_sample_t *)pCtx->pvRateBuffer; if (psConverted) { int csSrc = cs; int csDst = csConverted; Assert(pCtx->cbRateBufferAllocated >= csConverted * sizeof(st_sample_t)); st_rate_flow (pCtx->rate, ps, psConverted, &csSrc, &csDst); pvSamples = psConverted; cbSamples = csDst * sizeof(st_sample_t); /* Use csDst as it may be != csConverted */ } else { rc = VERR_NO_MEMORY; } } else { pvSamples = ps; cbSamples = cs * sizeof(st_sample_t); } if (cbSamples) { rc = pCtx->pfnFilterCallback(pCtx->pvFilterCallback, cbSamples, pvSamples); } } else { rc = VERR_NO_MEMORY; } } return rc; } static DECLCALLBACK(void) iface_AudioInputEventEnd (PPDMIAUDIOSNIFFERPORT pInterface, void *pvContext) { int32_t c; AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort); SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext; Log(("FilterAudio: AudioInputEventEnd: ended %d\n", pCtx->fEndedByFilter)); Assert(g_pData == pThis); c = ASMAtomicDecS32(&pCtx->cRefs); if (c == 0) { ictxDelete(pCtx); pCtx = NULL; } } static DECLCALLBACK(int) iface_Setup (PPDMIAUDIOSNIFFERPORT pInterface, bool fEnable, bool fKeepHostAudio) { AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort); Assert(g_pData == pThis); pThis->fEnabled = fEnable; pThis->fKeepHostAudio = fKeepHostAudio; return VINF_SUCCESS; } /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} */ static DECLCALLBACK(void *) iface_QueryInterface(PPDMIBASE pInterface, const char *pszIID) { AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOSNIFFERPORT, &pThis->IPort); return NULL; } /** * Destruct a device instance. * * Most VM resources are freed by the VM. This callback is provided so that any non-VM * resources can be freed correctly. * * @returns VBox status. * @param pDevIns The device instance data. */ static DECLCALLBACK(int) audioSnifferR3Destruct(PPDMDEVINS pDevIns) { PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); /* Zero the global pointer. */ g_pData = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{PDMDEVREG,pfnConstruct} */ static DECLCALLBACK(int) audioSnifferR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle) { int rc = VINF_SUCCESS; AUDIOSNIFFERSTATE *pThis = PDMINS_2_DATA(pDevIns, AUDIOSNIFFERSTATE *); Assert(iInstance == 0); PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); /* * Validate configuration. */ if (!CFGMR3AreValuesValid(pCfgHandle, "InterceptAudioInput\0")) { return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES; } /* * Initialize data. */ pThis->fEnabled = false; pThis->fKeepHostAudio = true; pThis->pDrv = NULL; rc = CFGMR3QueryBoolDef(pCfgHandle, "InterceptAudioInput", &pThis->fInterceptAudioInput, false); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"YieldOnLSRRead\" value")); /* * Interfaces */ /* Base */ pThis->IBase.pfnQueryInterface = iface_QueryInterface; /* Audio Sniffer port */ pThis->IPort.pfnSetup = iface_Setup; pThis->IPort.pfnAudioInputIntercept = iface_AudioInputIntercept; pThis->IPort.pfnAudioInputEventBegin = iface_AudioInputEventBegin; pThis->IPort.pfnAudioInputEventData = iface_AudioInputEventData; pThis->IPort.pfnAudioInputEventEnd = iface_AudioInputEventEnd; /* * Get the corresponding connector interface */ rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThis->IBase, &pThis->pDrvBase, "Audio Sniffer Port"); if (RT_SUCCESS(rc)) { pThis->pDrv = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIAUDIOSNIFFERCONNECTOR); AssertMsgStmt(pThis->pDrv, ("LUN #0 doesn't have a Audio Sniffer connector interface rc=%Rrc\n", rc), rc = VERR_PDM_MISSING_INTERFACE); } else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) { Log(("%s/%d: warning: no driver attached to LUN #0.\n", pDevIns->pReg->szName, pDevIns->iInstance)); rc = VINF_SUCCESS; } else { AssertMsgFailed(("Failed to attach LUN #0. rc=%Rrc\n", rc)); } if (RT_SUCCESS (rc)) { /* Save PDM device instance data for future reference. */ pThis->pDevIns = pDevIns; /* Save the pointer to created instance in the global variable, so other * functions could reach it. */ g_pData = pThis; } return rc; } /** * The Audio Sniffer device registration structure. */ const PDMDEVREG g_DeviceAudioSniffer = { /* u32Version */ PDM_DEVREG_VERSION, /* szName */ "AudioSniffer", /* szRCMod */ "", /* szR0Mod */ "", /* pszDescription */ "Audio Sniffer device. Redirects audio data to sniffer driver.", /* fFlags */ PDM_DEVREG_FLAGS_DEFAULT_BITS, /* fClass */ PDM_DEVREG_CLASS_AUDIO, /* cMaxInstances */ 1, /* cbInstance */ sizeof(AUDIOSNIFFERSTATE), /* pfnConstruct */ audioSnifferR3Construct, /* pfnDestruct */ audioSnifferR3Destruct, /* pfnRelocate */ NULL, /* pfnMemSetup */ NULL, /* pfnPowerOn */ NULL, /* pfnReset */ NULL, /* pfnSuspend */ NULL, /* pfnResume */ NULL, /* pfnAttach */ NULL, /* pfnDetach */ NULL, /* pfnQueryInterface */ NULL, /* pfnInitComplete */ NULL, /* pfnPowerOff */ NULL, /* pfnSoftReset */ NULL, PDM_DEVREG_VERSION };