VirtualBox

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

最後變更 在這個檔案從98341是 98103,由 vboxsync 提交於 2 年 前

Copyright year updates by scm.

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

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