VirtualBox

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

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

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. 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 88991 2021-05-12 00:46:35Z 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 enmCtxState = pa_context_get_state(pThis->pContext);
1570 if (PA_CONTEXT_IS_GOOD(enmCtxState))
1571 {
1572 pa_stream_state_t const enmStreamState = pa_stream_get_state(pStreamPA->pStream);
1573 if (PA_STREAM_IS_GOOD(enmState))
1574 {
1575 if (enmState != 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", enmStreamState));
1589 }
1590 else
1591 LogFunc(("non-good PA context state: %d\n", enmCtxState));
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