VirtualBox

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

Last change on this file since 97441 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

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