VirtualBox

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

Last change on this file since 89311 was 89282, checked in by vboxsync, 4 years ago

DrvAudio: A couple of fixes related to multi-threading. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 213.9 KB
Line 
1/* $Id: DrvAudio.cpp 89282 2021-05-25 20:11:23Z 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
2216 RTReqRelease(pStreamEx->hReqInitAsync);
2217 pStreamEx->hReqInitAsync = NIL_RTREQ;
2218
2219 RTCritSectLeave(&pStreamEx->Core.CritSect); /* (exit before releasing the stream to avoid assertion) */
2220
2221 if (RT_SUCCESS(rc2))
2222 {
2223 LogFlowFunc(("Successfully cancelled pending pfnStreamInitAsync call (hReqInitAsync=%p).\n",
2224 pStreamEx->hReqInitAsync));
2225 drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
2226 }
2227 else
2228 {
2229 LogFlowFunc(("Failed to cancel pending pfnStreamInitAsync call (hReqInitAsync=%p): %Rrc\n",
2230 pStreamEx->hReqInitAsync, rc2));
2231 Assert(rc2 == VERR_RT_REQUEST_STATE);
2232 }
2233 }
2234 else
2235 RTCritSectLeave(&pStreamEx->Core.CritSect);
2236
2237 /*
2238 * Now, if the backend requests asynchronous disabling and destruction
2239 * push the disabling and destroying over to a worker thread.
2240 *
2241 * This is a general offloading feature that all backends should make use of,
2242 * however it's rather precarious on macs where stopping an already draining
2243 * stream may take 8-10ms which naturally isn't something we should be doing
2244 * on an EMT.
2245 */
2246 if (!(pThis->BackendCfg.fFlags & PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY))
2247 drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate);
2248 else
2249 {
2250 int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/,
2251 RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
2252 (PFNRT)drvAudioStreamDestroyAsync, 3, pThis, pStreamEx, fImmediate);
2253 LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
2254 AssertRCStmt(rc2, drvAudioStreamDestroyAsync(pThis, pStreamEx, fImmediate));
2255 }
2256 }
2257 else
2258 {
2259 AssertLogRelMsgFailedStmt(("%p cRefs=%#x\n", pStreamEx, pStreamEx->cRefs), rc = VERR_CALLER_NO_REFERENCE);
2260 RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
2261 }
2262 }
2263 else
2264 {
2265 AssertLogRelMsgFailedStmt(("%p uMagic=%#x\n", pStreamEx, pStreamEx->uMagic), rc = VERR_INVALID_MAGIC);
2266 RTCritSectLeave(&pStreamEx->Core.CritSect); /*??*/
2267 }
2268
2269 LogFlowFuncLeaveRC(rc);
2270 return rc;
2271}
2272
2273
2274/**
2275 * Drops all audio data (and associated state) of a stream.
2276 *
2277 * Used by drvAudioStreamIterateInternal(), drvAudioStreamResetOnDisable(), and
2278 * drvAudioStreamReInitInternal().
2279 *
2280 * @param pStreamEx Stream to drop data for.
2281 */
2282static void drvAudioStreamResetInternal(PDRVAUDIOSTREAM pStreamEx)
2283{
2284 LogFunc(("[%s]\n", pStreamEx->Core.szName));
2285 Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
2286
2287 if (pStreamEx->fNoMixBufs)
2288 {
2289 AudioMixBufReset(&pStreamEx->Guest.MixBuf);
2290 AudioMixBufReset(&pStreamEx->Host.MixBuf);
2291 }
2292
2293 pStreamEx->nsLastIterated = 0;
2294 pStreamEx->nsLastPlayedCaptured = 0;
2295 pStreamEx->nsLastReadWritten = 0;
2296 if (pStreamEx->Host.Cfg.enmDir == PDMAUDIODIR_OUT)
2297 {
2298 pStreamEx->Out.cbPreBuffered = 0;
2299 pStreamEx->Out.offPreBuf = 0;
2300 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
2301 ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
2302 }
2303}
2304
2305
2306/**
2307 * Re-initializes an audio stream with its existing host and guest stream
2308 * configuration.
2309 *
2310 * This might be the case if the backend told us we need to re-initialize
2311 * because something on the host side has changed.
2312 *
2313 * @note Does not touch the stream's status flags.
2314 *
2315 * @returns VBox status code.
2316 * @param pThis Pointer to driver instance.
2317 * @param pStreamEx Stream to re-initialize.
2318 */
2319static int drvAudioStreamReInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
2320{
2321 char szTmp[RT_MAX(PDMAUDIOSTRMCFGTOSTRING_MAX, DRVAUDIO_STATUS_STR_MAX)];
2322 LogFlowFunc(("[%s] status: %s\n", pStreamEx->Core.szName, drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
2323 Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
2324 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
2325
2326 /*
2327 * Destroy and re-create stream on backend side.
2328 */
2329 if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
2330 == (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY))
2331 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2332
2333 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
2334 drvAudioStreamDestroyInternalBackend(pThis, pStreamEx);
2335
2336 int rc = VERR_AUDIO_STREAM_NOT_READY;
2337 if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED))
2338 {
2339 drvAudioStreamResetInternal(pStreamEx);
2340
2341/** @todo
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 * We need to zero the backend storage here!!
2347 * We need to zero the backend storage here!!
2348 * We need to zero the backend storage here!!
2349 * */
2350 PDMAUDIOSTREAMCFG CfgHostAcq;
2351 rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, &pStreamEx->Host.Cfg, &CfgHostAcq);
2352 if (RT_SUCCESS(rc))
2353 {
2354 LogFunc(("[%s] Acquired host format: %s\n",
2355 pStreamEx->Core.szName, PDMAudioStrmCfgToString(&CfgHostAcq, szTmp, sizeof(szTmp)) ));
2356 /** @todo Validate (re-)acquired configuration with pStreamEx->Core.Host.Cfg?
2357 * drvAudioStreamInitInternal() does some setup and a bunch of
2358 * validations + adjustments of the stream config, so this surely is quite
2359 * optimistic. */
2360 if (true)
2361 {
2362 /*
2363 * Kick off the asynchronous init.
2364 */
2365 if (!pStreamEx->fNeedAsyncInit)
2366 {
2367 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_BACKEND_READY;
2368 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2369 }
2370 else
2371 {
2372 drvAudioStreamRetainInternal(pStreamEx);
2373 int rc2 = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, &pStreamEx->hReqInitAsync,
2374 RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
2375 (PFNRT)drvAudioStreamInitAsync, 2, pThis, pStreamEx);
2376 LogFlowFunc(("hReqInitAsync=%p rc2=%Rrc\n", pStreamEx->hReqInitAsync, rc2));
2377 AssertRCStmt(rc2, drvAudioStreamInitAsync(pThis, pStreamEx));
2378 }
2379
2380 /*
2381 * Update the backend on the stream state if it's ready, otherwise
2382 * let the worker thread do it after the async init has completed.
2383 */
2384 if ( (pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
2385 == (PDMAUDIOSTREAM_STS_BACKEND_READY | PDMAUDIOSTREAM_STS_BACKEND_CREATED))
2386 {
2387 rc = drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "re-initializing");
2388 /** @todo not sure if we really need to care about this status code... */
2389 }
2390 else if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
2391 {
2392 Assert(pStreamEx->hReqInitAsync != NIL_RTREQ);
2393 LogFunc(("Asynchronous stream init (%p) ...\n", pStreamEx->hReqInitAsync));
2394 }
2395 else
2396 {
2397 LogRel(("Audio: Re-initializing stream '%s' somehow failed, status: %s\n", pStreamEx->Core.szName,
2398 drvAudioStreamStatusToStr(szTmp, pStreamEx->fStatus) ));
2399 AssertFailed();
2400 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
2401 }
2402 }
2403 }
2404 else
2405 LogRel(("Audio: Re-initializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
2406 }
2407 else
2408 {
2409 LogRel(("Audio: Re-initializing stream '%s' failed to destroy previous backend.\n", pStreamEx->Core.szName));
2410 AssertFailed();
2411 }
2412
2413 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
2414 LogFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));
2415 return rc;
2416}
2417
2418
2419/**
2420 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamReInit}
2421 */
2422static DECLCALLBACK(int) drvAudioStreamReInit(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2423{
2424 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2425 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2426 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2427 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2428 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2429 AssertReturn(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT, VERR_INVALID_STATE);
2430 LogFlowFunc(("\n"));
2431
2432 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
2433 AssertRCReturn(rc, rc);
2434
2435 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT)
2436 {
2437 const unsigned cMaxTries = 5;
2438 const uint64_t nsNow = RTTimeNanoTS();
2439
2440 /* Throttle re-initializing streams on failure. */
2441 if ( pStreamEx->cTriesReInit < cMaxTries
2442 && pStreamEx->hReqInitAsync == NIL_RTREQ
2443 && ( pStreamEx->nsLastReInit == 0
2444 || nsNow - pStreamEx->nsLastReInit >= RT_NS_1SEC * pStreamEx->cTriesReInit))
2445 {
2446 rc = drvAudioStreamReInitInternal(pThis, pStreamEx);
2447 if (RT_SUCCESS(rc))
2448 {
2449 /* Remove the pending re-init flag on success. */
2450 pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT;
2451 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2452 }
2453 else
2454 {
2455 pStreamEx->nsLastReInit = nsNow;
2456 pStreamEx->cTriesReInit++;
2457
2458 /* Did we exceed our tries re-initializing the stream?
2459 * Then this one is dead-in-the-water, so disable it for further use. */
2460 if (pStreamEx->cTriesReInit >= cMaxTries)
2461 {
2462 LogRel(("Audio: Re-initializing stream '%s' exceeded maximum retries (%u), leaving as disabled\n",
2463 pStreamEx->Core.szName, cMaxTries));
2464
2465 /* Don't try to re-initialize anymore and mark as disabled. */
2466 /** @todo should mark it as not-initialized too, shouldn't we? */
2467 pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_NEED_REINIT | PDMAUDIOSTREAM_STS_ENABLED);
2468 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2469
2470 /* Note: Further writes to this stream go to / will be read from the bit bucket (/dev/null) from now on. */
2471 }
2472 }
2473 }
2474 else
2475 Log8Func(("cTriesReInit=%d hReqInitAsync=%p nsLast=%RU64 nsNow=%RU64 nsDelta=%RU64\n", pStreamEx->cTriesReInit,
2476 pStreamEx->hReqInitAsync, pStreamEx->nsLastReInit, nsNow, nsNow - pStreamEx->nsLastReInit));
2477
2478#ifdef LOG_ENABLED
2479 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2480#endif
2481 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
2482 }
2483 else
2484 {
2485 AssertFailed();
2486 rc = VERR_INVALID_STATE;
2487 }
2488
2489 RTCritSectLeave(&pStreamEx->Core.CritSect);
2490
2491 LogFlowFuncLeaveRC(rc);
2492 return rc;
2493}
2494
2495
2496/**
2497 * Internal retain function.
2498 *
2499 * @returns New reference count, UINT32_MAX if bad stream.
2500 * @param pStreamEx The stream to reference.
2501 */
2502static uint32_t drvAudioStreamRetainInternal(PDRVAUDIOSTREAM pStreamEx)
2503{
2504 AssertPtrReturn(pStreamEx, UINT32_MAX);
2505 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);
2506 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);
2507
2508 uint32_t const cRefs = ASMAtomicIncU32(&pStreamEx->cRefs);
2509 Assert(cRefs > 1);
2510 Assert(cRefs < _1K);
2511
2512 Log12Func(("returns %u (%s)\n", cRefs, pStreamEx->Core.szName));
2513 return cRefs;
2514}
2515
2516
2517/**
2518 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}
2519 */
2520static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2521{
2522 RT_NOREF(pInterface);
2523 return drvAudioStreamRetainInternal((PDRVAUDIOSTREAM)pStream);
2524}
2525
2526
2527/**
2528 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}
2529 */
2530static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
2531{
2532 return drvAudioStreamReleaseInternal(RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector),
2533 (PDRVAUDIOSTREAM)pStream,
2534 false /*fMayDestroy*/);
2535}
2536
2537
2538/**
2539 * Controls a stream's backend.
2540 *
2541 * @returns VBox status code.
2542 * @param pThis Pointer to driver instance.
2543 * @param pStreamEx Stream to control.
2544 * @param enmStreamCmd Control command.
2545 *
2546 * @note Caller has entered the critical section of the stream.
2547 * @note Can be called w/o having entered DRVAUDIO::CritSectHotPlug.
2548 */
2549static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
2550{
2551 AssertPtr(pThis);
2552 AssertPtr(pStreamEx);
2553 Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
2554
2555 int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
2556 AssertRCReturn(rc, rc);
2557
2558 /*
2559 * Whether to propagate commands down to the backend.
2560 *
2561 * 1. If the stream direction is disabled on the driver level, we should
2562 * obviously not call the backend. Our stream status will reflect the
2563 * actual state so drvAudioEnable() can tell the backend if the user
2564 * re-enables the stream direction.
2565 *
2566 * 2. If the backend hasn't finished initializing yet, don't try call
2567 * it to start/stop/pause/whatever the stream. (Better to do it here
2568 * than to replicate this in the relevant backends.) When the backend
2569 * finish initializing the stream, we'll update it about the stream state.
2570 */
2571 bool const fDirEnabled = pStreamEx->Core.enmDir == PDMAUDIODIR_IN
2572 ? pThis->In.fEnabled : pThis->Out.fEnabled;
2573 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
2574 /* ^^^ (checks pThis->pHostDrvAudio != NULL too) */
2575
2576 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2577 LogRel2(("Audio: %s stream '%s' backend (%s is %s; status: %s; backend-status: %s)\n",
2578 PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir),
2579 fDirEnabled ? "enabled" : "disabled", drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus),
2580 PDMHostAudioStreamStateGetName(enmBackendState) ));
2581
2582 if (fDirEnabled)
2583 {
2584 if ( (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY /* don't really need this check, do we? */)
2585 && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
2586 || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) )
2587 {
2588 /** @todo Backend will change to explicit methods here, so please don't simplify
2589 * the switch. */
2590 switch (enmStreamCmd)
2591 {
2592 case PDMAUDIOSTREAMCMD_ENABLE:
2593 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend,
2594 PDMAUDIOSTREAMCMD_ENABLE);
2595 break;
2596
2597 case PDMAUDIOSTREAMCMD_DISABLE:
2598 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend,
2599 PDMAUDIOSTREAMCMD_DISABLE);
2600 break;
2601
2602 case PDMAUDIOSTREAMCMD_PAUSE:
2603 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend,
2604 PDMAUDIOSTREAMCMD_PAUSE);
2605 break;
2606
2607 case PDMAUDIOSTREAMCMD_RESUME:
2608 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend,
2609 PDMAUDIOSTREAMCMD_RESUME);
2610 break;
2611
2612 case PDMAUDIOSTREAMCMD_DRAIN:
2613 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend,
2614 PDMAUDIOSTREAMCMD_DRAIN);
2615 break;
2616
2617 default:
2618 AssertMsgFailedBreakStmt(("Command %RU32 not implemented\n", enmStreamCmd), rc = VERR_INTERNAL_ERROR_2);
2619 }
2620 if (RT_SUCCESS(rc))
2621 Log2Func(("[%s] %s succeeded (%Rrc)\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
2622 else
2623 {
2624 LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));
2625 if ( rc != VERR_NOT_IMPLEMENTED
2626 && rc != VERR_NOT_SUPPORTED
2627 && rc != VERR_AUDIO_STREAM_NOT_READY)
2628 LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, rc));
2629 }
2630 }
2631 else
2632 LogFlowFunc(("enmBackendStat(=%s) != OKAY || !(fStatus(=%#x) & BACKEND_READY)\n",
2633 PDMHostAudioStreamStateGetName(enmBackendState), pStreamEx->fStatus));
2634 }
2635 else
2636 LogFlowFunc(("fDirEnabled=false\n"));
2637
2638 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
2639 return rc;
2640}
2641
2642
2643/**
2644 * Resets the given audio stream.
2645 *
2646 * @param pStreamEx Stream to reset.
2647 */
2648static void drvAudioStreamResetOnDisable(PDRVAUDIOSTREAM pStreamEx)
2649{
2650 drvAudioStreamResetInternal(pStreamEx);
2651
2652 LogFunc(("[%s]\n", pStreamEx->Core.szName));
2653
2654 pStreamEx->fStatus &= PDMAUDIOSTREAM_STS_BACKEND_CREATED | PDMAUDIOSTREAM_STS_BACKEND_READY;
2655 pStreamEx->Core.fWarningsShown = PDMAUDIOSTREAM_WARN_FLAGS_NONE;
2656
2657#ifdef VBOX_WITH_STATISTICS
2658 /*
2659 * Reset statistics.
2660 */
2661 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN)
2662 {
2663 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalFramesCaptured);
2664 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalTimesCaptured);
2665 STAM_COUNTER_RESET(&pStreamEx->In.Stats.TotalTimesRead);
2666 }
2667 else if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
2668 {
2669 }
2670 else
2671 AssertFailed();
2672#endif
2673}
2674
2675
2676/**
2677 * Controls an audio stream.
2678 *
2679 * @returns VBox status code.
2680 * @param pThis Pointer to driver instance.
2681 * @param pStreamEx Stream to control.
2682 * @param enmStreamCmd Control command.
2683 */
2684static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)
2685{
2686 AssertPtr(pThis);
2687 AssertPtr(pStreamEx);
2688 Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
2689
2690#ifdef LOG_ENABLED
2691 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
2692#endif
2693 LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd),
2694 drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
2695
2696 int rc = VINF_SUCCESS;
2697
2698 switch (enmStreamCmd)
2699 {
2700 case PDMAUDIOSTREAMCMD_ENABLE:
2701 if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED))
2702 {
2703 /* Are we still draining this stream? Then we must disable it first. */
2704 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
2705 {
2706 LogFunc(("Stream '%s' is still draining - disabling...\n", pStreamEx->Core.szName));
2707 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2708 AssertRC(rc);
2709 if (drvAudioStreamGetBackendState(pThis, pStreamEx) != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
2710 {
2711 pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
2712 drvAudioStreamResetInternal(pStreamEx);
2713 rc = VINF_SUCCESS;
2714 }
2715 }
2716
2717 if (RT_SUCCESS(rc))
2718 {
2719 /* Reset the play state before we try to start. */
2720 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
2721 pStreamEx->enmLastBackendState = enmBackendState;
2722 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
2723 {
2724 pStreamEx->Out.cbPreBuffered = 0;
2725 pStreamEx->Out.offPreBuf = 0;
2726 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
2727 switch (enmBackendState)
2728 {
2729 case PDMHOSTAUDIOSTREAMSTATE_INITIALIZING:
2730 if (pStreamEx->Out.cbPreBufThreshold > 0)
2731 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
2732 break;
2733 case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
2734 AssertFailed();
2735 RT_FALL_THROUGH();
2736 case PDMHOSTAUDIOSTREAMSTATE_OKAY:
2737 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
2738 ? DRVAUDIOPLAYSTATE_PREBUF : DRVAUDIOPLAYSTATE_PLAY;
2739 break;
2740 case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
2741 case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
2742 break;
2743 /* no default */
2744 case PDMHOSTAUDIOSTREAMSTATE_INVALID:
2745 case PDMHOSTAUDIOSTREAMSTATE_END:
2746 case PDMHOSTAUDIOSTREAMSTATE_32BIT_HACK:
2747 break;
2748 }
2749 LogFunc(("ENABLE: enmBackendState=%s enmPlayState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState),
2750 drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
2751 }
2752 else
2753 LogFunc(("ENABLE: enmBackendState=%s\n", PDMHostAudioStreamStateGetName(enmBackendState)));
2754
2755 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);
2756 if (RT_SUCCESS(rc))
2757 {
2758 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_ENABLED;
2759 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2760 }
2761 }
2762 }
2763 break;
2764
2765 case PDMAUDIOSTREAMCMD_DISABLE:
2766 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2767 {
2768 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2769 LogFunc(("DISABLE '%s': Backend DISABLE -> %Rrc\n", pStreamEx->Core.szName, rc));
2770 if (RT_SUCCESS(rc)) /** @todo ignore this and reset it anyway? */
2771 drvAudioStreamResetOnDisable(pStreamEx);
2772 }
2773 break;
2774
2775 case PDMAUDIOSTREAMCMD_PAUSE:
2776 if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) == PDMAUDIOSTREAM_STS_ENABLED)
2777 {
2778 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);
2779 if (RT_SUCCESS(rc))
2780 {
2781 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PAUSED;
2782 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2783 }
2784 }
2785 break;
2786
2787 case PDMAUDIOSTREAMCMD_RESUME:
2788 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PAUSED)
2789 {
2790 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED);
2791 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);
2792 if (RT_SUCCESS(rc))
2793 {
2794 pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_PAUSED;
2795 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2796 }
2797 }
2798 break;
2799
2800 case PDMAUDIOSTREAMCMD_DRAIN:
2801 /*
2802 * Only for output streams and we don't want this command more than once.
2803 */
2804 AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_FUNCTION);
2805 AssertBreak(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE));
2806 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
2807 {
2808 rc = VERR_INTERNAL_ERROR_2;
2809 switch (pStreamEx->Out.enmPlayState)
2810 {
2811 case DRVAUDIOPLAYSTATE_PREBUF:
2812 if (pStreamEx->Out.cbPreBuffered > 0)
2813 {
2814 LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data...\n", pStreamEx->Core.szName));
2815 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
2816 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
2817 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2818 rc = VINF_SUCCESS;
2819 break;
2820 }
2821 RT_FALL_THROUGH();
2822 case DRVAUDIOPLAYSTATE_NOPLAY:
2823 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
2824 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
2825 LogFunc(("DRAIN '%s': Nothing to drain (enmPlayState=%s)\n",
2826 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
2827 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2828 AssertRC(rc);
2829 drvAudioStreamResetOnDisable(pStreamEx);
2830 break;
2831
2832 case DRVAUDIOPLAYSTATE_PLAY:
2833 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
2834 LogFunc(("DRAIN '%s': Initiating backend draining (enmPlayState=%s -> NOPLAY) ...\n",
2835 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
2836 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
2837 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
2838 if (RT_SUCCESS(rc))
2839 {
2840 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
2841 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2842 }
2843 else
2844 {
2845 LogFunc(("DRAIN '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
2846 pStreamEx->Core.szName, rc));
2847 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
2848 AssertRC(rc);
2849 drvAudioStreamResetOnDisable(pStreamEx);
2850 }
2851 break;
2852
2853 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
2854 LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data (already committing)...\n",
2855 pStreamEx->Core.szName));
2856 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
2857 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
2858 rc = VINF_SUCCESS;
2859 break;
2860
2861 /* no default */
2862 case DRVAUDIOPLAYSTATE_INVALID:
2863 case DRVAUDIOPLAYSTATE_END:
2864 AssertFailedBreak();
2865 }
2866 }
2867 break;
2868
2869 default:
2870 rc = VERR_NOT_IMPLEMENTED;
2871 break;
2872 }
2873
2874 if (RT_FAILURE(rc))
2875 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
2876
2877 return rc;
2878}
2879
2880
2881/**
2882 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}
2883 */
2884static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,
2885 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
2886{
2887 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
2888 AssertPtr(pThis);
2889
2890 /** @todo r=bird: why? It's not documented to ignore NULL streams. */
2891 if (!pStream)
2892 return VINF_SUCCESS;
2893 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
2894 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
2895 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2896 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
2897
2898 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
2899 AssertRCReturn(rc, rc);
2900
2901 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, PDMAudioStrmCmdGetName(enmStreamCmd)));
2902
2903 rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);
2904
2905 RTCritSectLeave(&pStreamEx->Core.CritSect);
2906 return rc;
2907}
2908
2909
2910/**
2911 * Copy data to the pre-buffer, ring-buffer style.
2912 *
2913 * The @a cbMax parameter is almost always set to the threshold size, the
2914 * exception is when commiting the buffer and we want to top it off to reduce
2915 * the number of transfers to the backend (the first transfer may start
2916 * playback, so more data is better).
2917 */
2918static int drvAudioStreamPreBuffer(PDRVAUDIOSTREAM pStreamEx, const uint8_t *pbBuf, uint32_t cbBuf, uint32_t cbMax)
2919{
2920 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
2921 AssertReturn(cbAlloc >= cbMax, VERR_INTERNAL_ERROR_3);
2922 AssertReturn(cbAlloc >= 8, VERR_INTERNAL_ERROR_4);
2923 AssertReturn(cbMax >= 8, VERR_INTERNAL_ERROR_5);
2924
2925 uint32_t offRead = pStreamEx->Out.offPreBuf;
2926 uint32_t cbCur = pStreamEx->Out.cbPreBuffered;
2927 AssertStmt(offRead < cbAlloc, offRead %= cbAlloc);
2928 AssertStmt(cbCur <= cbMax, offRead = (offRead + cbCur - cbMax) % cbAlloc; cbCur = cbMax);
2929
2930 /*
2931 * First chunk.
2932 */
2933 uint32_t offWrite = (offRead + cbCur) % cbAlloc;
2934 uint32_t cbToCopy = RT_MIN(cbAlloc - offWrite, cbBuf);
2935 memcpy(&pStreamEx->Out.pbPreBuf[offWrite], pbBuf, cbToCopy);
2936
2937 /* Advance. */
2938 offWrite = (offWrite + cbToCopy) % cbAlloc;
2939 for (;;)
2940 {
2941 pbBuf += cbToCopy;
2942 cbCur += cbToCopy;
2943 if (cbCur > cbMax)
2944 offRead = (offRead + cbCur - cbMax) % cbAlloc;
2945 cbBuf -= cbToCopy;
2946 if (!cbBuf)
2947 break;
2948
2949 /*
2950 * Second+ chunk, from the start of the buffer.
2951 *
2952 * Note! It is assumed very unlikely that we will ever see a cbBuf larger than
2953 * cbMax, so we don't waste space on clipping cbBuf here (can happen with
2954 * custom pre-buffer sizes).
2955 */
2956 Assert(offWrite == 0);
2957 cbToCopy = RT_MIN(cbAlloc, cbBuf);
2958 memcpy(pStreamEx->Out.pbPreBuf, pbBuf, cbToCopy);
2959 }
2960
2961 /*
2962 * Update the pre-buffering size and position.
2963 */
2964 pStreamEx->Out.cbPreBuffered = RT_MIN(cbCur, cbMax);
2965 pStreamEx->Out.offPreBuf = offRead;
2966 return VINF_SUCCESS;
2967}
2968
2969
2970/**
2971 * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
2972 *
2973 * Caller owns the lock.
2974 */
2975static int drvAudioStreamPlayLocked(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
2976 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2977{
2978 Log3Func(("%s: @%#RX64: cbBuf=%#x\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbBuf));
2979
2980 uint32_t cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
2981 pStreamEx->Out.Stats.cbBackendWritableBefore = cbWritable;
2982
2983 uint32_t cbWritten = 0;
2984 int rc = VINF_SUCCESS;
2985 uint8_t const cbFrame = PDMAudioPropsFrameSize(&pStreamEx->Core.Props);
2986 while (cbBuf >= cbFrame && cbWritable >= cbFrame)
2987 {
2988 uint32_t const cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Core.Props, RT_MIN(cbBuf, cbWritable));
2989 uint32_t cbWrittenNow = 0;
2990 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, pbBuf, cbToWrite, &cbWrittenNow);
2991 if (RT_SUCCESS(rc))
2992 {
2993 if (cbWrittenNow != cbToWrite)
2994 Log3Func(("%s: @%#RX64: Wrote less bytes than requested: %#x, requested %#x\n",
2995 pStreamEx->Core.szName, pStreamEx->offInternal, cbWrittenNow, cbToWrite));
2996#ifdef DEBUG_bird
2997 Assert(cbWrittenNow == cbToWrite);
2998#endif
2999 AssertStmt(cbWrittenNow <= cbToWrite, cbWrittenNow = cbToWrite);
3000 cbWritten += cbWrittenNow;
3001 cbBuf -= cbWrittenNow;
3002 pbBuf += cbWrittenNow;
3003 pStreamEx->offInternal += cbWrittenNow;
3004 }
3005 else
3006 {
3007 *pcbWritten = cbWritten;
3008 LogFunc(("%s: @%#RX64: pfnStreamPlay failed writing %#x bytes (%#x previous written, %#x writable): %Rrc\n",
3009 pStreamEx->Core.szName, pStreamEx->offInternal, cbToWrite, cbWritten, cbWritable, rc));
3010 return cbWritten ? VINF_SUCCESS : rc;
3011 }
3012
3013 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3014 }
3015
3016 *pcbWritten = cbWritten;
3017 pStreamEx->Out.Stats.cbBackendWritableAfter = cbWritable;
3018 if (cbWritten)
3019 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
3020
3021 Log3Func(("%s: @%#RX64: Wrote %#x bytes (%#x bytes left)\n", pStreamEx->Core.szName, pStreamEx->offInternal, cbWritten, cbBuf));
3022 return rc;
3023}
3024
3025
3026/**
3027 * Worker for drvAudioStreamPlay() and drvAudioStreamPreBufComitting().
3028 */
3029static int drvAudioStreamPlayToPreBuffer(PDRVAUDIOSTREAM pStreamEx, const void *pvBuf, uint32_t cbBuf, uint32_t cbMax,
3030 uint32_t *pcbWritten)
3031{
3032 int rc = drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, cbBuf, cbMax);
3033 if (RT_SUCCESS(rc))
3034 {
3035 *pcbWritten = cbBuf;
3036 pStreamEx->offInternal += cbBuf;
3037 Log3Func(("[%s] Pre-buffering (%s): wrote %#x bytes => %#x bytes / %u%%\n",
3038 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState), cbBuf, pStreamEx->Out.cbPreBuffered,
3039 pStreamEx->Out.cbPreBuffered * 100 / RT_MAX(pStreamEx->Out.cbPreBufThreshold, 1)));
3040
3041 }
3042 else
3043 *pcbWritten = 0;
3044 return rc;
3045}
3046
3047
3048/**
3049 * Used when we're committing (transfering) the pre-buffered bytes to the
3050 * device.
3051 *
3052 * This is called both from drvAudioStreamPlay() and
3053 * drvAudioStreamIterateInternal().
3054 *
3055 * @returns VBox status code.
3056 * @param pThis Pointer to the DrvAudio instance data.
3057 * @param pStreamEx The stream to commit the pre-buffering for.
3058 * @param pbBuf Buffer with new bytes to write. Can be NULL when called
3059 * in the PENDING_DISABLE state from
3060 * drvAudioStreamIterateInternal().
3061 * @param cbBuf Number of new bytes. Can be zero.
3062 * @param pcbWritten Where to return the number of bytes written.
3063 *
3064 * @note Locking: Stream critsect and hot-plug in shared mode.
3065 */
3066static int drvAudioStreamPreBufComitting(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
3067 const uint8_t *pbBuf, uint32_t cbBuf, uint32_t *pcbWritten)
3068{
3069 /*
3070 * First, top up the buffer with new data from pbBuf.
3071 */
3072 *pcbWritten = 0;
3073 if (cbBuf > 0)
3074 {
3075 uint32_t const cbToCopy = RT_MIN(pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered, cbBuf);
3076 if (cbToCopy > 0)
3077 {
3078 int rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pbBuf, cbBuf, pStreamEx->Out.cbPreBufAlloc, pcbWritten);
3079 AssertRCReturn(rc, rc);
3080 pbBuf += cbToCopy;
3081 cbBuf -= cbToCopy;
3082 }
3083 }
3084
3085 AssertReturn(pThis->pHostDrvAudio, VERR_AUDIO_BACKEND_NOT_ATTACHED);
3086
3087 /*
3088 * Write the pre-buffered chunk.
3089 */
3090 int rc = VINF_SUCCESS;
3091 uint32_t const cbAlloc = pStreamEx->Out.cbPreBufAlloc;
3092 AssertReturn(cbAlloc > 0, VERR_INTERNAL_ERROR_2);
3093 uint32_t off = pStreamEx->Out.offPreBuf;
3094 AssertStmt(off < pStreamEx->Out.cbPreBufAlloc, off %= cbAlloc);
3095 uint32_t cbLeft = pStreamEx->Out.cbPreBuffered;
3096 while (cbLeft > 0)
3097 {
3098 uint32_t const cbToWrite = RT_MIN(cbAlloc - off, cbLeft);
3099 Assert(cbToWrite > 0);
3100
3101 uint32_t cbPreBufWritten = 0;
3102 rc = pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, &pStreamEx->Out.pbPreBuf[off],
3103 cbToWrite, &cbPreBufWritten);
3104 AssertRCBreak(rc);
3105 if (!cbPreBufWritten)
3106 break;
3107 AssertStmt(cbPreBufWritten <= cbToWrite, cbPreBufWritten = cbToWrite);
3108 off = (off + cbPreBufWritten) % cbAlloc;
3109 cbLeft -= cbPreBufWritten;
3110 }
3111
3112 if (cbLeft == 0)
3113 {
3114 LogFunc(("@%#RX64: Wrote all %#x bytes of pre-buffered audio data. %s -> PLAY\n", pStreamEx->offInternal,
3115 pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3116 pStreamEx->Out.cbPreBuffered = 0;
3117 pStreamEx->Out.offPreBuf = 0;
3118 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY;
3119
3120 if (cbBuf > 0)
3121 {
3122 uint32_t cbWritten2 = 0;
3123 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, pbBuf, cbBuf, &cbWritten2);
3124 if (RT_SUCCESS(rc))
3125 *pcbWritten += cbWritten2;
3126 }
3127 else
3128 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
3129 }
3130 else
3131 {
3132 if (cbLeft != pStreamEx->Out.cbPreBuffered)
3133 pStreamEx->nsLastPlayedCaptured = RTTimeNanoTS();
3134
3135 LogRel2(("Audio: @%#RX64: Stream '%s' pre-buffering commit problem: wrote %#x out of %#x + %#x - rc=%Rrc *pcbWritten=%#x %s -> PREBUF_COMMITTING\n",
3136 pStreamEx->offInternal, pStreamEx->Core.szName, pStreamEx->Out.cbPreBuffered - cbLeft,
3137 pStreamEx->Out.cbPreBuffered, cbBuf, rc, *pcbWritten, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3138 AssertMsg( pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING
3139 || pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF
3140 || RT_FAILURE(rc),
3141 ("Buggy host driver buffer reporting? cbLeft=%#x cbPreBuffered=%#x enmPlayState=%s\n",
3142 cbLeft, pStreamEx->Out.cbPreBuffered, drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
3143
3144 pStreamEx->Out.cbPreBuffered = cbLeft;
3145 pStreamEx->Out.offPreBuf = off;
3146 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
3147 }
3148
3149 return *pcbWritten ? VINF_SUCCESS : rc;
3150}
3151
3152
3153/**
3154 * Does one iteration of an audio stream.
3155 *
3156 * This function gives the backend the chance of iterating / altering data and
3157 * does the actual mixing between the guest <-> host mixing buffers.
3158 *
3159 * @returns VBox status code.
3160 * @param pThis Pointer to driver instance.
3161 * @param pStreamEx Stream to iterate.
3162 */
3163static int drvAudioStreamIterateInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx)
3164{
3165 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
3166
3167#ifdef LOG_ENABLED
3168 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3169#endif
3170 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
3171
3172 /* Not enabled or paused? Skip iteration. */
3173 if ((pStreamEx->fStatus & (PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PAUSED)) != PDMAUDIOSTREAM_STS_ENABLED)
3174 return VINF_SUCCESS;
3175
3176 /*
3177 * Pending disable is really what we're here for.
3178 *
3179 * This only happens to output streams. We ASSUME the caller (MixerBuffer)
3180 * implements a timeout on the draining, so we skip that here.
3181 */
3182 if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
3183 { /* likely until we get to the end of the stream at least. */ }
3184 else
3185 {
3186 AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
3187 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3188
3189 /*
3190 * Move pre-buffered samples to the backend.
3191 */
3192 if (pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING)
3193 {
3194 if (pStreamEx->Out.cbPreBuffered > 0)
3195 {
3196 uint32_t cbIgnored = 0;
3197 drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
3198 Log3Func(("Stream '%s': Transferred %#x bytes\n", pStreamEx->Core.szName, cbIgnored));
3199 }
3200 if (pStreamEx->Out.cbPreBuffered == 0)
3201 {
3202 Log3Func(("Stream '%s': No more pre-buffered data -> NOPLAY + backend DRAIN\n", pStreamEx->Core.szName));
3203 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
3204
3205 int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
3206 if (RT_FAILURE(rc))
3207 {
3208 LogFunc(("Stream '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
3209 pStreamEx->Core.szName, rc));
3210 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3211 AssertRC(rc);
3212 drvAudioStreamResetOnDisable(pStreamEx);
3213 }
3214 }
3215 }
3216 else
3217 Assert(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_NOPLAY);
3218
3219 /*
3220 * Check the backend status to see if it's still draining and to
3221 * update our status when it stops doing so.
3222 */
3223 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
3224 if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
3225 {
3226 uint32_t cbIgnored = 0;
3227 pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, NULL, 0, &cbIgnored);
3228 }
3229 else
3230 {
3231 LogFunc(("Stream '%s': Backend finished draining.\n", pStreamEx->Core.szName));
3232 drvAudioStreamResetOnDisable(pStreamEx);
3233 }
3234
3235 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
3236 }
3237
3238 /* Update timestamps. */
3239 pStreamEx->nsLastIterated = RTTimeNanoTS();
3240
3241 return VINF_SUCCESS; /** @todo r=bird: What can the caller do with an error status here? */
3242}
3243
3244
3245/**
3246 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}
3247 */
3248static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3249{
3250 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3251 AssertPtr(pThis);
3252 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3253 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3254 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3255 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3256
3257 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3258 AssertRCReturn(rc, rc);
3259
3260 rc = drvAudioStreamIterateInternal(pThis, pStreamEx);
3261
3262 RTCritSectLeave(&pStreamEx->Core.CritSect);
3263
3264 if (RT_FAILURE(rc))
3265 LogFlowFuncLeaveRC(rc);
3266 return rc;
3267}
3268
3269
3270/**
3271 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetReadable}
3272 */
3273static DECLCALLBACK(uint32_t) drvAudioStreamGetReadable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3274{
3275 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3276 AssertPtr(pThis);
3277 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3278 AssertPtrReturn(pStreamEx, 0);
3279 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
3280 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
3281 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, ("Can't read from a non-input stream\n"));
3282
3283 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3284 AssertRCReturn(rc, 0);
3285 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3286
3287 /*
3288 * ...
3289 */
3290 uint32_t cbReadable = 0;
3291
3292 /* All input streams for this driver disabled? See @bugref{9882}. */
3293 const bool fDisabled = !pThis->In.fEnabled;
3294
3295 if ( pThis->pHostDrvAudio
3296 && ( PDMAudioStrmStatusCanRead(pStreamEx->fStatus)
3297 || fDisabled)
3298 )
3299 {
3300 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
3301 if (pStreamEx->fNoMixBufs)
3302 cbReadable = pThis->pHostDrvAudio
3303 && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)
3304 && enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
3305 && !fDisabled
3306 ? pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend)
3307 : 0;
3308 else
3309 {
3310 const uint32_t cfReadable = AudioMixBufLive(&pStreamEx->Guest.MixBuf);
3311 cbReadable = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadable);
3312 }
3313 if (!cbReadable)
3314 {
3315 /*
3316 * If nothing is readable, check if the stream on the backend side is ready to be read from.
3317 * If it isn't, return the number of bytes readable since the last read from this stream.
3318 *
3319 * This is needed for backends (e.g. VRDE) which do not provide any input data in certain
3320 * situations, but the device emulation needs input data to keep the DMA transfers moving.
3321 * Reading the actual data from a stream then will return silence then.
3322 */
3323 if ( enmBackendState != PDMHOSTAUDIOSTREAMSTATE_OKAY /** @todo probably not correct. OTOH, I'm not sure if we do what the above comment claims either. */
3324 || fDisabled)
3325 {
3326 cbReadable = PDMAudioPropsNanoToBytes(&pStreamEx->Host.Cfg.Props,
3327 RTTimeNanoTS() - pStreamEx->nsLastReadWritten);
3328 if (!(pStreamEx->Core.fWarningsShown & PDMAUDIOSTREAM_WARN_FLAGS_DISABLED))
3329 {
3330 if (fDisabled)
3331 LogRel(("Audio: Input for driver '%s' has been disabled, returning silence\n", pThis->BackendCfg.szName));
3332 else
3333 LogRel(("Audio: Warning: Input for stream '%s' of driver '%s' not ready (current input status is %s), returning silence\n",
3334 pStreamEx->Core.szName, pThis->BackendCfg.szName, PDMHostAudioStreamStateGetName(enmBackendState) ));
3335
3336 pStreamEx->Core.fWarningsShown |= PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;
3337 }
3338 }
3339 }
3340
3341 /* Make sure to align the readable size to the guest's frame size. */
3342 if (cbReadable)
3343 cbReadable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Guest.Cfg.Props, cbReadable);
3344 }
3345
3346 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
3347 RTCritSectLeave(&pStreamEx->Core.CritSect);
3348 Log3Func(("[%s] cbReadable=%#RX32 (%RU64ms)\n",
3349 pStreamEx->Core.szName, cbReadable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbReadable)));
3350 return cbReadable;
3351}
3352
3353
3354/**
3355 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetWritable}
3356 */
3357static DECLCALLBACK(uint32_t) drvAudioStreamGetWritable(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3358{
3359 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3360 AssertPtr(pThis);
3361 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3362 AssertPtrReturn(pStreamEx, 0);
3363 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, 0);
3364 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, 0);
3365 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, ("Can't write to a non-output stream\n"), 0);
3366
3367 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3368 AssertRCReturn(rc, 0);
3369 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3370
3371 /*
3372 * ...
3373 *
3374 * Note: We don't propagate the backend stream's status to the outside -- it's the job of this
3375 * audio connector to make sense of it.
3376 */
3377 uint32_t cbWritable = 0;
3378 DRVAUDIOPLAYSTATE const enmPlayMode = pStreamEx->Out.enmPlayState;
3379 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
3380 if ( PDMAudioStrmStatusCanWrite(pStreamEx->fStatus)
3381 && pThis->pHostDrvAudio != NULL
3382 && enmBackendState != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
3383 {
3384 switch (enmPlayMode)
3385 {
3386 /*
3387 * Whatever the backend can hold.
3388 */
3389 case DRVAUDIOPLAYSTATE_PLAY:
3390 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3391 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
3392 Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
3393 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3394 break;
3395
3396 /*
3397 * Whatever we've got of available space in the pre-buffer.
3398 * Note! For the last round when we pass the pre-buffering threshold, we may
3399 * report fewer bytes than what a DMA timer period for the guest device
3400 * typically produces, however that should be transfered in the following
3401 * round that goes directly to the backend buffer.
3402 */
3403 case DRVAUDIOPLAYSTATE_PREBUF:
3404 cbWritable = pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered;
3405 if (!cbWritable)
3406 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 2);
3407 break;
3408
3409 /*
3410 * These are slightly more problematic and can go wrong if the pre-buffer is
3411 * manually configured to be smaller than the output of a typeical DMA timer
3412 * period for the guest device. So, to overcompensate, we just report back
3413 * the backend buffer size (the pre-buffer is circular, so no overflow issue).
3414 */
3415 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3416 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3417 cbWritable = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props,
3418 RT_MAX(pStreamEx->Host.Cfg.Backend.cFramesBufferSize,
3419 pStreamEx->Host.Cfg.Backend.cFramesPreBuffering));
3420 break;
3421
3422 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
3423 {
3424 /* Buggy backend: We weren't able to copy all the pre-buffered data to it
3425 when reaching the threshold. Try escape this situation, or at least
3426 keep the extra buffering to a minimum. We must try write something
3427 as long as there is space for it, as we need the pfnStreamWrite call
3428 to move the data. */
3429 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
3430 Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
3431 uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 8);
3432 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3433 if (cbWritable >= pStreamEx->Out.cbPreBuffered + cbMin)
3434 cbWritable -= pStreamEx->Out.cbPreBuffered + cbMin / 2;
3435 else
3436 cbWritable = RT_MIN(cbMin, pStreamEx->Out.cbPreBufAlloc - pStreamEx->Out.cbPreBuffered);
3437 AssertLogRel(cbWritable);
3438 break;
3439 }
3440
3441 case DRVAUDIOPLAYSTATE_NOPLAY:
3442 break;
3443 case DRVAUDIOPLAYSTATE_INVALID:
3444 case DRVAUDIOPLAYSTATE_END:
3445 AssertFailed();
3446 break;
3447 }
3448
3449 /* Make sure to align the writable size to the host's frame size. */
3450 cbWritable = PDMAudioPropsFloorBytesToFrame(&pStreamEx->Host.Cfg.Props, cbWritable);
3451 }
3452
3453 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
3454 RTCritSectLeave(&pStreamEx->Core.CritSect);
3455 Log3Func(("[%s] cbWritable=%#RX32 (%RU64ms) enmPlayMode=%s enmBackendState=%s\n",
3456 pStreamEx->Core.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable),
3457 drvAudioPlayStateName(enmPlayMode), PDMHostAudioStreamStateGetName(enmBackendState) ));
3458 return cbWritable;
3459}
3460
3461
3462/**
3463 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetState}
3464 */
3465static DECLCALLBACK(PDMAUDIOSTREAMSTATE) drvAudioStreamGetState(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)
3466{
3467 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3468 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3469 AssertPtrReturn(pStreamEx, PDMAUDIOSTREAMSTATE_INVALID);
3470 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
3471 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, PDMAUDIOSTREAMSTATE_INVALID);
3472
3473 /*
3474 * Get the status mask.
3475 */
3476 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3477 AssertRCReturn(rc, PDMAUDIOSTREAMSTATE_INVALID);
3478 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3479
3480 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
3481 uint32_t const fStrmStatus = pStreamEx->fStatus;
3482 PDMAUDIODIR const enmDir = pStreamEx->Guest.Cfg.enmDir;
3483 Assert(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
3484
3485 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
3486 RTCritSectLeave(&pStreamEx->Core.CritSect);
3487
3488 /*
3489 * Translate it to state enum value.
3490 */
3491 PDMAUDIOSTREAMSTATE enmState;
3492 if (!(fStrmStatus & PDMAUDIOSTREAM_STS_NEED_REINIT))
3493 {
3494 if (fStrmStatus & PDMAUDIOSTREAM_STS_BACKEND_CREATED)
3495 {
3496 if ( (fStrmStatus & PDMAUDIOSTREAM_STS_ENABLED)
3497 && (enmDir == PDMAUDIODIR_IN ? pThis->In.fEnabled : pThis->Out.fEnabled)
3498 && ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
3499 || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING
3500 || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING ))
3501 enmState = enmDir == PDMAUDIODIR_IN ? PDMAUDIOSTREAMSTATE_ENABLED_READABLE : PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE;
3502 else
3503 enmState = PDMAUDIOSTREAMSTATE_INACTIVE;
3504 }
3505 else
3506 enmState = PDMAUDIOSTREAMSTATE_NOT_WORKING;
3507 }
3508 else
3509 enmState = PDMAUDIOSTREAMSTATE_NEED_REINIT;
3510
3511#ifdef LOG_ENABLED
3512 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3513#endif
3514 Log3Func(("[%s] returns %s (status: %s)\n", pStreamEx->Core.szName, PDMAudioStreamStateGetName(enmState),
3515 drvAudioStreamStatusToStr(szStreamSts, fStrmStatus)));
3516 return enmState;
3517}
3518
3519
3520/**
3521 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume}
3522 */
3523static DECLCALLBACK(int) drvAudioStreamSetVolume(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, PPDMAUDIOVOLUME pVol)
3524{
3525 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
3526 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3527 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3528 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
3529 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3530 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3531 AssertReturn(!pStreamEx->fNoMixBufs, VWRN_INVALID_STATE);
3532
3533 LogFlowFunc(("[%s] volL=%RU32, volR=%RU32, fMute=%RTbool\n", pStreamEx->Core.szName, pVol->uLeft, pVol->uRight, pVol->fMuted));
3534
3535 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3536 AssertRCReturn(rc, rc);
3537
3538 AudioMixBufSetVolume(&pStreamEx->Guest.MixBuf, pVol);
3539 AudioMixBufSetVolume(&pStreamEx->Host.MixBuf, pVol);
3540
3541 RTCritSectLeave(&pStreamEx->Core.CritSect);
3542 return VINF_SUCCESS;
3543}
3544
3545
3546/**
3547 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay}
3548 */
3549static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3550 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
3551{
3552 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3553 AssertPtr(pThis);
3554
3555 /*
3556 * Check input and sanity.
3557 */
3558 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
3559 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3560 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3561 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3562 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3563 uint32_t uTmp;
3564 if (!pcbWritten)
3565 pcbWritten = &uTmp;
3566 AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);
3567
3568 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3569 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3570 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT,
3571 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",
3572 pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir)), VERR_ACCESS_DENIED);
3573 Assert(pStreamEx->fNoMixBufs);
3574
3575 AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Guest.Cfg.Props, cbBuf),
3576 ("Stream '%s' got a non-frame-aligned write (%#RX32 bytes)\n", pStreamEx->Core.szName, cbBuf));
3577
3578 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3579 AssertRCReturn(rc, rc);
3580
3581 /*
3582 * First check that we can write to the stream, and if not,
3583 * whether to just drop the input into the bit bucket.
3584 */
3585 if (PDMAudioStrmStatusIsReady(pStreamEx->fStatus))
3586 {
3587 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3588 if ( pThis->Out.fEnabled /* (see @bugref{9882}) */
3589 && pThis->pHostDrvAudio != NULL)
3590 {
3591 /*
3592 * Get the backend state and process changes to it since last time we checked.
3593 */
3594 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendStateAndProcessChanges(pThis, pStreamEx);
3595
3596 /*
3597 * Do the transfering.
3598 */
3599 switch (pStreamEx->Out.enmPlayState)
3600 {
3601 case DRVAUDIOPLAYSTATE_PLAY:
3602 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
3603 Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
3604 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3605 break;
3606
3607 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
3608 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
3609 Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
3610 rc = drvAudioStreamPlayLocked(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3611 drvAudioStreamPreBuffer(pStreamEx, (uint8_t const *)pvBuf, *pcbWritten, pStreamEx->Out.cbPreBufThreshold);
3612 break;
3613
3614 case DRVAUDIOPLAYSTATE_PREBUF:
3615 if (cbBuf + pStreamEx->Out.cbPreBuffered < pStreamEx->Out.cbPreBufThreshold)
3616 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3617 else if ( enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
3618 && (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY))
3619 {
3620 Log3Func(("[%s] Pre-buffering completing: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x\n",
3621 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3622 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3623 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3624 }
3625 else
3626 {
3627 Log3Func(("[%s] Pre-buffering completing but device not ready: cbBuf=%#x cbPreBuffered=%#x => %#x vs cbPreBufThreshold=%#x; PREBUF -> PREBUF_OVERDUE\n",
3628 pStreamEx->Core.szName, cbBuf, pStreamEx->Out.cbPreBuffered,
3629 cbBuf + pStreamEx->Out.cbPreBuffered, pStreamEx->Out.cbPreBufThreshold));
3630 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_OVERDUE;
3631 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3632 }
3633 break;
3634
3635 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
3636 Assert( !(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)
3637 || enmBackendState != PDMHOSTAUDIOSTREAMSTATE_OKAY);
3638 RT_FALL_THRU();
3639 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
3640 rc = drvAudioStreamPlayToPreBuffer(pStreamEx, pvBuf, cbBuf, pStreamEx->Out.cbPreBufThreshold, pcbWritten);
3641 break;
3642
3643 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
3644 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
3645 Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY);
3646 rc = drvAudioStreamPreBufComitting(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);
3647 break;
3648
3649 case DRVAUDIOPLAYSTATE_NOPLAY:
3650 *pcbWritten = cbBuf;
3651 pStreamEx->offInternal += cbBuf;
3652 Log3Func(("[%s] Discarding the data, backend state: %s\n", pStreamEx->Core.szName,
3653 PDMHostAudioStreamStateGetName(enmBackendState) ));
3654 break;
3655
3656 default:
3657 *pcbWritten = cbBuf;
3658 AssertMsgFailedBreak(("%d; cbBuf=%#x\n", pStreamEx->Out.enmPlayState, cbBuf));
3659 }
3660
3661 if (!pThis->CfgOut.Dbg.fEnabled || RT_FAILURE(rc))
3662 { /* likely */ }
3663 else
3664 AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pvBuf, *pcbWritten, 0 /* fFlags */);
3665 }
3666 else
3667 {
3668 *pcbWritten = cbBuf;
3669 pStreamEx->offInternal += cbBuf;
3670 Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.szName,
3671 !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));
3672 }
3673 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
3674 }
3675 else
3676 rc = VERR_AUDIO_STREAM_NOT_READY;
3677
3678 RTCritSectLeave(&pStreamEx->Core.CritSect);
3679 return rc;
3680}
3681
3682
3683/**
3684 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}
3685 */
3686static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,
3687 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
3688{
3689 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3690 AssertPtr(pThis);
3691 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3692 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3693 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
3694 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
3695 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
3696 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3697 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3698 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3699 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",
3700 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3701
3702 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3703 AssertRCReturn(rc, rc);
3704
3705 /*
3706 * ...
3707 */
3708 uint32_t cbReadTotal = 0;
3709
3710 do
3711 {
3712 uint32_t cfReadTotal = 0;
3713
3714 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf);
3715
3716 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */
3717 {
3718 if (!PDMAudioStrmStatusCanRead(pStreamEx->fStatus))
3719 {
3720 rc = VERR_AUDIO_STREAM_NOT_READY;
3721 break;
3722 }
3723
3724 /*
3725 * Read from the parent buffer (that is, the guest buffer) which
3726 * should have the audio data in the format the guest needs.
3727 */
3728 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf));
3729 while (cfToRead)
3730 {
3731 uint32_t cfRead;
3732 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf,
3733 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3734 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead);
3735 if (RT_FAILURE(rc))
3736 break;
3737
3738#ifdef VBOX_WITH_STATISTICS
3739 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead);
3740 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);
3741 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead);
3742 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead);
3743#endif
3744 Assert(cfToRead >= cfRead);
3745 cfToRead -= cfRead;
3746
3747 cfReadTotal += cfRead;
3748
3749 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead);
3750 }
3751
3752 if (cfReadTotal)
3753 {
3754 if (pThis->CfgIn.Dbg.fEnabled)
3755 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead,
3756 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);
3757
3758 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal);
3759 }
3760 }
3761
3762 /* If we were not able to read as much data as requested, fill up the returned
3763 * data with silence.
3764 *
3765 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */
3766 if (cfReadTotal < cfBuf)
3767 {
3768 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,
3769 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal),
3770 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf)));
3771
3772 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props,
3773 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),
3774 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal),
3775 cfBuf - cfReadTotal);
3776
3777 cfReadTotal = cfBuf;
3778 }
3779
3780 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal);
3781
3782 pStreamEx->nsLastReadWritten = RTTimeNanoTS();
3783
3784 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));
3785
3786 } while (0);
3787
3788 RTCritSectLeave(&pStreamEx->Core.CritSect);
3789
3790 if (RT_SUCCESS(rc) && pcbRead)
3791 *pcbRead = cbReadTotal;
3792 return rc;
3793}
3794
3795
3796/**
3797 * Captures non-interleaved input from a host stream.
3798 *
3799 * @returns VBox status code.
3800 * @param pThis Driver instance.
3801 * @param pStreamEx Stream to capture from.
3802 * @param pcfCaptured Number of (host) audio frames captured.
3803 */
3804static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3805{
3806 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3807 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED);
3808
3809 /*
3810 * ...
3811 */
3812 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3813 if (!cbReadable)
3814 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3815
3816 uint32_t cbFree = AudioMixBufFreeBytes(&pStreamEx->Guest.MixBuf); /* Parent */
3817 if (!cbFree)
3818 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3819
3820 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */
3821 cbReadable = cbFree;
3822
3823 /*
3824 * ...
3825 */
3826 int rc = VINF_SUCCESS;
3827 uint32_t cfCapturedTotal = 0;
3828 while (cbReadable)
3829 {
3830 uint8_t abChunk[_4K];
3831 uint32_t cbCaptured;
3832 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3833 abChunk, RT_MIN(cbReadable, (uint32_t)sizeof(abChunk)), &cbCaptured);
3834 if (RT_FAILURE(rc))
3835 {
3836 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3837 AssertRC(rc2);
3838 break;
3839 }
3840
3841 Assert(cbCaptured <= sizeof(abChunk));
3842 if (cbCaptured > sizeof(abChunk)) /* Paranoia. */
3843 cbCaptured = (uint32_t)sizeof(abChunk);
3844
3845 if (!cbCaptured) /* Nothing captured? Take a shortcut. */
3846 break;
3847
3848 /* We use the host side mixing buffer as an intermediate buffer to do some
3849 * (first) processing (if needed), so always write the incoming data at offset 0. */
3850 uint32_t cfHstWritten = 0;
3851 rc = AudioMixBufWriteAt(&pStreamEx->Host.MixBuf, 0 /* offFrames */, abChunk, cbCaptured, &cfHstWritten);
3852 if ( RT_FAILURE(rc)
3853 || !cfHstWritten)
3854 {
3855 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n",
3856 pStreamEx->Core.szName, cbCaptured, cfHstWritten, rc));
3857 break;
3858 }
3859
3860 if (pThis->CfgIn.Dbg.fEnabled)
3861 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCaptureNonInterleaved, abChunk, cbCaptured, 0 /* fFlags */);
3862
3863 uint32_t cfHstMixed = 0;
3864 if (cfHstWritten)
3865 {
3866 int rc2 = AudioMixBufMixToParentEx(&pStreamEx->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */,
3867 &cfHstMixed /* pcSrcMixed */);
3868 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n",
3869 pStreamEx->Core.szName, cbCaptured, cfHstWritten, cfHstMixed, rc2));
3870 AssertRC(rc2);
3871 }
3872
3873 Assert(cbReadable >= cbCaptured);
3874 cbReadable -= cbCaptured;
3875 cfCapturedTotal += cfHstMixed;
3876 }
3877
3878 if (RT_SUCCESS(rc))
3879 {
3880 if (cfCapturedTotal)
3881 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3882 }
3883 else
3884 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStreamEx->Core.szName, rc));
3885
3886 if (pcfCaptured)
3887 *pcfCaptured = cfCapturedTotal;
3888
3889 return rc;
3890}
3891
3892
3893/**
3894 * Captures raw input from a host stream.
3895 *
3896 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without
3897 * no data layout processing done in between.
3898 *
3899 * Needed for e.g. the VRDP audio backend (in Main).
3900 *
3901 * @returns VBox status code.
3902 * @param pThis Driver instance.
3903 * @param pStreamEx Stream to capture from.
3904 * @param pcfCaptured Number of (host) audio frames captured.
3905 */
3906static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured)
3907{
3908 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN);
3909 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW);
3910 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable);
3911
3912 /*
3913 * ...
3914 */
3915 /* Note: Raw means *audio frames*, not bytes! */
3916 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend);
3917 if (!cfReadable)
3918 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName));
3919
3920 const uint32_t cfFree = AudioMixBufFree(&pStreamEx->Guest.MixBuf); /* Parent */
3921 if (!cfFree)
3922 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName));
3923
3924 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */
3925 cfReadable = cfFree;
3926
3927 /*
3928 * ...
3929 */
3930 int rc = VINF_SUCCESS;
3931 uint32_t cfCapturedTotal = 0;
3932 while (cfReadable)
3933 {
3934 PPDMAUDIOFRAME paFrames;
3935 uint32_t cfWritable;
3936 rc = AudioMixBufPeekMutable(&pStreamEx->Host.MixBuf, cfReadable, &paFrames, &cfWritable);
3937 if ( RT_FAILURE(rc)
3938 || !cfWritable)
3939 break;
3940
3941 uint32_t cfCaptured;
3942 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend,
3943 paFrames, cfWritable, &cfCaptured);
3944 if (RT_FAILURE(rc))
3945 {
3946 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
3947 AssertRC(rc2);
3948 break;
3949 }
3950
3951 Assert(cfCaptured <= cfWritable);
3952 if (cfCaptured > cfWritable) /* Paranoia. */
3953 cfCaptured = cfWritable;
3954
3955 Assert(cfReadable >= cfCaptured);
3956 cfReadable -= cfCaptured;
3957 cfCapturedTotal += cfCaptured;
3958 }
3959
3960 if (pcfCaptured)
3961 *pcfCaptured = cfCapturedTotal;
3962 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc));
3963 return rc;
3964}
3965
3966
3967/**
3968 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture}
3969 */
3970static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface,
3971 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured)
3972{
3973 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);
3974 AssertPtr(pThis);
3975 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;
3976 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
3977 AssertPtrNull(pcFramesCaptured);
3978 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3979 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
3980 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,
3981 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n",
3982 pStreamEx->Core.szName, pStreamEx->Core.enmDir));
3983 int rc = RTCritSectEnter(&pStreamEx->Core.CritSect);
3984 AssertRCReturn(rc, rc);
3985 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
3986
3987#ifdef LOG_ENABLED
3988 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];
3989#endif
3990 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, drvAudioStreamStatusToStr(szStreamSts, pStreamEx->fStatus)));
3991
3992 /*
3993 * ...
3994 */
3995 uint32_t cfCaptured = 0;
3996 do
3997 {
3998 if (!pThis->pHostDrvAudio)
3999 {
4000 rc = VERR_PDM_NO_ATTACHED_DRIVER;
4001 break;
4002 }
4003
4004 if ( !pThis->In.fEnabled
4005 || !PDMAudioStrmStatusCanRead(pStreamEx->fStatus)
4006 || !(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY))
4007 {
4008 rc = VERR_AUDIO_STREAM_NOT_READY;
4009 break;
4010 }
4011
4012 PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
4013 if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
4014 {
4015 /*
4016 * Do the actual capturing.
4017 */
4018 if (RT_LIKELY(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED))
4019 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStreamEx, &cfCaptured);
4020 else if (pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW)
4021 rc = drvAudioStreamCaptureRaw(pThis, pStreamEx, &cfCaptured);
4022 else
4023 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
4024
4025 if (RT_SUCCESS(rc))
4026 {
4027 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCaptured, rc));
4028
4029 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured);
4030 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesCaptured, cfCaptured);
4031 }
4032 else if (RT_UNLIKELY(RT_FAILURE(rc)))
4033 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));
4034 }
4035 else
4036 rc = VERR_AUDIO_STREAM_NOT_READY;
4037 } while (0);
4038
4039 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4040 RTCritSectLeave(&pStreamEx->Core.CritSect);
4041
4042 if (pcFramesCaptured)
4043 *pcFramesCaptured = cfCaptured;
4044
4045 if (RT_FAILURE(rc))
4046 LogFlowFuncLeaveRC(rc);
4047 return rc;
4048}
4049
4050
4051/*********************************************************************************************************************************
4052* PDMIHOSTAUDIOPORT interface implementation. *
4053*********************************************************************************************************************************/
4054
4055/**
4056 * Worker for drvAudioHostPort_DoOnWorkerThread with stream argument, called on
4057 * worker thread.
4058 */
4059static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadStreamWorker(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,
4060 uintptr_t uUser, void *pvUser)
4061{
4062 LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
4063 AssertPtrReturnVoid(pThis);
4064 AssertPtrReturnVoid(pStreamEx);
4065 AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
4066
4067 /*
4068 * The CritSectHotPlug lock should not be needed here as detach will destroy
4069 * the thread pool. So, we'll leave taking the stream lock to the worker we're
4070 * calling as there are no lock order concerns.
4071 */
4072 PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
4073 AssertPtrReturnVoid(pIHostDrvAudio);
4074 AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
4075 pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, pStreamEx->pBackend, uUser, pvUser);
4076
4077 drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
4078 LogFlowFunc(("returns\n"));
4079}
4080
4081
4082/**
4083 * Worker for drvAudioHostPort_DoOnWorkerThread without stream argument, called
4084 * on worker thread.
4085 *
4086 * This wrapper isn't technically required, but it helps with logging and a few
4087 * extra sanity checks.
4088 */
4089static DECLCALLBACK(void) drvAudioHostPort_DoOnWorkerThreadWorker(PDRVAUDIO pThis, uintptr_t uUser, void *pvUser)
4090{
4091 LogFlowFunc(("pThis=%p uUser=%#zx pvUser=%p\n", pThis, uUser, pvUser));
4092 AssertPtrReturnVoid(pThis);
4093
4094 /*
4095 * The CritSectHotPlug lock should not be needed here as detach will destroy
4096 * the thread pool.
4097 */
4098 PPDMIHOSTAUDIO const pIHostDrvAudio = pThis->pHostDrvAudio;
4099 AssertPtrReturnVoid(pIHostDrvAudio);
4100 AssertPtrReturnVoid(pIHostDrvAudio->pfnDoOnWorkerThread);
4101
4102 pIHostDrvAudio->pfnDoOnWorkerThread(pIHostDrvAudio, NULL, uUser, pvUser);
4103
4104 LogFlowFunc(("returns\n"));
4105}
4106
4107
4108/**
4109 * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnDoOnWorkerThread}
4110 */
4111static DECLCALLBACK(int) drvAudioHostPort_DoOnWorkerThread(PPDMIHOSTAUDIOPORT pInterface, PPDMAUDIOBACKENDSTREAM pStream,
4112 uintptr_t uUser, void *pvUser)
4113{
4114 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
4115 LogFlowFunc(("pStream=%p uUser=%#zx pvUser=%p\n", pStream, uUser, pvUser));
4116
4117 /*
4118 * Assert some sanity.
4119 */
4120 PDRVAUDIOSTREAM pStreamEx;
4121 if (!pStream)
4122 pStreamEx = NULL;
4123 else
4124 {
4125 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
4126 AssertReturn(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC, VERR_INVALID_MAGIC);
4127 pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
4128 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);
4129 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
4130 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);
4131 }
4132
4133 int rc = RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
4134 AssertRCReturn(rc, rc);
4135
4136 Assert(pThis->hReqPool != NIL_RTREQPOOL);
4137 AssertPtr(pThis->pHostDrvAudio);
4138 if ( pThis->hReqPool != NIL_RTREQPOOL
4139 && pThis->pHostDrvAudio != NULL)
4140 {
4141 AssertPtr(pThis->pHostDrvAudio->pfnDoOnWorkerThread);
4142 if (pThis->pHostDrvAudio->pfnDoOnWorkerThread)
4143 {
4144 /*
4145 * Try do the work.
4146 */
4147 if (!pStreamEx)
4148 {
4149 rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL /*phReq*/, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
4150 (PFNRT)drvAudioHostPort_DoOnWorkerThreadWorker, 3, pThis, uUser, pvUser);
4151 AssertRC(rc);
4152 }
4153 else
4154 {
4155 uint32_t cRefs = drvAudioStreamRetainInternal(pStreamEx);
4156 if (cRefs != UINT32_MAX)
4157 {
4158 rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
4159 (PFNRT)drvAudioHostPort_DoOnWorkerThreadStreamWorker,
4160 4, pThis, pStreamEx, uUser, pvUser);
4161 AssertRC(rc);
4162 if (RT_FAILURE(rc))
4163 {
4164 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4165 drvAudioStreamReleaseInternal(pThis, pStreamEx, true /*fMayDestroy*/);
4166 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
4167 }
4168 }
4169 else
4170 rc = VERR_INVALID_PARAMETER;
4171 }
4172 }
4173 else
4174 rc = VERR_INVALID_FUNCTION;
4175 }
4176 else
4177 rc = VERR_INVALID_STATE;
4178
4179 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4180 LogFlowFunc(("returns %Rrc\n", rc));
4181 return rc;
4182}
4183
4184
4185/**
4186 * Marks a stream for re-init.
4187 */
4188static void drvAudioStreamMarkNeedReInit(PDRVAUDIOSTREAM pStreamEx, const char *pszCaller)
4189{
4190 LogFlow((LOG_FN_FMT ": Flagging %s for re-init.\n", pszCaller, pStreamEx->Core.szName)); RT_NOREF(pszCaller);
4191 Assert(RTCritSectIsOwner(&pStreamEx->Core.CritSect));
4192
4193 pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_NEED_REINIT;
4194 PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
4195 pStreamEx->cTriesReInit = 0;
4196 pStreamEx->nsLastReInit = 0;
4197}
4198
4199
4200/**
4201 * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDeviceChanged}
4202 */
4203static DECLCALLBACK(void) drvAudioHostPort_NotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface, PDMAUDIODIR enmDir, void *pvUser)
4204{
4205 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
4206 AssertReturnVoid(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT);
4207 LogRel(("Audio: The %s device for %s is changing.\n", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->BackendCfg.szName));
4208
4209 /*
4210 * Grab the list lock in shared mode and do the work.
4211 */
4212 int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
4213 AssertRCReturnVoid(rc);
4214
4215 PDRVAUDIOSTREAM pStreamEx;
4216 RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
4217 {
4218 if (pStreamEx->Core.enmDir == enmDir)
4219 {
4220 RTCritSectEnter(&pStreamEx->Core.CritSect);
4221 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
4222
4223 if (pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged)
4224 {
4225 LogFlowFunc(("Calling pfnStreamNotifyDeviceChanged on %s, old backend state: %s...\n", pStreamEx->Core.szName,
4226 PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
4227 pThis->pHostDrvAudio->pfnStreamNotifyDeviceChanged(pThis->pHostDrvAudio, pStreamEx->pBackend, pvUser);
4228 LogFlowFunc(("New stream backend state: %s\n",
4229 PDMHostAudioStreamStateGetName(drvAudioStreamGetBackendState(pThis, pStreamEx)) ));
4230 }
4231 else
4232 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
4233
4234 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4235 RTCritSectLeave(&pStreamEx->Core.CritSect);
4236 }
4237 }
4238
4239 RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
4240}
4241
4242
4243/**
4244 * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyPreparingDeviceSwitch}
4245 */
4246static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyPreparingDeviceSwitch(PPDMIHOSTAUDIOPORT pInterface,
4247 PPDMAUDIOBACKENDSTREAM pStream)
4248{
4249 RT_NOREF(pInterface);
4250
4251 /*
4252 * Backend stream to validated DrvAudio stream:
4253 */
4254 AssertPtrReturnVoid(pStream);
4255 AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
4256 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
4257 AssertPtrReturnVoid(pStreamEx);
4258 AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
4259 AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
4260 LogFlowFunc(("pStreamEx=%p '%s'\n", pStreamEx, pStreamEx->Core.szName));
4261
4262 /*
4263 * Grab the lock and do switch the state (only needed for output streams for now).
4264 */
4265 RTCritSectEnter(&pStreamEx->Core.CritSect);
4266 AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
4267
4268 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
4269 {
4270 if (pStreamEx->Out.cbPreBufThreshold > 0)
4271 {
4272 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
4273 switch (enmPlayState)
4274 {
4275 case DRVAUDIOPLAYSTATE_PREBUF:
4276 case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
4277 case DRVAUDIOPLAYSTATE_NOPLAY:
4278 case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING: /* simpler */
4279 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_SWITCHING;
4280 break;
4281 case DRVAUDIOPLAYSTATE_PLAY:
4282 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PLAY_PREBUF;
4283 break;
4284 case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
4285 case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
4286 break;
4287 /* no default */
4288 case DRVAUDIOPLAYSTATE_END:
4289 case DRVAUDIOPLAYSTATE_INVALID:
4290 break;
4291 }
4292 LogFunc(("%s -> %s\n", drvAudioPlayStateName(enmPlayState), drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
4293 }
4294 else
4295 LogFunc(("No pre-buffering configured.\n"));
4296 }
4297 else
4298 LogFunc(("input stream, nothing to do.\n"));
4299
4300 RTCritSectLeave(&pStreamEx->Core.CritSect);
4301}
4302
4303
4304/**
4305 * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnStreamNotifyDeviceChanged}
4306 */
4307static DECLCALLBACK(void) drvAudioHostPort_StreamNotifyDeviceChanged(PPDMIHOSTAUDIOPORT pInterface,
4308 PPDMAUDIOBACKENDSTREAM pStream, bool fReInit)
4309{
4310 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
4311
4312 /*
4313 * Backend stream to validated DrvAudio stream:
4314 */
4315 AssertPtrReturnVoid(pStream);
4316 AssertReturnVoid(pStream->uMagic == PDMAUDIOBACKENDSTREAM_MAGIC);
4317 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream->pStream;
4318 AssertPtrReturnVoid(pStreamEx);
4319 AssertReturnVoid(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC);
4320 AssertReturnVoid(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC);
4321
4322 /*
4323 * Grab the lock and do the requested work.
4324 */
4325 RTCritSectEnter(&pStreamEx->Core.CritSect);
4326 AssertReturnVoidStmt(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, RTCritSectLeave(&pStreamEx->Core.CritSect)); /* paranoia */
4327
4328 if (fReInit)
4329 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
4330 else
4331 {
4332 /*
4333 * Adjust the stream state now that the device has (perhaps finally) been switched.
4334 *
4335 * For enabled output streams, we must update the play state. We could try commit
4336 * pre-buffered data here, but it's really not worth the hazzle and risk (don't
4337 * know which thread we're on, do we now).
4338 */
4339 AssertStmt(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_NEED_REINIT),
4340 pStreamEx->fStatus &= ~PDMAUDIOSTREAM_STS_NEED_REINIT);
4341
4342
4343 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
4344 {
4345 DRVAUDIOPLAYSTATE const enmPlayState = pStreamEx->Out.enmPlayState;
4346 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
4347 LogFunc(("%s: %s -> %s\n", pStreamEx->Core.szName, drvAudioPlayStateName(enmPlayState),
4348 drvAudioPlayStateName(pStreamEx->Out.enmPlayState) ));
4349 RT_NOREF(enmPlayState);
4350 }
4351
4352 /* Disable and then fully resync. */
4353 /** @todo This doesn't work quite reliably if we're in draining mode
4354 * (PENDING_DISABLE, so the backend needs to take care of that prior to calling
4355 * us. Sigh. The idea was to avoid extra state mess in the backend... */
4356 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
4357 drvAudioStreamUpdateBackendOnStatus(pThis, pStreamEx, "device changed");
4358 }
4359
4360 RTCritSectLeave(&pStreamEx->Core.CritSect);
4361}
4362
4363
4364#ifdef VBOX_WITH_AUDIO_ENUM
4365/**
4366 * @callback_method_impl{FNTMTIMERDRV, Re-enumerate backend devices.}
4367 *
4368 * Used to do/trigger re-enumeration of backend devices with a delay after we
4369 * got notification as there can be further notifications following shortly
4370 * after the first one. Also good to get it of random COM/whatever threads.
4371 */
4372static DECLCALLBACK(void) drvAudioEnumerateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
4373{
4374 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4375 RT_NOREF(hTimer, pvUser);
4376
4377 /* Try push the work over to the thread-pool if we've got one. */
4378 RTCritSectRwEnterShared(&pThis->CritSectHotPlug);
4379 if (pThis->hReqPool != NIL_RTREQPOOL)
4380 {
4381 int rc = RTReqPoolCallEx(pThis->hReqPool, 0 /*cMillies*/, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
4382 (PFNRT)drvAudioDevicesEnumerateInternal,
4383 3, pThis, true /*fLog*/, (PPDMAUDIOHOSTENUM)NULL /*pDevEnum*/);
4384 LogFunc(("RTReqPoolCallEx: %Rrc\n", rc));
4385 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4386 if (RT_SUCCESS(rc))
4387 return;
4388 }
4389 else
4390 RTCritSectRwLeaveShared(&pThis->CritSectHotPlug);
4391
4392 LogFunc(("Calling drvAudioDevicesEnumerateInternal...\n"));
4393 drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
4394}
4395#endif /* VBOX_WITH_AUDIO_ENUM */
4396
4397
4398/**
4399 * @interface_method_impl{PDMIHOSTAUDIOPORT,pfnNotifyDevicesChanged}
4400 */
4401static DECLCALLBACK(void) drvAudioHostPort_NotifyDevicesChanged(PPDMIHOSTAUDIOPORT pInterface)
4402{
4403 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IHostAudioPort);
4404 LogRel(("Audio: Device configuration of driver '%s' has changed\n", pThis->BackendCfg.szName));
4405
4406#ifdef RT_OS_DARWIN /** @todo Remove legacy behaviour: */
4407 /* Mark all host streams to re-initialize. */
4408 int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
4409 AssertRCReturnVoid(rc2);
4410 PDRVAUDIOSTREAM pStreamEx;
4411 RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
4412 {
4413 RTCritSectEnter(&pStreamEx->Core.CritSect);
4414 drvAudioStreamMarkNeedReInit(pStreamEx, __PRETTY_FUNCTION__);
4415 RTCritSectLeave(&pStreamEx->Core.CritSect);
4416 }
4417 RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
4418#endif
4419
4420#ifdef VBOX_WITH_AUDIO_ENUM
4421 /*
4422 * Re-enumerate all host devices with a tiny delay to avoid re-doing this
4423 * when a bunch of changes happens at once (they typically do on windows).
4424 * We'll keep postponing it till it quiesces for a fraction of a second.
4425 */
4426 int rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hEnumTimer, RT_MS_1SEC / 3);
4427 AssertRC(rc);
4428#endif
4429}
4430
4431
4432/*********************************************************************************************************************************
4433* PDMIBASE interface implementation. *
4434*********************************************************************************************************************************/
4435
4436/**
4437 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
4438 */
4439static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
4440{
4441 LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));
4442
4443 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
4444 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4445
4446 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
4447 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);
4448 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIOPORT, &pThis->IHostAudioPort);
4449
4450 return NULL;
4451}
4452
4453
4454/*********************************************************************************************************************************
4455* PDMDRVREG interface implementation. *
4456*********************************************************************************************************************************/
4457
4458/**
4459 * Power Off notification.
4460 *
4461 * @param pDrvIns The driver instance data.
4462 */
4463static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
4464{
4465 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4466
4467 LogFlowFuncEnter();
4468
4469 /** @todo locking? */
4470 if (pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */
4471 {
4472 /*
4473 * Just destroy the host stream on the backend side.
4474 * The rest will either be destructed by the device emulation or
4475 * in drvAudioDestruct().
4476 */
4477 int rc = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
4478 AssertRCReturnVoid(rc);
4479
4480 PDRVAUDIOSTREAM pStreamEx;
4481 RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
4482 {
4483 RTCritSectEnter(&pStreamEx->Core.CritSect);
4484 drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
4485 RTCritSectLeave(&pStreamEx->Core.CritSect);
4486 }
4487
4488 RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
4489 }
4490
4491 LogFlowFuncLeave();
4492}
4493
4494
4495/**
4496 * Detach notification.
4497 *
4498 * @param pDrvIns The driver instance data.
4499 * @param fFlags Detach flags.
4500 */
4501static DECLCALLBACK(void) drvAudioDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
4502{
4503 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
4504 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4505 RT_NOREF(fFlags);
4506
4507 int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
4508 AssertLogRelRCReturnVoid(rc);
4509
4510 LogFunc(("%s (detached %p, hReqPool=%p)\n", pThis->BackendCfg.szName, pThis->pHostDrvAudio, pThis->hReqPool));
4511
4512 /*
4513 * Must first destroy the thread pool first so we are certain no threads
4514 * are still using the instance being detached. Release lock while doing
4515 * this as the thread functions may need to take it to complete.
4516 */
4517 if (pThis->pHostDrvAudio && pThis->hReqPool != NIL_RTREQPOOL)
4518 {
4519 RTREQPOOL hReqPool = pThis->hReqPool;
4520 pThis->hReqPool = NIL_RTREQPOOL;
4521
4522 RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
4523
4524 RTReqPoolRelease(hReqPool);
4525
4526 RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
4527 }
4528
4529 /*
4530 * Now we can safely set pHostDrvAudio to NULL.
4531 */
4532 pThis->pHostDrvAudio = NULL;
4533
4534 RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
4535}
4536
4537
4538/**
4539 * Initializes the host backend and queries its initial configuration.
4540 *
4541 * @returns VBox status code.
4542 * @param pThis Driver instance to be called.
4543 */
4544static int drvAudioHostInit(PDRVAUDIO pThis)
4545{
4546 LogFlowFuncEnter();
4547
4548 /*
4549 * Check the function pointers, make sure the ones we define as
4550 * mandatory are present.
4551 */
4552 PPDMIHOSTAUDIO pIHostDrvAudio = pThis->pHostDrvAudio;
4553 AssertPtrReturn(pIHostDrvAudio, VERR_INVALID_POINTER);
4554 AssertPtrReturn(pIHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);
4555 AssertPtrNullReturn(pIHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);
4556 AssertPtrNullReturn(pIHostDrvAudio->pfnSetDevice, VERR_INVALID_POINTER);
4557 AssertPtrNullReturn(pIHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);
4558 AssertPtrNullReturn(pIHostDrvAudio->pfnDoOnWorkerThread, VERR_INVALID_POINTER);
4559 AssertPtrNullReturn(pIHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);
4560 AssertPtrReturn(pIHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);
4561 AssertPtrNullReturn(pIHostDrvAudio->pfnStreamInitAsync, VERR_INVALID_POINTER);
4562 AssertPtrReturn(pIHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);
4563 AssertPtrNullReturn(pIHostDrvAudio->pfnStreamNotifyDeviceChanged, VERR_INVALID_POINTER);
4564 AssertPtrReturn(pIHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER);
4565 AssertPtrReturn(pIHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);
4566 AssertPtrReturn(pIHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);
4567 AssertPtrNullReturn(pIHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);
4568 AssertPtrReturn(pIHostDrvAudio->pfnStreamGetState, VERR_INVALID_POINTER);
4569 AssertPtrReturn(pIHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);
4570 AssertPtrReturn(pIHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);
4571
4572 /*
4573 * Get the backend configuration.
4574 *
4575 * Note! Limit the number of streams to max 128 in each direction to
4576 * prevent wasting resources.
4577 * Note! Take care not to wipe the DriverName config value on failure.
4578 */
4579 PDMAUDIOBACKENDCFG BackendCfg;
4580 RT_ZERO(BackendCfg);
4581 int rc = pIHostDrvAudio->pfnGetConfig(pIHostDrvAudio, &BackendCfg);
4582 if (RT_SUCCESS(rc))
4583 {
4584 if (LogIsEnabled() && strcmp(BackendCfg.szName, pThis->BackendCfg.szName) != 0)
4585 LogFunc(("BackendCfg.szName: '%s' -> '%s'\n", pThis->BackendCfg.szName, BackendCfg.szName));
4586 pThis->BackendCfg = BackendCfg;
4587 pThis->In.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsIn, 128);
4588 pThis->Out.cStreamsFree = RT_MIN(BackendCfg.cMaxStreamsOut, 128);
4589
4590 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
4591 }
4592 else
4593 {
4594 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->BackendCfg.szName, rc));
4595 return VERR_AUDIO_BACKEND_INIT_FAILED;
4596 }
4597
4598 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",
4599 pThis->BackendCfg.szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));
4600
4601#ifdef VBOX_WITH_AUDIO_ENUM
4602 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);
4603 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */
4604 AssertRC(rc2);
4605 /* Ignore rc2. */
4606#endif
4607
4608 /*
4609 * Create a thread pool if stream creation can be asynchronous.
4610 *
4611 * The pool employs no pushback as the caller is typically EMT and
4612 * shouldn't be delayed.
4613 *
4614 * The number of threads limits and the device implementations use
4615 * of pfnStreamDestroy limits the number of streams pending async
4616 * init. We use RTReqCancel in drvAudioStreamDestroy to allow us
4617 * to release extra reference held by the pfnStreamInitAsync call
4618 * if successful. Cancellation will only be possible if the call
4619 * hasn't been picked up by a worker thread yet, so the max number
4620 * of threads in the pool defines how many destroyed streams that
4621 * can be lingering. (We must keep this under control, otherwise
4622 * an evil guest could just rapidly trigger stream creation and
4623 * destruction to consume host heap and hog CPU resources for
4624 * configuring audio backends.)
4625 */
4626 if ( pThis->hReqPool == NIL_RTREQPOOL
4627 && ( pIHostDrvAudio->pfnStreamInitAsync
4628 || pIHostDrvAudio->pfnDoOnWorkerThread
4629 || (pThis->BackendCfg.fFlags & (PDMAUDIOBACKEND_F_ASYNC_HINT | PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY)) ))
4630 {
4631 char szName[16];
4632 RTStrPrintf(szName, sizeof(szName), "Aud%uWr", pThis->pDrvIns->iInstance);
4633 RTREQPOOL hReqPool = NIL_RTREQPOOL;
4634 rc = RTReqPoolCreate(3 /*cMaxThreads*/, RT_MS_30SEC /*cMsMinIdle*/, UINT32_MAX /*cThreadsPushBackThreshold*/,
4635 1 /*cMsMaxPushBack*/, szName, &hReqPool);
4636 LogFlowFunc(("Creating thread pool '%s': %Rrc, hReqPool=%p\n", szName, rc, hReqPool));
4637 AssertRCReturn(rc, rc);
4638
4639 rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_THREAD_FLAGS, RTTHREADFLAGS_COM_MTA);
4640 AssertRCReturnStmt(rc, RTReqPoolRelease(hReqPool), rc);
4641
4642 rc = RTReqPoolSetCfgVar(hReqPool, RTREQPOOLCFGVAR_MIN_THREADS, 1);
4643 AssertRC(rc); /* harmless */
4644
4645 pThis->hReqPool = hReqPool;
4646 }
4647 else
4648 LogFlowFunc(("No thread pool.\n"));
4649
4650 LogFlowFuncLeave();
4651 return VINF_SUCCESS;
4652}
4653
4654
4655/**
4656 * Does the actual backend driver attaching and queries the backend's interface.
4657 *
4658 * This is a worker for both drvAudioAttach and drvAudioConstruct.
4659 *
4660 * @returns VBox status code.
4661 * @param pDrvIns The driver instance.
4662 * @param pThis Pointer to driver instance.
4663 * @param fFlags Attach flags; see PDMDrvHlpAttach().
4664 */
4665static int drvAudioDoAttachInternal(PPDMDRVINS pDrvIns, PDRVAUDIO pThis, uint32_t fFlags)
4666{
4667 Assert(pThis->pHostDrvAudio == NULL); /* No nested attaching. */
4668
4669 /*
4670 * Attach driver below and query its connector interface.
4671 */
4672 PPDMIBASE pDownBase;
4673 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
4674 if (RT_SUCCESS(rc))
4675 {
4676 pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
4677 if (pThis->pHostDrvAudio)
4678 {
4679 /*
4680 * If everything went well, initialize the lower driver.
4681 */
4682 rc = drvAudioHostInit(pThis);
4683 if (RT_FAILURE(rc))
4684 pThis->pHostDrvAudio = NULL;
4685 }
4686 else
4687 {
4688 LogRel(("Audio: Failed to query interface for underlying host driver '%s'\n", pThis->BackendCfg.szName));
4689 rc = PDMDRV_SET_ERROR(pThis->pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
4690 N_("The host audio driver does not implement PDMIHOSTAUDIO!"));
4691 }
4692 }
4693 /*
4694 * If the host driver below us failed to construct for some beningn reason,
4695 * we'll report it as a runtime error and replace it with the Null driver.
4696 *
4697 * Note! We do NOT change anything in PDM (or CFGM), so pDrvIns->pDownBase
4698 * will remain NULL in this case.
4699 */
4700 else if ( rc == VERR_AUDIO_BACKEND_INIT_FAILED
4701 || rc == VERR_MODULE_NOT_FOUND
4702 || rc == VERR_SYMBOL_NOT_FOUND
4703 || rc == VERR_FILE_NOT_FOUND
4704 || rc == VERR_PATH_NOT_FOUND)
4705 {
4706 /* Complain: */
4707 LogRel(("DrvAudio: Host audio driver '%s' init failed with %Rrc. Switching to the NULL driver for now.\n",
4708 pThis->BackendCfg.szName, rc));
4709 PDMDrvHlpVMSetRuntimeError(pDrvIns, 0 /*fFlags*/, "HostAudioNotResponding",
4710 N_("Host audio backend (%s) initialization has failed. Selecting the NULL audio backend with the consequence that no sound is audible"),
4711 pThis->BackendCfg.szName);
4712
4713 /* Replace with null audio: */
4714 pThis->pHostDrvAudio = (PPDMIHOSTAUDIO)&g_DrvHostAudioNull;
4715 RTStrCopy(pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "NULL");
4716 rc = drvAudioHostInit(pThis);
4717 AssertRC(rc);
4718 }
4719
4720 LogFunc(("[%s] rc=%Rrc\n", pThis->BackendCfg.szName, rc));
4721 return rc;
4722}
4723
4724
4725/**
4726 * Attach notification.
4727 *
4728 * @param pDrvIns The driver instance data.
4729 * @param fFlags Attach flags.
4730 */
4731static DECLCALLBACK(int) drvAudioAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
4732{
4733 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
4734 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4735 LogFunc(("%s\n", pThis->BackendCfg.szName));
4736
4737 int rc = RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
4738 AssertRCReturn(rc, rc);
4739
4740 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
4741
4742 RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
4743 return rc;
4744}
4745
4746
4747/**
4748 * Handles state changes for all audio streams.
4749 *
4750 * @param pDrvIns Pointer to driver instance.
4751 * @param enmCmd Stream command to set for all streams.
4752 */
4753static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
4754{
4755 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
4756 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4757 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));
4758
4759 int rc2 = RTCritSectRwEnterShared(&pThis->CritSectGlobals);
4760 AssertRCReturnVoid(rc2);
4761
4762 PDRVAUDIOSTREAM pStreamEx;
4763 RTListForEach(&pThis->LstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)
4764 {
4765 RTCritSectEnter(&pStreamEx->Core.CritSect);
4766 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);
4767 RTCritSectLeave(&pStreamEx->Core.CritSect);
4768 }
4769
4770 RTCritSectRwLeaveShared(&pThis->CritSectGlobals);
4771}
4772
4773
4774/**
4775 * Resume notification.
4776 *
4777 * @param pDrvIns The driver instance data.
4778 */
4779static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
4780{
4781 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_RESUME);
4782}
4783
4784
4785/**
4786 * Suspend notification.
4787 *
4788 * @param pDrvIns The driver instance data.
4789 */
4790static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
4791{
4792 drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_PAUSE);
4793}
4794
4795
4796/**
4797 * Destructs an audio driver instance.
4798 *
4799 * @copydoc FNPDMDRVDESTRUCT
4800 */
4801static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
4802{
4803 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
4804 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4805
4806 LogFlowFuncEnter();
4807
4808 /*
4809 * Note: No calls here to the driver below us anymore,
4810 * as PDM already has destroyed it.
4811 * If you need to call something from the host driver,
4812 * do this in drvAudioPowerOff() instead.
4813 */
4814
4815 /* Thus, NULL the pointer to the host audio driver first,
4816 * so that routines like drvAudioStreamDestroyInternal() don't call the driver(s) below us anymore. */
4817 if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
4818 {
4819 RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
4820 pThis->pHostDrvAudio = NULL;
4821 RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
4822 }
4823 else
4824 {
4825 Assert(pThis->pHostDrvAudio == NULL);
4826 pThis->pHostDrvAudio = NULL;
4827 }
4828
4829 if (RTCritSectRwIsInitialized(&pThis->CritSectGlobals))
4830 {
4831 RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
4832
4833 PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
4834 RTListForEachSafe(&pThis->LstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
4835 {
4836 int rc = drvAudioStreamUninitInternal(pThis, pStreamEx);
4837 if (RT_SUCCESS(rc))
4838 {
4839 RTListNodeRemove(&pStreamEx->ListEntry);
4840 drvAudioStreamFree(pStreamEx);
4841 }
4842 }
4843
4844 RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
4845 RTCritSectRwDelete(&pThis->CritSectGlobals);
4846 }
4847
4848
4849 /* Sanity. */
4850 Assert(RTListIsEmpty(&pThis->LstStreams));
4851
4852 if (RTCritSectRwIsInitialized(&pThis->CritSectHotPlug))
4853 RTCritSectRwDelete(&pThis->CritSectHotPlug);
4854
4855#ifdef VBOX_WITH_STATISTICS
4856 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsActive);
4857 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalStreamsCreated);
4858 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesRead);
4859 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalFramesIn);
4860 PDMDrvHlpSTAMDeregister(pDrvIns, &pThis->Stats.TotalBytesRead);
4861#endif
4862
4863 if (pThis->hReqPool != NIL_RTREQPOOL)
4864 {
4865 uint32_t cRefs = RTReqPoolRelease(pThis->hReqPool);
4866 Assert(cRefs == 0); RT_NOREF(cRefs);
4867 pThis->hReqPool = NIL_RTREQPOOL;
4868 }
4869
4870 LogFlowFuncLeave();
4871}
4872
4873
4874/**
4875 * Constructs an audio driver instance.
4876 *
4877 * @copydoc FNPDMDRVCONSTRUCT
4878 */
4879static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
4880{
4881 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
4882 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
4883 LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfg, fFlags));
4884
4885 /*
4886 * Basic instance init.
4887 */
4888 RTListInit(&pThis->LstStreams);
4889 pThis->hReqPool = NIL_RTREQPOOL;
4890
4891 /*
4892 * Read configuration.
4893 */
4894 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns,
4895 "DriverName|"
4896 "InputEnabled|"
4897 "OutputEnabled|"
4898 "DebugEnabled|"
4899 "DebugPathOut|"
4900 /* Deprecated: */
4901 "PCMSampleBitIn|"
4902 "PCMSampleBitOut|"
4903 "PCMSampleHzIn|"
4904 "PCMSampleHzOut|"
4905 "PCMSampleSignedIn|"
4906 "PCMSampleSignedOut|"
4907 "PCMSampleSwapEndianIn|"
4908 "PCMSampleSwapEndianOut|"
4909 "PCMSampleChannelsIn|"
4910 "PCMSampleChannelsOut|"
4911 "PeriodSizeMsIn|"
4912 "PeriodSizeMsOut|"
4913 "BufferSizeMsIn|"
4914 "BufferSizeMsOut|"
4915 "PreBufferSizeMsIn|"
4916 "PreBufferSizeMsOut",
4917 "In|Out");
4918
4919 int rc = CFGMR3QueryStringDef(pCfg, "DriverName", pThis->BackendCfg.szName, sizeof(pThis->BackendCfg.szName), "Untitled");
4920 AssertLogRelRCReturn(rc, rc);
4921
4922 /* Neither input nor output by default for security reasons. */
4923 rc = CFGMR3QueryBoolDef(pCfg, "InputEnabled", &pThis->In.fEnabled, false);
4924 AssertLogRelRCReturn(rc, rc);
4925
4926 rc = CFGMR3QueryBoolDef(pCfg, "OutputEnabled", &pThis->Out.fEnabled, false);
4927 AssertLogRelRCReturn(rc, rc);
4928
4929 /* Debug stuff (same for both directions). */
4930 rc = CFGMR3QueryBoolDef(pCfg, "DebugEnabled", &pThis->CfgIn.Dbg.fEnabled, false);
4931 AssertLogRelRCReturn(rc, rc);
4932
4933 rc = CFGMR3QueryStringDef(pCfg, "DebugPathOut", pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut), "");
4934 AssertLogRelRCReturn(rc, rc);
4935 if (pThis->CfgIn.Dbg.szPathOut[0] == '\0')
4936 {
4937 rc = RTPathTemp(pThis->CfgIn.Dbg.szPathOut, sizeof(pThis->CfgIn.Dbg.szPathOut));
4938 if (RT_FAILURE(rc))
4939 {
4940 LogRel(("Audio: Warning! Failed to retrieve temporary directory: %Rrc - disabling debugging.\n", rc));
4941 pThis->CfgIn.Dbg.szPathOut[0] = '\0';
4942 pThis->CfgIn.Dbg.fEnabled = false;
4943 }
4944 }
4945 if (pThis->CfgIn.Dbg.fEnabled)
4946 LogRel(("Audio: Debugging for driver '%s' enabled (audio data written to '%s')\n",
4947 pThis->BackendCfg.szName, pThis->CfgIn.Dbg.szPathOut));
4948
4949 /* Copy debug setup to the output direction. */
4950 pThis->CfgOut.Dbg = pThis->CfgIn.Dbg;
4951
4952 LogRel2(("Audio: Verbose logging for driver '%s' is probably enabled too.\n", pThis->BackendCfg.szName));
4953 /* This ^^^^^^^ is the *WRONG* place for that kind of statement. Verbose logging might only be enabled for DrvAudio. */
4954 LogRel2(("Audio: Initial status for driver '%s' is: input is %s, output is %s\n",
4955 pThis->BackendCfg.szName, pThis->In.fEnabled ? "enabled" : "disabled", pThis->Out.fEnabled ? "enabled" : "disabled"));
4956
4957 /*
4958 * Per direction configuration. A bit complicated as
4959 * these wasn't originally in sub-nodes.
4960 */
4961 for (unsigned iDir = 0; iDir < 2; iDir++)
4962 {
4963 char szNm[48];
4964 PDRVAUDIOCFG pAudioCfg = iDir == 0 ? &pThis->CfgIn : &pThis->CfgOut;
4965 const char *pszDir = iDir == 0 ? "In" : "Out";
4966
4967#define QUERY_VAL_RET(a_Width, a_szName, a_pValue, a_uDefault, a_ExprValid, a_szValidRange) \
4968 do { \
4969 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pDirNode, strcpy(szNm, a_szName), a_pValue); \
4970 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4971 { \
4972 rc = RT_CONCAT(CFGMR3QueryU,a_Width)(pCfg, strcat(szNm, pszDir), a_pValue); \
4973 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) \
4974 { \
4975 *(a_pValue) = a_uDefault; \
4976 rc = VINF_SUCCESS; \
4977 } \
4978 else \
4979 LogRel(("DrvAudio: Warning! Please use '%s/" a_szName "' instead of '%s' for your VBoxInternal hacks\n", pszDir, szNm)); \
4980 } \
4981 AssertRCReturn(rc, PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, \
4982 N_("Configuration error: Failed to read %s config value '%s'"), pszDir, szNm)); \
4983 if (!(a_ExprValid)) \
4984 return PDMDrvHlpVMSetError(pDrvIns, VERR_OUT_OF_RANGE, RT_SRC_POS, \
4985 N_("Configuration error: Unsupported %s value %u. " a_szValidRange), szNm, *(a_pValue)); \
4986 } while (0)
4987
4988 PCFGMNODE const pDirNode = CFGMR3GetChild(pCfg, pszDir);
4989 rc = CFGMR3ValidateConfig(pDirNode, iDir == 0 ? "In/" : "Out/",
4990 "PCMSampleBit|"
4991 "PCMSampleHz|"
4992 "PCMSampleSigned|"
4993 "PCMSampleSwapEndian|"
4994 "PCMSampleChannels|"
4995 "PeriodSizeMs|"
4996 "BufferSizeMs|"
4997 "PreBufferSizeMs",
4998 "", pDrvIns->pReg->szName, pDrvIns->iInstance);
4999 AssertRCReturn(rc, rc);
5000
5001 uint8_t cSampleBits = 0;
5002 QUERY_VAL_RET(8, "PCMSampleBit", &cSampleBits, 0,
5003 cSampleBits == 0
5004 || cSampleBits == 8
5005 || cSampleBits == 16
5006 || cSampleBits == 32
5007 || cSampleBits == 64,
5008 "Must be either 0, 8, 16, 32 or 64");
5009 if (cSampleBits)
5010 PDMAudioPropsSetSampleSize(&pAudioCfg->Props, cSampleBits / 8);
5011
5012 uint8_t cChannels;
5013 QUERY_VAL_RET(8, "PCMSampleChannels", &cChannels, 0, cChannels <= 16, "Max 16");
5014 if (cChannels)
5015 PDMAudioPropsSetChannels(&pAudioCfg->Props, cChannels);
5016
5017 QUERY_VAL_RET(32, "PCMSampleHz", &pAudioCfg->Props.uHz, 0,
5018 pAudioCfg->Props.uHz == 0 || (pAudioCfg->Props.uHz >= 6000 && pAudioCfg->Props.uHz <= 768000),
5019 "In the range 6000 thru 768000, or 0");
5020
5021 QUERY_VAL_RET(8, "PCMSampleSigned", &pAudioCfg->uSigned, UINT8_MAX,
5022 pAudioCfg->uSigned == 0 || pAudioCfg->uSigned == 1 || pAudioCfg->uSigned == UINT8_MAX,
5023 "Must be either 0, 1, or 255");
5024
5025 QUERY_VAL_RET(8, "PCMSampleSwapEndian", &pAudioCfg->uSwapEndian, UINT8_MAX,
5026 pAudioCfg->uSwapEndian == 0 || pAudioCfg->uSwapEndian == 1 || pAudioCfg->uSwapEndian == UINT8_MAX,
5027 "Must be either 0, 1, or 255");
5028
5029 QUERY_VAL_RET(32, "PeriodSizeMs", &pAudioCfg->uPeriodSizeMs, 0,
5030 pAudioCfg->uPeriodSizeMs <= RT_MS_1SEC, "Max 1000");
5031
5032 QUERY_VAL_RET(32, "BufferSizeMs", &pAudioCfg->uBufferSizeMs, 0,
5033 pAudioCfg->uBufferSizeMs <= RT_MS_5SEC, "Max 5000");
5034
5035 QUERY_VAL_RET(32, "PreBufferSizeMs", &pAudioCfg->uPreBufSizeMs, UINT32_MAX,
5036 pAudioCfg->uPreBufSizeMs <= RT_MS_1SEC || pAudioCfg->uPreBufSizeMs == UINT32_MAX,
5037 "Max 1000, or 0xffffffff");
5038#undef QUERY_VAL_RET
5039 }
5040
5041 /*
5042 * Init the rest of the driver instance data.
5043 */
5044 rc = RTCritSectRwInit(&pThis->CritSectHotPlug);
5045 AssertRCReturn(rc, rc);
5046 rc = RTCritSectRwInit(&pThis->CritSectGlobals);
5047 AssertRCReturn(rc, rc);
5048#ifdef VBOX_STRICT
5049 /* Define locking order: */
5050 RTCritSectRwEnterExcl(&pThis->CritSectGlobals);
5051 RTCritSectRwEnterExcl(&pThis->CritSectHotPlug);
5052 RTCritSectRwLeaveExcl(&pThis->CritSectHotPlug);
5053 RTCritSectRwLeaveExcl(&pThis->CritSectGlobals);
5054#endif
5055
5056 pThis->pDrvIns = pDrvIns;
5057 /* IBase. */
5058 pDrvIns->IBase.pfnQueryInterface = drvAudioQueryInterface;
5059 /* IAudioConnector. */
5060 pThis->IAudioConnector.pfnEnable = drvAudioEnable;
5061 pThis->IAudioConnector.pfnIsEnabled = drvAudioIsEnabled;
5062 pThis->IAudioConnector.pfnGetConfig = drvAudioGetConfig;
5063 pThis->IAudioConnector.pfnGetStatus = drvAudioGetStatus;
5064 pThis->IAudioConnector.pfnStreamConfigHint = drvAudioStreamConfigHint;
5065 pThis->IAudioConnector.pfnStreamCreate = drvAudioStreamCreate;
5066 pThis->IAudioConnector.pfnStreamDestroy = drvAudioStreamDestroy;
5067 pThis->IAudioConnector.pfnStreamReInit = drvAudioStreamReInit;
5068 pThis->IAudioConnector.pfnStreamRetain = drvAudioStreamRetain;
5069 pThis->IAudioConnector.pfnStreamRelease = drvAudioStreamRelease;
5070 pThis->IAudioConnector.pfnStreamControl = drvAudioStreamControl;
5071 pThis->IAudioConnector.pfnStreamIterate = drvAudioStreamIterate;
5072 pThis->IAudioConnector.pfnStreamGetReadable = drvAudioStreamGetReadable;
5073 pThis->IAudioConnector.pfnStreamGetWritable = drvAudioStreamGetWritable;
5074 pThis->IAudioConnector.pfnStreamGetState = drvAudioStreamGetState;
5075 pThis->IAudioConnector.pfnStreamSetVolume = drvAudioStreamSetVolume;
5076 pThis->IAudioConnector.pfnStreamPlay = drvAudioStreamPlay;
5077 pThis->IAudioConnector.pfnStreamRead = drvAudioStreamRead;
5078 pThis->IAudioConnector.pfnStreamCapture = drvAudioStreamCapture;
5079 /* IHostAudioPort */
5080 pThis->IHostAudioPort.pfnDoOnWorkerThread = drvAudioHostPort_DoOnWorkerThread;
5081 pThis->IHostAudioPort.pfnNotifyDeviceChanged = drvAudioHostPort_NotifyDeviceChanged;
5082 pThis->IHostAudioPort.pfnStreamNotifyPreparingDeviceSwitch = drvAudioHostPort_StreamNotifyPreparingDeviceSwitch;
5083 pThis->IHostAudioPort.pfnStreamNotifyDeviceChanged = drvAudioHostPort_StreamNotifyDeviceChanged;
5084 pThis->IHostAudioPort.pfnNotifyDevicesChanged = drvAudioHostPort_NotifyDevicesChanged;
5085
5086 /*
5087 * Statistics.
5088 */
5089#ifdef VBOX_WITH_STATISTICS
5090 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsActive, "TotalStreamsActive",
5091 STAMUNIT_COUNT, "Total active audio streams.");
5092 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalStreamsCreated, "TotalStreamsCreated",
5093 STAMUNIT_COUNT, "Total created audio streams.");
5094 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesRead, "TotalFramesRead",
5095 STAMUNIT_COUNT, "Total frames read by device emulation.");
5096 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalFramesIn, "TotalFramesIn",
5097 STAMUNIT_COUNT, "Total frames captured by backend.");
5098 PDMDrvHlpSTAMRegCounterEx(pDrvIns, &pThis->Stats.TotalBytesRead, "TotalBytesRead",
5099 STAMUNIT_BYTES, "Total bytes read.");
5100#endif
5101
5102#ifdef VBOX_WITH_AUDIO_ENUM
5103 /*
5104 * Create a timer to trigger delayed device enumeration on device changes.
5105 */
5106 RTStrPrintf(pThis->szEnumTimerName, sizeof(pThis->szEnumTimerName), "AudioEnum-%u", pDrvIns->iInstance);
5107 rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvAudioEnumerateTimer, NULL /*pvUser*/,
5108 0 /*fFlags*/, pThis->szEnumTimerName, &pThis->hEnumTimer);
5109 AssertRCReturn(rc, rc);
5110#endif
5111
5112 /*
5113 * Attach the host driver, if present.
5114 */
5115 rc = drvAudioDoAttachInternal(pDrvIns, pThis, fFlags);
5116 if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
5117 rc = VINF_SUCCESS;
5118
5119 LogFlowFuncLeaveRC(rc);
5120 return rc;
5121}
5122
5123/**
5124 * Audio driver registration record.
5125 */
5126const PDMDRVREG g_DrvAUDIO =
5127{
5128 /* u32Version */
5129 PDM_DRVREG_VERSION,
5130 /* szName */
5131 "AUDIO",
5132 /* szRCMod */
5133 "",
5134 /* szR0Mod */
5135 "",
5136 /* pszDescription */
5137 "Audio connector driver",
5138 /* fFlags */
5139 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
5140 /* fClass */
5141 PDM_DRVREG_CLASS_AUDIO,
5142 /* cMaxInstances */
5143 UINT32_MAX,
5144 /* cbInstance */
5145 sizeof(DRVAUDIO),
5146 /* pfnConstruct */
5147 drvAudioConstruct,
5148 /* pfnDestruct */
5149 drvAudioDestruct,
5150 /* pfnRelocate */
5151 NULL,
5152 /* pfnIOCtl */
5153 NULL,
5154 /* pfnPowerOn */
5155 NULL,
5156 /* pfnReset */
5157 NULL,
5158 /* pfnSuspend */
5159 drvAudioSuspend,
5160 /* pfnResume */
5161 drvAudioResume,
5162 /* pfnAttach */
5163 drvAudioAttach,
5164 /* pfnDetach */
5165 drvAudioDetach,
5166 /* pfnPowerOff */
5167 drvAudioPowerOff,
5168 /* pfnSoftReset */
5169 NULL,
5170 /* u32EndVersion */
5171 PDM_DRVREG_VERSION
5172};
5173
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