From d84317d5a95ac21de8aa673eff64e00e40f82557 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Jun 2006 19:29:12 +0000 Subject: rename source files git-svn-id: file:///home/lennart/svn/public/gst-pulse/trunk@41 bb39ca4e-bce3-0310-b5d4-eea78a553289 --- src/polypmixer.c | 246 ------------------- src/polypmixer.h | 66 ----- src/polypmixerctrl.c | 452 ----------------------------------- src/polypmixerctrl.h | 143 ----------- src/polypmixertrack.c | 59 ----- src/polypmixertrack.h | 56 ----- src/polypprobe.c | 322 ------------------------- src/polypprobe.h | 115 --------- src/polypsink.c | 647 -------------------------------------------------- src/polypsink.h | 70 ------ src/polypsrc.c | 616 ----------------------------------------------- src/polypsrc.h | 75 ------ src/polyputil.c | 63 ----- src/polyputil.h | 33 --- src/pulsemixer.c | 246 +++++++++++++++++++ src/pulsemixer.h | 66 +++++ src/pulsemixerctrl.c | 452 +++++++++++++++++++++++++++++++++++ src/pulsemixerctrl.h | 143 +++++++++++ src/pulsemixertrack.c | 59 +++++ src/pulsemixertrack.h | 56 +++++ src/pulseprobe.c | 322 +++++++++++++++++++++++++ src/pulseprobe.h | 115 +++++++++ src/pulsesink.c | 647 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/pulsesink.h | 70 ++++++ src/pulsesrc.c | 616 +++++++++++++++++++++++++++++++++++++++++++++++ src/pulsesrc.h | 75 ++++++ src/pulseutil.c | 63 +++++ src/pulseutil.h | 33 +++ 28 files changed, 2963 insertions(+), 2963 deletions(-) delete mode 100644 src/polypmixer.c delete mode 100644 src/polypmixer.h delete mode 100644 src/polypmixerctrl.c delete mode 100644 src/polypmixerctrl.h delete mode 100644 src/polypmixertrack.c delete mode 100644 src/polypmixertrack.h delete mode 100644 src/polypprobe.c delete mode 100644 src/polypprobe.h delete mode 100644 src/polypsink.c delete mode 100644 src/polypsink.h delete mode 100644 src/polypsrc.c delete mode 100644 src/polypsrc.h delete mode 100644 src/polyputil.c delete mode 100644 src/polyputil.h create mode 100644 src/pulsemixer.c create mode 100644 src/pulsemixer.h create mode 100644 src/pulsemixerctrl.c create mode 100644 src/pulsemixerctrl.h create mode 100644 src/pulsemixertrack.c create mode 100644 src/pulsemixertrack.h create mode 100644 src/pulseprobe.c create mode 100644 src/pulseprobe.h create mode 100644 src/pulsesink.c create mode 100644 src/pulsesink.h create mode 100644 src/pulsesrc.c create mode 100644 src/pulsesrc.h create mode 100644 src/pulseutil.c create mode 100644 src/pulseutil.h diff --git a/src/polypmixer.c b/src/polypmixer.c deleted file mode 100644 index 3abba78..0000000 --- a/src/polypmixer.c +++ /dev/null @@ -1,246 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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(GstPolypMixer, gst_pulsemixer) -GST_IMPLEMENT_PULSEPROBE_METHODS(GstPolypMixer, gst_pulsemixer) -GST_BOILERPLATE_FULL(GstPolypMixer, gst_pulsemixer, GstElement, GST_TYPE_ELEMENT, gst_pulsemixer_init_interfaces) - -static gboolean gst_pulsemixer_interface_supported(GstImplementsInterface* iface, GType interface_type) { - GstPolypMixer *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(GstPolypMixerClass *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(GstPolypMixer *this, GstPolypMixerClass *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) { - GstPolypMixer *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) { - - GstPolypMixer *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) { - - GstPolypMixer *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 [%s]", this->mixer->type == GST_PULSEMIXER_SINK ? "Playback" : "Capture", this->mixer->description, this->mixer->name); - 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) { - GstPolypMixer *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/src/polypmixer.h b/src/polypmixer.h deleted file mode 100644 index fd71d9e..0000000 --- a/src/polypmixer.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef __GST_PULSEMIXER_H__ -#define __GST_PULSEMIXER_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#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,GstPolypMixer)) -#define GST_PULSEMIXER_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSEMIXER,GstPolypMixerClass)) -#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 _GstPolypMixer GstPolypMixer; -typedef struct _GstPolypMixerClass GstPolypMixerClass; - -struct _GstPolypMixer { - GstElement parent; - - gchar *server, *device; - - GstPolypMixerCtrl *mixer; - GstPolypProbe *probe; -}; - -struct _GstPolypMixerClass { - GstElementClass parent_class; -}; - -GType gst_pulsemixer_get_type(void); - -G_END_DECLS - -#endif /* __GST_PULSEMIXER_H__ */ diff --git a/src/polypmixerctrl.c b/src/polypmixerctrl.c deleted file mode 100644 index 4786004..0000000 --- a/src/polypmixerctrl.c +++ /dev/null @@ -1,452 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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) { - GstPolypMixerCtrl *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) { - GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) userdata; - - /* Called from the background thread! */ - - 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) - c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); - - 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) { - GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) userdata; - - /* Called from the background thread! */ - - 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) - c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); - - 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) { - GstPolypMixerCtrl *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); -} - -static void gst_pulsemixer_ctrl_success_cb(pa_context *context, int success, void *userdata) { - GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) 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(GstPolypMixerCtrl *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(GstPolypMixerCtrl *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; - } -} - -GstPolypMixerCtrl* gst_pulsemixer_ctrl_new(const gchar *server, const gchar *device, GstPolypMixerType type) { - GstPolypMixerCtrl *c = NULL; - - c = g_new(GstPolypMixerCtrl, 1); - c->tracklist = NULL; - c->server = g_strdup(server); - c->device = g_strdup(device); - c->mainloop = NULL; - c->context = NULL; - c->track = NULL; - - 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; - - if (!(gst_pulsemixer_ctrl_open(c))) { - gst_pulsemixer_ctrl_free(c); - return NULL; - } - - return c; -} - -void gst_pulsemixer_ctrl_free(GstPolypMixerCtrl *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(GstPolypMixerCtrl *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; - GstPolypMixerCtrl *c = GST_PULSEMIXER_CTRL(userdata); - - 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); - - g_assert(e == c->time_event); - a->time_free(e); - c->time_event = NULL; -} - -void gst_pulsemixer_ctrl_set_volume(GstPolypMixerCtrl *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; - - if (!c->time_event) { - /* 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 100ms */ - 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), 100000), gst_pulsemixer_ctrl_timeout_event, c); - } - - pa_threaded_mainloop_unlock(c->mainloop); -} - -void gst_pulsemixer_ctrl_get_volume(GstPolypMixerCtrl *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(GstPolypMixerCtrl *c, GstMixerTrack *track, gboolean record) { - g_assert(c); - g_assert(track == c->track); -} - -void gst_pulsemixer_ctrl_set_mute(GstPolypMixerCtrl *c, GstMixerTrack *track, gboolean mute) { - pa_operation *o = NULL; - - g_assert(c); - g_assert(track == c->track); - - pa_threaded_mainloop_lock(c->mainloop); - - if (c->type == GST_PULSEMIXER_SINK) - o = pa_context_set_sink_mute_by_index(c->context, c->index, !!mute, NULL, NULL); - else - o = pa_context_set_source_mute_by_index(c->context, c->index, !!mute, NULL, NULL); - - if (!(o)) { - GST_WARNING("Failed to set device mute status: %s", pa_strerror(pa_context_errno(c->context))); - goto unlock_and_fail; - } - - c->muted = mute; - - if (c->track) - c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); - - /* We're not interested in the return code of this operation */ - -unlock_and_fail: - - if (o) - pa_operation_unref(o); - - pa_threaded_mainloop_unlock(c->mainloop); -} diff --git a/src/polypmixerctrl.h b/src/polypmixerctrl.h deleted file mode 100644 index f901f56..0000000 --- a/src/polypmixerctrl.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef __GST_PULSEMIXERCTRL_H__ -#define __GST_PULSEMIXERCTRL_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#include -#include - -#include -#include - -G_BEGIN_DECLS - -#define GST_PULSEMIXER_CTRL(obj) ((GstPolypMixerCtrl*)(obj)) - -typedef struct _GstPolypMixerCtrl GstPolypMixerCtrl; - - -typedef enum { - GST_PULSEMIXER_UNKNOWN, - GST_PULSEMIXER_SINK, - GST_PULSEMIXER_SOURCE } -GstPolypMixerType; - -struct _GstPolypMixerCtrl { - 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; - GstPolypMixerType type; - int operation_success; - - GstMixerTrack *track; - - pa_time_event *time_event; -}; - -GstPolypMixerCtrl* gst_pulsemixer_ctrl_new(const gchar *server, const gchar *device, GstPolypMixerType type); -void gst_pulsemixer_ctrl_free(GstPolypMixerCtrl*mixer); - -const GList* gst_pulsemixer_ctrl_list_tracks(GstPolypMixerCtrl *mixer); -void gst_pulsemixer_ctrl_set_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); -void gst_pulsemixer_ctrl_get_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); -void gst_pulsemixer_ctrl_set_mute(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gboolean mute); -void gst_pulsemixer_ctrl_set_record(GstPolypMixerCtrl *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/src/polypmixertrack.c b/src/polypmixertrack.c deleted file mode 100644 index 3e9b746..0000000 --- a/src/polypmixertrack.c +++ /dev/null @@ -1,59 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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(GstPolypMixerTrack, gst_pulsemixer_track, GST_TYPE_MIXER_TRACK); - -static void gst_pulsemixer_track_class_init(GstPolypMixerTrackClass *klass) { -} - -static void gst_pulsemixer_track_init(GstPolypMixerTrack *track) { - track->control = NULL; -} - -GstMixerTrack *gst_pulsemixer_track_new(GstPolypMixerCtrl *control) { - GstPolypMixerTrack *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/src/polypmixertrack.h b/src/polypmixertrack.h deleted file mode 100644 index 68b1a50..0000000 --- a/src/polypmixertrack.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef __GST_PULSEMIXERTRACK_H__ -#define __GST_PULSEMIXERTRACK_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#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, GstPolypMixerTrack)) -#define GST_PULSEMIXER_TRACK_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_PULSEMIXER_TRACK, GstPolypMixerTrackClass)) -#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 _GstPolypMixerTrack { - GstMixerTrack parent; - GstPolypMixerCtrl *control; -} GstPolypMixerTrack; - -typedef struct _GstPolypMixerTrackClass { - GstMixerTrackClass parent; -} GstPolypMixerTrackClass; - -GType gst_pulsemixer_track_get_type(void); -GstMixerTrack* gst_pulsemixer_track_new(GstPolypMixerCtrl *control); - -G_END_DECLS - -#endif diff --git a/src/polypprobe.c b/src/polypprobe.c deleted file mode 100644 index adb5212..0000000 --- a/src/polypprobe.c +++ /dev/null @@ -1,322 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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) { - GstPolypProbe *c = (GstPolypProbe*) 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) { - GstPolypProbe *c = (GstPolypProbe*) 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) { - GstPolypProbe *c = (GstPolypProbe*) 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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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; - } -} - -GstPolypProbe* gst_pulseprobe_new(GObjectClass *klass, guint prop_id, const gchar *server, gboolean sinks, gboolean sources) { - GstPolypProbe *c = NULL; - - c = g_new(GstPolypProbe, 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(GstPolypProbe* 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(GstPolypProbe *c) { - return c->properties; -} - -gboolean gst_pulseprobe_needs_probe(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *c, const gchar *server) { - g_assert(c); - - gst_pulseprobe_invalidate(c); - - g_free(c->server); - c->server = g_strdup(server); -} diff --git a/src/polypprobe.h b/src/polypprobe.h deleted file mode 100644 index ae936e9..0000000 --- a/src/polypprobe.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef __GST_PULSEPROBE_H__ -#define __GST_PULSEPROBE_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#include - -G_BEGIN_DECLS - -#include -#include -#include - -typedef struct _GstPolypProbe GstPolypProbe; - -struct _GstPolypProbe { - 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; -}; - -GstPolypProbe* gst_pulseprobe_new(GObjectClass *klass, guint prop_id, const gchar *server, gboolean sinks, gboolean sources); -void gst_pulseprobe_free(GstPolypProbe* probe); - -const GList* gst_pulseprobe_get_properties(GstPolypProbe *probe); -gboolean gst_pulseprobe_needs_probe(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); -void gst_pulseprobe_probe_property(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); -GValueArray *gst_pulseprobe_get_values(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); - -void gst_pulseprobe_set_server(GstPolypProbe *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/src/polypsink.c b/src/polypsink.c deleted file mode 100644 index a1f5990..0000000 --- a/src/polypsink.c +++ /dev/null @@ -1,647 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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(GstPolypSink *pulsesink); -static void gst_pulsesink_destroy_context(GstPolypSink *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-int, " - "signed = (boolean) FALSE, " - "width = (int) 8, " - "depth = (int) 8, " - "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-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) { - - GstPolypSink *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(GstPolypSink* 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(GstPolypSink* 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) { - GstPolypSink *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) { - GstPolypSink *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) { - - GstPolypSink *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) { - GstPolypSink *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) { - GstPolypSink *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) { - GstPolypSink *pulsesink = GST_PULSESINK(userdata); - - pa_threaded_mainloop_signal(pulsesink->mainloop, 0); -} - -static void gst_pulsesink_stream_latency_update_cb(pa_stream *s, void *userdata) { - GstPolypSink *pulsesink = GST_PULSESINK(userdata); - - pa_threaded_mainloop_signal(pulsesink->mainloop, 0); -} - -static gboolean gst_pulsesink_open(GstAudioSink *asink) { - GstPolypSink *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) { - GstPolypSink *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; - - GstPolypSink *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, NULL))) { - 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.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) { - GstPolypSink *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) { - GstPolypSink *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) { - GstPolypSink *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) { - GstPolypSink *pulsesink = GST_PULSESINK(userdata); - - pulsesink->operation_success = success; - pa_threaded_mainloop_signal(pulsesink->mainloop, 0); -} - -static void gst_pulsesink_reset(GstAudioSink *asink) { - GstPolypSink *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(GstPolypSink *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) { - GstPolypSink *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'", title, artist); - else if (title) - t = title; - else if (description) - t = description; - else if (location) - t = 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(GstPolypSinkClass), - gst_pulsesink_base_init, - NULL, - gst_pulsesink_class_init, - NULL, - NULL, - sizeof(GstPolypSink), - 0, - gst_pulsesink_init, - }; - - pulsesink_type = g_type_register_static( - GST_TYPE_AUDIO_SINK, - "GstPolypSink", - &pulsesink_info, - 0); - } - - return pulsesink_type; -} diff --git a/src/polypsink.h b/src/polypsink.h deleted file mode 100644 index 84153ad..0000000 --- a/src/polypsink.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef __GST_PULSESINK_H__ -#define __GST_PULSESINK_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#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,GstPolypSink)) -#define GST_PULSESINK_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESINK,GstPolypSinkClass)) -#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 _GstPolypSink GstPolypSink; -typedef struct _GstPolypSinkClass GstPolypSinkClass; - -struct _GstPolypSink { - 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 _GstPolypSinkClass { - GstAudioSinkClass parent_class; -}; - -GType gst_pulsesink_get_type(void); - -G_END_DECLS - -#endif /* __GST_PULSESINK_H__ */ diff --git a/src/polypsrc.c b/src/polypsrc.c deleted file mode 100644 index c830ca8..0000000 --- a/src/polypsrc.c +++ /dev/null @@ -1,616 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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(GstPolypSrc, gst_pulsesrc) - -static void gst_pulsesrc_destroy_stream(GstPolypSrc *pulsesrc); -static void gst_pulsesrc_destroy_context(GstPolypSrc *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) { - GstPolypSrc *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, " - "signed = (boolean) FALSE, " - "width = (int) 8, " - "depth = (int) 8, " - "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-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) { - - GstPolypSrc *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(GstPolypSrc* pulsesrc) { - if (pulsesrc->stream) { - pa_stream_disconnect(pulsesrc->stream); - pa_stream_unref(pulsesrc->stream); - pulsesrc->stream = NULL; - } -} - -static void gst_pulsesrc_destroy_context(GstPolypSrc* 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) { - GstPolypSrc *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) { - - GstPolypSrc *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) { - - GstPolypSrc *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) { - GstPolypSrc *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) { - GstPolypSrc *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) { - GstPolypSrc *pulsesrc = GST_PULSESRC(userdata); - - pa_threaded_mainloop_signal(pulsesrc->mainloop, 0); -} - -static gboolean gst_pulsesrc_open(GstAudioSrc *asrc) { - GstPolypSrc *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) { - GstPolypSrc *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; - - GstPolypSrc *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, NULL))) { - 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) { - GstPolypSrc *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) { - GstPolypSrc *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) { - GstPolypSrc *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) { - GstPolypSrc *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(GstPolypSrcClass), - gst_pulsesrc_base_init, - NULL, - gst_pulsesrc_class_init, - NULL, - NULL, - sizeof(GstPolypSrc), - 0, - gst_pulsesrc_init, - }; - - pulsesrc_type = g_type_register_static( - GST_TYPE_AUDIO_SRC, - "GstPolypSrc", - &pulsesrc_info, - 0); - - gst_pulsesrc_init_interfaces(pulsesrc_type); - } - - return pulsesrc_type; -} diff --git a/src/polypsrc.h b/src/polypsrc.h deleted file mode 100644 index c78072f..0000000 --- a/src/polypsrc.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef __GST_PULSESRC_H__ -#define __GST_PULSESRC_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#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,GstPolypSrc)) -#define GST_PULSESRC_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESRC,GstPolypSrcClass)) -#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 _GstPolypSrc GstPolypSrc; -typedef struct _GstPolypSrcClass GstPolypSrcClass; - -struct _GstPolypSrc { - 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; - - GstPolypMixerCtrl *mixer; -}; - -struct _GstPolypSrcClass { - GstAudioSrcClass parent_class; -}; - -GType gst_pulsesrc_get_type(void); - -G_END_DECLS - -#endif /* __GST_PULSESRC_H__ */ diff --git a/src/polyputil.c b/src/polyputil.c deleted file mode 100644 index ab0c852..0000000 --- a/src/polyputil.c +++ /dev/null @@ -1,63 +0,0 @@ -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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" - -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 - 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]; - - if (pa_get_binary_name(buf, sizeof(buf))) - return g_strdup_printf("gstreamer[%s]", pa_path_get_filename(buf)); - else - return g_strdup("gstreamer"); -} diff --git a/src/polyputil.h b/src/polyputil.h deleted file mode 100644 index 8cd6a5c..0000000 --- a/src/polyputil.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef __GST_PULSEUTIL_H__ -#define __GST_PULSEUTIL_H__ - -/* $Id$ */ - -/*** - This file is part of gst-pulse. - - 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. -***/ - -#include -#include -#include - -gboolean gst_pulse_fill_sample_spec(GstRingBufferSpec *spec, pa_sample_spec *ss); - -gchar *gst_pulse_client_name(void); - -#endif diff --git a/src/pulsemixer.c b/src/pulsemixer.c new file mode 100644 index 0000000..3abba78 --- /dev/null +++ b/src/pulsemixer.c @@ -0,0 +1,246 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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(GstPolypMixer, gst_pulsemixer) +GST_IMPLEMENT_PULSEPROBE_METHODS(GstPolypMixer, gst_pulsemixer) +GST_BOILERPLATE_FULL(GstPolypMixer, gst_pulsemixer, GstElement, GST_TYPE_ELEMENT, gst_pulsemixer_init_interfaces) + +static gboolean gst_pulsemixer_interface_supported(GstImplementsInterface* iface, GType interface_type) { + GstPolypMixer *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(GstPolypMixerClass *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(GstPolypMixer *this, GstPolypMixerClass *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) { + GstPolypMixer *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) { + + GstPolypMixer *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) { + + GstPolypMixer *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 [%s]", this->mixer->type == GST_PULSEMIXER_SINK ? "Playback" : "Capture", this->mixer->description, this->mixer->name); + 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) { + GstPolypMixer *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/src/pulsemixer.h b/src/pulsemixer.h new file mode 100644 index 0000000..fd71d9e --- /dev/null +++ b/src/pulsemixer.h @@ -0,0 +1,66 @@ +#ifndef __GST_PULSEMIXER_H__ +#define __GST_PULSEMIXER_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#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,GstPolypMixer)) +#define GST_PULSEMIXER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSEMIXER,GstPolypMixerClass)) +#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 _GstPolypMixer GstPolypMixer; +typedef struct _GstPolypMixerClass GstPolypMixerClass; + +struct _GstPolypMixer { + GstElement parent; + + gchar *server, *device; + + GstPolypMixerCtrl *mixer; + GstPolypProbe *probe; +}; + +struct _GstPolypMixerClass { + GstElementClass parent_class; +}; + +GType gst_pulsemixer_get_type(void); + +G_END_DECLS + +#endif /* __GST_PULSEMIXER_H__ */ diff --git a/src/pulsemixerctrl.c b/src/pulsemixerctrl.c new file mode 100644 index 0000000..4786004 --- /dev/null +++ b/src/pulsemixerctrl.c @@ -0,0 +1,452 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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) { + GstPolypMixerCtrl *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) { + GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) userdata; + + /* Called from the background thread! */ + + 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) + c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + + 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) { + GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) userdata; + + /* Called from the background thread! */ + + 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) + c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + + 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) { + GstPolypMixerCtrl *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); +} + +static void gst_pulsemixer_ctrl_success_cb(pa_context *context, int success, void *userdata) { + GstPolypMixerCtrl *c = (GstPolypMixerCtrl*) 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(GstPolypMixerCtrl *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(GstPolypMixerCtrl *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; + } +} + +GstPolypMixerCtrl* gst_pulsemixer_ctrl_new(const gchar *server, const gchar *device, GstPolypMixerType type) { + GstPolypMixerCtrl *c = NULL; + + c = g_new(GstPolypMixerCtrl, 1); + c->tracklist = NULL; + c->server = g_strdup(server); + c->device = g_strdup(device); + c->mainloop = NULL; + c->context = NULL; + c->track = NULL; + + 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; + + if (!(gst_pulsemixer_ctrl_open(c))) { + gst_pulsemixer_ctrl_free(c); + return NULL; + } + + return c; +} + +void gst_pulsemixer_ctrl_free(GstPolypMixerCtrl *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(GstPolypMixerCtrl *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; + GstPolypMixerCtrl *c = GST_PULSEMIXER_CTRL(userdata); + + 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); + + g_assert(e == c->time_event); + a->time_free(e); + c->time_event = NULL; +} + +void gst_pulsemixer_ctrl_set_volume(GstPolypMixerCtrl *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; + + if (!c->time_event) { + /* 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 100ms */ + 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), 100000), gst_pulsemixer_ctrl_timeout_event, c); + } + + pa_threaded_mainloop_unlock(c->mainloop); +} + +void gst_pulsemixer_ctrl_get_volume(GstPolypMixerCtrl *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(GstPolypMixerCtrl *c, GstMixerTrack *track, gboolean record) { + g_assert(c); + g_assert(track == c->track); +} + +void gst_pulsemixer_ctrl_set_mute(GstPolypMixerCtrl *c, GstMixerTrack *track, gboolean mute) { + pa_operation *o = NULL; + + g_assert(c); + g_assert(track == c->track); + + pa_threaded_mainloop_lock(c->mainloop); + + if (c->type == GST_PULSEMIXER_SINK) + o = pa_context_set_sink_mute_by_index(c->context, c->index, !!mute, NULL, NULL); + else + o = pa_context_set_source_mute_by_index(c->context, c->index, !!mute, NULL, NULL); + + if (!(o)) { + GST_WARNING("Failed to set device mute status: %s", pa_strerror(pa_context_errno(c->context))); + goto unlock_and_fail; + } + + c->muted = mute; + + if (c->track) + c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + + /* We're not interested in the return code of this operation */ + +unlock_and_fail: + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(c->mainloop); +} diff --git a/src/pulsemixerctrl.h b/src/pulsemixerctrl.h new file mode 100644 index 0000000..f901f56 --- /dev/null +++ b/src/pulsemixerctrl.h @@ -0,0 +1,143 @@ +#ifndef __GST_PULSEMIXERCTRL_H__ +#define __GST_PULSEMIXERCTRL_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define GST_PULSEMIXER_CTRL(obj) ((GstPolypMixerCtrl*)(obj)) + +typedef struct _GstPolypMixerCtrl GstPolypMixerCtrl; + + +typedef enum { + GST_PULSEMIXER_UNKNOWN, + GST_PULSEMIXER_SINK, + GST_PULSEMIXER_SOURCE } +GstPolypMixerType; + +struct _GstPolypMixerCtrl { + 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; + GstPolypMixerType type; + int operation_success; + + GstMixerTrack *track; + + pa_time_event *time_event; +}; + +GstPolypMixerCtrl* gst_pulsemixer_ctrl_new(const gchar *server, const gchar *device, GstPolypMixerType type); +void gst_pulsemixer_ctrl_free(GstPolypMixerCtrl*mixer); + +const GList* gst_pulsemixer_ctrl_list_tracks(GstPolypMixerCtrl *mixer); +void gst_pulsemixer_ctrl_set_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); +void gst_pulsemixer_ctrl_get_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); +void gst_pulsemixer_ctrl_set_mute(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gboolean mute); +void gst_pulsemixer_ctrl_set_record(GstPolypMixerCtrl *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/src/pulsemixertrack.c b/src/pulsemixertrack.c new file mode 100644 index 0000000..3e9b746 --- /dev/null +++ b/src/pulsemixertrack.c @@ -0,0 +1,59 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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(GstPolypMixerTrack, gst_pulsemixer_track, GST_TYPE_MIXER_TRACK); + +static void gst_pulsemixer_track_class_init(GstPolypMixerTrackClass *klass) { +} + +static void gst_pulsemixer_track_init(GstPolypMixerTrack *track) { + track->control = NULL; +} + +GstMixerTrack *gst_pulsemixer_track_new(GstPolypMixerCtrl *control) { + GstPolypMixerTrack *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/src/pulsemixertrack.h b/src/pulsemixertrack.h new file mode 100644 index 0000000..68b1a50 --- /dev/null +++ b/src/pulsemixertrack.h @@ -0,0 +1,56 @@ +#ifndef __GST_PULSEMIXERTRACK_H__ +#define __GST_PULSEMIXERTRACK_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#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, GstPolypMixerTrack)) +#define GST_PULSEMIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_PULSEMIXER_TRACK, GstPolypMixerTrackClass)) +#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 _GstPolypMixerTrack { + GstMixerTrack parent; + GstPolypMixerCtrl *control; +} GstPolypMixerTrack; + +typedef struct _GstPolypMixerTrackClass { + GstMixerTrackClass parent; +} GstPolypMixerTrackClass; + +GType gst_pulsemixer_track_get_type(void); +GstMixerTrack* gst_pulsemixer_track_new(GstPolypMixerCtrl *control); + +G_END_DECLS + +#endif diff --git a/src/pulseprobe.c b/src/pulseprobe.c new file mode 100644 index 0000000..adb5212 --- /dev/null +++ b/src/pulseprobe.c @@ -0,0 +1,322 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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) { + GstPolypProbe *c = (GstPolypProbe*) 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) { + GstPolypProbe *c = (GstPolypProbe*) 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) { + GstPolypProbe *c = (GstPolypProbe*) 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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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; + } +} + +GstPolypProbe* gst_pulseprobe_new(GObjectClass *klass, guint prop_id, const gchar *server, gboolean sinks, gboolean sources) { + GstPolypProbe *c = NULL; + + c = g_new(GstPolypProbe, 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(GstPolypProbe* 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(GstPolypProbe *c) { + return c->properties; +} + +gboolean gst_pulseprobe_needs_probe(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *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(GstPolypProbe *c, const gchar *server) { + g_assert(c); + + gst_pulseprobe_invalidate(c); + + g_free(c->server); + c->server = g_strdup(server); +} diff --git a/src/pulseprobe.h b/src/pulseprobe.h new file mode 100644 index 0000000..ae936e9 --- /dev/null +++ b/src/pulseprobe.h @@ -0,0 +1,115 @@ +#ifndef __GST_PULSEPROBE_H__ +#define __GST_PULSEPROBE_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#include + +G_BEGIN_DECLS + +#include +#include +#include + +typedef struct _GstPolypProbe GstPolypProbe; + +struct _GstPolypProbe { + 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; +}; + +GstPolypProbe* gst_pulseprobe_new(GObjectClass *klass, guint prop_id, const gchar *server, gboolean sinks, gboolean sources); +void gst_pulseprobe_free(GstPolypProbe* probe); + +const GList* gst_pulseprobe_get_properties(GstPolypProbe *probe); +gboolean gst_pulseprobe_needs_probe(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); +void gst_pulseprobe_probe_property(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); +GValueArray *gst_pulseprobe_get_values(GstPolypProbe *probe, guint prop_id, const GParamSpec *pspec); + +void gst_pulseprobe_set_server(GstPolypProbe *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/src/pulsesink.c b/src/pulsesink.c new file mode 100644 index 0000000..a1f5990 --- /dev/null +++ b/src/pulsesink.c @@ -0,0 +1,647 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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(GstPolypSink *pulsesink); +static void gst_pulsesink_destroy_context(GstPolypSink *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-int, " + "signed = (boolean) FALSE, " + "width = (int) 8, " + "depth = (int) 8, " + "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-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) { + + GstPolypSink *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(GstPolypSink* 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(GstPolypSink* 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) { + GstPolypSink *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) { + GstPolypSink *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) { + + GstPolypSink *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) { + GstPolypSink *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) { + GstPolypSink *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) { + GstPolypSink *pulsesink = GST_PULSESINK(userdata); + + pa_threaded_mainloop_signal(pulsesink->mainloop, 0); +} + +static void gst_pulsesink_stream_latency_update_cb(pa_stream *s, void *userdata) { + GstPolypSink *pulsesink = GST_PULSESINK(userdata); + + pa_threaded_mainloop_signal(pulsesink->mainloop, 0); +} + +static gboolean gst_pulsesink_open(GstAudioSink *asink) { + GstPolypSink *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) { + GstPolypSink *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; + + GstPolypSink *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, NULL))) { + 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.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) { + GstPolypSink *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) { + GstPolypSink *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) { + GstPolypSink *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) { + GstPolypSink *pulsesink = GST_PULSESINK(userdata); + + pulsesink->operation_success = success; + pa_threaded_mainloop_signal(pulsesink->mainloop, 0); +} + +static void gst_pulsesink_reset(GstAudioSink *asink) { + GstPolypSink *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(GstPolypSink *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) { + GstPolypSink *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'", title, artist); + else if (title) + t = title; + else if (description) + t = description; + else if (location) + t = 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(GstPolypSinkClass), + gst_pulsesink_base_init, + NULL, + gst_pulsesink_class_init, + NULL, + NULL, + sizeof(GstPolypSink), + 0, + gst_pulsesink_init, + }; + + pulsesink_type = g_type_register_static( + GST_TYPE_AUDIO_SINK, + "GstPolypSink", + &pulsesink_info, + 0); + } + + return pulsesink_type; +} diff --git a/src/pulsesink.h b/src/pulsesink.h new file mode 100644 index 0000000..84153ad --- /dev/null +++ b/src/pulsesink.h @@ -0,0 +1,70 @@ +#ifndef __GST_PULSESINK_H__ +#define __GST_PULSESINK_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#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,GstPolypSink)) +#define GST_PULSESINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESINK,GstPolypSinkClass)) +#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 _GstPolypSink GstPolypSink; +typedef struct _GstPolypSinkClass GstPolypSinkClass; + +struct _GstPolypSink { + 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 _GstPolypSinkClass { + GstAudioSinkClass parent_class; +}; + +GType gst_pulsesink_get_type(void); + +G_END_DECLS + +#endif /* __GST_PULSESINK_H__ */ diff --git a/src/pulsesrc.c b/src/pulsesrc.c new file mode 100644 index 0000000..c830ca8 --- /dev/null +++ b/src/pulsesrc.c @@ -0,0 +1,616 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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(GstPolypSrc, gst_pulsesrc) + +static void gst_pulsesrc_destroy_stream(GstPolypSrc *pulsesrc); +static void gst_pulsesrc_destroy_context(GstPolypSrc *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) { + GstPolypSrc *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, " + "signed = (boolean) FALSE, " + "width = (int) 8, " + "depth = (int) 8, " + "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-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) { + + GstPolypSrc *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(GstPolypSrc* pulsesrc) { + if (pulsesrc->stream) { + pa_stream_disconnect(pulsesrc->stream); + pa_stream_unref(pulsesrc->stream); + pulsesrc->stream = NULL; + } +} + +static void gst_pulsesrc_destroy_context(GstPolypSrc* 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) { + GstPolypSrc *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) { + + GstPolypSrc *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) { + + GstPolypSrc *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) { + GstPolypSrc *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) { + GstPolypSrc *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) { + GstPolypSrc *pulsesrc = GST_PULSESRC(userdata); + + pa_threaded_mainloop_signal(pulsesrc->mainloop, 0); +} + +static gboolean gst_pulsesrc_open(GstAudioSrc *asrc) { + GstPolypSrc *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) { + GstPolypSrc *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; + + GstPolypSrc *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, NULL))) { + 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) { + GstPolypSrc *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) { + GstPolypSrc *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) { + GstPolypSrc *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) { + GstPolypSrc *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(GstPolypSrcClass), + gst_pulsesrc_base_init, + NULL, + gst_pulsesrc_class_init, + NULL, + NULL, + sizeof(GstPolypSrc), + 0, + gst_pulsesrc_init, + }; + + pulsesrc_type = g_type_register_static( + GST_TYPE_AUDIO_SRC, + "GstPolypSrc", + &pulsesrc_info, + 0); + + gst_pulsesrc_init_interfaces(pulsesrc_type); + } + + return pulsesrc_type; +} diff --git a/src/pulsesrc.h b/src/pulsesrc.h new file mode 100644 index 0000000..c78072f --- /dev/null +++ b/src/pulsesrc.h @@ -0,0 +1,75 @@ +#ifndef __GST_PULSESRC_H__ +#define __GST_PULSESRC_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#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,GstPolypSrc)) +#define GST_PULSESRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PULSESRC,GstPolypSrcClass)) +#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 _GstPolypSrc GstPolypSrc; +typedef struct _GstPolypSrcClass GstPolypSrcClass; + +struct _GstPolypSrc { + 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; + + GstPolypMixerCtrl *mixer; +}; + +struct _GstPolypSrcClass { + GstAudioSrcClass parent_class; +}; + +GType gst_pulsesrc_get_type(void); + +G_END_DECLS + +#endif /* __GST_PULSESRC_H__ */ diff --git a/src/pulseutil.c b/src/pulseutil.c new file mode 100644 index 0000000..ab0c852 --- /dev/null +++ b/src/pulseutil.c @@ -0,0 +1,63 @@ +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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" + +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 + 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]; + + if (pa_get_binary_name(buf, sizeof(buf))) + return g_strdup_printf("gstreamer[%s]", pa_path_get_filename(buf)); + else + return g_strdup("gstreamer"); +} diff --git a/src/pulseutil.h b/src/pulseutil.h new file mode 100644 index 0000000..8cd6a5c --- /dev/null +++ b/src/pulseutil.h @@ -0,0 +1,33 @@ +#ifndef __GST_PULSEUTIL_H__ +#define __GST_PULSEUTIL_H__ + +/* $Id$ */ + +/*** + This file is part of gst-pulse. + + 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. +***/ + +#include +#include +#include + +gboolean gst_pulse_fill_sample_spec(GstRingBufferSpec *spec, pa_sample_spec *ss); + +gchar *gst_pulse_client_name(void); + +#endif -- cgit