From 30402d8fc7fe2d07f3cce0f6b2b0e6fcc0f2bc0e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 May 2006 23:59:56 +0000 Subject: implement mixer object git-svn-id: file:///home/lennart/svn/public/gst-pulse/trunk@19 bb39ca4e-bce3-0310-b5d4-eea78a553289 --- src/Makefile.am | 5 +- src/plugin.c | 8 +- src/polypmixer.c | 194 +++++++++++++++++++++ src/polypmixer.h | 63 +++++++ src/polypmixerctrl.c | 458 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/polypmixerctrl.h | 148 ++++++++++++++++ src/polypmixertrack.c | 59 +++++++ src/polypmixertrack.h | 56 ++++++ src/polypsink.c | 5 +- src/polypsrc.c | 2 +- 10 files changed, 991 insertions(+), 7 deletions(-) create mode 100644 src/polypmixer.c create mode 100644 src/polypmixer.h create mode 100644 src/polypmixerctrl.c create mode 100644 src/polypmixerctrl.h create mode 100644 src/polypmixertrack.c create mode 100644 src/polypmixertrack.h diff --git a/src/Makefile.am b/src/Makefile.am index bb55802..1010c5f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,10 @@ libgstpolyp_la_SOURCES = \ plugin.c \ polypsink.c polypsink.h \ polypsrc.c polypsrc.h \ - polyputil.c polyputil.h + polyputil.c polyputil.h \ + polypmixer.c polypmixer.h \ + polypmixerctrl.c polypmixerctrl.h \ + polypmixertrack.c polypmixertrack.h libgstpolyp_la_CFLAGS = $(GST_CFLAGS) $(POLYP_CFLAGS) libgstpolyp_la_LIBADD = $(POLYP_LIBS) $(GST_LIBS) -lgstaudio-0.10 diff --git a/src/plugin.c b/src/plugin.c index f034222..bae4773 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -25,6 +25,7 @@ #include "polypsink.h" #include "polypsrc.h" +#include "polypmixer.h" GST_DEBUG_CATEGORY(polyp_debug); @@ -35,6 +36,9 @@ static gboolean plugin_init(GstPlugin* plugin) { if (!gst_element_register(plugin, "polypsrc", GST_RANK_NONE, GST_TYPE_POLYPSRC)) return FALSE; + + if (!gst_element_register(plugin, "polypmixer", GST_RANK_NONE, GST_TYPE_POLYPMIXER)) + return FALSE; GST_DEBUG_CATEGORY_INIT(polyp_debug, "polyp", 0, "Polypaudio elements"); return TRUE; @@ -43,8 +47,8 @@ static gboolean plugin_init(GstPlugin* plugin) { GST_PLUGIN_DEFINE( GST_VERSION_MAJOR, GST_VERSION_MINOR, - "polypsink", - "Polypaudio Element Plugins", + "polypaudio", + "Polypaudio Elements Plugin", plugin_init, VERSION, "LGPL", diff --git a/src/polypmixer.c b/src/polypmixer.c new file mode 100644 index 0000000..dfc8166 --- /dev/null +++ b/src/polypmixer.c @@ -0,0 +1,194 @@ +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; 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 "polypmixer.h" + +enum { + ARG_SERVER = 1, + ARG_DEVICE, + ARG_DEVICE_NAME +}; + +GST_DEBUG_CATEGORY_EXTERN(polyp_debug); +#define GST_CAT_DEFAULT polyp_debug + +GST_IMPLEMENT_POLYPMIXER_CTRL_METHODS(GstPolypMixer, gst_polypmixer) +GST_BOILERPLATE_WITH_INTERFACE(GstPolypMixer, gst_polypmixer, GstElement, GST_TYPE_ELEMENT, GstMixer, GST_TYPE_MIXER, gst_polypmixer) + +static void gst_polypmixer_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void gst_polypmixer_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void gst_polypmixer_finalize(GObject *object); +static GstStateChangeReturn gst_polypmixer_change_state(GstElement *element, GstStateChange transition); + +static void gst_polypmixer_base_init(gpointer g_class) { + + static const GstElementDetails details = + GST_ELEMENT_DETAILS( + "Polypaudio Mixer", + "Generic/Audio", + "Control sound input and output levels for Polypaudio", + "Lennart Poettering"); + + gst_element_class_set_details(GST_ELEMENT_CLASS(g_class), &details); +} + +static void gst_polypmixer_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_polypmixer_change_state); + + gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_polypmixer_finalize); + gobject_class->get_property = GST_DEBUG_FUNCPTR(gst_polypmixer_get_property); + gobject_class->set_property = GST_DEBUG_FUNCPTR(gst_polypmixer_set_property); + + g_object_class_install_property( + gobject_class, + ARG_SERVER, + g_param_spec_string("server", "Server", "The Polypaudio server to connect to", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, + ARG_DEVICE, + g_param_spec_string("device", "Sink/Source", "The Polypaudio sink or source to control", NULL, G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, + ARG_DEVICE_NAME, + g_param_spec_string("device-name", "Device name", "Human-readable name of the sound device", NULL, G_PARAM_READABLE)); +} + +static void gst_polypmixer_init(GstPolypMixer *this, GstPolypMixerClass *g_class) { + this->mixer = NULL; + this->server = NULL; + this->device = NULL; +} + +static void gst_polypmixer_finalize(GObject *object) { + GstPolypMixer *this = GST_POLYPMIXER(object); + + g_free(this->server); + g_free(this->device); + + if (this->mixer) { + gst_polypmixer_ctrl_free(this->mixer); + this->mixer = NULL; + } + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void gst_polypmixer_set_property( + GObject * object, + guint prop_id, + const GValue * value, + GParamSpec * pspec) { + + GstPolypMixer *this = GST_POLYPMIXER(object); + + switch (prop_id) { + case ARG_SERVER: + g_free(this->server); + this->server = g_value_dup_string(value); + break; + + case ARG_DEVICE: + g_free(this->device); + this->device = g_value_dup_string(value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void gst_polypmixer_get_property( + GObject * object, + guint prop_id, + GValue * value, + GParamSpec * pspec) { + + GstPolypMixer *this = GST_POLYPMIXER(object); + + switch(prop_id) { + + case ARG_SERVER: + g_value_set_string(value, this->server); + break; + + case ARG_DEVICE: + g_value_set_string(value, this->device); + break; + + case ARG_DEVICE_NAME: + + if (this->mixer) { + char *t = g_strdup_printf("[%s] %s", this->mixer->name, this->mixer->description); + g_value_set_string(value, t); + g_free(t); + } else + g_value_set_string(value, NULL); + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn gst_polypmixer_change_state(GstElement *element, GstStateChange transition) { + GstPolypMixer *this = GST_POLYPMIXER(element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + + if (!this->mixer) + this->mixer = gst_polypmixer_ctrl_new(this->server, this->device); + + break; + + case GST_STATE_CHANGE_READY_TO_NULL: + + if (this->mixer) { + gst_polypmixer_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 new file mode 100644 index 0000000..63d0609 --- /dev/null +++ b/src/polypmixer.h @@ -0,0 +1,63 @@ +#ifndef __GST_POLYPMIXER_H__ +#define __GST_POLYPMIXER_H__ + +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +#include +#include + +#include "polypmixerctrl.h" + +G_BEGIN_DECLS + +#define GST_TYPE_POLYPMIXER \ + (gst_polypmixer_get_type()) +#define GST_POLYPMIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_POLYPMIXER,GstPolypMixer)) +#define GST_POLYPMIXER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_POLYPMIXER,GstPolypMixerClass)) +#define GST_IS_POLYPMIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_POLYPMIXER)) +#define GST_IS_POLYPMIXER_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_POLYPMIXER)) + +typedef struct _GstPolypMixer GstPolypMixer; +typedef struct _GstPolypMixerClass GstPolypMixerClass; + +struct _GstPolypMixer { + GstElement parent; + + GstPolypMixerCtrl *mixer; + gchar *server, *device; +}; + +struct _GstPolypMixerClass { + GstElementClass parent_class; +}; + +GType gst_polypmixer_get_type(void); + +G_END_DECLS + +#endif /* __GST_POLYPMIXER_H__ */ diff --git a/src/polypmixerctrl.c b/src/polypmixerctrl.c new file mode 100644 index 0000000..122ade8 --- /dev/null +++ b/src/polypmixerctrl.c @@ -0,0 +1,458 @@ +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; 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 "polypmixerctrl.h" +#include "polypmixertrack.h" +#include "polyputil.h" + +GST_DEBUG_CATEGORY_EXTERN(polyp_debug); +#define GST_CAT_DEFAULT polyp_debug + +static void gst_polypmixer_ctrl_context_state_cb(pa_context *context, void *userdata) { + GstPolypMixerCtrl *c = GST_POLYPMIXER_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_polypmixer_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 (eol || !i) + 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_POLYPMIXER_SINK; + + c->operation_success = 1; + + if (c->track) + c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + + pa_threaded_mainloop_signal(c->mainloop, 0); +} + +static void gst_polypmixer_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 (eol || !i) + return; + + g_free(c->name); + g_free(c->description); + c->name = g_strdup(i->description); + 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_POLYPMIXER_SOURCE; + + c->operation_success = 1; + + if (c->track) + c->track->flags = (c->track->flags & ~GST_MIXER_TRACK_MUTE) | (c->muted ? GST_MIXER_TRACK_MUTE : 0); + + pa_threaded_mainloop_signal(c->mainloop, 0); +} + +static void gst_polypmixer_ctrl_subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + GstPolypMixerCtrl *c = GST_POLYPMIXER_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_POLYPMIXER_SINK) + o = pa_context_get_sink_info_by_index(c->context, c->index, gst_polypmixer_ctrl_sink_info_cb, c); + else + o = pa_context_get_source_info_by_index(c->context, c->index, gst_polypmixer_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_polypmixer_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_polypmixer_ctrl_open(GstPolypMixerCtrl *c) { + int e; + gchar *name = gst_polyp_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_polypmixer_ctrl_context_state_cb, c); + pa_context_set_subscribe_callback(c->context, gst_polypmixer_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_EVENT_SOURCE, gst_polypmixer_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 (!(o = pa_context_get_sink_info_by_name(c->context, c->device, gst_polypmixer_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); + } + + if (!c->operation_success) { + + if (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; + } + + /* This device wasn't a sink, hence look for a source */ + + pa_operation_unref(o); + + if (!(o = pa_context_get_source_info_by_name(c->context, c->device, gst_polypmixer_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); + } + + if (!c->operation_success) { + GST_WARNING("Failed to get source info: %s", pa_strerror(pa_context_errno(c->context))); + goto unlock_and_fail; + } + } + + c->track = gst_polypmixer_track_new(c); + c->tracklist = g_list_append(c->tracklist, c->track); + + pa_operation_unref(o); + + 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_polypmixer_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_POLYPMIXER_TRACK(c->track)->control = NULL; + g_object_unref(c->track); + c->track = NULL; + } +} + +GstPolypMixerCtrl* gst_polypmixer_ctrl_new(const gchar *server, const gchar *device) { + 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 = GST_POLYPMIXER_UNKNOWN; + c->name = NULL; + c->description = NULL; + + c->time_event = NULL; + + if (!(gst_polypmixer_ctrl_open(c))) { + gst_polypmixer_ctrl_free(c); + return NULL; + } + + return c; +} + +void gst_polypmixer_ctrl_free(GstPolypMixerCtrl *c) { + g_assert(c); + + gst_polypmixer_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_polypmixer_ctrl_list_tracks(GstPolypMixerCtrl *c) { + g_assert(c); + + return c->tracklist; +} + +static void gst_polypmixer_ctrl_timeout_event(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { + pa_operation *o; + GstPolypMixerCtrl *c = GST_POLYPMIXER_CTRL(userdata); + + if (c->type == GST_POLYPMIXER_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; +} + +static struct timeval* gst_polypmixer_ctrl_elapse(struct timeval *tv, unsigned msec) { + unsigned long secs; + + gettimeofday(tv, NULL); + + secs = (msec/1000); + tv->tv_sec += (unsigned long) secs; + msec -= secs*1000; + + tv->tv_usec += msec*1000; + + /* Normalize */ + while (tv->tv_usec >= 1000000) { + tv->tv_sec++; + tv->tv_usec -= 1000000; + } + + return tv; +} + +void gst_polypmixer_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, gst_polypmixer_ctrl_elapse(&tv, 100), gst_polypmixer_ctrl_timeout_event, c); + } + + pa_threaded_mainloop_unlock(c->mainloop); +} + +void gst_polypmixer_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_polypmixer_ctrl_set_record(GstPolypMixerCtrl *c, GstMixerTrack *track, gboolean record) { + g_assert(c); + g_assert(track == c->track); +} + +void gst_polypmixer_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_POLYPMIXER_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 new file mode 100644 index 0000000..18313c6 --- /dev/null +++ b/src/polypmixerctrl.h @@ -0,0 +1,148 @@ +#ifndef __GST_POLYPMIXERCTRL_H__ +#define __GST_POLYPMIXERCTRL_H__ + +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; 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_POLYPMIXER_CTRL(obj) ((GstPolypMixerCtrl*)(obj)) + +typedef struct _GstPolypMixerCtrl GstPolypMixerCtrl; + +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; + enum { GST_POLYPMIXER_UNKNOWN, GST_POLYPMIXER_SINK, GST_POLYPMIXER_SOURCE } type; + int operation_success; + + GstMixerTrack *track; + + pa_time_event *time_event; +}; + +GstPolypMixerCtrl* gst_polypmixer_ctrl_new(const gchar *server, const gchar *device); +void gst_polypmixer_ctrl_free(GstPolypMixerCtrl *mixer); + +const GList* gst_polypmixer_ctrl_list_tracks(GstPolypMixerCtrl *mixer); +void gst_polypmixer_ctrl_set_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); +void gst_polypmixer_ctrl_get_volume(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gint *volumes); +void gst_polypmixer_ctrl_set_mute(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gboolean mute); +void gst_polypmixer_ctrl_set_record(GstPolypMixerCtrl *mixer, GstMixerTrack *track, gboolean record); + +#define GST_IMPLEMENT_POLYPMIXER_CTRL_METHODS(Type, interface_as_function) \ +static gboolean \ +interface_as_function ## _supported (Type *this, GType iface_type) \ +{ \ + g_assert (iface_type == GST_TYPE_MIXER); \ + \ + return (this->mixer != NULL); \ +} \ + \ +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_polypmixer_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_polypmixer_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_polypmixer_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_polypmixer_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_polypmixer_ctrl_set_mute (this->mixer, track, mute); \ +} \ + \ +static void \ +interface_as_function ## _interface_init (GstMixerClass * klass) \ +{ \ + GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE; \ + \ + /* set up the interface hooks */ \ + 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 new file mode 100644 index 0000000..75101bd --- /dev/null +++ b/src/polypmixertrack.c @@ -0,0 +1,59 @@ +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; 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 "polypmixertrack.h" + +GST_DEBUG_CATEGORY_EXTERN(polyp_debug); +#define GST_CAT_DEFAULT polyp_debug + +G_DEFINE_TYPE(GstPolypMixerTrack, gst_polypmixer_track, GST_TYPE_MIXER_TRACK); + +static void gst_polypmixer_track_class_init(GstPolypMixerTrackClass *klass) { +} + +static void gst_polypmixer_track_init(GstPolypMixerTrack *track) { + track->control = NULL; +} + +GstMixerTrack *gst_polypmixer_track_new(GstPolypMixerCtrl *control) { + GstPolypMixerTrack *polyptrack; + GstMixerTrack *track; + + polyptrack = g_object_new(GST_TYPE_POLYPMIXER_TRACK, NULL); + polyptrack->control = control; + + track = GST_MIXER_TRACK(polyptrack); + track->label = g_strdup("Master"); + track->num_channels = control->channel_map.channels; + track->flags = + (control->type == GST_POLYPMIXER_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 new file mode 100644 index 0000000..b7cc901 --- /dev/null +++ b/src/polypmixertrack.h @@ -0,0 +1,56 @@ +#ifndef __GST_POLYPMIXERTRACK_H__ +#define __GST_POLYPMIXERTRACK_H__ + +/* $Id$ */ + +/*** + This file is part of gst-polyp. + + gst-polyp 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-polyp 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-polyp; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include + +#include "polypmixerctrl.h" + +G_BEGIN_DECLS + +#define GST_TYPE_POLYPMIXER_TRACK \ + (gst_polypmixer_track_get_type()) +#define GST_POLYPMIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_POLYPMIXER_TRACK, GstPolypMixerTrack)) +#define GST_POLYPMIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_POLYPMIXER_TRACK, GstPolypMixerTrackClass)) +#define GST_IS_POLYPMIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_POLYPMIXER_TRACK)) +#define GST_IS_POLYPMIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_POLYPMIXER_TRACK)) + +typedef struct _GstPolypMixerTrack { + GstMixerTrack parent; + GstPolypMixerCtrl *control; +} GstPolypMixerTrack; + +typedef struct _GstPolypMixerTrackClass { + GstMixerTrackClass parent; +} GstPolypMixerTrackClass; + +GType gst_polypmixer_track_get_type(void); +GstMixerTrack* gst_polypmixer_track_new(GstPolypMixerCtrl *control); + +G_END_DECLS + +#endif diff --git a/src/polypsink.c b/src/polypsink.c index e154e37..f64685d 100644 --- a/src/polypsink.c +++ b/src/polypsink.c @@ -36,8 +36,7 @@ GST_DEBUG_CATEGORY_EXTERN(polyp_debug); #define GST_CAT_DEFAULT polyp_debug enum { - ARG_0, - ARG_SERVER, + ARG_SERVER = 1, ARG_SINK, }; @@ -109,7 +108,7 @@ static void gst_polypsink_base_init(gpointer g_class) { static const GstElementDetails details = GST_ELEMENT_DETAILS( - "Polypaudio audio sink", + "Polypaudio Audio Sink", "Sink/Audio", "Plays audio to a Polypaudio server", "Lennart Poettering"); diff --git a/src/polypsrc.c b/src/polypsrc.c index a790e6d..00d5657 100644 --- a/src/polypsrc.c +++ b/src/polypsrc.c @@ -106,7 +106,7 @@ static void gst_polypsrc_base_init(gpointer g_class) { static const GstElementDetails details = GST_ELEMENT_DETAILS( - "Polypaudio audio source", + "Polypaudio Audio Source", "Source/Audio", "Captures audio from a Polypaudio server", "Lennart Poettering"); -- cgit