VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/audiosniffer.c@ 40754

Last change on this file since 40754 was 35402, checked in by vboxsync, 13 years ago

Devices/Audio: optimize allocation of temporary buffers for audio input filter.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.1 KB
Line 
1/* $Id: audiosniffer.c 35402 2011-01-04 14:59:31Z vboxsync $ */
2/** @file
3 * VBox audio device: Audio sniffer device
4 */
5
6/*
7 * Copyright (C) 2006-2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_DEV_AUDIO
19#define AUDIO_CAP "sniffer"
20#include <VBox/vmm/pdm.h>
21#include <VBox/err.h>
22
23#include <VBox/log.h>
24#include <iprt/asm.h>
25#include <iprt/assert.h>
26#include <iprt/uuid.h>
27#include <iprt/string.h>
28#include <iprt/alloc.h>
29
30#include "VBoxDD.h"
31#include "vl_vbox.h"
32
33#include "audio.h"
34#include "audio_int.h"
35
36typedef struct _AUDIOSNIFFERSTATE
37{
38 /** If the device is enabled. */
39 bool fEnabled;
40
41 /** Whether audio should reach the host driver too. */
42 bool fKeepHostAudio;
43
44 /** Whether audio input operations should be forwarded to the connector. */
45 bool fInterceptAudioInput;
46
47 /** Pointer to device instance. */
48 PPDMDEVINS pDevIns;
49
50 /** Audio Sniffer port base interface. */
51 PDMIBASE IBase;
52 /** Audio Sniffer port interface. */
53 PDMIAUDIOSNIFFERPORT IPort;
54
55 /** Pointer to base interface of the driver. */
56 PPDMIBASE pDrvBase;
57 /** Audio Sniffer connector interface */
58 PPDMIAUDIOSNIFFERCONNECTOR pDrv;
59
60} AUDIOSNIFFERSTATE;
61
62static AUDIOSNIFFERSTATE *g_pData = NULL;
63
64/*
65 * Public sniffer callbacks to be called from audio driver.
66 */
67
68/* *** Subject to change ***
69 * Process audio output. The function is called when an audio output
70 * driver is about to play audio samples.
71 *
72 * It is expected that there is only one audio data flow,
73 * i.e. one voice.
74 *
75 * @param hw Audio samples information.
76 * @param pvSamples Pointer to audio samples.
77 * @param cSamples Number of audio samples in the buffer.
78 * @returns 'true' if audio also to be played back by the output driver.
79 * 'false' if audio should not be played.
80 */
81DECLCALLBACK(bool) sniffer_run_out (HWVoiceOut *hw, void *pvSamples, unsigned cSamples)
82{
83 int samplesPerSec;
84 int nChannels;
85 int bitsPerSample;
86 bool fUnsigned;
87
88 if (!g_pData || !g_pData->pDrv || !g_pData->fEnabled)
89 {
90 return true;
91 }
92
93 samplesPerSec = hw->info.freq;
94 nChannels = hw->info.nchannels;
95 bitsPerSample = hw->info.bits;
96 fUnsigned = (hw->info.sign == 0);
97
98 g_pData->pDrv->pfnAudioSamplesOut (g_pData->pDrv, pvSamples, cSamples,
99 samplesPerSec, nChannels, bitsPerSample, fUnsigned);
100
101 return g_pData->fKeepHostAudio;
102}
103
104
105/*
106 * Filter interface.
107 */
108
109/* Internal audio input context, which makes sure that:
110 * - the filter audio input callback is not called after the filter has issued filter_input_end;
111 * - maintains internal information and state of the audio stream.
112 */
113typedef struct SnifferInputCtx
114{
115 /* Whether the context is still in use by the filter or I'll check. */
116 int32_t volatile cRefs;
117
118 /* The filter callback for incoming audio data. */
119 PFNAUDIOINPUTCALLBACK pfnFilterCallback;
120 void *pvFilterCallback;
121
122 /* Whether the stream has been ended by the filter. */
123 bool fEndedByFilter;
124
125 /* Context pointer returned by pfnAudioInputBegin. */
126 void *pvUserCtx;
127
128 /* Audio format used for recording. */
129 HWVoiceIn *phw;
130
131 /* Number of bytes per frame (bitsPerSample * channels) of the actual input format. */
132 uint32_t cBytesPerFrame;
133
134 /* Frequency of the actual audio format. */
135 int iFreq;
136
137 /* Convertion from the actual input format to st_sample_t. */
138 t_sample *conv;
139
140 /* If the actual format frequence differs from the requested format, this is not NULL. */
141 void *rate;
142
143 /* Temporary buffer for st_sample_t representation of the input audio data. */
144 void *pvSamplesBuffer;
145 uint32_t cbSamplesBufferAllocated;
146
147 /* Temporary buffer for frequency conversion. */
148 void *pvRateBuffer;
149 uint32_t cbRateBufferAllocated;
150
151} SnifferInputCtx;
152
153static void ictxDelete(SnifferInputCtx *pCtx)
154{
155 /* The caller will not use this context anymore. */
156 if (pCtx->rate)
157 {
158 st_rate_stop (pCtx->rate);
159 }
160
161 RTMemFree(pCtx->pvSamplesBuffer);
162 RTMemFree(pCtx->pvRateBuffer);
163
164 memset(pCtx, 0, sizeof(*pCtx));
165 RTMemFree(pCtx);
166}
167
168static void ictxReallocSamplesBuffer(SnifferInputCtx *pCtx, uint32_t cs)
169{
170 uint32_t cbBuffer = cs * sizeof(st_sample_t);
171
172 if (cbBuffer > pCtx->cbSamplesBufferAllocated)
173 {
174 RTMemFree(pCtx->pvSamplesBuffer);
175
176 pCtx->pvSamplesBuffer = RTMemAlloc(cbBuffer);
177 if (pCtx->pvSamplesBuffer)
178 {
179 pCtx->cbSamplesBufferAllocated = cbBuffer;
180 }
181 else
182 {
183 pCtx->cbSamplesBufferAllocated = 0;
184 }
185 }
186}
187
188static void ictxReallocRateBuffer(SnifferInputCtx *pCtx, uint32_t cs)
189{
190 uint32_t cbBuffer = cs * sizeof(st_sample_t);
191
192 if (cbBuffer > pCtx->cbRateBufferAllocated)
193 {
194 RTMemFree(pCtx->pvRateBuffer);
195
196 pCtx->pvRateBuffer = RTMemAlloc(cbBuffer);
197 if (pCtx->pvRateBuffer)
198 {
199 pCtx->cbRateBufferAllocated = cbBuffer;
200 }
201 else
202 {
203 pCtx->cbRateBufferAllocated = 0;
204 }
205 }
206}
207
208
209/*
210 * Filter audio output.
211 */
212
213/* Whether the filter should intercept audio output. */
214int filter_output_intercepted(void)
215{
216 return 0; /* @todo Not implemented yet.*/
217}
218
219/* Filter informs that an audio output is starting. */
220int filter_output_begin(void **ppvOutputCtx, struct audio_pcm_info *pinfo, int samples)
221{
222 return VERR_NOT_SUPPORTED; /* @todo Not implemented yet.*/
223}
224
225/* Filter informs that the audio output has been stopped. */
226void filter_output_end(void *pvOutputCtx)
227{
228 return; /* @todo Not implemented yet.*/
229}
230
231/*
232 * Filter audio input.
233 */
234
235/* Whether the filter should intercept audio input. */
236int filter_input_intercepted(void)
237{
238 if (!g_pData || !g_pData->pDrv)
239 {
240 return 0;
241 }
242
243 return g_pData->fInterceptAudioInput;
244}
245
246/* Filter informs that an audio input is starting. */
247int filter_input_begin (void **ppvInputCtx, PFNAUDIOINPUTCALLBACK pfnCallback, void *pvCallback, HWVoiceIn *phw, int cSamples)
248{
249 int rc = VINF_SUCCESS;
250
251 SnifferInputCtx *pCtx = NULL;
252
253 if (!g_pData || !g_pData->pDrv)
254 {
255 return VERR_NOT_SUPPORTED;
256 }
257
258 pCtx = (SnifferInputCtx *)RTMemAlloc(sizeof(SnifferInputCtx));
259
260 if (!pCtx)
261 {
262 return VERR_NO_MEMORY;
263 }
264
265 pCtx->cRefs = 2; /* Context is used by both the filter and the user. */
266 pCtx->pfnFilterCallback = pfnCallback;
267 pCtx->pvFilterCallback = pvCallback;
268 pCtx->fEndedByFilter = false;
269 pCtx->pvUserCtx = NULL;
270 pCtx->phw = phw;
271 pCtx->cBytesPerFrame = 1;
272 pCtx->iFreq = 0;
273 pCtx->conv = NULL;
274 pCtx->rate = NULL;
275 pCtx->pvSamplesBuffer = NULL;
276 pCtx->cbSamplesBufferAllocated = 0;
277 pCtx->pvRateBuffer = NULL;
278 pCtx->cbRateBufferAllocated = 0;
279
280 rc = g_pData->pDrv->pfnAudioInputBegin (g_pData->pDrv,
281 &pCtx->pvUserCtx, /* Returned by the pDrv. */
282 pCtx,
283 cSamples, /* How many samples in one block is preferred. */
284 phw->info.freq, /* Required frequency. */
285 phw->info.nchannels, /* Number of audio channels. */
286 phw->info.bits); /* A sample size in one channel, samples are signed. */
287
288 if (RT_SUCCESS(rc))
289 {
290 *ppvInputCtx = pCtx;
291 }
292 else
293 {
294 RTMemFree(pCtx);
295 }
296
297 Log(("input_begin rc = %Rrc\n", rc));
298
299 return rc;
300}
301
302/* Filter informs that the audio input must be stopped. */
303void filter_input_end(void *pvCtx)
304{
305 int32_t c;
306
307 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvCtx;
308
309 void *pvUserCtx = pCtx->pvUserCtx;
310
311 pCtx->fEndedByFilter = true;
312
313 c = ASMAtomicDecS32(&pCtx->cRefs);
314
315 if (c == 0)
316 {
317 ictxDelete(pCtx);
318 pCtx = NULL;
319 }
320
321 if (!g_pData || !g_pData->pDrv)
322 {
323 AssertFailed();
324 return;
325 }
326
327 g_pData->pDrv->pfnAudioInputEnd (g_pData->pDrv,
328 pvUserCtx);
329
330 Log(("input_end\n"));
331}
332
333
334/*
335 * Audio PDM device.
336 */
337static DECLCALLBACK(int) iface_AudioInputIntercept (PPDMIAUDIOSNIFFERPORT pInterface, bool fIntercept)
338{
339 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
340
341 Assert(g_pData == pThis);
342
343 pThis->fInterceptAudioInput = fIntercept;
344
345 return VINF_SUCCESS;
346}
347
348static DECLCALLBACK(int) iface_AudioInputEventBegin (PPDMIAUDIOSNIFFERPORT pInterface,
349 void *pvContext,
350 int iSampleHz,
351 int cChannels,
352 int cBits,
353 bool fUnsigned)
354{
355 int bitIdx;
356
357 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
358
359 int rc = VINF_SUCCESS;
360
361 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
362
363 Log(("FilterAudio: AudioInputEventBegin: %dHz,%dch,%dbits,%d ended %d\n",
364 iSampleHz, cChannels, cBits, fUnsigned, pCtx->fEndedByFilter));
365
366 Assert(g_pData == pThis);
367
368 /* Prepare a format convertion for the actually used format. */
369 pCtx->cBytesPerFrame = ((cBits + 7) / 8) * cChannels;
370
371 if (cBits == 16)
372 {
373 bitIdx = 1;
374 }
375 else if (cBits == 32)
376 {
377 bitIdx = 2;
378 }
379 else
380 {
381 bitIdx = 0;
382 }
383
384 pCtx->conv = mixeng_conv[(cChannels == 2)? 1: 0] /* stereo */
385 [!fUnsigned] /* sign */
386 [0] /* big endian */
387 [bitIdx]; /* bits */
388
389 if (iSampleHz && iSampleHz != pCtx->phw->info.freq)
390 {
391 pCtx->rate = st_rate_start (iSampleHz, pCtx->phw->info.freq);
392 pCtx->iFreq = iSampleHz;
393 }
394
395 return rc;
396}
397
398static DECLCALLBACK(int) iface_AudioInputEventData (PPDMIAUDIOSNIFFERPORT pInterface,
399 void *pvContext,
400 const void *pvData,
401 uint32_t cbData)
402{
403 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
404
405 int rc = VINF_SUCCESS;
406
407 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
408
409 Log(("FilterAudio: AudioInputEventData: pvData %p. cbData %d, ended %d\n", pvData, cbData, pCtx->fEndedByFilter));
410
411 Assert(g_pData == pThis);
412
413 if ( !pCtx->fEndedByFilter
414 && pCtx->conv)
415 {
416 /* Convert PCM samples to st_sample_t.
417 * And then apply rate conversion if necessary.
418 */
419
420 /* Optimization: allocate 'ps' buffer once per context and reallocate if cbData changes.
421 * Usually size of packets is constant.
422 */
423 st_sample_t *ps;
424 uint32_t cs = cbData / pCtx->cBytesPerFrame; /* How many samples. */
425
426 ictxReallocSamplesBuffer(pCtx, cs);
427
428 ps = (st_sample_t *)pCtx->pvSamplesBuffer;
429 if (ps)
430 {
431 void *pvSamples = NULL;
432 uint32_t cbSamples = 0;
433
434 Assert(pCtx->cbSamplesBufferAllocated >= cs * sizeof(st_sample_t));
435
436 pCtx->conv(ps, pvData, cs, &nominal_volume);
437
438 if (pCtx->rate)
439 {
440 st_sample_t *psConverted;
441 uint32_t csConverted = (cs * pCtx->phw->info.freq) / pCtx->iFreq;
442
443 ictxReallocRateBuffer(pCtx, csConverted);
444
445 psConverted = (st_sample_t *)pCtx->pvRateBuffer;
446 if (psConverted)
447 {
448 int csSrc = cs;
449 int csDst = csConverted;
450
451 Assert(pCtx->cbRateBufferAllocated >= csConverted * sizeof(st_sample_t));
452
453 st_rate_flow (pCtx->rate,
454 ps, psConverted,
455 &csSrc, &csDst);
456
457 pvSamples = psConverted;
458 cbSamples = csDst * sizeof(st_sample_t); /* Use csDst as it may be != csConverted */
459 }
460 else
461 {
462 rc = VERR_NO_MEMORY;
463 }
464 }
465 else
466 {
467 pvSamples = ps;
468 cbSamples = cs * sizeof(st_sample_t);
469 }
470
471 if (cbSamples)
472 {
473 rc = pCtx->pfnFilterCallback(pCtx->pvFilterCallback, cbSamples, pvSamples);
474 }
475 }
476 else
477 {
478 rc = VERR_NO_MEMORY;
479 }
480 }
481
482 return rc;
483}
484
485static DECLCALLBACK(void) iface_AudioInputEventEnd (PPDMIAUDIOSNIFFERPORT pInterface,
486 void *pvContext)
487{
488 int32_t c;
489
490 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
491
492 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
493
494 Log(("FilterAudio: AudioInputEventEnd: ended %d\n", pCtx->fEndedByFilter));
495
496 Assert(g_pData == pThis);
497
498 c = ASMAtomicDecS32(&pCtx->cRefs);
499
500 if (c == 0)
501 {
502 ictxDelete(pCtx);
503 pCtx = NULL;
504 }
505}
506
507static DECLCALLBACK(int) iface_Setup (PPDMIAUDIOSNIFFERPORT pInterface, bool fEnable, bool fKeepHostAudio)
508{
509 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
510
511 Assert(g_pData == pThis);
512
513 pThis->fEnabled = fEnable;
514 pThis->fKeepHostAudio = fKeepHostAudio;
515
516 return VINF_SUCCESS;
517}
518
519/**
520 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
521 */
522static DECLCALLBACK(void *) iface_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
523{
524 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IBase);
525 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
526 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOSNIFFERPORT, &pThis->IPort);
527 return NULL;
528}
529
530/**
531 * Destruct a device instance.
532 *
533 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
534 * resources can be freed correctly.
535 *
536 * @returns VBox status.
537 * @param pDevIns The device instance data.
538 */
539static DECLCALLBACK(int) audioSnifferR3Destruct(PPDMDEVINS pDevIns)
540{
541 PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
542
543 /* Zero the global pointer. */
544 g_pData = NULL;
545
546 return VINF_SUCCESS;
547}
548
549/**
550 * @interface_method_impl{PDMDEVREG,pfnConstruct}
551 */
552static DECLCALLBACK(int) audioSnifferR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
553{
554 int rc = VINF_SUCCESS;
555 AUDIOSNIFFERSTATE *pThis = PDMINS_2_DATA(pDevIns, AUDIOSNIFFERSTATE *);
556
557 Assert(iInstance == 0);
558 PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
559
560 /*
561 * Validate configuration.
562 */
563 if (!CFGMR3AreValuesValid(pCfgHandle, "InterceptAudioInput\0"))
564 {
565 return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
566 }
567
568 /*
569 * Initialize data.
570 */
571 pThis->fEnabled = false;
572 pThis->fKeepHostAudio = true;
573 pThis->pDrv = NULL;
574 rc = CFGMR3QueryBoolDef(pCfgHandle, "InterceptAudioInput", &pThis->fInterceptAudioInput, false);
575 if (RT_FAILURE(rc))
576 return PDMDEV_SET_ERROR(pDevIns, rc,
577 N_("Configuration error: Failed to get the \"YieldOnLSRRead\" value"));
578
579 /*
580 * Interfaces
581 */
582 /* Base */
583 pThis->IBase.pfnQueryInterface = iface_QueryInterface;
584
585 /* Audio Sniffer port */
586 pThis->IPort.pfnSetup = iface_Setup;
587 pThis->IPort.pfnAudioInputIntercept = iface_AudioInputIntercept;
588 pThis->IPort.pfnAudioInputEventBegin = iface_AudioInputEventBegin;
589 pThis->IPort.pfnAudioInputEventData = iface_AudioInputEventData;
590 pThis->IPort.pfnAudioInputEventEnd = iface_AudioInputEventEnd;
591
592 /*
593 * Get the corresponding connector interface
594 */
595 rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThis->IBase, &pThis->pDrvBase, "Audio Sniffer Port");
596
597 if (RT_SUCCESS(rc))
598 {
599 pThis->pDrv = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIAUDIOSNIFFERCONNECTOR);
600 AssertMsgStmt(pThis->pDrv, ("LUN #0 doesn't have a Audio Sniffer connector interface rc=%Rrc\n", rc),
601 rc = VERR_PDM_MISSING_INTERFACE);
602 }
603 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
604 {
605 Log(("%s/%d: warning: no driver attached to LUN #0.\n", pDevIns->pReg->szName, pDevIns->iInstance));
606 rc = VINF_SUCCESS;
607 }
608 else
609 {
610 AssertMsgFailed(("Failed to attach LUN #0. rc=%Rrc\n", rc));
611 }
612
613 if (RT_SUCCESS (rc))
614 {
615 /* Save PDM device instance data for future reference. */
616 pThis->pDevIns = pDevIns;
617
618 /* Save the pointer to created instance in the global variable, so other
619 * functions could reach it.
620 */
621 g_pData = pThis;
622 }
623
624 return rc;
625}
626
627/**
628 * The Audio Sniffer device registration structure.
629 */
630const PDMDEVREG g_DeviceAudioSniffer =
631{
632 /* u32Version */
633 PDM_DEVREG_VERSION,
634 /* szName */
635 "AudioSniffer",
636 /* szRCMod */
637 "",
638 /* szR0Mod */
639 "",
640 /* pszDescription */
641 "Audio Sniffer device. Redirects audio data to sniffer driver.",
642 /* fFlags */
643 PDM_DEVREG_FLAGS_DEFAULT_BITS,
644 /* fClass */
645 PDM_DEVREG_CLASS_AUDIO,
646 /* cMaxInstances */
647 1,
648 /* cbInstance */
649 sizeof(AUDIOSNIFFERSTATE),
650 /* pfnConstruct */
651 audioSnifferR3Construct,
652 /* pfnDestruct */
653 audioSnifferR3Destruct,
654 /* pfnRelocate */
655 NULL,
656 /* pfnIOCtl */
657 NULL,
658 /* pfnPowerOn */
659 NULL,
660 /* pfnReset */
661 NULL,
662 /* pfnSuspend */
663 NULL,
664 /* pfnResume */
665 NULL,
666 /* pfnAttach */
667 NULL,
668 /* pfnDetach */
669 NULL,
670 /* pfnQueryInterface */
671 NULL,
672 /* pfnInitComplete */
673 NULL,
674 /* pfnPowerOff */
675 NULL,
676 /* pfnSoftReset */
677 NULL,
678 PDM_DEVREG_VERSION
679};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use