VirtualBox

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

最後變更 在這個檔案從88897是 88897,由 vboxsync 提交於 4 年 前

DrvAudio: Process backend state changes in drvAudioStreamGetState too (not just drvAudioStreamPlay). bugref:9890

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

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