VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvAudio.cpp@ 68897

Last change on this file since 68897 was 68897, checked in by vboxsync, 8 years ago

D'oh!

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 118.8 KB
Line 
1/* $Id: DrvAudio.cpp 68897 2017-09-27 16:49:21Z vboxsync $ */
2/** @file
3 * Intermediate audio driver header.
4 *
5 * @remarks Intermediate audio driver for connecting the audio device emulation
6 * with the host backend.
7 */
8
9/*
10 * Copyright (C) 2006-2017 Oracle Corporation
11 *
12 * This file is part of VirtualBox Open Source Edition (OSE), as
13 * available from http://www.virtualbox.org. This file is free software;
14 * you can redistribute it and/or modify it under the terms of the GNU
15 * General Public License (GPL) as published by the Free Software
16 * Foundation, in version 2 as it comes in the "COPYING" file of the
17 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19 */
20#define LOG_GROUP LOG_GROUP_DRV_AUDIO
21#include <VBox/log.h>
22#include <VBox/vmm/pdm.h>
23#include <VBox/err.h>
24#include <VBox/vmm/mm.h>
25#include <VBox/vmm/pdmaudioifs.h>
26
27#include <iprt/alloc.h>
28#include <iprt/asm-math.h>
29#include <iprt/assert.h>
30#include <iprt/circbuf.h>
31#include <iprt/string.h>
32#include <iprt/uuid.h>
33
34#include "VBoxDD.h"
35
36#include <ctype.h>
37#include <stdlib.h>
38
39#include "DrvAudio.h"
40#include "AudioMixBuffer.h"
41
42#ifdef VBOX_WITH_AUDIO_ENUM
43static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum);
44#endif
45
46static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream);
47static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
48static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd);
49static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pHstStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq);
50static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pHstStream);
51static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream);
52static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
53static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest);
54static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
55static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream);
56static int drvAudioStreamLinkToInternal(PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAM pPair);
57
58#ifndef VBOX_AUDIO_TESTCASE
59
60# if 0 /* unused */
61
62static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey,
63 PDMAUDIOFMT enmDefault, bool *pfDefault)
64{
65 if ( pCfgHandle == NULL
66 || pszKey == NULL)
67 {
68 *pfDefault = true;
69 return enmDefault;
70 }
71
72 char *pszValue = NULL;
73 int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
74 if (RT_FAILURE(rc))
75 {
76 *pfDefault = true;
77 return enmDefault;
78 }
79
80 PDMAUDIOFMT fmt = DrvAudioHlpStrToAudFmt(pszValue);
81 if (fmt == PDMAUDIOFMT_INVALID)
82 {
83 *pfDefault = true;
84 return enmDefault;
85 }
86
87 *pfDefault = false;
88 return fmt;
89}
90
91static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey,
92 int iDefault, bool *pfDefault)
93{
94
95 if ( pCfgHandle == NULL
96 || pszKey == NULL)
97 {
98 *pfDefault = true;
99 return iDefault;
100 }
101
102 uint64_t u64Data = 0;
103 int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data);
104 if (RT_FAILURE(rc))
105 {
106 *pfDefault = true;
107 return iDefault;
108
109 }
110
111 *pfDefault = false;
112 return u64Data;
113}
114
115static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey,
116 const char *pszDefault, bool *pfDefault)
117{
118 if ( pCfgHandle == NULL
119 || pszKey == NULL)
120 {
121 *pfDefault = true;
122 return pszDefault;
123 }
124
125 char *pszValue = NULL;
126 int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
127 if (RT_FAILURE(rc))
128 {
129 *pfDefault = true;
130 return pszDefault;
131 }
132
133 *pfDefault = false;
134 return pszValue;
135}
136
137# endif /* unused */
138
139#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
140static char *drvAudioDbgGetFileNameA(PDRVAUDIO pThis, const char *pszPath, const char *pszSuffix);
141static void drvAudioDbgPCMDelete(PDRVAUDIO pThis, const char *pszPath, const char *pszSuffix);
142static void drvAudioDbgPCMDump(PDRVAUDIO pThis, const char *pszPath, const char *pszSuffix, const void *pvData, size_t cbData);
143#endif
144
145#ifdef LOG_ENABLED
146/**
147 * Converts an audio stream status to a string.
148 *
149 * @returns Stringified stream status flags. Must be free'd with RTStrFree().
150 * "NONE" if no flags set.
151 * @param fFlags Stream status flags to convert.
152 */
153static char *dbgAudioStreamStatusToStr(PDMAUDIOSTREAMSTS fStatus)
154{
155#define APPEND_FLAG_TO_STR(_aFlag) \
156 if (fStatus & PDMAUDIOSTREAMSTS_FLAG_##_aFlag) \
157 { \
158 if (pszFlags) \
159 { \
160 rc2 = RTStrAAppend(&pszFlags, " "); \
161 if (RT_FAILURE(rc2)) \
162 break; \
163 } \
164 \
165 rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
166 if (RT_FAILURE(rc2)) \
167 break; \
168 } \
169
170 char *pszFlags = NULL;
171 int rc2 = VINF_SUCCESS;
172
173 do
174 {
175 APPEND_FLAG_TO_STR(INITIALIZED );
176 APPEND_FLAG_TO_STR(ENABLED );
177 APPEND_FLAG_TO_STR(PAUSED );
178 APPEND_FLAG_TO_STR(PENDING_DISABLE);
179 APPEND_FLAG_TO_STR(PENDING_REINIT );
180 } while (0);
181
182 if (!pszFlags)
183 rc2 = RTStrAAppend(&pszFlags, "NONE");
184
185 if ( RT_FAILURE(rc2)
186 && pszFlags)
187 {
188 RTStrFree(pszFlags);
189 pszFlags = NULL;
190 }
191
192#undef APPEND_FLAG_TO_STR
193
194 return pszFlags;
195}
196#endif /* LOG_ENABLED */
197
198/**
199 * Returns the host stream part of an audio stream pair, or NULL
200 * if no host stream has been assigned / is not available.
201 *
202 * @returns IPRT status code.
203 * @param pStream Audio stream to retrieve host stream part for.
204 */
205DECLINLINE(PPDMAUDIOSTREAM) drvAudioGetHostStream(PPDMAUDIOSTREAM pStream)
206{
207 AssertPtrReturn(pStream, NULL);
208
209 if (!pStream)
210 return NULL;
211
212 PPDMAUDIOSTREAM pHstStream = pStream->enmCtx == PDMAUDIOSTREAMCTX_HOST
213 ? pStream
214 : pStream->pPair;
215 if (pHstStream)
216 {
217 AssertReleaseMsg(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST,
218 ("Stream '%s' resolved as host part is not marked as such (enmCtx=%RU32)\n",
219 pHstStream->szName, pHstStream->enmCtx));
220
221 AssertReleaseMsg(pHstStream->pPair != NULL,
222 ("Stream '%s' resolved as host part has no guest part (anymore)\n", pHstStream->szName));
223 }
224 else
225 LogRel(("Audio: Warning: Stream '%s' does not have a host stream (anymore)\n", pStream->szName));
226
227 return pHstStream;
228}
229
230# if 0 /* unused */
231static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, audio_option *paOpts, size_t cOpts)
232{
233 AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
234 AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER);
235 /* oaOpts and cOpts are optional. */
236
237 PCFGMNODE pCfgChildHandle = NULL;
238 PCFGMNODE pCfgChildChildHandle = NULL;
239
240 /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
241 * The getter function will return default values.
242 */
243 if (pCfgHandle != NULL)
244 {
245 /* If its audio general setting, need to traverse to one child node.
246 * /Devices/ichac97/0/LUN#0/Config/Audio
247 */
248 if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a \#define */
249 {
250 pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
251 if(pCfgChildHandle)
252 pCfgHandle = pCfgChildHandle;
253 }
254 else
255 {
256 /* If its driver specific configuration , then need to traverse two level deep child
257 * child nodes. for eg. in case of DirectSoundConfiguration item
258 * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
259 */
260 pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
261 if (pCfgChildHandle)
262 {
263 pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle);
264 if (pCfgChildChildHandle)
265 pCfgHandle = pCfgChildChildHandle;
266 }
267 }
268 }
269
270 for (size_t i = 0; i < cOpts; i++)
271 {
272 audio_option *pOpt = &paOpts[i];
273 if (!pOpt->valp)
274 {
275 LogFlowFunc(("Option value pointer for `%s' is not set\n", pOpt->name));
276 continue;
277 }
278
279 bool fUseDefault;
280
281 switch (pOpt->tag)
282 {
283 case AUD_OPT_BOOL:
284 case AUD_OPT_INT:
285 {
286 int *intp = (int *)pOpt->valp;
287 *intp = drvAudioGetConfInt(pCfgHandle, pOpt->name, *intp, &fUseDefault);
288
289 break;
290 }
291
292 case AUD_OPT_FMT:
293 {
294 PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)pOpt->valp;
295 *fmtp = drvAudioGetConfFormat(pCfgHandle, pOpt->name, *fmtp, &fUseDefault);
296
297 break;
298 }
299
300 case AUD_OPT_STR:
301 {
302 const char **strp = (const char **)pOpt->valp;
303 *strp = drvAudioGetConfStr(pCfgHandle, pOpt->name, *strp, &fUseDefault);
304
305 break;
306 }
307
308 default:
309 LogFlowFunc(("Bad value tag for option `%s' - %d\n", pOpt->name, pOpt->tag));
310 fUseDefault = false;
311 break;
312 }
313
314 if (!pOpt->overridenp)
315 pOpt->overridenp = &pOpt->overriden;
316
317 *pOpt->overridenp = !fUseDefault;
318 }
319
320 return VINF_SUCCESS;
321}
322# endif /* unused */
323#endif /* !VBOX_AUDIO_TESTCASE */
324
325/**
326 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
327 */
328static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
329 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
330{
331 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
332
333 if (!pStream)
334 return VINF_SUCCESS;
335
336 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
337
338 int rc = RTCritSectEnter(&pThis->CritSect);
339 if (RT_FAILURE(rc))
340 return rc;
341
342 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
343
344 rc = drvAudioStreamControlInternal(pThis, pStream, enmStreamCmd);
345
346 int rc2 = RTCritSectLeave(&pThis->CritSect);
347 if (RT_SUCCESS(rc))
348 rc = rc2;
349
350 return rc;
351}
352
353/**
354 * Controls an audio stream.
355 *
356 * @returns IPRT status code.
357 * @param pThis Pointer to driver instance.
358 * @param pStream Stream to control.
359 * @param enmStreamCmd Control command.
360 */
361static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
362{
363 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
364 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
365
366 LogFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd)));
367
368 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
369 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : pStream;
370 AssertPtr(pGstStream);
371
372#ifdef LOG_ENABLED
373 char *pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
374 char *pszGstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
375 LogFlowFunc(("Status host=%s, guest=%s\n", pszHstSts, pszGstSts));
376 RTStrFree(pszGstSts);
377 RTStrFree(pszHstSts);
378#endif /* LOG_ENABLED */
379
380 int rc = VINF_SUCCESS;
381
382 switch (enmStreamCmd)
383 {
384 case PDMAUDIOSTREAMCMD_ENABLE:
385 {
386 if (!(pGstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
387 {
388 if (pHstStream)
389 {
390 /* Is a pending disable outstanding? Then disable first. */
391 if (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE)
392 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
393
394 if (RT_SUCCESS(rc))
395 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_ENABLE);
396 }
397
398 if (RT_SUCCESS(rc))
399 pGstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
400 }
401 break;
402 }
403
404 case PDMAUDIOSTREAMCMD_DISABLE:
405 {
406 if (pGstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
407 {
408 if (pHstStream)
409 {
410 /*
411 * For playback (output) streams first mark the host stream as pending disable,
412 * so that the rest of the remaining audio data will be played first before
413 * closing the stream.
414 */
415 if (pHstStream->enmDir == PDMAUDIODIR_OUT)
416 {
417 LogFunc(("[%s] Pending disable/pause\n", pHstStream->szName));
418 pHstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE;
419 }
420
421 /* Can we close the host stream as well (not in pending disable mode)? */
422 if (!(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE))
423 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
424 }
425
426 if (RT_SUCCESS(rc))
427 pGstStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_ENABLED;
428 }
429 break;
430 }
431
432 case PDMAUDIOSTREAMCMD_PAUSE:
433 {
434 if (!(pGstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
435 {
436 if (pHstStream)
437 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_PAUSE);
438
439 if (RT_SUCCESS(rc))
440 pGstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED;
441 }
442 break;
443 }
444
445 case PDMAUDIOSTREAMCMD_RESUME:
446 {
447 if (pGstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
448 {
449 if (pHstStream)
450 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_RESUME);
451
452 if (RT_SUCCESS(rc))
453 pGstStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED;
454 }
455 break;
456 }
457
458 default:
459 AssertMsgFailed(("Command %s (%RU32) not implemented\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), enmStreamCmd));
460 rc = VERR_NOT_IMPLEMENTED;
461 break;
462 }
463
464 if (RT_FAILURE(rc))
465 LogFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
466
467 return rc;
468}
469
470/**
471 * Controls a stream's backend.
472 * If the stream has no backend available, VERR_NOT_FOUND is returned.
473 *
474 * @returns IPRT status code.
475 * @param pThis Pointer to driver instance.
476 * @param pStream Stream to control.
477 * @param enmStreamCmd Control command.
478 */
479static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
480{
481 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
482 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
483
484 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
485 if (!pHstStream) /* Stream does not have a host backend? Bail out. */
486 return VERR_NOT_FOUND;
487
488#ifdef LOG_ENABLED
489 char *pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
490 LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pHstStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), pszHstSts));
491 RTStrFree(pszHstSts);
492#endif /* LOG_ENABLED */
493
494 LogRel2(("Audio: %s stream '%s'\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pHstStream->szName));
495
496 AssertPtr(pThis->pHostDrvAudio);
497
498 int rc = VINF_SUCCESS;
499
500 switch (enmStreamCmd)
501 {
502 case PDMAUDIOSTREAMCMD_ENABLE:
503 {
504 if (!(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
505 {
506 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pHstStream->pvBackend,
507 PDMAUDIOSTREAMCMD_ENABLE);
508 if (RT_SUCCESS(rc))
509 {
510 pHstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_ENABLED;
511 }
512 }
513 break;
514 }
515
516 case PDMAUDIOSTREAMCMD_DISABLE:
517 {
518 if (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
519 {
520 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pHstStream->pvBackend,
521 PDMAUDIOSTREAMCMD_DISABLE);
522 if (RT_SUCCESS(rc))
523 {
524 pHstStream->fStatus &= ~(PDMAUDIOSTREAMSTS_FLAG_ENABLED | PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE);
525 AudioMixBufReset(&pHstStream->MixBuf);
526 }
527 }
528 break;
529 }
530
531 case PDMAUDIOSTREAMCMD_PAUSE:
532 {
533 /* Only pause if the stream is enabled. */
534 if (!(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
535 break;
536
537 if (!(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
538 {
539 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pHstStream->pvBackend,
540 PDMAUDIOSTREAMCMD_PAUSE);
541 if (RT_SUCCESS(rc))
542 {
543 pHstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PAUSED;
544 }
545 }
546 break;
547 }
548
549 case PDMAUDIOSTREAMCMD_RESUME:
550 {
551 /* Only need to resume if the stream is enabled. */
552 if (!(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
553 break;
554
555 if (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED)
556 {
557 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pHstStream->pvBackend,
558 PDMAUDIOSTREAMCMD_RESUME);
559 if (RT_SUCCESS(rc))
560 {
561 pHstStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PAUSED;
562 }
563 }
564 break;
565 }
566
567 default:
568 {
569 AssertMsgFailed(("Command %RU32 not implemented\n", enmStreamCmd));
570 rc = VERR_NOT_IMPLEMENTED;
571 break;
572 }
573 }
574
575 if (RT_FAILURE(rc))
576 {
577 LogRel2(("Audio: %s stream '%s' failed with %Rrc\n", DrvAudioHlpStreamCmdToStr(enmStreamCmd), pHstStream->szName, rc));
578 LogFunc(("[%s] %s failed with %Rrc\n", pStream->szName, DrvAudioHlpStreamCmdToStr(enmStreamCmd), rc));
579 }
580
581 return rc;
582}
583
584/**
585 * Initializes an audio stream with a given host and guest stream configuration.
586 *
587 * @returns IPRT status code.
588 * @param pThis Pointer to driver instance.
589 * @param pStream Stream to initialize.
590 * @param pCfgHost Stream configuration to use for the host side (backend).
591 * @param pCfgGuest Stream configuration to use for the guest side.
592 */
593static int drvAudioStreamInitInternal(PDRVAUDIO pThis,
594 PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
595{
596 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
597 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
598 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
599 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
600
601 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
602 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : pStream;
603 AssertPtr(pGstStream);
604
605 /*
606 * Init host stream.
607 */
608
609 /* Set the host's default audio data layout. */
610 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
611
612#ifdef DEBUG
613 LogFunc(("[%s] Requested host format:\n", pStream->szName));
614 DrvAudioHlpStreamCfgPrint(pCfgHost);
615#else
616 LogRel2(("Audio: Creating stream '%s'\n", pStream->szName));
617 LogRel2(("Audio: Requested %s host format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
618 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
619 pCfgHost->Props.uHz, pCfgHost->Props.cBits, pCfgHost->Props.fSigned ? "S" : "U",
620 pCfgHost->Props.cChannels, pCfgHost->Props.cChannels == 0 ? "Channel" : "Channels"));
621#endif
622
623 PDMAUDIOSTREAMCFG CfgHostAcq;
624 int rc = drvAudioStreamCreateInternalBackend(pThis, pHstStream, pCfgHost, &CfgHostAcq);
625 if (RT_FAILURE(rc))
626 return rc;
627
628#ifdef DEBUG
629 LogFunc(("[%s] Acquired host format:\n", pStream->szName));
630 DrvAudioHlpStreamCfgPrint(&CfgHostAcq);
631#else
632 LogRel2(("Audio: Acquired %s host format for '%s': %RU32Hz, %RU8%s, %RU8 %s\n",
633 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStream->szName,
634 CfgHostAcq.Props.uHz, CfgHostAcq.Props.cBits, CfgHostAcq.Props.fSigned ? "S" : "U",
635 CfgHostAcq.Props.cChannels, CfgHostAcq.Props.cChannels == 0 ? "Channel" : "Channels"));
636#endif
637
638 /* No frame buffer size hint given by the backend? Default to some sane value. */
639 if (!CfgHostAcq.cFrameBufferHint)
640 {
641 CfgHostAcq.cFrameBufferHint = _1K; /** @todo Make this configurable? */
642 }
643
644 /* Destroy any former mixing buffer. */
645 AudioMixBufDestroy(&pHstStream->MixBuf);
646
647 /* Make sure to (re-)set the host buffer's shift size. */
648 CfgHostAcq.Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(CfgHostAcq.Props.cBits, CfgHostAcq.Props.cChannels);
649
650 /* Set set host buffer size multiplicator. */
651 const unsigned cFrameBufferHostFactor = 2; /** @todo Make this configurable. */
652
653 LogFunc(("[%s] cFrames=%RU32 (x %u)\n", pHstStream->szName, CfgHostAcq.cFrameBufferHint, cFrameBufferHostFactor));
654
655 int rc2 = AudioMixBufInit(&pHstStream->MixBuf, pHstStream->szName, &CfgHostAcq.Props,
656 CfgHostAcq.cFrameBufferHint * cFrameBufferHostFactor);
657 AssertRC(rc2);
658
659 /* Make a copy of the acquired host stream configuration. */
660 rc2 = DrvAudioHlpStreamCfgCopy(&pHstStream->Cfg, &CfgHostAcq);
661 AssertRC(rc2);
662
663 /*
664 * Init guest stream.
665 */
666
667 /* Destroy any former mixing buffer. */
668 AudioMixBufDestroy(&pGstStream->MixBuf);
669
670 /* Set the guests's default audio data layout. */
671 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
672
673 /* Make sure to (re-)set the guest buffer's shift size. */
674 pCfgGuest->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgGuest->Props.cBits, pCfgGuest->Props.cChannels);
675
676 /* Set set guest buffer size multiplicator. */
677 const unsigned cFrameBufferGuestFactor = 10; /** @todo Make this configurable. */
678
679 LogFunc(("[%s] cFrames=%RU32 (x %u)\n", pGstStream->szName, CfgHostAcq.cFrameBufferHint, cFrameBufferGuestFactor));
680
681 rc2 = AudioMixBufInit(&pGstStream->MixBuf, pGstStream->szName, &pCfgGuest->Props,
682 CfgHostAcq.cFrameBufferHint * cFrameBufferGuestFactor);
683 AssertRC(rc2);
684
685 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
686 {
687 /* Host (Parent) -> Guest (Child). */
688 rc2 = AudioMixBufLinkTo(&pHstStream->MixBuf, &pGstStream->MixBuf);
689 AssertRC(rc2);
690 }
691 else
692 {
693 /* Guest (Parent) -> Host (Child). */
694 rc2 = AudioMixBufLinkTo(&pGstStream->MixBuf, &pHstStream->MixBuf);
695 AssertRC(rc2);
696 }
697
698 /* Make a copy of the guest stream configuration. */
699 rc2 = DrvAudioHlpStreamCfgCopy(&pGstStream->Cfg, pCfgGuest);
700 AssertRC(rc2);
701
702 if (RT_FAILURE(rc))
703 LogRel2(("Audio: Creating stream '%s' failed with %Rrc\n", pStream->szName, rc));
704
705 LogFlowFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
706 return rc;
707}
708
709/**
710 * Frees an audio stream and its allocated resources.
711 *
712 * @param pStream Audio stream to free.
713 * After this call the pointer will not be valid anymore.
714 */
715static void drvAudioStreamFree(PPDMAUDIOSTREAM pStream)
716{
717 if (!pStream)
718 return;
719
720 if (pStream->pvBackend)
721 {
722 Assert(pStream->cbBackend);
723 RTMemFree(pStream->pvBackend);
724 pStream->pvBackend = NULL;
725 }
726
727 RTMemFree(pStream);
728 pStream = NULL;
729}
730
731#ifdef VBOX_WITH_AUDIO_CALLBACKS
732/**
733 * Schedules a re-initialization of all current audio streams.
734 * The actual re-initialization will happen at some later point in time.
735 *
736 * @returns IPRT status code.
737 * @param pThis Pointer to driver instance.
738 */
739static int drvAudioScheduleReInitInternal(PDRVAUDIO pThis)
740{
741 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
742
743 LogFunc(("\n"));
744
745 /* Mark all host streams to re-initialize. */
746 PPDMAUDIOSTREAM pHstStream;
747 RTListForEach(&pThis->lstHstStreams, pHstStream, PDMAUDIOSTREAM, Node)
748 pHstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
749
750# ifdef VBOX_WITH_AUDIO_ENUM
751 /* Re-enumerate all host devices as soon as possible. */
752 pThis->fEnumerateDevices = true;
753# endif
754
755 return VINF_SUCCESS;
756}
757#endif /* VBOX_WITH_AUDIO_CALLBACKS */
758
759/**
760 * Re-initializes an audio stream with its existing host and guest stream configuration.
761 * This might be the case if the backend told us we need to re-initialize because something
762 * on the host side has changed.
763 *
764 * @returns IPRT status code.
765 * @param pThis Pointer to driver instance.
766 * @param pStream Stream to re-initialize.
767 */
768static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
769{
770 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
771 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
772
773 LogFlowFunc(("[%s]\n", pStream->szName));
774
775 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
776 AssertPtr(pHstStream);
777
778 /*
779 * Gather current stream status.
780 */
781 bool fIsEnabled = RT_BOOL(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED); /* Stream is enabled? */
782
783 /*
784 * Destroy and re-create stream on backend side.
785 */
786 int rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
787 if (RT_SUCCESS(rc))
788 {
789 rc = drvAudioStreamDestroyInternalBackend(pThis, pHstStream);
790 if (RT_SUCCESS(rc))
791 {
792 rc = drvAudioStreamCreateInternalBackend(pThis, pHstStream, &pHstStream->Cfg, NULL /* pCfgAcq */);
793 /** @todo Validate (re-)acquired configuration with pHstStream->Cfg? */
794 }
795 }
796
797 /*
798 * Restore previous stream state.
799 */
800 if (RT_SUCCESS(rc))
801 {
802 PPDMAUDIOSTREAM pGstStream = pHstStream->pPair;
803
804 if (fIsEnabled)
805 {
806 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_ENABLE);
807 if (RT_SUCCESS(rc))
808 {
809 if (pGstStream)
810 {
811 /* Also reset the guest stream mixing buffer. */
812 AudioMixBufReset(&pGstStream->MixBuf);
813 }
814 }
815 }
816
817#ifdef VBOX_WITH_STATISTICS
818 /*
819 * Reset statistics.
820 */
821 if (RT_SUCCESS(rc))
822 {
823 if (pHstStream->enmDir == PDMAUDIODIR_IN)
824 {
825 STAM_COUNTER_RESET(&pHstStream->In.StatBytesElapsed);
826 STAM_COUNTER_RESET(&pHstStream->In.StatBytesTotalRead);
827 STAM_COUNTER_RESET(&pHstStream->In.StatFramesCaptured);
828
829 if (pGstStream)
830 {
831 Assert(pGstStream->enmDir == pHstStream->enmDir);
832
833 STAM_COUNTER_RESET(&pGstStream->In.StatBytesElapsed);
834 STAM_COUNTER_RESET(&pGstStream->In.StatBytesTotalRead);
835 STAM_COUNTER_RESET(&pGstStream->In.StatFramesCaptured);
836 }
837 }
838 else if (pHstStream->enmDir == PDMAUDIODIR_OUT)
839 {
840 STAM_COUNTER_RESET(&pHstStream->Out.StatBytesElapsed);
841 STAM_COUNTER_RESET(&pHstStream->Out.StatBytesTotalWritten);
842 STAM_COUNTER_RESET(&pHstStream->Out.StatFramesPlayed);
843
844 if (pGstStream)
845 {
846 Assert(pGstStream->enmDir == pHstStream->enmDir);
847
848 STAM_COUNTER_RESET(&pGstStream->Out.StatBytesElapsed);
849 STAM_COUNTER_RESET(&pGstStream->Out.StatBytesTotalWritten);
850 STAM_COUNTER_RESET(&pGstStream->Out.StatFramesPlayed);
851 }
852 }
853 else
854 AssertFailed();
855 }
856#endif
857 }
858
859 if (RT_FAILURE(rc))
860 LogRel2(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStream->szName, rc));
861
862 LogFunc(("[%s] Returning %Rrc\n", pStream->szName, rc));
863 return rc;
864}
865
866/**
867 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite}
868 */
869static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
870 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
871{
872 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
873 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
874 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
875 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
876 /* pcbWritten is optional. */
877
878 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
879
880 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
881 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is 0x%x)\n",
882 pStream->szName, pStream->enmDir));
883
884 uint32_t cbWrittenTotal = 0;
885
886 int rc = RTCritSectEnter(&pThis->CritSect);
887 if (RT_FAILURE(rc))
888 return rc;
889
890#ifdef VBOX_WITH_STATISTICS
891 STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out);
892#endif
893
894#ifdef LOG_ENABLED
895 char *pszGstSts = NULL;
896 char *pszHstSts = NULL;
897#endif
898
899 do
900 {
901 if (!pThis->Out.fEnabled)
902 {
903 cbWrittenTotal = cbBuf;
904 break;
905 }
906
907 if ( pThis->pHostDrvAudio
908 && pThis->pHostDrvAudio->pfnGetStatus
909 && pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, PDMAUDIODIR_OUT) != PDMAUDIOBACKENDSTS_RUNNING)
910 {
911 rc = VERR_NOT_AVAILABLE;
912 break;
913 }
914
915 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
916 if (!pHstStream)
917 {
918 rc = VERR_NOT_AVAILABLE;
919 break;
920 }
921
922#ifdef LOG_ENABLED
923 pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
924 AssertPtr(pszHstSts);
925#endif
926 PPDMAUDIOSTREAM pGstStream = pHstStream->pPair;
927 AssertPtr(pGstStream);
928
929#ifdef LOG_ENABLED
930 pszGstSts = dbgAudioStreamStatusToStr(pGstStream->fStatus);
931 AssertPtr(pszGstSts);
932#endif
933
934 if (!(pGstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED))
935 {
936 Log3Func(("[%s] Writing to disabled guest output stream not possible (status is %s, host status %s)\n",
937 pGstStream->szName, pszGstSts, pszHstSts));
938#ifdef DEBUG_andy
939 AssertFailed();
940#endif
941 rc = VERR_NOT_AVAILABLE;
942 break;
943 }
944
945 const uint32_t cbFree = AudioMixBufFreeBytes(&pGstStream->MixBuf);
946 if (!cbFree) /* No free guest side buffer space, bail out. */
947 {
948 AssertMsgFailed(("[%s] Stream full\n", pGstStream->szName));
949 break;
950 }
951
952 /* Do not attempt to write more than the guest side currently can handle. */
953 if (cbBuf > cbFree)
954 cbBuf = cbFree;
955
956 /* We use the guest side mixing buffer as an intermediate buffer to do some
957 * (first) processing (if needed), so always write the incoming data at offset 0. */
958 uint32_t cfWritten = 0;
959 rc = AudioMixBufWriteAt(&pGstStream->MixBuf, 0 /* offFrames */, pvBuf, cbBuf, &cfWritten);
960 if ( RT_FAILURE(rc)
961 || !cfWritten)
962 {
963 AssertMsgFailed(("[%s] Write failed: cbBuf=%RU32, cfWritten=%RU32, rc=%Rrc\n",
964 pGstStream->szName, cbBuf, cfWritten, rc));
965 break;
966 }
967
968#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
969 drvAudioDbgPCMDump(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "StreamWrite.pcm", pvBuf, cbBuf);
970#endif
971
972#ifdef VBOX_WITH_STATISTICS
973 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesWritten, cfWritten);
974#endif
975 uint32_t cfMixed = 0;
976 if (cfWritten)
977 {
978 int rc2 = AudioMixBufMixToParentEx(&pGstStream->MixBuf, 0 /* cSrcOffset */, cfWritten /* cSrcFrames */,
979 &cfMixed /* pcSrcMixed */);
980 if ( RT_FAILURE(rc2)
981 || cfMixed < cfWritten)
982 {
983 AssertMsgFailed(("[%s] Mixing failed: cbBuf=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
984 pGstStream->szName, cbBuf, cfWritten, cfMixed, rc2));
985
986 LogRel2(("Audio: Lost audio frames (%RU32) due to full host stream '%s', expect stuttering audio output\n",
987 cfWritten - cfMixed, pHstStream->szName));
988
989 /* Keep going. */
990 }
991
992 if (RT_SUCCESS(rc))
993 rc = rc2;
994
995 cbWrittenTotal = AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cfWritten);
996
997#ifdef VBOX_WITH_STATISTICS
998 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedOut, cfMixed);
999 Assert(cfWritten >= cfMixed);
1000 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostOut, cfWritten - cfMixed);
1001 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesWritten, cbWrittenTotal);
1002 STAM_COUNTER_ADD(&pGstStream->Out.StatBytesTotalWritten, cbWrittenTotal);
1003#endif
1004 }
1005
1006 Log3Func(("[%s] cbBuf=%RU32, cUsed=%RU32, cLive=%RU32, cWritten=%RU32, cMixed=%RU32, rc=%Rrc\n",
1007 pGstStream->szName,
1008 cbBuf, AudioMixBufUsed(&pGstStream->MixBuf), AudioMixBufLive(&pGstStream->MixBuf), cfWritten, cfMixed, rc));
1009
1010 } while (0);
1011
1012#ifdef LOG_ENABLED
1013 RTStrFree(pszHstSts);
1014 RTStrFree(pszGstSts);
1015#endif
1016
1017 int rc2 = RTCritSectLeave(&pThis->CritSect);
1018 if (RT_SUCCESS(rc))
1019 rc = rc2;
1020
1021 if (RT_SUCCESS(rc))
1022 {
1023 if (pcbWritten)
1024 *pcbWritten = cbWrittenTotal;
1025 }
1026
1027 return rc;
1028}
1029
1030/**
1031 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
1032 */
1033static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1034{
1035 AssertPtrReturn(pInterface, UINT32_MAX);
1036 AssertPtrReturn(pStream, UINT32_MAX);
1037
1038 NOREF(pInterface);
1039
1040 return ++pStream->cRefs;
1041}
1042
1043/**
1044 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
1045 */
1046static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1047{
1048 AssertPtrReturn(pInterface, UINT32_MAX);
1049 AssertPtrReturn(pStream, UINT32_MAX);
1050
1051 NOREF(pInterface);
1052
1053 if (pStream->cRefs > 1) /* 1 reference always is kept by this audio driver. */
1054 pStream->cRefs--;
1055
1056 return pStream->cRefs;
1057}
1058
1059/**
1060 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
1061 */
1062static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
1063{
1064 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1065 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1066
1067 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1068
1069 int rc = RTCritSectEnter(&pThis->CritSect);
1070 if (RT_FAILURE(rc))
1071 return rc;
1072
1073 rc = drvAudioStreamIterateInternal(pThis, pStream);
1074
1075 int rc2 = RTCritSectLeave(&pThis->CritSect);
1076 if (RT_SUCCESS(rc))
1077 rc = rc2;
1078
1079 if (RT_FAILURE(rc))
1080 LogFlowFuncLeaveRC(rc);
1081
1082 return rc;
1083}
1084
1085/**
1086 * Does one iteration of an audio stream.
1087 * This function gives the backend the chance of iterating / altering data and
1088 * does the actual mixing between the guest <-> host mixing buffers.
1089 *
1090 * @returns IPRT status code.
1091 * @param pThis Pointer to driver instance.
1092 * @param pStream Stream to iterate.
1093 */
1094static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
1095{
1096 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1097
1098 if (!pThis->pHostDrvAudio)
1099 return VINF_SUCCESS;
1100
1101 if (!pStream)
1102 return VINF_SUCCESS;
1103
1104 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
1105 AssertPtr(pHstStream);
1106 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : NULL;
1107 AssertPtr(pGstStream);
1108
1109 int rc;
1110
1111 /* Is the stream scheduled for re-initialization? Do so now. */
1112 if (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT)
1113 {
1114#ifdef VBOX_WITH_AUDIO_ENUM
1115 if (pThis->fEnumerateDevices)
1116 {
1117 /* Re-enumerate all host devices. */
1118 drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
1119
1120 pThis->fEnumerateDevices = false;
1121 }
1122#endif /* VBOX_WITH_AUDIO_ENUM */
1123
1124 /* Remove the pending re-init flag in any case, regardless whether the actual re-initialization succeeded
1125 * or not. If it failed, the backend needs to notify us again to try again at some later point in time. */
1126 pHstStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_PENDING_REINIT;
1127
1128 rc = drvAudioStreamReInitInternal(pThis, pStream);
1129 if (RT_FAILURE(rc))
1130 return rc;
1131 }
1132
1133#ifdef LOG_ENABLED
1134 char *pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
1135 Log3Func(("[%s] fStatus=%s\n", pHstStream->szName, pszHstSts));
1136 RTStrFree(pszHstSts);
1137#endif /* LOG_ENABLED */
1138
1139 /* Not enabled or paused? Skip iteration. */
1140 if ( !(pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_ENABLED)
1141 || (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PAUSED))
1142 {
1143 return VINF_SUCCESS;
1144 }
1145
1146 /* Whether to try closing a pending to close stream. */
1147 bool fTryClosePending = false;
1148
1149 do
1150 {
1151 uint32_t cfMixed = 0;
1152
1153 rc = pThis->pHostDrvAudio->pfnStreamIterate(pThis->pHostDrvAudio, pHstStream->pvBackend);
1154 if (RT_FAILURE(rc))
1155 break;
1156
1157 if (pHstStream->enmDir == PDMAUDIODIR_IN)
1158 {
1159 /* Has the host captured any frames which were not mixed to the guest side yet? */
1160 uint32_t cfCaptured = AudioMixBufUsed(&pHstStream->MixBuf);
1161 if (cfCaptured)
1162 {
1163 /* When capturing frames, the guest is the parent while the host is the child.
1164 * So try mixing not yet mixed host-side frames to the guest-side buffer. */
1165 rc = AudioMixBufMixToParent(&pHstStream->MixBuf, cfCaptured, &cfMixed);
1166 if (RT_FAILURE(rc))
1167 {
1168 if (rc == VERR_BUFFER_OVERFLOW)
1169 LogRel2(("Audio: Guest input stream '%s' full, expect stuttering audio capture\n", pGstStream->szName));
1170 else
1171 LogRel2(("Audio: Mixing to guest input stream '%s' failed: %Rrc\n", pGstStream->szName, rc));
1172#ifdef DEBUG_andy_disabled
1173 AssertFailed();
1174#endif
1175 }
1176
1177#ifdef VBOX_WITH_STATISTICS
1178 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesMixedIn, cfMixed);
1179 Assert(cfCaptured >= cfMixed);
1180 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesLostIn, cfCaptured - cfMixed);
1181#endif
1182 Log3Func(("[%s] %RU32/%RU32 input frames mixed, rc=%Rrc\n", pHstStream->szName, cfMixed, cfCaptured, rc));
1183 }
1184 else
1185 {
1186 /* No audio frames to transfer host to the guest (anymore)?
1187 * Then try closing this stream if marked so in the next block. */
1188 fTryClosePending = true;
1189 }
1190 }
1191 else if (pHstStream->enmDir == PDMAUDIODIR_OUT)
1192 {
1193 /* No audio frames to transfer from guest to host (anymore)?
1194 * Then try closing this stream if marked so in the next block. */
1195 fTryClosePending = AudioMixBufLive(&pHstStream->MixBuf) == 0;
1196 }
1197 else
1198 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1199
1200 /* Has the host stream marked as pending to disable?
1201 * Try disabling the stream then. */
1202 if ( pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_PENDING_DISABLE
1203 && fTryClosePending)
1204 {
1205 if (pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional to implement. */
1206 {
1207 const uint32_t cxPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pHstStream->pvBackend);
1208 Log3Func(("[%s] cxPending=%RU32\n", pHstStream->szName, cxPending));
1209
1210 /* Only try close pending if no audio data is pending on the backend-side anymore. */
1211 fTryClosePending = cxPending == 0;
1212 }
1213
1214 if (fTryClosePending)
1215 {
1216 LogFunc(("[%s] Closing pending stream\n", pHstStream->szName));
1217 rc = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
1218 if (RT_FAILURE(rc))
1219 LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pHstStream->szName, rc));
1220 }
1221 }
1222
1223 } while (0);
1224
1225 /* Update timestamps. */
1226 pHstStream->tsLastIterateMS = RTTimeMilliTS();
1227 pGstStream->tsLastIterateMS = RTTimeMilliTS();
1228
1229 if (RT_FAILURE(rc))
1230 LogFunc(("[%s] Failed with %Rrc\n", pHstStream->szName, rc));
1231
1232 return rc;
1233}
1234
1235/**
1236 * Links an audio stream to another audio stream (pair).
1237 *
1238 * @returns IPRT status code.
1239 * @param pStream Stream to handle linking for.
1240 * @param pPair Stream to link pStream to. Specify NULL to unlink pStream from a former linked stream.
1241 */
1242static int drvAudioStreamLinkToInternal(PPDMAUDIOSTREAM pStream, PPDMAUDIOSTREAM pPair)
1243{
1244 if (pPair) /* Link. */
1245 {
1246 pStream->pPair = pPair;
1247 pPair->pPair = pStream;
1248
1249 LogRel2(("Audio: Linked audio stream '%s' to '%s'\n", pStream->szName, pPair->szName));
1250 }
1251 else /* Unlink. */
1252 {
1253 if (pStream->pPair)
1254 {
1255 LogRel2(("Audio: Unlinked pair '%s' from stream '%s'\n", pStream->pPair->szName, pStream->szName));
1256
1257 AssertMsg(pStream->pPair->pPair == pStream,
1258 ("Pair '%s' is not linked to '%s' (linked to '%s')\n",
1259 pStream->pPair->szName, pStream->szName, pStream->pPair->pPair ? pStream->pPair->pPair->szName : "<NONE>"));
1260
1261 pStream->pPair->pPair = NULL; /* Also make sure to unlink the pair from pStream */
1262 pStream->pPair = NULL;
1263 }
1264 }
1265
1266 return VINF_SUCCESS;
1267}
1268
1269/**
1270 * Plays an audio host output stream which has been configured for non-interleaved (layout) data.
1271 *
1272 * @return IPRT status code.
1273 * @param pThis Pointer to driver instance.
1274 * @param pHstStream Host stream to play.
1275 * @param cfToPlay Number of audio frames to play.
1276 * @param pcfPlayed Returns number of audio frames played. Optional.
1277 */
1278static int drvAudioStreamPlayNonInterleaved(PDRVAUDIO pThis,
1279 PPDMAUDIOSTREAM pHstStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
1280{
1281 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1282 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
1283 /* pcfPlayed is optional. */
1284
1285 /* Sanity. */
1286 Assert(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST);
1287 Assert(pHstStream->enmDir == PDMAUDIODIR_OUT);
1288 Assert(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
1289
1290 if (!cfToPlay)
1291 {
1292 if (pcfPlayed)
1293 *pcfPlayed = 0;
1294 return VINF_SUCCESS;
1295 }
1296
1297 int rc = VINF_SUCCESS;
1298
1299 uint32_t cfPlayedTotal = 0;
1300
1301 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetWritable);
1302 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pHstStream->pvBackend);
1303 if (cbWritable)
1304 {
1305 if (cfToPlay > AUDIOMIXBUF_B2F(&pHstStream->MixBuf, cbWritable)) /* More frames available than we can write? Limit. */
1306 cfToPlay = AUDIOMIXBUF_B2F(&pHstStream->MixBuf, cbWritable);
1307
1308 if (cfToPlay)
1309 {
1310 uint8_t auBuf[_4K]; /** @todo Get rid of this here. */
1311
1312 uint32_t cbLeft = AUDIOMIXBUF_F2B(&pHstStream->MixBuf, cfToPlay);
1313 uint32_t cbChunk = sizeof(auBuf);
1314
1315 while (cbLeft)
1316 {
1317 uint32_t cfRead = 0;
1318 rc = AudioMixBufReadCirc(&pHstStream->MixBuf, auBuf, RT_MIN(cbChunk, cbLeft), &cfRead);
1319 if ( !cfRead
1320 || RT_FAILURE(rc))
1321 {
1322 break;
1323 }
1324
1325 uint32_t cbRead = AUDIOMIXBUF_F2B(&pHstStream->MixBuf, cfRead);
1326 Assert(cbRead <= cbChunk);
1327
1328 uint32_t cbPlayed = 0;
1329 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pHstStream->pvBackend,
1330 auBuf, cbRead, &cbPlayed);
1331 if ( RT_FAILURE(rc)
1332 || !cbPlayed)
1333 {
1334 break;
1335 }
1336
1337#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1338 drvAudioDbgPCMDump(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "PlayNonInterleaved.pcm",
1339 auBuf, cbPlayed);
1340#endif
1341 AssertMsg(cbPlayed <= cbRead, ("Played more than available (%RU32 available but got %RU32)\n", cbRead, cbPlayed));
1342#if 0 /** @todo Also handle mono channels. Needs fixing */
1343 AssertMsg(cbPlayed % 2 == 0,
1344 ("Backend for stream '%s' returned uneven played bytes count (cfRead=%RU32, cbPlayed=%RU32)\n",
1345 pHstStream->szName, cfRead, cbPlayed));*/
1346#endif
1347 cfPlayedTotal += AUDIOMIXBUF_B2F(&pHstStream->MixBuf, cbPlayed);
1348 Assert(cbLeft >= cbPlayed);
1349 cbLeft -= cbPlayed;
1350 }
1351 }
1352 }
1353
1354 Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pHstStream->szName, cfPlayedTotal, cfToPlay, rc));
1355
1356 if (RT_SUCCESS(rc))
1357 {
1358 if (pcfPlayed)
1359 *pcfPlayed = cfPlayedTotal;
1360 }
1361
1362 return rc;
1363}
1364
1365/**
1366 * Plays an audio host output stream which has been configured for raw audio (layout) data.
1367 *
1368 * @return IPRT status code.
1369 * @param pThis Pointer to driver instance.
1370 * @param pHstStream Host stream to play.
1371 * @param cfToPlay Number of audio frames to play.
1372 * @param pcfPlayed Returns number of audio frames played. Optional.
1373 */
1374static int drvAudioStreamPlayRaw(PDRVAUDIO pThis,
1375 PPDMAUDIOSTREAM pHstStream, uint32_t cfToPlay, uint32_t *pcfPlayed)
1376{
1377 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1378 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
1379 /* pcfPlayed is optional. */
1380
1381 /* Sanity. */
1382 Assert(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST);
1383 Assert(pHstStream->enmDir == PDMAUDIODIR_OUT);
1384 Assert(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
1385
1386 if (!cfToPlay)
1387 {
1388 if (pcfPlayed)
1389 *pcfPlayed = 0;
1390 return VINF_SUCCESS;
1391 }
1392
1393 int rc = VINF_SUCCESS;
1394
1395 uint32_t cfPlayedTotal = 0;
1396
1397 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetWritable);
1398 uint32_t cfWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pHstStream->pvBackend);
1399 if (cfWritable)
1400 {
1401 if (cfToPlay > cfWritable) /* More frames available than we can write? Limit. */
1402 cfToPlay = cfWritable;
1403
1404 PDMAUDIOFRAME aFrameBuf[_4K]; /** @todo Get rid of this here. */
1405
1406 uint32_t cfLeft = cfToPlay;
1407 while (cfLeft)
1408 {
1409 uint32_t cfRead = 0;
1410 rc = AudioMixBufPeek(&pHstStream->MixBuf, cfLeft, aFrameBuf,
1411 RT_MIN(cfLeft, RT_ELEMENTS(aFrameBuf)), &cfRead);
1412
1413 if (RT_SUCCESS(rc))
1414 {
1415 if (cfRead)
1416 {
1417 uint32_t cfPlayed;
1418
1419 /* Note: As the stream layout is RPDMAUDIOSTREAMLAYOUT_RAW, operate on audio frames
1420 * rather on bytes. */
1421 Assert(cfRead <= RT_ELEMENTS(aFrameBuf));
1422 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pHstStream->pvBackend,
1423 aFrameBuf, cfRead, &cfPlayed);
1424 if ( RT_FAILURE(rc)
1425 || !cfPlayed)
1426 {
1427 break;
1428 }
1429
1430 cfPlayedTotal += cfPlayed;
1431 Assert(cfPlayedTotal <= cfToPlay);
1432
1433 Assert(cfLeft >= cfRead);
1434 cfLeft -= cfRead;
1435 }
1436 else
1437 {
1438 if (rc == VINF_AUDIO_MORE_DATA_AVAILABLE) /* Do another peeking round if there is more data available. */
1439 continue;
1440
1441 break;
1442 }
1443 }
1444 else if (RT_FAILURE(rc))
1445 break;
1446 }
1447 }
1448
1449 Log3Func(("[%s] Played %RU32/%RU32 frames, rc=%Rrc\n", pHstStream->szName, cfPlayedTotal, cfToPlay, rc));
1450
1451 if (RT_SUCCESS(rc))
1452 {
1453 if (pcfPlayed)
1454 *pcfPlayed = cfPlayedTotal;
1455
1456 }
1457
1458 return rc;
1459}
1460
1461/**
1462 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
1463 */
1464static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface,
1465 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed)
1466{
1467 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1468 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1469 /* pcFramesPlayed is optional. */
1470
1471 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1472
1473 int rc = RTCritSectEnter(&pThis->CritSect);
1474 if (RT_FAILURE(rc))
1475 return rc;
1476
1477 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT,
1478 ("Stream '%s' is not an output stream and therefore cannot be played back (direction is 0x%x)\n",
1479 pStream->szName, pStream->enmDir));
1480
1481 uint32_t cfPlayedTotal = 0;
1482
1483 do
1484 {
1485 if (!pThis->pHostDrvAudio)
1486 break;
1487
1488 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
1489 AssertPtr(pHstStream);
1490 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : NULL;
1491 AssertPtr(pGstStream);
1492
1493 AssertReleaseMsgBreakStmt(pHstStream != NULL,
1494 ("[%s] Host stream is NULL (cRefs=%RU32, fStatus=%x, enmCtx=%ld)\n",
1495 pStream->szName, pStream->cRefs, pStream->fStatus, pStream->enmCtx),
1496 rc = VERR_NOT_AVAILABLE);
1497 AssertReleaseMsgBreakStmt(pGstStream != NULL,
1498 ("[%s] Guest stream is NULL (cRefs=%RU32, fStatus=%x, enmCtx=%ld)\n",
1499 pStream->szName, pStream->cRefs, pStream->fStatus, pStream->enmCtx),
1500 rc = VERR_NOT_AVAILABLE);
1501
1502 /*
1503 * Check if the backend is ready to operate.
1504 */
1505
1506 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetStatus);
1507 PDMAUDIOSTREAMSTS stsBackend = pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pHstStream->pvBackend);
1508#ifdef LOG_ENABLED
1509 char *pszBackendSts = dbgAudioStreamStatusToStr(stsBackend);
1510 Log3Func(("[%s] Start: stsBackend=%s\n", pHstStream->szName, pszBackendSts));
1511 RTStrFree(pszBackendSts);
1512#endif /* LOG_ENABLED */
1513 if (!(stsBackend & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) /* Backend disabled? Bail out. */
1514 break;
1515
1516 uint32_t cfToPlay = AudioMixBufLive(&pHstStream->MixBuf);
1517
1518 if (pThis->pHostDrvAudio->pfnStreamPlayBegin)
1519 pThis->pHostDrvAudio->pfnStreamPlayBegin(pThis->pHostDrvAudio, pHstStream->pvBackend);
1520
1521 if (RT_LIKELY(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
1522 {
1523 rc = drvAudioStreamPlayNonInterleaved(pThis, pHstStream, cfToPlay, &cfPlayedTotal);
1524 }
1525 else if (pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
1526 {
1527 rc = drvAudioStreamPlayRaw(pThis, pHstStream, cfToPlay, &cfPlayedTotal);
1528 }
1529 else
1530 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1531
1532 if (pThis->pHostDrvAudio->pfnStreamPlayEnd)
1533 pThis->pHostDrvAudio->pfnStreamPlayEnd(pThis->pHostDrvAudio, pHstStream->pvBackend);
1534
1535#ifdef LOG_ENABLED
1536 uint32_t cfLive = 0;
1537#endif
1538 if (RT_SUCCESS(rc))
1539 {
1540 AudioMixBufFinish(&pHstStream->MixBuf, cfPlayedTotal);
1541
1542#ifdef VBOX_WITH_STATISTICS
1543 STAM_COUNTER_ADD (&pThis->Stats.TotalFramesOut, cfPlayedTotal);
1544 STAM_PROFILE_ADV_STOP(&pThis->Stats.DelayOut, out);
1545 STAM_COUNTER_ADD (&pHstStream->Out.StatFramesPlayed, cfPlayedTotal);
1546#endif
1547
1548#ifdef LOG_ENABLED
1549 cfLive = AudioMixBufLive(&pHstStream->MixBuf);
1550#endif
1551 }
1552
1553#ifdef LOG_ENABLED
1554 pszBackendSts = dbgAudioStreamStatusToStr(stsBackend);
1555 Log3Func(("[%s] End: stsBackend=%s, cfLive=%RU32, cfPlayedTotal=%RU32, rc=%Rrc\n",
1556 pHstStream->szName, pszBackendSts, cfLive, cfPlayedTotal, rc));
1557 RTStrFree(pszBackendSts);
1558#endif /* LOG_ENABLED */
1559
1560 } while (0);
1561
1562 int rc2 = RTCritSectLeave(&pThis->CritSect);
1563 if (RT_SUCCESS(rc))
1564 rc = rc2;
1565
1566 if (RT_SUCCESS(rc))
1567 {
1568 if (pcFramesPlayed)
1569 *pcFramesPlayed = cfPlayedTotal;
1570 }
1571
1572 if (RT_FAILURE(rc))
1573 LogFlowFunc(("[%s] Failed with %Rrc\n", pStream->szName, rc));
1574
1575 return rc;
1576}
1577
1578/**
1579 * Captures non-interleaved input from a host stream.
1580 *
1581 * @returns IPRT status code.
1582 * @param pThis Driver instance.
1583 * @param pHstStream Host stream to capture from.
1584 * @param pcfCaptured Number of (host) audio frames captured. Optional.
1585 */
1586static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PPDMAUDIOSTREAM pHstStream, uint32_t *pcfCaptured)
1587{
1588 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1589 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
1590 /* pcfCaptured is optional. */
1591
1592 /* Sanity. */
1593 Assert(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST);
1594 Assert(pHstStream->enmDir == PDMAUDIODIR_IN);
1595 Assert(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
1596
1597 int rc = VINF_SUCCESS;
1598
1599 uint32_t cfCapturedTotal = 0;
1600
1601 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
1602
1603 uint8_t auBuf[_1K]; /** @todo Get rid of this. */
1604 size_t cbBuf = sizeof(auBuf);
1605
1606 for (;;)
1607 {
1608 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pHstStream->pvBackend);
1609 if (!cbReadable)
1610 {
1611 Log2Func(("[%s] No readable data available, skipping\n", pHstStream->szName));
1612 break;
1613 }
1614
1615 uint32_t cbFree = AUDIOMIXBUF_F2B(&pHstStream->MixBuf, AudioMixBufFree(&pHstStream->MixBuf));
1616 if (!cbFree)
1617 {
1618 Log2Func(("[%s] Host buffer full, skipping\n", pHstStream->szName));
1619 break;
1620 }
1621
1622 if (cbFree < cbReadable) /* More data captured than we can read? */
1623 {
1624 /** @todo Warn? */
1625 }
1626
1627 if (cbFree > cbBuf) /* Limit to buffer size. */
1628 cbFree = (uint32_t)cbBuf;
1629
1630 uint32_t cbCaptured;
1631 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pHstStream->pvBackend,
1632 auBuf, cbFree, &cbCaptured);
1633 if (RT_FAILURE(rc))
1634 {
1635 int rc2 = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
1636 AssertRC(rc2);
1637 }
1638 else if (cbCaptured)
1639 {
1640#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1641 drvAudioDbgPCMDump(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "CaptureNonInterleaved.pcm",
1642 auBuf, cbCaptured);
1643#endif
1644 Assert(cbCaptured <= cbBuf);
1645 if (cbCaptured > cbBuf) /* Paranoia. */
1646 cbCaptured = (uint32_t)cbBuf;
1647
1648 uint32_t cfCaptured = 0;
1649 rc = AudioMixBufWriteCirc(&pHstStream->MixBuf, auBuf, cbCaptured, &cfCaptured);
1650 if (RT_SUCCESS(rc))
1651 cfCapturedTotal += cfCaptured;
1652 }
1653 else /* Nothing captured -- bail out. */
1654 break;
1655
1656 if (RT_FAILURE(rc))
1657 break;
1658 }
1659
1660 if (RT_SUCCESS(rc))
1661 {
1662 if (cfCapturedTotal)
1663 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pHstStream->szName, cfCapturedTotal, rc));
1664 }
1665 else
1666 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pHstStream->szName, rc));
1667
1668 if (pcfCaptured)
1669 *pcfCaptured = cfCapturedTotal;
1670
1671 return rc;
1672}
1673
1674/**
1675 * Captures raw input from a host stream.
1676 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
1677 * no data layout processing done in between.
1678 *
1679 * Needed for e.g. the VRDP audio backend (in Main).
1680 *
1681 * @returns IPRT status code.
1682 * @param pThis Driver instance.
1683 * @param pHstStream Host stream to capture from.
1684 * @param pcfCaptured Number of (host) audio frames captured. Optional.
1685 */
1686static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PPDMAUDIOSTREAM pHstStream, uint32_t *pcfCaptured)
1687{
1688 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1689 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
1690 /* pcfCaptured is optional. */
1691
1692 /* Sanity. */
1693 Assert(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST);
1694 Assert(pHstStream->enmDir == PDMAUDIODIR_IN);
1695 Assert(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
1696
1697 int rc = VINF_SUCCESS;
1698
1699 uint32_t cfCapturedTotal = 0;
1700
1701 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
1702
1703 for (;;)
1704 {
1705 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pHstStream->pvBackend);
1706 if (!cbReadable) /* Nothing to read on the backend side? Bail out. */
1707 break;
1708
1709 const uint32_t cbFree = AudioMixBufFreeBytes(&pHstStream->MixBuf);
1710 if (!cbFree) /* No space left in the host stream? */
1711 break;
1712
1713 if (cbReadable > cbFree) /* Don't capture more than the host stream currently can hold. */
1714 cbReadable = cbFree;
1715
1716 PPDMAUDIOFRAME paFrames;
1717 uint32_t cfWritable;
1718 rc = AudioMixBufPeekMutable(&pHstStream->MixBuf, AUDIOMIXBUF_B2F(&pHstStream->MixBuf, cbReadable),
1719 &paFrames, &cfWritable);
1720 if ( RT_FAILURE(rc)
1721 || !cfWritable)
1722 {
1723 break;
1724 }
1725
1726 uint32_t cfCaptured;
1727 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pHstStream->pvBackend,
1728 paFrames, cfWritable, &cfCaptured);
1729 if (RT_FAILURE(rc))
1730 {
1731 int rc2 = drvAudioStreamControlInternalBackend(pThis, pHstStream, PDMAUDIOSTREAMCMD_DISABLE);
1732 AssertRC(rc2);
1733 }
1734 else if (cfCaptured)
1735 {
1736 Assert(cfCaptured <= cfWritable);
1737 if (cfCaptured > cfWritable) /* Paranoia. */
1738 cfCaptured = cfWritable;
1739
1740 cfCapturedTotal += cfCaptured;
1741 }
1742 else /* Nothing captured -- bail out. */
1743 break;
1744
1745 if (RT_FAILURE(rc))
1746 break;
1747 }
1748
1749 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pHstStream->szName, cfCapturedTotal, rc));
1750
1751 if (pcfCaptured)
1752 *pcfCaptured = cfCapturedTotal;
1753
1754 return rc;
1755}
1756
1757/**
1758 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
1759 */
1760static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
1761 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
1762{
1763 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1764
1765 int rc = RTCritSectEnter(&pThis->CritSect);
1766 if (RT_FAILURE(rc))
1767 return rc;
1768
1769 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
1770 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
1771 pStream->szName, pStream->enmDir));
1772
1773 uint32_t cfCaptured = 0;
1774
1775 do
1776 {
1777 if (!pThis->pHostDrvAudio)
1778 break;
1779
1780 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
1781 AssertPtr(pHstStream);
1782 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : NULL;
1783 AssertPtr(pGstStream);
1784
1785 AssertReleaseMsgBreakStmt(pHstStream != NULL,
1786 ("[%s] Host stream is NULL (cRefs=%RU32, fStatus=%x, enmCtx=%ld)\n",
1787 pStream->szName, pStream->cRefs, pStream->fStatus, pStream->enmCtx),
1788 rc = VERR_NOT_AVAILABLE);
1789 AssertReleaseMsgBreakStmt(pGstStream != NULL,
1790 ("[%s] Guest stream is NULL (cRefs=%RU32, fStatus=%x, enmCtx=%ld)\n",
1791 pStream->szName, pStream->cRefs, pStream->fStatus, pStream->enmCtx),
1792 rc = VERR_NOT_AVAILABLE);
1793
1794 /*
1795 * Check if the backend is ready to operate.
1796 */
1797
1798 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetStatus);
1799 PDMAUDIOSTREAMSTS stsBackend = pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pHstStream->pvBackend);
1800#ifdef LOG_ENABLED
1801 char *pszBackendSts = dbgAudioStreamStatusToStr(stsBackend);
1802 Log3Func(("[%s] Start: stsBackend=%s\n", pHstStream->szName, pszBackendSts));
1803 RTStrFree(pszBackendSts);
1804#endif /* LOG_ENABLED */
1805 if (!(stsBackend & PDMAUDIOSTREAMSTS_FLAG_ENABLED)) /* Backend disabled? Bail out. */
1806 break;
1807
1808 /*
1809 * Do the actual capturing.
1810 */
1811
1812 if (pThis->pHostDrvAudio->pfnStreamCaptureBegin)
1813 pThis->pHostDrvAudio->pfnStreamCaptureBegin(pThis->pHostDrvAudio, pHstStream->pvBackend);
1814
1815 if (RT_LIKELY(pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
1816 {
1817 rc = drvAudioStreamCaptureNonInterleaved(pThis, pHstStream, &cfCaptured);
1818 }
1819 else if (pHstStream->Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
1820 {
1821 rc = drvAudioStreamCaptureRaw(pThis, pHstStream, &cfCaptured);
1822 }
1823 else
1824 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1825
1826 if (pThis->pHostDrvAudio->pfnStreamCaptureEnd)
1827 pThis->pHostDrvAudio->pfnStreamCaptureEnd(pThis->pHostDrvAudio, pHstStream->pvBackend);
1828
1829#ifdef LOG_ENABLED
1830 pszBackendSts = dbgAudioStreamStatusToStr(stsBackend);
1831 Log3Func(("[%s] End: stsBackend=%s, cfCaptured=%RU32, rc=%Rrc\n",
1832 pHstStream->szName, pszBackendSts, cfCaptured, rc));
1833 RTStrFree(pszBackendSts);
1834#endif /* LOG_ENABLED */
1835
1836 if (RT_SUCCESS(rc))
1837 {
1838 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pHstStream->szName, cfCaptured, rc));
1839
1840#ifdef VBOX_WITH_STATISTICS
1841 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
1842 STAM_COUNTER_ADD(&pHstStream->In.StatFramesCaptured, cfCaptured);
1843#endif
1844 }
1845 else if (RT_UNLIKELY(RT_FAILURE(rc)))
1846 {
1847 LogRel2(("Audio: Capturing stream '%s' failed with %Rrc\n", pHstStream->szName, rc));
1848 }
1849
1850 } while (0);
1851
1852 if (pcFramesCaptured)
1853 *pcFramesCaptured = cfCaptured;
1854
1855 int rc2 = RTCritSectLeave(&pThis->CritSect);
1856 if (RT_SUCCESS(rc))
1857 rc = rc2;
1858
1859 if (RT_FAILURE(rc))
1860 LogFlowFuncLeaveRC(rc);
1861
1862 return rc;
1863}
1864
1865#ifdef VBOX_WITH_AUDIO_CALLBACKS
1866/**
1867 * Duplicates an audio callback.
1868 *
1869 * @returns Pointer to duplicated callback, or NULL on failure.
1870 * @param pCB Callback to duplicate.
1871 */
1872static PPDMAUDIOCBRECORD drvAudioCallbackDuplicate(PPDMAUDIOCBRECORD pCB)
1873{
1874 AssertPtrReturn(pCB, NULL);
1875
1876 PPDMAUDIOCBRECORD pCBCopy = (PPDMAUDIOCBRECORD)RTMemDup((void *)pCB, sizeof(PDMAUDIOCBRECORD));
1877 if (!pCBCopy)
1878 return NULL;
1879
1880 if (pCB->pvCtx)
1881 {
1882 pCBCopy->pvCtx = RTMemDup(pCB->pvCtx, pCB->cbCtx);
1883 if (!pCBCopy->pvCtx)
1884 {
1885 RTMemFree(pCBCopy);
1886 return NULL;
1887 }
1888
1889 pCBCopy->cbCtx = pCB->cbCtx;
1890 }
1891
1892 return pCBCopy;
1893}
1894
1895/**
1896 * Destroys a given callback.
1897 *
1898 * @param pCB Callback to destroy.
1899 */
1900static void drvAudioCallbackDestroy(PPDMAUDIOCBRECORD pCB)
1901{
1902 if (!pCB)
1903 return;
1904
1905 RTListNodeRemove(&pCB->Node);
1906 if (pCB->pvCtx)
1907 {
1908 Assert(pCB->cbCtx);
1909 RTMemFree(pCB->pvCtx);
1910 }
1911 RTMemFree(pCB);
1912}
1913
1914/**
1915 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnRegisterCallbacks}
1916 */
1917static DECLCALLBACK(int) drvAudioRegisterCallbacks(PPDMIAUDIOCONNECTOR pInterface,
1918 PPDMAUDIOCBRECORD paCallbacks, size_t cCallbacks)
1919{
1920 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1921 AssertPtrReturn(paCallbacks, VERR_INVALID_POINTER);
1922 AssertReturn(cCallbacks, VERR_INVALID_PARAMETER);
1923
1924 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1925
1926 int rc = RTCritSectEnter(&pThis->CritSect);
1927 if (RT_FAILURE(rc))
1928 return rc;
1929
1930 for (size_t i = 0; i < cCallbacks; i++)
1931 {
1932 PPDMAUDIOCBRECORD pCB = drvAudioCallbackDuplicate(&paCallbacks[i]);
1933 if (!pCB)
1934 {
1935 rc = VERR_NO_MEMORY;
1936 break;
1937 }
1938
1939 switch (pCB->enmSource)
1940 {
1941 case PDMAUDIOCBSOURCE_DEVICE:
1942 {
1943 switch (pCB->Device.enmType)
1944 {
1945 case PDMAUDIODEVICECBTYPE_DATA_INPUT:
1946 RTListAppend(&pThis->In.lstCB, &pCB->Node);
1947 break;
1948
1949 case PDMAUDIODEVICECBTYPE_DATA_OUTPUT:
1950 RTListAppend(&pThis->Out.lstCB, &pCB->Node);
1951 break;
1952
1953 default:
1954 AssertMsgFailed(("Not supported\n"));
1955 break;
1956 }
1957
1958 break;
1959 }
1960
1961 default:
1962 AssertMsgFailed(("Not supported\n"));
1963 break;
1964 }
1965 }
1966
1967 /** @todo Undo allocations on error. */
1968
1969 int rc2 = RTCritSectLeave(&pThis->CritSect);
1970 if (RT_SUCCESS(rc))
1971 rc = rc2;
1972
1973 return rc;
1974}
1975#endif /* VBOX_WITH_AUDIO_CALLBACKS */
1976
1977#ifdef VBOX_WITH_AUDIO_CALLBACKS
1978/**
1979 * Backend callback implementation.
1980 *
1981 * Important: No calls back to the backend within this function, as the backend
1982 * might hold any locks / critical sections while executing this callback.
1983 * Will result in some ugly deadlocks (or at least locking order violations) then.
1984 *
1985 * @copydoc FNPDMHOSTAUDIOCALLBACK
1986 */
1987static DECLCALLBACK(int) drvAudioBackendCallback(PPDMDRVINS pDrvIns,
1988 PDMAUDIOBACKENDCBTYPE enmType, void *pvUser, size_t cbUser)
1989{
1990 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1991 RT_NOREF(pvUser, cbUser);
1992 /* pvUser and cbUser are optional. */
1993
1994 /* Get the upper driver (PDMIAUDIOCONNECTOR). */
1995 AssertPtr(pDrvIns->pUpBase);
1996 PPDMIAUDIOCONNECTOR pInterface = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
1997 AssertPtr(pInterface);
1998 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
1999
2000 int rc = RTCritSectEnter(&pThis->CritSect);
2001 AssertRCReturn(rc, rc);
2002
2003 LogFunc(("pThis=%p, enmType=%RU32, pvUser=%p, cbUser=%zu\n", pThis, enmType, pvUser, cbUser));
2004
2005 switch (enmType)
2006 {
2007 case PDMAUDIOBACKENDCBTYPE_DEVICES_CHANGED:
2008 LogRel(("Audio: Host audio device configuration has changed\n"));
2009 rc = drvAudioScheduleReInitInternal(pThis);
2010 break;
2011
2012 default:
2013 AssertMsgFailed(("Not supported\n"));
2014 break;
2015 }
2016
2017 int rc2 = RTCritSectLeave(&pThis->CritSect);
2018 if (RT_SUCCESS(rc))
2019 rc = rc2;
2020
2021 LogFlowFunc(("Returning %Rrc\n", rc));
2022 return rc;
2023}
2024#endif /* VBOX_WITH_AUDIO_CALLBACKS */
2025
2026#ifdef VBOX_WITH_AUDIO_ENUM
2027/**
2028 * Enumerates all host audio devices.
2029 * This functionality might not be implemented by all backends and will return VERR_NOT_SUPPORTED
2030 * if not being supported.
2031 *
2032 * @returns IPRT status code.
2033 * @param pThis Driver instance to be called.
2034 * @param fLog Whether to print the enumerated device to the release log or not.
2035 * @param pDevEnum Where to store the device enumeration.
2036 */
2037static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIODEVICEENUM pDevEnum)
2038{
2039 int rc;
2040
2041 /*
2042 * If the backend supports it, do a device enumeration.
2043 */
2044 if (pThis->pHostDrvAudio->pfnGetDevices)
2045 {
2046 PDMAUDIODEVICEENUM DevEnum;
2047 rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);
2048 if (RT_SUCCESS(rc))
2049 {
2050 if (fLog)
2051 LogRel(("Audio: Found %RU16 devices\n", DevEnum.cDevices));
2052
2053 PPDMAUDIODEVICE pDev;
2054 RTListForEach(&DevEnum.lstDevices, pDev, PDMAUDIODEVICE, Node)
2055 {
2056 if (fLog)
2057 {
2058 char *pszFlags = DrvAudioHlpAudDevFlagsToStrA(pDev->fFlags);
2059
2060 LogRel(("Audio: Device '%s':\n", pDev->szName));
2061 LogRel(("Audio: \tUsage = %s\n", DrvAudioHlpAudDirToStr(pDev->enmUsage)));
2062 LogRel(("Audio: \tFlags = %s\n", pszFlags ? pszFlags : "<NONE>"));
2063 LogRel(("Audio: \tInput channels = %RU8\n", pDev->cMaxInputChannels));
2064 LogRel(("Audio: \tOutput channels = %RU8\n", pDev->cMaxOutputChannels));
2065
2066 if (pszFlags)
2067 RTStrFree(pszFlags);
2068 }
2069 }
2070
2071 if (pDevEnum)
2072 rc = DrvAudioHlpDeviceEnumCopy(pDevEnum, &DevEnum);
2073
2074 DrvAudioHlpDeviceEnumFree(&DevEnum);
2075 }
2076 else
2077 {
2078 if (fLog)
2079 LogRel(("Audio: Device enumeration failed with %Rrc\n", rc));
2080 /* Not fatal. */
2081 }
2082 }
2083 else
2084 {
2085 rc = VERR_NOT_SUPPORTED;
2086
2087 if (fLog)
2088 LogRel3(("Audio: Host audio backend does not support audio device enumeration, skipping\n"));
2089 }
2090
2091 LogFunc(("Returning %Rrc\n", rc));
2092 return rc;
2093}
2094#endif /* VBOX_WITH_AUDIO_ENUM */
2095
2096#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2097/**
2098 * Returns an unique file name for this given audio connector instance.
2099 *
2100 * @return Allocated file name. Must be free'd using RTStrFree().
2101 * @param pThis Driver instance.
2102 * @param pszPath Path name of the file to delete. The path must exist.
2103 * @param pszSuffix File name suffix to use.
2104 */
2105static char *drvAudioDbgGetFileNameA(PDRVAUDIO pThis, const char *pszPath, const char *pszSuffix)
2106{
2107 char szFileName[64];
2108 RTStrPrintf(szFileName, sizeof(szFileName), "drvAudio%RU32-%s", pThis->pDrvIns->iInstance, pszSuffix);
2109
2110 char szFilePath[RTPATH_MAX];
2111 int rc2 = RTStrCopy(szFilePath, sizeof(szFilePath), pszPath);
2112 AssertRC(rc2);
2113 rc2 = RTPathAppend(szFilePath, sizeof(szFilePath), szFileName);
2114 AssertRC(rc2);
2115
2116 return RTStrDup(szFilePath);
2117}
2118
2119/**
2120 * Deletes a PCM audio dump file.
2121 *
2122 * @param pThis Driver instance.
2123 * @param pszPath Path name of the file to delete. The path must exist.
2124 * @param pszSuffix File name suffix to use.
2125 */
2126static void drvAudioDbgPCMDelete(PDRVAUDIO pThis, const char *pszPath, const char *pszSuffix)
2127{
2128 char *pszFileName = drvAudioDbgGetFileNameA(pThis, pszPath, pszSuffix);
2129 AssertPtr(pszFileName);
2130
2131 RTFileDelete(pszFileName);
2132
2133 RTStrFree(pszFileName);
2134}
2135
2136/**
2137 * Dumps (raw) PCM audio data into a file by appending.
2138 * Every driver instance will have an own prefix to not introduce writing races.
2139 *
2140 * @param pThis Driver instance.
2141 * @param pszPath Path name to save file to. The path must exist.
2142 * @param pszSuffix File name suffix to use.
2143 * @param pvData Pointer to PCM data to dump.
2144 * @param cbData Size (in bytes) of PCM data to dump.
2145 */
2146static void drvAudioDbgPCMDump(PDRVAUDIO pThis,
2147 const char *pszPath, const char *pszSuffix, const void *pvData, size_t cbData)
2148{
2149 if (!pvData || !cbData)
2150 return;
2151
2152 AssertPtr(pThis->pDrvIns);
2153
2154 char *pszFileName = drvAudioDbgGetFileNameA(pThis, pszPath, pszSuffix);
2155 AssertPtr(pszFileName);
2156
2157 RTFILE fh;
2158 int rc2 = RTFileOpen(&fh, pszFileName, RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
2159 AssertRC(rc2);
2160 if (RT_SUCCESS(rc2))
2161 {
2162 RTFileWrite(fh, pvData, cbData, NULL);
2163 RTFileClose(fh);
2164 }
2165
2166 RTStrFree(pszFileName);
2167}
2168#endif /* VBOX_AUDIO_DEBUG_DUMP_PCM_DATA */
2169
2170/**
2171 * Initializes the host backend and queries its initial configuration.
2172 * If the host backend fails, VERR_AUDIO_BACKEND_INIT_FAILED will be returned.
2173 *
2174 * Note: As this routine is called when attaching to the device LUN in the
2175 * device emulation, we either check for success or VERR_AUDIO_BACKEND_INIT_FAILED.
2176 * Everything else is considered as fatal and must be handled separately in
2177 * the device emulation!
2178 *
2179 * @return IPRT status code.
2180 * @param pThis Driver instance to be called.
2181 * @param pCfgHandle CFGM configuration handle to use for this driver.
2182 */
2183static int drvAudioHostInit(PDRVAUDIO pThis, PCFGMNODE pCfgHandle)
2184{
2185 /* pCfgHandle is optional. */
2186 NOREF(pCfgHandle);
2187 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2188
2189 LogFlowFuncEnter();
2190
2191 AssertPtr(pThis->pHostDrvAudio);
2192 int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
2193 if (RT_FAILURE(rc))
2194 {
2195 LogRel(("Audio: Initialization of host backend failed with %Rrc\n", rc));
2196 return VERR_AUDIO_BACKEND_INIT_FAILED;
2197 }
2198
2199 /*
2200 * Get the backend configuration.
2201 */
2202 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
2203 if (RT_FAILURE(rc))
2204 {
2205 LogRel(("Audio: Getting host backend configuration failed with %Rrc\n", rc));
2206 return VERR_AUDIO_BACKEND_INIT_FAILED;
2207 }
2208
2209 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
2210 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
2211
2212 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
2213
2214 LogRel2(("Audio: Host audio backend supports %RU32 input streams and %RU32 output streams at once\n",
2215 /* Clamp for logging. Unlimited streams are defined by UINT32_MAX. */
2216 RT_MIN(64, pThis->In.cStreamsFree), RT_MIN(64, pThis->Out.cStreamsFree)));
2217
2218#ifdef VBOX_WITH_AUDIO_ENUM
2219 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
2220 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
2221 AssertRC(rc2);
2222
2223 RT_NOREF(rc2);
2224 /* Ignore rc. */
2225#endif
2226
2227#ifdef VBOX_WITH_AUDIO_CALLBACKS
2228 /*
2229 * If the backend supports it, offer a callback to this connector.
2230 */
2231 if (pThis->pHostDrvAudio->pfnSetCallback)
2232 {
2233 rc2 = pThis->pHostDrvAudio->pfnSetCallback(pThis->pHostDrvAudio, drvAudioBackendCallback);
2234 if (RT_FAILURE(rc2))
2235 LogRel(("Audio: Error registering backend callback, rc=%Rrc\n", rc2));
2236 /* Not fatal. */
2237 }
2238#endif
2239
2240 LogFlowFuncLeave();
2241 return VINF_SUCCESS;
2242}
2243
2244/**
2245 * Handles state changes for all audio streams.
2246 *
2247 * @param pDrvIns Pointer to driver instance.
2248 * @param enmCmd Stream command to set for all streams.
2249 */
2250static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
2251{
2252 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2253 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
2254
2255 LogFlowFunc(("enmCmd=%s\n", DrvAudioHlpStreamCmdToStr(enmCmd)));
2256
2257 int rc2 = RTCritSectEnter(&pThis->CritSect);
2258 AssertRC(rc2);
2259
2260 if (!pThis->pHostDrvAudio)
2261 return;
2262
2263 PPDMAUDIOSTREAM pHstStream;
2264 RTListForEach(&pThis->lstHstStreams, pHstStream, PDMAUDIOSTREAM, Node)
2265 drvAudioStreamControlInternalBackend(pThis, pHstStream, enmCmd);
2266
2267 rc2 = RTCritSectLeave(&pThis->CritSect);
2268 AssertRC(rc2);
2269}
2270
2271/**
2272 * Intializes an audio driver instance.
2273 *
2274 * @returns IPRT status code.
2275 * @param pDrvIns Pointer to driver instance.
2276 * @param pCfgHandle CFGM handle to use for configuration.
2277 */
2278static int drvAudioInit(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
2279{
2280 AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
2281 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
2282
2283 LogRel2(("Audio: Verbose logging enabled\n"));
2284
2285 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
2286
2287 int rc = RTCritSectInit(&pThis->CritSect);
2288 AssertRCReturn(rc, rc);
2289
2290 /*
2291 * Configure driver from CFGM stuff.
2292 */
2293#ifdef DEBUG
2294 CFGMR3Dump(pCfgHandle);
2295#endif
2296
2297 int rc2 = CFGMR3QueryString(pCfgHandle, "DriverName", pThis->szName, sizeof(pThis->szName));
2298 if (RT_FAILURE(rc2))
2299 RTStrPrintf(pThis->szName, sizeof(pThis->szName), "Untitled");
2300
2301 /* By default we don't enable anything if wrongly / not set-up. */
2302 pThis->In.fEnabled = false;
2303 pThis->Out.fEnabled = false;
2304
2305 uint64_t u64Temp;
2306 rc2 = CFGMR3QueryInteger(pCfgHandle, "InputEnabled", &u64Temp);
2307 if (RT_SUCCESS(rc2))
2308 pThis->In.fEnabled = RT_BOOL(u64Temp);
2309
2310 rc2 = CFGMR3QueryInteger(pCfgHandle, "OutputEnabled", &u64Temp);
2311 if (RT_SUCCESS(rc2))
2312 pThis->Out.fEnabled = RT_BOOL(u64Temp);
2313
2314 LogRel2(("Audio: Initial status for driver '%s': Input is %s, output is %s\n",
2315 pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
2316
2317 /*
2318 * If everything went well, initialize the lower driver.
2319 */
2320 rc = drvAudioHostInit(pThis, pCfgHandle);
2321
2322 LogFlowFuncLeaveRC(rc);
2323 return rc;
2324}
2325
2326/**
2327 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
2328 */
2329static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
2330 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2331{
2332 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2333 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
2334
2335 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2336 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2337 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2338 /* pcbRead is optional. */
2339
2340 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN,
2341 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
2342 pStream->szName, pStream->enmDir));
2343
2344 uint32_t cbReadTotal = 0;
2345
2346 int rc = RTCritSectEnter(&pThis->CritSect);
2347 if (RT_FAILURE(rc))
2348 return rc;
2349
2350 do
2351 {
2352 if (!pThis->In.fEnabled)
2353 {
2354 RT_BZERO(pvBuf, cbBuf);
2355 cbReadTotal = cbBuf;
2356 break;
2357 }
2358
2359 if ( pThis->pHostDrvAudio
2360 && pThis->pHostDrvAudio->pfnGetStatus
2361 && pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, PDMAUDIODIR_IN) != PDMAUDIOBACKENDSTS_RUNNING)
2362 {
2363 rc = VERR_NOT_AVAILABLE;
2364 break;
2365 }
2366
2367 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2368 if (!pHstStream)
2369 {
2370 rc = VERR_NOT_AVAILABLE;
2371 break;
2372 }
2373
2374 PPDMAUDIOSTREAM pGstStream = pHstStream->pPair;
2375 AssertPtr(pGstStream);
2376
2377 /*
2378 * Read from the parent buffer (that is, the guest buffer) which
2379 * should have the audio data in the format the guest needs.
2380 */
2381 uint32_t cReadTotal = 0;
2382
2383 uint32_t cToRead = RT_MIN(AUDIOMIXBUF_B2F(&pGstStream->MixBuf, cbBuf), AudioMixBufUsed(&pGstStream->MixBuf));
2384 while (cToRead)
2385 {
2386 uint32_t cRead;
2387 rc = AudioMixBufReadCirc(&pGstStream->MixBuf, (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cReadTotal),
2388 AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cToRead), &cRead);
2389 if (RT_FAILURE(rc))
2390 break;
2391
2392#if defined (VBOX_WITH_STATISTICS) || defined (VBOX_AUDIO_DEBUG_DUMP_PCM_DATA)
2393 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cRead);
2394#endif
2395
2396#ifdef VBOX_WITH_STATISTICS
2397 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
2398 STAM_COUNTER_ADD(&pGstStream->In.StatBytesTotalRead, cbRead);
2399#endif
2400 Assert(cToRead >= cRead);
2401 cToRead -= cRead;
2402
2403 cReadTotal += cRead;
2404 }
2405
2406 if (cReadTotal)
2407 {
2408#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
2409 drvAudioDbgPCMDump(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "StreamRead.pcm",
2410 pvBuf, AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cReadTotal));
2411#endif
2412 AudioMixBufFinish(&pGstStream->MixBuf, cReadTotal);
2413
2414 pGstStream->In.tsLastReadMS = RTTimeMilliTS();
2415
2416 cbReadTotal = AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cReadTotal);
2417 }
2418
2419 } while (0);
2420
2421 Log3Func(("[%s] cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, cbReadTotal, rc));
2422
2423 int rc2 = RTCritSectLeave(&pThis->CritSect);
2424 if (RT_SUCCESS(rc))
2425 rc = rc2;
2426
2427 if (RT_SUCCESS(rc))
2428 {
2429 if (pcbRead)
2430 *pcbRead = cbReadTotal;
2431 }
2432
2433 return rc;
2434}
2435
2436/**
2437 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
2438 */
2439static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface,
2440 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest,
2441 PPDMAUDIOSTREAM *ppStream)
2442{
2443 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2444 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
2445 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
2446 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
2447
2448 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2449
2450 int rc = RTCritSectEnter(&pThis->CritSect);
2451 if (RT_FAILURE(rc))
2452 return rc;
2453
2454 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
2455#ifdef DEBUG
2456 DrvAudioHlpStreamCfgPrint(pCfgHost);
2457 DrvAudioHlpStreamCfgPrint(pCfgGuest);
2458#endif
2459
2460 /*
2461 * The guest stream always will get the audio stream configuration told
2462 * by the device emulation (which in turn was/could be set by the guest OS).
2463 */
2464 PPDMAUDIOSTREAM pGstStrm = NULL;
2465
2466 /** @todo Docs! */
2467 PPDMAUDIOSTREAM pHstStrm = NULL;
2468
2469#define RC_BREAK(x) { rc = x; break; }
2470
2471 do
2472 {
2473 if ( !DrvAudioHlpStreamCfgIsValid(pCfgHost)
2474 || !DrvAudioHlpStreamCfgIsValid(pCfgGuest))
2475 {
2476 RC_BREAK(VERR_INVALID_PARAMETER);
2477 }
2478
2479 /* Make sure that both configurations actually intend the same thing. */
2480 if (pCfgHost->enmDir != pCfgGuest->enmDir)
2481 {
2482 AssertMsgFailed(("Stream configuration directions do not match\n"));
2483 RC_BREAK(VERR_INVALID_PARAMETER);
2484 }
2485
2486 /* Note: cbHstStrm will contain the size of the data the backend needs to operate on. */
2487 size_t cbHstStrm = 0;
2488 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2489 {
2490 if (!pThis->In.cStreamsFree)
2491 LogFunc(("Warning: No more input streams free to use\n"));
2492
2493 cbHstStrm = pThis->BackendCfg.cbStreamIn;
2494 }
2495 else /* Out */
2496 {
2497 if (!pThis->Out.cStreamsFree)
2498 {
2499 LogFlowFunc(("Maximum number of host output streams reached\n"));
2500 RC_BREAK(VERR_AUDIO_NO_FREE_OUTPUT_STREAMS);
2501 }
2502
2503 cbHstStrm = pThis->BackendCfg.cbStreamOut;
2504 }
2505
2506 pHstStrm = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM));
2507 AssertPtrBreakStmt(pHstStrm, rc = VERR_NO_MEMORY);
2508
2509 if (cbHstStrm) /* High unlikely that backends do not have an own space for data, but better check. */
2510 {
2511 pHstStrm->pvBackend = RTMemAllocZ(cbHstStrm);
2512 AssertPtrBreakStmt(pHstStrm->pvBackend, rc = VERR_NO_MEMORY);
2513
2514 pHstStrm->cbBackend = cbHstStrm;
2515 }
2516
2517 pHstStrm->enmCtx = PDMAUDIOSTREAMCTX_HOST;
2518 pHstStrm->enmDir = pCfgHost->enmDir;
2519
2520 pGstStrm = (PPDMAUDIOSTREAM)RTMemAllocZ(sizeof(PDMAUDIOSTREAM));
2521 AssertPtrBreakStmt(pGstStrm, rc = VERR_NO_MEMORY);
2522
2523 pGstStrm->enmCtx = PDMAUDIOSTREAMCTX_GUEST;
2524 pGstStrm->enmDir = pCfgGuest->enmDir;
2525
2526 /* Retrieve host driver name for easier identification. */
2527 AssertPtr(pThis->pHostDrvAudio);
2528 PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase);
2529 AssertPtr(pDrvAudioInst);
2530 AssertPtr(pDrvAudioInst->pReg);
2531
2532 char szDriver[64];
2533 RTStrPrintf(szDriver, RT_ELEMENTS(szDriver), "%s", pDrvAudioInst->pReg->szName);
2534 if (!strlen(szDriver))
2535 {
2536 RTStrPrintf(szDriver, RT_ELEMENTS(szDriver), "Untitled");
2537 AssertFailed(); /* Should never happen. */
2538 }
2539
2540 /*
2541 * Init host stream.
2542 */
2543 RTStrPrintf(pHstStrm->szName, RT_ELEMENTS(pHstStrm->szName), "[%s] %s (Host)",
2544 szDriver, strlen(pCfgHost->szName) ? pCfgHost->szName : "<Untitled>");
2545
2546 rc = drvAudioStreamLinkToInternal(pHstStrm, pGstStrm);
2547 AssertRCBreak(rc);
2548
2549 /*
2550 * Init guest stream.
2551 */
2552 RTStrPrintf(pGstStrm->szName, RT_ELEMENTS(pGstStrm->szName), "[%s] %s (Guest)",
2553 szDriver, strlen(pCfgGuest->szName) ? pCfgGuest->szName : "<Untitled>");
2554
2555 pGstStrm->fStatus = pHstStrm->fStatus; /* Reflect the host stream's status. */
2556
2557 rc = drvAudioStreamLinkToInternal(pGstStrm, pHstStrm);
2558 AssertRCBreak(rc);
2559
2560 /*
2561 * Try to init the rest.
2562 */
2563 rc = drvAudioStreamInitInternal(pThis, pHstStrm, pCfgHost, pCfgGuest);
2564 if (RT_FAILURE(rc))
2565 break;
2566
2567#ifdef VBOX_WITH_STATISTICS
2568 char szStatName[255];
2569
2570 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
2571 {
2572 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesElapsed", pGstStrm->szName);
2573 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pGstStrm->In.StatBytesElapsed,
2574 szStatName, STAMUNIT_BYTES, "Elapsed bytes read.");
2575
2576 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesRead", pGstStrm->szName);
2577 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pGstStrm->In.StatBytesTotalRead,
2578 szStatName, STAMUNIT_BYTES, "Total bytes read.");
2579
2580 RTStrPrintf(szStatName, sizeof(szStatName), "Host/%s/FramesCaptured", pHstStrm->szName);
2581 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pHstStrm->In.StatFramesCaptured,
2582 szStatName, STAMUNIT_COUNT, "Total frames captured.");
2583 }
2584 else if (pCfgGuest->enmDir == PDMAUDIODIR_OUT)
2585 {
2586 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesElapsed", pGstStrm->szName);
2587 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pGstStrm->Out.StatBytesElapsed,
2588 szStatName, STAMUNIT_BYTES, "Elapsed bytes written.");
2589
2590 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/BytesWritten", pGstStrm->szName);
2591 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pGstStrm->Out.StatBytesTotalWritten,
2592 szStatName, STAMUNIT_BYTES, "Total bytes written.");
2593
2594 RTStrPrintf(szStatName, sizeof(szStatName), "Host/%s/FramesPlayed", pHstStrm->szName);
2595 PDMDrvHlpSTAMRegCounterEx(pThis->pDrvIns, &pHstStrm->Out.StatFramesPlayed,
2596 szStatName, STAMUNIT_COUNT, "Total frames played.");
2597 }
2598 else
2599 AssertFailed();
2600#endif
2601
2602 } while (0);
2603
2604#undef RC_BREAK
2605
2606 if (RT_FAILURE(rc))
2607 {
2608 if (pGstStrm)
2609 {
2610 int rc2 = drvAudioStreamUninitInternal(pThis, pGstStrm);
2611 if (RT_SUCCESS(rc2))
2612 {
2613 RTMemFree(pGstStrm);
2614 pGstStrm = NULL;
2615 }
2616 }
2617
2618 if (pHstStrm)
2619 {
2620 int rc2 = drvAudioStreamUninitInternal(pThis, pHstStrm);
2621 if (RT_SUCCESS(rc2))
2622 {
2623 drvAudioStreamFree(pHstStrm);
2624 pHstStrm = NULL;
2625 }
2626 }
2627 }
2628 else
2629 {
2630 /* Set initial reference counts. */
2631 RTListAppend(&pThis->lstGstStreams, &pGstStrm->Node);
2632 pGstStrm->cRefs = 1;
2633
2634 RTListAppend(&pThis->lstHstStreams, &pHstStrm->Node);
2635 pHstStrm->cRefs = 1;
2636
2637 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2638 {
2639 if (pThis->In.cStreamsFree)
2640 pThis->In.cStreamsFree--;
2641 }
2642 else /* Out */
2643 {
2644 if (pThis->Out.cStreamsFree)
2645 pThis->Out.cStreamsFree--;
2646 }
2647
2648#ifdef VBOX_WITH_STATISTICS
2649 STAM_COUNTER_ADD(&pThis->Stats.TotalStreamsCreated, 1);
2650#endif
2651 /* Always return the guest-side part to the device emulation. */
2652 *ppStream = pGstStrm;
2653 }
2654
2655 int rc2 = RTCritSectLeave(&pThis->CritSect);
2656 if (RT_SUCCESS(rc))
2657 rc = rc2;
2658
2659 LogFlowFuncLeaveRC(rc);
2660 return rc;
2661}
2662
2663/**
2664 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
2665 */
2666static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
2667{
2668 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2669
2670 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2671
2672 int rc = RTCritSectEnter(&pThis->CritSect);
2673 if (RT_FAILURE(rc))
2674 return rc;
2675
2676 bool *pfEnabled;
2677 if (enmDir == PDMAUDIODIR_IN)
2678 pfEnabled = &pThis->In.fEnabled;
2679 else if (enmDir == PDMAUDIODIR_OUT)
2680 pfEnabled = &pThis->Out.fEnabled;
2681 else
2682 AssertFailedReturn(VERR_INVALID_PARAMETER);
2683
2684 if (fEnable != *pfEnabled)
2685 {
2686 PPDMAUDIOSTREAM pStream;
2687 RTListForEach(&pThis->lstHstStreams, pStream, PDMAUDIOSTREAM, Node)
2688 {
2689 if (pStream->enmDir != enmDir) /* Skip unwanted streams. */
2690 continue;
2691
2692 int rc2 = drvAudioStreamControlInternal(pThis, pStream,
2693 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
2694 if (RT_FAILURE(rc2))
2695 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n",
2696 fEnable ? "enable" : "disable", enmDir == PDMAUDIODIR_IN ? "input" : "output", pStream->szName, rc2));
2697
2698 if (RT_SUCCESS(rc))
2699 rc = rc2;
2700
2701 /* Keep going. */
2702 }
2703
2704 *pfEnabled = fEnable;
2705 }
2706
2707 int rc3 = RTCritSectLeave(&pThis->CritSect);
2708 if (RT_SUCCESS(rc))
2709 rc = rc3;
2710
2711 LogFlowFuncLeaveRC(rc);
2712 return rc;
2713}
2714
2715/**
2716 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
2717 */
2718static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
2719{
2720 AssertPtrReturn(pInterface, false);
2721
2722 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2723
2724 int rc2 = RTCritSectEnter(&pThis->CritSect);
2725 if (RT_FAILURE(rc2))
2726 return false;
2727
2728 bool *pfEnabled;
2729 if (enmDir == PDMAUDIODIR_IN)
2730 pfEnabled = &pThis->In.fEnabled;
2731 else if (enmDir == PDMAUDIODIR_OUT)
2732 pfEnabled = &pThis->Out.fEnabled;
2733 else
2734 AssertFailedReturn(false);
2735
2736 const bool fIsEnabled = *pfEnabled;
2737
2738 rc2 = RTCritSectLeave(&pThis->CritSect);
2739 AssertRC(rc2);
2740
2741 return fIsEnabled;
2742}
2743
2744/**
2745 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
2746 */
2747static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
2748{
2749 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2750 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
2751
2752 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2753
2754 int rc = RTCritSectEnter(&pThis->CritSect);
2755 if (RT_FAILURE(rc))
2756 return rc;
2757
2758 if (pThis->pHostDrvAudio)
2759 {
2760 if (pThis->pHostDrvAudio->pfnGetConfig)
2761 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
2762 else
2763 rc = VERR_NOT_SUPPORTED;
2764 }
2765 else
2766 AssertFailed();
2767
2768 int rc2 = RTCritSectLeave(&pThis->CritSect);
2769 if (RT_SUCCESS(rc))
2770 rc = rc2;
2771
2772 LogFlowFuncLeaveRC(rc);
2773 return rc;
2774}
2775
2776/**
2777 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
2778 */
2779static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
2780{
2781 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
2782
2783 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2784
2785 PDMAUDIOBACKENDSTS backendSts = PDMAUDIOBACKENDSTS_UNKNOWN;
2786
2787 int rc = RTCritSectEnter(&pThis->CritSect);
2788 if (RT_SUCCESS(rc))
2789 {
2790 if ( pThis->pHostDrvAudio
2791 && pThis->pHostDrvAudio->pfnGetStatus)
2792 {
2793 backendSts = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
2794 }
2795
2796 int rc2 = RTCritSectLeave(&pThis->CritSect);
2797 if (RT_SUCCESS(rc))
2798 rc = rc2;
2799 }
2800
2801 LogFlowFuncLeaveRC(rc);
2802 return backendSts;
2803}
2804
2805/**
2806 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
2807 */
2808static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2809{
2810 AssertPtrReturn(pInterface, 0);
2811 AssertPtrReturn(pStream, 0);
2812
2813 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2814
2815 int rc2 = RTCritSectEnter(&pThis->CritSect);
2816 AssertRC(rc2);
2817
2818 AssertMsg(pStream->enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
2819
2820 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2821 if (!pHstStream) /* No host stream available? Bail out early. */
2822 {
2823 rc2 = RTCritSectLeave(&pThis->CritSect);
2824 AssertRC(rc2);
2825
2826 return 0;
2827 }
2828
2829 uint32_t cReadable = 0;
2830
2831 PPDMAUDIOSTREAM pGstStream = pHstStream->pPair;
2832 if (pGstStream)
2833 cReadable = AudioMixBufLive(&pGstStream->MixBuf);
2834
2835 Log3Func(("[%s] cbReadable=%RU32 (%zu bytes)\n", pHstStream->szName, cReadable,
2836 AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cReadable)));
2837
2838 uint32_t cbReadable = AUDIOMIXBUF_F2B(&pGstStream->MixBuf, cReadable);
2839
2840 rc2 = RTCritSectLeave(&pThis->CritSect);
2841 AssertRC(rc2);
2842
2843 /* Return bytes instead of audio frames. */
2844 return cbReadable;
2845}
2846
2847/**
2848 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
2849 */
2850static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2851{
2852 AssertPtrReturn(pInterface, 0);
2853 AssertPtrReturn(pStream, 0);
2854
2855 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2856
2857 int rc2 = RTCritSectEnter(&pThis->CritSect);
2858 AssertRC(rc2);
2859
2860 AssertMsg(pStream->enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"));
2861
2862 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2863 if (!pHstStream) /* No host stream available? Bail out early. */
2864 {
2865 rc2 = RTCritSectLeave(&pThis->CritSect);
2866 AssertRC(rc2);
2867
2868 AssertMsgFailed(("Guest stream '%s' does not have a host stream attached\n", pStream->szName));
2869 return 0;
2870 }
2871
2872 /* As the host side sets the overall pace, return the writable bytes from that side. */
2873 uint32_t cbWritable = AudioMixBufFreeBytes(&pHstStream->MixBuf);
2874
2875 Log3Func(("[%s] cbWritable=%RU32\n", pHstStream->szName, cbWritable));
2876
2877 rc2 = RTCritSectLeave(&pThis->CritSect);
2878 AssertRC(rc2);
2879
2880 return cbWritable;
2881}
2882
2883/**
2884 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
2885 */
2886static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2887{
2888 AssertPtrReturn(pInterface, false);
2889
2890 if (!pStream)
2891 return PDMAUDIOSTREAMSTS_FLAG_NONE;
2892
2893 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2894
2895 int rc2 = RTCritSectEnter(&pThis->CritSect);
2896 AssertRC(rc2);
2897
2898 PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
2899
2900 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2901 if (pHstStream)
2902 {
2903 strmSts = pHstStream->fStatus;
2904#ifdef LOG_ENABLED
2905 char *pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
2906 Log3Func(("[%s] %s\n", pHstStream->szName, pszHstSts));
2907 RTStrFree(pszHstSts);
2908#endif
2909 }
2910
2911 rc2 = RTCritSectLeave(&pThis->CritSect);
2912 AssertRC(rc2);
2913
2914 return strmSts;
2915}
2916
2917/**
2918 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
2919 */
2920static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
2921{
2922 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2923 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2924 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
2925
2926 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStream->szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
2927
2928 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2929 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : pStream;
2930
2931 AudioMixBufSetVolume(&pHstStream->MixBuf, pVol);
2932 AudioMixBufSetVolume(&pGstStream->MixBuf, pVol);
2933 return VINF_SUCCESS;
2934}
2935
2936/**
2937 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
2938 */
2939static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2940{
2941 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
2942 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
2943
2944 PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
2945
2946 int rc = RTCritSectEnter(&pThis->CritSect);
2947 AssertRC(rc);
2948
2949 PDMAUDIODIR enmDir = pStream->enmDir;
2950
2951 LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
2952 if (pStream->cRefs > 1)
2953 rc = VERR_WRONG_ORDER;
2954
2955 if (RT_SUCCESS(rc))
2956 {
2957 PPDMAUDIOSTREAM pHstStream = drvAudioGetHostStream(pStream);
2958 PPDMAUDIOSTREAM pGstStream = pHstStream ? pHstStream->pPair : pStream;
2959
2960 LogRel2(("Audio: Destroying host stream '%s' (guest stream '%s')\n",
2961 pHstStream ? pHstStream->szName : "<None>",
2962 pGstStream ? pGstStream->szName : "<None>"));
2963
2964 /* Should prevent double frees. */
2965 Assert(pHstStream != pGstStream);
2966
2967 if (pHstStream)
2968 {
2969 rc = drvAudioStreamUninitInternal(pThis, pHstStream);
2970 if (RT_SUCCESS(rc))
2971 {
2972#ifdef VBOX_WITH_STATISTICS
2973 if (pHstStream->enmDir == PDMAUDIODIR_IN)
2974 {
2975 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pHstStream->In.StatFramesCaptured);
2976 }
2977 else if (pHstStream->enmDir == PDMAUDIODIR_OUT)
2978 {
2979 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pHstStream->Out.StatFramesPlayed);
2980 }
2981 else
2982 AssertFailed();
2983#endif
2984 RTListNodeRemove(&pHstStream->Node);
2985
2986 drvAudioStreamFree(pHstStream);
2987 pHstStream = NULL;
2988 }
2989 else
2990 LogRel2(("Audio: Uninitializing host stream '%s' failed with %Rrc\n", pHstStream->szName, rc));
2991 }
2992
2993 if ( RT_SUCCESS(rc)
2994 && pGstStream)
2995 {
2996 rc = drvAudioStreamUninitInternal(pThis, pGstStream);
2997 if (RT_SUCCESS(rc))
2998 {
2999#ifdef VBOX_WITH_STATISTICS
3000 if (pGstStream->enmDir == PDMAUDIODIR_IN)
3001 {
3002 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->In.StatBytesElapsed);
3003 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->In.StatBytesTotalRead);
3004 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->In.StatFramesCaptured);
3005 }
3006 else if (pGstStream->enmDir == PDMAUDIODIR_OUT)
3007 {
3008 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->Out.StatBytesElapsed);
3009 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->Out.StatBytesTotalWritten);
3010 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pGstStream->Out.StatFramesPlayed);
3011 }
3012 else
3013 AssertFailed();
3014#endif
3015 RTListNodeRemove(&pGstStream->Node);
3016
3017 RTMemFree(pGstStream);
3018 pGstStream = NULL;
3019 }
3020 else
3021 LogRel2(("Audio: Uninitializing guest stream '%s' failed with %Rrc\n", pGstStream->szName, rc));
3022 }
3023 }
3024
3025 if (RT_SUCCESS(rc))
3026 {
3027 if (enmDir == PDMAUDIODIR_IN)
3028 {
3029 pThis->In.cStreamsFree++;
3030 }
3031 else /* Out */
3032 {
3033 pThis->Out.cStreamsFree++;
3034 }
3035 }
3036
3037 int rc2 = RTCritSectLeave(&pThis->CritSect);
3038 if (RT_SUCCESS(rc))
3039 rc = rc2;
3040
3041 LogFlowFuncLeaveRC(rc);
3042 return rc;
3043}
3044
3045/**
3046 * Creates an audio stream on the backend side.
3047 *
3048 * @returns IPRT status code.
3049 * @param pThis Pointer to driver instance.
3050 * @param pHstStream (Host) audio stream to use for creating the stream on the backend side.
3051 * @param pCfgReq Requested audio stream configuration to use for stream creation.
3052 * @param pCfgAcq Acquired audio stream configuration returned by the backend. Optional, can be NULL.
3053 */
3054static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis,
3055 PPDMAUDIOSTREAM pHstStream, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
3056{
3057 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3058 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
3059 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
3060 /* pCfgAcq is optional. */
3061
3062 AssertMsg(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST,
3063 ("Stream '%s' is not a host stream and therefore has no backend\n", pHstStream->szName));
3064
3065 AssertMsg((pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED) == 0,
3066 ("Stream '%s' already initialized in backend\n", pHstStream->szName));
3067
3068 /* Make the acquired host configuration the requested host configuration initially,
3069 * in case the backend does not report back an acquired configuration. */
3070 PDMAUDIOSTREAMCFG CfgAcq;
3071 int rc = DrvAudioHlpStreamCfgCopy(&CfgAcq, pCfgReq);
3072 if (RT_FAILURE(rc))
3073 {
3074 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
3075 pHstStream->szName));
3076 return rc;
3077 }
3078
3079 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pHstStream->pvBackend, pCfgReq, &CfgAcq);
3080 if (RT_FAILURE(rc))
3081 {
3082 if (rc == VERR_NOT_SUPPORTED)
3083 LogRel2(("Audio: Creating stream '%s' in backend not supported, skipping\n", pHstStream->szName));
3084 else
3085 LogRel2(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pHstStream->szName, rc));
3086
3087 return rc;
3088 }
3089
3090 /* Validate acquired configuration. */
3091 if (!DrvAudioHlpStreamCfgIsValid(&CfgAcq))
3092 {
3093 LogRel(("Audio: Creating stream '%s' returned an invalid backend configuration, skipping\n", pHstStream->szName));
3094 return VERR_INVALID_PARAMETER;
3095 }
3096
3097 /* Only set the host's stream to initialized if we were able create the stream
3098 * in the host backend. This is necessary for trying to re-initialize the stream
3099 * at some later point in time. */
3100 pHstStream->fStatus |= PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
3101
3102 if (pCfgAcq)
3103 {
3104 int rc2 = DrvAudioHlpStreamCfgCopy(pCfgAcq, &CfgAcq);
3105 AssertRC(rc2);
3106 }
3107
3108 return VINF_SUCCESS;
3109}
3110
3111/**
3112 * Calls the backend to give it the chance to destroy its part of the audio stream.
3113 *
3114 * @returns IPRT status code.
3115 * @param pThis Pointer to driver instance.
3116 * @param pHstStream Host audio stream to call the backend destruction for.
3117 */
3118static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PPDMAUDIOSTREAM pHstStream)
3119{
3120 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3121 AssertPtrReturn(pHstStream, VERR_INVALID_POINTER);
3122
3123 AssertMsg(pHstStream->enmCtx == PDMAUDIOSTREAMCTX_HOST,
3124 ("Stream '%s' is not a host stream and therefore has no backend\n", pHstStream->szName));
3125
3126 int rc = VINF_SUCCESS;
3127
3128#ifdef LOG_ENABLED
3129 char *pszHstSts = dbgAudioStreamStatusToStr(pHstStream->fStatus);
3130 LogFunc(("[%s] fStatus=%s\n", pHstStream->szName, pszHstSts));
3131 RTStrFree(pszHstSts);
3132#endif /* LOG_ENABLED */
3133
3134 if (pHstStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)
3135 {
3136 /* Check if the pointer to the host audio driver is still valid.
3137 * It can be NULL if we were called in drvAudioDestruct, for example. */
3138 if (pThis->pHostDrvAudio)
3139 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pHstStream->pvBackend);
3140 if (RT_SUCCESS(rc))
3141 {
3142 pHstStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
3143#if 0 /** @todo r=andy Disabled for now -- need to test this on Windows hosts. */
3144 Assert(pHstStream->fStatus == PDMAUDIOSTRMSTS_FLAG_NONE);
3145#endif
3146 }
3147 }
3148
3149 LogFlowFunc(("[%s] Returning %Rrc\n", pHstStream->szName, rc));
3150 return rc;
3151}
3152
3153/**
3154 * Uninitializes an audio stream.
3155 *
3156 * @returns IPRT status code.
3157 * @param pThis Pointer to driver instance.
3158 * @param pStream Pointer to audio stream to uninitialize.
3159 */
3160static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PPDMAUDIOSTREAM pStream)
3161{
3162 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3163 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
3164
3165 LogFlowFunc(("[%s] cRefs=%RU32\n", pStream->szName, pStream->cRefs));
3166
3167 if (pStream->cRefs > 1)
3168 return VERR_WRONG_ORDER;
3169
3170 int rc = VINF_SUCCESS;
3171
3172 if (pStream->enmCtx == PDMAUDIOSTREAMCTX_GUEST)
3173 {
3174 if (pStream->fStatus & PDMAUDIOSTREAMSTS_FLAG_INITIALIZED)
3175 {
3176 rc = drvAudioStreamControlInternal(pThis, pStream, PDMAUDIOSTREAMCMD_DISABLE);
3177 if (RT_SUCCESS(rc))
3178 {
3179 pStream->fStatus &= ~PDMAUDIOSTREAMSTS_FLAG_INITIALIZED;
3180 Assert(pStream->fStatus == PDMAUDIOSTREAMSTS_FLAG_NONE);
3181 }
3182 }
3183 }
3184 else if (pStream->enmCtx == PDMAUDIOSTREAMCTX_HOST)
3185 {
3186 rc = drvAudioStreamDestroyInternalBackend(pThis, pStream);
3187 }
3188 else
3189 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
3190
3191 if (RT_SUCCESS(rc))
3192 {
3193 /* Make sure that the pair (if any) knows that we're not valid anymore. */
3194 int rc2 = drvAudioStreamLinkToInternal(pStream, NULL);
3195 AssertRC(rc2);
3196
3197 /* Reset status. */
3198 pStream->fStatus = PDMAUDIOSTREAMSTS_FLAG_NONE;
3199
3200 /* Destroy mixing buffer. */
3201 AudioMixBufDestroy(&pStream->MixBuf);
3202 }
3203
3204 LogFlowFunc(("Returning %Rrc\n", rc));
3205 return rc;
3206}
3207
3208/********************************************************************/
3209
3210/**
3211 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
3212 */
3213static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3214{
3215 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
3216
3217 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3218 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3219
3220 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3221 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
3222
3223 return NULL;
3224}
3225
3226/**
3227 * Power Off notification.
3228 *
3229 * @param pDrvIns The driver instance data.
3230 */
3231static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
3232{
3233 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3234
3235 LogFlowFuncEnter();
3236
3237 /* Just destroy the host stream on the backend side.
3238 * The rest will either be destructed by the device emulation or
3239 * in drvAudioDestruct(). */
3240 PPDMAUDIOSTREAM pStream;
3241 RTListForEach(&pThis->lstHstStreams, pStream, PDMAUDIOSTREAM, Node)
3242 drvAudioStreamDestroyInternalBackend(pThis, pStream);
3243
3244 /*
3245 * Last call for the driver below us.
3246 * Let it know that we reached end of life.
3247 */
3248 if (pThis->pHostDrvAudio->pfnShutdown)
3249 pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);
3250
3251 pThis->pHostDrvAudio = NULL;
3252
3253 LogFlowFuncLeave();
3254}
3255
3256/**
3257 * Constructs an audio driver instance.
3258 *
3259 * @copydoc FNPDMDRVCONSTRUCT
3260 */
3261static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
3262{
3263 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
3264
3265 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3266 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3267
3268 RTListInit(&pThis->lstHstStreams);
3269 RTListInit(&pThis->lstGstStreams);
3270#ifdef VBOX_WITH_AUDIO_CALLBACKS
3271 RTListInit(&pThis->In.lstCB);
3272 RTListInit(&pThis->Out.lstCB);
3273#endif
3274
3275 /*
3276 * Init the static parts.
3277 */
3278 pThis->pDrvIns = pDrvIns;
3279 /* IBase. */
3280 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
3281 /* IAudioConnector. */
3282 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
3283 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
3284 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
3285 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
3286 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
3287 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
3288 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
3289 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
3290 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
3291 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
3292 pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite;
3293 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
3294 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
3295 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
3296 pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
3297 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
3298 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
3299 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
3300#ifdef VBOX_WITH_AUDIO_CALLBACKS
3301 pThis->IAudioConnector.pfnRegisterCallbacks = drvAudioRegisterCallbacks;
3302#endif
3303
3304 /*
3305 * Attach driver below and query its connector interface.
3306 */
3307 PPDMIBASE pDownBase;
3308 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
3309 if (RT_FAILURE(rc))
3310 {
3311 LogRel(("Audio: Failed to attach to driver %p below (flags=0x%x), rc=%Rrc\n",
3312 pDrvIns, fFlags, rc));
3313 return rc;
3314 }
3315
3316 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
3317 if (!pThis->pHostDrvAudio)
3318 {
3319 LogRel(("Audio: Failed to query interface for underlying host driver\n"));
3320 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
3321 N_("Host audio backend missing or invalid"));
3322 }
3323
3324 rc = drvAudioInit(pDrvIns, pCfg);
3325 if (RT_SUCCESS(rc))
3326 {
3327 pThis->fTerminate = false;
3328 pThis->pDrvIns = pDrvIns;
3329
3330#ifdef VBOX_WITH_STATISTICS
3331 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
3332 STAMUNIT_COUNT, "Total active audio streams.");
3333 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
3334 STAMUNIT_COUNT, "Total created audio streams.");
3335 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
3336 STAMUNIT_COUNT, "Total frames read by device emulation.");
3337 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten",
3338 STAMUNIT_COUNT, "Total frames written by device emulation ");
3339 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn",
3340 STAMUNIT_COUNT, "Total input frames mixed.");
3341 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut",
3342 STAMUNIT_COUNT, "Total output frames mixed.");
3343 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn",
3344 STAMUNIT_COUNT, "Total input frames lost.");
3345 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut",
3346 STAMUNIT_COUNT, "Total output frames lost.");
3347 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesPlayed",
3348 STAMUNIT_COUNT, "Total frames played by backend.");
3349 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesCaptured",
3350 STAMUNIT_COUNT, "Total frames captured by backend.");
3351 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
3352 STAMUNIT_BYTES, "Total bytes read.");
3353 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten",
3354 STAMUNIT_BYTES, "Total bytes written.");
3355
3356 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn",
3357 STAMUNIT_NS_PER_CALL, "Profiling of input data processing.");
3358 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut",
3359 STAMUNIT_NS_PER_CALL, "Profiling of output data processing.");
3360#endif
3361
3362#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
3363 drvAudioDbgPCMDelete(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "StreamRead.pcm");
3364 drvAudioDbgPCMDelete(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "StreamWrite.pcm");
3365 drvAudioDbgPCMDelete(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "PlayNonInterleaved.pcm");
3366 drvAudioDbgPCMDelete(pThis, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "CaptureNonInterleaved.pcm");
3367#endif
3368 }
3369
3370 LogFlowFuncLeaveRC(rc);
3371 return rc;
3372}
3373
3374/**
3375 * Destructs an audio driver instance.
3376 *
3377 * @copydoc FNPDMDRVDESTRUCT
3378 */
3379static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
3380{
3381 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3382 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3383
3384 LogFlowFuncEnter();
3385
3386 int rc2 = RTCritSectEnter(&pThis->CritSect);
3387 AssertRC(rc2);
3388
3389 /*
3390 * Note: No calls here to the driver below us anymore,
3391 * as PDM already has destroyed it.
3392 * If you need to call something from the host driver,
3393 * do this in drvAudioPowerOff() instead.
3394 */
3395
3396 /* Thus, NULL the pointer to the host audio driver first,
3397 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
3398 pThis->pHostDrvAudio = NULL;
3399
3400 PPDMAUDIOSTREAM pStream, pStreamNext;
3401 RTListForEachSafe(&pThis->lstHstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node)
3402 {
3403 rc2 = drvAudioStreamUninitInternal(pThis, pStream);
3404 if (RT_SUCCESS(rc2))
3405 {
3406 RTListNodeRemove(&pStream->Node);
3407
3408 drvAudioStreamFree(pStream);
3409 pStream = NULL;
3410 }
3411 }
3412
3413 /* Sanity. */
3414 Assert(RTListIsEmpty(&pThis->lstHstStreams));
3415
3416 RTListForEachSafe(&pThis->lstGstStreams, pStream, pStreamNext, PDMAUDIOSTREAM, Node)
3417 {
3418 rc2 = drvAudioStreamUninitInternal(pThis, pStream);
3419 if (RT_SUCCESS(rc2))
3420 {
3421 RTListNodeRemove(&pStream->Node);
3422
3423 RTMemFree(pStream);
3424 pStream = NULL;
3425 }
3426 }
3427
3428 /* Sanity. */
3429 Assert(RTListIsEmpty(&pThis->lstGstStreams));
3430
3431#ifdef VBOX_WITH_AUDIO_CALLBACKS
3432 /*
3433 * Destroy callbacks, if any.
3434 */
3435 PPDMAUDIOCBRECORD pCB, pCBNext;
3436 RTListForEachSafe(&pThis->In.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
3437 drvAudioCallbackDestroy(pCB);
3438
3439 RTListForEachSafe(&pThis->Out.lstCB, pCB, pCBNext, PDMAUDIOCBRECORD, Node)
3440 drvAudioCallbackDestroy(pCB);
3441#endif
3442
3443 rc2 = RTCritSectLeave(&pThis->CritSect);
3444 AssertRC(rc2);
3445
3446 rc2 = RTCritSectDelete(&pThis->CritSect);
3447 AssertRC(rc2);
3448
3449#ifdef VBOX_WITH_STATISTICS
3450 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsActive);
3451 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalStreamsCreated);
3452 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesRead);
3453 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesWritten);
3454 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedIn);
3455 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesMixedOut);
3456 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostIn);
3457 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesLostOut);
3458 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesOut);
3459 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalFramesIn);
3460 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesRead);
3461 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.TotalBytesWritten);
3462 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayIn);
3463 PDMDrvHlpSTAMDeregister(pThis->pDrvIns, &pThis->Stats.DelayOut);
3464#endif
3465
3466 LogFlowFuncLeave();
3467}
3468
3469/**
3470 * Suspend notification.
3471 *
3472 * @param pDrvIns The driver instance data.
3473 */
3474static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
3475{
3476 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
3477}
3478
3479/**
3480 * Resume notification.
3481 *
3482 * @param pDrvIns The driver instance data.
3483 */
3484static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
3485{
3486 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
3487}
3488
3489/**
3490 * Attach notification.
3491 *
3492 * @param pDrvIns The driver instance data.
3493 * @param fFlags Attach flags.
3494 */
3495static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3496{
3497 RT_NOREF(fFlags);
3498
3499 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3500 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3501
3502 int rc2 = RTCritSectEnter(&pThis->CritSect);
3503 AssertRC(rc2);
3504
3505 int rc = VINF_SUCCESS;
3506
3507 LogFunc(("%s\n", pThis->szName));
3508
3509 rc2 = RTCritSectLeave(&pThis->CritSect);
3510 if (RT_SUCCESS(rc))
3511 rc = rc2;
3512
3513 return rc;
3514}
3515
3516/**
3517 * Detach notification.
3518 *
3519 * @param pDrvIns The driver instance data.
3520 * @param fFlags Detach flags.
3521 */
3522static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3523{
3524 RT_NOREF(fFlags);
3525
3526 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3527 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3528
3529 int rc2 = RTCritSectEnter(&pThis->CritSect);
3530 AssertRC(rc2);
3531
3532 pThis->pHostDrvAudio = NULL;
3533
3534 LogFunc(("%s\n", pThis->szName));
3535
3536 rc2 = RTCritSectLeave(&pThis->CritSect);
3537 AssertRC(rc2);
3538}
3539
3540/**
3541 * Audio driver registration record.
3542 */
3543const PDMDRVREG g_DrvAUDIO =
3544{
3545 /* u32Version */
3546 PDM_DRVREG_VERSION,
3547 /* szName */
3548 "AUDIO",
3549 /* szRCMod */
3550 "",
3551 /* szR0Mod */
3552 "",
3553 /* pszDescription */
3554 "Audio connector driver",
3555 /* fFlags */
3556 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
3557 /* fClass */
3558 PDM_DRVREG_CLASS_AUDIO,
3559 /* cMaxInstances */
3560 UINT32_MAX,
3561 /* cbInstance */
3562 sizeof(DRVAUDIO),
3563 /* pfnConstruct */
3564 drvAudioConstruct,
3565 /* pfnDestruct */
3566 drvAudioDestruct,
3567 /* pfnRelocate */
3568 NULL,
3569 /* pfnIOCtl */
3570 NULL,
3571 /* pfnPowerOn */
3572 NULL,
3573 /* pfnReset */
3574 NULL,
3575 /* pfnSuspend */
3576 drvAudioSuspend,
3577 /* pfnResume */
3578 drvAudioResume,
3579 /* pfnAttach */
3580 drvAudioAttach,
3581 /* pfnDetach */
3582 drvAudioDetach,
3583 /* pfnPowerOff */
3584 drvAudioPowerOff,
3585 /* pfnSoftReset */
3586 NULL,
3587 /* u32EndVersion */
3588 PDM_DRVREG_VERSION
3589};
3590
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