VirtualBox

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

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

Audio: Changed PDMIHOSTAUDIO::pfnStreamGetStatus into pfnStreamGetState and defined a simpler state enum (PDMHOSTAUDIOSTREAMSTATE) that fits what DrvAudio needs and the backends actually want to tell us. Fixes one VRDE issue. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 30.9 KB
Line 
1/* $Id: DrvAudioVRDE.cpp 88887 2021-05-05 23:38:58Z 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.215389.xyz. 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; /** @todo r=bird: WTF? */
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,pfnStreamGetState}
598 */
599static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVrdeHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
600 PPDMAUDIOBACKENDSTREAM pStream)
601{
602 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
603 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
604
605 return pDrv->cClients > 0 ? PDMHOSTAUDIOSTREAMSTATE_OKAY : PDMHOSTAUDIOSTREAMSTATE_INACTIVE;
606}
607
608
609/**
610 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
611 */
612static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
613 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
614{
615 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
616 AssertPtr(pDrv);
617 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
618 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
619 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
620 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
621 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
622
623 if (!pDrv->pConsoleVRDPServer)
624 return VERR_NOT_AVAILABLE;
625
626 /* Prepate the format. */
627 PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props;
628 VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps),
629 PDMAudioPropsChannels(pProps),
630 PDMAudioPropsSampleBits(pProps),
631 pProps->fSigned);
632 Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true));
633
634 /* We specified PDMAUDIOSTREAMLAYOUT_RAW (== S64), so
635 convert the buffer pointe and size accordingly: */
636 PCPDMAUDIOFRAME paSampleBuf = (PCPDMAUDIOFRAME)pvBuf;
637 uint32_t const cFramesToWrite = cbBuf / sizeof(paSampleBuf[0]);
638 Assert(cFramesToWrite * sizeof(paSampleBuf[0]) == cbBuf);
639
640 /** @todo r=bird: there was some incoherent mumbling about "using the
641 * internal counter to track if we (still) can write to the VRDP
642 * server or if need to wait anothe round (time slot)". However it
643 * wasn't accessing any internal counter nor doing anything else
644 * sensible, so I've removed it. */
645
646 /*
647 * Call the VRDP server with the data.
648 */
649 uint32_t cFramesWritten = 0;
650 while (cFramesWritten < cFramesToWrite)
651 {
652 uint32_t const cFramesChunk = cFramesToWrite - cFramesWritten; /** @todo For now write all at once. */
653
654 /* Note: The VRDP server expects int64_t samples per channel, regardless
655 of the actual sample bits (e.g 8 or 16 bits). */
656 pDrv->pConsoleVRDPServer->SendAudioSamples(&paSampleBuf[cFramesWritten], cFramesChunk /* Frames */, uVrdpFormat);
657
658 cFramesWritten += cFramesChunk;
659 }
660
661 Log3Func(("cFramesWritten=%RU32\n", cFramesWritten));
662 if (pcbWritten)
663 *pcbWritten = cFramesWritten * sizeof(PDMAUDIOFRAME);
664 return VINF_SUCCESS;
665}
666
667
668/**
669 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
670 */
671static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
672 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
673{
674 RT_NOREF(pInterface);
675 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
676 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
677 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
678 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
679 AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
680
681 size_t cbData = 0;
682 if (RTCircBufUsed(pStreamVRDE->In.pCircBuf))
683 {
684 void *pvData = NULL;
685 RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData);
686
687 if (cbData)
688 memcpy(pvBuf, pvData, cbData);
689
690 RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData);
691 }
692
693 *pcbRead = (uint32_t)cbData;
694 return VINF_SUCCESS;
695}
696
697
698/*********************************************************************************************************************************
699* PDMIBASE *
700*********************************************************************************************************************************/
701
702/**
703 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
704 */
705static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
706{
707 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
708 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
709
710 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
711 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
712 return NULL;
713}
714
715
716/*********************************************************************************************************************************
717* PDMDRVREG *
718*********************************************************************************************************************************/
719
720/**
721 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
722 */
723/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns)
724{
725 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
726 LogFlowFuncEnter();
727
728 if (pThis->pConsoleVRDPServer)
729 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL);
730}
731
732
733/**
734 * @interface_method_impl{PDMDRVREG,pfnDestruct}
735 */
736/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns)
737{
738 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
739 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
740 LogFlowFuncEnter();
741
742 /** @todo For runtime detach maybe:
743 if (pThis->pConsoleVRDPServer)
744 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */
745
746 /*
747 * If the AudioVRDE object is still alive, we must clear it's reference to
748 * us since we'll be invalid when we return from this method.
749 */
750 AudioVRDE *pAudioVRDE = pThis->pAudioVRDE;
751 if (pAudioVRDE)
752 {
753 RTCritSectEnter(&pAudioVRDE->mCritSect);
754 pAudioVRDE->mpDrv = NULL;
755 pThis->pAudioVRDE = NULL;
756 RTCritSectLeave(&pAudioVRDE->mCritSect);
757 }
758}
759
760
761/**
762 * Construct a VRDE audio driver instance.
763 *
764 * @copydoc FNPDMDRVCONSTRUCT
765 */
766/* static */
767DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
768{
769 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
770 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
771 RT_NOREF(fFlags);
772
773 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
774 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
775
776 LogRel(("Audio: Initializing VRDE driver\n"));
777 LogFlowFunc(("fFlags=0x%x\n", fFlags));
778
779 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
780 ("Configuration error: Not possible to attach anything to this driver!\n"),
781 VERR_PDM_DRVINS_NO_ATTACH);
782
783 /*
784 * Init the static parts.
785 */
786 pThis->pDrvIns = pDrvIns;
787 /* IBase */
788 pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface;
789 /* IHostAudio */
790 pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig;
791 pThis->IHostAudio.pfnGetDevices = NULL;
792 pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus;
793 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
794 pThis->IHostAudio.pfnStreamConfigHint = NULL;
795 pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate;
796 pThis->IHostAudio.pfnStreamInitAsync = NULL;
797 pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy;
798 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
799 pThis->IHostAudio.pfnStreamControl = drvAudioVrdeHA_StreamControl;
800 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable;
801 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable;
802 pThis->IHostAudio.pfnStreamGetPending = NULL;
803 pThis->IHostAudio.pfnStreamGetState = drvAudioVrdeHA_StreamGetState;
804 pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay;
805 pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture;
806
807 /*
808 * Resolve the interface to the driver above us.
809 */
810 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
811 AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
812
813 /*
814 * Get the ConsoleVRDPServer object pointer.
815 */
816 void *pvUser;
817 int rc = CFGMR3QueryPtr(pCfg, "ObjectVRDPServer", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
818 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectVRDPServer\" value, rc=%Rrc\n", rc), rc);
819
820 /* CFGM tree saves the pointer to ConsoleVRDPServer in the Object node of AudioVRDE. */
821 pThis->pConsoleVRDPServer = (ConsoleVRDPServer *)pvUser;
822 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer,
823 ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER);
824 pThis->cClients = 0;
825
826 /*
827 * Get the AudioVRDE object pointer.
828 */
829 pvUser = NULL;
830 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
831 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
832
833 pThis->pAudioVRDE = (AudioVRDE *)pvUser;
834 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER);
835 RTCritSectEnter(&pThis->pAudioVRDE->mCritSect);
836 pThis->pAudioVRDE->mpDrv = pThis;
837 RTCritSectLeave(&pThis->pAudioVRDE->mCritSect);
838
839 return VINF_SUCCESS;
840}
841
842
843/**
844 * VRDE audio driver registration record.
845 */
846const PDMDRVREG AudioVRDE::DrvReg =
847{
848 PDM_DRVREG_VERSION,
849 /* szName */
850 "AudioVRDE",
851 /* szRCMod */
852 "",
853 /* szR0Mod */
854 "",
855 /* pszDescription */
856 "Audio driver for VRDE backend",
857 /* fFlags */
858 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
859 /* fClass. */
860 PDM_DRVREG_CLASS_AUDIO,
861 /* cMaxInstances */
862 ~0U,
863 /* cbInstance */
864 sizeof(DRVAUDIOVRDE),
865 /* pfnConstruct */
866 AudioVRDE::drvConstruct,
867 /* pfnDestruct */
868 AudioVRDE::drvDestruct,
869 /* pfnRelocate */
870 NULL,
871 /* pfnIOCtl */
872 NULL,
873 /* pfnPowerOn */
874 NULL,
875 /* pfnReset */
876 NULL,
877 /* pfnSuspend */
878 NULL,
879 /* pfnResume */
880 NULL,
881 /* pfnAttach */
882 NULL,
883 /* pfnDetach */
884 NULL,
885 /* pfnPowerOff */
886 AudioVRDE::drvPowerOff,
887 /* pfnSoftReset */
888 NULL,
889 /* u32EndVersion */
890 PDM_DRVREG_VERSION
891};
892
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