summaryrefslogtreecommitdiff
path: root/backends/pulse/pulse-monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'backends/pulse/pulse-monitor.c')
-rw-r--r--backends/pulse/pulse-monitor.c271
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);
+}