VirtualBox

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

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

scm --update-copyright-year

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