VirtualBox

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

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

Audio: We don't return IPRT status codes, but VBox status codes. duh. bugref:9890

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette