VirtualBox

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

Last change on this file since 54575 was 54491, checked in by vboxsync, 10 years ago

PDM/Audio: Fixed crashes on termination.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.0 KB
Line 
1/* $Id: DrvHostPulseAudio.cpp 54491 2015-02-25 13:23:21Z vboxsync $ */
2/** @file
3 * VBox audio devices: Pulse Audio audio driver.
4 */
5
6/*
7 * Copyright (C) 2006-2015 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
23#include <stdio.h>
24
25#include <iprt/alloc.h>
26#include <iprt/mem.h>
27#include <iprt/uuid.h>
28
29RT_C_DECLS_BEGIN
30 #include "pulse_mangling.h"
31 #include "pulse_stubs.h"
32RT_C_DECLS_END
33
34#include <pulse/pulseaudio.h>
35#include "vl_vbox.h"
36
37#include "DrvAudio.h"
38#include "AudioMixBuffer.h"
39
40#ifdef LOG_GROUP
41# undef LOG_GROUP
42#endif
43#define LOG_GROUP LOG_GROUP_DEV_AUDIO
44#include <VBox/log.h>
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/*
57 * We use a g_pMainLoop in a separate thread g_pContext. We have to call functions for
58 * manipulating objects either from callback functions or we have to protect
59 * these functions by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
60 */
61static struct pa_threaded_mainloop *g_pMainLoop;
62static struct pa_context *g_pContext;
63
64/**
65 * Host Pulse audio driver instance data.
66 * @implements PDMIAUDIOCONNECTOR
67 */
68typedef struct DRVHOSTPULSEAUDIO
69{
70 /** Pointer to the driver instance structure. */
71 PPDMDRVINS pDrvIns;
72 /** Pointer to host audio interface. */
73 PDMIHOSTAUDIO IHostAudio;
74 /** Error count for not flooding the release log.
75 * UINT32_MAX for unlimited logging. */
76 uint32_t cLogErrors;
77} DRVHOSTPULSEAUDIO, *PDRVHOSTPULSEAUDIO;
78
79typedef struct PULSEAUDIOSTREAM
80{
81 /** Must come first, as this struct might be
82 * casted to one of these structs. */
83 union
84 {
85 PDMAUDIOHSTSTRMIN In;
86 PDMAUDIOHSTSTRMOUT Out;
87 } hw;
88 /** Pointer to driver instance. */
89 PDRVHOSTPULSEAUDIO pDrv;
90 /** DAC/ADC buffer. */
91 void *pPCMBuf;
92 /** Pointer to opaque PulseAudio stream. */
93 pa_stream *pStream;
94 /** Pulse sample format and attribute specification. */
95 pa_sample_spec SampleSpec;
96 /** Pulse playback and buffer metrics. */
97 pa_buffer_attr BufAttr;
98 int fOpSuccess;
99 /** Pointer to Pulse sample peeking buffer. */
100 const uint8_t *pu8PeekBuf;
101 /** Current size (in bytes) of peeking data in
102 * buffer. */
103 size_t cbPeekBuf;
104 /** Our offset (in bytes) in peeking buffer. */
105 size_t offPeekBuf;
106 pa_operation *pDrainOp;
107} PULSEAUDIOSTREAM, *PPULSEAUDIOSTREAM;
108
109/* The desired buffer length in milliseconds. Will be the target total stream
110 * latency on newer version of pulse. Apparent latency can be less (or more.)
111 */
112typedef struct PULSEAUDIOCFG
113{
114 RTMSINTERVAL buffer_msecs_out;
115 RTMSINTERVAL buffer_msecs_in;
116} PULSEAUDIOCFG, *PPULSEAUDIOCFG;
117
118static PULSEAUDIOCFG s_pulseCfg =
119{
120 100, /* buffer_msecs_out */
121 100 /* buffer_msecs_in */
122};
123
124/** Makes DRVHOSTPULSEAUDIO out of PDMIHOSTAUDIO. */
125#define PDMIHOSTAUDIO_2_DRVHOSTPULSEAUDIO(pInterface) \
126 ( (PDRVHOSTPULSEAUDIO)((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTPULSEAUDIO, IHostAudio)) )
127
128static int drvHostPulseAudioError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg);
129static void drvHostPulseAudioCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext);
130
131static pa_sample_format_t drvHostPulseAudioFmtToPulse(PDMAUDIOFMT fmt)
132{
133 switch (fmt)
134 {
135 case AUD_FMT_U8:
136 return PA_SAMPLE_U8;
137
138 case AUD_FMT_S16:
139 return PA_SAMPLE_S16LE;
140
141#ifdef PA_SAMPLE_S32LE
142 case AUD_FMT_S32:
143 return PA_SAMPLE_S32LE;
144#endif
145 default:
146 break;
147 }
148
149 AssertMsgFailed(("Format %ld not supported\n", fmt));
150 return PA_SAMPLE_U8;
151}
152
153static int drvHostPulseAudioPulseToFmt(pa_sample_format_t pulsefmt,
154 PDMAUDIOFMT *pFmt, PDMAUDIOENDIANESS *pEndianess)
155{
156 switch (pulsefmt)
157 {
158 case PA_SAMPLE_U8:
159 *pFmt = AUD_FMT_U8;
160 *pEndianess = PDMAUDIOENDIANESS_LITTLE;
161 break;
162
163 case PA_SAMPLE_S16LE:
164 *pFmt = AUD_FMT_S16;
165 *pEndianess = PDMAUDIOENDIANESS_LITTLE;
166 break;
167
168 case PA_SAMPLE_S16BE:
169 *pFmt = AUD_FMT_S16;
170 *pEndianess = PDMAUDIOENDIANESS_BIG;
171 break;
172
173#ifdef PA_SAMPLE_S32LE
174 case PA_SAMPLE_S32LE:
175 *pFmt = AUD_FMT_S32;
176 *pEndianess = PDMAUDIOENDIANESS_LITTLE;
177 break;
178#endif
179
180#ifdef PA_SAMPLE_S32BE
181 case PA_SAMPLE_S32BE:
182 *pFmt = AUD_FMT_S32;
183 *pEndianess = PDMAUDIOENDIANESS_BIG;
184 break;
185#endif
186
187 default:
188 AssertMsgFailed(("Format %ld not supported\n", pulsefmt));
189 return VERR_NOT_SUPPORTED;
190 }
191
192 return VINF_SUCCESS;
193}
194
195/**
196 * Synchronously wait until an operation completed.
197 */
198static int drvHostPulseAudioWaitFor(pa_operation *pOP, RTMSINTERVAL cMsTimeout)
199{
200 AssertPtrReturn(pOP, VERR_INVALID_POINTER);
201
202 int rc = VINF_SUCCESS;
203 if (pOP)
204 {
205 uint64_t u64StartMs = RTTimeMilliTS();
206 uint64_t u64ElapsedMs;
207
208 while (pa_operation_get_state(pOP) == PA_OPERATION_RUNNING)
209 {
210 pa_threaded_mainloop_wait(g_pMainLoop);
211
212 u64ElapsedMs = RTTimeMilliTS() - u64StartMs;
213 if (u64ElapsedMs >= cMsTimeout)
214 {
215 rc = VERR_TIMEOUT;
216 break;
217 }
218 }
219
220 pa_operation_unref(pOP);
221 }
222
223 return rc;
224}
225
226/**
227 * Context status changed.
228 */
229static void drvHostPulseAudioCbCtxState(pa_context *pContext, void *pvContext)
230{
231 AssertPtrReturnVoid(pContext);
232
233 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
234 NOREF(pStrm);
235
236 switch (pa_context_get_state(pContext))
237 {
238 case PA_CONTEXT_READY:
239 case PA_CONTEXT_TERMINATED:
240 pa_threaded_mainloop_signal(g_pMainLoop, 0);
241 break;
242
243 case PA_CONTEXT_FAILED:
244 LogRel(("PulseAudio: Audio input/output stopped!\n"));
245 pa_threaded_mainloop_signal(g_pMainLoop, 0);
246 break;
247
248 default:
249 break;
250 }
251}
252
253/**
254 * Callback called when our pa_stream_drain operation was completed.
255 */
256static void drvHostPulseAudioCbStreamDrain(pa_stream *pStream, int fSuccess, void *pvContext)
257{
258 AssertPtrReturnVoid(pStream);
259
260 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
261 AssertPtrReturnVoid(pStrm);
262
263 pStrm->fOpSuccess = fSuccess;
264 if (fSuccess)
265 {
266 pa_operation_unref(pa_stream_cork(pStream, 1,
267 drvHostPulseAudioCbSuccess, pvContext));
268 }
269 else
270 drvHostPulseAudioError(pStrm->pDrv, "Failed to drain stream");
271
272 pa_operation_unref(pStrm->pDrainOp);
273 pStrm->pDrainOp = NULL;
274}
275
276/**
277 * Stream status changed.
278 */
279static void drvHostPulseAudioCbStreamState(pa_stream *pStream, void *pvContext)
280{
281 AssertPtrReturnVoid(pStream);
282 NOREF(pvContext);
283
284 switch (pa_stream_get_state(pStream))
285 {
286 case PA_STREAM_READY:
287 case PA_STREAM_FAILED:
288 case PA_STREAM_TERMINATED:
289 pa_threaded_mainloop_signal(g_pMainLoop, 0 /* fWait */);
290 break;
291
292 default:
293 break;
294 }
295}
296
297static void drvHostPulseAudioCbSuccess(pa_stream *pStream, int fSuccess, void *pvContext)
298{
299 AssertPtrReturnVoid(pStream);
300
301 PPULSEAUDIOSTREAM pStrm = (PPULSEAUDIOSTREAM)pvContext;
302 AssertPtrReturnVoid(pStrm);
303
304 pStrm->fOpSuccess = fSuccess;
305
306 if (fSuccess)
307 {
308 pa_threaded_mainloop_signal(g_pMainLoop, 0 /* fWait */);
309 }
310 else
311 drvHostPulseAudioError(pStrm->pDrv, "Failed to finish stream operation");
312}
313
314static int drvHostPulseAudioOpen(bool fIn, const char *pszName,
315 pa_sample_spec *pSampleSpec, pa_buffer_attr *pBufAttr,
316 pa_stream **ppStream)
317{
318 AssertPtrReturn(pszName, VERR_INVALID_POINTER);
319 AssertPtrReturn(pSampleSpec, VERR_INVALID_POINTER);
320 AssertPtrReturn(pBufAttr, VERR_INVALID_POINTER);
321 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);
322
323 if (!pa_sample_spec_valid(pSampleSpec))
324 {
325 LogRel(("PulseAudio: Unsupported sample specification for stream \"%s\"\n",
326 pszName));
327 return VERR_NOT_SUPPORTED;
328 }
329
330 int rc = VINF_SUCCESS;
331
332 pa_stream *pStream = NULL;
333 uint32_t flags = PA_STREAM_NOFLAGS;
334
335 LogFunc(("Opening \"%s\", rate=%dHz, channels=%d, format=%s\n",
336 pszName, pSampleSpec->rate, pSampleSpec->channels,
337 pa_sample_format_to_string(pSampleSpec->format)));
338
339 pa_threaded_mainloop_lock(g_pMainLoop);
340
341 do
342 {
343 if (!(pStream = pa_stream_new(g_pContext, pszName, pSampleSpec,
344 NULL /* pa_channel_map */)))
345 {
346 LogRel(("PulseAudio: Could not create stream \"%s\"\n", pszName));
347 rc = VERR_NO_MEMORY;
348 break;
349 }
350
351 pa_stream_set_state_callback(pStream, drvHostPulseAudioCbStreamState, NULL);
352
353#if PA_API_VERSION >= 12
354 /* XXX */
355 flags |= PA_STREAM_ADJUST_LATENCY;
356#endif
357
358#if 0
359 /* Not applicable as we don't use pa_stream_get_latency() and pa_stream_get_time(). */
360 flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
361#endif
362 /* No input/output right away after the stream was started. */
363 flags |= PA_STREAM_START_CORKED;
364
365 if (fIn)
366 {
367 LogFunc(("Input stream attributes: maxlength=%d fragsize=%d\n",
368 pBufAttr->maxlength, pBufAttr->fragsize));
369
370 if (pa_stream_connect_record(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags) < 0)
371 {
372 LogRel(("PulseAudio: Could not connect input stream \"%s\": %s\n",
373 pszName, pa_strerror(pa_context_errno(g_pContext))));
374 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
375 break;
376 }
377 }
378 else
379 {
380 LogFunc(("Output buffer attributes: maxlength=%d tlength=%d prebuf=%d minreq=%d\n",
381 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
382
383 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, pBufAttr, (pa_stream_flags_t)flags,
384 /*cvolume=*/NULL, /*sync_stream=*/NULL) < 0)
385 {
386 LogRel(("PulseAudio: Could not connect playback stream \"%s\": %s\n",
387 pszName, pa_strerror(pa_context_errno(g_pContext))));
388 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
389 break;
390 }
391 }
392
393 /* Wait until the stream is ready. */
394 pa_stream_state_t sstate;
395 for (;;)
396 {
397 pa_threaded_mainloop_wait(g_pMainLoop);
398
399 sstate = pa_stream_get_state(pStream);
400 if (sstate == PA_STREAM_READY)
401 break;
402 else if ( sstate == PA_STREAM_FAILED
403 || sstate == PA_STREAM_TERMINATED)
404 {
405 LogRel(("PulseAudio: Failed to initialize stream \"%s\" (state %ld)\n",
406 pszName, sstate));
407 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
408 break;
409 }
410 }
411
412 if (RT_FAILURE(rc))
413 break;
414
415 const pa_buffer_attr *pBufAttrObtained = pa_stream_get_buffer_attr(pStream);
416 AssertPtr(pBufAttrObtained);
417 memcpy(pBufAttr, pBufAttrObtained, sizeof(pa_buffer_attr));
418
419 if (fIn)
420 LogFunc(("Obtained record buffer attributes: maxlength=%RU32, fragsize=%RU32\n",
421 pBufAttr->maxlength, pBufAttr->fragsize));
422 else
423 LogFunc(("Obtained playback buffer attributes: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d\n",
424 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
425
426 pa_threaded_mainloop_unlock(g_pMainLoop);
427 }
428 while (0);
429
430 if (RT_FAILURE(rc))
431 {
432 if (pStream)
433 pa_stream_disconnect(pStream);
434
435 pa_threaded_mainloop_unlock(g_pMainLoop);
436
437 if (pStream)
438 pa_stream_unref(pStream);
439 }
440 else
441 *ppStream = pStream;
442
443 LogFlowFuncLeaveRC(rc);
444 return rc;
445}
446
447static DECLCALLBACK(int) drvHostPulseAudioInit(PPDMIHOSTAUDIO pInterface)
448{
449 NOREF(pInterface);
450
451 LogFlowFuncEnter();
452
453 int rc = audioLoadPulseLib();
454 if (RT_FAILURE(rc))
455 {
456 LogRel(("PulseAudio: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
457 return rc;
458 }
459
460 bool fLocked = false;
461
462 do
463 {
464 if (!(g_pMainLoop = pa_threaded_mainloop_new()))
465 {
466 LogRel(("PulseAudio: Failed to allocate main loop: %s\n",
467 pa_strerror(pa_context_errno(g_pContext))));
468 rc = VERR_NO_MEMORY;
469 break;
470 }
471
472 if (!(g_pContext = pa_context_new(pa_threaded_mainloop_get_api(g_pMainLoop), "VirtualBox")))
473 {
474 LogRel(("PulseAudio: Failed to allocate context: %s\n",
475 pa_strerror(pa_context_errno(g_pContext))));
476 rc = VERR_NO_MEMORY;
477 break;
478 }
479
480 if (pa_threaded_mainloop_start(g_pMainLoop) < 0)
481 {
482 LogRel(("PulseAudio: Failed to start threaded mainloop: %s\n",
483 pa_strerror(pa_context_errno(g_pContext))));
484 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
485 break;
486 }
487
488 pa_context_set_state_callback(g_pContext, drvHostPulseAudioCbCtxState, NULL);
489 pa_threaded_mainloop_lock(g_pMainLoop);
490 fLocked = true;
491
492 if (pa_context_connect(g_pContext, NULL /* pszServer */,
493 PA_CONTEXT_NOFLAGS, NULL) < 0)
494 {
495 LogRel(("PulseAudio: Failed to connect to server: %s\n",
496 pa_strerror(pa_context_errno(g_pContext))));
497 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
498 break;
499 }
500
501 /* Wait until the g_pContext is ready */
502 for (;;)
503 {
504 pa_context_state_t cstate;
505 pa_threaded_mainloop_wait(g_pMainLoop);
506
507 cstate = pa_context_get_state(g_pContext);
508 if (cstate == PA_CONTEXT_READY)
509 break;
510 else if ( cstate == PA_CONTEXT_TERMINATED
511 || cstate == PA_CONTEXT_FAILED)
512 {
513 LogRel(("PulseAudio: Failed to initialize context (state %d)\n", cstate));
514 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
515 break;
516 }
517 }
518
519 pa_threaded_mainloop_unlock(g_pMainLoop);
520 }
521 while (0);
522
523 if (RT_FAILURE(rc))
524 {
525 if (g_pMainLoop)
526 {
527 if (fLocked)
528 pa_threaded_mainloop_unlock(g_pMainLoop);
529
530 if (g_pMainLoop)
531 pa_threaded_mainloop_stop(g_pMainLoop);
532 }
533
534 if (g_pContext)
535 {
536 pa_context_disconnect(g_pContext);
537 pa_context_unref(g_pContext);
538 g_pContext = NULL;
539 }
540
541 if (g_pMainLoop)
542 {
543 pa_threaded_mainloop_free(g_pMainLoop);
544 g_pMainLoop = NULL;
545 }
546 }
547
548 LogFlowFuncLeaveRC(rc);
549 return rc;
550}
551
552static DECLCALLBACK(int) drvHostPulseAudioInitOut(PPDMIHOSTAUDIO pInterface,
553 PPDMAUDIOHSTSTRMOUT pHstStrmOut, PPDMAUDIOSTREAMCFG pCfg,
554 uint32_t *pcSamples)
555{
556 NOREF(pInterface);
557 AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
558 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
559 /* pcSamples is optional. */
560
561 PPULSEAUDIOSTREAM pThisStrmOut = (PPULSEAUDIOSTREAM)pHstStrmOut;
562
563 LogFlowFuncEnter();
564
565 pThisStrmOut->pDrainOp = NULL;
566
567 pThisStrmOut->SampleSpec.format = drvHostPulseAudioFmtToPulse(pCfg->enmFormat);
568 pThisStrmOut->SampleSpec.rate = pCfg->uHz;
569 pThisStrmOut->SampleSpec.channels = pCfg->cChannels;
570
571 /* Note that setting maxlength to -1 does not work on PulseAudio servers
572 * older than 0.9.10. So use the suggested value of 3/2 of tlength */
573 pThisStrmOut->BufAttr.tlength = (pa_bytes_per_second(&pThisStrmOut->SampleSpec)
574 * s_pulseCfg.buffer_msecs_out) / 1000;
575 pThisStrmOut->BufAttr.maxlength = (pThisStrmOut->BufAttr.tlength * 3) / 2;
576 pThisStrmOut->BufAttr.prebuf = -1; /* Same as tlength */
577 pThisStrmOut->BufAttr.minreq = -1; /* Pulse should set something sensible for minreq on it's own */
578
579 /* Note that the struct BufAttr is updated to the obtained values after this call! */
580 int rc = drvHostPulseAudioOpen(false /* fIn */, "pa.out", &pThisStrmOut->SampleSpec, &pThisStrmOut->BufAttr,
581 &pThisStrmOut->pStream);
582 if (RT_FAILURE(rc))
583 return rc;
584
585 PDMAUDIOSTREAMCFG streamCfg;
586 rc = drvHostPulseAudioPulseToFmt(pThisStrmOut->SampleSpec.format,
587 &streamCfg.enmFormat, &streamCfg.enmEndianness);
588 if (RT_FAILURE(rc))
589 {
590 LogRel(("PulseAudio: Cannot find audio output format %ld\n", pThisStrmOut->SampleSpec.format));
591 return rc;
592 }
593
594 streamCfg.uHz = pThisStrmOut->SampleSpec.rate;
595 streamCfg.cChannels = pThisStrmOut->SampleSpec.channels;
596
597 rc = drvAudioStreamCfgToProps(&streamCfg, &pHstStrmOut->Props);
598 if (RT_SUCCESS(rc))
599 {
600 int cbBuf = RT_MIN(pThisStrmOut->BufAttr.tlength * 2, pThisStrmOut->BufAttr.maxlength);
601
602 pThisStrmOut->pPCMBuf = RTMemAllocZ(cbBuf);
603 if (pThisStrmOut->pPCMBuf)
604 {
605 if (pcSamples)
606 *pcSamples = cbBuf >> pHstStrmOut->Props.cShift;
607 }
608 else
609 rc = VERR_NO_MEMORY;
610 }
611
612 LogFlowFuncLeaveRC(rc);
613 return rc;
614}
615
616static DECLCALLBACK(bool) drvHostPulseAudioIsEnabled(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
617{
618 NOREF(pInterface);
619 NOREF(enmDir);
620 return true; /* Always all enabled. */
621}
622
623static DECLCALLBACK(int) drvHostPulseAudioInitIn(PPDMIHOSTAUDIO pInterface,
624 PPDMAUDIOHSTSTRMIN pHstStrmIn, PPDMAUDIOSTREAMCFG pCfg,
625 PDMAUDIORECSOURCE enmRecSource,
626 uint32_t *pcSamples)
627{
628 NOREF(pInterface);
629 AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
630 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
631 /* pcSamples is optional. */
632
633 PPULSEAUDIOSTREAM pThisStrmIn = (PPULSEAUDIOSTREAM)pHstStrmIn;
634
635 LogFunc(("enmRecSrc=%ld\n", enmRecSource));
636
637 pThisStrmIn->SampleSpec.format = drvHostPulseAudioFmtToPulse(pCfg->enmFormat);
638 pThisStrmIn->SampleSpec.rate = pCfg->uHz;
639 pThisStrmIn->SampleSpec.channels = pCfg->cChannels;
640
641 /* XXX check these values */
642 pThisStrmIn->BufAttr.fragsize = (pa_bytes_per_second(&pThisStrmIn->SampleSpec)
643 * s_pulseCfg.buffer_msecs_in) / 1000;
644 pThisStrmIn->BufAttr.maxlength = (pThisStrmIn->BufAttr.fragsize * 3) / 2;
645 /* Note: Other members of pa_buffer_attr are ignored for record streams. */
646
647 int rc = drvHostPulseAudioOpen(true /* fIn */, "pa.in", &pThisStrmIn->SampleSpec, &pThisStrmIn->BufAttr,
648 &pThisStrmIn->pStream);
649 if (RT_FAILURE(rc))
650 return rc;
651
652 PDMAUDIOSTREAMCFG streamCfg;
653 rc = drvHostPulseAudioPulseToFmt(pThisStrmIn->SampleSpec.format, &streamCfg.enmFormat,
654 &streamCfg.enmEndianness);
655 if (RT_FAILURE(rc))
656 {
657 LogRel(("PulseAudio: Cannot find audio capture format %ld\n", pThisStrmIn->SampleSpec.format));
658 return rc;
659 }
660
661 streamCfg.uHz = pThisStrmIn->SampleSpec.rate;
662 streamCfg.cChannels = pThisStrmIn->SampleSpec.channels;
663
664 rc = drvAudioStreamCfgToProps(&streamCfg, &pHstStrmIn->Props);
665 if (RT_SUCCESS(rc))
666 {
667 uint32_t cSamples = RT_MIN(pThisStrmIn->BufAttr.fragsize * 10, pThisStrmIn->BufAttr.maxlength)
668 >> pHstStrmIn->Props.cShift;
669 LogFunc(("cShift=%RU8, cSamples=%RU32\n", pHstStrmIn->Props.cShift, cSamples));
670
671 if (pcSamples)
672 *pcSamples = cSamples;
673
674 pThisStrmIn->pu8PeekBuf = NULL;
675 }
676
677 LogFlowFuncLeaveRC(rc);
678 return rc;
679}
680
681static DECLCALLBACK(int) drvHostPulseAudioCaptureIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
682 uint32_t *pcSamplesCaptured)
683{
684 NOREF(pInterface);
685 AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
686 /* pcSamplesPlayed is optional. */
687
688 PPULSEAUDIOSTREAM pThisStrmIn = (PPULSEAUDIOSTREAM)pHstStrmIn;
689
690 /* We should only call pa_stream_readable_size() once and trust the first value. */
691 pa_threaded_mainloop_lock(g_pMainLoop);
692 size_t cbAvail = pa_stream_readable_size(pThisStrmIn->pStream);
693 pa_threaded_mainloop_unlock(g_pMainLoop);
694
695 if (cbAvail == (size_t)-1)
696 return drvHostPulseAudioError(pThisStrmIn->pDrv, "Failed to determine input data size");
697
698 /* If the buffer was not dropped last call, add what remains. */
699 if (pThisStrmIn->pu8PeekBuf)
700 {
701 Assert(pThisStrmIn->cbPeekBuf >= pThisStrmIn->offPeekBuf);
702 cbAvail += (pThisStrmIn->cbPeekBuf - pThisStrmIn->offPeekBuf);
703 }
704
705 if (!cbAvail) /* No data? Bail out. */
706 {
707 if (pcSamplesCaptured)
708 *pcSamplesCaptured = 0;
709 return VINF_SUCCESS;
710 }
711
712 int rc = VINF_SUCCESS;
713 size_t cbToRead = RT_MIN(cbAvail, audioMixBufFreeBytes(&pHstStrmIn->MixBuf));
714
715 LogFlowFunc(("cbToRead=%zu, cbAvail=%zu, offPeekBuf=%zu, cbPeekBuf=%zu\n",
716 cbToRead, cbAvail, pThisStrmIn->offPeekBuf, pThisStrmIn->cbPeekBuf));
717
718 size_t offWrite = 0;
719 uint32_t cWrittenTotal = 0;
720
721 while (cbToRead)
722 {
723 /* If there is no data, do another peek. */
724 if (!pThisStrmIn->pu8PeekBuf)
725 {
726 pa_threaded_mainloop_lock(g_pMainLoop);
727 pa_stream_peek(pThisStrmIn->pStream,
728 (const void**)&pThisStrmIn->pu8PeekBuf, &pThisStrmIn->cbPeekBuf);
729 pa_threaded_mainloop_unlock(g_pMainLoop);
730
731 pThisStrmIn->offPeekBuf = 0;
732
733 /* No data anymore?
734 * Note: If there's a data hole (cbPeekBuf then contains the length of the hole)
735 * we need to drop the stream lateron. */
736 if ( !pThisStrmIn->pu8PeekBuf
737 && !pThisStrmIn->cbPeekBuf)
738 {
739 break;
740 }
741 }
742
743 Assert(pThisStrmIn->cbPeekBuf >= pThisStrmIn->offPeekBuf);
744 size_t cbToWrite = pThisStrmIn->cbPeekBuf - pThisStrmIn->offPeekBuf;
745
746 LogFlowFunc(("cbToRead=%zu, cbToWrite=%zu, offPeekBuf=%zu, cbPeekBuf=%zu, pu8PeekBuf=%p\n",
747 cbToRead, cbToWrite,
748 pThisStrmIn->offPeekBuf, pThisStrmIn->cbPeekBuf, pThisStrmIn->pu8PeekBuf));
749
750 if (cbToWrite)
751 {
752 uint32_t cWritten;
753 rc = audioMixBufWriteCirc(&pHstStrmIn->MixBuf,
754 pThisStrmIn->pu8PeekBuf + pThisStrmIn->offPeekBuf,
755 cbToWrite, &cWritten);
756 if (RT_FAILURE(rc))
757 break;
758
759 uint32_t cbWritten = AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf, cWritten);
760
761 Assert(cbToRead >= cbWritten);
762 cbToRead -= cbWritten;
763 cWrittenTotal += cWritten;
764 pThisStrmIn->offPeekBuf += cbWritten;
765 }
766
767 if (/* Nothing to write anymore? Drop the buffer. */
768 !cbToWrite
769 /* Was there a hole in the peeking buffer? Drop it. */
770 || !pThisStrmIn->pu8PeekBuf
771 /* If the buffer is done, drop it. */
772 || pThisStrmIn->offPeekBuf == pThisStrmIn->cbPeekBuf)
773 {
774 pa_threaded_mainloop_lock(g_pMainLoop);
775 pa_stream_drop(pThisStrmIn->pStream);
776 pa_threaded_mainloop_unlock(g_pMainLoop);
777
778 pThisStrmIn->pu8PeekBuf = NULL;
779 }
780 }
781
782 if (RT_SUCCESS(rc))
783 {
784 uint32_t cProcessed = 0;
785 if (cWrittenTotal)
786 rc = audioMixBufMixToParent(&pHstStrmIn->MixBuf, cWrittenTotal,
787 &cProcessed);
788
789 if (pcSamplesCaptured)
790 *pcSamplesCaptured = cWrittenTotal;
791
792 LogFlowFunc(("cWrittenTotal=%RU32 (%RU32 processed), rc=%Rrc\n",
793 cWrittenTotal, cProcessed, rc));
794 }
795
796 LogFlowFuncLeaveRC(rc);
797 return rc;
798}
799
800static DECLCALLBACK(int) drvHostPulseAudioPlayOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
801 uint32_t *pcSamplesPlayed)
802{
803 NOREF(pInterface);
804 AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
805 /* pcSamplesPlayed is optional. */
806
807 PPULSEAUDIOSTREAM pThisStrmOut = (PPULSEAUDIOSTREAM)pHstStrmOut;
808
809 int rc = VINF_SUCCESS;
810 uint32_t cbReadTotal = 0;
811
812 uint32_t cLive = drvAudioHstOutSamplesLive(pHstStrmOut,
813 NULL /* pcStreamsLive */);
814 if (!cLive)
815 {
816 LogFlowFunc(("%p: No live samples, skipping\n", pHstStrmOut));
817
818 if (pcSamplesPlayed)
819 *pcSamplesPlayed = 0;
820 return VINF_SUCCESS;
821 }
822
823 pa_threaded_mainloop_lock(g_pMainLoop);
824
825 do
826 {
827 size_t cbWriteable = pa_stream_writable_size(pThisStrmOut->pStream);
828 if (cbWriteable == (size_t)-1)
829 {
830 rc = drvHostPulseAudioError(pThisStrmOut->pDrv, "Failed to determine output data size");
831 break;
832 }
833
834 size_t cbLive = AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cLive);
835 size_t cbToRead = RT_MIN(cbWriteable, cbLive);
836
837 LogFlowFunc(("cbToRead=%zu, cbWriteable=%zu, cbLive=%zu\n",
838 cbToRead, cbWriteable, cbLive));
839
840 uint32_t cRead, cbRead;
841 while (cbToRead)
842 {
843 rc = audioMixBufReadCirc(&pHstStrmOut->MixBuf, pThisStrmOut->pPCMBuf, cbToRead, &cRead);
844 if (RT_FAILURE(rc))
845 break;
846
847 cbRead = AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cRead);
848 if (pa_stream_write(pThisStrmOut->pStream, pThisStrmOut->pPCMBuf, cbRead,
849 NULL /* Cleanup callback */, 0, PA_SEEK_RELATIVE) < 0)
850 {
851 rc = drvHostPulseAudioError(pThisStrmOut->pDrv, "Failed to write to output stream");
852 break;
853 }
854
855 Assert(cbToRead >= cRead);
856 cbToRead -= cbRead;
857 cbReadTotal += cbRead;
858 }
859
860 } while (0);
861
862 pa_threaded_mainloop_unlock(g_pMainLoop);
863
864 if (RT_SUCCESS(rc))
865 {
866 uint32_t cReadTotal = AUDIOMIXBUF_B2S(&pHstStrmOut->MixBuf, cbReadTotal);
867 if (cReadTotal)
868 audioMixBufFinish(&pHstStrmOut->MixBuf, cReadTotal);
869
870 if (pcSamplesPlayed)
871 *pcSamplesPlayed = cReadTotal;
872
873 LogFlowFunc(("cReadTotal=%RU32 (%RU32 bytes), rc=%Rrc\n",
874 cReadTotal, cbReadTotal, rc));
875 }
876
877 LogFlowFuncLeaveRC(rc);
878 return rc;
879}
880
881/** @todo Implement va handling. */
882static int drvHostPulseAudioError(PDRVHOSTPULSEAUDIO pThis, const char *szMsg)
883{
884 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
885 AssertPtrReturn(szMsg, VERR_INVALID_POINTER);
886
887 if (pThis->cLogErrors++ < VBOX_PULSEAUDIO_MAX_LOG_REL_ERRORS)
888 {
889 int rc2 = pa_context_errno(g_pContext);
890 LogRel(("PulseAudio: %s: %s\n", szMsg, pa_strerror(rc2)));
891 }
892
893 /** @todo Implement some PulseAudio -> IPRT mapping here. */
894 return VERR_GENERAL_FAILURE;
895}
896
897static DECLCALLBACK(int) drvHostPulseAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
898{
899 NOREF(pInterface);
900 AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
901
902 LogFlowFuncEnter();
903
904 PPULSEAUDIOSTREAM pThisStrmIn = (PPULSEAUDIOSTREAM)pHstStrmIn;
905 if (pThisStrmIn->pStream)
906 {
907 pa_threaded_mainloop_lock(g_pMainLoop);
908 pa_stream_disconnect(pThisStrmIn->pStream);
909 pa_stream_unref(pThisStrmIn->pStream);
910 pa_threaded_mainloop_unlock(g_pMainLoop);
911
912 pThisStrmIn->pStream = NULL;
913 }
914
915 return VINF_SUCCESS;
916}
917
918static DECLCALLBACK(int) drvHostPulseAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
919{
920 NOREF(pInterface);
921 AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
922
923 LogFlowFuncEnter();
924
925 PPULSEAUDIOSTREAM pThisStrmOut = (PPULSEAUDIOSTREAM)pHstStrmOut;
926 if (pThisStrmOut->pStream)
927 {
928 pa_threaded_mainloop_lock(g_pMainLoop);
929 pa_stream_disconnect(pThisStrmOut->pStream);
930 pa_stream_unref(pThisStrmOut->pStream);
931 pa_threaded_mainloop_unlock(g_pMainLoop);
932
933 pThisStrmOut->pStream = NULL;
934 }
935
936 if (pThisStrmOut->pPCMBuf)
937 {
938 RTMemFree(pThisStrmOut->pPCMBuf);
939 pThisStrmOut->pPCMBuf = NULL;
940 }
941
942 return VINF_SUCCESS;
943}
944
945static DECLCALLBACK(int) drvHostPulseAudioControlOut(PPDMIHOSTAUDIO pInterface,
946 PPDMAUDIOHSTSTRMOUT pHstStrmOut, PDMAUDIOSTREAMCMD enmStreamCmd)
947{
948 NOREF(pInterface);
949 AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);
950
951 PPULSEAUDIOSTREAM pThisStrmOut = (PPULSEAUDIOSTREAM)pHstStrmOut;
952 int rc = VINF_SUCCESS;
953
954 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
955
956 switch (enmStreamCmd)
957 {
958 case PDMAUDIOSTREAMCMD_ENABLE:
959 {
960 pa_threaded_mainloop_lock(g_pMainLoop);
961
962 if ( pThisStrmOut->pDrainOp
963 && pa_operation_get_state(pThisStrmOut->pDrainOp) != PA_OPERATION_DONE)
964 {
965 pa_operation_cancel(pThisStrmOut->pDrainOp);
966 pa_operation_unref(pThisStrmOut->pDrainOp);
967
968 pThisStrmOut->pDrainOp = NULL;
969 }
970 else
971 {
972 /* This should return immediately. */
973 rc = drvHostPulseAudioWaitFor(pa_stream_cork(pThisStrmOut->pStream, 0,
974 drvHostPulseAudioCbSuccess, pThisStrmOut),
975 15 * 1000 /* 15s timeout */);
976 }
977
978 pa_threaded_mainloop_unlock(g_pMainLoop);
979 break;
980 }
981
982 case PDMAUDIOSTREAMCMD_DISABLE:
983 {
984 /* Pause audio output (the Pause bit of the AC97 x_CR register is set).
985 * Note that we must return immediately from here! */
986 pa_threaded_mainloop_lock(g_pMainLoop);
987 if (!pThisStrmOut->pDrainOp)
988 {
989 /* This should return immediately. */
990 rc = drvHostPulseAudioWaitFor(pa_stream_trigger(pThisStrmOut->pStream,
991 drvHostPulseAudioCbSuccess, pThisStrmOut),
992 15 * 1000 /* 15s timeout */);
993 if (RT_LIKELY(RT_SUCCESS(rc)))
994 pThisStrmOut->pDrainOp = pa_stream_drain(pThisStrmOut->pStream,
995 drvHostPulseAudioCbStreamDrain, pThisStrmOut);
996 }
997 pa_threaded_mainloop_unlock(g_pMainLoop);
998 break;
999 }
1000
1001 default:
1002 AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
1003 rc = VERR_INVALID_PARAMETER;
1004 break;
1005 }
1006
1007 LogFlowFuncLeaveRC(rc);
1008 return rc;
1009}
1010
1011static DECLCALLBACK(int) drvHostPulseAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
1012 PDMAUDIOSTREAMCMD enmStreamCmd)
1013{
1014 NOREF(pInterface);
1015 AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
1016
1017 PPULSEAUDIOSTREAM pThisStrmIn = (PPULSEAUDIOSTREAM)pHstStrmIn;
1018 int rc = VINF_SUCCESS;
1019
1020 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1021
1022 switch (enmStreamCmd)
1023 {
1024 case PDMAUDIOSTREAMCMD_ENABLE:
1025 {
1026 pa_threaded_mainloop_lock(g_pMainLoop);
1027 /* This should return immediately. */
1028 rc = drvHostPulseAudioWaitFor(pa_stream_cork(pThisStrmIn->pStream, 0 /* Play / resume */,
1029 drvHostPulseAudioCbSuccess, pThisStrmIn),
1030 15 * 1000 /* 15s timeout */);
1031 pa_threaded_mainloop_unlock(g_pMainLoop);
1032 break;
1033 }
1034
1035 case PDMAUDIOSTREAMCMD_DISABLE:
1036 {
1037 pa_threaded_mainloop_lock(g_pMainLoop);
1038 if (pThisStrmIn->pu8PeekBuf) /* Do we need to drop the peek buffer?*/
1039 {
1040 pa_stream_drop(pThisStrmIn->pStream);
1041 pThisStrmIn->pu8PeekBuf = NULL;
1042 }
1043 /* This should return immediately. */
1044 rc = drvHostPulseAudioWaitFor(pa_stream_cork(pThisStrmIn->pStream, 1 /* Stop / pause */,
1045 drvHostPulseAudioCbSuccess, pThisStrmIn),
1046 15 * 1000 /* 15s timeout */);
1047 pa_threaded_mainloop_unlock(g_pMainLoop);
1048 break;
1049 }
1050
1051 default:
1052 AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
1053 rc = VERR_INVALID_PARAMETER;
1054 break;
1055 }
1056
1057 return rc;
1058}
1059
1060static DECLCALLBACK(int) drvHostPulseAudioGetConf(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pCfg)
1061{
1062 NOREF(pInterface);
1063 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
1064
1065 pCfg->cbStreamOut = sizeof(PULSEAUDIOSTREAM);
1066 pCfg->cbStreamIn = sizeof(PULSEAUDIOSTREAM);
1067 pCfg->cMaxHstStrmsOut = INT_MAX;
1068 pCfg->cMaxHstStrmsIn = INT_MAX;
1069
1070 return VINF_SUCCESS;
1071}
1072
1073static DECLCALLBACK(void) drvHostPulseAudioShutdown(PPDMIHOSTAUDIO pInterface)
1074{
1075 NOREF(pInterface);
1076
1077 LogFlowFuncEnter();
1078
1079 if (g_pMainLoop)
1080 pa_threaded_mainloop_stop(g_pMainLoop);
1081
1082 if (g_pContext)
1083 {
1084 pa_context_disconnect(g_pContext);
1085 pa_context_unref(g_pContext);
1086 g_pContext = NULL;
1087 }
1088
1089 if (g_pMainLoop)
1090 {
1091 pa_threaded_mainloop_free(g_pMainLoop);
1092 g_pMainLoop = NULL;
1093 }
1094
1095 LogFlowFuncLeave();
1096}
1097
1098/**
1099 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1100 */
1101static DECLCALLBACK(void *) drvHostPulseAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1102{
1103 AssertPtrReturn(pInterface, NULL);
1104 AssertPtrReturn(pszIID, NULL);
1105
1106 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1107 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1108 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1109 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1110
1111 return NULL;
1112}
1113
1114/**
1115 * Constructs a PulseAudio Audio driver instance.
1116 *
1117 * @copydoc FNPDMDRVCONSTRUCT
1118 */
1119static DECLCALLBACK(int) drvHostPulseAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1120{
1121 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
1122 /* pCfg is optional. */
1123
1124 PDRVHOSTPULSEAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPULSEAUDIO);
1125 LogRel(("Audio: Initializing PulseAudio driver\n"));
1126
1127 pThis->pDrvIns = pDrvIns;
1128 /* IBase */
1129 pDrvIns->IBase.pfnQueryInterface = drvHostPulseAudioQueryInterface;
1130 /* IHostAudio */
1131 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostPulseAudio);
1132
1133 return VINF_SUCCESS;
1134}
1135
1136/**
1137 * Destructs a PulseAudio Audio driver instance.
1138 *
1139 * @copydoc FNPDMDRVCONSTRUCT
1140 */
1141static DECLCALLBACK(void) drvHostPulseAudioDestruct(PPDMDRVINS pDrvIns)
1142{
1143 NOREF(pDrvIns);
1144 LogFlowFuncEnter();
1145}
1146
1147/**
1148 * Char driver registration record.
1149 */
1150const PDMDRVREG g_DrvHostPulseAudio =
1151{
1152 /* u32Version */
1153 PDM_DRVREG_VERSION,
1154 /* szName */
1155 "PulseAudio",
1156 /* szRCMod */
1157 "",
1158 /* szR0Mod */
1159 "",
1160 /* pszDescription */
1161 "Pulse Audio host driver",
1162 /* fFlags */
1163 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1164 /* fClass. */
1165 PDM_DRVREG_CLASS_AUDIO,
1166 /* cMaxInstances */
1167 ~0U,
1168 /* cbInstance */
1169 sizeof(DRVHOSTPULSEAUDIO),
1170 /* pfnConstruct */
1171 drvHostPulseAudioConstruct,
1172 /* pfnDestruct */
1173 drvHostPulseAudioDestruct,
1174 /* pfnRelocate */
1175 NULL,
1176 /* pfnIOCtl */
1177 NULL,
1178 /* pfnPowerOn */
1179 NULL,
1180 /* pfnReset */
1181 NULL,
1182 /* pfnSuspend */
1183 NULL,
1184 /* pfnResume */
1185 NULL,
1186 /* pfnAttach */
1187 NULL,
1188 /* pfnDetach */
1189 NULL,
1190 /* pfnPowerOff */
1191 NULL,
1192 /* pfnSoftReset */
1193 NULL,
1194 /* u32EndVersion */
1195 PDM_DRVREG_VERSION
1196};
1197
1198static struct audio_option pulse_options[] =
1199{
1200 {"DAC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_out,
1201 "DAC period size in milliseconds", NULL, 0},
1202 {"ADC_MS", AUD_OPT_INT, &s_pulseCfg.buffer_msecs_in,
1203 "ADC period size in milliseconds", NULL, 0},
1204
1205 NULL
1206};
1207
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