VirtualBox

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

Last change on this file since 87875 was 87861, checked in by vboxsync, 4 years ago

Audio: Unified debug .WAV path file generation so that all data lands at the same place.

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