VirtualBox

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

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

Audio: Removed PDMAUDIOSTREAMCFG::enmLayout and PDMAUDIOSTREAMLAYOUT. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 30.8 KB
Line 
1/* $Id: DrvAudioVRDE.cpp 89379 2021-05-30 14:33:49Z 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 PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/, 22050 /*Hz*/,
316 true /*fLittleEndian*/, true /*fRaw*/);
317
318 /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */
319 const uint32_t cFramesVrdpServer = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 200 /*ms*/);
320
321 int rc = RTCircBufCreate(&pStreamVRDE->In.pCircBuf, PDMAudioPropsFramesToBytes(&pCfgAcq->Props, cFramesVrdpServer));
322 if (RT_SUCCESS(rc))
323 {
324 pCfgAcq->Backend.cFramesPeriod = cFramesVrdpServer;
325/** @todo r=bird: This is inconsistent with the above buffer allocation and I
326 * think also ALSA and Pulse backends way of setting cFramesBufferSize. */
327 pCfgAcq->Backend.cFramesBufferSize = cFramesVrdpServer * 2; /* Use "double buffering". */
328 pCfgAcq->Backend.cFramesPreBuffering = cFramesVrdpServer;
329 }
330
331 return rc;
332}
333
334
335static int vrdeCreateStreamOut(PPDMAUDIOSTREAMCFG pCfgAcq)
336{
337 /*
338 * The VRDP server does its own mixing and resampling because it may be
339 * sending the audio to any number of different clients all with different
340 * formats (including clients which hasn't yet connected). So, it desires
341 * the raw data from the mixer (somewhat akind to stereo signed 64-bit,
342 * see st_sample_t and PDMAUDIOFRAME).
343 */
344 PDMAudioPropsInitEx(&pCfgAcq->Props, 8 /*64-bit*/, true /*fSigned*/, 2 /*stereo*/, 22050 /*Hz*/,
345 true /*fLittleEndian*/, true /*fRaw*/);
346
347 /* According to the VRDP docs, the VRDP server stores audio in 200ms chunks. */
348 /** @todo r=bird: So, if VRDP does 200ms chunks, why do we report 100ms
349 * buffer and 20ms period? How does these parameters at all correlate
350 * with the above comment?!? */
351 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 20 /*ms*/);
352 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/);
353 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
354
355 return VINF_SUCCESS;
356}
357
358
359/**
360 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
361 */
362static DECLCALLBACK(int) drvAudioVrdeHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
363 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
364{
365 PDRVAUDIOVRDE pThis = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
366 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
367 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
368 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
369 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
370
371 /*
372 * Only create a stream if we have clients.
373 */
374 int rc;
375 NOREF(pThis);
376#if 0 /* later maybe */
377 if (pThis->cClients == 0)
378 {
379 LogFunc(("No clients, failing with VERR_AUDIO_STREAM_COULD_NOT_CREATE.\n"));
380 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
381 }
382 else
383#endif
384 {
385 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
386 rc = vrdeCreateStreamIn(pStreamVRDE, pCfgAcq);
387 else
388 rc = vrdeCreateStreamOut(pCfgAcq);
389 PDMAudioStrmCfgCopy(&pStreamVRDE->Cfg, pCfgAcq);
390 }
391 return rc;
392}
393
394
395/**
396 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
397 */
398static DECLCALLBACK(int) drvAudioVrdeHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
399 bool fImmediate)
400{
401 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
402 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
403 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
404 RT_NOREF(fImmediate);
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
418 return VINF_SUCCESS;
419}
420
421
422/**
423 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
424 */
425static DECLCALLBACK(int) drvAudioVrdeHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
426{
427 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
428 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
429
430 int rc;
431 if (!pDrv->pConsoleVRDPServer)
432 {
433 LogRelMax(32, ("Audio: VRDP console not ready (enable)\n"));
434 rc = VERR_AUDIO_STREAM_NOT_READY;
435 }
436 else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
437 {
438 rc = pDrv->pConsoleVRDPServer->SendAudioInputBegin(NULL, pStreamVRDE,
439 PDMAudioPropsMilliToFrames(&pStreamVRDE->Cfg.Props, 200 /*ms*/),
440 PDMAudioPropsHz(&pStreamVRDE->Cfg.Props),
441 PDMAudioPropsChannels(&pStreamVRDE->Cfg.Props),
442 PDMAudioPropsSampleBits(&pStreamVRDE->Cfg.Props));
443 if (rc == VERR_NOT_SUPPORTED)
444 {
445 LogRelMax(64, ("Audio: No VRDE client connected, so no input recording available\n"));
446 rc = VERR_AUDIO_STREAM_NOT_READY;
447 }
448 }
449 else
450 rc = VINF_SUCCESS;
451 LogFlowFunc(("returns %Rrc\n", rc));
452 return rc;
453}
454
455
456/**
457 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
458 */
459static DECLCALLBACK(int) drvAudioVrdeHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
460{
461 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
462 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
463
464 int rc;
465 if (!pDrv->pConsoleVRDPServer)
466 {
467 LogRelMax(32, ("Audio: VRDP console not ready (disable)\n"));
468 rc = VERR_AUDIO_STREAM_NOT_READY;
469 }
470 else if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
471 {
472 pDrv->pConsoleVRDPServer->SendAudioInputEnd(NULL /* pvUserCtx */);
473 rc = VINF_SUCCESS;
474 }
475 else
476 rc = VINF_SUCCESS;
477 LogFlowFunc(("returns %Rrc\n", rc));
478 return rc;
479}
480
481
482/**
483 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
484 */
485static DECLCALLBACK(int) drvAudioVrdeHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
486{
487 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
488 RT_NOREF(pStream);
489
490 if (!pDrv->pConsoleVRDPServer)
491 {
492 LogRelMax(32, ("Audio: VRDP console not ready (pause)\n"));
493 return VERR_AUDIO_STREAM_NOT_READY;
494 }
495 LogFlowFunc(("returns VINF_SUCCESS\n"));
496 return VINF_SUCCESS;
497}
498
499
500/**
501 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
502 */
503static DECLCALLBACK(int) drvAudioVrdeHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
504{
505 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
506 RT_NOREF(pStream);
507
508 if (!pDrv->pConsoleVRDPServer)
509 {
510 LogRelMax(32, ("Audio: VRDP console not ready (resume)\n"));
511 return VERR_AUDIO_STREAM_NOT_READY;
512 }
513 LogFlowFunc(("returns VINF_SUCCESS\n"));
514 return VINF_SUCCESS;
515}
516
517
518/**
519 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
520 */
521static DECLCALLBACK(int) drvAudioVrdeHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
522{
523 RT_NOREF(pInterface, pStream);
524 LogFlowFunc(("returns VINF_SUCCESS\n"));
525 return VINF_SUCCESS;
526}
527
528
529/**
530 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
531 */
532static DECLCALLBACK(int) drvAudioVrdeHA_StreamControl(PPDMIHOSTAUDIO pInterface,
533 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
534{
535 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
536 * replacing it with individual StreamXxxx methods. That would save us
537 * potentally huge switches and more easily see which drivers implement
538 * which operations (grep for pfnStreamXxxx). */
539 switch (enmStreamCmd)
540 {
541 case PDMAUDIOSTREAMCMD_ENABLE:
542 return drvAudioVrdeHA_StreamEnable(pInterface, pStream);
543 case PDMAUDIOSTREAMCMD_DISABLE:
544 return drvAudioVrdeHA_StreamDisable(pInterface, pStream);
545 case PDMAUDIOSTREAMCMD_PAUSE:
546 return drvAudioVrdeHA_StreamPause(pInterface, pStream);
547 case PDMAUDIOSTREAMCMD_RESUME:
548 return drvAudioVrdeHA_StreamResume(pInterface, pStream);
549 case PDMAUDIOSTREAMCMD_DRAIN:
550 return drvAudioVrdeHA_StreamDrain(pInterface, pStream);
551
552 case PDMAUDIOSTREAMCMD_END:
553 case PDMAUDIOSTREAMCMD_32BIT_HACK:
554 case PDMAUDIOSTREAMCMD_INVALID:
555 /* no default*/
556 break;
557 }
558 return VERR_NOT_SUPPORTED;
559}
560
561
562/**
563 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
564 */
565static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
566{
567 RT_NOREF(pInterface);
568 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
569
570 if (pStreamVRDE->Cfg.enmDir == PDMAUDIODIR_IN)
571 {
572 /* Return frames instead of bytes here
573 * (since we specified PDMAUDIOSTREAMLAYOUT_RAW as the audio data layout). */
574 return PDMAudioPropsBytesToFrames(&pStreamVRDE->Cfg.Props, (uint32_t)RTCircBufUsed(pStreamVRDE->In.pCircBuf));
575 }
576 return 0;
577}
578
579
580/**
581 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
582 */
583static DECLCALLBACK(uint32_t) drvAudioVrdeHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
584{
585 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
586 RT_NOREF(pStream);
587
588 /** @todo Find some sane value here. We probably need a VRDE API VRDE to specify this. */
589 if (pDrv->cClients)
590 return _16K * sizeof(PDMAUDIOFRAME);
591 return 0;
592}
593
594
595/**
596 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
597 */
598static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVrdeHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
599 PPDMAUDIOBACKENDSTREAM pStream)
600{
601 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
602 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
603
604 return pDrv->cClients > 0 ? PDMHOSTAUDIOSTREAMSTATE_OKAY : PDMHOSTAUDIOSTREAMSTATE_INACTIVE;
605}
606
607
608/**
609 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
610 */
611static DECLCALLBACK(int) drvAudioVrdeHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
612 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
613{
614 PDRVAUDIOVRDE pDrv = RT_FROM_MEMBER(pInterface, DRVAUDIOVRDE, IHostAudio);
615 AssertPtr(pDrv);
616 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
617 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
618 if (cbBuf)
619 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
620 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
621
622 if (!pDrv->pConsoleVRDPServer)
623 return VERR_NOT_AVAILABLE;
624
625 /* Prepate the format. */
626 PPDMAUDIOPCMPROPS pProps = &pStreamVRDE->Cfg.Props;
627 VRDEAUDIOFORMAT const uVrdpFormat = VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps),
628 PDMAudioPropsChannels(pProps),
629 PDMAudioPropsSampleBits(pProps),
630 pProps->fSigned);
631 Assert(uVrdpFormat == VRDE_AUDIO_FMT_MAKE(PDMAudioPropsHz(pProps), 2, 64, true));
632
633 /* We specified PDMAUDIOSTREAMLAYOUT_RAW (== S64), so
634 convert the buffer pointe and size accordingly: */
635 PCPDMAUDIOFRAME paSampleBuf = (PCPDMAUDIOFRAME)pvBuf;
636 uint32_t const cFramesToWrite = cbBuf / sizeof(paSampleBuf[0]);
637 Assert(cFramesToWrite * sizeof(paSampleBuf[0]) == cbBuf);
638
639 /** @todo r=bird: there was some incoherent mumbling about "using the
640 * internal counter to track if we (still) can write to the VRDP
641 * server or if need to wait another round (time slot)". However it
642 * wasn't accessing any internal counter nor doing anything else
643 * sensible, so I've removed it. */
644
645 /*
646 * Call the VRDP server with the data.
647 */
648 uint32_t cFramesWritten = 0;
649 while (cFramesWritten < cFramesToWrite)
650 {
651 uint32_t const cFramesChunk = cFramesToWrite - cFramesWritten; /** @todo For now write all at once. */
652
653 /* Note: The VRDP server expects int64_t samples per channel, regardless
654 of the actual sample bits (e.g 8 or 16 bits). */
655 pDrv->pConsoleVRDPServer->SendAudioSamples(&paSampleBuf[cFramesWritten], cFramesChunk /* Frames */, uVrdpFormat);
656
657 cFramesWritten += cFramesChunk;
658 }
659
660 Log3Func(("cFramesWritten=%RU32\n", cFramesWritten));
661 *pcbWritten = cFramesWritten * sizeof(PDMAUDIOFRAME);
662 return VINF_SUCCESS;
663}
664
665
666/**
667 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
668 */
669static DECLCALLBACK(int) drvAudioVrdeHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
670 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
671{
672 RT_NOREF(pInterface);
673 PVRDESTREAM pStreamVRDE = (PVRDESTREAM)pStream;
674 AssertPtrReturn(pStreamVRDE, VERR_INVALID_POINTER);
675 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
676 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
677 AssertPtrReturn(pcbRead, VERR_INVALID_PARAMETER);
678
679 size_t cbData = 0;
680 if (RTCircBufUsed(pStreamVRDE->In.pCircBuf))
681 {
682 void *pvData = NULL;
683 RTCircBufAcquireReadBlock(pStreamVRDE->In.pCircBuf, cbBuf, &pvData, &cbData);
684
685 if (cbData)
686 memcpy(pvBuf, pvData, cbData);
687
688 RTCircBufReleaseReadBlock(pStreamVRDE->In.pCircBuf, cbData);
689 }
690
691 *pcbRead = (uint32_t)cbData;
692 return VINF_SUCCESS;
693}
694
695
696/*********************************************************************************************************************************
697* PDMIBASE *
698*********************************************************************************************************************************/
699
700/**
701 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
702 */
703static DECLCALLBACK(void *) drvAudioVrdeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
704{
705 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
706 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
707
708 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
709 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
710 return NULL;
711}
712
713
714/*********************************************************************************************************************************
715* PDMDRVREG *
716*********************************************************************************************************************************/
717
718/**
719 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
720 */
721/*static*/ DECLCALLBACK(void) AudioVRDE::drvPowerOff(PPDMDRVINS pDrvIns)
722{
723 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
724 LogFlowFuncEnter();
725
726 if (pThis->pConsoleVRDPServer)
727 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL);
728}
729
730
731/**
732 * @interface_method_impl{PDMDRVREG,pfnDestruct}
733 */
734/*static*/ DECLCALLBACK(void) AudioVRDE::drvDestruct(PPDMDRVINS pDrvIns)
735{
736 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
737 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
738 LogFlowFuncEnter();
739
740 /** @todo For runtime detach maybe:
741 if (pThis->pConsoleVRDPServer)
742 pThis->pConsoleVRDPServer->SendAudioInputEnd(NULL); */
743
744 /*
745 * If the AudioVRDE object is still alive, we must clear it's reference to
746 * us since we'll be invalid when we return from this method.
747 */
748 AudioVRDE *pAudioVRDE = pThis->pAudioVRDE;
749 if (pAudioVRDE)
750 {
751 RTCritSectEnter(&pAudioVRDE->mCritSect);
752 pAudioVRDE->mpDrv = NULL;
753 pThis->pAudioVRDE = NULL;
754 RTCritSectLeave(&pAudioVRDE->mCritSect);
755 }
756}
757
758
759/**
760 * Construct a VRDE audio driver instance.
761 *
762 * @copydoc FNPDMDRVCONSTRUCT
763 */
764/* static */
765DECLCALLBACK(int) AudioVRDE::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
766{
767 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
768 PDRVAUDIOVRDE pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVRDE);
769 RT_NOREF(fFlags);
770
771 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
772 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
773
774 LogRel(("Audio: Initializing VRDE driver\n"));
775 LogFlowFunc(("fFlags=0x%x\n", fFlags));
776
777 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
778 ("Configuration error: Not possible to attach anything to this driver!\n"),
779 VERR_PDM_DRVINS_NO_ATTACH);
780
781 /*
782 * Init the static parts.
783 */
784 pThis->pDrvIns = pDrvIns;
785 /* IBase */
786 pDrvIns->IBase.pfnQueryInterface = drvAudioVrdeQueryInterface;
787 /* IHostAudio */
788 pThis->IHostAudio.pfnGetConfig = drvAudioVrdeHA_GetConfig;
789 pThis->IHostAudio.pfnGetDevices = NULL;
790 pThis->IHostAudio.pfnSetDevice = NULL;
791 pThis->IHostAudio.pfnGetStatus = drvAudioVrdeHA_GetStatus;
792 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
793 pThis->IHostAudio.pfnStreamConfigHint = NULL;
794 pThis->IHostAudio.pfnStreamCreate = drvAudioVrdeHA_StreamCreate;
795 pThis->IHostAudio.pfnStreamInitAsync = NULL;
796 pThis->IHostAudio.pfnStreamDestroy = drvAudioVrdeHA_StreamDestroy;
797 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
798 pThis->IHostAudio.pfnStreamControl = drvAudioVrdeHA_StreamControl;
799 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVrdeHA_StreamGetReadable;
800 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVrdeHA_StreamGetWritable;
801 pThis->IHostAudio.pfnStreamGetPending = NULL;
802 pThis->IHostAudio.pfnStreamGetState = drvAudioVrdeHA_StreamGetState;
803 pThis->IHostAudio.pfnStreamPlay = drvAudioVrdeHA_StreamPlay;
804 pThis->IHostAudio.pfnStreamCapture = drvAudioVrdeHA_StreamCapture;
805
806 /*
807 * Resolve the interface to the driver above us.
808 */
809 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
810 AssertPtrReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
811
812 /*
813 * Get the ConsoleVRDPServer object pointer.
814 */
815 void *pvUser;
816 int rc = CFGMR3QueryPtr(pCfg, "ObjectVRDPServer", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
817 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectVRDPServer\" value, rc=%Rrc\n", rc), rc);
818
819 /* CFGM tree saves the pointer to ConsoleVRDPServer in the Object node of AudioVRDE. */
820 pThis->pConsoleVRDPServer = (ConsoleVRDPServer *)pvUser;
821 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pConsoleVRDPServer) || !pThis->pConsoleVRDPServer,
822 ("pConsoleVRDPServer=%p\n", pThis->pConsoleVRDPServer), VERR_INVALID_POINTER);
823 pThis->cClients = 0;
824
825 /*
826 * Get the AudioVRDE object pointer.
827 */
828 pvUser = NULL;
829 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
830 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
831
832 pThis->pAudioVRDE = (AudioVRDE *)pvUser;
833 AssertLogRelMsgReturn(RT_VALID_PTR(pThis->pAudioVRDE), ("pAudioVRDE=%p\n", pThis->pAudioVRDE), VERR_INVALID_POINTER);
834 RTCritSectEnter(&pThis->pAudioVRDE->mCritSect);
835 pThis->pAudioVRDE->mpDrv = pThis;
836 RTCritSectLeave(&pThis->pAudioVRDE->mCritSect);
837
838 return VINF_SUCCESS;
839}
840
841
842/**
843 * VRDE audio driver registration record.
844 */
845const PDMDRVREG AudioVRDE::DrvReg =
846{
847 PDM_DRVREG_VERSION,
848 /* szName */
849 "AudioVRDE",
850 /* szRCMod */
851 "",
852 /* szR0Mod */
853 "",
854 /* pszDescription */
855 "Audio driver for VRDE backend",
856 /* fFlags */
857 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
858 /* fClass. */
859 PDM_DRVREG_CLASS_AUDIO,
860 /* cMaxInstances */
861 ~0U,
862 /* cbInstance */
863 sizeof(DRVAUDIOVRDE),
864 /* pfnConstruct */
865 AudioVRDE::drvConstruct,
866 /* pfnDestruct */
867 AudioVRDE::drvDestruct,
868 /* pfnRelocate */
869 NULL,
870 /* pfnIOCtl */
871 NULL,
872 /* pfnPowerOn */
873 NULL,
874 /* pfnReset */
875 NULL,
876 /* pfnSuspend */
877 NULL,
878 /* pfnResume */
879 NULL,
880 /* pfnAttach */
881 NULL,
882 /* pfnDetach */
883 NULL,
884 /* pfnPowerOff */
885 AudioVRDE::drvPowerOff,
886 /* pfnSoftReset */
887 NULL,
888 /* u32EndVersion */
889 PDM_DRVREG_VERSION
890};
891
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