VirtualBox

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

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

vmm/pdmaudioifs.h: More of the same. bugref:9218

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