Files
wireplumber/lib/wp/spa-type.c

785 lines
22 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/*!
* @file spa-type
*/
/*!
* @struct WpSpaType
* @section spa_type_section Spa Type Information
*
* 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.
*
* @section spa_type_hierarchy_section Type hierarchy
*
* @subsection spa_type_subsection WpSpaType
*
* On the top level, there is a list of types like Int, Bool, String, Id, Object.
* These are called fundamental types (terms borrowed from
* <a href="https://developer.gnome.org/gobject/stable/gobject-Type-Information.html#GType">
* GType</a>).
* 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](@ref spa_id_table_section) 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](@ref spa_id_value_section). 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().
*/
#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>
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;
}
/*
* WpSpaIdTable:
*/
G_DEFINE_POINTER_TYPE (WpSpaIdTable, wp_spa_id_table)
/*
* WpSpaIdValue:
*/
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;
}
/*!
* @memberof WpSpaType
* @param name: the name to look up
*
* @brief Looks up the type id from a given type name
*
* @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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns (transfer none): the direct parent type of the given @em type; if the
* type is fundamental (i.e. has no parent), the returned type is the same
* as @em 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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns the complete name of the given @em type or %NULL if @em 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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns %TRUE if the @em 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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns %TRUE if the @em 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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns %TRUE if the @em 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;
}
/*!
* @memberof WpSpaType
* @param type: the type id of an object type
*
* @brief Object pods (see [WpSpaPod](@ref spa_pod_section)) 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.
*
* @returns the table with the values that can be stored in the special "id"
* field of an object of the given @em 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;
}
/*!
* @memberof WpSpaType
* @param type: a type id
*
* @returns the associated [WpSpaIdTable](@ref spa_id_table_section) 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,
};
/*!
* @memberof WpSpaType
* @param name: the full name of an id table
*
* @brief Finds a [WpSpaIdTable](@ref spa_id_table_section) given its name.
* This name can either be the full type
* name of an object type, or the name of an enum (which is not(!!) a type).
*
* For example, "Spa:Pod:Object:Param:Format" and "Spa:Enum:ParamId" are
* both valid table names.
*
* @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;
}
/*!
* @memberof WpSpaType
* @param table: the id table
*
* @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](@ref spa_id_value_section).
*
* @returns a [WpIterator](@ref iterator_section) that iterates over
* [WpSpaIdValue](@ref spa_id_value_section) 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;
}
/*!
* @memberof WpSpaType
* @param table: the id table
* @param value: a numeric value that is contained in the table
*
* @returns (nullable): the [WpSpaIdValue](@ref spa_id_value_section)
* associated with @em 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;
}
/*!
* @memberof WpSpaType
* @param table: the id table
* @param name: the full name of a value that is contained in the table
*
* @returns (nullable): the [WpSpaIdValue](@ref spa_id_value_section)
* associated with @em 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;
}
/*!
* @memberof 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](@ref spa_id_value_section)
* associated with @em 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;
}
/*!
* @memberof WpSpaType
* @param name: the full name of an id value
*
* @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.
*
* @returns the id value for @em 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);
}
/*!
* @memberof WpSpaType
* @param table_name: the name of the [WpSpaIdTable](@ref spa_id_table_section) to look up the value in
* @param short_name: the short name of the value to look up
*
* @brief Looks up an id value given its container @em table_name and its @em short_name
*
* @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);
}
/*!
* @memberof WpSpaType
* @param table_name: the name of the [WpSpaIdTable](@ref spa_id_table_section) to look up the value in
* @param id: the numeric representation of the value to look up
*
* @brief Looks up an id value given its container @em table_name and its numeric
* representation, @em id
*
* @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);
}
/*!
* @memberof 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;
}
/*!
* @memberof 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;
}
/*!
* @memberof 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);
}
/*!
* @memberof WpSpaType
* @param id: an id value
* @param table: (out) (optional): the associated [WpSpaIdTable](@ref spa_id_table_section)
*
* @brief Returns the value type associated with this [WpSpaIdValue](@ref spa_id_value_section).
* This information is useful when @em 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`, @em table is set to point to the [WpSpaIdTable](@ref spa_id_table_section)
* that contains the possible Id values / object fields.
*
* @returns (transfer none): the value type associated with @em 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;
}
/*!
* @memberof WpSpaType
* @param id: an id value
* @param table: (out) (optional): the associated [WpSpaIdTable](@ref spa_id_table_section)
*
* @brief If the value type of @em 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`, @em table is set to point to the [WpSpaIdTable](@ref spa_id_table_section)
* that contains the possible Id values / object fields.
*
* @returns (transfer none): the type that is allowed in the array, if @em 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;
}
/*!
* @memberof WpSpaType
* @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.
*/
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);
}
/*!
* @memberof WpSpaType
*
* @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.
*/
void
wp_spa_dynamic_type_deinit (void)
{
g_clear_pointer (&extra_types, g_array_unref);
g_clear_pointer (&extra_id_tables, g_array_unref);
}
/*!
* @memberof 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
*
* @brief Registers an additional type in the spa type system.
* This is useful to add a custom pod object type.
*
* Note that both @em name and @em 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.
*
* @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;
}
/*!
* @memberof WpSpaType
* @param name: the name of the id table
* @param values: an array of `spa_type_info` that contains the values of the table
*
* @brief Registers an additional [WpSpaIdTable](@ref spa_id_table_section) in the spa type system.
* This is useful to add custom enumeration types.
*
* Note that both @em name and @em 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.
*
* @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;
}