VirtualBox

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

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

Audio/PulseAudio: removed unintended LogRel() and release a string during destroy

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