VirtualBox

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

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

Audio: Added optional pfnStreamConfigHint methods to PDMIAUDIOCONNECTOR and PDMIHOSTAUDIO so the WASAPI backend can get some useful cache hints to avoid potentially horried EMT blocking when the guest tries to play audio later. This is rather crude, but with typical guest config it helps a lot. bugref:9890

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