VirtualBox

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

Last change on this file since 88858 was 88853, checked in by vboxsync, 4 years ago

Audio: Reworking PDMIHOSTAUDIOPORT::pfnNotifyDevicesChanged a little, from now on it will only trigger re-enumeration (darwin still needs some adjusting). Use a timer to sligtly delay the enumeration and move it out of the re-init stream code, so that we don't redo it too frequently and that we do it even when there are active streams around. Made DrvHostAudioWasApi call pfnNotifyDevicesChanged. Reduced the DSound notification client code a little while adding pfnNotifyDeviceChanged calls to it. bugref:9890

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

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