From f3b03cd77318bccf2fd0d724a3f3f6d457b4277f Mon Sep 17 00:00:00 2001 From: Sebastian Dröge Date: Tue, 10 Jun 2008 06:45:33 +0000 Subject: Add pulseaudio GStreamer element from gst-pulse. Development will continue here instead of pulseaudio SVN. Fixes bug ... Original commit message from CVS: * configure.ac: * ext/pulse/Makefile.am: * ext/pulse/plugin.c: (plugin_init): * ext/pulse/pulsemixer.c: (gst_pulsemixer_interface_supported), (gst_pulsemixer_implements_interface_init), (gst_pulsemixer_init_interfaces), (gst_pulsemixer_base_init), (gst_pulsemixer_class_init), (gst_pulsemixer_init), (gst_pulsemixer_finalize), (gst_pulsemixer_set_property), (gst_pulsemixer_get_property), (gst_pulsemixer_change_state): * ext/pulse/pulsemixer.h: * ext/pulse/pulsemixerctrl.c: (gst_pulsemixer_ctrl_context_state_cb), (gst_pulsemixer_ctrl_sink_info_cb), (gst_pulsemixer_ctrl_source_info_cb), (gst_pulsemixer_ctrl_subscribe_cb), (gst_pulsemixer_ctrl_success_cb), (gst_pulsemixer_ctrl_open), (gst_pulsemixer_ctrl_close), (gst_pulsemixer_ctrl_new), (gst_pulsemixer_ctrl_free), (gst_pulsemixer_ctrl_list_tracks), (gst_pulsemixer_ctrl_timeout_event), (restart_time_event), (gst_pulsemixer_ctrl_set_volume), (gst_pulsemixer_ctrl_get_volume), (gst_pulsemixer_ctrl_set_record), (gst_pulsemixer_ctrl_set_mute): * ext/pulse/pulsemixerctrl.h: * ext/pulse/pulsemixertrack.c: (gst_pulsemixer_track_class_init), (gst_pulsemixer_track_init), (gst_pulsemixer_track_new): * ext/pulse/pulsemixertrack.h: * ext/pulse/pulseprobe.c: (gst_pulseprobe_context_state_cb), (gst_pulseprobe_sink_info_cb), (gst_pulseprobe_source_info_cb), (gst_pulseprobe_invalidate), (gst_pulseprobe_open), (gst_pulseprobe_enumerate), (gst_pulseprobe_close), (gst_pulseprobe_new), (gst_pulseprobe_free), (gst_pulseprobe_get_properties), (gst_pulseprobe_needs_probe), (gst_pulseprobe_probe_property), (gst_pulseprobe_get_values), (gst_pulseprobe_set_server): * ext/pulse/pulseprobe.h: * ext/pulse/pulsesink.c: (gst_pulsesink_base_init), (gst_pulsesink_class_init), (gst_pulsesink_init), (gst_pulsesink_destroy_stream), (gst_pulsesink_destroy_context), (gst_pulsesink_finalize), (gst_pulsesink_dispose), (gst_pulsesink_set_property), (gst_pulsesink_get_property), (gst_pulsesink_context_state_cb), (gst_pulsesink_stream_state_cb), (gst_pulsesink_stream_request_cb), (gst_pulsesink_stream_latency_update_cb), (gst_pulsesink_open), (gst_pulsesink_close), (gst_pulsesink_prepare), (gst_pulsesink_unprepare), (gst_pulsesink_write), (gst_pulsesink_delay), (gst_pulsesink_success_cb), (gst_pulsesink_reset), (gst_pulsesink_change_title), (gst_pulsesink_event), (gst_pulsesink_get_type): * ext/pulse/pulsesink.h: * ext/pulse/pulsesrc.c: (gst_pulsesrc_interface_supported), (gst_pulsesrc_implements_interface_init), (gst_pulsesrc_init_interfaces), (gst_pulsesrc_base_init), (gst_pulsesrc_class_init), (gst_pulsesrc_init), (gst_pulsesrc_destroy_stream), (gst_pulsesrc_destroy_context), (gst_pulsesrc_finalize), (gst_pulsesrc_dispose), (gst_pulsesrc_set_property), (gst_pulsesrc_get_property), (gst_pulsesrc_context_state_cb), (gst_pulsesrc_stream_state_cb), (gst_pulsesrc_stream_request_cb), (gst_pulsesrc_open), (gst_pulsesrc_close), (gst_pulsesrc_prepare), (gst_pulsesrc_unprepare), (gst_pulsesrc_read), (gst_pulsesrc_delay), (gst_pulsesrc_change_state), (gst_pulsesrc_get_type): * ext/pulse/pulsesrc.h: * ext/pulse/pulseutil.c: (gst_pulse_fill_sample_spec), (gst_pulse_client_name), (gst_pulse_gst_to_channel_map): * ext/pulse/pulseutil.h: Add pulseaudio GStreamer element from gst-pulse. Development will continue here instead of pulseaudio SVN. Fixes bug #400679. Only changes over gst-pulse SVN are added copyright to the top of files and coding style changes. --- ext/pulse/pulsemixerctrl.c | 583 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 583 insertions(+) create mode 100644 ext/pulse/pulsemixerctrl.c (limited to 'ext/pulse/pulsemixerctrl.c') diff --git a/ext/pulse/pulsemixerctrl.c b/ext/pulse/pulsemixerctrl.c new file mode 100644 index 00000000..5dac558c --- /dev/null +++ b/ext/pulse/pulsemixerctrl.c @@ -0,0 +1,583 @@ +/* + * GStreamer pulseaudio plugin + * + * Copyright (c) 2004-2008 Lennart Poettering + * + * gst-pulse is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * gst-pulse 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with gst-pulse; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "pulsemixerctrl.h" +#include "pulsemixertrack.h" +#include "pulseutil.h" + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +static void +gst_pulsemixer_ctrl_context_state_cb (pa_context * context, void *userdata) +{ + GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata); + + /* Called from the background thread! */ + + switch (pa_context_get_state (context)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal (c->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +gst_pulsemixer_ctrl_sink_info_cb (pa_context * context, const pa_sink_info * i, + int eol, void *userdata) +{ + GstPulseMixerCtrl *c = userdata; + + /* Called from the background thread! */ + + if (c->outstandig_queries > 0) + c->outstandig_queries--; + + if (c->ignore_queries > 0 || c->time_event) { + + if (c->ignore_queries > 0) + c->ignore_queries--; + + return; + } + + if (!i && eol < 0) { + c->operation_success = 0; + pa_threaded_mainloop_signal (c->mainloop, 0); + return; + } + + if (eol) + return; + + g_free (c->name); + g_free (c->description); + c->name = g_strdup (i->name); + c->description = g_strdup (i->description); + c->index = i->index; + c->channel_map = i->channel_map; + c->volume = i->volume; + c->muted = i->mute; + c->type = GST_PULSEMIXER_SINK; + + if (c->track) { + int i = g_atomic_int_get (&c->track->flags); + + i = (i & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + g_atomic_int_set (&c->track->flags, i); + } + + c->operation_success = 1; + pa_threaded_mainloop_signal (c->mainloop, 0); +} + +static void +gst_pulsemixer_ctrl_source_info_cb (pa_context * context, + const pa_source_info * i, int eol, void *userdata) +{ + GstPulseMixerCtrl *c = userdata; + + /* Called from the background thread! */ + + if (c->outstandig_queries > 0) + c->outstandig_queries--; + + if (c->ignore_queries > 0 || c->time_event) { + + if (c->ignore_queries > 0) + c->ignore_queries--; + + return; + } + + if (!i && eol < 0) { + c->operation_success = 0; + pa_threaded_mainloop_signal (c->mainloop, 0); + return; + } + + if (eol) + return; + + g_free (c->name); + g_free (c->description); + c->name = g_strdup (i->name); + c->description = g_strdup (i->description); + c->index = i->index; + c->channel_map = i->channel_map; + c->volume = i->volume; + c->muted = i->mute; + c->type = GST_PULSEMIXER_SOURCE; + + if (c->track) { + int i = g_atomic_int_get (&c->track->flags); + + i = (i & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + g_atomic_int_set (&c->track->flags, i); + } + + c->operation_success = 1; + pa_threaded_mainloop_signal (c->mainloop, 0); +} + +static void +gst_pulsemixer_ctrl_subscribe_cb (pa_context * context, + pa_subscription_event_type_t t, uint32_t idx, void *userdata) +{ + GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata); + + pa_operation *o = NULL; + + /* Called from the background thread! */ + + if (c->index != idx) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (c->type == GST_PULSEMIXER_SINK) + o = pa_context_get_sink_info_by_index (c->context, c->index, + gst_pulsemixer_ctrl_sink_info_cb, c); + else + o = pa_context_get_source_info_by_index (c->context, c->index, + gst_pulsemixer_ctrl_source_info_cb, c); + + if (!o) { + GST_WARNING ("Failed to get sink info: %s", + pa_strerror (pa_context_errno (c->context))); + return; + } + + pa_operation_unref (o); + + c->outstandig_queries++; +} + +static void +gst_pulsemixer_ctrl_success_cb (pa_context * context, int success, + void *userdata) +{ + GstPulseMixerCtrl *c = (GstPulseMixerCtrl *) userdata; + + c->operation_success = success; + pa_threaded_mainloop_signal (c->mainloop, 0); +} + +#define CHECK_DEAD_GOTO(c, label) do { \ +if (!(c)->context || pa_context_get_state((c)->context) != PA_CONTEXT_READY) { \ + GST_WARNING("Not connected: %s", (c)->context ? pa_strerror(pa_context_errno((c)->context)) : "NULL"); \ + goto label; \ +} \ +} while(0); + +static gboolean +gst_pulsemixer_ctrl_open (GstPulseMixerCtrl * c) +{ + int e; + + gchar *name = gst_pulse_client_name (); + + pa_operation *o = NULL; + + g_assert (c); + + c->mainloop = pa_threaded_mainloop_new (); + g_assert (c->mainloop); + + e = pa_threaded_mainloop_start (c->mainloop); + g_assert (e == 0); + + pa_threaded_mainloop_lock (c->mainloop); + + if (!(c->context = + pa_context_new (pa_threaded_mainloop_get_api (c->mainloop), name))) { + GST_WARNING ("Failed to create context"); + goto unlock_and_fail; + } + + pa_context_set_state_callback (c->context, + gst_pulsemixer_ctrl_context_state_cb, c); + pa_context_set_subscribe_callback (c->context, + gst_pulsemixer_ctrl_subscribe_cb, c); + + if (pa_context_connect (c->context, c->server, 0, NULL) < 0) { + GST_WARNING ("Failed to connect context: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (c->mainloop); + + if (pa_context_get_state (c->context) != PA_CONTEXT_READY) { + GST_WARNING ("Failed to connect context: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + /* Subscribe to events */ + + if (!(o = + pa_context_subscribe (c->context, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, + gst_pulsemixer_ctrl_success_cb, c))) { + GST_WARNING ("Failed to subscribe to events: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + c->operation_success = 0; + while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait (c->mainloop); + CHECK_DEAD_GOTO (c, unlock_and_fail); + } + + if (!c->operation_success) { + GST_WARNING ("Failed to subscribe to events: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + /* Get sink info */ + + if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SINK) { + if (!(o = + pa_context_get_sink_info_by_name (c->context, c->device, + gst_pulsemixer_ctrl_sink_info_cb, c))) { + GST_WARNING ("Failed to get sink info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + c->operation_success = 0; + while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait (c->mainloop); + CHECK_DEAD_GOTO (c, unlock_and_fail); + } + + pa_operation_unref (o); + o = NULL; + + if (!c->operation_success && (c->type == GST_PULSEMIXER_SINK + || pa_context_errno (c->context) != PA_ERR_NOENTITY)) { + GST_WARNING ("Failed to get sink info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + } + + if (c->type == GST_PULSEMIXER_UNKNOWN || c->type == GST_PULSEMIXER_SOURCE) { + if (!(o = + pa_context_get_source_info_by_name (c->context, c->device, + gst_pulsemixer_ctrl_source_info_cb, c))) { + GST_WARNING ("Failed to get source info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + c->operation_success = 0; + while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait (c->mainloop); + CHECK_DEAD_GOTO (c, unlock_and_fail); + } + + pa_operation_unref (o); + o = NULL; + + if (!c->operation_success) { + GST_WARNING ("Failed to get source info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + } + + g_assert (c->type != GST_PULSEMIXER_UNKNOWN); + + c->track = gst_pulsemixer_track_new (c); + c->tracklist = g_list_append (c->tracklist, c->track); + + pa_threaded_mainloop_unlock (c->mainloop); + g_free (name); + + return TRUE; + +unlock_and_fail: + + if (o) + pa_operation_unref (o); + + if (c->mainloop) + pa_threaded_mainloop_unlock (c->mainloop); + + g_free (name); + + return FALSE; +} + +static void +gst_pulsemixer_ctrl_close (GstPulseMixerCtrl * c) +{ + g_assert (c); + + if (c->mainloop) + pa_threaded_mainloop_stop (c->mainloop); + + if (c->context) { + pa_context_disconnect (c->context); + pa_context_unref (c->context); + c->context = NULL; + } + + if (c->mainloop) { + pa_threaded_mainloop_free (c->mainloop); + c->mainloop = NULL; + c->time_event = NULL; + } + + if (c->tracklist) { + g_list_free (c->tracklist); + c->tracklist = NULL; + } + + if (c->track) { + GST_PULSEMIXER_TRACK (c->track)->control = NULL; + g_object_unref (c->track); + c->track = NULL; + } +} + +GstPulseMixerCtrl * +gst_pulsemixer_ctrl_new (const gchar * server, const gchar * device, + GstPulseMixerType type) +{ + GstPulseMixerCtrl *c = NULL; + + c = g_new (GstPulseMixerCtrl, 1); + c->tracklist = NULL; + c->server = g_strdup (server); + c->device = g_strdup (device); + c->mainloop = NULL; + c->context = NULL; + c->track = NULL; + c->ignore_queries = c->outstandig_queries = 0; + + pa_cvolume_mute (&c->volume, PA_CHANNELS_MAX); + pa_channel_map_init (&c->channel_map); + c->muted = 0; + c->index = PA_INVALID_INDEX; + c->type = type; + c->name = NULL; + c->description = NULL; + + c->time_event = NULL; + c->update_volume = c->update_mute = FALSE; + + if (!(gst_pulsemixer_ctrl_open (c))) { + gst_pulsemixer_ctrl_free (c); + return NULL; + } + + return c; +} + +void +gst_pulsemixer_ctrl_free (GstPulseMixerCtrl * c) +{ + g_assert (c); + + gst_pulsemixer_ctrl_close (c); + + g_free (c->server); + g_free (c->device); + g_free (c->name); + g_free (c->description); + g_free (c); +} + +const GList * +gst_pulsemixer_ctrl_list_tracks (GstPulseMixerCtrl * c) +{ + g_assert (c); + + return c->tracklist; +} + +static void +gst_pulsemixer_ctrl_timeout_event (pa_mainloop_api * a, pa_time_event * e, + const struct timeval *tv, void *userdata) +{ + pa_operation *o; + + GstPulseMixerCtrl *c = GST_PULSEMIXER_CTRL (userdata); + + if (c->update_volume) { + if (c->type == GST_PULSEMIXER_SINK) + o = pa_context_set_sink_volume_by_index (c->context, c->index, &c->volume, + NULL, NULL); + else + o = pa_context_set_source_volume_by_index (c->context, c->index, + &c->volume, NULL, NULL); + + if (!o) + GST_WARNING ("Failed to set device volume: %s", + pa_strerror (pa_context_errno (c->context))); + else + pa_operation_unref (o); + + c->update_volume = FALSE; + } + + if (c->update_mute) { + if (c->type == GST_PULSEMIXER_SINK) + o = pa_context_set_sink_mute_by_index (c->context, c->index, !!c->muted, + NULL, NULL); + else + o = pa_context_set_source_mute_by_index (c->context, c->index, !!c->muted, + NULL, NULL); + + if (!o) + GST_WARNING ("Failed to set device mute: %s", + pa_strerror (pa_context_errno (c->context))); + else + pa_operation_unref (o); + + c->update_mute = FALSE; + } + + /* Make sure that all outstanding queries are being ignored */ + c->ignore_queries = c->outstandig_queries; + + g_assert (e == c->time_event); + a->time_free (e); + c->time_event = NULL; +} + +#define UPDATE_DELAY 50000 + +static void +restart_time_event (GstPulseMixerCtrl * c) +{ + g_assert (c); + + if (c->time_event) + return; + + /* Updating the volume too often will cause a lot of traffic + * when accessing a networked server. Therefore we make sure + * to update the volume only once every 50ms */ + struct timeval tv; + + pa_mainloop_api *api = pa_threaded_mainloop_get_api (c->mainloop); + + c->time_event = + api->time_new (api, pa_timeval_add (pa_gettimeofday (&tv), UPDATE_DELAY), + gst_pulsemixer_ctrl_timeout_event, c); +} + +void +gst_pulsemixer_ctrl_set_volume (GstPulseMixerCtrl * c, GstMixerTrack * track, + gint * volumes) +{ + pa_cvolume v; + + int i; + + g_assert (c); + g_assert (track == c->track); + + pa_threaded_mainloop_lock (c->mainloop); + + for (i = 0; i < c->channel_map.channels; i++) + v.values[i] = (pa_volume_t) volumes[i]; + + v.channels = c->channel_map.channels; + + c->volume = v; + c->update_volume = TRUE; + + restart_time_event (c); + + pa_threaded_mainloop_unlock (c->mainloop); +} + +void +gst_pulsemixer_ctrl_get_volume (GstPulseMixerCtrl * c, GstMixerTrack * track, + gint * volumes) +{ + int i; + + g_assert (c); + g_assert (track == c->track); + + pa_threaded_mainloop_lock (c->mainloop); + + for (i = 0; i < c->channel_map.channels; i++) + volumes[i] = c->volume.values[i]; + + pa_threaded_mainloop_unlock (c->mainloop); +} + +void +gst_pulsemixer_ctrl_set_record (GstPulseMixerCtrl * c, GstMixerTrack * track, + gboolean record) +{ + g_assert (c); + g_assert (track == c->track); +} + +void +gst_pulsemixer_ctrl_set_mute (GstPulseMixerCtrl * c, GstMixerTrack * track, + gboolean mute) +{ + g_assert (c); + g_assert (track == c->track); + + pa_threaded_mainloop_lock (c->mainloop); + + c->muted = !!mute; + c->update_mute = TRUE; + + if (c->track) { + int i = g_atomic_int_get (&c->track->flags); + + i = (i & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + g_atomic_int_set (&c->track->flags, i); + } + + restart_time_event (c); + + pa_threaded_mainloop_unlock (c->mainloop); +} -- cgit