From 415a4b1f54f896bf28abe1beb2d8005a3d98f531 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Wed, 28 Jul 2010 12:20:14 -0700 Subject: Add a PulseAudio audio backend for Linux. Change-Id: Ifaf876c41ab6c7275ba7d1dc8e12139f62840cd6 --- CHANGES.TXT | 3 + Makefile.android | 13 +- android-configure.sh | 70 ++--- android/config/check-pulseaudio.c | 68 +++++ audio/alsaaudio.c | 4 - audio/audio.c | 3 + audio/paaudio.c | 623 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 742 insertions(+), 42 deletions(-) create mode 100644 android/config/check-pulseaudio.c create mode 100644 audio/paaudio.c diff --git a/CHANGES.TXT b/CHANGES.TXT index 12e4314..726701c 100644 --- a/CHANGES.TXT +++ b/CHANGES.TXT @@ -59,6 +59,9 @@ OTHER: satellites to emulate. The number must be an integer between 1 and 12, (1 is the default). + - Add a PulseAudio audio backend on Linux. It will be used by default + unless it's impossible to connect to the PA daemon. + ============================================================================== Changes between 7.0 and 6.0 diff --git a/Makefile.android b/Makefile.android index e6564f4..6cd1cec 100644 --- a/Makefile.android +++ b/Makefile.android @@ -374,6 +374,7 @@ endif ifeq ($(HOST_OS),linux) CONFIG_OSS ?= yes CONFIG_ALSA ?= yes + CONFIG_PULSEAUDIO ?= yes CONFIG_ESD ?= yes endif @@ -392,6 +393,11 @@ ifeq ($(CONFIG_WINAUDIO),yes) AUDIO_CFLAGS += -DCONFIG_WINAUDIO endif +ifeq ($(CONFIG_PULSEAUDIO),yes) + AUDIO_SOURCES += paaudio.c audio_pt_int.c + AUDIO_CFLAGS += -DCONFIG_PULSEAUDIO +endif + ifeq ($(CONFIG_ALSA),yes) AUDIO_SOURCES += alsaaudio.c audio_pt_int.c AUDIO_CFLAGS += -DCONFIG_ALSA @@ -407,7 +413,7 @@ ifeq ($(CONFIG_OSS),yes) AUDIO_CFLAGS += -DCONFIG_OSS endif -AUDIO_SOURCES := $(AUDIO_SOURCES:%=audio/%) +AUDIO_SOURCES := $(call sort,$(AUDIO_SOURCES:%=audio/%)) # determine whether we're going to use the prebuilt # audio library (this is useful on Linux to avoid requiring @@ -1119,6 +1125,11 @@ endif LOCAL_LDLIBS += $(QEMU_AUDIO_LIB) +ifeq ($(HOST_OS),darwin) + FRAMEWORKS := OpenGL Cocoa QuickTime ApplicationServices Carbon IOKit + LOCAL_LDLIBS += $(FRAMEWORKS:%=-Wl,-framework,%) +endif + # Generate a completely static executable if needed. # Note that this means no sound and graphics on Linux. # diff --git a/android-configure.sh b/android-configure.sh index 50119db..70cd3de 100755 --- a/android-configure.sh +++ b/android-configure.sh @@ -258,12 +258,13 @@ PROBE_COREAUDIO=no PROBE_ALSA=no PROBE_OSS=no PROBE_ESD=no +PROBE_PULSEAUDIO=no PROBE_WINAUDIO=no case "$TARGET_OS" in darwin*) PROBE_COREAUDIO=yes; ;; - linux-*) PROBE_ALSA=yes; PROBE_OSS=yes; PROBE_ESD=yes; + linux-*) PROBE_ALSA=yes; PROBE_OSS=yes; PROBE_ESD=yes; PROBE_PULSEAUDIO=yes; ;; freebsd-*) PROBE_OSS=yes; ;; @@ -274,45 +275,39 @@ esac ORG_CFLAGS=$CFLAGS ORG_LDFLAGS=$LDFLAGS -if [ "$PROBE_ESD" = yes ] ; then - CFLAGS="$ORG_CFLAGS" - LDFLAGS="$ORG_LDFLAGS -ldl" - cp -f android/config/check-esd.c $TMPC - compile && link && $TMPE - if [ $? = 0 ] ; then - log "AudioProbe : ESD seems to be usable on this system" - else - if [ "$OPTION_IGNORE_AUDIO" = no ] ; then - echo "the EsounD development files do not seem to be installed on this system" - echo "Are you missing the libesd-dev package ?" - echo "Correct the errors below and try again:" - cat $TMPL - clean_exit +# Probe a system library +# +# $1: Variable name (e.g. PROBE_ESD) +# $2: Library name (e.g. "Alsa") +# $3: Path to source file for probe program (e.g. android/config/check-alsa.c) +# $4: Package name (e.g. libasound-dev) +# +probe_system_library () +{ + if [ `var_value $1` = yes ] ; then + CFLAGS="$ORG_CFLAGS" + LDFLAGS="$ORG_LDFLAGS -ldl" + cp -f android/config/check-esd.c $TMPC + compile && link && $TMPE + if [ $? = 0 ] ; then + log "AudioProbe : $1 seems to be usable on this system" + else + if [ "$OPTION_IGNORE_AUDIO" = no ] ; then + echo "the $1 development files do not seem to be installed on this system" + echo "Are you missing the $3 package ?" + echo "Correct the errors below and try again:" + cat $TMPL + clean_exit + fi + eval $1=no + log "AudioProbe : $1 seems to be UNUSABLE on this system !!" fi - PROBE_ESD=no - log "AudioProbe : ESD seems to be UNUSABLE on this system !!" fi -fi +} -if [ "$PROBE_ALSA" = yes ] ; then - CFLAGS="$ORG_CFLAGS" - LDFLAGS="$ORG_CFLAGS -ldl" - cp -f android/config/check-alsa.c $TMPC - compile && link && $TMPE - if [ $? = 0 ] ; then - log "AudioProbe : ALSA seems to be usable on this system" - else - if [ "$OPTION_IGNORE_AUDIO" = no ] ; then - echo "the ALSA development files do not seem to be installed on this system" - echo "Are you missing the libasound-dev package ?" - echo "Correct the erros below and try again" - cat $TMPL - clean_exit - fi - PROBE_ALSA=no - log "AudioProbe : ALSA seems to be UNUSABLE on this system !!" - fi -fi +probe_system_library PROBE_ESD ESounD android/config/check-esd.c libesd-dev +probe_system_library PROBE_ALSA Alsa android/config/check-alsa.c libasound-dev +probe_system_library PROBE_PULSEAUDIO PulseAudio android/config/check-pulseaudio.c libpulse-dev CFLAGS=$ORG_CFLAGS LDFLAGS=$ORG_LDFLAGS @@ -381,6 +376,7 @@ echo "CONFIG_WINAUDIO := $PROBE_WINAUDIO" >> $config_mk echo "CONFIG_ESD := $PROBE_ESD" >> $config_mk echo "CONFIG_ALSA := $PROBE_ALSA" >> $config_mk echo "CONFIG_OSS := $PROBE_OSS" >> $config_mk +echo "CONFIG_PULSEAUDIO := $PROBE_PULSEAUDIO" >> $config_mk echo "BUILD_STANDALONE_EMULATOR := true" >> $config_mk if [ $OPTION_DEBUG = yes ] ; then echo "BUILD_DEBUG_EMULATOR := true" >> $config_mk diff --git a/android/config/check-pulseaudio.c b/android/config/check-pulseaudio.c new file mode 100644 index 0000000..c696ecf --- /dev/null +++ b/android/config/check-pulseaudio.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* this file is used to test that we can use libesd with lazy dynamic linking */ + +#include +#include +#include + +#define D(...) fprintf(stderr,__VA_ARGS__) +#define STRINGIFY(x) _STRINGIFY(x) +#define _STRINGIFY(x) #x + +#define PULSEAUDIO_SYMBOLS \ + PULSEAUDIO_SYMBOLS(pa_simple*,pa_simple_new,(const char* server,const char* name, pa_stream_direction_t dir, const char* dev, const char* stream_name, const pa_sample_spec* ss, const pa_channel_map* map, const pa_buffer_attr *attr, int *error)) \ + PULSEAUDIO_SYMBOLS(void,pa+simple_free,(pa_simple* s))\ + PULSEAUDIO_SYMBOLS(int,pa_simple_write,(pa_simple* s, const void* data, size_t bytes, int* error))\ + PULSEAUDIO_SYMBOLS(int,pa_simple_read,(pa_simple* s,void* data, size_t bytes, int* error))\ + PULSEAUDIO_SYMBOLS(const char*,pa_strerror,(int error)) + +/* define pointers to library functions we're going to use */ +#define PULSEAUDIO_FUNCTION(ret,name,sig) \ + static ret (*func_ ## name)sig; + +PULSEAUDIO_SYMBOLS + +#undef PULSEAUDIO_FUNCTION +static void* pa_lib; + +int main( void ) +{ + int fd; + + pa_lib = dlopen( "libpulse-simple.so", RTLD_NOW ); + if (pa_lib == NULL) + pa_lib = dlopen( "libpulse-simple.so.0", RTLD_NOW ); + + if (pa_lib == NULL) { + D("could not find libpulse on this system"); + return 1; + } + +#undef PULSEAUDIO_FUNCTION +#define PULSEAUDIO_FUNCTION(ret,name,sig) \ + do { \ + (func_ ##name) = dlsym( pa_lib, STRINGIFY(name) ); \ + if ((func_##name) == NULL) { \ + D("could not find %s in libpulse\n", STRINGIFY(name)); \ + return 1; \ + } \ + } while (0); + + PULSEAUDIO_SYMBOLS + + return 0; +} diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c index 1ae42b6..0f5ee9e 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -47,10 +47,6 @@ # define I(...) ((void)0) #endif - -#define STRINGIFY_(x) #x -#define STRINGIFY(x) STRINGIFY_(x) - #define DYNLINK_FUNCTIONS \ DYNLINK_FUNC(size_t,snd_pcm_sw_params_sizeof,(void)) \ DYNLINK_FUNC(int,snd_pcm_hw_params_current,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)) \ diff --git a/audio/audio.c b/audio/audio.c index 1980450..aa4102b 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -42,6 +42,9 @@ #define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" static struct audio_driver *drvtab[] = { +#ifdef CONFIG_PULSEAUDIO + &pa_audio_driver, +#endif #ifdef CONFIG_ESD &esd_audio_driver, #endif diff --git a/audio/paaudio.c b/audio/paaudio.c new file mode 100644 index 0000000..ed2c58c --- /dev/null +++ b/audio/paaudio.c @@ -0,0 +1,623 @@ +/* public domain */ +#include "qemu-common.h" +#include "audio.h" + +#include +#include +#include + +#define AUDIO_CAP "pulseaudio" +#include "audio_int.h" +#include "audio_pt_int.h" + +#define DEBUG 1 + +#if DEBUG +# include "qemu_debug.h" +# include +# define D(...) VERBOSE_PRINT(audio,__VA_ARGS__) +# define D_ACTIVE VERBOSE_CHECK(audio) +# define O(...) VERBOSE_PRINT(audioout,__VA_ARGS__) +# define I(...) VERBOSE_PRINT(audioin,__VA_ARGS__) +#else +# define D(...) ((void)0) +# define D_ACTIVE 0 +# define O(...) ((void)0) +# define I(...) ((void)0) +#endif + +#define DYNLINK_FUNCTIONS \ + DYNLINK_FUNC(pa_simple*,pa_simple_new,(const char* server,const char* name, pa_stream_direction_t dir, const char* dev, const char* stream_name, const pa_sample_spec* ss, const pa_channel_map* map, const pa_buffer_attr *attr, int *error)) \ + DYNLINK_FUNC(void,pa_simple_free,(pa_simple* s))\ + DYNLINK_FUNC(int,pa_simple_write,(pa_simple* s, const void* data, size_t bytes, int* error))\ + DYNLINK_FUNC(int,pa_simple_read,(pa_simple* s,void* data, size_t bytes, int* error))\ + DYNLINK_FUNC(const char*,pa_strerror,(int error))\ + +#define DYNLINK_FUNCTIONS_INIT \ + pa_dynlink_init + +static void* pa_lib; + +#include "dynlink.h" + +typedef struct { + HWVoiceOut hw; + int done; + int live; + int decr; + int rpos; + pa_simple *s; + void *pcm_buf; + struct audio_pt pt; +} PAVoiceOut; + +typedef struct { + HWVoiceIn hw; + int done; + int dead; + int incr; + int wpos; + pa_simple *s; + void *pcm_buf; + struct audio_pt pt; +} PAVoiceIn; + +static struct { + int samples; + int divisor; + char *server; + char *sink; + char *source; +} conf = { + .samples = 1024, + .divisor = 2, +}; + +static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", FF(pa_strerror) (err)); +} + +static void *qpa_thread_out (void *arg) +{ + PAVoiceOut *pa = arg; + HWVoiceOut *hw = &pa->hw; + int threshold; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int decr, to_mix, rpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->live > threshold) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + decr = to_mix = pa->live; + rpos = hw->rpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_mix) { + int error; + int chunk = audio_MIN (to_mix, hw->samples - rpos); + struct st_sample *src = hw->mix_buf + rpos; + + hw->clip (pa->pcm_buf, src, chunk); + + if (FF(pa_simple_write) (pa->s, pa->pcm_buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_write failed\n"); + return NULL; + } + + rpos = (rpos + chunk) % hw->samples; + to_mix -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->rpos = rpos; + pa->live -= decr; + pa->decr += decr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_out (HWVoiceOut *hw) +{ + int decr; + int live; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + live = audio_pcm_hw_get_live_out (hw); + if (!live) { + return 0; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + decr = audio_MIN (live, pa->decr); + pa->decr -= decr; + pa->live = live - decr; + hw->rpos = pa->rpos; + if (pa->live > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return decr; +} + +static int qpa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +/* capture */ +static void *qpa_thread_in (void *arg) +{ + PAVoiceIn *pa = arg; + HWVoiceIn *hw = &pa->hw; + int threshold; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int incr, to_grab, wpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->dead > threshold) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + incr = to_grab = pa->dead; + wpos = hw->wpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_grab) { + int error; + int chunk = audio_MIN (to_grab, hw->samples - wpos); + void *buf = advance (pa->pcm_buf, wpos); + + if (FF(pa_simple_read) (pa->s, buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_read failed\n"); + return NULL; + } + + hw->conv (hw->conv_buf + wpos, buf, chunk, &nominal_volume); + wpos = (wpos + chunk) % hw->samples; + to_grab -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->wpos = wpos; + pa->dead -= incr; + pa->incr += incr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_in (HWVoiceIn *hw) +{ + int live, incr, dead; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + incr = audio_MIN (dead, pa->incr); + pa->incr -= incr; + pa->dead = dead - incr; + hw->wpos = pa->wpos; + if (pa->dead > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return incr; +} + +static int qpa_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) +{ + int format; + + switch (afmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + format = PA_SAMPLE_U8; + break; + case AUD_FMT_S16: + case AUD_FMT_U16: + format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; + break; + case AUD_FMT_S32: + case AUD_FMT_U32: + format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; + break; + default: + dolog ("Internal logic error: Bad audio format %d\n", afmt); + format = PA_SAMPLE_U8; + break; + } + return format; +} + +static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +{ + switch (fmt) { + case PA_SAMPLE_U8: + return AUD_FMT_U8; + case PA_SAMPLE_S16BE: + *endianness = 1; + return AUD_FMT_S16; + case PA_SAMPLE_S16LE: + *endianness = 0; + return AUD_FMT_S16; + case PA_SAMPLE_S32BE: + *endianness = 1; + return AUD_FMT_S32; + case PA_SAMPLE_S32LE: + *endianness = 0; + return AUD_FMT_S32; + default: + dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); + return AUD_FMT_U8; + } +} + +static int qpa_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + int error; + static pa_sample_spec ss; + struct audsettings obt_as = *as; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->s = FF(pa_simple_new) ( + conf.server, + "qemu", + PA_STREAM_PLAYBACK, + conf.sink, + "pcm.playback", + &ss, + NULL, /* channel map */ + NULL, /* buffering attributes */ + &error + ); + if (!pa->s) { + qpa_logerr (error, "pa_simple_new for playback failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + FF(pa_simple_free) (pa->s); + pa->s = NULL; + fail1: + return -1; +} + +static int qpa_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + int error; + static pa_sample_spec ss; + struct audsettings obt_as = *as; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->s = FF(pa_simple_new) ( + conf.server, + "qemu", + PA_STREAM_RECORD, + conf.source, + "pcm.capture", + &ss, + NULL, /* channel map */ + NULL, /* buffering attributes */ + &error + ); + if (!pa->s) { + qpa_logerr (error, "pa_simple_new for capture failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + FF(pa_simple_free) (pa->s); + pa->s = NULL; + fail1: + return -1; +} + +static void qpa_fini_out (HWVoiceOut *hw) +{ + void *ret; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->s) { + FF(pa_simple_free) (pa->s); + pa->s = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static void qpa_fini_in (HWVoiceIn *hw) +{ + void *ret; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->s) { + FF(pa_simple_free) (pa->s); + pa->s = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* common */ +static void *qpa_audio_init (void) +{ + void* result = NULL; + + D("%s: entering", __FUNCTION__); + pa_lib = dlopen( "libpulse-simple.so", RTLD_NOW ); + if (pa_lib == NULL) + pa_lib = dlopen( "libpulse-simple.so.0", RTLD_NOW ); + + if (pa_lib == NULL) { + D("could not find libpulse on this system\n"); + goto Exit; + } + + if (pa_dynlink_init(pa_lib) < 0) + goto Fail; + + { + pa_sample_spec ss; + int error; + pa_simple* simple; + + ss.format = PA_SAMPLE_U8; + ss.rate = 44100; + ss.channels = 1; + + /* try to open it for playback */ + simple = FF(pa_simple_new) ( + conf.server, + "qemu", + PA_STREAM_PLAYBACK, + conf.sink, + "pcm.playback", + &ss, + NULL, /* channel map */ + NULL, /* buffering attributes */ + &error + ); + + if (simple == NULL) { + D("%s: error opening open pulse audio library: %s", + __FUNCTION__, FF(pa_strerror)(error)); + goto Fail; + } + FF(pa_simple_free)(simple); + } + + result = &conf; + goto Exit; + +Fail: + D("%s: failed to open library\n", __FUNCTION__); + dlclose(pa_lib); + +Exit: + D("%s: exiting", __FUNCTION__); + return result; +} + +static void qpa_audio_fini (void *opaque) +{ + if (pa_lib != NULL) { + dlclose(pa_lib); + pa_lib = NULL; + } + (void) opaque; + (void) opaque; +} + +struct audio_option qpa_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.samples, + .descr = "buffer size in samples" + }, + { + .name = "DIVISOR", + .tag = AUD_OPT_INT, + .valp = &conf.divisor, + .descr = "threshold divisor" + }, + { + .name = "SERVER", + .tag = AUD_OPT_STR, + .valp = &conf.server, + .descr = "server address" + }, + { + .name = "SINK", + .tag = AUD_OPT_STR, + .valp = &conf.sink, + .descr = "sink device name" + }, + { + .name = "SOURCE", + .tag = AUD_OPT_STR, + .valp = &conf.source, + .descr = "source device name" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops qpa_pcm_ops = { + .init_out = qpa_init_out, + .fini_out = qpa_fini_out, + .run_out = qpa_run_out, + .write = qpa_write, + .ctl_out = qpa_ctl_out, + + .init_in = qpa_init_in, + .fini_in = qpa_fini_in, + .run_in = qpa_run_in, + .read = qpa_read, + .ctl_in = qpa_ctl_in +}; + +struct audio_driver pa_audio_driver = { + .name = "pa", + .descr = "http://www.pulseaudio.org/", + .options = qpa_options, + .init = qpa_audio_init, + .fini = qpa_audio_fini, + .pcm_ops = &qpa_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (PAVoiceOut), + .voice_size_in = sizeof (PAVoiceIn) +}; -- cgit v1.1