VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/DrvAudioVRDE.cpp@ 88885

Last change on this file since 88885 was 88885, checked in by vboxsync, 4 years ago

DrvAudioVRDE: Experimenting with alternative ways of signalling that we want to play/record stuff when a client connects. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.0 KB
Line 
1/* $Id: DrvAudioVRDE.cpp 88885 2021-05-05 18:28:38Z vboxsync $ */
2/** @file
3 * VRDE audio backend for Main.
4 */
5
6/*
7 * Copyright (C) 2013-2020 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
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include "LoggingNew.h"
24
25#include <VBox/log.h>
26#include "DrvAudioVRDE.h"
27#include "ConsoleImpl.h"
28#include "ConsoleVRDPServer.h"
29
30#include <iprt/mem.h>
31#include <iprt/cdefs.h>
32#include <iprt/circbuf.h>
33
34#include <VBox/vmm/cfgm.h>
35#include <VBox/vmm/pdmdrv.h>
36#include <VBox/vmm/pdmaudioifs.h>
37#include <VBox/vmm/pdmaudioinline.h>
38#include <VBox/RemoteDesktop/VRDE.h>
39#include <VBox/err.h>
40
41
42/*********************************************************************************************************************************
43* Structures and Typedefs *
44*********************************************************************************************************************************/
45/**
46 * VRDE stream.
47 */
48typedef struct VRDESTREAM
49{
50 /** Common part. */
51 PDMAUDIOBACKENDSTREAM Core;
52 /** The stream's acquired configuration. */
53 PDMAUDIOSTREAMCFG Cfg;
54 union
55 {
56 struct
57 {
58 /** Circular buffer for holding the recorded audio frames from the host. */
59 PRTCIRCBUF pCircBuf;
60 } In;
61 };
62} VRDESTREAM;
63/** Pointer to a VRDE stream. */
64typedef VRDESTREAM *PVRDESTREAM;
65
66/**
67 * VRDE (host) audio driver instance data.
68 */
69typedef struct DRVAUDIOVRDE
70{
71 /** Pointer to audio VRDE object. */
72 AudioVRDE *pAudioVRDE;
73 /** Pointer to the driver instance structure. */
74 PPDMDRVINS pDrvIns;
75 /** Pointer to the VRDP's console object. */
76 ConsoleVRDPServer *pConsoleVRDPServer;
77 /** Number of connected clients to this VRDE instance. */
78 uint32_t cClients;
79 /** Interface to the driver above us (DrvAudio). */
80 PDMIHOSTAUDIOPORT *pIHostAudioPort;
81 /** Pointer to host audio interface. */
82 PDMIHOSTAUDIO IHostAudio;
83} DRVAUDIOVRDE;
84/** Pointer to the instance data for an VRDE audio driver. */
85typedef DRVAUDIOVRDE *PDRVAUDIOVRDE;
86
87/* Sanity. */
88AssertCompileSize(PDMAUDIOFRAME, sizeof(int64_t) * 2 /* st_sample_t using by VRDP server */);
89
90
91
92/*********************************************************************************************************************************
93* Class AudioVRDE *
94*********************************************************************************************************************************/
95
96AudioVRDE::AudioVRDE(Console *pConsole)
97 : AudioDriver(pConsole)
98 , mpDrv(NULL)
99{
100 RTCritSectInit(&mCritSect);
101}
102
103
104AudioVRDE::~AudioVRDE(void)
105{
106 RTCritSectEnter(&mCritSect);
107 if (mpDrv)
108 {
109 mpDrv->pAudioVRDE = NULL;
110 mpDrv = NULL;
111 }
112 RTCritSectLeave(&mCritSect);
113 RTCritSectDelete(&mCritSect);
114}
115
116
117/**
118 * @copydoc AudioDriver::configureDriver
119 */
120int AudioVRDE::configureDriver(PCFGMNODE pLunCfg)
121{
122 int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)this);
123 AssertRCReturn(rc, rc);
124 CFGMR3InsertInteger(pLunCfg, "ObjectVRDPServer", (uintptr_t)mpConsole->i_consoleVRDPServer());
125 AssertRCReturn(rc, rc);
126
127 return AudioDriver::configureDriver(pLunCfg);
128}
129
130
131void AudioVRDE::onVRDEClientConnect(uint32_t uClientID)
132{
133 RT_NOREF(uClientID);
134
135 RTCritSectEnter(&mCritSect);
136 if (mpDrv)
137 {
138 mpDrv->cClients++;
139 LogRel2(("Audio: VRDE client connected (#%u)\n", mpDrv->cClients));
140
141#if 0 /* later, maybe */
142 /*
143 * The first client triggers a device change event in both directions
144 * so that can start talking to the audio device.
145 *
146 * Note! Should be okay to stay in the critical section here, as it's only
147 * used at construction and destruction time.
148 */
149 if (mpDrv->cClients == 1)
150 {
151 VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
152 if (enmState <= VMSTATE_POWERING_OFF)
153 {
154 PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
155 AssertPtr(pIHostAudioPort);
156 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
157 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
158 }
159 }
160#endif
161 }
162 RTCritSectLeave(&mCritSect);
163}
164
165
166void AudioVRDE::onVRDEClientDisconnect(uint32_t uClientID)
167{
168 RT_NOREF(uClientID);
169
170 RTCritSectEnter(&mCritSect);
171 if (mpDrv)
172 {
173 Assert(mpDrv->cClients > 0);
174 mpDrv->cClients--;
175 LogRel2(("Audio: VRDE client disconnected (%u left)\n", mpDrv->cClients));
176#if 0 /* later maybe */
177 /*
178 * The last client leaving triggers a device change event in both
179 * directions so the audio devices can stop wasting time trying to
180 * talk to us. (There is an additional safeguard in
181 * drvAudioVrdeHA_StreamGetStatus.)
182 */
183 if (mpDrv->cClients == 0)
184 {
185 VMSTATE enmState = PDMDrvHlpVMState(mpDrv->pDrvIns);
186 if (enmState <= VMSTATE_POWERING_OFF)
187 {
188 PDMIHOSTAUDIOPORT *pIHostAudioPort = mpDrv->pIHostAudioPort;
189 AssertPtr(pIHostAudioPort);
190 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
191 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
192 }
193 }
194#endif
195 }
196 RTCritSectLeave(&mCritSect);
197}
198
199
200int AudioVRDE::onVRDEControl(bool fEnable, uint32_t uFlags)
201{
202 RT_NOREF(fEnable, uFlags);
203 LogFlowThisFunc(("fEnable=%RTbool, uFlags=0x%x\n", fEnable, uFlags));
204
205 if (mpDrv == NULL)
206 return VERR_INVALID_STATE;
207
208 return VINF_SUCCESS; /* Never veto. */
209}
210
211
212/**
213 * Marks the beginning of sending captured audio data from a connected
214 * RDP client.
215 *
216 * @returns VBox status code.
217 * @param pvContext The context; in this case a pointer to a
218 * VRDESTREAMIN structure.
219 * @param pVRDEAudioBegin Pointer to a VRDEAUDIOINBEGIN structure.
220 */
221int AudioVRDE::onVRDEInputBegin(void *pvContext, PVRDEAUDIOINBEGIN pVRDEAudioBegin)
222{
223 AssertPtrReturn(pvContext, VERR_INVALID_POINTER);
224 AssertPtrReturn(pVRDEAudioBegin, VERR_INVALID_POINTER);
225 PVRDESTREAM pVRDEStrmIn = (PVRDESTREAM)pvContext;
226 AssertPtrReturn(pVRDEStrmIn, VERR_INVALID_POINTER);
227
228#ifdef LOG_ENABLED
229 VRDEAUDIOFORMAT const audioFmt = pVRDEAudioBegin->fmt;
230 LogFlowFunc(("cbSample=%RU32, iSampleHz=%d, cChannels=%d, cBits=%d, fUnsigned=%RTbool\n",
231 VRDE_AUDIO_FMT_BYTES_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SAMPLE_FREQ(audioFmt),
232 VRDE_AUDIO_FMT_CHANNELS(audioFmt), VRDE_AUDIO_FMT_BITS_PER_SAMPLE(audioFmt), VRDE_AUDIO_FMT_SIGNED(audioFmt)));
233#endif
234
235 return VINF_SUCCESS;
236}
237
238
239int AudioVRDE::onVRDEInputData(void *pvContext, const void *pvData, uint32_t cbData)
240{
241 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pvContext;
242 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
243
244 void *pvBuf = NULL;
245 size_t cbBuf = 0;
246 RTCircBufAcquireWriteBlock(pStreamVRDE->In.pCircBuf, cbData, &pvBuf, &cbBuf);
247
248 if (cbBuf)
249 memcpy(pvBuf, pvData, cbBuf);
250
251 RTCircBufReleaseWriteBlock(pStreamVRDE->In.pCircBuf, cbBuf);
252
253 if (cbBuf < cbData)
254 LogRelMax(999, ("VRDE: Capturing audio data lost %zu bytes\n", cbData - cbBuf)); /** @todo Use an error counter. */
255
256 return VINF_SUCCESS; /** @todo r=andy How to tell the caller if we were not able to handle *all* input data? */
257}
258
259
260int AudioVRDE::onVRDEInputEnd(void *pvContext)
261{
262 RT_NOREF(pvContext);
263 return VINF_SUCCESS;
264}
265
266
267int AudioVRDE::onVRDEInputIntercept(bool fEnabled)
268{
269 RT_NOREF(fEnabled);
270 return VINF_SUCCESS; /* Never veto. */
271}
272
273
274
275/*********************************************************************************************************************************
276* PDMIHOSTAUDIO *
277*********************************************************************************************************************************/
278
279/**
280 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
281 */
282static DECLCALLBACK(int) drvAudioVrdeHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
283{
284 RT_NOREF(pInterface);
285 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
286
287 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VRDE");
288 pBackendCfg->cbStream = sizeof(VRDESTREAM);
289 pBackendCfg->fFlags = 0;
290 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
291 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
292
293 return VINF_SUCCESS;
294}
295
296
297/**
298 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
299 */
300static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVrdeHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
301{
302 RT_NOREF(pInterface, enmDir);
303 return PDMAUDIOBACKENDSTS_RUNNING;
304}
305
306
307static int vrdeCreateStreamIn(PVRDESTREAM pStreamVRDE, PPDMAUDIOSTREAMCFG pCfgAcq)
308{
309 /*
310 * The VRDP server does its own mixing and resampling as it may server
311 * multiple clients all with different sound formats. So, it feeds us
312 * raw mixer frames (somewhat akind to stereo signed 64-bit, see
313 * st_sample_t and PDMAUDIOFRAME).
314 */
315 pCfgAcq->enmLayout = PDMAUDIOSTREAMLAYOUT_RAW;
316 PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/, 22050 /*Hz*/,
317 true /*fLittleEndian*/, true /*fRaw*/);
318
319 /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */
320 const uint32_t cFramesVrdpServer = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 200 /*ms*/);
321
322 int rc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, PDMAudioPropsFramesToBytes(&pCfgAcq->Props, cFramesVrdpServer));
323 if (RT_SUCCESS(rc))
324 {
325 pCfgAcq->Backend.cFramesPeriod = cFramesVrdpServer;
326/** @todo r=bird: This is inconsistent with the above buffer allocation and I
327 * think also ALSA and Pulse backends way of setting cFramesBufferSize. */
328 pCfgAcq->Backend.cFramesBufferSize = cFramesVrdpServer * 2; /* Use "double buffering". */
329 pCfgAcq->Backend.cFramesPreBuffering = cFramesVrdpServer;
330 }
331
332 return rc;
333}
334
335
336static int vrdeCreateStreamOut(PPDMAUDIOSTREAMCFG pCfgAcq)
337{
338 /*
339 * The VRDP server does its own mixing and resampling because it may be
340 * sending the audio to any number of different clients all with different
341 * formats (including clients which hasn't yet connected). So, it desires
342 * the raw data from the mixer (somewhat akind to stereo signed 64-bit,
343 * see st_sample_t and PDMAUDIOFRAME).
344 */
345 pCfgAcq->enmLayout = PDMAUDIOSTREAMLAYOUT_RAW;
346 PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/, 22050 /*Hz*/,
347 true /*fLittleEndian*/, true /*fRaw*/);
348
349 /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */
350 /** @todo r=bird: So, if VRDP does 200ms chunks, why do we report 100ms
351 * buffer and 20ms period? How does these parameters at all correlate
352 * with the above comment?!? */
353 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 20 /*ms*/);
354 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/);
355 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
356
357 return VINF_SUCCESS;
358}
359
360
361/**
362 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
363 */
364static DECLCALLBACK(int) drvAudioVrdeHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
365 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
366{
367 PDRVAUDIOVRDE pThis = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
368 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
369 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
370 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
371 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
372
373 /*
374 * Only create a stream if we have clients.
375 */
376 int rc;
377 NOREF(pThis);
378#if 0 /* later maybe */
379 if (pThis->cClients == 0)
380 {
381 LogFunc(("No clients, failing with VERR_AUDIO_STREAM_COULD_NOT_CREATE.\n"));
382 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
383 }
384 else
385#endif
386 {
387 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
388 rc = vrdeCreateStreamIn(pStreamVRDE, pCfgAcq);
389 else
390 rc = vrdeCreateStreamOut(pCfgAcq);
391 PDMAudioStrmCfgCopy(&pStreamVRDE->Cfg, pCfgAcq);
392 }
393 return rc;
394}
395
396
397/**
398 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
399 */
400static DECLCALLBACK(int) drvAudioVrdeHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
401{
402 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
403 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
404 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
405
406 if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_OUT)
407 {
408 if (pDrv->pConsoleVRDPServer)
409 pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL);
410
411 if (pStreamVRDE->In.pCircBuf)
412 {
413 RTCircBufDestroy(pStreamVRDE->In.pCircBuf);
414 pStreamVRDE->In.pCircBuf = NULL;
415 }
416 }
417 pDrv->pConsoleVRDPServer = NULL;
418
419 return VINF_SUCCESS;
420}
421
422
423/**
424 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
425 */
426static DECLCALLBACK(int) drvAudioVrdeHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
427{
428 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
429 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
430
431 int rc;
432 if (!pDrv->pConsoleVRDPServer)
433 {
434 LogRelMax(32, ("Audio: VRDP console not ready (enable)\n"));
435 rc = VERR_AUDIO_STREAM_NOT_READY;
436 }
437 else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
438 {
439 rc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE,
440 PDMAudioPropsMilliToFrames(&pStreamVRDE->Cfg.Props, 200 /*ms*/),
441 PDMAudioPropsHz(&pStreamVRDE->Cfg.Props),
442 PDMAudioPropsChannels(&pStreamVRDE->Cfg.Props),
443 PDMAudioPropsSampleBits(&pStreamVRDE->Cfg.Props));
444 if (rc == VERR_NOT_SUPPORTED)
445 {
446 LogRelMax(64, ("Audio: No VRDE client connected, so no input recording available\n"));
447 rc = VERR_AUDIO_STREAM_NOT_READY;
448 }
449 }
450 else
451 rc = VINF_SUCCESS;
452 LogFlowFunc(("returns %Rrc\n", rc));
453 return rc;
454}
455
456
457/**
458 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
459 */
460static DECLCALLBACK(int) drvAudioVrdeHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
461{
462 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
463 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
464
465 int rc;
466 if (!pDrv->pConsoleVRDPServer)
467 {
468 LogRelMax(32, ("Audio: VRDP console not ready (disable)\n"));
469 rc = VERR_AUDIO_STREAM_NOT_READY;
470 }
471 else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
472 {
473 pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */);
474 rc = VINF_SUCCESS;
475 }
476 else
477 rc = VINF_SUCCESS;
478 LogFlowFunc(("returns %Rrc\n", rc));
479 return rc;
480}
481
482
483/**
484 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
485 */
486static DECLCALLBACK(int) drvAudioVrdeHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
487{
488 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
489 RT_NOREF(pStream);
490
491 if (!pDrv->pConsoleVRDPServer)
492 {
493 LogRelMax(32, ("Audio: VRDP console not ready (pause)\n"));
494 return VERR_AUDIO_STREAM_NOT_READY;
495 }
496 LogFlowFunc(("returns VINF_SUCCESS\n"));
497 return VINF_SUCCESS;
498}
499
500
501/**
502 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
503 */
504static DECLCALLBACK(int) drvAudioVrdeHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
505{
506 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
507 RT_NOREF(pStream);
508
509 if (!pDrv->pConsoleVRDPServer)
510 {
511 LogRelMax(32, ("Audio: VRDP console not ready (resume)\n"));
512 return VERR_AUDIO_STREAM_NOT_READY;
513 }
514 LogFlowFunc(("returns VINF_SUCCESS\n"));
515 return VINF_SUCCESS;
516}
517
518
519/**
520 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
521 */
522static DECLCALLBACK(int) drvAudioVrdeHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
523{
524 RT_NOREF(pInterface, pStream);
525 LogFlowFunc(("returns VINF_SUCCESS\n"));
526 return VINF_SUCCESS;
527}
528
529
530/**
531 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
532 */
533static DECLCALLBACK(int) drvAudioVrdeHA_StreamControl(PPDMIHOSTAUDIO pInterface,
534 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
535{
536 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
537 * replacing it with individual StreamXxxx methods. That would save us
538 * potentally huge switches and more easily see which drivers implement
539 * which operations (grep for pfnStreamXxxx). */
540 switch (enmStreamCmd)
541 {
542 case PDMAUDIOSTREAMCMD_ENABLE:
543 return drvAudioVrdeHA_StreamEnable(pInterface, pStream);
544 case PDMAUDIOSTREAMCMD_DISABLE:
545 return drvAudioVrdeHA_StreamDisable(pInterface, pStream);
546 case PDMAUDIOSTREAMCMD_PAUSE:
547 return drvAudioVrdeHA_StreamPause(pInterface, pStream);
548 case PDMAUDIOSTREAMCMD_RESUME:
549 return drvAudioVrdeHA_StreamResume(pInterface, pStream);
550 case PDMAUDIOSTREAMCMD_DRAIN:
551 return drvAudioVrdeHA_StreamDrain(pInterface, pStream);
552
553 case PDMAUDIOSTREAMCMD_END:
554 case PDMAUDIOSTREAMCMD_32BIT_HACK:
555 case PDMAUDIOSTREAMCMD_INVALID:
556 /* no default*/
557 break;
558 }
559 return VERR_NOT_SUPPORTED;
560}
561
562
563/**
564 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
565 */
566static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
567{
568 RT_NOREF(pInterface);
569 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
570
571 if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
572 {
573 /* Return frames instead of bytes here
574 * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout). */
575 return PDMAudioPropsBytesToFrames(&pStreamVRDE->Cfg.Props, (uint32_t)RTCircBufUsed(pStreamVRDE->In.pCircBuf));
576 }
577 return 0;
578}
579
580
581/**
582 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
583 */
584static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
585{
586 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
587 RT_NOREF(pStream);
588
589 /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */
590 if (pDrv->cClients)
591 return _16K * sizeof(PDMAUDIOFRAME);
592 return 0;
593}
594
595
596/**
597 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
598 */
599static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
600{
601 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
602 RT_NOREF(pStream);
603
604 return pDrv->cClients > 0
605 ? PDMAUDIOSTREAM_STS_INITIALIZED | PDMAUDIOSTREAM_STS_ENABLED
606#if 0 /* later mabye */ /** @todo r=bird: Weird backend status mess. */
607 : PDMAUDIOSTREAM_STS_NONE /* play possum if the clients all disappears. Re-init should be underways. */;
608#else
609 : PDMAUDIOSTREAM_STS_INITIALIZED /* If any clients are connected, flag the stream as enabled. */;
610#endif
611}
612
613
614/**
615 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
616 */
617static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
618 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
619{
620 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
621 AssertPtr(pDrv);
622 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
623 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
624 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
625 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
626 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
627
628 if (!pDrv->pConsoleVRDPServer)
629 return VERR_NOT_AVAILABLE;
630
631 /* Prepate the format. */
632 PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props;
633 VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps),
634 PDMAudioPropsChannels(pProps),
635 PDMAudioPropsSampleBits(pProps),
636 pProps->fSigned);
637 Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true));
638
639 /* We specified PDMAUDIOSTREAMLAYOUT_RAW (== S64), so
640 convert the buffer pointe and size accordingly: */
641 PCPDMAUDIOFRAME paSampleBuf = (PCPDMAUDIOFRAME)pvBuf;
642 uint32_t const cFramesToWrite = cbBuf / sizeof(paSampleBuf[0]);
643 Assert(cFramesToWrite * sizeof(paSampleBuf[0]) == cbBuf);
644
645 /** @todo r=bird: there was some incoherent mumbling about "using the
646 * internal counter to track if we (still) can write to the VRDP
647 * server or if need to wait anothe round (time slot)". However it
648 * wasn't accessing any internal counter nor doing anything else
649 * sensible, so I've removed it. */
650
651 /*
652 * Call the VRDP server with the data.
653 */
654 uint32_t cFramesWritten = 0;
655 while (cFramesWritten < cFramesToWrite)
656 {
657 uint32_t const cFramesChunk = cFramesToWrite - cFramesWritten; /** @todo For now write all at once. */
658
659 /* Note: The VRDP server expects int64_t samples per channel, regardless
660 of the actual sample bits (e.g 8 or 16 bits). */
661 pDrv->pConsoleVRDPServer->SendAudioSamples(&paSampleBuf[cFramesWritten], cFramesChunk /* Frames */, uVrdpFormat);
662
663 cFramesWritten += cFramesChunk;
664 }
665
666 Log3Func(("cFramesWritten=%RU32\n", cFramesWritten));
667 if (pcbWritten)
668 *pcbWritten = cFramesWritten * sizeof(PDMAUDIOFRAME);
669 return VINF_SUCCESS;
670}
671
672
673/**
674 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
675 */
676static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
677 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
678{
679 RT_NOREF(pInterface);
680 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
681 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
682 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
683 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
684 AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
685
686 size_t cbData = 0;
687 if (RTCircBufUsed(pStreamVRDE->In.pCircBuf))
688 {
689 void *pvData = NULL;
690 RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData);
691
692 if (cbData)
693 memcpy(pvBuf, pvData, cbData);
694
695 RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData);
696 }
697
698 *pcbRead = (uint32_t)cbData;
699 return VINF_SUCCESS;
700}
701
702
703/*********************************************************************************************************************************
704* PDMIBASE *
705*********************************************************************************************************************************/
706
707/**
708 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
709 */
710static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
711{
712 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
713 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
714
715 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
716 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
717 return NULL;
718}
719
720
721/*********************************************************************************************************************************
722* PDMDRVREG *
723*********************************************************************************************************************************/
724
725/**
726 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
727 */
728/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns)
729{
730 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
731 LogFlowFuncEnter();
732
733 if (pThis->pConsoleVRDPServer)
734 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL);
735}
736
737
738/**
739 * @interface_method_impl{PDMDRVREG,pfnDestruct}
740 */
741/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns)
742{
743 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
744 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
745 LogFlowFuncEnter();
746
747 /** @todo For runtime detach maybe:
748 if (pThis->pConsoleVRDPServer)
749 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */
750
751 /*
752 * If the AudioVRDE object is still alive, we must clear it's reference to
753 * us since we'll be invalid when we return from this method.
754 */
755 AudioVRDE *pAudioVRDE = pThis->pAudioVRDE;
756 if (pAudioVRDE)
757 {
758 RTCritSectEnter(&pAudioVRDE->mCritSect);
759 pAudioVRDE->mpDrv = NULL;
760 pThis->pAudioVRDE = NULL;
761 RTCritSectLeave(&pAudioVRDE->mCritSect);
762 }
763}
764
765
766/**
767 * Construct a VRDE audio driver instance.
768 *
769 * @copydoc FNPDMDRVCONSTRUCT
770 */
771/* static */
772DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
773{
774 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
775 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
776 RT_NOREF(fFlags);
777
778 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
779 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
780
781 LogRel(("Audio: Initializing VRDE driver\n"));
782 LogFlowFunc(("fFlags=0x%x\n", fFlags));
783
784 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
785 ("Configuration error: Not possible to attach anything to this driver!\n"),
786 VERR_PDM_DRVINS_NO_ATTACH);
787
788 /*
789 * Init the static parts.
790 */
791 pThis->pDrvIns = pDrvIns;
792 /* IBase */
793 pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface;
794 /* IHostAudio */
795 pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig;
796 pThis->IHostAudio.pfnGetDevices = NULL;
797 pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus;
798 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
799 pThis->IHostAudio.pfnStreamConfigHint = NULL;
800 pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate;
801 pThis->IHostAudio.pfnStreamInitAsync = NULL;
802 pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy;
803 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
804 pThis->IHostAudio.pfnStreamControl = drvAudioVrdeHA_StreamControl;
805 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable;
806 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable;
807 pThis->IHostAudio.pfnStreamGetPending = NULL;
808 pThis->IHostAudio.pfnStreamGetStatus = drvAudioVrdeHA_StreamGetStatus;
809 pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay;
810 pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture;
811
812 /*
813 * Resolve the interface to the driver above us.
814 */
815 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
816 AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
817
818 /*
819 * Get the ConsoleVRDPServer object pointer.
820 */
821 void *pvUser;
822 int rc = CFGMR3QueryPtr(pCfg, "ObjectVRDPServer", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
823 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectVRDPServer\" value, rc=%Rrc\n", rc), rc);
824
825 /* CFGM tree saves the pointer to ConsoleVRDPServer in the Object node of AudioVRDE. */
826 pThis->pConsoleVRDPServer = (ConsoleVRDPServer *)pvUser;
827 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer,
828 ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER);
829 pThis->cClients = 0;
830
831 /*
832 * Get the AudioVRDE object pointer.
833 */
834 pvUser = NULL;
835 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
836 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
837
838 pThis->pAudioVRDE = (AudioVRDE *)pvUser;
839 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER);
840 RTCritSectEnter(&pThis->pAudioVRDE->mCritSect);
841 pThis->pAudioVRDE->mpDrv = pThis;
842 RTCritSectLeave(&pThis->pAudioVRDE->mCritSect);
843
844 return VINF_SUCCESS;
845}
846
847
848/**
849 * VRDE audio driver registration record.
850 */
851const PDMDRVREG AudioVRDE::DrvReg =
852{
853 PDM_DRVREG_VERSION,
854 /* szName */
855 "AudioVRDE",
856 /* szRCMod */
857 "",
858 /* szR0Mod */
859 "",
860 /* pszDescription */
861 "Audio driver for VRDE backend",
862 /* fFlags */
863 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
864 /* fClass. */
865 PDM_DRVREG_CLASS_AUDIO,
866 /* cMaxInstances */
867 ~0U,
868 /* cbInstance */
869 sizeof(DRVAUDIOVRDE),
870 /* pfnConstruct */
871 AudioVRDE::drvConstruct,
872 /* pfnDestruct */
873 AudioVRDE::drvDestruct,
874 /* pfnRelocate */
875 NULL,
876 /* pfnIOCtl */
877 NULL,
878 /* pfnPowerOn */
879 NULL,
880 /* pfnReset */
881 NULL,
882 /* pfnSuspend */
883 NULL,
884 /* pfnResume */
885 NULL,
886 /* pfnAttach */
887 NULL,
888 /* pfnDetach */
889 NULL,
890 /* pfnPowerOff */
891 AudioVRDE::drvPowerOff,
892 /* pfnSoftReset */
893 NULL,
894 /* u32EndVersion */
895 PDM_DRVREG_VERSION
896};
897
Note: See TracBrowser for help on using the repository browser.

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