aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES.TXT3
-rw-r--r--Makefile.android13
-rwxr-xr-xandroid-configure.sh70
-rw-r--r--android/config/check-pulseaudio.c68
-rw-r--r--audio/alsaaudio.c4
-rw-r--r--audio/audio.c3
-rw-r--r--audio/paaudio.c623
7 files changed, 742 insertions, 42 deletions
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 <esd.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+#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 <pulse/simple.h>
+#include <pulse/error.h>
+#include <dlfcn.h>
+
+#define AUDIO_CAP "pulseaudio"
+#include "audio_int.h"
+#include "audio_pt_int.h"
+
+#define DEBUG 1
+
+#if DEBUG
+# include "qemu_debug.h"
+# include <stdio.h>
+# 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)
+};