VirtualBox

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

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

Audio: Cleaned up the alsa and pulse audio play methods. Two doxygen fixes. bugref:9890

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