VirtualBox

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

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

DrvAudio,ConsoleImpl2: Constructor cleanup, moved the input/output config and added range checks to it. bugref:9890

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

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