VirtualBox

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

Last change on this file since 35353 was 35353, checked in by vboxsync, 14 years ago

Move the misc files the in src/VBox/Devices/ directory into a build/ subdirectory, changing their names to match the target module.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.4 KB
Line 
1/* $Id: audiosniffer.c 35353 2010-12-27 17:25:52Z 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} SnifferInputCtx;
144
145/*
146 * Filter audio output.
147 */
148
149/* Whether the filter should intercept audio output. */
150int filter_output_intercepted(void)
151{
152 return 0; /* @todo Not implemented yet.*/
153}
154
155/* Filter informs that an audio output is starting. */
156int filter_output_begin(void **ppvOutputCtx, struct audio_pcm_info *pinfo, int samples)
157{
158 return VERR_NOT_SUPPORTED; /* @todo Not implemented yet.*/
159}
160
161/* Filter informs that the audio output has been stopped. */
162void filter_output_end(void *pvOutputCtx)
163{
164 return; /* @todo Not implemented yet.*/
165}
166
167/*
168 * Filter audio input.
169 */
170
171/* Whether the filter should intercept audio input. */
172int filter_input_intercepted(void)
173{
174 if (!g_pData || !g_pData->pDrv)
175 {
176 return 0;
177 }
178
179 return g_pData->fInterceptAudioInput;
180}
181
182/* Filter informs that an audio input is starting. */
183int filter_input_begin (void **ppvInputCtx, PFNAUDIOINPUTCALLBACK pfnCallback, void *pvCallback, HWVoiceIn *phw, int cSamples)
184{
185 int rc = VINF_SUCCESS;
186
187 SnifferInputCtx *pCtx = NULL;
188
189 if (!g_pData || !g_pData->pDrv)
190 {
191 return VERR_NOT_SUPPORTED;
192 }
193
194 pCtx = (SnifferInputCtx *)RTMemAlloc(sizeof(SnifferInputCtx));
195
196 if (!pCtx)
197 {
198 return VERR_NO_MEMORY;
199 }
200
201 pCtx->cRefs = 2; /* Context is used by both the filter and the user. */
202 pCtx->pfnFilterCallback = pfnCallback;
203 pCtx->pvFilterCallback = pvCallback;
204 pCtx->fEndedByFilter = false;
205 pCtx->pvUserCtx = NULL;
206 pCtx->phw = phw;
207 pCtx->cBytesPerFrame = 1;
208 pCtx->iFreq = 0;
209 pCtx->conv = NULL;
210 pCtx->rate = NULL;
211
212 rc = g_pData->pDrv->pfnAudioInputBegin (g_pData->pDrv,
213 &pCtx->pvUserCtx, /* Returned by the pDrv. */
214 pCtx,
215 cSamples, /* How many samples in one block is preferred. */
216 phw->info.freq, /* Required frequency. */
217 phw->info.nchannels, /* Number of audio channels. */
218 phw->info.bits); /* A sample size in one channel, samples are signed. */
219
220 if (RT_SUCCESS(rc))
221 {
222 *ppvInputCtx = pCtx;
223 }
224 else
225 {
226 RTMemFree(pCtx);
227 }
228
229 Log(("input_begin rc = %Rrc\n", rc));
230
231 return rc;
232}
233
234/* Filter informs that the audio input must be stopped. */
235void filter_input_end(void *pvCtx)
236{
237 int32_t c;
238
239 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvCtx;
240
241 void *pvUserCtx = pCtx->pvUserCtx;
242
243 pCtx->fEndedByFilter = true;
244
245 c = ASMAtomicDecS32(&pCtx->cRefs);
246
247 if (c == 0)
248 {
249 /* The caller will not use this context anymore. */
250 if (pCtx->rate)
251 {
252 st_rate_stop (pCtx->rate);
253 }
254 RTMemFree(pCtx);
255 pCtx = NULL;
256 }
257
258 if (!g_pData || !g_pData->pDrv)
259 {
260 AssertFailed();
261 return;
262 }
263
264 g_pData->pDrv->pfnAudioInputEnd (g_pData->pDrv,
265 pvUserCtx);
266
267 Log(("input_end\n"));
268}
269
270
271/*
272 * Audio PDM device.
273 */
274static DECLCALLBACK(int) iface_AudioInputIntercept (PPDMIAUDIOSNIFFERPORT pInterface, bool fIntercept)
275{
276 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
277
278 Assert(g_pData == pThis);
279
280 pThis->fInterceptAudioInput = fIntercept;
281
282 return VINF_SUCCESS;
283}
284
285static DECLCALLBACK(int) iface_AudioInputEventBegin (PPDMIAUDIOSNIFFERPORT pInterface,
286 void *pvContext,
287 int iSampleHz,
288 int cChannels,
289 int cBits,
290 bool fUnsigned)
291{
292 int bitIdx;
293
294 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
295
296 int rc = VINF_SUCCESS;
297
298 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
299
300 Log(("FilterAudio: AudioInputEventBegin: %dHz,%dch,%dbits,%d ended %d\n",
301 iSampleHz, cChannels, cBits, fUnsigned, pCtx->fEndedByFilter));
302
303 Assert(g_pData == pThis);
304
305 /* Prepare a format convertion for the actually used format. */
306 pCtx->cBytesPerFrame = ((cBits + 7) / 8) * cChannels;
307
308 if (cBits == 16)
309 {
310 bitIdx = 1;
311 }
312 else if (cBits == 32)
313 {
314 bitIdx = 2;
315 }
316 else
317 {
318 bitIdx = 0;
319 }
320
321 pCtx->conv = mixeng_conv[(cChannels == 2)? 1: 0] /* stereo */
322 [!fUnsigned] /* sign */
323 [0] /* big endian */
324 [bitIdx]; /* bits */
325
326 if (iSampleHz && iSampleHz != pCtx->phw->info.freq)
327 {
328 pCtx->rate = st_rate_start (iSampleHz, pCtx->phw->info.freq);
329 pCtx->iFreq = iSampleHz;
330 }
331
332 return rc;
333}
334
335static DECLCALLBACK(int) iface_AudioInputEventData (PPDMIAUDIOSNIFFERPORT pInterface,
336 void *pvContext,
337 const void *pvData,
338 uint32_t cbData)
339{
340 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
341
342 int rc = VINF_SUCCESS;
343
344 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
345
346 Log(("FilterAudio: AudioInputEventData: pvData %p. cbData %d, ended %d\n", pvData, cbData, pCtx->fEndedByFilter));
347
348 Assert(g_pData == pThis);
349
350 if ( !pCtx->fEndedByFilter
351 && pCtx->conv)
352 {
353 /* Convert PCM samples to st_sample_t.
354 * And then apply rate convertion if necessary.
355 */
356 /* @todo Optimization: allocate ps buffer once per context and reallocate if cbData changes. */
357 uint32_t cs = cbData / pCtx->cBytesPerFrame;
358 st_sample_t *ps = (st_sample_t *)RTMemAlloc(cs * sizeof(st_sample_t));
359 if (ps)
360 {
361 void *pvSamplesRateDst = NULL;
362
363 void *pvSamples = NULL;
364 uint32_t cbSamples = 0;
365
366 pCtx->conv(ps, pvData, cs, &nominal_volume);
367
368 if (pCtx->rate)
369 {
370 uint32_t csConverted = (cs * pCtx->phw->info.freq) / pCtx->iFreq;
371 pvSamplesRateDst = RTMemAlloc(csConverted * sizeof(st_sample_t));
372
373 if (pvSamplesRateDst)
374 {
375 int csSrc = cs;
376 int csDst = csConverted;
377
378 st_rate_flow (pCtx->rate,
379 ps, (st_sample_t *)pvSamplesRateDst,
380 &csSrc, &csDst);
381
382 pvSamples = pvSamplesRateDst;
383 cbSamples = csDst * sizeof(st_sample_t);
384 }
385 else
386 {
387 rc = VERR_NO_MEMORY;
388 }
389 }
390 else
391 {
392 pvSamples = ps;
393 cbSamples = cs * sizeof(st_sample_t);
394 }
395
396 if (cbSamples)
397 {
398 rc = pCtx->pfnFilterCallback(pCtx->pvFilterCallback, cbSamples, pvSamples);
399 }
400
401 RTMemFree(pvSamplesRateDst);
402 RTMemFree(ps);
403 }
404 else
405 {
406 rc = VERR_NO_MEMORY;
407 }
408 }
409
410 return rc;
411}
412
413static DECLCALLBACK(void) iface_AudioInputEventEnd (PPDMIAUDIOSNIFFERPORT pInterface,
414 void *pvContext)
415{
416 int32_t c;
417
418 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
419
420 SnifferInputCtx *pCtx = (SnifferInputCtx *)pvContext;
421
422 Log(("FilterAudio: AudioInputEventEnd: ended %d\n", pCtx->fEndedByFilter));
423
424 Assert(g_pData == pThis);
425
426 c = ASMAtomicDecS32(&pCtx->cRefs);
427
428 if (c == 0)
429 {
430 /* The caller will not use this context anymore. */
431 if (pCtx->rate)
432 {
433 st_rate_stop (pCtx->rate);
434 }
435 RTMemFree(pCtx);
436 }
437}
438
439
440static DECLCALLBACK(int) iface_Setup (PPDMIAUDIOSNIFFERPORT pInterface, bool fEnable, bool fKeepHostAudio)
441{
442 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IPort);
443
444 Assert(g_pData == pThis);
445
446 pThis->fEnabled = fEnable;
447 pThis->fKeepHostAudio = fKeepHostAudio;
448
449 return VINF_SUCCESS;
450}
451
452/**
453 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
454 */
455static DECLCALLBACK(void *) iface_QueryInterface(PPDMIBASE pInterface, const char *pszIID)
456{
457 AUDIOSNIFFERSTATE *pThis = RT_FROM_MEMBER(pInterface, AUDIOSNIFFERSTATE, IBase);
458 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
459 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOSNIFFERPORT, &pThis->IPort);
460 return NULL;
461}
462
463/**
464 * Destruct a device instance.
465 *
466 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
467 * resources can be freed correctly.
468 *
469 * @returns VBox status.
470 * @param pDevIns The device instance data.
471 */
472static DECLCALLBACK(int) audioSnifferR3Destruct(PPDMDEVINS pDevIns)
473{
474 PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
475
476 /* Zero the global pointer. */
477 g_pData = NULL;
478
479 return VINF_SUCCESS;
480}
481
482/**
483 * @interface_method_impl{PDMDEVREG,pfnConstruct}
484 */
485static DECLCALLBACK(int) audioSnifferR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfgHandle)
486{
487 int rc = VINF_SUCCESS;
488 AUDIOSNIFFERSTATE *pThis = PDMINS_2_DATA(pDevIns, AUDIOSNIFFERSTATE *);
489
490 Assert(iInstance == 0);
491 PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
492
493 /*
494 * Validate configuration.
495 */
496 if (!CFGMR3AreValuesValid(pCfgHandle, "InterceptAudioInput\0"))
497 {
498 return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
499 }
500
501 /*
502 * Initialize data.
503 */
504 pThis->fEnabled = false;
505 pThis->fKeepHostAudio = true;
506 pThis->pDrv = NULL;
507 rc = CFGMR3QueryBoolDef(pCfgHandle, "InterceptAudioInput", &pThis->fInterceptAudioInput, false);
508 if (RT_FAILURE(rc))
509 return PDMDEV_SET_ERROR(pDevIns, rc,
510 N_("Configuration error: Failed to get the \"YieldOnLSRRead\" value"));
511
512 /*
513 * Interfaces
514 */
515 /* Base */
516 pThis->IBase.pfnQueryInterface = iface_QueryInterface;
517
518 /* Audio Sniffer port */
519 pThis->IPort.pfnSetup = iface_Setup;
520 pThis->IPort.pfnAudioInputIntercept = iface_AudioInputIntercept;
521 pThis->IPort.pfnAudioInputEventBegin = iface_AudioInputEventBegin;
522 pThis->IPort.pfnAudioInputEventData = iface_AudioInputEventData;
523 pThis->IPort.pfnAudioInputEventEnd = iface_AudioInputEventEnd;
524
525 /*
526 * Get the corresponding connector interface
527 */
528 rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThis->IBase, &pThis->pDrvBase, "Audio Sniffer Port");
529
530 if (RT_SUCCESS(rc))
531 {
532 pThis->pDrv = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIAUDIOSNIFFERCONNECTOR);
533 AssertMsgStmt(pThis->pDrv, ("LUN #0 doesn't have a Audio Sniffer connector interface rc=%Rrc\n", rc),
534 rc = VERR_PDM_MISSING_INTERFACE);
535 }
536 else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
537 {
538 Log(("%s/%d: warning: no driver attached to LUN #0.\n", pDevIns->pReg->szName, pDevIns->iInstance));
539 rc = VINF_SUCCESS;
540 }
541 else
542 {
543 AssertMsgFailed(("Failed to attach LUN #0. rc=%Rrc\n", rc));
544 }
545
546 if (RT_SUCCESS (rc))
547 {
548 /* Save PDM device instance data for future reference. */
549 pThis->pDevIns = pDevIns;
550
551 /* Save the pointer to created instance in the global variable, so other
552 * functions could reach it.
553 */
554 g_pData = pThis;
555 }
556
557 return rc;
558}
559
560/**
561 * The Audio Sniffer device registration structure.
562 */
563const PDMDEVREG g_DeviceAudioSniffer =
564{
565 /* u32Version */
566 PDM_DEVREG_VERSION,
567 /* szName */
568 "AudioSniffer",
569 /* szRCMod */
570 "",
571 /* szR0Mod */
572 "",
573 /* pszDescription */
574 "Audio Sniffer device. Redirects audio data to sniffer driver.",
575 /* fFlags */
576 PDM_DEVREG_FLAGS_DEFAULT_BITS,
577 /* fClass */
578 PDM_DEVREG_CLASS_AUDIO,
579 /* cMaxInstances */
580 1,
581 /* cbInstance */
582 sizeof(AUDIOSNIFFERSTATE),
583 /* pfnConstruct */
584 audioSnifferR3Construct,
585 /* pfnDestruct */
586 audioSnifferR3Destruct,
587 /* pfnRelocate */
588 NULL,
589 /* pfnIOCtl */
590 NULL,
591 /* pfnPowerOn */
592 NULL,
593 /* pfnReset */
594 NULL,
595 /* pfnSuspend */
596 NULL,
597 /* pfnResume */
598 NULL,
599 /* pfnAttach */
600 NULL,
601 /* pfnDetach */
602 NULL,
603 /* pfnQueryInterface */
604 NULL,
605 /* pfnInitComplete */
606 NULL,
607 /* pfnPowerOff */
608 NULL,
609 /* pfnSoftReset */
610 NULL,
611 PDM_DEVREG_VERSION
612};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette