VirtualBox

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

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

Audio: More statistics tweaking. bugref:9890

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

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