VirtualBox

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

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

Audio: Moving some of the DrvAudio.h stuff into PDM - VBox/vmm/pdmaudioinline.h. bugref:9890

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

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