VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp@ 88467

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

DrvHostAudioPulseAudio: Some more cleaning up. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 55.4 KB
Line 
1/* $Id: DrvHostAudioPulseAudio.cpp 88467 2021-04-12 12:06:58Z vboxsync $ */
2/** @file
3 * Host audio driver - Pulse Audio.
4 */
5
6/*
7 * Copyright (C) 2006-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include <VBox/log.h>
24#include <VBox/vmm/pdmaudioifs.h>
25#include <VBox/vmm/pdmaudioinline.h>
26
27#include <stdio.h>
28
29#include <iprt/alloc.h>
30#include <iprt/mem.h>
31#include <iprt/uuid.h>
32#include <iprt/semaphore.h>
33
34#include "DrvHostAudioPulseAudioStubsMangling.h"
35#include "DrvHostAudioPulseAudioStubs.h"
36
37#include <pulse/pulseaudio.h>
38#ifndef PA_STREAM_NOFLAGS
39# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
40#endif
41#ifndef PA_CONTEXT_NOFLAGS
42# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
43#endif
44
45#include "VBoxDD.h"
46
47
48/*********************************************************************************************************************************
49* Defines *
50*********************************************************************************************************************************/
51/** Max number of errors reported by drvHostAudioPaError per instance.
52 * @todo Make this configurable thru driver config. */
53#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 64
54
55/** No flags specified. */
56#define PULSEAUDIOENUMCBFLAGS_NONE 0
57/** (Release) log found devices. */
58#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0)
59
60
61/*********************************************************************************************************************************
62* Structures *
63*********************************************************************************************************************************/
64/**
65 * Callback context for the server init context state changed callback.
66 */
67typedef struct PULSEAUDIOSTATECHGCTX
68{
69 /** The event semaphore. */
70 RTSEMEVENT hEvtInit;
71 /** The returned context state. */
72 volatile pa_context_state_t enmCtxState;
73} PULSEAUDIOSTATECHGCTX;
74/** Pointer to a server init context state changed callback context. */
75typedef PULSEAUDIOSTATECHGCTX *PPULSEAUDIOSTATECHGCTX;
76
77/**
78 * Host Pulse audio driver instance data.
79 * @implements PDMIAUDIOCONNECTOR
80 */
81typedef struct DRVHOSTPULSEAUDIO
82{
83 /** Pointer to the driver instance structure. */
84 PPDMDRVINS pDrvIns;
85 /** Pointer to PulseAudio's threaded main loop. */
86 pa_threaded_mainloop *pMainLoop;
87 /**
88 * Pointer to our PulseAudio context.
89 * Note: We use a pMainLoop in a separate thread (pContext).
90 * So either use callback functions or protect these functions
91 * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
92 */
93 pa_context *pContext;
94 /** Shutdown indicator. */
95 volatile bool fAbortLoop;
96 /** Enumeration operation successful? */
97 volatile bool fEnumOpSuccess;
98 /** Pointer to host audio interface. */
99 PDMIHOSTAUDIO IHostAudio;
100 /** Error count for not flooding the release log.
101 * Specify UINT32_MAX for unlimited logging. */
102 uint32_t cLogErrors;
103 /** The stream (base) name; needed for distinguishing
104 * streams in the PulseAudio mixer controls if multiple
105 * VMs are running at the same time. */
106 char szStreamName[64];
107
108 /** Don't want to put this on the stack... */
109 PULSEAUDIOSTATECHGCTX InitStateChgCtx;
110} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO;
111
112typedef struct PULSEAUDIOSTREAM
113{
114 /** The stream's acquired configuration. */
115 PPDMAUDIOSTREAMCFG pCfg;
116 /** Pointer to driver instance. */
117 PDRVHOSTPULSEAUDIO pDrv;
118 /** Pointer to opaque PulseAudio stream. */
119 pa_stream *pStream;
120 /** Pulse sample format and attribute specification. */
121 pa_sample_spec SampleSpec;
122 /** Pulse playback and buffer metrics. */
123 pa_buffer_attr BufAttr;
124 int fOpSuccess;
125 /** Pointer to Pulse sample peeking buffer. */
126 const uint8_t *pu8PeekBuf;
127 /** Current size (in bytes) of peeking data in
128 * buffer. */
129 size_t cbPeekBuf;
130 /** Our offset (in bytes) in peeking buffer. */
131 size_t offPeekBuf;
132 pa_operation *pDrainOp;
133 /** Number of occurred audio data underflows. */
134 uint32_t cUnderflows;
135 /** Current latency (in us). */
136 uint64_t curLatencyUs;
137#ifdef LOG_ENABLED
138 /** Start time stamp (in us) of stream playback / recording. */
139 pa_usec_t tsStartUs;
140 /** Time stamp (in us) when last read from / written to the stream. */
141 pa_usec_t tsLastReadWrittenUs;
142#endif
143} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM;
144
145/**
146 * Callback context for server enumeration callbacks.
147 */
148typedef struct PULSEAUDIOENUMCBCTX
149{
150 /** Pointer to host backend driver. */
151 PDRVHOSTPULSEAUDIO pDrv;
152 /** Enumeration flags. */
153 uint32_t fFlags;
154 /** Number of found input devices. */
155 uint8_t cDevIn;
156 /** Number of found output devices. */
157 uint8_t cDevOut;
158 /** Name of default sink being used. Must be free'd using RTStrFree(). */
159 char *pszDefaultSink;
160 /** Name of default source being used. Must be free'd using RTStrFree(). */
161 char *pszDefaultSource;
162} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX;
163
164
165
166/*
167 * Glue to make the code work systems with PulseAudio < 0.9.11.
168 */
169#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
170DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState)
171{
172 return enmState == PA_CONTEXT_CONNECTING
173 || enmState == PA_CONTEXT_AUTHORIZING
174 || enmState == PA_CONTEXT_SETTING_NAME
175 || enmState == PA_CONTEXT_READY;
176}
177#endif
178
179#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
180DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState)
181{
182 return enmState == PA_STREAM_CREATING
183 || enmState == PA_STREAM_READY;
184}
185#endif
186
187
188
189/** @todo Implement va handling. */
190static int drvHostAudioPaError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
191{
192 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
193 AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
194
195 if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS
196 && LogRelIs2Enabled())
197 {
198 pThis->cLogErrors++;
199 int rc2 = pa_context_errno(pThis->pContext);
200 LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
201 }
202
203 /** @todo Implement some PulseAudio -> IPRT mapping here. */
204 return VERR_GENERAL_FAILURE;
205}
206
207
208/**
209 * Signal the main loop to abort. Just signalling isn't sufficient as the
210 * mainloop might not have been entered yet.
211 */
212static void drvHostAudioPaSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
213{
214 if (!pThis)
215 return;
216
217 pThis->fAbortLoop = true;
218 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
219}
220
221
222
223/**
224 * Pulse audio callback for context status changes, init variant.
225 */
226static void drvHostAudioPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser)
227{
228 AssertPtrReturnVoid(pCtx);
229
230 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
231 AssertPtrReturnVoid(pThis);
232
233 switch (pa_context_get_state(pCtx))
234 {
235 case PA_CONTEXT_READY:
236 case PA_CONTEXT_TERMINATED:
237 case PA_CONTEXT_FAILED:
238 drvHostAudioPaSignalWaiter(pThis);
239 break;
240
241 default:
242 break;
243 }
244}
245
246
247/**
248 * Callback used with pa_stream_cork() in a number of places.
249 */
250static void drvHostAudioPaStreamSuccessCallback(pa_stream *pStream, int fSuccess, void *pvUser)
251{
252 AssertPtrReturnVoid(pStream);
253 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
254 AssertPtrReturnVoid(pStrm);
255
256 pStrm->fOpSuccess = fSuccess;
257
258 if (fSuccess)
259 drvHostAudioPaSignalWaiter(pStrm->pDrv);
260 else
261 drvHostAudioPaError(pStrm->pDrv, "Failed to finish stream operation");
262}
263
264
265/**
266 * Synchronously wait until an operation completed.
267 */
268static int drvHostAudioPaWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout)
269{
270 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
271 AssertPtrReturn(pOP, VERR_INVALID_POINTER);
272
273 int rc = VINF_SUCCESS;
274
275 uint64_t u64StartMs = RTTimeMilliTS();
276 while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING)
277 {
278 if (!pThis->fAbortLoop)
279 {
280 AssertPtr(pThis->pMainLoop);
281 pa_threaded_mainloop_wait(pThis->pMainLoop);
282 if ( !pThis->pContext
283 || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
284 {
285 LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
286 break;
287 }
288 }
289 pThis->fAbortLoop = false;
290
291 uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
292 if (u64ElapsedMs >= cMsTimeout)
293 {
294 rc = VERR_TIMEOUT;
295 break;
296 }
297 }
298
299 pa_operation_unref(pOP);
300
301 return rc;
302}
303
304
305static int drvHostAudioPaWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
306{
307 return drvHostAudioPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC);
308}
309
310
311static void drvHostAudioPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
312{
313 if (eol > 0)
314 return;
315
316 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
317 AssertPtrReturnVoid(pCbCtx);
318 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
319 AssertPtrReturnVoid(pThis);
320 if (eol < 0)
321 {
322 pThis->fEnumOpSuccess = false;
323 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
324 return;
325 }
326
327 AssertPtrReturnVoid(pCtx);
328 AssertPtrReturnVoid(pInfo);
329
330 LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name));
331
332 /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */
333 pCbCtx->cDevOut++;
334
335 pThis->fEnumOpSuccess = true;
336 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
337}
338
339
340static void drvHostAudioPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
341{
342 if (eol > 0)
343 return;
344
345 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
346 AssertPtrReturnVoid(pCbCtx);
347 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
348 AssertPtrReturnVoid(pThis);
349 if (eol < 0)
350 {
351 pThis->fEnumOpSuccess = false;
352 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
353 return;
354 }
355
356 AssertPtrReturnVoid(pCtx);
357 AssertPtrReturnVoid(pInfo);
358
359 LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name));
360
361 /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */
362 pCbCtx->cDevIn++;
363
364 pThis->fEnumOpSuccess = true;
365 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
366}
367
368
369static void drvHostAudioPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
370{
371 AssertPtrReturnVoid(pCtx);
372 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
373 AssertPtrReturnVoid(pCbCtx);
374 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
375 AssertPtrReturnVoid(pThis);
376
377 if (!pInfo)
378 {
379 pThis->fEnumOpSuccess = false;
380 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
381 return;
382 }
383
384 if (pInfo->default_sink_name)
385 {
386 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
387 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
388 }
389
390 if (pInfo->default_sink_name)
391 {
392 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
393 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
394 }
395
396 pThis->fEnumOpSuccess = true;
397 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
398}
399
400
401static int drvHostAudioPaEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
402{
403 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
404 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
405
406 PDMAUDIOBACKENDCFG Cfg;
407 RT_ZERO(Cfg);
408
409 RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio");
410
411 Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM);
412 Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM);
413 Cfg.cMaxStreamsOut = UINT32_MAX;
414 Cfg.cMaxStreamsIn = UINT32_MAX;
415
416 PULSEAUDIOENUMCBCTX CbCtx;
417 RT_ZERO(CbCtx);
418
419 CbCtx.pDrv = pThis;
420 CbCtx.fFlags = fEnum;
421
422 bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
423
424 pa_threaded_mainloop_lock(pThis->pMainLoop);
425
426 pThis->fEnumOpSuccess = false;
427
428 LogRel(("PulseAudio: Retrieving server information ...\n"));
429
430 /* Check if server information is available and bail out early if it isn't. */
431 pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHostAudioPaEnumServerCallback, &CbCtx);
432 if (!paOpServerInfo)
433 {
434 pa_threaded_mainloop_unlock(pThis->pMainLoop);
435
436 LogRel(("PulseAudio: Server information not available, skipping enumeration\n"));
437 return VINF_SUCCESS;
438 }
439
440 int rc = drvHostAudioPaWaitFor(pThis, paOpServerInfo);
441 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
442 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
443 if (RT_SUCCESS(rc))
444 {
445 if (CbCtx.pszDefaultSink)
446 {
447 if (fLog)
448 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
449
450 pThis->fEnumOpSuccess = false;
451 rc = drvHostAudioPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
452 drvHostAudioPaEnumSinkCallback, &CbCtx));
453 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
454 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
455 if ( RT_FAILURE(rc)
456 && fLog)
457 {
458 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink));
459 }
460 }
461 else if (fLog)
462 LogRel2(("PulseAudio: No default output sink found\n"));
463
464 if (RT_SUCCESS(rc))
465 {
466 if (CbCtx.pszDefaultSource)
467 {
468 if (fLog)
469 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
470
471 pThis->fEnumOpSuccess = false;
472 rc = drvHostAudioPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
473 drvHostAudioPaEnumSourceCallback, &CbCtx));
474 if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess)
475 && fLog)
476 {
477 LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource));
478 }
479 }
480 else if (fLog)
481 LogRel2(("PulseAudio: No default input source found\n"));
482 }
483
484 if (RT_SUCCESS(rc))
485 {
486 if (fLog)
487 {
488 LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut));
489 LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn));
490 }
491
492 if (pCfg)
493 memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
494 }
495
496 if (CbCtx.pszDefaultSink)
497 {
498 RTStrFree(CbCtx.pszDefaultSink);
499 CbCtx.pszDefaultSink = NULL;
500 }
501
502 if (CbCtx.pszDefaultSource)
503 {
504 RTStrFree(CbCtx.pszDefaultSource);
505 CbCtx.pszDefaultSource = NULL;
506 }
507 }
508 else if (fLog)
509 LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n"));
510
511 pa_threaded_mainloop_unlock(pThis->pMainLoop);
512
513 LogFlowFuncLeaveRC(rc);
514 return rc;
515}
516
517
518/**
519 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
520 */
521static DECLCALLBACK(int) drvHostAudioPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
522{
523 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
524 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
525
526 return drvHostAudioPaEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */);
527}
528
529
530/**
531 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
532 */
533static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
534{
535 RT_NOREF(enmDir);
536 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
537
538 return PDMAUDIOBACKENDSTS_RUNNING;
539}
540
541
542static pa_sample_format_t drvHostAudioPaPropsToPulse(PPDMAUDIOPCMPROPS pProps)
543{
544 switch (PDMAudioPropsSampleSize(pProps))
545 {
546 case 1:
547 if (!pProps->fSigned)
548 return PA_SAMPLE_U8;
549 break;
550
551 case 2:
552 if (pProps->fSigned)
553 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
554 break;
555
556#ifdef PA_SAMPLE_S32LE
557 case 4:
558 if (pProps->fSigned)
559 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE;
560 break;
561#endif
562 }
563
564 AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), pProps->fSigned ? "S" : "U"));
565 return PA_SAMPLE_INVALID;
566}
567
568
569static int drvHostAudioPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t pulsefmt, uint8_t cChannels, uint32_t uHz)
570{
571 AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
572 AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
573
574 switch (pulsefmt)
575 {
576 case PA_SAMPLE_U8:
577 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
578 break;
579
580 case PA_SAMPLE_S16LE:
581 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
582 break;
583
584 case PA_SAMPLE_S16BE:
585 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
586 break;
587
588#ifdef PA_SAMPLE_S32LE
589 case PA_SAMPLE_S32LE:
590 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
591 break;
592#endif
593
594#ifdef PA_SAMPLE_S32BE
595 case PA_SAMPLE_S32BE:
596 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
597 break;
598#endif
599
600 default:
601 AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", pulsefmt));
602 return VERR_NOT_SUPPORTED;
603 }
604
605 return VINF_SUCCESS;
606}
607
608
609/**
610 * Stream status changed.
611 */
612static void drvHostAudioPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser)
613{
614 AssertPtrReturnVoid(pStream);
615
616 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
617 AssertPtrReturnVoid(pThis);
618
619 switch (pa_stream_get_state(pStream))
620 {
621 case PA_STREAM_READY:
622 case PA_STREAM_FAILED:
623 case PA_STREAM_TERMINATED:
624 drvHostAudioPaSignalWaiter(pThis);
625 break;
626
627 default:
628 break;
629 }
630}
631
632#ifdef DEBUG
633
634static void drvHostAudioPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext)
635{
636 RT_NOREF(cbLen, pvContext);
637
638 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
639 AssertPtrReturnVoid(pStrm);
640
641 pa_usec_t usec = 0;
642 int neg = 0;
643 pa_stream_get_latency(pStream, &usec, &neg);
644
645 Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000));
646}
647
648
649static void drvHostAudioPaStreamUnderflowDebugCallback(pa_stream *pStream, void *pvContext)
650{
651 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
652 AssertPtrReturnVoid(pStrm);
653
654 pStrm->cUnderflows++;
655
656 LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
657
658 if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
659 && pStrm->curLatencyUs < 2000000 /* 2s */)
660 {
661 pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2;
662
663 LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */));
664
665 pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
666 pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
667
668 pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
669
670 pStrm->cUnderflows = 0;
671 }
672
673 pa_usec_t curLatencyUs = 0;
674 pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */);
675
676 LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */));
677
678# ifdef LOG_ENABLED
679 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
680 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
681
682 pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
683 pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec);
684 pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
685
686 Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n",
687 curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64,
688 curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels));
689# endif
690}
691
692
693static void drvHostAudioPaStreamOverflowDebugCallback(pa_stream *pStream, void *pvContext)
694{
695 RT_NOREF(pStream, pvContext);
696
697 Log2Func(("Warning: Hit overflow\n"));
698}
699
700#endif /* DEBUG */
701
702
703static int drvHostAudioPaStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName)
704{
705 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
706 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
707 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
708
709 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
710 pa_stream *pStream = NULL;
711
712 pa_threaded_mainloop_lock(pThis->pMainLoop);
713
714 do /* goto avoidance non-loop */
715 {
716 pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec;
717
718 LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n",
719 pszName, pSampleSpec->rate, pSampleSpec->channels,
720 pa_sample_format_to_string(pSampleSpec->format)));
721
722 if (!pa_sample_spec_valid(pSampleSpec))
723 {
724 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName));
725 break;
726 }
727
728 pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr;
729
730 /** @todo r=andy Use pa_stream_new_with_proplist instead. */
731 if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */)))
732 {
733 LogRel(("PulseAudio: Could not create stream '%s'\n", pszName));
734 rc = VERR_NO_MEMORY;
735 break;
736 }
737
738#ifdef DEBUG
739 pa_stream_set_write_callback( pStream, drvHostAudioPaStreamReqWriteDebugCallback, pStreamPA);
740 pa_stream_set_underflow_callback( pStream, drvHostAudioPaStreamUnderflowDebugCallback, pStreamPA);
741 if (!fIn) /* Only for output streams. */
742 pa_stream_set_overflow_callback(pStream, drvHostAudioPaStreamOverflowDebugCallback, pStreamPA);
743#endif
744 pa_stream_set_state_callback( pStream, drvHostAudioPaStreamStateChangedCallback, pThis);
745
746 uint32_t flags = PA_STREAM_NOFLAGS;
747#if PA_API_VERSION >= 12
748 /* XXX */
749 flags |= PA_STREAM_ADJUST_LATENCY;
750#endif
751 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
752 flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
753
754 /* No input/output right away after the stream was started. */
755 flags |= PA_STREAM_START_CORKED;
756
757 if (fIn)
758 {
759 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
760 pBufAttr->maxlength, pBufAttr->fragsize));
761
762 if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
763 {
764 LogRel(("PulseAudio: Could not connect input stream '%s': %s\n",
765 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
766 break;
767 }
768 }
769 else
770 {
771 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
772 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
773
774 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
775 /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
776 {
777 LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n",
778 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
779 break;
780 }
781 }
782
783 /* Wait until the stream is ready. */
784 pa_stream_state_t enmStreamState;
785 for (;;)
786 {
787 enmStreamState = pa_stream_get_state(pStream);
788 if ( enmStreamState == PA_STREAM_READY
789 || !PA_STREAM_IS_GOOD(enmStreamState))
790 break;
791
792 if (!pThis->fAbortLoop)
793 pa_threaded_mainloop_wait(pThis->pMainLoop);
794 pThis->fAbortLoop = false;
795 }
796 if (!PA_STREAM_IS_GOOD(enmStreamState))
797 {
798 LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, enmStreamState));
799 break;
800 }
801
802#ifdef LOG_ENABLED
803 pStreamPA->tsStartUs = pa_rtclock_now();
804#endif
805 const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
806 AssertPtrBreak(pBufAttrObtained);
807 memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
808
809 LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n",
810 fIn ? "capture" : "playback",
811 pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf));
812
813 pStreamPA->pStream = pStream;
814
815 pa_threaded_mainloop_unlock(pThis->pMainLoop);
816 LogFlowFuncLeaveRC(VINF_SUCCESS);
817 return VINF_SUCCESS;
818
819 } while (0);
820
821 /* We failed. */
822 if (pStream)
823 pa_stream_disconnect(pStream);
824
825 pa_threaded_mainloop_unlock(pThis->pMainLoop);
826
827 if (pStream)
828 pa_stream_unref(pStream);
829 LogFlowFuncLeaveRC(rc);
830 return rc;
831}
832
833
834static int drvHostAudioPaCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
835 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
836{
837 pStreamPA->pDrainOp = NULL;
838
839 pStreamPA->SampleSpec.format = drvHostAudioPaPropsToPulse(&pCfgReq->Props);
840 pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
841 pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
842
843 pStreamPA->curLatencyUs = PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize) * RT_US_1MS;
844
845 const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec);
846
847 LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency));
848
849 pStreamPA->BufAttr.tlength = cbLatency;
850 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
851 pStreamPA->BufAttr.prebuf = cbLatency;
852 pStreamPA->BufAttr.minreq = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
853
854 LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
855 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
856
857 Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT);
858
859 char szName[256];
860 RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", PDMAudioPlaybackDstGetName(pCfgReq->u.enmDst), pThis->szStreamName);
861
862 /* Note that the struct BufAttr is updated to the obtained values after this call! */
863 int rc = drvHostAudioPaStreamOpen(pThis, pStreamPA, false /* fIn */, szName);
864 if (RT_FAILURE(rc))
865 return rc;
866
867 rc = drvHostAudioPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
868 pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
869 if (RT_FAILURE(rc))
870 {
871 LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format));
872 return rc;
873 }
874
875 LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
876 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
877
878 pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq);
879 pCfgAcq->Backend.cFramesBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength);
880 pCfgAcq->Backend.cFramesPreBuffering = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf);
881
882 pStreamPA->pDrv = pThis;
883
884 return rc;
885}
886
887
888static int drvHostAudioPaCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
889 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
890{
891 pStreamPA->SampleSpec.format = drvHostAudioPaPropsToPulse(&pCfgReq->Props);
892 pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
893 pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
894
895 pStreamPA->BufAttr.fragsize = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
896 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
897
898 Assert(pCfgReq->enmDir == PDMAUDIODIR_IN);
899
900 char szName[256];
901 RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", PDMAudioRecSrcGetName(pCfgReq->u.enmSrc), pThis->szStreamName);
902
903 /* Note: Other members of BufAttr are ignored for record streams. */
904 int rc = drvHostAudioPaStreamOpen(pThis, pStreamPA, true /* fIn */, szName);
905 if (RT_FAILURE(rc))
906 return rc;
907
908 rc = drvHostAudioPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
909 pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
910 if (RT_FAILURE(rc))
911 {
912 LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format));
913 return rc;
914 }
915
916 pStreamPA->pDrv = pThis;
917 pStreamPA->pu8PeekBuf = NULL;
918
919 pCfgAcq->Backend.cFramesPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize);
920 pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */
921 ? PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.maxlength)
922 : pCfgAcq->Backend.cFramesPeriod * 2 /* whatever */;
923 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
924
925 LogFlowFuncLeaveRC(rc);
926 return rc;
927}
928
929
930/**
931 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
932 */
933static DECLCALLBACK(int) drvHostAudioPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
934 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
935{
936 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
937 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
938 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
939 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
940 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
941
942
943 int rc;
944 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
945 rc = drvHostAudioPaCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq);
946 else if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
947 rc = drvHostAudioPaCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq);
948 else
949 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
950
951 if (RT_SUCCESS(rc))
952 {
953 pStreamPA->pCfg = PDMAudioStrmCfgDup(pCfgAcq);
954 if (!pStreamPA->pCfg)
955 rc = VERR_NO_MEMORY;
956 }
957
958 return rc;
959}
960
961
962/**
963 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
964 */
965static DECLCALLBACK(int) drvHostAudioPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
966{
967 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
968 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
969 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
970
971 if (pStreamPA->pStream)
972 {
973 pa_threaded_mainloop_lock(pThis->pMainLoop);
974
975 /* Make sure to cancel a pending draining operation, if any. */
976 if (pStreamPA->pDrainOp)
977 {
978 pa_operation_cancel(pStreamPA->pDrainOp);
979 pStreamPA->pDrainOp = NULL;
980 }
981
982 pa_stream_disconnect(pStreamPA->pStream);
983 pa_stream_unref(pStreamPA->pStream);
984
985 pStreamPA->pStream = NULL;
986
987 pa_threaded_mainloop_unlock(pThis->pMainLoop);
988 }
989
990 if (pStreamPA->pCfg)
991 {
992 PDMAudioStrmCfgFree(pStreamPA->pCfg);
993 pStreamPA->pCfg = NULL;
994 }
995
996 return VINF_SUCCESS;
997}
998
999
1000/**
1001 * Pulse audio pa_stream_drain() completion callback.
1002 */
1003static void drvHostAudioPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1004{
1005 AssertPtrReturnVoid(pStream);
1006 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
1007 AssertPtrReturnVoid(pStreamPA);
1008
1009 pStreamPA->fOpSuccess = fSuccess;
1010 if (fSuccess)
1011 pa_operation_unref(pa_stream_cork(pStream, 1, drvHostAudioPaStreamSuccessCallback, pvUser));
1012 else
1013 drvHostAudioPaError(pStreamPA->pDrv, "Failed to drain stream");
1014
1015 if (pStreamPA->pDrainOp)
1016 {
1017 pa_operation_unref(pStreamPA->pDrainOp);
1018 pStreamPA->pDrainOp = NULL;
1019 }
1020}
1021
1022
1023static int drvHostAudioPaStreamControlOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1024{
1025 int rc = VINF_SUCCESS;
1026
1027 switch (enmStreamCmd)
1028 {
1029 case PDMAUDIOSTREAMCMD_ENABLE:
1030 case PDMAUDIOSTREAMCMD_RESUME:
1031 {
1032 pa_threaded_mainloop_lock(pThis->pMainLoop);
1033
1034 if ( pStreamPA->pDrainOp
1035 && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
1036 {
1037 pa_operation_cancel(pStreamPA->pDrainOp);
1038 pa_operation_unref(pStreamPA->pDrainOp);
1039
1040 pStreamPA->pDrainOp = NULL;
1041 }
1042 else
1043 {
1044 /* Uncork (resume) stream. */
1045 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1046 }
1047
1048 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1049 break;
1050 }
1051
1052 case PDMAUDIOSTREAMCMD_DISABLE:
1053 case PDMAUDIOSTREAMCMD_PAUSE:
1054 {
1055 /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
1056 * Note that we must return immediately from here! */
1057 pa_threaded_mainloop_lock(pThis->pMainLoop);
1058 if (!pStreamPA->pDrainOp)
1059 {
1060 rc = drvHostAudioPaWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1061 if (RT_SUCCESS(rc))
1062 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHostAudioPaStreamDrainCompletionCallback, pStreamPA);
1063 }
1064 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1065 break;
1066 }
1067
1068 default:
1069 rc = VERR_NOT_SUPPORTED;
1070 break;
1071 }
1072
1073 LogFlowFuncLeaveRC(rc);
1074 return rc;
1075}
1076
1077
1078static int drvHostAudioPaStreamControlIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1079{
1080 int rc = VINF_SUCCESS;
1081
1082 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1083
1084 switch (enmStreamCmd)
1085 {
1086 case PDMAUDIOSTREAMCMD_ENABLE:
1087 case PDMAUDIOSTREAMCMD_RESUME:
1088 {
1089 pa_threaded_mainloop_lock(pThis->pMainLoop);
1090 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1091 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1092 break;
1093 }
1094
1095 case PDMAUDIOSTREAMCMD_DISABLE:
1096 case PDMAUDIOSTREAMCMD_PAUSE:
1097 {
1098 pa_threaded_mainloop_lock(pThis->pMainLoop);
1099 if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
1100 {
1101 pa_stream_drop(pStreamPA->pStream);
1102 pStreamPA->pu8PeekBuf = NULL;
1103 }
1104
1105 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1106 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1107 break;
1108 }
1109
1110 default:
1111 rc = VERR_NOT_SUPPORTED;
1112 break;
1113 }
1114
1115 return rc;
1116}
1117
1118
1119/**
1120 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1121 */
1122static DECLCALLBACK(int) drvHostAudioPaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
1123 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1124{
1125 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1126 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1127 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1128
1129 if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
1130 return VINF_SUCCESS;
1131
1132 int rc;
1133 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1134 rc = drvHostAudioPaStreamControlIn (pThis, pStreamPA, enmStreamCmd);
1135 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1136 rc = drvHostAudioPaStreamControlOut(pThis, pStreamPA, enmStreamCmd);
1137 else
1138 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1139
1140 return rc;
1141}
1142
1143
1144static uint32_t drvHostAudioPaStreamGetAvailable(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1145{
1146 pa_threaded_mainloop_lock(pThis->pMainLoop);
1147
1148 uint32_t cbAvail = 0;
1149
1150 if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
1151 {
1152 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1153 {
1154 cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
1155 Log3Func(("cbReadable=%RU32\n", cbAvail));
1156 }
1157 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1158 {
1159 size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
1160
1161 Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
1162 cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1163
1164 /* Don't report more writable than the PA server can handle. */
1165 if (cbWritable > pStreamPA->BufAttr.maxlength)
1166 cbWritable = pStreamPA->BufAttr.maxlength;
1167
1168 cbAvail = (uint32_t)cbWritable;
1169 }
1170 else
1171 AssertFailed();
1172 }
1173
1174 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1175
1176 return cbAvail;
1177}
1178
1179
1180/**
1181 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1182 */
1183static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1184{
1185 return drvHostAudioPaStreamGetAvailable(RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio),
1186 (PPULSEAUDIOSTREAM)pStream);
1187}
1188
1189
1190/**
1191 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1192 */
1193static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1194{
1195 return drvHostAudioPaStreamGetAvailable(RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio),
1196 (PPULSEAUDIOSTREAM)pStream);
1197}
1198
1199
1200/**
1201 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
1202 */
1203static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostAudioPaHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1204{
1205 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1206 RT_NOREF(pStream);
1207
1208 /* Check PulseAudio's general status. */
1209 PDMAUDIOSTREAMSTS fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_NONE;
1210 if ( pThis->pContext
1211 && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
1212 fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
1213
1214 return fStrmSts;
1215}
1216
1217
1218/**
1219 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1220 */
1221static DECLCALLBACK(int) drvHostAudioPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1222 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1223{
1224 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1225 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1226 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1227 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1228 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1229 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1230
1231 pa_threaded_mainloop_lock(pThis->pMainLoop);
1232
1233#ifdef LOG_ENABLED
1234 const pa_usec_t tsNowUs = pa_rtclock_now();
1235 const pa_usec_t tsDeltaPlayedUs = tsNowUs - pStreamPA->tsLastReadWrittenUs;
1236 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1237 Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / RT_US_1MS));
1238#endif
1239
1240 int rc;
1241 size_t const cbWriteable = pa_stream_writable_size(pStreamPA->pStream);
1242 if (cbWriteable != (size_t)-1)
1243 {
1244 size_t cbLeft = RT_MIN(cbWriteable, cbBuf);
1245 Assert(cbLeft > 0 /* At this point we better have *something* to write (DrvAudio checked before calling). */);
1246 if (pa_stream_write(pStreamPA->pStream, pvBuf, cbLeft, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0)
1247 {
1248 *pcbWritten = (uint32_t)cbLeft;
1249 rc = VINF_SUCCESS;
1250 }
1251 else
1252 rc = drvHostAudioPaError(pStreamPA->pDrv, "Failed to write to output stream");
1253 }
1254 else
1255 rc = drvHostAudioPaError(pStreamPA->pDrv, "Failed to determine output data size");
1256
1257 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1258 return rc;
1259}
1260
1261
1262/**
1263 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1264 */
1265static DECLCALLBACK(int) drvHostAudioPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1266 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1267{
1268 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1269 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1270 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1271 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1272 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1273 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1274
1275 /* We should only call pa_stream_readable_size() once and trust the first value. */
1276 pa_threaded_mainloop_lock(pThis->pMainLoop);
1277 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
1278 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1279
1280 if (cbAvail == (size_t)-1)
1281 return drvHostAudioPaError(pStreamPA->pDrv, "Failed to determine input data size");
1282
1283 /* If the buffer was not dropped last call, add what remains. */
1284 if (pStreamPA->pu8PeekBuf)
1285 {
1286 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
1287 cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
1288 }
1289
1290 Log3Func(("cbAvail=%zu\n", cbAvail));
1291
1292 if (!cbAvail) /* No data? Bail out. */
1293 {
1294 *pcbRead = 0;
1295 return VINF_SUCCESS;
1296 }
1297
1298 int rc = VINF_SUCCESS;
1299
1300 size_t cbToRead = RT_MIN(cbAvail, cbBuf);
1301
1302 Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
1303 cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
1304
1305 uint32_t cbReadTotal = 0;
1306
1307 while (cbToRead)
1308 {
1309 /* If there is no data, do another peek. */
1310 if (!pStreamPA->pu8PeekBuf)
1311 {
1312 pa_threaded_mainloop_lock(pThis->pMainLoop);
1313 pa_stream_peek(pStreamPA->pStream,
1314 (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
1315 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1316
1317 pStreamPA->offPeekBuf = 0;
1318
1319 /* No data anymore?
1320 * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
1321 * we need to drop the stream lateron. */
1322 if ( !pStreamPA->pu8PeekBuf
1323 && !pStreamPA->cbPeekBuf)
1324 {
1325 break;
1326 }
1327 }
1328
1329 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
1330 size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
1331
1332 Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
1333 cbToRead, cbToWrite,
1334 pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
1335
1336 if ( cbToWrite
1337 /* Only copy data if it's not a data hole (see above). */
1338 && pStreamPA->pu8PeekBuf
1339 && pStreamPA->cbPeekBuf)
1340 {
1341 memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
1342
1343 Assert(cbToRead >= cbToWrite);
1344 cbToRead -= cbToWrite;
1345 cbReadTotal += cbToWrite;
1346
1347 pStreamPA->offPeekBuf += cbToWrite;
1348 Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
1349 }
1350
1351 if (/* Nothing to write anymore? Drop the buffer. */
1352 !cbToWrite
1353 /* Was there a hole in the peeking buffer? Drop it. */
1354 || !pStreamPA->pu8PeekBuf
1355 /* If the buffer is done, drop it. */
1356 || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
1357 {
1358 pa_threaded_mainloop_lock(pThis->pMainLoop);
1359 pa_stream_drop(pStreamPA->pStream);
1360 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1361
1362 pStreamPA->pu8PeekBuf = NULL;
1363 }
1364 }
1365
1366 if (RT_SUCCESS(rc))
1367 *pcbRead = cbReadTotal;
1368 return rc;
1369}
1370
1371
1372/*********************************************************************************************************************************
1373* PDMIBASE *
1374*********************************************************************************************************************************/
1375
1376/**
1377 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1378 */
1379static DECLCALLBACK(void *) drvHostAudioPaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1380{
1381 AssertPtrReturn(pInterface, NULL);
1382 AssertPtrReturn(pszIID, NULL);
1383
1384 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1385 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1386 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1387 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1388
1389 return NULL;
1390}
1391
1392
1393/*********************************************************************************************************************************
1394* PDMDRVREG *
1395*********************************************************************************************************************************/
1396
1397/**
1398 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
1399 */
1400static DECLCALLBACK(void) drvHostAudioPaPowerOff(PPDMDRVINS pDrvIns)
1401{
1402 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1403 LogFlowFuncEnter();
1404
1405 if (pThis->pMainLoop)
1406 pa_threaded_mainloop_stop(pThis->pMainLoop);
1407
1408 if (pThis->pContext)
1409 {
1410 pa_context_disconnect(pThis->pContext);
1411 pa_context_unref(pThis->pContext);
1412 pThis->pContext = NULL;
1413 }
1414
1415 if (pThis->pMainLoop)
1416 {
1417 pa_threaded_mainloop_free(pThis->pMainLoop);
1418 pThis->pMainLoop = NULL;
1419 }
1420
1421 LogFlowFuncLeave();
1422}
1423
1424
1425/**
1426 * Destructs a PulseAudio Audio driver instance.
1427 *
1428 * @copydoc FNPDMDRVDESTRUCT
1429 */
1430static DECLCALLBACK(void) drvHostAudioPaDestruct(PPDMDRVINS pDrvIns)
1431{
1432 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1433 LogFlowFuncEnter();
1434 drvHostAudioPaPowerOff(pDrvIns);
1435 LogFlowFuncLeave();
1436}
1437
1438
1439/**
1440 * Pulse audio callback for context status changes, init variant.
1441 *
1442 * Signalls our event semaphore so we can do a timed wait from
1443 * drvHostAudioPaConstruct().
1444 */
1445static void drvHostAudioPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser)
1446{
1447 AssertPtrReturnVoid(pCtx);
1448 PPULSEAUDIOSTATECHGCTX pStateChgCtx = (PPULSEAUDIOSTATECHGCTX)pvUser;
1449 pa_context_state_t enmCtxState = pa_context_get_state(pCtx);
1450 switch (enmCtxState)
1451 {
1452 case PA_CONTEXT_READY:
1453 case PA_CONTEXT_TERMINATED:
1454 case PA_CONTEXT_FAILED:
1455 AssertPtrReturnVoid(pStateChgCtx);
1456 pStateChgCtx->enmCtxState = enmCtxState;
1457 RTSemEventSignal(pStateChgCtx->hEvtInit);
1458 break;
1459
1460 default:
1461 break;
1462 }
1463}
1464
1465
1466/**
1467 * Constructs a PulseAudio Audio driver instance.
1468 *
1469 * @copydoc FNPDMDRVCONSTRUCT
1470 */
1471static DECLCALLBACK(int) drvHostAudioPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1472{
1473 RT_NOREF(pCfg, fFlags);
1474 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1475 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1476
1477 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1478 LogRel(("Audio: Initializing PulseAudio driver\n"));
1479
1480 /*
1481 * Initialize instance data.
1482 */
1483 pThis->pDrvIns = pDrvIns;
1484 /* IBase */
1485 pDrvIns->IBase.pfnQueryInterface = drvHostAudioPaQueryInterface;
1486 /* IHostAudio */
1487 pThis->IHostAudio.pfnGetConfig = drvHostAudioPaHA_GetConfig;
1488 pThis->IHostAudio.pfnGetDevices = NULL;
1489 pThis->IHostAudio.pfnGetStatus = drvHostAudioPaHA_GetStatus;
1490 pThis->IHostAudio.pfnStreamCreate = drvHostAudioPaHA_StreamCreate;
1491 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioPaHA_StreamDestroy;
1492 pThis->IHostAudio.pfnStreamControl = drvHostAudioPaHA_StreamControl;
1493 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioPaHA_StreamGetReadable;
1494 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioPaHA_StreamGetWritable;
1495 pThis->IHostAudio.pfnStreamGetPending = NULL;
1496 pThis->IHostAudio.pfnStreamGetStatus = drvHostAudioPaHA_StreamGetStatus;
1497 pThis->IHostAudio.pfnStreamPlay = drvHostAudioPaHA_StreamPlay;
1498 pThis->IHostAudio.pfnStreamCapture = drvHostAudioPaHA_StreamCapture;
1499
1500 /*
1501 * Read configuration.
1502 */
1503 int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
1504 AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
1505
1506 /*
1507 * Load the pulse audio library.
1508 */
1509 int rc = audioLoadPulseLib();
1510 if (RT_SUCCESS(rc))
1511 LogRel(("PulseAudio: Using version %s\n", pa_get_library_version()));
1512 else
1513 {
1514 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
1515 return rc;
1516 }
1517
1518 /*
1519 * Set up the basic pulse audio bits (remember the destructore is always called).
1520 */
1521 //pThis->fAbortLoop = false;
1522 pThis->pMainLoop = pa_threaded_mainloop_new();
1523 if (!pThis->pMainLoop)
1524 {
1525 LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1526 return VERR_NO_MEMORY;
1527 }
1528
1529 pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox");
1530 if (!pThis->pContext)
1531 {
1532 LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1533 return VERR_NO_MEMORY;
1534 }
1535
1536 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
1537 {
1538 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1539 return VERR_AUDIO_BACKEND_INIT_FAILED;
1540 }
1541
1542 /*
1543 * Connect to the pulse audio server.
1544 *
1545 * We install an init state callback so we can do a timed wait in case
1546 * connecting to the pulseaudio server should take too long.
1547 */
1548 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
1549 pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED;
1550 rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit);
1551 AssertLogRelRCReturn(rc, rc);
1552
1553 pa_threaded_mainloop_lock(pThis->pMainLoop);
1554 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx);
1555 if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL))
1556 {
1557 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1558
1559 rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */
1560 if (RT_SUCCESS(rc))
1561 {
1562 if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY)
1563 {
1564 /* Install the main state changed callback to know if something happens to our acquired context. */
1565 pa_threaded_mainloop_lock(pThis->pMainLoop);
1566 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChanged, pThis /* pvUserData */);
1567 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1568 }
1569 else
1570 {
1571 LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc));
1572 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
1573 }
1574 }
1575 else
1576 {
1577 LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc));
1578 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
1579 }
1580 }
1581 else
1582 {
1583 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1584 LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1585 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */
1586 }
1587
1588 RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit);
1589 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
1590
1591 return rc;
1592}
1593
1594
1595/**
1596 * Pulse audio driver registration record.
1597 */
1598const PDMDRVREG g_DrvHostPulseAudio =
1599{
1600 /* u32Version */
1601 PDM_DRVREG_VERSION,
1602 /* szName */
1603 "PulseAudio",
1604 /* szRCMod */
1605 "",
1606 /* szR0Mod */
1607 "",
1608 /* pszDescription */
1609 "Pulse Audio host driver",
1610 /* fFlags */
1611 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1612 /* fClass. */
1613 PDM_DRVREG_CLASS_AUDIO,
1614 /* cMaxInstances */
1615 ~0U,
1616 /* cbInstance */
1617 sizeof(DRVHOSTPULSEAUDIO),
1618 /* pfnConstruct */
1619 drvHostAudioPaConstruct,
1620 /* pfnDestruct */
1621 drvHostAudioPaDestruct,
1622 /* pfnRelocate */
1623 NULL,
1624 /* pfnIOCtl */
1625 NULL,
1626 /* pfnPowerOn */
1627 NULL,
1628 /* pfnReset */
1629 NULL,
1630 /* pfnSuspend */
1631 NULL,
1632 /* pfnResume */
1633 NULL,
1634 /* pfnAttach */
1635 NULL,
1636 /* pfnDetach */
1637 NULL,
1638 /* pfnPowerOff */
1639 drvHostAudioPaPowerOff,
1640 /* pfnSoftReset */
1641 NULL,
1642 /* u32EndVersion */
1643 PDM_DRVREG_VERSION
1644};
1645
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