VirtualBox

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

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

DrvHostAudioPulseAudio: Cleaning up the stream control bits. The 'disable' orequest now does not drain the buffer, but leaves that to the 'drain' request. bugref:9890

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