VirtualBox

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

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

Audio: Removed the pfnInit and pfnShutdown methods from PDMIHOSTAUDIO. These methods duplicates PDMDRVREG callbacks and were therefore superfluous. bugref:9890

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