VirtualBox

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

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

Audio: Made the pfnInit method of the PDMIHOSTAUDIO interface optional. Removed a bunch of stubs and moved the DSound init code to the constructor. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 157.3 KB
Line 
1/* $Id: DrvAudio.cpp 88381 2021-04-07 08:43:55Z 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 AssertPtrNullReturn(pHostDrvAudio->pfnInit, VERR_INVALID_POINTER);
2051 AssertPtrNullReturn(pHostDrvAudio->pfnShutdown, VERR_INVALID_POINTER);
2052 AssertPtrReturn(pHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
2053 AssertPtrNullReturn(pHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
2054 AssertPtrNullReturn(pHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
2055 AssertPtrReturn(pHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
2056 AssertPtrReturn(pHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
2057 AssertPtrReturn(pHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER);
2058 AssertPtrReturn(pHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
2059 AssertPtrReturn(pHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
2060 AssertPtrNullReturn(pHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
2061 AssertPtrReturn(pHostDrvAudio->pfnStreamGetStatus, VERR_INVALID_POINTER);
2062 AssertPtrReturn(pHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
2063 AssertPtrReturn(pHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
2064
2065 /*
2066 * Call the init method.
2067 */
2068 /** @todo r=bird: This is superfluous. This duplicates the driver
2069 * constructor code. Just get rid of it!! */
2070 AssertPtr(pThis->pHostDrvAudio);
2071 int rc = VINF_SUCCESS;
2072 if (pThis->pHostDrvAudio->pfnInit)
2073 rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
2074 if (RT_FAILURE(rc))
2075 {
2076 LogRel(("Audio: Initialization of host driver '%s' failed with %Rrc\n", pThis->szName, rc));
2077 return VERR_AUDIO_BACKEND_INIT_FAILED;
2078 }
2079
2080 /*
2081 * Get the backend configuration.
2082 */
2083 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);
2084 if (RT_FAILURE(rc))
2085 {
2086 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));
2087 return VERR_AUDIO_BACKEND_INIT_FAILED;
2088 }
2089
2090 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;
2091 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;
2092
2093 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
2094
2095 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
2096 pThis->szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
2097
2098#ifdef VBOX_WITH_AUDIO_ENUM
2099 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
2100 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
2101 AssertRC(rc2);
2102
2103 RT_NOREF(rc2);
2104 /* Ignore rc. */
2105#endif
2106
2107 LogFlowFuncLeave();
2108 return VINF_SUCCESS;
2109}
2110
2111/**
2112 * Handles state changes for all audio streams.
2113 *
2114 * @param pDrvIns Pointer to driver instance.
2115 * @param enmCmd Stream command to set for all streams.
2116 */
2117static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
2118{
2119 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2120 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
2121 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
2122
2123 int rc2 = RTCritSectEnter(&pThis->CritSect);
2124 AssertRCReturnVoid(rc2);
2125
2126 if (pThis->pHostDrvAudio)
2127 {
2128 PDRVAUDIOSTREAM pStreamEx;
2129 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
2130 {
2131 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
2132 }
2133 }
2134
2135 rc2 = RTCritSectLeave(&pThis->CritSect);
2136 AssertRC(rc2);
2137}
2138
2139/**
2140 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
2141 */
2142static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
2143 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2144{
2145 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2146 AssertPtr(pThis);
2147 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2148 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2149 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2150 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2151 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
2152 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2153 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2154 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
2155 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
2156 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
2157
2158 int rc = RTCritSectEnter(&pThis->CritSect);
2159 AssertRCReturn(rc, rc);
2160
2161 /*
2162 * ...
2163 */
2164 uint32_t cbReadTotal = 0;
2165
2166 do
2167 {
2168 uint32_t cfReadTotal = 0;
2169
2170 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf);
2171
2172 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */
2173 {
2174 if (!PDMAudioStrmStatusCanRead(pStream->fStatus))
2175 {
2176 rc = VERR_AUDIO_STREAM_NOT_READY;
2177 break;
2178 }
2179
2180 /*
2181 * Read from the parent buffer (that is, the guest buffer) which
2182 * should have the audio data in the format the guest needs.
2183 */
2184 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf));
2185 while (cfToRead)
2186 {
2187 uint32_t cfRead;
2188 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf,
2189 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
2190 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead);
2191 if (RT_FAILURE(rc))
2192 break;
2193
2194#ifdef VBOX_WITH_STATISTICS
2195 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead);
2196 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
2197 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead);
2198 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead);
2199#endif
2200 Assert(cfToRead >= cfRead);
2201 cfToRead -= cfRead;
2202
2203 cfReadTotal += cfRead;
2204
2205 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead);
2206 }
2207
2208 if (cfReadTotal)
2209 {
2210 if (pThis->In.Cfg.Dbg.fEnabled)
2211 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead,
2212 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
2213
2214 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal);
2215 }
2216 }
2217
2218 /* If we were not able to read as much data as requested, fill up the returned
2219 * data with silence.
2220 *
2221 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
2222 if (cfReadTotal < cfBuf)
2223 {
2224 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
2225 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal),
2226 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf)));
2227
2228 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props,
2229 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
2230 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal),
2231 cfBuf - cfReadTotal);
2232
2233 cfReadTotal = cfBuf;
2234 }
2235
2236 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal);
2237
2238 pStreamEx->nsLastReadWritten = RTTimeNanoTS();
2239
2240 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
2241
2242 } while (0);
2243
2244 RTCritSectLeave(&pThis->CritSect);
2245
2246 if (RT_SUCCESS(rc) && pcbRead)
2247 *pcbRead = cbReadTotal;
2248 return rc;
2249}
2250
2251/**
2252 * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that
2253 * creates the backend (host driver) side of an audio stream.
2254 *
2255 * @returns VBox status code.
2256 * @param pThis Pointer to driver instance.
2257 * @param pStreamEx Audio stream to create the backend side for.
2258 * @param pCfgReq Requested audio stream configuration to use for
2259 * stream creation.
2260 * @param pCfgAcq Acquired audio stream configuration returned by
2261 * the backend.
2262 *
2263 * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):
2264 * - per global extra-data
2265 * - per-VM extra-data
2266 * - requested configuration (by pCfgReq)
2267 * - default value
2268 */
2269static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2270 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
2271{
2272 AssertMsg((pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) == 0,
2273 ("Stream '%s' already initialized in backend\n", pStreamEx->Core.szName));
2274
2275 /* Get the right configuration for the stream to be created. */
2276 PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;
2277
2278 /* Fill in the tweakable parameters into the requested host configuration.
2279 * All parameters in principle can be changed and returned by the backend via the acquired configuration. */
2280
2281 /*
2282 * PCM
2283 */
2284 if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
2285 {
2286 PDMAudioPropsSetSampleSize(&pCfgReq->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props));
2287 LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n",
2288 PDMAudioPropsSampleSize(&pCfgReq->Props), pStreamEx->Core.szName));
2289 }
2290
2291 if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */
2292 {
2293 pCfgReq->Props.uHz = pDrvCfg->Props.uHz;
2294 LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfgReq->Props.uHz, pStreamEx->Core.szName));
2295 }
2296
2297 if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */
2298 {
2299 pCfgReq->Props.fSigned = RT_BOOL(pDrvCfg->uSigned);
2300 LogRel2(("Audio: Using custom %s sample format for stream '%s'\n",
2301 pCfgReq->Props.fSigned ? "signed" : "unsigned", pStreamEx->Core.szName));
2302 }
2303
2304 if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */
2305 {
2306 pCfgReq->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian);
2307 LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n",
2308 pCfgReq->Props.fSwapEndian ? "swapped" : "original", pStreamEx->Core.szName));
2309 }
2310
2311 if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */
2312 {
2313 PDMAudioPropsSetChannels(&pCfgReq->Props, PDMAudioPropsChannels(&pDrvCfg->Props));
2314 LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pStreamEx->Core.szName));
2315 }
2316
2317 /* Validate PCM properties. */
2318 if (!AudioHlpPcmPropsAreValid(&pCfgReq->Props))
2319 {
2320 LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pStreamEx->Core.szName));
2321 return VERR_INVALID_PARAMETER;
2322 }
2323
2324 /*
2325 * Period size
2326 */
2327 const char *pszWhat = "device-specific";
2328 if (pDrvCfg->uPeriodSizeMs)
2329 {
2330 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPeriodSizeMs);
2331 pszWhat = "custom";
2332 }
2333
2334 if (!pCfgReq->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */
2335 {
2336 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 150 /*ms*/);
2337 pszWhat = "default";
2338 }
2339
2340 LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n",
2341 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod),
2342 pCfgReq->Backend.cFramesPeriod, pStreamEx->Core.szName));
2343
2344 /*
2345 * Buffer size
2346 */
2347 pszWhat = "device-specific";
2348 if (pDrvCfg->uBufferSizeMs)
2349 {
2350 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uBufferSizeMs);
2351 pszWhat = "custom";
2352 }
2353
2354 if (!pCfgReq->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */
2355 {
2356 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 300 /*ms*/);
2357 pszWhat = "default";
2358 }
2359
2360 LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n",
2361 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
2362 pCfgReq->Backend.cFramesBufferSize, pStreamEx->Core.szName));
2363
2364 /*
2365 * Pre-buffering size
2366 */
2367 pszWhat = "device-specific";
2368 if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */
2369 {
2370 pCfgReq->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPreBufSizeMs);
2371 pszWhat = "custom";
2372 }
2373 else /* No, then either use the default or device-specific settings (if any). */
2374 {
2375 if (pCfgReq->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */
2376 {
2377 /* For pre-buffering to finish the buffer at least must be full one time. */
2378 pCfgReq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesBufferSize;
2379 pszWhat = "default";
2380 }
2381 }
2382
2383 LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n",
2384 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),
2385 pCfgReq->Backend.cFramesPreBuffering, pStreamEx->Core.szName));
2386
2387 /*
2388 * Validate input.
2389 */
2390 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPeriod)
2391 {
2392 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",
2393 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),
2394 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod)));
2395 return VERR_INVALID_PARAMETER;
2396 }
2397
2398 if ( pCfgReq->Backend.cFramesPreBuffering != UINT32_MAX /* Custom pre-buffering set? */
2399 && pCfgReq->Backend.cFramesPreBuffering)
2400 {
2401 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPreBuffering)
2402 {
2403 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",
2404 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),
2405 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize)));
2406 return VERR_INVALID_PARAMETER;
2407 }
2408 }
2409
2410 /*
2411 * Make the acquired host configuration the requested host configuration initially,
2412 * in case the backend does not report back an acquired configuration.
2413 */
2414 /** @todo r=bird: This is conveniently not documented in the interface... */
2415 int rc = PDMAudioStrmCfgCopy(pCfgAcq, pCfgReq);
2416 if (RT_FAILURE(rc))
2417 {
2418 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",
2419 pStreamEx->Core.szName));
2420 return rc;
2421 }
2422
2423 /*
2424 * Call the host driver to create the stream.
2425 */
2426 AssertPtr(pThis->pHostDrvAudio);
2427 if (pThis->pHostDrvAudio)
2428 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pvBackend, pCfgReq, pCfgAcq);
2429 else
2430 rc = VERR_PDM_NO_ATTACHED_DRIVER;
2431 if (RT_FAILURE(rc))
2432 {
2433 if (rc == VERR_NOT_SUPPORTED)
2434 LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.szName));
2435 else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)
2436 LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStreamEx->Core.szName));
2437 else
2438 LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.szName, rc));
2439 return rc;
2440 }
2441
2442 /* Validate acquired configuration. */
2443 char szTmp[PDMAUDIOPROPSTOSTRING_MAX];
2444 AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq),
2445 ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n",
2446 pStreamEx->Core.szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))),
2447 VERR_INVALID_PARAMETER);
2448
2449 /* Let the user know that the backend changed one of the values requested above. */
2450 if (pCfgAcq->Backend.cFramesBufferSize != pCfgReq->Backend.cFramesBufferSize)
2451 LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
2452 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));
2453
2454 if (pCfgAcq->Backend.cFramesPeriod != pCfgReq->Backend.cFramesPeriod)
2455 LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
2456 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod));
2457
2458 /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */
2459 if (pCfgReq->Backend.cFramesPreBuffering)
2460 {
2461 if (pCfgAcq->Backend.cFramesPreBuffering != pCfgReq->Backend.cFramesPreBuffering)
2462 LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",
2463 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
2464
2465 if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize)
2466 {
2467 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize;
2468 LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n",
2469 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));
2470 }
2471 }
2472 else if (pCfgReq->Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */
2473 {
2474 LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStreamEx->Core.szName));
2475 pCfgAcq->Backend.cFramesPreBuffering = 0;
2476 }
2477
2478 /* Sanity for detecting buggy backends. */
2479 AssertMsgReturn(pCfgAcq->Backend.cFramesPeriod < pCfgAcq->Backend.cFramesBufferSize,
2480 ("Acquired period size must be smaller than buffer size\n"),
2481 VERR_INVALID_PARAMETER);
2482 AssertMsgReturn(pCfgAcq->Backend.cFramesPreBuffering <= pCfgAcq->Backend.cFramesBufferSize,
2483 ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"),
2484 VERR_INVALID_PARAMETER);
2485
2486 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED;
2487
2488 return VINF_SUCCESS;
2489}
2490
2491
2492/**
2493 * Worker for drvAudioStreamCreate that initializes the audio stream.
2494 *
2495 * @returns VBox status code.
2496 * @param pThis Pointer to driver instance.
2497 * @param pStreamEx Stream to initialize.
2498 * @param fFlags PDMAUDIOSTREAM_CREATE_F_XXX.
2499 * @param pCfgHost Stream configuration to use for the host side (backend).
2500 * @param pCfgGuest Stream configuration to use for the guest side.
2501 */
2502static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fFlags,
2503 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)
2504{
2505 /*
2506 * Init host stream.
2507 */
2508 pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC;
2509
2510 /* Set the host's default audio data layout. */
2511/** @todo r=bird: Why, oh why? OTOH, the layout stuff is non-sense anyway. */
2512 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
2513
2514#ifdef LOG_ENABLED
2515 LogFunc(("[%s] Requested host format:\n", pStreamEx->Core.szName));
2516 PDMAudioStrmCfgLog(pCfgHost);
2517#endif
2518
2519 LogRel2(("Audio: Creating stream '%s'\n", pStreamEx->Core.szName));
2520 LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
2521 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
2522 pCfgGuest->Props.uHz, PDMAudioPropsSampleBits(&pCfgGuest->Props), pCfgGuest->Props.fSigned ? "S" : "U",
2523 PDMAudioPropsChannels(&pCfgGuest->Props), PDMAudioPropsChannels(&pCfgGuest->Props) == 1 ? "" : "s"));
2524 LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
2525 pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
2526 pCfgHost->Props.uHz, PDMAudioPropsSampleBits(&pCfgHost->Props), pCfgHost->Props.fSigned ? "S" : "U",
2527 PDMAudioPropsChannels(&pCfgHost->Props), PDMAudioPropsChannels(&pCfgHost->Props) == 1 ? "" : "s"));
2528
2529 PDMAUDIOSTREAMCFG CfgHostAcq;
2530 int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, pCfgHost, &CfgHostAcq);
2531 if (RT_FAILURE(rc))
2532 return rc;
2533
2534 LogFunc(("[%s] Acquired host format:\n", pStreamEx->Core.szName));
2535 PDMAudioStrmCfgLog(&CfgHostAcq);
2536 LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",
2537 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,
2538 CfgHostAcq.Props.uHz, PDMAudioPropsSampleBits(&CfgHostAcq.Props), CfgHostAcq.Props.fSigned ? "S" : "U",
2539 PDMAudioPropsChannels(&CfgHostAcq.Props), PDMAudioPropsChannels(&CfgHostAcq.Props) == 1 ? "" : "s"));
2540 Assert(PDMAudioPropsAreValid(&CfgHostAcq.Props));
2541
2542 /* Let the user know if the backend changed some of the tweakable values. */
2543 if (CfgHostAcq.Backend.cFramesBufferSize != pCfgHost->Backend.cFramesBufferSize)
2544 LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
2545 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesBufferSize), pCfgHost->Backend.cFramesBufferSize,
2546 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize));
2547
2548 if (CfgHostAcq.Backend.cFramesPeriod != pCfgHost->Backend.cFramesPeriod)
2549 LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
2550 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPeriod), pCfgHost->Backend.cFramesPeriod,
2551 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod), CfgHostAcq.Backend.cFramesPeriod));
2552
2553 if (CfgHostAcq.Backend.cFramesPreBuffering != pCfgHost->Backend.cFramesPreBuffering)
2554 LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",
2555 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPreBuffering), pCfgHost->Backend.cFramesPreBuffering,
2556 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering));
2557
2558 /*
2559 * Configure host buffers.
2560 */
2561
2562 /* Check if the backend did return sane values and correct if necessary.
2563 * Should never happen with our own backends, but you never know ... */
2564 if (CfgHostAcq.Backend.cFramesBufferSize < CfgHostAcq.Backend.cFramesPreBuffering)
2565 {
2566 LogRel2(("Audio: Warning: Pre-buffering size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), "
2567 "setting pre-buffering size to %RU32 frames\n",
2568 CfgHostAcq.Backend.cFramesPreBuffering, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize));
2569 CfgHostAcq.Backend.cFramesPreBuffering = CfgHostAcq.Backend.cFramesBufferSize;
2570 }
2571
2572 if (CfgHostAcq.Backend.cFramesPeriod > CfgHostAcq.Backend.cFramesBufferSize)
2573 {
2574 LogRel2(("Audio: Warning: Period size (%RU32 frames) of stream '%s' does not match buffer size (%RU32 frames), setting to %RU32 frames\n",
2575 CfgHostAcq.Backend.cFramesPeriod, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize));
2576 CfgHostAcq.Backend.cFramesPeriod = CfgHostAcq.Backend.cFramesBufferSize;
2577 }
2578
2579 uint64_t msBufferSize = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize);
2580 LogRel2(("Audio: Buffer size of stream '%s' is %RU64ms (%RU32 frames)\n",
2581 pStreamEx->Core.szName, msBufferSize, CfgHostAcq.Backend.cFramesBufferSize));
2582
2583 /* If no own pre-buffer is set, let the backend choose. */
2584 uint64_t msPreBuf = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering);
2585 LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64ms (%RU32 frames)\n",
2586 pStreamEx->Core.szName, msPreBuf, CfgHostAcq.Backend.cFramesPreBuffering));
2587
2588 /* Make sure the configured buffer size by the backend at least can hold the configured latency. */
2589 const uint32_t msPeriod = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod);
2590
2591 LogRel2(("Audio: Period size of stream '%s' is %RU64ms (%RU32 frames)\n",
2592 pStreamEx->Core.szName, msPeriod, CfgHostAcq.Backend.cFramesPeriod));
2593
2594 if ( pCfgGuest->Device.cMsSchedulingHint /* Any scheduling hint set? */
2595 && pCfgGuest->Device.cMsSchedulingHint > msPeriod) /* This might lead to buffer underflows. */
2596 {
2597 LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",
2598 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint, msPeriod));
2599 }
2600
2601 /* Destroy any former mixing buffer. */
2602 AudioMixBufDestroy(&pStreamEx->Host.MixBuf);
2603
2604 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
2605 {
2606 rc = AudioMixBufInit(&pStreamEx->Host.MixBuf, pStreamEx->Core.szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize);
2607 AssertRCReturn(rc, rc);
2608 }
2609
2610 /* Make a copy of the acquired host stream configuration. */
2611 rc = PDMAudioStrmCfgCopy(&pStreamEx->Host.Cfg, &CfgHostAcq);
2612 AssertRC(rc);
2613
2614 /*
2615 * Init guest stream.
2616 */
2617
2618 if (pCfgGuest->Device.cMsSchedulingHint)
2619 LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",
2620 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint,
2621 PDMAudioPropsMilliToBytes(&pCfgGuest->Props, pCfgGuest->Device.cMsSchedulingHint)));
2622
2623 /* Destroy any former mixing buffer. */
2624 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf);
2625
2626 /* Set the guests's default audio data layout. */
2627 pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;
2628
2629 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
2630 {
2631 rc = AudioMixBufInit(&pStreamEx->Guest.MixBuf, pStreamEx->Core.szName, &pCfgGuest->Props, CfgHostAcq.Backend.cFramesBufferSize);
2632 AssertRCReturn(rc, rc);
2633 }
2634
2635 /* Make a copy of the guest stream configuration. */
2636 rc = PDMAudioStrmCfgCopy(&pStreamEx->Guest.Cfg, pCfgGuest);
2637 AssertRC(rc);
2638
2639 if (RT_FAILURE(rc))
2640 LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
2641
2642 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
2643 {
2644 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
2645 {
2646 /* Host (Parent) -> Guest (Child). */
2647 rc = AudioMixBufLinkTo(&pStreamEx->Host.MixBuf, &pStreamEx->Guest.MixBuf);
2648 AssertRC(rc);
2649 }
2650 else
2651 {
2652 /* Guest (Parent) -> Host (Child). */
2653 rc = AudioMixBufLinkTo(&pStreamEx->Guest.MixBuf, &pStreamEx->Host.MixBuf);
2654 AssertRC(rc);
2655 }
2656 }
2657
2658 /*
2659 * Register statistics.
2660 */
2661 PPDMDRVINS const pDrvIns = pThis->pDrvIns;
2662 /** @todo expose config and more. */
2663 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2664 "Host side: The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.szName);
2665 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))
2666 {
2667 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2668 "Host side: The size of the mixer buffer (in frames)", "%s/1-HostMixBufSize", pStreamEx->Core.szName);
2669 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2670 "Guest side: The size of the mixer buffer (in frames)", "%s/2-GuestMixBufSize", pStreamEx->Core.szName);
2671 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
2672 {
2673 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cMixed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2674 "Host side: Number of frames in the mixer buffer", "%s/1-HostMixBufUsed", pStreamEx->Core.szName);
2675 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2676 "Guest side: Number of frames in the mixer buffer", "%s/2-GuestMixBufUsed", pStreamEx->Core.szName);
2677 }
2678 else
2679 {
2680 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2681 "Host side: Number of frames in the mixer buffer", "%s/1-HostMixBufUsed", pStreamEx->Core.szName);
2682 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cMixed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2683 "Guest side: Number of frames in the mixer buffer", "%s/2-GuestMixBufUsed", pStreamEx->Core.szName);
2684 }
2685 }
2686 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
2687 {
2688 /** @todo later? */
2689 }
2690 else
2691 {
2692 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2693 "Host side: Free space in backend buffer before play", "%s/0-HostBackendBufFreeBefore", pStreamEx->Core.szName);
2694 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,
2695 "Host side: Free space in backend buffer after play", "%s/0-HostBackendBufFreeAfter", pStreamEx->Core.szName);
2696 }
2697
2698#ifdef VBOX_WITH_STATISTICS
2699 char szStatName[255];
2700 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)
2701 {
2702 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesCaptured", pStreamEx->Core.szName);
2703 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->In.Stats.TotalFramesCaptured,
2704 szStatName, STAMUNIT_COUNT, "Total frames played.");
2705 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesCaptured", pStreamEx->Core.szName);
2706 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->In.Stats.TotalTimesCaptured,
2707 szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
2708 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesRead", pStreamEx->Core.szName);
2709 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->In.Stats.TotalFramesRead,
2710 szStatName, STAMUNIT_COUNT, "Total frames read.");
2711 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesRead", pStreamEx->Core.szName);
2712 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->In.Stats.TotalTimesRead,
2713 szStatName, STAMUNIT_COUNT, "Total number of reads.");
2714 }
2715 else
2716 {
2717 Assert(pCfgGuest->enmDir == PDMAUDIODIR_OUT);
2718 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesPlayed", pStreamEx->Core.szName);
2719 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->Out.Stats.TotalFramesPlayed,
2720 szStatName, STAMUNIT_COUNT, "Total frames played.");
2721
2722 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesPlayed", pStreamEx->Core.szName);
2723 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->Out.Stats.TotalTimesPlayed,
2724 szStatName, STAMUNIT_COUNT, "Total number of playbacks.");
2725 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalFramesWritten", pStreamEx->Core.szName);
2726 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->Out.Stats.TotalFramesWritten,
2727 szStatName, STAMUNIT_COUNT, "Total frames written.");
2728
2729 RTStrPrintf(szStatName, sizeof(szStatName), "Guest/%s/TotalTimesWritten", pStreamEx->Core.szName);
2730 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pStreamEx->Out.Stats.TotalTimesWritten,
2731 szStatName, STAMUNIT_COUNT, "Total number of writes.");
2732 }
2733#endif /* VBOX_WITH_STATISTICS */
2734
2735 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
2736 return rc;
2737}
2738
2739/**
2740 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}
2741 */
2742static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PPDMAUDIOSTREAMCFG pCfgHost,
2743 PPDMAUDIOSTREAMCFG pCfgGuest, PPDMAUDIOSTREAM *ppStream)
2744{
2745 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2746 AssertPtr(pThis);
2747
2748 /*
2749 * Assert sanity.
2750 */
2751 AssertReturn(!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS);
2752 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);
2753 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);
2754 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
2755 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));
2756#ifdef LOG_ENABLED
2757 PDMAudioStrmCfgLog(pCfgHost);
2758 PDMAudioStrmCfgLog(pCfgGuest);
2759#endif
2760 AssertReturn(AudioHlpStreamCfgIsValid(pCfgHost), VERR_INVALID_PARAMETER);
2761 AssertReturn(AudioHlpStreamCfgIsValid(pCfgGuest), VERR_INVALID_PARAMETER);
2762 AssertReturn(pCfgHost->enmDir == pCfgGuest->enmDir, VERR_MISMATCH);
2763 AssertReturn(pCfgHost->enmDir == PDMAUDIODIR_IN || pCfgHost->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED);
2764
2765 /*
2766 * Lock the whole driver instance.
2767 */
2768 int rc = RTCritSectEnter(&pThis->CritSect);
2769 AssertRCReturn(rc, rc);
2770
2771 /*
2772 * Check that we have free streams in the backend and get the
2773 * size of the backend specific stream data.
2774 */
2775 uint32_t *pcFreeStreams;
2776 size_t cbHstStrm;
2777 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2778 {
2779 if (!pThis->In.cStreamsFree)
2780 {
2781 LogFlowFunc(("Maximum number of host input streams reached\n"));
2782 rc = VERR_AUDIO_NO_FREE_INPUT_STREAMS;
2783 }
2784 pcFreeStreams = &pThis->In.cStreamsFree;
2785 cbHstStrm = pThis->BackendCfg.cbStreamIn;
2786 }
2787 else /* Out */
2788 {
2789 if (!pThis->Out.cStreamsFree)
2790 {
2791 LogFlowFunc(("Maximum number of host output streams reached\n"));
2792 rc = VERR_AUDIO_NO_FREE_OUTPUT_STREAMS;
2793 }
2794 pcFreeStreams = &pThis->Out.cStreamsFree;
2795 cbHstStrm = pThis->BackendCfg.cbStreamOut;
2796 }
2797 AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE);
2798 if (RT_SUCCESS(rc))
2799 {
2800 /*
2801 * Allocate and initialize common state.
2802 */
2803 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64));
2804 if (pStreamEx)
2805 {
2806 /* Retrieve host driver name for easier identification. */
2807 AssertPtr(pThis->pHostDrvAudio);
2808 PPDMDRVINS pDrvAudioInst = PDMIBASE_2_PDMDRV(pThis->pDrvIns->pDownBase);
2809 RTStrPrintf(pStreamEx->Core.szName, RT_ELEMENTS(pStreamEx->Core.szName), "[%s] %s",
2810 pDrvAudioInst && pDrvAudioInst->pReg && pDrvAudioInst->pReg->szName[0]
2811 ? pDrvAudioInst->pReg->szName : "none",
2812 pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");
2813
2814 pStreamEx->Core.enmDir = pCfgHost->enmDir;
2815 pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm;
2816 if (cbHstStrm)
2817 pStreamEx->pvBackend = pStreamEx + 1;
2818 pStreamEx->fNoMixBufs = RT_BOOL(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF);
2819 pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC;
2820
2821 /*
2822 * Try to init the rest.
2823 */
2824 rc = drvAudioStreamInitInternal(pThis, pStreamEx, fFlags, pCfgHost, pCfgGuest);
2825 if (RT_SUCCESS(rc))
2826 {
2827 /* Set initial reference counts. */
2828 pStreamEx->Core.cRefs = 1;
2829
2830 /* Decrement the free stream counter. */
2831 Assert(*pcFreeStreams > 0);
2832 *pcFreeStreams -= 1;
2833
2834 /*
2835 * We're good.
2836 */
2837 RTListAppend(&pThis->lstStreams, &pStreamEx->ListEntry);
2838 STAM_COUNTER_INC(&pThis->Stats.TotalStreamsCreated);
2839 *ppStream = &pStreamEx->Core;
2840
2841 /*
2842 * Init debug stuff if enabled (ignore failures).
2843 */
2844 if (pCfgHost->enmDir == PDMAUDIODIR_IN)
2845 {
2846 if (pThis->In.Cfg.Dbg.fEnabled)
2847 {
2848 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCaptureNonInterleaved, pThis->In.Cfg.Dbg.szPathOut,
2849 "DrvAudioCapNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
2850 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileStreamRead, pThis->In.Cfg.Dbg.szPathOut,
2851 "DrvAudioRead", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
2852 }
2853 }
2854 else /* Out */
2855 {
2856 if (pThis->Out.Cfg.Dbg.fEnabled)
2857 {
2858 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pThis->Out.Cfg.Dbg.szPathOut,
2859 "DrvAudioPlayNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
2860 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFileStreamWrite, pThis->Out.Cfg.Dbg.szPathOut,
2861 "DrvAudioWrite", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);
2862 }
2863 }
2864 }
2865 else
2866 {
2867 LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc));
2868 int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx);
2869 AssertRC(rc2);
2870 drvAudioStreamFree(pStreamEx);
2871 }
2872 }
2873 else
2874 rc = VERR_NO_MEMORY;
2875 }
2876
2877 RTCritSectLeave(&pThis->CritSect);
2878 LogFlowFuncLeaveRC(rc);
2879 return rc;
2880}
2881
2882/**
2883 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}
2884 */
2885static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)
2886{
2887 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2888 AssertPtr(pThis);
2889
2890 bool *pfEnabled;
2891 if (enmDir == PDMAUDIODIR_IN)
2892 pfEnabled = &pThis->In.fEnabled;
2893 else if (enmDir == PDMAUDIODIR_OUT)
2894 pfEnabled = &pThis->Out.fEnabled;
2895 else
2896 AssertFailedReturn(VERR_INVALID_PARAMETER);
2897
2898 int rc = RTCritSectEnter(&pThis->CritSect);
2899 AssertRCReturn(rc, rc);
2900
2901 if (fEnable != *pfEnabled)
2902 {
2903 LogRel(("Audio: %s %s for driver '%s'\n",
2904 fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));
2905
2906 /* Update the status first, as this will be checked for in drvAudioStreamControlInternalBackend() below. */
2907 *pfEnabled = fEnable;
2908
2909 PDRVAUDIOSTREAM pStreamEx;
2910 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
2911 {
2912 if (pStreamEx->Core.enmDir != enmDir) /* Skip unwanted streams. */
2913 continue;
2914
2915 /* Note: Only enable / disable the backend, do *not* change the stream's internal status.
2916 * Callers (device emulation, mixer, ...) from outside will not see any status or behavior change,
2917 * to not confuse the rest of the state machine.
2918 *
2919 * When disabling:
2920 * - playing back audo data would go to /dev/null
2921 * - recording audio data would return silence instead
2922 *
2923 * See @bugref{9882}.
2924 */
2925 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx,
2926 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);
2927 if (RT_FAILURE(rc2))
2928 {
2929 if (rc2 == VERR_AUDIO_STREAM_NOT_READY)
2930 LogRel(("Audio: Stream '%s' not available\n", pStreamEx->Core.szName));
2931 else
2932 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", fEnable ? "enable" : "disable",
2933 enmDir == PDMAUDIODIR_IN ? "input" : "output", pStreamEx->Core.szName, rc2));
2934 }
2935 else
2936 {
2937 /* When (re-)enabling a stream, clear the disabled warning bit again. */
2938 if (fEnable)
2939 pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
2940 }
2941
2942 if (RT_SUCCESS(rc))
2943 rc = rc2;
2944
2945 /* Keep going. */
2946 }
2947 }
2948
2949 RTCritSectLeave(&pThis->CritSect);
2950 LogFlowFuncLeaveRC(rc);
2951 return rc;
2952}
2953
2954/**
2955 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}
2956 */
2957static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
2958{
2959 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2960 AssertPtr(pThis);
2961 int rc = RTCritSectEnter(&pThis->CritSect);
2962 AssertRCReturn(rc, false);
2963
2964 bool fEnabled;
2965 if (enmDir == PDMAUDIODIR_IN)
2966 fEnabled = pThis->In.fEnabled;
2967 else if (enmDir == PDMAUDIODIR_OUT)
2968 fEnabled = pThis->Out.fEnabled;
2969 else
2970 AssertFailedStmt(fEnabled = false);
2971
2972 RTCritSectLeave(&pThis->CritSect);
2973 return fEnabled;
2974}
2975
2976/**
2977 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}
2978 */
2979static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)
2980{
2981 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2982 AssertPtr(pThis);
2983 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
2984 int rc = RTCritSectEnter(&pThis->CritSect);
2985 AssertRCReturn(rc, rc);
2986
2987 if (pThis->pHostDrvAudio)
2988 {
2989 if (pThis->pHostDrvAudio->pfnGetConfig)
2990 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);
2991 else
2992 rc = VERR_NOT_SUPPORTED;
2993 }
2994 else
2995 rc = VERR_PDM_NO_ATTACHED_DRIVER;
2996
2997 RTCritSectLeave(&pThis->CritSect);
2998 LogFlowFuncLeaveRC(rc);
2999 return rc;
3000}
3001
3002/**
3003 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}
3004 */
3005static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)
3006{
3007 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3008 AssertPtr(pThis);
3009 int rc = RTCritSectEnter(&pThis->CritSect);
3010 AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN);
3011
3012 PDMAUDIOBACKENDSTS fBackendStatus;
3013 if (pThis->pHostDrvAudio)
3014 {
3015 if (pThis->pHostDrvAudio->pfnGetStatus)
3016 fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);
3017 else
3018 fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN;
3019 }
3020 else
3021 fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED;
3022
3023 RTCritSectLeave(&pThis->CritSect);
3024 LogFlowFunc(("LEAVE - %#x\n", fBackendStatus));
3025 return fBackendStatus;
3026}
3027
3028/**
3029 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
3030 */
3031static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3032{
3033 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3034 AssertPtr(pThis);
3035 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3036 AssertPtrReturn(pStreamEx, 0);
3037 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
3038 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
3039 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
3040 int rc = RTCritSectEnter(&pThis->CritSect);
3041 AssertRCReturn(rc, 0);
3042
3043 /*
3044 * ...
3045 */
3046 uint32_t cbReadable = 0;
3047
3048 /* All input streams for this driver disabled? See @bugref{9882}. */
3049 const bool fDisabled = !pThis->In.fEnabled;
3050
3051 if ( pThis->pHostDrvAudio
3052 && ( PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus)
3053 || fDisabled)
3054 )
3055 {
3056 if (pStreamEx->fNoMixBufs)
3057 cbReadable = pThis->pHostDrvAudio ? pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStream) : 0;
3058 else
3059 {
3060 const uint32_t cfReadable = AudioMixBufLive(&pStreamEx->Guest.MixBuf);
3061 cbReadable = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadable);
3062 }
3063
3064 if (!cbReadable)
3065 {
3066 /*
3067 * If nothing is readable, check if the stream on the backend side is ready to be read from.
3068 * If it isn't, return the number of bytes readable since the last read from this stream.
3069 *
3070 * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
3071 * situations, but the device emulation needs input data to keep the DMA transfers moving.
3072 * Reading the actual data from a stream then will return silence then.
3073 */
3074 PDMAUDIOSTREAMSTS fStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE;
3075 if (pThis->pHostDrvAudio->pfnStreamGetStatus)
3076 fStatus = pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStreamEx->pvBackend);
3077 if ( !PDMAudioStrmStatusCanRead(fStatus)
3078 || fDisabled)
3079 {
3080 cbReadable = PDMAudioPropsNanoToBytes(&pStreamEx->Host.Cfg.Props,
3081 RTTimeNanoTS() - pStreamEx->nsLastReadWritten);
3082 if (!(pStreamEx->Core.fWarningsShown & PDMAUDIOSTREAM_WARN_FLAGS_DISABLED))
3083 {
3084 if (fDisabled)
3085 LogRel(("Audio: Input for driver '%s' has been disabled, returning silence\n", pThis->szName));
3086 else
3087 LogRel(("Audio: Warning: Input for stream '%s' of driver '%s' not ready (current input status is %#x), returning silence\n",
3088 pStreamEx->Core.szName, pThis->szName, fStatus));
3089
3090 pStreamEx->Core.fWarningsShown |= PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
3091 }
3092 }
3093 }
3094
3095 /* Make sure to align the readable size to the guest's frame size. */
3096 if (cbReadable)
3097 cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Guest.Cfg.Props, cbReadable);
3098 }
3099
3100 RTCritSectLeave(&pThis->CritSect);
3101 Log3Func(("[%s] cbReadable=%RU32 (%RU64ms)\n",
3102 pStreamEx->Core.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbReadable)));
3103 return cbReadable;
3104}
3105
3106/**
3107 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
3108 */
3109static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3110{
3111 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3112 AssertPtr(pThis);
3113 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3114 AssertPtrReturn(pStreamEx, 0);
3115 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
3116 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
3117 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"));
3118 int rc = RTCritSectEnter(&pThis->CritSect);
3119 AssertRCReturn(rc, 0);
3120
3121 /*
3122 * ...
3123 */
3124 uint32_t cbWritable = 0;
3125
3126 /* Note: We don't propage the backend stream's status to the outside -- it's the job of this
3127 * audio connector to make sense of it. */
3128 if (PDMAudioStrmStatusCanWrite(pStreamEx->Core.fStatus))
3129 {
3130 if (pStreamEx->fNoMixBufs)
3131 cbWritable = pThis->pHostDrvAudio ? pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStream) : 0;
3132 else
3133 cbWritable = AudioMixBufFreeBytes(&pStreamEx->Host.MixBuf);
3134
3135 /* Make sure to align the writable size to the host's frame size. */
3136 cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Host.Cfg.Props, cbWritable);
3137 }
3138
3139 RTCritSectLeave(&pThis->CritSect);
3140 Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
3141 pStreamEx->Core.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable)));
3142 return cbWritable;
3143}
3144
3145/**
3146 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus}
3147 */
3148static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioStreamGetStatus(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3149{
3150 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3151 AssertPtr(pThis);
3152
3153 /** @todo r=bird: It is not documented that we ignore NULL streams... Why is
3154 * this necessary? */
3155 if (!pStream)
3156 return PDMAUDIOSTREAMSTS_FLAGS_NONE;
3157 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3158 AssertPtrReturn(pStreamEx, PDMAUDIOSTREAMSTS_FLAGS_NONE);
3159 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTS_FLAGS_NONE);
3160 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTS_FLAGS_NONE);
3161
3162 int rc = RTCritSectEnter(&pThis->CritSect);
3163 AssertRCReturn(rc, PDMAUDIOSTREAMSTS_FLAGS_NONE);
3164
3165 /* Is the stream scheduled for re-initialization? Do so now. */
3166 drvAudioStreamMaybeReInit(pThis, pStreamEx);
3167
3168 PDMAUDIOSTREAMSTS fStrmStatus = pStreamEx->Core.fStatus;
3169
3170 RTCritSectLeave(&pThis->CritSect);
3171#ifdef LOG_ENABLED
3172 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3173#endif
3174 Log3Func(("[%s] %s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
3175 return fStrmStatus;
3176}
3177
3178/**
3179 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
3180 */
3181static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
3182{
3183 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
3184 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3185 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3186 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
3187 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3188 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3189
3190 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStreamEx->Core.szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
3191 AssertReturn(!pStreamEx->fNoMixBufs, VERR_ACCESS_DENIED);
3192
3193 AudioMixBufSetVolume(&pStreamEx->Guest.MixBuf, pVol);
3194 AudioMixBufSetVolume(&pStreamEx->Host.MixBuf, pVol);
3195
3196 return VINF_SUCCESS;
3197}
3198
3199/**
3200 * Calls the backend to give it the chance to destroy its part of the audio stream.
3201 *
3202 * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and
3203 * drvAudioStreamReInitInternal.
3204 *
3205 * @returns VBox status code.
3206 * @param pThis Pointer to driver instance.
3207 * @param pStreamEx Audio stream destruct backend for.
3208 */
3209static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
3210{
3211 AssertPtr(pThis);
3212 AssertPtr(pStreamEx);
3213
3214 int rc = VINF_SUCCESS;
3215
3216#ifdef LOG_ENABLED
3217 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3218#endif
3219 LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
3220
3221 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED)
3222 {
3223 AssertPtr(pStreamEx->pvBackend);
3224
3225 /* Check if the pointer to the host audio driver is still valid.
3226 * It can be NULL if we were called in drvAudioDestruct, for example. */
3227 if (pThis->pHostDrvAudio)
3228 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pvBackend);
3229
3230 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED;
3231 }
3232
3233 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
3234 return rc;
3235}
3236
3237/**
3238 * Uninitializes an audio stream - worker for drvAudioStreamDestroy,
3239 * drvAudioDestruct and drvAudioStreamCreate.
3240 *
3241 * @returns VBox status code.
3242 * @param pThis Pointer to driver instance.
3243 * @param pStreamEx Pointer to audio stream to uninitialize.
3244 *
3245 * @note Caller owns the critical section.
3246 */
3247static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
3248{
3249 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3250 AssertMsgReturn(pStreamEx->Core.cRefs <= 1,
3251 ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs),
3252 VERR_WRONG_ORDER);
3253 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs));
3254
3255 /*
3256 * ...
3257 */
3258 int rc = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3259 if (RT_SUCCESS(rc))
3260 rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
3261
3262 /* Destroy mixing buffers. */
3263 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf);
3264 AudioMixBufDestroy(&pStreamEx->Host.MixBuf);
3265
3266 if (RT_SUCCESS(rc))
3267 {
3268#ifdef LOG_ENABLED
3269 if (pStreamEx->Core.fStatus != PDMAUDIOSTREAMSTS_FLAGS_NONE)
3270 {
3271 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3272 LogFunc(("[%s] Warning: Still has %s set when uninitializing\n",
3273 pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));
3274 }
3275#endif
3276 pStreamEx->Core.fStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE;
3277 }
3278
3279 PPDMDRVINS const pDrvIns = pThis->pDrvIns;
3280 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
3281 {
3282#ifdef VBOX_WITH_STATISTICS
3283 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->In.Stats.TotalFramesCaptured);
3284 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->In.Stats.TotalTimesCaptured);
3285 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->In.Stats.TotalFramesRead);
3286 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->In.Stats.TotalTimesRead);
3287#endif
3288 if (pThis->In.Cfg.Dbg.fEnabled)
3289 {
3290 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCaptureNonInterleaved);
3291 pStreamEx->In.Dbg.pFileCaptureNonInterleaved = NULL;
3292
3293 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileStreamRead);
3294 pStreamEx->In.Dbg.pFileStreamRead = NULL;
3295 }
3296 if (!pStreamEx->fNoMixBufs)
3297 {
3298 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Host.MixBuf.cMixed);
3299 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Guest.MixBuf.cUsed);
3300 }
3301 }
3302 else
3303 {
3304 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT);
3305#ifdef VBOX_WITH_STATISTICS
3306 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.TotalFramesPlayed);
3307 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.TotalTimesPlayed);
3308 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.TotalFramesWritten);
3309 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.TotalTimesWritten);
3310#endif
3311 if (pThis->Out.Cfg.Dbg.fEnabled)
3312 {
3313 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlayNonInterleaved);
3314 pStreamEx->Out.Dbg.pFilePlayNonInterleaved = NULL;
3315
3316 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFileStreamWrite);
3317 pStreamEx->Out.Dbg.pFileStreamWrite = NULL;
3318 }
3319 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter);
3320 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore);
3321 if (!pStreamEx->fNoMixBufs)
3322 {
3323 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Host.MixBuf.cUsed);
3324 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Guest.MixBuf.cMixed);
3325 }
3326 }
3327 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Host.Cfg.Backend.cFramesBufferSize);
3328 if (!pStreamEx->fNoMixBufs)
3329 {
3330 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Host.MixBuf.cFrames);
3331 PDMDrvHlpSTAMDeregister(pDrvIns, &pStreamEx->Guest.MixBuf.cFrames);
3332 }
3333
3334 LogFlowFunc(("Returning %Rrc\n", rc));
3335 return rc;
3336}
3337
3338/**
3339 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy}
3340 */
3341static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3342{
3343 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3344 AssertPtr(pThis);
3345
3346 if (!pStream)
3347 return VINF_SUCCESS;
3348 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
3349 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */
3350 Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
3351 Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
3352
3353 int rc = RTCritSectEnter(&pThis->CritSect);
3354 AssertRCReturn(rc, rc);
3355
3356 LogRel2(("Audio: Destroying stream '%s'\n", pStreamEx->Core.szName));
3357
3358 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs));
3359 AssertMsg(pStreamEx->Core.cRefs <= 1, ("%u %s\n", pStreamEx->Core.cRefs, pStreamEx->Core.szName));
3360 if (pStreamEx->Core.cRefs <= 1)
3361 {
3362 rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
3363 if (RT_SUCCESS(rc))
3364 {
3365 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
3366 pThis->In.cStreamsFree++;
3367 else /* Out */
3368 pThis->Out.cStreamsFree++;
3369
3370 RTListNodeRemove(&pStreamEx->ListEntry);
3371
3372 drvAudioStreamFree(pStreamEx);
3373 pStreamEx = NULL;
3374 pStream = NULL;
3375 }
3376 else
3377 LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
3378 }
3379 else
3380 rc = VERR_WRONG_ORDER;
3381
3382 RTCritSectLeave(&pThis->CritSect);
3383 LogFlowFuncLeaveRC(rc);
3384 return rc;
3385}
3386
3387
3388/*********************************************************************************************************************************
3389* PDMIAUDIONOTIFYFROMHOST interface implementation. *
3390*********************************************************************************************************************************/
3391#ifdef VBOX_WITH_AUDIO_CALLBACKS
3392/**
3393 * @interface_method_impl{PDMIAUDIONOTIFYFROMHOST,pfnNotifyDevicesChanged}
3394 */
3395static DECLCALLBACK(void) drvAudioNotifyFromHost_NotifyDevicesChanged(PPDMIAUDIONOTIFYFROMHOST pInterface)
3396{
3397 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioNotifyFromHost);
3398
3399 LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->szName));
3400 drvAudioScheduleReInitInternal(pThis);
3401}
3402
3403#endif /* VBOX_WITH_AUDIO_CALLBACKS */
3404
3405
3406/*********************************************************************************************************************************
3407* PDMIBASE interface implementation. *
3408*********************************************************************************************************************************/
3409
3410/**
3411 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
3412 */
3413static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
3414{
3415 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
3416
3417 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
3418 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3419
3420 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
3421 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
3422#ifdef VBOX_WITH_AUDIO_CALLBACKS
3423 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIONOTIFYFROMHOST, &pThis->IAudioNotifyFromHost);
3424#endif
3425
3426 return NULL;
3427}
3428
3429
3430/*********************************************************************************************************************************
3431* PDMDRVREG interface implementation. *
3432*********************************************************************************************************************************/
3433
3434/**
3435 * Power Off notification.
3436 *
3437 * @param pDrvIns The driver instance data.
3438 */
3439static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
3440{
3441 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3442
3443 LogFlowFuncEnter();
3444
3445 /** @todo locking? */
3446 if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
3447 {
3448 /*
3449 * Just destroy the host stream on the backend side.
3450 * The rest will either be destructed by the device emulation or
3451 * in drvAudioDestruct().
3452 */
3453 PDRVAUDIOSTREAM pStreamEx;
3454 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
3455 {
3456 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3457 drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
3458 }
3459
3460 /*
3461 * Last call for the driver below us.
3462 * Let it know that we reached end of life.
3463 */
3464 if (pThis->pHostDrvAudio->pfnShutdown)
3465 pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);
3466
3467 pThis->pHostDrvAudio = NULL;
3468 }
3469
3470 LogFlowFuncLeave();
3471}
3472
3473
3474/**
3475 * Detach notification.
3476 *
3477 * @param pDrvIns The driver instance data.
3478 * @param fFlags Detach flags.
3479 */
3480static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3481{
3482 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3483 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3484 RT_NOREF(fFlags);
3485
3486 int rc = RTCritSectEnter(&pThis->CritSect);
3487 AssertRC(rc);
3488
3489 LogFunc(("%s (detached %p)\n", pThis->szName, pThis->pHostDrvAudio));
3490 pThis->pHostDrvAudio = NULL;
3491
3492 RTCritSectLeave(&pThis->CritSect);
3493}
3494
3495
3496/**
3497 * Does the actual backend driver attaching and queries the backend's interface.
3498 *
3499 * This is a worker for both drvAudioAttach and drvAudioConstruct.
3500 *
3501 * @returns VBox status code.
3502 * @param pThis Pointer to driver instance.
3503 * @param fFlags Attach flags; see PDMDrvHlpAttach().
3504 */
3505static int drvAudioDoAttachInternal(PDRVAUDIO pThis, uint32_t fFlags)
3506{
3507 Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
3508
3509 /*
3510 * Attach driver below and query its connector interface.
3511 */
3512 PPDMIBASE pDownBase;
3513 int rc = PDMDrvHlpAttach(pThis->pDrvIns, fFlags, &pDownBase);
3514 if (RT_SUCCESS(rc))
3515 {
3516 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
3517 if (pThis->pHostDrvAudio)
3518 {
3519 /*
3520 * If everything went well, initialize the lower driver.
3521 */
3522 rc = drvAudioHostInit(pThis);
3523 if (RT_FAILURE(rc))
3524 pThis->pHostDrvAudio = NULL;
3525 }
3526 else
3527 {
3528 LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->szName));
3529 rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
3530 N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
3531 }
3532 }
3533
3534 LogFunc(("[%s] rc=%Rrc\n", pThis->szName, rc));
3535 return rc;
3536}
3537
3538
3539/**
3540 * Attach notification.
3541 *
3542 * @param pDrvIns The driver instance data.
3543 * @param fFlags Attach flags.
3544 */
3545static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
3546{
3547 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3548 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3549 LogFunc(("%s\n", pThis->szName));
3550
3551 int rc = RTCritSectEnter(&pThis->CritSect);
3552 AssertRCReturn(rc, rc);
3553
3554 rc = drvAudioDoAttachInternal(pThis, fFlags);
3555
3556 RTCritSectLeave(&pThis->CritSect);
3557 return rc;
3558}
3559
3560
3561/**
3562 * Resume notification.
3563 *
3564 * @param pDrvIns The driver instance data.
3565 */
3566static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
3567{
3568 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
3569}
3570
3571
3572/**
3573 * Suspend notification.
3574 *
3575 * @param pDrvIns The driver instance data.
3576 */
3577static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
3578{
3579 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
3580}
3581
3582
3583/**
3584 * Destructs an audio driver instance.
3585 *
3586 * @copydoc FNPDMDRVDESTRUCT
3587 */
3588static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
3589{
3590 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
3591 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3592
3593 LogFlowFuncEnter();
3594
3595 if (RTCritSectIsInitialized(&pThis->CritSect))
3596 {
3597 int rc = RTCritSectEnter(&pThis->CritSect);
3598 AssertRC(rc);
3599 }
3600
3601 /*
3602 * Note: No calls here to the driver below us anymore,
3603 * as PDM already has destroyed it.
3604 * If you need to call something from the host driver,
3605 * do this in drvAudioPowerOff() instead.
3606 */
3607
3608 /* Thus, NULL the pointer to the host audio driver first,
3609 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
3610 pThis->pHostDrvAudio = NULL;
3611
3612 PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
3613 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
3614 {
3615 int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
3616 if (RT_SUCCESS(rc))
3617 {
3618 RTListNodeRemove(&pStreamEx->ListEntry);
3619 drvAudioStreamFree(pStreamEx);
3620 }
3621 }
3622
3623 /* Sanity. */
3624 Assert(RTListIsEmpty(&pThis->lstStreams));
3625
3626 if (RTCritSectIsInitialized(&pThis->CritSect))
3627 {
3628 int rc = RTCritSectLeave(&pThis->CritSect);
3629 AssertRC(rc);
3630
3631 rc = RTCritSectDelete(&pThis->CritSect);
3632 AssertRC(rc);
3633 }
3634
3635 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Out.StatsReBuffering);
3636#ifdef VBOX_WITH_STATISTICS
3637 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsActive);
3638 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsCreated);
3639 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesRead);
3640 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesWritten);
3641 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesMixedIn);
3642 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesMixedOut);
3643 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesLostIn);
3644 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesLostOut);
3645 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesOut);
3646 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesIn);
3647 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalBytesRead);
3648 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalBytesWritten);
3649 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.DelayIn);
3650 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.DelayOut);
3651#endif
3652
3653 LogFlowFuncLeave();
3654}
3655
3656
3657/**
3658 * Constructs an audio driver instance.
3659 *
3660 * @copydoc FNPDMDRVCONSTRUCT
3661 */
3662static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
3663{
3664 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
3665 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
3666 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
3667
3668 /*
3669 * Basic instance init.
3670 */
3671 RTListInit(&pThis->lstStreams);
3672
3673 /*
3674 * Read configuration.
3675 */
3676 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
3677 "DriverName|"
3678 "InputEnabled|"
3679 "OutputEnabled|"
3680 "DebugEnabled|"
3681 "DebugPathOut|"
3682 /* Deprecated: */
3683 "PCMSampleBitIn|"
3684 "PCMSampleBitOut|"
3685 "PCMSampleHzIn|"
3686 "PCMSampleHzOut|"
3687 "PCMSampleSignedIn|"
3688 "PCMSampleSignedOut|"
3689 "PCMSampleSwapEndianIn|"
3690 "PCMSampleSwapEndianOut|"
3691 "PCMSampleChannelsIn|"
3692 "PCMSampleChannelsOut|"
3693 "PeriodSizeMsIn|"
3694 "PeriodSizeMsOut|"
3695 "BufferSizeMsIn|"
3696 "BufferSizeMsOut|"
3697 "PreBufferSizeMsIn|"
3698 "PreBufferSizeMsOut",
3699 "In|Out");
3700
3701 int rc = CFGMR3QueryStringDef(pCfg, "DriverName", pThis->szName, sizeof(pThis->szName), "Untitled");
3702 AssertLogRelRCReturn(rc, rc);
3703
3704 /* Neither input nor output by default for security reasons. */
3705 rc = CFGMR3QueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
3706 AssertLogRelRCReturn(rc, rc);
3707
3708 rc = CFGMR3QueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
3709 AssertLogRelRCReturn(rc, rc);
3710
3711 /* Debug stuff (same for both directions). */
3712 rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->In.Cfg.Dbg.fEnabled, false);
3713 AssertLogRelRCReturn(rc, rc);
3714
3715 rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut), "");
3716 AssertLogRelRCReturn(rc, rc);
3717 if (pThis->In.Cfg.Dbg.szPathOut[0] == '\0')
3718 {
3719 rc = RTPathTemp(pThis->In.Cfg.Dbg.szPathOut, sizeof(pThis->In.Cfg.Dbg.szPathOut));
3720 if (RT_FAILURE(rc))
3721 {
3722 LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
3723 pThis->In.Cfg.Dbg.szPathOut[0] = '\0';
3724 pThis->In.Cfg.Dbg.fEnabled = false;
3725 }
3726 }
3727 if (pThis->In.Cfg.Dbg.fEnabled)
3728 LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n", pThis->szName, pThis->In.Cfg.Dbg.szPathOut));
3729
3730 /* Copy debug setup to the output direction. */
3731 pThis->Out.Cfg.Dbg = pThis->In.Cfg.Dbg;
3732
3733 LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->szName));
3734 /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
3735 LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
3736 pThis->szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
3737
3738 /*
3739 * Per direction configuration. A bit complicated as
3740 * these wasn't originally in sub-nodes.
3741 */
3742 for (unsigned iDir = 0; iDir < 2; iDir++)
3743 {
3744 char szNm[48];
3745 PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->In.Cfg : &pThis->Out.Cfg;
3746 const char *pszDir = iDir == 0 ? "In" : "Out";
3747
3748#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
3749 do { \
3750 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
3751 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
3752 { \
3753 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
3754 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
3755 { \
3756 *(a_pValue) = a_uDefault; \
3757 rc = VINF_SUCCESS; \
3758 } \
3759 else \
3760 LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
3761 } \
3762 AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
3763 N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
3764 if (!(a_ExprValid)) \
3765 return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
3766 N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
3767 } while (0)
3768
3769 PCFGMNODE const pDirNode = CFGMR3GetChild(pCfg, pszDir);
3770 rc = CFGMR3ValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
3771 "PCMSampleBit|"
3772 "PCMSampleHz|"
3773 "PCMSampleSigned|"
3774 "PCMSampleSwapEndian|"
3775 "PCMSampleChannels|"
3776 "PeriodSizeMs|"
3777 "BufferSizeMs|"
3778 "PreBufferSizeMs",
3779 "", pDrvIns->pReg->szName, pDrvIns->iInstance);
3780 AssertRCReturn(rc, rc);
3781
3782 uint8_t cSampleBits = 0;
3783 QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
3784 cSampleBits == 0
3785 || cSampleBits == 8
3786 || cSampleBits == 16
3787 || cSampleBits == 32
3788 || cSampleBits == 64,
3789 "Must be either 0, 8, 16, 32 or 64");
3790 if (cSampleBits)
3791 PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
3792
3793 uint8_t cChannels;
3794 QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
3795 if (cChannels)
3796 PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
3797
3798 QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
3799 pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
3800 "In the range 6000 thru 768000, or 0");
3801
3802 QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
3803 pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
3804 "Must be either 0, 1, or 255");
3805
3806 QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
3807 pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
3808 "Must be either 0, 1, or 255");
3809
3810 QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
3811 pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
3812
3813 QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
3814 pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
3815
3816 QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
3817 pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
3818 "Max 1000, or 0xffffffff");
3819#undef QUERY_VAL_RET
3820 }
3821
3822 /*
3823 * Init the rest of the driver instance data.
3824 */
3825 rc = RTCritSectInit(&pThis->CritSect);
3826 AssertRCReturn(rc, rc);
3827
3828 pThis->fTerminate = false;
3829 pThis->pDrvIns = pDrvIns;
3830 /* IBase. */
3831 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
3832 /* IAudioConnector. */
3833 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
3834 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
3835 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
3836 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
3837 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
3838 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
3839 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
3840 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
3841 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
3842 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
3843 pThis->IAudioConnector.pfnStreamWrite = drvAudioStreamWrite;
3844 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
3845 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
3846 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
3847 pThis->IAudioConnector.pfnStreamGetStatus = drvAudioStreamGetStatus;
3848 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
3849 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
3850 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
3851#ifdef VBOX_WITH_AUDIO_CALLBACKS
3852 /* IAudioNotifyFromHost */
3853 pThis->IAudioNotifyFromHost.pfnNotifyDevicesChanged = drvAudioNotifyFromHost_NotifyDevicesChanged;
3854#endif
3855
3856 /*
3857 * Statistics.
3858 */
3859 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Out.StatsReBuffering, "OutputReBuffering",
3860 STAMUNIT_COUNT, "Number of times the output stream was re-buffered after starting.");
3861
3862#ifdef VBOX_WITH_STATISTICS
3863 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
3864 STAMUNIT_COUNT, "Total active audio streams.");
3865 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
3866 STAMUNIT_COUNT, "Total created audio streams.");
3867 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
3868 STAMUNIT_COUNT, "Total frames read by device emulation.");
3869 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesWritten, "TotalFramesWritten",
3870 STAMUNIT_COUNT, "Total frames written by device emulation ");
3871 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedIn, "TotalFramesMixedIn",
3872 STAMUNIT_COUNT, "Total input frames mixed.");
3873 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesMixedOut, "TotalFramesMixedOut",
3874 STAMUNIT_COUNT, "Total output frames mixed.");
3875 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostIn, "TotalFramesLostIn",
3876 STAMUNIT_COUNT, "Total input frames lost.");
3877 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesLostOut, "TotalFramesLostOut",
3878 STAMUNIT_COUNT, "Total output frames lost.");
3879 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesOut, "TotalFramesOut",
3880 STAMUNIT_COUNT, "Total frames played by backend.");
3881 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
3882 STAMUNIT_COUNT, "Total frames captured by backend.");
3883 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
3884 STAMUNIT_BYTES, "Total bytes read.");
3885 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesWritten, "TotalBytesWritten",
3886 STAMUNIT_BYTES, "Total bytes written.");
3887
3888 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayIn, "DelayIn",
3889 STAMUNIT_NS_PER_CALL, "Profiling of input data processing.");
3890 PDMDrvHlpSTAMRegProfileAdvEx(pDrvIns, &pThis->Stats.DelayOut, "DelayOut",
3891 STAMUNIT_NS_PER_CALL, "Profiling of output data processing.");
3892#endif
3893
3894 /*
3895 * Create a timer to do finish closing output streams in PENDING_DISABLE state.
3896 *
3897 * The device won't call us again after it has disabled a the stream and this is
3898 * a real problem for truely cyclic buffer backends like DSound which will just
3899 * continue to loop and loop if not stopped.
3900 */
3901 RTStrPrintf(pThis->szTimerName, sizeof(pThis->szTimerName), "AudioIterate-%u", pDrvIns->iInstance);
3902 rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, drvAudioEmergencyIterateTimer, NULL /*pvUser*/,
3903 0 /*fFlags*/, pThis->szTimerName, &pThis->hTimer);
3904 AssertRCReturn(rc, rc);
3905
3906 /*
3907 * Attach the host driver, if present.
3908 */
3909 rc = drvAudioDoAttachInternal(pThis, fFlags);
3910 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
3911 rc = VINF_SUCCESS;
3912
3913 LogFlowFuncLeaveRC(rc);
3914 return rc;
3915}
3916
3917/**
3918 * Audio driver registration record.
3919 */
3920const PDMDRVREG g_DrvAUDIO =
3921{
3922 /* u32Version */
3923 PDM_DRVREG_VERSION,
3924 /* szName */
3925 "AUDIO",
3926 /* szRCMod */
3927 "",
3928 /* szR0Mod */
3929 "",
3930 /* pszDescription */
3931 "Audio connector driver",
3932 /* fFlags */
3933 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
3934 /* fClass */
3935 PDM_DRVREG_CLASS_AUDIO,
3936 /* cMaxInstances */
3937 UINT32_MAX,
3938 /* cbInstance */
3939 sizeof(DRVAUDIO),
3940 /* pfnConstruct */
3941 drvAudioConstruct,
3942 /* pfnDestruct */
3943 drvAudioDestruct,
3944 /* pfnRelocate */
3945 NULL,
3946 /* pfnIOCtl */
3947 NULL,
3948 /* pfnPowerOn */
3949 NULL,
3950 /* pfnReset */
3951 NULL,
3952 /* pfnSuspend */
3953 drvAudioSuspend,
3954 /* pfnResume */
3955 drvAudioResume,
3956 /* pfnAttach */
3957 drvAudioAttach,
3958 /* pfnDetach */
3959 drvAudioDetach,
3960 /* pfnPowerOff */
3961 drvAudioPowerOff,
3962 /* pfnSoftReset */
3963 NULL,
3964 /* u32EndVersion */
3965 PDM_DRVREG_VERSION
3966};
3967
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