/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #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 spa_list ports; }; struct port_data { struct spa_list link; 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 GstPipeWireDevice * gst_pipewire_device_new (int fd, uint32_t id, uint64_t serial, GstPipeWireDeviceType type, const gchar * element, int priority, const gchar * klass, const gchar * display_name, const GstCaps * caps, const GstStructure * props) { GstPipeWireDevice *gstdev; gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE, "display-name", display_name, "caps", caps, "device-class", klass, "id", id, "serial", serial, "fd", fd, "properties", props, NULL); gstdev->id = id; gstdev->serial = serial; gstdev->type = type; gstdev->element = element; gstdev->priority = priority; return gstdev; } 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; int priority = 0; 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 ("pipewire-proplist", "is-default", G_TYPE_BOOLEAN, FALSE, NULL); if (info->props) { const struct spa_dict_item *item; const char *str; klass = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS); name = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); spa_dict_for_each (item, info->props) { gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); if (spa_streq(item->key, "node.name") && klass) { if (spa_streq(klass, "Audio/Source") && spa_streq(item->value, self->default_audio_source_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); else if (spa_streq(klass, "Audio/Sink") && spa_streq(item->value, self->default_audio_sink_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); else if (spa_streq(klass, "Video/Source") && spa_streq(item->value, self->default_video_source_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); } } if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) priority = atoi(str); } if (klass == NULL) klass = "unknown/unknown"; if (name == NULL) name = "unknown"; gstdev = gst_pipewire_device_new (self->fd, data->id, data->serial, type, element, priority, klass, name, data->caps, props); if (props) gst_structure_free (props); return GST_DEVICE (gstdev); } static int compare_device_session_priority (const void *a, const void *b) { const GstPipeWireDevice *dev_a = a; const GstPipeWireDevice *dev_b = b; if (dev_a->priority < dev_b->priority) return 1; else if (dev_a->priority > dev_b->priority) return -1; else return 0; } static void do_add_nodes(GstPipeWireDeviceProvider *self) { struct node_data *nd; g_autoptr (GList) new_devices = NULL; GList *l; 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) new_devices = g_list_prepend (new_devices, nd->dev); } if (!new_devices) return; new_devices = g_list_sort (new_devices, compare_device_session_priority); for (l = new_devices; l != NULL; l = l->next) { GstDevice *device = l->data; if(self->list_only) { self->devices = g_list_insert_sorted (self->devices, gst_object_ref_sink (device), compare_device_session_priority); } else { gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device); } } } 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 (node_data == NULL) return; 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; if (node_data == NULL) return; 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; struct port_data *pd; GstPipeWireDeviceProvider *self = nd->self; GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self); pw_log_debug("destroy %p", nd); spa_list_consume(pd, &nd->ports, link) { spa_list_remove(&pd->link); pd->node_data = NULL; } 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); if (pd->node_data != NULL) { spa_list_remove(&pd->link); pd->node_data = NULL; } } static const struct pw_proxy_events proxy_port_events = { PW_VERSION_PROXY_EVENTS, .removed = removed_port, .destroy = destroy_port, }; static gboolean is_default_device_name (GstPipeWireDeviceProvider * self, const gchar * name, const gchar * klass, GstPipeWireDeviceType type) { gboolean ret = FALSE; GST_OBJECT_LOCK (self); switch (type) { case GST_PIPEWIRE_DEVICE_TYPE_SINK: if (g_str_has_prefix (klass, "Audio")) ret = !g_strcmp0 (name, self->default_audio_sink_name); break; case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: if (g_str_has_prefix (klass, "Audio")) ret = !g_strcmp0 (name, self->default_audio_source_name); else if (g_str_has_prefix (klass, "Video")) ret = !g_strcmp0 (name, self->default_video_source_name); break; default: GST_ERROR_OBJECT (self, "Unknown pipewire device type!"); break; } GST_OBJECT_UNLOCK (self); return ret; } static void sync_default_devices (GstPipeWireDeviceProvider * self) { GList *tmp, *devices = NULL; for (tmp = GST_DEVICE_PROVIDER_CAST (self)->devices; tmp; tmp = tmp->next) devices = g_list_prepend (devices, gst_object_ref (tmp->data)); for (tmp = devices; tmp; tmp = tmp->next) { GstPipeWireDevice *dev = tmp->data; GstStructure *props = gst_device_get_properties (GST_DEVICE_CAST (dev)); gboolean was_default = FALSE, is_default = FALSE; const gchar *name; gchar *klass = gst_device_get_device_class (GST_DEVICE_CAST (dev)); g_assert (props); gst_structure_get_boolean (props, "is-default", &was_default); name = gst_structure_get_string (props, "node.name"); switch (dev->type) { case GST_PIPEWIRE_DEVICE_TYPE_SINK: is_default = is_default_device_name (self, name, klass, dev->type); break; case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: is_default = is_default_device_name (self, name, klass, dev->type); break; case GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN: break; } if (was_default != is_default) { GstPipeWireDevice *updated_device; gchar *display_name = gst_device_get_display_name (GST_DEVICE_CAST (dev)); GstCaps *caps = gst_device_get_caps (GST_DEVICE_CAST (dev)); gst_structure_set (props, "is-default", G_TYPE_BOOLEAN, is_default, NULL); updated_device = gst_pipewire_device_new (self->fd, dev->id, dev->serial, dev->type, dev->element, dev->priority, klass, display_name, caps, props); gst_device_provider_device_changed (GST_DEVICE_PROVIDER_CAST (self), GST_DEVICE_CAST (updated_device), GST_DEVICE_CAST (dev)); g_free (display_name); gst_caps_unref (caps); } gst_structure_free (props); g_free (klass); } g_list_free_full (devices, gst_object_unref); } static int metadata_property(void *data, uint32_t id, const char *key, const char *type, const char *value) { GstPipeWireDeviceProvider *self = data; char name[1024]; if (value == NULL) return 0; if (spa_streq(key, "default.audio.source")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_audio_source_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_audio_source_name = g_strdup(name); goto sync_devices; } if (spa_streq(key, "default.audio.sink")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_audio_sink_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_audio_sink_name = g_strdup(name); goto sync_devices; } if (spa_streq(key, "default.video.source")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_video_source_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_video_source_name = g_strdup(name); goto sync_devices; } return 0; sync_devices: sync_default_devices (self); return 0; } static const struct pw_metadata_events metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property}; 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_init(&nd->ports); 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; spa_list_append(&nd->ports, &pd->link); 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); } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata) && props) { const char *name; name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); if (name == NULL) return; if (!spa_streq(name, "default")) return; self->metadata = pw_registry_bind(self->registry, id, type, PW_VERSION_METADATA, 0); pw_metadata_add_listener(self->metadata, &self->metadata_listener, &metadata_events, 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); 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); 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); /* core might be NULL if we failed to connect in _start. */ if (self->core != NULL) { pw_thread_loop_lock (self->core->loop); } GST_DEBUG_OBJECT (self, "stopping provider"); if (self->metadata) { spa_hook_remove(&self->metadata_listener); pw_proxy_destroy((struct pw_proxy *)self->metadata); self->metadata = NULL; } g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); if (self->core != NULL) { pw_thread_loop_unlock (self->core->loop); } 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_free (self->default_audio_source_name); g_free (self->default_audio_sink_name); g_free (self->default_video_source_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 "); } static void gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self) { self->client_name = g_strdup(pw_get_client_name ()); self->fd = -1; }