VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/pulseaudio.c@ 6133

Last change on this file since 6133 was 6133, checked in by vboxsync, 17 years ago

pulse: lock a little bit more

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.5 KB
Line 
1/** @file
2 *
3 * VBox PulseAudio backend
4 */
5
6/*
7 * Copyright (C) 2006-2007 innotek GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#define LOG_GROUP LOG_GROUP_DEV_AUDIO
23#include <VBox/log.h>
24#include <iprt/mem.h>
25
26#include <pulse/pulseaudio.h>
27#include "pulse_stubs.h"
28
29#include "../../vl_vbox.h"
30#include "audio.h"
31#define AUDIO_CAP "pulse"
32#include "audio_int.h"
33#include <stdio.h>
34
35/*
36 * We use a g_pMainLoop in a separate thread g_pContext. We have to call functions for
37 * manipulating objects either from callback functions or we have to protect
38 * these functions by pa_threaded_mainloop_lock() / pa_threaded_mainloop_unlock().
39 */
40static struct pa_threaded_mainloop *g_pMainLoop;
41static struct pa_context *g_pContext;
42
43static void pulse_audio_fini (void *);
44
45typedef struct PulseVoice
46{
47 HWVoiceOut hw;
48 void *pPCMBuf;
49 pa_stream *pStream;
50 int fOpSuccess;
51} PulseVoice;
52
53static struct
54{
55 int buffer_msecs_out;
56 int buffer_msecs_in;
57} conf
58=
59{
60 INIT_FIELD (.buffer_msecs_out = ) 100,
61 INIT_FIELD (.buffer_msecs_in = ) 100,
62};
63
64struct pulse_params_req
65{
66 int freq;
67 pa_sample_format_t pa_format;
68 int nchannels;
69};
70
71struct pulse_params_obt
72{
73 int freq;
74 pa_sample_format_t pa_format;
75 int nchannels;
76 unsigned long buffer_size;
77};
78
79static pa_sample_format_t aud_to_pulsefmt (audfmt_e fmt)
80{
81 switch (fmt)
82 {
83 case AUD_FMT_U8:
84 return PA_SAMPLE_U8;
85
86 case AUD_FMT_S16:
87 return PA_SAMPLE_S16LE;
88
89#ifdef PA_SAMPLE_S32LE
90 case AUD_FMT_S32:
91 return PA_SAMPLE_S32LE;
92#endif
93
94 default:
95 dolog ("Bad audio format %d\n", fmt);
96 return PA_SAMPLE_U8;
97 }
98}
99
100
101static int pulse_to_audfmt (pa_sample_format_t pulsefmt, audfmt_e *fmt, int *endianess)
102{
103 switch (pulsefmt)
104 {
105 case PA_SAMPLE_U8:
106 *endianess = 0;
107 *fmt = AUD_FMT_U8;
108 break;
109
110 case PA_SAMPLE_S16LE:
111 *fmt = AUD_FMT_S16;
112 *endianess = 0;
113 break;
114
115 case PA_SAMPLE_S16BE:
116 *fmt = AUD_FMT_S16;
117 *endianess = 1;
118 break;
119
120#ifdef PA_SAMPLE_S32LE
121 case PA_SAMPLE_S32LE:
122 *fmt = AUD_FMT_S32;
123 *endianess = 0;
124 break;
125#endif
126
127#ifdef PA_SAMPLE_S32BE
128 case PA_SAMPLE_S32BE:
129 *fmt = AUD_FMT_S32;
130 *endianess = 1;
131 break;
132#endif
133
134 default:
135 return -1;
136 }
137 return 0;
138}
139
140static void context_state_callback(pa_context *c, void *userdata)
141{
142 switch (pa_context_get_state(c))
143 {
144 case PA_CONTEXT_READY:
145 case PA_CONTEXT_TERMINATED:
146 case PA_CONTEXT_FAILED:
147 pa_threaded_mainloop_signal(g_pMainLoop, 0);
148 break;
149 default:
150 break;
151 }
152}
153
154static void stream_state_callback(pa_stream *s, void *userdata)
155{
156 switch (pa_stream_get_state(s))
157 {
158 case PA_STREAM_READY:
159 case PA_STREAM_FAILED:
160 case PA_STREAM_TERMINATED:
161 pa_threaded_mainloop_signal(g_pMainLoop, 0);
162 break;
163 default:
164 break;
165 }
166}
167
168static void stream_latency_update_callback(pa_stream *s, void *userdata)
169{
170 pa_threaded_mainloop_signal(g_pMainLoop, 0);
171}
172
173static int pulse_open (int fIn, struct pulse_params_req *req,
174 struct pulse_params_obt *obt, pa_stream **ppStream)
175{
176 pa_sample_spec sspec;
177 pa_channel_map cmap;
178 pa_stream *pStream = NULL;
179 pa_buffer_attr bufAttr;
180 const pa_buffer_attr *pBufAttr;
181 const pa_sample_spec *pSampSpec;
182 const char *pchPCMName = fIn ? "pcm_in" : "pcm_out";
183 pa_stream_flags_t flags;
184 int ms = fIn ? conf.buffer_msecs_in : conf.buffer_msecs_out;
185
186 sspec.rate = req->freq;
187 sspec.channels = req->nchannels;
188 sspec.format = req->pa_format;
189
190 LogRel(("Pulse: open %s rate=%dHz channels=%d format=%s\n",
191 fIn ? "PCM_IN" : "PCM_OUT", req->freq, req->nchannels,
192 pa_sample_format_to_string(req->pa_format)));
193
194 if (!pa_sample_spec_valid(&sspec))
195 {
196 LogRel(("Pulse: Unsupported sample specification\n"));
197 goto fail;
198 }
199
200 pa_channel_map_init_auto(&cmap, sspec.channels, PA_CHANNEL_MAP_ALSA);
201
202#if 0
203 pa_cvolume_reset(&volume, sspec.channels);
204#endif
205
206 pa_threaded_mainloop_lock(g_pMainLoop);
207
208 if (!(pStream = pa_stream_new(g_pContext, pchPCMName, &sspec, &cmap)))
209 {
210 LogRel(("Pulse: Cannot create stream %s\n", pchPCMName));
211 goto unlock_and_fail;
212 }
213
214 pSampSpec = pa_stream_get_sample_spec(pStream);
215 obt->pa_format = pSampSpec->format;
216 obt->nchannels = pSampSpec->channels;
217 obt->freq = pSampSpec->rate;
218
219 pa_stream_set_state_callback(pStream, stream_state_callback, NULL);
220 pa_stream_set_latency_update_callback(pStream, stream_latency_update_callback, NULL);
221
222 memset(&bufAttr, 0, sizeof(bufAttr));
223 bufAttr.tlength = (pa_bytes_per_second(pSampSpec) * ms) / 1000;
224 bufAttr.maxlength = (bufAttr.tlength*3) / 2;
225 bufAttr.minreq = pa_bytes_per_second(pSampSpec) / 100; /* 10ms */
226 bufAttr.prebuf = bufAttr.tlength - bufAttr.minreq;
227 bufAttr.fragsize = pa_bytes_per_second(pSampSpec) / 100; /* 10ms */
228
229 flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
230 if (fIn)
231 {
232 if (pa_stream_connect_record(pStream, /*dev=*/NULL, &bufAttr, flags) < 0)
233 {
234 LogRel(("Pulse: Cannot connect record stream : %s\n",
235 pa_strerror(pa_context_errno(g_pContext))));
236 goto disconnect_unlock_and_fail;
237 }
238 }
239 else
240 {
241 if (pa_stream_connect_playback(pStream, /*dev=*/NULL, &bufAttr, flags,
242 NULL, NULL) < 0)
243 {
244 LogRel(("Pulse: Cannot connect playback stream: %s\n",
245 pa_strerror(pa_context_errno(g_pContext))));
246 goto disconnect_unlock_and_fail;
247 }
248 }
249
250 /* Wait until the stream is ready */
251 pa_threaded_mainloop_wait(g_pMainLoop);
252
253 if (pa_stream_get_state(pStream) != PA_STREAM_READY)
254 {
255 LogRel(("Pulse: Wrong stream state %d\n", pa_stream_get_state(pStream)));
256 goto disconnect_unlock_and_fail;
257 }
258
259 pBufAttr = pa_stream_get_buffer_attr(pStream);
260 obt->buffer_size = pBufAttr->maxlength;
261
262 pa_threaded_mainloop_unlock(g_pMainLoop);
263
264 LogRel(("Pulse: buffer settings: max=%d tlength=%d prebuf=%d minreq=%d\n",
265 pBufAttr->maxlength, pBufAttr->tlength, pBufAttr->prebuf, pBufAttr->minreq));
266
267 *ppStream = pStream;
268 return 0;
269
270disconnect_unlock_and_fail:
271 pa_stream_disconnect(pStream);
272
273unlock_and_fail:
274 pa_threaded_mainloop_unlock(g_pMainLoop);
275
276fail:
277 if (pStream)
278 pa_stream_unref(pStream);
279
280 *ppStream = NULL;
281 return -1;
282}
283
284static int pulse_init_out (HWVoiceOut *hw, audsettings_t *as)
285{
286 PulseVoice *pulse = (PulseVoice *) hw;
287 struct pulse_params_req req;
288 struct pulse_params_obt obt;
289 audfmt_e effective_fmt;
290 int endianness;
291 audsettings_t obt_as;
292
293 req.pa_format = aud_to_pulsefmt (as->fmt);
294 req.freq = as->freq;
295 req.nchannels = as->nchannels;
296
297 if (pulse_open (/*fIn=*/0, &req, &obt, &pulse->pStream))
298 return -1;
299
300 if (pulse_to_audfmt (obt.pa_format, &effective_fmt, &endianness))
301 {
302 LogRel(("Pulse: Cannot find audio format %d\n", obt.pa_format));
303 return -1;
304 }
305
306 obt_as.freq = obt.freq;
307 obt_as.nchannels = obt.nchannels;
308 obt_as.fmt = effective_fmt;
309 obt_as.endianness = endianness;
310
311 audio_pcm_init_info (&hw->info, &obt_as);
312 hw->samples = obt.buffer_size >> hw->info.shift;
313
314 pulse->pPCMBuf = RTMemAllocZ(obt.buffer_size);
315 if (!pulse->pPCMBuf)
316 {
317 LogRel(("Pulse: Could not allocate DAC buffer of %d bytes\n", obt.buffer_size));
318 return -1;
319 }
320
321 return 0;
322}
323
324static void pulse_fini_out (HWVoiceOut *hw)
325{
326 PulseVoice *pulse = (PulseVoice *)hw;
327 if (pulse->pStream)
328 {
329 pa_stream_disconnect(pulse->pStream);
330 pa_stream_unref(pulse->pStream);
331 pulse->pStream = NULL;
332 }
333 if (pulse->pPCMBuf)
334 {
335 RTMemFree (pulse->pPCMBuf);
336 pulse->pPCMBuf = NULL;
337 }
338}
339
340static int pulse_run_out (HWVoiceOut *hw)
341{
342 PulseVoice *pulse = (PulseVoice *) hw;
343 int csLive, csDecr, csSamples, csToWrite, csAvail;
344 size_t cbAvail, cbToWrite;
345 uint8_t *pu8Dst;
346 st_sample_t *psSrc;
347
348 csLive = audio_pcm_hw_get_live_out (hw);
349 if (!csLive)
350 return 0;
351
352 pa_threaded_mainloop_lock(g_pMainLoop);
353
354 cbAvail = pa_stream_writable_size (pulse->pStream);
355 if (cbAvail == -1)
356 {
357 LogRel(("Pulse: Failed to determine the writable size: %s\n",
358 pa_strerror(pa_context_errno(g_pContext))));
359 return 0;
360 }
361
362 csAvail = cbAvail >> hw->info.shift; /* bytes => samples */
363 csDecr = audio_MIN (csLive, csAvail);
364 csSamples = csDecr;
365
366 while (csSamples)
367 {
368 /* split request at the end of our samples buffer */
369 csToWrite = audio_MIN (csSamples, hw->samples - hw->rpos);
370 cbToWrite = csToWrite << hw->info.shift;
371 psSrc = hw->mix_buf + hw->rpos;
372 pu8Dst = advance (pulse->pPCMBuf, hw->rpos << hw->info.shift);
373
374 hw->clip (pu8Dst, psSrc, csToWrite);
375
376 if (pa_stream_write (pulse->pStream, pu8Dst, cbToWrite,
377 /*cleanup_callback=*/NULL, 0, PA_SEEK_RELATIVE) < 0)
378 {
379 LogRel(("Pulse: Failed to write %d samples: %s\n",
380 csToWrite, pa_strerror(pa_context_errno(g_pContext))));
381 break;
382 }
383 hw->rpos = (hw->rpos + csToWrite) % hw->samples;
384 csSamples -= csToWrite;
385 }
386
387 pa_threaded_mainloop_unlock(g_pMainLoop);
388
389 return csDecr;
390}
391
392static int pulse_write (SWVoiceOut *sw, void *buf, int len)
393{
394 return audio_pcm_sw_write (sw, buf, len);
395}
396
397static void stream_success_callback(pa_stream *pStream, int success, void *userdata)
398{
399 PulseVoice *pulse = (PulseVoice *) userdata;
400 pulse->fOpSuccess = success;
401 pa_threaded_mainloop_signal(g_pMainLoop, 0);
402}
403
404typedef enum
405{
406 Unpause = 0,
407 Pause = 1,
408 Flush = 2,
409 Trigger = 3
410} pulse_cmd_t;
411
412static int pulse_ctrl (HWVoiceOut *hw, pulse_cmd_t cmd)
413{
414 PulseVoice *pulse = (PulseVoice *) hw;
415 pa_operation *op = NULL;
416
417 if (!pulse->pStream)
418 return 0;
419
420 pa_threaded_mainloop_lock(g_pMainLoop);
421 switch (cmd)
422 {
423 case Pause:
424 op = pa_stream_cork(pulse->pStream, 1, stream_success_callback, pulse);
425 break;
426 case Unpause:
427 op = pa_stream_cork(pulse->pStream, 0, stream_success_callback, pulse);
428 break;
429 case Flush:
430 op = pa_stream_flush(pulse->pStream, stream_success_callback, pulse);
431 break;
432 case Trigger:
433 op = pa_stream_trigger(pulse->pStream, stream_success_callback, pulse);
434 break;
435 default:
436 goto fail;
437 }
438 if (!op)
439 LogRel(("Pulse: Failed ctrl cmd=%d to stream: %s\n",
440 cmd, pa_strerror(pa_context_errno(g_pContext))));
441 else
442 pa_operation_unref(op);
443
444fail:
445 pa_threaded_mainloop_unlock(g_pMainLoop);
446 return 0;
447}
448
449static int pulse_ctl_out (HWVoiceOut *hw, int cmd, ...)
450{
451 switch (cmd)
452 {
453 case VOICE_ENABLE:
454 pulse_ctrl(hw, Unpause);
455 pulse_ctrl(hw, Trigger);
456 break;
457 case VOICE_DISABLE:
458 pulse_ctrl(hw, Flush);
459 break;
460 default:
461 return -1;
462 }
463 return 0;
464}
465
466static int pulse_init_in (HWVoiceIn *hw, audsettings_t *as)
467{
468 PulseVoice *pulse = (PulseVoice *) hw;
469 struct pulse_params_req req;
470 struct pulse_params_obt obt;
471 audfmt_e effective_fmt;
472 int endianness;
473 audsettings_t obt_as;
474
475 req.pa_format = aud_to_pulsefmt (as->fmt);
476 req.freq = as->freq;
477 req.nchannels = as->nchannels;
478
479 if (pulse_open (/*fIn=*/1, &req, &obt, &pulse->pStream))
480 return -1;
481
482 if (pulse_to_audfmt (obt.pa_format, &effective_fmt, &endianness))
483 {
484 LogRel(("Pulse: Cannot find audio format %d\n", obt.pa_format));
485 return -1;
486 }
487
488 obt_as.freq = obt.freq;
489 obt_as.nchannels = obt.nchannels;
490 obt_as.fmt = effective_fmt;
491 obt_as.endianness = endianness;
492
493 audio_pcm_init_info (&hw->info, &obt_as);
494
495 /* pcm_in: reserve twice as the maximum buffer length because of peek()/drop(). */
496 hw->samples = 2 * (obt.buffer_size >> hw->info.shift);
497
498 /* no buffer for input */
499 pulse->pPCMBuf = NULL;
500
501 return 0;
502}
503
504static void pulse_fini_in (HWVoiceIn *hw)
505{
506 PulseVoice *pulse = (PulseVoice *)hw;
507 if (pulse->pStream)
508 {
509 pa_stream_disconnect(pulse->pStream);
510 pa_stream_unref(pulse->pStream);
511 pulse->pStream = NULL;
512 }
513 if (pulse->pPCMBuf)
514 {
515 RTMemFree (pulse->pPCMBuf);
516 pulse->pPCMBuf = NULL;
517 }
518}
519
520static int pulse_run_in (HWVoiceIn *hw)
521{
522 PulseVoice *pulse = (PulseVoice *) hw;
523 int csDead, csDecr = 0, csSamples, csRead, csAvail;
524 size_t cbAvail;
525 const void *pu8Src;
526 st_sample_t *psDst;
527
528 csDead = hw->samples - audio_pcm_hw_get_live_in (hw);
529
530 if (!csDead)
531 return 0; /* no buffer available */
532
533 pa_threaded_mainloop_lock(g_pMainLoop);
534
535 if (pa_stream_peek(pulse->pStream, &pu8Src, &cbAvail) < 0)
536 {
537 LogRel(("Pulse: Peek failed: %s\n",
538 pa_strerror(pa_context_errno(g_pContext))));
539 goto exit;
540 }
541 if (!pu8Src)
542 goto exit;
543
544 csAvail = cbAvail >> hw->info.shift;
545 csDecr = audio_MIN (csDead, csAvail);
546
547 csSamples = csDecr;
548
549 while (csSamples)
550 {
551 /* split request at the end of our samples buffer */
552 psDst = hw->conv_buf + hw->wpos;
553 csRead = audio_MIN (csSamples, hw->samples - hw->wpos);
554 hw->conv (psDst, pu8Src, csRead, &nominal_volume);
555 hw->wpos = (hw->wpos + csRead) % hw->samples;
556 csSamples -= csRead;
557 pu8Src = (const void*)((uint8_t*)pu8Src + (csRead << hw->info.shift));
558 }
559
560 pa_stream_drop(pulse->pStream);
561
562exit:
563 pa_threaded_mainloop_unlock(g_pMainLoop);
564
565 return csDecr;
566}
567
568static int pulse_read (SWVoiceIn *sw, void *buf, int size)
569{
570 return audio_pcm_sw_read (sw, buf, size);
571}
572
573static int pulse_ctl_in (HWVoiceIn *hw, int cmd, ...)
574{
575 return 0;
576}
577
578static void *pulse_audio_init (void)
579{
580 int rc;
581
582 rc = audioLoadPulseLib();
583 if (RT_FAILURE(rc))
584 {
585 LogRel(("Pulse: Failed to load the PulseAudio shared library! Error %Rrc\n", rc));
586 return NULL;
587 }
588 if (!(g_pMainLoop = pa_threaded_mainloop_new()))
589 {
590 LogRel(("Pulse: Failed to allocate main loop: %s\n",
591 pa_strerror(pa_context_errno(g_pContext))));
592 goto fail;
593 }
594 if (!(g_pContext = pa_context_new(pa_threaded_mainloop_get_api(g_pMainLoop), "VBox")))
595 {
596 LogRel(("Pulse: Failed to allocate context: %s\n",
597 pa_strerror(pa_context_errno(g_pContext))));
598 goto fail;
599 }
600 pa_context_set_state_callback(g_pContext, context_state_callback, NULL);
601 if (pa_context_connect(g_pContext, /*server=*/NULL, 0, NULL) < 0)
602 {
603 LogRel(("Pulse: Failed to connect to server: %s\n",
604 pa_strerror(pa_context_errno(g_pContext))));
605 goto fail;
606 }
607
608 if (pa_threaded_mainloop_start(g_pMainLoop) < 0)
609 {
610 LogRel(("Pulse: Failed to start threaded mainloop: %s\n",
611 pa_strerror(pa_context_errno(g_pContext))));
612 goto fail;
613 }
614
615 /* Wait until the g_pContext is ready */
616 pa_threaded_mainloop_lock(g_pMainLoop);
617 pa_threaded_mainloop_wait(g_pMainLoop);
618 if (pa_context_get_state(g_pContext) != PA_CONTEXT_READY)
619 {
620 LogRel(("Pulse: Wrong context state %d\n", pa_context_get_state(g_pContext)));
621 goto unlock_and_fail;
622 }
623 pa_threaded_mainloop_unlock(g_pMainLoop);
624
625 return &conf;
626
627unlock_and_fail:
628 if (g_pMainLoop)
629 pa_threaded_mainloop_unlock(g_pMainLoop);
630
631fail:
632 if (g_pMainLoop)
633 pa_threaded_mainloop_stop(g_pMainLoop);
634
635 if (g_pContext)
636 {
637 pa_context_disconnect(g_pContext);
638 pa_context_unref(g_pContext);
639 g_pContext = NULL;
640 }
641 if (g_pMainLoop)
642 {
643 pa_threaded_mainloop_free(g_pMainLoop);
644 g_pMainLoop = NULL;
645 }
646 return NULL;
647}
648
649static void pulse_audio_fini (void *opaque)
650{
651 if (g_pMainLoop)
652 pa_threaded_mainloop_stop(g_pMainLoop);
653 if (g_pContext)
654 {
655 pa_context_disconnect(g_pContext);
656 pa_context_unref(g_pContext);
657 g_pContext = NULL;
658 }
659 if (g_pMainLoop)
660 {
661 pa_threaded_mainloop_free(g_pMainLoop);
662 g_pMainLoop = NULL;
663 }
664 (void) opaque;
665}
666
667static struct audio_option pulse_options[] =
668{
669 {"DAC_MS", AUD_OPT_INT, &conf.buffer_msecs_out,
670 "DAC period size in milliseconds", NULL, 0},
671 {"ADC_MS", AUD_OPT_INT, &conf.buffer_msecs_in,
672 "ADC period size in milliseconds", NULL, 0},
673 {NULL, 0, NULL, NULL, NULL, 0}
674};
675
676static struct audio_pcm_ops pulse_pcm_ops =
677{
678 pulse_init_out,
679 pulse_fini_out,
680 pulse_run_out,
681 pulse_write,
682 pulse_ctl_out,
683
684 pulse_init_in,
685 pulse_fini_in,
686 pulse_run_in,
687 pulse_read,
688 pulse_ctl_in
689};
690
691struct audio_driver pulse_audio_driver =
692{
693 INIT_FIELD (name = ) "pulse",
694 INIT_FIELD (descr = ) "PulseAudio http://www.pulseaudio.org",
695 INIT_FIELD (options = ) pulse_options,
696 INIT_FIELD (init = ) pulse_audio_init,
697 INIT_FIELD (fini = ) pulse_audio_fini,
698 INIT_FIELD (pcm_ops = ) &pulse_pcm_ops,
699 INIT_FIELD (can_be_default = ) 1,
700 INIT_FIELD (max_voices_out = ) INT_MAX,
701 INIT_FIELD (max_voices_in = ) INT_MAX,
702 INIT_FIELD (voice_size_out = ) sizeof (PulseVoice),
703 INIT_FIELD (voice_size_in = ) sizeof (PulseVoice)
704};
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