VirtualBox

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

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

DrvAudioHostPulseAudio: Mostly done cleaning up stream creation. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 63.6 KB
Line 
1/* $Id: DrvHostAudioPulseAudio.cpp 88486 2021-04-13 09:36:47Z 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 64
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 int fOpSuccess;
146 /** Pointer to Pulse sample peeking buffer. */
147 const uint8_t *pu8PeekBuf;
148 /** Current size (in bytes) of peeking data in
149 * buffer. */
150 size_t cbPeekBuf;
151 /** Our offset (in bytes) in peeking buffer. */
152 size_t offPeekBuf;
153 pa_operation *pDrainOp;
154 /** Number of occurred audio data underflows. */
155 uint32_t cUnderflows;
156 /** Current latency (in us). */
157 uint64_t cUsLatency;
158#ifdef LOG_ENABLED
159 /** Start time stamp (in us) of stream playback / recording. */
160 pa_usec_t tsStartUs;
161 /** Time stamp (in us) when last read from / written to the stream. */
162 pa_usec_t tsLastReadWrittenUs;
163#endif
164} PULSEAUDIOSTREAM;
165/** Pointer to pulse audio stream data. */
166typedef PULSEAUDIOSTREAM *PPULSEAUDIOSTREAM;
167
168
169/**
170 * Pulse audio host audio driver instance data.
171 * @implements PDMIAUDIOCONNECTOR
172 */
173typedef struct DRVHOSTPULSEAUDIO
174{
175 /** Pointer to the driver instance structure. */
176 PPDMDRVINS pDrvIns;
177 /** Pointer to PulseAudio's threaded main loop. */
178 pa_threaded_mainloop *pMainLoop;
179 /**
180 * Pointer to our PulseAudio context.
181 * @note We use a pMainLoop in a separate thread (pContext).
182 * So either use callback functions or protect these functions
183 * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
184 */
185 pa_context *pContext;
186 /** Shutdown indicator. */
187 volatile bool fAbortLoop;
188 /** Error count for not flooding the release log.
189 * Specify UINT32_MAX for unlimited logging. */
190 uint32_t cLogErrors;
191 /** The stream (base) name; needed for distinguishing
192 * streams in the PulseAudio mixer controls if multiple
193 * VMs are running at the same time. */
194 char szStreamName[64];
195 /** Don't want to put this on the stack... */
196 PULSEAUDIOSTATECHGCTX InitStateChgCtx;
197 /** Pointer to host audio interface. */
198 PDMIHOSTAUDIO IHostAudio;
199} DRVHOSTPULSEAUDIO;
200
201
202
203/*
204 * Glue to make the code work systems with PulseAudio < 0.9.11.
205 */
206#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
207DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState)
208{
209 return enmState == PA_CONTEXT_CONNECTING
210 || enmState == PA_CONTEXT_AUTHORIZING
211 || enmState == PA_CONTEXT_SETTING_NAME
212 || enmState == PA_CONTEXT_READY;
213}
214#endif
215
216#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
217DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState)
218{
219 return enmState == PA_STREAM_CREATING
220 || enmState == PA_STREAM_READY;
221}
222#endif
223
224
225
226/** @todo Implement va handling. */
227static int drvHostAudioPaError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
228{
229 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
230 AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
231
232 if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS
233 && LogRelIs2Enabled())
234 {
235 pThis->cLogErrors++;
236 int rc2 = pa_context_errno(pThis->pContext);
237 LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
238 }
239
240 /** @todo Implement some PulseAudio -> IPRT mapping here. */
241 return VERR_GENERAL_FAILURE;
242}
243
244
245/**
246 * Signal the main loop to abort. Just signalling isn't sufficient as the
247 * mainloop might not have been entered yet.
248 */
249static void drvHostAudioPaSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
250{
251 if (pThis)
252 {
253 pThis->fAbortLoop = true;
254 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
255 }
256}
257
258
259/**
260 * Wrapper around pa_threaded_mainloop_wait().
261 */
262static void drvHostAudioPaMainloopWait(PDRVHOSTPULSEAUDIO pThis)
263{
264 /** @todo r=bird: explain this logic. */
265 if (!pThis->fAbortLoop)
266 pa_threaded_mainloop_wait(pThis->pMainLoop);
267 pThis->fAbortLoop = false;
268}
269
270
271/**
272 * Pulse audio callback for context status changes, init variant.
273 */
274static void drvHostAudioPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser)
275{
276 AssertPtrReturnVoid(pCtx);
277
278 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
279 AssertPtrReturnVoid(pThis);
280
281 switch (pa_context_get_state(pCtx))
282 {
283 case PA_CONTEXT_READY:
284 case PA_CONTEXT_TERMINATED:
285 case PA_CONTEXT_FAILED:
286 drvHostAudioPaSignalWaiter(pThis);
287 break;
288
289 default:
290 break;
291 }
292}
293
294
295/**
296 * Callback used with pa_stream_cork() in a number of places.
297 */
298static void drvHostAudioPaStreamSuccessCallback(pa_stream *pStream, int fSuccess, void *pvUser)
299{
300 AssertPtrReturnVoid(pStream);
301 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
302 AssertPtrReturnVoid(pStrm);
303
304 pStrm->fOpSuccess = fSuccess;
305
306 if (fSuccess)
307 drvHostAudioPaSignalWaiter(pStrm->pDrv);
308 else
309 drvHostAudioPaError(pStrm->pDrv, "Failed to finish stream operation");
310}
311
312
313/**
314 * Synchronously wait until an operation completed.
315 *
316 * This will consume the pOperation reference.
317 */
318static int drvHostAudioPaWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOperation, RTMSINTERVAL cMsTimeout)
319{
320 AssertPtrReturn(pOperation, VERR_INVALID_POINTER);
321
322 uint64_t const msStart = RTTimeMilliTS();
323 pa_operation_state_t enmOpState;
324 while ((enmOpState = pa_operation_get_state(pOperation)) == PA_OPERATION_RUNNING)
325 {
326 if (!pThis->fAbortLoop) /** @todo r=bird: I do _not_ get the logic behind this fAbortLoop mechanism, it looks more
327 * than a little mixed up and too much generalized see drvHostAudioPaSignalWaiter. */
328 {
329 AssertPtr(pThis->pMainLoop);
330 pa_threaded_mainloop_wait(pThis->pMainLoop);
331 if ( !pThis->pContext
332 || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
333 {
334 pa_operation_cancel(pOperation);
335 pa_operation_unref(pOperation);
336 LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
337 return VERR_INVALID_STATE;
338 }
339 }
340 pThis->fAbortLoop = false;
341
342 /*
343 * Note! This timeout business is a bit bogus as pa_threaded_mainloop_wait is indefinite.
344 */
345 if (RTTimeMilliTS() - msStart >= cMsTimeout)
346 {
347 enmOpState = pa_operation_get_state(pOperation);
348 if (enmOpState != PA_OPERATION_RUNNING)
349 break;
350 pa_operation_cancel(pOperation);
351 pa_operation_unref(pOperation);
352 return VERR_TIMEOUT;
353 }
354 }
355
356 pa_operation_unref(pOperation);
357 if (enmOpState == PA_OPERATION_DONE)
358 return VINF_SUCCESS;
359 return VERR_CANCELLED;
360}
361
362
363static int drvHostAudioPaWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
364{
365 return drvHostAudioPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC);
366}
367
368
369
370/*********************************************************************************************************************************
371* PDMIHOSTAUDIO *
372*********************************************************************************************************************************/
373
374/**
375 * Worker for drvHostAudioPaEnumSourceCallback() and
376 * drvHostAudioPaEnumSinkCallback() that adds an entry to the enumeration
377 * result.
378 */
379static void drvHostAudioPaEnumAddDevice(PPULSEAUDIOENUMCBCTX pCbCtx, PDMAUDIODIR enmDir, const char *pszName,
380 const char *pszDesc, uint8_t cChannelsInput, uint8_t cChannelsOutput,
381 const char *pszDefaultName)
382{
383 size_t const cchName = strlen(pszName);
384 PPULSEAUDIODEVENTRY pDev = (PPULSEAUDIODEVENTRY)PDMAudioHostDevAlloc(RT_UOFFSETOF(PULSEAUDIODEVENTRY, szPulseName)
385 + RT_ALIGN_Z(cchName + 1, 16));
386 if (pDev != NULL)
387 {
388 memcpy(pDev->szPulseName, pszName, cchName);
389 pDev->szPulseName[cchName] = '\0';
390
391 pDev->Core.enmUsage = enmDir;
392 pDev->Core.enmType = RTStrIStr(pszDesc, "built-in") != NULL
393 ? PDMAUDIODEVICETYPE_BUILTIN : PDMAUDIODEVICETYPE_UNKNOWN;
394 pDev->Core.fFlags = RTStrCmp(pszName, pszDefaultName) == 0
395 ? PDMAUDIOHOSTDEV_F_DEFAULT : PDMAUDIOHOSTDEV_F_NONE;
396 pDev->Core.cMaxInputChannels = cChannelsInput;
397 pDev->Core.cMaxOutputChannels = cChannelsOutput;
398 RTStrCopy(pDev->Core.szName, sizeof(pDev->Core.szName),
399 pszDesc && *pszDesc ? pszDesc : pszName);
400
401 PDMAudioHostEnumAppend(pCbCtx->pDeviceEnum, &pDev->Core);
402 }
403 else
404 pCbCtx->rcEnum = VERR_NO_MEMORY;
405}
406
407
408/**
409 * Enumeration callback - source info.
410 *
411 * @param pCtx The context (DRVHOSTPULSEAUDIO::pContext).
412 * @param pInfo The info. NULL when @a eol is not zero.
413 * @param eol Error-or-last indicator or something like that:
414 * - 0: Normal call with info.
415 * - 1: End of list, no info.
416 * - -1: Error callback, no info.
417 * @param pvUserData Pointer to our PULSEAUDIOENUMCBCTX structure.
418 */
419static void drvHostAudioPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
420{
421 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
422 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
423 AssertPtrReturnVoid(pCbCtx);
424 Assert((pInfo == NULL) == (eol != 0));
425 RT_NOREF(pCtx);
426
427 if (eol == 0 && pInfo != NULL)
428 {
429 LogRel2(("Pulse Audio: Source #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
430 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
431 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
432 drvHostAudioPaEnumAddDevice(pCbCtx, PDMAUDIODIR_IN, pInfo->name, pInfo->description,
433 pInfo->sample_spec.channels, 0 /*cChannelsOutput*/, pCbCtx->pszDefaultSource);
434 }
435 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
436 pCbCtx->rcEnum = VINF_SUCCESS;
437
438 /* Wake up the calling thread when done: */
439 if (eol != 0)
440 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
441}
442
443
444/**
445 * Enumeration callback - sink info.
446 *
447 * @param pCtx The context (DRVHOSTPULSEAUDIO::pContext).
448 * @param pInfo The info. NULL when @a eol is not zero.
449 * @param eol Error-or-last indicator or something like that:
450 * - 0: Normal call with info.
451 * - 1: End of list, no info.
452 * - -1: Error callback, no info.
453 * @param pvUserData Pointer to our PULSEAUDIOENUMCBCTX structure.
454 */
455static void drvHostAudioPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
456{
457 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
458 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
459 AssertPtrReturnVoid(pCbCtx);
460 Assert((pInfo == NULL) == (eol != 0));
461 RT_NOREF(pCtx);
462
463 if (eol == 0 && pInfo != NULL)
464 {
465 LogRel2(("Pulse Audio: Sink #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
466 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
467 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
468 drvHostAudioPaEnumAddDevice(pCbCtx, PDMAUDIODIR_OUT, pInfo->name, pInfo->description,
469 0 /*cChannelsInput*/, pInfo->sample_spec.channels, pCbCtx->pszDefaultSink);
470 }
471 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
472 pCbCtx->rcEnum = VINF_SUCCESS;
473
474 /* Wake up the calling thread when done: */
475 if (eol != 0)
476 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
477}
478
479
480/**
481 * Enumeration callback - service info.
482 *
483 * Copy down the default names.
484 */
485static void drvHostAudioPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
486{
487 LogFlowFunc(("pCtx=%p pInfo=%p pvUserData=%p\n", pCtx, pInfo, pvUserData));
488 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
489 AssertPtrReturnVoid(pCbCtx);
490 RT_NOREF(pCtx);
491
492 if (pInfo)
493 {
494 LogRel2(("PulseAudio: Server info: user=%s host=%s ver=%s name=%s defsink=%s defsrc=%s spec: %d %uHz %uch\n",
495 pInfo->user_name, pInfo->host_name, pInfo->server_version, pInfo->server_name,
496 pInfo->default_sink_name, pInfo->default_source_name,
497 pInfo->sample_spec.format, pInfo->sample_spec.rate, pInfo->sample_spec.channels));
498
499 Assert(!pCbCtx->pszDefaultSink);
500 Assert(!pCbCtx->pszDefaultSource);
501 Assert(pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED);
502 pCbCtx->rcEnum = VINF_SUCCESS;
503
504 if (pInfo->default_sink_name)
505 {
506 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
507 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
508 AssertStmt(pCbCtx->pszDefaultSink, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
509 }
510
511 if (pInfo->default_source_name)
512 {
513 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
514 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
515 AssertStmt(pCbCtx->pszDefaultSource, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
516 }
517 }
518 else
519 pCbCtx->rcEnum = VERR_INVALID_POINTER;
520
521 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
522}
523
524
525/**
526 * @note Called with the PA main loop locked.
527 */
528static int drvHostAudioPaEnumerate(PDRVHOSTPULSEAUDIO pThis, uint32_t fEnum, PPDMAUDIOHOSTENUM pDeviceEnum)
529{
530 PULSEAUDIOENUMCBCTX CbCtx = { pThis->pMainLoop, fEnum, VERR_AUDIO_ENUMERATION_FAILED, NULL, NULL, pDeviceEnum };
531 bool const fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
532 bool const fOnlyDefault = (fEnum & PULSEAUDIOENUMCBFLAGS_DEFAULT_ONLY);
533 int rc;
534
535 /*
536 * Check if server information is available and bail out early if it isn't.
537 * This should give us a default (playback) sink and (recording) source.
538 */
539 LogRel(("PulseAudio: Retrieving server information ...\n"));
540 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
541 pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHostAudioPaEnumServerCallback, &CbCtx);
542 if (paOpServerInfo)
543 rc = drvHostAudioPaWaitFor(pThis, paOpServerInfo);
544 else
545 {
546 LogRel(("PulseAudio: Server information not available, skipping enumeration.\n"));
547 return VINF_SUCCESS;
548 }
549 if (RT_SUCCESS(rc))
550 rc = CbCtx.rcEnum;
551 if (RT_FAILURE(rc))
552 {
553 if (fLog)
554 LogRel(("PulseAudio: Error enumerating PulseAudio server properties: %Rrc\n", rc));
555 return rc;
556 }
557
558 /*
559 * Get info about the playback sink.
560 */
561 if (fLog && CbCtx.pszDefaultSink)
562 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
563 else if (fLog)
564 LogRel2(("PulseAudio: No default output sink found\n"));
565
566 if (CbCtx.pszDefaultSink || !fOnlyDefault)
567 {
568 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
569 if (!fOnlyDefault)
570 rc = drvHostAudioPaWaitFor(pThis,
571 pa_context_get_sink_info_list(pThis->pContext, drvHostAudioPaEnumSinkCallback, &CbCtx));
572 else
573 rc = drvHostAudioPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
574 drvHostAudioPaEnumSinkCallback, &CbCtx));
575 if (RT_SUCCESS(rc))
576 rc = CbCtx.rcEnum;
577 if (fLog && RT_FAILURE(rc))
578 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s': %Rrc\n",
579 CbCtx.pszDefaultSink, rc));
580 }
581
582 /*
583 * Get info about the recording source.
584 */
585 if (fLog && CbCtx.pszDefaultSource)
586 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
587 else if (fLog)
588 LogRel2(("PulseAudio: No default input source found\n"));
589 if (CbCtx.pszDefaultSource || !fOnlyDefault)
590 {
591 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
592 int rc2;
593 if (!fOnlyDefault)
594 rc2 = drvHostAudioPaWaitFor(pThis, pa_context_get_source_info_list(pThis->pContext,
595 drvHostAudioPaEnumSourceCallback, &CbCtx));
596 else
597 rc2 = drvHostAudioPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
598 drvHostAudioPaEnumSourceCallback, &CbCtx));
599 if (RT_SUCCESS(rc2))
600 rc2 = CbCtx.rcEnum;
601 if (fLog && RT_FAILURE(rc2))
602 LogRel(("PulseAudio: Error enumerating properties for default input source '%s': %Rrc\n",
603 CbCtx.pszDefaultSource, rc));
604 if (RT_SUCCESS(rc))
605 rc = rc2;
606 }
607
608 /* clean up */
609 RTStrFree(CbCtx.pszDefaultSink);
610 RTStrFree(CbCtx.pszDefaultSource);
611
612 LogFlowFuncLeaveRC(rc);
613 return rc;
614}
615
616
617/**
618 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
619 */
620static DECLCALLBACK(int) drvHostAudioPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
621{
622 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
623 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
624
625 /*
626 * The configuration.
627 */
628 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "PulseAudio");
629 pBackendCfg->cbStreamOut = sizeof(PULSEAUDIOSTREAM);
630 pBackendCfg->cbStreamIn = sizeof(PULSEAUDIOSTREAM);
631 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
632 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
633
634#if 0
635 /*
636 * In case we want to gather info about default devices, we can do this:
637 */
638 PDMAUDIOHOSTENUM DeviceEnum;
639 PDMAudioHostEnumInit(&DeviceEnum);
640 pa_threaded_mainloop_lock(pThis->pMainLoop);
641 int rc = drvHostAudioPaEnumerate(pThis, PULSEAUDIOENUMCBFLAGS_DEFAULT_ONLY | PULSEAUDIOENUMCBFLAGS_LOG, &DeviceEnum);
642 pa_threaded_mainloop_unlock(pThis->pMainLoop);
643 AssertRCReturn(rc, rc);
644 /** @todo do stuff with DeviceEnum. */
645 PDMAudioHostEnumDelete(&DeviceEnum);
646#else
647 RT_NOREF(pThis);
648#endif
649 return VINF_SUCCESS;
650}
651
652
653/**
654 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
655 */
656static DECLCALLBACK(int) drvHostAudioPaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
657{
658 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
659 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
660 PDMAudioHostEnumInit(pDeviceEnum);
661
662 /* Refine it or something (currently only some LogRel2 stuff): */
663 pa_threaded_mainloop_lock(pThis->pMainLoop);
664 int rc = drvHostAudioPaEnumerate(pThis, PULSEAUDIOENUMCBFLAGS_NONE, pDeviceEnum);
665 pa_threaded_mainloop_unlock(pThis->pMainLoop);
666 return rc;
667}
668
669
670/**
671 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
672 */
673static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostAudioPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
674{
675 RT_NOREF(pInterface, enmDir);
676 return PDMAUDIOBACKENDSTS_RUNNING;
677}
678
679
680/**
681 * Stream status changed.
682 */
683static void drvHostAudioPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser)
684{
685 AssertPtrReturnVoid(pStream);
686
687 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
688 AssertPtrReturnVoid(pThis);
689
690 switch (pa_stream_get_state(pStream))
691 {
692 case PA_STREAM_READY:
693 case PA_STREAM_FAILED:
694 case PA_STREAM_TERMINATED:
695 drvHostAudioPaSignalWaiter(pThis);
696 break;
697
698 default:
699 break;
700 }
701}
702
703#ifdef DEBUG
704
705static void drvHostAudioPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext)
706{
707 RT_NOREF(cbLen, pvContext);
708
709 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
710 AssertPtrReturnVoid(pStrm);
711
712 pa_usec_t usec = 0;
713 int neg = 0;
714 pa_stream_get_latency(pStream, &usec, &neg);
715
716 Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000));
717}
718
719
720static void drvHostAudioPaStreamUnderflowDebugCallback(pa_stream *pStream, void *pvContext)
721{
722 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
723 AssertPtrReturnVoid(pStrm);
724
725 pStrm->cUnderflows++;
726
727 LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
728
729 if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
730 && pStrm->cUsLatency < 2000000 /* 2s */)
731 {
732 pStrm->cUsLatency = (pStrm->cUsLatency * 3) / 2;
733
734 LogRel2(("PulseAudio: Output latency increased to %RU64 us\n", pStrm->cUsLatency));
735
736 pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->cUsLatency, &pStrm->SampleSpec);
737 pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->cUsLatency, &pStrm->SampleSpec);
738
739 pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
740
741 pStrm->cUnderflows = 0;
742 }
743
744 pa_usec_t cUsLatency = 0;
745 pa_stream_get_latency(pStream, &cUsLatency, NULL /* Neg */);
746
747 LogRel2(("PulseAudio: Latency now is %RU64 us\n", cUsLatency));
748
749# ifdef LOG_ENABLED
750 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
751 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
752
753 pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
754 pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec);
755 pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
756
757 Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n",
758 curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64,
759 curTsUs / RT_US_1MS_64, cUsLatency / RT_US_1MS_64, pSpec->rate, pSpec->channels));
760# endif
761}
762
763
764static void drvHostAudioPaStreamOverflowDebugCallback(pa_stream *pStream, void *pvContext)
765{
766 RT_NOREF(pStream, pvContext);
767
768 Log2Func(("Warning: Hit overflow\n"));
769}
770
771#endif /* DEBUG */
772
773/**
774 * Converts from PDM PCM properties to pulse audio format.
775 *
776 * Worker for the stream creation code.
777 *
778 * @returns PA format.
779 * @retval PA_SAMPLE_INVALID if format not supported.
780 * @param pProps The PDM audio source properties.
781 */
782static pa_sample_format_t drvHostAudioPaPropsToPulse(PCPDMAUDIOPCMPROPS pProps)
783{
784 switch (PDMAudioPropsSampleSize(pProps))
785 {
786 case 1:
787 if (!PDMAudioPropsIsSigned(pProps))
788 return PA_SAMPLE_U8;
789 break;
790
791 case 2:
792 if (PDMAudioPropsIsSigned(pProps))
793 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
794 break;
795
796#ifdef PA_SAMPLE_S32LE
797 case 4:
798 if (PDMAudioPropsIsSigned(pProps))
799 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE;
800 break;
801#endif
802 }
803
804 AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), PDMAudioPropsIsSigned(pProps) ? "S" : "U"));
805 return PA_SAMPLE_INVALID;
806}
807
808
809/**
810 * Converts from pulse audio sample specification to PDM PCM audio properties.
811 *
812 * Worker for the stream creation code.
813 *
814 * @returns VBox status code.
815 * @param pProps The PDM audio source properties.
816 * @param enmPulseFmt The PA format.
817 * @param cChannels The number of channels.
818 * @param uHz The frequency.
819 */
820static int drvHostAudioPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t enmPulseFmt, uint8_t cChannels, uint32_t uHz)
821{
822 AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
823 AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
824
825 switch (enmPulseFmt)
826 {
827 case PA_SAMPLE_U8:
828 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
829 break;
830
831 case PA_SAMPLE_S16LE:
832 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
833 break;
834
835 case PA_SAMPLE_S16BE:
836 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
837 break;
838
839#ifdef PA_SAMPLE_S32LE
840 case PA_SAMPLE_S32LE:
841 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
842 break;
843#endif
844
845#ifdef PA_SAMPLE_S32BE
846 case PA_SAMPLE_S32BE:
847 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
848 break;
849#endif
850
851 default:
852 AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", enmPulseFmt));
853 return VERR_NOT_SUPPORTED;
854 }
855
856 return VINF_SUCCESS;
857}
858
859
860/**
861 * Worker that does the actual creation of an PA stream.
862 *
863 * @returns VBox status code.
864 * @param pThis Our driver instance data.
865 * @param pStreamPA Our stream data.
866 * @param pszName How we name the stream.
867 * @param pCfgAcq The requested stream properties, the Props member is
868 * updated upon successful return.
869 *
870 * @note Caller owns the mainloop lock.
871 */
872static int drvHostAudioPaStreamCreateLocked(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
873 const char *pszName, PPDMAUDIOSTREAMCFG pCfgAcq)
874{
875 /*
876 * Create the stream.
877 */
878 pa_stream *pStream = pa_stream_new(pThis->pContext, pszName, &pStreamPA->SampleSpec, NULL /* pa_channel_map */);
879 if (!pStream)
880 {
881 LogRel(("PulseAudio: Failed to create stream '%s': %s (%d)\n",
882 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
883 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
884 }
885
886 /*
887 * Set the state callback, and in debug builds a few more...
888 */
889#ifdef DEBUG
890 pa_stream_set_write_callback( pStream, drvHostAudioPaStreamReqWriteDebugCallback, pStreamPA);
891 pa_stream_set_underflow_callback( pStream, drvHostAudioPaStreamUnderflowDebugCallback, pStreamPA);
892 if (pCfgAcq->enmDir == PDMAUDIODIR_OUT)
893 pa_stream_set_overflow_callback(pStream, drvHostAudioPaStreamOverflowDebugCallback, pStreamPA);
894#endif
895 pa_stream_set_state_callback( pStream, drvHostAudioPaStreamStateChangedCallback, pThis);
896
897 /*
898 * Connect the stream.
899 */
900 int rc;
901 unsigned const fFlags = PA_STREAM_START_CORKED /* Require explicit starting (uncorking). */
902 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
903 | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE
904#if PA_API_VERSION >= 12
905 | PA_STREAM_ADJUST_LATENCY
906#endif
907 ;
908 if (pCfgAcq->enmDir == PDMAUDIODIR_IN)
909 {
910 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
911 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.fragsize));
912 rc = pa_stream_connect_record(pStream, NULL /*dev*/, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags);
913 }
914 else
915 {
916 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
917 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.minreq));
918 rc = pa_stream_connect_playback(pStream, NULL /*dev*/, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags,
919 NULL /*volume*/, NULL /*sync_stream*/);
920 }
921 if (rc >= 0)
922 {
923 /*
924 * Wait for the stream to become ready.
925 */
926 uint64_t const nsStart = RTTimeNanoTS();
927 pa_stream_state_t enmStreamState;
928 while ( (enmStreamState = pa_stream_get_state(pStream)) != PA_STREAM_READY
929 && PA_STREAM_IS_GOOD(enmStreamState)
930 && RTTimeNanoTS() - nsStart < RT_NS_10SEC /* not really timed */ )
931 drvHostAudioPaMainloopWait(pThis);
932 if (enmStreamState == PA_STREAM_READY)
933 {
934 LogFunc(("Connecting stream took %'RU64 ns\n", RTTimeNanoTS() - nsStart));
935#ifdef LOG_ENABLED
936 pStreamPA->tsStartUs = pa_rtclock_now();
937#endif
938 /*
939 * Update the buffer attributes.
940 */
941 const pa_buffer_attr *pBufAttribs = pa_stream_get_buffer_attr(pStream);
942 AssertPtr(pBufAttribs);
943 if (pBufAttribs)
944 {
945 pStreamPA->BufAttr = *pBufAttribs;
946 LogFunc(("Obtained %s buffer attributes: maxlength=%RU32 tlength=%RU32 prebuf=%RU32 minreq=%RU32 fragsize=%RU32\n",
947 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pBufAttribs->maxlength, pBufAttribs->tlength,
948 pBufAttribs->prebuf, pBufAttribs->minreq, pBufAttribs->fragsize));
949
950 /*
951 * Convert the sample spec back to PDM speak.
952 * Note! This isn't strictly speaking needed as SampleSpec has *not* been
953 * modified since the caller converted it from pCfgReq.
954 */
955 rc = drvHostAudioPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
956 pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
957 if (RT_SUCCESS(rc))
958 {
959 pStreamPA->pStream = pStream;
960 LogFlowFunc(("returns VINF_SUCCESS\n"));
961 return VINF_SUCCESS;
962 }
963 }
964 else
965 {
966 LogRelMax(99, ("PulseAudio: Failed to get buffer attribs for stream '%s': %s (%d)\n",
967 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
968 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
969 }
970 }
971 else
972 {
973 LogRelMax(99, ("PulseAudio: Failed to initialize stream '%s': state=%d, waited %'RU64 ns\n",
974 pszName, enmStreamState, RTTimeNanoTS() - nsStart));
975 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
976 }
977 pa_stream_disconnect(pStream);
978 }
979 else
980 {
981 LogRelMax(99, ("PulseAudio: Could not connect %s stream '%s': %s (%d/%d)\n",
982 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output",
983 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext), rc));
984 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
985 }
986
987 pa_stream_unref(pStream);
988 Assert(RT_FAILURE_NP(rc));
989 LogFlowFunc(("returns %Rrc\n", rc));
990 return rc;
991}
992
993
994/**
995 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
996 */
997static DECLCALLBACK(int) drvHostAudioPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
998 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
999{
1000 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1001 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1002 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1003 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1004 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1005 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1006 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
1007 int rc;
1008
1009 /*
1010 * Prepare name, sample spec and the stream instance data.
1011 */
1012 char szName[256];
1013 RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]",
1014 pCfgReq->enmDir == PDMAUDIODIR_IN
1015 ? PDMAudioRecSrcGetName(pCfgReq->u.enmSrc) : PDMAudioPlaybackDstGetName(pCfgReq->u.enmDst),
1016 pThis->szStreamName);
1017
1018 pStreamPA->pDrv = pThis;
1019 pStreamPA->pDrainOp = NULL;
1020 pStreamPA->pu8PeekBuf = NULL;
1021 pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
1022 pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
1023 pStreamPA->SampleSpec.format = drvHostAudioPaPropsToPulse(&pCfgReq->Props);
1024
1025 LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n", szName, pStreamPA->SampleSpec.rate,
1026 pStreamPA->SampleSpec.channels, pa_sample_format_to_string(pStreamPA->SampleSpec.format)));
1027
1028 if (pa_sample_spec_valid(&pStreamPA->SampleSpec))
1029 {
1030 /*
1031 * Set up buffer attributes according to the stream type.
1032 */
1033 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
1034 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1035 {
1036 pStreamPA->BufAttr.fragsize = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
1037 LogFunc(("Requesting: BufAttr: fragsize=%RU32 maxlength=-1\n", pStreamPA->BufAttr.fragsize));
1038 }
1039 else
1040 {
1041 pStreamPA->cUsLatency = PDMAudioPropsFramesToMicro(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize);
1042 pStreamPA->BufAttr.tlength = pa_usec_to_bytes(pStreamPA->cUsLatency, &pStreamPA->SampleSpec);
1043 pStreamPA->BufAttr.prebuf = pStreamPA->BufAttr.tlength;
1044 pStreamPA->BufAttr.minreq = PDMAudioPropsFramesToBytes(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod);
1045 LogRel2(("PulseAudio: Initial output latency is %RU64 us (%RU32 bytes)\n",
1046 pStreamPA->cUsLatency, pStreamPA->BufAttr.tlength));
1047 LogFunc(("Requesting: BufAttr: tlength=%RU32 maxLength=%RU32 minReq=%RU32 maxlength=-1\n",
1048 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1049 }
1050
1051 /*
1052 * Do the actual PA stream creation.
1053 */
1054 pa_threaded_mainloop_lock(pThis->pMainLoop);
1055 rc = drvHostAudioPaStreamCreateLocked(pThis, pStreamPA, szName, pCfgAcq);
1056 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1057 if (RT_SUCCESS(rc))
1058 {
1059 /*
1060 * Set the acquired stream config according to the actual buffer
1061 * attributes we got and the stream type.
1062 */
1063 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1064 {
1065 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.fragsize);
1066 pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */
1067 ? PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.maxlength)
1068 : pCfgAcq->Backend.cFramesPeriod * 2 /* whatever */;
1069 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
1070 }
1071 else
1072 {
1073 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.minreq);
1074 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.tlength);
1075 pCfgAcq->Backend.cFramesPreBuffering = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.prebuf);
1076 }
1077 PDMAudioStrmCfgCopy(&pStreamPA->Cfg, pCfgAcq);
1078 }
1079 }
1080 else
1081 {
1082 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", szName));
1083 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1084 }
1085
1086 LogFlowFuncLeaveRC(rc);
1087 return rc;
1088}
1089
1090
1091/**
1092 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1093 */
1094static DECLCALLBACK(int) drvHostAudioPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1095{
1096 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1097 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1098 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1099
1100 if (pStreamPA->pStream)
1101 {
1102 pa_threaded_mainloop_lock(pThis->pMainLoop);
1103
1104 /* Make sure to cancel a pending draining operation, if any. */
1105 if (pStreamPA->pDrainOp)
1106 {
1107 pa_operation_cancel(pStreamPA->pDrainOp);
1108 pStreamPA->pDrainOp = NULL;
1109 }
1110
1111 pa_stream_disconnect(pStreamPA->pStream);
1112 pa_stream_unref(pStreamPA->pStream);
1113
1114 pStreamPA->pStream = NULL;
1115
1116 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1117 }
1118
1119 return VINF_SUCCESS;
1120}
1121
1122
1123/**
1124 * Pulse audio pa_stream_drain() completion callback.
1125 */
1126static void drvHostAudioPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1127{
1128 AssertPtrReturnVoid(pStream);
1129 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
1130 AssertPtrReturnVoid(pStreamPA);
1131
1132 pStreamPA->fOpSuccess = fSuccess;
1133 if (fSuccess)
1134 pa_operation_unref(pa_stream_cork(pStream, 1, drvHostAudioPaStreamSuccessCallback, pvUser));
1135 else
1136 drvHostAudioPaError(pStreamPA->pDrv, "Failed to drain stream");
1137
1138 if (pStreamPA->pDrainOp)
1139 {
1140 pa_operation_unref(pStreamPA->pDrainOp);
1141 pStreamPA->pDrainOp = NULL;
1142 }
1143}
1144
1145
1146static int drvHostAudioPaStreamControlOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1147{
1148 int rc = VINF_SUCCESS;
1149
1150 switch (enmStreamCmd)
1151 {
1152 case PDMAUDIOSTREAMCMD_ENABLE:
1153 case PDMAUDIOSTREAMCMD_RESUME:
1154 {
1155 pa_threaded_mainloop_lock(pThis->pMainLoop);
1156
1157 if ( pStreamPA->pDrainOp
1158 && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
1159 {
1160 pa_operation_cancel(pStreamPA->pDrainOp);
1161 pa_operation_unref(pStreamPA->pDrainOp);
1162
1163 pStreamPA->pDrainOp = NULL;
1164 }
1165 else
1166 {
1167 /* Uncork (resume) stream. */
1168 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1169 }
1170
1171 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1172 break;
1173 }
1174
1175 case PDMAUDIOSTREAMCMD_DISABLE:
1176 case PDMAUDIOSTREAMCMD_PAUSE:
1177 {
1178 /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
1179 * Note that we must return immediately from here! */
1180 pa_threaded_mainloop_lock(pThis->pMainLoop);
1181 if (!pStreamPA->pDrainOp)
1182 {
1183 rc = drvHostAudioPaWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1184 if (RT_SUCCESS(rc))
1185 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHostAudioPaStreamDrainCompletionCallback, pStreamPA);
1186 }
1187 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1188 break;
1189 }
1190
1191 default:
1192 rc = VERR_NOT_SUPPORTED;
1193 break;
1194 }
1195
1196 LogFlowFuncLeaveRC(rc);
1197 return rc;
1198}
1199
1200
1201static int drvHostAudioPaStreamControlIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1202{
1203 int rc = VINF_SUCCESS;
1204
1205 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1206
1207 switch (enmStreamCmd)
1208 {
1209 case PDMAUDIOSTREAMCMD_ENABLE:
1210 case PDMAUDIOSTREAMCMD_RESUME:
1211 {
1212 pa_threaded_mainloop_lock(pThis->pMainLoop);
1213 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1214 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1215 break;
1216 }
1217
1218 case PDMAUDIOSTREAMCMD_DISABLE:
1219 case PDMAUDIOSTREAMCMD_PAUSE:
1220 {
1221 pa_threaded_mainloop_lock(pThis->pMainLoop);
1222 if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
1223 {
1224 pa_stream_drop(pStreamPA->pStream);
1225 pStreamPA->pu8PeekBuf = NULL;
1226 }
1227
1228 rc = drvHostAudioPaWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, drvHostAudioPaStreamSuccessCallback, pStreamPA));
1229 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1230 break;
1231 }
1232
1233 default:
1234 rc = VERR_NOT_SUPPORTED;
1235 break;
1236 }
1237
1238 return rc;
1239}
1240
1241
1242/**
1243 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1244 */
1245static DECLCALLBACK(int) drvHostAudioPaHA_StreamControl(PPDMIHOSTAUDIO pInterface,
1246 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1247{
1248 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1249 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1250 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1251
1252 int rc;
1253 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN)
1254 rc = drvHostAudioPaStreamControlIn (pThis, pStreamPA, enmStreamCmd);
1255 else if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1256 rc = drvHostAudioPaStreamControlOut(pThis, pStreamPA, enmStreamCmd);
1257 else
1258 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1259
1260 return rc;
1261}
1262
1263
1264static uint32_t drvHostAudioPaStreamGetAvailable(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1265{
1266 pa_threaded_mainloop_lock(pThis->pMainLoop);
1267
1268 uint32_t cbAvail = 0;
1269
1270 if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
1271 {
1272 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN)
1273 {
1274 cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
1275 Log3Func(("cbReadable=%RU32\n", cbAvail));
1276 }
1277 else if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1278 {
1279 size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
1280
1281 Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
1282 cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1283
1284 /* Don't report more writable than the PA server can handle. */
1285 if (cbWritable > pStreamPA->BufAttr.maxlength)
1286 cbWritable = pStreamPA->BufAttr.maxlength;
1287
1288 cbAvail = (uint32_t)cbWritable;
1289 }
1290 else
1291 AssertFailed();
1292 }
1293
1294 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1295
1296 return cbAvail;
1297}
1298
1299
1300/**
1301 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1302 */
1303static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1304{
1305 return drvHostAudioPaStreamGetAvailable(RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio),
1306 (PPULSEAUDIOSTREAM)pStream);
1307}
1308
1309
1310/**
1311 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1312 */
1313static DECLCALLBACK(uint32_t) drvHostAudioPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1314{
1315 return drvHostAudioPaStreamGetAvailable(RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio),
1316 (PPULSEAUDIOSTREAM)pStream);
1317}
1318
1319
1320/**
1321 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
1322 */
1323static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostAudioPaHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1324{
1325 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1326 RT_NOREF(pStream);
1327
1328 /* Check PulseAudio's general status. */
1329 PDMAUDIOSTREAMSTS fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_NONE;
1330 if ( pThis->pContext
1331 && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
1332 fStrmSts = PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;
1333
1334 return fStrmSts;
1335}
1336
1337
1338/**
1339 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1340 */
1341static DECLCALLBACK(int) drvHostAudioPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1342 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1343{
1344 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1345 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1346 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1347 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1348 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1349 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1350
1351 pa_threaded_mainloop_lock(pThis->pMainLoop);
1352
1353#ifdef LOG_ENABLED
1354 const pa_usec_t tsNowUs = pa_rtclock_now();
1355 const pa_usec_t tsDeltaPlayedUs = tsNowUs - pStreamPA->tsLastReadWrittenUs;
1356 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1357 Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / RT_US_1MS));
1358#endif
1359
1360 int rc;
1361 size_t const cbWriteable = pa_stream_writable_size(pStreamPA->pStream);
1362 if (cbWriteable != (size_t)-1)
1363 {
1364 size_t cbLeft = RT_MIN(cbWriteable, cbBuf);
1365 Assert(cbLeft > 0 /* At this point we better have *something* to write (DrvAudio checked before calling). */);
1366 if (pa_stream_write(pStreamPA->pStream, pvBuf, cbLeft, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0)
1367 {
1368 *pcbWritten = (uint32_t)cbLeft;
1369 rc = VINF_SUCCESS;
1370 }
1371 else
1372 rc = drvHostAudioPaError(pStreamPA->pDrv, "Failed to write to output stream");
1373 }
1374 else
1375 rc = drvHostAudioPaError(pStreamPA->pDrv, "Failed to determine output data size");
1376
1377 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1378 return rc;
1379}
1380
1381
1382/**
1383 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1384 */
1385static DECLCALLBACK(int) drvHostAudioPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1386 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1387{
1388 PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
1389 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1390 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1391 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1392 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1393 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1394
1395 /* We should only call pa_stream_readable_size() once and trust the first value. */
1396 pa_threaded_mainloop_lock(pThis->pMainLoop);
1397 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
1398 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1399
1400 if (cbAvail == (size_t)-1)
1401 return drvHostAudioPaError(pStreamPA->pDrv, "Failed to determine input data size");
1402
1403 /* If the buffer was not dropped last call, add what remains. */
1404 if (pStreamPA->pu8PeekBuf)
1405 {
1406 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
1407 cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
1408 }
1409
1410 Log3Func(("cbAvail=%zu\n", cbAvail));
1411
1412 if (!cbAvail) /* No data? Bail out. */
1413 {
1414 *pcbRead = 0;
1415 return VINF_SUCCESS;
1416 }
1417
1418 int rc = VINF_SUCCESS;
1419
1420 size_t cbToRead = RT_MIN(cbAvail, cbBuf);
1421
1422 Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
1423 cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
1424
1425 uint32_t cbReadTotal = 0;
1426
1427 while (cbToRead)
1428 {
1429 /* If there is no data, do another peek. */
1430 if (!pStreamPA->pu8PeekBuf)
1431 {
1432 pa_threaded_mainloop_lock(pThis->pMainLoop);
1433 pa_stream_peek(pStreamPA->pStream,
1434 (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
1435 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1436
1437 pStreamPA->offPeekBuf = 0;
1438
1439 /* No data anymore?
1440 * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
1441 * we need to drop the stream lateron. */
1442 if ( !pStreamPA->pu8PeekBuf
1443 && !pStreamPA->cbPeekBuf)
1444 {
1445 break;
1446 }
1447 }
1448
1449 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
1450 size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
1451
1452 Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
1453 cbToRead, cbToWrite,
1454 pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
1455
1456 if ( cbToWrite
1457 /* Only copy data if it's not a data hole (see above). */
1458 && pStreamPA->pu8PeekBuf
1459 && pStreamPA->cbPeekBuf)
1460 {
1461 memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
1462
1463 Assert(cbToRead >= cbToWrite);
1464 cbToRead -= cbToWrite;
1465 cbReadTotal += cbToWrite;
1466
1467 pStreamPA->offPeekBuf += cbToWrite;
1468 Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
1469 }
1470
1471 if (/* Nothing to write anymore? Drop the buffer. */
1472 !cbToWrite
1473 /* Was there a hole in the peeking buffer? Drop it. */
1474 || !pStreamPA->pu8PeekBuf
1475 /* If the buffer is done, drop it. */
1476 || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
1477 {
1478 pa_threaded_mainloop_lock(pThis->pMainLoop);
1479 pa_stream_drop(pStreamPA->pStream);
1480 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1481
1482 pStreamPA->pu8PeekBuf = NULL;
1483 }
1484 }
1485
1486 if (RT_SUCCESS(rc))
1487 *pcbRead = cbReadTotal;
1488 return rc;
1489}
1490
1491
1492/*********************************************************************************************************************************
1493* PDMIBASE *
1494*********************************************************************************************************************************/
1495
1496/**
1497 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1498 */
1499static DECLCALLBACK(void *) drvHostAudioPaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1500{
1501 AssertPtrReturn(pInterface, NULL);
1502 AssertPtrReturn(pszIID, NULL);
1503
1504 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1505 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1506 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1507 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1508
1509 return NULL;
1510}
1511
1512
1513/*********************************************************************************************************************************
1514* PDMDRVREG *
1515*********************************************************************************************************************************/
1516
1517/**
1518 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
1519 */
1520static DECLCALLBACK(void) drvHostAudioPaPowerOff(PPDMDRVINS pDrvIns)
1521{
1522 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1523 LogFlowFuncEnter();
1524
1525 if (pThis->pMainLoop)
1526 pa_threaded_mainloop_stop(pThis->pMainLoop);
1527
1528 if (pThis->pContext)
1529 {
1530 pa_context_disconnect(pThis->pContext);
1531 pa_context_unref(pThis->pContext);
1532 pThis->pContext = NULL;
1533 }
1534
1535 if (pThis->pMainLoop)
1536 {
1537 pa_threaded_mainloop_free(pThis->pMainLoop);
1538 pThis->pMainLoop = NULL;
1539 }
1540
1541 LogFlowFuncLeave();
1542}
1543
1544
1545/**
1546 * Destructs a PulseAudio Audio driver instance.
1547 *
1548 * @copydoc FNPDMDRVDESTRUCT
1549 */
1550static DECLCALLBACK(void) drvHostAudioPaDestruct(PPDMDRVINS pDrvIns)
1551{
1552 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1553 LogFlowFuncEnter();
1554 drvHostAudioPaPowerOff(pDrvIns);
1555 LogFlowFuncLeave();
1556}
1557
1558
1559/**
1560 * Pulse audio callback for context status changes, init variant.
1561 *
1562 * Signalls our event semaphore so we can do a timed wait from
1563 * drvHostAudioPaConstruct().
1564 */
1565static void drvHostAudioPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser)
1566{
1567 AssertPtrReturnVoid(pCtx);
1568 PPULSEAUDIOSTATECHGCTX pStateChgCtx = (PPULSEAUDIOSTATECHGCTX)pvUser;
1569 pa_context_state_t enmCtxState = pa_context_get_state(pCtx);
1570 switch (enmCtxState)
1571 {
1572 case PA_CONTEXT_READY:
1573 case PA_CONTEXT_TERMINATED:
1574 case PA_CONTEXT_FAILED:
1575 AssertPtrReturnVoid(pStateChgCtx);
1576 pStateChgCtx->enmCtxState = enmCtxState;
1577 RTSemEventSignal(pStateChgCtx->hEvtInit);
1578 break;
1579
1580 default:
1581 break;
1582 }
1583}
1584
1585
1586/**
1587 * Constructs a PulseAudio Audio driver instance.
1588 *
1589 * @copydoc FNPDMDRVCONSTRUCT
1590 */
1591static DECLCALLBACK(int) drvHostAudioPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1592{
1593 RT_NOREF(pCfg, fFlags);
1594 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1595 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1596
1597 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1598 LogRel(("Audio: Initializing PulseAudio driver\n"));
1599
1600 /*
1601 * Initialize instance data.
1602 */
1603 pThis->pDrvIns = pDrvIns;
1604 /* IBase */
1605 pDrvIns->IBase.pfnQueryInterface = drvHostAudioPaQueryInterface;
1606 /* IHostAudio */
1607 pThis->IHostAudio.pfnGetConfig = drvHostAudioPaHA_GetConfig;
1608 pThis->IHostAudio.pfnGetDevices = drvHostAudioPaHA_GetDevices;
1609 pThis->IHostAudio.pfnGetStatus = drvHostAudioPaHA_GetStatus;
1610 pThis->IHostAudio.pfnStreamCreate = drvHostAudioPaHA_StreamCreate;
1611 pThis->IHostAudio.pfnStreamDestroy = drvHostAudioPaHA_StreamDestroy;
1612 pThis->IHostAudio.pfnStreamControl = drvHostAudioPaHA_StreamControl;
1613 pThis->IHostAudio.pfnStreamGetReadable = drvHostAudioPaHA_StreamGetReadable;
1614 pThis->IHostAudio.pfnStreamGetWritable = drvHostAudioPaHA_StreamGetWritable;
1615 pThis->IHostAudio.pfnStreamGetPending = NULL;
1616 pThis->IHostAudio.pfnStreamGetStatus = drvHostAudioPaHA_StreamGetStatus;
1617 pThis->IHostAudio.pfnStreamPlay = drvHostAudioPaHA_StreamPlay;
1618 pThis->IHostAudio.pfnStreamCapture = drvHostAudioPaHA_StreamCapture;
1619
1620 /*
1621 * Read configuration.
1622 */
1623 int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
1624 AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
1625
1626 /*
1627 * Load the pulse audio library.
1628 */
1629 int rc = audioLoadPulseLib();
1630 if (RT_SUCCESS(rc))
1631 LogRel(("PulseAudio: Using version %s\n", pa_get_library_version()));
1632 else
1633 {
1634 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
1635 return rc;
1636 }
1637
1638 /*
1639 * Set up the basic pulse audio bits (remember the destructore is always called).
1640 */
1641 //pThis->fAbortLoop = false;
1642 pThis->pMainLoop = pa_threaded_mainloop_new();
1643 if (!pThis->pMainLoop)
1644 {
1645 LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1646 return VERR_NO_MEMORY;
1647 }
1648
1649 pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox");
1650 if (!pThis->pContext)
1651 {
1652 LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1653 return VERR_NO_MEMORY;
1654 }
1655
1656 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
1657 {
1658 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1659 return VERR_AUDIO_BACKEND_INIT_FAILED;
1660 }
1661
1662 /*
1663 * Connect to the pulse audio server.
1664 *
1665 * We install an init state callback so we can do a timed wait in case
1666 * connecting to the pulseaudio server should take too long.
1667 */
1668 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
1669 pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED;
1670 rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit);
1671 AssertLogRelRCReturn(rc, rc);
1672
1673 pa_threaded_mainloop_lock(pThis->pMainLoop);
1674 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx);
1675 if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL))
1676 {
1677 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1678
1679 rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */
1680 if (RT_SUCCESS(rc))
1681 {
1682 if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY)
1683 {
1684 /* Install the main state changed callback to know if something happens to our acquired context. */
1685 pa_threaded_mainloop_lock(pThis->pMainLoop);
1686 pa_context_set_state_callback(pThis->pContext, drvHostAudioPaCtxCallbackStateChanged, pThis /* pvUserData */);
1687 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1688 }
1689 else
1690 {
1691 LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc));
1692 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
1693 }
1694 }
1695 else
1696 {
1697 LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc));
1698 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
1699 }
1700 }
1701 else
1702 {
1703 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1704 LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
1705 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */
1706 }
1707
1708 RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit);
1709 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
1710
1711 return rc;
1712}
1713
1714
1715/**
1716 * Pulse audio driver registration record.
1717 */
1718const PDMDRVREG g_DrvHostPulseAudio =
1719{
1720 /* u32Version */
1721 PDM_DRVREG_VERSION,
1722 /* szName */
1723 "PulseAudio",
1724 /* szRCMod */
1725 "",
1726 /* szR0Mod */
1727 "",
1728 /* pszDescription */
1729 "Pulse Audio host driver",
1730 /* fFlags */
1731 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1732 /* fClass. */
1733 PDM_DRVREG_CLASS_AUDIO,
1734 /* cMaxInstances */
1735 ~0U,
1736 /* cbInstance */
1737 sizeof(DRVHOSTPULSEAUDIO),
1738 /* pfnConstruct */
1739 drvHostAudioPaConstruct,
1740 /* pfnDestruct */
1741 drvHostAudioPaDestruct,
1742 /* pfnRelocate */
1743 NULL,
1744 /* pfnIOCtl */
1745 NULL,
1746 /* pfnPowerOn */
1747 NULL,
1748 /* pfnReset */
1749 NULL,
1750 /* pfnSuspend */
1751 NULL,
1752 /* pfnResume */
1753 NULL,
1754 /* pfnAttach */
1755 NULL,
1756 /* pfnDetach */
1757 NULL,
1758 /* pfnPowerOff */
1759 drvHostAudioPaPowerOff,
1760 /* pfnSoftReset */
1761 NULL,
1762 /* u32EndVersion */
1763 PDM_DRVREG_VERSION
1764};
1765
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