diff options
Diffstat (limited to 'backends/pulse/pulse-monitor.c')
-rw-r--r-- | backends/pulse/pulse-monitor.c | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/backends/pulse/pulse-monitor.c b/backends/pulse/pulse-monitor.c new file mode 100644 index 0000000..21613d0 --- /dev/null +++ b/backends/pulse/pulse-monitor.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library 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 of the licence, or (at your option) any later version. + * + * This library 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 this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> + +#include <pulse/pulseaudio.h> + +#include "pulse-monitor.h" + +struct _PulseMonitorPrivate +{ + pa_context *context; + pa_proplist *proplist; + pa_stream *stream; + guint32 index_source; + guint32 index_sink_input; + gboolean enabled; +}; + +enum { + VALUE, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0, }; + +static void pulse_monitor_class_init (PulseMonitorClass *klass); +static void pulse_monitor_init (PulseMonitor *port); +static void pulse_monitor_finalize (GObject *object); + +G_DEFINE_TYPE (PulseMonitor, pulse_monitor, G_TYPE_OBJECT); + +static gboolean monitor_prepare (PulseMonitor *monitor); +static gboolean monitor_connect_record (PulseMonitor *monitor); +static void monitor_read_cb (pa_stream *stream, + size_t length, + void *userdata); + +static void +pulse_monitor_class_init (PulseMonitorClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = pulse_monitor_finalize; + + signals[VALUE] = + g_signal_new ("value", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PulseMonitorClass, value), + NULL, + NULL, + g_cclosure_marshal_VOID__DOUBLE, + G_TYPE_NONE, + 1, + G_TYPE_DOUBLE); + + g_type_class_add_private (object_class, sizeof (PulseMonitorPrivate)); +} + +static void +pulse_monitor_init (PulseMonitor *monitor) +{ + monitor->priv = G_TYPE_INSTANCE_GET_PRIVATE (monitor, + PULSE_TYPE_MONITOR, + PulseMonitorPrivate); +} + +static void +pulse_monitor_finalize (GObject *object) +{ + PulseMonitor *monitor; + + monitor = PULSE_MONITOR (object); + + if (monitor->priv->stream) + pa_stream_unref (monitor->priv->stream); + + pa_context_unref (monitor->priv->context); + pa_proplist_free (monitor->priv->proplist); + + G_OBJECT_CLASS (pulse_monitor_parent_class)->finalize (object); +} + +PulseMonitor * +pulse_monitor_new (pa_context *context, + pa_proplist *proplist, + guint32 index_source, + guint32 index_sink_input) +{ + PulseMonitor *monitor; + + monitor = g_object_new (PULSE_TYPE_MONITOR, NULL); + + monitor->priv->context = pa_context_ref (context); + monitor->priv->proplist = pa_proplist_copy (proplist); + + monitor->priv->index_source = index_source; + monitor->priv->index_sink_input = index_sink_input; + + return monitor; +} + +gboolean +pulse_monitor_enable (PulseMonitor *monitor) +{ + g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); + + if (!monitor->priv->enabled) { + if (monitor->priv->stream == NULL) + monitor_prepare (monitor); + + if (G_LIKELY (monitor->priv->stream != NULL)) + monitor->priv->enabled = monitor_connect_record (monitor); + } + + return monitor->priv->enabled; +} + +void +pulse_monitor_disable (PulseMonitor *monitor) +{ + g_return_if_fail (PULSE_IS_MONITOR (monitor)); + + if (!monitor->priv->enabled) + return; + + pa_stream_disconnect (monitor->priv->stream); + + monitor->priv->enabled = FALSE; +} + +gboolean +pulse_monitor_is_enabled (PulseMonitor *monitor) +{ + g_return_if_fail (PULSE_IS_MONITOR (monitor)); + + return monitor->priv->enabled; +} + +gboolean +pulse_monitor_update_index (PulseMonitor *monitor, + guint32 index_source, + guint32 index_sink_input) +{ + g_return_val_if_fail (PULSE_IS_MONITOR (monitor), FALSE); + + if (monitor->priv->index_source == index_source && + monitor->priv->index_sink_input == index_sink_input) + return TRUE; + + monitor->priv->index_source = index_source; + monitor->priv->index_sink_input = index_sink_input; + + if (pulse_monitor_is_enabled (monitor)) { + pulse_monitor_disable (monitor); + + /* Unset the Pulse stream to let enable recreate it */ + g_clear_pointer (&monitor->priv->stream, pa_stream_unref); + + pulse_monitor_enable (monitor); + } else if (monitor->priv->stream) { + /* Disabled now but was enabled before and still holds source index */ + g_clear_pointer (&monitor->priv->stream, pa_stream_unref); + } + return TRUE; +} + +static gboolean +monitor_prepare (PulseMonitor *monitor) +{ + pa_sample_spec spec; + + spec.channels = 1; + spec.format = PA_SAMPLE_FLOAT32; + spec.rate = 25; + + monitor->priv->stream = + pa_stream_new_with_proplist (monitor->priv->context, "Peak detect", + &spec, + NULL, + monitor->priv->proplist); + + if (G_UNLIKELY (monitor->priv->stream == NULL)) { + g_warning ("Failed to create PulseAudio monitor: %s", + pa_strerror (pa_context_errno (monitor->priv->context))); + return FALSE; + } + + if (monitor->priv->index_sink_input != PA_INVALID_INDEX) + pa_stream_set_monitor_stream (monitor->priv->stream, + monitor->priv->index_sink_input); + + pa_stream_set_read_callback (monitor->priv->stream, + monitor_read_cb, + monitor); + return TRUE; +} + +static gboolean +monitor_connect_record (PulseMonitor *monitor) +{ + pa_buffer_attr attr; + gchar *name; + int ret; + + attr.maxlength = (guint32) -1; + attr.tlength = 0; + attr.prebuf = 0; + attr.minreq = 0; + attr.fragsize = sizeof (gfloat); + + name = g_strdup_printf ("%u", monitor->priv->index_source); + ret = pa_stream_connect_record (monitor->priv->stream, + name, + &attr, + PA_STREAM_DONT_MOVE | + PA_STREAM_PEAK_DETECT | + PA_STREAM_ADJUST_LATENCY | + PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND); + g_free (name); + + if (ret < 0) { + g_warning ("Failed to connect PulseAudio monitor: %s", pa_strerror (ret)); + return FALSE; + } + return TRUE; +} + +static void +monitor_read_cb (pa_stream *stream, size_t length, void *userdata) +{ + const void *data; + int ret; + + ret = pa_stream_peek (stream, &data, &length); + if (ret < 0) { + g_debug ("Failed to read PulseAudio stream data: %s", pa_strerror (ret)); + return; + } + + if (data) { + gdouble v = ((const gfloat *) data)[length / sizeof (gfloat) - 1]; + + g_signal_emit (G_OBJECT (userdata), + signals[VALUE], + 0, + CLAMP (v, 0, 1)); + } + + /* pa_stream_drop() should not be called if the buffer is empty, but it + * should be called if there is a hole */ + if (length) + pa_stream_drop (stream); +} |