Files
wireplumber/lib/wp/spa-type.c
Jason Francis ed8346e77d docs: Add brief descriptions to all functions
This ensures all of these functions are picked up by g-ir-scanner.
2021-06-02 17:38:34 +00:00

753 lines
22 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#define G_LOG_DOMAIN "wp-spa-type"
#include "spa-type.h"
#include <spa/utils/type-info.h>
#include <spa/debug/types.h>
#include <pipewire/pipewire.h>
/*! \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_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 volatile 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;
}