diff options
Diffstat (limited to 'src/gst/gstpipewiredeviceprovider.c')
-rw-r--r-- | src/gst/gstpipewiredeviceprovider.c | 754 |
1 files changed, 754 insertions, 0 deletions
diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c new file mode 100644 index 0000000..598a7c5 --- /dev/null +++ b/src/gst/gstpipewiredeviceprovider.c @@ -0,0 +1,754 @@ +/* GStreamer + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <string.h> + +#include <spa/utils/result.h> +#include <spa/utils/string.h> + +#include <gst/gst.h> + +#include "gstpipewireformat.h" +#include "gstpipewiredeviceprovider.h" +#include "gstpipewiresrc.h" +#include "gstpipewiresink.h" + +GST_DEBUG_CATEGORY_EXTERN (pipewire_debug); +#define GST_CAT_DEFAULT pipewire_debug + +G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE); + +enum +{ + PROP_ID = 1, + PROP_SERIAL, + PROP_FD_DEVICE, +}; + +static GstElement * +gst_pipewire_device_create_element (GstDevice * device, const gchar * name) +{ + GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); + GstElement *elem; + gchar *serial_str; + + elem = gst_element_factory_make (pipewire_dev->element, name); + + serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial); + g_object_set (elem, "target-object", serial_str, + "fd", pipewire_dev->fd, NULL); + g_free (serial_str); + + return elem; +} + +static gboolean +gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element) +{ + GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); + gchar *serial_str; + + if (spa_streq(pipewire_dev->element, "pipewiresrc")) { + if (!GST_IS_PIPEWIRE_SRC (element)) + return FALSE; + } else if (spa_streq(pipewire_dev->element, "pipewiresink")) { + if (!GST_IS_PIPEWIRE_SINK (element)) + return FALSE; + } else { + g_assert_not_reached (); + } + + serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial); + g_object_set (element, "target-object", serial_str, + "fd", pipewire_dev->fd, NULL); + g_free (serial_str); + + return TRUE; +} + + +static void +gst_pipewire_device_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPipeWireDevice *device; + + device = GST_PIPEWIRE_DEVICE_CAST (object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint (value, device->id); + break; + case PROP_SERIAL: + g_value_set_uint64 (value, device->serial); + break; + case PROP_FD_DEVICE: + g_value_set_int (value, device->fd); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPipeWireDevice *device; + + device = GST_PIPEWIRE_DEVICE_CAST (object); + + switch (prop_id) { + case PROP_ID: + device->id = g_value_get_uint (value); + break; + case PROP_SERIAL: + device->serial = g_value_get_uint64 (value); + break; + case PROP_FD_DEVICE: + device->fd = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_finalize (GObject * object) +{ + G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object); +} + +static void +gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass) +{ + GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + dev_class->create_element = gst_pipewire_device_create_element; + dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element; + + object_class->get_property = gst_pipewire_device_get_property; + object_class->set_property = gst_pipewire_device_set_property; + object_class->finalize = gst_pipewire_device_finalize; + + g_object_class_install_property (object_class, PROP_ID, + g_param_spec_uint ("id", "Id", + "The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_SERIAL, + g_param_spec_uint64 ("serial", "Serial", + "The internal serial of the PipeWire device", 0, G_MAXUINT64, SPA_ID_INVALID, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, + PROP_FD_DEVICE, + g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gst_pipewire_device_init (GstPipeWireDevice * device) +{ +} + +G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider, + GST_TYPE_DEVICE_PROVIDER); + +enum +{ + PROP_0, + PROP_CLIENT_NAME, + PROP_FD, + PROP_LAST +}; + +struct node_data { + struct spa_list link; + GstPipeWireDeviceProvider *self; + struct pw_node *proxy; + struct spa_hook proxy_listener; + uint32_t id; + uint64_t serial; + struct spa_hook node_listener; + struct pw_node_info *info; + GstCaps *caps; + GstDevice *dev; +}; + +struct port_data { + struct node_data *node_data; + struct pw_port *proxy; + struct spa_hook proxy_listener; + uint32_t id; + uint64_t serial; + struct spa_hook port_listener; +}; + +static struct node_data *find_node_data(struct spa_list *nodes, uint32_t id) +{ + struct node_data *n; + spa_list_for_each(n, nodes, link) { + if (n->id == id) + return n; + } + return NULL; +} + +static GstDevice * +new_node (GstPipeWireDeviceProvider *self, struct node_data *data) +{ + GstStructure *props; + const gchar *klass = NULL, *name = NULL; + GstPipeWireDeviceType type; + const struct pw_node_info *info = data->info; + const gchar *element = NULL; + GstPipeWireDevice *gstdev; + + if (info->max_input_ports > 0 && info->max_output_ports == 0) { + type = GST_PIPEWIRE_DEVICE_TYPE_SINK; + element = "pipewiresink"; + } else if (info->max_output_ports > 0 && info->max_input_ports == 0) { + type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE; + element = "pipewiresrc"; + } else { + return NULL; + } + + props = gst_structure_new_empty ("pipewire-proplist"); + if (info->props) { + const struct spa_dict_item *item; + spa_dict_for_each (item, info->props) + gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); + + klass = spa_dict_lookup (info->props, PW_KEY_MEDIA_CLASS); + name = spa_dict_lookup (info->props, PW_KEY_NODE_DESCRIPTION); + } + if (klass == NULL) + klass = "unknown/unknown"; + if (name == NULL) + name = "unknown"; + + gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE, + "display-name", name, "caps", data->caps, "device-class", klass, + "id", data->id, "serial", data->serial, "fd", self->fd, + "properties", props, NULL); + + gstdev->id = data->id; + gstdev->serial = data->serial; + gstdev->type = type; + gstdev->element = element; + if (props) + gst_structure_free (props); + + return GST_DEVICE (gstdev); +} + +static void do_add_nodes(GstPipeWireDeviceProvider *self) +{ + struct node_data *nd; + + spa_list_for_each(nd, &self->nodes, link) { + if (nd->dev != NULL) + continue; + pw_log_info("add node %d", nd->id); + nd->dev = new_node (self, nd); + if (nd->dev) { + if(self->list_only) + self->devices = g_list_prepend (self->devices, gst_object_ref_sink (nd->dev)); + else + gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), nd->dev); + } + } +} + +static void resync(GstPipeWireDeviceProvider *self) +{ + self->seq = pw_core_sync(self->core->core, PW_ID_CORE, self->seq); + pw_log_debug("resync %d", self->seq); +} + +static void +on_core_done (void *data, uint32_t id, int seq) +{ + GstPipeWireDeviceProvider *self = data; + + pw_log_debug("check %d %d", seq, self->seq); + if (id == PW_ID_CORE && seq == self->seq) { + do_add_nodes(self); + self->end = true; + if (self->core) + pw_thread_loop_signal (self->core->loop, FALSE); + } +} + + +static void +on_core_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + GstPipeWireDeviceProvider *self = data; + + pw_log_warn("error id:%u seq:%d res:%d (%s): %s", + id, seq, res, spa_strerror(res), message); + + if (id == PW_ID_CORE) { + self->error = res; + } + pw_thread_loop_signal(self->core->loop, FALSE); +} + +static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = on_core_done, + .error = on_core_error, +}; + +static void port_event_info(void *data, const struct pw_port_info *info) +{ + struct port_data *port_data = data; + struct node_data *node_data = port_data->node_data; + uint32_t i; + + pw_log_debug("%p", port_data); + + if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t id = info->params[i].id; + + if (id == SPA_PARAM_EnumFormat && + info->params[i].flags & SPA_PARAM_INFO_READ && + node_data->caps == NULL) { + node_data->caps = gst_caps_new_empty (); + pw_port_enum_params(port_data->proxy, 0, id, 0, UINT32_MAX, NULL); + resync(node_data->self); + } + } + } +} + +static void port_event_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, const struct spa_pod *param) +{ + struct port_data *port_data = data; + struct node_data *node_data = port_data->node_data; + GstCaps *c1; + + c1 = gst_caps_from_format (param); + if (c1 && node_data->caps) + gst_caps_append (node_data->caps, c1); +} + +static const struct pw_port_events port_events = { + PW_VERSION_PORT_EVENTS, + .info = port_event_info, + .param = port_event_param +}; + +static void node_event_info(void *data, const struct pw_node_info *info) +{ + struct node_data *node_data = data; + uint32_t i; + + pw_log_debug("%p", node_data->proxy); + + info = node_data->info = pw_node_info_update(node_data->info, info); + + if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { + for (i = 0; i < info->n_params; i++) { + uint32_t id = info->params[i].id; + + if (id == SPA_PARAM_EnumFormat && + info->params[i].flags & SPA_PARAM_INFO_READ && + node_data->caps == NULL) { + node_data->caps = gst_caps_new_empty (); + pw_node_enum_params(node_data->proxy, 0, id, 0, UINT32_MAX, NULL); + resync(node_data->self); + } + } + } +} + +static void node_event_param(void *data, int seq, uint32_t id, + uint32_t index, uint32_t next, const struct spa_pod *param) +{ + struct node_data *node_data = data; + GstCaps *c1; + + c1 = gst_caps_from_format (param); + if (c1 && node_data->caps) + gst_caps_append (node_data->caps, c1); +} + +static const struct pw_node_events node_events = { + PW_VERSION_NODE_EVENTS, + .info = node_event_info, + .param = node_event_param +}; + +static void +removed_node (void *data) +{ + struct node_data *nd = data; + pw_proxy_destroy((struct pw_proxy*)nd->proxy); +} + +static void +destroy_node (void *data) +{ + struct node_data *nd = data; + GstPipeWireDeviceProvider *self = nd->self; + GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self); + + pw_log_debug("destroy %p", nd); + + if (nd->dev != NULL) { + gst_device_provider_device_remove (provider, GST_DEVICE (nd->dev)); + } + if (nd->caps) + gst_caps_unref(nd->caps); + if (nd->info) + pw_node_info_free(nd->info); + + spa_list_remove(&nd->link); +} + +static const struct pw_proxy_events proxy_node_events = { + PW_VERSION_PROXY_EVENTS, + .removed = removed_node, + .destroy = destroy_node, +}; + +static void +removed_port (void *data) +{ + struct port_data *pd = data; + pw_proxy_destroy((struct pw_proxy*)pd->proxy); +} + +static void +destroy_port (void *data) +{ + struct port_data *pd = data; + pw_log_debug("destroy %p", pd); +} + +static const struct pw_proxy_events proxy_port_events = { + PW_VERSION_PROXY_EVENTS, + .removed = removed_port, + .destroy = destroy_port, +}; + +static void registry_event_global(void *data, uint32_t id, uint32_t permissions, + const char *type, uint32_t version, + const struct spa_dict *props) +{ + GstPipeWireDeviceProvider *self = data; + GstDeviceProvider *provider = (GstDeviceProvider*)self; + struct node_data *nd; + const char *str; + + if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { + struct pw_node *node; + + node = pw_registry_bind(self->registry, + id, type, PW_VERSION_NODE, sizeof(*nd)); + if (node == NULL) + goto no_mem; + + if (props != NULL) { + str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH); + if (str != NULL) { + if (g_str_has_prefix(str, "alsa:")) + gst_device_provider_hide_provider (provider, "pulsedeviceprovider"); + else if (g_str_has_prefix(str, "v4l2:")) + gst_device_provider_hide_provider (provider, "v4l2deviceprovider"); + else if (g_str_has_prefix(str, "libcamera:")) + gst_device_provider_hide_provider (provider, "libcameraprovider"); + } + } + + nd = pw_proxy_get_user_data((struct pw_proxy*)node); + nd->self = self; + nd->proxy = node; + nd->id = id; + if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &nd->serial, 0)) + nd->serial = SPA_ID_INVALID; + spa_list_append(&self->nodes, &nd->link); + pw_node_add_listener(node, &nd->node_listener, &node_events, nd); + pw_proxy_add_listener((struct pw_proxy*)node, &nd->proxy_listener, &proxy_node_events, nd); + resync(self); + } + else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) { + struct pw_port *port; + struct port_data *pd; + + if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) + return; + + if ((nd = find_node_data(&self->nodes, atoi(str))) == NULL) + return; + + port = pw_registry_bind(self->registry, + id, type, PW_VERSION_PORT, sizeof(*pd)); + if (port == NULL) + goto no_mem; + + pd = pw_proxy_get_user_data((struct pw_proxy*)port); + pd->node_data = nd; + pd->proxy = port; + pd->id = id; + if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &pd->serial, 0)) + pd->serial = SPA_ID_INVALID; + pw_port_add_listener(port, &pd->port_listener, &port_events, pd); + pw_proxy_add_listener((struct pw_proxy*)port, &pd->proxy_listener, &proxy_port_events, pd); + resync(self); + } + + return; + +no_mem: + GST_ERROR_OBJECT(self, "failed to create proxy"); + return; +} + +static void registry_event_global_remove(void *data, uint32_t id) +{ +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, + .global_remove = registry_event_global_remove, +}; + +static GList * +gst_pipewire_device_provider_probe (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + + GST_DEBUG_OBJECT (self, "starting probe"); + + self->core = gst_pipewire_core_get(self->fd); + if (self->core == NULL) { + GST_ERROR_OBJECT (self, "Failed to connect"); + goto failed; + } + + GST_DEBUG_OBJECT (self, "connected"); + + pw_thread_loop_lock (self->core->loop); + + spa_list_init(&self->nodes); + spa_list_init(&self->pending); + self->end = FALSE; + self->error = 0; + self->list_only = TRUE; + self->devices = NULL; + self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0); + + pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self); + pw_registry_add_listener(self->registry, &self->registry_listener, ®istry_events, self); + + resync(self); + + for (;;) { + if (self->error < 0) + break; + if (self->end) + break; + pw_thread_loop_wait (self->core->loop); + } + + GST_DEBUG_OBJECT (self, "disconnect"); + + g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); + pw_thread_loop_unlock (self->core->loop); + g_clear_pointer (&self->core, gst_pipewire_core_release); + + return self->devices; + +failed: + return NULL; +} + +static gboolean +gst_pipewire_device_provider_start (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + + GST_DEBUG_OBJECT (self, "starting provider"); + + self->core = gst_pipewire_core_get(self->fd); + if (self->core == NULL) { + GST_ERROR_OBJECT (self, "Failed to connect"); + goto failed; + } + + GST_DEBUG_OBJECT (self, "connected"); + + pw_thread_loop_lock (self->core->loop); + + spa_list_init(&self->nodes); + spa_list_init(&self->pending); + self->end = FALSE; + self->error = 0; + self->list_only = FALSE; + self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0); + + pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self); + pw_registry_add_listener(self->registry, &self->registry_listener, ®istry_events, self); + + resync(self); + + for (;;) { + if (self->error < 0) + break; + if (self->end) + break; + pw_thread_loop_wait (self->core->loop); + } + + GST_DEBUG_OBJECT (self, "started"); + + pw_thread_loop_unlock (self->core->loop); + + return TRUE; + +failed: + return TRUE; +} + +static void +gst_pipewire_device_provider_stop (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + + GST_DEBUG_OBJECT (self, "stopping provider"); + + g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); + g_clear_pointer (&self->core, gst_pipewire_core_release); +} + +static void +gst_pipewire_device_provider_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + switch (prop_id) { + case PROP_CLIENT_NAME: + g_free (self->client_name); + if (!g_value_get_string (value)) { + GST_WARNING_OBJECT (self, + "Empty PipeWire client name not allowed. " + "Resetting to default value"); + self->client_name = g_strdup(pw_get_client_name ()); + } else + self->client_name = g_value_dup_string (value); + break; + + case PROP_FD: + self->fd = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_provider_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + switch (prop_id) { + case PROP_CLIENT_NAME: + g_value_set_string (value, self->client_name); + break; + + case PROP_FD: + g_value_set_int (value, self->fd); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_provider_finalize (GObject * object) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + g_free (self->client_name); + + G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object); +} + +static void +gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass); + + gobject_class->set_property = gst_pipewire_device_provider_set_property; + gobject_class->get_property = gst_pipewire_device_provider_get_property; + gobject_class->finalize = gst_pipewire_device_provider_finalize; + + dm_class->probe = gst_pipewire_device_provider_probe; + dm_class->start = gst_pipewire_device_provider_start; + dm_class->stop = gst_pipewire_device_provider_stop; + + g_object_class_install_property (gobject_class, + PROP_CLIENT_NAME, + g_param_spec_string ("client-name", "Client Name", + "The PipeWire client_name_to_use", pw_get_client_name (), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + + g_object_class_install_property (gobject_class, + PROP_FD, + g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); + + gst_device_provider_class_set_static_metadata (dm_class, + "PipeWire Device Provider", "Sink/Source/Audio/Video", + "List and provide PipeWire source and sink devices", + "Wim Taymans <wim.taymans@gmail.com>"); +} + +static void +gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self) +{ + self->client_name = g_strdup(pw_get_client_name ()); + self->fd = -1; +} |