VirtualBox

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

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

Logging.

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

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