VirtualBox

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

Last change on this file since 89005 was 88993, checked in by vboxsync, 4 years ago

DrvAudio: Build fix (draining revamp). bugref:9890

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