VirtualBox

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

Last change on this file since 94267 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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