VirtualBox

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

Last change on this file since 85499 was 85499, checked in by vboxsync, 5 years ago

Devices/Audio/DrvHostPulseAudio: Cache the context state in paContextCbStateChangedInit() and don't retrieve it again when RTSemEventWait() retrurns successfully. Older pulseaudio versions (seen on v10.0) might hang in pa_threaded_mainloop_lock() if the server is not accessible preventing the VM to start up.

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