VirtualBox

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

最後變更 在這個檔案從88852是 88852,由 vboxsync 提交於 4 年 前

DrvAudio: Logging fix. bugref:9890

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

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