VirtualBox

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

Last change on this file since 103914 was 103430, checked in by vboxsync, 10 months ago

DrvAudio: No need to use either RTStrCopy[2] or RTStrCat[2] here, memcpy is all you need. bugref:3409

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

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