VirtualBox

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

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

Audio: Renamed host audio files to the DrvHostAudioXxxx scheme. bugref:9890

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