VirtualBox

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

Last change on this file since 106061 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

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