VirtualBox

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

Last change on this file since 65624 was 65624, checked in by vboxsync, 8 years ago

Audio: More abstraction for the backends: Now the backend stream's data is completely separate from the audio connector interface. That way the backends cannot mess with the audio connector's data (e.g. mixing buffers and friends) anymore, and those are forced to use the audio connector API as meant now. Needs more testing, partly work in progress.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 54.5 KB
Line 
1/* $Id: AudioMixer.cpp 65624 2017-02-06 14:13:36Z vboxsync $ */
2/** @file
3 * VBox audio: Mixing routines, mainly used by the various audio device
4 * emulations to achieve proper multiplexing from/to attached
5 * devices LUNs.
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) and
9 * audio sinks (output).
10 *
11 * Think of this mixer as kind of a high(er) level interface for the audio connector
12 * interface, abstracting common tasks such as creating and managing various audio
13 * sources and sinks. This mixer class is purely optional and can be left out when
14 * implementing a new device emulation, using only the audi connector interface
15 * instead. For example, the SB16 emulation does not use this mixer and does all its
16 * 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
31/*
32 * Copyright (C) 2014-2017 Oracle Corporation
33 *
34 * This file is part of VirtualBox Open Source Edition (OSE), as
35 * available from http://www.virtualbox.org. This file is free software;
36 * you can redistribute it and/or modify it under the terms of the GNU
37 * General Public License (GPL) as published by the Free Software
38 * Foundation, in version 2 as it comes in the "COPYING" file of the
39 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
40 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
41 */
42#define LOG_GROUP LOG_GROUP_AUDIO_MIXER
43#include <VBox/log.h>
44#include "AudioMixer.h"
45#include "AudioMixBuffer.h"
46#include "DrvAudio.h"
47
48#include <VBox/vmm/pdm.h>
49#include <VBox/err.h>
50#include <VBox/vmm/mm.h>
51#include <VBox/vmm/pdmaudioifs.h>
52
53#include <iprt/alloc.h>
54#include <iprt/asm-math.h>
55#include <iprt/assert.h>
56#include <iprt/string.h>
57
58static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
59
60static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink);
61static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster);
62static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink);
63static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
64static void audioMixerSinkReset(PAUDMIXSINK pSink);
65static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink);
66
67int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl);
68static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream);
69
70
71#ifdef LOG_ENABLED
72/**
73 * Converts a mixer sink status to a string.
74 *
75 * @returns Stringified mixer sink flags. Must be free'd with RTStrFree().
76 * "NONE" if no flags set.
77 * @param fFlags Mixer sink flags to convert.
78 */
79static char *dbgAudioMixerSinkStatusToStr(AUDMIXSINKSTS fStatus)
80{
81#define APPEND_FLAG_TO_STR(_aFlag) \
82 if (fStatus & AUDMIXSINK_STS_##_aFlag) \
83 { \
84 if (pszFlags) \
85 { \
86 rc2 = RTStrAAppend(&pszFlags, " "); \
87 if (RT_FAILURE(rc2)) \
88 break; \
89 } \
90 \
91 rc2 = RTStrAAppend(&pszFlags, #_aFlag); \
92 if (RT_FAILURE(rc2)) \
93 break; \
94 } \
95
96 char *pszFlags = NULL;
97 int rc2 = VINF_SUCCESS;
98
99 do
100 {
101 APPEND_FLAG_TO_STR(NONE);
102 APPEND_FLAG_TO_STR(RUNNING);
103 APPEND_FLAG_TO_STR(PENDING_DISABLE);
104 APPEND_FLAG_TO_STR(DIRTY);
105
106 } while (0);
107
108 if ( RT_FAILURE(rc2)
109 && pszFlags)
110 {
111 RTStrFree(pszFlags);
112 pszFlags = NULL;
113 }
114
115#undef APPEND_FLAG_TO_STR
116
117 return pszFlags;
118}
119#endif /* DEBUG */
120
121/**
122 * Creates an audio sink and attaches it to the given mixer.
123 *
124 * @returns IPRT status code.
125 * @param pMixer Mixer to attach created sink to.
126 * @param pszName Name of the sink to create.
127 * @param enmDir Direction of the sink to create.
128 * @param ppSink Pointer which returns the created sink on success.
129 */
130int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink)
131{
132 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
133 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
134 /* ppSink is optional. */
135
136 int rc = RTCritSectEnter(&pMixer->CritSect);
137 if (RT_FAILURE(rc))
138 return rc;
139
140 PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK));
141 if (pSink)
142 {
143 pSink->pszName = RTStrDup(pszName);
144 if (!pSink->pszName)
145 rc = VERR_NO_MEMORY;
146
147 if (RT_SUCCESS(rc))
148 rc = RTCritSectInit(&pSink->CritSect);
149
150 if (RT_SUCCESS(rc))
151 {
152 pSink->pParent = pMixer;
153 pSink->enmDir = enmDir;
154 RTListInit(&pSink->lstStreams);
155
156 /* Set initial volume to max. */
157 pSink->Volume.fMuted = false;
158 pSink->Volume.uLeft = PDMAUDIO_VOLUME_MAX;
159 pSink->Volume.uRight = PDMAUDIO_VOLUME_MAX;
160
161 /* Ditto for the combined volume. */
162 pSink->VolumeCombined.fMuted = false;
163 pSink->VolumeCombined.uLeft = PDMAUDIO_VOLUME_MAX;
164 pSink->VolumeCombined.uRight = PDMAUDIO_VOLUME_MAX;
165
166 RTListAppend(&pMixer->lstSinks, &pSink->Node);
167 pMixer->cSinks++;
168
169 LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n",
170 pMixer, pSink, pMixer->cSinks));
171
172 if (ppSink)
173 *ppSink = pSink;
174 }
175
176 if (RT_FAILURE(rc))
177 {
178 RTCritSectDelete(&pSink->CritSect);
179
180 if (pSink)
181 {
182 RTMemFree(pSink);
183 pSink = NULL;
184 }
185 }
186 }
187 else
188 rc = VERR_NO_MEMORY;
189
190 int rc2 = RTCritSectLeave(&pMixer->CritSect);
191 AssertRC(rc2);
192
193 return rc;
194}
195
196/**
197 * Creates an audio mixer.
198 *
199 * @returns IPRT status code.
200 * @param pszName Name of the audio mixer.
201 * @param fFlags Creation flags. Not used at the moment and must be 0.
202 * @param ppMixer Pointer which returns the created mixer object.
203 */
204int AudioMixerCreate(const char *pszName, uint32_t fFlags, PAUDIOMIXER *ppMixer)
205{
206 RT_NOREF(fFlags);
207 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
208 /** @todo Add fFlags validation. */
209 AssertPtrReturn(ppMixer, VERR_INVALID_POINTER);
210
211 int rc = VINF_SUCCESS;
212
213 PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER));
214 if (pMixer)
215 {
216 pMixer->pszName = RTStrDup(pszName);
217 if (!pMixer->pszName)
218 rc = VERR_NO_MEMORY;
219
220 if (RT_SUCCESS(rc))
221 rc = RTCritSectInit(&pMixer->CritSect);
222
223 if (RT_SUCCESS(rc))
224 {
225 pMixer->cSinks = 0;
226 RTListInit(&pMixer->lstSinks);
227
228 /* Set master volume to the max. */
229 pMixer->VolMaster.fMuted = false;
230 pMixer->VolMaster.uLeft = PDMAUDIO_VOLUME_MAX;
231 pMixer->VolMaster.uRight = PDMAUDIO_VOLUME_MAX;
232
233 LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName));
234
235 *ppMixer = pMixer;
236 }
237 else
238 RTMemFree(pMixer);
239 }
240 else
241 rc = VERR_NO_MEMORY;
242
243 LogFlowFuncLeaveRC(rc);
244 return rc;
245}
246
247#ifdef DEBUG
248/**
249 * Helper function for the internal debugger to print the mixer's current
250 * state, along with the attached sinks.
251 *
252 * @param pMixer Mixer to print debug output for.
253 * @param pHlp Debug info helper to use.
254 * @param pszArgs Optional arguments. Not being used at the moment.
255 */
256void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs)
257{
258 RT_NOREF(pszArgs);
259 PAUDMIXSINK pSink;
260 unsigned iSink = 0;
261
262 int rc2 = RTCritSectEnter(&pMixer->CritSect);
263 if (RT_FAILURE(rc2))
264 return;
265
266 pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
267 pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
268
269 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
270 {
271 pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName,
272 pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted);
273 ++iSink;
274 }
275
276 rc2 = RTCritSectLeave(&pMixer->CritSect);
277 AssertRC(rc2);
278}
279#endif
280
281/**
282 * Destroys an audio mixer.
283 *
284 * @param pMixer Audio mixer to destroy.
285 */
286void AudioMixerDestroy(PAUDIOMIXER pMixer)
287{
288 if (!pMixer)
289 return;
290
291 int rc2 = RTCritSectEnter(&pMixer->CritSect);
292 AssertRC(rc2);
293
294 LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
295
296 PAUDMIXSINK pSink, pSinkNext;
297 RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node)
298 {
299 /* Save a pointer to the sink to remove, as pSink
300 * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */
301 PAUDMIXSINK pSinkToRemove = pSink;
302
303 audioMixerRemoveSinkInternal(pMixer, pSinkToRemove);
304 audioMixerSinkDestroyInternal(pSinkToRemove);
305 }
306
307 pMixer->cSinks = 0;
308
309 if (pMixer->pszName)
310 {
311 RTStrFree(pMixer->pszName);
312 pMixer->pszName = NULL;
313 }
314
315 rc2 = RTCritSectLeave(&pMixer->CritSect);
316 AssertRC(rc2);
317
318 RTCritSectDelete(&pMixer->CritSect);
319
320 RTMemFree(pMixer);
321 pMixer = NULL;
322}
323
324/**
325 * Invalidates all internal data, internal version.
326 *
327 * @returns IPRT status code.
328 * @param pMixer Mixer to invalidate data for.
329 */
330int audioMixerInvalidateInternal(PAUDIOMIXER pMixer)
331{
332 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
333
334 LogFlowFunc(("[%s]\n", pMixer->pszName));
335
336 /* Propagate new master volume to all connected sinks. */
337 PAUDMIXSINK pSink;
338 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
339 {
340 int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster);
341 AssertRC(rc2);
342 }
343
344 return VINF_SUCCESS;
345}
346
347/**
348 * Invalidates all internal data.
349 *
350 * @returns IPRT status code.
351 * @param pMixer Mixer to invalidate data for.
352 */
353void AudioMixerInvalidate(PAUDIOMIXER pMixer)
354{
355 AssertPtrReturnVoid(pMixer);
356
357 int rc2 = RTCritSectEnter(&pMixer->CritSect);
358 AssertRC(rc2);
359
360 LogFlowFunc(("[%s]\n", pMixer->pszName));
361
362 rc2 = audioMixerInvalidateInternal(pMixer);
363 AssertRC(rc2);
364
365 rc2 = RTCritSectLeave(&pMixer->CritSect);
366 AssertRC(rc2);
367}
368
369/**
370 * Removes a formerly attached audio sink for an audio mixer, internal version.
371 *
372 * @returns IPRT status code.
373 * @param pMixer Mixer to remove sink from.
374 * @param pSink Sink to remove.
375 */
376static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
377{
378 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
379 if (!pSink)
380 return VERR_NOT_FOUND;
381
382 AssertMsgReturn(pSink->pParent == pMixer, ("%s: Is not part of mixer '%s'\n",
383 pSink->pszName, pMixer->pszName), VERR_NOT_FOUND);
384
385 LogFlowFunc(("[%s]: pSink=%s, cSinks=%RU8\n",
386 pMixer->pszName, pSink->pszName, pMixer->cSinks));
387
388 /* Remove sink from mixer. */
389 RTListNodeRemove(&pSink->Node);
390 Assert(pMixer->cSinks);
391
392 /* Set mixer to NULL so that we know we're not part of any mixer anymore. */
393 pSink->pParent = NULL;
394
395 return VINF_SUCCESS;
396}
397
398/**
399 * Removes a formerly attached audio sink for an audio mixer.
400 *
401 * @returns IPRT status code.
402 * @param pMixer Mixer to remove sink from.
403 * @param pSink Sink to remove.
404 */
405
406void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
407{
408 int rc2 = RTCritSectEnter(&pMixer->CritSect);
409 AssertRC(rc2);
410
411 audioMixerSinkRemoveAllStreamsInternal(pSink);
412 audioMixerRemoveSinkInternal(pMixer, pSink);
413
414 rc2 = RTCritSectLeave(&pMixer->CritSect);
415}
416
417/**
418 * Sets the mixer's master volume.
419 *
420 * @returns IPRT status code.
421 * @param pMixer Mixer to set master volume for.
422 * @param pVol Volume to set.
423 */
424int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol)
425{
426 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
427 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
428
429 int rc = RTCritSectEnter(&pMixer->CritSect);
430 if (RT_FAILURE(rc))
431 return rc;
432
433 memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME));
434
435 LogFlowFunc(("[%s]: lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
436 pMixer->pszName, pVol->uLeft, pVol->uRight,
437 pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight));
438
439 rc = audioMixerInvalidateInternal(pMixer);
440
441 int rc2 = RTCritSectLeave(&pMixer->CritSect);
442 AssertRC(rc2);
443
444 return rc;
445}
446
447/*********************************************************************************************************************************
448 * Mixer Sink implementation.
449 ********************************************************************************************************************************/
450
451/**
452 * Adds an audio stream to a specific audio sink.
453 *
454 * @returns IPRT status code.
455 * @param pSink Sink to add audio stream to.
456 * @param pStream Stream to add.
457 */
458int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
459{
460 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
461 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
462
463 int rc = RTCritSectEnter(&pSink->CritSect);
464 if (RT_FAILURE(rc))
465 return rc;
466
467 if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */
468 {
469 int rc2 = RTCritSectLeave(&pSink->CritSect);
470 AssertRC(rc2);
471
472 return VERR_NO_MORE_HANDLES;
473 }
474
475 LogFlowFuncEnter();
476
477#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
478 /* Make sure only compatible streams are added. */
479 if (pStream->enmDir == PDMAUDIODIR_IN)
480 {
481 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, &pStream->InOut.pIn->Props))
482 {
483#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
484 /* Chain: Stream (Child) -> Sink (Child) -> Guest (Parent). */
485 PPDMAUDIOMIXBUF pHstIn = &pStream->InOut.pIn->pHstStrmIn->MixBuf;
486 PPDMAUDIOMIXBUF pGstIn = &pStream->InOut.pIn->MixBuf;
487
488 /* Unlink any former parent from host input. */
489 AudioMixBufUnlink(pHstIn);
490
491 /* Link host input to this sink as a parent. */
492 rc = AudioMixBufLinkTo(pHstIn, &pSink->MixBuf);
493 AssertRC(rc);
494
495 /* Unlink any former parent from this sink. */
496 AudioMixBufUnlink(&pSink->MixBuf);
497
498 /* Link guest input to this sink as a parent. */
499 rc = AudioMixBufLinkTo(&pSink->MixBuf, pGstIn);
500 AssertRC(rc);
501# ifdef DEBUG
502 AudioMixBufDbgPrintChain(&pStream->InOut.pIn->MixBuf);
503# endif
504#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
505 }
506 else
507 AssertFailedStmt(rc = VERR_WRONG_TYPE);
508 }
509 else if (pStream->enmDir == PDMAUDIODIR_OUT)
510 {
511 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, &pStream->InOut.pOut->Props))
512 {
513#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
514 /* Chain: Guest (Child) -> Sink (Child) -> Stream (Parent). */
515 rc = AudioMixBufLinkTo(&pStream->InOut.pOut->pHstStrmOut->MixBuf, &pSink->MixBuf);
516# ifdef DEBUG
517 AudioMixBufDbgPrintChain(&pSink->MixBuf);
518# endif
519#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
520 }
521 else
522 AssertFailedStmt(rc = VERR_WRONG_TYPE);
523 }
524 else
525 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
526#else
527 rc = VINF_SUCCESS;
528#endif
529
530 if (RT_SUCCESS(rc))
531 {
532 /** @todo Check if stream already is assigned to (another) sink. */
533
534 /* If the sink is running and not in pending disable mode,
535 * make sure that the added stream also is enabled. */
536 if ( (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
537 && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
538 {
539 rc = audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE, AUDMIXSTRMCTL_FLAG_NONE);
540 }
541
542 if (RT_SUCCESS(rc))
543 {
544 /* Apply the sink's combined volume to the stream. */
545 rc = pStream->pConn->pfnStreamSetVolume(pStream->pConn, pStream->pStream, &pSink->VolumeCombined);
546 AssertRC(rc);
547 }
548
549 if (RT_SUCCESS(rc))
550 {
551 /* Save pointer to sink the stream is attached to. */
552 pStream->pSink = pSink;
553
554 /* Append stream to sink's list. */
555 RTListAppend(&pSink->lstStreams, &pStream->Node);
556 pSink->cStreams++;
557 }
558 }
559
560 LogFlowFunc(("[%s]: cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc));
561
562 int rc2 = RTCritSectLeave(&pSink->CritSect);
563 AssertRC(rc2);
564
565 return rc;
566}
567
568/**
569 * Creates an audio mixer stream.
570 *
571 * @returns IPRT status code.
572 * @param pSink Sink to use for creating the stream.
573 * @param pConn Audio connector interface to use.
574 * @param pCfg Audio stream configuration to use.
575 * @param fFlags Stream creation flags. Currently unused, set to 0.
576 * @param ppStream Pointer which receives the newly created audio stream.
577 */
578int AudioMixerSinkCreateStream(PAUDMIXSINK pSink,
579 PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, uint32_t fFlags, PAUDMIXSTREAM *ppStream)
580{
581 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
582 AssertPtrReturn(pConn, VERR_INVALID_POINTER);
583 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
584 /** @todo Validate fFlags. */
585 /* ppStream is optional. */
586
587 PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM));
588 if (!pMixStream)
589 return VERR_NO_MEMORY;
590
591 pMixStream->pszName = RTStrDup(pCfg->szName);
592 if (!pMixStream->pszName)
593 {
594 RTMemFree(pMixStream);
595 return VERR_NO_MEMORY;
596 }
597
598 int rc = RTCritSectEnter(&pSink->CritSect);
599 if (RT_FAILURE(rc))
600 return rc;
601
602 LogFlowFunc(("[%s]: fFlags=0x%x (enmDir=%ld, %RU8 bits, %RU8 channels, %RU32Hz)\n",
603 pSink->pszName, fFlags, pCfg->enmDir, pCfg->Props.cBits, pCfg->Props.cChannels, pCfg->Props.uHz));
604
605 /*
606 * Initialize the host-side configuration for the stream to be created.
607 * Always use the sink's PCM audio format as the host side when creating a stream for it.
608 */
609 PDMAUDIOSTREAMCFG CfgHost;
610 rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost);
611 AssertRCReturn(rc, rc);
612
613 /* Apply the sink's direction for the configuration to use to
614 * create the stream. */
615 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
616 {
617 CfgHost.DestSource.Source = pCfg->DestSource.Source;
618 CfgHost.enmDir = PDMAUDIODIR_IN;
619 }
620 else
621 {
622 CfgHost.DestSource.Dest = pCfg->DestSource.Dest;
623 CfgHost.enmDir = PDMAUDIODIR_OUT;
624 }
625
626 RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName);
627
628 rc = RTCritSectInit(&pMixStream->CritSect);
629 if (RT_SUCCESS(rc))
630 {
631 PPDMAUDIOSTREAM pStream;
632 rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream);
633 if (RT_SUCCESS(rc))
634 {
635 /* Save the audio stream pointer to this mixing stream. */
636 pMixStream->pStream = pStream;
637
638 /* Increase the stream's reference count to let others know
639 * we're reyling on it to be around now. */
640 pConn->pfnStreamRetain(pConn, pStream);
641 }
642 }
643
644 if (RT_SUCCESS(rc))
645 {
646 pMixStream->fFlags = fFlags;
647 pMixStream->pConn = pConn;
648
649 if (ppStream)
650 *ppStream = pMixStream;
651 }
652 else if (pMixStream)
653 {
654 int rc2 = RTCritSectDelete(&pMixStream->CritSect);
655 AssertRC(rc2);
656
657 if (pMixStream->pszName)
658 {
659 RTStrFree(pMixStream->pszName);
660 pMixStream->pszName = NULL;
661 }
662
663 RTMemFree(pMixStream);
664 pMixStream = NULL;
665 }
666
667 int rc2 = RTCritSectLeave(&pSink->CritSect);
668 AssertRC(rc2);
669
670 return rc;
671}
672
673/**
674 * Static helper function to translate a sink command
675 * to a PDM audio stream command.
676 *
677 * @returns PDM audio stream command, or PDMAUDIOSTREAMCMD_UNKNOWN if not found.
678 * @param enmCmd Mixer sink command to translate.
679 */
680static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd)
681{
682 switch (enmCmd)
683 {
684 case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE;
685 case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE;
686 case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE;
687 case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME;
688 default: break;
689 }
690
691 AssertMsgFailed(("Unsupported sink command %d\n", enmCmd));
692 return PDMAUDIOSTREAMCMD_UNKNOWN;
693}
694
695/**
696 * Controls a mixer sink.
697 *
698 * @returns IPRT status code.
699 * @param pSink Mixer sink to control.
700 * @param enmSinkCmd Sink command to set.
701 */
702int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd)
703{
704 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
705
706 PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd);
707 if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN)
708 return VERR_NOT_SUPPORTED;
709
710 int rc = RTCritSectEnter(&pSink->CritSect);
711 if (RT_FAILURE(rc))
712 return rc;
713
714 PAUDMIXSTREAM pStream;
715 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
716 {
717 int rc2 = audioMixerStreamCtlInternal(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE);
718 if (RT_SUCCESS(rc))
719 rc = rc2;
720 /* Keep going. Flag? */
721 }
722
723 if (enmSinkCmd == AUDMIXSINKCMD_ENABLE)
724 {
725 /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */
726 pSink->fStatus = AUDMIXSINK_STS_RUNNING;
727 }
728 else if (enmSinkCmd == AUDMIXSINKCMD_DISABLE)
729 {
730 /* Set the sink in a pending disable state first.
731 * The final status (disabled) will be set in the sink's iteration. */
732 pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE;
733 }
734
735#ifdef LOG_ENABLED
736 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
737 LogFlowFunc(("[%s]: enmCmd=%d, fStatus=%s, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pszStatus, rc));
738 RTStrFree(pszStatus);
739#endif
740
741 int rc2 = RTCritSectLeave(&pSink->CritSect);
742 AssertRC(rc2);
743
744 return rc;
745}
746
747/**
748 * Destroys a mixer sink and removes it from the attached mixer (if any).
749 *
750 * @param pSink Mixer sink to destroy.
751 */
752void AudioMixerSinkDestroy(PAUDMIXSINK pSink)
753{
754 if (!pSink)
755 return;
756
757 int rc2 = RTCritSectEnter(&pSink->CritSect);
758 AssertRC(rc2);
759
760 if (pSink->pParent)
761 {
762 /* Save mixer pointer, as after audioMixerRemoveSinkInternal() the
763 * pointer will be gone from the stream. */
764 PAUDIOMIXER pMixer = pSink->pParent;
765 AssertPtr(pMixer);
766
767 audioMixerRemoveSinkInternal(pMixer, pSink);
768
769 Assert(pMixer->cSinks);
770 pMixer->cSinks--;
771 }
772
773 rc2 = RTCritSectLeave(&pSink->CritSect);
774 AssertRC(rc2);
775
776 audioMixerSinkDestroyInternal(pSink);
777}
778
779/**
780 * Destroys a mixer sink.
781 *
782 * @param pSink Mixer sink to destroy.
783 */
784static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink)
785{
786 AssertPtrReturnVoid(pSink);
787
788 LogFunc(("%s\n", pSink->pszName));
789
790 PAUDMIXSTREAM pStream, pStreamNext;
791 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
792 {
793 /* Save a pointer to the stream to remove, as pStream
794 * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */
795 PAUDMIXSTREAM pStreamToRemove = pStream;
796
797 audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove);
798 audioMixerStreamDestroyInternal(pStreamToRemove);
799 }
800
801 if (pSink->pszName)
802 {
803 RTStrFree(pSink->pszName);
804 pSink->pszName = NULL;
805 }
806
807 RTMemFree(pSink);
808 pSink = NULL;
809}
810
811/**
812 * Returns the amount of bytes ready to be read from a sink since the last call
813 * to AudioMixerSinkUpdate().
814 *
815 * @returns Amount of bytes ready to be read from the sink.
816 * @param pSink Sink to return number of available samples for.
817 */
818uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink)
819{
820 AssertPtrReturn(pSink, 0);
821
822 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("%s: Can't read from a non-input sink\n", pSink->pszName));
823
824 int rc = RTCritSectEnter(&pSink->CritSect);
825 if (RT_FAILURE(rc))
826 return 0;
827
828 uint32_t cbReadable;
829
830#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
831# error "Implement me!"
832#else
833 /* Get the time delta and calculate the bytes that need to be processed.
834 *
835 * Example for 16 bit, 44.1KhZ, stereo:
836 * 16 bits per sample x 44.100 samples per second = 705.600 bits per second x 2 channels
837 * = 1.411.200 bits per second of stereo.
838 */
839 uint64_t tsDeltaMS = RTTimeMilliTS() - pSink->tsLastUpdatedMS;
840
841 cbReadable = ((DrvAudioHlpCalcBitrate(&pSink->PCMProps) / 8) / 1000 /* s to ms */) * tsDeltaMS;
842
843 Log3Func(("[%s] Bitrate is %RU32 bytes/s -> %RU64ms / %RU32 bytes elapsed\n",
844 pSink->pszName, DrvAudioHlpCalcBitrate(&pSink->PCMProps) / 8, tsDeltaMS, cbReadable));
845#endif
846
847 Log3Func(("[%s] cbReadable=%RU32\n", pSink->pszName, cbReadable));
848
849 int rc2 = RTCritSectLeave(&pSink->CritSect);
850 AssertRC(rc2);
851
852 return cbReadable;
853}
854
855/**
856 * Returns the amount of bytes ready to be written to a sink since the last call
857 * to AudioMixerSinkUpdate().
858 *
859 * @returns Amount of bytes ready to be written to the sink.
860 * @param pSink Sink to return number of available samples for.
861 */
862uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink)
863{
864 AssertPtrReturn(pSink, 0);
865
866 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("%s: Can't write to a non-output sink\n", pSink->pszName));
867
868 int rc = RTCritSectEnter(&pSink->CritSect);
869 if (RT_FAILURE(rc))
870 return 0;
871
872 uint32_t cbWritable;
873
874 if ( !(pSink->fStatus & AUDMIXSINK_STS_RUNNING)
875 || pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)
876 {
877 cbWritable = 0;
878 }
879 else
880 {
881#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
882# error "Implement me!"
883#else
884 /* Get the time delta and calculate the bytes that need to be processed.
885 *
886 * Example for 16 bit, 44.1KhZ, stereo:
887 * 16 bits per sample x 44.100 samples per second = 705.600 bits per second x 2 channels
888 * = 1.411.200 bits per second of stereo.
889 */
890 uint64_t tsDeltaMS = RTTimeMilliTS() - pSink->tsLastUpdatedMS;
891
892 cbWritable = ((DrvAudioHlpCalcBitrate(&pSink->PCMProps) / 8) / 1000 /* s to ms */) * tsDeltaMS;
893
894 Log3Func(("[%s] Bitrate is %RU32 bytes/s -> %RU64ms / %RU32 bytes elapsed\n",
895 pSink->pszName, DrvAudioHlpCalcBitrate(&pSink->PCMProps) / 8, tsDeltaMS, cbWritable));
896#endif
897 }
898
899 Log3Func(("[%s] cbWritable=%RU32\n", pSink->pszName, cbWritable));
900
901 int rc2 = RTCritSectLeave(&pSink->CritSect);
902 AssertRC(rc2);
903
904 return cbWritable;
905}
906
907/**
908 * Returns the sink's mixing direction.
909 *
910 * @returns Mixing direction.
911 * @param pSink Sink to return direction for.
912 */
913AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink)
914{
915 AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN);
916
917 int rc = RTCritSectEnter(&pSink->CritSect);
918 if (RT_FAILURE(rc))
919 return AUDMIXSINKDIR_UNKNOWN;
920
921 AUDMIXSINKDIR enmDir = pSink->enmDir;
922
923 int rc2 = RTCritSectLeave(&pSink->CritSect);
924 AssertRC(rc2);
925
926 return enmDir;
927}
928
929/**
930 * Returns a specific mixer stream from a sink, based on its index.
931 *
932 * @returns Mixer stream if found, or NULL if not found.
933 * @param pSink Sink to retrieve mixer stream from.
934 * @param uIndex Index of the mixer stream to return.
935 */
936PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex)
937{
938 AssertPtrReturn(pSink, NULL);
939
940 int rc = RTCritSectEnter(&pSink->CritSect);
941 if (RT_FAILURE(rc))
942 return NULL;
943
944 AssertMsgReturn(uIndex < pSink->cStreams,
945 ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL);
946
947 /* Slow lookup, d'oh. */
948 PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node);
949 while (uIndex)
950 {
951 pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node);
952 uIndex--;
953 }
954
955 /** @todo Do we need to raise the stream's reference count here? */
956
957 int rc2 = RTCritSectLeave(&pSink->CritSect);
958 AssertRC(rc2);
959
960 AssertPtr(pStream);
961 return pStream;
962}
963
964/**
965 * Returns the current status of a mixer sink.
966 *
967 * @returns The sink's current status.
968 * @param pSink Mixer sink to return status for.
969 */
970AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink)
971{
972 if (!pSink)
973 return AUDMIXSINK_STS_NONE;
974
975 int rc2 = RTCritSectEnter(&pSink->CritSect);
976 if (RT_FAILURE(rc2))
977 return AUDMIXSINK_STS_NONE;
978
979 /* If the dirty flag is set, there is unprocessed data in the sink. */
980 AUDMIXSINKSTS stsSink = pSink->fStatus;
981
982 rc2 = RTCritSectLeave(&pSink->CritSect);
983 AssertRC(rc2);
984
985 return stsSink;
986}
987
988/**
989 * Returns the number of attached mixer streams to a mixer sink.
990 *
991 * @returns The number of attached mixer streams.
992 * @param pSink Mixer sink to return number for.
993 */
994uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink)
995{
996 if (!pSink)
997 return 0;
998
999 int rc2 = RTCritSectEnter(&pSink->CritSect);
1000 if (RT_FAILURE(rc2))
1001 return 0;
1002
1003 uint8_t cStreams = pSink->cStreams;
1004
1005 rc2 = RTCritSectLeave(&pSink->CritSect);
1006 AssertRC(rc2);
1007
1008 return cStreams;
1009}
1010
1011/**
1012 * Returns whether the sink is in an active state or not.
1013 * Note: The pending disable state also counts as active.
1014 *
1015 * @returns True if active, false if not.
1016 * @param pSink Sink to return active state for.
1017 */
1018bool AudioMixerSinkIsActive(PAUDMIXSINK pSink)
1019{
1020 if (!pSink)
1021 return false;
1022
1023 int rc2 = RTCritSectEnter(&pSink->CritSect);
1024 if (RT_FAILURE(rc2))
1025 return 0;
1026
1027 bool fIsActive = (pSink->fStatus & AUDMIXSINK_STS_RUNNING);
1028
1029 rc2 = RTCritSectLeave(&pSink->CritSect);
1030 AssertRC(rc2);
1031
1032 return fIsActive;
1033}
1034
1035/**
1036 * Reads audio data from a mixer sink.
1037 *
1038 * @returns IPRT status code.
1039 * @param pSink Mixer sink to read data from.
1040 * @param enmOp Mixer operation to use for reading the data.
1041 * @param pvBuf Buffer where to store the read data.
1042 * @param cbBuf Buffer size (in bytes) where to store the data.
1043 * @param pcbRead Number of bytes read. Optional.
1044 */
1045int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1046{
1047 RT_NOREF(enmOp);
1048 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1049 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1050 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1051 /* pcbRead is optional. */
1052
1053 /** @todo Handle mixing operation enmOp! */
1054
1055 int rc = RTCritSectEnter(&pSink->CritSect);
1056 if (RT_FAILURE(rc))
1057 return rc;
1058
1059 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT,
1060 ("Can't read from a sink which is not an input sink\n"));
1061
1062#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1063# error "Implement me!"
1064#else
1065 uint8_t *pvMixBuf = (uint8_t *)RTMemAlloc(cbBuf);
1066 if (!pvMixBuf)
1067 {
1068 int rc2 = RTCritSectLeave(&pSink->CritSect);
1069 AssertRC(rc2);
1070
1071 return VERR_NO_MEMORY;
1072 }
1073#endif
1074
1075 uint32_t cbRead = 0;
1076
1077 /* Flag indicating whether this sink is in a 'clean' state,
1078 * e.g. there is no more data to read from. */
1079 bool fClean = true;
1080
1081 PAUDMIXSTREAM pMixStream;
1082 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1083 {
1084 if (!(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED))
1085 {
1086 Log3Func(("[%s] Stream '%s' disabled, skipping ...\n", pSink->pszName, pMixStream->pszName));
1087 continue;
1088 }
1089
1090 uint32_t cbTotalRead = 0;
1091 uint32_t cbToRead = cbBuf;
1092
1093 int rc2 = VINF_SUCCESS;
1094
1095 while (cbToRead)
1096 {
1097 uint32_t cbReadStrm;
1098 AssertPtr(pMixStream->pConn);
1099#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1100# error "Implement me!"
1101#else
1102 rc2 = pMixStream->pConn->pfnStreamRead(pMixStream->pConn, pMixStream->pStream,
1103 (uint8_t *)pvMixBuf + cbTotalRead, cbToRead, &cbReadStrm);
1104#endif
1105 if (RT_FAILURE(rc2))
1106 LogFunc(("[%s] Failed reading from stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc2));
1107
1108 Log3Func(("[%s] Stream '%s': Read %RU32 bytes\n", pSink->pszName, pMixStream->pszName, cbReadStrm));
1109
1110 if ( RT_FAILURE(rc2)
1111 || !cbReadStrm)
1112 break;
1113
1114 /** @todo Right now we only handle one stream (the last one added in fact). */
1115
1116 AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW);
1117 cbToRead -= cbReadStrm;
1118 cbTotalRead += cbReadStrm;
1119 }
1120
1121 if (RT_FAILURE(rc2))
1122 continue;
1123
1124 cbRead = RT_MAX(cbRead, cbTotalRead);
1125
1126 PDMAUDIOSTRMSTS strmSts = pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream);
1127
1128 /* Still some data available? Then sink is not clean (yet). */
1129 if (strmSts & PDMAUDIOSTRMSTS_FLAG_DATA_READABLE)
1130 fClean = false;
1131 }
1132
1133 if (RT_SUCCESS(rc))
1134 {
1135 if (fClean)
1136 pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
1137
1138#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1139# error "Implement me!"
1140#else
1141 if (cbRead)
1142 memcpy(pvBuf, pvMixBuf, cbRead);
1143#endif
1144 if (pcbRead)
1145 *pcbRead = cbRead;
1146 }
1147
1148#ifndef VBOX_AUDIO_MIXER_WITH_MIXBUF
1149 RTMemFree(pvMixBuf);
1150#endif
1151
1152
1153#ifdef LOG_ENABLED
1154 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
1155 Log2Func(("[%s] cbRead=%RU32, fClean=%RTbool, fStatus=%s, rc=%Rrc\n", pSink->pszName, cbRead, fClean, pszStatus, rc));
1156 RTStrFree(pszStatus);
1157#endif
1158
1159 int rc2 = RTCritSectLeave(&pSink->CritSect);
1160 AssertRC(rc2);
1161
1162 return rc;
1163}
1164
1165/**
1166 * Removes a mixer stream from a mixer sink, internal version.
1167 *
1168 * @returns IPRT status code.
1169 * @param pSink Sink to remove mixer stream from.
1170 * @param pStream Stream to remove.
1171 */
1172static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1173{
1174 AssertPtrReturn(pSink, VERR_INVALID_PARAMETER);
1175 if ( !pStream
1176 || !pStream->pSink) /* Not part of a sink anymore? */
1177 {
1178 return VERR_NOT_FOUND;
1179 }
1180
1181 AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n",
1182 pStream->pszName, pSink->pszName), VERR_NOT_FOUND);
1183
1184 LogFlowFunc(("[%s]: (Stream = %s), cStreams=%RU8\n",
1185 pSink->pszName, pStream->pStream->szName, pSink->cStreams));
1186
1187#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1188 /* Unlink mixing buffer. */
1189 AudioMixBufUnlink(&pStream->pStream->MixBuf);
1190#endif
1191
1192 /* Remove stream from sink. */
1193 RTListNodeRemove(&pStream->Node);
1194
1195 /* Set sink to NULL so that we know we're not part of any sink anymore. */
1196 pStream->pSink = NULL;
1197
1198 return VINF_SUCCESS;
1199}
1200
1201/**
1202 * Removes a mixer stream from a mixer sink.
1203 *
1204 * @param pSink Sink to remove mixer stream from.
1205 * @param pStream Stream to remove.
1206 */
1207void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
1208{
1209 int rc2 = RTCritSectEnter(&pSink->CritSect);
1210 AssertRC(rc2);
1211
1212 rc2 = audioMixerSinkRemoveStreamInternal(pSink, pStream);
1213 if (RT_SUCCESS(rc2))
1214 {
1215 Assert(pSink->cStreams);
1216 pSink->cStreams--;
1217 }
1218
1219 rc2 = RTCritSectLeave(&pSink->CritSect);
1220 AssertRC(rc2);
1221}
1222
1223/**
1224 * Removes all attached streams from a given sink.
1225 *
1226 * @param pSink Sink to remove attached streams from.
1227 */
1228static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink)
1229{
1230 if (!pSink)
1231 return;
1232
1233 LogFunc(("%s\n", pSink->pszName));
1234
1235 PAUDMIXSTREAM pStream, pStreamNext;
1236 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
1237 audioMixerSinkRemoveStreamInternal(pSink, pStream);
1238}
1239
1240/**
1241 * Resets the sink's state.
1242 *
1243 * @param pSink Sink to reset.
1244 */
1245static void audioMixerSinkReset(PAUDMIXSINK pSink)
1246{
1247 if (!pSink)
1248 return;
1249
1250 LogFunc(("[%s]\n", pSink->pszName));
1251
1252 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
1253 {
1254#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1255 AudioMixBufReset(&pSink->MixBuf);
1256#else
1257 pSink->In.cbReadable = 0;
1258#endif
1259 }
1260 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
1261 {
1262#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1263 AudioMixBufReset(&pSink->MixBuf);
1264#else
1265 pSink->Out.cbWritable = 0;
1266#endif
1267 }
1268
1269 /* Update last updated timestamp. */
1270 pSink->tsLastUpdatedMS = RTTimeMilliTS();
1271
1272 /* Reset status. */
1273 pSink->fStatus = AUDMIXSINK_STS_NONE;
1274}
1275
1276/**
1277 * Removes all attached streams from a given sink.
1278 *
1279 * @param pSink Sink to remove attached streams from.
1280 */
1281void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
1282{
1283 if (!pSink)
1284 return;
1285
1286 int rc2 = RTCritSectEnter(&pSink->CritSect);
1287 AssertRC(rc2);
1288
1289 audioMixerSinkRemoveAllStreamsInternal(pSink);
1290
1291 pSink->cStreams = 0;
1292
1293 rc2 = RTCritSectLeave(&pSink->CritSect);
1294 AssertRC(rc2);
1295}
1296
1297/**
1298 * Resets a sink. This will immediately stop all processing.
1299 *
1300 * @param pSink Sink to reset.
1301 */
1302void AudioMixerSinkReset(PAUDMIXSINK pSink)
1303{
1304 if (!pSink)
1305 return;
1306
1307 int rc2 = RTCritSectEnter(&pSink->CritSect);
1308 AssertRC(rc2);
1309
1310 audioMixerSinkReset(pSink);
1311
1312 rc2 = RTCritSectLeave(&pSink->CritSect);
1313 AssertRC(rc2);
1314}
1315
1316/**
1317 * Sets the audio format of a mixer sink.
1318 *
1319 * @returns IPRT status code.
1320 * @param pSink Sink to set audio format for.
1321 * @param pPCMProps Audio format (PCM properties) to set.
1322 */
1323int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMAUDIOPCMPROPS pPCMProps)
1324{
1325 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1326 AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER);
1327
1328 int rc = RTCritSectEnter(&pSink->CritSect);
1329 if (RT_FAILURE(rc))
1330 return rc;
1331
1332 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */
1333 {
1334 rc = RTCritSectLeave(&pSink->CritSect);
1335 AssertRC(rc);
1336
1337 return rc;
1338 }
1339
1340 if (pSink->PCMProps.uHz)
1341 LogFlowFunc(("[%s]: Old format: %RU8 bit, %RU8 channels, %RU32Hz\n",
1342 pSink->pszName, pSink->PCMProps.cBits, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
1343
1344 memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMAUDIOPCMPROPS));
1345
1346 LogFlowFunc(("[%s]: New format %RU8 bit, %RU8 channels, %RU32Hz\n",
1347 pSink->pszName, pSink->PCMProps.cBits, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
1348
1349#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
1350 /* Also update the sink's mixing buffer format. */
1351 AudioMixBufDestroy(&pSink->MixBuf);
1352 rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, pPCMProps, _4K /** @todo Make configurable? */);
1353 if (RT_SUCCESS(rc))
1354 {
1355 PAUDMIXSTREAM pStream;
1356 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
1357 {
1358 /** @todo Invalidate mix buffers! */
1359 }
1360 }
1361#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
1362
1363 int rc2 = RTCritSectLeave(&pSink->CritSect);
1364 AssertRC(rc2);
1365
1366 LogFlowFuncLeaveRC(rc);
1367 return rc;
1368}
1369
1370/**
1371 * Set the volume of an individual sink.
1372 *
1373 * @returns IPRT status code.
1374 * @param pSink Sink to set volume for.
1375 * @param pVol Volume to set.
1376 */
1377int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol)
1378{
1379 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1380 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
1381
1382 int rc = RTCritSectEnter(&pSink->CritSect);
1383 if (RT_FAILURE(rc))
1384 return rc;
1385
1386 memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME));
1387
1388 LogFlowFunc(("[%s]: fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1389 pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
1390
1391 AssertPtr(pSink->pParent);
1392 rc = audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster);
1393
1394 int rc2 = RTCritSectLeave(&pSink->CritSect);
1395 AssertRC(rc2);
1396
1397 return rc;
1398}
1399
1400/**
1401 * Updates a mixer sink, internal version.
1402 *
1403 * @returns IPRT status code.
1404 * @param pSink Mixer sink to update.
1405 */
1406static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink)
1407{
1408 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1409
1410 int rc = VINF_SUCCESS;
1411
1412#ifdef LOG_ENABLED
1413 char *pszStatus = dbgAudioMixerSinkStatusToStr(pSink->fStatus);
1414 Log3Func(("[%s] fStatus=%s\n", pSink->pszName, pszStatus));
1415 RTStrFree(pszStatus);
1416#endif
1417
1418 /* Sink disabled? Take a shortcut. */
1419 if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
1420 return rc;
1421
1422 /* Number of detected disabled streams of this sink. */
1423 uint8_t cStreamsDisabled = 0;
1424
1425 PAUDMIXSTREAM pMixStream, pMixStreamNext;
1426 RTListForEachSafe(&pSink->lstStreams, pMixStream, pMixStreamNext, AUDMIXSTREAM, Node)
1427 {
1428 PPDMAUDIOSTREAM pStream = pMixStream->pStream;
1429 AssertPtr(pStream);
1430
1431 PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn;
1432 AssertPtr(pConn);
1433
1434 uint32_t cProc = 0;
1435
1436 int rc2 = pConn->pfnStreamIterate(pConn, pStream);
1437 if (RT_SUCCESS(rc2))
1438 {
1439 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
1440 {
1441 rc = pConn->pfnStreamCapture(pConn, pStream, &cProc);
1442 if (RT_FAILURE(rc2))
1443 {
1444 LogFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
1445 if (RT_SUCCESS(rc))
1446 rc = rc2;
1447 continue;
1448 }
1449
1450 if (cProc)
1451 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
1452 }
1453 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
1454 {
1455 rc2 = pConn->pfnStreamPlay(pConn, pStream, &cProc);
1456 if (RT_FAILURE(rc2))
1457 {
1458 LogFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
1459 if (RT_SUCCESS(rc))
1460 rc = rc2;
1461 continue;
1462 }
1463 }
1464 else
1465 {
1466 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1467 continue;
1468 }
1469
1470 rc2 = pConn->pfnStreamIterate(pConn, pStream);
1471 if (RT_FAILURE(rc2))
1472 {
1473 LogFunc(("%s: Failed re-iterating stream '%s', rc=%Rrc\n", pSink->pszName, pStream->szName, rc2));
1474 if (RT_SUCCESS(rc))
1475 rc = rc2;
1476 continue;
1477 }
1478
1479 PDMAUDIOSTRMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pStream);
1480
1481 /* Is the stream not enabled and also is not in a pending disable state anymore? */
1482 if ( !(strmSts & PDMAUDIOSTRMSTS_FLAG_ENABLED)
1483 && !(strmSts & PDMAUDIOSTRMSTS_FLAG_PENDING_DISABLE))
1484 {
1485 cStreamsDisabled++;
1486 }
1487 }
1488
1489 Log3Func(("\t%s: cPlayed/cCaptured=%RU32, rc2=%Rrc\n", pStream->szName, cProc, rc2));
1490 }
1491
1492 Log3Func(("[%s] fPendingDisable=%RTbool, %RU8/%RU8 streams disabled\n",
1493 pSink->pszName, RT_BOOL(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE), cStreamsDisabled, pSink->cStreams));
1494
1495 /* Update last updated timestamp. */
1496 pSink->tsLastUpdatedMS = RTTimeMilliTS();
1497
1498 /* All streams disabled and the sink is in pending disable mode? */
1499 if ( cStreamsDisabled == pSink->cStreams
1500 && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
1501 {
1502 audioMixerSinkReset(pSink);
1503 }
1504
1505 Log3Func(("[%s] cbReadable=%RU32, cbWritable=%RU32, rc=%Rrc\n",
1506 pSink->pszName, pSink->In.cbReadable, pSink->Out.cbWritable, rc));
1507
1508 return rc;
1509}
1510
1511/**
1512 * Updates (invalidates) a mixer sink.
1513 *
1514 * @returns IPRT status code.
1515 * @param pSink Mixer sink to update.
1516 */
1517int AudioMixerSinkUpdate(PAUDMIXSINK pSink)
1518{
1519 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1520
1521 int rc = RTCritSectEnter(&pSink->CritSect);
1522 if (RT_FAILURE(rc))
1523 return rc;
1524
1525 rc = audioMixerSinkUpdateInternal(pSink);
1526
1527 int rc2 = RTCritSectLeave(&pSink->CritSect);
1528 AssertRC(rc2);
1529
1530 return rc;
1531}
1532
1533/**
1534 * Updates the (master) volume of a mixer sink.
1535 *
1536 * @returns IPRT status code.
1537 * @param pSink Mixer sink to update volume for.
1538 * @param pVolMaster Master volume to set.
1539 */
1540static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster)
1541{
1542 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1543 AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER);
1544
1545 LogFlowFunc(("[%s] Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1546 pSink->pszName, pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight));
1547 LogFlowFunc(("[%s] fMuted=%RTbool, lVol=%RU32, rVol=%RU32 ",
1548 pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
1549
1550 /** @todo Very crude implementation for now -- needs more work! */
1551
1552 pSink->VolumeCombined.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted;
1553
1554 pSink->VolumeCombined.uLeft = ( (pSink->Volume.uLeft ? pSink->Volume.uLeft : 1)
1555 * (pVolMaster->uLeft ? pVolMaster->uLeft : 1)) / PDMAUDIO_VOLUME_MAX;
1556
1557 pSink->VolumeCombined.uRight = ( (pSink->Volume.uRight ? pSink->Volume.uRight : 1)
1558 * (pVolMaster->uRight ? pVolMaster->uRight : 1)) / PDMAUDIO_VOLUME_MAX;
1559
1560 LogFlow(("-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1561 pSink->VolumeCombined.fMuted, pSink->VolumeCombined.uLeft, pSink->VolumeCombined.uRight));
1562
1563 /* Propagate new sink volume to all streams in the sink. */
1564 PAUDMIXSTREAM pMixStream;
1565 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1566 {
1567 int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &pSink->VolumeCombined);
1568 AssertRC(rc2);
1569 }
1570
1571 return VINF_SUCCESS;
1572}
1573
1574/**
1575 * Writes data to a mixer sink.
1576 *
1577 * @returns IPRT status code.
1578 * @param pSink Sink to write data to.
1579 * @param enmOp Mixer operation to use when writing data to the sink.
1580 * @param pvBuf Buffer containing the audio data to write.
1581 * @param cbBuf Size (in bytes) of the buffer containing the audio data.
1582 * @param pcbWritten Number of bytes written. Optional.
1583 */
1584int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1585{
1586 RT_NOREF(enmOp);
1587 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1588 /* pcbWritten is optional. */
1589
1590 if (!pvBuf || !cbBuf)
1591 {
1592 if (pcbWritten)
1593 *pcbWritten = 0;
1594 return VINF_SUCCESS;
1595 }
1596
1597 int rc = RTCritSectEnter(&pSink->CritSect);
1598 if (RT_FAILURE(rc))
1599 return rc;
1600
1601 AssertMsg(pSink->fStatus & AUDMIXSINK_STS_RUNNING,
1602 ("%s: Can't write to a sink which is not running (anymore) (status 0x%x)\n", pSink->pszName, pSink->fStatus));
1603 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
1604 ("%s: Can't write to a sink which is not an output sink\n", pSink->pszName));
1605
1606 Log3Func(("[%s] enmOp=%d, cbBuf=%RU32\n", pSink->pszName, enmOp, cbBuf));
1607
1608 uint32_t cbWritten = 0;
1609
1610 PAUDMIXSTREAM pMixStream;
1611 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1612 {
1613 if (!(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED))
1614 {
1615 Log3Func(("\t%s: Stream '%s' disabled, skipping ...\n", pMixStream->pszName, pMixStream->pszName));
1616 continue;
1617 }
1618
1619 uint32_t cbProcessed = 0;
1620 int rc2 = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvBuf, cbBuf, &cbProcessed);
1621 if (RT_FAILURE(rc2))
1622 LogFunc(("[%s] Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pszName, rc2));
1623
1624 if (cbProcessed < cbBuf)
1625 LogFunc(("[%s] Only written %RU32/%RU32 bytes for stream '%s', rc=%Rrc\n",
1626 pSink->pszName, cbProcessed, cbBuf, pMixStream->pszName, rc2));
1627
1628 if (cbProcessed)
1629 {
1630 /* Set dirty bit. */
1631 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
1632 }
1633
1634 Log3Func(("\t%s: cbProcessed=%RU32\n", pMixStream->pszName, cbProcessed));
1635
1636 /*
1637 * Return the maximum bytes processed by all connected streams.
1638 * Streams which have processed less than the current maximum have to deal with
1639 * this themselves.
1640 */
1641 cbWritten = RT_MAX(cbWritten, cbProcessed);
1642 }
1643
1644 if (pcbWritten)
1645 *pcbWritten = cbWritten;
1646
1647 int rc2 = RTCritSectLeave(&pSink->CritSect);
1648 AssertRC(rc2);
1649
1650 return rc;
1651}
1652
1653/*********************************************************************************************************************************
1654 * Mixer Stream implementation.
1655 ********************************************************************************************************************************/
1656
1657/**
1658 * Controls a mixer stream, internal version.
1659 *
1660 * @returns IPRT status code.
1661 * @param pMixStream Mixer stream to control.
1662 * @param enmCmd Mixer stream command to use.
1663 * @param fCtl Additional control flags. Pass 0.
1664 */
1665int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
1666{
1667 AssertPtr(pMixStream->pConn);
1668 AssertPtr(pMixStream->pStream);
1669
1670 RT_NOREF(fCtl);
1671
1672 int rc = pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd);
1673
1674 LogFlowFunc(("[%s] enmCmd=%ld, rc=%Rrc\n", pMixStream->pszName, enmCmd, rc));
1675
1676 return rc;
1677}
1678
1679/**
1680 * Controls a mixer stream.
1681 *
1682 * @returns IPRT status code.
1683 * @param pMixStream Mixer stream to control.
1684 * @param enmCmd Mixer stream command to use.
1685 * @param fCtl Additional control flags. Pass 0.
1686 */
1687int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
1688{
1689 RT_NOREF(fCtl);
1690 AssertPtrReturn(pMixStream, VERR_INVALID_POINTER);
1691 /** @todo Validate fCtl. */
1692
1693 int rc = RTCritSectEnter(&pMixStream->CritSect);
1694 if (RT_FAILURE(rc))
1695 return rc;
1696
1697 rc = audioMixerStreamCtlInternal(pMixStream, enmCmd, fCtl);
1698
1699 int rc2 = RTCritSectLeave(&pMixStream->CritSect);
1700 if (RT_SUCCESS(rc))
1701 rc = rc2;
1702
1703 return rc;
1704}
1705
1706/**
1707 * Destroys a mixer stream, internal version.
1708 *
1709 * @param pMixStream Mixer stream to destroy.
1710 */
1711static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream)
1712{
1713 AssertPtrReturnVoid(pMixStream);
1714
1715 LogFunc(("%s\n", pMixStream->pszName));
1716
1717 if (pMixStream->pConn) /* Stream has a connector interface present? */
1718 {
1719 if (pMixStream->pStream)
1720 {
1721 pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream);
1722 pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream);
1723
1724 pMixStream->pStream = NULL;
1725 }
1726
1727 pMixStream->pConn = NULL;
1728 }
1729
1730 if (pMixStream->pszName)
1731 {
1732 RTStrFree(pMixStream->pszName);
1733 pMixStream->pszName = NULL;
1734 }
1735
1736 int rc2 = RTCritSectDelete(&pMixStream->CritSect);
1737 AssertRC(rc2);
1738
1739 RTMemFree(pMixStream);
1740 pMixStream = NULL;
1741}
1742
1743/**
1744 * Destroys a mixer stream.
1745 *
1746 * @param pMixStream Mixer stream to destroy.
1747 */
1748void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream)
1749{
1750 if (!pMixStream)
1751 return;
1752
1753 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
1754 AssertRC(rc2);
1755
1756 LogFunc(("%s\n", pMixStream->pszName));
1757
1758 if (pMixStream->pSink) /* Is the stream part of a sink? */
1759 {
1760 /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the
1761 * pointer will be gone from the stream. */
1762 PAUDMIXSINK pSink = pMixStream->pSink;
1763
1764 rc2 = audioMixerSinkRemoveStreamInternal(pSink, pMixStream);
1765 if (RT_SUCCESS(rc2))
1766 {
1767 Assert(pSink->cStreams);
1768 pSink->cStreams--;
1769 }
1770 }
1771 else
1772 rc2 = VINF_SUCCESS;
1773
1774 int rc3 = RTCritSectLeave(&pMixStream->CritSect);
1775 AssertRC(rc3);
1776
1777 if (RT_SUCCESS(rc2))
1778 {
1779 audioMixerStreamDestroyInternal(pMixStream);
1780 pMixStream = NULL;
1781 }
1782
1783 LogFlowFunc(("Returning %Rrc\n", rc2));
1784}
1785
1786/**
1787 * Returns whether a mixer stream currently is active (playing/recording) or not.
1788 *
1789 * @returns @c true if playing/recording, @c false if not.
1790 * @param pMixStream Mixer stream to return status for.
1791 */
1792bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream)
1793{
1794 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
1795 if (RT_FAILURE(rc2))
1796 return false;
1797
1798 AssertPtr(pMixStream->pConn);
1799 AssertPtr(pMixStream->pStream);
1800
1801 bool fIsActive;
1802
1803 if ( pMixStream->pConn
1804 && pMixStream->pStream
1805 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED))
1806 {
1807 fIsActive = true;
1808 }
1809 else
1810 fIsActive = false;
1811
1812 rc2 = RTCritSectLeave(&pMixStream->CritSect);
1813 AssertRC(rc2);
1814
1815 return fIsActive;
1816}
1817
1818/**
1819 * Returns whether a mixer stream is valid (e.g. initialized and in a working state) or not.
1820 *
1821 * @returns @c true if valid, @c false if not.
1822 * @param pMixStream Mixer stream to return status for.
1823 */
1824bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream)
1825{
1826 if (!pMixStream)
1827 return false;
1828
1829 int rc2 = RTCritSectEnter(&pMixStream->CritSect);
1830 if (RT_FAILURE(rc2))
1831 return false;
1832
1833 bool fIsValid;
1834
1835 if ( pMixStream->pConn
1836 && pMixStream->pStream
1837 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_INITIALIZED))
1838 {
1839 fIsValid = true;
1840 }
1841 else
1842 fIsValid = false;
1843
1844 rc2 = RTCritSectLeave(&pMixStream->CritSect);
1845 AssertRC(rc2);
1846
1847 return fIsValid;
1848}
1849
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