VirtualBox

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

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

DrvHostAudioPulseAudio: Multi channel support. bugref:9890

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