aboutsummaryrefslogtreecommitdiff
path: root/ext/pulse/pulsedeviceprovider.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/pulse/pulsedeviceprovider.c')
-rw-r--r--ext/pulse/pulsedeviceprovider.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/ext/pulse/pulsedeviceprovider.c b/ext/pulse/pulsedeviceprovider.c
new file mode 100644
index 00000000..8e917009
--- /dev/null
+++ b/ext/pulse/pulsedeviceprovider.c
@@ -0,0 +1,679 @@
+/* GStreamer
+ * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
+ *
+ * gstv4l2deviceprovider.c: V4l2 device probing and monitoring
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; 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 "pulsedeviceprovider.h"
+
+#include <string.h>
+
+#include <gst/gst.h>
+
+#include "pulsesrc.h"
+#include "pulsesink.h"
+#include "pulseutil.h"
+
+
+GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
+#define GST_CAT_DEFAULT pulse_debug
+
+
+static GstDevice *gst_pulse_device_new (guint id,
+ const gchar * device_name, GstCaps * caps, const gchar * internal_name,
+ GstPulseDeviceType type);
+
+G_DEFINE_TYPE (GstPulseDeviceProvider, gst_pulse_device_provider,
+ GST_TYPE_DEVICE_PROVIDER);
+
+static void gst_pulse_device_provider_finalize (GObject * object);
+static void gst_pulse_device_provider_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_pulse_device_provider_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+
+
+static GList *gst_pulse_device_provider_probe (GstDeviceProvider * provider);
+static gboolean gst_pulse_device_provider_start (GstDeviceProvider * provider);
+static void gst_pulse_device_provider_stop (GstDeviceProvider * provider);
+
+enum
+{
+ PROP_0,
+ PROP_SERVER,
+ PROP_CLIENT_NAME,
+ PROP_LAST
+};
+
+
+static void
+gst_pulse_device_provider_class_init (GstPulseDeviceProviderClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
+ gchar *client_name;
+
+ gobject_class->set_property = gst_pulse_device_provider_set_property;
+ gobject_class->get_property = gst_pulse_device_provider_get_property;
+ gobject_class->finalize = gst_pulse_device_provider_finalize;
+
+ dm_class->probe = gst_pulse_device_provider_probe;
+ dm_class->start = gst_pulse_device_provider_start;
+ dm_class->stop = gst_pulse_device_provider_stop;
+
+ 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_PARAM_STATIC_STRINGS));
+
+ client_name = gst_pulse_client_name ();
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name", "Client Name",
+ "The PulseAudio client_name_to_use", client_name,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_READY));
+ g_free (client_name);
+
+ gst_device_provider_class_set_static_metadata (dm_class,
+ "PulseAudio Device Provider", "Sink/Source/Audio",
+ "List and provider PulseAudio source and sink devices",
+ "Olivier Crete <olivier.crete@collabora.com>");
+}
+
+static void
+gst_pulse_device_provider_init (GstPulseDeviceProvider * self)
+{
+ self->client_name = gst_pulse_client_name ();
+}
+
+static void
+gst_pulse_device_provider_finalize (GObject * object)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
+
+ g_free (self->client_name);
+ g_free (self->server);
+
+ G_OBJECT_CLASS (gst_pulse_device_provider_parent_class)->finalize (object);
+}
+
+
+static void
+gst_pulse_device_provider_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_SERVER:
+ g_free (self->server);
+ self->server = g_value_dup_string (value);
+ break;
+ case PROP_CLIENT_NAME:
+ g_free (self->client_name);
+ if (!g_value_get_string (value)) {
+ GST_WARNING_OBJECT (self,
+ "Empty PulseAudio client name not allowed. "
+ "Resetting to default value");
+ self->client_name = gst_pulse_client_name ();
+ } else
+ self->client_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pulse_device_provider_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_SERVER:
+ g_value_set_string (value, self->server);
+ break;
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, self->client_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+context_state_cb (pa_context * c, void *userdata)
+{
+ GstPulseDeviceProvider *self = userdata;
+
+ switch (pa_context_get_state (c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal (self->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static GstDevice *
+new_source (const pa_source_info * info)
+{
+ GstCaps *caps;
+ guint i;
+
+ caps = gst_caps_new_empty ();
+
+ for (i = 0; i < info->n_formats; i++)
+ gst_caps_append (caps, gst_pulse_format_info_to_caps (info->formats[i]));
+
+ return gst_pulse_device_new (info->index, info->description,
+ caps, info->name, GST_PULSE_DEVICE_TYPE_SOURCE);
+}
+
+static GstDevice *
+new_sink (const pa_sink_info * info)
+{
+ GstCaps *caps;
+ guint i;
+
+ caps = gst_caps_new_empty ();
+
+ for (i = 0; i < info->n_formats; i++)
+ gst_caps_append (caps, gst_pulse_format_info_to_caps (info->formats[i]));
+
+ return gst_pulse_device_new (info->index, info->description,
+ caps, info->name, GST_PULSE_DEVICE_TYPE_SINK);
+}
+
+static void
+get_source_info_cb (pa_context * context,
+ const pa_source_info * info, int eol, void *userdata)
+{
+ GstPulseDeviceProvider *self = userdata;
+ GstDevice *dev;
+
+ if (eol) {
+ pa_threaded_mainloop_signal (self->mainloop, 0);
+ return;
+ }
+
+ dev = new_source (info);
+
+ if (dev)
+ gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
+}
+
+static void
+get_sink_info_cb (pa_context * context,
+ const pa_sink_info * info, int eol, void *userdata)
+{
+ GstPulseDeviceProvider *self = userdata;
+ GstDevice *dev;
+
+ if (eol) {
+ pa_threaded_mainloop_signal (self->mainloop, 0);
+ return;
+ }
+
+ dev = new_sink (info);
+
+ if (dev)
+ gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
+}
+
+static void
+context_subscribe_cb (pa_context * context, pa_subscription_event_type_t type,
+ uint32_t idx, void *userdata)
+{
+ GstPulseDeviceProvider *self = userdata;
+ GstDeviceProvider *provider = userdata;
+ pa_subscription_event_type_t facility =
+ type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+ pa_subscription_event_type_t event_type =
+ type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+
+ if (facility != PA_SUBSCRIPTION_EVENT_SOURCE &&
+ facility != PA_SUBSCRIPTION_EVENT_SINK)
+ return;
+
+ if (event_type == PA_SUBSCRIPTION_EVENT_NEW) {
+ /* Microphone in the source output has changed */
+
+ if (facility == PA_SUBSCRIPTION_EVENT_SOURCE)
+ pa_context_get_source_info_by_index (context, idx, get_source_info_cb,
+ self);
+ else if (facility == PA_SUBSCRIPTION_EVENT_SINK)
+ pa_context_get_sink_info_by_index (context, idx, get_sink_info_cb, self);
+ } else if (event_type == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ GstPulseDevice *dev = NULL;
+ GList *item;
+
+ GST_OBJECT_LOCK (self);
+ for (item = provider->devices; item; item = item->next) {
+ dev = item->data;
+
+ if (((facility == PA_SUBSCRIPTION_EVENT_SOURCE &&
+ dev->type == GST_PULSE_DEVICE_TYPE_SOURCE) ||
+ (facility == PA_SUBSCRIPTION_EVENT_SINK &&
+ dev->type == GST_PULSE_DEVICE_TYPE_SINK)) &&
+ dev->device_index == idx) {
+ gst_object_ref (dev);
+ break;
+ }
+ dev = NULL;
+ }
+ GST_OBJECT_UNLOCK (self);
+
+ if (dev) {
+ gst_device_provider_device_remove (GST_DEVICE_PROVIDER (self),
+ GST_DEVICE (dev));
+ gst_object_unref (dev);
+ }
+ }
+}
+
+static void
+get_source_info_list_cb (pa_context * context, const pa_source_info * info,
+ int eol, void *userdata)
+{
+ GList **devices = userdata;
+
+ if (eol)
+ return;
+
+ *devices = g_list_prepend (*devices, gst_object_ref_sink (new_source (info)));
+}
+
+static void
+get_sink_info_list_cb (pa_context * context, const pa_sink_info * info,
+ int eol, void *userdata)
+{
+ GList **devices = userdata;
+
+ if (eol)
+ return;
+
+ *devices = g_list_prepend (*devices, gst_object_ref_sink (new_sink (info)));
+}
+
+static GList *
+gst_pulse_device_provider_probe (GstDeviceProvider * provider)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
+ GList *devices = NULL;
+ pa_mainloop *m = NULL;
+ pa_context *c = NULL;
+ pa_operation *o;
+
+ if (!(m = pa_mainloop_new ()))
+ return NULL;
+
+ if (!(c = pa_context_new (pa_mainloop_get_api (m), self->client_name))) {
+ GST_ERROR_OBJECT (self, "Failed to create context");
+ goto failed;
+ }
+
+ if (pa_context_connect (c, self->server, 0, NULL) < 0) {
+ GST_ERROR_OBJECT (self, "Failed to connect: %s",
+ pa_strerror (pa_context_errno (self->context)));
+ goto failed;
+ }
+
+ for (;;) {
+ pa_context_state_t state;
+
+ state = pa_context_get_state (c);
+
+ if (!PA_CONTEXT_IS_GOOD (state)) {
+ GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("Failed to connect: %s",
+ pa_strerror (pa_context_errno (c))), (NULL));
+ goto failed;
+ }
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ /* Wait until the context is ready */
+ if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
+ goto failed;
+
+ }
+ GST_DEBUG_OBJECT (self, "connected");
+
+ o = pa_context_get_sink_info_list (c, get_sink_info_list_cb, &devices);
+ while (pa_operation_get_state (o) == PA_OPERATION_RUNNING &&
+ pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
+ if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
+ break;
+ }
+ pa_operation_unref (o);
+
+ o = pa_context_get_source_info_list (c, get_source_info_list_cb, &devices);
+ while (pa_operation_get_state (o) == PA_OPERATION_RUNNING &&
+ pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
+ if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
+ break;
+ }
+ pa_operation_unref (o);
+
+ pa_context_disconnect (c);
+ pa_mainloop_free (m);
+
+ return devices;
+
+failed:
+
+ return NULL;
+}
+
+static gboolean
+gst_pulse_device_provider_start (GstDeviceProvider * provider)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
+ pa_operation *initial_operation;
+
+ if (!(self->mainloop = pa_threaded_mainloop_new ())) {
+ GST_ERROR_OBJECT (self, "Could not create pulseaudio mainloop");
+ goto mainloop_failed;
+ }
+ if (pa_threaded_mainloop_start (self->mainloop) < 0) {
+ GST_ERROR_OBJECT (self, "Could not start pulseaudio mainloop");
+ pa_threaded_mainloop_free (self->mainloop);
+ self->mainloop = NULL;
+ goto mainloop_failed;
+ }
+
+ pa_threaded_mainloop_lock (self->mainloop);
+
+ if (!(self->context =
+ pa_context_new (pa_threaded_mainloop_get_api (self->mainloop),
+ self->client_name))) {
+ GST_ERROR_OBJECT (self, "Failed to create context");
+ goto unlock_and_fail;
+ }
+
+ pa_context_set_state_callback (self->context, context_state_cb, self);
+ pa_context_set_subscribe_callback (self->context, context_subscribe_cb, self);
+
+
+ GST_DEBUG_OBJECT (self, "connect to server %s", GST_STR_NULL (self->server));
+
+ if (pa_context_connect (self->context, self->server, 0, NULL) < 0) {
+ GST_ERROR_OBJECT (self, "Failed to connect: %s",
+ pa_strerror (pa_context_errno (self->context)));
+ goto unlock_and_fail;
+ }
+
+ for (;;) {
+ pa_context_state_t state;
+
+ state = pa_context_get_state (self->context);
+
+ if (!PA_CONTEXT_IS_GOOD (state)) {
+ GST_ERROR_OBJECT (self, "Failed to connect: %s",
+ pa_strerror (pa_context_errno (self->context)));
+ goto unlock_and_fail;
+ }
+
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait (self->mainloop);
+ }
+ GST_DEBUG_OBJECT (self, "connected");
+
+ pa_context_subscribe (self->context,
+ PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
+
+ initial_operation = pa_context_get_source_info_list (self->context,
+ get_source_info_cb, self);
+ while (pa_operation_get_state (initial_operation) == PA_OPERATION_RUNNING) {
+ if (!PA_CONTEXT_IS_GOOD (pa_context_get_state ((self->context))))
+ goto cancel_and_fail;
+
+ pa_threaded_mainloop_wait (self->mainloop);
+ }
+ pa_operation_unref (initial_operation);
+
+ initial_operation = pa_context_get_sink_info_list (self->context,
+ get_sink_info_cb, self);
+ if (!initial_operation)
+ goto unlock_and_fail;
+ while (pa_operation_get_state (initial_operation) == PA_OPERATION_RUNNING) {
+ if (!PA_CONTEXT_IS_GOOD (pa_context_get_state ((self->context))))
+ goto cancel_and_fail;
+
+ pa_threaded_mainloop_wait (self->mainloop);
+ }
+ pa_operation_unref (initial_operation);
+
+ pa_threaded_mainloop_unlock (self->mainloop);
+
+ return TRUE;
+
+unlock_and_fail:
+ pa_threaded_mainloop_unlock (self->mainloop);
+ gst_pulse_device_provider_stop (provider);
+ return FALSE;
+
+mainloop_failed:
+ return FALSE;
+
+cancel_and_fail:
+ pa_operation_cancel (initial_operation);
+ pa_operation_unref (initial_operation);
+ goto unlock_and_fail;
+}
+
+static void
+gst_pulse_device_provider_stop (GstDeviceProvider * provider)
+{
+ GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
+
+ pa_threaded_mainloop_stop (self->mainloop);
+
+ if (self->context) {
+ pa_context_disconnect (self->context);
+
+ /* Make sure we don't get any further callbacks */
+ pa_context_set_state_callback (self->context, NULL, NULL);
+ pa_context_set_subscribe_callback (self->context, NULL, NULL);
+
+ pa_context_unref (self->context);
+ self->context = NULL;
+ }
+
+ pa_threaded_mainloop_free (self->mainloop);
+ self->mainloop = NULL;
+}
+
+enum
+{
+ PROP_INTERNAL_NAME = 1,
+};
+
+G_DEFINE_TYPE (GstPulseDevice, gst_pulse_device, GST_TYPE_DEVICE);
+
+static void gst_pulse_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_pulse_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_pulse_device_finalize (GObject * object);
+static GstElement *gst_pulse_device_create_element (GstDevice * device,
+ const gchar * name);
+static gboolean gst_pulse_device_reconfigure_element (GstDevice * device,
+ GstElement * element);
+
+static void
+gst_pulse_device_class_init (GstPulseDeviceClass * klass)
+{
+ GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ dev_class->create_element = gst_pulse_device_create_element;
+ dev_class->reconfigure_element = gst_pulse_device_reconfigure_element;
+
+ object_class->get_property = gst_pulse_device_get_property;
+ object_class->set_property = gst_pulse_device_set_property;
+ object_class->finalize = gst_pulse_device_finalize;
+
+ g_object_class_install_property (object_class, PROP_INTERNAL_NAME,
+ g_param_spec_string ("internal-name", "Internal PulseAudio device name",
+ "The internal name of the PulseAudio device", "",
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_pulse_device_init (GstPulseDevice * device)
+{
+}
+
+static void
+gst_pulse_device_finalize (GObject * object)
+{
+ GstPulseDevice *device = GST_PULSE_DEVICE (object);
+
+ g_free (device->internal_name);
+
+ G_OBJECT_CLASS (gst_pulse_device_parent_class)->finalize (object);
+}
+
+static GstElement *
+gst_pulse_device_create_element (GstDevice * device, const gchar * name)
+{
+ GstPulseDevice *pulse_dev = GST_PULSE_DEVICE (device);
+ GstElement *elem;
+
+ elem = gst_element_factory_make (pulse_dev->element, name);
+ g_object_set (elem, "device", pulse_dev->internal_name, NULL);
+
+ return elem;
+}
+
+static gboolean
+gst_pulse_device_reconfigure_element (GstDevice * device, GstElement * element)
+{
+ GstPulseDevice *pulse_dev = GST_PULSE_DEVICE (device);
+
+ if (!strcmp (pulse_dev->element, "pulsesrc")) {
+ if (!GST_IS_PULSESRC (element))
+ return FALSE;
+ } else if (!strcmp (pulse_dev->element, "pulsesink")) {
+ if (!GST_IS_PULSESINK (element))
+ return FALSE;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ g_object_set (element, "device", pulse_dev->internal_name, NULL);
+
+ return TRUE;
+}
+
+static GstDevice *
+gst_pulse_device_new (guint device_index, const gchar * device_name,
+ GstCaps * caps, const gchar * internal_name, GstPulseDeviceType type)
+{
+ GstPulseDevice *gstdev;
+ const gchar *element;
+ const gchar *klass;
+
+ g_return_val_if_fail (device_name, NULL);
+ g_return_val_if_fail (internal_name, NULL);
+ g_return_val_if_fail (caps, NULL);
+
+
+ switch (type) {
+ case GST_PULSE_DEVICE_TYPE_SOURCE:
+ element = "pulsesrc";
+ klass = "Audio/Source";
+ break;
+ case GST_PULSE_DEVICE_TYPE_SINK:
+ element = "pulsesink";
+ klass = "Audio/Sink";
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+
+ gstdev = g_object_new (GST_TYPE_PULSE_DEVICE,
+ "display-name", device_name, "caps", caps, "device-class", klass,
+ "internal-name", internal_name, NULL);
+
+ gstdev->type = type;
+ gstdev->device_index = device_index;
+ gstdev->element = element;
+
+ return GST_DEVICE (gstdev);
+}
+
+
+static void
+gst_pulse_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPulseDevice *device;
+
+ device = GST_PULSE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_INTERNAL_NAME:
+ g_value_set_string (value, device->internal_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gst_pulse_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPulseDevice *device;
+
+ device = GST_PULSE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_INTERNAL_NAME:
+ device->internal_name = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}