VirtualBox

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

Last change on this file since 73643 was 73529, checked in by vboxsync, 7 years ago

Audio: Changed cBits -> cBytes of PDMAUDIOPCMPROPS to avoid some unnecessary calculations (light optimization).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.9 KB
Line 
1/* $Id: DrvHostPulseAudio.cpp 73529 2018-08-06 16:26:43Z vboxsync $ */
2/** @file
3 * VBox audio devices: Pulse Audio audio driver.
4 */
5
6/*
7 * Copyright (C) 2006-2018 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 Log2Func(("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# ifdef LOG_ENABLED
436 pa_usec_t curLatencyUs = 0;
437 pa_stream_get_latency(pStream, &curLatencyUs, NULL /* Neg */);
438
439 const pa_timing_info *pTInfo = pa_stream_get_timing_info(pStream);
440 const pa_sample_spec *pSpec = pa_stream_get_sample_spec(pStream);
441
442 pa_usec_t curPosWritesUs = pa_bytes_to_usec(pTInfo->write_index, pSpec);
443 pa_usec_t curTsUs = pa_rtclock_now() - pStrm->tsStartUs;
444
445 Log2Func(("curPosWrite=%RU64ms, curTs=%RU64ms, curDelta=%RI64ms, curLatency=%RU64ms\n",
446 curPosWritesUs / 1000, curTsUs / 1000, (((int64_t)curPosWritesUs - (int64_t)curTsUs) / 1000), curLatencyUs / 1000));
447# endif
448}
449
450
451static void paStreamCbOverflow(pa_stream *pStream, void *pvContext)
452{
453 RT_NOREF(pStream, pvContext);
454
455 Log2Func(("Warning: Hit overflow\n"));
456}
457#endif /* DEBUG */
458
459
460static void paStreamCbSuccess(pa_stream *pStream, int fSuccess, void *pvUser)
461{
462 AssertPtrReturnVoid(pStream);
463
464 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvUser;
465 AssertPtrReturnVoid(pStrm);
466
467 pStrm->fOpSuccess = fSuccess;
468
469 if (fSuccess)
470 paSignalWaiter(pStrm->pDrv);
471 else
472 paError(pStrm->pDrv, "Failed to finish stream operation");
473}
474
475
476static int paStreamOpen(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, bool fIn, const char *pszName)
477{
478 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
479 AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
480 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
481
482 int rc = VINF_SUCCESS;
483
484 pa_stream *pStream = NULL;
485 uint32_t flags = PA_STREAM_NOFLAGS;
486
487 pa_threaded_mainloop_lock(pThis->pMainLoop);
488
489 do
490 {
491 pa_sample_spec *pSampleSpec = &pStreamPA->SampleSpec;
492
493 LogFunc(("Opening '%s', rate=%dHz, channels=%d, format=%s\n",
494 pszName, pSampleSpec->rate, pSampleSpec->channels,
495 pa_sample_format_to_string(pSampleSpec->format)));
496
497 if (!pa_sample_spec_valid(pSampleSpec))
498 {
499 LogRel(("PulseAudio: Unsupported sample specification for stream '%s'\n", pszName));
500 rc = VERR_NOT_SUPPORTED;
501 break;
502 }
503
504 pa_buffer_attr *pBufAttr = &pStreamPA->BufAttr;
505
506 /** @todo r=andy Use pa_stream_new_with_proplist instead. */
507 if (!(pStream = pa_stream_new(pThis->pContext, pszName, pSampleSpec, NULL /* pa_channel_map */)))
508 {
509 LogRel(("PulseAudio: Could not create stream '%s'\n", pszName));
510 rc = VERR_NO_MEMORY;
511 break;
512 }
513
514#ifdef DEBUG
515 pa_stream_set_write_callback (pStream, paStreamCbReqWrite, pStreamPA);
516 pa_stream_set_underflow_callback (pStream, paStreamCbUnderflow, pStreamPA);
517 if (!fIn) /* Only for output streams. */
518 pa_stream_set_overflow_callback(pStream, paStreamCbOverflow, pStreamPA);
519#endif
520 pa_stream_set_state_callback (pStream, paStreamCbStateChanged, pThis);
521
522#if PA_API_VERSION >= 12
523 /* XXX */
524 flags |= PA_STREAM_ADJUST_LATENCY;
525#endif
526 /* For using pa_stream_get_latency() and pa_stream_get_time(). */
527 flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
528
529 /* No input/output right away after the stream was started. */
530 flags |= PA_STREAM_START_CORKED;
531
532 if (fIn)
533 {
534 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
535 pBufAttr->maxlength, pBufAttr->fragsize));
536
537 if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
538 {
539 LogRel(("PulseAudio: Could not connect input stream '%s': %s\n",
540 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
541 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
542 break;
543 }
544 }
545 else
546 {
547 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
548 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
549
550 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
551 /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
552 {
553 LogRel(("PulseAudio: Could not connect playback stream '%s': %s\n",
554 pszName, pa_strerror(pa_context_errno(pThis->pContext))));
555 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
556 break;
557 }
558 }
559
560 /* Wait until the stream is ready. */
561 for (;;)
562 {
563 if (!pThis->fAbortLoop)
564 pa_threaded_mainloop_wait(pThis->pMainLoop);
565 pThis->fAbortLoop = false;
566
567 pa_stream_state_t streamSt = pa_stream_get_state(pStream);
568 if (streamSt == PA_STREAM_READY)
569 break;
570 else if ( streamSt == PA_STREAM_FAILED
571 || streamSt == PA_STREAM_TERMINATED)
572 {
573 LogRel(("PulseAudio: Failed to initialize stream '%s' (state %ld)\n", pszName, streamSt));
574 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
575 break;
576 }
577 }
578
579#ifdef LOG_ENABLED
580 pStreamPA->tsStartUs = pa_rtclock_now();
581#endif
582 if (RT_FAILURE(rc))
583 break;
584
585 const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
586 AssertPtr(pBufAttrObtained);
587 memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
588
589 LogFunc(("Obtained %s buffer attributes: tLength=%RU32, maxLength=%RU32, minReq=%RU32, fragSize=%RU32, preBuf=%RU32\n",
590 fIn ? "capture" : "playback",
591 pBufAttr->tlength, pBufAttr->maxlength, pBufAttr->minreq, pBufAttr->fragsize, pBufAttr->prebuf));
592
593 pStreamPA->pStream = pStream;
594
595 } while (0);
596
597 if ( RT_FAILURE(rc)
598 && pStream)
599 pa_stream_disconnect(pStream);
600
601 pa_threaded_mainloop_unlock(pThis->pMainLoop);
602
603 if (RT_FAILURE(rc))
604 {
605 if (pStream)
606 pa_stream_unref(pStream);
607 }
608
609 LogFlowFuncLeaveRC(rc);
610 return rc;
611}
612
613
614/**
615 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
616 */
617static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface)
618{
619 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
620
621 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
622
623 LogFlowFuncEnter();
624
625 int rc = audioLoadPulseLib();
626 if (RT_FAILURE(rc))
627 {
628 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
629 return rc;
630 }
631
632 LogRel(("PulseAudio: Using v%s\n", pa_get_library_version()));
633
634 pThis->fAbortLoop = false;
635 pThis->pMainLoop = NULL;
636
637 bool fLocked = false;
638
639 do
640 {
641 if (!(pThis->pMainLoop = pa_threaded_mainloop_new()))
642 {
643 LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
644 pa_strerror(pa_context_errno(pThis->pContext))));
645 rc = VERR_NO_MEMORY;
646 break;
647 }
648
649 if (!(pThis->pContext = pa_context_new(pa_threaded_mainloop_get_api(pThis->pMainLoop), "VirtualBox")))
650 {
651 LogRel(("PulseAudio: Failed to allocate context: %s\n",
652 pa_strerror(pa_context_errno(pThis->pContext))));
653 rc = VERR_NO_MEMORY;
654 break;
655 }
656
657 if (pa_threaded_mainloop_start(pThis->pMainLoop) < 0)
658 {
659 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
660 pa_strerror(pa_context_errno(pThis->pContext))));
661 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
662 break;
663 }
664
665 /* Install a global callback to known if something happens to our acquired context. */
666 pa_context_set_state_callback(pThis->pContext, paContextCbStateChanged, pThis /* pvUserData */);
667
668 pa_threaded_mainloop_lock(pThis->pMainLoop);
669 fLocked = true;
670
671 if (pa_context_connect(pThis->pContext, NULL /* pszServer */,
672 PA_CONTEXT_NOFLAGS, NULL) < 0)
673 {
674 LogRel(("PulseAudio: Failed to connect to server: %s\n",
675 pa_strerror(pa_context_errno(pThis->pContext))));
676 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
677 break;
678 }
679
680 /* Wait until the pThis->pContext is ready. */
681 for (;;)
682 {
683 if (!pThis->fAbortLoop)
684 pa_threaded_mainloop_wait(pThis->pMainLoop);
685 pThis->fAbortLoop = false;
686
687 pa_context_state_t cstate = pa_context_get_state(pThis->pContext);
688 if (cstate == PA_CONTEXT_READY)
689 break;
690 else if ( cstate == PA_CONTEXT_TERMINATED
691 || cstate == PA_CONTEXT_FAILED)
692 {
693 LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate));
694 rc = VERR_AUDIO_BACKEND_INIT_FAILED;
695 break;
696 }
697 }
698 }
699 while (0);
700
701 if (fLocked)
702 pa_threaded_mainloop_unlock(pThis->pMainLoop);
703
704 if (RT_FAILURE(rc))
705 {
706 if (pThis->pMainLoop)
707 pa_threaded_mainloop_stop(pThis->pMainLoop);
708
709 if (pThis->pContext)
710 {
711 pa_context_disconnect(pThis->pContext);
712 pa_context_unref(pThis->pContext);
713 pThis->pContext = NULL;
714 }
715
716 if (pThis->pMainLoop)
717 {
718 pa_threaded_mainloop_free(pThis->pMainLoop);
719 pThis->pMainLoop = NULL;
720 }
721 }
722
723 LogFlowFuncLeaveRC(rc);
724 return rc;
725}
726
727
728static int paCreateStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
729 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
730{
731 pStreamPA->pDrainOp = NULL;
732
733 pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
734 pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
735 pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
736
737 pStreamPA->curLatencyUs = DrvAudioHlpFramesToMilli(pCfgReq->Backend.cfBufferSize, &pCfgReq->Props) * RT_US_1MS;
738
739 const uint32_t cbLatency = pa_usec_to_bytes(pStreamPA->curLatencyUs, &pStreamPA->SampleSpec);
740
741 LogRel2(("PulseAudio: Initial output latency is %RU64ms (%RU32 bytes)\n", pStreamPA->curLatencyUs / RT_US_1MS, cbLatency));
742
743 pStreamPA->BufAttr.tlength = cbLatency;
744 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
745 pStreamPA->BufAttr.prebuf = cbLatency;
746 pStreamPA->BufAttr.minreq = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
747
748 LogFunc(("Requested: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
749 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
750
751 Assert(pCfgReq->enmDir == PDMAUDIODIR_OUT);
752
753 char szName[256];
754 RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
755 DrvAudioHlpPlaybackDstToStr(pCfgReq->DestSource.Dest), pThis->szStreamName);
756
757 /* Note that the struct BufAttr is updated to the obtained values after this call! */
758 int rc = paStreamOpen(pThis, pStreamPA, false /* fIn */, szName);
759 if (RT_FAILURE(rc))
760 return rc;
761
762 rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
763 if (RT_FAILURE(rc))
764 {
765 LogRel(("PulseAudio: Cannot find audio output format %ld\n", pStreamPA->SampleSpec.format));
766 return rc;
767 }
768
769 pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
770 pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
771 pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBytes, pCfgAcq->Props.cChannels);
772
773 uint32_t cbBuf = RT_MIN(pStreamPA->BufAttr.tlength * 2,
774 pStreamPA->BufAttr.maxlength); /** @todo Make this configurable! */
775
776 LogFunc(("Acquired: BufAttr tlength=%RU32, maxLength=%RU32, minReq=%RU32\n",
777 pStreamPA->BufAttr.tlength, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
778
779 if (cbBuf)
780 {
781 pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.minreq);
782 pCfgAcq->Backend.cfBufferSize = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.tlength);
783 pCfgAcq->Backend.cfPreBuf = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.prebuf);
784
785 pStreamPA->pDrv = pThis;
786 }
787 else
788 rc = VERR_INVALID_PARAMETER;
789
790 return rc;
791}
792
793
794static int paCreateStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA,
795 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
796{
797 pStreamPA->SampleSpec.format = paAudioPropsToPulse(&pCfgReq->Props);
798 pStreamPA->SampleSpec.rate = pCfgReq->Props.uHz;
799 pStreamPA->SampleSpec.channels = pCfgReq->Props.cChannels;
800
801 pStreamPA->BufAttr.fragsize = DrvAudioHlpFramesToBytes(pCfgReq->Backend.cfPeriod, &pCfgReq->Props);
802 pStreamPA->BufAttr.maxlength = -1; /* Let the PulseAudio server choose the biggest size it can handle. */
803
804 Assert(pCfgReq->enmDir == PDMAUDIODIR_IN);
805
806 char szName[256];
807 RTStrPrintf2(szName, sizeof(szName), "VirtualBox %s [%s]",
808 DrvAudioHlpRecSrcToStr(pCfgReq->DestSource.Source), pThis->szStreamName);
809
810 /* Note: Other members of BufAttr are ignored for record streams. */
811 int rc = paStreamOpen(pThis, pStreamPA, true /* fIn */, szName);
812 if (RT_FAILURE(rc))
813 return rc;
814
815 rc = paPulseToAudioProps(pStreamPA->SampleSpec.format, &pCfgAcq->Props);
816 if (RT_FAILURE(rc))
817 {
818 LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pStreamPA->SampleSpec.format));
819 return rc;
820 }
821
822 pStreamPA->pDrv = pThis;
823 pStreamPA->pu8PeekBuf = NULL;
824
825 pCfgAcq->Props.uHz = pStreamPA->SampleSpec.rate;
826 pCfgAcq->Props.cChannels = pStreamPA->SampleSpec.channels;
827
828 pCfgAcq->Backend.cfPeriod = PDMAUDIOSTREAMCFG_B2F(pCfgAcq, pStreamPA->BufAttr.fragsize);
829 pCfgAcq->Backend.cfBufferSize = pCfgAcq->Backend.cfPeriod * 2; /* Use double buffering. */
830
831 LogFlowFuncLeaveRC(rc);
832 return rc;
833}
834
835
836/**
837 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
838 */
839static DECLCALLBACK(int) drvHostPulseAudioStreamCapture(PPDMIHOSTAUDIO pInterface,
840 PPDMAUDIOBACKENDSTREAM pStream, void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
841{
842 RT_NOREF(pvBuf, cxBuf);
843 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
844 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
845 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
846 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
847 /* pcbRead is optional. */
848
849 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
850 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
851
852 /* We should only call pa_stream_readable_size() once and trust the first value. */
853 pa_threaded_mainloop_lock(pThis->pMainLoop);
854 size_t cbAvail = pa_stream_readable_size(pStreamPA->pStream);
855 pa_threaded_mainloop_unlock(pThis->pMainLoop);
856
857 if (cbAvail == (size_t)-1)
858 return paError(pStreamPA->pDrv, "Failed to determine input data size");
859
860 /* If the buffer was not dropped last call, add what remains. */
861 if (pStreamPA->pu8PeekBuf)
862 {
863 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
864 cbAvail += (pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf);
865 }
866
867 Log3Func(("cbAvail=%zu\n", cbAvail));
868
869 if (!cbAvail) /* No data? Bail out. */
870 {
871 if (pcxRead)
872 *pcxRead = 0;
873 return VINF_SUCCESS;
874 }
875
876 int rc = VINF_SUCCESS;
877
878 size_t cbToRead = RT_MIN(cbAvail, cxBuf);
879
880 Log3Func(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
881 cbToRead, cbAvail, pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf));
882
883 uint32_t cbReadTotal = 0;
884
885 while (cbToRead)
886 {
887 /* If there is no data, do another peek. */
888 if (!pStreamPA->pu8PeekBuf)
889 {
890 pa_threaded_mainloop_lock(pThis->pMainLoop);
891 pa_stream_peek(pStreamPA->pStream,
892 (const void**)&pStreamPA->pu8PeekBuf, &pStreamPA->cbPeekBuf);
893 pa_threaded_mainloop_unlock(pThis->pMainLoop);
894
895 pStreamPA->offPeekBuf = 0;
896
897 /* No data anymore?
898 * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
899 * we need to drop the stream lateron. */
900 if ( !pStreamPA->pu8PeekBuf
901 && !pStreamPA->cbPeekBuf)
902 {
903 break;
904 }
905 }
906
907 Assert(pStreamPA->cbPeekBuf >= pStreamPA->offPeekBuf);
908 size_t cbToWrite = RT_MIN(pStreamPA->cbPeekBuf - pStreamPA->offPeekBuf, cbToRead);
909
910 Log3Func(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
911 cbToRead, cbToWrite,
912 pStreamPA->offPeekBuf, pStreamPA->cbPeekBuf, pStreamPA->pu8PeekBuf));
913
914 if ( cbToWrite
915 /* Only copy data if it's not a data hole (see above). */
916 && pStreamPA->pu8PeekBuf
917 && pStreamPA->cbPeekBuf)
918 {
919 memcpy((uint8_t *)pvBuf + cbReadTotal, pStreamPA->pu8PeekBuf + pStreamPA->offPeekBuf, cbToWrite);
920
921 Assert(cbToRead >= cbToWrite);
922 cbToRead -= cbToWrite;
923 cbReadTotal += cbToWrite;
924
925 pStreamPA->offPeekBuf += cbToWrite;
926 Assert(pStreamPA->offPeekBuf <= pStreamPA->cbPeekBuf);
927 }
928
929 if (/* Nothing to write anymore? Drop the buffer. */
930 !cbToWrite
931 /* Was there a hole in the peeking buffer? Drop it. */
932 || !pStreamPA->pu8PeekBuf
933 /* If the buffer is done, drop it. */
934 || pStreamPA->offPeekBuf == pStreamPA->cbPeekBuf)
935 {
936 pa_threaded_mainloop_lock(pThis->pMainLoop);
937 pa_stream_drop(pStreamPA->pStream);
938 pa_threaded_mainloop_unlock(pThis->pMainLoop);
939
940 pStreamPA->pu8PeekBuf = NULL;
941 }
942 }
943
944 if (RT_SUCCESS(rc))
945 {
946 if (pcxRead)
947 *pcxRead = cbReadTotal;
948 }
949
950 return rc;
951}
952
953
954/**
955 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
956 */
957static DECLCALLBACK(int) drvHostPulseAudioStreamPlay(PPDMIHOSTAUDIO pInterface,
958 PPDMAUDIOBACKENDSTREAM pStream, const void *pvBuf, uint32_t cxBuf,
959 uint32_t *pcxWritten)
960{
961 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
962 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
963 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
964 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
965 /* pcxWritten is optional. */
966
967 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
968 PPULSEAUDIOSTREAM pPAStream = (PPULSEAUDIOSTREAM)pStream;
969
970 int rc = VINF_SUCCESS;
971
972 uint32_t cbWrittenTotal = 0;
973
974 pa_threaded_mainloop_lock(pThis->pMainLoop);
975
976#ifdef LOG_ENABLED
977 const pa_usec_t tsNowUs = pa_rtclock_now();
978 const pa_usec_t tsDeltaPlayedUs = tsNowUs - pPAStream->tsLastReadWrittenUs;
979
980 Log3Func(("tsDeltaPlayedMs=%RU64\n", tsDeltaPlayedUs / 1000 /* ms */));
981
982 pPAStream->tsLastReadWrittenUs = tsNowUs;
983#endif
984
985 do
986 {
987 size_t cbWriteable = pa_stream_writable_size(pPAStream->pStream);
988 if (cbWriteable == (size_t)-1)
989 {
990 rc = paError(pPAStream->pDrv, "Failed to determine output data size");
991 break;
992 }
993
994 size_t cbLeft = RT_MIN(cbWriteable, cxBuf);
995 Assert(cbLeft); /* At this point we better have *something* to write. */
996
997 while (cbLeft)
998 {
999 uint32_t cbChunk = cbLeft; /* Write all at once for now. */
1000
1001 if (pa_stream_write(pPAStream->pStream, (uint8_t *)pvBuf + cbWrittenTotal, cbChunk, NULL /* Cleanup callback */,
1002 0, PA_SEEK_RELATIVE) < 0)
1003 {
1004 rc = paError(pPAStream->pDrv, "Failed to write to output stream");
1005 break;
1006 }
1007
1008 Assert(cbLeft >= cbChunk);
1009 cbLeft -= cbChunk;
1010 cbWrittenTotal += cbChunk;
1011 }
1012
1013 } while (0);
1014
1015 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1016
1017 if (RT_SUCCESS(rc))
1018 {
1019 if (pcxWritten)
1020 *pcxWritten = cbWrittenTotal;
1021 }
1022
1023 return rc;
1024}
1025
1026
1027/** @todo Implement va handling. */
1028static int paError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
1029{
1030 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1031 AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
1032
1033 if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
1034 {
1035 int rc2 = pa_context_errno(pThis->pContext);
1036 LogRel2(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
1037 }
1038
1039 /** @todo Implement some PulseAudio -> IPRT mapping here. */
1040 return VERR_GENERAL_FAILURE;
1041}
1042
1043
1044static void paEnumSinkCb(pa_context *pCtx, const pa_sink_info *pInfo, int eol, void *pvUserData)
1045{
1046 if (eol > 0)
1047 return;
1048
1049 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1050 AssertPtrReturnVoid(pCbCtx);
1051 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1052 AssertPtrReturnVoid(pThis);
1053 if (eol < 0)
1054 {
1055 pThis->fEnumOpSuccess = false;
1056 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1057 return;
1058 }
1059
1060 AssertPtrReturnVoid(pCtx);
1061 AssertPtrReturnVoid(pInfo);
1062
1063 LogRel2(("PulseAudio: Using output sink '%s'\n", pInfo->name));
1064
1065 /** @todo Store sinks + channel mapping in callback context as soon as we have surround support. */
1066 pCbCtx->cDevOut++;
1067
1068 pThis->fEnumOpSuccess = true;
1069 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1070}
1071
1072
1073static void paEnumSourceCb(pa_context *pCtx, const pa_source_info *pInfo, int eol, void *pvUserData)
1074{
1075 if (eol > 0)
1076 return;
1077
1078 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1079 AssertPtrReturnVoid(pCbCtx);
1080 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1081 AssertPtrReturnVoid(pThis);
1082 if (eol < 0)
1083 {
1084 pThis->fEnumOpSuccess = false;
1085 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1086 return;
1087 }
1088
1089 AssertPtrReturnVoid(pCtx);
1090 AssertPtrReturnVoid(pInfo);
1091
1092 LogRel2(("PulseAudio: Using input source '%s'\n", pInfo->name));
1093
1094 /** @todo Store sources + channel mapping in callback context as soon as we have surround support. */
1095 pCbCtx->cDevIn++;
1096
1097 pThis->fEnumOpSuccess = true;
1098 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1099}
1100
1101
1102static void paEnumServerCb(pa_context *pCtx, const pa_server_info *pInfo, void *pvUserData)
1103{
1104 AssertPtrReturnVoid(pCtx);
1105 PPULSEAUDIOENUMCBCTX pCbCtx = (PPULSEAUDIOENUMCBCTX)pvUserData;
1106 AssertPtrReturnVoid(pCbCtx);
1107 PDRVHOSTPULSEAUDIO pThis = pCbCtx->pDrv;
1108 AssertPtrReturnVoid(pThis);
1109
1110 if (!pInfo)
1111 {
1112 pThis->fEnumOpSuccess = false;
1113 pa_threaded_mainloop_signal(pCbCtx->pDrv->pMainLoop, 0);
1114 return;
1115 }
1116
1117 if (pInfo->default_sink_name)
1118 {
1119 Assert(RTStrIsValidEncoding(pInfo->default_sink_name));
1120 pCbCtx->pszDefaultSink = RTStrDup(pInfo->default_sink_name);
1121 }
1122
1123 if (pInfo->default_sink_name)
1124 {
1125 Assert(RTStrIsValidEncoding(pInfo->default_source_name));
1126 pCbCtx->pszDefaultSource = RTStrDup(pInfo->default_source_name);
1127 }
1128
1129 pThis->fEnumOpSuccess = true;
1130 pa_threaded_mainloop_signal(pThis->pMainLoop, 0);
1131}
1132
1133
1134static int paEnumerate(PDRVHOSTPULSEAUDIO pThis, PPDMAUDIOBACKENDCFG pCfg, uint32_t fEnum)
1135{
1136 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1137 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
1138
1139 PDMAUDIOBACKENDCFG Cfg;
1140 RT_ZERO(Cfg);
1141
1142 Cfg.cbStreamOut = sizeof(PULSEAUDIOSTREAM);
1143 Cfg.cbStreamIn = sizeof(PULSEAUDIOSTREAM);
1144 Cfg.cMaxStreamsOut = UINT32_MAX;
1145 Cfg.cMaxStreamsIn = UINT32_MAX;
1146
1147 PULSEAUDIOENUMCBCTX CbCtx;
1148 RT_ZERO(CbCtx);
1149
1150 CbCtx.pDrv = pThis;
1151 CbCtx.fFlags = fEnum;
1152
1153 bool fLog = (fEnum & PULSEAUDIOENUMCBFLAGS_LOG);
1154
1155 pa_threaded_mainloop_lock(pThis->pMainLoop);
1156
1157 pThis->fEnumOpSuccess = false;
1158 int rc = paWaitFor(pThis, pa_context_get_server_info(pThis->pContext, paEnumServerCb, &CbCtx));
1159 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
1160 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
1161 if (RT_SUCCESS(rc))
1162 {
1163 if (CbCtx.pszDefaultSink)
1164 {
1165 if (fLog)
1166 LogRel2(("PulseAudio: Default output sink is '%s'\n", CbCtx.pszDefaultSink));
1167
1168 pThis->fEnumOpSuccess = false;
1169 rc = paWaitFor(pThis, pa_context_get_sink_info_by_name(pThis->pContext, CbCtx.pszDefaultSink,
1170 paEnumSinkCb, &CbCtx));
1171 if (RT_SUCCESS(rc) && !pThis->fEnumOpSuccess)
1172 rc = VERR_AUDIO_BACKEND_INIT_FAILED; /* error code does not matter */
1173 if ( RT_FAILURE(rc)
1174 && fLog)
1175 {
1176 LogRel(("PulseAudio: Error enumerating properties for default output sink '%s'\n", CbCtx.pszDefaultSink));
1177 }
1178 }
1179 else if (fLog)
1180 LogRel2(("PulseAudio: No default output sink found\n"));
1181
1182 if (RT_SUCCESS(rc))
1183 {
1184 if (CbCtx.pszDefaultSource)
1185 {
1186 if (fLog)
1187 LogRel2(("PulseAudio: Default input source is '%s'\n", CbCtx.pszDefaultSource));
1188
1189 pThis->fEnumOpSuccess = false;
1190 rc = paWaitFor(pThis, pa_context_get_source_info_by_name(pThis->pContext, CbCtx.pszDefaultSource,
1191 paEnumSourceCb, &CbCtx));
1192 if ( (RT_FAILURE(rc) || !pThis->fEnumOpSuccess)
1193 && fLog)
1194 {
1195 LogRel(("PulseAudio: Error enumerating properties for default input source '%s'\n", CbCtx.pszDefaultSource));
1196 }
1197 }
1198 else if (fLog)
1199 LogRel2(("PulseAudio: No default input source found\n"));
1200 }
1201
1202 if (RT_SUCCESS(rc))
1203 {
1204 if (fLog)
1205 {
1206 LogRel2(("PulseAudio: Found %RU8 host playback device(s)\n", CbCtx.cDevOut));
1207 LogRel2(("PulseAudio: Found %RU8 host capturing device(s)\n", CbCtx.cDevIn));
1208 }
1209
1210 if (pCfg)
1211 memcpy(pCfg, &Cfg, sizeof(PDMAUDIOBACKENDCFG));
1212 }
1213
1214 if (CbCtx.pszDefaultSink)
1215 {
1216 RTStrFree(CbCtx.pszDefaultSink);
1217 CbCtx.pszDefaultSink = NULL;
1218 }
1219
1220 if (CbCtx.pszDefaultSource)
1221 {
1222 RTStrFree(CbCtx.pszDefaultSource);
1223 CbCtx.pszDefaultSource = NULL;
1224 }
1225 }
1226 else if (fLog)
1227 LogRel(("PulseAudio: Error enumerating PulseAudio server properties\n"));
1228
1229 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1230
1231 LogFlowFuncLeaveRC(rc);
1232 return rc;
1233}
1234
1235
1236static int paDestroyStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1237{
1238 LogFlowFuncEnter();
1239
1240 if (pStreamPA->pStream)
1241 {
1242 pa_threaded_mainloop_lock(pThis->pMainLoop);
1243
1244 pa_stream_disconnect(pStreamPA->pStream);
1245 pa_stream_unref(pStreamPA->pStream);
1246
1247 pStreamPA->pStream = NULL;
1248
1249 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1250 }
1251
1252 return VINF_SUCCESS;
1253}
1254
1255
1256static int paDestroyStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1257{
1258 if (pStreamPA->pStream)
1259 {
1260 pa_threaded_mainloop_lock(pThis->pMainLoop);
1261
1262 /* Make sure to cancel a pending draining operation, if any. */
1263 if (pStreamPA->pDrainOp)
1264 {
1265 pa_operation_cancel(pStreamPA->pDrainOp);
1266 pStreamPA->pDrainOp = NULL;
1267 }
1268
1269 pa_stream_disconnect(pStreamPA->pStream);
1270 pa_stream_unref(pStreamPA->pStream);
1271
1272 pStreamPA->pStream = NULL;
1273
1274 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1275 }
1276
1277 return VINF_SUCCESS;
1278}
1279
1280
1281static int paControlStreamOut(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1282{
1283 int rc = VINF_SUCCESS;
1284
1285 switch (enmStreamCmd)
1286 {
1287 case PDMAUDIOSTREAMCMD_ENABLE:
1288 case PDMAUDIOSTREAMCMD_RESUME:
1289 {
1290 pa_threaded_mainloop_lock(pThis->pMainLoop);
1291
1292 if ( pStreamPA->pDrainOp
1293 && pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_DONE)
1294 {
1295 pa_operation_cancel(pStreamPA->pDrainOp);
1296 pa_operation_unref(pStreamPA->pDrainOp);
1297
1298 pStreamPA->pDrainOp = NULL;
1299 }
1300 else
1301 {
1302 /* Uncork (resume) stream. */
1303 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Uncork */, paStreamCbSuccess, pStreamPA));
1304 }
1305
1306 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1307 break;
1308 }
1309
1310 case PDMAUDIOSTREAMCMD_DISABLE:
1311 case PDMAUDIOSTREAMCMD_PAUSE:
1312 {
1313 /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
1314 * Note that we must return immediately from here! */
1315 pa_threaded_mainloop_lock(pThis->pMainLoop);
1316 if (!pStreamPA->pDrainOp)
1317 {
1318 rc = paWaitFor(pThis, pa_stream_trigger(pStreamPA->pStream, paStreamCbSuccess, pStreamPA));
1319 if (RT_SUCCESS(rc))
1320 pStreamPA->pDrainOp = pa_stream_drain(pStreamPA->pStream, paStreamCbDrain, pStreamPA);
1321 }
1322 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1323 break;
1324 }
1325
1326 default:
1327 rc = VERR_NOT_SUPPORTED;
1328 break;
1329 }
1330
1331 LogFlowFuncLeaveRC(rc);
1332 return rc;
1333}
1334
1335
1336static int paControlStreamIn(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA, PDMAUDIOSTREAMCMD enmStreamCmd)
1337{
1338 int rc = VINF_SUCCESS;
1339
1340 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1341
1342 switch (enmStreamCmd)
1343 {
1344 case PDMAUDIOSTREAMCMD_ENABLE:
1345 case PDMAUDIOSTREAMCMD_RESUME:
1346 {
1347 pa_threaded_mainloop_lock(pThis->pMainLoop);
1348 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 0 /* Play / resume */, paStreamCbSuccess, pStreamPA));
1349 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1350 break;
1351 }
1352
1353 case PDMAUDIOSTREAMCMD_DISABLE:
1354 case PDMAUDIOSTREAMCMD_PAUSE:
1355 {
1356 pa_threaded_mainloop_lock(pThis->pMainLoop);
1357 if (pStreamPA->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
1358 {
1359 pa_stream_drop(pStreamPA->pStream);
1360 pStreamPA->pu8PeekBuf = NULL;
1361 }
1362
1363 rc = paWaitFor(pThis, pa_stream_cork(pStreamPA->pStream, 1 /* Stop / pause */, paStreamCbSuccess, pStreamPA));
1364 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1365 break;
1366 }
1367
1368 default:
1369 rc = VERR_NOT_SUPPORTED;
1370 break;
1371 }
1372
1373 return rc;
1374}
1375
1376
1377/**
1378 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
1379 */
1380static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface)
1381{
1382 AssertPtrReturnVoid(pInterface);
1383
1384 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1385
1386 LogFlowFuncEnter();
1387
1388 if (pThis->pMainLoop)
1389 pa_threaded_mainloop_stop(pThis->pMainLoop);
1390
1391 if (pThis->pContext)
1392 {
1393 pa_context_disconnect(pThis->pContext);
1394 pa_context_unref(pThis->pContext);
1395 pThis->pContext = NULL;
1396 }
1397
1398 if (pThis->pMainLoop)
1399 {
1400 pa_threaded_mainloop_free(pThis->pMainLoop);
1401 pThis->pMainLoop = NULL;
1402 }
1403
1404 LogFlowFuncLeave();
1405}
1406
1407
1408/**
1409 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1410 */
1411static DECLCALLBACK(int) drvHostPulseAudioGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1412{
1413 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1414 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1415
1416 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1417
1418 return paEnumerate(pThis, pBackendCfg, PULSEAUDIOENUMCBFLAGS_LOG /* fEnum */);
1419}
1420
1421
1422/**
1423 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1424 */
1425static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHostPulseAudioGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1426{
1427 RT_NOREF(enmDir);
1428 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
1429
1430 return PDMAUDIOBACKENDSTS_RUNNING;
1431}
1432
1433
1434/**
1435 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1436 */
1437static DECLCALLBACK(int) drvHostPulseAudioStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1438 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1439{
1440 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1441 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1442 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1443 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1444
1445 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1446 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1447
1448 int rc;
1449 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1450 rc = paCreateStreamIn (pThis, pStreamPA, pCfgReq, pCfgAcq);
1451 else if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
1452 rc = paCreateStreamOut(pThis, pStreamPA, pCfgReq, pCfgAcq);
1453 else
1454 AssertFailedReturn(VERR_NOT_IMPLEMENTED);
1455
1456 if (RT_SUCCESS(rc))
1457 {
1458 pStreamPA->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
1459 if (!pStreamPA->pCfg)
1460 rc = VERR_NO_MEMORY;
1461 }
1462
1463 return rc;
1464}
1465
1466
1467/**
1468 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1469 */
1470static DECLCALLBACK(int) drvHostPulseAudioStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1471{
1472 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1473 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1474
1475 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1476 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1477
1478 if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
1479 return VINF_SUCCESS;
1480
1481 int rc;
1482 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1483 rc = paDestroyStreamIn (pThis, pStreamPA);
1484 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1485 rc = paDestroyStreamOut(pThis, pStreamPA);
1486 else
1487 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1488
1489 if (RT_SUCCESS(rc))
1490 {
1491 DrvAudioHlpStreamCfgFree(pStreamPA->pCfg);
1492 pStreamPA->pCfg = NULL;
1493 }
1494
1495 return rc;
1496}
1497
1498
1499/**
1500 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
1501 */
1502static DECLCALLBACK(int) drvHostPulseAudioStreamControl(PPDMIHOSTAUDIO pInterface,
1503 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
1504{
1505 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1506 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1507
1508 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1509 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1510
1511 if (!pStreamPA->pCfg) /* Not (yet) configured? Skip. */
1512 return VINF_SUCCESS;
1513
1514 int rc;
1515 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1516 rc = paControlStreamIn (pThis, pStreamPA, enmStreamCmd);
1517 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1518 rc = paControlStreamOut(pThis, pStreamPA, enmStreamCmd);
1519 else
1520 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
1521
1522 return rc;
1523}
1524
1525
1526static uint32_t paStreamGetAvail(PDRVHOSTPULSEAUDIO pThis, PPULSEAUDIOSTREAM pStreamPA)
1527{
1528 pa_threaded_mainloop_lock(pThis->pMainLoop);
1529
1530 uint32_t cbAvail = 0;
1531
1532 if (PA_STREAM_IS_GOOD(pa_stream_get_state(pStreamPA->pStream)))
1533 {
1534 if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_IN)
1535 {
1536 cbAvail = (uint32_t)pa_stream_readable_size(pStreamPA->pStream);
1537 Log3Func(("cbReadable=%RU32\n", cbAvail));
1538 }
1539 else if (pStreamPA->pCfg->enmDir == PDMAUDIODIR_OUT)
1540 {
1541 size_t cbWritable = pa_stream_writable_size(pStreamPA->pStream);
1542
1543 Log3Func(("cbWritable=%zu, maxLength=%RU32, minReq=%RU32\n",
1544 cbWritable, pStreamPA->BufAttr.maxlength, pStreamPA->BufAttr.minreq));
1545
1546 /* Don't report more writable than the PA server can handle. */
1547 if (cbWritable > pStreamPA->BufAttr.maxlength)
1548 cbWritable = pStreamPA->BufAttr.maxlength;
1549
1550 cbAvail = (uint32_t)cbWritable;
1551 }
1552 else
1553 AssertFailed();
1554 }
1555
1556 pa_threaded_mainloop_unlock(pThis->pMainLoop);
1557
1558 return cbAvail;
1559}
1560
1561
1562/**
1563 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
1564 */
1565static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1566{
1567 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1568 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1569
1570 return paStreamGetAvail(pThis, pStreamPA);
1571}
1572
1573
1574/**
1575 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
1576 */
1577static DECLCALLBACK(uint32_t) drvHostPulseAudioStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1578{
1579 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1580 PPULSEAUDIOSTREAM pStreamPA = (PPULSEAUDIOSTREAM)pStream;
1581
1582 return paStreamGetAvail(pThis, pStreamPA);
1583}
1584
1585
1586/**
1587 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
1588 */
1589static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvHostPulseAudioStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1590{
1591 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1592 RT_NOREF(pStream);
1593
1594 PDRVHOSTPULSEAUDIO pThis = PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface);
1595
1596 PDMAUDIOSTREAMSTS strmSts = PDMAUDIOSTREAMSTS_FLAG_NONE;
1597
1598 /* Check PulseAudio's general status. */
1599 if ( pThis->pContext
1600 && PA_CONTEXT_IS_GOOD(pa_context_get_state(pThis->pContext)))
1601 {
1602 strmSts = PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED;
1603 }
1604
1605 return strmSts;
1606}
1607
1608
1609/**
1610 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
1611 */
1612static DECLCALLBACK(int) drvHostPulseAudioStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1613{
1614 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
1615 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1616
1617 LogFlowFuncEnter();
1618
1619 /* Nothing to do here for PulseAudio. */
1620 return VINF_SUCCESS;
1621}
1622
1623
1624/**
1625 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1626 */
1627static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1628{
1629 AssertPtrReturn(pInterface, NULL);
1630 AssertPtrReturn(pszIID, NULL);
1631
1632 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1633 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1634 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1635 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1636
1637 return NULL;
1638}
1639
1640
1641/**
1642 * Destructs a PulseAudio Audio driver instance.
1643 *
1644 * @copydoc FNPDMDRVDESTRUCT
1645 */
1646static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns)
1647{
1648 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1649 LogFlowFuncEnter();
1650}
1651
1652
1653/**
1654 * Constructs a PulseAudio Audio driver instance.
1655 *
1656 * @copydoc FNPDMDRVCONSTRUCT
1657 */
1658static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1659{
1660 RT_NOREF(pCfg, fFlags);
1661 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1662 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1663
1664 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1665 LogRel(("Audio: Initializing PulseAudio driver\n"));
1666
1667 pThis->pDrvIns = pDrvIns;
1668 /* IBase */
1669 pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface;
1670 /* IHostAudio */
1671 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio);
1672
1673 int rc2 = CFGMR3QueryString(pCfg, "StreamName", pThis->szStreamName, sizeof(pThis->szStreamName));
1674 AssertMsgRCReturn(rc2, ("Confguration error: No/bad \"StreamName\" value, rc=%Rrc\n", rc2), rc2);
1675
1676 return VINF_SUCCESS;
1677}
1678
1679
1680/**
1681 * Pulse audio driver registration record.
1682 */
1683const PDMDRVREG g_DrvHostPulseAudio =
1684{
1685 /* u32Version */
1686 PDM_DRVREG_VERSION,
1687 /* szName */
1688 "PulseAudio",
1689 /* szRCMod */
1690 "",
1691 /* szR0Mod */
1692 "",
1693 /* pszDescription */
1694 "Pulse Audio host driver",
1695 /* fFlags */
1696 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1697 /* fClass. */
1698 PDM_DRVREG_CLASS_AUDIO,
1699 /* cMaxInstances */
1700 ~0U,
1701 /* cbInstance */
1702 sizeof(DRVHOSTPULSEAUDIO),
1703 /* pfnConstruct */
1704 drvHostPulseAudioConstruct,
1705 /* pfnDestruct */
1706 drvHostPulseAudioDestruct,
1707 /* pfnRelocate */
1708 NULL,
1709 /* pfnIOCtl */
1710 NULL,
1711 /* pfnPowerOn */
1712 NULL,
1713 /* pfnReset */
1714 NULL,
1715 /* pfnSuspend */
1716 NULL,
1717 /* pfnResume */
1718 NULL,
1719 /* pfnAttach */
1720 NULL,
1721 /* pfnDetach */
1722 NULL,
1723 /* pfnPowerOff */
1724 NULL,
1725 /* pfnSoftReset */
1726 NULL,
1727 /* u32EndVersion */
1728 PDM_DRVREG_VERSION
1729};
1730
1731#if 0 /* unused */
1732static struct audio_option pulse_options[] =
1733{
1734 {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out,
1735 "DAC period size in milliseconds", NULL, 0},
1736 {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in,
1737 "ADC period size in milliseconds", NULL, 0}
1738};
1739#endif
1740
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette