VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/AudioMixer.cpp@ 87758

Last change on this file since 87758 was 87273, checked in by vboxsync, 4 years ago

Audio/Mixer: Got rid of the stack-allocated scratch buffer when mixing / multiplexing stuff. Instead, each sink has a heap allocated one now, to avoid hammering the stack [build fix]. ticketoem2ref:36

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 74.2 KB
Line 
1/* $Id: AudioMixer.cpp 87273 2021-01-15 13:37:58Z vboxsync $ */
2/** @file
3 * Audio mixing routines for multiplexing audio sources in device emulations.
4 *
5 * == Overview
6 *
7 * This mixer acts as a layer between the audio connector interface and
8 * the actual device emulation, providing mechanisms for audio sources (input)
9 * and audio sinks (output).
10 *
11 * Think of this mixer as kind of a high(er) level interface for the audio
12 * connector interface, abstracting common tasks such as creating and managing
13 * various audio sources and sinks. This mixer class is purely optional and can
14 * be left out when implementing a new device emulation, using only the audi
15 * connector interface instead. For example, the SB16 emulation does not use
16 * this mixer and does all its stream management on its own.
17 *
18 * As audio driver instances are handled as LUNs on the device level, this
19 * audio mixer then can take care of e.g. mixing various inputs/outputs to/from
20 * a specific source/sink.
21 *
22 * How and which audio streams are connected to sinks/sources depends on how
23 * the audio mixer has been set up.
24 *
25 * A sink can connect multiple output streams together, whereas a source
26 * does this with input streams. Each sink / source consists of one or more
27 * so-called mixer streams, which then in turn have pointers to the actual
28 * PDM audio input/output streams.
29 *
30 * == Playback
31 *
32 * For output sinks there can be one or more mixing stream attached.
33 * As the host sets the overall pace for the device emulation (virtual time
34 * in the guest OS vs. real time on the host OS), an output mixing sink
35 * needs to make sure that all connected output streams are able to accept
36 * all the same amount of data at a time.
37 *
38 * This is called synchronous multiplexing.
39 *
40 * A mixing sink employs an own audio mixing buffer, which in turn can convert
41 * the audio (output) data supplied from the device emulation into the sink's
42 * audio format. As all connected mixing streams in theory could have the same
43 * audio format as the mixing sink (parent), this can save processing time when
44 * it comes to serving a lot of mixing streams at once. That way only one
45 * conversion must be done, instead of each stream having to iterate over the
46 * data.
47 *
48 * == Recording
49 *
50 * For input sinks only one mixing stream at a time can be the recording
51 * source currently. A recording source is optional, e.g. it is possible to
52 * have no current recording source set. Switching to a different recording
53 * source at runtime is possible.
54 */
55
56/*
57 * Copyright (C) 2014-2020 Oracle Corporation
58 *
59 * This file is part of VirtualBox Open Source Edition (OSE), as
60 * available from http://www.virtualbox.org. This file is free software;
61 * you can redistribute it and/or modify it under the terms of the GNU
62 * General Public License (GPL) as published by the Free Software
63 * Foundation, in version 2 as it comes in the "COPYING" file of the
64 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
65 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
66 */
67
68
69/*********************************************************************************************************************************
70* Header Files *
71*********************************************************************************************************************************/
72#define LOG_GROUP LOG_GROUP_AUDIO_MIXER
73#include <VBox/log.h>
74#include "AudioMixer.h"
75#include "AudioMixBuffer.h"
76#include "DrvAudio.h"
77
78#include <VBox/vmm/pdm.h>
79#include <VBox/err.h>
80#include <VBox/vmm/mm.h>
81#include <VBox/vmm/pdmaudioifs.h>
82
83#include <iprt/alloc.h>
84#include <iprt/asm-math.h>
85#include <iprt/assert.h>
86#include <iprt/string.h>
87
88
89/*********************************************************************************************************************************
90* Internal Functions *
91*********************************************************************************************************************************/
92static int audioMixerAddSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
93static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
94
95static int audioMixerSinkInit(PAUDMIXSINK pSink, PAUDIOMIXER pMixer, const char *pcszName, AUDMIXSINKDIR enmDir);
96static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink);
97static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster);
98static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink);
99static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
100static void audioMixerSinkReset(PAUDMIXSINK pSink);
101static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
102static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink);
103static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWrittenMin);
104static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream);
105static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten);
106
107static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl);
108static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream);
109static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream);
110
111
112/**
113 * Converts a mixer sink status to a string.
114 *
115 * @returns Stringified mixer sink status flags. Must be free'd with RTStrFree().
116 * "NONE" if no flags set.
117 * @param fStatus Mixer sink status to convert.
118 */
119static char *dbgAudioMixerSinkStatusToStr(AUDMIXSINKSTS fStatus)
120{
121#define APPEND_FLAG_TO_STR(_aFlag) \
122 if (fStatus & AUDMIXSINK_STS_##_aFlag) \
123 { \
124 if (pszFlags) \
125 { \
126 rc2 = RTStrAAppend(&pszFlags, " "); \
127 if (RT_FAILURE(rc2)) \
128 break; \
129 } \
130 \
131 rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
132 if (RT_FAILURE(rc2)) \
133 break; \
134 } \
135
136 char *pszFlags = NULL;
137 int rc2 = VINF_SUCCESS;
138
139 if (fStatus == AUDMIXSINK_STS_NONE) /* This is special, as this is value 0. */
140 {
141 rc2 = RTStrAAppend(&pszFlags, "NONE");
142 }
143 else
144 {
145 do
146 {
147 APPEND_FLAG_TO_STR(RUNNING);
148 APPEND_FLAG_TO_STR(PENDING_DISABLE);
149 APPEND_FLAG_TO_STR(DIRTY);
150
151 } while (0);
152 }
153
154 if ( RT_FAILURE(rc2)
155 && pszFlags)
156 {
157 RTStrFree(pszFlags);
158 pszFlags = NULL;
159 }
160
161#undef APPEND_FLAG_TO_STR
162
163 return pszFlags;
164}
165
166/**
167 * Creates an audio sink and attaches it to the given mixer.
168 *
169 * @returns IPRT status code.
170 * @param pMixer Mixer to attach created sink to.
171 * @param pszName Name of the sink to create.
172 * @param enmDir Direction of the sink to create.
173 * @param ppSink Pointer which returns the created sink on success.
174 */
175int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink)
176{
177 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
178 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
179 /* ppSink is optional. */
180
181 int rc = RTCritSectEnter(&pMixer->CritSect);
182 if (RT_FAILURE(rc))
183 return rc;
184
185 PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK));
186 if (pSink)
187 {
188 rc = audioMixerSinkInit(pSink, pMixer, pszName, enmDir);
189 if (RT_SUCCESS(rc))
190 {
191 rc = audioMixerAddSinkInternal(pMixer, pSink);
192 if (RT_SUCCESS(rc))
193 {
194 if (ppSink)
195 *ppSink = pSink;
196 }
197 }
198
199 if (RT_FAILURE(rc))
200 {
201 audioMixerSinkDestroyInternal(pSink);
202
203 RTMemFree(pSink);
204 pSink = NULL;
205 }
206 }
207 else
208 rc = VERR_NO_MEMORY;
209
210 int rc2 = RTCritSectLeave(&pMixer->CritSect);
211 AssertRC(rc2);
212
213 return rc;
214}
215
216/**
217 * Creates an audio mixer.
218 *
219 * @returns IPRT status code.
220 * @param pcszName Name of the audio mixer.
221 * @param fFlags Creation flags. Not used at the moment and must be 0.
222 * @param ppMixer Pointer which returns the created mixer object.
223 */
224int AudioMixerCreate(const char *pcszName, uint32_t fFlags, PAUDIOMIXER *ppMixer)
225{
226 RT_NOREF(fFlags);
227 AssertPtrReturn(pcszName, VERR_INVALID_POINTER);
228 /** @todo Add fFlags validation. */
229 AssertPtrReturn(ppMixer, VERR_INVALID_POINTER);
230
231 int rc = VINF_SUCCESS;
232
233 PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER));
234 if (pMixer)
235 {
236 pMixer->pszName = RTStrDup(pcszName);
237 if (!pMixer->pszName)
238 rc = VERR_NO_MEMORY;
239
240 if (RT_SUCCESS(rc))
241 rc = RTCritSectInit(&pMixer->CritSect);
242
243 if (RT_SUCCESS(rc))
244 {
245 pMixer->cSinks = 0;
246 RTListInit(&pMixer->lstSinks);
247
248 /* Set master volume to the max. */
249 pMixer->VolMaster.fMuted = false;
250 pMixer->VolMaster.uLeft = PDMAUDIO_VOLUME_MAX;
251 pMixer->VolMaster.uRight = PDMAUDIO_VOLUME_MAX;
252
253 LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName));
254
255 *ppMixer = pMixer;
256 }
257 else
258 RTMemFree(pMixer);
259 }
260 else
261 rc = VERR_NO_MEMORY;
262
263 LogFlowFuncLeaveRC(rc);
264 return rc;
265}
266
267/**
268 * Helper function for the internal debugger to print the mixer's current
269 * state, along with the attached sinks.
270 *
271 * @param pMixer Mixer to print debug output for.
272 * @param pHlp Debug info helper to use.
273 * @param pszArgs Optional arguments. Not being used at the moment.
274 */
275void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs)
276{
277 RT_NOREF(pszArgs);
278 PAUDMIXSINK pSink;
279 unsigned iSink = 0;
280
281 int rc2 = RTCritSectEnter(&pMixer->CritSect);
282 if (RT_FAILURE(rc2))
283 return;
284
285 pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
286 pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
287
288 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
289 {
290 pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName,
291 pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted);
292 ++iSink;
293 }
294
295 rc2 = RTCritSectLeave(&pMixer->CritSect);
296 AssertRC(rc2);
297}
298
299/**
300 * Destroys an audio mixer.
301 *
302 * @param pMixer Audio mixer to destroy.
303 */
304void AudioMixerDestroy(PAUDIOMIXER pMixer)
305{
306 if (!pMixer)
307 return;
308
309 int rc2 = RTCritSectEnter(&pMixer->CritSect);
310 AssertRC(rc2);
311
312 LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
313
314 PAUDMIXSINK pSink, pSinkNext;
315 RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node)
316 {
317 /* Save a pointer to the sink to remove, as pSink
318 * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */
319 PAUDMIXSINK pSinkToRemove = pSink;
320
321 audioMixerRemoveSinkInternal(pMixer, pSinkToRemove);
322
323 audioMixerSinkDestroyInternal(pSinkToRemove);
324
325 RTMemFree(pSinkToRemove);
326 }
327
328 pMixer->cSinks = 0;
329
330 if (pMixer->pszName)
331 {
332 RTStrFree(pMixer->pszName);
333 pMixer->pszName = NULL;
334 }
335
336 rc2 = RTCritSectLeave(&pMixer->CritSect);
337 AssertRC(rc2);
338
339 RTCritSectDelete(&pMixer->CritSect);
340
341 RTMemFree(pMixer);
342 pMixer = NULL;
343}
344
345/**
346 * Invalidates all internal data, internal version.
347 *
348 * @returns IPRT status code.
349 * @param pMixer Mixer to invalidate data for.
350 */
351int audioMixerInvalidateInternal(PAUDIOMIXER pMixer)
352{
353 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
354
355 LogFlowFunc(("[%s]\n", pMixer->pszName));
356
357 /* Propagate new master volume to all connected sinks. */
358 PAUDMIXSINK pSink;
359 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
360 {
361 int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster);
362 AssertRC(rc2);
363 }
364
365 return VINF_SUCCESS;
366}
367
368/**
369 * Invalidates all internal data.
370 *
371 * @returns IPRT status code.
372 * @param pMixer Mixer to invalidate data for.
373 */
374void AudioMixerInvalidate(PAUDIOMIXER pMixer)
375{
376 AssertPtrReturnVoid(pMixer);
377
378 int rc2 = RTCritSectEnter(&pMixer->CritSect);
379 AssertRC(rc2);
380
381 LogFlowFunc(("[%s]\n", pMixer->pszName));
382
383 rc2 = audioMixerInvalidateInternal(pMixer);
384 AssertRC(rc2);
385
386 rc2 = RTCritSectLeave(&pMixer->CritSect);
387 AssertRC(rc2);
388}
389
390/**
391 * Adds sink to an existing mixer.
392 *
393 * @returns VBox status code.
394 * @param pMixer Mixer to add sink to.
395 * @param pSink Sink to add.
396 */
397static int audioMixerAddSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
398{
399 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
400 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
401
402 /** @todo Check upper sink limit? */
403 /** @todo Check for double-inserted sinks? */
404
405 RTListAppend(&pMixer->lstSinks, &pSink->Node);
406 pMixer->cSinks++;
407
408 LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n",
409 pMixer, pSink, pMixer->cSinks));
410
411 return VINF_SUCCESS;
412}
413
414/**
415 * Removes a formerly attached audio sink for an audio mixer, internal version.
416 *
417 * @returns IPRT status code.
418 * @param pMixer Mixer to remove sink from.
419 * @param pSink Sink to remove.
420 */
421static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
422{
423 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
424 if (!pSink)
425 return VERR_NOT_FOUND;
426
427 AssertMsgReturn(pSink->pParent == pMixer, ("%s: Is not part of mixer '%s'\n",
428 pSink->pszName, pMixer->pszName), VERR_NOT_FOUND);
429
430 LogFlowFunc(("[%s] pSink=%s, cSinks=%RU8\n",
431 pMixer->pszName, pSink->pszName, pMixer->cSinks));
432
433 /* Remove sink from mixer. */
434 RTListNodeRemove(&pSink->Node);
435
436 Assert(pMixer->cSinks);
437 pMixer->cSinks--;
438
439 /* Set mixer to NULL so that we know we're not part of any mixer anymore. */
440 pSink->pParent = NULL;
441
442 return VINF_SUCCESS;
443}
444
445/**
446 * Removes a formerly attached audio sink for an audio mixer.
447 *
448 * @returns IPRT status code.
449 * @param pMixer Mixer to remove sink from.
450 * @param pSink Sink to remove.
451 */
452void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
453{
454 int rc2 = RTCritSectEnter(&pMixer->CritSect);
455 AssertRC(rc2);
456
457 audioMixerSinkRemoveAllStreamsInternal(pSink);
458 audioMixerRemoveSinkInternal(pMixer, pSink);
459
460 rc2 = RTCritSectLeave(&pMixer->CritSect);
461}
462
463/**
464 * Sets the mixer's master volume.
465 *
466 * @returns IPRT status code.
467 * @param pMixer Mixer to set master volume for.
468 * @param pVol Volume to set.
469 */
470int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol)
471{
472 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
473 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
474
475 int rc = RTCritSectEnter(&pMixer->CritSect);
476 if (RT_FAILURE(rc))
477 return rc;
478
479 memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME));
480
481 LogFlowFunc(("[%s] lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
482 pMixer->pszName, pVol->uLeft, pVol->uRight,
483 pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight));
484
485 rc = audioMixerInvalidateInternal(pMixer);
486
487 int rc2 = RTCritSectLeave(&pMixer->CritSect);
488 AssertRC(rc2);
489
490 return rc;
491}
492
493/*********************************************************************************************************************************
494 * Mixer Sink implementation.
495 ********************************************************************************************************************************/
496
497/**
498 * Adds an audio stream to a specific audio sink.
499 *
500 * @returns IPRT status code.
501 * @param pSink Sink to add audio stream to.
502 * @param pStream Stream to add.
503 */
504int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
505{
506 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
507 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
508
509 int rc = RTCritSectEnter(&pSink->CritSect);
510 if (RT_FAILURE(rc))
511 return rc;
512
513 if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */
514 {
515 int rc2 = RTCritSectLeave(&pSink->CritSect);
516 AssertRC(rc2);
517
518 return VERR_NO_MORE_HANDLES;
519 }
520
521 LogFlowFuncEnter();
522
523 /** @todo Check if stream already is assigned to (another) sink. */
524
525 /* If the sink is running and not in pending disable mode,
526 * make sure that the added stream also is enabled. */
527 if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
528 && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
529 {
530 rc = audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE, AUDMIXSTRMCTL_F_NONE);
531 if (rc == VERR_AUDIO_STREAM_NOT_READY)
532 rc = VINF_SUCCESS; /* Not fatal here, stream can become available at some later point in time. */
533 }
534
535 if (RT_SUCCESS(rc))
536 {
537 /* Apply the sink's combined volume to the stream. */
538 rc = pStream->pConn->pfnStreamSetVolume(pStream->pConn, pStream->pStream, &pSink->VolumeCombined);
539 AssertRC(rc);
540 }
541
542 if (RT_SUCCESS(rc))
543 {
544 /* Save pointer to sink the stream is attached to. */
545 pStream->pSink = pSink;
546
547 /* Append stream to sink's list. */
548 RTListAppend(&pSink->lstStreams, &pStream->Node);
549 pSink->cStreams++;
550 }
551
552 LogFlowFunc(("[%s] cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc));
553
554 int rc2 = RTCritSectLeave(&pSink->CritSect);
555 AssertRC(rc2);
556
557 return rc;
558}
559
560/**
561 * Creates an audio mixer stream.
562 *
563 * @returns IPRT status code.
564 * @param pSink Sink to use for creating the stream.
565 * @param pConn Audio connector interface to use.
566 * @param pCfg Audio stream configuration to use.
567 * @param fFlags Stream flags. Currently unused, set to 0.
568 * @param ppStream Pointer which receives the newly created audio stream.
569 */
570int AudioMixerSinkCreateStream(PAUDMIXSINK pSink,
571 PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, AUDMIXSTREAMFLAGS fFlags, PAUDMIXSTREAM *ppStream)
572{
573 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
574 AssertPtrReturn(pConn, VERR_INVALID_POINTER);
575 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
576 /** @todo Validate fFlags. */
577 /* ppStream is optional. */
578
579 if (pConn->pfnGetStatus(pConn, PDMAUDIODIR_ANY) == PDMAUDIOBACKENDSTS_NOT_ATTACHED)
580 return VERR_AUDIO_BACKEND_NOT_ATTACHED;
581
582 PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM));
583 if (!pMixStream)
584 return VERR_NO_MEMORY;
585
586 PDMAUDIOBACKENDCFG BackendCfg;
587 int rc = pConn->pfnGetConfig(pConn, &BackendCfg);
588 if (RT_FAILURE(rc))
589 {
590 RTMemFree(pMixStream);
591 return rc;
592 }
593
594 /* Assign the backend's name to the mixer stream's name for easier identification in the (release) log. */
595 pMixStream->pszName = RTStrAPrintf2("[%s] %s", pCfg->szName, BackendCfg.szName);
596 if (!pMixStream->pszName)
597 {
598 RTMemFree(pMixStream);
599 return VERR_NO_MEMORY;
600 }
601
602 rc = RTCritSectEnter(&pSink->CritSect);
603 if (RT_FAILURE(rc))
604 return rc;
605
606 LogFlowFunc(("[%s] fFlags=0x%x (enmDir=%ld, %u bits, %RU8 channels, %RU32Hz)\n",
607 pSink->pszName, fFlags, pCfg->enmDir, pCfg->Props.cbSample * 8, pCfg->Props.cChannels, pCfg->Props.uHz));
608
609 /*
610 * Initialize the host-side configuration for the stream to be created.
611 * Always use the sink's PCM audio format as the host side when creating a stream for it.
612 */
613 AssertMsg(DrvAudioHlpPCMPropsAreValid(&pSink->PCMProps),
614 ("%s: Does not (yet) have a format set when it must\n", pSink->pszName));
615
616 PDMAUDIOSTREAMCFG CfgHost;
617 rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost);
618 AssertRCReturn(rc, rc);
619
620 /* Apply the sink's direction for the configuration to use to
621 * create the stream. */
622 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
623 {
624 CfgHost.enmDir = PDMAUDIODIR_IN;
625 CfgHost.u.enmSrc = pCfg->u.enmSrc;
626 CfgHost.enmLayout = pCfg->enmLayout;
627 }
628 else
629 {
630 CfgHost.enmDir = PDMAUDIODIR_OUT;
631 CfgHost.u.enmDst = pCfg->u.enmDst;
632 CfgHost.enmLayout = pCfg->enmLayout;
633 }
634
635 RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName);
636
637 rc = RTCritSectInit(&pMixStream->CritSect);
638 if (RT_SUCCESS(rc))
639 {
640 PPDMAUDIOSTREAM pStream;
641 rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream);
642 if (RT_SUCCESS(rc))
643 {
644 /* Save the audio stream pointer to this mixing stream. */
645 pMixStream->pStream = pStream;
646
647 /* Increase the stream's reference count to let others know
648 * we're reyling on it to be around now. */
649 pConn->pfnStreamRetain(pConn, pStream);
650 }
651 }
652
653 if (RT_SUCCESS(rc))
654 {
655 rc = RTCircBufCreate(&pMixStream->pCircBuf, DrvAudioHlpMilliToBytes(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable. */
656 AssertRC(rc);
657 }
658
659 if (RT_SUCCESS(rc))
660 {
661 pMixStream->fFlags = fFlags;
662 pMixStream->pConn = pConn;
663
664 if (ppStream)
665 *ppStream = pMixStream;
666 }
667 else if (pMixStream)
668 {
669 int rc2 = RTCritSectDelete(&pMixStream->CritSect);
670 AssertRC(rc2);
671
672 if (pMixStream->pszName)
673 {
674 RTStrFree(pMixStream->pszName);
675 pMixStream->pszName = NULL;
676 }
677
678 RTMemFree(pMixStream);
679 pMixStream = NULL;
680 }
681
682 int rc2 = RTCritSectLeave(&pSink->CritSect);
683 AssertRC(rc2);
684
685 return rc;
686}
687
688/**
689 * Static helper function to translate a sink command
690 * to a PDM audio stream command.
691 *
692 * @returns PDM audio stream command, or PDMAUDIOSTREAMCMD_UNKNOWN if not found.
693 * @param enmCmd Mixer sink command to translate.
694 */
695static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd)
696{
697 switch (enmCmd)
698 {
699 case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE;
700 case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE;
701 case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE;
702 case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME;
703 case AUDMIXSINKCMD_DROP: return PDMAUDIOSTREAMCMD_DROP;
704 default: break;
705 }
706
707 AssertMsgFailed(("Unsupported sink command %d\n", enmCmd));
708 return PDMAUDIOSTREAMCMD_UNKNOWN;
709}
710
711/**
712 * Controls a mixer sink.
713 *
714 * @returns IPRT status code.
715 * @param pSink Mixer sink to control.
716 * @param enmSinkCmd Sink command to set.
717 */
718int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd)
719{
720 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
721
722 PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd);
723 if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN)
724 return VERR_NOT_SUPPORTED;
725
726 int rc = RTCritSectEnter(&pSink->CritSect);
727 if (RT_FAILURE(rc))
728 return rc;
729
730 /* Input sink and no recording source set? Bail out early. */
731 if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
732 && pSink->In.pStreamRecSource == NULL)
733 {
734 int rc2 = RTCritSectLeave(&pSink->CritSect);
735 AssertRC(rc2);
736
737 return rc;
738 }
739
740 PAUDMIXSTREAM pStream;
741 if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
742 && pSink->In.pStreamRecSource) /* Any recording source set? */
743 {
744 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
745 {
746 if (pStream == pSink->In.pStreamRecSource)
747 {
748 int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_F_NONE);
749 if (rc2 == VERR_NOT_SUPPORTED)
750 rc2 = VINF_SUCCESS;
751
752 if (RT_SUCCESS(rc))
753 rc = rc2;
754 /* Keep going. Flag? */
755 }
756 }
757 }
758 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
759 {
760 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
761 {
762 int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_F_NONE);
763 if (rc2 == VERR_NOT_SUPPORTED)
764 rc2 = VINF_SUCCESS;
765
766 if (RT_SUCCESS(rc))
767 rc = rc2;
768 /* Keep going. Flag? */
769 }
770 }
771
772 switch (enmSinkCmd)
773 {
774 case AUDMIXSINKCMD_ENABLE:
775 {
776 /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */
777 pSink->fStatus = AUDMIXSINK_STS_RUNNING;
778 break;
779 }
780
781 case AUDMIXSINKCMD_DISABLE:
782 {
783 if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
784 {
785 /* Set the sink in a pending disable state first.
786 * The final status (disabled) will be set in the sink's iteration. */
787 pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE;
788 }
789 break;
790 }
791
792 case AUDMIXSINKCMD_DROP:
793 {
794 AudioMixBufReset(&pSink->MixBuf);
795
796 /* Clear dirty bit, keep others. */
797 pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
798 break;
799 }
800
801 default:
802 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
803 break;
804 }
805
806 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
807 LogRel2(("Audio Mixer: Set new status of sink '%s' to %s\n", pSink->pszName, pszStatus));
808 LogFlowFunc(("[%s] enmCmd=%RU32, fStatus=%s, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pszStatus, rc));
809 RTStrFree(pszStatus);
810
811 int rc2 = RTCritSectLeave(&pSink->CritSect);
812 AssertRC(rc2);
813
814 return rc;
815}
816
817/**
818 * Initializes a sink.
819 *
820 * @returns VBox status code.
821 * @param pSink Sink to initialize.
822 * @param pMixer Mixer the sink is assigned to.
823 * @param pcszName Name of the sink.
824 * @param enmDir Direction of the sink.
825 */
826static int audioMixerSinkInit(PAUDMIXSINK pSink, PAUDIOMIXER pMixer, const char *pcszName, AUDMIXSINKDIR enmDir)
827{
828 pSink->pszName = RTStrDup(pcszName);
829 if (!pSink->pszName)
830 return VERR_NO_MEMORY;
831
832 int rc = RTCritSectInit(&pSink->CritSect);
833 if (RT_SUCCESS(rc))
834 {
835 pSink->pParent = pMixer;
836 pSink->enmDir = enmDir;
837
838 RTListInit(&pSink->lstStreams);
839
840 /* Set initial volume to max. */
841 pSink->Volume.fMuted = false;
842 pSink->Volume.uLeft = PDMAUDIO_VOLUME_MAX;
843 pSink->Volume.uRight = PDMAUDIO_VOLUME_MAX;
844
845 /* Ditto for the combined volume. */
846 pSink->VolumeCombined.fMuted = false;
847 pSink->VolumeCombined.uLeft = PDMAUDIO_VOLUME_MAX;
848 pSink->VolumeCombined.uRight = PDMAUDIO_VOLUME_MAX;
849
850 const size_t cbScratchBuf = _1K; /** @todo Make this configurable? */
851
852 pSink->pabScratchBuf = (uint8_t *)RTMemAlloc(cbScratchBuf);
853 AssertPtrReturn(pSink->pabScratchBuf, VERR_NO_MEMORY);
854 pSink->cbScratchBuf = cbScratchBuf;
855 }
856
857 LogFlowFuncLeaveRC(rc);
858 return rc;
859}
860
861/**
862 * Destroys a mixer sink and removes it from the attached mixer (if any).
863 *
864 * @param pSink Mixer sink to destroy.
865 */
866void AudioMixerSinkDestroy(PAUDMIXSINK pSink)
867{
868 if (!pSink)
869 return;
870
871 int rc2 = RTCritSectEnter(&pSink->CritSect);
872 AssertRC(rc2);
873
874 if (pSink->pParent)
875 {
876 /* Save mixer pointer, as after audioMixerRemoveSinkInternal() the
877 * pointer will be gone from the stream. */
878 PAUDIOMIXER pMixer = pSink->pParent;
879 AssertPtr(pMixer);
880
881 audioMixerRemoveSinkInternal(pMixer, pSink);
882 }
883
884 rc2 = RTCritSectLeave(&pSink->CritSect);
885 AssertRC(rc2);
886
887 audioMixerSinkDestroyInternal(pSink);
888
889 RTMemFree(pSink);
890 pSink = NULL;
891}
892
893/**
894 * Destroys a mixer sink.
895 *
896 * @param pSink Mixer sink to destroy.
897 */
898static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink)
899{
900 AssertPtrReturnVoid(pSink);
901
902 LogFunc(("%s\n", pSink->pszName));
903
904 PAUDMIXSTREAM pStream, pStreamNext;
905 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
906 {
907 /* Save a pointer to the stream to remove, as pStream
908 * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */
909 PAUDMIXSTREAM pStreamToRemove = pStream;
910
911 audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove);
912 audioMixerStreamDestroyInternal(pStreamToRemove);
913 }
914
915#ifdef VBOX_AUDIO_MIXER_DEBUG
916 DrvAudioHlpFileDestroy(pSink->Dbg.pFile);
917 pSink->Dbg.pFile = NULL;
918#endif
919
920 if (pSink->pszName)
921 {
922 RTStrFree(pSink->pszName);
923 pSink->pszName = NULL;
924 }
925
926 if (pSink->pabScratchBuf)
927 {
928 Assert(pSink->cbScratchBuf);
929
930 RTMemFree(pSink->pabScratchBuf);
931 pSink->pabScratchBuf = NULL;
932
933 pSink->cbScratchBuf = 0;
934 }
935
936 AudioMixBufDestroy(&pSink->MixBuf);
937 RTCritSectDelete(&pSink->CritSect);
938}
939
940/**
941 * Returns the amount of bytes ready to be read from a sink since the last call
942 * to AudioMixerSinkUpdate().
943 *
944 * @returns Amount of bytes ready to be read from the sink.
945 * @param pSink Sink to return number of available bytes for.
946 */
947uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink)
948{
949 AssertPtrReturn(pSink, 0);
950
951 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("%s: Can't read from a non-input sink\n", pSink->pszName));
952
953 int rc = RTCritSectEnter(&pSink->CritSect);
954 if (RT_FAILURE(rc))
955 return 0;
956
957 uint32_t cbReadable = 0;
958
959 if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
960 {
961#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN
962# error "Implement me!"
963#else
964 PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource;
965 if (!pStreamRecSource)
966 {
967 Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName));
968 }
969 else
970 {
971 AssertPtr(pStreamRecSource->pConn);
972 cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream);
973 }
974#endif
975 }
976
977 Log3Func(("[%s] cbReadable=%RU32\n", pSink->pszName, cbReadable));
978
979 int rc2 = RTCritSectLeave(&pSink->CritSect);
980 AssertRC(rc2);
981
982 return cbReadable;
983}
984
985/**
986 * Returns the sink's current recording source.
987 *
988 * @return Mixer stream which currently is set as current recording source, NULL if none is set.
989 * @param pSink Audio mixer sink to return current recording source for.
990 */
991PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink)
992{
993 int rc = RTCritSectEnter(&pSink->CritSect);
994 if (RT_FAILURE(rc))
995 return NULL;
996
997 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n"));
998
999 PAUDMIXSTREAM pStream = pSink->In.pStreamRecSource;
1000
1001 int rc2 = RTCritSectLeave(&pSink->CritSect);
1002 AssertRC(rc2);
1003
1004 return pStream;
1005}
1006
1007/**
1008 * Returns the amount of bytes ready to be written to a sink since the last call
1009 * to AudioMixerSinkUpdate().
1010 *
1011 * @returns Amount of bytes ready to be written to the sink.
1012 * @param pSink Sink to return number of available bytes for.
1013 */
1014uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink)
1015{
1016 AssertPtrReturn(pSink, 0);
1017
1018 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("%s: Can't write to a non-output sink\n", pSink->pszName));
1019
1020 int rc = RTCritSectEnter(&pSink->CritSect);
1021 if (RT_FAILURE(rc))
1022 return 0;
1023
1024 uint32_t cbWritable = 0;
1025
1026 if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
1027 && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
1028 {
1029 cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf);
1030 }
1031
1032 Log3Func(("[%s] cbWritable=%RU32 (%RU64ms)\n",
1033 pSink->pszName, cbWritable, DrvAudioHlpBytesToMilli(cbWritable, &pSink->PCMProps)));
1034
1035 int rc2 = RTCritSectLeave(&pSink->CritSect);
1036 AssertRC(rc2);
1037
1038 return cbWritable;
1039}
1040
1041/**
1042 * Returns the sink's mixing direction.
1043 *
1044 * @returns Mixing direction.
1045 * @param pSink Sink to return direction for.
1046 */
1047AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink)
1048{
1049 AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN);
1050
1051 int rc = RTCritSectEnter(&pSink->CritSect);
1052 if (RT_FAILURE(rc))
1053 return AUDMIXSINKDIR_UNKNOWN;
1054
1055 const AUDMIXSINKDIR enmDir = pSink->enmDir;
1056
1057 int rc2 = RTCritSectLeave(&pSink->CritSect);
1058 AssertRC(rc2);
1059
1060 return enmDir;
1061}
1062
1063/**
1064 * Returns the sink's (friendly) name.
1065 *
1066 * @returns The sink's (friendly) name.
1067 */
1068const char *AudioMixerSinkGetName(const PAUDMIXSINK pSink)
1069{
1070 AssertPtrReturn(pSink, "<Unknown>");
1071
1072 return pSink->pszName;
1073}
1074
1075/**
1076 * Returns a specific mixer stream from a sink, based on its index.
1077 *
1078 * @returns Mixer stream if found, or NULL if not found.
1079 * @param pSink Sink to retrieve mixer stream from.
1080 * @param uIndex Index of the mixer stream to return.
1081 */
1082PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex)
1083{
1084 AssertPtrReturn(pSink, NULL);
1085
1086 int rc = RTCritSectEnter(&pSink->CritSect);
1087 if (RT_FAILURE(rc))
1088 return NULL;
1089
1090 AssertMsgReturn(uIndex < pSink->cStreams,
1091 ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL);
1092
1093 /* Slow lookup, d'oh. */
1094 PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node);
1095 while (uIndex)
1096 {
1097 pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node);
1098 uIndex--;
1099 }
1100
1101 /** @todo Do we need to raise the stream's reference count here? */
1102
1103 int rc2 = RTCritSectLeave(&pSink->CritSect);
1104 AssertRC(rc2);
1105
1106 AssertPtr(pStream);
1107 return pStream;
1108}
1109
1110/**
1111 * Returns the current status of a mixer sink.
1112 *
1113 * @returns The sink's current status.
1114 * @param pSink Mixer sink to return status for.
1115 */
1116AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink)
1117{
1118 if (!pSink)
1119 return AUDMIXSINK_STS_NONE;
1120
1121 int rc2 = RTCritSectEnter(&pSink->CritSect);
1122 if (RT_FAILURE(rc2))
1123 return AUDMIXSINK_STS_NONE;
1124
1125 /* If the dirty flag is set, there is unprocessed data in the sink. */
1126 AUDMIXSINKSTS stsSink = pSink->fStatus;
1127
1128 rc2 = RTCritSectLeave(&pSink->CritSect);
1129 AssertRC(rc2);
1130
1131 return stsSink;
1132}
1133
1134/**
1135 * Returns the number of attached mixer streams to a mixer sink.
1136 *
1137 * @returns The number of attached mixer streams.
1138 * @param pSink Mixer sink to return number for.
1139 */
1140uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink)
1141{
1142 if (!pSink)
1143 return 0;
1144
1145 int rc2 = RTCritSectEnter(&pSink->CritSect);
1146 if (RT_FAILURE(rc2))
1147 return 0;
1148
1149 const uint8_t cStreams = pSink->cStreams;
1150
1151 rc2 = RTCritSectLeave(&pSink->CritSect);
1152 AssertRC(rc2);
1153
1154 return cStreams;
1155}
1156
1157/**
1158 * Returns whether the sink is in an active state or not.
1159 * Note: The pending disable state also counts as active.
1160 *
1161 * @returns True if active, false if not.
1162 * @param pSink Sink to return active state for.
1163 */
1164bool AudioMixerSinkIsActive(PAUDMIXSINK pSink)
1165{
1166 if (!pSink)
1167 return false;
1168
1169 int rc2 = RTCritSectEnter(&pSink->CritSect);
1170 if (RT_FAILURE(rc2))
1171 return false;
1172
1173 const bool fIsActive = pSink->fStatus & AUDMIXSINK_STS_RUNNING;
1174 /* Note: AUDMIXSINK_STS_PENDING_DISABLE implies AUDMIXSINK_STS_RUNNING. */
1175
1176 Log3Func(("[%s] fActive=%RTbool\n", pSink->pszName, fIsActive));
1177
1178 rc2 = RTCritSectLeave(&pSink->CritSect);
1179 AssertRC(rc2);
1180
1181 return fIsActive;
1182}
1183
1184/**
1185 * Reads audio data from a mixer sink.
1186 *
1187 * @returns IPRT status code.
1188 * @param pSink Mixer sink to read data from.
1189 * @param enmOp Mixer operation to use for reading the data.
1190 * @param pvBuf Buffer where to store the read data.
1191 * @param cbBuf Buffer size (in bytes) where to store the data.
1192 * @param pcbRead Number of bytes read. Optional.
1193 */
1194int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1195{
1196 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1197 RT_NOREF(enmOp);
1198 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1199 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1200 /* pcbRead is optional. */
1201
1202 /** @todo Handle mixing operation enmOp! */
1203
1204 int rc = RTCritSectEnter(&pSink->CritSect);
1205 if (RT_FAILURE(rc))
1206 return rc;
1207
1208 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT,
1209 ("Can't read from a sink which is not an input sink\n"));
1210
1211 uint32_t cbRead = 0;
1212
1213 /* Flag indicating whether this sink is in a 'clean' state,
1214 * e.g. there is no more data to read from. */
1215 bool fClean = true;
1216
1217 PAUDMIXSTREAM pStreamRecSource = pSink->In.pStreamRecSource;
1218 if (!pStreamRecSource)
1219 {
1220 Log3Func(("[%s] No recording source specified, skipping ...\n", pSink->pszName));
1221 }
1222 else if (!(pStreamRecSource->fStatus & AUDMIXSTREAM_STATUS_ENABLED))
1223 {
1224 Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pStreamRecSource->pszName));
1225 }
1226 else
1227 {
1228 uint32_t cbToRead = cbBuf;
1229 while (cbToRead)
1230 {
1231 uint32_t cbReadStrm;
1232 AssertPtr(pStreamRecSource->pConn);
1233#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF_IN
1234# error "Implement me!"
1235#else
1236 rc = pStreamRecSource->pConn->pfnStreamRead(pStreamRecSource->pConn, pStreamRecSource->pStream,
1237 (uint8_t *)pvBuf + cbRead, cbToRead, &cbReadStrm);
1238#endif
1239 if (RT_FAILURE(rc))
1240 LogFunc(("[%s] Failed reading from stream '%s': %Rrc\n", pSink->pszName, pStreamRecSource->pszName, rc));
1241
1242 Log3Func(("[%s] Stream '%s': Read %RU32 bytes\n", pSink->pszName, pStreamRecSource->pszName, cbReadStrm));
1243
1244 if ( RT_FAILURE(rc)
1245 || !cbReadStrm)
1246 break;
1247
1248 AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW);
1249 cbToRead -= cbReadStrm;
1250 cbRead += cbReadStrm;
1251 Assert(cbRead <= cbBuf);
1252 }
1253
1254 uint32_t cbReadable = pStreamRecSource->pConn->pfnStreamGetReadable(pStreamRecSource->pConn, pStreamRecSource->pStream);
1255
1256 /* Still some data available? Then sink is not clean (yet). */
1257 if (cbReadable)
1258 fClean = false;
1259
1260 if (RT_SUCCESS(rc))
1261 {
1262 if (fClean)
1263 pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
1264
1265 /* Update our last read time stamp. */
1266 pSink->tsLastReadWrittenNs = RTTimeNanoTS();
1267
1268#ifdef VBOX_AUDIO_MIXER_DEBUG
1269 int rc2 = DrvAudioHlpFileWrite(pSink->Dbg.pFile, pvBuf, cbRead, 0 /* fFlags */);
1270 AssertRC(rc2);
1271#endif
1272 }
1273 }
1274
1275#ifdef LOG_ENABLED
1276 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
1277 Log2Func(("[%s] cbRead=%RU32, fClean=%RTbool, fStatus=%s, rc=%Rrc\n", pSink->pszName, cbRead, fClean, pszStatus, rc));
1278 RTStrFree(pszStatus);
1279#endif
1280
1281 if (pcbRead)
1282 *pcbRead = cbRead;
1283
1284 int rc2 = RTCritSectLeave(&pSink->CritSect);
1285 AssertRC(rc2);
1286
1287 return rc;
1288}
1289
1290/**
1291 * Removes a mixer stream from a mixer sink, internal version.
1292 *
1293 * @returns IPRT status code.
1294 * @param pSink Sink to remove mixer stream from.
1295 * @param pStream Stream to remove.
1296 */
1297static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1298{
1299 AssertPtrReturn(pSink, VERR_INVALID_PARAMETER);
1300 if ( !pStream
1301 || !pStream->pSink) /* Not part of a sink anymore? */
1302 {
1303 return VERR_NOT_FOUND;
1304 }
1305
1306 AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n",
1307 pStream->pszName, pSink->pszName), VERR_NOT_FOUND);
1308
1309 LogFlowFunc(("[%s] (Stream = %s), cStreams=%RU8\n",
1310 pSink->pszName, pStream->pStream->szName, pSink->cStreams));
1311
1312 /* Remove stream from sink. */
1313 RTListNodeRemove(&pStream->Node);
1314
1315 int rc = VINF_SUCCESS;
1316
1317 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
1318 {
1319 /* Make sure to also un-set the recording source if this stream was set
1320 * as the recording source before. */
1321 if (pStream == pSink->In.pStreamRecSource)
1322 rc = audioMixerSinkSetRecSourceInternal(pSink, NULL);
1323 }
1324
1325 /* Set sink to NULL so that we know we're not part of any sink anymore. */
1326 pStream->pSink = NULL;
1327
1328 return rc;
1329}
1330
1331/**
1332 * Removes a mixer stream from a mixer sink.
1333 *
1334 * @param pSink Sink to remove mixer stream from.
1335 * @param pStream Stream to remove.
1336 */
1337void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1338{
1339 int rc2 = RTCritSectEnter(&pSink->CritSect);
1340 AssertRC(rc2);
1341
1342 rc2 = audioMixerSinkRemoveStreamInternal(pSink, pStream);
1343 if (RT_SUCCESS(rc2))
1344 {
1345 Assert(pSink->cStreams);
1346 pSink->cStreams--;
1347 }
1348
1349 rc2 = RTCritSectLeave(&pSink->CritSect);
1350 AssertRC(rc2);
1351}
1352
1353/**
1354 * Removes all attached streams from a given sink.
1355 *
1356 * @param pSink Sink to remove attached streams from.
1357 */
1358static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink)
1359{
1360 if (!pSink)
1361 return;
1362
1363 LogFunc(("%s\n", pSink->pszName));
1364
1365 PAUDMIXSTREAM pStream, pStreamNext;
1366 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
1367 audioMixerSinkRemoveStreamInternal(pSink, pStream);
1368}
1369
1370/**
1371 * Resets the sink's state.
1372 *
1373 * @param pSink Sink to reset.
1374 */
1375static void audioMixerSinkReset(PAUDMIXSINK pSink)
1376{
1377 if (!pSink)
1378 return;
1379
1380 LogFunc(("[%s]\n", pSink->pszName));
1381
1382 AudioMixBufReset(&pSink->MixBuf);
1383
1384 /* Update last updated timestamp. */
1385 pSink->tsLastUpdatedMs = 0;
1386
1387 /* Reset status. */
1388 pSink->fStatus = AUDMIXSINK_STS_NONE;
1389}
1390
1391/**
1392 * Removes all attached streams from a given sink.
1393 *
1394 * @param pSink Sink to remove attached streams from.
1395 */
1396void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
1397{
1398 if (!pSink)
1399 return;
1400
1401 int rc2 = RTCritSectEnter(&pSink->CritSect);
1402 AssertRC(rc2);
1403
1404 audioMixerSinkRemoveAllStreamsInternal(pSink);
1405
1406 pSink->cStreams = 0;
1407
1408 rc2 = RTCritSectLeave(&pSink->CritSect);
1409 AssertRC(rc2);
1410}
1411
1412/**
1413 * Resets a sink. This will immediately stop all processing.
1414 *
1415 * @param pSink Sink to reset.
1416 */
1417void AudioMixerSinkReset(PAUDMIXSINK pSink)
1418{
1419 if (!pSink)
1420 return;
1421
1422 int rc2 = RTCritSectEnter(&pSink->CritSect);
1423 AssertRC(rc2);
1424
1425 LogFlowFunc(("[%s]\n", pSink->pszName));
1426
1427 audioMixerSinkReset(pSink);
1428
1429 rc2 = RTCritSectLeave(&pSink->CritSect);
1430 AssertRC(rc2);
1431}
1432
1433/**
1434 * Returns the audio format of a mixer sink.
1435 *
1436 * @param pSink Sink to retrieve audio format for.
1437 * @param pPCMProps Where to the returned audio format.
1438 */
1439void AudioMixerSinkGetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps)
1440{
1441 AssertPtrReturnVoid(pSink);
1442 AssertPtrReturnVoid(pPCMProps);
1443
1444 int rc2 = RTCritSectEnter(&pSink->CritSect);
1445 if (RT_FAILURE(rc2))
1446 return;
1447
1448 memcpy(pPCMProps, &pSink->PCMProps, sizeof(PDMAUDIOPCMPROPS));
1449
1450 rc2 = RTCritSectLeave(&pSink->CritSect);
1451 AssertRC(rc2);
1452}
1453
1454/**
1455 * Sets the audio format of a mixer sink.
1456 *
1457 * @returns IPRT status code.
1458 * @param pSink Sink to set audio format for.
1459 * @param pPCMProps Audio format (PCM properties) to set.
1460 */
1461int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps)
1462{
1463 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1464 AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER);
1465 AssertReturn(DrvAudioHlpPCMPropsAreValid(pPCMProps), VERR_INVALID_PARAMETER);
1466
1467 int rc = RTCritSectEnter(&pSink->CritSect);
1468 if (RT_FAILURE(rc))
1469 return rc;
1470
1471 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */
1472 {
1473 rc = RTCritSectLeave(&pSink->CritSect);
1474 AssertRC(rc);
1475
1476 return rc;
1477 }
1478
1479 if (pSink->PCMProps.uHz)
1480 LogFlowFunc(("[%s] Old format: %u bit, %RU8 channels, %RU32Hz\n",
1481 pSink->pszName, pSink->PCMProps.cbSample * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
1482
1483 memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMAUDIOPCMPROPS));
1484
1485 LogFlowFunc(("[%s] New format %u bit, %RU8 channels, %RU32Hz\n",
1486 pSink->pszName, pSink->PCMProps.cbSample * 8, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
1487
1488 /* Also update the sink's mixing buffer format. */
1489 AudioMixBufDestroy(&pSink->MixBuf);
1490 rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, &pSink->PCMProps,
1491 DrvAudioHlpMilliToFrames(100 /* ms */, &pSink->PCMProps)); /** @todo Make this configurable? */
1492 if (RT_SUCCESS(rc))
1493 {
1494 PAUDMIXSTREAM pStream;
1495 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
1496 {
1497 /** @todo Invalidate mix buffers! */
1498 }
1499 }
1500
1501#ifdef VBOX_AUDIO_MIXER_DEBUG
1502 if (RT_SUCCESS(rc))
1503 {
1504 DrvAudioHlpFileClose(pSink->Dbg.pFile);
1505
1506 char szTemp[RTPATH_MAX];
1507 int rc2 = RTPathTemp(szTemp, sizeof(szTemp));
1508 if (RT_SUCCESS(rc2))
1509 {
1510 /** @todo Sanitize sink name. */
1511
1512 char szName[64];
1513 RTStrPrintf(szName, sizeof(szName), "MixerSink-%s", pSink->pszName);
1514
1515 char szFile[RTPATH_MAX];
1516 rc2 = DrvAudioHlpFileNameGet(szFile, RT_ELEMENTS(szFile), szTemp, szName,
1517 0 /* Instance */, PDMAUDIOFILETYPE_WAV, PDMAUDIOFILENAME_FLAGS_NONE);
1518 if (RT_SUCCESS(rc2))
1519 {
1520 rc2 = DrvAudioHlpFileCreate(PDMAUDIOFILETYPE_WAV, szFile, PDMAUDIOFILE_FLAGS_NONE,
1521 &pSink->Dbg.pFile);
1522 if (RT_SUCCESS(rc2))
1523 rc2 = DrvAudioHlpFileOpen(pSink->Dbg.pFile, PDMAUDIOFILE_DEFAULT_OPEN_FLAGS, &pSink->PCMProps);
1524 }
1525 }
1526 }
1527#endif
1528
1529 int rc2 = RTCritSectLeave(&pSink->CritSect);
1530 AssertRC(rc2);
1531
1532 LogFlowFuncLeaveRC(rc);
1533 return rc;
1534}
1535
1536/**
1537 * Set the current recording source of an input mixer sink, internal version.
1538 *
1539 * @return IPRT status code.
1540 * @param pSink Input mixer sink to set recording source for.
1541 * @param pStream Mixer stream to set as current recording source. Must be an input stream.
1542 * Specify NULL to un-set the current recording source.
1543 */
1544static int audioMixerSinkSetRecSourceInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1545{
1546 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Specified sink is not an input sink\n"));
1547
1548 int rc;
1549
1550 /*
1551 * Warning: Do *not* use pfnConn->pfnEnable() for enabling/disabling streams here, as this will unconditionally (re-)enable
1552 * streams, which would violate / run against the (global) VM settings. See @bugref{9882}.
1553 */
1554
1555 /* Get pointers of current recording source to make code easier to read below. */
1556 PAUDMIXSTREAM pCurRecSrc = pSink->In.pStreamRecSource; /* Can be NULL. */
1557 PPDMIAUDIOCONNECTOR pCurRecSrcConn = NULL;
1558 PPDMAUDIOSTREAM pCurRecSrcStream = NULL;
1559
1560 if (pCurRecSrc) /* First, disable old recording source, if any is set. */
1561 {
1562 pCurRecSrcConn = pSink->In.pStreamRecSource->pConn;
1563 AssertPtrReturn(pCurRecSrcConn, VERR_INVALID_POINTER);
1564 pCurRecSrcStream = pCurRecSrc->pStream;
1565 AssertPtrReturn(pCurRecSrcStream, VERR_INVALID_POINTER);
1566
1567 rc = pCurRecSrcConn->pfnStreamControl(pCurRecSrcConn, pCurRecSrcStream, PDMAUDIOSTREAMCMD_DISABLE);
1568 }
1569 else
1570 rc = VINF_SUCCESS;
1571
1572 if (RT_SUCCESS(rc))
1573 {
1574 if (pStream)
1575 {
1576 AssertPtr(pStream->pStream);
1577 AssertMsg(pStream->pStream->enmDir == PDMAUDIODIR_IN, ("Specified stream is not an input stream\n"));
1578 AssertPtr(pStream->pConn);
1579 rc = pStream->pConn->pfnStreamControl(pStream->pConn, pStream->pStream, PDMAUDIOSTREAMCMD_ENABLE);
1580 if (RT_SUCCESS(rc))
1581 {
1582 pCurRecSrc = pStream;
1583 }
1584 else if (pCurRecSrc) /* Stay with the current recording source (if any) and re-enable it. */
1585 {
1586 rc = pCurRecSrcConn->pfnStreamControl(pCurRecSrcConn, pCurRecSrcStream, PDMAUDIOSTREAMCMD_ENABLE);
1587 }
1588 }
1589 else
1590 pCurRecSrc = NULL; /* Unsetting, see audioMixerSinkRemoveStreamInternal. */
1591 }
1592
1593 /* Invalidate pointers. */
1594 pSink->In.pStreamRecSource = pCurRecSrc;
1595
1596 LogFunc(("[%s] Recording source is now '%s', rc=%Rrc\n",
1597 pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "<None>", rc));
1598
1599 if (RT_SUCCESS(rc))
1600 LogRel(("Audio Mixer: Setting recording source of sink '%s' to '%s'\n",
1601 pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "<None>"));
1602 else if (rc != VERR_AUDIO_STREAM_NOT_READY)
1603 LogRel(("Audio Mixer: Setting recording source of sink '%s' to '%s' failed with %Rrc\n",
1604 pSink->pszName, pSink->In.pStreamRecSource ? pSink->In.pStreamRecSource->pszName : "<None>", rc));
1605
1606 return rc;
1607}
1608
1609/**
1610 * Set the current recording source of an input mixer sink.
1611 *
1612 * @return IPRT status code.
1613 * @param pSink Input mixer sink to set recording source for.
1614 * @param pStream Mixer stream to set as current recording source. Must be an input stream.
1615 * Set to NULL to un-set the current recording source.
1616 */
1617int AudioMixerSinkSetRecordingSource(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1618{
1619 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1620
1621 int rc = RTCritSectEnter(&pSink->CritSect);
1622 if (RT_FAILURE(rc))
1623 return rc;
1624
1625 rc = audioMixerSinkSetRecSourceInternal(pSink, pStream);
1626
1627 int rc2 = RTCritSectLeave(&pSink->CritSect);
1628 AssertRC(rc2);
1629
1630 return rc;
1631}
1632
1633/**
1634 * Sets the volume of an individual sink.
1635 *
1636 * @returns IPRT status code.
1637 * @param pSink Sink to set volume for.
1638 * @param pVol Volume to set.
1639 */
1640int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol)
1641{
1642 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1643 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
1644
1645 int rc = RTCritSectEnter(&pSink->CritSect);
1646 if (RT_FAILURE(rc))
1647 return rc;
1648
1649 memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME));
1650
1651 LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU8, rVol=%RU8\n",
1652 pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
1653
1654 LogRel2(("Audio Mixer: Setting volume of sink '%s' to %RU8/%RU8 (%s)\n",
1655 pSink->pszName, pVol->uLeft, pVol->uRight, pVol->fMuted ? "Muted" : "Unmuted"));
1656
1657 AssertPtr(pSink->pParent);
1658 rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster);
1659
1660 int rc2 = RTCritSectLeave(&pSink->CritSect);
1661 AssertRC(rc2);
1662
1663 return rc;
1664}
1665
1666/**
1667 * Updates a mixer sink, internal version.
1668 *
1669 * @returns IPRT status code.
1670 * @param pSink Mixer sink to update.
1671 */
1672static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink)
1673{
1674 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1675
1676 int rc = VINF_SUCCESS;
1677
1678#ifdef LOG_ENABLED
1679 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
1680 Log3Func(("[%s] fStatus=%s\n", pSink->pszName, pszStatus));
1681 RTStrFree(pszStatus);
1682#endif
1683
1684 /* Sink disabled? Take a shortcut. */
1685 if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
1686 return rc;
1687
1688 /* Input sink and no recording source set? Bail out early. */
1689 if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
1690 && pSink->In.pStreamRecSource == NULL)
1691 return rc;
1692
1693 /* Sanity. */
1694 AssertPtr(pSink->pabScratchBuf);
1695 Assert(pSink->cbScratchBuf);
1696
1697 /* Update each mixing sink stream's status. */
1698 PAUDMIXSTREAM pMixStream;
1699 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1700 {
1701 int rc2 = audioMixerStreamUpdateStatus(pMixStream);
1702 AssertRC(rc2);
1703 }
1704
1705 /* Number of disabled streams of this sink. */
1706 uint8_t cStreamsDisabled = pSink->cStreams;
1707
1708 /* Next, try to write (multiplex) as much audio data as possible to all connected mixer streams. */
1709 uint32_t cbToWriteToStreams = AudioMixBufUsedBytes(&pSink->MixBuf);
1710
1711 while (cbToWriteToStreams)
1712 {
1713 uint32_t cfChunk;
1714 rc = AudioMixBufAcquireReadBlock(&pSink->MixBuf, pSink->pabScratchBuf, RT_MIN(cbToWriteToStreams, (uint32_t)pSink->cbScratchBuf),
1715 &cfChunk);
1716 if (RT_FAILURE(rc))
1717 break;
1718
1719 const uint32_t cbChunk = DrvAudioHlpFramesToBytes(cfChunk, &pSink->PCMProps);
1720 Assert(cbChunk <= pSink->cbScratchBuf);
1721
1722 /* Multiplex the current chunk in a synchronized fashion to all connected streams. */
1723 uint32_t cbChunkWrittenMin = 0;
1724 rc = audioMixerSinkMultiplexSync(pSink, AUDMIXOP_COPY, pSink->pabScratchBuf, cbChunk, &cbChunkWrittenMin);
1725 if (RT_SUCCESS(rc))
1726 {
1727 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1728 {
1729 int rc2 = audioMixerSinkWriteToStream(pSink, pMixStream);
1730 AssertRC(rc2);
1731 }
1732 }
1733
1734 Log3Func(("[%s] cbChunk=%RU32, cbChunkWrittenMin=%RU32\n", pSink->pszName, cbChunk, cbChunkWrittenMin));
1735
1736 AudioMixBufReleaseReadBlock(&pSink->MixBuf, AUDIOMIXBUF_B2F(&pSink->MixBuf, cbChunkWrittenMin));
1737
1738 if ( RT_FAILURE(rc)
1739 || cbChunkWrittenMin == 0)
1740 break;
1741
1742 Assert(cbToWriteToStreams >= cbChunkWrittenMin);
1743 cbToWriteToStreams -= cbChunkWrittenMin;
1744 }
1745
1746 if ( !(pSink->fStatus & AUDMIXSINK_STS_DIRTY)
1747 && AudioMixBufUsed(&pSink->MixBuf)) /* Still audio output data left? Consider the sink as being "dirty" then. */
1748 {
1749 /* Set dirty bit. */
1750 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
1751 }
1752
1753 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1754 {
1755 /* Input sink and not the recording source? Skip. */
1756 if ( pSink->enmDir == AUDMIXSINKDIR_INPUT
1757 && pSink->In.pStreamRecSource != pMixStream)
1758 continue;
1759
1760 PPDMAUDIOSTREAM pStream = pMixStream->pStream;
1761 AssertPtr(pStream);
1762
1763 PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn;
1764 AssertPtr(pConn);
1765
1766 uint32_t cfProc = 0;
1767
1768 if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED))
1769 continue;
1770
1771 int rc2 = pConn->pfnStreamIterate(pConn, pStream);
1772 if (RT_SUCCESS(rc2))
1773 {
1774 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
1775 {
1776 rc2 = pConn->pfnStreamCapture(pConn, pStream, &cfProc);
1777 if (RT_FAILURE(rc2))
1778 {
1779 LogFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
1780 continue;
1781 }
1782
1783 if (cfProc)
1784 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
1785 }
1786 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
1787 {
1788 rc2 = pConn->pfnStreamPlay(pConn, pStream, &cfProc);
1789 if (RT_FAILURE(rc2))
1790 {
1791 LogFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
1792 continue;
1793 }
1794 }
1795 else
1796 {
1797 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1798 continue;
1799 }
1800 }
1801
1802 const PDMAUDIOSTREAMSTS fStreamStatusNew = pConn->pfnStreamGetStatus(pConn, pStream);
1803
1804 /* Is the stream enabled or in pending disable state?
1805 * Don't consider this stream as being disabled then. */
1806 if (fStreamStatusNew & (PDMAUDIOSTREAMSTS_FLAGS_ENABLED | PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE))
1807 cStreamsDisabled--;
1808 /* Note: The mixer stream's internal status will be updated in the next iteration of this function. */
1809
1810 Log3Func(("\t%s: cPlayed/cCaptured=%RU32, rc2=%Rrc\n", pStream->szName, cfProc, rc2));
1811 }
1812
1813 Log3Func(("[%s] fPendingDisable=%RTbool, %RU8/%RU8 streams disabled\n",
1814 pSink->pszName, RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE), cStreamsDisabled, pSink->cStreams));
1815
1816 /* Update last updated timestamp. */
1817 pSink->tsLastUpdatedMs = RTTimeMilliTS();
1818
1819 /* All streams disabled and the sink is in pending disable mode? */
1820 if ( cStreamsDisabled == pSink->cStreams
1821 && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
1822 {
1823 audioMixerSinkReset(pSink);
1824 }
1825
1826 return rc;
1827}
1828
1829/**
1830 * Updates (invalidates) a mixer sink.
1831 *
1832 * @returns IPRT status code.
1833 * @param pSink Mixer sink to update.
1834 */
1835int AudioMixerSinkUpdate(PAUDMIXSINK pSink)
1836{
1837 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1838
1839 int rc = RTCritSectEnter(&pSink->CritSect);
1840 if (RT_FAILURE(rc))
1841 return rc;
1842
1843 rc = audioMixerSinkUpdateInternal(pSink);
1844
1845 int rc2 = RTCritSectLeave(&pSink->CritSect);
1846 AssertRC(rc2);
1847
1848 return rc;
1849}
1850
1851/**
1852 * Updates the (master) volume of a mixer sink.
1853 *
1854 * @returns IPRT status code.
1855 * @param pSink Mixer sink to update volume for.
1856 * @param pVolMaster Master volume to set.
1857 */
1858static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster)
1859{
1860 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1861 AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER);
1862
1863 LogFlowFunc(("[%s] Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1864 pSink->pszName, pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight));
1865 LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU32, rVol=%RU32 ",
1866 pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
1867
1868 /** @todo Very crude implementation for now -- needs more work! */
1869
1870 pSink->VolumeCombined.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted;
1871
1872 pSink->VolumeCombined.uLeft = ( (pSink->Volume.uLeft ? pSink->Volume.uLeft : 1)
1873 * (pVolMaster->uLeft ? pVolMaster->uLeft : 1)) / PDMAUDIO_VOLUME_MAX;
1874
1875 pSink->VolumeCombined.uRight = ( (pSink->Volume.uRight ? pSink->Volume.uRight : 1)
1876 * (pVolMaster->uRight ? pVolMaster->uRight : 1)) / PDMAUDIO_VOLUME_MAX;
1877
1878 LogFlow(("-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1879 pSink->VolumeCombined.fMuted, pSink->VolumeCombined.uLeft, pSink->VolumeCombined.uRight));
1880
1881 /* Propagate new sink volume to all streams in the sink. */
1882 PAUDMIXSTREAM pMixStream;
1883 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1884 {
1885 int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &pSink->VolumeCombined);
1886 AssertRC(rc2);
1887 }
1888
1889 return VINF_SUCCESS;
1890}
1891
1892/**
1893 * Writes (buffered) output data of a sink's stream to the bound audio connector stream.
1894 *
1895 * @returns IPRT status code.
1896 * @param pSink Sink of stream that contains the mixer stream.
1897 * @param pMixStream Mixer stream to write output data for.
1898 */
1899static int audioMixerSinkWriteToStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream)
1900{
1901 if (!pMixStream->pCircBuf)
1902 return VINF_SUCCESS;
1903
1904 return audioMixerSinkWriteToStreamEx(pSink, pMixStream, (uint32_t)RTCircBufUsed(pMixStream->pCircBuf), NULL /* pcbWritten */);
1905}
1906
1907/**
1908 * Writes (buffered) output data of a sink's stream to the bound audio connector stream, extended version.
1909 *
1910 * @returns IPRT status code.
1911 * @param pSink Sink of stream that contains the mixer stream.
1912 * @param pMixStream Mixer stream to write output data for.
1913 * @param cbToWrite Size (in bytes) to write.
1914 * @param pcbWritten Size (in bytes) written on success. Optional.
1915 */
1916static int audioMixerSinkWriteToStreamEx(PAUDMIXSINK pSink, PAUDMIXSTREAM pMixStream, uint32_t cbToWrite, uint32_t *pcbWritten)
1917{
1918 /* pcbWritten is optional. */
1919
1920 if ( !cbToWrite
1921 || !(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED))
1922 {
1923 if (pcbWritten)
1924 *pcbWritten = 0;
1925
1926 return VINF_SUCCESS;
1927 }
1928
1929 PRTCIRCBUF pCircBuf = pMixStream->pCircBuf;
1930
1931 const uint32_t cbWritableStream = pMixStream->pConn->pfnStreamGetWritable(pMixStream->pConn, pMixStream->pStream);
1932 cbToWrite = RT_MIN(cbToWrite, RT_MIN((uint32_t)RTCircBufUsed(pCircBuf), cbWritableStream));
1933
1934 Log3Func(("[%s] cbWritableStream=%RU32, cbToWrite=%RU32\n",
1935 pMixStream->pszName, cbWritableStream, cbToWrite));
1936
1937 uint32_t cbWritten = 0;
1938
1939 int rc = VINF_SUCCESS;
1940
1941 while (cbToWrite)
1942 {
1943 void *pvChunk;
1944 size_t cbChunk;
1945 RTCircBufAcquireReadBlock(pCircBuf, cbToWrite, &pvChunk, &cbChunk);
1946
1947 Log3Func(("[%s] cbChunk=%RU32\n", pMixStream->pszName, cbChunk));
1948
1949 uint32_t cbChunkWritten = 0;
1950 if (cbChunk)
1951 {
1952 rc = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvChunk, (uint32_t)cbChunk,
1953 &cbChunkWritten);
1954 if (RT_FAILURE(rc))
1955 {
1956 if (rc == VERR_BUFFER_OVERFLOW)
1957 {
1958 LogRel2(("Audio Mixer: Buffer overrun for mixer stream '%s' (sink '%s')\n", pMixStream->pszName, pSink->pszName));
1959 break;
1960 }
1961 else if (rc == VERR_AUDIO_STREAM_NOT_READY)
1962 {
1963 /* Stream is not enabled, just skip. */
1964 rc = VINF_SUCCESS;
1965 }
1966 else
1967 LogRel2(("Audio Mixer: Writing to mixer stream '%s' (sink '%s') failed, rc=%Rrc\n",
1968 pMixStream->pszName, pSink->pszName, rc));
1969
1970 if (RT_FAILURE(rc))
1971 LogFunc(("[%s] Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc));
1972 }
1973 }
1974
1975 RTCircBufReleaseReadBlock(pCircBuf, cbChunkWritten);
1976
1977 if ( RT_FAILURE(rc)
1978 || !cbChunkWritten)
1979 break;
1980
1981 Assert(cbToWrite >= cbChunkWritten);
1982 cbToWrite -= (uint32_t)cbChunkWritten;
1983
1984 cbWritten += (uint32_t)cbChunkWritten;
1985 }
1986
1987 Log3Func(("[%s] cbWritten=%RU32\n", pMixStream->pszName, cbWritten));
1988
1989 if (pcbWritten)
1990 *pcbWritten = cbWritten;
1991
1992#ifdef DEBUG_andy
1993 AssertRC(rc);
1994#endif
1995
1996 return rc;
1997}
1998
1999/**
2000 * Multiplexes audio output data to all connected mixer streams in a synchronized fashion, e.g.
2001 * only multiplex as much data as all streams can handle at this time.
2002 *
2003 * @returns IPRT status code.
2004 * @param pSink Sink to write audio output to.
2005 * @param enmOp What mixing operation to use. Currently not implemented.
2006 * @param pvBuf Pointer to audio data to write.
2007 * @param cbBuf Size (in bytes) of audio data to write.
2008 * @param pcbWrittenMin Returns minimum size (in bytes) successfully written to all mixer streams. Optional.
2009 */
2010static int audioMixerSinkMultiplexSync(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf,
2011 uint32_t *pcbWrittenMin)
2012{
2013 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2014 RT_NOREF(enmOp);
2015
2016 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
2017 ("%s: Can't multiplex to a sink which is not an output sink\n", pSink->pszName));
2018
2019 int rc = VINF_SUCCESS;
2020
2021 uint32_t cbToWriteMin = UINT32_MAX;
2022
2023 Log3Func(("[%s] cbBuf=%RU32\n", pSink->pszName, cbBuf));
2024
2025 PAUDMIXSTREAM pMixStream;
2026 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
2027 {
2028 if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) /* Mixing stream not enabled? Skip handling. */
2029 {
2030 Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pMixStream->pszName));
2031 continue;
2032 }
2033
2034 cbToWriteMin = RT_MIN(cbBuf, RT_MIN(cbToWriteMin, (uint32_t)RTCircBufFree(pMixStream->pCircBuf)));
2035 }
2036
2037 if (cbToWriteMin == UINT32_MAX) /* No space at all? */
2038 cbToWriteMin = 0;
2039
2040 if (cbToWriteMin)
2041 {
2042 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
2043 {
2044 if (!(pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)) /* Mixing stream not enabled? Skip handling. */
2045 continue;
2046
2047 PRTCIRCBUF pCircBuf = pMixStream->pCircBuf;
2048 void *pvChunk;
2049 size_t cbChunk;
2050
2051 uint32_t cbWrittenBuf = 0;
2052 uint32_t cbToWriteBuf = cbToWriteMin;
2053
2054 while (cbToWriteBuf)
2055 {
2056 RTCircBufAcquireWriteBlock(pCircBuf, cbToWriteBuf, &pvChunk, &cbChunk);
2057
2058 if (cbChunk)
2059 memcpy(pvChunk, (uint8_t *)pvBuf + cbWrittenBuf, cbChunk);
2060
2061 RTCircBufReleaseWriteBlock(pCircBuf, cbChunk);
2062
2063 cbWrittenBuf += (uint32_t)cbChunk;
2064 Assert(cbWrittenBuf <= cbBuf);
2065
2066 Assert(cbToWriteBuf >= cbChunk);
2067 cbToWriteBuf -= (uint32_t)cbChunk;
2068 }
2069
2070 if (cbWrittenBuf) /* Update the mixer stream's last written time stamp. */
2071 pMixStream->tsLastReadWrittenNs = RTTimeNanoTS();
2072
2073 Log3Func(("[%s] Mixer stream '%s' -> cbWrittenBuf=%RU32\n", pSink->pszName, pMixStream->pszName, cbWrittenBuf));
2074 }
2075 }
2076
2077 Log3Func(("[%s] cbBuf=%RU32, cbToWriteMin=%RU32\n", pSink->pszName, cbBuf, cbToWriteMin));
2078
2079 if (pcbWrittenMin)
2080 *pcbWrittenMin = cbToWriteMin;
2081
2082 return rc;
2083}
2084
2085/**
2086 * Writes data to a mixer sink.
2087 *
2088 * @returns IPRT status code.
2089 * @param pSink Sink to write data to.
2090 * @param enmOp Mixer operation to use when writing data to the sink.
2091 * @param pvBuf Buffer containing the audio data to write.
2092 * @param cbBuf Size (in bytes) of the buffer containing the audio data.
2093 * @param pcbWritten Number of bytes written. Optional.
2094 */
2095int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2096{
2097 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
2098 RT_NOREF(enmOp);
2099 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2100 AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
2101 /* pcbWritten is optional. */
2102
2103 int rc = RTCritSectEnter(&pSink->CritSect);
2104 if (RT_FAILURE(rc))
2105 return rc;
2106
2107 AssertMsg(pSink->fStatus & AUDMIXSINK_STS_RUNNING,
2108 ("%s: Can't write to a sink which is not running (anymore) (status 0x%x)\n", pSink->pszName, pSink->fStatus));
2109 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
2110 ("%s: Can't write to a sink which is not an output sink\n", pSink->pszName));
2111
2112 uint32_t cbWritten = 0;
2113 uint32_t cbToWrite = RT_MIN(AudioMixBufFreeBytes(&pSink->MixBuf), cbBuf);
2114 while (cbToWrite)
2115 {
2116 /* First, write the data to the mixer sink's own mixing buffer.
2117 * Here the audio data can be transformed into the mixer sink's format. */
2118 uint32_t cfWritten = 0;
2119 rc = AudioMixBufWriteCirc(&pSink->MixBuf, (uint8_t *)pvBuf + cbWritten, cbToWrite, &cfWritten);
2120 if (RT_FAILURE(rc))
2121 break;
2122
2123 const uint32_t cbWrittenChunk = DrvAudioHlpFramesToBytes(cfWritten, &pSink->PCMProps);
2124
2125 Assert(cbToWrite >= cbWrittenChunk);
2126 cbToWrite -= cbWrittenChunk;
2127 cbWritten += cbWrittenChunk;
2128 }
2129
2130 Log3Func(("[%s] cbBuf=%RU32 -> cbWritten=%RU32\n", pSink->pszName, cbBuf, cbWritten));
2131
2132 /* Update the sink's last written time stamp. */
2133 pSink->tsLastReadWrittenNs = RTTimeNanoTS();
2134
2135 if (pcbWritten)
2136 *pcbWritten = cbWritten;
2137
2138 int rc2 = RTCritSectLeave(&pSink->CritSect);
2139 AssertRC(rc2);
2140
2141 return rc;
2142}
2143
2144/*********************************************************************************************************************************
2145 * Mixer Stream implementation.
2146 ********************************************************************************************************************************/
2147
2148/**
2149 * Controls a mixer stream, internal version.
2150 *
2151 * @returns IPRT status code.
2152 * @param pMixStream Mixer stream to control.
2153 * @param enmCmd Mixer stream command to use.
2154 * @param fCtl Additional control flags. Pass 0.
2155 */
2156static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
2157{
2158 AssertPtr(pMixStream->pConn);
2159 AssertPtr(pMixStream->pStream);
2160
2161 RT_NOREF(fCtl);
2162
2163 int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd);
2164
2165 LogFlowFunc(("[%s] enmCmd=%ld, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc));
2166
2167 return rc;
2168}
2169
2170/**
2171 * Updates a mixer stream's internal status.
2172 *
2173 * @returns VBox status code.
2174 * @param pMixStream Mixer stream to to update internal status for.
2175 */
2176static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream)
2177{
2178 pMixStream->fStatus = AUDMIXSTREAM_STATUS_NONE;
2179
2180 if (pMixStream->pConn) /* Audio connector available? */
2181 {
2182 const uint32_t fStreamStatus = pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream);
2183
2184 if (DrvAudioHlpStreamStatusIsReady(fStreamStatus))
2185 pMixStream->fStatus |= AUDMIXSTREAM_STATUS_ENABLED;
2186
2187 AssertPtr(pMixStream->pSink);
2188 switch (pMixStream->pSink->enmDir)
2189 {
2190 case AUDMIXSINKDIR_INPUT:
2191 if (DrvAudioHlpStreamStatusCanRead(fStreamStatus))
2192 pMixStream->fStatus |= AUDMIXSTREAM_STATUS_CAN_READ;
2193 break;
2194
2195 case AUDMIXSINKDIR_OUTPUT:
2196 if (DrvAudioHlpStreamStatusCanWrite(fStreamStatus))
2197 pMixStream->fStatus |= AUDMIXSTREAM_STATUS_CAN_WRITE;
2198 break;
2199
2200 default:
2201 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
2202 break;
2203 }
2204 }
2205
2206 LogFlowFunc(("[%s] -> 0x%x\n", pMixStream->pszName, pMixStream->fStatus));
2207 return VINF_SUCCESS;
2208}
2209
2210/**
2211 * Controls a mixer stream.
2212 *
2213 * @returns IPRT status code.
2214 * @param pMixStream Mixer stream to control.
2215 * @param enmCmd Mixer stream command to use.
2216 * @param fCtl Additional control flags. Pass 0.
2217 */
2218int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
2219{
2220 RT_NOREF(fCtl);
2221 AssertPtrReturn(pMixStream, VERR_INVALID_POINTER);
2222 /** @todo Validate fCtl. */
2223
2224 int rc = RTCritSectEnter(&pMixStream->CritSect);
2225 if (RT_FAILURE(rc))
2226 return rc;
2227
2228 rc = audioMixerStreamCtlInternal(pMixStream, enmCmd, fCtl);
2229
2230 int rc2 = RTCritSectLeave(&pMixStream->CritSect);
2231 if (RT_SUCCESS(rc))
2232 rc = rc2;
2233
2234 return rc;
2235}
2236
2237/**
2238 * Destroys a mixer stream, internal version.
2239 *
2240 * @param pMixStream Mixer stream to destroy.
2241 */
2242static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream)
2243{
2244 AssertPtrReturnVoid(pMixStream);
2245
2246 LogFunc(("%s\n", pMixStream->pszName));
2247
2248 if (pMixStream->pConn) /* Stream has a connector interface present? */
2249 {
2250 if (pMixStream->pStream)
2251 {
2252 pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream);
2253 pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream);
2254
2255 pMixStream->pStream = NULL;
2256 }
2257
2258 pMixStream->pConn = NULL;
2259 }
2260
2261 if (pMixStream->pszName)
2262 {
2263 RTStrFree(pMixStream->pszName);
2264 pMixStream->pszName = NULL;
2265 }
2266
2267 if (pMixStream->pCircBuf)
2268 {
2269 RTCircBufDestroy(pMixStream->pCircBuf);
2270 pMixStream->pCircBuf = NULL;
2271 }
2272
2273 int rc2 = RTCritSectDelete(&pMixStream->CritSect);
2274 AssertRC(rc2);
2275
2276 RTMemFree(pMixStream);
2277 pMixStream = NULL;
2278}
2279
2280/**
2281 * Destroys a mixer stream.
2282 *
2283 * @param pMixStream Mixer stream to destroy.
2284 */
2285void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream)
2286{
2287 if (!pMixStream)
2288 return;
2289
2290 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
2291 AssertRC(rc2);
2292
2293 LogFunc(("%s\n", pMixStream->pszName));
2294
2295 if (pMixStream->pSink) /* Is the stream part of a sink? */
2296 {
2297 /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the
2298 * pointer will be gone from the stream. */
2299 PAUDMIXSINK pSink = pMixStream->pSink;
2300
2301 rc2 = audioMixerSinkRemoveStreamInternal(pSink, pMixStream);
2302 if (RT_SUCCESS(rc2))
2303 {
2304 Assert(pSink->cStreams);
2305 pSink->cStreams--;
2306 }
2307 }
2308 else
2309 rc2 = VINF_SUCCESS;
2310
2311 int rc3 = RTCritSectLeave(&pMixStream->CritSect);
2312 AssertRC(rc3);
2313
2314 if (RT_SUCCESS(rc2))
2315 {
2316 audioMixerStreamDestroyInternal(pMixStream);
2317 pMixStream = NULL;
2318 }
2319
2320 LogFlowFunc(("Returning %Rrc\n", rc2));
2321}
2322
2323/**
2324 * Returns whether a mixer stream currently is active (playing/recording) or not.
2325 *
2326 * @returns @c true if playing/recording, @c false if not.
2327 * @param pMixStream Mixer stream to return status for.
2328 */
2329bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream)
2330{
2331 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
2332 if (RT_FAILURE(rc2))
2333 return false;
2334
2335 AssertPtr(pMixStream->pConn);
2336 AssertPtr(pMixStream->pStream);
2337
2338 bool fIsActive;
2339
2340 if ( pMixStream->pConn
2341 && pMixStream->pStream
2342 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAGS_ENABLED))
2343 {
2344 fIsActive = true;
2345 }
2346 else
2347 fIsActive = false;
2348
2349 rc2 = RTCritSectLeave(&pMixStream->CritSect);
2350 AssertRC(rc2);
2351
2352 return fIsActive;
2353}
2354
2355/**
2356 * Returns whether a mixer stream is valid (e.g. initialized and in a working state) or not.
2357 *
2358 * @returns @c true if valid, @c false if not.
2359 * @param pMixStream Mixer stream to return status for.
2360 */
2361bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream)
2362{
2363 if (!pMixStream)
2364 return false;
2365
2366 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
2367 if (RT_FAILURE(rc2))
2368 return false;
2369
2370 bool fIsValid;
2371
2372 if ( pMixStream->pConn
2373 && pMixStream->pStream
2374 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED))
2375 {
2376 fIsValid = true;
2377 }
2378 else
2379 fIsValid = false;
2380
2381 rc2 = RTCritSectLeave(&pMixStream->CritSect);
2382 AssertRC(rc2);
2383
2384 return fIsValid;
2385}
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