diff --git a/modules/meson.build b/modules/meson.build index 328c4d0e803a8987c2b42dca5ed452ac7086b023..5ec0544df3d85984a1a7ef34139be6515b00b4f9 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -138,6 +138,17 @@ shared_library( dependencies : [wp_dep, pipewire_dep], ) +shared_library( + 'wireplumber-module-si-audio-softdsp-endpoint', + [ + 'module-si-audio-softdsp-endpoint.c', + ], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-audio-softdsp-endpoint"'], + install : true, + install_dir : wireplumber_module_dir, + dependencies : [wp_dep, pipewire_dep], +) + shared_library( 'wireplumber-module-si-standard-link', [ diff --git a/modules/module-si-audio-softdsp-endpoint.c b/modules/module-si-audio-softdsp-endpoint.c new file mode 100644 index 0000000000000000000000000000000000000000..37bfa85362778585f0ce23ab3fecc10f34a82920 --- /dev/null +++ b/modules/module-si-audio-softdsp-endpoint.c @@ -0,0 +1,350 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> +#include <pipewire/pipewire.h> +#include <pipewire/extensions/session-manager/keys.h> +#include <spa/utils/names.h> + +enum { + STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START, + STEP_ENSURE_ADAPTER_FEATURES, + STEP_ENSURE_CONVERT_FEATURES, +}; + +struct _WpSiAudioSoftdspEndpoint +{ + WpSessionBin parent; + + /* configuration */ + WpSessionItem *adapter; + guint num_streams; + + guint activated_streams; +}; + +static void si_audio_softdsp_endpoint_endpoint_init (WpSiEndpointInterface * iface); + +G_DECLARE_FINAL_TYPE(WpSiAudioSoftdspEndpoint, si_audio_softdsp_endpoint, + WP, SI_AUDIO_SOFTDSP_ENDPOINT, WpSessionBin) +G_DEFINE_TYPE_WITH_CODE (WpSiAudioSoftdspEndpoint, si_audio_softdsp_endpoint, WP_TYPE_SESSION_BIN, + G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ENDPOINT, si_audio_softdsp_endpoint_endpoint_init)) + +static GVariant * +si_audio_softdsp_endpoint_get_registration_info (WpSiEndpoint * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + + return wp_si_endpoint_get_registration_info (WP_SI_ENDPOINT (self->adapter)); +} + +static WpProperties * +si_audio_softdsp_endpoint_get_properties (WpSiEndpoint * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + + return wp_si_endpoint_get_properties (WP_SI_ENDPOINT (self->adapter)); +} + +static guint +si_audio_softdsp_endpoint_get_n_streams (WpSiEndpoint * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + return self->num_streams; +} + +static WpSiStream * +si_audio_softdsp_endpoint_get_stream (WpSiEndpoint * item, guint index) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + g_autoptr (WpIterator) it = wp_session_bin_iterate (WP_SESSION_BIN (self)); + g_auto (GValue) val = G_VALUE_INIT; + + /* TODO: do not asume the items are always sorted */ + guint i = 0; + for (; wp_iterator_next (it, &val); g_value_unset (&val)) { + if (index + 1 == i) + return g_value_get_object (&val); + i++; + } + + return NULL; +} + +static void +si_audio_softdsp_endpoint_endpoint_init (WpSiEndpointInterface * iface) +{ + iface->get_registration_info = si_audio_softdsp_endpoint_get_registration_info; + iface->get_properties = si_audio_softdsp_endpoint_get_properties; + iface->get_n_streams = si_audio_softdsp_endpoint_get_n_streams; + iface->get_stream = si_audio_softdsp_endpoint_get_stream; +} + +static void +si_audio_softdsp_endpoint_init (WpSiAudioSoftdspEndpoint * self) +{ + self->activated_streams = 0; +} + +static void +si_audio_softdsp_endpoint_reset (WpSessionItem * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + + /* unexport & deactivate first */ + WP_SESSION_ITEM_CLASS (si_audio_softdsp_endpoint_parent_class)->reset (item); + + g_clear_object (&self->adapter); + self->num_streams = 0; + self->activated_streams = 0; + + wp_session_item_clear_flag (item, WP_SI_FLAG_CONFIGURED); +} + +static gpointer +si_audio_softdsp_endpoint_get_associated_proxy (WpSessionItem * item, + GType proxy_type) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + + if (proxy_type == WP_TYPE_NODE) + return wp_session_item_get_associated_proxy (self->adapter, proxy_type); + + return WP_SESSION_ITEM_CLASS ( + si_audio_softdsp_endpoint_parent_class)->get_associated_proxy ( + item, proxy_type); +} + +static WpNode * +si_audio_softdsp_endpoint_create_convert_node (WpSiAudioSoftdspEndpoint *self, + guint index) +{ + g_autoptr (WpNode) node = NULL; + g_autoptr (WpCore) core = NULL; + g_autoptr (WpProperties) props = NULL; + + /* Get the node and core */ + node = wp_session_item_get_associated_proxy (self->adapter, WP_TYPE_NODE); + core = wp_proxy_get_core (WP_PROXY (node)); + + /* Create the convert properties based on the adapter properties */ + props = wp_properties_copy (wp_proxy_get_properties (WP_PROXY (node))); + wp_properties_setf (props, PW_KEY_OBJECT_PATH, "%p/convert", self); + wp_properties_setf (props, PW_KEY_NODE_NAME, "%p/convert/%d", self, index); + wp_properties_set (props, PW_KEY_MEDIA_CLASS, "Audio/Convert"); + wp_properties_set (props, SPA_KEY_FACTORY_NAME, SPA_NAME_AUDIO_CONVERT); + + /* Create the node */ + return wp_node_new_from_factory (core, "spa-node-factory", + g_steal_pointer (&props)); +} + +static gboolean +si_audio_softdsp_endpoint_configure (WpSessionItem * item, GVariant * args) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + guint64 adapter_i; + g_autoptr (WpProperties) props = NULL; + g_autoptr (WpNode) node = NULL; + g_autoptr (WpCore) core = NULL; + g_autoptr (WpSessionItem) adapter = NULL; + GVariantBuilder b; + + if (wp_session_item_get_flags (item) & (WP_SI_FLAG_ACTIVATING | WP_SI_FLAG_ACTIVE)) + return FALSE; + + /* reset previous config */ + si_audio_softdsp_endpoint_reset (WP_SESSION_ITEM (self)); + + /* get the adapter and its core */ + if (!g_variant_lookup (args, "adapter", "t", &adapter_i)) + return FALSE; + g_return_val_if_fail (WP_IS_SI_ENDPOINT (GUINT_TO_POINTER (adapter_i)), FALSE); + self->adapter = g_object_ref (GUINT_TO_POINTER (adapter_i)); + node = wp_session_item_get_associated_proxy (self->adapter, WP_TYPE_NODE); + core = wp_proxy_get_core (WP_PROXY (node)); + + /* add the adapter into the bin */ + wp_session_bin_add (WP_SESSION_BIN (self), g_object_ref (self->adapter)); + + /* get the number of streams */ + g_variant_lookup (args, "num-streams", "u", &self->num_streams); + + /* create, configure and add the convert items into the bin */ + for (guint i = 0; i < self->num_streams; i++) { + g_autoptr (WpSessionItem) convert = + wp_session_item_make (core, "si-convert"); + g_autoptr (WpNode) convert_node = + si_audio_softdsp_endpoint_create_convert_node (self, i); + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", + "node", g_variant_new_uint64 ((guint64) convert_node)); + g_variant_builder_add (&b, "{sv}", + "target", g_variant_new_uint64 ((guint64) self->adapter)); + wp_session_item_configure (convert, g_variant_builder_end (&b)); + wp_session_bin_add (WP_SESSION_BIN (self), g_steal_pointer (&convert)); + } + + wp_session_item_set_flag (item, WP_SI_FLAG_CONFIGURED); + + return TRUE; +} + +static GVariant * +si_audio_softdsp_endpoint_get_configuration (WpSessionItem * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + GVariantBuilder b; + + /* Set the properties */ + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", + "adapter", g_variant_new_uint64 ((guint64) self->adapter)); + g_variant_builder_add (&b, "{sv}", + "num-streams", g_variant_new_uint32 (self->num_streams)); + return g_variant_builder_end (&b); +} + +static guint +si_audio_softdsp_endpoint_activate_get_next_step (WpSessionItem * item, + WpTransition * transition, guint step) +{ + switch (step) { + case WP_TRANSITION_STEP_NONE: + return STEP_VERIFY_CONFIG; + + case STEP_VERIFY_CONFIG: + return STEP_ENSURE_ADAPTER_FEATURES; + + case STEP_ENSURE_ADAPTER_FEATURES: + return STEP_ENSURE_CONVERT_FEATURES; + + case STEP_ENSURE_CONVERT_FEATURES: + return WP_TRANSITION_STEP_NONE; + + default: + return WP_TRANSITION_STEP_ERROR; + } +} + +static void +on_adapter_activated (WpSessionItem * item, GAsyncResult * res, + WpTransition *transition) +{ + g_autoptr (GError) error = NULL; + gboolean activate_ret = wp_session_item_activate_finish (item, res, &error); + g_return_if_fail (error == NULL); + g_return_if_fail (activate_ret); + wp_transition_advance (transition); +} + +static void +on_convert_activated (WpSessionItem * item, GAsyncResult * res, + WpTransition *transition) +{ + WpSiAudioSoftdspEndpoint *self = wp_transition_get_data (transition); + g_autoptr (GError) error = NULL; + + gboolean activate_ret = wp_session_item_activate_finish (item, res, &error); + g_return_if_fail (error == NULL); + g_return_if_fail (activate_ret); + + self->activated_streams++; + if (self->activated_streams >= self->num_streams) + wp_transition_advance (transition); +} + +static void +si_audio_softdsp_endpoint_activate_execute_step (WpSessionItem * item, + WpTransition * transition, guint step) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + + wp_transition_set_data (transition, g_object_ref (self), g_object_unref); + + switch (step) { + case STEP_VERIFY_CONFIG: + if (G_UNLIKELY (!(wp_session_item_get_flags (item) & WP_SI_FLAG_CONFIGURED))) { + wp_transition_return_error (transition, + g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "si-audio-softdsp-endpoint: cannot activate item without it " + "being configured first")); + } + wp_transition_advance (transition); + break; + + case STEP_ENSURE_ADAPTER_FEATURES: + g_return_if_fail (self->activated_streams == 0); + wp_session_item_activate (self->adapter, + (GAsyncReadyCallback)on_adapter_activated, transition); + break; + + case STEP_ENSURE_CONVERT_FEATURES: + { + g_autoptr (WpIterator) it = wp_session_bin_iterate (WP_SESSION_BIN (self)); + g_auto (GValue) val = G_VALUE_INIT; + for (; wp_iterator_next (it, &val); g_value_unset (&val)) { + WpSessionItem *item = g_value_get_object (&val); + if (item == self->adapter) + continue; + wp_session_item_activate (item, + (GAsyncReadyCallback)on_convert_activated, transition); + } + break; + } + + default: + g_return_if_reached (); + } +} + +static void +si_audio_softdsp_endpoint_activate_rollback (WpSessionItem * item) +{ + WpSiAudioSoftdspEndpoint *self = WP_SI_AUDIO_SOFTDSP_ENDPOINT (item); + g_autoptr (WpIterator) it = wp_session_bin_iterate (WP_SESSION_BIN (self)); + g_auto (GValue) val = G_VALUE_INIT; + + /* deactivate all items */ + for (; wp_iterator_next (it, &val); g_value_unset (&val)) + wp_session_item_deactivate (g_value_get_object (&val)); + + self->activated_streams = 0; +} + +static void +si_audio_softdsp_endpoint_class_init (WpSiAudioSoftdspEndpointClass * klass) +{ + WpSessionItemClass *si_class = (WpSessionItemClass *) klass; + + si_class->reset = si_audio_softdsp_endpoint_reset; + si_class->get_associated_proxy = si_audio_softdsp_endpoint_get_associated_proxy; + si_class->configure = si_audio_softdsp_endpoint_configure; + si_class->get_configuration = si_audio_softdsp_endpoint_get_configuration; + si_class->activate_get_next_step = + si_audio_softdsp_endpoint_activate_get_next_step; + si_class->activate_execute_step = + si_audio_softdsp_endpoint_activate_execute_step; + si_class->activate_rollback = si_audio_softdsp_endpoint_activate_rollback; +} + +WP_PLUGIN_EXPORT void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + GVariantBuilder b; + + g_variant_builder_init (&b, G_VARIANT_TYPE ("a(ssymv)")); + g_variant_builder_add (&b, "(ssymv)", "adapter", "t", + WP_SI_CONFIG_OPTION_WRITEABLE | WP_SI_CONFIG_OPTION_REQUIRED, NULL); + g_variant_builder_add (&b, "(ssymv)", "num-streams", "u", + WP_SI_CONFIG_OPTION_WRITEABLE, NULL); + + wp_si_factory_register (core, wp_si_factory_new_simple ( + "si-audio-softdsp-endpoint", si_audio_softdsp_endpoint_get_type (), + g_variant_builder_end (&b))); +}