VirtualBox

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

Last change on this file since 68253 was 68132, checked in by vboxsync, 7 years ago

Audio: Renamed audio samples to audio frame because that's what they really are. No actual code changes.

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