VirtualBox

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

最後變更 在這個檔案從89532是 89532,由 vboxsync 提交於 4 年 前

DrvHostAudioPulseAudio: Implemented input/output device selection. bugref:9890

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette