VirtualBox

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

Last change on this file since 89188 was 89184, checked in by vboxsync, 4 years ago

Audio: Working on asynchronous stream creation, only there is a stupid stupid single lock approach in DrvAudio that significantly hampers the effect. bugref:9890

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

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