VirtualBox

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

Last change on this file since 91162 was 91162, checked in by vboxsync, 3 years ago

Audio/Validation Kit: Some more diagnostics to find out why some testbox guests refuse to play any test tones. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 94.3 KB
Line 
1/* $Id: DrvHostAudioPulseAudio.cpp 91162 2021-09-08 15:15:55Z vboxsync $ */
2/** @file
3 * Host audio driver - Pulse Audio.
4 */
5
6/*
7 * Copyright (C) 2006-2021 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
23#include <VBox/log.h>
24#include <VBox/vmm/pdmaudioifs.h>
25#include <VBox/vmm/pdmaudioinline.h>
26#include <VBox/vmm/pdmaudiohostenuminline.h>
27
28#include <stdio.h>
29
30#include <iprt/alloc.h>
31#include <iprt/mem.h>
32#include <iprt/uuid.h>
33#include <iprt/semaphore.h>
34
35#include "DrvHostAudioPulseAudioStubsMangling.h"
36#include "DrvHostAudioPulseAudioStubs.h"
37
38#include <pulse/pulseaudio.h>
39#ifndef PA_STREAM_NOFLAGS
40# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
41#endif
42#ifndef PA_CONTEXT_NOFLAGS
43# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
44#endif
45
46#include "VBoxDD.h"
47
48
49/*********************************************************************************************************************************
50* Defines *
51*********************************************************************************************************************************/
52/** Max number of errors reported by drvHstAudPaError per instance.
53 * @todo Make this configurable thru driver config. */
54#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 99
55
56
57/** @name DRVHSTAUDPAENUMCB_F_XXX
58 * @{ */
59/** No flags specified. */
60#define DRVHSTAUDPAENUMCB_F_NONE 0
61/** (Release) log found devices. */
62#define DRVHSTAUDPAENUMCB_F_LOG RT_BIT(0)
63/** Only do default devices. */
64#define DRVHSTAUDPAENUMCB_F_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 DRVHSTAUDPA *PDRVHSTAUDPA;
73
74
75/**
76 * Callback context for the server init context state changed callback.
77 */
78typedef struct DRVHSTAUDPASTATECHGCTX
79{
80 /** The event semaphore. */
81 RTSEMEVENT hEvtInit;
82 /** The returned context state. */
83 pa_context_state_t volatile enmCtxState;
84} DRVHSTAUDPASTATECHGCTX;
85/** Pointer to a server init context state changed callback context. */
86typedef DRVHSTAUDPASTATECHGCTX *PDRVHSTAUDPASTATECHGCTX;
87
88
89/**
90 * Enumeration callback context used by the pfnGetConfig code.
91 */
92typedef struct DRVHSTAUDPAENUMCBCTX
93{
94 /** Pointer to PulseAudio's threaded main loop. */
95 pa_threaded_mainloop *pMainLoop;
96 /** Enumeration flags, DRVHSTAUDPAENUMCB_F_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} DRVHSTAUDPAENUMCBCTX;
109/** Pointer to an enumeration callback context. */
110typedef DRVHSTAUDPAENUMCBCTX *PDRVHSTAUDPAENUMCBCTX;
111
112
113/**
114 * Pulse audio device enumeration entry.
115 */
116typedef struct DRVHSTAUDPADEVENTRY
117{
118 /** The part we share with others. */
119 PDMAUDIOHOSTDEV Core;
120} DRVHSTAUDPADEVENTRY;
121/** Pointer to a pulse audio device enumeration entry. */
122typedef DRVHSTAUDPADEVENTRY *PDRVHSTAUDPADEVENTRY;
123
124
125/**
126 * Pulse audio stream data.
127 */
128typedef struct DRVHSTAUDPASTREAM
129{
130 /** Common part. */
131 PDMAUDIOBACKENDSTREAM Core;
132 /** The stream's acquired configuration. */
133 PDMAUDIOSTREAMCFG Cfg;
134 /** Pointer to driver instance. */
135 PDRVHSTAUDPA pDrv;
136 /** Pointer to opaque PulseAudio stream. */
137 pa_stream *pStream;
138 /** Input: Pointer to Pulse sample peek buffer. */
139 const uint8_t *pbPeekBuf;
140 /** Input: Current size (in bytes) of peeked data in buffer. */
141 size_t cbPeekBuf;
142 /** Input: Our offset (in bytes) in peek data buffer. */
143 size_t offPeekBuf;
144 /** Output: Asynchronous drain operation. This is used as an indicator of
145 * whether we're currently draining the stream (will be cleaned up before
146 * resume/re-enable). */
147 pa_operation *pDrainOp;
148 /** Asynchronous cork/uncork operation.
149 * (This solely for cancelling before destroying the stream, so the callback
150 * won't do any after-freed accesses.) */
151 pa_operation *pCorkOp;
152 /** Asynchronous trigger operation.
153 * (This solely for cancelling before destroying the stream, so the callback
154 * won't do any after-freed accesses.) */
155 pa_operation *pTriggerOp;
156 /** Internal byte offset. */
157 uint64_t offInternal;
158#ifdef LOG_ENABLED
159 /** Creation timestamp (in microsecs) of stream playback / recording. */
160 pa_usec_t tsStartUs;
161 /** Timestamp (in microsecs) when last read from / written to the stream. */
162 pa_usec_t tsLastReadWrittenUs;
163#endif
164 /** Number of occurred audio data underflows. */
165 uint32_t cUnderflows;
166 /** Pulse sample format and attribute specification. */
167 pa_sample_spec SampleSpec;
168 /** Channel map. */
169 pa_channel_map ChannelMap;
170 /** Pulse playback and buffer metrics. */
171 pa_buffer_attr BufAttr;
172} DRVHSTAUDPASTREAM;
173/** Pointer to pulse audio stream data. */
174typedef DRVHSTAUDPASTREAM *PDRVHSTAUDPASTREAM;
175
176
177/**
178 * Pulse audio host audio driver instance data.
179 * @implements PDMIAUDIOCONNECTOR
180 */
181typedef struct DRVHSTAUDPA
182{
183 /** Pointer to the driver instance structure. */
184 PPDMDRVINS pDrvIns;
185 /** Pointer to PulseAudio's threaded main loop. */
186 pa_threaded_mainloop *pMainLoop;
187 /**
188 * Pointer to our PulseAudio context.
189 * @note We use a pMainLoop in a separate thread (pContext).
190 * So either use callback functions or protect these functions
191 * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
192 */
193 pa_context *pContext;
194 /** Shutdown indicator. */
195 volatile bool fAbortLoop;
196 /** Error count for not flooding the release log.
197 * Specify UINT32_MAX for unlimited logging. */
198 uint32_t cLogErrors;
199 /** Don't want to put this on the stack... */
200 DRVHSTAUDPASTATECHGCTX InitStateChgCtx;
201 /** Pointer to host audio interface. */
202 PDMIHOSTAUDIO IHostAudio;
203 /** Upwards notification interface. */
204 PPDMIHOSTAUDIOPORT pIHostAudioPort;
205
206 /** The stream (base) name.
207 * This is needed for distinguishing streams in the PulseAudio mixer controls if
208 * multiple VMs are running at the same time. */
209 char szStreamName[64];
210 /** The name of the input device to use. Empty string for default. */
211 char szInputDev[256];
212 /** The name of the output device to use. Empty string for default. */
213 char szOutputDev[256];
214
215 /** Number of buffer underruns (for all streams). */
216 STAMCOUNTER StatUnderruns;
217 /** Number of buffer overruns (for all streams). */
218 STAMCOUNTER StatOverruns;
219} DRVHSTAUDPA;
220
221
222
223/*
224 * Glue to make the code work systems with PulseAudio < 0.9.11.
225 */
226#if !defined(PA_CONTEXT_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
227DECLINLINE(bool) PA_CONTEXT_IS_GOOD(pa_context_state_t enmState)
228{
229 return enmState == PA_CONTEXT_CONNECTING
230 || enmState == PA_CONTEXT_AUTHORIZING
231 || enmState == PA_CONTEXT_SETTING_NAME
232 || enmState == PA_CONTEXT_READY;
233}
234#endif
235
236#if !defined(PA_STREAM_IS_GOOD) && PA_API_VERSION < 12 /* 12 = 0.9.11 where PA_STREAM_IS_GOOD was added */
237DECLINLINE(bool) PA_STREAM_IS_GOOD(pa_stream_state_t enmState)
238{
239 return enmState == PA_STREAM_CREATING
240 || enmState == PA_STREAM_READY;
241}
242#endif
243
244
245/**
246 * Converts a pulse audio error to a VBox status.
247 *
248 * @returns VBox status code.
249 * @param rcPa The error code to convert.
250 */
251static int drvHstAudPaErrorToVBox(int rcPa)
252{
253 /** @todo Implement some PulseAudio -> VBox mapping here. */
254 RT_NOREF(rcPa);
255 return VERR_GENERAL_FAILURE;
256}
257
258
259/**
260 * Logs a pulse audio (from context) and converts it to VBox status.
261 *
262 * @returns VBox status code.
263 * @param pThis Our instance data.
264 * @param pszFormat The format string for the release log (no newline) .
265 * @param ... Format string arguments.
266 */
267static int drvHstAudPaError(PDRVHSTAUDPA pThis, const char *pszFormat, ...)
268{
269 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
270 AssertPtr(pszFormat);
271
272 int const rcPa = pa_context_errno(pThis->pContext);
273 int const rcVBox = drvHstAudPaErrorToVBox(rcPa);
274
275 if ( pThis->cLogErrors < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS
276 && LogRelIs2Enabled())
277 {
278 va_list va;
279 va_start(va, pszFormat);
280 LogRel(("PulseAudio: %N: %s (%d, %Rrc)\n", pszFormat, &va, pa_strerror(rcPa), rcPa, rcVBox));
281 va_end(va);
282
283 if (++pThis->cLogErrors == VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
284 LogRel(("PulseAudio: muting errors (max %u)\n", VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS));
285 }
286
287 return rcVBox;
288}
289
290
291/**
292 * Signal the main loop to abort. Just signalling isn't sufficient as the
293 * mainloop might not have been entered yet.
294 */
295static void drvHstAudPaSignalWaiter(PDRVHSTAUDPA pThis)
296{
297 if (pThis)
298 {
299 pThis->fAbortLoop = true;
300 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
301 }
302}
303
304
305/**
306 * Wrapper around pa_threaded_mainloop_wait().
307 */
308static void drvHstAudPaMainloopWait(PDRVHSTAUDPA pThis)
309{
310 /** @todo r=bird: explain this logic. */
311 if (!pThis->fAbortLoop)
312 pa_threaded_mainloop_wait(pThis->pMainLoop);
313 pThis->fAbortLoop = false;
314}
315
316
317/**
318 * Pulse audio callback for context status changes, init variant.
319 */
320static void drvHstAudPaCtxCallbackStateChanged(pa_context *pCtx, void *pvUser)
321{
322 AssertPtrReturnVoid(pCtx);
323
324 PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
325 AssertPtrReturnVoid(pThis);
326
327 switch (pa_context_get_state(pCtx))
328 {
329 case PA_CONTEXT_READY:
330 case PA_CONTEXT_TERMINATED:
331 case PA_CONTEXT_FAILED:
332 drvHstAudPaSignalWaiter(pThis);
333 break;
334
335 default:
336 break;
337 }
338}
339
340
341/**
342 * Synchronously wait until an operation completed.
343 *
344 * This will consume the pOperation reference.
345 */
346static int drvHstAudPaWaitForEx(PDRVHSTAUDPA pThis, pa_operation *pOperation, RTMSINTERVAL cMsTimeout)
347{
348 AssertPtrReturn(pOperation, VERR_INVALID_POINTER);
349
350 uint64_t const msStart = RTTimeMilliTS();
351 pa_operation_state_t enmOpState;
352 while ((enmOpState = pa_operation_get_state(pOperation)) == PA_OPERATION_RUNNING)
353 {
354 if (!pThis->fAbortLoop) /** @todo r=bird: I do _not_ get the logic behind this fAbortLoop mechanism, it looks more
355 * than a little mixed up and too much generalized see drvHstAudPaSignalWaiter. */
356 {
357 AssertPtr(pThis->pMainLoop);
358 pa_threaded_mainloop_wait(pThis->pMainLoop);
359 if ( !pThis->pContext
360 || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
361 {
362 pa_operation_cancel(pOperation);
363 pa_operation_unref(pOperation);
364 LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
365 return VERR_INVALID_STATE;
366 }
367 }
368 pThis->fAbortLoop = false;
369
370 /*
371 * Note! This timeout business is a bit bogus as pa_threaded_mainloop_wait is indefinite.
372 */
373 if (RTTimeMilliTS() - msStart >= cMsTimeout)
374 {
375 enmOpState = pa_operation_get_state(pOperation);
376 if (enmOpState != PA_OPERATION_RUNNING)
377 break;
378 pa_operation_cancel(pOperation);
379 pa_operation_unref(pOperation);
380 return VERR_TIMEOUT;
381 }
382 }
383
384 pa_operation_unref(pOperation);
385 if (enmOpState == PA_OPERATION_DONE)
386 return VINF_SUCCESS;
387 return VERR_CANCELLED;
388}
389
390
391static int drvHstAudPaWaitFor(PDRVHSTAUDPA pThis, pa_operation *pOP)
392{
393 return drvHstAudPaWaitForEx(pThis, pOP, 10 * RT_MS_1SEC);
394}
395
396
397
398/*********************************************************************************************************************************
399* PDMIHOSTAUDIO *
400*********************************************************************************************************************************/
401
402/**
403 * Worker for drvHstAudPaEnumSourceCallback() and
404 * drvHstAudPaEnumSinkCallback() that adds an entry to the enumeration
405 * result.
406 */
407static void drvHstAudPaEnumAddDevice(PDRVHSTAUDPAENUMCBCTX pCbCtx, PDMAUDIODIR enmDir, const char *pszName,
408 const char *pszDesc, uint8_t cChannelsInput, uint8_t cChannelsOutput,
409 const char *pszDefaultName)
410{
411 size_t const cbId = strlen(pszName) + 1;
412 size_t const cbName = pszDesc && *pszDesc ? strlen(pszDesc) + 1 : cbId;
413 PDRVHSTAUDPADEVENTRY pDev = (PDRVHSTAUDPADEVENTRY)PDMAudioHostDevAlloc(sizeof(*pDev), cbName, cbId);
414 if (pDev != NULL)
415 {
416 pDev->Core.enmUsage = enmDir;
417 pDev->Core.enmType = RTStrIStr(pszDesc, "built-in") != NULL
418 ? PDMAUDIODEVICETYPE_BUILTIN : PDMAUDIODEVICETYPE_UNKNOWN;
419 if (RTStrCmp(pszName, pszDefaultName) != 0)
420 pDev->Core.fFlags = PDMAUDIOHOSTDEV_F_NONE;
421 else
422 pDev->Core.fFlags = enmDir == PDMAUDIODIR_IN ? PDMAUDIOHOSTDEV_F_DEFAULT_IN : PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
423 pDev->Core.cMaxInputChannels = cChannelsInput;
424 pDev->Core.cMaxOutputChannels = cChannelsOutput;
425
426 int rc = RTStrCopy(pDev->Core.pszId, cbId, pszName);
427 AssertRC(rc);
428
429 rc = RTStrCopy(pDev->Core.pszName, cbName, pszDesc && *pszDesc ? pszDesc : pszName);
430 AssertRC(rc);
431
432 PDMAudioHostEnumAppend(pCbCtx->pDeviceEnum, &pDev->Core);
433 }
434 else
435 pCbCtx->rcEnum = VERR_NO_MEMORY;
436}
437
438
439/**
440 * Enumeration callback - source info.
441 *
442 * @param pCtx The context (DRVHSTAUDPA::pContext).
443 * @param pInfo The info. NULL when @a eol is not zero.
444 * @param eol Error-or-last indicator or something like that:
445 * - 0: Normal call with info.
446 * - 1: End of list, no info.
447 * - -1: Error callback, no info.
448 * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure.
449 */
450static void drvHstAudPaEnumSourceCallback(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
451{
452 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
453 PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
454 AssertPtrReturnVoid(pCbCtx);
455 Assert((pInfo == NULL) == (eol != 0));
456 RT_NOREF(pCtx);
457
458 if (eol == 0 && pInfo != NULL)
459 {
460 LogRel2(("PulseAudio: Source #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
461 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
462 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
463 drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_IN, pInfo->name, pInfo->description,
464 pInfo->sample_spec.channels, 0 /*cChannelsOutput*/, pCbCtx->pszDefaultSource);
465 }
466 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
467 pCbCtx->rcEnum = VINF_SUCCESS;
468
469 /* Wake up the calling thread when done: */
470 if (eol != 0)
471 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
472}
473
474
475/**
476 * Enumeration callback - sink info.
477 *
478 * @param pCtx The context (DRVHSTAUDPA::pContext).
479 * @param pInfo The info. NULL when @a eol is not zero.
480 * @param eol Error-or-last indicator or something like that:
481 * - 0: Normal call with info.
482 * - 1: End of list, no info.
483 * - -1: Error callback, no info.
484 * @param pvUserData Pointer to our DRVHSTAUDPAENUMCBCTX structure.
485 */
486static void drvHstAudPaEnumSinkCallback(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
487{
488 LogFlowFunc(("pCtx=%p pInfo=%p eol=%d pvUserData=%p\n", pCtx, pInfo, eol, pvUserData));
489 PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
490 AssertPtrReturnVoid(pCbCtx);
491 Assert((pInfo == NULL) == (eol != 0));
492 RT_NOREF(pCtx);
493
494 if (eol == 0 && pInfo != NULL)
495 {
496 LogRel2(("PulseAudio: Sink #%u: %u Hz %uch format=%u name='%s' desc='%s' driver='%s' flags=%#x\n",
497 pInfo->index, pInfo->sample_spec.rate, pInfo->sample_spec.channels, pInfo->sample_spec.format,
498 pInfo->name, pInfo->description, pInfo->driver, pInfo->flags));
499 drvHstAudPaEnumAddDevice(pCbCtx, PDMAUDIODIR_OUT, pInfo->name, pInfo->description,
500 0 /*cChannelsInput*/, pInfo->sample_spec.channels, pCbCtx->pszDefaultSink);
501 }
502 else if (eol == 1 && !pInfo && pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED)
503 pCbCtx->rcEnum = VINF_SUCCESS;
504
505 /* Wake up the calling thread when done: */
506 if (eol != 0)
507 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
508}
509
510
511/**
512 * Enumeration callback - service info.
513 *
514 * Copy down the default names.
515 */
516static void drvHstAudPaEnumServerCallback(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
517{
518 LogFlowFunc(("pCtx=%p pInfo=%p pvUserData=%p\n", pCtx, pInfo, pvUserData));
519 PDRVHSTAUDPAENUMCBCTX pCbCtx = (PDRVHSTAUDPAENUMCBCTX)pvUserData;
520 AssertPtrReturnVoid(pCbCtx);
521 RT_NOREF(pCtx);
522
523 if (pInfo)
524 {
525 LogRel2(("PulseAudio: Server info: user=%s host=%s ver=%s name=%s defsink=%s defsrc=%s spec: %d %uHz %uch\n",
526 pInfo->user_name, pInfo->host_name, pInfo->server_version, pInfo->server_name,
527 pInfo->default_sink_name, pInfo->default_source_name,
528 pInfo->sample_spec.format, pInfo->sample_spec.rate, pInfo->sample_spec.channels));
529
530 Assert(!pCbCtx->pszDefaultSink);
531 Assert(!pCbCtx->pszDefaultSource);
532 Assert(pCbCtx->rcEnum == VERR_AUDIO_ENUMERATION_FAILED);
533 pCbCtx->rcEnum = VINF_SUCCESS;
534
535 if (pInfo->default_sink_name)
536 {
537 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
538 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
539 AssertStmt(pCbCtx->pszDefaultSink, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
540 }
541
542 if (pInfo->default_source_name)
543 {
544 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
545 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
546 AssertStmt(pCbCtx->pszDefaultSource, pCbCtx->rcEnum = VERR_NO_STR_MEMORY);
547 }
548 }
549 else
550 pCbCtx->rcEnum = VERR_INVALID_POINTER;
551
552 pa_threaded_mainloop_signal(pCbCtx->pMainLoop, 0);
553}
554
555
556/**
557 * @note Called with the PA main loop locked.
558 */
559static int drvHstAudPaEnumerate(PDRVHSTAUDPA pThis, uint32_t fEnum, PPDMAUDIOHOSTENUM pDeviceEnum)
560{
561 DRVHSTAUDPAENUMCBCTX CbCtx = { pThis->pMainLoop, fEnum, VERR_AUDIO_ENUMERATION_FAILED, NULL, NULL, pDeviceEnum };
562 bool const fLog = (fEnum & DRVHSTAUDPAENUMCB_F_LOG);
563 bool const fOnlyDefault = (fEnum & DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY);
564 int rc;
565
566 /*
567 * Check if server information is available and bail out early if it isn't.
568 * This should give us a default (playback) sink and (recording) source.
569 */
570 LogRel(("PulseAudio: Retrieving server information ...\n"));
571 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
572 pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, drvHstAudPaEnumServerCallback, &CbCtx);
573 if (paOpServerInfo)
574 rc = drvHstAudPaWaitFor(pThis, paOpServerInfo);
575 else
576 {
577 LogRel(("PulseAudio: Server information not available, skipping enumeration.\n"));
578 return VINF_SUCCESS;
579 }
580 if (RT_SUCCESS(rc))
581 rc = CbCtx.rcEnum;
582 if (RT_FAILURE(rc))
583 {
584 if (fLog)
585 LogRel(("PulseAudio: Error enumerating PulseAudio server properties: %Rrc\n", rc));
586 return rc;
587 }
588
589 /*
590 * Get info about the playback sink.
591 */
592 if (fLog && CbCtx.pszDefaultSink)
593 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
594 else if (fLog)
595 LogRel2(("PulseAudio: No default output sink found\n"));
596
597 if (CbCtx.pszDefaultSink || !fOnlyDefault)
598 {
599 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
600 if (!fOnlyDefault)
601 rc = drvHstAudPaWaitFor(pThis,
602 pa_context_get_sink_info_list(pThis->pContext, drvHstAudPaEnumSinkCallback, &CbCtx));
603 else
604 rc = drvHstAudPaWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
605 drvHstAudPaEnumSinkCallback, &CbCtx));
606 if (RT_SUCCESS(rc))
607 rc = CbCtx.rcEnum;
608 if (fLog && RT_FAILURE(rc))
609 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s': %Rrc\n",
610 CbCtx.pszDefaultSink, rc));
611 }
612
613 /*
614 * Get info about the recording source.
615 */
616 if (fLog && CbCtx.pszDefaultSource)
617 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
618 else if (fLog)
619 LogRel2(("PulseAudio: No default input source found\n"));
620 if (CbCtx.pszDefaultSource || !fOnlyDefault)
621 {
622 CbCtx.rcEnum = VERR_AUDIO_ENUMERATION_FAILED;
623 int rc2;
624 if (!fOnlyDefault)
625 rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_list(pThis->pContext,
626 drvHstAudPaEnumSourceCallback, &CbCtx));
627 else
628 rc2 = drvHstAudPaWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
629 drvHstAudPaEnumSourceCallback, &CbCtx));
630 if (RT_SUCCESS(rc2))
631 rc2 = CbCtx.rcEnum;
632 if (fLog && RT_FAILURE(rc2))
633 LogRel(("PulseAudio: Error enumerating properties for default input source '%s': %Rrc\n",
634 CbCtx.pszDefaultSource, rc));
635 if (RT_SUCCESS(rc))
636 rc = rc2;
637 }
638
639 /* clean up */
640 RTStrFree(CbCtx.pszDefaultSink);
641 RTStrFree(CbCtx.pszDefaultSource);
642
643 LogFlowFuncLeaveRC(rc);
644 return rc;
645}
646
647
648/**
649 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
650 */
651static DECLCALLBACK(int) drvHstAudPaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
652{
653 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
654 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
655
656 /*
657 * The configuration.
658 */
659 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "PulseAudio");
660 pBackendCfg->cbStream = sizeof(DRVHSTAUDPASTREAM);
661 pBackendCfg->fFlags = 0;
662 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
663 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
664
665#if 0
666 /*
667 * In case we want to gather info about default devices, we can do this:
668 */
669 PDMAUDIOHOSTENUM DeviceEnum;
670 PDMAudioHostEnumInit(&DeviceEnum);
671 pa_threaded_mainloop_lock(pThis->pMainLoop);
672 int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_DEFAULT_ONLY | DRVHSTAUDPAENUMCB_F_LOG, &DeviceEnum);
673 pa_threaded_mainloop_unlock(pThis->pMainLoop);
674 AssertRCReturn(rc, rc);
675 /** @todo do stuff with DeviceEnum. */
676 PDMAudioHostEnumDelete(&DeviceEnum);
677#else
678 RT_NOREF(pThis);
679#endif
680 return VINF_SUCCESS;
681}
682
683
684/**
685 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
686 */
687static DECLCALLBACK(int) drvHstAudPaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
688{
689 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
690 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
691 PDMAudioHostEnumInit(pDeviceEnum);
692
693 /* Refine it or something (currently only some LogRel2 stuff): */
694 pa_threaded_mainloop_lock(pThis->pMainLoop);
695 int rc = drvHstAudPaEnumerate(pThis, DRVHSTAUDPAENUMCB_F_NONE, pDeviceEnum);
696 pa_threaded_mainloop_unlock(pThis->pMainLoop);
697 return rc;
698}
699
700
701/**
702 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
703 */
704static DECLCALLBACK(int) drvHstAudPaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
705{
706 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
707
708 /*
709 * Validate and normalize input.
710 */
711 AssertReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, VERR_INVALID_PARAMETER);
712 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
713 if (!pszId || !*pszId)
714 pszId = "";
715 else
716 {
717 size_t cch = strlen(pszId);
718 AssertReturn(cch < sizeof(pThis->szInputDev), VERR_INVALID_NAME);
719 }
720 LogFunc(("enmDir=%d pszId=%s\n", enmDir, pszId));
721
722 /*
723 * Update input.
724 */
725 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
726 {
727 pa_threaded_mainloop_lock(pThis->pMainLoop);
728 if (strcmp(pThis->szInputDev, pszId) == 0)
729 pa_threaded_mainloop_unlock(pThis->pMainLoop);
730 else
731 {
732 LogRel(("PulseAudio: Changing input device: '%s' -> '%s'\n", pThis->szInputDev, pszId));
733 RTStrCopy(pThis->szInputDev, sizeof(pThis->szInputDev), pszId);
734 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
735 pa_threaded_mainloop_unlock(pThis->pMainLoop);
736 if (pIHostAudioPort)
737 {
738 LogFlowFunc(("Notifying parent driver about input device change...\n"));
739 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_IN, NULL /*pvUser*/);
740 }
741 }
742 }
743
744 /*
745 * Update output.
746 */
747 if (enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX)
748 {
749 pa_threaded_mainloop_lock(pThis->pMainLoop);
750 if (strcmp(pThis->szOutputDev, pszId) == 0)
751 pa_threaded_mainloop_unlock(pThis->pMainLoop);
752 else
753 {
754 LogRel(("PulseAudio: Changing output device: '%s' -> '%s'\n", pThis->szOutputDev, pszId));
755 RTStrCopy(pThis->szOutputDev, sizeof(pThis->szOutputDev), pszId);
756 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
757 pa_threaded_mainloop_unlock(pThis->pMainLoop);
758 if (pIHostAudioPort)
759 {
760 LogFlowFunc(("Notifying parent driver about output device change...\n"));
761 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, PDMAUDIODIR_OUT, NULL /*pvUser*/);
762 }
763 }
764 }
765
766 return VINF_SUCCESS;
767}
768
769
770
771
772/**
773 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
774 */
775static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudPaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
776{
777 RT_NOREF(pInterface, enmDir);
778 return PDMAUDIOBACKENDSTS_RUNNING;
779}
780
781
782/**
783 * Stream status changed.
784 */
785static void drvHstAudPaStreamStateChangedCallback(pa_stream *pStream, void *pvUser)
786{
787 AssertPtrReturnVoid(pStream);
788
789 PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
790 AssertPtrReturnVoid(pThis);
791
792 switch (pa_stream_get_state(pStream))
793 {
794 case PA_STREAM_READY:
795 case PA_STREAM_FAILED:
796 case PA_STREAM_TERMINATED:
797 drvHstAudPaSignalWaiter(pThis);
798 break;
799
800 default:
801 break;
802 }
803}
804
805
806/**
807 * Underflow notification.
808 */
809static void drvHstAudPaStreamUnderflowStatsCallback(pa_stream *pStream, void *pvContext)
810{
811 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext;
812 AssertPtrReturnVoid(pStreamPA);
813 AssertPtrReturnVoid(pStreamPA->pDrv);
814
815 /* This may happen when draining/corking, so don't count those. */
816 if (!pStreamPA->pDrainOp)
817 STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatUnderruns);
818
819 pStreamPA->cUnderflows++;
820
821 LogRel2(("PulseAudio: Warning: Hit underflow #%RU32%s%s\n", pStreamPA->cUnderflows,
822 pStreamPA->pDrainOp && pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING ? " (draining)" : "",
823 pStreamPA->pCorkOp && pa_operation_get_state(pStreamPA->pCorkOp) == PA_OPERATION_RUNNING ? " (corking)" : "" ));
824
825 if (LogRelIs2Enabled() || LogIs2Enabled())
826 {
827 pa_usec_t cUsLatency = 0;
828 int fNegative = 0;
829 pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
830 LogRel2(("PulseAudio: Latency now is %'RU64 us\n", cUsLatency));
831
832# ifdef LOG_ENABLED
833 if (LogRelIs2Enabled())
834 {
835 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
836 AssertReturnVoid(pTInfo);
837 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
838 AssertReturnVoid(pSpec);
839 LogRel2(("PulseAudio: Timing info: writepos=%'RU64 us, readpost=%'RU64 us, age=%'RU64 us, latency=%'RU64 us (%RU32Hz %RU8ch)\n",
840 pa_bytes_to_usec(pTInfo->write_index, pSpec), pa_bytes_to_usec(pTInfo->read_index, pSpec),
841 pa_rtclock_now() - pStreamPA->tsStartUs, cUsLatency, pSpec->rate, pSpec->channels));
842 }
843# endif
844 }
845}
846
847
848/**
849 * Overflow notification.
850 */
851static void drvHstAudPaStreamOverflowStatsCallback(pa_stream *pStream, void *pvContext)
852{
853 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvContext;
854 AssertPtrReturnVoid(pStreamPA);
855 AssertPtrReturnVoid(pStreamPA->pDrv);
856
857 STAM_REL_COUNTER_INC(&pStreamPA->pDrv->StatOverruns);
858 LogRel2(("PulseAudio: Warning: Hit overflow.\n"));
859 RT_NOREF(pStream);
860}
861
862
863#ifdef DEBUG
864/**
865 * Debug PA callback: Need data to output.
866 */
867static void drvHstAudPaStreamReqWriteDebugCallback(pa_stream *pStream, size_t cbLen, void *pvContext)
868{
869 RT_NOREF(cbLen, pvContext);
870 pa_usec_t cUsLatency = 0;
871 int fNegative = 0;
872 int rcPa = pa_stream_get_latency(pStream, &cUsLatency, &fNegative);
873 Log2Func(("Requesting %zu bytes; Latency: %'RU64 us (rcPa=%d n=%d)\n", cbLen, cUsLatency, rcPa, fNegative));
874}
875#endif /* DEBUG */
876
877/**
878 * Converts from PDM PCM properties to pulse audio format.
879 *
880 * Worker for the stream creation code.
881 *
882 * @returns PA format.
883 * @retval PA_SAMPLE_INVALID if format not supported.
884 * @param pProps The PDM audio source properties.
885 */
886static pa_sample_format_t drvHstAudPaPropsToPulse(PCPDMAUDIOPCMPROPS pProps)
887{
888 switch (PDMAudioPropsSampleSize(pProps))
889 {
890 case 1:
891 if (!PDMAudioPropsIsSigned(pProps))
892 return PA_SAMPLE_U8;
893 break;
894
895 case 2:
896 if (PDMAudioPropsIsSigned(pProps))
897 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE;
898 break;
899
900#ifdef PA_SAMPLE_S32LE
901 case 4:
902 if (PDMAudioPropsIsSigned(pProps))
903 return PDMAudioPropsIsLittleEndian(pProps) ? PA_SAMPLE_S32LE : PA_SAMPLE_S32BE;
904 break;
905#endif
906 }
907
908 AssertMsgFailed(("%RU8%s not supported\n", PDMAudioPropsSampleSize(pProps), PDMAudioPropsIsSigned(pProps) ? "S" : "U"));
909 return PA_SAMPLE_INVALID;
910}
911
912
913/**
914 * Converts from pulse audio sample specification to PDM PCM audio properties.
915 *
916 * Worker for the stream creation code.
917 *
918 * @returns VBox status code.
919 * @param pProps The PDM audio source properties.
920 * @param enmPulseFmt The PA format.
921 * @param cChannels The number of channels.
922 * @param uHz The frequency.
923 */
924static int drvHstAudPaToAudioProps(PPDMAUDIOPCMPROPS pProps, pa_sample_format_t enmPulseFmt, uint8_t cChannels, uint32_t uHz)
925{
926 AssertReturn(cChannels > 0, VERR_INVALID_PARAMETER);
927 AssertReturn(cChannels < 16, VERR_INVALID_PARAMETER);
928
929 switch (enmPulseFmt)
930 {
931 case PA_SAMPLE_U8:
932 PDMAudioPropsInit(pProps, 1 /*8-bit*/, false /*signed*/, cChannels, uHz);
933 break;
934
935 case PA_SAMPLE_S16LE:
936 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
937 break;
938
939 case PA_SAMPLE_S16BE:
940 PDMAudioPropsInitEx(pProps, 2 /*16-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
941 break;
942
943#ifdef PA_SAMPLE_S32LE
944 case PA_SAMPLE_S32LE:
945 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, true /*fLittleEndian*/, false /*fRaw*/);
946 break;
947#endif
948
949#ifdef PA_SAMPLE_S32BE
950 case PA_SAMPLE_S32BE:
951 PDMAudioPropsInitEx(pProps, 4 /*32-bit*/, true /*signed*/, cChannels, uHz, false /*fLittleEndian*/, false /*fRaw*/);
952 break;
953#endif
954
955 default:
956 AssertLogRelMsgFailed(("PulseAudio: Format (%d) not supported\n", enmPulseFmt));
957 return VERR_NOT_SUPPORTED;
958 }
959
960 return VINF_SUCCESS;
961}
962
963
964#if 0 /* experiment */
965/**
966 * Completion callback used with pa_stream_set_buffer_attr().
967 */
968static void drvHstAudPaStreamSetBufferAttrCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
969{
970 PDRVHSTAUDPA pThis = (PDRVHSTAUDPA)pvUser;
971 LogFlowFunc(("fSuccess=%d\n", fSuccess));
972 pa_threaded_mainloop_signal(pThis->pMainLoop, 0 /*fWaitForAccept*/);
973 RT_NOREF(pStream);
974}
975#endif
976
977
978/**
979 * Worker that does the actual creation of an PA stream.
980 *
981 * @returns VBox status code.
982 * @param pThis Our driver instance data.
983 * @param pStreamPA Our stream data.
984 * @param pszName How we name the stream.
985 * @param pCfgAcq The requested stream properties, the Props member is
986 * updated upon successful return.
987 *
988 * @note Caller owns the mainloop lock.
989 */
990static int drvHstAudPaStreamCreateLocked(PDRVHSTAUDPA pThis, PDRVHSTAUDPASTREAM pStreamPA,
991 const char *pszName, PPDMAUDIOSTREAMCFG pCfgAcq)
992{
993 /*
994 * Create the stream.
995 */
996 pa_stream *pStream = pa_stream_new(pThis->pContext, pszName, &pStreamPA->SampleSpec, &pStreamPA->ChannelMap);
997 if (!pStream)
998 {
999 LogRel(("PulseAudio: Failed to create stream '%s': %s (%d)\n",
1000 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
1001 return VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1002 }
1003
1004 /*
1005 * Set the state callback, and in debug builds a few more...
1006 */
1007 pa_stream_set_state_callback(pStream, drvHstAudPaStreamStateChangedCallback, pThis);
1008 pa_stream_set_underflow_callback(pStream, drvHstAudPaStreamUnderflowStatsCallback, pStreamPA);
1009 pa_stream_set_overflow_callback(pStream, drvHstAudPaStreamOverflowStatsCallback, pStreamPA);
1010#ifdef DEBUG
1011 pa_stream_set_write_callback(pStream, drvHstAudPaStreamReqWriteDebugCallback, pStreamPA);
1012#endif
1013
1014 /*
1015 * Connect the stream.
1016 */
1017 int rc;
1018 unsigned const fFlags = PA_STREAM_START_CORKED /* Require explicit starting (uncorking). */
1019 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
1020 | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE
1021#if PA_API_VERSION >= 12
1022 | PA_STREAM_ADJUST_LATENCY
1023#endif
1024 ;
1025 if (pCfgAcq->enmDir == PDMAUDIODIR_IN)
1026 {
1027 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
1028 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.fragsize));
1029 rc = pa_stream_connect_record(pStream, pThis->szInputDev[0] ? pThis->szInputDev : NULL,
1030 &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags);
1031 }
1032 else
1033 {
1034 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
1035 pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.minreq));
1036 rc = pa_stream_connect_playback(pStream, pThis->szOutputDev[0] ? pThis->szOutputDev : NULL, &pStreamPA->BufAttr, (pa_stream_flags_t)fFlags,
1037 NULL /*volume*/, NULL /*sync_stream*/);
1038 }
1039 if (rc >= 0)
1040 {
1041 /*
1042 * Wait for the stream to become ready.
1043 */
1044 uint64_t const nsStart = RTTimeNanoTS();
1045 pa_stream_state_t enmStreamState;
1046 while ( (enmStreamState = pa_stream_get_state(pStream)) != PA_STREAM_READY
1047 && PA_STREAM_IS_GOOD(enmStreamState)
1048 && RTTimeNanoTS() - nsStart < RT_NS_10SEC /* not really timed */ )
1049 drvHstAudPaMainloopWait(pThis);
1050 if (enmStreamState == PA_STREAM_READY)
1051 {
1052 LogFunc(("Connecting stream took %'RU64 ns\n", RTTimeNanoTS() - nsStart));
1053#ifdef LOG_ENABLED
1054 pStreamPA->tsStartUs = pa_rtclock_now();
1055#endif
1056 /*
1057 * Update the buffer attributes.
1058 */
1059 const pa_buffer_attr *pBufAttribs = pa_stream_get_buffer_attr(pStream);
1060#if 0 /* Experiment for getting tlength closer to what we requested (ADJUST_LATENCY effect).
1061 Will slow down stream creation, so not pursued any further at present. */
1062 if ( pCfgAcq->enmDir == PDMAUDIODIR_OUT
1063 && pBufAttribs
1064 && pBufAttribs->tlength < pStreamPA->BufAttr.tlength)
1065 {
1066 pStreamPA->BufAttr.maxlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2;
1067 pStreamPA->BufAttr.tlength += (pStreamPA->BufAttr.tlength - pBufAttribs->tlength) * 2;
1068 LogRel(("Before pa_stream_set_buffer_attr: tlength=%#x (trying =%#x)\n", pBufAttribs->tlength, pStreamPA->BufAttr.tlength));
1069 drvHstAudPaWaitFor(pThis, pa_stream_set_buffer_attr(pStream, &pStreamPA->BufAttr,
1070 drvHstAudPaStreamSetBufferAttrCompletionCallback, pThis));
1071 pBufAttribs = pa_stream_get_buffer_attr(pStream);
1072 LogRel(("After pa_stream_set_buffer_attr: tlength=%#x\n", pBufAttribs->tlength));
1073 }
1074#endif
1075 AssertPtr(pBufAttribs);
1076 if (pBufAttribs)
1077 {
1078 pStreamPA->BufAttr = *pBufAttribs;
1079 LogFunc(("Obtained %s buffer attributes: maxlength=%RU32 tlength=%RU32 prebuf=%RU32 minreq=%RU32 fragsize=%RU32\n",
1080 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output", pBufAttribs->maxlength, pBufAttribs->tlength,
1081 pBufAttribs->prebuf, pBufAttribs->minreq, pBufAttribs->fragsize));
1082
1083 /*
1084 * Convert the sample spec back to PDM speak.
1085 * Note! This isn't strictly speaking needed as SampleSpec has *not* been
1086 * modified since the caller converted it from pCfgReq.
1087 */
1088 rc = drvHstAudPaToAudioProps(&pCfgAcq->Props, pStreamPA->SampleSpec.format,
1089 pStreamPA->SampleSpec.channels, pStreamPA->SampleSpec.rate);
1090 if (RT_SUCCESS(rc))
1091 {
1092 pStreamPA->pStream = pStream;
1093 LogFlowFunc(("returns VINF_SUCCESS\n"));
1094 return VINF_SUCCESS;
1095 }
1096 }
1097 else
1098 {
1099 LogRelMax(99, ("PulseAudio: Failed to get buffer attribs for stream '%s': %s (%d)\n",
1100 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext)));
1101 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1102 }
1103 }
1104 else
1105 {
1106 LogRelMax(99, ("PulseAudio: Failed to initialize stream '%s': state=%d, waited %'RU64 ns\n",
1107 pszName, enmStreamState, RTTimeNanoTS() - nsStart));
1108 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1109 }
1110 pa_stream_disconnect(pStream);
1111 }
1112 else
1113 {
1114 LogRelMax(99, ("PulseAudio: Could not connect %s stream '%s': %s (%d/%d)\n",
1115 pCfgAcq->enmDir == PDMAUDIODIR_IN ? "input" : "output",
1116 pszName, pa_strerror(pa_context_errno(pThis->pContext)), pa_context_errno(pThis->pContext), rc));
1117 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1118 }
1119
1120 pa_stream_unref(pStream);
1121 Assert(RT_FAILURE_NP(rc));
1122 LogFlowFunc(("returns %Rrc\n", rc));
1123 return rc;
1124}
1125
1126
1127/**
1128 * Translates a PDM channel ID to a PA channel position.
1129 *
1130 * @returns PA channel position, INVALID if no mapping found.
1131 */
1132static pa_channel_position_t drvHstAudPaConvertChannelId(uint8_t idChannel)
1133{
1134 switch (idChannel)
1135 {
1136 case PDMAUDIOCHANNELID_FRONT_LEFT: return PA_CHANNEL_POSITION_FRONT_LEFT;
1137 case PDMAUDIOCHANNELID_FRONT_RIGHT: return PA_CHANNEL_POSITION_FRONT_RIGHT;
1138 case PDMAUDIOCHANNELID_FRONT_CENTER: return PA_CHANNEL_POSITION_FRONT_CENTER;
1139 case PDMAUDIOCHANNELID_LFE: return PA_CHANNEL_POSITION_LFE;
1140 case PDMAUDIOCHANNELID_REAR_LEFT: return PA_CHANNEL_POSITION_REAR_LEFT;
1141 case PDMAUDIOCHANNELID_REAR_RIGHT: return PA_CHANNEL_POSITION_REAR_RIGHT;
1142 case PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
1143 case PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
1144 case PDMAUDIOCHANNELID_REAR_CENTER: return PA_CHANNEL_POSITION_REAR_CENTER;
1145 case PDMAUDIOCHANNELID_SIDE_LEFT: return PA_CHANNEL_POSITION_SIDE_LEFT;
1146 case PDMAUDIOCHANNELID_SIDE_RIGHT: return PA_CHANNEL_POSITION_SIDE_RIGHT;
1147 case PDMAUDIOCHANNELID_TOP_CENTER: return PA_CHANNEL_POSITION_TOP_CENTER;
1148 case PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
1149 case PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
1150 case PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
1151 case PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
1152 case PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
1153 case PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
1154 default: return PA_CHANNEL_POSITION_INVALID;
1155 }
1156}
1157
1158
1159/**
1160 * Translates a PA channel position to a PDM channel ID.
1161 *
1162 * @returns PDM channel ID, UNKNOWN if no mapping found.
1163 */
1164static PDMAUDIOCHANNELID drvHstAudPaConvertChannelPos(pa_channel_position_t enmChannelPos)
1165{
1166 switch (enmChannelPos)
1167 {
1168 case PA_CHANNEL_POSITION_INVALID: return PDMAUDIOCHANNELID_INVALID;
1169 case PA_CHANNEL_POSITION_MONO: return PDMAUDIOCHANNELID_MONO;
1170 case PA_CHANNEL_POSITION_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT;
1171 case PA_CHANNEL_POSITION_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT;
1172 case PA_CHANNEL_POSITION_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER;
1173 case PA_CHANNEL_POSITION_LFE: return PDMAUDIOCHANNELID_LFE;
1174 case PA_CHANNEL_POSITION_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT;
1175 case PA_CHANNEL_POSITION_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT;
1176 case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_LEFT_OF_CENTER;
1177 case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return PDMAUDIOCHANNELID_FRONT_RIGHT_OF_CENTER;
1178 case PA_CHANNEL_POSITION_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER;
1179 case PA_CHANNEL_POSITION_SIDE_LEFT: return PDMAUDIOCHANNELID_SIDE_LEFT;
1180 case PA_CHANNEL_POSITION_SIDE_RIGHT: return PDMAUDIOCHANNELID_SIDE_RIGHT;
1181 case PA_CHANNEL_POSITION_TOP_CENTER: return PDMAUDIOCHANNELID_TOP_CENTER;
1182 case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return PDMAUDIOCHANNELID_FRONT_LEFT_HEIGHT;
1183 case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return PDMAUDIOCHANNELID_FRONT_CENTER_HEIGHT;
1184 case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return PDMAUDIOCHANNELID_FRONT_RIGHT_HEIGHT;
1185 case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return PDMAUDIOCHANNELID_REAR_LEFT_HEIGHT;
1186 case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return PDMAUDIOCHANNELID_REAR_CENTER_HEIGHT;
1187 case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return PDMAUDIOCHANNELID_REAR_RIGHT_HEIGHT;
1188 default: return PDMAUDIOCHANNELID_UNKNOWN;
1189 }
1190}
1191
1192
1193
1194/**
1195 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1196 */
1197static DECLCALLBACK(int) drvHstAudPaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1198 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1199{
1200 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1201 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1202 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1203 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1204 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1205 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1206 Assert(PDMAudioStrmCfgEquals(pCfgReq, pCfgAcq));
1207 int rc;
1208
1209 /*
1210 * Prepare name, sample spec and the stream instance data.
1211 */
1212 char szName[256];
1213 RTStrPrintf(szName, sizeof(szName), "VirtualBox %s [%s]", PDMAudioPathGetName(pCfgReq->enmPath), pThis->szStreamName);
1214
1215 pStreamPA->pDrv = pThis;
1216 pStreamPA->pDrainOp = NULL;
1217 pStreamPA->pbPeekBuf = NULL;
1218 pStreamPA->SampleSpec.rate = PDMAudioPropsHz(&pCfgReq->Props);
1219 pStreamPA->SampleSpec.channels = PDMAudioPropsChannels(&pCfgReq->Props);
1220 pStreamPA->SampleSpec.format = drvHstAudPaPropsToPulse(&pCfgReq->Props);
1221
1222 /*
1223 * Initialize the channelmap. This may change the channel count.
1224 */
1225 AssertCompile(RT_ELEMENTS(pStreamPA->ChannelMap.map) >= PDMAUDIO_MAX_CHANNELS);
1226 uint8_t const cSrcChannels = pStreamPA->ChannelMap.channels = PDMAudioPropsChannels(&pCfgReq->Props);
1227 uintptr_t iDst = 0;
1228 if (cSrcChannels == 1 && pCfgReq->Props.aidChannels[0] == PDMAUDIOCHANNELID_MONO)
1229 pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_MONO;
1230 else
1231 {
1232 uintptr_t iSrc;
1233 for (iSrc = iDst = 0; iSrc < cSrcChannels; iSrc++)
1234 {
1235 pStreamPA->ChannelMap.map[iDst] = drvHstAudPaConvertChannelId(pCfgReq->Props.aidChannels[iSrc]);
1236 if (pStreamPA->ChannelMap.map[iDst] != PA_CHANNEL_POSITION_INVALID)
1237 iDst++;
1238 else
1239 {
1240 LogRel2(("PulseAudio: Dropping channel #%u (%d/%s)\n", iSrc, pCfgReq->Props.aidChannels[iSrc],
1241 PDMAudioChannelIdGetName((PDMAUDIOCHANNELID)pCfgReq->Props.aidChannels[iSrc])));
1242 pStreamPA->ChannelMap.channels--;
1243 pStreamPA->SampleSpec.channels--;
1244 PDMAudioPropsSetChannels(&pCfgAcq->Props, pStreamPA->SampleSpec.channels);
1245 }
1246 }
1247 Assert(iDst == pStreamPA->ChannelMap.channels);
1248 }
1249 while (iDst < RT_ELEMENTS(pStreamPA->ChannelMap.map))
1250 pStreamPA->ChannelMap.map[iDst++] = PA_CHANNEL_POSITION_INVALID;
1251
1252 LogFunc(("Opening '%s', rate=%dHz, channels=%d (%d), format=%s\n", szName, pStreamPA->SampleSpec.rate,
1253 pStreamPA->SampleSpec.channels, cSrcChannels, pa_sample_format_to_string(pStreamPA->SampleSpec.format)));
1254
1255 if (pa_sample_spec_valid(&pStreamPA->SampleSpec))
1256 {
1257 /*
1258 * Convert the requested buffer parameters to PA bytes.
1259 */
1260 uint32_t const cbBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props,
1261 pCfgReq->Backend.cFramesBufferSize),
1262 &pStreamPA->SampleSpec);
1263 uint32_t const cbPreBuffer = pa_usec_to_bytes(PDMAudioPropsFramesToMicro(&pCfgAcq->Props,
1264 pCfgReq->Backend.cFramesPreBuffering),
1265 &pStreamPA->SampleSpec);
1266 uint32_t const cbSchedHint = pa_usec_to_bytes(pCfgReq->Device.cMsSchedulingHint * RT_US_1MS, &pStreamPA->SampleSpec);
1267 RT_NOREF(cbBuffer, cbSchedHint, cbPreBuffer);
1268
1269 /*
1270 * Set up buffer attributes according to the stream type.
1271 */
1272 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1273 {
1274 /* Set maxlength to the requested buffer size. */
1275 pStreamPA->BufAttr.maxlength = cbBuffer;
1276
1277 /* Set the fragment size according to the scheduling hint (forget
1278 cFramesPeriod, it's generally rubbish on input). */
1279 pStreamPA->BufAttr.fragsize = cbSchedHint;
1280
1281 /* (tlength, minreq and prebuf are playback only) */
1282 LogRel2(("PulseAudio: Requesting: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n",
1283 pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength));
1284 }
1285 else
1286 {
1287 /* Set tlength to the desired buffer size as PA doesn't have any way
1288 of telling us if anything beyond tlength is writable or not (see
1289 drvHstAudPaStreamGetWritableLocked for more). Because of the
1290 ADJUST_LATENCY flag, this value will be adjusted down, so we'll
1291 end up with less buffer than what we requested, however it should
1292 probably reflect the actual latency a bit closer. Probably not
1293 worth trying to adjust this via pa_stream_set_buffer_attr. */
1294 pStreamPA->BufAttr.tlength = cbBuffer;
1295
1296 /* Set maxlength to the same as tlength as we won't ever write more
1297 than tlength. */
1298 pStreamPA->BufAttr.maxlength = pStreamPA->BufAttr.tlength;
1299
1300 /* According to vlc, pulseaudio goes berserk if the minreq is not
1301 significantly smaller than half of tlength. They use a 1:3 ratio
1302 between minreq and tlength. Traditionally, we've used to just
1303 pass the period value here, however the quality of the incoming
1304 cFramesPeriod value is so variable that just ignore it. This
1305 minreq value is mainly about updating the pa_stream_writable_size
1306 return value, so it makes sense that it need to be well below
1307 half of the buffer length, otherwise we will think the buffer
1308 is full for too long when it isn't.
1309
1310 The DMA scheduling hint is often a much better indicator. Just
1311 to avoid generating too much IPC, limit this to 10 ms. */
1312 uint32_t const cbMinUpdate = pa_usec_to_bytes(RT_US_10MS, &pStreamPA->SampleSpec);
1313 pStreamPA->BufAttr.minreq = RT_MIN(RT_MAX(cbSchedHint, cbMinUpdate),
1314 pStreamPA->BufAttr.tlength / 4);
1315
1316 /* Just pass along the requested pre-buffering size. This seems
1317 typically to be unaltered by pa_stream_connect_playback. Not
1318 sure if tlength is perhaps adjusted relative to it... Ratio
1319 seen here is prebuf=93.75% of tlength. This isn't entirely
1320 optimal as we use 50% by default (see DrvAudio) so that there
1321 is equal room for the guest to run too fast and too slow. Not
1322 much we can do about it w/o slowing down stream creation. */
1323 pStreamPA->BufAttr.prebuf = cbPreBuffer;
1324
1325 /* (fragsize is capture only) */
1326 LogRel2(("PulseAudio: Requesting: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n",
1327 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength));
1328 }
1329
1330 /*
1331 * Do the actual PA stream creation.
1332 */
1333 pa_threaded_mainloop_lock(pThis->pMainLoop);
1334 rc = drvHstAudPaStreamCreateLocked(pThis, pStreamPA, szName, pCfgAcq);
1335 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1336 if (RT_SUCCESS(rc))
1337 {
1338 /*
1339 * Set the acquired stream config according to the actual buffer
1340 * attributes we got and the stream type.
1341 *
1342 * Note! We use maxlength for input buffer and tlength for the
1343 * output buffer size. See above for why.
1344 */
1345 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1346 {
1347 LogRel2(("PulseAudio: Got: BufAttr: fragsize=%#RX32 maxLength=%#RX32\n",
1348 pStreamPA->BufAttr.fragsize, pStreamPA->BufAttr.maxlength));
1349 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.fragsize);
1350 pCfgAcq->Backend.cFramesBufferSize = pStreamPA->BufAttr.maxlength != UINT32_MAX /* paranoia */
1351 ? PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.maxlength)
1352 : pCfgAcq->Backend.cFramesPeriod * 3 /* whatever */;
1353 pCfgAcq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesPreBuffering * pCfgAcq->Backend.cFramesBufferSize
1354 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1355 }
1356 else
1357 {
1358 LogRel2(("PulseAudio: Got: BufAttr: tlength=%#RX32 minReq=%#RX32 prebuf=%#RX32 maxLength=%#RX32\n",
1359 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.minreq, pStreamPA->BufAttr.prebuf, pStreamPA->BufAttr.maxlength));
1360 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.minreq);
1361 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.tlength);
1362 pCfgAcq->Backend.cFramesPreBuffering = PDMAudioPropsBytesToFrames(&pCfgAcq->Props, pStreamPA->BufAttr.prebuf);
1363
1364 LogRel2(("PulseAudio: Initial output latency is %RU64 us (%RU32 bytes)\n",
1365 PDMAudioPropsBytesToMicro(&pCfgAcq->Props, pStreamPA->BufAttr.tlength), pStreamPA->BufAttr.tlength));
1366 }
1367
1368 /*
1369 * Translate back the channel mapping.
1370 */
1371 for (iDst = 0; iDst < pStreamPA->ChannelMap.channels; iDst++)
1372 pCfgAcq->Props.aidChannels[iDst] = drvHstAudPaConvertChannelPos(pStreamPA->ChannelMap.map[iDst]);
1373 while (iDst < RT_ELEMENTS(pCfgAcq->Props.aidChannels))
1374 pCfgAcq->Props.aidChannels[iDst++] = PDMAUDIOCHANNELID_INVALID;
1375
1376 PDMAudioStrmCfgCopy(&pStreamPA->Cfg, pCfgAcq);
1377 }
1378 }
1379 else
1380 {
1381 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", szName));
1382 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1383 }
1384
1385 LogFlowFuncLeaveRC(rc);
1386 return rc;
1387}
1388
1389/**
1390 * Cancel and release any pending stream requests (drain and cork/uncork).
1391 *
1392 * @note Caller has locked the mainloop.
1393 */
1394static void drvHstAudPaStreamCancelAndReleaseOperations(PDRVHSTAUDPASTREAM pStreamPA)
1395{
1396 if (pStreamPA->pDrainOp)
1397 {
1398 LogFlowFunc(("drain operation (%p) status: %d\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
1399 pa_operation_cancel(pStreamPA->pDrainOp);
1400 pa_operation_unref(pStreamPA->pDrainOp);
1401 pStreamPA->pDrainOp = NULL;
1402 }
1403
1404 if (pStreamPA->pCorkOp)
1405 {
1406 LogFlowFunc(("cork operation (%p) status: %d\n", pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
1407 pa_operation_cancel(pStreamPA->pCorkOp);
1408 pa_operation_unref(pStreamPA->pCorkOp);
1409 pStreamPA->pCorkOp = NULL;
1410 }
1411
1412 if (pStreamPA->pTriggerOp)
1413 {
1414 LogFlowFunc(("trigger operation (%p) status: %d\n", pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
1415 pa_operation_cancel(pStreamPA->pTriggerOp);
1416 pa_operation_unref(pStreamPA->pTriggerOp);
1417 pStreamPA->pTriggerOp = NULL;
1418 }
1419}
1420
1421
1422/**
1423 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1424 */
1425static DECLCALLBACK(int) drvHstAudPaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
1426{
1427 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1428 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1429 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1430 RT_NOREF(fImmediate);
1431
1432 if (pStreamPA->pStream)
1433 {
1434 pa_threaded_mainloop_lock(pThis->pMainLoop);
1435
1436 drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
1437 pa_stream_disconnect(pStreamPA->pStream);
1438
1439 pa_stream_unref(pStreamPA->pStream);
1440 pStreamPA->pStream = NULL;
1441
1442 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1443 }
1444
1445 return VINF_SUCCESS;
1446}
1447
1448
1449/**
1450 * Common worker for the cork/uncork completion callbacks.
1451 * @note This is fully async, so nobody is waiting for this.
1452 */
1453static void drvHstAudPaStreamCorkUncorkCommon(PDRVHSTAUDPASTREAM pStreamPA, int fSuccess, const char *pszOperation)
1454{
1455 AssertPtrReturnVoid(pStreamPA);
1456 LogFlowFunc(("%s '%s': fSuccess=%RTbool\n", pszOperation, pStreamPA->Cfg.szName, fSuccess));
1457
1458 if (!fSuccess)
1459 drvHstAudPaError(pStreamPA->pDrv, "%s stream '%s' failed", pszOperation, pStreamPA->Cfg.szName);
1460
1461 if (pStreamPA->pCorkOp)
1462 {
1463 pa_operation_unref(pStreamPA->pCorkOp);
1464 pStreamPA->pCorkOp = NULL;
1465 }
1466}
1467
1468
1469/**
1470 * Completion callback used with pa_stream_cork(,false,).
1471 */
1472static void drvHstAudPaStreamUncorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1473{
1474 RT_NOREF(pStream);
1475 drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Uncorking");
1476}
1477
1478
1479/**
1480 * Completion callback used with pa_stream_cork(,true,).
1481 */
1482static void drvHstAudPaStreamCorkCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1483{
1484 RT_NOREF(pStream);
1485 drvHstAudPaStreamCorkUncorkCommon((PDRVHSTAUDPASTREAM)pvUser, fSuccess, "Corking");
1486}
1487
1488
1489/**
1490 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1491 */
1492static DECLCALLBACK(int) drvHstAudPaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1493{
1494 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1495 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1496 LogFlowFunc(("\n"));
1497
1498 /*
1499 * Uncork (start or resume playback/capture) the stream.
1500 */
1501 pa_threaded_mainloop_lock(pThis->pMainLoop);
1502
1503 drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
1504 pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 0 /*uncork it*/,
1505 drvHstAudPaStreamUncorkCompletionCallback, pStreamPA);
1506 LogFlowFunc(("Uncorking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
1507 int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
1508 : drvHstAudPaError(pThis, "pa_stream_cork('%s', 0 /*uncork it*/,,) failed", pStreamPA->Cfg.szName);
1509
1510 pStreamPA->offInternal = 0;
1511
1512 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1513
1514 LogFlowFunc(("returns %Rrc\n", rc));
1515 return rc;
1516}
1517
1518
1519/**
1520 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1521 */
1522static DECLCALLBACK(int) drvHstAudPaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1523{
1524 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1525 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1526 LogFlowFunc(("\n"));
1527
1528 pa_threaded_mainloop_lock(pThis->pMainLoop);
1529
1530 /*
1531 * For output streams, we will ignore the request if there is a pending drain
1532 * as it will cork the stream in the end.
1533 */
1534 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1535 {
1536 if (pStreamPA->pDrainOp)
1537 {
1538 pa_operation_state_t const enmOpState = pa_operation_get_state(pStreamPA->pDrainOp);
1539 if (enmOpState == PA_OPERATION_RUNNING)
1540 {
1541/** @todo consider corking it immediately instead, as that's what the caller
1542 * wants now... */
1543 LogFlowFunc(("Drain (%p) already running on '%s', skipping.\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
1544 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1545 return VINF_SUCCESS;
1546 }
1547 LogFlowFunc(("Drain (%p) not running: %d\n", pStreamPA->pDrainOp, enmOpState));
1548 }
1549 }
1550 /*
1551 * For input stream we always cork it, but we clean up the peek buffer first.
1552 */
1553 /** @todo r=bird: It is (probably) not technically be correct to drop the peek buffer
1554 * here when we're only pausing the stream (VM paused) as it means we'll
1555 * risk underruns when later resuming. */
1556 else if (pStreamPA->pbPeekBuf) /** @todo Do we need to drop the peek buffer?*/
1557 {
1558 pStreamPA->pbPeekBuf = NULL;
1559 pStreamPA->cbPeekBuf = 0;
1560 pa_stream_drop(pStreamPA->pStream);
1561 }
1562
1563 /*
1564 * Cork (pause playback/capture) the stream.
1565 */
1566 drvHstAudPaStreamCancelAndReleaseOperations(pStreamPA);
1567 pStreamPA->pCorkOp = pa_stream_cork(pStreamPA->pStream, 1 /* cork it */,
1568 drvHstAudPaStreamCorkCompletionCallback, pStreamPA);
1569 LogFlowFunc(("Corking '%s': %p (async)\n", pStreamPA->Cfg.szName, pStreamPA->pCorkOp));
1570 int const rc = pStreamPA->pCorkOp ? VINF_SUCCESS
1571 : drvHstAudPaError(pThis, "pa_stream_cork('%s', 1 /*cork*/,,) failed", pStreamPA->Cfg.szName);
1572
1573 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1574 LogFlowFunc(("returns %Rrc\n", rc));
1575 return rc;
1576}
1577
1578
1579/**
1580 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1581 */
1582static DECLCALLBACK(int) drvHstAudPaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1583{
1584 /* Same as disable. */
1585 return drvHstAudPaHA_StreamDisable(pInterface, pStream);
1586}
1587
1588
1589/**
1590 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
1591 */
1592static DECLCALLBACK(int) drvHstAudPaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1593{
1594 /* Same as enable. */
1595 return drvHstAudPaHA_StreamEnable(pInterface, pStream);
1596}
1597
1598
1599/**
1600 * Pulse audio pa_stream_drain() completion callback.
1601 * @note This is fully async, so nobody is waiting for this.
1602 */
1603static void drvHstAudPaStreamDrainCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1604{
1605 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser;
1606 AssertPtrReturnVoid(pStreamPA);
1607 Assert(pStreamPA->pStream == pStream);
1608 LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
1609
1610 if (!fSuccess)
1611 drvHstAudPaError(pStreamPA->pDrv, "Draining stream '%s' failed", pStreamPA->Cfg.szName);
1612
1613 /* Now cork the stream (doing it unconditionally atm). */
1614 if (pStreamPA->pCorkOp)
1615 {
1616 LogFlowFunc(("Cancelling & releasing cork/uncork operation %p (state: %d)\n",
1617 pStreamPA->pCorkOp, pa_operation_get_state(pStreamPA->pCorkOp)));
1618 pa_operation_cancel(pStreamPA->pCorkOp);
1619 pa_operation_unref(pStreamPA->pCorkOp);
1620 }
1621
1622 pStreamPA->pCorkOp = pa_stream_cork(pStream, 1 /* cork it*/, drvHstAudPaStreamCorkCompletionCallback, pStreamPA);
1623 if (pStreamPA->pCorkOp)
1624 LogFlowFunc(("Started cork operation %p of %s (following drain)\n", pStreamPA->pCorkOp, pStreamPA->Cfg.szName));
1625 else
1626 drvHstAudPaError(pStreamPA->pDrv, "pa_stream_cork failed on '%s' (following drain)", pStreamPA->Cfg.szName);
1627}
1628
1629
1630/**
1631 * Callback used with pa_stream_tigger(), starts draining.
1632 */
1633static void drvHstAudPaStreamTriggerCompletionCallback(pa_stream *pStream, int fSuccess, void *pvUser)
1634{
1635 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pvUser;
1636 AssertPtrReturnVoid(pStreamPA);
1637 RT_NOREF(pStream);
1638 LogFlowFunc(("'%s': fSuccess=%RTbool\n", pStreamPA->Cfg.szName, fSuccess));
1639
1640 if (!fSuccess)
1641 drvHstAudPaError(pStreamPA->pDrv, "Forcing playback before drainig '%s' failed", pStreamPA->Cfg.szName);
1642
1643 if (pStreamPA->pTriggerOp)
1644 {
1645 pa_operation_unref(pStreamPA->pTriggerOp);
1646 pStreamPA->pTriggerOp = NULL;
1647 }
1648}
1649
1650
1651/**
1652 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
1653 */
1654static DECLCALLBACK(int) drvHstAudPaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1655{
1656 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1657 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1658 AssertReturn(pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1659 LogFlowFunc(("\n"));
1660
1661 pa_threaded_mainloop_lock(pThis->pMainLoop);
1662
1663 /*
1664 * If there is a drain running already, don't try issue another as pulse
1665 * doesn't support more than one concurrent drain per stream.
1666 */
1667 if (pStreamPA->pDrainOp)
1668 {
1669 if (pa_operation_get_state(pStreamPA->pDrainOp) == PA_OPERATION_RUNNING)
1670 {
1671 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1672 LogFlowFunc(("returns VINF_SUCCESS (drain already running)\n"));
1673 return VINF_SUCCESS;
1674 }
1675 LogFlowFunc(("Releasing drain operation %p (state: %d)\n", pStreamPA->pDrainOp, pa_operation_get_state(pStreamPA->pDrainOp)));
1676 pa_operation_unref(pStreamPA->pDrainOp);
1677 pStreamPA->pDrainOp = NULL;
1678 }
1679
1680 /*
1681 * Make sure pre-buffered data is played before we drain it.
1682 *
1683 * ASSUMES that the async stream requests are executed in the order they're
1684 * issued here, so that we avoid waiting for the trigger request to complete.
1685 */
1686 int rc = VINF_SUCCESS;
1687 if ( pStreamPA->offInternal
1688 < PDMAudioPropsFramesToBytes(&pStreamPA->Cfg.Props, pStreamPA->Cfg.Backend.cFramesPreBuffering) * 2)
1689 {
1690 if (pStreamPA->pTriggerOp)
1691 {
1692 LogFlowFunc(("Cancelling & releasing trigger operation %p (state: %d)\n",
1693 pStreamPA->pTriggerOp, pa_operation_get_state(pStreamPA->pTriggerOp)));
1694 pa_operation_cancel(pStreamPA->pTriggerOp);
1695 pa_operation_unref(pStreamPA->pTriggerOp);
1696 }
1697 pStreamPA->pTriggerOp = pa_stream_trigger(pStreamPA->pStream, drvHstAudPaStreamTriggerCompletionCallback, pStreamPA);
1698 if (pStreamPA->pTriggerOp)
1699 LogFlowFunc(("Started tigger operation %p on %s\n", pStreamPA->pTriggerOp, pStreamPA->Cfg.szName));
1700 else
1701 rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_trigger failed on '%s'", pStreamPA->Cfg.szName);
1702 }
1703
1704 /*
1705 * Initiate the draining (async), will cork the stream when it completes.
1706 */
1707 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, drvHstAudPaStreamDrainCompletionCallback, pStreamPA);
1708 if (pStreamPA->pDrainOp)
1709 LogFlowFunc(("Started drain operation %p of %s\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
1710 else
1711 rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_drain failed on '%s'", pStreamPA->Cfg.szName);
1712
1713 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1714 LogFlowFunc(("returns %Rrc\n", rc));
1715 return rc;
1716}
1717
1718
1719/**
1720 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
1721 */
1722static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudPaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
1723 PPDMAUDIOBACKENDSTREAM pStream)
1724{
1725 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1726 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1727 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1728 AssertPtrReturn(pStreamPA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
1729
1730 /* Check PulseAudio's general status. */
1731 PDMHOSTAUDIOSTREAMSTATE enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
1732 if (pThis->pContext)
1733 {
1734 pa_context_state_t const enmPaCtxState = pa_context_get_state(pThis->pContext);
1735 if (PA_CONTEXT_IS_GOOD(enmPaCtxState))
1736 {
1737 pa_stream_state_t const enmPaStreamState = pa_stream_get_state(pStreamPA->pStream);
1738 if (PA_STREAM_IS_GOOD(enmPaStreamState))
1739 {
1740 if (enmPaStreamState != PA_STREAM_CREATING)
1741 {
1742 if ( pStreamPA->Cfg.enmDir != PDMAUDIODIR_OUT
1743 || pStreamPA->pDrainOp == NULL
1744 || pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_RUNNING)
1745 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
1746 else
1747 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
1748 }
1749 else
1750 enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
1751 }
1752 else
1753 LogFunc(("non-good PA stream state: %d\n", enmPaStreamState));
1754 }
1755 else
1756 LogFunc(("non-good PA context state: %d\n", enmPaCtxState));
1757 }
1758 else
1759 LogFunc(("No context!\n"));
1760 LogFlowFunc(("returns %s for stream '%s'\n", PDMHostAudioStreamStateGetName(enmBackendStreamState), pStreamPA->Cfg.szName));
1761 return enmBackendStreamState;
1762}
1763
1764
1765/**
1766 * Gets the number of bytes that can safely be written to a stream.
1767 *
1768 * @returns Number of writable bytes, ~(size_t)0 on error.
1769 * @param pStreamPA The stream.
1770 */
1771DECLINLINE(uint32_t) drvHstAudPaStreamGetWritableLocked(PDRVHSTAUDPASTREAM pStreamPA)
1772{
1773 /* pa_stream_writable_size() returns the amount requested currently by the
1774 server, we could write more than this if we liked. The documentation says
1775 up to maxlength, whoever I'm not sure how that limitation is enforced or
1776 what would happen if we exceed it. There seems to be no (simple) way to
1777 figure out how much buffer we have left between what pa_stream_writable_size
1778 returns and what maxlength indicates.
1779
1780 An alternative would be to guess the difference using the read and write
1781 positions in the timing info, however the read position is only updated
1782 when starting and stopping. In the auto update mode it's updated at a
1783 sharply decreasing rate starting at 10ms and ending at 1500ms. So, not
1784 all that helpful. (As long as pa_stream_writable_size returns a non-zero
1785 value, though, we could just add the maxlength-tlength difference. But
1786 the problem is after that.)
1787
1788 So, for now we just use tlength = maxlength for output streams and
1789 problem solved. */
1790 size_t const cbWritablePa = pa_stream_writable_size(pStreamPA->pStream);
1791#if 1
1792 return cbWritablePa;
1793#else
1794 if (cbWritablePa > 0 && cbWritablePa != (size_t)-1)
1795 return cbWritablePa + (pStreamPA->BufAttr.maxlength - pStreamPA->BufAttr.tlength);
1796 //const pa_timing_info * const pTimingInfo = pa_stream_get_timing_info(pStreamPA->pStream);
1797 return 0;
1798#endif
1799}
1800
1801
1802/**
1803 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1804 */
1805static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1806{
1807 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1808 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1809 uint32_t cbWritable = 0;
1810 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_OUT)
1811 {
1812 pa_threaded_mainloop_lock(pThis->pMainLoop);
1813
1814 pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
1815 if (PA_STREAM_IS_GOOD(enmState))
1816 {
1817 size_t cbWritablePa = drvHstAudPaStreamGetWritableLocked(pStreamPA);
1818 if (cbWritablePa != (size_t)-1)
1819 cbWritable = cbWritablePa <= UINT32_MAX ? (uint32_t)cbWritablePa : UINT32_MAX;
1820 else
1821 drvHstAudPaError(pThis, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
1822 }
1823 else
1824 drvHstAudPaError(pThis, "Non-good %s stream state for '%s' (%#x)\n",
1825 PDMAudioDirGetName(pStreamPA->Cfg.enmDir), pStreamPA->Cfg.szName, enmState);
1826
1827 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1828 }
1829 Log3Func(("returns %#x (%u) [max=%#RX32 min=%#RX32]\n",
1830 cbWritable, cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1831 return cbWritable;
1832}
1833
1834
1835/**
1836 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
1837 */
1838static DECLCALLBACK(int) drvHstAudPaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1839 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
1840{
1841 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1842 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1843 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1844 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
1845 if (cbBuf)
1846 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1847 else
1848 {
1849 /* Fend off draining calls. */
1850 *pcbWritten = 0;
1851 return VINF_SUCCESS;
1852 }
1853
1854 pa_threaded_mainloop_lock(pThis->pMainLoop);
1855
1856#ifdef LOG_ENABLED
1857 const pa_usec_t tsNowUs = pa_rtclock_now();
1858 Log3Func(("play delta: %'RI64 us; cbBuf=%#x @%#RX64\n",
1859 pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal));
1860 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1861#endif
1862
1863 /*
1864 * Using a loop here so we can stuff the buffer as full as it gets.
1865 */
1866 int rc = VINF_SUCCESS;
1867 uint32_t cbTotalWritten = 0;
1868 uint32_t iLoop;
1869 for (iLoop = 0; ; iLoop++)
1870 {
1871 size_t const cbWriteable = drvHstAudPaStreamGetWritableLocked(pStreamPA);
1872 if ( cbWriteable != (size_t)-1
1873 && cbWriteable >= PDMAudioPropsFrameSize(&pStreamPA->Cfg.Props))
1874 {
1875 uint32_t cbToWrite = (uint32_t)RT_MIN(cbWriteable, cbBuf);
1876 cbToWrite = PDMAudioPropsFloorBytesToFrame(&pStreamPA->Cfg.Props, cbToWrite);
1877 if (pa_stream_write(pStreamPA->pStream, pvBuf, cbToWrite, NULL /*pfnFree*/, 0 /*offset*/, PA_SEEK_RELATIVE) >= 0)
1878 {
1879 cbTotalWritten += cbToWrite;
1880 cbBuf -= cbToWrite;
1881 pStreamPA->offInternal += cbToWrite;
1882 if (!cbBuf)
1883 break;
1884 pvBuf = (uint8_t const *)pvBuf + cbToWrite;
1885 Log3Func(("%#x left to write\n", cbBuf));
1886 }
1887 else
1888 {
1889 rc = drvHstAudPaError(pStreamPA->pDrv, "Failed to write to output stream");
1890 break;
1891 }
1892 }
1893 else
1894 {
1895 if (cbWriteable == (size_t)-1)
1896 rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_writable_size failed on '%s'", pStreamPA->Cfg.szName);
1897 break;
1898 }
1899 }
1900
1901 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1902
1903 *pcbWritten = cbTotalWritten;
1904 if (RT_SUCCESS(rc) || cbTotalWritten == 0)
1905 { /* likely */ }
1906 else
1907 {
1908 LogFunc(("Supressing %Rrc because we wrote %#x bytes\n", rc, cbTotalWritten));
1909 rc = VINF_SUCCESS;
1910 }
1911 Log3Func(("returns %Rrc *pcbWritten=%#x iLoop=%u @%#RX64\n", rc, cbTotalWritten, iLoop, pStreamPA->offInternal));
1912 return rc;
1913}
1914
1915
1916/**
1917 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1918 */
1919static DECLCALLBACK(uint32_t) drvHstAudPaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1920{
1921 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1922 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1923 uint32_t cbReadable = 0;
1924 if (pStreamPA->Cfg.enmDir == PDMAUDIODIR_IN)
1925 {
1926 pa_threaded_mainloop_lock(pThis->pMainLoop);
1927
1928 pa_stream_state_t const enmState = pa_stream_get_state(pStreamPA->pStream);
1929 if (PA_STREAM_IS_GOOD(enmState))
1930 {
1931 size_t cbReadablePa = pa_stream_readable_size(pStreamPA->pStream);
1932 if (cbReadablePa != (size_t)-1)
1933 {
1934 /* As with WASAPI on Windows, the peek buffer must be subtracted.*/
1935 if (cbReadablePa >= pStreamPA->cbPeekBuf)
1936 cbReadable = (uint32_t)(cbReadablePa - pStreamPA->cbPeekBuf);
1937 else
1938 {
1939 AssertMsgFailed(("%#zx vs %#zx\n", cbReadablePa, pStreamPA->cbPeekBuf));
1940 cbReadable = 0;
1941 }
1942 }
1943 else
1944 drvHstAudPaError(pThis, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
1945 }
1946 else
1947 drvHstAudPaError(pThis, "Non-good %s stream state for '%s' (%#x)\n",
1948 PDMAudioDirGetName(pStreamPA->Cfg.enmDir), pStreamPA->Cfg.szName, enmState);
1949
1950 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1951 }
1952 Log3Func(("returns %#x (%u)\n", cbReadable, cbReadable));
1953 return cbReadable;
1954}
1955
1956
1957/**
1958 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
1959 */
1960static DECLCALLBACK(int) drvHstAudPaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1961 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
1962{
1963 PDRVHSTAUDPA pThis = RT_FROM_MEMBER(pInterface, DRVHSTAUDPA, IHostAudio);
1964 PDRVHSTAUDPASTREAM pStreamPA = (PDRVHSTAUDPASTREAM)pStream;
1965 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
1966 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
1967 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
1968 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
1969
1970#ifdef LOG_ENABLED
1971 const pa_usec_t tsNowUs = pa_rtclock_now();
1972 Log3Func(("capture delta: %'RI64 us; cbBuf=%#x @%#RX64\n",
1973 pStreamPA->tsLastReadWrittenUs ? tsNowUs - pStreamPA->tsLastReadWrittenUs : -1, cbBuf, pStreamPA->offInternal));
1974 pStreamPA->tsLastReadWrittenUs = tsNowUs;
1975#endif
1976
1977 /*
1978 * If we have left over peek buffer space from the last call,
1979 * copy out the data from there.
1980 */
1981 uint32_t cbTotalRead = 0;
1982 if ( pStreamPA->pbPeekBuf
1983 && pStreamPA->offPeekBuf < pStreamPA->cbPeekBuf)
1984 {
1985 uint32_t cbToCopy = pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf;
1986 if (cbToCopy >= cbBuf)
1987 {
1988 memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbBuf);
1989 pStreamPA->offPeekBuf += cbBuf;
1990 pStreamPA->offInternal += cbBuf;
1991 *pcbRead = cbBuf;
1992
1993 if (cbToCopy == cbBuf)
1994 {
1995 pa_threaded_mainloop_lock(pThis->pMainLoop);
1996 pStreamPA->pbPeekBuf = NULL;
1997 pStreamPA->cbPeekBuf = 0;
1998 pa_stream_drop(pStreamPA->pStream);
1999 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2000 }
2001 Log3Func(("returns *pcbRead=%#x from prev peek buf (%#x/%#x) @%#RX64\n",
2002 cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal));
2003 return VINF_SUCCESS;
2004 }
2005
2006 memcpy(pvBuf, &pStreamPA->pbPeekBuf[pStreamPA->offPeekBuf], cbToCopy);
2007 cbBuf -= cbToCopy;
2008 pvBuf = (uint8_t *)pvBuf + cbToCopy;
2009 cbTotalRead += cbToCopy;
2010 pStreamPA->offPeekBuf = pStreamPA->cbPeekBuf;
2011 }
2012
2013 /*
2014 * Copy out what we can.
2015 */
2016 int rc = VINF_SUCCESS;
2017 pa_threaded_mainloop_lock(pThis->pMainLoop);
2018 while (cbBuf > 0)
2019 {
2020 /*
2021 * Drop the old peek buffer first, if we have one.
2022 */
2023 if (pStreamPA->pbPeekBuf)
2024 {
2025 Assert(pStreamPA->offPeekBuf >= pStreamPA->cbPeekBuf);
2026 pStreamPA->pbPeekBuf = NULL;
2027 pStreamPA->cbPeekBuf = 0;
2028 pa_stream_drop(pStreamPA->pStream);
2029 }
2030
2031 /*
2032 * Check if there is anything to read, the get the peek buffer for it.
2033 */
2034 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
2035 if (cbAvail > 0 && cbAvail != (size_t)-1)
2036 {
2037 pStreamPA->pbPeekBuf = NULL;
2038 pStreamPA->cbPeekBuf = 0;
2039 int rcPa = pa_stream_peek(pStreamPA->pStream, (const void **)&pStreamPA->pbPeekBuf, &pStreamPA->cbPeekBuf);
2040 if (rcPa == 0)
2041 {
2042 if (pStreamPA->cbPeekBuf)
2043 {
2044 if (pStreamPA->pbPeekBuf)
2045 {
2046 /*
2047 * We got data back. Copy it into the return buffer, return if it's full.
2048 */
2049 if (cbBuf < pStreamPA->cbPeekBuf)
2050 {
2051 memcpy(pvBuf, pStreamPA->pbPeekBuf, cbBuf);
2052 cbTotalRead += cbBuf;
2053 pStreamPA->offPeekBuf = cbBuf;
2054 pStreamPA->offInternal += cbBuf;
2055 cbBuf = 0;
2056 break;
2057 }
2058 memcpy(pvBuf, pStreamPA->pbPeekBuf, pStreamPA->cbPeekBuf);
2059 cbBuf -= pStreamPA->cbPeekBuf;
2060 pvBuf = (uint8_t *)pvBuf + pStreamPA->cbPeekBuf;
2061 cbTotalRead += pStreamPA->cbPeekBuf;
2062 pStreamPA->offInternal += cbBuf;
2063
2064 pStreamPA->pbPeekBuf = NULL;
2065 }
2066 else
2067 {
2068 /*
2069 * We got a hole (drop needed). We will skip it as we leave it to
2070 * the device's DMA engine to fill in buffer gaps with silence.
2071 */
2072 LogFunc(("pa_stream_peek returned a %#zx (%zu) byte hole - skipping.\n",
2073 pStreamPA->cbPeekBuf, pStreamPA->cbPeekBuf));
2074 }
2075 pStreamPA->cbPeekBuf = 0;
2076 pa_stream_drop(pStreamPA->pStream);
2077 }
2078 else
2079 {
2080 Assert(!pStreamPA->pbPeekBuf);
2081 LogFunc(("pa_stream_peek returned empty buffer\n"));
2082 break;
2083 }
2084 }
2085 else
2086 {
2087 rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_peek failed on '%s' (%d)", pStreamPA->Cfg.szName, rcPa);
2088 pStreamPA->pbPeekBuf = NULL;
2089 pStreamPA->cbPeekBuf = 0;
2090 break;
2091 }
2092 }
2093 else
2094 {
2095 if (cbAvail == (size_t)-1)
2096 rc = drvHstAudPaError(pStreamPA->pDrv, "pa_stream_readable_size failed on '%s'", pStreamPA->Cfg.szName);
2097 break;
2098 }
2099 }
2100 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2101
2102 *pcbRead = cbTotalRead;
2103 if (RT_SUCCESS(rc) || cbTotalRead == 0)
2104 { /* likely */ }
2105 else
2106 {
2107 LogFunc(("Supressing %Rrc because we're returning %#x bytes\n", rc, cbTotalRead));
2108 rc = VINF_SUCCESS;
2109 }
2110 Log3Func(("returns %Rrc *pcbRead=%#x (%#x left, peek %#x/%#x) @%#RX64\n",
2111 rc, cbTotalRead, cbBuf, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->offInternal));
2112 return rc;
2113}
2114
2115
2116/*********************************************************************************************************************************
2117* PDMIBASE *
2118*********************************************************************************************************************************/
2119
2120/**
2121 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2122 */
2123static DECLCALLBACK(void *) drvHstAudPaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2124{
2125 AssertPtrReturn(pInterface, NULL);
2126 AssertPtrReturn(pszIID, NULL);
2127
2128 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2129 PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
2130 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2131 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2132
2133 return NULL;
2134}
2135
2136
2137/*********************************************************************************************************************************
2138* PDMDRVREG *
2139*********************************************************************************************************************************/
2140
2141/**
2142 * Destructs a PulseAudio Audio driver instance.
2143 *
2144 * @copydoc FNPDMDRVDESTRUCT
2145 */
2146static DECLCALLBACK(void) drvHstAudPaDestruct(PPDMDRVINS pDrvIns)
2147{
2148 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2149 PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
2150 LogFlowFuncEnter();
2151
2152 if (pThis->pMainLoop)
2153 pa_threaded_mainloop_stop(pThis->pMainLoop);
2154
2155 if (pThis->pContext)
2156 {
2157 pa_context_disconnect(pThis->pContext);
2158 pa_context_unref(pThis->pContext);
2159 pThis->pContext = NULL;
2160 }
2161
2162 if (pThis->pMainLoop)
2163 {
2164 pa_threaded_mainloop_free(pThis->pMainLoop);
2165 pThis->pMainLoop = NULL;
2166 }
2167
2168 LogFlowFuncLeave();
2169}
2170
2171
2172/**
2173 * Pulse audio callback for context status changes, init variant.
2174 *
2175 * Signalls our event semaphore so we can do a timed wait from
2176 * drvHstAudPaConstruct().
2177 */
2178static void drvHstAudPaCtxCallbackStateChangedInit(pa_context *pCtx, void *pvUser)
2179{
2180 AssertPtrReturnVoid(pCtx);
2181 PDRVHSTAUDPASTATECHGCTX pStateChgCtx = (PDRVHSTAUDPASTATECHGCTX)pvUser;
2182 pa_context_state_t enmCtxState = pa_context_get_state(pCtx);
2183 switch (enmCtxState)
2184 {
2185 case PA_CONTEXT_READY:
2186 case PA_CONTEXT_TERMINATED:
2187 case PA_CONTEXT_FAILED:
2188 AssertPtrReturnVoid(pStateChgCtx);
2189 pStateChgCtx->enmCtxState = enmCtxState;
2190 RTSemEventSignal(pStateChgCtx->hEvtInit);
2191 break;
2192
2193 default:
2194 break;
2195 }
2196}
2197
2198
2199/**
2200 * Constructs a PulseAudio Audio driver instance.
2201 *
2202 * @copydoc FNPDMDRVCONSTRUCT
2203 */
2204static DECLCALLBACK(int) drvHstAudPaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2205{
2206 RT_NOREF(pCfg, fFlags);
2207 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2208 PDRVHSTAUDPA pThis = PDMINS_2_DATA(pDrvIns, PDRVHSTAUDPA);
2209 LogRel(("Audio: Initializing PulseAudio driver\n"));
2210
2211 /*
2212 * Initialize instance data.
2213 */
2214 pThis->pDrvIns = pDrvIns;
2215 /* IBase */
2216 pDrvIns->IBase.pfnQueryInterface = drvHstAudPaQueryInterface;
2217 /* IHostAudio */
2218 pThis->IHostAudio.pfnGetConfig = drvHstAudPaHA_GetConfig;
2219 pThis->IHostAudio.pfnGetDevices = drvHstAudPaHA_GetDevices;
2220 pThis->IHostAudio.pfnSetDevice = drvHstAudPaHA_SetDevice;
2221 pThis->IHostAudio.pfnGetStatus = drvHstAudPaHA_GetStatus;
2222 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2223 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2224 pThis->IHostAudio.pfnStreamCreate = drvHstAudPaHA_StreamCreate;
2225 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2226 pThis->IHostAudio.pfnStreamDestroy = drvHstAudPaHA_StreamDestroy;
2227 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2228 pThis->IHostAudio.pfnStreamEnable = drvHstAudPaHA_StreamEnable;
2229 pThis->IHostAudio.pfnStreamDisable = drvHstAudPaHA_StreamDisable;
2230 pThis->IHostAudio.pfnStreamPause = drvHstAudPaHA_StreamPause;
2231 pThis->IHostAudio.pfnStreamResume = drvHstAudPaHA_StreamResume;
2232 pThis->IHostAudio.pfnStreamDrain = drvHstAudPaHA_StreamDrain;
2233 pThis->IHostAudio.pfnStreamGetState = drvHstAudPaHA_StreamGetState;
2234 pThis->IHostAudio.pfnStreamGetPending = NULL;
2235 pThis->IHostAudio.pfnStreamGetWritable = drvHstAudPaHA_StreamGetWritable;
2236 pThis->IHostAudio.pfnStreamPlay = drvHstAudPaHA_StreamPlay;
2237 pThis->IHostAudio.pfnStreamGetReadable = drvHstAudPaHA_StreamGetReadable;
2238 pThis->IHostAudio.pfnStreamCapture = drvHstAudPaHA_StreamCapture;
2239
2240 /*
2241 * Read configuration.
2242 */
2243 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "VmName|InputDeviceID|OutputDeviceID", "");
2244 int rc = CFGMR3QueryString(pCfg, "VmName", pThis->szStreamName, sizeof(pThis->szStreamName));
2245 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"VmName\" value, rc=%Rrc\n", rc), rc);
2246 rc = CFGMR3QueryStringDef(pCfg, "InputDeviceID", pThis->szInputDev, sizeof(pThis->szInputDev), "");
2247 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"InputDeviceID\" as string: rc=%Rrc\n", rc), rc);
2248 rc = CFGMR3QueryStringDef(pCfg, "OutputDeviceID", pThis->szOutputDev, sizeof(pThis->szOutputDev), "");
2249 AssertMsgRCReturn(rc, ("Confguration error: Failed to read \"OutputDeviceID\" as string: rc=%Rrc\n", rc), rc);
2250
2251 /*
2252 * Query the notification interface from the driver/device above us.
2253 */
2254 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2255 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2256
2257 /*
2258 * Load the pulse audio library.
2259 */
2260 rc = audioLoadPulseLib();
2261 if (RT_SUCCESS(rc))
2262 LogRel(("PulseAudio: Using version %s\n", pa_get_library_version()));
2263 else
2264 {
2265 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
2266 return rc;
2267 }
2268
2269 /*
2270 * Set up the basic pulse audio bits (remember the destructore is always called).
2271 */
2272 //pThis->fAbortLoop = false;
2273 pThis->pMainLoop = pa_threaded_mainloop_new();
2274 if (!pThis->pMainLoop)
2275 {
2276 LogRel(("PulseAudio: Failed to allocate main loop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
2277 return VERR_NO_MEMORY;
2278 }
2279
2280 pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox");
2281 if (!pThis->pContext)
2282 {
2283 LogRel(("PulseAudio: Failed to allocate context: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
2284 return VERR_NO_MEMORY;
2285 }
2286
2287 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
2288 {
2289 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
2290 return VERR_AUDIO_BACKEND_INIT_FAILED;
2291 }
2292
2293 /*
2294 * Connect to the pulse audio server.
2295 *
2296 * We install an init state callback so we can do a timed wait in case
2297 * connecting to the pulseaudio server should take too long.
2298 */
2299 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
2300 pThis->InitStateChgCtx.enmCtxState = PA_CONTEXT_UNCONNECTED;
2301 rc = RTSemEventCreate(&pThis->InitStateChgCtx.hEvtInit);
2302 AssertLogRelRCReturn(rc, rc);
2303
2304 pa_threaded_mainloop_lock(pThis->pMainLoop);
2305 pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChangedInit, &pThis->InitStateChgCtx);
2306 if (!pa_context_connect(pThis->pContext, NULL /* pszServer */, PA_CONTEXT_NOFLAGS, NULL))
2307 {
2308 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2309
2310 rc = RTSemEventWait(pThis->InitStateChgCtx.hEvtInit, RT_MS_10SEC); /* 10 seconds should be plenty. */
2311 if (RT_SUCCESS(rc))
2312 {
2313 if (pThis->InitStateChgCtx.enmCtxState == PA_CONTEXT_READY)
2314 {
2315 /* Install the main state changed callback to know if something happens to our acquired context. */
2316 pa_threaded_mainloop_lock(pThis->pMainLoop);
2317 pa_context_set_state_callback(pThis->pContext, drvHstAudPaCtxCallbackStateChanged, pThis /* pvUserData */);
2318 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2319 }
2320 else
2321 {
2322 LogRel(("PulseAudio: Failed to initialize context (state %d, rc=%Rrc)\n", pThis->InitStateChgCtx.enmCtxState, rc));
2323 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
2324 }
2325 }
2326 else
2327 {
2328 LogRel(("PulseAudio: Waiting for context to become ready failed: %Rrc\n", rc));
2329 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
2330 }
2331 }
2332 else
2333 {
2334 pa_threaded_mainloop_unlock(pThis->pMainLoop);
2335 LogRel(("PulseAudio: Failed to connect to server: %s\n", pa_strerror(pa_context_errno(pThis->pContext))));
2336 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* bird: This used to be VINF_SUCCESS. */
2337 }
2338
2339 RTSemEventDestroy(pThis->InitStateChgCtx.hEvtInit);
2340 pThis->InitStateChgCtx.hEvtInit = NIL_RTSEMEVENT;
2341
2342 /*
2343 * Register statistics.
2344 */
2345 if (RT_SUCCESS(rc))
2346 {
2347 PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatOverruns, STAMTYPE_COUNTER, "Overruns", STAMUNIT_OCCURENCES,
2348 "Pulse-server side buffer overruns (all streams)");
2349 PDMDrvHlpSTAMRegister(pDrvIns, &pThis->StatUnderruns, STAMTYPE_COUNTER, "Underruns", STAMUNIT_OCCURENCES,
2350 "Pulse-server side buffer underruns (all streams)");
2351 }
2352
2353 return rc;
2354}
2355
2356
2357/**
2358 * Pulse audio driver registration record.
2359 */
2360const PDMDRVREG g_DrvHostPulseAudio =
2361{
2362 /* u32Version */
2363 PDM_DRVREG_VERSION,
2364 /* szName */
2365 "PulseAudio",
2366 /* szRCMod */
2367 "",
2368 /* szR0Mod */
2369 "",
2370 /* pszDescription */
2371 "Pulse Audio host driver",
2372 /* fFlags */
2373 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2374 /* fClass. */
2375 PDM_DRVREG_CLASS_AUDIO,
2376 /* cMaxInstances */
2377 ~0U,
2378 /* cbInstance */
2379 sizeof(DRVHSTAUDPA),
2380 /* pfnConstruct */
2381 drvHstAudPaConstruct,
2382 /* pfnDestruct */
2383 drvHstAudPaDestruct,
2384 /* pfnRelocate */
2385 NULL,
2386 /* pfnIOCtl */
2387 NULL,
2388 /* pfnPowerOn */
2389 NULL,
2390 /* pfnReset */
2391 NULL,
2392 /* pfnSuspend */
2393 NULL,
2394 /* pfnResume */
2395 NULL,
2396 /* pfnAttach */
2397 NULL,
2398 /* pfnDetach */
2399 NULL,
2400 /* pfnPowerOff */
2401 NULL,
2402 /* pfnSoftReset */
2403 NULL,
2404 /* u32EndVersion */
2405 PDM_DRVREG_VERSION
2406};
2407
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