VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostPulseAudio.cpp@ 80957

Last change on this file since 80957 was 76855, checked in by vboxsync, 6 years ago

Audio/PulseAudio: (Release) Log current output latency in case of buffer underruns.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 54.0 KB
Line 
1/* $Id: DrvHostPulseAudio.cpp 76855 2019-01-17 12:04:18Z vboxsync $ */
2/** @file
3 * VBox audio devices: Pulse Audio audio driver.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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
26#include <stdio.h>
27
28#include <iprt/alloc.h>
29#include <iprt/mem.h>
30#include <iprt/uuid.h>
31
32RT_C_DECLS_BEGIN
33 #include "pulse_mangling.h"
34 #include "pulse_stubs.h"
35RT_C_DECLS_END
36
37#include <pulse/pulseaudio.h>
38
39#include "DrvAudio.h"
40#include "VBoxDD.h"
41
42
43/*********************************************************************************************************************************
44* Defines *
45*********************************************************************************************************************************/
46#define VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS 32 /** @todo Make this configurable thru driver options. */
47
48#ifndef PA_STREAM_NOFLAGS
49# define PA_STREAM_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
50#endif
51
52#ifndef PA_CONTEXT_NOFLAGS
53# define PA_CONTEXT_NOFLAGS (pa_context_flags_t)0x0000U /* since 0.9.19 */
54#endif
55
56/** No flags specified. */
57#define PULSEAUDIOENUMCBFLAGS_NONE 0
58/** (Release) log found devices. */
59#define PULSEAUDIOENUMCBFLAGS_LOG RT_BIT(0)
60
61/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */
62#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \
63 ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) )
64
65
66/*********************************************************************************************************************************
67* Structures *
68*********************************************************************************************************************************/
69
70/**
71 * Host Pulse audio driver instance data.
72 * @implements PDMIAUDIOCONNECTOR
73 */
74typedef struct DRVHOSTPULSEAUDIO
75{
76 /** Pointer to the driver instance structure. */
77 PPDMDRVINS pDrvIns;
78 /** Pointer to PulseAudio's threaded main loop. */
79 pa_threaded_mainloop *pMainLoop;
80 /**
81 * Pointer to our PulseAudio context.
82 * Note: We use a pMainLoop in a separate thread (pContext).
83 * So either use callback functions or protect these functions
84 * by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
85 */
86 pa_context *pContext;
87 /** Shutdown indicator. */
88 volatile bool fAbortLoop;
89 /** Enumeration operation successful? */
90 volatile bool fEnumOpSuccess;
91 /** Pointer to host audio interface. */
92 PDMIHOSTAUDIO IHostAudio;
93 /** Error count for not flooding the release log.
94 * Specify UINT32_MAX for unlimited logging. */
95 uint32_t cLogErrors;
96 /** The stream (base) name; needed for distinguishing
97 * streams in the PulseAudio mixer controls if multiple
98 * VMs are running at the same time. */
99 char szStreamName[64];
100} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO;
101
102typedef struct PULSEAUDIOSTREAM
103{
104 /** The stream's acquired configuration. */
105 PPDMAUDIOSTREAMCFG pCfg;
106 /** Pointer to driver instance. */
107 PDRVHOSTPULSEAUDIO pDrv;
108 /** Pointer to opaque PulseAudio stream. */
109 pa_stream *pStream;
110 /** Pulse sample format and attribute specification. */
111 pa_sample_spec SampleSpec;
112 /** Pulse playback and buffer metrics. */
113 pa_buffer_attr BufAttr;
114 int fOpSuccess;
115 /** Pointer to Pulse sample peeking buffer. */
116 const uint8_t *pu8PeekBuf;
117 /** Current size (in bytes) of peeking data in
118 * buffer. */
119 size_t cbPeekBuf;
120 /** Our offset (in bytes) in peeking buffer. */
121 size_t offPeekBuf;
122 pa_operation *pDrainOp;
123 /** Number of occurred audio data underflows. */
124 uint32_t cUnderflows;
125 /** Current latency (in us). */
126 uint64_t curLatencyUs;
127#ifdef LOG_ENABLED
128 /** Start time stamp (in us) of stream playback / recording. */
129 pa_usec_t tsStartUs;
130 /** Time stamp (in us) when last read from / written to the stream. */
131 pa_usec_t tsLastReadWrittenUs;
132#endif
133} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM;
134
135/**
136 * Callback context for server enumeration callbacks.
137 */
138typedef struct PULSEAUDIOENUMCBCTX
139{
140 /** Pointer to host backend driver. */
141 PDRVHOSTPULSEAUDIO pDrv;
142 /** Enumeration flags. */
143 uint32_t fFlags;
144 /** Number of found input devices. */
145 uint8_t cDevIn;
146 /** Number of found output devices. */
147 uint8_t cDevOut;
148 /** Name of default sink being used. Must be free'd using RTStrFree(). */
149 char *pszDefaultSink;
150 /** Name of default source being used. Must be free'd using RTStrFree(). */
151 char *pszDefaultSource;
152} PULSEAUDIOENUMCBCTX, *PPULSEAUDIOENUMCBCTX;
153
154#ifndef PA_CONTEXT_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
155static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
156 return
157 x == PA_CONTEXT_CONNECTING ||
158 x == PA_CONTEXT_AUTHORIZING ||
159 x == PA_CONTEXT_SETTING_NAME ||
160 x == PA_CONTEXT_READY;
161}
162#endif /* !PA_CONTEXT_IS_GOOD */
163
164#ifndef PA_STREAM_IS_GOOD /* To allow running on systems with PulseAudio < 0.9.11. */
165static inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) {
166 return
167 x == PA_STREAM_CREATING ||
168 x == PA_STREAM_READY;
169}
170#endif /* !PA_STREAM_IS_GOOD */
171
172
173/*********************************************************************************************************************************
174* Prototypes *
175*********************************************************************************************************************************/
176
177static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum);
178static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg);
179#ifdef DEBUG
180static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext);
181static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext);
182#endif
183static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext);
184
185
186/**
187 * Signal the main loop to abort. Just signalling isn't sufficient as the
188 * mainloop might not have been entered yet.
189 */
190static void paSignalWaiter(PDRVHOSTPULSEAUDIO pThis)
191{
192 if (!pThis)
193 return;
194
195 pThis->fAbortLoop = true;
196 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
197}
198
199
200static pa_sample_format_t paAudioPropsToPulse(PPDMAUDIOPCMPROPS pProps)
201{
202 switch (pProps->cBytes)
203 {
204 case 1:
205 if (!pProps->fSigned)
206 return PA_SAMPLE_U8;
207 break;
208
209 case 2:
210 if (pProps->fSigned)
211 return PA_SAMPLE_S16LE;
212 break;
213
214#ifdef PA_SAMPLE_S32LE
215 case 4:
216 if (pProps->fSigned)
217 return PA_SAMPLE_S32LE;
218 break;
219#endif
220
221 default:
222 break;
223 }
224
225 AssertMsgFailed(("%RU8%s not supported\n", pProps->cBytes, pProps->fSigned ? "S" : "U"));
226 return PA_SAMPLE_INVALID;
227}
228
229
230static int paPulseToAudioProps(pa_sample_format_t pulsefmt, PPDMAUDIOPCMPROPS pProps)
231{
232 switch (pulsefmt)
233 {
234 case PA_SAMPLE_U8:
235 pProps->cBytes = 1;
236 pProps->fSigned = false;
237 break;
238
239 case PA_SAMPLE_S16LE:
240 pProps->cBytes = 2;
241 pProps->fSigned = true;
242 break;
243
244 case PA_SAMPLE_S16BE:
245 pProps->cBytes = 2;
246 pProps->fSigned = true;
247 /** @todo Handle Endianess. */
248 break;
249
250#ifdef PA_SAMPLE_S32LE
251 case PA_SAMPLE_S32LE:
252 pProps->cBytes = 4;
253 pProps->fSigned = true;
254 break;
255#endif
256
257#ifdef PA_SAMPLE_S32BE
258 case PA_SAMPLE_S32BE:
259 pProps->cBytes = 4;
260 pProps->fSigned = true;
261 /** @todo Handle Endianess. */
262 break;
263#endif
264
265 default:
266 AssertLogRelMsgFailed(("PulseAudio: Format (%ld) not supported\n", pulsefmt));
267 return VERR_NOT_SUPPORTED;
268 }
269
270 return VINF_SUCCESS;
271}
272
273
274/**
275 * Synchronously wait until an operation completed.
276 */
277static int paWaitForEx(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP, RTMSINTERVAL cMsTimeout)
278{
279 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
280 AssertPtrReturn(pOP, VERR_INVALID_POINTER);
281
282 int rc = VINF_SUCCESS;
283
284 uint64_t u64StartMs = RTTimeMilliTS();
285 while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING)
286 {
287 if (!pThis->fAbortLoop)
288 {
289 AssertPtr(pThis->pMainLoop);
290 pa_threaded_mainloop_wait(pThis->pMainLoop);
291 if ( !pThis->pContext
292 || pa_context_get_state(pThis->pContext) != PA_CONTEXT_READY)
293 {
294 LogRel(("PulseAudio: pa_context_get_state context not ready\n"));
295 break;
296 }
297 }
298 pThis->fAbortLoop = false;
299
300 uint64_t u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
301 if (u64ElapsedMs >= cMsTimeout)
302 {
303 rc = VERR_TIMEOUT;
304 break;
305 }
306 }
307
308 pa_operation_unref(pOP);
309
310 return rc;
311}
312
313
314static int paWaitFor(PDRVHOSTPULSEAUDIO pThis, pa_operation *pOP)
315{
316 return paWaitForEx(pThis, pOP, 10 * 1000 /* 10s timeout */);
317}
318
319
320/**
321 * Context status changed.
322 */
323static void paContextCbStateChanged(pa_context *pCtx, void *pvUser)
324{
325 AssertPtrReturnVoid(pCtx);
326
327 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
328 AssertPtrReturnVoid(pThis);
329
330 switch (pa_context_get_state(pCtx))
331 {
332 case PA_CONTEXT_READY:
333 case PA_CONTEXT_TERMINATED:
334 case PA_CONTEXT_FAILED:
335 paSignalWaiter(pThis);
336 break;
337
338 default:
339 break;
340 }
341}
342
343
344/**
345 * Callback called when our pa_stream_drain operation was completed.
346 */
347static void paStreamCbDrain(pa_stream *pStream, int fSuccess, void *pvUser)
348{
349 AssertPtrReturnVoid(pStream);
350
351 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pvUser;
352 AssertPtrReturnVoid(pStreamPA);
353
354 pStreamPA->fOpSuccess = fSuccess;
355 if (fSuccess)
356 {
357 pa_operation_unref(pa_stream_cork(pStream, 1,
358 paStreamCbSuccess, pvUser));
359 }
360 else
361 paError(pStreamPA->pDrv, "Failed to drain stream");
362
363 if (pStreamPA->pDrainOp)
364 {
365 pa_operation_unref(pStreamPA->pDrainOp);
366 pStreamPA->pDrainOp = NULL;
367 }
368}
369
370
371/**
372 * Stream status changed.
373 */
374static void paStreamCbStateChanged(pa_stream *pStream, void *pvUser)
375{
376 AssertPtrReturnVoid(pStream);
377
378 PDRVHOSTPULSEAUDIO pThis = (PDRVHOSTPULSEAUDIO)pvUser;
379 AssertPtrReturnVoid(pThis);
380
381 switch (pa_stream_get_state(pStream))
382 {
383 case PA_STREAM_READY:
384 case PA_STREAM_FAILED:
385 case PA_STREAM_TERMINATED:
386 paSignalWaiter(pThis);
387 break;
388
389 default:
390 break;
391 }
392}
393
394
395#ifdef DEBUG
396static void paStreamCbReqWrite(pa_stream *pStream, size_t cbLen, void *pvContext)
397{
398 RT_NOREF(cbLen, pvContext);
399
400 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
401 AssertPtrReturnVoid(pStrm);
402
403 pa_usec_t usec = 0;
404 int neg = 0;
405 pa_stream_get_latency(pStream, &usec, &neg);
406
407 Log2Func(("Requested %zu bytes -- Current latency is %RU64ms\n", cbLen, usec / 1000));
408}
409
410
411static void paStreamCbUnderflow(pa_stream *pStream, void *pvContext)
412{
413 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
414 AssertPtrReturnVoid(pStrm);
415
416 pStrm->cUnderflows++;
417
418 LogRel2(("PulseAudio: Warning: Hit underflow #%RU32\n", pStrm->cUnderflows));
419
420 if ( pStrm->cUnderflows >= 6 /** @todo Make this check configurable. */
421 && pStrm->curLatencyUs < 2000000 /* 2s */)
422 {
423 pStrm->curLatencyUs = (pStrm->curLatencyUs * 3) / 2;
424
425 LogRel2(("PulseAudio: Output latency increased to %RU64ms\n", pStrm->curLatencyUs / 1000 /* ms */));
426
427 pStrm->BufAttr.maxlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
428 pStrm->BufAttr.tlength = pa_usec_to_bytes(pStrm->curLatencyUs, &pStrm->SampleSpec);
429
430 pa_stream_set_buffer_attr(pStream, &pStrm->BufAttr, NULL, NULL);
431
432 pStrm->cUnderflows = 0;
433 }
434
435 pa_usec_t curLatencyUs = 0;
436 pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */);
437
438 LogRel2(("PulseAudio: Latency now is %RU64ms\n", curLatencyUs / 1000 /* ms */));
439
440# ifdef LOG_ENABLED
441 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
442 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
443
444 pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
445 pa_usec_t curPosReadsUs = pa_bytes_to_usec(pTInfo->read_index, pSpec);
446 pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
447
448 Log2Func(("curPosWrite=%RU64ms, curPosRead=%RU64ms, curTs=%RU64ms, curLatency=%RU64ms (%RU32Hz, %RU8 channels)\n",
449 curPosWritesUs / RT_US_1MS_64, curPosReadsUs / RT_US_1MS_64,
450 curTsUs / RT_US_1MS_64, curLatencyUs / RT_US_1MS_64, pSpec->rate, pSpec->channels));
451# endif
452}
453
454
455static void paStreamCbOverflow(pa_stream *pStream, void *pvContext)
456{
457 RT_NOREF(pStream, pvContext);
458
459 Log2Func(("Warning: Hit overflow\n"));
460}
461#endif /* DEBUG */
462
463
464static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser)
465{
466 AssertPtrReturnVoid(pStream);
467
468 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
469 AssertPtrReturnVoid(pStrm);
470
471 pStrm->fOpSuccess = fSuccess;
472
473 if (fSuccess)
474 paSignalWaiter(pStrm->pDrv);
475 else
476 paError(pStrm->pDrv, "Failed to finish stream operation");
477}
478
479
480static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName)
481{
482 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
483 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
484 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
485
486 int rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
487
488 pa_stream *pStream = NULL;
489 uint32_t flags = PA_STREAM_NOFLAGS;
490
491 pa_threaded_mainloop_lock(pThis->pMainLoop);
492
493 do
494 {
495 pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec;
496
497 LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n",
498 pszName, pSampleSpec->rate, pSampleSpec->channels,
499 pa_sample_format_to_string(pSampleSpec->format)));
500
501 if (!pa_sample_spec_valid(pSampleSpec))
502 {
503 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName));
504 break;
505 }
506
507 pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr;
508
509 /** @todo r=andy Use pa_stream_new_with_proplist instead. */
510 if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */)))
511 {
512 LogRel(("PulseAudio: Could not create stream '%s'\n", pszName));
513 rc = VERR_NO_MEMORY;
514 break;
515 }
516
517#ifdef DEBUG
518 pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA);
519 pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA);
520 if (!fIn) /* Only for output streams. */
521 pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA);
522#endif
523 pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis);
524
525#if PA_API_VERSION >= 12
526 /* XXX */
527 flags |= PA_STREAM_ADJUST_LATENCY;
528#endif
529 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
530 flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
531
532 /* No input/output right away after the stream was started. */
533 flags |= PA_STREAM_START_CORKED;
534
535 if (fIn)
536 {
537 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
538 pBufAttr->maxlength, pBufAttr->fragsize));
539
540 if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
541 {
542 LogRel(("PulseAudio: Could not connect input stream '%s': %s\n",
543 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
544 break;
545 }
546 }
547 else
548 {
549 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
550 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
551
552 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
553 /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
554 {
555 LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n",
556 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
557 break;
558 }
559 }
560
561 /* Wait until the stream is ready. */
562 for (;;)
563 {
564 if (!pThis->fAbortLoop)
565 pa_threaded_mainloop_wait(pThis->pMainLoop);
566 pThis->fAbortLoop = false;
567
568 pa_stream_state_t streamSt = pa_stream_get_state(pStream);
569 if (streamSt == PA_STREAM_READY)
570 break;
571 else if ( streamSt == PA_STREAM_FAILED
572 || streamSt == PA_STREAM_TERMINATED)
573 {
574 LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt));
575 break;
576 }
577 }
578
579#ifdef LOG_ENABLED
580 pStreamPA->tsStartUs = pa_rtclock_now();
581#endif
582 const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
583 AssertPtr(pBufAttrObtained);
584 memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
585
586 LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n",
587 fIn ? "capture" : "playback",
588 pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf));
589
590 pStreamPA->pStream = pStream;
591
592 rc = VINF_SUCCESS;
593
594 } while (0);
595
596 if ( RT_FAILURE(rc)
597 && pStream)
598 pa_stream_disconnect(pStream);
599
600 pa_threaded_mainloop_unlock(pThis->pMainLoop);
601
602 if (RT_FAILURE(rc))
603 {
604 if (pStream)
605 pa_stream_unref(pStream);
606 }
607
608 LogFlowFuncLeaveRC(rc);
609 return rc;
610}
611
612
613/**
614 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
615 */
616static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface)
617{
618 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
619
620 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
621
622 LogFlowFuncEnter();
623
624 int rc = audioLoadPulseLib();
625 if (RT_FAILURE(rc))
626 {
627 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
628 return rc;
629 }
630
631 LogRel(("PulseAudio: Using v%s\n", pa_get_library_version()));
632
633 pThis->fAbortLoop = false;
634 pThis->pMainLoop = NULL;
635
636 bool fLocked = false;
637
638 do
639 {
640 if (!(pThis->pMainLoop = pa_threaded_mainloop_new()))
641 {
642 LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
643 pa_strerror(pa_context_errno(pThis->pContext))));
644 rc = VERR_NO_MEMORY;
645 break;
646 }
647
648 if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox")))
649 {
650 LogRel(("PulseAudio: Failed to allocate context: %s\n",
651 pa_strerror(pa_context_errno(pThis->pContext))));
652 rc = VERR_NO_MEMORY;
653 break;
654 }
655
656 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
657 {
658 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
659 pa_strerror(pa_context_errno(pThis->pContext))));
660 break;
661 }
662
663 /* Install a global callback to known if something happens to our acquired context. */
664 pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */);
665
666 pa_threaded_mainloop_lock(pThis->pMainLoop);
667 fLocked = true;
668
669 if (pa_context_connect(pThis->pContext, NULL /* pszServer */,
670 PA_CONTEXT_NOFLAGS, NULL) < 0)
671 {
672 LogRel(("PulseAudio: Failed to connect to server: %s\n",
673 pa_strerror(pa_context_errno(pThis->pContext))));
674 break;
675 }
676
677 /* Wait until the pThis->pContext is ready. */
678 for (;;)
679 {
680 if (!pThis->fAbortLoop)
681 pa_threaded_mainloop_wait(pThis->pMainLoop);
682 pThis->fAbortLoop = false;
683
684 pa_context_state_t cstate = pa_context_get_state(pThis->pContext);
685 if (cstate == PA_CONTEXT_READY)
686 break;
687 else if ( cstate == PA_CONTEXT_TERMINATED
688 || cstate == PA_CONTEXT_FAILED)
689 {
690 LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate));
691 break;
692 }
693 }
694 }
695 while (0);
696
697 if (fLocked)
698 pa_threaded_mainloop_unlock(pThis->pMainLoop);
699
700 if (RT_FAILURE(rc))
701 {
702 if (pThis->pMainLoop)
703 pa_threaded_mainloop_stop(pThis->pMainLoop);
704
705 if (pThis->pContext)
706 {
707 pa_context_disconnect(pThis->pContext);
708 pa_context_unref(pThis->pContext);
709 pThis->pContext = NULL;
710 }
711
712 if (pThis->pMainLoop)
713 {
714 pa_threaded_mainloop_free(pThis->pMainLoop);
715 pThis->pMainLoop = NULL;
716 }
717 }
718
719 LogFlowFuncLeaveRC(rc);
720 return rc;
721}
722
723
724static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
725 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
726{
727 pStreamPA->pDrainOp = NULL;
728
729 pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
730 pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
731 pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
732
733 pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props) * RT_US_1MS;
734
735 const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec);
736
737 LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency));
738
739 pStreamPA->BufAttr.tlength = cbLatency;
740 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
741 pStreamPA->BufAttr.prebuf = cbLatency;
742 pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
743
744 LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
745 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
746
747 Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT);
748
749 char szName[256];
750 RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
751 DrvAudioHlpPlaybackDstToStr(pCfgReq->DestSource.Dest), pThis->szStreamName);
752
753 /* Note that the struct BufAttr is updated to the obtained values after this call! */
754 int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName);
755 if (RT_FAILURE(rc))
756 return rc;
757
758 rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
759 if (RT_FAILURE(rc))
760 {
761 LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format));
762 return rc;
763 }
764
765 pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
766 pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
767 pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels);
768
769 LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
770 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
771
772 pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq);
773 pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength);
774 pCfgAcq->Backend.cfPreBuf = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf);
775
776 pStreamPA->pDrv = pThis;
777
778 return rc;
779}
780
781
782static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
783 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
784{
785 pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
786 pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
787 pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
788
789 pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
790 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
791
792 Assert(pCfgReq->enmDir == PDMAUDIODIR_IN);
793
794 char szName[256];
795 RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
796 DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source), pThis->szStreamName);
797
798 /* Note: Other members of BufAttr are ignored for record streams. */
799 int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName);
800 if (RT_FAILURE(rc))
801 return rc;
802
803 rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
804 if (RT_FAILURE(rc))
805 {
806 LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format));
807 return rc;
808 }
809
810 pStreamPA->pDrv = pThis;
811 pStreamPA->pu8PeekBuf = NULL;
812
813 pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
814 pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
815
816 pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize);
817 pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfBufferSize;
818 pCfgAcq->Backend.cfPreBuf = pCfgAcq->Backend.cfPeriod;
819
820 LogFlowFuncLeaveRC(rc);
821 return rc;
822}
823
824
825/**
826 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
827 */
828static DECLCALLBACK(int) drvHostPulseAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
829 PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
830{
831 RT_NOREF(pvBuf, cxBuf);
832 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
833 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
834 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
835 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
836 /* pcbRead is optional. */
837
838 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
839 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
840
841 /* We should only call pa_stream_readable_size() once and trust the first value. */
842 pa_threaded_mainloop_lock(pThis->pMainLoop);
843 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
844 pa_threaded_mainloop_unlock(pThis->pMainLoop);
845
846 if (cbAvail == (size_t)-1)
847 return paError(pStreamPA->pDrv, "Failed to determine input data size");
848
849 /* If the buffer was not dropped last call, add what remains. */
850 if (pStreamPA->pu8PeekBuf)
851 {
852 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
853 cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
854 }
855
856 Log3Func(("cbAvail=%zu\n", cbAvail));
857
858 if (!cbAvail) /* No data? Bail out. */
859 {
860 if (pcxRead)
861 *pcxRead = 0;
862 return VINF_SUCCESS;
863 }
864
865 int rc = VINF_SUCCESS;
866
867 size_t cbToRead = RT_MIN(cbAvail, cxBuf);
868
869 Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
870 cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
871
872 uint32_t cbReadTotal = 0;
873
874 while (cbToRead)
875 {
876 /* If there is no data, do another peek. */
877 if (!pStreamPA->pu8PeekBuf)
878 {
879 pa_threaded_mainloop_lock(pThis->pMainLoop);
880 pa_stream_peek(pStreamPA->pStream,
881 (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
882 pa_threaded_mainloop_unlock(pThis->pMainLoop);
883
884 pStreamPA->offPeekBuf = 0;
885
886 /* No data anymore?
887 * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
888 * we need to drop the stream lateron. */
889 if ( !pStreamPA->pu8PeekBuf
890 && !pStreamPA->cbPeekBuf)
891 {
892 break;
893 }
894 }
895
896 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
897 size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
898
899 Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
900 cbToRead, cbToWrite,
901 pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
902
903 if ( cbToWrite
904 /* Only copy data if it's not a data hole (see above). */
905 && pStreamPA->pu8PeekBuf
906 && pStreamPA->cbPeekBuf)
907 {
908 memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
909
910 Assert(cbToRead >= cbToWrite);
911 cbToRead -= cbToWrite;
912 cbReadTotal += cbToWrite;
913
914 pStreamPA->offPeekBuf += cbToWrite;
915 Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
916 }
917
918 if (/* Nothing to write anymore? Drop the buffer. */
919 !cbToWrite
920 /* Was there a hole in the peeking buffer? Drop it. */
921 || !pStreamPA->pu8PeekBuf
922 /* If the buffer is done, drop it. */
923 || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
924 {
925 pa_threaded_mainloop_lock(pThis->pMainLoop);
926 pa_stream_drop(pStreamPA->pStream);
927 pa_threaded_mainloop_unlock(pThis->pMainLoop);
928
929 pStreamPA->pu8PeekBuf = NULL;
930 }
931 }
932
933 if (RT_SUCCESS(rc))
934 {
935 if (pcxRead)
936 *pcxRead = cbReadTotal;
937 }
938
939 return rc;
940}
941
942
943/**
944 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
945 */
946static DECLCALLBACK(int) drvHostPulseAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
947 PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
948 uint32_t *pcxWritten)
949{
950 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
951 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
952 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
953 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
954 /* pcxWritten is optional. */
955
956 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
957 PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream;
958
959 int rc = VINF_SUCCESS;
960
961 uint32_t cbWrittenTotal = 0;
962
963 pa_threaded_mainloop_lock(pThis->pMainLoop);
964
965#ifdef LOG_ENABLED
966 const pa_usec_t tsNowUs = pa_rtclock_now();
967 const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs;
968
969 Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */));
970
971 pPAStream->tsLastReadWrittenUs = tsNowUs;
972#endif
973
974 do
975 {
976 size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream);
977 if (cbWriteable == (size_t)-1)
978 {
979 rc = paError(pPAStream->pDrv, "Failed to determine output data size");
980 break;
981 }
982
983 size_t cbLeft = RT_MIN(cbWriteable, cxBuf);
984 Assert(cbLeft); /* At this point we better have *something* to write. */
985
986 while (cbLeft)
987 {
988 uint32_t cbChunk = cbLeft; /* Write all at once for now. */
989
990 if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */,
991 0, PA_SEEK_RELATIVE) < 0)
992 {
993 rc = paError(pPAStream->pDrv, "Failed to write to output stream");
994 break;
995 }
996
997 Assert(cbLeft >= cbChunk);
998 cbLeft -= cbChunk;
999 cbWrittenTotal += cbChunk;
1000 }
1001
1002 } while (0);
1003
1004 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1005
1006 if (RT_SUCCESS(rc))
1007 {
1008 if (pcxWritten)
1009 *pcxWritten = cbWrittenTotal;
1010 }
1011
1012 return rc;
1013}
1014
1015
1016/** @todo Implement va handling. */
1017static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
1018{
1019 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1020 AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
1021
1022 if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
1023 {
1024 int rc2 = pa_context_errno(pThis->pContext);
1025 LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
1026 }
1027
1028 /** @todo Implement some PulseAudio -> IPRT mapping here. */
1029 return VERR_GENERAL_FAILURE;
1030}
1031
1032
1033static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
1034{
1035 if (eol > 0)
1036 return;
1037
1038 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1039 AssertPtrReturnVoid(pCbCtx);
1040 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1041 AssertPtrReturnVoid(pThis);
1042 if (eol < 0)
1043 {
1044 pThis->fEnumOpSuccess = false;
1045 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1046 return;
1047 }
1048
1049 AssertPtrReturnVoid(pCtx);
1050 AssertPtrReturnVoid(pInfo);
1051
1052 LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name));
1053
1054 /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */
1055 pCbCtx->cDevOut++;
1056
1057 pThis->fEnumOpSuccess = true;
1058 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1059}
1060
1061
1062static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
1063{
1064 if (eol > 0)
1065 return;
1066
1067 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1068 AssertPtrReturnVoid(pCbCtx);
1069 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1070 AssertPtrReturnVoid(pThis);
1071 if (eol < 0)
1072 {
1073 pThis->fEnumOpSuccess = false;
1074 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1075 return;
1076 }
1077
1078 AssertPtrReturnVoid(pCtx);
1079 AssertPtrReturnVoid(pInfo);
1080
1081 LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name));
1082
1083 /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */
1084 pCbCtx->cDevIn++;
1085
1086 pThis->fEnumOpSuccess = true;
1087 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1088}
1089
1090
1091static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
1092{
1093 AssertPtrReturnVoid(pCtx);
1094 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1095 AssertPtrReturnVoid(pCbCtx);
1096 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1097 AssertPtrReturnVoid(pThis);
1098
1099 if (!pInfo)
1100 {
1101 pThis->fEnumOpSuccess = false;
1102 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1103 return;
1104 }
1105
1106 if (pInfo->default_sink_name)
1107 {
1108 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
1109 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
1110 }
1111
1112 if (pInfo->default_sink_name)
1113 {
1114 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
1115 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
1116 }
1117
1118 pThis->fEnumOpSuccess = true;
1119 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
1120}
1121
1122
1123static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
1124{
1125 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1126 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
1127
1128 PDMAUDIOBACKENDCFG Cfg;
1129 RT_ZERO(Cfg);
1130
1131 RTStrPrintf2(Cfg.szName, sizeof(Cfg.szName), "PulseAudio driver");
1132
1133 Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM);
1134 Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM);
1135 Cfg.cMaxStreamsOut = UINT32_MAX;
1136 Cfg.cMaxStreamsIn = UINT32_MAX;
1137
1138 PULSEAUDIOENUMCBCTX CbCtx;
1139 RT_ZERO(CbCtx);
1140
1141 CbCtx.pDrv = pThis;
1142 CbCtx.fFlags = fEnum;
1143
1144 bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
1145
1146 pa_threaded_mainloop_lock(pThis->pMainLoop);
1147
1148 pThis->fEnumOpSuccess = false;
1149
1150 LogRel(("PulseAudio: Retrieving server information ...\n"));
1151
1152 /* Check if server information is available and bail out early if it isn't. */
1153 pa_operation *paOpServerInfo = pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx);
1154 if (!paOpServerInfo)
1155 {
1156 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1157
1158 LogRel(("PulseAudio: Server information not available, skipping enumeration\n"));
1159 return VINF_SUCCESS;
1160 }
1161
1162 int rc = paWaitFor(pThis, paOpServerInfo);
1163 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
1164 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
1165 if (RT_SUCCESS(rc))
1166 {
1167 if (CbCtx.pszDefaultSink)
1168 {
1169 if (fLog)
1170 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
1171
1172 pThis->fEnumOpSuccess = false;
1173 rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
1174 paEnumSinkCb, &CbCtx));
1175 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
1176 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
1177 if ( RT_FAILURE(rc)
1178 && fLog)
1179 {
1180 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink));
1181 }
1182 }
1183 else if (fLog)
1184 LogRel2(("PulseAudio: No default output sink found\n"));
1185
1186 if (RT_SUCCESS(rc))
1187 {
1188 if (CbCtx.pszDefaultSource)
1189 {
1190 if (fLog)
1191 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
1192
1193 pThis->fEnumOpSuccess = false;
1194 rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
1195 paEnumSourceCb, &CbCtx));
1196 if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess)
1197 && fLog)
1198 {
1199 LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource));
1200 }
1201 }
1202 else if (fLog)
1203 LogRel2(("PulseAudio: No default input source found\n"));
1204 }
1205
1206 if (RT_SUCCESS(rc))
1207 {
1208 if (fLog)
1209 {
1210 LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut));
1211 LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn));
1212 }
1213
1214 if (pCfg)
1215 memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
1216 }
1217
1218 if (CbCtx.pszDefaultSink)
1219 {
1220 RTStrFree(CbCtx.pszDefaultSink);
1221 CbCtx.pszDefaultSink = NULL;
1222 }
1223
1224 if (CbCtx.pszDefaultSource)
1225 {
1226 RTStrFree(CbCtx.pszDefaultSource);
1227 CbCtx.pszDefaultSource = NULL;
1228 }
1229 }
1230 else if (fLog)
1231 LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n"));
1232
1233 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1234
1235 LogFlowFuncLeaveRC(rc);
1236 return rc;
1237}
1238
1239
1240static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1241{
1242 LogFlowFuncEnter();
1243
1244 if (pStreamPA->pStream)
1245 {
1246 pa_threaded_mainloop_lock(pThis->pMainLoop);
1247
1248 pa_stream_disconnect(pStreamPA->pStream);
1249 pa_stream_unref(pStreamPA->pStream);
1250
1251 pStreamPA->pStream = NULL;
1252
1253 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1254 }
1255
1256 return VINF_SUCCESS;
1257}
1258
1259
1260static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1261{
1262 if (pStreamPA->pStream)
1263 {
1264 pa_threaded_mainloop_lock(pThis->pMainLoop);
1265
1266 /* Make sure to cancel a pending draining operation, if any. */
1267 if (pStreamPA->pDrainOp)
1268 {
1269 pa_operation_cancel(pStreamPA->pDrainOp);
1270 pStreamPA->pDrainOp = NULL;
1271 }
1272
1273 pa_stream_disconnect(pStreamPA->pStream);
1274 pa_stream_unref(pStreamPA->pStream);
1275
1276 pStreamPA->pStream = NULL;
1277
1278 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1279 }
1280
1281 return VINF_SUCCESS;
1282}
1283
1284
1285static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1286{
1287 int rc = VINF_SUCCESS;
1288
1289 switch (enmStreamCmd)
1290 {
1291 case PDMAUDIOSTREAMCMD_ENABLE:
1292 case PDMAUDIOSTREAMCMD_RESUME:
1293 {
1294 pa_threaded_mainloop_lock(pThis->pMainLoop);
1295
1296 if ( pStreamPA->pDrainOp
1297 && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
1298 {
1299 pa_operation_cancel(pStreamPA->pDrainOp);
1300 pa_operation_unref(pStreamPA->pDrainOp);
1301
1302 pStreamPA->pDrainOp = NULL;
1303 }
1304 else
1305 {
1306 /* Uncork (resume) stream. */
1307 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA));
1308 }
1309
1310 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1311 break;
1312 }
1313
1314 case PDMAUDIOSTREAMCMD_DISABLE:
1315 case PDMAUDIOSTREAMCMD_PAUSE:
1316 {
1317 /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
1318 * Note that we must return immediately from here! */
1319 pa_threaded_mainloop_lock(pThis->pMainLoop);
1320 if (!pStreamPA->pDrainOp)
1321 {
1322 rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA));
1323 if (RT_SUCCESS(rc))
1324 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA);
1325 }
1326 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1327 break;
1328 }
1329
1330 default:
1331 rc = VERR_NOT_SUPPORTED;
1332 break;
1333 }
1334
1335 LogFlowFuncLeaveRC(rc);
1336 return rc;
1337}
1338
1339
1340static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1341{
1342 int rc = VINF_SUCCESS;
1343
1344 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1345
1346 switch (enmStreamCmd)
1347 {
1348 case PDMAUDIOSTREAMCMD_ENABLE:
1349 case PDMAUDIOSTREAMCMD_RESUME:
1350 {
1351 pa_threaded_mainloop_lock(pThis->pMainLoop);
1352 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA));
1353 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1354 break;
1355 }
1356
1357 case PDMAUDIOSTREAMCMD_DISABLE:
1358 case PDMAUDIOSTREAMCMD_PAUSE:
1359 {
1360 pa_threaded_mainloop_lock(pThis->pMainLoop);
1361 if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
1362 {
1363 pa_stream_drop(pStreamPA->pStream);
1364 pStreamPA->pu8PeekBuf = NULL;
1365 }
1366
1367 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA));
1368 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1369 break;
1370 }
1371
1372 default:
1373 rc = VERR_NOT_SUPPORTED;
1374 break;
1375 }
1376
1377 return rc;
1378}
1379
1380
1381/**
1382 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
1383 */
1384static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface)
1385{
1386 AssertPtrReturnVoid(pInterface);
1387
1388 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1389
1390 LogFlowFuncEnter();
1391
1392 if (pThis->pMainLoop)
1393 pa_threaded_mainloop_stop(pThis->pMainLoop);
1394
1395 if (pThis->pContext)
1396 {
1397 pa_context_disconnect(pThis->pContext);
1398 pa_context_unref(pThis->pContext);
1399 pThis->pContext = NULL;
1400 }
1401
1402 if (pThis->pMainLoop)
1403 {
1404 pa_threaded_mainloop_free(pThis->pMainLoop);
1405 pThis->pMainLoop = NULL;
1406 }
1407
1408 LogFlowFuncLeave();
1409}
1410
1411
1412/**
1413 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1414 */
1415static DECLCALLBACK(int) drvHostPulseAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1416{
1417 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1418 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1419
1420 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1421
1422 return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */);
1423}
1424
1425
1426/**
1427 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1428 */
1429static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1430{
1431 RT_NOREF(enmDir);
1432 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
1433
1434 return PDMAUDIOBACKENDSTS_RUNNING;
1435}
1436
1437
1438/**
1439 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1440 */
1441static DECLCALLBACK(int) drvHostPulseAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1442 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1443{
1444 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1445 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1446 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1447 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1448
1449 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1450 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1451
1452 int rc;
1453 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1454 rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq);
1455 else if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
1456 rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq);
1457 else
1458 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
1459
1460 if (RT_SUCCESS(rc))
1461 {
1462 pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
1463 if (!pStreamPA->pCfg)
1464 rc = VERR_NO_MEMORY;
1465 }
1466
1467 return rc;
1468}
1469
1470
1471/**
1472 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1473 */
1474static DECLCALLBACK(int) drvHostPulseAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1475{
1476 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1477 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1478
1479 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1480 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1481
1482 if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
1483 return VINF_SUCCESS;
1484
1485 int rc;
1486 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1487 rc = paDestroyStreamIn (pThis, pStreamPA);
1488 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1489 rc = paDestroyStreamOut(pThis, pStreamPA);
1490 else
1491 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1492
1493 if (RT_SUCCESS(rc))
1494 {
1495 DrvAudioHlpStreamCfgFree(pStreamPA->pCfg);
1496 pStreamPA->pCfg = NULL;
1497 }
1498
1499 return rc;
1500}
1501
1502
1503/**
1504 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1505 */
1506static DECLCALLBACK(int) drvHostPulseAudioStreamControl(PPDMIHOSTAUDIO pInterface,
1507 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1508{
1509 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1510 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1511
1512 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1513 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1514
1515 if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
1516 return VINF_SUCCESS;
1517
1518 int rc;
1519 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1520 rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd);
1521 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1522 rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd);
1523 else
1524 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1525
1526 return rc;
1527}
1528
1529
1530static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1531{
1532 pa_threaded_mainloop_lock(pThis->pMainLoop);
1533
1534 uint32_t cbAvail = 0;
1535
1536 if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
1537 {
1538 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1539 {
1540 cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
1541 Log3Func(("cbReadable=%RU32\n", cbAvail));
1542 }
1543 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1544 {
1545 size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
1546
1547 Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
1548 cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1549
1550 /* Don't report more writable than the PA server can handle. */
1551 if (cbWritable > pStreamPA->BufAttr.maxlength)
1552 cbWritable = pStreamPA->BufAttr.maxlength;
1553
1554 cbAvail = (uint32_t)cbWritable;
1555 }
1556 else
1557 AssertFailed();
1558 }
1559
1560 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1561
1562 return cbAvail;
1563}
1564
1565
1566/**
1567 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1568 */
1569static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1570{
1571 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1572 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1573
1574 return paStreamGetAvail(pThis, pStreamPA);
1575}
1576
1577
1578/**
1579 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1580 */
1581static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1582{
1583 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1584 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1585
1586 return paStreamGetAvail(pThis, pStreamPA);
1587}
1588
1589
1590/**
1591 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
1592 */
1593static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1594{
1595 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1596 RT_NOREF(pStream);
1597
1598 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1599
1600 PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
1601
1602 /* Check PulseAudio's general status. */
1603 if ( pThis->pContext
1604 && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
1605 {
1606 strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
1607 }
1608
1609 return strmSts;
1610}
1611
1612
1613/**
1614 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
1615 */
1616static DECLCALLBACK(int) drvHostPulseAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1617{
1618 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1619 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1620
1621 LogFlowFuncEnter();
1622
1623 /* Nothing to do here for PulseAudio. */
1624 return VINF_SUCCESS;
1625}
1626
1627
1628/**
1629 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1630 */
1631static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1632{
1633 AssertPtrReturn(pInterface, NULL);
1634 AssertPtrReturn(pszIID, NULL);
1635
1636 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1637 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1638 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1639 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1640
1641 return NULL;
1642}
1643
1644
1645/**
1646 * Destructs a PulseAudio Audio driver instance.
1647 *
1648 * @copydoc FNPDMDRVDESTRUCT
1649 */
1650static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns)
1651{
1652 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1653 LogFlowFuncEnter();
1654}
1655
1656
1657/**
1658 * Constructs a PulseAudio Audio driver instance.
1659 *
1660 * @copydoc FNPDMDRVCONSTRUCT
1661 */
1662static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1663{
1664 RT_NOREF(pCfg, fFlags);
1665 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1666 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1667
1668 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1669 LogRel(("Audio: Initializing PulseAudio driver\n"));
1670
1671 pThis->pDrvIns = pDrvIns;
1672 /* IBase */
1673 pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface;
1674 /* IHostAudio */
1675 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio);
1676
1677 int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
1678 AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
1679
1680 return VINF_SUCCESS;
1681}
1682
1683
1684/**
1685 * Pulse audio driver registration record.
1686 */
1687const PDMDRVREG g_DrvHostPulseAudio =
1688{
1689 /* u32Version */
1690 PDM_DRVREG_VERSION,
1691 /* szName */
1692 "PulseAudio",
1693 /* szRCMod */
1694 "",
1695 /* szR0Mod */
1696 "",
1697 /* pszDescription */
1698 "Pulse Audio host driver",
1699 /* fFlags */
1700 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1701 /* fClass. */
1702 PDM_DRVREG_CLASS_AUDIO,
1703 /* cMaxInstances */
1704 ~0U,
1705 /* cbInstance */
1706 sizeof(DRVHOSTPULSEAUDIO),
1707 /* pfnConstruct */
1708 drvHostPulseAudioConstruct,
1709 /* pfnDestruct */
1710 drvHostPulseAudioDestruct,
1711 /* pfnRelocate */
1712 NULL,
1713 /* pfnIOCtl */
1714 NULL,
1715 /* pfnPowerOn */
1716 NULL,
1717 /* pfnReset */
1718 NULL,
1719 /* pfnSuspend */
1720 NULL,
1721 /* pfnResume */
1722 NULL,
1723 /* pfnAttach */
1724 NULL,
1725 /* pfnDetach */
1726 NULL,
1727 /* pfnPowerOff */
1728 NULL,
1729 /* pfnSoftReset */
1730 NULL,
1731 /* u32EndVersion */
1732 PDM_DRVREG_VERSION
1733};
1734
1735#if 0 /* unused */
1736static struct audio_option pulse_options[] =
1737{
1738 {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out,
1739 "DAC period size in milliseconds", NULL, 0},
1740 {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in,
1741 "ADC period size in milliseconds", NULL, 0}
1742};
1743#endif
1744
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