VirtualBox

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

Last change on this file since 61798 was 61764, checked in by vboxsync, 9 years ago

Audio: Implemented backend-independent handling of added/removed audio devices.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.1 KB
Line 
1/* $Id: AudioMixer.cpp 61764 2016-06-20 09:49:55Z 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 * As audio driver instances are handled as LUNs on the device level, this
12 * audio mixer then can take care of e.g. mixing various inputs/outputs to/from
13 * a specific source/sink.
14 *
15 * How and which audio streams are connected to sinks/sources depends on how
16 * the audio mixer has been set up.
17 *
18 * A sink can connect multiple output streams together, whereas a source
19 * does this with input streams. Each sink / source consists of one or more
20 * so-called mixer streams, which then in turn have pointers to the actual
21 * PDM audio input/output streams.
22 */
23
24/*
25 * Copyright (C) 2014-2016 Oracle Corporation
26 *
27 * This file is part of VirtualBox Open Source Edition (OSE), as
28 * available from http://www.virtualbox.org. This file is free software;
29 * you can redistribute it and/or modify it under the terms of the GNU
30 * General Public License (GPL) as published by the Free Software
31 * Foundation, in version 2 as it comes in the "COPYING" file of the
32 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
33 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
34 */
35#define LOG_GROUP LOG_GROUP_AUDIO_MIXER
36#include <VBox/log.h>
37#include "AudioMixer.h"
38#include "AudioMixBuffer.h"
39#include "DrvAudio.h"
40
41#include <VBox/vmm/pdm.h>
42#include <VBox/err.h>
43#include <VBox/vmm/mm.h>
44#include <VBox/vmm/pdmaudioifs.h>
45
46#include <iprt/alloc.h>
47#include <iprt/asm-math.h>
48#include <iprt/assert.h>
49#include <iprt/string.h>
50
51static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink);
52
53static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink);
54static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster);
55static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink);
56static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
57static void audioMixerSinkReset(PAUDMIXSINK pSink);
58static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink);
59
60static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pStream);
61
62
63int AudioMixerCreateSink(PAUDIOMIXER pMixer, const char *pszName, AUDMIXSINKDIR enmDir, PAUDMIXSINK *ppSink)
64{
65 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
66 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
67 /* ppSink is optional. */
68
69 int rc = VINF_SUCCESS;
70
71 PAUDMIXSINK pSink = (PAUDMIXSINK)RTMemAllocZ(sizeof(AUDMIXSINK));
72 if (pSink)
73 {
74 pSink->pszName = RTStrDup(pszName);
75 if (!pSink->pszName)
76 rc = VERR_NO_MEMORY;
77
78 if (RT_SUCCESS(rc))
79 {
80 pSink->pParent = pMixer;
81 pSink->enmDir = enmDir;
82 RTListInit(&pSink->lstStreams);
83
84 /* Set initial volume to max. */
85 pSink->Volume.fMuted = false;
86 pSink->Volume.uLeft = 0x7F;
87 pSink->Volume.uRight = 0x7F;
88
89 RTListAppend(&pMixer->lstSinks, &pSink->Node);
90 pMixer->cSinks++;
91
92 LogFlowFunc(("pMixer=%p, pSink=%p, cSinks=%RU8\n",
93 pMixer, pSink, pMixer->cSinks));
94
95 if (ppSink)
96 *ppSink = pSink;
97 }
98 else
99 RTMemFree(pSink);
100 }
101 else
102 rc = VERR_NO_MEMORY;
103
104 return rc;
105}
106
107int AudioMixerCreate(const char *pszName, uint32_t uFlags, PAUDIOMIXER *ppMixer)
108{
109 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
110 /** @todo Add flag validation. */
111 AssertPtrReturn(ppMixer, VERR_INVALID_POINTER);
112
113 int rc = VINF_SUCCESS;
114
115 PAUDIOMIXER pMixer = (PAUDIOMIXER)RTMemAllocZ(sizeof(AUDIOMIXER));
116 if (pMixer)
117 {
118 pMixer->pszName = RTStrDup(pszName);
119 if (!pMixer->pszName)
120 rc = VERR_NO_MEMORY;
121
122 if (RT_SUCCESS(rc))
123 {
124 pMixer->cSinks = 0;
125 RTListInit(&pMixer->lstSinks);
126
127 pMixer->VolMaster.fMuted = false;
128 pMixer->VolMaster.uLeft = UINT32_MAX;
129 pMixer->VolMaster.uRight = UINT32_MAX;
130
131 LogFlowFunc(("Created mixer '%s'\n", pMixer->pszName));
132
133 *ppMixer = pMixer;
134 }
135 else
136 RTMemFree(pMixer);
137 }
138 else
139 rc = VERR_NO_MEMORY;
140
141 LogFlowFuncLeaveRC(rc);
142 return rc;
143}
144
145void AudioMixerDebug(PAUDIOMIXER pMixer, PCDBGFINFOHLP pHlp, const char *pszArgs)
146{
147 PAUDMIXSINK pSink;
148 unsigned iSink = 0;
149
150 pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
151 pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
152
153 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
154 {
155 pHlp->pfnPrintf(pHlp, "[Sink %u] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", iSink, pSink->pszName,
156 pSink->Volume.uLeft, pSink->Volume.uRight, pSink->Volume.fMuted);
157 ++iSink;
158 }
159}
160
161void AudioMixerDestroy(PAUDIOMIXER pMixer)
162{
163 if (!pMixer)
164 return;
165
166 LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
167
168 PAUDMIXSINK pSink, pSinkNext;
169 RTListForEachSafe(&pMixer->lstSinks, pSink, pSinkNext, AUDMIXSINK, Node)
170 {
171 /* Save a pointer to the sink to remove, as pSink
172 * will not be valid anymore after calling audioMixerRemoveSinkInternal(). */
173 PAUDMIXSINK pSinkToRemove = pSink;
174
175 audioMixerRemoveSinkInternal(pMixer, pSinkToRemove);
176 audioMixerSinkDestroyInternal(pSinkToRemove);
177 }
178
179 pMixer->cSinks = 0;
180
181 if (pMixer->pszName)
182 {
183 RTStrFree(pMixer->pszName);
184 pMixer->pszName = NULL;
185 }
186
187 RTMemFree(pMixer);
188 pMixer = NULL;
189}
190
191int AudioMixerGetDeviceFormat(PAUDIOMIXER pMixer, PPDMAUDIOSTREAMCFG pCfg)
192{
193 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
194 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
195
196 /** @todo Perform a deep copy, if needed. */
197 *pCfg = pMixer->devFmt;
198
199 return VINF_SUCCESS;
200}
201
202void AudioMixerInvalidate(PAUDIOMIXER pMixer)
203{
204 AssertPtrReturnVoid(pMixer);
205
206 LogFlowFunc(("[%s]: Invalidating ...\n", pMixer->pszName));
207
208 /* Propagate new master volume to all connected sinks. */
209 PAUDMIXSINK pSink;
210 RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
211 {
212 int rc2 = audioMixerSinkUpdateVolume(pSink, &pMixer->VolMaster);
213 AssertRC(rc2);
214 }
215}
216
217static int audioMixerRemoveSinkInternal(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
218{
219 AssertPtrReturn(pMixer, VERR_INVALID_PARAMETER);
220 if (!pSink)
221 return VERR_NOT_FOUND;
222
223 AssertMsgReturn(pSink->pParent == pMixer, ("Sink '%s' is not part of mixer '%s'\n",
224 pSink->pszName, pMixer->pszName), VERR_NOT_FOUND);
225
226 LogFlowFunc(("[%s]: pSink=%s, cSinks=%RU8\n",
227 pMixer->pszName, pSink->pszName, pMixer->cSinks));
228
229 /* Remove sink from mixer. */
230 RTListNodeRemove(&pSink->Node);
231 Assert(pMixer->cSinks);
232 pMixer->cSinks--;
233
234 /* Set mixer to NULL so that we know we're not part of any mixer anymore. */
235 pSink->pParent = NULL;
236
237 return VINF_SUCCESS;
238}
239
240void AudioMixerRemoveSink(PAUDIOMIXER pMixer, PAUDMIXSINK pSink)
241{
242 audioMixerSinkRemoveAllStreamsInternal(pSink);
243 audioMixerRemoveSinkInternal(pMixer, pSink);
244}
245
246int AudioMixerSetDeviceFormat(PAUDIOMIXER pMixer, PPDMAUDIOSTREAMCFG pCfg)
247{
248 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
249 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
250
251 /** @todo Perform a deep copy, if needed. */
252 pMixer->devFmt = *pCfg;
253
254 return VINF_SUCCESS;
255}
256
257/**
258 * Sets the mixer's master volume.
259 *
260 * @returns IPRT status code.
261 * @param pMixer Mixer to set master volume for.
262 * @param pVol Volume to set.
263 */
264int AudioMixerSetMasterVolume(PAUDIOMIXER pMixer, PPDMAUDIOVOLUME pVol)
265{
266 AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
267 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
268
269 pMixer->VolMaster = *pVol;
270
271 LogFlowFunc(("[%s]: lVol=%RU32, rVol=%RU32 => fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
272 pMixer->pszName, pVol->uLeft, pVol->uRight,
273 pMixer->VolMaster.fMuted, pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight));
274
275 AudioMixerInvalidate(pMixer);
276 return VINF_SUCCESS;
277}
278
279/*********************************************************************************************************************************
280 * Mixer Sink implementation.
281 ********************************************************************************************************************************/
282
283int AudioMixerSinkAddStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
284{
285 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
286 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
287
288 if (pSink->cStreams == UINT8_MAX) /* 255 streams per sink max. */
289 return VERR_NO_MORE_HANDLES;
290
291 int rc;
292
293 LogFlowFuncEnter();
294
295#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
296 /* Make sure only compatible streams are added. */
297 if (pStream->enmDir == PDMAUDIODIR_IN)
298 {
299 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, &pStream->InOut.pIn->Props))
300 {
301#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
302 /* Chain: Stream (Child) -> Sink (Child) -> Guest (Parent). */
303 PPDMAUDIOMIXBUF pHstIn = &pStream->InOut.pIn->pHstStrmIn->MixBuf;
304 PPDMAUDIOMIXBUF pGstIn = &pStream->InOut.pIn->MixBuf;
305
306 /* Unlink any former parent from host input. */
307 AudioMixBufUnlink(pHstIn);
308
309 /* Link host input to this sink as a parent. */
310 rc = AudioMixBufLinkTo(pHstIn, &pSink->MixBuf);
311 AssertRC(rc);
312
313 /* Unlink any former parent from this sink. */
314 AudioMixBufUnlink(&pSink->MixBuf);
315
316 /* Link guest input to this sink as a parent. */
317 rc = AudioMixBufLinkTo(&pSink->MixBuf, pGstIn);
318 AssertRC(rc);
319# ifdef DEBUG
320 AudioMixBufDbgPrintChain(&pStream->InOut.pIn->MixBuf);
321# endif
322#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
323 }
324 else
325 rc = VERR_WRONG_TYPE;
326 }
327 else if (pStream->enmDir == PDMAUDIODIR_OUT)
328 {
329 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, &pStream->InOut.pOut->Props))
330 {
331#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
332 /* Chain: Guest (Child) -> Sink (Child) -> Stream (Parent). */
333 rc = AudioMixBufLinkTo(&pStream->InOut.pOut->pHstStrmOut->MixBuf, &pSink->MixBuf);
334# ifdef DEBUG
335 AudioMixBufDbgPrintChain(&pSink->MixBuf);
336# endif
337#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
338 }
339 else
340 rc = VERR_WRONG_TYPE;
341 }
342 else
343 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
344#else
345 rc = VINF_SUCCESS;
346#endif
347
348 if (RT_SUCCESS(rc))
349 {
350 /** @todo Check if stream already is assigned to (another) sink. */
351
352 /* Save pointer to sink the stream is attached to. */
353 pStream->pSink = pSink;
354
355 /* Append stream to sink's list. */
356 RTListAppend(&pSink->lstStreams, &pStream->Node);
357 pSink->cStreams++;
358 }
359
360 LogFlowFunc(("[%s]: cStreams=%RU8, rc=%Rrc\n", pSink->pszName, pSink->cStreams, rc));
361 return rc;
362}
363
364int AudioMixerSinkCreateStream(PAUDMIXSINK pSink,
365 PPDMIAUDIOCONNECTOR pConn, PPDMAUDIOSTREAMCFG pCfg, uint32_t fFlags, PAUDMIXSTREAM *ppStream)
366{
367 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
368 AssertPtrReturn(pConn, VERR_INVALID_POINTER);
369 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
370 /** @todo Validate fFlags. */
371 /* ppStream is optional. */
372
373 PAUDMIXSTREAM pMixStream = (PAUDMIXSTREAM)RTMemAllocZ(sizeof(AUDMIXSTREAM));
374 if (!pMixStream)
375 return VERR_NO_MEMORY;
376
377 pMixStream->pszName = RTStrDup(pCfg->szName);
378 if (!pMixStream->pszName)
379 {
380 RTMemFree(pMixStream);
381 return VERR_NO_MEMORY;
382 }
383
384 LogFlowFunc(("[%s]: fFlags=0x%x (enmDir=%ld, %s, %RU8 channels, %RU32Hz)\n",
385 pSink->pszName, fFlags, pCfg->enmDir, DrvAudioHlpAudFmtToStr(pCfg->enmFormat), pCfg->cChannels, pCfg->uHz));
386
387 /*
388 * Initialize the host-side configuration for the stream to be created.
389 * Always use the sink's PCM audio format as the host side when creating a stream for it.
390 */
391 PDMAUDIOSTREAMCFG CfgHost;
392 int rc = DrvAudioHlpPCMPropsToStreamCfg(&pSink->PCMProps, &CfgHost);
393 AssertRCReturn(rc, rc);
394
395 /* Apply the sink's direction for the configuration to use to
396 * create the stream. */
397 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
398 {
399 CfgHost.DestSource.Source = pCfg->DestSource.Source;
400 CfgHost.enmDir = PDMAUDIODIR_IN;
401 }
402 else
403 {
404 CfgHost.DestSource.Dest = pCfg->DestSource.Dest;
405 CfgHost.enmDir = PDMAUDIODIR_OUT;
406 }
407
408 RTStrPrintf(CfgHost.szName, sizeof(CfgHost.szName), "%s", pCfg->szName);
409
410 PPDMAUDIOSTREAM pStream;
411 rc = pConn->pfnStreamCreate(pConn, &CfgHost, pCfg, &pStream);
412 if (RT_SUCCESS(rc))
413 {
414 /* Save the audio stream pointer to this mixing stream. */
415 pMixStream->pStream = pStream;
416
417 /* Increase the stream's reference count to let others know
418 * we're reyling on it to be around now. */
419 pConn->pfnStreamAddRef(pConn, pStream);
420 }
421
422 if (RT_SUCCESS(rc))
423 {
424 pMixStream->fFlags = fFlags;
425 pMixStream->pConn = pConn;
426
427 if (ppStream)
428 *ppStream = pMixStream;
429 }
430 else if (pMixStream)
431 {
432 if (pMixStream->pszName)
433 {
434 RTStrFree(pMixStream->pszName);
435 pMixStream->pszName = NULL;
436 }
437
438 RTMemFree(pMixStream);
439 pMixStream = NULL;
440 }
441
442 return rc;
443}
444
445static PDMAUDIOSTREAMCMD audioMixerSinkToStreamCmd(AUDMIXSINKCMD enmCmd)
446{
447 switch (enmCmd)
448 {
449 case AUDMIXSINKCMD_ENABLE: return PDMAUDIOSTREAMCMD_ENABLE;
450 case AUDMIXSINKCMD_DISABLE: return PDMAUDIOSTREAMCMD_DISABLE;
451 case AUDMIXSINKCMD_PAUSE: return PDMAUDIOSTREAMCMD_PAUSE;
452 case AUDMIXSINKCMD_RESUME: return PDMAUDIOSTREAMCMD_RESUME;
453 default: break;
454 }
455
456 AssertMsgFailed(("Unsupported sink command %ld\n", enmCmd));
457 return PDMAUDIOSTREAMCMD_UNKNOWN;
458}
459
460int AudioMixerSinkCtl(PAUDMIXSINK pSink, AUDMIXSINKCMD enmSinkCmd)
461{
462 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
463
464 PDMAUDIOSTREAMCMD enmCmdStream = audioMixerSinkToStreamCmd(enmSinkCmd);
465 if (enmCmdStream == PDMAUDIOSTREAMCMD_UNKNOWN)
466 return VERR_NOT_SUPPORTED;
467
468 int rc = VINF_SUCCESS;
469
470 PAUDMIXSTREAM pStream;
471 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
472 {
473 int rc2 = AudioMixerStreamCtl(pStream, enmCmdStream, AUDMIXSTRMCTL_FLAG_NONE);
474 if (RT_SUCCESS(rc))
475 rc = rc2;
476 /* Keep going. Flag? */
477 }
478
479 if (enmSinkCmd == AUDMIXSINKCMD_ENABLE)
480 {
481 pSink->fStatus |= AUDMIXSINK_STS_RUNNING;
482 }
483 else if (enmSinkCmd == AUDMIXSINKCMD_DISABLE)
484 {
485 /* Set the sink in a pending disable state first.
486 * The final status (disabled) will be set in the sink's iteration. */
487 pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE;
488 }
489
490 /* Not running anymore? Reset. */
491 if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
492 audioMixerSinkReset(pSink);
493
494 LogFlowFunc(("[%s]: enmCmd=%ld, fStatus=0x%x, rc=%Rrc\n", pSink->pszName, enmSinkCmd, pSink->fStatus, rc));
495 return rc;
496}
497
498void AudioMixerSinkDestroy(PAUDMIXSINK pSink)
499{
500 if (!pSink)
501 return;
502
503 if (pSink->pParent)
504 {
505 /* Save sink pointer, as after audioMixerRemoveSinkInternal() the
506 * pointer will be gone from the stream. */
507 PAUDIOMIXER pMixer = pSink->pParent;
508
509 audioMixerRemoveSinkInternal(pMixer, pSink);
510
511 Assert(pMixer->cSinks);
512 pMixer->cSinks--;
513 }
514
515 audioMixerSinkDestroyInternal(pSink);
516}
517
518static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink)
519{
520 AssertPtrReturnVoid(pSink);
521
522 LogFunc(("%s\n", pSink->pszName));
523
524 PAUDMIXSTREAM pStream, pStreamNext;
525 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
526 {
527 /* Save a pointer to the stream to remove, as pStream
528 * will not be valid anymore after calling audioMixerSinkRemoveStreamInternal(). */
529 PAUDMIXSTREAM pStreamToRemove = pStream;
530
531 audioMixerSinkRemoveStreamInternal(pSink, pStreamToRemove);
532 audioMixerStreamDestroyInternal(pStreamToRemove);
533 }
534
535 if (pSink->pszName)
536 {
537 RTStrFree(pSink->pszName);
538 pSink->pszName = NULL;
539 }
540
541 RTMemFree(pSink);
542 pSink = NULL;
543}
544
545/**
546 * Returns the amount of bytes ready to be read from a sink since the last call
547 * to AudioMixerSinkUpdate().
548 *
549 * @returns Amount of bytes ready to be read from the sink.
550 * @param pSink Sink to return number of available samples for.
551 */
552uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink)
553{
554 AssertPtrReturn(pSink, 0);
555
556 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT, ("Can't read from a non-input sink\n"));
557
558#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
559# error "Implement me!"
560#else
561 Log3Func(("[%s]: cbReadable=%RU32\n", pSink->pszName, pSink->In.cbReadable));
562 return pSink->In.cbReadable;
563#endif
564}
565
566/**
567 * Returns the amount of bytes ready to be written to a sink since the last call
568 * to AudioMixerSinkUpdate().
569 *
570 * @returns Amount of bytes ready to be written to the sink.
571 * @param pSink Sink to return number of available samples for.
572 */
573uint32_t AudioMixerSinkGetWritable(PAUDMIXSINK pSink)
574{
575 AssertPtrReturn(pSink, 0);
576
577 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT, ("Can't write to a non-output sink\n"));
578
579#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
580# error "Implement me!"
581#else
582 Log3Func(("[%s]: cbWritable=%RU32\n", pSink->pszName, pSink->Out.cbWritable));
583 return pSink->Out.cbWritable;
584#endif
585}
586
587/**
588 * Returns the sink's mixing direction.
589 *
590 * @returns Mixing direction.
591 * @param pSink Sink to return direction for.
592 *
593 * @remark
594 */
595AUDMIXSINKDIR AudioMixerSinkGetDir(PAUDMIXSINK pSink)
596{
597 AssertPtrReturn(pSink, AUDMIXSINKDIR_UNKNOWN);
598 return pSink->enmDir;
599}
600
601PAUDMIXSTREAM AudioMixerSinkGetStream(PAUDMIXSINK pSink, uint8_t uIndex)
602{
603 AssertPtrReturn(pSink, NULL);
604 AssertMsgReturn(uIndex < pSink->cStreams,
605 ("Index %RU8 exceeds stream count (%RU8)", uIndex, pSink->cStreams), NULL);
606
607 /* Slow lookup, d'oh. */
608 PAUDMIXSTREAM pStream = RTListGetFirst(&pSink->lstStreams, AUDMIXSTREAM, Node);
609 while (uIndex)
610 {
611 pStream = RTListGetNext(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node);
612 uIndex--;
613 }
614
615 AssertPtr(pStream);
616 return pStream;
617}
618
619AUDMIXSINKSTS AudioMixerSinkGetStatus(PAUDMIXSINK pSink)
620{
621 if (!pSink)
622 return false;
623
624 LogFlowFunc(("[%s]: fStatus=0x%x\n", pSink->pszName, pSink->fStatus));
625
626 /* If the dirty flag is set, there is unprocessed data in the sink. */
627 return pSink->fStatus;
628}
629
630uint8_t AudioMixerSinkGetStreamCount(PAUDMIXSINK pSink)
631{
632 if (!pSink)
633 return 0;
634
635 return pSink->cStreams;
636}
637
638int AudioMixerSinkRead(PAUDMIXSINK pSink, AUDMIXOP enmOp, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
639{
640 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
641 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
642 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
643 /* pcbRead is optional. */
644
645 /** @todo Handle mixing operation enmOp! */
646
647 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_INPUT,
648 ("Can't read from a sink which is not an input sink\n"));
649
650#ifndef VBOX_AUDIO_MIXER_WITH_MIXBUF
651 uint8_t *pvMixBuf = (uint8_t *)RTMemAlloc(cbBuf);
652 if (!pvMixBuf)
653 return VERR_NO_MEMORY;
654#endif
655
656 int rc = VERR_NOT_FOUND;
657 uint32_t cbRead = 0;
658
659 /* Flag indicating whether this sink is in a 'clean' state,
660 * e.g. there is no more data to read from. */
661 bool fClean = true;
662
663 PAUDMIXSTREAM pMixStream;
664 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
665 {
666 if (!(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED))
667 {
668 LogFlowFunc(("%s: Stream '%s' Disabled, skipping ...\n", pMixStream->pszName, pMixStream->pStream->szName));
669 continue;
670 }
671
672 uint32_t cbTotalRead = 0;
673 uint32_t cbToRead = cbBuf;
674
675 while (cbToRead)
676 {
677 uint32_t cbReadStrm;
678 AssertPtr(pMixStream->pConn);
679#ifndef VBOX_AUDIO_MIXER_WITH_MIXBUF
680 rc = pMixStream->pConn->pfnStreamRead(pMixStream->pConn, pMixStream->pStream,
681 (uint8_t *)pvMixBuf + cbTotalRead, cbToRead, &cbReadStrm);
682#endif
683 if ( RT_FAILURE(rc)
684 || !cbReadStrm)
685 break;
686
687 /** @todo Right now we only handle one stream (the last one added in fact). */
688
689 AssertBreakStmt(cbReadStrm <= cbToRead, rc = VERR_BUFFER_OVERFLOW);
690 cbToRead -= cbReadStrm;
691 cbTotalRead += cbReadStrm;
692 }
693
694 if (RT_FAILURE(rc))
695 continue;
696
697 cbRead = RT_MAX(cbRead, cbTotalRead);
698
699 PDMAUDIOSTRMSTS strmSts = pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream);
700 fClean &= !(strmSts & PDMAUDIOSTRMSTS_FLAG_DATA_READABLE);
701 }
702
703 if (RT_SUCCESS(rc))
704 {
705 if (fClean)
706 pSink->fStatus &= ~AUDMIXSINK_STS_DIRTY;
707
708#ifndef VBOX_AUDIO_MIXER_WITH_MIXBUF
709 if (cbRead)
710 memcpy(pvBuf, pvMixBuf, cbRead);
711#endif
712 if (pcbRead)
713 *pcbRead = cbRead;
714 }
715
716#ifndef VBOX_AUDIO_MIXER_WITH_MIXBUF
717 RTMemFree(pvMixBuf);
718#endif
719
720 Log3Func(("[%s]: cbRead=%RU32, fStatus=0x%x, rc=%Rrc\n", pSink->pszName, cbRead, pSink->fStatus, rc));
721 return rc;
722}
723
724static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
725{
726 AssertPtrReturn(pSink, VERR_INVALID_PARAMETER);
727 if ( !pStream
728 || !pStream->pSink) /* Not part of a sink anymore? */
729 {
730 return VERR_NOT_FOUND;
731 }
732
733 AssertMsgReturn(pStream->pSink == pSink, ("Stream '%s' is not part of sink '%s'\n",
734 pStream->pszName, pSink->pszName), VERR_NOT_FOUND);
735
736 LogFlowFunc(("[%s]: (Stream = %s), cStreams=%RU8\n",
737 pSink->pszName, pStream->pStream->szName, pSink->cStreams));
738
739#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
740 /* Unlink mixing buffer. */
741 AudioMixBufUnlink(&pStream->pStream->MixBuf);
742#endif
743
744 /* Remove stream from sink. */
745 RTListNodeRemove(&pStream->Node);
746
747 /* Set sink to NULL so that we know we're not part of any sink anymore. */
748 pStream->pSink = NULL;
749
750 return VINF_SUCCESS;
751}
752
753void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
754{
755 int rc = audioMixerSinkRemoveStreamInternal(pSink, pStream);
756 if (RT_SUCCESS(rc))
757 {
758 Assert(pSink->cStreams);
759 pSink->cStreams--;
760 }
761}
762
763/**
764 * Removes all attached streams from a given sink.
765 *
766 * @param pSink Sink to remove attached streams from.
767 */
768static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink)
769{
770 if (!pSink)
771 return;
772
773 LogFunc(("%s\n", pSink->pszName));
774
775 PAUDMIXSTREAM pStream, pStreamNext;
776 RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
777 audioMixerSinkRemoveStreamInternal(pSink, pStream);
778}
779
780/**
781 * Resets the sink's state.
782 *
783 * @param pSink Sink to reset.
784 */
785static void audioMixerSinkReset(PAUDMIXSINK pSink)
786{
787 if (!pSink)
788 return;
789
790 LogFunc(("%s\n", pSink->pszName));
791
792 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
793 {
794#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
795 AudioMixBufReset(&pSink->MixBuf);
796#else
797 pSink->In.cbReadable = 0;
798#endif
799 }
800 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
801 {
802#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
803 AudioMixBufReset(&pSink->MixBuf);
804#else
805 pSink->Out.cbWritable = 0;
806#endif
807 }
808
809 /* Reset status. */
810 pSink->fStatus = AUDMIXSINK_STS_NONE;
811}
812
813/**
814 * Removes all attached streams from a given sink.
815 *
816 * @param pSink Sink to remove attached streams from.
817 */
818void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
819{
820 if (!pSink)
821 return;
822
823 audioMixerSinkRemoveAllStreamsInternal(pSink);
824
825 pSink->cStreams = 0;
826}
827
828int AudioMixerSinkSetFormat(PAUDMIXSINK pSink, PPDMPCMPROPS pPCMProps)
829{
830 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
831 AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER);
832
833 if (DrvAudioHlpPCMPropsAreEqual(&pSink->PCMProps, pPCMProps)) /* Bail out early if PCM properties are equal. */
834 return VINF_SUCCESS;
835
836 if (pSink->PCMProps.uHz)
837 LogFlowFunc(("[%s]: Old format: %RU8 bit, %RU8 channels, %RU32Hz\n",
838 pSink->pszName, pSink->PCMProps.cBits, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
839
840 memcpy(&pSink->PCMProps, pPCMProps, sizeof(PDMPCMPROPS));
841
842 LogFlowFunc(("[%s]: New format %RU8 bit, %RU8 channels, %RU32Hz\n",
843 pSink->pszName, pSink->PCMProps.cBits, pSink->PCMProps.cChannels, pSink->PCMProps.uHz));
844
845 int rc = VINF_SUCCESS;
846
847#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
848 /* Also update the sink's mixing buffer format. */
849 AudioMixBufDestroy(&pSink->MixBuf);
850 rc = AudioMixBufInit(&pSink->MixBuf, pSink->pszName, pPCMProps, _4K /** @todo Make configurable? */);
851 if (RT_SUCCESS(rc))
852 {
853 PAUDMIXSTREAM pStream;
854 RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
855 {
856 /** @todo Invalidate mix buffers! */
857 }
858 }
859#endif /* VBOX_AUDIO_MIXER_WITH_MIXBUF */
860
861 LogFlowFuncLeaveRC(rc);
862 return rc;
863}
864
865/**
866 * Set the volume of an individual sink.
867 *
868 * @returns IPRT status code.
869 * @param pSink Sink to set volume for.
870 * @param pVol Volume to set.
871 */
872int AudioMixerSinkSetVolume(PAUDMIXSINK pSink, PPDMAUDIOVOLUME pVol)
873{
874 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
875 AssertPtrReturn(pVol, VERR_INVALID_POINTER);
876 AssertPtr(pSink->pParent);
877
878 LogFlowFunc(("[%s]: fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n", pSink->pszName, pVol->fMuted, pVol->uLeft, pVol->uRight));
879
880 pSink->Volume = *pVol;
881
882 return audioMixerSinkUpdateVolume(pSink, &pSink->pParent->VolMaster);
883}
884
885static int audioMixerSinkUpdateInternal(PAUDMIXSINK pSink)
886{
887 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
888
889 int rc = VINF_SUCCESS;
890
891 Log3Func(("[%s] fStatus=0x%x\n", pSink->pszName, pSink->fStatus));
892
893 /* Sink disabled? Take a shortcut. */
894 if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
895 return rc;
896
897 /* Number of detected disabled streams of this sink. */
898 uint8_t cStreamsDisabled = 0;
899
900 /* Update last updated timestamp. */
901 pSink->tsLastUpdatedNS = RTTimeNanoTS();
902
903 PAUDMIXSTREAM pMixStream, pMixStreamNext;
904 RTListForEachSafe(&pSink->lstStreams, pMixStream, pMixStreamNext, AUDMIXSTREAM, Node)
905 {
906 PPDMAUDIOSTREAM pStream = pMixStream->pStream;
907 AssertPtr(pStream);
908
909 PPDMIAUDIOCONNECTOR pConn = pMixStream->pConn;
910 AssertPtr(pConn);
911
912 uint32_t cPlayed = 0;
913 uint32_t cCaptured = 0;
914
915 int rc2 = pConn->pfnStreamIterate(pConn, pStream);
916 if (RT_SUCCESS(rc2))
917 {
918 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
919 {
920 rc = pConn->pfnStreamCapture(pConn, pMixStream->pStream, &cCaptured);
921 if (RT_FAILURE(rc2))
922 {
923 LogFlowFunc(("%s: Failed capturing stream '%s', rc=%Rrc\n", pSink->pszName, pMixStream->pStream->szName, rc2));
924 if (RT_SUCCESS(rc))
925 rc = rc2;
926 continue;
927 }
928
929 if (cCaptured)
930 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
931 }
932 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
933 {
934 rc2 = pConn->pfnStreamPlay(pConn, pMixStream->pStream, NULL /* cPlayed */);
935 if (RT_FAILURE(rc2))
936 {
937 LogFlowFunc(("%s: Failed playing stream '%s', rc=%Rrc\n", pSink->pszName, pMixStream->pStream->szName, rc2));
938 if (RT_SUCCESS(rc))
939 rc = rc2;
940 continue;
941 }
942 }
943 else
944 {
945 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
946 continue;
947 }
948
949 rc2 = pConn->pfnStreamIterate(pConn, pStream);
950 if (RT_FAILURE(rc2))
951 {
952 LogFlowFunc(("%s: Failed re-iterating stream '%s', rc=%Rrc\n", pSink->pszName, pMixStream->pStream->szName, rc2));
953 if (RT_SUCCESS(rc))
954 rc = rc2;
955 continue;
956 }
957
958 PDMAUDIOSTRMSTS strmSts = pConn->pfnStreamGetStatus(pConn, pMixStream->pStream);
959
960 /* Is the stream not enabled and also is not in a pending disable state anymore? */
961 if ( !(strmSts & PDMAUDIOSTRMSTS_FLAG_ENABLED)
962 && !(strmSts & PDMAUDIOSTRMSTS_FLAG_PENDING_DISABLE))
963 {
964 cStreamsDisabled++;
965 }
966
967 if (pSink->enmDir == AUDMIXSINKDIR_INPUT)
968 {
969#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
970# error "Implement me!"
971#else
972 pSink->In.cbReadable = pConn->pfnStreamGetReadable(pConn, pMixStream->pStream);
973#endif
974 }
975 else if (pSink->enmDir == AUDMIXSINKDIR_OUTPUT)
976 {
977#ifdef VBOX_AUDIO_MIXER_WITH_MIXBUF
978# error "Implement me!"
979#else
980 pSink->Out.cbWritable = pConn->pfnStreamGetWritable(pConn, pMixStream->pStream);
981#endif
982 }
983 }
984
985 Log3Func(("\t%s: cPlayed=%RU32, cCaptured=%RU32\n", pMixStream->pStream->szName, cPlayed, cCaptured));
986 }
987
988 /* All streams disabled and the sink is in pending disable mode? */
989 if ( cStreamsDisabled == pSink->cStreams
990 && (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
991 {
992 audioMixerSinkReset(pSink);
993 }
994
995 if (RT_FAILURE(rc))
996 LogFlowFunc(("Failed with rc=%Rrc\n", rc));
997
998 return rc;
999}
1000
1001int AudioMixerSinkUpdate(PAUDMIXSINK pSink)
1002{
1003 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1004
1005 uint64_t tsElapsed = RTTimeNanoTS() - pSink->tsLastUpdatedNS;
1006
1007 /*
1008 * Note: Hz elapsed = cTicksElapsed / cTimerTicks
1009 * Bytes / second = Sample rate (Hz) * Audio channels * Bytes per sample
1010 */
1011// uint32_t cSamples = (int)((pSink->PCMProps.cChannels * cTicksElapsed * pSink->PCMProps.uHz + cTimerTicks) / cTimerTicks / pSink->PCMProps.cChannels);
1012
1013// LogFlowFunc(("[%s]: cTimerTicks=%RU64, cTicksElapsed=%RU64\n", pSink->pszName, cTimerTicks, cTicksElapsed));
1014// LogFlowFunc(("\t%zuHz elapsed, %RU32 samples (%RU32 bytes)\n", cTicksElapsed / cTimerTicks, cSamples, cSamples << 1));
1015
1016 return audioMixerSinkUpdateInternal(pSink);
1017}
1018
1019static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster)
1020{
1021 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1022 AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER);
1023
1024 LogFlowFunc(("Master fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1025 pVolMaster->fMuted, pVolMaster->uLeft, pVolMaster->uRight));
1026 LogFlowFunc(("[%s]: fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1027 pSink->pszName, pSink->Volume.fMuted, pSink->Volume.uLeft, pSink->Volume.uRight));
1028
1029 /** @todo Very crude implementation for now -- needs more work! */
1030
1031 PDMAUDIOVOLUME volSink;
1032 volSink.fMuted = pVolMaster->fMuted || pSink->Volume.fMuted;
1033 volSink.uLeft = (pSink->Volume.uLeft * pVolMaster->uLeft) / UINT8_MAX;
1034 volSink.uRight = (pSink->Volume.uRight * pVolMaster->uRight) / UINT8_MAX;
1035
1036 LogFlowFunc(("\t-> fMuted=%RTbool, lVol=%RU32, rVol=%RU32\n",
1037 volSink.fMuted, volSink.uLeft, volSink.uRight));
1038
1039 bool fOut = pSink->enmDir == AUDMIXSINKDIR_OUTPUT;
1040
1041 /* Propagate new sink volume to all streams in the sink. */
1042 PAUDMIXSTREAM pMixStream;
1043 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1044 {
1045 int rc2 = pMixStream->pConn->pfnStreamSetVolume(pMixStream->pConn, pMixStream->pStream, &volSink);
1046 AssertRC(rc2);
1047 }
1048
1049 return VINF_SUCCESS;
1050}
1051
1052int AudioMixerSinkWrite(PAUDMIXSINK pSink, AUDMIXOP enmOp, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1053{
1054 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
1055 /* pcbWritten is optional. */
1056
1057 if (!pvBuf || !cbBuf)
1058 {
1059 if (pcbWritten)
1060 *pcbWritten = 0;
1061 return VINF_SUCCESS;
1062 }
1063
1064 AssertMsg(pSink->enmDir == AUDMIXSINKDIR_OUTPUT,
1065 ("Can't write to a sink which is not an output sink\n"));
1066
1067 LogFlowFunc(("%s: enmOp=%ld, cbBuf=%RU32\n", pSink->pszName, enmOp, cbBuf));
1068
1069 uint32_t cbProcessed;
1070
1071 PAUDMIXSTREAM pMixStream;
1072 RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
1073 {
1074 if (!(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED))
1075 {
1076 LogFlowFunc(("%s: Stream '%s' Disabled, skipping ...\n", pMixStream->pszName, pMixStream->pStream->szName));
1077 continue;
1078 }
1079
1080 int rc2 = pMixStream->pConn->pfnStreamWrite(pMixStream->pConn, pMixStream->pStream, pvBuf, cbBuf, &cbProcessed);
1081 if (RT_FAILURE(rc2))
1082 LogFlowFunc(("%s: Failed writing to stream '%s': %Rrc\n", pSink->pszName, pMixStream->pStream->szName, rc2));
1083
1084 if (cbProcessed < cbBuf)
1085 {
1086 LogFlowFunc(("%s: Only written %RU32/%RU32 bytes for stream '%s'\n",
1087 pSink->pszName, cbProcessed, cbBuf, pMixStream->pStream->szName));
1088 }
1089 }
1090
1091 if (cbBuf)
1092 {
1093 /* Set dirty bit. */
1094 pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
1095 }
1096
1097 if (pcbWritten)
1098 *pcbWritten = cbBuf; /* Always report back a complete write for now. */
1099
1100 return VINF_SUCCESS;
1101}
1102
1103/*********************************************************************************************************************************
1104 * Mixer Stream implementation.
1105 ********************************************************************************************************************************/
1106
1107int AudioMixerStreamCtl(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd, uint32_t fCtl)
1108{
1109 AssertPtrReturn(pMixStream, VERR_INVALID_POINTER);
1110 /** @todo Validate fCtl. */
1111
1112 LogFlowFunc(("[%s] enmCmd=%ld\n", pMixStream->pszName, enmCmd));
1113
1114 return pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, enmCmd);
1115}
1116
1117static void audioMixerStreamDestroyInternal(PAUDMIXSTREAM pMixStream)
1118{
1119 AssertPtrReturnVoid(pMixStream);
1120
1121 LogFunc(("%s\n", pMixStream->pszName));
1122
1123 if (pMixStream->pConn) /* Stream has a connector interface present? */
1124 {
1125 if (pMixStream->pStream)
1126 {
1127 pMixStream->pConn->pfnStreamRelease(pMixStream->pConn, pMixStream->pStream);
1128 pMixStream->pConn->pfnStreamDestroy(pMixStream->pConn, pMixStream->pStream);
1129
1130 pMixStream->pStream = NULL;
1131 }
1132
1133 pMixStream->pConn = NULL;
1134 }
1135
1136 if (pMixStream->pszName)
1137 {
1138 RTStrFree(pMixStream->pszName);
1139 pMixStream->pszName = NULL;
1140 }
1141
1142 RTMemFree(pMixStream);
1143 pMixStream = NULL;
1144}
1145
1146void AudioMixerStreamDestroy(PAUDMIXSTREAM pMixStream)
1147{
1148 if (!pMixStream)
1149 return;
1150
1151 LogFunc(("%s\n", pMixStream->pszName));
1152
1153 int rc;
1154
1155 if (pMixStream->pSink) /* Is the stream part of a sink? */
1156 {
1157 /* Save sink pointer, as after audioMixerSinkRemoveStreamInternal() the
1158 * pointer will be gone from the stream. */
1159 PAUDMIXSINK pSink = pMixStream->pSink;
1160
1161 rc = audioMixerSinkRemoveStreamInternal(pSink, pMixStream);
1162 if (RT_SUCCESS(rc))
1163 {
1164 Assert(pSink->cStreams);
1165 pSink->cStreams--;
1166 }
1167 }
1168 else
1169 rc = VINF_SUCCESS;
1170
1171 if (RT_SUCCESS(rc))
1172 audioMixerStreamDestroyInternal(pMixStream);
1173
1174 LogFlowFunc(("Returning %Rrc\n", rc));
1175}
1176
1177bool AudioMixerStreamIsActive(PAUDMIXSTREAM pMixStream)
1178{
1179 bool fIsActive =
1180 pMixStream
1181 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_ENABLED);
1182
1183 return fIsActive;
1184}
1185
1186bool AudioMixerStreamIsValid(PAUDMIXSTREAM pMixStream)
1187{
1188 bool fIsValid =
1189 pMixStream
1190 && RT_BOOL(pMixStream->pConn->pfnStreamGetStatus(pMixStream->pConn, pMixStream->pStream) & PDMAUDIOSTRMSTS_FLAG_INITIALIZED);
1191
1192 return fIsValid;
1193}
1194
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