VirtualBox

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

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

Initial audio filter implementation, which is used for audio input via remote desktop server.

  • 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 34906 2010-12-09 16:29:49Z 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/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 "Builtins.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 = ASMAtomicDecU32(&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 = ASMAtomicDecU32(&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