VirtualBox

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

最後變更 在這個檔案從108236是 107925,由 vboxsync 提交於 7 週 前

Audio: Added a new, optional config option 'CacheEnabled' (via global/per-VM 'VBoxInternal2/Audio/CacheEnabled') for audio drivers to disable caching code (if any). Currently only supported by DrvHostAudioWasApi, ignored everywhere else. bugref:10844

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

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