VirtualBox

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

Last change on this file since 89132 was 89131, checked in by vboxsync, 4 years ago

DrvAudio: Fixed missing VINF_SUCCESS indicators in some DRAIN command paths. Log byte counts/offset as hex (easier to see stereo misalignments in hex among other things). bugref:9890

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

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