VirtualBox

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

Last change on this file since 89081 was 89022, checked in by vboxsync, 4 years ago

DrvHostAudioCoreAudio: Cleanups. bugref:9890

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

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