VirtualBox

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

Last change on this file since 67576 was 67576, checked in by vboxsync, 7 years ago

DrvHostPulseAudio: removed more logging

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