VirtualBox

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

Last change on this file since 89224 was 89213, checked in by vboxsync, 4 years ago

Audio: Added an fImmediate indicator to the pfnStreamDestroy methods so the backend knows whether it's okay to continue draining the stream or if it must be destroyed without delay. The latter is typically only for shutdown and driver plumbing. This helps quite a bit for HDA/CoreAudio/knoppix. bugref:9890

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

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