Index: configure.ac =================================================================== --- configure.ac (revision 25086) +++ configure.ac (working copy) @@ -4646,6 +4646,24 @@ fi]) dnl +dnl Pulseaudio module +dnl +AC_ARG_ENABLE(pulse, + [ --enable-pulse Pulseaudio support (default disabled)], + [ + if test "x$enable_pulse" != "xno"; then + PKG_CHECK_MODULES([PULSEAUDIO], [libpulse], [ + VLC_ADD_PLUGINS([pulse]) + VLC_ADD_CFLAGS([pulse],[`pkg-config --cflags libpulse`]) + VLC_ADD_LIBS([pulse],[`pkg-config --libs libpulse`]) + ], [ + AS_IF([test "${enable_pulse}" = "yes"],[ + AC_MSG_WARN( libpulse not found) + ]) + ]) + fi]) + +dnl dnl Portaudio module dnl AC_ARG_ENABLE(portaudio, Index: modules/audio_output/Modules.am =================================================================== --- modules/audio_output/Modules.am (revision 25086) +++ modules/audio_output/Modules.am (working copy) @@ -10,3 +10,4 @@ SOURCES_portaudio = portaudio.c SOURCES_auhal = auhal.c SOURCES_jack = jack.c +SOURCES_pulse = pulse.c Index: modules/audio_output/pulse.c =================================================================== --- modules/audio_output/pulse.c (revision 0) +++ modules/audio_output/pulse.c (revision 0) @@ -0,0 +1,455 @@ +/***************************************************************************** + * pulse.c : Pulseaudio output plugin for vlc + ***************************************************************************** + * Copyright (C) 2000-2002 the VideoLAN team + * + * Authors: Martin Hamrle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include + +/***************************************************************************** + * aout_sys_t: Pulseaudio output method descriptor + ***************************************************************************** + * This structure is part of the audio output thread descriptor. + * It describes the specific properties of an audio device. + *****************************************************************************/ +struct aout_sys_t +{ + /** PulseAudio playback stream object */ + struct pa_stream *stream; + + /** PulseAudio connection context */ + struct pa_context *context; + + /** Main event loop object */ + struct pa_threaded_mainloop *mainloop; + + int started; + size_t buffer_size; + mtime_t start_date; +}; + +#define PULSE_CLIENT_NAME "vlc" + +#define CHECK_DEAD_GOTO(label) do { \ +if (!p_sys->context || pa_context_get_state(p_sys->context) != PA_CONTEXT_READY || \ + !p_sys->stream || pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { \ + msg_Err(p_aout, "Connection died: %s\n", p_sys->context ? pa_strerror(pa_context_errno(p_sys->context)) : "NULL"); \ + goto label; \ + } \ +} while(0); +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static int Open ( vlc_object_t * ); +static void Close ( vlc_object_t * ); +static void Play ( aout_instance_t * ); + +static void context_state_cb(pa_context *c, void *userdata); +static void stream_state_cb(pa_stream *s, void * userdata); +static void stream_request_cb(pa_stream *s, size_t length, void *userdata); +static void stream_latency_update_cb(pa_stream *s, void *userdata); +static void success_cb(pa_stream *s, int success, void *userdata); +static void uninit(aout_instance_t *p_aout); +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +vlc_module_begin(); + set_shortname( "pulse" ); + set_description( _("Pulseaudio audio output") ); + set_capability( "audio output", 40 ); + set_category( CAT_AUDIO ); + set_subcategory( SUBCAT_AUDIO_AOUT ); + add_shortcut( "pulse" ); + set_callbacks( Open, Close ); +vlc_module_end(); + +/***************************************************************************** + * Open: open the audio device + *****************************************************************************/ +static int Open ( vlc_object_t *p_this ) +{ + aout_instance_t *p_aout = (aout_instance_t *)p_this; + struct aout_sys_t * p_sys; + struct pa_sample_spec ss; + const struct pa_buffer_attr *buffer_attr; + struct pa_buffer_attr a; + struct pa_channel_map map; + + /* Allocate structures */ + p_aout->output.p_sys = p_sys = malloc( sizeof( aout_sys_t ) ); + if( p_sys == NULL ) + { + msg_Err( p_aout, "out of memory" ); + return VLC_ENOMEM; + } + p_sys->started = 0; + p_sys->stream = NULL; + p_sys->mainloop = NULL; + p_sys->context = NULL; + + msg_Dbg(p_aout, "Pulse start initialization\n"); + + ss.rate = p_aout->output.output.i_rate; + ss.channels = 2; + + ss.format = PA_SAMPLE_S16LE; + p_aout->output.output.i_physical_channels = + AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; + p_aout->output.output.i_format = AOUT_FMT_S16_NE; + + if (!pa_sample_spec_valid(&ss)) { + msg_Err(p_aout,"Invalid sample spec\n"); + goto fail; + } + + a.maxlength = pa_bytes_per_second(&ss)/4/pa_frame_size(&ss); + a.tlength = a.maxlength*9/10; + a.prebuf = a.tlength/2; + a.minreq = a.tlength/10; + + a.maxlength *= pa_frame_size(&ss); + a.tlength *= pa_frame_size(&ss); + a.prebuf *= pa_frame_size(&ss); + a.minreq *= pa_frame_size(&ss); + + p_sys->buffer_size = a.minreq; + + pa_channel_map_init_stereo(&map); + + + if (!(p_sys->mainloop = pa_threaded_mainloop_new())) { + msg_Err(p_aout, "Failed to allocate main loop\n"); + goto fail; + } + + if (!(p_sys->context = pa_context_new(pa_threaded_mainloop_get_api(p_sys->mainloop), PULSE_CLIENT_NAME))) { + msg_Err(p_aout, "Failed to allocate context\n"); + goto fail; + } + + pa_context_set_state_callback(p_sys->context, context_state_cb, p_aout); + + msg_Dbg(p_aout, "Pulse before context connect\n"); + + if (pa_context_connect(p_sys->context, NULL, 0, NULL) < 0) { + msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + goto fail; + } + + msg_Dbg(p_aout, "Pulse after context connect\n"); + + pa_threaded_mainloop_lock(p_sys->mainloop); + + if (pa_threaded_mainloop_start(p_sys->mainloop) < 0) { + msg_Err(p_aout, "Failed to start main loop\n"); + goto unlock_and_fail; + } + + msg_Dbg(p_aout, "Pulse mainloop started\n"); + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(p_sys->mainloop); + + if (pa_context_get_state(p_sys->context) != PA_CONTEXT_READY) { + msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + goto unlock_and_fail; + } + + if (!(p_sys->stream = pa_stream_new(p_sys->context, "audio stream", &ss, &map))) { + msg_Err(p_aout, "Failed to create stream: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + goto unlock_and_fail; + } + + msg_Dbg(p_aout, "Pulse after new stream\n"); + + pa_stream_set_state_callback(p_sys->stream, stream_state_cb, p_aout); + pa_stream_set_write_callback(p_sys->stream, stream_request_cb, p_aout); + pa_stream_set_latency_update_callback(p_sys->stream, stream_latency_update_cb, p_aout); + + if (pa_stream_connect_playback(p_sys->stream, NULL, &a, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) { + msg_Err(p_aout, "Failed to connect stream: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + goto unlock_and_fail; + } + + msg_Dbg(p_aout, "Pulse stream connect\n"); + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(p_sys->mainloop); + + msg_Dbg(p_aout, "Pulse stream connected\n"); + + if (pa_stream_get_state(p_sys->stream) != PA_STREAM_READY) { + msg_Err(p_aout, "Failed to connect to server: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + goto unlock_and_fail; + } + + + msg_Dbg(p_aout, "Pulse after stream get status\n"); + + pa_threaded_mainloop_unlock(p_sys->mainloop); + + buffer_attr = pa_stream_get_buffer_attr(p_sys->stream); + p_aout->output.i_nb_samples = buffer_attr->minreq / pa_frame_size(&ss); + p_aout->output.pf_play = Play; + msg_Dbg(p_aout, "Pulse initialized successfully\n"); + { + char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + msg_Dbg(p_aout, "Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n", buffer_attr->maxlength, buffer_attr->tlength, buffer_attr->prebuf, buffer_attr->minreq); + msg_Dbg(p_aout, "Using sample spec '%s', channel map '%s'.\n", + pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(p_sys->stream)), + pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(p_sys->stream))); + + msg_Dbg(p_aout, "Connected to device %s (%u, %ssuspended).\n", + pa_stream_get_device_name(p_sys->stream), + pa_stream_get_device_index(p_sys->stream), + pa_stream_is_suspended(p_sys->stream) ? "" : "not "); + } + + return VLC_SUCCESS; + +unlock_and_fail: + msg_Dbg(p_aout, "Pulse initialization unlock and fail\n"); + + if (p_sys->mainloop) + pa_threaded_mainloop_unlock(p_sys->mainloop); +fail: + msg_Err(p_aout, "Pulse initialization failed\n"); + uninit(p_aout); + return VLC_EGENERIC; +} + +/***************************************************************************** + * Play: play a sound samples buffer + *****************************************************************************/ +static void Play( aout_instance_t * p_aout ) +{ + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + + pa_operation *o; + + if(!p_sys->started){ + msg_Dbg(p_aout, "Pulse stream started\n"); + p_sys->start_date = + aout_FifoFirstDate( p_aout, &p_aout->output.fifo ); + p_sys->started = 1; + + + pa_threaded_mainloop_lock(p_sys->mainloop); + if((o = pa_stream_flush(p_sys->stream, success_cb, p_aout))){ + pa_operation_unref(o); + } + pa_threaded_mainloop_unlock(p_sys->mainloop); + + pa_threaded_mainloop_signal(p_sys->mainloop, 0); + } +} + +/***************************************************************************** + * Close: close the audio device + *****************************************************************************/ +static void Close ( vlc_object_t *p_this ) +{ + aout_instance_t *p_aout = (aout_instance_t *)p_this; + struct aout_sys_t * p_sys = p_aout->output.p_sys; + + msg_Dbg(p_aout, "Pulse Close\n"); + + if(p_sys->stream){ + pa_operation *o; + pa_threaded_mainloop_lock(p_sys->mainloop); + pa_stream_set_write_callback(p_sys->stream, NULL, NULL); + + if((o = pa_stream_drain(p_sys->stream, success_cb, p_aout))){ + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO(fail); + pa_threaded_mainloop_wait(p_sys->mainloop); + } + + fail: + + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p_sys->mainloop); + } + uninit(p_aout); +} + +static void uninit(aout_instance_t *p_aout){ + struct aout_sys_t * p_sys = p_aout->output.p_sys; + + if (p_sys->mainloop) + pa_threaded_mainloop_stop(p_sys->mainloop); + + if (p_sys->stream) { + pa_stream_disconnect(p_sys->stream); + pa_stream_unref(p_sys->stream); + p_sys->stream = NULL; + } + + if (p_sys->context) { + pa_context_disconnect(p_sys->context); + pa_context_unref(p_sys->context); + p_sys->context = NULL; + } + + if (p_sys->mainloop) { + pa_threaded_mainloop_free(p_sys->mainloop); + p_sys->mainloop = NULL; + } + + free(p_sys); + p_aout->output.p_sys = NULL; +} + +static void context_state_cb(pa_context *c, void *userdata) { + aout_instance_t *p_aout = (aout_instance_t *)userdata; + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + + assert(c); + + msg_Dbg(p_aout, "Pulse context state changed\n"); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + msg_Dbg(p_aout, "Pulse context state changed signal\n"); + pa_threaded_mainloop_signal(p_sys->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + msg_Dbg(p_aout, "Pulse context state changed no signal\n"); + break; + } +} + +static void stream_state_cb(pa_stream *s, void * userdata) { + aout_instance_t *p_aout = (aout_instance_t *)userdata; + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + + assert(s); + + msg_Dbg(p_aout, "Pulse stream state changed\n"); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(p_sys->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + aout_instance_t *p_aout = (aout_instance_t *)userdata; + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + mtime_t next_date; + + assert(s); + assert(p_sys); + + size_t buffer_size = p_sys->buffer_size; + + msg_Dbg(p_aout, "Pulse stream request %d\n", length); + + do{ + aout_buffer_t * p_buffer = NULL; + if(p_sys->started){ + pa_usec_t latency; + int negative; + if(pa_stream_get_latency(p_sys->stream, &latency, &negative)<0){ + if (pa_context_errno(p_sys->context) != PA_ERR_NODATA) { + msg_Err(p_aout, "pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(p_sys->context))); + } + latency = 0; + + } + msg_Dbg(p_aout, "Pulse stream request latency="I64Fd"\n", latency); + next_date = mdate() + latency; + + + if(p_sys->start_date < next_date + AOUT_PTS_TOLERANCE ){ + /* + vlc_mutex_lock( &p_aout->output_fifo_lock ); + p_buffer = aout_FifoPop( p_aout, &p_aout->output.fifo ); + vlc_mutex_unlock( &p_aout->output_fifo_lock ); + */ + p_buffer = aout_OutputNextBuffer( p_aout, next_date, 0); + } + } + + if ( p_buffer != NULL ) + { + msg_Dbg(p_aout, "Pulse stream request write buffer %d\n", p_buffer->i_nb_bytes); + pa_stream_write(p_sys->stream, p_buffer->p_buffer, p_buffer->i_nb_bytes, NULL, 0, PA_SEEK_RELATIVE); + length -= p_buffer->i_nb_bytes; + aout_BufferFree( p_buffer ); + } + else + { + msg_Dbg(p_aout, "Pulse stream request write zeroes\n"); + void *data = pa_xmalloc(buffer_size); + bzero(data, buffer_size); + pa_stream_write(p_sys->stream, data, buffer_size, pa_xfree, 0, PA_SEEK_RELATIVE); + length -= buffer_size; + } + }while(length > buffer_size); + + pa_threaded_mainloop_signal(p_sys->mainloop, 0); +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + aout_instance_t *p_aout = (aout_instance_t *)userdata; + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + + assert(s); + + msg_Dbg(p_aout, "Pulse stream latency update\n"); + + pa_threaded_mainloop_signal(p_sys->mainloop, 0); +} + +static void success_cb(pa_stream *s, int success, void *userdata) { + aout_instance_t *p_aout = (aout_instance_t *)userdata; + struct aout_sys_t * p_sys = (struct aout_sys_t *) p_aout->output.p_sys; + assert(s); + + pa_threaded_mainloop_signal(p_sys->mainloop, 0); +} +