VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp@ 85531

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

DrvHostPulseAudio.cpp: Don't crash in paStreamOpen if pa_stream_connect_playback/record fails (pa_stream_get_buffer_attr will return NULL if state isn't READY). Untested.

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