VirtualBox

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

Last change on this file since 89496 was 89489, checked in by vboxsync, 4 years ago

Audio: Simplified PDMIAUDIOCONNECTOR::pfnStreamCreate by moving the acquired configuration to the resulting stream (PDMAUDIOSTREAM::Cfg). bugref:9890

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