VirtualBox

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

Last change on this file since 89234 was 89229, checked in by vboxsync, 4 years ago

Audio: Must have a PDMAUDIOHOSTDEV_F_DEFAULT flag for each direction or we'll mess up when two duplex devices are default devices for one direction each. Added a pszId member to the enumeration entires. Rewrote the core audio enumeration code. bugref:9890

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

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