/* WirePlumber * * Copyright © 2020 Collabora Ltd. * @author Julian Bouzas * * SPDX-License-Identifier: MIT */ #include "spa-type.h" #include "log.h" #include #include #include WP_DEFINE_LOCAL_LOG_TOPIC ("wp-spa-type") /*! \defgroup wpspatype WpSpaType * * Spa has a type system that is represented by a set of arrays that contain * `spa_type_info` structures. This type system is simple, yet complex to * work with for a couple of reasons. * * WirePlumber uses this API to access the spa type system, which makes some * things easier to understand and work with. The main benefit of using this * API is that it makes it easy to work with string representations of the * types, allowing easier access from script bindings. * * \b Type \b hierarchy * * On the top level, there is a list of types like Int, Bool, String, Id, Object. * These are called fundamental types (terms borrowed from GType). * Fundamental types can be derived and therefore we can have other types * that represent specific objects, for instance. * * Enum and flag types are all represented with `SPA_TYPE_Id`. These types * may have a list of possible values that one can select from (enums) * or combine (flags). These values are accessed with the WpSpaIdTable API. * * Object types can have fields. All objects always have a special "id" field, * which is an enum. Its possible values can be given by * wp_spa_type_get_object_id_values_table(). Optionally, objects can also have * other object-specific fields, which can be accessed with * wp_spa_type_get_values_table(). * * Every object field or enum value is represented by a WpSpaIdValue. In the * case of object fields, each field can be of a specific type, which is * returned by wp_spa_id_value_get_value_type(). */ static const WpSpaType SPA_TYPE_VENDOR_WirePlumber = 0x03000000; static GArray *extra_types = NULL; static GArray *extra_id_tables = NULL; typedef struct { const char *name; const struct spa_type_info *values; } WpSpaIdTableInfo; static const WpSpaIdTableInfo static_id_tables[] = { { SPA_TYPE_INFO_Choice, spa_type_choice }, { SPA_TYPE_INFO_Direction, spa_type_direction }, { SPA_TYPE_INFO_ParamId, spa_type_param }, { SPA_TYPE_INFO_MediaType, spa_type_media_type }, { SPA_TYPE_INFO_MediaSubtype, spa_type_media_subtype }, { SPA_TYPE_INFO_ParamAvailability, spa_type_param_availability }, { SPA_TYPE_INFO_ParamPortConfigMode, spa_type_param_port_config_mode }, { SPA_TYPE_INFO_VideoFormat, spa_type_video_format }, { SPA_TYPE_INFO_AudioFormat, spa_type_audio_format }, { SPA_TYPE_INFO_AudioFlags, spa_type_audio_flags }, { SPA_TYPE_INFO_AudioChannel, spa_type_audio_channel }, { SPA_TYPE_INFO_AudioIEC958Codec, spa_type_audio_iec958_codec }, { SPA_TYPE_INFO_IO, spa_type_io }, { SPA_TYPE_INFO_Control, spa_type_control }, { SPA_TYPE_INFO_Data, spa_type_data_type }, { SPA_TYPE_INFO_Meta, spa_type_meta_type }, { SPA_TYPE_INFO_DeviceEventId, spa_type_device_event_id }, { SPA_TYPE_INFO_NodeEvent, spa_type_node_event_id }, { SPA_TYPE_INFO_NodeCommand, spa_type_node_command_id }, { NULL, NULL } }; GType wp_spa_type_get_type (void) { static gsize id__volatile = 0; if (g_once_init_enter (&id__volatile)) { GType id = g_type_register_static_simple ( G_TYPE_UINT, g_intern_static_string ("WpSpaType"), 0, NULL, 0, NULL, 0); g_once_init_leave (&id__volatile, id); } return id__volatile; } G_DEFINE_POINTER_TYPE (WpSpaIdTable, wp_spa_id_table) G_DEFINE_POINTER_TYPE (WpSpaIdValue, wp_spa_id_value) static const struct spa_type_info * wp_spa_type_info_find_by_type (WpSpaType type) { const struct spa_type_info *info; g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, NULL); g_return_val_if_fail (type != 0, NULL); if (extra_types) info = spa_debug_type_find ( (const struct spa_type_info *) extra_types->data, type); else info = spa_debug_type_find (SPA_TYPE_ROOT, type); return info; } /* similar to spa_debug_type_find() and unlike spa_debug_type_find_type(), which steps into id values / object fields */ static const struct spa_type_info * _spa_type_find_by_name (const struct spa_type_info * info, const char * name) { const struct spa_type_info * res; while (info->name) { if (info->type == SPA_ID_INVALID) { if (info->values && (res = _spa_type_find_by_name (info->values, name))) return res; } if (strcmp (info->name, name) == 0) return info; info++; } return NULL; } static const struct spa_type_info * wp_spa_type_info_find_by_name (const gchar *name) { const struct spa_type_info *info = NULL; g_return_val_if_fail (name != NULL, NULL); if (extra_types) info = _spa_type_find_by_name ( (const struct spa_type_info *) extra_types->data, name); else info = _spa_type_find_by_name (SPA_TYPE_ROOT, name); return info; } /*! * \brief Looks up the type id from a given type name * * \ingroup wpspatype * \param name the name to look up * \returns (transfer none): the corresponding type id or WP_SPA_TYPE_INVALID * if not found */ WpSpaType wp_spa_type_from_name (const gchar *name) { const struct spa_type_info *info = wp_spa_type_info_find_by_name (name); return info ? info->type : WP_SPA_TYPE_INVALID; } /*! * \brief Gets the parent type of an SPA type * * \ingroup wpspatype * \param type a type id * \returns (transfer none): the direct parent type of the given \a type; if the * type is fundamental (i.e. has no parent), the returned type is the same * as \a type */ WpSpaType wp_spa_type_parent (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); return info ? info->parent : WP_SPA_TYPE_INVALID; } /*! * \brief Gets the name of an SPA type * * \ingroup wpspatype * \param type a type id * \returns the complete name of the given \a type or NULL if \a type is invalid */ const gchar * wp_spa_type_name (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); return info ? info->name : NULL; } /*! * \brief Checks if an SPA type is a fundamental type * * \ingroup wpspatype * \param type a type id * \returns TRUE if the \a type has no parent, FALSE otherwise */ gboolean wp_spa_type_is_fundamental (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); return info ? (info->type == info->parent) : FALSE; } /*! * \brief Checks if an SPA type is an Id type * * \ingroup wpspatype * \param type a type id * \returns TRUE if the \a type is a SPA_TYPE_Id, FALSE otherwise */ gboolean wp_spa_type_is_id (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); return info ? (info->parent == SPA_TYPE_Id) : FALSE; } /*! * \brief Checks if an SPA type is an Object type * * \ingroup wpspatype * \param type a type id * \returns TRUE if the \a type is a SPA_TYPE_Object, FALSE otherwise */ gboolean wp_spa_type_is_object (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); return info ? (info->parent == SPA_TYPE_Object) : FALSE; } /*! * \brief Gets the table with the values that can be stored in the special "id" * field of an object of the given \a type * * Object pods (see WpSpaPod) always have a special "id" field along with * other fields that can be defined. This "id" field can only store values * of a specific `SPA_TYPE_Id` type. This function returns the table that * contains the possible values for that field. * * \ingroup wpspatype * \param type the type id of an object type * \returns the table with the values that can be stored in the special "id" * field of an object of the given \a type */ WpSpaIdTable wp_spa_type_get_object_id_values_table (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); g_return_val_if_fail (info != NULL, NULL); g_return_val_if_fail (info->parent == SPA_TYPE_Object, NULL); g_return_val_if_fail (info->values != NULL, NULL); g_return_val_if_fail (info->values->name != NULL, NULL); g_return_val_if_fail (info->values->parent == SPA_TYPE_Id, NULL); return info->values->values; } /*! * \brief Gets the values table of an SPA type * * \ingroup wpspatype * \param type a type id * \returns the associated WpSpaIdTable that contains possible * values or object fields for this type, or NULL */ WpSpaIdTable wp_spa_type_get_values_table (WpSpaType type) { const struct spa_type_info *info = wp_spa_type_info_find_by_type (type); g_return_val_if_fail (info != NULL, NULL); return info->values; } struct spa_type_info_iterator_data { const struct spa_type_info *base; const struct spa_type_info *cur; }; static void spa_type_info_iterator_reset (WpIterator *it) { struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it); it_data->cur = it_data->base; } static gboolean spa_type_info_iterator_next (WpIterator *it, GValue *item) { struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it); if (it_data->cur->name) { g_value_init (item, WP_TYPE_SPA_ID_VALUE); g_value_set_pointer (item, (gpointer) it_data->cur); it_data->cur++; return TRUE; } return FALSE; } static gboolean spa_type_info_iterator_fold (WpIterator *it, WpIteratorFoldFunc func, GValue *ret, gpointer data) { struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it); const struct spa_type_info *cur, *base; cur = base = it_data->base; while (cur->name) { g_auto (GValue) item = G_VALUE_INIT; g_value_init (&item, WP_TYPE_SPA_ID_VALUE); g_value_set_pointer (&item, (gpointer) cur); if (!func (&item, ret, data)) return FALSE; cur++; } return TRUE; } static const WpIteratorMethods spa_type_info_iterator_methods = { .version = WP_ITERATOR_METHODS_VERSION, .reset = spa_type_info_iterator_reset, .next = spa_type_info_iterator_next, .fold = spa_type_info_iterator_fold, }; /*! * \brief Finds a WpSpaIdTable given its name. * * This name can either be the full type name of an object type, * or the name of an enum (which is \b not(!!) a type). * For example, "Spa:Pod:Object:Param:Format" and "Spa:Enum:ParamId" are * both valid table names. * * \ingroup wpspatype * \param name the full name of an id table * \returns (nullable): the associated table, or NULL */ WpSpaIdTable wp_spa_id_table_from_name (const gchar *name) { g_return_val_if_fail (name != NULL, NULL); const WpSpaIdTableInfo *info = NULL; /* first look in dynamic id tables */ if (extra_id_tables) { info = (const WpSpaIdTableInfo *) extra_id_tables->data; while (info && info->name) { if (strcmp (info->name, name) == 0) return info->values; info++; } } /* then look at the well-known static ones */ info = static_id_tables; while (info && info->name) { if (strcmp (info->name, name) == 0) return info->values; info++; } /* then look into types, hoping to find an object type */ const struct spa_type_info *tinfo = wp_spa_type_info_find_by_name (name); return tinfo ? tinfo->values : NULL; } /*! * \brief This function returns an iterator that allows you to iterate * through the values associated with this table. * * The items in the iterator are of type WpSpaIdValue. * * \ingroup wpspatype * \param table the id table * \returns a WpIterator that iterates over WpSpaIdValue items */ WpIterator * wp_spa_id_table_new_iterator (WpSpaIdTable table) { g_return_val_if_fail (table != NULL, NULL); WpIterator *it = wp_iterator_new (&spa_type_info_iterator_methods, sizeof (struct spa_type_info_iterator_data)); struct spa_type_info_iterator_data *it_data = wp_iterator_get_user_data (it); it_data->base = (const struct spa_type_info *) table; it_data->cur = it_data->base; return it; } /*! * \brief Finds a value in an SPA Id table * * \ingroup wpspatype * \param table the id table * \param value a numeric value that is contained in the table * \returns (nullable): the WpSpaIdValue associated with \a value, or NULL */ WpSpaIdValue wp_spa_id_table_find_value (WpSpaIdTable table, guint value) { g_return_val_if_fail (table != NULL, NULL); const struct spa_type_info *info = table; while (info && info->name) { if (info->type == value) return info; info++; } return NULL; } /*! * \brief Finds a named value in an SPA Id table * * \ingroup wpspatype * \param table the id table * \param name the full name of a value that is contained in the table * \returns (nullable): the WpSpaIdValue associated with \a name, or NULL */ WpSpaIdValue wp_spa_id_table_find_value_from_name (WpSpaIdTable table, const gchar * name) { g_return_val_if_fail (table != NULL, NULL); const struct spa_type_info *info = table; while (info && info->name) { if (!strcmp (info->name, name)) return info; info++; } return NULL; } /*! * \brief Finds a short named value in an SPA Id table * * \ingroup wpspatype * \param table the id table * \param short_name the short name of a value that is contained in the table * \returns (nullable): the WpSpaIdValue associated with \a short_name, or NULL */ WpSpaIdValue wp_spa_id_table_find_value_from_short_name (WpSpaIdTable table, const gchar * short_name) { g_return_val_if_fail (table != NULL, NULL); const struct spa_type_info *info = table; while (info && info->name) { if (!strcmp (spa_debug_type_short_name (info->name), short_name)) return info; info++; } return NULL; } static WpSpaIdTable wp_spa_id_name_find_id_table (const gchar * name) { WpSpaIdTable table = NULL; g_autofree gchar *parent_name = g_strdup (name); gchar *h; if ((h = strrchr(parent_name, ':')) != NULL) { /* chop the enum name to get the type, ex: Spa:Enum:Direction:Input -> Spa:Enum:Direction */ *h = '\0'; table = wp_spa_id_table_from_name (parent_name); /* in some cases, the parent name is one layer further up, ex: Spa:Pod:Object:Param:Format:Audio:rate -> Spa:Pod:Object:Param:Format */ if (!table && (h = strrchr(parent_name, ':')) != NULL) { *h = '\0'; table = wp_spa_id_table_from_name (parent_name); } } return table; } /*! * \brief Looks up an id value (enum, flag or object field) directly from its * full name. * * For instance, "Spa:Enum:Direction:Input" will resolve to the * id value that represents "Input" in the "Spa:Enum:Direction" enum. * * \ingroup wpspatype * \param name the full name of an id value * \returns the id value for \a name, or NULL if no such id value was found */ WpSpaIdValue wp_spa_id_value_from_name (const gchar * name) { g_return_val_if_fail (name != NULL, NULL); WpSpaIdTable table = wp_spa_id_name_find_id_table (name); return wp_spa_id_table_find_value_from_name (table, name); } /*! * \brief Looks up an id value given its container \a table_name and its * \a short_name * * \ingroup wpspatype * \param table_name the name of the WpSpaIdTable to look up the value in * \param short_name the short name of the value to look up * \returns the id value or NULL if it was not found */ WpSpaIdValue wp_spa_id_value_from_short_name (const gchar * table_name, const gchar * short_name) { g_return_val_if_fail (table_name != NULL, NULL); g_return_val_if_fail (short_name != NULL, NULL); WpSpaIdTable table = wp_spa_id_table_from_name (table_name); return wp_spa_id_table_find_value_from_short_name (table, short_name); } /*! * \brief Looks up an id value given its container \a table_name and its numeric * representation, \a id * * \ingroup wpspatype * \param table_name the name of the WpSpaIdTable to look up the value in * \param id the numeric representation of the value to look up * \returns the id value or NULL if it was not found */ WpSpaIdValue wp_spa_id_value_from_number (const gchar * table_name, guint id) { g_return_val_if_fail (table_name != NULL, NULL); WpSpaIdTable table = wp_spa_id_table_from_name (table_name); return wp_spa_id_table_find_value (table, id); } /*! * \brief Gets the numeric value of an id value * * \ingroup wpspatype * \param id an id value * \returns the numeric representation of this id value */ guint wp_spa_id_value_number (WpSpaIdValue id) { g_return_val_if_fail (id != NULL, -1); const struct spa_type_info *info = id; return info->type; } /*! * \brief Gets the name of an id value * * \ingroup wpspatype * \param id an id value * \returns the full name of this id value */ const gchar * wp_spa_id_value_name (WpSpaIdValue id) { g_return_val_if_fail (id != NULL, NULL); const struct spa_type_info *info = id; return info->name; } /*! * \brief Gets the short name of an id value * * \ingroup wpspatype * \param id an id value * \returns the short name of this id value */ const gchar * wp_spa_id_value_short_name (WpSpaIdValue id) { g_return_val_if_fail (id != NULL, NULL); const struct spa_type_info *info = id; return spa_debug_type_short_name (info->name); } /*! * \brief Returns the value type associated with this WpSpaIdValue. * * This information is useful when \a id represents an object field, * which can take a value of an arbitrary type. * * When the returned type is (or is derived from) `SPA_TYPE_Id` or * `SPA_TYPE_Object`, \a table is set to point to the WpSpaIdTable * that contains the possible Id values / object fields. * * \ingroup wpspatype * \param id an id value * \param table (out) (optional): the associated WpSpaIdTable * \returns (transfer none): the value type associated with \a id */ WpSpaType wp_spa_id_value_get_value_type (WpSpaIdValue id, WpSpaIdTable * table) { g_return_val_if_fail (id != NULL, WP_SPA_TYPE_INVALID); const struct spa_type_info *info = id; if (table) { /* info->values has different semantics on Array types */ if (info->values && info->parent != SPA_TYPE_Array) { *table = info->values; } /* derived object types normally don't have info->values directly set, so we need to look them up */ else if (wp_spa_type_is_object (info->parent)) { WpSpaIdTable t = wp_spa_type_get_values_table (info->parent); if (t) *table = t; } } return info->parent; } /*! * \brief If the value type of \a id is `SPA_TYPE_Array`, this function * returns the type that is allowed to be contained inside the array. * * When the returned type is (or is derived from) `SPA_TYPE_Id` or * `SPA_TYPE_Object`, \a table is set to point to the WpSpaIdTable * that contains the possible Id values / object fields. * * \ingroup wpspatype * \param id an id value * \param table (out) (optional): the associated WpSpaIdTable * \returns (transfer none): the type that is allowed in the array, if \a id * represents an object field that takes an array as value */ WpSpaType wp_spa_id_value_array_get_item_type (WpSpaIdValue id, WpSpaIdTable * table) { g_return_val_if_fail (id != NULL, WP_SPA_TYPE_INVALID); const struct spa_type_info *info = id; g_return_val_if_fail (info->parent == SPA_TYPE_Array, WP_SPA_TYPE_INVALID); return info->values ? wp_spa_id_value_get_value_type (info->values, table) : WP_SPA_TYPE_INVALID; } /*! * \brief Initializes the spa dynamic type registry. * * This allows registering new spa types at runtime. The spa type system * still works if this function is not called. * * Normally called by wp_init() when WP_INIT_SPA_TYPES is passed in its flags. * * \ingroup wpspatype */ void wp_spa_dynamic_type_init (void) { extra_types = g_array_new (TRUE, FALSE, sizeof (struct spa_type_info)); extra_id_tables = g_array_new (TRUE, FALSE, sizeof (WpSpaIdTableInfo)); /* init to chain up to spa types */ struct spa_type_info info = { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", SPA_TYPE_ROOT }; g_array_append_val (extra_types, info); } /*! * \brief Deinitializes the spa type registry. * * You do not need to ever call this, unless you want to free memory at the * end of the execution of a test, so that it doesn't show as leaked in * the memory profiler. * * \ingroup wpspatype */ void wp_spa_dynamic_type_deinit (void) { g_clear_pointer (&extra_types, g_array_unref); g_clear_pointer (&extra_id_tables, g_array_unref); } /*! * \brief Registers an additional type in the spa type system. * * This is useful to add a custom pod object type. * * Note that both \a name and \a values must be statically allocated, or * otherwise guaranteed to be kept in memory until wp_spa_dynamic_type_deinit() * is called. No memory copy is done by this function. * * \ingroup wpspatype * \param name the name of the type * \param parent the parent type * \param values an array of `spa_type_info` that contains the values of the type, * used only for Object types * \returns (transfer none): the new type */ WpSpaType wp_spa_dynamic_type_register (const gchar *name, WpSpaType parent, const struct spa_type_info * values) { struct spa_type_info info; info.type = SPA_TYPE_VENDOR_WirePlumber + extra_types->len; info.name = name; info.parent = parent; info.values = values; g_array_append_val (extra_types, info); return info.type; } /*! * \brief Registers an additional WpSpaIdTable in the spa type system. * * This is useful to add custom enumeration types. * * Note that both \a name and \a values must be statically allocated, or * otherwise guaranteed to be kept in memory until wp_spa_dynamic_type_deinit() * is called. No memory copy is done by this function. * * \ingroup wpspatype * \param name the name of the id table * \param values an array of `spa_type_info` that contains the values of the table * \returns the new table */ WpSpaIdTable wp_spa_dynamic_id_table_register (const gchar *name, const struct spa_type_info * values) { WpSpaIdTableInfo info; info.name = name; info.values = values; g_array_append_val (extra_id_tables, info); return values; }