From 6df6757e3811a8084495f2d03c32f0dcc1d9e8cb Mon Sep 17 00:00:00 2001 From: faust3 Date: Fri, 5 Nov 2004 14:02:41 +0000 Subject: [PATCH] polyaudio audio driver patch by Lennart Poettering git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@13879 b3059339-0415-0410-9bf9-f77b7e298cf2 --- AUTHORS | 3 + Makefile | 2 +- libao2/Makefile | 2 +- libao2/ao_polyp.c | 325 +++++++++++++++++++++++++++++++++++++++++++++ libao2/audio_out.c | 6 + 5 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 libao2/ao_polyp.c diff --git a/AUTHORS b/AUTHORS index 800f58f74b..7a7f963ee5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -581,6 +581,9 @@ Plourde, Nicolas * quartz video output driver for Mac OS X * Darwin VCD/SVCD support +Poettering, Lennart + * Audio driver for the Polypaudio sound server + Poirier, Guillaume * French documentation translation and synchronization * XviD documentation diff --git a/Makefile b/Makefile index 80c72aeda3..a246f90339 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ OBJS_MENCODER = $(SRCS_MENCODER:.c=.o) OBJS_MPLAYER = $(SRCS_MPLAYER:.c=.o) VO_LIBS = $(AA_LIB) $(X_LIB) $(SDL_LIB) $(GGI_LIB) $(MP1E_LIB) $(MLIB_LIB) $(SVGA_LIB) $(DIRECTFB_LIB) $(CACA_LIB) -AO_LIBS = $(ARTS_LIB) $(ESD_LIB) $(JACK_LIB) $(NAS_LIB) $(SGIAUDIO_LIB) +AO_LIBS = $(ARTS_LIB) $(ESD_LIB) $(JACK_LIB) $(NAS_LIB) $(SGIAUDIO_LIB) $(POLYP_LIB) CODEC_LIBS = $(AV_LIB) $(FAME_LIB) $(MAD_LIB) $(VORBIS_LIB) $(THEORA_LIB) $(FAAD_LIB) $(LIBLZO_LIB) $(DECORE_LIB) $(XVID_LIB) $(DTS_LIB) $(PNG_LIB) $(Z_LIB) $(JPEG_LIB) $(ALSA_LIB) $(XMMS_LIB) $(X264_LIB) COMMON_LIBS = libmpcodecs/libmpcodecs.a $(W32_LIB) $(DS_LIB) libaf/libaf.a libmpdemux/libmpdemux.a input/libinput.a postproc/libswscale.a osdep/libosdep.a $(DVDREAD_LIB) $(CODEC_LIBS) $(FREETYPE_LIB) $(TERMCAP_LIB) $(CDPARANOIA_LIB) $(MPLAYER_NETWORK_LIB) $(WIN32_LIB) $(GIF_LIB) $(MACOSX_FRAMEWORKS) $(SMBSUPPORT_LIB) $(FRIBIDI_LIB) $(FONTCONFIG_LIB) $(ENCA_LIB) diff --git a/libao2/Makefile b/libao2/Makefile index 1a71775a75..bc3002b1fb 100644 --- a/libao2/Makefile +++ b/libao2/Makefile @@ -6,7 +6,7 @@ SRCS=afmt.c audio_out.c ao_mpegpes.c ao_null.c ao_pcm.c ao_plugin.c pl_delay.c p OBJS=$(SRCS:.c=.o) -CFLAGS = $(OPTFLAGS) -I. -I.. $(ARTS_INC) $(ESD_INC) $(JACK_INC) $(SDL_INC) $(X11_INC) $(EXTRA_INC) $(DXR2_INC) $(DVB_INC) +CFLAGS = $(OPTFLAGS) -I. -I.. $(ARTS_INC) $(ESD_INC) $(JACK_INC) $(SDL_INC) $(X11_INC) $(EXTRA_INC) $(DXR2_INC) $(DVB_INC) $(POLYP_INC) .SUFFIXES: .c .o diff --git a/libao2/ao_polyp.c b/libao2/ao_polyp.c new file mode 100644 index 0000000000..ad27cbba7e --- /dev/null +++ b/libao2/ao_polyp.c @@ -0,0 +1,325 @@ +#include + +#include "../config.h" + +#include +#include +#include + +#include "audio_out.h" +#include "audio_out_internal.h" +#include "afmt.h" +#include "../config.h" +#include "../mp_msg.h" + +#define POLYP_CLIENT_NAME "MPlayer" + +/** General driver info */ +static ao_info_t info = { + "Polypaudio audio output", + "polyp", + "Lennart Poettering", + "" +}; + +/** The sink to connect to */ +static char *sink = NULL; + +/** Polypaudio playback stream object */ +static struct pa_stream *stream = NULL; + +/** Polypaudio connection context */ +static struct pa_context *context = NULL; + +/** Main event loop object */ +static struct pa_mainloop *mainloop = NULL; + +/** Some special libao macro magic */ +LIBAO_EXTERN(polyp) + +/** Wait until no further actions are pending on the connection context */ +static void wait_for_completion(void) { + assert(context && mainloop); + + while (pa_context_is_pending(context)) + pa_mainloop_iterate(mainloop, 1, NULL); +} + +/** Make sure that the connection context doesn't starve to death */ +static void keep_alive(void) { + assert(context && mainloop); + + while (pa_mainloop_iterate(mainloop, 0, NULL) > 0); +} + +/** Wait until the specified operation completes */ +static void wait_for_operation(struct pa_operation *o) { + assert(o && context && mainloop); + + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_mainloop_iterate(mainloop, 1, NULL); + + pa_operation_unref(o); +} + +/** libao initialization function, arguments are sampling frequency, + * number of channels, sample type and some flags */ +static int init(int rate_hz, int channels, int format, int flags) { + struct pa_sample_spec ss; + struct pa_buffer_attr a; + char hn[128]; + char *host = NULL; + + assert(!context && !stream && !mainloop); + + if (ao_subdevice) { + int i = strcspn(ao_subdevice, ":"); + if (i >= sizeof(hn)) + i = sizeof(hn)-1; + + if (i > 0) { + strncpy(host = hn, ao_subdevice, i); + hn[i] = 0; + } + + if (ao_subdevice[i] == ':') + sink = ao_subdevice+i+1; + } + + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] -%s-%s-\n", host, sink); + + + ss.channels = channels; + ss.rate = rate_hz; + + switch (format) { + case AFMT_U8: + ss.format = PA_SAMPLE_U8; + break; + case AFMT_S16_LE: + ss.format = PA_SAMPLE_S16LE; + break; + case AFMT_S16_BE: + ss.format = PA_SAMPLE_S16BE; + break; + case AFMT_FLOAT: + ss.format = PA_SAMPLE_FLOAT32; + break; + default: + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Unsupported sample spec\n"); + goto fail; + } + + + if (!pa_sample_spec_valid(&ss)) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Invalid sample spec\n"); + goto fail; + } + + + mainloop = pa_mainloop_new(); + assert(mainloop); + + context = pa_context_new(pa_mainloop_get_api(mainloop), POLYP_CLIENT_NAME); + assert(context); + + pa_context_connect(context, host, 1, NULL); + + wait_for_completion(); + + if (pa_context_get_state(context) != PA_CONTEXT_READY) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Failed to connect to server: %s\n", pa_strerror(pa_context_errno(context))); + goto fail; + } + + stream = pa_stream_new(context, "audio stream", &ss); + assert(stream); + + a.maxlength = pa_bytes_per_second(&ss)*1; + a.tlength = a.maxlength*9/10; + a.prebuf = a.tlength/2; + a.minreq = a.tlength/10; + + pa_stream_connect_playback(stream, sink, &a, PA_STREAM_INTERPOLATE_LATENCY, PA_VOLUME_NORM); + + wait_for_completion(); + + if (pa_stream_get_state(stream) != PA_STREAM_READY) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Failed to connect to server: %s\n", pa_strerror(pa_context_errno(context))); + goto fail; + } + + return 1; + +fail: + uninit(1); + return 0; +} + +/** Destroy libao driver */ +static void uninit(int immed) { + if (stream) { + if (!immed && pa_stream_get_state(stream) == PA_STREAM_READY) + wait_for_operation(pa_stream_drain(stream, NULL, NULL)); + + pa_stream_unref(stream); + stream = NULL; + } + + if (context) { + pa_context_unref(context); + context = NULL; + } + + if (mainloop) { + pa_mainloop_free(mainloop); + mainloop = NULL; + } +} + +/** Play the specified data to the polypaudio server */ +static int play(void* data, int len, int flags) { + assert(stream && context); + + if (pa_stream_get_state(stream) != PA_STREAM_READY) + return -1; + + if (!len) + wait_for_operation(pa_stream_trigger(stream, NULL, NULL)); + else + pa_stream_write(stream, data, len, NULL, 0); + + wait_for_completion(); + + if (pa_stream_get_state(stream) != PA_STREAM_READY) + return -1; + + return len; +} + +/** Pause the audio stream by corking it on the server */ +static void audio_pause() { + assert(stream && context && pa_stream_get_state(stream) == PA_STREAM_READY); + wait_for_operation(pa_stream_cork(stream, 1, NULL, NULL)); +} + +/** Resume the audio stream by uncorking it on the server */ +static void audio_resume() { + assert(stream && context && pa_stream_get_state(stream) == PA_STREAM_READY); + wait_for_operation(pa_stream_cork(stream, 0, NULL, NULL)); +} + +/** Reset the audio stream, i.e. flush the playback buffer on the server side */ +static void reset() { + assert(stream && context && pa_stream_get_state(stream) == PA_STREAM_READY); + wait_for_operation(pa_stream_flush(stream, NULL, NULL)); +} + +/** Return number of bytes that may be written to the server without blocking */ +static int get_space(void) { + uint32_t l; + assert(stream && context && pa_stream_get_state(stream) == PA_STREAM_READY); + + keep_alive(); + + l = pa_stream_writable_size(stream); + + return l; +} + +/* A temporary latency variable */ +/* static pa_usec_t latency = 0; */ + +/* static void latency_func(struct pa_stream *s, const struct pa_latency_info *l, void *userdata) { */ +/* int negative = 0; */ + +/* if (!l) { */ +/* mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Invalid sample spec: %s\n", pa_strerror(pa_context_errno(context))); */ +/* return; */ +/* } */ + +/* latency = pa_stream_get_latency(s, l, &negative); */ + +/* /\* Nor really required *\/ */ +/* if (negative) */ +/* latency = 0; */ +/* } */ + +/** Return the current latency in seconds */ +static float get_delay(void) { + assert(stream && context && pa_stream_get_state(stream) == PA_STREAM_READY); + pa_usec_t latency; + + /* latency = 0; */ +/* wait_for_operation(pa_stream_get_latency(stream, latency_func, NULL)); */ + /* pa_operation_unref(pa_stream_get_latency(stream, latency_func, NULL)); */ + + latency = pa_stream_get_interpolated_latency(stream, NULL); + + return (float) latency/1000000; +} + +/** A temporary variable to store the current volume */ +static pa_volume_t volume = PA_VOLUME_NORM; + +/** A callback function that is called when the + * pa_context_get_sink_input_info() operation completes. Saves the + * volume field of the specified structure to the global variable volume. */ +static void info_func(struct pa_context *c, const struct pa_sink_input_info *i, int is_last, void *userdata) { + if (is_last < 0) { + mp_msg(MSGT_AO, MSGL_ERR, "AO: [polyp] Failed to get sink input info: %s\n", pa_strerror(pa_context_errno(context))); + return; + } + + if (!i) + return; + + volume = i->volume; +} + +/** Issue special libao controls on the device */ +static int control(int cmd, void *arg) { + + if (!context || !stream) + return CONTROL_ERROR; + + switch (cmd) { + + case AOCONTROL_SET_DEVICE: + /* Change the playback device */ + sink = (char*)arg; + return CONTROL_OK; + + case AOCONTROL_GET_DEVICE: + /* Return the playback device */ + *(char**)arg = sink; + return CONTROL_OK; + + case AOCONTROL_GET_VOLUME: { + /* Return the current volume of the playback stream */ + ao_control_vol_t *vol = (ao_control_vol_t*) arg; + + volume = PA_VOLUME_NORM; + wait_for_operation(pa_context_get_sink_input_info(context, pa_stream_get_index(stream), info_func, NULL)); + vol->left = vol->right = (int) (pa_volume_to_user(volume)*100); + return CONTROL_OK; + } + + case AOCONTROL_SET_VOLUME: { + /* Set the playback volume of the stream */ + const ao_control_vol_t *vol = (ao_control_vol_t*) arg; + int v = vol->left; + if (vol->right > v) + v = vol->left; + + wait_for_operation(pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), pa_volume_from_user((double)v/100), NULL, NULL)); + + return CONTROL_OK; + } + + default: + /* Unknown CONTROL command */ + return CONTROL_UNKNOWN; + } +} + diff --git a/libao2/audio_out.c b/libao2/audio_out.c index d05d4d7d9c..fcc4fa9fec 100644 --- a/libao2/audio_out.c +++ b/libao2/audio_out.c @@ -25,6 +25,9 @@ extern ao_functions_t audio_out_arts; #ifdef USE_ESD extern ao_functions_t audio_out_esd; #endif +#ifdef USE_POLYP +extern ao_functions_t audio_out_polyp; +#endif #ifdef USE_JACK extern ao_functions_t audio_out_jack; #endif @@ -103,6 +106,9 @@ ao_functions_t* audio_out_drivers[] = #ifdef USE_ESD &audio_out_esd, #endif +#ifdef USE_POLYP + &audio_out_polyp, +#endif #ifdef USE_JACK &audio_out_jack, #endif