VirtualBox

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

Last change on this file since 54988 was 54910, checked in by vboxsync, 10 years ago

DrvHostPulseAudio.cpp: Remember allocation size for scratch buffer.

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