From f3b03cd77318bccf2fd0d724a3f3f6d457b4277f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 10 Jun 2008 06:45:33 +0000 Subject: [PATCH] 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. --- ChangeLog | 72 +++++ configure.ac | 7 + ext/pulse/Makefile.am | 25 ++ ext/pulse/plugin.c | 56 ++++ ext/pulse/pulsemixer.c | 277 ++++++++++++++++ ext/pulse/pulsemixer.h | 68 ++++ ext/pulse/pulsemixerctrl.c | 583 ++++++++++++++++++++++++++++++++++ ext/pulse/pulsemixerctrl.h | 154 +++++++++ ext/pulse/pulsemixertrack.c | 68 ++++ ext/pulse/pulsemixertrack.h | 60 ++++ ext/pulse/pulseprobe.c | 370 ++++++++++++++++++++++ ext/pulse/pulseprobe.h | 121 +++++++ ext/pulse/pulsesink.c | 746 ++++++++++++++++++++++++++++++++++++++++++++ ext/pulse/pulsesink.h | 72 +++++ ext/pulse/pulsesrc.c | 703 +++++++++++++++++++++++++++++++++++++++++ ext/pulse/pulsesrc.h | 77 +++++ ext/pulse/pulseutil.c | 138 ++++++++ ext/pulse/pulseutil.h | 37 +++ 18 files changed, 3634 insertions(+) create mode 100644 ext/pulse/Makefile.am create mode 100644 ext/pulse/plugin.c create mode 100644 ext/pulse/pulsemixer.c create mode 100644 ext/pulse/pulsemixer.h create mode 100644 ext/pulse/pulsemixerctrl.c create mode 100644 ext/pulse/pulsemixerctrl.h create mode 100644 ext/pulse/pulsemixertrack.c create mode 100644 ext/pulse/pulsemixertrack.h create mode 100644 ext/pulse/pulseprobe.c create mode 100644 ext/pulse/pulseprobe.h create mode 100644 ext/pulse/pulsesink.c create mode 100644 ext/pulse/pulsesink.h create mode 100644 ext/pulse/pulsesrc.c create mode 100644 ext/pulse/pulsesrc.h create mode 100644 ext/pulse/pulseutil.c create mode 100644 ext/pulse/pulseutil.h diff --git a/ChangeLog b/ChangeLog index dbe4343..7273299 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,75 @@ +2008-06-10 Sebastian Dröge + + * 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. + 2008-06-09 Tim-Philipp Müller Patch by: Benjamin Kampmann diff --git a/configure.ac b/configure.ac index dc4f065..5d6fc90 100644 --- a/configure.ac +++ b/configure.ac @@ -776,6 +776,12 @@ AG_GST_CHECK_FEATURE(LIBPNG, [Portable Network Graphics library], png, [ AG_GST_PKG_CHECK_MODULES(LIBPNG, libpng12) ]) +dnl *** pulseaudio *** +translit(dnm, m, l) AM_CONDITIONAL(USE_PULSE, true) +AG_GST_CHECK_FEATURE(PULSE, [pulseaudio plug-in], pulseaudio, [ + AG_GST_PKG_CHECK_MODULES(PULSE, libpulse >= 0.9.8) +]) + dnl *** dv1394 *** translit(dnm, m, l) AM_CONDITIONAL(USE_DV1394, true) AG_GST_CHECK_FEATURE(DV1394, [raw1394 and avc1394 library], 1394, [ @@ -1073,6 +1079,7 @@ ext/hal/Makefile ext/ladspa/Makefile ext/libcaca/Makefile ext/libpng/Makefile +ext/pulse/Makefile ext/raw1394/Makefile ext/shout2/Makefile ext/soup/Makefile diff --git a/ext/pulse/Makefile.am b/ext/pulse/Makefile.am new file mode 100644 index 0000000..b16871e --- /dev/null +++ b/ext/pulse/Makefile.am @@ -0,0 +1,25 @@ +plugin_LTLIBRARIES = libgstpulse.la + +libgstpulse_la_SOURCES = \ + plugin.c \ + pulsemixer.c \ + pulsemixerctrl.c \ + pulsemixertrack.c \ + pulseprobe.c \ + pulsesink.c \ + pulsesrc.c \ + pulseutil.c + +libgstpulse_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(PULSE_CFLAGS) +libgstpulse_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) -lgstinterfaces-$(GST_MAJORMINOR) $(PULSE_LIBS) +libgstpulse_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = \ + pulsemixerctrl.h \ + pulsemixer.h \ + pulsemixertrack.h \ + pulseprobe.h \ + pulsesink.h \ + pulsesrc.h \ + pulseutil.h + diff --git a/ext/pulse/plugin.c b/ext/pulse/plugin.c new file mode 100644 index 0000000..9cfb469 --- /dev/null +++ b/ext/pulse/plugin.c @@ -0,0 +1,56 @@ +/* + * 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 "pulsesink.h" +#include "pulsesrc.h" +#include "pulsemixer.h" + +GST_DEBUG_CATEGORY (pulse_debug); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + + if (!gst_element_register (plugin, "pulsesink", GST_RANK_PRIMARY, + GST_TYPE_PULSESINK)) + return FALSE; + + if (!gst_element_register (plugin, "pulsesrc", GST_RANK_PRIMARY, + GST_TYPE_PULSESRC)) + return FALSE; + + if (!gst_element_register (plugin, "pulsemixer", GST_RANK_NONE, + GST_TYPE_PULSEMIXER)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (pulse_debug, "pulse", 0, "PulseAudio elements"); + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "pulseaudio", + "PulseAudio Elements Plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/pulse/pulsemixer.c b/ext/pulse/pulsemixer.c new file mode 100644 index 0000000..e2957aa --- /dev/null +++ b/ext/pulse/pulsemixer.c @@ -0,0 +1,277 @@ +/* + * 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 + +#include "pulsemixer.h" + +enum +{ + PROP_SERVER = 1, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +static void gst_pulsemixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pulsemixer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_pulsemixer_finalize (GObject * object); + +static GstStateChangeReturn gst_pulsemixer_change_state (GstElement * element, + GstStateChange transition); + +static void gst_pulsemixer_init_interfaces (GType type); + +GST_IMPLEMENT_PULSEMIXER_CTRL_METHODS (GstPulseMixer, gst_pulsemixer); +GST_IMPLEMENT_PULSEPROBE_METHODS (GstPulseMixer, gst_pulsemixer); +GST_BOILERPLATE_FULL (GstPulseMixer, gst_pulsemixer, GstElement, + GST_TYPE_ELEMENT, gst_pulsemixer_init_interfaces); + +static gboolean +gst_pulsemixer_interface_supported (GstImplementsInterface + * iface, GType interface_type) +{ + GstPulseMixer *this = GST_PULSEMIXER (iface); + + if (interface_type == GST_TYPE_MIXER && this->mixer) + return TRUE; + + if (interface_type == GST_TYPE_PROPERTY_PROBE && this->probe) + return TRUE; + + return FALSE; +} + +static void +gst_pulsemixer_implements_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_pulsemixer_interface_supported; +} + +static void +gst_pulsemixer_init_interfaces (GType type) +{ + static const GInterfaceInfo implements_iface_info = { + (GInterfaceInitFunc) gst_pulsemixer_implements_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo mixer_iface_info = { + (GInterfaceInitFunc) gst_pulsemixer_mixer_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo probe_iface_info = { + (GInterfaceInitFunc) gst_pulsemixer_property_probe_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &implements_iface_info); + g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info); + g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, + &probe_iface_info); +} + +static void +gst_pulsemixer_base_init (gpointer g_class) +{ + + static const GstElementDetails details = + GST_ELEMENT_DETAILS ("PulseAudio Mixer", + "Generic/Audio", + "Control sound input and output levels for PulseAudio", + "Lennart Poettering"); + + gst_element_class_set_details (GST_ELEMENT_CLASS (g_class), &details); +} + +static void +gst_pulsemixer_class_init (GstPulseMixerClass * g_class) +{ + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_pulsemixer_change_state); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulsemixer_finalize); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_pulsemixer_get_property); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_pulsemixer_set_property); + + g_object_class_install_property (gobject_class, + PROP_SERVER, + g_param_spec_string ("server", "Server", + "The PulseAudio server to connect to", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_DEVICE, + g_param_spec_string ("device", "Sink/Source", + "The PulseAudio sink or source to control", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", NULL, G_PARAM_READABLE)); +} + +static void +gst_pulsemixer_init (GstPulseMixer * this, GstPulseMixerClass * g_class) +{ + this->mixer = NULL; + this->server = NULL; + this->device = NULL; + + this->probe = + gst_pulseprobe_new (G_OBJECT_GET_CLASS (this), PROP_DEVICE, this->device, + TRUE, TRUE); +} + +static void +gst_pulsemixer_finalize (GObject * object) +{ + GstPulseMixer *this = GST_PULSEMIXER (object); + + g_free (this->server); + g_free (this->device); + + if (this->mixer) { + gst_pulsemixer_ctrl_free (this->mixer); + this->mixer = NULL; + } + + if (this->probe) { + gst_pulseprobe_free (this->probe); + this->probe = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_pulsemixer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + + GstPulseMixer *this = GST_PULSEMIXER (object); + + switch (prop_id) { + case PROP_SERVER: + g_free (this->server); + this->server = g_value_dup_string (value); + break; + + case PROP_DEVICE: + g_free (this->device); + this->device = g_value_dup_string (value); + + if (this->probe) + gst_pulseprobe_set_server (this->probe, this->device); + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pulsemixer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + + GstPulseMixer *this = GST_PULSEMIXER (object); + + switch (prop_id) { + + case PROP_SERVER: + g_value_set_string (value, this->server); + break; + + case PROP_DEVICE: + g_value_set_string (value, this->device); + break; + + case PROP_DEVICE_NAME: + + if (this->mixer) { + char *t = g_strdup_printf ("%s: %s", + this->mixer->type == GST_PULSEMIXER_SINK ? "Playback" : "Capture", + this->mixer->description); + + g_value_set_string (value, t); + g_free (t); + } else + g_value_set_string (value, NULL); + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_pulsemixer_change_state (GstElement * element, GstStateChange transition) +{ + GstPulseMixer *this = GST_PULSEMIXER (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + + if (!this->mixer) + this->mixer = + gst_pulsemixer_ctrl_new (this->server, this->device, + GST_PULSEMIXER_UNKNOWN); + + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + + if (this->mixer) { + gst_pulsemixer_ctrl_free (this->mixer); + this->mixer = NULL; + } + + break; + + default: + ; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + return GST_STATE_CHANGE_SUCCESS; +} diff --git a/ext/pulse/pulsemixer.h b/ext/pulse/pulsemixer.h new file mode 100644 index 0000000..7ba3d2f --- /dev/null +++ b/ext/pulse/pulsemixer.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef __GST_PULSEMIXER_H__ +#define __GST_PULSEMIXER_H__ + +#include + +#include +#include + +#include "pulsemixerctrl.h" +#include "pulseprobe.h" + +G_BEGIN_DECLS + +#define GST_TYPE_PULSEMIXER \ + (gst_pulsemixer_get_type()) +#define GST_PULSEMIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSEMIXER,GstPulseMixer)) +#define GST_PULSEMIXER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSEMIXER,GstPulseMixerClass)) +#define GST_IS_PULSEMIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSEMIXER)) +#define GST_IS_PULSEMIXER_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSEMIXER)) + +typedef struct _GstPulseMixer GstPulseMixer; +typedef struct _GstPulseMixerClass GstPulseMixerClass; + +struct _GstPulseMixer +{ + GstElement parent; + + gchar *server, *device; + + GstPulseMixerCtrl *mixer; + GstPulseProbe *probe; +}; + +struct _GstPulseMixerClass +{ + GstElementClass parent_class; +}; + +GType gst_pulsemixer_get_type (void); + +G_END_DECLS + +#endif /* __GST_PULSEMIXER_H__ */ diff --git a/ext/pulse/pulsemixerctrl.c b/ext/pulse/pulsemixerctrl.c new file mode 100644 index 0000000..5dac558 --- /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); +} diff --git a/ext/pulse/pulsemixerctrl.h b/ext/pulse/pulsemixerctrl.h new file mode 100644 index 0000000..360711a --- /dev/null +++ b/ext/pulse/pulsemixerctrl.h @@ -0,0 +1,154 @@ +/* + * 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. + */ + +#ifndef __GST_PULSEMIXERCTRL_H__ +#define __GST_PULSEMIXERCTRL_H__ + +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define GST_PULSEMIXER_CTRL(obj) ((GstPulseMixerCtrl*)(obj)) +typedef struct _GstPulseMixerCtrl GstPulseMixerCtrl; + +typedef enum +{ + GST_PULSEMIXER_UNKNOWN, + GST_PULSEMIXER_SINK, + GST_PULSEMIXER_SOURCE +} GstPulseMixerType; + +struct _GstPulseMixerCtrl +{ + GList *tracklist; + + gchar *server, *device; + + pa_threaded_mainloop *mainloop; + pa_context *context; + + gchar *name, *description; + pa_channel_map channel_map; + pa_cvolume volume; + int muted; + guint32 index; + GstPulseMixerType type; + int operation_success; + + GstMixerTrack *track; + + pa_time_event *time_event; + + int outstandig_queries; + int ignore_queries; + + gboolean update_volume, update_mute; +}; + +GstPulseMixerCtrl *gst_pulsemixer_ctrl_new (const gchar * server, + const gchar * device, GstPulseMixerType type); +void gst_pulsemixer_ctrl_free (GstPulseMixerCtrl * mixer); + +const GList *gst_pulsemixer_ctrl_list_tracks (GstPulseMixerCtrl * mixer); + +void gst_pulsemixer_ctrl_set_volume (GstPulseMixerCtrl * mixer, + GstMixerTrack * track, gint * volumes); +void gst_pulsemixer_ctrl_get_volume (GstPulseMixerCtrl * mixer, + GstMixerTrack * track, gint * volumes); +void gst_pulsemixer_ctrl_set_mute (GstPulseMixerCtrl * mixer, + GstMixerTrack * track, gboolean mute); +void gst_pulsemixer_ctrl_set_record (GstPulseMixerCtrl * mixer, + GstMixerTrack * track, gboolean record); + +#define GST_IMPLEMENT_PULSEMIXER_CTRL_METHODS(Type, interface_as_function) \ +static const GList* \ +interface_as_function ## _list_tracks (GstMixer * mixer) \ +{ \ + Type *this = (Type*) mixer; \ + \ + g_return_val_if_fail (this != NULL, NULL); \ + g_return_val_if_fail (this->mixer != NULL, NULL); \ + \ + return gst_pulsemixer_ctrl_list_tracks (this->mixer); \ +} \ +static void \ +interface_as_function ## _set_volume (GstMixer * mixer, GstMixerTrack * track, \ + gint * volumes) \ +{ \ + Type *this = (Type*) mixer; \ + \ + g_return_if_fail (this != NULL); \ + g_return_if_fail (this->mixer != NULL); \ + \ + gst_pulsemixer_ctrl_set_volume (this->mixer, track, volumes); \ +} \ +static void \ +interface_as_function ## _get_volume (GstMixer * mixer, GstMixerTrack * track, \ + gint * volumes) \ +{ \ + Type *this = (Type*) mixer; \ + \ + g_return_if_fail (this != NULL); \ + g_return_if_fail (this->mixer != NULL); \ + \ + gst_pulsemixer_ctrl_get_volume (this->mixer, track, volumes); \ +} \ +static void \ +interface_as_function ## _set_record (GstMixer * mixer, GstMixerTrack * track, \ + gboolean record) \ +{ \ + Type *this = (Type*) mixer; \ + \ + g_return_if_fail (this != NULL); \ + g_return_if_fail (this->mixer != NULL); \ + \ + gst_pulsemixer_ctrl_set_record (this->mixer, track, record); \ +} \ +static void \ +interface_as_function ## _set_mute (GstMixer * mixer, GstMixerTrack * track, \ + gboolean mute) \ +{ \ + Type *this = (Type*) mixer; \ + \ + g_return_if_fail (this != NULL); \ + g_return_if_fail (this->mixer != NULL); \ + \ + gst_pulsemixer_ctrl_set_mute (this->mixer, track, mute); \ +} \ +static void \ +interface_as_function ## _mixer_interface_init (GstMixerClass * klass) \ +{ \ + GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE; \ + \ + klass->list_tracks = interface_as_function ## _list_tracks; \ + klass->set_volume = interface_as_function ## _set_volume; \ + klass->get_volume = interface_as_function ## _get_volume; \ + klass->set_mute = interface_as_function ## _set_mute; \ + klass->set_record = interface_as_function ## _set_record; \ +} + +G_END_DECLS + +#endif diff --git a/ext/pulse/pulsemixertrack.c b/ext/pulse/pulsemixertrack.c new file mode 100644 index 0000000..be9de63 --- /dev/null +++ b/ext/pulse/pulsemixertrack.c @@ -0,0 +1,68 @@ +/* + * 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 "pulsemixertrack.h" + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +G_DEFINE_TYPE (GstPulseMixerTrack, gst_pulsemixer_track, GST_TYPE_MIXER_TRACK); + +static void +gst_pulsemixer_track_class_init (GstPulseMixerTrackClass * klass) +{ +} + +static void +gst_pulsemixer_track_init (GstPulseMixerTrack * track) +{ + track->control = NULL; +} + +GstMixerTrack * +gst_pulsemixer_track_new (GstPulseMixerCtrl * control) +{ + GstPulseMixerTrack *pulsetrack; + + GstMixerTrack *track; + + pulsetrack = g_object_new (GST_TYPE_PULSEMIXER_TRACK, NULL); + pulsetrack->control = control; + + track = GST_MIXER_TRACK (pulsetrack); + track->label = g_strdup ("Master"); + track->num_channels = control->channel_map.channels; + track->flags = + (control->type == + GST_PULSEMIXER_SINK ? GST_MIXER_TRACK_OUTPUT | GST_MIXER_TRACK_MASTER : + GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_RECORD) | (control->muted ? + GST_MIXER_TRACK_MUTE : 0); + track->min_volume = PA_VOLUME_MUTED; + track->max_volume = PA_VOLUME_NORM; + + return track; +} diff --git a/ext/pulse/pulsemixertrack.h b/ext/pulse/pulsemixertrack.h new file mode 100644 index 0000000..5c958ed --- /dev/null +++ b/ext/pulse/pulsemixertrack.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef __GST_PULSEMIXERTRACK_H__ +#define __GST_PULSEMIXERTRACK_H__ + +#include + +#include "pulsemixerctrl.h" + +G_BEGIN_DECLS + +#define GST_TYPE_PULSEMIXER_TRACK \ + (gst_pulsemixer_track_get_type()) +#define GST_PULSEMIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_PULSEMIXER_TRACK, GstPulseMixerTrack)) +#define GST_PULSEMIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_PULSEMIXER_TRACK, GstPulseMixerTrackClass)) +#define GST_IS_PULSEMIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_PULSEMIXER_TRACK)) +#define GST_IS_PULSEMIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_PULSEMIXER_TRACK)) + + +typedef struct _GstPulseMixerTrack +{ + GstMixerTrack parent; + GstPulseMixerCtrl *control; +} GstPulseMixerTrack; + +typedef struct _GstPulseMixerTrackClass +{ + GstMixerTrackClass parent; +} GstPulseMixerTrackClass; + +GType gst_pulsemixer_track_get_type (void); + +GstMixerTrack *gst_pulsemixer_track_new (GstPulseMixerCtrl * control); + +G_END_DECLS + +#endif diff --git a/ext/pulse/pulseprobe.c b/ext/pulse/pulseprobe.c new file mode 100644 index 0000000..97f3c7c --- /dev/null +++ b/ext/pulse/pulseprobe.c @@ -0,0 +1,370 @@ +/* + * 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 "pulseprobe.h" +#include "pulseutil.h" + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +static void +gst_pulseprobe_context_state_cb (pa_context * context, void *userdata) +{ + GstPulseProbe *c = (GstPulseProbe *) 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_pulseprobe_sink_info_cb (pa_context * context, const pa_sink_info * i, + int eol, void *userdata) +{ + GstPulseProbe *c = (GstPulseProbe *) userdata; + + /* Called from the background thread! */ + + if (eol || !i) { + c->operation_success = eol > 0; + pa_threaded_mainloop_signal (c->mainloop, 0); + } + + if (i) + c->devices = g_list_append (c->devices, g_strdup (i->name)); + +} + +static void +gst_pulseprobe_source_info_cb (pa_context * context, const pa_source_info * i, + int eol, void *userdata) +{ + GstPulseProbe *c = (GstPulseProbe *) userdata; + + /* Called from the background thread! */ + + if (eol || !i) { + c->operation_success = eol > 0; + pa_threaded_mainloop_signal (c->mainloop, 0); + } + + if (i) + c->devices = g_list_append (c->devices, g_strdup (i->name)); +} + +static void +gst_pulseprobe_invalidate (GstPulseProbe * c) +{ + g_list_foreach (c->devices, (GFunc) g_free, NULL); + g_list_free (c->devices); + c->devices = NULL; + c->devices_valid = 0; +} + +static gboolean +gst_pulseprobe_open (GstPulseProbe * c) +{ + int e; + + gchar *name = gst_pulse_client_name (); + + 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_pulseprobe_context_state_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; + } + + pa_threaded_mainloop_unlock (c->mainloop); + g_free (name); + + gst_pulseprobe_invalidate (c); + + return TRUE; + +unlock_and_fail: + + if (c->mainloop) + pa_threaded_mainloop_unlock (c->mainloop); + + g_free (name); + + return FALSE; +} + +#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_pulseprobe_enumerate (GstPulseProbe * c) +{ + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (c->mainloop); + + if (c->enumerate_sinks) { + /* Get sink info */ + + if (!(o = + pa_context_get_sink_info_list (c->context, + gst_pulseprobe_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); + } + + if (!c->operation_success) { + GST_WARNING ("Failed to get sink info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + pa_operation_unref (o); + o = NULL; + } + + if (c->enumerate_sources) { + /* Get source info */ + + if (!(o = + pa_context_get_source_info_list (c->context, + gst_pulseprobe_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); + } + + if (!c->operation_success) { + GST_WARNING ("Failed to get sink info: %s", + pa_strerror (pa_context_errno (c->context))); + goto unlock_and_fail; + } + + pa_operation_unref (o); + o = NULL; + } + + c->devices_valid = 1; + + pa_threaded_mainloop_unlock (c->mainloop); + + return TRUE; + +unlock_and_fail: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (c->mainloop); + + return FALSE; +} + +static void +gst_pulseprobe_close (GstPulseProbe * 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; + } +} + +GstPulseProbe * +gst_pulseprobe_new (GObjectClass * klass, guint prop_id, const gchar * server, + gboolean sinks, gboolean sources) +{ + GstPulseProbe *c = NULL; + + c = g_new (GstPulseProbe, 1); + c->server = g_strdup (server); + c->enumerate_sinks = sinks; + c->enumerate_sources = sources; + + c->mainloop = NULL; + c->context = NULL; + + c->prop_id = prop_id; + c->properties = + g_list_append (NULL, g_object_class_find_property (klass, "device")); + c->devices = NULL; + c->devices_valid = 0; + + return c; +} + +void +gst_pulseprobe_free (GstPulseProbe * c) +{ + g_assert (c); + + gst_pulseprobe_close (c); + + g_list_free (c->properties); + g_free (c->server); + + g_list_foreach (c->devices, (GFunc) g_free, NULL); + g_list_free (c->devices); + + g_free (c); +} + +const GList * +gst_pulseprobe_get_properties (GstPulseProbe * c) +{ + return c->properties; +} + +gboolean +gst_pulseprobe_needs_probe (GstPulseProbe * c, guint prop_id, + const GParamSpec * pspec) +{ + + if (prop_id == c->prop_id) + return !c->devices_valid; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (c, prop_id, pspec); + return FALSE; +} + +void +gst_pulseprobe_probe_property (GstPulseProbe * c, guint prop_id, + const GParamSpec * pspec) +{ + + if (prop_id != c->prop_id) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (c, prop_id, pspec); + return; + } + + if (gst_pulseprobe_open (c)) { + gst_pulseprobe_enumerate (c); + gst_pulseprobe_close (c); + } +} + +GValueArray * +gst_pulseprobe_get_values (GstPulseProbe * c, guint prop_id, + const GParamSpec * pspec) +{ + GValueArray *array; + GValue value = { 0 }; + GList *item; + + if (prop_id != c->prop_id) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (c, prop_id, pspec); + return NULL; + } + + if (!c->devices_valid) + return NULL; + + array = g_value_array_new (g_list_length (c->devices)); + g_value_init (&value, G_TYPE_STRING); + for (item = c->devices; item != NULL; item = item->next) { + GST_WARNING ("device found: %s", (const gchar *) item->data); + g_value_set_string (&value, (const gchar *) item->data); + g_value_array_append (array, &value); + } + g_value_unset (&value); + + return array; +} + +void +gst_pulseprobe_set_server (GstPulseProbe * c, const gchar * server) +{ + g_assert (c); + + gst_pulseprobe_invalidate (c); + + g_free (c->server); + c->server = g_strdup (server); +} diff --git a/ext/pulse/pulseprobe.h b/ext/pulse/pulseprobe.h new file mode 100644 index 0000000..d27c44d --- /dev/null +++ b/ext/pulse/pulseprobe.h @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#ifndef __GST_PULSEPROBE_H__ +#define __GST_PULSEPROBE_H__ + +#include + +G_BEGIN_DECLS + +#include +#include +#include + +typedef struct _GstPulseProbe GstPulseProbe; + +struct _GstPulseProbe +{ + gchar *server; + GList *devices; + int devices_valid; + + pa_threaded_mainloop *mainloop; + pa_context *context; + + GList *properties; + guint prop_id; + + int enumerate_sinks, enumerate_sources; + int operation_success; +}; + +GstPulseProbe *gst_pulseprobe_new (GObjectClass * klass, guint prop_id, + const gchar * server, gboolean sinks, gboolean sources); +void gst_pulseprobe_free (GstPulseProbe * probe); + +const GList *gst_pulseprobe_get_properties (GstPulseProbe * probe); + +gboolean gst_pulseprobe_needs_probe (GstPulseProbe * probe, guint prop_id, + const GParamSpec * pspec); +void gst_pulseprobe_probe_property (GstPulseProbe * probe, guint prop_id, + const GParamSpec * pspec); +GValueArray *gst_pulseprobe_get_values (GstPulseProbe * probe, guint prop_id, + const GParamSpec * pspec); + +void gst_pulseprobe_set_server (GstPulseProbe * c, const gchar * server); + +#define GST_IMPLEMENT_PULSEPROBE_METHODS(Type, interface_as_function) \ +static const GList* \ +interface_as_function ## _get_properties(GstPropertyProbe * probe) \ +{ \ + Type *this = (Type*) probe; \ + \ + g_return_val_if_fail(this != NULL, NULL); \ + g_return_val_if_fail(this->probe != NULL, NULL); \ + \ + return gst_pulseprobe_get_properties(this->probe); \ +} \ +static gboolean \ +interface_as_function ## _needs_probe(GstPropertyProbe *probe, guint prop_id, \ + const GParamSpec *pspec) \ +{ \ + Type *this = (Type*) probe; \ + \ + g_return_val_if_fail(this != NULL, FALSE); \ + g_return_val_if_fail(this->probe != NULL, FALSE); \ + \ + return gst_pulseprobe_needs_probe(this->probe, prop_id, pspec); \ +} \ +static void \ +interface_as_function ## _probe_property(GstPropertyProbe *probe, \ + guint prop_id, const GParamSpec *pspec) \ +{ \ + Type *this = (Type*) probe; \ + \ + g_return_if_fail(this != NULL); \ + g_return_if_fail(this->probe != NULL); \ + \ + gst_pulseprobe_probe_property(this->probe, prop_id, pspec); \ +} \ +static GValueArray* \ +interface_as_function ## _get_values(GstPropertyProbe *probe, guint prop_id, \ + const GParamSpec *pspec) \ +{ \ + Type *this = (Type*) probe; \ + \ + g_return_val_if_fail(this != NULL, NULL); \ + g_return_val_if_fail(this->probe != NULL, NULL); \ + \ + return gst_pulseprobe_get_values(this->probe, prop_id, pspec); \ +} \ +static void \ +interface_as_function ## _property_probe_interface_init(GstPropertyProbeInterface *iface)\ +{ \ + iface->get_properties = interface_as_function ## _get_properties; \ + iface->needs_probe = interface_as_function ## _needs_probe; \ + iface->probe_property = interface_as_function ## _probe_property; \ + iface->get_values = interface_as_function ## _get_values; \ +} + +G_END_DECLS + +#endif diff --git a/ext/pulse/pulsesink.c b/ext/pulse/pulsesink.c new file mode 100644 index 0000000..0d24d39 --- /dev/null +++ b/ext/pulse/pulsesink.c @@ -0,0 +1,746 @@ +/* + * 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 + +#include +#include + +#include "pulsesink.h" +#include "pulseutil.h" + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +enum +{ + PROP_SERVER = 1, + PROP_DEVICE, +}; + +static GstAudioSinkClass *parent_class = NULL; + +static void gst_pulsesink_destroy_stream (GstPulseSink * pulsesink); + +static void gst_pulsesink_destroy_context (GstPulseSink * pulsesink); + +static void gst_pulsesink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pulsesink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_pulsesink_finalize (GObject * object); + +static void gst_pulsesink_dispose (GObject * object); + +static gboolean gst_pulsesink_open (GstAudioSink * asink); + +static gboolean gst_pulsesink_close (GstAudioSink * asink); + +static gboolean gst_pulsesink_prepare (GstAudioSink * asink, + GstRingBufferSpec * spec); +static gboolean gst_pulsesink_unprepare (GstAudioSink * asink); + +static guint gst_pulsesink_write (GstAudioSink * asink, gpointer data, + guint length); +static guint gst_pulsesink_delay (GstAudioSink * asink); + +static void gst_pulsesink_reset (GstAudioSink * asink); + +static gboolean gst_pulsesink_event (GstBaseSink * sink, GstEvent * event); + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) +# define ENDIANNESS "LITTLE_ENDIAN, BIG_ENDIAN" +#else +# define ENDIANNESS "BIG_ENDIAN, LITTLE_ENDIAN" +#endif + +static void +gst_pulsesink_base_init (gpointer g_class) +{ + + static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) { " ENDIANNESS " }, " + "signed = (boolean) TRUE, " + "width = (int) 16, " + "depth = (int) 16, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-float, " + "endianness = (int) { " ENDIANNESS " }, " + "width = (int) 32, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-int, " + "endianness = (int) { " ENDIANNESS " }, " + "signed = (boolean) TRUE, " + "width = (int) 32, " + "depth = (int) 32, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-int, " + "signed = (boolean) FALSE, " + "width = (int) 8, " + "depth = (int) 8, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-alaw, " + "rate = (int) [ 1, MAX], " + "channels = (int) [ 1, 16 ];" + "audio/x-mulaw, " + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ]") + ); + + static const GstElementDetails details = + GST_ELEMENT_DETAILS ("PulseAudio Audio Sink", + "Sink/Audio", + "Plays audio to a PulseAudio server", + "Lennart Poettering"); + + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &details); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&pad_template)); +} + +static void +gst_pulsesink_class_init (gpointer g_class, gpointer class_data) +{ + + GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); + + GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (g_class); + + GstAudioSinkClass *gstaudiosink_class = GST_AUDIO_SINK_CLASS (g_class); + + parent_class = g_type_class_peek_parent (g_class); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_pulsesink_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulsesink_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_pulsesink_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_pulsesink_get_property); + + gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_pulsesink_event); + + gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_pulsesink_open); + gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_pulsesink_close); + gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_pulsesink_prepare); + gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_pulsesink_unprepare); + gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_pulsesink_write); + gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_pulsesink_delay); + gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_pulsesink_reset); + + /* Overwrite GObject fields */ + g_object_class_install_property (gobject_class, + PROP_SERVER, + g_param_spec_string ("server", "Server", + "The PulseAudio server to connect to", NULL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Sink", + "The PulseAudio sink device to connect to", NULL, G_PARAM_READWRITE)); +} + +static void +gst_pulsesink_init (GTypeInstance * instance, gpointer g_class) +{ + + GstPulseSink *pulsesink = GST_PULSESINK (instance); + + int e; + + pulsesink->server = pulsesink->device = pulsesink->stream_name = NULL; + + pulsesink->context = NULL; + pulsesink->stream = NULL; + + pulsesink->mainloop = pa_threaded_mainloop_new (); + g_assert (pulsesink->mainloop); + + e = pa_threaded_mainloop_start (pulsesink->mainloop); + g_assert (e == 0); +} + +static void +gst_pulsesink_destroy_stream (GstPulseSink * pulsesink) +{ + if (pulsesink->stream) { + pa_stream_disconnect (pulsesink->stream); + pa_stream_unref (pulsesink->stream); + pulsesink->stream = NULL; + } + + g_free (pulsesink->stream_name); + pulsesink->stream_name = NULL; +} + +static void +gst_pulsesink_destroy_context (GstPulseSink * pulsesink) +{ + + gst_pulsesink_destroy_stream (pulsesink); + + if (pulsesink->context) { + pa_context_disconnect (pulsesink->context); + pa_context_unref (pulsesink->context); + pulsesink->context = NULL; + } +} + +static void +gst_pulsesink_finalize (GObject * object) +{ + GstPulseSink *pulsesink = GST_PULSESINK (object); + + pa_threaded_mainloop_stop (pulsesink->mainloop); + + gst_pulsesink_destroy_context (pulsesink); + + g_free (pulsesink->server); + g_free (pulsesink->device); + g_free (pulsesink->stream_name); + + pa_threaded_mainloop_free (pulsesink->mainloop); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_pulsesink_dispose (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_pulsesink_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPulseSink *pulsesink = GST_PULSESINK (object); + + switch (prop_id) { + case PROP_SERVER: + g_free (pulsesink->server); + pulsesink->server = g_value_dup_string (value); + break; + + case PROP_DEVICE: + g_free (pulsesink->device); + pulsesink->device = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pulsesink_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + + GstPulseSink *pulsesink = GST_PULSESINK (object); + + switch (prop_id) { + case PROP_SERVER: + g_value_set_string (value, pulsesink->server); + break; + + case PROP_DEVICE: + g_value_set_string (value, pulsesink->device); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pulsesink_context_state_cb (pa_context * c, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + switch (pa_context_get_state (c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +gst_pulsesink_stream_state_cb (pa_stream * s, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + switch (pa_stream_get_state (s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +gst_pulsesink_stream_request_cb (pa_stream * s, size_t length, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); +} + +static void +gst_pulsesink_stream_latency_update_cb (pa_stream * s, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); +} + +static gboolean +gst_pulsesink_open (GstAudioSink * asink) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + gchar *name = gst_pulse_client_name (); + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + if (!(pulsesink->context = + pa_context_new (pa_threaded_mainloop_get_api (pulsesink->mainloop), + name))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to create context"), (NULL)); + goto unlock_and_fail; + } + + pa_context_set_state_callback (pulsesink->context, + gst_pulsesink_context_state_cb, pulsesink); + + if (pa_context_connect (pulsesink->context, pulsesink->server, 0, NULL) < 0) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (pulsesink->mainloop); + + if (pa_context_get_state (pulsesink->context) != PA_CONTEXT_READY) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + g_free (name); + return TRUE; + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + g_free (name); + return FALSE; +} + +static gboolean +gst_pulsesink_close (GstAudioSink * asink) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + pa_threaded_mainloop_lock (pulsesink->mainloop); + gst_pulsesink_destroy_context (pulsesink); + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return TRUE; +} + +static gboolean +gst_pulsesink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) +{ + pa_buffer_attr buf_attr; + + pa_channel_map channel_map; + + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + if (!gst_pulse_fill_sample_spec (spec, &pulsesink->sample_spec)) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, SETTINGS, + ("Invalid sample specification."), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + if (!pulsesink->context + || pa_context_get_state (pulsesink->context) != PA_CONTEXT_READY) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Bad context state: %s", + pulsesink->context ? pa_strerror (pa_context_errno (pulsesink-> + context)) : NULL), (NULL)); + goto unlock_and_fail; + } + + if (!(pulsesink->stream = pa_stream_new (pulsesink->context, + pulsesink->stream_name ? pulsesink-> + stream_name : "Playback Stream", &pulsesink->sample_spec, + gst_pulse_gst_to_channel_map (&channel_map, spec)))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to create stream: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + pa_stream_set_state_callback (pulsesink->stream, + gst_pulsesink_stream_state_cb, pulsesink); + pa_stream_set_write_callback (pulsesink->stream, + gst_pulsesink_stream_request_cb, pulsesink); + pa_stream_set_latency_update_callback (pulsesink->stream, + gst_pulsesink_stream_latency_update_cb, pulsesink); + + memset (&buf_attr, 0, sizeof (buf_attr)); + buf_attr.tlength = spec->segtotal * spec->segsize; + buf_attr.maxlength = buf_attr.tlength * 2; + buf_attr.prebuf = buf_attr.tlength - spec->segsize; + buf_attr.minreq = spec->segsize; + + if (pa_stream_connect_playback (pulsesink->stream, pulsesink->device, + &buf_attr, + PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_NOT_MONOTONOUS, NULL, NULL) < 0) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (pulsesink->mainloop); + + if (pa_stream_get_state (pulsesink->stream) != PA_STREAM_READY) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + spec->bytes_per_sample = pa_frame_size (&pulsesink->sample_spec); + memset (spec->silence_sample, 0, spec->bytes_per_sample); + + return TRUE; + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + return FALSE; +} + +static gboolean +gst_pulsesink_unprepare (GstAudioSink * asink) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + pa_threaded_mainloop_lock (pulsesink->mainloop); + gst_pulsesink_destroy_stream (pulsesink); + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return TRUE; +} + +#define CHECK_DEAD_GOTO(pulsesink, label) \ +if (!(pulsesink)->context || pa_context_get_state((pulsesink)->context) != PA_CONTEXT_READY || \ + !(pulsesink)->stream || pa_stream_get_state((pulsesink)->stream) != PA_STREAM_READY) { \ + GST_ELEMENT_ERROR((pulsesink), RESOURCE, FAILED, ("Disconnected: %s", (pulsesink)->context ? pa_strerror(pa_context_errno((pulsesink)->context)) : NULL), (NULL)); \ + goto label; \ +} + +static guint +gst_pulsesink_write (GstAudioSink * asink, gpointer data, guint length) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + size_t sum = 0; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + while (length > 0) { + size_t l; + + for (;;) { + CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + + if ((l = pa_stream_writable_size (pulsesink->stream)) == (size_t) - 1) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_writable_size() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + if (l > 0) + break; + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + + if (l > length) + l = length; + + if (pa_stream_write (pulsesink->stream, data, l, NULL, 0, + PA_SEEK_RELATIVE) < 0) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_write() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + data = (guint8 *) data + l; + length -= l; + + sum += l; + } + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return sum; + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + return 0; +} + +static guint +gst_pulsesink_delay (GstAudioSink * asink) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + pa_usec_t t; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + for (;;) { + CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + + if (pa_stream_get_latency (pulsesink->stream, &t, NULL) >= 0) + break; + + if (pa_context_errno (pulsesink->context) != PA_ERR_NODATA) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_get_latency() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + + return gst_util_uint64_scale_int (t, pulsesink->sample_spec.rate, 1000000LL); + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesink->mainloop); + return 0; +} + +static void +gst_pulsesink_success_cb (pa_stream * s, int success, void *userdata) +{ + GstPulseSink *pulsesink = GST_PULSESINK (userdata); + + pulsesink->operation_success = success; + pa_threaded_mainloop_signal (pulsesink->mainloop, 0); +} + +static void +gst_pulsesink_reset (GstAudioSink * asink) +{ + GstPulseSink *pulsesink = GST_PULSESINK (asink); + + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + + if (!(o = + pa_stream_flush (pulsesink->stream, gst_pulsesink_success_cb, + pulsesink))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_flush() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + pulsesink->operation_success = 0; + while (pa_operation_get_state (o) != PA_OPERATION_DONE) { + CHECK_DEAD_GOTO (pulsesink, unlock_and_fail); + + pa_threaded_mainloop_wait (pulsesink->mainloop); + } + + if (!pulsesink->operation_success) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, ("Flush failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + +unlock_and_fail: + + if (o) { + pa_operation_cancel (o); + pa_operation_unref (o); + } + + pa_threaded_mainloop_unlock (pulsesink->mainloop); +} + +static void +gst_pulsesink_change_title (GstPulseSink * pulsesink, const gchar * t) +{ + pa_operation *o = NULL; + + pa_threaded_mainloop_lock (pulsesink->mainloop); + + g_free (pulsesink->stream_name); + pulsesink->stream_name = g_strdup (t); + + if (!(pulsesink)->context + || pa_context_get_state ((pulsesink)->context) != PA_CONTEXT_READY + || !(pulsesink)->stream + || pa_stream_get_state ((pulsesink)->stream) != PA_STREAM_READY) { + goto unlock_and_fail; + } + + if (!(o = + pa_stream_set_name (pulsesink->stream, pulsesink->stream_name, NULL, + pulsesink))) { + GST_ELEMENT_ERROR (pulsesink, RESOURCE, FAILED, + ("pa_stream_set_name() failed: %s", + pa_strerror (pa_context_errno (pulsesink->context))), (NULL)); + goto unlock_and_fail; + } + + /* We're not interested if this operation failed or not */ + +unlock_and_fail: + + if (o) + pa_operation_unref (o); + + pa_threaded_mainloop_unlock (pulsesink->mainloop); +} + +static gboolean +gst_pulsesink_event (GstBaseSink * sink, GstEvent * event) +{ + GstPulseSink *pulsesink = GST_PULSESINK (sink); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_TAG:{ + gchar *title = NULL, *artist = NULL, *location = NULL, *description = + NULL, *t = NULL, *buf = NULL; + GstTagList *l; + + gst_event_parse_tag (event, &l); + + gst_tag_list_get_string (l, GST_TAG_TITLE, &title); + gst_tag_list_get_string (l, GST_TAG_ARTIST, &artist); + gst_tag_list_get_string (l, GST_TAG_LOCATION, &location); + gst_tag_list_get_string (l, GST_TAG_DESCRIPTION, &description); + + if (title && artist) + t = buf = + g_strdup_printf ("'%s' by '%s'", g_strstrip (title), + g_strstrip (artist)); + else if (title) + t = g_strstrip (title); + else if (description) + t = g_strstrip (description); + else if (location) + t = g_strstrip (location); + + if (t) + gst_pulsesink_change_title (pulsesink, t); + + g_free (title); + g_free (artist); + g_free (location); + g_free (description); + g_free (buf); + + break; + } + default: + ; + } + + return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); +} + +GType +gst_pulsesink_get_type (void) +{ + static GType pulsesink_type = 0; + + if (!pulsesink_type) { + + static const GTypeInfo pulsesink_info = { + sizeof (GstPulseSinkClass), + gst_pulsesink_base_init, + NULL, + gst_pulsesink_class_init, + NULL, + NULL, + sizeof (GstPulseSink), + 0, + gst_pulsesink_init, + }; + + pulsesink_type = g_type_register_static (GST_TYPE_AUDIO_SINK, + "GstPulseSink", &pulsesink_info, 0); + } + + return pulsesink_type; +} diff --git a/ext/pulse/pulsesink.h b/ext/pulse/pulsesink.h new file mode 100644 index 0000000..d7a3b78 --- /dev/null +++ b/ext/pulse/pulsesink.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef __GST_PULSESINK_H__ +#define __GST_PULSESINK_H__ + +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_PULSESINK \ + (gst_pulsesink_get_type()) +#define GST_PULSESINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSESINK,GstPulseSink)) +#define GST_PULSESINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESINK,GstPulseSinkClass)) +#define GST_IS_PULSESINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSESINK)) +#define GST_IS_PULSESINK_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSESINK)) + +typedef struct _GstPulseSink GstPulseSink; +typedef struct _GstPulseSinkClass GstPulseSinkClass; + +struct _GstPulseSink +{ + GstAudioSink sink; + + gchar *server, *device, *stream_name; + + pa_threaded_mainloop *mainloop; + + pa_context *context; + pa_stream *stream; + + pa_sample_spec sample_spec; + + int operation_success; +}; + +struct _GstPulseSinkClass +{ + GstAudioSinkClass parent_class; +}; + +GType gst_pulsesink_get_type (void); + +G_END_DECLS + +#endif /* __GST_PULSESINK_H__ */ diff --git a/ext/pulse/pulsesrc.c b/ext/pulse/pulsesrc.c new file mode 100644 index 0000000..e69c5ed --- /dev/null +++ b/ext/pulse/pulsesrc.c @@ -0,0 +1,703 @@ +/* + * 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 + +#include +#include + +#include "pulsesrc.h" +#include "pulseutil.h" +#include "pulsemixerctrl.h" + +GST_DEBUG_CATEGORY_EXTERN (pulse_debug); +#define GST_CAT_DEFAULT pulse_debug + +enum +{ + PROP_SERVER = 1, + PROP_DEVICE +}; + +static GstAudioSrcClass *parent_class = NULL; + +GST_IMPLEMENT_PULSEMIXER_CTRL_METHODS (GstPulseSrc, gst_pulsesrc) + + static void gst_pulsesrc_destroy_stream (GstPulseSrc * pulsesrc); + + static void gst_pulsesrc_destroy_context (GstPulseSrc * pulsesrc); + + static void gst_pulsesrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + static void gst_pulsesrc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + static void gst_pulsesrc_finalize (GObject * object); + + static void gst_pulsesrc_dispose (GObject * object); + + static gboolean gst_pulsesrc_open (GstAudioSrc * asrc); + + static gboolean gst_pulsesrc_close (GstAudioSrc * asrc); + + static gboolean gst_pulsesrc_prepare (GstAudioSrc * asrc, + GstRingBufferSpec * spec); + static gboolean gst_pulsesrc_unprepare (GstAudioSrc * asrc); + + static guint gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, + guint length); + static guint gst_pulsesrc_delay (GstAudioSrc * asrc); + + static GstStateChangeReturn gst_pulsesrc_change_state (GstElement * + element, GstStateChange transition); + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) +# define ENDIANNESS "LITTLE_ENDIAN, BIG_ENDIAN" +#else +# define ENDIANNESS "BIG_ENDIAN, LITTLE_ENDIAN" +#endif + + static gboolean gst_pulsesrc_interface_supported (GstImplementsInterface * + iface, GType interface_type) +{ + GstPulseSrc *this = GST_PULSESRC (iface); + + if (interface_type == GST_TYPE_MIXER && this->mixer) + return TRUE; + + return FALSE; +} + +static void +gst_pulsesrc_implements_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_pulsesrc_interface_supported; +} + +static void +gst_pulsesrc_init_interfaces (GType type) +{ + static const GInterfaceInfo implements_iface_info = { + (GInterfaceInitFunc) gst_pulsesrc_implements_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo mixer_iface_info = { + (GInterfaceInitFunc) gst_pulsesrc_mixer_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &implements_iface_info); + g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info); +} + +static void +gst_pulsesrc_base_init (gpointer g_class) +{ + + static GstStaticPadTemplate pad_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) { " ENDIANNESS " }, " + "signed = (boolean) TRUE, " + "width = (int) 16, " + "depth = (int) 16, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-int, " + "endianness = (int) { " ENDIANNESS " }, " + "signed = (boolean) TRUE, " + "width = (int) 32, " + "depth = (int) 32, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-float, " + "endianness = (int) { " ENDIANNESS " }, " + "width = (int) 32, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-raw-int, " + "signed = (boolean) FALSE, " + "width = (int) 8, " + "depth = (int) 8, " + "rate = (int) [ 1, MAX ], " + "channels = (int) [ 1, 16 ];" + "audio/x-alaw, " + "rate = (int) [ 1, MAX], " + "channels = (int) [ 1, 16 ];" + "audio/x-mulaw, " + "rate = (int) [ 1, MAX], " "channels = (int) [ 1, 16 ]") + ); + + static const GstElementDetails details = + GST_ELEMENT_DETAILS ("PulseAudio Audio Source", + "Source/Audio", + "Captures audio from a PulseAudio server", + "Lennart Poettering"); + + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &details); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&pad_template)); +} + +static void +gst_pulsesrc_class_init (gpointer g_class, gpointer class_data) +{ + + GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); + + GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS (g_class); + + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + + parent_class = g_type_class_peek_parent (g_class); + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_pulsesrc_change_state); + + gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_pulsesrc_dispose); + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_pulsesrc_finalize); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_pulsesrc_set_property); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_pulsesrc_get_property); + + gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_pulsesrc_open); + gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_pulsesrc_close); + gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_pulsesrc_prepare); + gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_pulsesrc_unprepare); + gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_pulsesrc_read); + gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_pulsesrc_delay); + + /* Overwrite GObject fields */ + g_object_class_install_property (gobject_class, + PROP_SERVER, + g_param_spec_string ("server", "Server", + "The PulseAudio server to connect to", NULL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Source", + "The PulseAudio source device to connect to", NULL, + G_PARAM_READWRITE)); +} + +static void +gst_pulsesrc_init (GTypeInstance * instance, gpointer g_class) +{ + + GstPulseSrc *pulsesrc = GST_PULSESRC (instance); + + int e; + + pulsesrc->server = pulsesrc->device = NULL; + + pulsesrc->context = NULL; + pulsesrc->stream = NULL; + + pulsesrc->read_buffer = NULL; + pulsesrc->read_buffer_length = 0; + + pulsesrc->mainloop = pa_threaded_mainloop_new (); + g_assert (pulsesrc->mainloop); + + e = pa_threaded_mainloop_start (pulsesrc->mainloop); + g_assert (e == 0); + + pulsesrc->mixer = NULL; +} + +static void +gst_pulsesrc_destroy_stream (GstPulseSrc * pulsesrc) +{ + if (pulsesrc->stream) { + pa_stream_disconnect (pulsesrc->stream); + pa_stream_unref (pulsesrc->stream); + pulsesrc->stream = NULL; + } +} + +static void +gst_pulsesrc_destroy_context (GstPulseSrc * pulsesrc) +{ + + gst_pulsesrc_destroy_stream (pulsesrc); + + if (pulsesrc->context) { + pa_context_disconnect (pulsesrc->context); + pa_context_unref (pulsesrc->context); + pulsesrc->context = NULL; + } +} + +static void +gst_pulsesrc_finalize (GObject * object) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (object); + + pa_threaded_mainloop_stop (pulsesrc->mainloop); + + gst_pulsesrc_destroy_context (pulsesrc); + + g_free (pulsesrc->server); + g_free (pulsesrc->device); + + pa_threaded_mainloop_free (pulsesrc->mainloop); + + if (pulsesrc->mixer) + gst_pulsemixer_ctrl_free (pulsesrc->mixer); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_pulsesrc_dispose (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_pulsesrc_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + + GstPulseSrc *pulsesrc = GST_PULSESRC (object); + + switch (prop_id) { + case PROP_SERVER: + g_free (pulsesrc->server); + pulsesrc->server = g_value_dup_string (value); + break; + + case PROP_DEVICE: + g_free (pulsesrc->device); + pulsesrc->device = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pulsesrc_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + + GstPulseSrc *pulsesrc = GST_PULSESRC (object); + + switch (prop_id) { + case PROP_SERVER: + g_value_set_string (value, pulsesrc->server); + break; + + case PROP_DEVICE: + g_value_set_string (value, pulsesrc->device); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pulsesrc_context_state_cb (pa_context * c, void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + switch (pa_context_get_state (c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +gst_pulsesrc_stream_state_cb (pa_stream * s, void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + switch (pa_stream_get_state (s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +gst_pulsesrc_stream_request_cb (pa_stream * s, size_t length, void *userdata) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (userdata); + + pa_threaded_mainloop_signal (pulsesrc->mainloop, 0); +} + +static gboolean +gst_pulsesrc_open (GstAudioSrc * asrc) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + gchar *name = gst_pulse_client_name (); + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + if (!(pulsesrc->context = + pa_context_new (pa_threaded_mainloop_get_api (pulsesrc->mainloop), + name))) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to create context"), + (NULL)); + goto unlock_and_fail; + } + + pa_context_set_state_callback (pulsesrc->context, + gst_pulsesrc_context_state_cb, pulsesrc); + + if (pa_context_connect (pulsesrc->context, pulsesrc->server, 0, NULL) < 0) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait (pulsesrc->mainloop); + + if (pa_context_get_state (pulsesrc->context) != PA_CONTEXT_READY) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Failed to connect: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + g_free (name); + return TRUE; + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + g_free (name); + return FALSE; +} + +static gboolean +gst_pulsesrc_close (GstAudioSrc * asrc) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + gst_pulsesrc_destroy_context (pulsesrc); + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + return TRUE; +} + +static gboolean +gst_pulsesrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) +{ + pa_buffer_attr buf_attr; + + pa_channel_map channel_map; + + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + if (!gst_pulse_fill_sample_spec (spec, &pulsesrc->sample_spec)) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, SETTINGS, + ("Invalid sample specification."), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + if (!pulsesrc->context + || pa_context_get_state (pulsesrc->context) != PA_CONTEXT_READY) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, ("Bad context state: %s", + pulsesrc->context ? pa_strerror (pa_context_errno (pulsesrc-> + context)) : NULL), (NULL)); + goto unlock_and_fail; + } + + if (!(pulsesrc->stream = pa_stream_new (pulsesrc->context, + "Record Stream", + &pulsesrc->sample_spec, + gst_pulse_gst_to_channel_map (&channel_map, spec)))) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("Failed to create stream: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + pa_stream_set_state_callback (pulsesrc->stream, gst_pulsesrc_stream_state_cb, + pulsesrc); + pa_stream_set_read_callback (pulsesrc->stream, gst_pulsesrc_stream_request_cb, + pulsesrc); + + memset (&buf_attr, 0, sizeof (buf_attr)); + buf_attr.maxlength = spec->segtotal * spec->segsize * 2; + buf_attr.fragsize = spec->segsize; + + if (pa_stream_connect_record (pulsesrc->stream, pulsesrc->device, &buf_attr, + PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_NOT_MONOTONOUS) < 0) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait (pulsesrc->mainloop); + + if (pa_stream_get_state (pulsesrc->stream) != PA_STREAM_READY) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("Failed to connect stream: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + spec->bytes_per_sample = pa_frame_size (&pulsesrc->sample_spec); + memset (spec->silence_sample, 0, spec->bytes_per_sample); + + return TRUE; + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + return FALSE; +} + +static gboolean +gst_pulsesrc_unprepare (GstAudioSrc * asrc) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + gst_pulsesrc_destroy_stream (pulsesrc); + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + pulsesrc->read_buffer = NULL; + pulsesrc->read_buffer_length = 0; + + return TRUE; +} + +#define CHECK_DEAD_GOTO(pulsesrc, label) \ +if (!(pulsesrc)->context || pa_context_get_state((pulsesrc)->context) != PA_CONTEXT_READY || \ + !(pulsesrc)->stream || pa_stream_get_state((pulsesrc)->stream) != PA_STREAM_READY) { \ + GST_ELEMENT_ERROR((pulsesrc), RESOURCE, FAILED, ("Disconnected: %s", (pulsesrc)->context ? pa_strerror(pa_context_errno((pulsesrc)->context)) : NULL), (NULL)); \ + goto label; \ +} + +static guint +gst_pulsesrc_read (GstAudioSrc * asrc, gpointer data, guint length) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + size_t sum = 0; + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + + while (length > 0) { + size_t l; + + if (!pulsesrc->read_buffer) { + + for (;;) { + if (pa_stream_peek (pulsesrc->stream, &pulsesrc->read_buffer, + &pulsesrc->read_buffer_length) < 0) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_peek() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + if (pulsesrc->read_buffer) + break; + + pa_threaded_mainloop_wait (pulsesrc->mainloop); + + CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + } + } + + g_assert (pulsesrc->read_buffer && pulsesrc->read_buffer_length); + + l = pulsesrc->read_buffer_length > + length ? length : pulsesrc->read_buffer_length; + + memcpy (data, pulsesrc->read_buffer, l); + + pulsesrc->read_buffer = (const guint8 *) pulsesrc->read_buffer + l; + pulsesrc->read_buffer_length -= l; + + data = (guint8 *) data + l; + length -= l; + + sum += l; + + if (pulsesrc->read_buffer_length <= 0) { + + if (pa_stream_drop (pulsesrc->stream) < 0) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_drop() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + pulsesrc->read_buffer = NULL; + pulsesrc->read_buffer_length = 0; + } + } + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + return sum; + +unlock_and_fail: + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + return 0; +} + +static guint +gst_pulsesrc_delay (GstAudioSrc * asrc) +{ + GstPulseSrc *pulsesrc = GST_PULSESRC (asrc); + + pa_usec_t t; + + int negative; + + pa_threaded_mainloop_lock (pulsesrc->mainloop); + + CHECK_DEAD_GOTO (pulsesrc, unlock_and_fail); + + if (pa_stream_get_latency (pulsesrc->stream, &t, &negative) < 0) { + + if (pa_context_errno (pulsesrc->context) != PA_ERR_NODATA) { + GST_ELEMENT_ERROR (pulsesrc, RESOURCE, FAILED, + ("pa_stream_get_latency() failed: %s", + pa_strerror (pa_context_errno (pulsesrc->context))), (NULL)); + goto unlock_and_fail; + } + + GST_WARNING ("Not data while querying latency"); + t = 0; + } else if (negative) + t = 0; + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + + return (guint) ((t * pulsesrc->sample_spec.rate) / 1000000LL); + +unlock_and_fail: + + pa_threaded_mainloop_unlock (pulsesrc->mainloop); + return 0; +} + +static GstStateChangeReturn +gst_pulsesrc_change_state (GstElement * element, GstStateChange transition) +{ + GstPulseSrc *this = GST_PULSESRC (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + + if (!this->mixer) + this->mixer = + gst_pulsemixer_ctrl_new (this->server, this->device, + GST_PULSEMIXER_SOURCE); + + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + + if (this->mixer) { + gst_pulsemixer_ctrl_free (this->mixer); + this->mixer = NULL; + } + + break; + + default: + ; + } + + if (GST_ELEMENT_CLASS (parent_class)->change_state) + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + return GST_STATE_CHANGE_SUCCESS; +} + +GType +gst_pulsesrc_get_type (void) +{ + static GType pulsesrc_type = 0; + + if (!pulsesrc_type) { + + static const GTypeInfo pulsesrc_info = { + sizeof (GstPulseSrcClass), + gst_pulsesrc_base_init, + NULL, + gst_pulsesrc_class_init, + NULL, + NULL, + sizeof (GstPulseSrc), + 0, + gst_pulsesrc_init, + }; + + pulsesrc_type = g_type_register_static (GST_TYPE_AUDIO_SRC, + "GstPulseSrc", &pulsesrc_info, 0); + + gst_pulsesrc_init_interfaces (pulsesrc_type); + } + + return pulsesrc_type; +} diff --git a/ext/pulse/pulsesrc.h b/ext/pulse/pulsesrc.h new file mode 100644 index 0000000..408a158 --- /dev/null +++ b/ext/pulse/pulsesrc.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef __GST_PULSESRC_H__ +#define __GST_PULSESRC_H__ + +#include +#include + +#include +#include + +#include "pulsemixerctrl.h" + +G_BEGIN_DECLS + +#define GST_TYPE_PULSESRC \ + (gst_pulsesrc_get_type()) +#define GST_PULSESRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PULSESRC,GstPulseSrc)) +#define GST_PULSESRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESRC,GstPulseSrcClass)) +#define GST_IS_PULSESRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PULSESRC)) +#define GST_IS_PULSESRC_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PULSESRC)) + +typedef struct _GstPulseSrc GstPulseSrc; +typedef struct _GstPulseSrcClass GstPulseSrcClass; + +struct _GstPulseSrc +{ + GstAudioSrc src; + + gchar *server, *device; + + pa_threaded_mainloop *mainloop; + + pa_context *context; + pa_stream *stream; + + pa_sample_spec sample_spec; + + const void *read_buffer; + size_t read_buffer_length; + + GstPulseMixerCtrl *mixer; +}; + +struct _GstPulseSrcClass +{ + GstAudioSrcClass parent_class; +}; + +GType gst_pulsesrc_get_type (void); + +G_END_DECLS + +#endif /* __GST_PULSESRC_H__ */ diff --git a/ext/pulse/pulseutil.c b/ext/pulse/pulseutil.c new file mode 100644 index 0000000..518bce2 --- /dev/null +++ b/ext/pulse/pulseutil.c @@ -0,0 +1,138 @@ +/* + * 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 "pulseutil.h" +#include + +static const pa_channel_position_t gst_pos_to_pa[GST_AUDIO_CHANNEL_POSITION_NUM] + = { + [GST_AUDIO_CHANNEL_POSITION_FRONT_MONO] = PA_CHANNEL_POSITION_MONO, + [GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT] = PA_CHANNEL_POSITION_FRONT_LEFT, + [GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT] = PA_CHANNEL_POSITION_FRONT_RIGHT, + [GST_AUDIO_CHANNEL_POSITION_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER, + [GST_AUDIO_CHANNEL_POSITION_REAR_LEFT] = PA_CHANNEL_POSITION_REAR_LEFT, + [GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT] = PA_CHANNEL_POSITION_REAR_RIGHT, + [GST_AUDIO_CHANNEL_POSITION_LFE] = PA_CHANNEL_POSITION_LFE, + [GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER] = PA_CHANNEL_POSITION_FRONT_CENTER, + [GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + [GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + [GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT, + [GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT, + [GST_AUDIO_CHANNEL_POSITION_NONE] = PA_CHANNEL_POSITION_INVALID +}; + +gboolean +gst_pulse_fill_sample_spec (GstRingBufferSpec * spec, pa_sample_spec * ss) +{ + + if (spec->format == GST_MU_LAW && spec->width == 8) + ss->format = PA_SAMPLE_ULAW; + else if (spec->format == GST_A_LAW && spec->width == 8) + ss->format = PA_SAMPLE_ALAW; + else if (spec->format == GST_U8 && spec->width == 8) + ss->format = PA_SAMPLE_U8; + else if (spec->format == GST_S16_LE && spec->width == 16) + ss->format = PA_SAMPLE_S16LE; + else if (spec->format == GST_S16_BE && spec->width == 16) + ss->format = PA_SAMPLE_S16BE; + else if (spec->format == GST_FLOAT32_LE && spec->width == 32) + ss->format = PA_SAMPLE_FLOAT32LE; + else if (spec->format == GST_FLOAT32_BE && spec->width == 32) + ss->format = PA_SAMPLE_FLOAT32BE; + else if (spec->format == GST_S32_LE && spec->width == 32) + ss->format = PA_SAMPLE_S32LE; + else if (spec->format == GST_S32_BE && spec->width == 32) + ss->format = PA_SAMPLE_S32BE; + else + return FALSE; + + ss->channels = spec->channels; + ss->rate = spec->rate; + + if (!pa_sample_spec_valid (ss)) + return FALSE; + + return TRUE; +} + +gchar * +gst_pulse_client_name (void) +{ + gchar buf[PATH_MAX]; + + const char *c; + + if ((c = g_get_application_name ())) + return g_strdup_printf ("%s", c); + else if (pa_get_binary_name (buf, sizeof (buf))) + return g_strdup_printf ("%s", buf); + else + return g_strdup ("GStreamer"); +} + +pa_channel_map * +gst_pulse_gst_to_channel_map (pa_channel_map * map, GstRingBufferSpec * spec) +{ + int i; + + GstAudioChannelPosition *pos; + + pa_channel_map_init (map); + + if (!(pos = + gst_audio_get_channel_positions (gst_caps_get_structure (spec->caps, + 0)))) { +/* g_debug("%s: No channel positions!\n", G_STRFUNC); */ + return NULL; + } + +/* g_debug("%s: Got channel positions:\n", G_STRFUNC); */ + + for (i = 0; i < spec->channels; i++) { + + if (pos[i] == GST_AUDIO_CHANNEL_POSITION_NONE) { + /* no valid mappings for these channels */ + g_free (pos); + return NULL; + } else if (pos[i] < GST_AUDIO_CHANNEL_POSITION_NUM) + map->map[i] = gst_pos_to_pa[pos[i]]; + else + map->map[i] = PA_CHANNEL_POSITION_INVALID; + + /*g_debug(" channel %d: gst: %d pulse: %d\n", i, pos[i], map->map[i]); */ + } + + g_free (pos); + map->channels = spec->channels; + + if (!pa_channel_map_valid (map)) { +/* g_debug("generated invalid map!\n"); */ + return NULL; + } + + return map; +} diff --git a/ext/pulse/pulseutil.h b/ext/pulse/pulseutil.h new file mode 100644 index 0000000..f468984 --- /dev/null +++ b/ext/pulse/pulseutil.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef __GST_PULSEUTIL_H__ +#define __GST_PULSEUTIL_H__ + +#include +#include +#include + +gboolean gst_pulse_fill_sample_spec (GstRingBufferSpec * spec, + pa_sample_spec * ss); + +gchar *gst_pulse_client_name (void); + +pa_channel_map *gst_pulse_gst_to_channel_map (pa_channel_map * map, + GstRingBufferSpec * spec); + +#endif -- 2.7.4