VirtualBox

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

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

DrvHostAudioPulseAudio: Build fix (draining revamp). bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 78.3 KB
Line 
1/* $Id: DrvHostAudioPulseAudio.cpp 88992 2021-05-12 00:50:41Z vboxsync $ */
2/** @file
3 * Host audio driver - Pulse Audio.
4 */
5
6/*
7 * Copyright (C) 2006-2021 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#include <VBox/vmm/pdmaudiohostenuminline.h>
27
28#include <stdio.h>
29
30#include <iprt/alloc.h>
31#include <iprt/mem.h>
32#include <iprt/uuid.h>
33#include <iprt/semaphore.h>
34
35#include "DrvHostAudioPulseAudioStubsMangling.h"
36#include "DrvHostAudioPulseAudioStubs.h"
37
38#include <pulse/pulseaudio.h>
39#ifndef PA_STREAM_NOFLAGS
40# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
41#endif
42#ifndef PA_CONTEXT_NOFLAGS
43# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
44#endif
45
46#ifdef VBOX_AUDIO_VKAT
47# include "VBoxDDVKAT.h"
48#else
49# include "VBoxDD.h"
50#endif
51
52
53/*********************************************************************************************************************************
54* Defines *
55*********************************************************************************************************************************/
56/** Max number of errors reported by drvHostAudioPaError per instance.
57 * @todo Make this configurable thru driver config. */
58#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 99
59
60
61/** @name PULSEAUDIOENUMCBFLAGS_XXX
62 * @{ */
63/** No flags specified. */
64#define PULSEAUDIOENUMCBFLAGS_NONE 0
65/** (Release) log found devices. */
66#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0)
67/** Only do default devices. */
68#define PULSEAUDIOENUMCBFLAGS_DEFAULT_ONLY RT_BIT(1)
69/** @} */
70
71
72/*********************************************************************************************************************************
73* Structures *
74*********************************************************************************************************************************/
75/** Pointer to the instance data for a pulse audio host audio driver. */
76typedef struct DRVHOSTPULSEAUDIO *PDRVHOSTPULSEAUDIO;
77
78
79/**
80 * Callback context for the server init context state changed callback.
81 */
82typedef struct PULSEAUDIOSTATECHGCTX
83{
84 /** The event semaphore. */
85 RTSEMEVENT hEvtInit;
86 /** The returned context state. */
87 pa_context_state_t volatile enmCtxState;
88} PULSEAUDIOSTATECHGCTX;
89/** Pointer to a server init context state changed callback context. */
90typedef PULSEAUDIOSTATECHGCTX *PPULSEAUDIOSTATECHGCTX;
91
92
93/**
94 * Enumeration callback context used by the pfnGetConfig code.
95 */
96typedef struct PULSEAUDIOENUMCBCTX
97{
98 /** Pointer to PulseAudio's threaded main loop. */
99 pa_threaded_mainloop *pMainLoop;
100 /** Enumeration flags, PULSEAUDIOENUMCBFLAGS_XXX. */
101 uint32_t fFlags;
102 /** VBox status code for the operation.
103 * The caller sets this to VERR_AUDIO_ENUMERATION_FAILED, the callback never
104 * uses that status code. */
105 int32_t rcEnum;
106 /** Name of default sink being used. Must be free'd using RTStrFree(). */
107 char *pszDefaultSink;
108 /** Name of default source being used. Must be free'd using RTStrFree(). */
109 char *pszDefaultSource;
110 /** The device enumeration to fill, NULL if pfnGetConfig context. */
111 PPDMAUDIOHOSTENUM pDeviceEnum;
112} PULSEAUDIOENUMCBCTX;
113/** Pointer to an enumeration callback context. */
114typedef PULSEAUDIOENUMCBCTX *PPULSEAUDIOENUMCBCTX;
115
116
117/**
118 * Pulse audio device enumeration entry.
119 */
120typedef struct PULSEAUDIODEVENTRY
121{
122 /** The part we share with others. */
123 PDMAUDIOHOSTDEV Core;
124 /** The pulse audio name.
125 * @note Kind of must use fixed size field here as that allows
126 * PDMAudioHostDevDup() and PDMAudioHostEnumCopy() to work. */
127 RT_FLEXIBLE_ARRAY_EXTENSION
128 char szPulseName[RT_FLEXIBLE_ARRAY];
129} PULSEAUDIODEVENTRY;
130/** Pointer to a pulse audio device enumeration entry. */
131typedef PULSEAUDIODEVENTRY *PPULSEAUDIODEVENTRY;
132
133
134/**
135 * Pulse audio stream data.
136 */
137typedef struct PULSEAUDIOSTREAM
138{
139 /** Common part. */
140 PDMAUDIOBACKENDSTREAM Core;
141 /** The stream's acquired configuration. */
142 PDMAUDIOSTREAMCFG Cfg;
143 /** Pointer to driver instance. */
144 PDRVHOSTPULSEAUDIO pDrv;
145 /** Pointer to opaque PulseAudio stream. */
146 pa_stream *pStream;
147 /** Pulse sample format and attribute specification. */
148 pa_sample_spec SampleSpec;
149 /** Pulse playback and buffer metrics. */
150 pa_buffer_attr BufAttr;
151 /** Input: Pointer to Pulse sample peek buffer. */
152 const uint8_t *pbPeekBuf;
153 /** Input: Current size (in bytes) of peeked data in buffer. */
154 size_t cbPeekBuf;
155 /** Input: Our offset (in bytes) in peek data buffer. */
156 size_t offPeekBuf;
157 /** Output: Asynchronous drain operation. This is used as an indicator of
158 * whether we're currently draining the stream (will be cleaned up before
159 * resume/re-enable). */
160 pa_operation *pDrainOp;
161 /** Asynchronous cork/uncork operation.
162 * (This solely for cancelling before destroying the stream, so the callback
163 * won't do any after-freed accesses.) */
164 pa_operation *pCorkOp;
165 /** Asynchronous trigger operation.
166 * (This solely for cancelling before destroying the stream, so the callback
167 * won't do any after-freed accesses.) */
168 pa_operation *pTriggerOp;
169 /** Output: Current latency (in microsecs). */
170 uint64_t cUsLatency;
171#ifdef LOG_ENABLED
172 /** Creation timestamp (in microsecs) of stream playback / recording. */
173 pa_usec_t tsStartUs;
174 /** Timestamp (in microsecs) when last read from / written to the stream. */
175 pa_usec_t tsLastReadWrittenUs;
176#endif
177#ifdef DEBUG
178 /** Number of occurred audio data underflows. */
179 uint32_t cUnderflows;
180#endif
181} PULSEAUDIOSTREAM;
182/** Pointer to pulse audio stream data. */
183typedef PULSEAUDIOSTREAM *PPULSEAUDIOSTREAM;
184
185
186/**
187 * Pulse audio host audio driver instance data.
188 * @implements PDMIAUDIOCONNECTOR
189 */
190typedef struct DRVHOSTPULSEAUDIO
191{
192 /** Pointer to the driver instance structure. */
193 PPDMDRVINS pDrvIns;
194 /** Pointer to PulseAudio's threaded main loop. */
195 pa_threaded_mainloop *pMainLoop;
196 /**
197 * Pointer to our PulseAudio context.
198 * @note We use a pMainLoop in a separate thread (pContext).
199 * So either use callback functions or protect these functions
200 * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
201 */
202 pa_context *pContext;
203 /** Shutdown indicator. */
204 volatile bool fAbortLoop;
205 /** Error count for not flooding the release log.
206 * Specify UINT32_MAX for unlimited logging. */
207 uint32_t cLogErrors;
208 /** The stream (base) name; needed for distinguishing
209 * streams in the PulseAudio mixer controls if multiple
210 * VMs are running at the same time. */
211 char szStreamName[64];
212 /** Don't want to put this on the stack... */
213 PULSEAUDIOSTATECHGCTX InitStateChgCtx;
214 /** Pointer to host audio interface. */
215 PDMIHOSTAUDIO IHostAudio;
216} DRVHOSTPULSEAUDIO;
217
218
219
220/*
221 * Glue to make the code work systems with PulseAudio < 0.9.11.
222 */
223#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
224DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState)
225{
226 return enmState == PA_CONTEXT_CONNECTING
227 || enmState == PA_CONTEXT_AUTHORIZING
228 || enmState == PA_CONTEXT_SETTING_NAME
229 || enmState == PA_CONTEXT_READY;
230}
231#endif
232
233#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
234DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState)
235{
236 return enmState == PA_STREAM_CREATING
237 || enmState == PA_STREAM_READY;
238}
239#endif
240
241
242/**
243 * Converts a pulse audio error to a VBox status.
244 *
245 * @returns VBox status code.
246 * @param rcPa The error code to convert.
247 */
248static int drvHostAudioPaErrorToVBox(int rcPa)
249{
250 /** @todo Implement some PulseAudio -> VBox mapping here. */
251 RT_NOREF(rcPa);
252 return VERR_GENERAL_FAILURE;
253}
254
255
256/**
257 * Logs a pulse audio (from context) and converts it to VBox status.
258 *
259 * @returns VBox status code.
260 * @param pThis Our instance data.
261 * @param pszFormat The format string for the release log (no newline) .
262 * @param ... Format string arguments.
263 */
264static int drvHostAudioPaError(PDRVHOSTPULSEAUDIO pThis, const char *pszFormat, ...)
265{
266 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
267 AssertPtr(pszFormat);
268
269 int const rcPa = pa_context_errno(pThis->pContext);
270 int const rcVBox = drvHostAudioPaErrorToVBox(rcPa);
271
272 if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS
273 && LogRelIs2Enabled())
274 {
275 va_list va;
276 va_start(va, pszFormat);
277 LogRel(("PulseAudio: %N: %s (%d, %Rrc)\n", pszFormat, &va, pa_strerror(rcPa), rcPa, rcVBox));
278 va_end(va);
279
280 if (++pThis->cLogErrors == VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
281 LogRel(("PulseAudio: muting errors (max %u)\n", VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS));
282 }
283
284 return rcVBox;
285}
286
287
288/**
289 * Signal the main loop to abort. Just signalling isn't sufficient as the
290 * mainloop might not have been entered yet.
291 */
292static void drvHostAudioPaSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
293{
294 if (pThis)
295 {
296 pThis->fAbortLoop = true;
297 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
298 }
299}
300
301
302/**
303 * Wrapper around pa_threaded_mainloop_wait().
304 */
305static void drvHostAudioPaMainloopWait(PDRVHOSTPULSEAUDIO pThis)
306{
307 /** @todo r=bird: explain this logic. */
308 if (!pThis->fAbortLoop)
309 pa_threaded_mainloop_wait(pThis->pMainLoop);
310 pThis->fAbortLoop = false;
311}
312
313
314/**
315 * Pulse audio callback for context status changes, init variant.
316 */
317static void drvHostAudioPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser)
318{
319 AssertPtrReturnVoid(pCtx);
320
321 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
322 AssertPtrReturnVoid(pThis);
323
324 switch (pa_context_get_state(pCtx))
325 {
326 case PA_CONTEXT_READY:
327 case PA_CONTEXT_TERMINATED:
328 case PA_CONTEXT_FAILED:
329 drvHostAudioPaSignalWaiter(pThis);
330 break;
331
332 default:
333 break;
334 }
335}
336
337
338/**
339 * Synchronously wait until an operation completed.
340 *
341 * This will consume the pOperation reference.
342 */
343static int drvHostAudioPaWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOperation, RTMSINTERVAL cMsTimeout)
344{
345 AssertPtrReturn(pOperation, VERR_INVALID_POINTER);
346
347 uint64_t const msStart = RTTimeMilliTS();
348 pa_operation_state_t enmOpState;
349 while ((enmOpState = pa_operation_get_state(pOperation)) == PA_OPERATION_RUNNING)
350 {
351 if (!pThis->fAbortLoop) /** @todo r=bird: I do _not_ get the logic behind this fAbortLoop mechanism, it looks more
352 * than a little mixed up and too much generalized see drvHostAudioPaSignalWaiter. */
353 {
354 AssertPtr(pThis->pMainLoop);
355 pa_threaded_mainloop_wait(pThis->pMainLoop);
356 if ( !pThis->pContext
357 || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
358 {
359 pa_operation_cancel(pOperation);
360 pa_operation_unref(pOperation);
361 LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
362 return VERR_INVALID_STATE;
363 }
364 }
365 pThis->fAbortLoop = false;
366
367 /*
368 * Note! This timeout business is a bit bogus as pa_threaded_mainloop_wait is indefinite.
369 */
370 if (RTTimeMilliTS() - msStart >= cMsTimeout)
371 {
372 enmOpState = pa_operation_get_state(pOperation);
373 if (enmOpState != PA_OPERATION_RUNNING)
374 break;
375 pa_operation_cancel(pOperation);
376 pa_operation_unref(pOperation);
377 return VERR_TIMEOUT;
378 }
379 }
380
381 pa_operation_unref(pOperation);
382 if (enmOpState == PA_OPERATION_DONE)
383 return VINF_SUCCESS;
384 return VERR_CANCELLED;
385}
386
387
388static int drvHostAudioPaWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
389{
390 return drvHostAudioPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC);
391}
392
393
394
395/*********************************************************************************************************************************
396* PDMIHOSTAUDIO *
397*********************************************************************************************************************************/
398
399/**
400 * Worker for drvHostAudioPaEnumSourceCallback() and
401 * drvHostAudioPaEnumSinkCallback() that adds an entry to the enumeration
402 * result.
403 */
404static void drvHostAudioPaEnumAddDevice(PPULSEAUDIOENUMCBCTX pCbCtx, PDMAUDIODIR enmDir, const char *pszName,
405 const char *pszDesc, uint8_t cChannelsInput, uint8_t cChannelsOutput,
406 const char *pszDefaultName)
407{
408 size_t const cchName = strlen(pszName);
409 PPULSEAUDIODEVENTRY pDev = (PPULSEAUDIODEVENTRY)PDMAudioHostDevAlloc(RT_UOFFSETOF(PULSEAUDIODEVENTRY, szPulseName)
410 + RT_ALIGN_Z(cchName + 1, 16));
411 if (pDev != NULL)
412 {
413 memcpy(pDev->szPulseName, pszName, cchName);
414 pDev->szPulseName[cchName] = '\0';
415
416 pDev->Core.enmUsage = enmDir;
417 pDev->Core.enmType = RTStrIStr(pszDesc, "built-in") != NULL
418 ? PDMAUDIODEVICETYPE_BUILTIN : PDMAUDIODEVICETYPE_UNKNOWN;
419 pDev->Core.fFlags = RTStrCmp(pszName, pszDefaultName) == 0
420 ? PDMAUDIOHOSTDEV_F_DEFAULT : PDMAUDIOHOSTDEV_F_NONE;
421 pDev->Core.cMaxInputChannels = cChannelsInput;
422 pDev->Core.cMaxOutputChannels = cChannelsOutput;
423 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName),
424 pszDesc && *pszDesc ? pszDesc : pszName);
425
426 PDMAudioHostEnumAppend(pCbCtx->pDeviceEnum, &pDev->Core);
427 }
428 else
429 pCbCtx->rcEnum = VERR_NO_MEMORY;
430}
431
432
433/**
434 * Enumeration callback - source info.
435 *
436 * @param pCtx The context (DRVHOSTPULSEAUDIO::pContext).
437 * @param pInfo The info. NULL when @a eol is not zero.
438 * @param eol Error-or-last indicator or something like that:
439 * - 0: Normal call with info.
440 * - 1: End of list, no info.
441 * - -1: Error callback, no info.
442 * @param pvUserData Pointer to our PULSEAUDIOENUMCBCTX structure.
443 */
444static void drvHostAudioPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
445{
446 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
447 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
448 AssertPtrReturnVoid(pCbCtx);
449 Assert((pInfo == NULL) == (eol != 0));
450 RT_NOREF(pCtx);
451
452 if (eol == 0 && pInfo != NULL)
453 {
454 LogRel2(("Pulse Audio: Source #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
455 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
456 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
457 drvHostAudioPaEnumAddDevice(pCbCtx, PDMAUDIODIR_IN, pInfo->name, pInfo->description,
458 pInfo->sample_spec.channels, 0 /*cChannelsOutput*/, pCbCtx->pszDefaultSource);
459 }
460 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
461 pCbCtx->rcEnum = VINF_SUCCESS;
462
463 /* Wake up the calling thread when done: */
464 if (eol != 0)
465 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
466}
467
468
469/**
470 * Enumeration callback - sink info.
471 *
472 * @param pCtx The context (DRVHOSTPULSEAUDIO::pContext).
473 * @param pInfo The info. NULL when @a eol is not zero.
474 * @param eol Error-or-last indicator or something like that:
475 * - 0: Normal call with info.
476 * - 1: End of list, no info.
477 * - -1: Error callback, no info.
478 * @param pvUserData Pointer to our PULSEAUDIOENUMCBCTX structure.
479 */
480static void drvHostAudioPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
481{
482 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
483 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
484 AssertPtrReturnVoid(pCbCtx);
485 Assert((pInfo == NULL) == (eol != 0));
486 RT_NOREF(pCtx);
487
488 if (eol == 0 && pInfo != NULL)
489 {
490 LogRel2(("Pulse Audio: Sink #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
491 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
492 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
493 drvHostAudioPaEnumAddDevice(pCbCtx, PDMAUDIODIR_OUT, pInfo->name, pInfo->description,
494 0 /*cChannelsInput*/, pInfo->sample_spec.channels, pCbCtx->pszDefaultSink);
495 }
496 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
497 pCbCtx->rcEnum = VINF_SUCCESS;
498
499 /* Wake up the calling thread when done: */
500 if (eol != 0)
501 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
502}
503
504
505/**
506 * Enumeration callback - service info.
507 *
508 * Copy down the default names.
509 */
510static void drvHostAudioPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
511{
512 LogFlowFunc(("pCtx=%p pInfo=%p pvUserData=%p\n", pCtx, pInfo, pvUserData));
513 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
514 AssertPtrReturnVoid(pCbCtx);
515 RT_NOREF(pCtx);
516
517 if (pInfo)
518 {
519 LogRel2(("PulseAudio: Server info: user=%s host=%s ver=%s name=%s defsink=%s defsrc=%s spec: %d %uHz %uch\n",
520 pInfo->user_name, pInfo->host_name, pInfo->server_version, pInfo->server_name,
521 pInfo->default_sink_name, pInfo->default_source_name,
522 pInfo->sample_spec.format, pInfo->sample_spec.rate, pInfo->sample_spec.channels));
523
524 Assert(!pCbCtx->pszDefaultSink);
525 Assert(!pCbCtx->pszDefaultSource);
526 Assert(pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED);
527 pCbCtx->rcEnum = VINF_SUCCESS;
528
529 if (pInfo->default_sink_name)
530 {
531 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
532 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
533 AssertStmt(pCbCtx->pszDefaultSink, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
534 }
535
536 if (pInfo->default_source_name)
537 {
538 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
539 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
540 AssertStmt(pCbCtx->pszDefaultSource, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
541 }
542 }
543 else
544 pCbCtx->rcEnum = VERR_INVALID_POINTER;
545
546 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
547}
548
549
550/**
551 * @note Called with the PA main loop locked.
552 */
553static int drvHostAudioPaEnumerate(PDRVHOSTPULSEAUDIO pThis, uint32_t fEnum, PPDMAUDIOHOSTENUM pDeviceEnum)
554{
555 PULSEAUDIOENUMCBCTX CbCtx = { pThis->pMainLoop, fEnum, VERR_AUDIO_ENUMERATION_FAILED, NULL, NULL, pDeviceEnum };
556 bool const fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
557 bool const fOnlyDefault = (fEnum & PULSEAUDIOENUMCBFLAGS_DEFAULT_ONLY);
558 int rc;
559
560 /*
561 * Check if server information is available and bail out early if it isn't.
562 * This should give us a default (playback) sink and (recording) source.
563 */
564 LogRel(("PulseAudio: Retrieving server information ...\n"));
565 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
566 pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHostAudioPaEnumServerCallback, &CbCtx);
567 if (paOpServerInfo)
568 rc = drvHostAudioPaWaitFor(pThis, paOpServerInfo);
569 else
570 {
571 LogRel(("PulseAudio: Server information not available, skipping enumeration.\n"));
572 return VINF_SUCCESS;
573 }
574 if (RT_SUCCESS(rc))
575 rc = CbCtx.rcEnum;
576 if (RT_FAILURE(rc))
577 {
578 if (fLog)
579 LogRel(("PulseAudio: Error enumerating PulseAudio server properties: %Rrc\n", rc));
580 return rc;
581 }
582
583 /*
584 * Get info about the playback sink.
585 */
586 if (fLog && CbCtx.pszDefaultSink)
587 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
588 else if (fLog)
589 LogRel2(("PulseAudio: No default output sink found\n"));
590
591 if (CbCtx.pszDefaultSink || !fOnlyDefault)
592 {
593 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
594 if (!fOnlyDefault)
595 rc = drvHostAudioPaWaitFor(pThis,
596 pa_context_get_sink_info_list(pThis->pContext, drvHostAudioPaEnumSinkCallback, &CbCtx));
597 else
598 rc = drvHostAudioPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
599 drvHostAudioPaEnumSinkCallback, &CbCtx));
600 if (RT_SUCCESS(rc))
601 rc = CbCtx.rcEnum;
602 if (fLog && RT_FAILURE(rc))
603 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s': %Rrc\n",
604 CbCtx.pszDefaultSink, rc));
605 }
606
607 /*
608 * Get info about the recording source.
609 */
610 if (fLog && CbCtx.pszDefaultSource)
611 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
612 else if (fLog)
613 LogRel2(("PulseAudio: No default input source found\n"));
614 if (CbCtx.pszDefaultSource || !fOnlyDefault)
615 {
616 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
617 int rc2;
618 if (!fOnlyDefault)
619 rc2 = drvHostAudioPaWaitFor(pThis, pa_context_get_source_info_list(pThis->pContext,
620 drvHostAudioPaEnumSourceCallback, &CbCtx));
621 else
622 rc2 = drvHostAudioPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
623 drvHostAudioPaEnumSourceCallback, &CbCtx));
624 if (RT_SUCCESS(rc2))
625 rc2 = CbCtx.rcEnum;
626 if (fLog && RT_FAILURE(rc2))
627 LogRel(("PulseAudio: Error enumerating properties for default input source '%s': %Rrc\n",
628 CbCtx.pszDefaultSource, rc));
629 if (RT_SUCCESS(rc))
630 rc = rc2;
631 }
632
633 /* clean up */
634 RTStrFree(CbCtx.pszDefaultSink);
635 RTStrFree(CbCtx.pszDefaultSource);
636
637 LogFlowFuncLeaveRC(rc);
638 return rc;
639}
640
641
642/**
643 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
644 */
645static DECLCALLBACK(int) drvHostAudioPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
646{
647 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
648 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
649
650 /*
651 * The configuration.
652 */
653 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "PulseAudio");
654 pBackendCfg->cbStream = sizeof(PULSEAUDIOSTREAM);
655 pBackendCfg->fFlags = 0;
656 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
657 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
658
659#if 0
660 /*
661 * In case we want to gather info about default devices, we can do this:
662 */
663 PDMAUDIOHOSTENUM DeviceEnum;
664 PDMAudioHostEnumInit(&DeviceEnum);
665 pa_threaded_mainloop_lock(pThis->pMainLoop);
666 int rc = drvHostAudioPaEnumerate(pThis, PULSEAUDIOENUMCBFLAGS_DEFAULT_ONLY | PULSEAUDIOENUMCBFLAGS_LOG, &DeviceEnum);
667 pa_threaded_mainloop_unlock(pThis->pMainLoop);
668 AssertRCReturn(rc, rc);
669 /** @todo do stuff with DeviceEnum. */
670 PDMAudioHostEnumDelete(&DeviceEnum);
671#else
672 RT_NOREF(pThis);
673#endif
674 return VINF_SUCCESS;
675}
676
677
678/**
679 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
680 */
681static DECLCALLBACK(int) drvHostAudioPaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
682{
683 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
684 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
685 PDMAudioHostEnumInit(pDeviceEnum);
686
687 /* Refine it or something (currently only some LogRel2 stuff): */
688 pa_threaded_mainloop_lock(pThis->pMainLoop);
689 int rc = drvHostAudioPaEnumerate(pThis, PULSEAUDIOENUMCBFLAGS_NONE, pDeviceEnum);
690 pa_threaded_mainloop_unlock(pThis->pMainLoop);
691 return rc;
692}
693
694
695/**
696 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
697 */
698static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
699{
700 RT_NOREF(pInterface, enmDir);
701 return PDMAUDIOBACKENDSTS_RUNNING;
702}
703
704
705/**
706 * Stream status changed.
707 */
708static void drvHostAudioPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser)
709{
710 AssertPtrReturnVoid(pStream);
711
712 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
713 AssertPtrReturnVoid(pThis);
714
715 switch (pa_stream_get_state(pStream))
716 {
717 case PA_STREAM_READY:
718 case PA_STREAM_FAILED:
719 case PA_STREAM_TERMINATED:
720 drvHostAudioPaSignalWaiter(pThis);
721 break;
722
723 default:
724 break;
725 }
726}
727
728#ifdef DEBUG
729
730/**
731 * Debug PA callback: Need data to output.
732 */
733static void drvHostAudioPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext)
734{
735 RT_NOREF(cbLen, pvContext);
736 pa_usec_t cUsLatency = 0;
737 int fNegative = 0;
738 int rcPa = pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
739 Log2Func(("Requesting %zu bytes; Latency: %'RU64 us%s\n",
740 cbLen, cUsLatency, rcPa == 0 ? " - pa_stream_get_latency failed!" : ""));
741}
742
743
744/**
745 * Debug PA callback: Underflow. This may happen when draing/corking.
746 */
747static void drvHostAudioPaStreamUnderflowDebugCallback(pa_stream *pStream, void *pvContext)
748{
749 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
750 AssertPtrReturnVoid(pStrm);
751
752 pStrm->cUnderflows++;
753
754 LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
755
756 if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
757 && pStrm->cUsLatency < 2U*RT_US_1SEC)
758 {
759 pStrm->cUsLatency = pStrm->cUsLatency * 3 / 2;
760 LogRel2(("PulseAudio: Increasing output latency to %'RU64 us\n", pStrm->cUsLatency));
761
762 pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->cUsLatency, &pStrm->SampleSpec);
763 pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->cUsLatency, &pStrm->SampleSpec);
764 pa_operation *pOperation = pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
765 if (pOperation)
766 pa_operation_unref(pOperation);
767 else
768 LogRel2(("pa_stream_set_buffer_attr failed!\n"));
769
770 pStrm->cUnderflows = 0;
771 }
772
773 pa_usec_t cUsLatency = 0;
774 int fNegative = 0;
775 pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
776 LogRel2(("PulseAudio: Latency now is %'RU64 us\n", cUsLatency));
777
778# ifdef LOG_ENABLED
779 if (LogIs2Enabled())
780 {
781 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
782 AssertReturnVoid(pTInfo);
783 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
784 AssertReturnVoid(pSpec);
785 Log2Func(("writepos=%'RU64 us, readpost=%'RU64 us, age=%'RU64 us, latency=%'RU64 us (%RU32Hz %RU8ch)\n",
786 pa_bytes_to_usec(pTInfo->write_index, pSpec), pa_bytes_to_usec(pTInfo->read_index, pSpec),
787 pa_rtclock_now() - pStrm->tsStartUs, cUsLatency, pSpec->rate, pSpec->channels));
788 }
789# endif
790}
791
792
793/**
794 * Debug PA callback: Overflow. This may happen when draing/corking.
795 */
796static void drvHostAudioPaStreamOverflowDebugCallback(pa_stream *pStream, void *pvContext)
797{
798 RT_NOREF(pStream, pvContext);
799 Log2Func(("Warning: Hit overflow\n"));
800}
801
802#endif /* DEBUG */
803
804/**
805 * Converts from PDM PCM properties to pulse audio format.
806 *
807 * Worker for the stream creation code.
808 *
809 * @returns PA format.
810 * @retval PA_SAMPLE_INVALID if format not supported.
811 * @param pProps The PDM audio source properties.
812 */
813static pa_sample_format_t drvHostAudioPaPropsToPulse(PCPDMAUDIOPCMPROPS pProps)
814{
815 switch (PDMAudioPropsSampleSize(pProps))
816 {
817 case 1:
818 if (!PDMAudioPropsIsSigned(pProps))
819 return PA_SAMPLE_U8;
820 break;
821
822 case 2:
823 if (PDMAudioPropsIsSigned(pProps))
824 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
825 break;
826
827#ifdef PA_SAMPLE_S32LE
828 case 4:
829 if (PDMAudioPropsIsSigned(pProps))
830 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE;
831 break;
832#endif
833 }
834
835 AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), PDMAudioPropsIsSigned(pProps) ? "S" : "U"));
836 return PA_SAMPLE_INVALID;
837}
838
839
840/**
841 * Converts from pulse audio sample specification to PDM PCM audio properties.
842 *
843 * Worker for the stream creation code.
844 *
845 * @returns VBox status code.
846 * @param pProps The PDM audio source properties.
847 * @param enmPulseFmt The PA format.
848 * @param cChannels The number of channels.
849 * @param uHz The frequency.
850 */
851static int drvHostAudioPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t enmPulseFmt, uint8_t cChannels, uint32_t uHz)
852{
853 AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
854 AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
855
856 switch (enmPulseFmt)
857 {
858 case PA_SAMPLE_U8:
859 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
860 break;
861
862 case PA_SAMPLE_S16LE:
863 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
864 break;
865
866 case PA_SAMPLE_S16BE:
867 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
868 break;
869
870#ifdef PA_SAMPLE_S32LE
871 case PA_SAMPLE_S32LE:
872 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
873 break;
874#endif
875
876#ifdef PA_SAMPLE_S32BE
877 case PA_SAMPLE_S32BE:
878 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
879 break;
880#endif
881
882 default:
883 AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", enmPulseFmt));
884 return VERR_NOT_SUPPORTED;
885 }
886
887 return VINF_SUCCESS;
888}
889
890
891/**
892 * Worker that does the actual creation of an PA stream.
893 *
894 * @returns VBox status code.
895 * @param pThis Our driver instance data.
896 * @param pStreamPA Our stream data.
897 * @param pszName How we name the stream.
898 * @param pCfgAcq The requested stream properties, the Props member is
899 * updated upon successful return.
900 *
901 * @note Caller owns the mainloop lock.
902 */
903static int drvHostAudioPaStreamCreateLocked(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
904 const char *pszName, PPDMAUDIOSTREAMCFG pCfgAcq)
905{
906 /*
907 * Create the stream.
908 */
909 pa_stream *pStream = pa_stream_new(pThis->pContext, pszName, &pStreamPA->SampleSpec, NULL /* pa_channel_map */);
910 if (!pStream)
911 {
912 LogRel(("PulseAudio: Failed to create stream '%s': %s (%d)\n",
913 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
914 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
915 }
916
917 /*
918 * Set the state callback, and in debug builds a few more...
919 */
920#ifdef DEBUG
921 pa_stream_set_write_callback( pStream, drvHostAudioPaStreamReqWriteDebugCallback, pStreamPA);
922 pa_stream_set_underflow_callback( pStream, drvHostAudioPaStreamUnderflowDebugCallback, pStreamPA);
923 if (pCfgAcq->enmDir == PDMAUDIODIR_OUT)
924 pa_stream_set_overflow_callback(pStream, drvHostAudioPaStreamOverflowDebugCallback, pStreamPA);
925#endif
926 pa_stream_set_state_callback( pStream, drvHostAudioPaStreamStateChangedCallback, pThis);
927
928 /*
929 * Connect the stream.
930 */
931 int rc;
932 unsigned const fFlags = PA_STREAM_START_CORKED /* Require explicit starting (uncorking). */
933 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
934 | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE
935#if PA_API_VERSION >= 12
936 | PA_STREAM_ADJUST_LATENCY
937#endif
938 ;
939 if (pCfgAcq->enmDir == PDMAUDIODIR_IN)
940 {
941 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
942 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.fragsize));
943 rc = pa_stream_connect_record(pStream, NULL /*dev*/, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags);
944 }
945 else
946 {
947 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
948 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.minreq));
949 rc = pa_stream_connect_playback(pStream, NULL /*dev*/, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags,
950 NULL /*volume*/, NULL /*sync_stream*/);
951 }
952 if (rc >= 0)
953 {
954 /*
955 * Wait for the stream to become ready.
956 */
957 uint64_t const nsStart = RTTimeNanoTS();
958 pa_stream_state_t enmStreamState;
959 while ( (enmStreamState = pa_stream_get_state(pStream)) != PA_STREAM_READY
960 && PA_STREAM_IS_GOOD(enmStreamState)
961 && RTTimeNanoTS() - nsStart < RT_NS_10SEC /* not really timed */ )
962 drvHostAudioPaMainloopWait(pThis);
963 if (enmStreamState == PA_STREAM_READY)
964 {
965 LogFunc(("Connecting stream took %'RU64 ns\n", RTTimeNanoTS() - nsStart));
966#ifdef LOG_ENABLED
967 pStreamPA->tsStartUs = pa_rtclock_now();
968#endif
969 /*
970 * Update the buffer attributes.
971 */
972 const pa_buffer_attr *pBufAttribs = pa_stream_get_buffer_attr(pStream);
973 AssertPtr(pBufAttribs);
974 if (pBufAttribs)
975 {
976 pStreamPA->BufAttr = *pBufAttribs;
977 LogFunc(("Obtained %s buffer attributes: maxlength=%RU32 tlength=%RU32 prebuf=%RU32 minreq=%RU32 fragsize=%RU32\n",
978 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pBufAttribs->maxlength, pBufAttribs->tlength,
979 pBufAttribs->prebuf, pBufAttribs->minreq, pBufAttribs->fragsize));
980
981 /*
982 * Convert the sample spec back to PDM speak.
983 * Note! This isn't strictly speaking needed as SampleSpec has *not* been
984 * modified since the caller converted it from pCfgReq.
985 */
986 rc = drvHostAudioPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
987 pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
988 if (RT_SUCCESS(rc))
989 {
990 pStreamPA->pStream = pStream;
991 LogFlowFunc(("returns VINF_SUCCESS\n"));
992 return VINF_SUCCESS;
993 }
994 }
995 else
996 {
997 LogRelMax(99, ("PulseAudio: Failed to get buffer attribs for stream '%s': %s (%d)\n",
998 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
999 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1000 }
1001 }
1002 else
1003 {
1004 LogRelMax(99, ("PulseAudio: Failed to initialize stream '%s': state=%d, waited %'RU64 ns\n",
1005 pszName, enmStreamState, RTTimeNanoTS() - nsStart));
1006 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1007 }
1008 pa_stream_disconnect(pStream);
1009 }
1010 else
1011 {
1012 LogRelMax(99, ("PulseAudio: Could not connect %s stream '%s': %s (%d/%d)\n",
1013 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output",
1014 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext), rc));
1015 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1016 }
1017
1018 pa_stream_unref(pStream);
1019 Assert(RT_FAILURE_NP(rc));
1020 LogFlowFunc(("returns %Rrc\n", rc));
1021 return rc;
1022}
1023
1024
1025/**
1026 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1027 */
1028static DECLCALLBACK(int) drvHostAudioPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1029 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1030{
1031 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1032 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1033 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1034 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1035 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1036 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1037 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
1038 int rc;
1039
1040 /*
1041 * Prepare name, sample spec and the stream instance data.
1042 */
1043 char szName[256];
1044 RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]",
1045 pCfgReq->enmDir == PDMAUDIODIR_IN
1046 ? PDMAudioRecSrcGetName(pCfgReq->u.enmSrc) : PDMAudioPlaybackDstGetName(pCfgReq->u.enmDst),
1047 pThis->szStreamName);
1048
1049 pStreamPA->pDrv = pThis;
1050 pStreamPA->pDrainOp = NULL;
1051 pStreamPA->pbPeekBuf = NULL;
1052 pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
1053 pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
1054 pStreamPA->SampleSpec.format = drvHostAudioPaPropsToPulse(&pCfgReq->Props);
1055
1056 LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n", szName, pStreamPA->SampleSpec.rate,
1057 pStreamPA->SampleSpec.channels, pa_sample_format_to_string(pStreamPA->SampleSpec.format)));
1058
1059 if (pa_sample_spec_valid(&pStreamPA->SampleSpec))
1060 {
1061 /*
1062 * Set up buffer attributes according to the stream type.
1063 *
1064 * For output streams we configure pre-buffering as requested, since
1065 * there is little point in using a different size than DrvAudio. This
1066 * assumes that a 'drain' request will override the prebuf size.
1067 */
1068 pStreamPA->BufAttr.maxlength = UINT32_MAX; /* Let the PulseAudio server choose the biggest size it can handle. */
1069 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1070 {
1071 pStreamPA->BufAttr.fragsize = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
1072 LogFunc(("Requesting: BufAttr: fragsize=%RU32\n", pStreamPA->BufAttr.fragsize));
1073 /* (rlength, minreq and prebuf are playback only) */
1074 }
1075 else
1076 {
1077 pStreamPA->cUsLatency = PDMAudioPropsFramesToMicro(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize);
1078 pStreamPA->BufAttr.tlength = pa_usec_to_bytes(pStreamPA->cUsLatency, &pStreamPA->SampleSpec);
1079 pStreamPA->BufAttr.minreq = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
1080 pStreamPA->BufAttr.prebuf = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgReq->Props,
1081 pCfgReq->Backend.cFramesPreBuffering),
1082 &pStreamPA->SampleSpec);
1083 /* (fragsize is capture only) */
1084 LogRel2(("PulseAudio: Initial output latency is %RU64 us (%RU32 bytes)\n",
1085 pStreamPA->cUsLatency, pStreamPA->BufAttr.tlength));
1086 LogFunc(("Requesting: BufAttr: tlength=%RU32 maxLength=%RU32 minReq=%RU32 maxlength=-1\n",
1087 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1088 }
1089
1090 /*
1091 * Do the actual PA stream creation.
1092 */
1093 pa_threaded_mainloop_lock(pThis->pMainLoop);
1094 rc = drvHostAudioPaStreamCreateLocked(pThis, pStreamPA, szName, pCfgAcq);
1095 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1096 if (RT_SUCCESS(rc))
1097 {
1098 /*
1099 * Set the acquired stream config according to the actual buffer
1100 * attributes we got and the stream type.
1101 */
1102 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1103 {
1104 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.fragsize);
1105 pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */
1106 ? PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.maxlength)
1107 : pCfgAcq->Backend.cFramesPeriod * 2 /* whatever */;
1108 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
1109 }
1110 else
1111 {
1112 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.minreq);
1113 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.tlength);
1114 pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering
1115 * pCfgAcq->Backend.cFramesBufferSize
1116 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1117 }
1118 PDMAudioStrmCfgCopy(&pStreamPA->Cfg, pCfgAcq);
1119 }
1120 }
1121 else
1122 {
1123 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", szName));
1124 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1125 }
1126
1127 LogFlowFuncLeaveRC(rc);
1128 return rc;
1129}
1130
1131/**
1132 * Cancel and release any pending stream requests (drain and cork/uncork).
1133 *
1134 * @note Caller has locked the mainloop.
1135 */
1136static void drvHostAudioPaStreamCancelAndReleaseOperations(PPULSEAUDIOSTREAM pStreamPA)
1137{
1138 if (pStreamPA->pDrainOp)
1139 {
1140 LogFlowFunc(("drain operation (%p) status: %d\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
1141 pa_operation_cancel(pStreamPA->pDrainOp);
1142 pa_operation_unref(pStreamPA->pDrainOp);
1143 pStreamPA->pDrainOp = NULL;
1144 }
1145
1146 if (pStreamPA->pCorkOp)
1147 {
1148 LogFlowFunc(("cork operation (%p) status: %d\n", pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
1149 pa_operation_cancel(pStreamPA->pCorkOp);
1150 pa_operation_unref(pStreamPA->pCorkOp);
1151 pStreamPA->pCorkOp = NULL;
1152 }
1153
1154 if (pStreamPA->pTriggerOp)
1155 {
1156 LogFlowFunc(("trigger operation (%p) status: %d\n", pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
1157 pa_operation_cancel(pStreamPA->pTriggerOp);
1158 pa_operation_unref(pStreamPA->pTriggerOp);
1159 pStreamPA->pTriggerOp = NULL;
1160 }
1161}
1162
1163
1164/**
1165 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1166 */
1167static DECLCALLBACK(int) drvHostAudioPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1168{
1169 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1170 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1171 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1172
1173 if (pStreamPA->pStream)
1174 {
1175 pa_threaded_mainloop_lock(pThis->pMainLoop);
1176
1177 drvHostAudioPaStreamCancelAndReleaseOperations(pStreamPA);
1178 pa_stream_disconnect(pStreamPA->pStream);
1179
1180 pa_stream_unref(pStreamPA->pStream);
1181 pStreamPA->pStream = NULL;
1182
1183 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1184 }
1185
1186 return VINF_SUCCESS;
1187}
1188
1189
1190/**
1191 * Common worker for the cork/uncork completion callbacks.
1192 * @note This is fully async, so nobody is waiting for this.
1193 */
1194static void drvHostAudioPaStreamCorkUncorkCommon(PPULSEAUDIOSTREAM pStreamPA, int fSuccess, const char *pszOperation)
1195{
1196 AssertPtrReturnVoid(pStreamPA);
1197 LogFlowFunc(("%s '%s': fSuccess=%RTbool\n", pszOperation, pStreamPA->Cfg.szName, fSuccess));
1198
1199 if (!fSuccess)
1200 drvHostAudioPaError(pStreamPA->pDrv, "%s stream '%s' failed", pszOperation, pStreamPA->Cfg.szName);
1201
1202 if (pStreamPA->pCorkOp)
1203 {
1204 pa_operation_unref(pStreamPA->pCorkOp);
1205 pStreamPA->pCorkOp = NULL;
1206 }
1207}
1208
1209
1210/**
1211 * Completion callback used with pa_stream_cork(,false,).
1212 */
1213static void drvHostAudioPaStreamUncorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1214{
1215 RT_NOREF(pStream);
1216 drvHostAudioPaStreamCorkUncorkCommon((PPULSEAUDIOSTREAM)pvUser, fSuccess, "Uncorking");
1217}
1218
1219
1220/**
1221 * Completion callback used with pa_stream_cork(,true,).
1222 */
1223static void drvHostAudioPaStreamCorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1224{
1225 RT_NOREF(pStream);
1226 drvHostAudioPaStreamCorkUncorkCommon((PPULSEAUDIOSTREAM)pvUser, fSuccess, "Corking");
1227}
1228
1229
1230/**
1231 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1232 */
1233static DECLCALLBACK(int) drvHostAudioPaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1234{
1235 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1236 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1237 LogFlowFunc(("\n"));
1238
1239 /*
1240 * Uncork (start or resume playback/capture) the stream.
1241 */
1242 pa_threaded_mainloop_lock(pThis->pMainLoop);
1243
1244 drvHostAudioPaStreamCancelAndReleaseOperations(pStreamPA);
1245 pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 0 /*uncork it*/,
1246 drvHostAudioPaStreamUncorkCompletionCallback, pStreamPA);
1247 LogFlowFunc(("Uncorking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
1248 int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
1249 : drvHostAudioPaError(pThis, "pa_stream_cork('%s', 0 /*uncork it*/,,) failed", pStreamPA->Cfg.szName);
1250
1251
1252 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1253
1254 LogFlowFunc(("returns %Rrc\n", rc));
1255 return rc;
1256}
1257
1258
1259/**
1260 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1261 */
1262static DECLCALLBACK(int) drvHostAudioPaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1263{
1264 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1265 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1266 LogFlowFunc(("\n"));
1267
1268 pa_threaded_mainloop_lock(pThis->pMainLoop);
1269
1270 /*
1271 * For output streams, we will ignore the request if there is a pending drain
1272 * as it will cork the stream in the end.
1273 */
1274 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1275 {
1276 if (pStreamPA->pDrainOp)
1277 {
1278 pa_operation_state_t const enmOpState = pa_operation_get_state(pStreamPA->pDrainOp);
1279 if (enmOpState == PA_OPERATION_RUNNING)
1280 {
1281/** @todo consider corking it immediately instead, as that's what the caller
1282 * wants now... */
1283 LogFlowFunc(("Drain (%p) already running on '%s', skipping.\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
1284 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1285 return VINF_SUCCESS;
1286 }
1287 LogFlowFunc(("Drain (%p) not running: %d\n", pStreamPA->pDrainOp, enmOpState));
1288 }
1289 }
1290 /*
1291 * For input stream we always cork it, but we clean up the peek buffer first.
1292 */
1293 /** @todo r=bird: It is (probably) not technically be correct to drop the peek buffer
1294 * here when we're only pausing the stream (VM paused) as it means we'll
1295 * risk underruns when later resuming. */
1296 else if (pStreamPA->pbPeekBuf) /** @todo Do we need to drop the peek buffer?*/
1297 {
1298 pStreamPA->pbPeekBuf = NULL;
1299 pStreamPA->cbPeekBuf = 0;
1300 pa_stream_drop(pStreamPA->pStream);
1301 }
1302
1303 /*
1304 * Cork (pause playback/capture) the stream.
1305 */
1306 drvHostAudioPaStreamCancelAndReleaseOperations(pStreamPA);
1307 pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 1 /* cork it */,
1308 drvHostAudioPaStreamCorkCompletionCallback, pStreamPA);
1309 LogFlowFunc(("Corking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
1310 int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
1311 : drvHostAudioPaError(pThis, "pa_stream_cork('%s', 1 /*cork*/,,) failed", pStreamPA->Cfg.szName);
1312
1313 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1314 LogFlowFunc(("returns %Rrc\n", rc));
1315 return rc;
1316}
1317
1318
1319/**
1320 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1321 */
1322static DECLCALLBACK(int) drvHostAudioPaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1323{
1324 /* Same as disable. */
1325 return drvHostAudioPaHA_StreamDisable(pInterface, pStream);
1326}
1327
1328
1329/**
1330 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
1331 */
1332static DECLCALLBACK(int) drvHostAudioPaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1333{
1334 /* Same as enable. */
1335 return drvHostAudioPaHA_StreamEnable(pInterface, pStream);
1336}
1337
1338
1339/**
1340 * Pulse audio pa_stream_drain() completion callback.
1341 * @note This is fully async, so nobody is waiting for this.
1342 */
1343static void drvHostAudioPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1344{
1345 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
1346 AssertPtrReturnVoid(pStreamPA);
1347 Assert(pStreamPA->pStream == pStream);
1348 LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
1349
1350 if (!fSuccess)
1351 drvHostAudioPaError(pStreamPA->pDrv, "Draining stream '%s' failed", pStreamPA->Cfg.szName);
1352
1353 /* Now cork the stream (doing it unconditionally atm). */
1354 if (pStreamPA->pCorkOp)
1355 {
1356 LogFlowFunc(("Cancelling & releasing cork/uncork operation %p (state: %d)\n",
1357 pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
1358 pa_operation_cancel(pStreamPA->pCorkOp);
1359 pa_operation_unref(pStreamPA->pCorkOp);
1360 }
1361
1362 pStreamPA->pCorkOp = pa_stream_cork(pStream, 1 /* cork it*/, drvHostAudioPaStreamCorkCompletionCallback, pStreamPA);
1363 if (pStreamPA->pCorkOp)
1364 LogFlowFunc(("Started cork operation %p of %s (following drain)\n", pStreamPA->pCorkOp, pStreamPA->Cfg.szName));
1365 else
1366 drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_cork failed on '%s' (following drain)", pStreamPA->Cfg.szName);
1367}
1368
1369
1370/**
1371 * Callback used with pa_stream_tigger(), starts draining.
1372 */
1373static void drvHostAudioPaStreamTriggerCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1374{
1375 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
1376 AssertPtrReturnVoid(pStreamPA);
1377 RT_NOREF(pStream);
1378 LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
1379
1380 if (!fSuccess)
1381 drvHostAudioPaError(pStreamPA->pDrv, "Forcing playback before drainig '%s' failed", pStreamPA->Cfg.szName);
1382
1383 if (pStreamPA->pTriggerOp)
1384 {
1385 pa_operation_unref(pStreamPA->pTriggerOp);
1386 pStreamPA->pTriggerOp = NULL;
1387 }
1388}
1389
1390
1391/**
1392 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
1393 */
1394static DECLCALLBACK(int) drvHostAudioPaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1395{
1396 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1397 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1398 AssertReturn(pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1399 LogFlowFunc(("\n"));
1400
1401 pa_threaded_mainloop_lock(pThis->pMainLoop);
1402
1403 /*
1404 * If there is a drain running already, don't try issue another as pulse
1405 * doesn't support more than one concurrent drain per stream.
1406 */
1407 if (pStreamPA->pDrainOp)
1408 {
1409 if (pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING)
1410 {
1411 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1412 LogFlowFunc(("returns VINF_SUCCESS (drain already running)\n"));
1413 return VINF_SUCCESS;
1414 }
1415 LogFlowFunc(("Releasing drain operation %p (state: %d)\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
1416 pa_operation_unref(pStreamPA->pDrainOp);
1417 pStreamPA->pDrainOp = NULL;
1418 }
1419
1420 /*
1421 * Make sure pre-buffered data is played before we drain it.
1422 *
1423 * ASSUMES that the async stream requests are executed in the order they're
1424 * issued here, so that we avoid waiting for the trigger request to complete.
1425 */
1426 int rc = VINF_SUCCESS;
1427 if (true /** @todo skip this if we're already playing or haven't written any data to the stream since xxxx. */)
1428 {
1429 if (pStreamPA->pTriggerOp)
1430 {
1431 LogFlowFunc(("Cancelling & releasing trigger operation %p (state: %d)\n",
1432 pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
1433 pa_operation_cancel(pStreamPA->pTriggerOp);
1434 pa_operation_unref(pStreamPA->pTriggerOp);
1435 }
1436 pStreamPA->pTriggerOp = pa_stream_trigger(pStreamPA->pStream, drvHostAudioPaStreamTriggerCompletionCallback, pStreamPA);
1437 if (pStreamPA->pTriggerOp)
1438 LogFlowFunc(("Started tigger operation %p on %s\n", pStreamPA->pTriggerOp, pStreamPA->Cfg.szName));
1439 else
1440 rc = drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_trigger failed on '%s'", pStreamPA->Cfg.szName);
1441 }
1442
1443 /*
1444 * Initiate the draining (async), will cork the stream when it completes.
1445 */
1446 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHostAudioPaStreamDrainCompletionCallback, pStreamPA);
1447 if (pStreamPA->pDrainOp)
1448 LogFlowFunc(("Started drain operation %p of %s\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
1449 else
1450 rc = drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_drain failed on '%s'", pStreamPA->Cfg.szName);
1451
1452 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1453 LogFlowFunc(("returns %Rrc\n", rc));
1454 return rc;
1455}
1456
1457
1458/**
1459 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1460 */
1461static DECLCALLBACK(int) drvHostAudioPaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
1462 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1463{
1464 /** @todo r=bird: I'd like to get rid of this pfnStreamControl method,
1465 * replacing it with individual StreamXxxx methods. That would save us
1466 * potentally huge switches and more easily see which drivers implement
1467 * which operations (grep for pfnStreamXxxx). */
1468 switch (enmStreamCmd)
1469 {
1470 case PDMAUDIOSTREAMCMD_ENABLE:
1471 return drvHostAudioPaHA_StreamEnable(pInterface, pStream);
1472 case PDMAUDIOSTREAMCMD_DISABLE:
1473 return drvHostAudioPaHA_StreamDisable(pInterface, pStream);
1474 case PDMAUDIOSTREAMCMD_PAUSE:
1475 return drvHostAudioPaHA_StreamPause(pInterface, pStream);
1476 case PDMAUDIOSTREAMCMD_RESUME:
1477 return drvHostAudioPaHA_StreamResume(pInterface, pStream);
1478 case PDMAUDIOSTREAMCMD_DRAIN:
1479 return drvHostAudioPaHA_StreamDrain(pInterface, pStream);
1480
1481 case PDMAUDIOSTREAMCMD_END:
1482 case PDMAUDIOSTREAMCMD_32BIT_HACK:
1483 case PDMAUDIOSTREAMCMD_INVALID:
1484 /* no default*/
1485 break;
1486 }
1487 return VERR_NOT_SUPPORTED;
1488}
1489
1490
1491/**
1492 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1493 */
1494static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1495{
1496 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1497 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1498 uint32_t cbReadable = 0;
1499 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN)
1500 {
1501 pa_threaded_mainloop_lock(pThis->pMainLoop);
1502
1503 pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
1504 if (PA_STREAM_IS_GOOD(enmState))
1505 {
1506 size_t cbReadablePa = pa_stream_readable_size(pStreamPA->pStream);
1507 if (cbReadablePa != (size_t)-1)
1508 cbReadable = (uint32_t)cbReadablePa;
1509 else
1510 drvHostAudioPaError(pThis, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
1511 }
1512 else
1513 LogFunc(("non-good stream state: %d\n", enmState));
1514
1515 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1516 }
1517 Log3Func(("returns %#x (%u)\n", cbReadable, cbReadable));
1518 return cbReadable;
1519}
1520
1521
1522/**
1523 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1524 */
1525static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1526{
1527 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1528 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1529 uint32_t cbWritable = 0;
1530 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1531 {
1532 pa_threaded_mainloop_lock(pThis->pMainLoop);
1533
1534 pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
1535 if (PA_STREAM_IS_GOOD(enmState))
1536 {
1537 size_t cbWritablePa = pa_stream_writable_size(pStreamPA->pStream);
1538 if (cbWritablePa != (size_t)-1)
1539 cbWritable = cbWritablePa <= UINT32_MAX ? (uint32_t)cbWritablePa : UINT32_MAX;
1540 else
1541 drvHostAudioPaError(pThis, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
1542 }
1543 else
1544 LogFunc(("non-good stream state: %d\n", enmState));
1545
1546 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1547 }
1548 Log3Func(("returns %#x (%u) [max=%#RX32 min=%#RX32]\n",
1549 cbWritable, cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1550 return cbWritable;
1551}
1552
1553
1554/**
1555 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
1556 */
1557static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHostAudioPaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1558 PPDMAUDIOBACKENDSTREAM pStream)
1559{
1560 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1561 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1562 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1563 AssertPtrReturn(pStreamPA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1564
1565 /* Check PulseAudio's general status. */
1566 PDMHOSTAUDIOSTREAMSTATE enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1567 if (pThis->pContext)
1568 {
1569 pa_context_state_t const enmPaCtxState = pa_context_get_state(pThis->pContext);
1570 if (PA_CONTEXT_IS_GOOD(enmPaCtxState))
1571 {
1572 pa_stream_state_t const enmPaStreamState = pa_stream_get_state(pStreamPA->pStream);
1573 if (PA_STREAM_IS_GOOD(enmPaStreamState))
1574 {
1575 if (enmPaStreamState != PA_STREAM_CREATING)
1576 {
1577 if ( pStreamPA->Cfg.enmDir != PDMAUDIODIR_OUT
1578 || pStreamPA->pDrainOp == NULL
1579 || pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_RUNNING)
1580 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1581 else
1582 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1583 }
1584 else
1585 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
1586 }
1587 else
1588 LogFunc(("non-good PA stream state: %d\n", enmPaStreamState));
1589 }
1590 else
1591 LogFunc(("non-good PA context state: %d\n", enmPaCtxState));
1592 }
1593 else
1594 LogFunc(("No context!\n"));
1595 LogFlowFunc(("returns %s for stream '%s'\n", PDMHostAudioStreamStateGetName(enmBackendStreamState), pStreamPA->Cfg.szName));
1596 return enmBackendStreamState;
1597}
1598
1599
1600/**
1601 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1602 */
1603static DECLCALLBACK(int) drvHostAudioPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1604 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1605{
1606 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1607 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1608 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1609 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1610 if (cbBuf)
1611 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1612 else
1613 {
1614 /* Fend off draining calls. */
1615 *pcbWritten = 0;
1616 return VINF_SUCCESS;
1617 }
1618
1619 pa_threaded_mainloop_lock(pThis->pMainLoop);
1620
1621#ifdef LOG_ENABLED
1622 const pa_usec_t tsNowUs = pa_rtclock_now();
1623 Log3Func(("play delta: %'RU64 us; cbBuf=%#x\n", tsNowUs - pStreamPA->tsLastReadWrittenUs, cbBuf));
1624 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1625#endif
1626
1627 /*
1628 * Using a loop here so we can take maxlength into account when writing.
1629 */
1630 int rc = VINF_SUCCESS;
1631 uint32_t cbTotalWritten = 0;
1632 uint32_t iLoop;
1633 for (iLoop = 0; ; iLoop++)
1634 {
1635 size_t const cbWriteable = pa_stream_writable_size(pStreamPA->pStream);
1636 if ( cbWriteable != (size_t)-1
1637 && cbWriteable >= PDMAudioPropsFrameSize(&pStreamPA->Cfg.Props))
1638 {
1639 uint32_t cbToWrite = (uint32_t)RT_MIN(RT_MIN(cbWriteable, pStreamPA->BufAttr.maxlength), cbBuf);
1640 cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamPA->Cfg.Props, cbToWrite);
1641 if (pa_stream_write(pStreamPA->pStream, pvBuf, cbToWrite, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0)
1642 {
1643 cbTotalWritten += cbToWrite;
1644 cbBuf -= cbToWrite;
1645 if (!cbBuf)
1646 break;
1647 pvBuf = (uint8_t const *)pvBuf + cbToWrite;
1648 Log3Func(("%#x left to write\n", cbBuf));
1649 }
1650 else
1651 {
1652 rc = drvHostAudioPaError(pStreamPA->pDrv, "Failed to write to output stream");
1653 break;
1654 }
1655 }
1656 else
1657 {
1658 if (cbWriteable == (size_t)-1)
1659 rc = drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
1660 break;
1661 }
1662 }
1663
1664 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1665
1666 *pcbWritten = cbTotalWritten;
1667 if (RT_SUCCESS(rc) || cbTotalWritten == 0)
1668 { /* likely */ }
1669 else
1670 {
1671 LogFunc(("Supressing %Rrc because we wrote %#x bytes\n", rc, cbTotalWritten));
1672 rc = VINF_SUCCESS;
1673 }
1674 Log3Func(("returns %Rrc *pcbWritten=%#x iLoop=%u\n", rc, cbTotalWritten, iLoop));
1675 return rc;
1676}
1677
1678
1679/**
1680 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1681 */
1682static DECLCALLBACK(int) drvHostAudioPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1683 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1684{
1685 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1686 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1687 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1688 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1689 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1690 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1691
1692#ifdef LOG_ENABLED
1693 const pa_usec_t tsNowUs = pa_rtclock_now();
1694 Log3Func(("capture delta: %'RU64 us; cbBuf=%#x\n", tsNowUs - pStreamPA->tsLastReadWrittenUs, cbBuf));
1695 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1696#endif
1697
1698 /*
1699 * If we have left over peek buffer space from the last call,
1700 * copy out the data from there.
1701 */
1702 uint32_t cbTotalRead = 0;
1703 if ( pStreamPA->pbPeekBuf
1704 && pStreamPA->offPeekBuf < pStreamPA->cbPeekBuf)
1705 {
1706 uint32_t cbToCopy = pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf;
1707 if (cbToCopy >= cbBuf)
1708 {
1709 memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbBuf);
1710 pStreamPA->offPeekBuf += cbBuf;
1711 *pcbRead = cbBuf;
1712 if (cbToCopy == cbBuf)
1713 {
1714 pa_threaded_mainloop_lock(pThis->pMainLoop);
1715 pStreamPA->pbPeekBuf = NULL;
1716 pStreamPA->cbPeekBuf = 0;
1717 pa_stream_drop(pStreamPA->pStream);
1718 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1719 }
1720 Log3Func(("returns *pcbRead=%#x from prev peek buf (%#x/%#x)\n", cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
1721 return VINF_SUCCESS;
1722 }
1723
1724 memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbToCopy);
1725 cbBuf -= cbToCopy;
1726 pvBuf = (uint8_t *)pvBuf + cbToCopy;
1727 cbTotalRead += cbToCopy;
1728 pStreamPA->offPeekBuf = pStreamPA->cbPeekBuf;
1729 }
1730
1731 /*
1732 * Copy out what we can.
1733 */
1734 int rc = VINF_SUCCESS;
1735 pa_threaded_mainloop_lock(pThis->pMainLoop);
1736 while (cbBuf > 0)
1737 {
1738 /*
1739 * Drop the old peek buffer first, if we have one.
1740 */
1741 if (pStreamPA->pbPeekBuf)
1742 {
1743 Assert(pStreamPA->offPeekBuf >= pStreamPA->cbPeekBuf);
1744 pStreamPA->pbPeekBuf = NULL;
1745 pStreamPA->cbPeekBuf = 0;
1746 pa_stream_drop(pStreamPA->pStream);
1747 }
1748
1749 /*
1750 * Check if there is anything to read, the get the peek buffer for it.
1751 */
1752 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
1753 if (cbAvail > 0 && cbAvail != (size_t)-1)
1754 {
1755 pStreamPA->pbPeekBuf = NULL;
1756 pStreamPA->cbPeekBuf = 0;
1757 int rcPa = pa_stream_peek(pStreamPA->pStream, (const void **)&pStreamPA->pbPeekBuf, &pStreamPA->cbPeekBuf);
1758 if (rcPa == 0)
1759 {
1760 if (pStreamPA->cbPeekBuf)
1761 {
1762 if (pStreamPA->pbPeekBuf)
1763 {
1764 /*
1765 * We got data back. Copy it into the return buffer, return if it's full.
1766 */
1767 if (cbBuf < pStreamPA->cbPeekBuf)
1768 {
1769 memcpy(pvBuf, pStreamPA->pbPeekBuf, cbBuf);
1770 cbTotalRead += cbBuf;
1771 pStreamPA->offPeekBuf = cbBuf;
1772 cbBuf = 0;
1773 break;
1774 }
1775 memcpy(pvBuf, pStreamPA->pbPeekBuf, pStreamPA->cbPeekBuf);
1776 cbBuf -= pStreamPA->cbPeekBuf;
1777 pvBuf = (uint8_t *)pvBuf + pStreamPA->cbPeekBuf;
1778 cbTotalRead += pStreamPA->cbPeekBuf;
1779
1780 pStreamPA->pbPeekBuf = NULL;
1781 }
1782 else
1783 {
1784 /*
1785 * We got a hole (drop needed). We will skip it as we leave it to
1786 * the device's DMA engine to fill in buffer gaps with silence.
1787 */
1788 LogFunc(("pa_stream_peek returned a %#zx (%zu) byte hole - skipping.\n",
1789 pStreamPA->cbPeekBuf, pStreamPA->cbPeekBuf));
1790 }
1791 pStreamPA->cbPeekBuf = 0;
1792 pa_stream_drop(pStreamPA->pStream);
1793 }
1794 else
1795 {
1796 Assert(!pStreamPA->pbPeekBuf);
1797 LogFunc(("pa_stream_peek returned empty buffer\n"));
1798 break;
1799 }
1800 }
1801 else
1802 {
1803 rc = drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_peek failed on '%s' (%d)", pStreamPA->Cfg.szName, rcPa);
1804 pStreamPA->pbPeekBuf = NULL;
1805 pStreamPA->cbPeekBuf = 0;
1806 break;
1807 }
1808 }
1809 else
1810 {
1811 if (cbAvail != (size_t)-1)
1812 rc = drvHostAudioPaError(pStreamPA->pDrv, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
1813 break;
1814 }
1815 }
1816 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1817
1818 *pcbRead = cbTotalRead;
1819 if (RT_SUCCESS(rc) || cbTotalRead == 0)
1820 { /* likely */ }
1821 else
1822 {
1823 LogFunc(("Supressing %Rrc because we're returning %#x bytes\n", rc, cbTotalRead));
1824 rc = VINF_SUCCESS;
1825 }
1826 Log3Func(("returns %Rrc *pcbRead=%#x (%#x left, peek %#x/%#x)\n",
1827 rc, cbTotalRead, cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
1828 return rc;
1829}
1830
1831
1832/*********************************************************************************************************************************
1833* PDMIBASE *
1834*********************************************************************************************************************************/
1835
1836/**
1837 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1838 */
1839static DECLCALLBACK(void *) drvHostAudioPaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1840{
1841 AssertPtrReturn(pInterface, NULL);
1842 AssertPtrReturn(pszIID, NULL);
1843
1844 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1845 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1846 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1847 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1848
1849 return NULL;
1850}
1851
1852
1853/*********************************************************************************************************************************
1854* PDMDRVREG *
1855*********************************************************************************************************************************/
1856
1857/**
1858 * Destructs a PulseAudio Audio driver instance.
1859 *
1860 * @copydoc FNPDMDRVDESTRUCT
1861 */
1862static DECLCALLBACK(void) drvHostAudioPaDestruct(PPDMDRVINS pDrvIns)
1863{
1864 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1865 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1866 LogFlowFuncEnter();
1867
1868 if (pThis->pMainLoop)
1869 pa_threaded_mainloop_stop(pThis->pMainLoop);
1870
1871 if (pThis->pContext)
1872 {
1873 pa_context_disconnect(pThis->pContext);
1874 pa_context_unref(pThis->pContext);
1875 pThis->pContext = NULL;
1876 }
1877
1878 if (pThis->pMainLoop)
1879 {
1880 pa_threaded_mainloop_free(pThis->pMainLoop);
1881 pThis->pMainLoop = NULL;
1882 }
1883
1884 LogFlowFuncLeave();
1885}
1886
1887
1888/**
1889 * Pulse audio callback for context status changes, init variant.
1890 *
1891 * Signalls our event semaphore so we can do a timed wait from
1892 * drvHostAudioPaConstruct().
1893 */
1894static void drvHostAudioPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser)
1895{
1896 AssertPtrReturnVoid(pCtx);
1897 PPULSEAUDIOSTATECHGCTX pStateChgCtx = (PPULSEAUDIOSTATECHGCTX)pvUser;
1898 pa_context_state_t enmCtxState = pa_context_get_state(pCtx);
1899 switch (enmCtxState)
1900 {
1901 case PA_CONTEXT_READY:
1902 case PA_CONTEXT_TERMINATED:
1903 case PA_CONTEXT_FAILED:
1904 AssertPtrReturnVoid(pStateChgCtx);
1905 pStateChgCtx->enmCtxState = enmCtxState;
1906 RTSemEventSignal(pStateChgCtx->hEvtInit);
1907 break;
1908
1909 default:
1910 break;
1911 }
1912}
1913
1914
1915/**
1916 * Constructs a PulseAudio Audio driver instance.
1917 *
1918 * @copydoc FNPDMDRVCONSTRUCT
1919 */
1920static DECLCALLBACK(int) drvHostAudioPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1921{
1922 RT_NOREF(pCfg, fFlags);
1923 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1924 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1925 LogRel(("Audio: Initializing PulseAudio driver\n"));
1926
1927 /*
1928 * Initialize instance data.
1929 */
1930 pThis->pDrvIns = pDrvIns;
1931 /* IBase */
1932 pDrvIns->IBase.pfnQueryInterface = drvHostAudioPaQueryInterface;
1933 /* IHostAudio */
1934 pThis->IHostAudio.pfnGetConfig = drvHostAudioPaHA_GetConfig;
1935 pThis->IHostAudio.pfnGetDevices = drvHostAudioPaHA_GetDevices;
1936 pThis->IHostAudio.pfnGetStatus = drvHostAudioPaHA_GetStatus;
1937 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1938 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1939 pThis->IHostAudio.pfnStreamCreate = drvHostAudioPaHA_StreamCreate;
1940 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1941 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioPaHA_StreamDestroy;
1942 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1943 pThis->IHostAudio.pfnStreamControl = drvHostAudioPaHA_StreamControl;
1944 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioPaHA_StreamGetReadable;
1945 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioPaHA_StreamGetWritable;
1946 pThis->IHostAudio.pfnStreamGetPending = NULL;
1947 pThis->IHostAudio.pfnStreamGetState = drvHostAudioPaHA_StreamGetState;
1948 pThis->IHostAudio.pfnStreamPlay = drvHostAudioPaHA_StreamPlay;
1949 pThis->IHostAudio.pfnStreamCapture = drvHostAudioPaHA_StreamCapture;
1950
1951 /*
1952 * Read configuration.
1953 */
1954 int rc2 = CFGMR3QueryString(pCfg, "VmName", pThis->szStreamName, sizeof(pThis->szStreamName));
1955 AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"VmName\" value, rc=%Rrc\n", rc2), rc2);
1956
1957 /*
1958 * Load the pulse audio library.
1959 */
1960 int rc = audioLoadPulseLib();
1961 if (RT_SUCCESS(rc))
1962 LogRel(("PulseAudio: Using version %s\n", pa_get_library_version()));
1963 else
1964 {
1965 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
1966 return rc;
1967 }
1968
1969 /*
1970 * Set up the basic pulse audio bits (remember the destructore is always called).
1971 */
1972 //pThis->fAbortLoop = false;
1973 pThis->pMainLoop = pa_threaded_mainloop_new();
1974 if (!pThis->pMainLoop)
1975 {
1976 LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1977 return VERR_NO_MEMORY;
1978 }
1979
1980 pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox");
1981 if (!pThis->pContext)
1982 {
1983 LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1984 return VERR_NO_MEMORY;
1985 }
1986
1987 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
1988 {
1989 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1990 return VERR_AUDIO_BACKEND_INIT_FAILED;
1991 }
1992
1993 /*
1994 * Connect to the pulse audio server.
1995 *
1996 * We install an init state callback so we can do a timed wait in case
1997 * connecting to the pulseaudio server should take too long.
1998 */
1999 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
2000 pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED;
2001 rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit);
2002 AssertLogRelRCReturn(rc, rc);
2003
2004 pa_threaded_mainloop_lock(pThis->pMainLoop);
2005 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx);
2006 if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL))
2007 {
2008 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2009
2010 rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */
2011 if (RT_SUCCESS(rc))
2012 {
2013 if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY)
2014 {
2015 /* Install the main state changed callback to know if something happens to our acquired context. */
2016 pa_threaded_mainloop_lock(pThis->pMainLoop);
2017 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChanged, pThis /* pvUserData */);
2018 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2019 }
2020 else
2021 {
2022 LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc));
2023 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
2024 }
2025 }
2026 else
2027 {
2028 LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc));
2029 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
2030 }
2031 }
2032 else
2033 {
2034 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2035 LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
2036 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */
2037 }
2038
2039 RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit);
2040 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
2041
2042 return rc;
2043}
2044
2045
2046/**
2047 * Pulse audio driver registration record.
2048 */
2049const PDMDRVREG g_DrvHostPulseAudio =
2050{
2051 /* u32Version */
2052 PDM_DRVREG_VERSION,
2053 /* szName */
2054 "PulseAudio",
2055 /* szRCMod */
2056 "",
2057 /* szR0Mod */
2058 "",
2059 /* pszDescription */
2060 "Pulse Audio host driver",
2061 /* fFlags */
2062 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2063 /* fClass. */
2064 PDM_DRVREG_CLASS_AUDIO,
2065 /* cMaxInstances */
2066 ~0U,
2067 /* cbInstance */
2068 sizeof(DRVHOSTPULSEAUDIO),
2069 /* pfnConstruct */
2070 drvHostAudioPaConstruct,
2071 /* pfnDestruct */
2072 drvHostAudioPaDestruct,
2073 /* pfnRelocate */
2074 NULL,
2075 /* pfnIOCtl */
2076 NULL,
2077 /* pfnPowerOn */
2078 NULL,
2079 /* pfnReset */
2080 NULL,
2081 /* pfnSuspend */
2082 NULL,
2083 /* pfnResume */
2084 NULL,
2085 /* pfnAttach */
2086 NULL,
2087 /* pfnDetach */
2088 NULL,
2089 /* pfnPowerOff */
2090 NULL,
2091 /* pfnSoftReset */
2092 NULL,
2093 /* u32EndVersion */
2094 PDM_DRVREG_VERSION
2095};
2096
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