VirtualBox

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

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

Audio: Use HostAudioWas on windows 10 instead of DSound. Only set the StreamName CFGM value for host audio drivers who needs it (PulseAudio and now WAS). Renamed StreamName to VmName. Added VmUuid to WAS. bugref:9890

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