From 376238883178fce7d3a1525dd840b5ff8cf824fd Mon Sep 17 00:00:00 2001
From: George Kiagiadakis <george.kiagiadakis@collabora.com>
Date: Wed, 13 Jan 2021 20:11:41 +0200
Subject: [PATCH] spa-type: refactor

* Use a more complete API to introspect SPA types
* Avoid the need for the Tables enumeration; the tables
  are now registered with a string
* Avoid the need for initialization, work directly on spa_types
  and other static data
* Allow working with Object pods that are not Params;
  the PARAMS table was previously hardcoded in the pod implementation
* Add a different dynamic type registration system, closer to
  how spa type works. The only regression is that we can no longer
  register additional custom object fields (custom SPA_PROP_* for example),
  but this feature can be re-added later
---
 lib/wp/endpoint-stream.c                      |   7 +-
 lib/wp/endpoint.c                             |   7 +-
 lib/wp/private/pipewire-object-mixin.c        |  41 +-
 lib/wp/spa-pod.c                              | 353 ++++----
 lib/wp/spa-pod.h                              |  31 +-
 lib/wp/spa-type.c                             | 854 +++++++++++++-----
 lib/wp/spa-type.h                             | 136 ++-
 lib/wp/wp.c                                   |   2 +-
 .../module-dbus-reservation/reserve-device.c  |   2 +-
 modules/module-default-profile.c              |   6 +-
 modules/module-device-activation.c            |   5 +-
 .../limited-creation-bluez5.c                 |   3 +-
 modules/module-si-adapter.c                   |  13 +-
 modules/module-si-adapter/algorithms.c        |  28 +-
 modules/module-si-bluez5-endpoint.c           |   2 +-
 modules/module-si-convert.c                   |  16 +-
 modules/module-si-simple-node-endpoint.c      |   8 +-
 tests/modules/algorithms.c                    |  12 +-
 tests/wp/endpoint.c                           |  26 +-
 tests/wp/proxy.c                              |   3 +-
 tests/wp/spa-pod.c                            | 174 ++--
 tests/wp/spa-type.c                           | 419 +++++++--
 tools/wpctl.c                                 |  16 +-
 23 files changed, 1384 insertions(+), 780 deletions(-)

diff --git a/lib/wp/endpoint-stream.c b/lib/wp/endpoint-stream.c
index 88bcf1fa..41a093ea 100644
--- a/lib/wp/endpoint-stream.c
+++ b/lib/wp/endpoint-stream.c
@@ -535,19 +535,20 @@ wp_impl_endpoint_stream_set_param (gpointer instance, guint32 id, guint32 flags,
   WpImplEndpointStream *self = WP_IMPL_ENDPOINT_STREAM (instance);
   g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy (
         WP_SESSION_ITEM (self->item), WP_TYPE_NODE);
-  const gchar *idstr = NULL;
 
   if (!node) {
     wp_warning_object (self, "associated node is no longer available");
     return -EPIPE;
   }
 
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, NULL, &idstr, NULL)) {
+  WpSpaIdValue idval = wp_spa_id_value_from_number ("Spa:Enum:ParamId", id);
+  if (!idval) {
     wp_critical_object (self, "invalid param id: %u", id);
     return -EINVAL;
   }
 
-  return wp_pipewire_object_set_param (node, idstr, flags, param) ? 0 : -EIO;
+  return wp_pipewire_object_set_param (node, wp_spa_id_value_short_name (idval),
+      flags, param) ? 0 : -EIO;
 }
 
 #define pw_endpoint_stream_emit(hooks,method,version,...) \
diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c
index fed1ad5b..8838bc7f 100644
--- a/lib/wp/endpoint.c
+++ b/lib/wp/endpoint.c
@@ -1081,19 +1081,20 @@ wp_impl_endpoint_set_param (gpointer instance, guint32 id, guint32 flags,
   WpImplEndpoint *self = WP_IMPL_ENDPOINT (instance);
   g_autoptr (WpPipewireObject) node = wp_session_item_get_associated_proxy (
         WP_SESSION_ITEM (self->item), WP_TYPE_NODE);
-  const gchar *idstr = NULL;
 
   if (!node) {
     wp_warning_object (self, "associated node is no longer available");
     return -EPIPE;
   }
 
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, NULL, &idstr, NULL)) {
+  WpSpaIdValue idval = wp_spa_id_value_from_number ("Spa:Enum:ParamId", id);
+  if (!idval) {
     wp_critical_object (self, "invalid param id: %u", id);
     return -EINVAL;
   }
 
-  return wp_pipewire_object_set_param (node, idstr, flags, param) ? 0 : -EIO;
+  return wp_pipewire_object_set_param (node, wp_spa_id_value_short_name (idval),
+      flags, param) ? 0 : -EIO;
 }
 
 #define pw_endpoint_emit(hooks,method,version,...) \
diff --git a/lib/wp/private/pipewire-object-mixin.c b/lib/wp/private/pipewire-object-mixin.c
index a0ef4641..62262db1 100644
--- a/lib/wp/private/pipewire-object-mixin.c
+++ b/lib/wp/private/pipewire-object-mixin.c
@@ -83,12 +83,13 @@ wp_pw_object_mixin_get_param_info (WpPipewireObject * obj)
   g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}"));
 
   for (guint i = 0; i < n_params; i++) {
+    WpSpaIdValue idval;
     const gchar *nick = NULL;
     gchar flags[3];
     guint flags_idx = 0;
 
-    wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, info[i].id, NULL, &nick,
-        NULL);
+    idval = wp_spa_id_value_from_number ("Spa:Enum:ParamId", info[i].id);
+    nick = wp_spa_id_value_short_name (idval);
     g_return_val_if_fail (nick != NULL, NULL);
 
     if (info[i].flags & SPA_PARAM_INFO_READ)
@@ -170,8 +171,8 @@ wp_pw_object_mixin_enum_params_unchecked (gpointer obj,
   /* debug */
   if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) {
     const gchar *name = NULL;
-    wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL);
-
+    name = wp_spa_id_value_short_name (
+        wp_spa_id_value_from_number ("Spa:Enum:ParamId", id));
     wp_debug_object (obj, "enum id %u (%s), seq 0x%x (%u), task "
         WP_OBJECT_FORMAT "%s", id, name, seq, seq, WP_OBJECT_ARGS (task),
         iface->enum_params_sync ? ", sync" : "");
@@ -199,7 +200,7 @@ wp_pw_object_mixin_enum_params (WpPipewireObject * obj, const gchar * id,
     GAsyncReadyCallback callback, gpointer user_data)
 {
   WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
-  guint32 param_id = 0;
+  WpSpaIdValue param_id;
 
   if (!(iface->enum_params || iface->enum_params_sync)) {
     g_task_report_new_error (obj, callback, user_data, NULL,
@@ -209,13 +210,14 @@ wp_pw_object_mixin_enum_params (WpPipewireObject * obj, const gchar * id,
   }
 
   /* translate the id */
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &param_id,
-          NULL, NULL)) {
+  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
+  if (!param_id) {
     wp_critical_object (obj, "invalid param id: %s", id);
     return;
   }
 
-  wp_pw_object_mixin_enum_params_unchecked (obj, param_id, filter,
+  wp_pw_object_mixin_enum_params_unchecked (obj,
+      wp_spa_id_value_number (param_id), filter,
       cancellable, callback, user_data);
 }
 
@@ -238,22 +240,24 @@ wp_pw_object_mixin_enum_params_sync (WpPipewireObject * obj, const gchar * id,
 {
   WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
   GPtrArray *params = NULL;
-  guint32 param_id = 0;
+  WpSpaIdValue param_id;
 
   /* translate the id */
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &param_id,
-          NULL, NULL)) {
+  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
+  if (!param_id) {
     wp_critical_object (obj, "invalid param id: %s", id);
     return NULL;
   }
 
   if (iface->enum_params_sync) {
     /* use enum_params_sync if supported */
-    params = iface->enum_params_sync (obj, param_id, 0, -1, filter);
+    params = iface->enum_params_sync (obj, wp_spa_id_value_number (param_id),
+        0, -1, filter);
   } else {
     /* otherwise, find and return the cached params */
     WpPwObjectMixinData *data = wp_pw_object_mixin_get_data (obj);
-    params = wp_pw_object_mixin_get_stored_params (data, param_id);
+    params = wp_pw_object_mixin_get_stored_params (data,
+        wp_spa_id_value_number (param_id));
     /* TODO filter */
   }
 
@@ -265,7 +269,7 @@ wp_pw_object_mixin_set_param (WpPipewireObject * obj, const gchar * id,
     guint32 flags, WpSpaPod * param)
 {
   WpPwObjectMixinPrivInterface *iface = WP_PW_OBJECT_MIXIN_PRIV_GET_IFACE (obj);
-  guint32 param_id = 0;
+  WpSpaIdValue param_id;
   gint ret;
 
   if (!iface->set_param) {
@@ -273,14 +277,14 @@ wp_pw_object_mixin_set_param (WpPipewireObject * obj, const gchar * id,
     return FALSE;
   }
 
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id, &param_id,
-          NULL, NULL)) {
+  param_id = wp_spa_id_value_from_short_name ("Spa:Enum:ParamId", id);
+  if (!param_id) {
     wp_critical_object (obj, "invalid param id: %s", id);
     wp_spa_pod_unref (param);
     return FALSE;
   }
 
-  ret = iface->set_param (obj, param_id, flags, param);
+  ret = iface->set_param (obj, wp_spa_id_value_number (param_id), flags, param);
 
   if (G_UNLIKELY (SPA_RESULT_IS_ERROR (ret))) {
     wp_message_object (obj, "set_param failed: %s", spa_strerror (ret));
@@ -940,7 +944,8 @@ wp_pw_object_mixin_notify_params_changed (gpointer instance, guint32 id)
 
   if (wp_log_level_is_enabled (G_LOG_LEVEL_DEBUG)) {
     const gchar *name = NULL;
-    wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, &name, NULL, NULL);
+    name = wp_spa_id_value_short_name (wp_spa_id_value_from_number (
+        "Spa:Enum:ParamId", id));
     wp_debug_object (instance, "notify param id:%u (%s)", id, name);
   }
 
diff --git a/lib/wp/spa-pod.c b/lib/wp/spa-pod.c
index b81f878b..f00b6a50 100644
--- a/lib/wp/spa-pod.c
+++ b/lib/wp/spa-pod.c
@@ -52,13 +52,13 @@ struct _WpSpaPod
     struct spa_pod_rectangle pod_rectangle;
     struct spa_pod_fraction pod_fraction;
     struct wp_property_data {
-      WpSpaTypeTable table;
+      WpSpaIdTable table;
       guint32 key;
       guint32 flags;
     } data_property;         /* Only used for property pods */
     struct wp_control_data {
       guint32 offset;
-      guint32 type;
+      enum spa_control_type type;
     } data_control;          /* Only used for control pods */
   } static_pod;              /* Only used for statically allocated pods */
   WpSpaPodBuilder *builder;  /* Only used for dynamically allocated pods */
@@ -69,12 +69,11 @@ G_DEFINE_BOXED_TYPE (WpSpaPod, wp_spa_pod, wp_spa_pod_ref, wp_spa_pod_unref)
 
 struct _WpSpaPodBuilder
 {
-  size_t size;
-  guint8 *buf;
   struct spa_pod_builder builder;
-  uint32_t type;
   struct spa_pod_frame frame;
-  WpSpaTypeTable prop_table;  /* Only used when parsing properties */
+  WpSpaType type;
+  size_t size;
+  guint8 *buf;
 };
 
 G_DEFINE_BOXED_TYPE (WpSpaPodBuilder, wp_spa_pod_builder,
@@ -82,11 +81,10 @@ G_DEFINE_BOXED_TYPE (WpSpaPodBuilder, wp_spa_pod_builder,
 
 struct _WpSpaPodParser
 {
-  guint32 type;
   struct spa_pod_parser parser;
-  WpSpaPod *pod;
   struct spa_pod_frame frame;
-  WpSpaTypeTable prop_table;  /* Only used when parsing properties */
+  WpSpaType type;
+  WpSpaPod *pod;
 };
 
 G_DEFINE_BOXED_TYPE (WpSpaPodParser, wp_spa_pod_parser,
@@ -111,7 +109,7 @@ static const struct spa_pod_builder_callbacks builder_callbacks = {
 };
 
 static WpSpaPodBuilder *
-wp_spa_pod_builder_new (size_t size, uint32_t type)
+wp_spa_pod_builder_new (size_t size, WpSpaType type)
 {
   WpSpaPodBuilder *self = g_rc_box_new0 (WpSpaPodBuilder);
   self->size = size;
@@ -180,10 +178,8 @@ wp_spa_pod_new (const struct spa_pod *pod, WpSpaPodType type, guint32 flags)
 
   /* Set the prop table if it is an object */
   if (pod->type == SPA_TYPE_Object) {
-    WpSpaTypeTable prop_table = 0;
-    wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC,
-        ((struct spa_pod_object *) pod)->body.type, NULL, NULL, &prop_table);
-    self->static_pod.data_property.table = prop_table;
+    self->static_pod.data_property.table =
+        wp_spa_type_get_values_table (((struct spa_pod_object *) pod)->body.type);
   }
 
   return self;
@@ -220,7 +216,7 @@ wp_spa_pod_new_wrap_const (const struct spa_pod *pod)
 }
 
 static WpSpaPod *
-wp_spa_pod_new_property_wrap (WpSpaTypeTable table, guint32 key, guint32 flags,
+wp_spa_pod_new_property_wrap (WpSpaIdTable table, guint32 key, guint32 flags,
     struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY, FLAG_NO_OWNERSHIP);
@@ -231,7 +227,7 @@ wp_spa_pod_new_property_wrap (WpSpaTypeTable table, guint32 key, guint32 flags,
 }
 
 static WpSpaPod *
-wp_spa_pod_new_control_wrap (guint32 offset, guint32 type,
+wp_spa_pod_new_control_wrap (guint32 offset, enum spa_control_type type,
     struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL, FLAG_NO_OWNERSHIP);
@@ -244,7 +240,7 @@ wp_spa_pod_new_control_wrap (guint32 offset, guint32 type,
 /* there is no use for these _const variants, but let's keep them just in case */
 
 static WpSpaPod *
-wp_spa_pod_new_property_wrap_const (WpSpaTypeTable table, guint32 key,
+wp_spa_pod_new_property_wrap_const (WpSpaIdTable table, guint32 key,
     guint32 flags, const struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY,
@@ -256,7 +252,7 @@ wp_spa_pod_new_property_wrap_const (WpSpaTypeTable table, guint32 key,
 }
 
 static WpSpaPod *
-wp_spa_pod_new_control_wrap_const (guint32 offset, guint32 type,
+wp_spa_pod_new_control_wrap_const (guint32 offset, enum spa_control_type type,
     const struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL,
@@ -274,7 +270,7 @@ wp_spa_pod_new_wrap_copy (const struct spa_pod *pod)
 }
 
 static WpSpaPod *
-wp_spa_pod_new_property_wrap_copy (WpSpaTypeTable table, guint32 key,
+wp_spa_pod_new_property_wrap_copy (WpSpaIdTable table, guint32 key,
     guint32 flags, const struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_PROPERTY, 0);
@@ -285,7 +281,7 @@ wp_spa_pod_new_property_wrap_copy (WpSpaTypeTable table, guint32 key,
 }
 
 static WpSpaPod *
-wp_spa_pod_new_control_wrap_copy (guint32 offset, guint32 type,
+wp_spa_pod_new_control_wrap_copy (guint32 offset, enum spa_control_type type,
     const struct spa_pod *pod)
 {
   WpSpaPod *self = wp_spa_pod_new (pod, WP_SPA_POD_CONTROL, 0);
@@ -311,45 +307,43 @@ wp_spa_pod_get_spa_pod (const WpSpaPod *self)
 }
 
 /**
- * wp_spa_pod_get_type_name:
- * @self: a spa pod object
+ * wp_spa_pod_get_spa_type:
+ * @self: a spa pod
  *
- * Gets the type name of the spa pod object
+ * Gets the SPA type of the spa pod.
+ * If the pod is an object or pointer, this will return the derived
+ * object/pointer type directly.
+ * If the pod is an object property or a control, this will return the type
+ * of the contained value.
  *
- * Returns: the type name of the spa pod object
+ * Returns: the type of the spa pod
  */
-const char *
-wp_spa_pod_get_type_name (WpSpaPod *self)
+WpSpaType
+wp_spa_pod_get_spa_type (WpSpaPod *self)
 {
-  const char *nick = NULL;
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, SPA_POD_TYPE (self->pod),
-      NULL, &nick, NULL))
-    g_return_val_if_reached (NULL);
-  return nick;
-}
+  g_return_val_if_fail (self != NULL, WP_SPA_TYPE_INVALID);
 
-const char *
-wp_spa_pod_get_choice_type_name (WpSpaPod *self)
-{
-  g_return_val_if_fail (wp_spa_pod_is_choice (self), NULL);
-
-  const char *nick = NULL;
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_CHOICE,
-      SPA_POD_CHOICE_TYPE (self->pod), NULL, &nick, NULL))
-    g_return_val_if_reached (NULL);
-  return nick;
+  if (wp_spa_pod_is_object (self) || wp_spa_pod_is_pointer (self))
+    return SPA_POD_OBJECT_TYPE (self->pod);
+  else
+    return SPA_POD_TYPE (self->pod);
 }
 
-const char *
-wp_spa_pod_get_object_type_name (WpSpaPod *self)
+/**
+ * wp_spa_pod_get_choice_type:
+ * @self: a choice pod
+ *
+ * If the pod is a Choice, this gets the choice type
+ * (Range, Step, Enum, ...)
+ *
+ * Returns: the choice type of the choice pod
+ */
+WpSpaIdValue
+wp_spa_pod_get_choice_type (WpSpaPod *self)
 {
-  g_return_val_if_fail (wp_spa_pod_is_object (self), NULL);
-
-  const char *nick = NULL;
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC,
-      SPA_POD_OBJECT_TYPE (self->pod), NULL, &nick, NULL))
-    g_return_val_if_reached (NULL);
-  return nick;
+  g_return_val_if_fail (wp_spa_pod_is_choice (self), NULL);
+  return wp_spa_id_value_from_number (
+      SPA_TYPE_INFO_Choice, SPA_POD_CHOICE_TYPE (self->pod));
 }
 
 /**
@@ -609,7 +603,7 @@ wp_spa_pod_new_bytes (gconstpointer value, guint32 len)
 
 /**
  * wp_spa_pod_new_pointer:
- * @type_name: the type name the pointer points to
+ * @type_name: the name of the type of the pointer
  * @value: the pointer value
  *
  * Creates a spa pod of type pointer
@@ -619,13 +613,12 @@ wp_spa_pod_new_bytes (gconstpointer value, guint32 len)
 WpSpaPod *
 wp_spa_pod_new_pointer (const char *type_name, gconstpointer value)
 {
+  WpSpaType type = wp_spa_type_from_name (type_name);
+  g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, NULL);
+
   WpSpaPod *self = g_slice_new0 (WpSpaPod);
   g_ref_count_init (&self->ref);
   self->type = WP_SPA_POD_REGULAR;
-  guint32 type = 0;
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, type_name, &type,
-      NULL, NULL))
-    g_return_val_if_reached (NULL);
   self->static_pod.pod_pointer = SPA_POD_INIT_Pointer (type, value);
   self->pod = &self->static_pod.pod_pointer.pod;
   return self;
@@ -694,7 +687,7 @@ wp_spa_pod_new_fraction (guint32 num, guint32 denom)
 
 /**
  * wp_spa_pod_new_choice:
- * @type_name: the type name of the choice type
+ * @choice_type: the name of the choice type ("Range", "Step", ...)
  * @...: a list of choice values, followed by %NULL
  *
  * Creates a spa pod of type choice
@@ -702,13 +695,13 @@ wp_spa_pod_new_fraction (guint32 num, guint32 denom)
  * Returns: (transfer full): The new spa pod
  */
 WpSpaPod *
-wp_spa_pod_new_choice (const char *type_name, ...)
+wp_spa_pod_new_choice (const char *choice_type, ...)
 {
   WpSpaPod *self;
   va_list args;
 
-  va_start (args, type_name);
-  self = wp_spa_pod_new_choice_valist (type_name, args);
+  va_start (args, choice_type);
+  self = wp_spa_pod_new_choice_valist (choice_type, args);
   va_end (args);
 
   return self;
@@ -716,7 +709,7 @@ wp_spa_pod_new_choice (const char *type_name, ...)
 
 /**
  * wp_spa_pod_new_choice_valist:
- * @type_name: the type name of the choice type
+ * @choice_type: the name of the choice type ("Range", "Step", ...)
  * @args: the variable arguments passed to wp_spa_pod_new_choice()
  *
  * This is the `va_list` version of wp_spa_pod_new_choice()
@@ -724,9 +717,9 @@ wp_spa_pod_new_choice (const char *type_name, ...)
  * Returns: (transfer full): The new spa pod
  */
 WpSpaPod *
-wp_spa_pod_new_choice_valist (const char *type_name, va_list args)
+wp_spa_pod_new_choice_valist (const char *choice_type, va_list args)
 {
-  g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice (type_name);
+  g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice (choice_type);
   wp_spa_pod_builder_add_valist (b, args);
   return wp_spa_pod_builder_end (b);
 }
@@ -838,8 +831,7 @@ wp_spa_pod_is_none (WpSpaPod *self)
 gboolean
 wp_spa_pod_is_boolean (WpSpaPod *self)
 {
-  return self->type == WP_SPA_POD_REGULAR &&
-      spa_pod_is_bool (self->pod) ? TRUE : FALSE;
+  return self->type == WP_SPA_POD_REGULAR && spa_pod_is_bool (self->pod);
 }
 
 /**
@@ -1241,7 +1233,6 @@ wp_spa_pod_get_bytes (WpSpaPod *self, gconstpointer *value, guint32 *len)
 /**
  * wp_spa_pod_get_pointer:
  * @self: the spa pod object
- * @type_name: (out): the type name of the pointer value
  * @value: (out): the pointer value
  *
  * Gets the pointer value and its type name of a spa pod object
@@ -1249,21 +1240,13 @@ wp_spa_pod_get_bytes (WpSpaPod *self, gconstpointer *value, guint32 *len)
  * Returns: TRUE if the value was obtained, FALSE otherwise
  */
 gboolean
-wp_spa_pod_get_pointer (WpSpaPod *self, const char **type_name,
-    gconstpointer *value)
+wp_spa_pod_get_pointer (WpSpaPod *self, gconstpointer *value)
 {
-  gboolean res;
-
   g_return_val_if_fail (self, FALSE);
-  g_return_val_if_fail (type_name, FALSE);
   g_return_val_if_fail (value, FALSE);
 
   guint32 type = 0;
-  res = spa_pod_get_pointer (self->pod, &type, value) >= 0;
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, type, NULL, type_name,
-      NULL))
-    g_return_val_if_reached (FALSE);
-  return res;
+  return spa_pod_get_pointer (self->pod, &type, value) >= 0;
 }
 
 /**
@@ -1440,7 +1423,7 @@ wp_spa_pod_set_double (WpSpaPod *self, double value)
 /**
  * wp_spa_pod_set_pointer:
  * @self: the spa pod object
- * @type_name: the type name the pointer points to
+ * @type_name: the name of the type of the pointer
  * @value: the pointer value
  *
  * Sets a pointer value with its type name in the spa pod object.
@@ -1451,16 +1434,13 @@ gboolean
 wp_spa_pod_set_pointer (WpSpaPod *self, const char *type_name,
     gconstpointer value)
 {
-  guint32 id = 0;
+  WpSpaType type = wp_spa_type_from_name (type_name);
 
   g_return_val_if_fail (wp_spa_pod_is_pointer (self), FALSE);
   g_return_val_if_fail (!(self->flags & FLAG_CONSTANT), FALSE);
+  g_return_val_if_fail (type != WP_SPA_TYPE_INVALID, FALSE);
 
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, type_name, &id, NULL,
-      NULL))
-    g_return_val_if_reached (FALSE);
-
-  ((struct spa_pod_pointer *)self->pod)->body.type = id;
+  ((struct spa_pod_pointer *)self->pod)->body.type = type;
   ((struct spa_pod_pointer *)self->pod)->body.value = value;
   return TRUE;
 }
@@ -1679,7 +1659,7 @@ wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod)
   switch (self->type) {
   case WP_SPA_POD_PROPERTY:
     if (self->static_pod.data_property.table != pod->static_pod.data_property.table ||
-        self->static_pod.data_property.table != pod->static_pod.data_property.table ||
+        self->static_pod.data_property.key != pod->static_pod.data_property.key ||
         self->static_pod.data_property.flags != pod->static_pod.data_property.flags)
       return FALSE;
     break;
@@ -1699,7 +1679,6 @@ wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod)
 /**
  * wp_spa_pod_get_object:
  * @self: the spa pod object
- * @type_name: the type name of the object type
  * @id_name: (out): the id name of the object
  * @...: (out): the list of the object properties values, followed by %NULL
  *
@@ -1708,13 +1687,12 @@ wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod)
  * Returns: TRUE if the object properties values were obtained, FALSE otherwise
  */
 gboolean
-wp_spa_pod_get_object (WpSpaPod *self, const char *type_name,
-    const char **id_name, ...)
+wp_spa_pod_get_object (WpSpaPod *self, const char **id_name, ...)
 {
   va_list args;
   gboolean res;
   va_start (args, id_name);
-  res = wp_spa_pod_get_object_valist (self, type_name, id_name, args);
+  res = wp_spa_pod_get_object_valist (self, id_name, args);
   va_end (args);
   return res;
 }
@@ -1722,7 +1700,6 @@ wp_spa_pod_get_object (WpSpaPod *self, const char *type_name,
 /**
  * wp_spa_pod_get_object_valist:
  * @self: the spa pod object
- * @type_name: the type name of the object type
  * @id_name: (out): the id name of the object
  * @args: (out): the variable arguments passed to wp_spa_pod_get_object()
  *
@@ -1731,13 +1708,11 @@ wp_spa_pod_get_object (WpSpaPod *self, const char *type_name,
  * Returns: TRUE if the object properties values were obtained, FALSE otherwise
  */
 gboolean
-wp_spa_pod_get_object_valist (WpSpaPod *self, const char *type_name,
-    const char **id_name, va_list args)
+wp_spa_pod_get_object_valist (WpSpaPod *self, const char **id_name, va_list args)
 {
   g_return_val_if_fail (self, FALSE);
   g_return_val_if_fail (wp_spa_pod_is_object (self), FALSE);
-  g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (self, type_name,
-      id_name);
+  g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (self, id_name);
   const gboolean res = wp_spa_pod_parser_get_valist (p, args);
   wp_spa_pod_parser_end (p);
   return res;
@@ -1800,9 +1775,13 @@ wp_spa_pod_get_property (WpSpaPod *self, const char **key,
   g_return_val_if_fail (self, FALSE);
   g_return_val_if_fail (wp_spa_pod_is_property (self), FALSE);
 
-  if (key && !wp_spa_type_get_by_id (self->static_pod.data_property.table,
-      self->static_pod.data_property.key, NULL, key, NULL))
-    return FALSE;
+  if (key) {
+    WpSpaIdValue key_val = wp_spa_id_table_find_value (
+        self->static_pod.data_property.table,
+        self->static_pod.data_property.key);
+    g_return_val_if_fail (key_val != NULL, FALSE);
+    *key = wp_spa_id_value_short_name (key_val);
+  }
   if (value)
     *value = wp_spa_pod_new_wrap (self->pod);
 
@@ -1813,7 +1792,7 @@ wp_spa_pod_get_property (WpSpaPod *self, const char **key,
  * wp_spa_pod_get_control:
  * @self: the spa pod object
  * @offset: (out) (optional): the offset of the control
- * @type_name: (out) (optional): the type name of the control
+ * @ctl_type: (out) (optional): the control type (Properties, Midi, ...)
  * @value: (out) (optional): the spa pod value of the control
  *
  * Gets the offset, type name and spa pod value of a spa pod control
@@ -1821,7 +1800,7 @@ wp_spa_pod_get_property (WpSpaPod *self, const char **key,
  * Returns: TRUE if the value was obtained, FALSE otherwise
  */
 gboolean
-wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset, const char **type_name,
+wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset, const char **ctl_type,
     WpSpaPod **value)
 {
   g_return_val_if_fail (self, FALSE);
@@ -1829,9 +1808,12 @@ wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset, const char **type_name,
 
   if (offset)
     *offset = self->static_pod.data_control.offset;
-  if (type_name && !wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_CONTROL,
-      self->static_pod.data_control.type, NULL, type_name, NULL))
-    g_return_val_if_reached (FALSE);
+  if (ctl_type) {
+    WpSpaIdValue type_val = wp_spa_id_value_from_number (
+        SPA_TYPE_INFO_Control, self->static_pod.data_control.type);
+    g_return_val_if_fail (type_val != NULL, FALSE);
+    *ctl_type = wp_spa_id_value_short_name (type_val);
+  }
   if (value)
     *value = wp_spa_pod_new_wrap (self->pod);
 
@@ -1917,30 +1899,27 @@ wp_spa_pod_builder_new_array (void)
 
 /**
  * wp_spa_pod_builder_new_choice:
- * @type_name: the type name of the choice type
+ * @choice_type: the name of the choice type ("Range", "Step", ...)
  *
  * Creates a spa pod builder of type choice
  *
  * Returns: (transfer full): the new spa pod builder
  */
 WpSpaPodBuilder *
-wp_spa_pod_builder_new_choice (const char *type_name)
+wp_spa_pod_builder_new_choice (const char *choice_type)
 {
   WpSpaPodBuilder *self = NULL;
-  guint32 type = 0;
+  WpSpaIdValue type = wp_spa_id_value_from_short_name (
+      SPA_TYPE_INFO_Choice, choice_type);
+  g_return_val_if_fail (type != NULL, NULL);
 
   /* Construct the builder */
   self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE,
       SPA_TYPE_Choice);
 
-  /* Find the choice type */
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_CHOICE, type_name, &type,
-      NULL, NULL))
-    g_return_val_if_reached (NULL);
-
   /* Push the array */
-  spa_pod_builder_push_choice (&self->builder, &self->frame, type,
-      WP_SPA_TYPE_TABLE_CHOICE);
+  spa_pod_builder_push_choice (&self->builder, &self->frame,
+      wp_spa_id_value_number (type), 0);
 
   return self;
 }
@@ -1958,25 +1937,27 @@ WpSpaPodBuilder *
 wp_spa_pod_builder_new_object (const char *type_name, const char *id_name)
 {
   WpSpaPodBuilder *self = NULL;
-  guint32 type = 0;
-  guint32 id = 0;
-
-  /* Construct the builder */
-  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE,
-      SPA_TYPE_Object);
+  WpSpaType type;
+  WpSpaIdTable table;
+  WpSpaIdValue id;
 
   /* Find the type */
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, type_name, &type,
-      NULL, &self->prop_table))
-    g_return_val_if_reached (NULL);
+  type = wp_spa_type_from_name (type_name);
+  g_return_val_if_fail (wp_spa_type_is_object (type), NULL);
 
   /* Find the id */
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_PARAM, id_name, &id, NULL,
-      NULL))
-    g_return_val_if_reached (NULL);
+  table = wp_spa_type_get_object_id_values_table (type);
+  g_return_val_if_fail (table != NULL, NULL);
+
+  id = wp_spa_id_table_find_value_from_short_name (table, id_name);
+  g_return_val_if_fail (id != NULL, NULL);
+
+  /* Construct the builder */
+  self = wp_spa_pod_builder_new (WP_SPA_POD_BUILDER_REALLOC_STEP_SIZE, type);
 
   /* Push the object */
-  spa_pod_builder_push_object (&self->builder, &self->frame, type, id);
+  spa_pod_builder_push_object (&self->builder, &self->frame, type,
+      wp_spa_id_value_number (id));
 
   return self;
 }
@@ -2145,10 +2126,8 @@ void
 wp_spa_pod_builder_add_pointer (WpSpaPodBuilder *self, const char *type_name,
     gconstpointer value)
 {
-  guint32 type = 0;
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, type_name, &type,
-      NULL, NULL))
-    g_return_if_reached ();
+  WpSpaType type = wp_spa_type_from_name (type_name);
+  g_return_if_fail (wp_spa_type_parent (type) == SPA_TYPE_Pointer);
   spa_pod_builder_pointer (&self->builder, type, value);
 }
 
@@ -2218,10 +2197,10 @@ wp_spa_pod_builder_add_pod (WpSpaPodBuilder *self, WpSpaPod *pod)
 void
 wp_spa_pod_builder_add_property (WpSpaPodBuilder *self, const char *key)
 {
-  guint32 id = 0;
-  if (!wp_spa_type_get_by_nick (self->prop_table, key, &id, NULL, NULL))
-      g_return_if_reached ();
-  spa_pod_builder_prop (&self->builder, id, 0);
+  WpSpaIdTable table = wp_spa_type_get_values_table (self->type);
+  WpSpaIdValue id = wp_spa_id_table_find_value_from_short_name (table, key);
+  g_return_if_fail (id != NULL);
+  spa_pod_builder_prop (&self->builder, wp_spa_id_value_number (id), 0);
 }
 
 /**
@@ -2249,11 +2228,10 @@ void
 wp_spa_pod_builder_add_control (WpSpaPodBuilder *self, guint32 offset,
     const char *type_name)
 {
-  guint type = 0;
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_CONTROL, type_name, &type,
-      NULL, NULL))
-    g_return_if_reached ();
-  spa_pod_builder_control (&self->builder, offset, type);
+  WpSpaIdValue id = wp_spa_id_value_from_short_name (
+      SPA_TYPE_INFO_Control, type_name);
+  g_return_if_fail (id != NULL);
+  spa_pod_builder_control (&self->builder, offset, wp_spa_id_value_number (id));
 }
 
 /**
@@ -2288,41 +2266,30 @@ wp_spa_pod_builder_add_valist (WpSpaPodBuilder *self, va_list args)
     struct spa_pod_frame f;
     gboolean choice;
 
-    switch (self->type) {
-    case SPA_TYPE_Object:
-    {
-      guint32 key = 0;
+    if (wp_spa_type_is_object (self->type)) {
       const char *key_name = va_arg(args, const char *);
       if (!key_name)
         return;
-      if (!wp_spa_type_get_by_nick (self->prop_table, key_name, &key, NULL,
-          NULL))
-        g_return_if_reached ();
-      if (key == 0)
-        return;
+      WpSpaIdTable table = wp_spa_type_get_values_table (self->type);
+      WpSpaIdValue key =
+          wp_spa_id_table_find_value_from_short_name (table, key_name);
+      g_return_if_fail (key != NULL);
 
-      spa_pod_builder_prop (&self->builder, key, 0);
-      break;
+      spa_pod_builder_prop (&self->builder, wp_spa_id_value_number (key), 0);
     }
-    case SPA_TYPE_Sequence:
-    {
+    else if (self->type == SPA_TYPE_Sequence) {
       guint32 offset = va_arg(args, uint32_t);
-      guint32 type = 0;
       if (offset == 0)
         return;
       const char *control_name = va_arg(args, const char *);
       if (!control_name)
         return;
-      if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_CONTROL, control_name,
-          &type, NULL, NULL))
-        g_return_if_reached ();
-      if (type == 0)
-        return;
+      WpSpaIdValue type = wp_spa_id_value_from_short_name (
+          SPA_TYPE_INFO_Control, control_name);
+      g_return_if_fail (type != NULL);
 
-      spa_pod_builder_control (&self->builder, offset, type);
-    }
-    default:
-      break;
+      spa_pod_builder_control (&self->builder, offset,
+          wp_spa_id_value_number (type));
     }
 
     if ((format = va_arg(args, const char *)) == NULL)
@@ -2376,9 +2343,10 @@ wp_spa_pod_builder_end (WpSpaPodBuilder *self)
   ret->pod = spa_pod_builder_pop (&self->builder, &self->frame);
   ret->builder = wp_spa_pod_builder_ref (self);
 
-  /* Also copy the property table if it is an object type */
-  if (ret->builder->type == SPA_TYPE_Object)
-    ret->static_pod.data_property.table = ret->builder->prop_table;
+  /* Also copy the specific object type if it is an object */
+  if (spa_pod_is_object (ret->pod))
+    ret->static_pod.data_property.table =
+        wp_spa_type_get_values_table (ret->builder->type);
 
   return ret;
 }
@@ -2427,7 +2395,6 @@ wp_spa_pod_parser_new (WpSpaPod *pod, guint32 type)
 /**
  * wp_spa_pod_parser_new:
  * @pod: the object spa pod to parse
- * @type_name: the type name of the object type
  * @id_name: the Id name of the object
  *
  * Creates an object spa pod parser. The @pod object must be valid for the
@@ -2436,22 +2403,21 @@ wp_spa_pod_parser_new (WpSpaPod *pod, guint32 type)
  * Returns: (transfer full): The new spa pod parser
  */
 WpSpaPodParser *
-wp_spa_pod_parser_new_object (WpSpaPod *pod, const char *type_name,
-    const char **id_name)
+wp_spa_pod_parser_new_object (WpSpaPod *pod, const char **id_name)
 {
   WpSpaPodParser *self = NULL;
-  guint32 type = 0;
-  guint32 id = 0;
+  WpSpaType type = wp_spa_pod_get_spa_type (pod);
+  guint32 id = SPA_ID_INVALID;
 
   g_return_val_if_fail (wp_spa_pod_is_object (pod), NULL);
 
-  self = wp_spa_pod_parser_new (pod, SPA_TYPE_Object);
-  if (!wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, type_name, &type,
-      NULL, &self->prop_table))
-    g_return_val_if_reached (NULL);
+  self = wp_spa_pod_parser_new (pod, type);
   spa_pod_parser_push_object (&self->parser, &self->frame, type, &id);
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, id, NULL, id_name, NULL))
-    g_return_val_if_reached (NULL);
+  if (id_name) {
+    WpSpaIdTable table = wp_spa_type_get_object_id_values_table (type);
+    *id_name = wp_spa_id_value_short_name (
+        wp_spa_id_table_find_value (table, id));
+  }
   return self;
 }
 
@@ -2611,7 +2577,6 @@ wp_spa_pod_parser_get_bytes (WpSpaPodParser *self, gconstpointer *value,
 /**
  * wp_spa_pod_parser_get_pointer:
  * @self: the spa pod parser object
- * @type_name: (out): the type name of the pointer value
  * @value: (out): the pointer value
  *
  * Gets the pointer value and its type name from a spa pod parser object
@@ -2619,20 +2584,11 @@ wp_spa_pod_parser_get_bytes (WpSpaPodParser *self, gconstpointer *value,
  * Returns: TRUE if the value was obtained, FALSE otherwise
  */
 gboolean
-wp_spa_pod_parser_get_pointer (WpSpaPodParser *self, const char **type_name,
-  gconstpointer *value)
+wp_spa_pod_parser_get_pointer (WpSpaPodParser *self, gconstpointer *value)
 {
-  guint32 type = 0;
-  gboolean res;
-
   g_return_val_if_fail (value, FALSE);
-
-  res = spa_pod_parser_get_pointer (&self->parser, &type, value) >= 0;
-
-  if (!wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, type, NULL, type_name,
-      NULL))
-    g_return_val_if_reached (FALSE);
-  return res;
+  guint32 type = 0;
+  return spa_pod_parser_get_pointer (&self->parser, &type, value) >= 0;
 }
 
 /**
@@ -2759,20 +2715,19 @@ wp_spa_pod_parser_get_valist (WpSpaPodParser *self, va_list args)
     const struct spa_pod *pod = NULL;
     const char *format;
 
-    if (self->type == SPA_TYPE_Object) {
-      guint32 key = 0;
-      const struct spa_pod_object *object;
+    if (wp_spa_type_is_object (self->type)) {
       const char *key_name = va_arg(args, const char *);
       if (!key_name)
         break;
-      if (!wp_spa_type_get_by_nick (self->prop_table, key_name, &key, NULL, NULL))
-        g_return_val_if_reached (FALSE);
-      if (key == 0)
-        break;
-
-      object = (const struct spa_pod_object *)spa_pod_parser_frame (
-          &self->parser, &self->frame);
-      prop = spa_pod_object_find_prop (object, prop, key);
+      WpSpaIdTable table = wp_spa_type_get_values_table (self->type);
+      WpSpaIdValue key =
+          wp_spa_id_table_find_value_from_short_name (table, key_name);
+      g_return_val_if_fail (key != NULL, FALSE);
+
+      const struct spa_pod_object *object = (const struct spa_pod_object *)
+          spa_pod_parser_frame (&self->parser, &self->frame);
+      prop = spa_pod_object_find_prop (object, prop,
+          wp_spa_id_value_number (key));
       pod = prop ? &prop->value : NULL;
     }
 
@@ -2884,7 +2839,6 @@ wp_spa_pod_iterator_next_object (WpSpaPodIterator *self, GValue *item)
 {
   const struct spa_pod_object *pod_obj =
       (const struct spa_pod_object *) self->pod->pod;
-  g_autoptr (WpSpaPod) pod = NULL;
 
   if (!self->curr.prop)
     self->curr.prop = spa_pod_prop_first (&pod_obj->body);
@@ -2928,7 +2882,6 @@ wp_spa_pod_iterator_next_sequence (WpSpaPodIterator *self, GValue *item)
 {
   const struct spa_pod_sequence *pod_seq =
       (const struct spa_pod_sequence *) self->pod->pod;
-  g_autoptr (WpSpaPod) pod = NULL;
 
   if (!self->curr.control)
     self->curr.control = spa_pod_control_first (&pod_seq->body);
diff --git a/lib/wp/spa-pod.h b/lib/wp/spa-pod.h
index f66c7879..6627208f 100644
--- a/lib/wp/spa-pod.h
+++ b/lib/wp/spa-pod.h
@@ -13,6 +13,7 @@
 
 #include "defs.h"
 #include "iterator.h"
+#include "spa-type.h"
 
 G_BEGIN_DECLS
 
@@ -45,19 +46,16 @@ WP_API
 const struct spa_pod * wp_spa_pod_get_spa_pod (const WpSpaPod *self);
 
 WP_API
-const char *wp_spa_pod_get_type_name (WpSpaPod *self);
+WpSpaType wp_spa_pod_get_spa_type (WpSpaPod *self);
 
 WP_API
-const char *wp_spa_pod_get_choice_type_name (WpSpaPod *self);
-
-WP_API
-const char *wp_spa_pod_get_object_type_name (WpSpaPod *self);
+WpSpaIdValue wp_spa_pod_get_choice_type (WpSpaPod *self);
 
 WP_API
 WpSpaPod *wp_spa_pod_copy (WpSpaPod *other);
 
 WP_API
-gboolean wp_spa_pod_is_unique_owner ( WpSpaPod *self);
+gboolean wp_spa_pod_is_unique_owner (WpSpaPod *self);
 
 WP_API
 WpSpaPod *wp_spa_pod_ensure_unique_owner (WpSpaPod *self);
@@ -102,11 +100,11 @@ WP_API
 WpSpaPod *wp_spa_pod_new_fraction (guint32 num, guint32 denom);
 
 WP_API
-WpSpaPod *wp_spa_pod_new_choice (const char *type_name, ...)
+WpSpaPod *wp_spa_pod_new_choice (const char *choice_type, ...)
     G_GNUC_NULL_TERMINATED;
 
 WP_API
-WpSpaPod *wp_spa_pod_new_choice_valist (const char *type_name, va_list args);
+WpSpaPod *wp_spa_pod_new_choice_valist (const char *choice_type, va_list args);
 
 WP_API
 WpSpaPod *wp_spa_pod_new_object (const char *type_name, const char *id_name,
@@ -208,8 +206,7 @@ gboolean wp_spa_pod_get_bytes (WpSpaPod *self, gconstpointer *value,
     guint32 *len);
 
 WP_API
-gboolean wp_spa_pod_get_pointer (WpSpaPod *self, const char **type_name,
-   gconstpointer *value);
+gboolean wp_spa_pod_get_pointer (WpSpaPod *self, gconstpointer *value);
 
 WP_API
 gboolean wp_spa_pod_get_fd (WpSpaPod *self, gint64 *value);
@@ -260,11 +257,11 @@ WP_API
 gboolean wp_spa_pod_equal (WpSpaPod *self, WpSpaPod *pod);
 
 WP_API
-gboolean wp_spa_pod_get_object (WpSpaPod *self, const char *type_name,
+gboolean wp_spa_pod_get_object (WpSpaPod *self,
     const char **id_name, ...) G_GNUC_NULL_TERMINATED;
 
 WP_API
-gboolean wp_spa_pod_get_object_valist (WpSpaPod *self, const char *type_name,
+gboolean wp_spa_pod_get_object_valist (WpSpaPod *self,
     const char **id_name, va_list args);
 
 WP_API
@@ -279,7 +276,7 @@ gboolean wp_spa_pod_get_property (WpSpaPod *self, const char **key,
 
 WP_API
 gboolean wp_spa_pod_get_control (WpSpaPod *self, guint32 *offset,
-    const char **type_name, WpSpaPod **value);
+    const char **ctl_type, WpSpaPod **value);
 
 WP_API
 WpSpaPod *wp_spa_pod_get_choice_child (WpSpaPod *self);
@@ -314,7 +311,7 @@ WP_API
 WpSpaPodBuilder *wp_spa_pod_builder_new_array (void);
 
 WP_API
-WpSpaPodBuilder *wp_spa_pod_builder_new_choice (const char *type_name);
+WpSpaPodBuilder *wp_spa_pod_builder_new_choice (const char *choice_type);
 
 WP_API
 WpSpaPodBuilder *wp_spa_pod_builder_new_object (const char *type_name,
@@ -380,7 +377,7 @@ void wp_spa_pod_builder_add_property_id (WpSpaPodBuilder *self, guint32 id);
 
 WP_API
 void wp_spa_pod_builder_add_control (WpSpaPodBuilder *self, guint32 offset,
-    const char *type_name);
+    const char *ctl_type);
 
 WP_API
 void wp_spa_pod_builder_add (WpSpaPodBuilder *self, ...) G_GNUC_NULL_TERMINATED;
@@ -413,7 +410,7 @@ void wp_spa_pod_parser_unref (WpSpaPodParser *self);
 
 WP_API
 WpSpaPodParser *wp_spa_pod_parser_new_object (WpSpaPod *pod,
-    const char *type_name, const char **id_name);
+    const char **id_name);
 
 WP_API
 WpSpaPodParser *wp_spa_pod_parser_new_struct (WpSpaPod *pod);
@@ -446,7 +443,7 @@ gboolean wp_spa_pod_parser_get_bytes (WpSpaPodParser *self,
 
 WP_API
 gboolean wp_spa_pod_parser_get_pointer (WpSpaPodParser *self,
-    const char **type_name, gconstpointer *value);
+    gconstpointer *value);
 
 WP_API
 gboolean wp_spa_pod_parser_get_fd (WpSpaPodParser *self, gint64 *value);
diff --git a/lib/wp/spa-type.c b/lib/wp/spa-type.c
index f7d5783b..9f011562 100644
--- a/lib/wp/spa-type.c
+++ b/lib/wp/spa-type.c
@@ -6,336 +6,706 @@
  * SPDX-License-Identifier: MIT
  */
 
+/**
+ * SECTION: spa-type
+ * @title: 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.
+ *
+ * ### Type 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 enums or objects, for instance.
+ *
+ * Enum and flag types are all derived directly from `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 #WpSpaIdValue API.
+ *
+ * Object types can have fields. All objects always have a special "id" field,
+ * whose type can be given by wp_spa_object_type_get_id_type() and optionally,
+ * they can also have other object-specific fields, which are also accessed
+ * with the #WpSpaIdValue API.
+ */
+
 #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_NodeEvent, spa_type_node_event_id },
+  { SPA_TYPE_INFO_NodeCommand, spa_type_node_command_id },
+  { NULL, NULL }
+};
 
-#include "spa-type.h"
+/**
+ * WpSpaType:
+ */
+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;
+}
 
-static gboolean
-name_equal_func (gconstpointer a, gconstpointer b) {
-  const struct spa_type_info *ti = a;
-  const char *name = b;
-  return g_strcmp0 (ti->name, name) == 0;
+/**
+ * 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 (const struct spa_type_info *table, GEqualFunc func,
-    gconstpointer data)
+_spa_type_find_by_name (const struct spa_type_info * info, const char * name)
 {
-  for (guint32 i = 0; table[i].name; i++)
-    if (func (table + i, data))
-      return table + i;
+  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 *
-spa_type_find_by_name (const struct spa_type_info *table, const char *name)
+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;
+}
+
+/**
+ * wp_spa_type_from_name:
+ * @name: the name to look up
+ *
+ * Looks up the type id from a given type name
+ *
+ * Returns: the corresponding type id or %WP_SPA_TYPE_INVALID if not found
+ */
+WpSpaType
+wp_spa_type_from_name (const gchar *name)
 {
-  return spa_type_find (table, name_equal_func, name);
+  const struct spa_type_info *info = wp_spa_type_info_find_by_name (name);
+  return info ? info->type : WP_SPA_TYPE_INVALID;
 }
 
-struct type_info {
-  gboolean is_spa_type;
-  union {
-    const struct spa_type_info *spa;
-    struct {
-      guint32 type;
-      char *name;
-    } custom;
-  } info;
-  char *nick;
-};
+/**
+ * wp_spa_type_parent:
+ * @type: a type id
+ *
+ * Returns: the direct parent type of the given @type; if the type is
+ *   fundamental (i.e. has no parent), the returned type is the same as @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;
+}
 
-static struct type_info *
-type_info_new_spa (const struct spa_type_info *info, const char* nick) {
-  struct type_info *ti = g_slice_new0 (struct type_info);
-  ti->is_spa_type = TRUE;
-  ti->info.spa = info;
-  ti->nick = g_strdup (nick);
-  return ti;
+/**
+ * wp_spa_type_name:
+ * @type: a type id
+ *
+ * Returns: the complete name of the given @type or %NULL if @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;
 }
 
-static struct type_info *
-type_info_new_custom (uint32_t type, const char *name, const char* nick) {
-  struct type_info *ti = g_slice_new0 (struct type_info);
-  ti->is_spa_type = FALSE;
-  ti->info.custom.type = type;
-  ti->info.custom.name = g_strdup (name);
-  ti->nick = g_strdup (nick);
-  return ti;
+/**
+ * wp_spa_type_is_fundamental:
+ * @type: a type id
+ *
+ * Returns: %TRUE if the @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;
 }
 
-static void
-type_info_free (struct type_info *ti) {
-  g_return_if_fail (ti);
-  if (!ti->is_spa_type)
-    g_clear_pointer (&ti->info.custom.name, g_free);
-  g_clear_pointer (&ti->nick, g_free);
-  g_slice_free (struct type_info, ti);
-}
-
-struct spa_type_table_data {
-  const struct spa_type_info *spa_table;
-  guint32 last_id;
-  GPtrArray *info_array;
-  GHashTable *id_table;
-  GHashTable *nick_table;
-};
+/**
+ * wp_spa_type_is_id:
+ * @type: a type id
+ *
+ * Returns: %TRUE if the @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;
+}
 
-static struct spa_type_table_data s_tables [WP_SPA_TYPE_TABLE_LAST] = {
-  [WP_SPA_TYPE_TABLE_BASIC]             = {spa_types, SPA_TYPE_VENDOR_Other, NULL, NULL, NULL },
-  [WP_SPA_TYPE_TABLE_PARAM]             = {spa_type_param, SPA_N_ELEMENTS (spa_type_param), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_PROPS]             = {spa_type_props, SPA_PROP_START_CUSTOM, NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_PROP_INFO]         = {spa_type_prop_info, SPA_N_ELEMENTS (spa_type_prop_info), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_FORMAT]            = {spa_type_format, SPA_N_ELEMENTS (spa_type_format), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_PARAM_PORT_CONFIG] = {spa_type_param_port_config, SPA_N_ELEMENTS (spa_type_param_port_config), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_PARAM_PROFILE]     = {spa_type_param_profile, SPA_N_ELEMENTS (spa_type_param_profile), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_CONTROL]           = {spa_type_control, SPA_CONTROL_LAST, NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_CHOICE]            = {spa_type_choice, SPA_N_ELEMENTS (spa_type_choice), NULL, NULL, NULL, },
-  [WP_SPA_TYPE_TABLE_AUDIO_CHANNEL]     = {spa_type_audio_channel, SPA_N_ELEMENTS (spa_type_audio_channel), NULL, NULL, NULL, },
-};
+/**
+ * wp_spa_type_is_object:
+ * @type: a type id
+ *
+ * Returns: %TRUE if the @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;
+}
 
-static WpSpaTypeTable
-wp_spa_type_table_find_by_spa_table (const struct spa_type_info *spa_table)
+/**
+ * wp_spa_type_get_object_id_values_table:
+ * @type: the type id of an object 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.
+ *
+ * Returns: the table with the values that can be stored in the special "id"
+ *   field of an object of the given @type
+ */
+WpSpaIdTable
+wp_spa_type_get_object_id_values_table (WpSpaType type)
 {
-  for (guint32 i = 0; i < WP_SPA_TYPE_TABLE_LAST; i++)
-    if (s_tables[i].spa_table == spa_table)
-      return i;
-  return 0;
+  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;
 }
 
 /**
- * wp_spa_type_init:
- * @register_spa: whether spa types will be registered or not
+ * wp_spa_type_get_values_table:
+ * @type: a type id
  *
- * Initializes the spa type registry
+ * Returns: the associated #WpSpaIdTable that contains possible
+ *   values or object fields for this type, or %NULL
  */
-void
-wp_spa_type_init (gboolean register_spa)
-{
-  /* Init the array and hash tables */
-  for (guint32 i = 0; i < WP_SPA_TYPE_TABLE_LAST; i++) {
-    struct spa_type_table_data *td = s_tables + i;
-    if (!td->info_array)
-        td->info_array = g_ptr_array_new_with_free_func ((GDestroyNotify)
-            type_info_free);
-    if (!td->id_table)
-        td->id_table = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-            NULL, NULL);
-    if (!td->nick_table)
-        td->nick_table = g_hash_table_new_full (g_str_hash, g_str_equal,
-            g_free, 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,
+};
 
-  /* Register the spa types if requested */
-  if (register_spa) {
-    for (guint32 i = 0; i < WP_SPA_TYPE_TABLE_LAST; i++) {
-      struct spa_type_table_data *td = s_tables + i;
-      for (guint32 j = 0; td->spa_table && td->spa_table[j].name; j++) {
-        const struct spa_type_info *t = td->spa_table + j;
-        const char *nick = strrchr (t->name, ':');
-        if (nick && strlen (nick) > 1)
-          wp_spa_type_register (i, t->name, nick + 1);
-      }
+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;
 }
 
 /**
- * wp_spa_type_deinit:
+ * wp_spa_id_table_iterate:
+ * @type: the id table
+ *
+ * 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.
  *
- * Deinitializes the spa type registry
+ * Returns: a #WpIterator that iterates over #WpSpaIdValue items
  */
-void
-wp_spa_type_deinit (void)
+WpIterator *
+wp_spa_id_table_iterate (WpSpaIdTable table)
 {
-  for (guint32 i = 0; i < WP_SPA_TYPE_TABLE_LAST; i++) {
-    struct spa_type_table_data *td = s_tables + i;
-    g_clear_pointer (&td->info_array, g_ptr_array_unref);
-    g_clear_pointer (&td->id_table, g_hash_table_unref);
-    g_clear_pointer (&td->nick_table, g_hash_table_unref);
+  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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
 }
 
 /**
- * wp_spa_type_get_table_size:
- * @table: the table
+ * wp_spa_id_value_from_name:
+ * @name: the full name of an id value
  *
- * Gets the number of registered types in a given table
+ * 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 number of registered types
+ * Returns: the id value for @name, or %NULL if no such id value was found
  */
-size_t
-wp_spa_type_get_table_size (WpSpaTypeTable table)
+WpSpaIdValue
+wp_spa_id_value_from_name (const gchar * name)
 {
-  struct spa_type_table_data *td = NULL;
+  g_return_val_if_fail (name != NULL, NULL);
 
-  g_return_val_if_fail (table < WP_SPA_TYPE_TABLE_LAST, 0);
+  WpSpaIdTable table = wp_spa_id_name_find_id_table (name);
+  return wp_spa_id_table_find_value_from_name (table, name);
+}
 
-  /* Get the table data */
-  td = s_tables + table;
+/**
+ * wp_spa_id_value_from_short_name:
+ * @table_name: the name of the #WpSpaIdTable to look up the value in
+ * @short_name: the short name of the value to look up
+ *
+ * Looks up an id value given its container @table_name and its @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);
 
-  return td->info_array->len;
+  WpSpaIdTable table = wp_spa_id_table_from_name (table_name);
+  return wp_spa_id_table_find_value_from_short_name (table, short_name);
 }
 
 /**
- * wp_spa_type_register:
- * @table: the table
- * @name: the name of the type
- * @nick: the nick name of the type
+ * wp_spa_id_value_from_number:
+ * @table_name: the name of the #WpSpaIdTable to look up the value in
+ * @id: the numeric representation of the value to look up
  *
- * Registers a type name with a nickname in the registry
+ * Looks up an id value given its container @table_name and its numeric
+ * representation, @id
  *
- * Returns: TRUE if the type could be registered, FALSE otherwise
+ * Returns: the id value or %NULL if it was not found
  */
-gboolean
-wp_spa_type_register (WpSpaTypeTable table, const char *name, const char *nick)
-{
-  struct spa_type_table_data *td = NULL;
-  const struct spa_type_info *spa_info = NULL;
-  struct type_info *info = NULL;
-  guint32 id;
-
-  g_return_val_if_fail (table < WP_SPA_TYPE_TABLE_LAST, FALSE);
-  g_return_val_if_fail (name, FALSE);
-  g_return_val_if_fail (nick, FALSE);
-
-  /* Get the table data */
-  td = s_tables + table;
-
-  /* Return false if the nick already exists in the nick table */
-  if (g_hash_table_contains (td->nick_table, nick))
-    return FALSE;
-
-  /* Add the type in the custom table */
-  spa_info = spa_type_find_by_name (td->spa_table, name);
-  if (spa_info) {
-    id = spa_info->type;
-    info = type_info_new_spa (spa_info, nick);
-  } else {
-    id = ++td->last_id;
-    info = type_info_new_custom (id, name, nick);
-  }
-  g_ptr_array_add (td->info_array, info);
+WpSpaIdValue
+wp_spa_id_value_from_number (const gchar * table_name, guint id)
+{
+  g_return_val_if_fail (table_name != NULL, NULL);
 
-  /* Insert the id and nick in the hash tables */
-  return g_hash_table_insert (td->id_table, GUINT_TO_POINTER (id), info) &&
-      g_hash_table_insert (td->nick_table, g_strdup (nick), info);
+  WpSpaIdTable table = wp_spa_id_table_from_name (table_name);
+  return wp_spa_id_table_find_value (table, id);
 }
 
 /**
- * wp_spa_type_unregister:
- * @table: the table
- * @nick: the nick name of the type
+ * wp_spa_id_value_number:
+ * @id: an id value
  *
- * Unregisters a type given its nick name
+ * Returns: the numeric representation of this id value
  */
-void
-wp_spa_type_unregister (WpSpaTypeTable table, const char *nick)
+guint
+wp_spa_id_value_number (WpSpaIdValue id)
 {
-  struct spa_type_table_data *td = NULL;
-  struct type_info *info = NULL;
-  guint32 id;
-
-  g_return_if_fail (table < WP_SPA_TYPE_TABLE_LAST);
-  g_return_if_fail (nick);
+  g_return_val_if_fail (id != NULL, -1);
 
-  /* Get the table data */
-  td = s_tables + table;
+  const struct spa_type_info *info = id;
+  return info->type;
+}
 
-  /* Lookup the info by nick */
-  info = g_hash_table_lookup (td->nick_table, nick);
-  if (!info)
-    return;
+/**
+ * wp_spa_id_value_name:
+ * @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);
 
-  /* Get id */
-  id = info->is_spa_type ? info->info.spa->type : info->info.custom.type;
+  const struct spa_type_info *info = id;
+  return info->name;
+}
 
-  /* Remove info from hash tables */
-  g_hash_table_remove (td->nick_table, nick);
-  g_hash_table_remove (td->id_table, GUINT_TO_POINTER (id));
+/**
+ * wp_spa_id_value_short_name:
+ * @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);
 
-  /* Remove info from array */
-  g_ptr_array_remove_fast (td->info_array, info);
+  const struct spa_type_info *info = id;
+  return spa_debug_type_short_name (info->name);
 }
 
 /**
- * wp_spa_type_get_by_nick:
- * @table: the table
- * @nick: the nick name of the type
- * @id: (out) (optional): the id of the type
- * @name: (out) (optional): the name of the type
- * @values_table: (out) (optional): the values table of the type
+ * wp_spa_id_value_get_value_type
+ * @id: an id value
+ * @table: (out) (optional): the associated #WpSpaIdTable
+ *
+ * Returns the value type associated with this #WpSpaIdValue. This information
+ * is useful when @id represents an object field, which can take a value
+ * of an arbitrary type.
  *
- * Gets the id and name of the registered type given its nick name
+ * When the returned type is (or is derived from) `SPA_TYPE_Id` or
+ * `SPA_TYPE_Object`, @table is set to point to the #WpSpaIdTable that contains
+ * the possible Id values / object fields.
  *
- * Returns: TRUE if the type was found, FALSE otherwise
+ * Returns: the value type associated with @id
  */
-gboolean
-wp_spa_type_get_by_nick (WpSpaTypeTable table, const char *nick,
-    guint32 *id, const char **name, WpSpaTypeTable *values_table)
+WpSpaType
+wp_spa_id_value_get_value_type (WpSpaIdValue id, WpSpaIdTable * table)
 {
-  struct spa_type_table_data *td = NULL;
-  const struct type_info *info = NULL;
+  g_return_val_if_fail (id != NULL, WP_SPA_TYPE_INVALID);
 
-  g_return_val_if_fail (table < WP_SPA_TYPE_TABLE_LAST, FALSE);
+  const struct spa_type_info *info = id;
 
-  /* Make sure nick is valid */
-  if (!nick)
-    return FALSE;
+  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;
+}
 
-  /* Get the table data */
-  td = s_tables + table;
+/**
+ * wp_spa_id_value_array_get_item_type:
+ * @id: an id value
+ * @table: (out) (optional): the associated #WpSpaIdTable
+ *
+ * If the value type of @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`, @table is set to point to the #WpSpaIdTable that contains
+ * the possible Id values / object fields.
+ *
+ * Returns: the type that is allowed in the array, if @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);
 
-  /* Lookup the info by nick */
-  info = g_hash_table_lookup (td->nick_table, nick);
-  if (!info)
-    return FALSE;
+  const struct spa_type_info *info = id;
+  g_return_val_if_fail (info->parent == SPA_TYPE_Array, WP_SPA_TYPE_INVALID);
 
-  if (id)
-    *id = info->is_spa_type ? info->info.spa->type : info->info.custom.type;
-  if (name)
-    *name = info->is_spa_type ? info->info.spa->name : info->info.custom.name;
-  if (values_table && info->is_spa_type)
-    *values_table = wp_spa_type_table_find_by_spa_table (info->info.spa->values);
-  return TRUE;
+  return info->values ?
+      wp_spa_id_value_get_value_type (info->values, table) :
+      WP_SPA_TYPE_INVALID;
 }
 
+
 /**
- * wp_spa_type_get_by_id:
- * @table: the table
- * @id: the id of the type
- * @name: (out) (optional): the name of the type
- * @nick: (out) (optional): the nick name of the type
- * @values_table: (out) (optional): the values table of the type
+ * wp_spa_dynamic_type_init:
  *
- * Gets the name and nick name of the registered type given its id
+ * 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.
  *
- * Returns: TRUE if the type was found, FALSE otherwise
+ * Normally called by wp_init() when %WP_INIT_SPA_TYPES is passed in its flags.
  */
-gboolean
-wp_spa_type_get_by_id (WpSpaTypeTable table, guint32 id,
-    const char **name, const char **nick, WpSpaTypeTable *values_table)
+void
+wp_spa_dynamic_type_init (void)
 {
-  struct spa_type_table_data *td = NULL;
-  const struct type_info *info = NULL;
-
-  g_return_val_if_fail (table < WP_SPA_TYPE_TABLE_LAST, FALSE);
+  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);
+}
 
-  /* Get the table data */
-  td = s_tables + table;
+/**
+ * wp_spa_dynamic_type_deinit:
+ *
+ * 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);
+}
 
-  /* Lookup the info by id */
-  info = g_hash_table_lookup (td->id_table, GUINT_TO_POINTER (id));
-  if (!info)
-    return FALSE;
+/**
+ * wp_spa_dynamic_type_register:
+ * @name: the name of the type
+ * @parent: the parent type
+ * @values: an array of `spa_type_info` that contains the values of the type,
+ *   used only for Object types
+ *
+ * Registers an additional type in the spa type system.
+ * This is useful to add a custom pod object type.
+ *
+ * Note that both @name and @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 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;
+}
 
-  if (name)
-    *name = info->is_spa_type ? info->info.spa->name : info->info.custom.name;
-  if (nick)
-    *nick = info->nick;
-  if (values_table && info->is_spa_type)
-    *values_table = wp_spa_type_table_find_by_spa_table (info->info.spa->values);
-  return TRUE;
+/**
+ * wp_spa_dynamic_id_table_register:
+ * @name: the name of the id table
+ * @values: an array of `spa_type_info` that contains the values of the table
+ *
+ * Registers an additional #WpSpaIdTable in the spa type system.
+ * This is useful to add custom enumeration types.
+ *
+ * Note that both @name and @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;
 }
diff --git a/lib/wp/spa-type.h b/lib/wp/spa-type.h
index 41ea40d1..2a85fe91 100644
--- a/lib/wp/spa-type.h
+++ b/lib/wp/spa-type.h
@@ -9,63 +9,121 @@
 #ifndef __WIREPLUMBER_SPA_TYPE_H__
 #define __WIREPLUMBER_SPA_TYPE_H__
 
-#include <gio/gio.h>
 #include "defs.h"
+#include "iterator.h"
 
 G_BEGIN_DECLS
 
-/**
- * WpSpaTypeTable:
- * @WP_SPA_TYPE_TABLE_BASIC: The basic type table
- * @WP_SPA_TYPE_TABLE_PARAM: The param type table (used as object id)
- * @WP_SPA_TYPE_TABLE_PROPS: The object properties type table
- * @WP_SPA_TYPE_TABLE_PROP_INFO: The object property info type table
- * @WP_SPA_TYPE_TABLE_CONTROL: The sequence control type table
- * @WP_SPA_TYPE_TABLE_CHOICE: The choice type table
- * @WP_SPA_TYPE_TABLE_FORMAT: The object format type table
- * @WP_SPA_TYPE_TABLE_PARAM_PORT_CONFIG: The object param port config type table
- * @WP_SPA_TYPE_TABLE_PARAM_PROFILE: The sequence control type table
- * @WP_SPA_TYPE_TABLE_AUDIO_CHANNEL: The audio channel type table
- *
- * The diferent tables (namespaces) the registry has.
- */
-typedef enum {
-  WP_SPA_TYPE_TABLE_BASIC = 0,
-  WP_SPA_TYPE_TABLE_PARAM,
-  WP_SPA_TYPE_TABLE_PROPS,
-  WP_SPA_TYPE_TABLE_PROP_INFO,
-  WP_SPA_TYPE_TABLE_CONTROL,
-  WP_SPA_TYPE_TABLE_CHOICE,
-  WP_SPA_TYPE_TABLE_FORMAT,
-  WP_SPA_TYPE_TABLE_PARAM_PORT_CONFIG,
-  WP_SPA_TYPE_TABLE_PARAM_PROFILE,
-  WP_SPA_TYPE_TABLE_AUDIO_CHANNEL,
-  WP_SPA_TYPE_TABLE_LAST,
-} WpSpaTypeTable;
+typedef guint32 WpSpaType;
+typedef gconstpointer WpSpaIdTable;
+typedef gconstpointer WpSpaIdValue;
+struct spa_type_info;
+
+/* WpSpaType */
+
+#define WP_TYPE_SPA_TYPE (wp_spa_type_get_type ())
+WP_API
+GType wp_spa_type_get_type (void);
+
+static const WpSpaType WP_SPA_TYPE_INVALID = 0xffffffff;
+
+WP_API
+WpSpaType wp_spa_type_from_name (const gchar *name);
+
+WP_API
+WpSpaType wp_spa_type_parent (WpSpaType type);
+
+WP_API
+const gchar * wp_spa_type_name (WpSpaType type);
+
+WP_API
+gboolean wp_spa_type_is_fundamental (WpSpaType type);
+
+WP_API
+gboolean wp_spa_type_is_id (WpSpaType type);
+
+WP_API
+gboolean wp_spa_type_is_object (WpSpaType type);
+
+WP_API
+WpSpaIdTable wp_spa_type_get_object_id_values_table (WpSpaType type);
+
+WP_API
+WpSpaIdTable wp_spa_type_get_values_table (WpSpaType type);
+
+
+/* WpSpaIdTable */
+
+#define WP_TYPE_SPA_ID_TABLE (wp_spa_id_table_get_type ())
+WP_API
+GType wp_spa_id_table_get_type (void);
 
 WP_API
-void wp_spa_type_init (gboolean register_spa);
+WpSpaIdTable wp_spa_id_table_from_name (const gchar *name);
 
 WP_API
-void wp_spa_type_deinit (void);
+WpIterator * wp_spa_id_table_iterate (WpSpaIdTable table);
 
 WP_API
-size_t wp_spa_type_get_table_size (WpSpaTypeTable table);
+WpSpaIdValue wp_spa_id_table_find_value (WpSpaIdTable table, guint value);
+
+WP_API
+WpSpaIdValue wp_spa_id_table_find_value_from_name (WpSpaIdTable table,
+    const gchar * name);
+
+WP_API
+WpSpaIdValue wp_spa_id_table_find_value_from_short_name (WpSpaIdTable table,
+    const gchar * short_name);
+
+
+/* WpSpaIdValue */
+
+#define WP_TYPE_SPA_ID_VALUE (wp_spa_id_value_get_type ())
+WP_API
+GType wp_spa_id_value_get_type (void);
+
+WP_API
+WpSpaIdValue wp_spa_id_value_from_name (const gchar * name);
+
+WP_API
+WpSpaIdValue wp_spa_id_value_from_short_name (const gchar * table_name,
+    const gchar * short_name);
+
+WP_API
+WpSpaIdValue wp_spa_id_value_from_number (const gchar * table_name, guint id);
+
+WP_API
+guint wp_spa_id_value_number (WpSpaIdValue id);
+
+WP_API
+const gchar * wp_spa_id_value_name (WpSpaIdValue id);
+
+WP_API
+const gchar * wp_spa_id_value_short_name (WpSpaIdValue id);
+
+WP_API
+WpSpaType wp_spa_id_value_get_value_type (WpSpaIdValue id, WpSpaIdTable *table);
+
+WP_API
+WpSpaType wp_spa_id_value_array_get_item_type (WpSpaIdValue id,
+    WpSpaIdTable *table);
+
+
+/* Dynamic type registration */
 
 WP_API
-gboolean wp_spa_type_register (WpSpaTypeTable table, const char *name,
-  const char *nick);
+void wp_spa_dynamic_type_init (void);
 
 WP_API
-void wp_spa_type_unregister (WpSpaTypeTable table, const char *nick);
+void wp_spa_dynamic_type_deinit (void);
 
 WP_API
-gboolean wp_spa_type_get_by_nick (WpSpaTypeTable table, const char *nick,
-    guint32 *id, const char **name, WpSpaTypeTable *values_table);
+WpSpaType wp_spa_dynamic_type_register (const gchar *name, WpSpaType parent,
+    const struct spa_type_info * values);
 
 WP_API
-gboolean wp_spa_type_get_by_id (WpSpaTypeTable table, guint32 id,
-    const char **name, const char **nick, WpSpaTypeTable *values_table);
+WpSpaIdTable wp_spa_dynamic_id_table_register (const gchar *name,
+    const struct spa_type_info * values);
 
 G_END_DECLS
 
diff --git a/lib/wp/wp.c b/lib/wp/wp.c
index 8a3ad8e1..be23c739 100644
--- a/lib/wp/wp.c
+++ b/lib/wp/wp.c
@@ -63,7 +63,7 @@ wp_init (WpInitFlags flags)
     pw_log_set_level (lvl);
 
   if (flags & WP_INIT_SPA_TYPES)
-    wp_spa_type_init (TRUE);
+    wp_spa_dynamic_type_init ();
 
   /* ensure WpProxy subclasses are loaded, which is needed to be able
     to autodetect the GType of proxies created through wp_proxy_new_global() */
diff --git a/modules/module-dbus-reservation/reserve-device.c b/modules/module-dbus-reservation/reserve-device.c
index 6b872e40..73db0add 100644
--- a/modules/module-dbus-reservation/reserve-device.c
+++ b/modules/module-dbus-reservation/reserve-device.c
@@ -42,7 +42,7 @@ set_device_profile (WpPipewireObject *device, gint index)
 {
   g_return_if_fail (device);
   g_autoptr (WpSpaPod) profile = wp_spa_pod_new_object (
-      "Profile", "Profile",
+      "Spa:Pod:Object:Param:Profile", "Profile",
       "index", "i", index,
       NULL);
   wp_debug_object (device, "set profile %d", index);
diff --git a/modules/module-default-profile.c b/modules/module-default-profile.c
index 35da6761..7cda94e2 100644
--- a/modules/module-default-profile.c
+++ b/modules/module-default-profile.c
@@ -63,8 +63,7 @@ find_device_profile (WpPipewireObject *device, const gchar *lookup_name)
     const gchar *name = NULL;
 
     /* Parse */
-    if (!wp_spa_pod_get_object (pod,
-        "Profile", NULL,
+    if (!wp_spa_pod_get_object (pod, NULL,
         "index", "i", &index,
         "name", "s", &name,
         NULL)) {
@@ -191,8 +190,7 @@ on_device_profile_notified (WpPipewireObject *device, GAsyncResult *res,
 
   /* Parse the profile */
   WpSpaPod *pod = g_value_get_boxed (&item);
-  if (!wp_spa_pod_get_object (pod,
-      "Profile", NULL,
+  if (!wp_spa_pod_get_object (pod, NULL,
       "index", "i", &index,
       "name", "s", &name,
       NULL)) {
diff --git a/modules/module-device-activation.c b/modules/module-device-activation.c
index 2c704427..d0c0c781 100644
--- a/modules/module-device-activation.c
+++ b/modules/module-device-activation.c
@@ -34,7 +34,7 @@ set_device_profile (WpDeviceActivation *self,
   /* Set profile */
   wp_pipewire_object_set_param (device, "Profile", 0,
       wp_spa_pod_new_object (
-          "Profile", "Profile",
+          "Spa:Pod:Object:Param:Profile", "Profile",
           "index", "i", index,
           NULL));
 
@@ -72,8 +72,7 @@ on_device_enum_profile_done (WpPipewireObject *proxy, GAsyncResult *res,
       const gchar *n = NULL;
 
       /* Parse */
-      if (!wp_spa_pod_get_object (pod,
-          "Profile", NULL,
+      if (!wp_spa_pod_get_object (pod, NULL,
           "index", "i", &i,
           "name", "s", &n,
           NULL)) {
diff --git a/modules/module-endpoint-creation/limited-creation-bluez5.c b/modules/module-endpoint-creation/limited-creation-bluez5.c
index 47ca635c..3ab8bb00 100644
--- a/modules/module-endpoint-creation/limited-creation-bluez5.c
+++ b/modules/module-endpoint-creation/limited-creation-bluez5.c
@@ -207,8 +207,7 @@ on_device_enum_profile_done (WpPipewireObject *proxy, GAsyncResult *res,
 
     /* Parse profile */
     {
-      g_autoptr (WpSpaPodParser) pp = wp_spa_pod_parser_new_object (pod,
-          "Profile", NULL);
+      g_autoptr (WpSpaPodParser) pp = wp_spa_pod_parser_new_object (pod, NULL);
       g_return_if_fail (pp);
       g_return_if_fail (wp_spa_pod_parser_get (pp, "index", "i", &index, NULL));
       if (index == 0) {
diff --git a/modules/module-si-adapter.c b/modules/module-si-adapter.c
index 33ac017a..c09d0826 100644
--- a/modules/module-si-adapter.c
+++ b/modules/module-si-adapter.c
@@ -259,7 +259,7 @@ static WpSpaPod *
 format_audio_raw_build (const struct spa_audio_info_raw *info)
 {
   g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
-      "Format", "Format");
+      "Spa:Pod:Object:Param:Format", "Format");
   wp_spa_pod_builder_add (builder,
       "mediaType",    "I", SPA_MEDIA_TYPE_audio,
       "mediaSubtype", "I", SPA_MEDIA_SUBTYPE_raw,
@@ -327,7 +327,8 @@ si_adapter_activate_execute_step (WpSessionItem * item,
           self->format.channels, self->format.rate);
 
       port_format = format_audio_raw_build (&self->format);
-      pod = wp_spa_pod_new_object ("PortConfig",  "PortConfig",
+      pod = wp_spa_pod_new_object (
+          "Spa:Pod:Object:Param:PortConfig", "PortConfig",
           "direction",  "I", self->direction,
           "mode",       "I", SPA_PARAM_PORT_CONFIG_MODE_dsp,
           "monitor",    "b", self->monitor,
@@ -543,8 +544,12 @@ si_adapter_get_ports (WpSiPortInfo * item, const gchar * context)
     /* try to find the audio channel; if channel is NULL, this will silently
        leave the channel_id to its default value, 0 */
     channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
-    wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_AUDIO_CHANNEL, channel,
-        &channel_id, NULL, NULL);
+    if (channel) {
+      WpSpaIdValue idval = wp_spa_id_value_from_short_name (
+          "Spa:Enum:AudioChannel", channel);
+      if (idval)
+        channel_id = wp_spa_id_value_number (idval);
+    }
 
     g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
   }
diff --git a/modules/module-si-adapter/algorithms.c b/modules/module-si-adapter/algorithms.c
index 052f3c9c..592ea888 100644
--- a/modules/module-si-adapter/algorithms.c
+++ b/modules/module-si-adapter/algorithms.c
@@ -84,16 +84,17 @@ select_format (WpSpaPod *value)
     return ret;
   }
 
-  const gchar * choice_type_name = wp_spa_pod_get_choice_type_name (value);
+  guint32 choice_type =
+      wp_spa_id_value_number (wp_spa_pod_get_choice_type (value));
 
   /* None */
-  if (g_strcmp0 ("None", choice_type_name) == 0) {
+  if (choice_type == SPA_CHOICE_None) {
     g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (value);
     wp_spa_pod_get_id (child, &ret);
   }
 
   /* Enum */
-  else if (g_strcmp0 ("Enum", choice_type_name) == 0) {
+  else if (choice_type == SPA_CHOICE_Enum) {
     g_autoptr (WpIterator) it = wp_spa_pod_iterate (value);
     GValue next = G_VALUE_INIT;
     while (wp_iterator_next (it, &next)) {
@@ -125,16 +126,17 @@ select_rate (WpSpaPod *value)
     return ret;
   }
 
-  const gchar * choice_type_name = wp_spa_pod_get_choice_type_name (value);
+  guint32 choice_type =
+      wp_spa_id_value_number (wp_spa_pod_get_choice_type (value));
 
   /* None */
-  if (g_strcmp0 ("None", choice_type_name) == 0) {
+  if (choice_type == SPA_CHOICE_None) {
     g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (value);
     wp_spa_pod_get_int (child, &ret);
   }
 
   /* Enum */
-  else if (g_strcmp0 ("Enum", choice_type_name) == 0) {
+  else if (choice_type == SPA_CHOICE_Enum) {
     /* pick the one closest to 48Khz */
     g_autoptr (WpIterator) it = wp_spa_pod_iterate (value);
     GValue next = G_VALUE_INIT;
@@ -147,7 +149,7 @@ select_rate (WpSpaPod *value)
   }
 
   /* Range */
-  else if (g_strcmp0 ("Range", choice_type_name) == 0) {
+  else if (choice_type == SPA_CHOICE_Range) {
     /* a range is typically 3 items: default, min, max;
        however, sometimes ALSA drivers give bad min & max values
        and pipewire picks a bad default... try to fix that here;
@@ -180,16 +182,17 @@ select_channels (WpSpaPod *value, gint preference)
     return ret;
   }
 
-  const gchar * choice_type_name = wp_spa_pod_get_choice_type_name (value);
+  guint32 choice_type =
+      wp_spa_id_value_number (wp_spa_pod_get_choice_type (value));
 
   /* None */
-  if (g_strcmp0 ("None", choice_type_name) == 0) {
+  if (choice_type == SPA_CHOICE_None) {
     g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (value);
     wp_spa_pod_get_int (child, &ret);
   }
 
   /* Enum */
-  else if (g_strcmp0 ("Enum", choice_type_name) == 0) {
+  else if (choice_type == SPA_CHOICE_Enum) {
     /* choose the most channels */
     g_autoptr (WpIterator) it = wp_spa_pod_iterate (value);
     GValue next = G_VALUE_INIT;
@@ -205,7 +208,7 @@ select_channels (WpSpaPod *value, gint preference)
   }
 
   /* Range */
-  else if (g_strcmp0 ("Range", choice_type_name) == 0) {
+  else if (choice_type == SPA_CHOICE_Range) {
     /* a range is typically 3 items: default, min, max;
        we want the most channels, but let's not trust max
        to really be the max... ALSA drivers can be broken */
@@ -247,8 +250,7 @@ choose_sensible_raw_audio_format (WpIterator *formats,
       continue;
     }
 
-    if (!wp_spa_pod_get_object (pod,
-        "Format", NULL,
+    if (!wp_spa_pod_get_object (pod, NULL,
         "mediaType", "I", &mtype,
         "mediaSubtype", "I", &mstype,
         NULL)) {
diff --git a/modules/module-si-bluez5-endpoint.c b/modules/module-si-bluez5-endpoint.c
index c3647a06..f8160dc5 100644
--- a/modules/module-si-bluez5-endpoint.c
+++ b/modules/module-si-bluez5-endpoint.c
@@ -453,7 +453,7 @@ set_device_profile (WpDevice *device, gint index)
 {
   g_return_if_fail (device);
   g_autoptr (WpSpaPod) profile = wp_spa_pod_new_object (
-      "Profile", "Profile",
+      "Spa:Pod:Object:Param:Profile", "Profile",
       "index", "i", index,
       NULL);
   wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (device),
diff --git a/modules/module-si-convert.c b/modules/module-si-convert.c
index a6da805c..5b8017ab 100644
--- a/modules/module-si-convert.c
+++ b/modules/module-si-convert.c
@@ -288,7 +288,7 @@ si_convert_activate_execute_step (WpSessionItem * item,
           g_steal_pointer (&props));
 
       format = wp_spa_pod_new_object (
-          "Format", "Format",
+          "Spa:Pod:Object:Param:Format", "Format",
           "mediaType",    "I", SPA_MEDIA_TYPE_audio,
           "mediaSubtype", "I", SPA_MEDIA_SUBTYPE_raw,
           "format",       "I", SPA_AUDIO_FORMAT_F32P,
@@ -303,7 +303,8 @@ si_convert_activate_execute_step (WpSessionItem * item,
         as doing merge + split is heavy for our needs */
       wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node),
           "PortConfig", 0,
-          wp_spa_pod_new_object ("PortConfig", "PortConfig",
+          wp_spa_pod_new_object (
+              "Spa:Pod:Object:Param:PortConfig", "PortConfig",
               "direction",  "I", pw_direction_reverse (self->direction),
               "mode",       "I", SPA_PARAM_PORT_CONFIG_MODE_dsp,
               "format",     "P", format,
@@ -311,7 +312,8 @@ si_convert_activate_execute_step (WpSessionItem * item,
 
       wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node),
           "PortConfig", 0,
-          wp_spa_pod_new_object ("PortConfig", "PortConfig",
+          wp_spa_pod_new_object (
+              "Spa:Pod:Object:Param:PortConfig", "PortConfig",
               "direction",  "I", self->direction,
               "mode",       "I", SPA_PARAM_PORT_CONFIG_MODE_dsp,
               "control",    "b", self->control_port,
@@ -460,8 +462,12 @@ si_convert_get_ports (WpSiPortInfo * item, const gchar * context)
        leave the channel_id to its default value, 0 */
     props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (port));
     channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
-    wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_AUDIO_CHANNEL, channel,
-        &channel_id, NULL, NULL);
+    if (channel) {
+      WpSpaIdValue idval = wp_spa_id_value_from_short_name (
+          "Spa:Enum:AudioChannel", channel);
+      if (idval)
+        channel_id = wp_spa_id_value_number (idval);
+    }
 
     g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
   }
diff --git a/modules/module-si-simple-node-endpoint.c b/modules/module-si-simple-node-endpoint.c
index 992925e4..ac264be4 100644
--- a/modules/module-si-simple-node-endpoint.c
+++ b/modules/module-si-simple-node-endpoint.c
@@ -396,8 +396,12 @@ si_simple_node_endpoint_get_ports (WpSiPortInfo * item, const gchar * context)
     /* try to find the audio channel; if channel is NULL, this will silently
        leave the channel_id to its default value, 0 */
     channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
-    wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_AUDIO_CHANNEL, channel,
-        &channel_id, NULL, NULL);
+    if (channel) {
+      WpSpaIdValue idval = wp_spa_id_value_from_short_name (
+          "Spa:Enum:AudioChannel", channel);
+      if (idval)
+        channel_id = wp_spa_id_value_number (idval);
+    }
 
     g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
   }
diff --git a/tests/modules/algorithms.c b/tests/modules/algorithms.c
index 130961d6..a61edd5e 100644
--- a/tests/modules/algorithms.c
+++ b/tests/modules/algorithms.c
@@ -19,8 +19,6 @@
 static void
 test_choose_sensible_raw_audio_format (void)
 {
-  wp_spa_type_init (TRUE);
-
   uint32_t layout[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR,
                         SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
                         SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
@@ -31,7 +29,7 @@ test_choose_sensible_raw_audio_format (void)
   {
     g_ptr_array_remove_range (formats, 0, formats->len);
     g_autoptr (WpSpaPod) param1 = wp_spa_pod_new_object (
-        "Format", "Format",
+        "Spa:Pod:Object:Param:Format", "Format",
         "mediaType",    SPA_POD_Id(SPA_MEDIA_TYPE_audio),
         "mediaSubtype", SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
         "format",       SPA_POD_CHOICE_ENUM_Id(3,
@@ -56,7 +54,7 @@ test_choose_sensible_raw_audio_format (void)
   {
     g_ptr_array_remove_range (formats, 0, formats->len);
     g_autoptr (WpSpaPod) param1 = wp_spa_pod_new_object (
-        "Format", "Format",
+        "Spa:Pod:Object:Param:Format", "Format",
         "mediaType",    SPA_POD_Id(SPA_MEDIA_TYPE_audio),
         "mediaSubtype", SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
         "format",       SPA_POD_CHOICE_ENUM_Id(3,
@@ -81,7 +79,7 @@ test_choose_sensible_raw_audio_format (void)
   {
     g_ptr_array_remove_range (formats, 0, formats->len);
     g_autoptr (WpSpaPod) param2 = wp_spa_pod_new_object (
-        "Format", "Format",
+        "Spa:Pod:Object:Param:Format", "Format",
         "mediaType",    SPA_POD_Id(SPA_MEDIA_TYPE_audio),
         "mediaSubtype", SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
         "format",       SPA_POD_CHOICE_ENUM_Id(3,
@@ -95,7 +93,7 @@ test_choose_sensible_raw_audio_format (void)
     g_assert_nonnull (param2);
     g_ptr_array_add (formats, g_steal_pointer (&param2));
     g_autoptr (WpSpaPod) param3 = wp_spa_pod_new_object (
-        "Format", "Format",
+        "Spa:Pod:Object:Param:Format", "Format",
         "mediaType",    SPA_POD_Id(SPA_MEDIA_TYPE_audio),
         "mediaSubtype", SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
         "format",       SPA_POD_CHOICE_ENUM_Id(3,
@@ -123,8 +121,6 @@ test_choose_sensible_raw_audio_format (void)
     g_assert_cmpint (info.position[4], ==, layout[4]);
     g_assert_cmpint (info.position[5], ==, 0);
   }
-
-  wp_spa_type_deinit ();
 }
 
 int
diff --git a/tests/wp/endpoint.c b/tests/wp/endpoint.c
index 0b5bb380..7847b00c 100644
--- a/tests/wp/endpoint.c
+++ b/tests/wp/endpoint.c
@@ -527,7 +527,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -547,7 +547,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
   fixture->n_events = 0;
   wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (fixture->proxy_endpoint),
       "Props", 0,
-      wp_spa_pod_new_object ("Props", "Props",
+      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
           "volume", "f", 0.7f, NULL));
 
   /* run until the change is on all sides */
@@ -569,7 +569,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -590,7 +590,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -611,7 +611,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -623,7 +623,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
   fixture->n_events = 0;
   wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (fixture->impl_endpoint),
       "Props", 0,
-      wp_spa_pod_new_object ("Props", "Props",
+      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
           "mute", "b", TRUE, NULL));
 
   /* run until the change is on all sides */
@@ -645,7 +645,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -666,7 +666,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -687,7 +687,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -699,7 +699,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
   fixture->n_events = 0;
   wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (endpoint->node),
       "Props", 0,
-      wp_spa_pod_new_object ("Props", "Props",
+      wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props",
           "volume", "f", 0.2f, NULL));
 
   /* run until the change is on all sides */
@@ -721,7 +721,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -742,7 +742,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
@@ -763,7 +763,7 @@ test_endpoint_with_props (TestEndpointFixture *fixture, gconstpointer data)
     g_assert_true (wp_iterator_next (iterator, &item));
     g_assert_nonnull ((pod = g_value_dup_boxed (&item)));
 
-    g_assert_true (wp_spa_pod_get_object (pod, "Props", NULL,
+    g_assert_true (wp_spa_pod_get_object (pod, NULL,
             "volume", "f", &float_value,
             "mute", "b", &boolean_value,
             NULL));
diff --git a/tests/wp/proxy.c b/tests/wp/proxy.c
index e18a4132..023acdac 100644
--- a/tests/wp/proxy.c
+++ b/tests/wp/proxy.c
@@ -106,7 +106,8 @@ test_node_enum_params_done (WpPipewireObject *node, GAsyncResult *res,
     g_assert_cmpuint (G_VALUE_TYPE (&item), ==, WP_TYPE_SPA_POD);
     g_assert_nonnull (pod = g_value_get_boxed (&item));
     g_assert_true (wp_spa_pod_is_object (pod));
-    g_assert_cmpstr ("PropInfo", ==, wp_spa_pod_get_object_type_name (pod));
+    g_assert_cmpuint (wp_spa_type_from_name ("Spa:Pod:Object:Param:PropInfo"),
+        ==, wp_spa_pod_get_spa_type (pod));
     n_params++;
   }
   g_assert_cmpint (n_params, >, 0);
diff --git a/tests/wp/spa-pod.c b/tests/wp/spa-pod.c
index d48f77f4..c5a218eb 100644
--- a/tests/wp/spa-pod.c
+++ b/tests/wp/spa-pod.c
@@ -11,15 +11,14 @@
 static void
 test_spa_pod_basic (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* None */
   {
     g_autoptr (WpSpaPod) pod = wp_spa_pod_new_none ();
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_none (pod));
     g_assert_false (wp_spa_pod_is_id (pod));
-    g_assert_cmpstr ("None", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:None", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_autoptr (WpSpaPod) other = wp_spa_pod_new_none ();
     g_assert_true (wp_spa_pod_equal (pod, other));
   }
@@ -36,7 +35,8 @@ test_spa_pod_basic (void)
       gboolean value = FALSE;
       g_assert_true (wp_spa_pod_get_boolean (pod, &value));
       g_assert_true (value);
-      g_assert_cmpstr ("Bool", ==, wp_spa_pod_get_type_name (pod));
+      g_assert_cmpstr ("Spa:Bool", ==,
+          wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
       g_assert_true (wp_spa_pod_set_boolean (pod, FALSE));
       g_assert_true (wp_spa_pod_get_boolean (pod, &value));
       g_assert_false (value);
@@ -49,7 +49,8 @@ test_spa_pod_basic (void)
     gboolean value = FALSE;
     g_assert_true (wp_spa_pod_get_boolean (copy, &value));
     g_assert_false (value);
-    g_assert_cmpstr ("Bool", ==, wp_spa_pod_get_type_name (copy));
+    g_assert_cmpstr ("Spa:Bool", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (copy)));
     g_autoptr (WpSpaPod) other = wp_spa_pod_new_boolean (TRUE);
     g_assert_true (wp_spa_pod_set_pod (copy, other));
     g_assert_true (wp_spa_pod_get_boolean (copy, &value));
@@ -65,7 +66,8 @@ test_spa_pod_basic (void)
     guint32 value = 0;
     g_assert_true (wp_spa_pod_get_id (pod, &value));
     g_assert_cmpuint (value, ==, 5);
-    g_assert_cmpstr ("Id", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Id", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_id (pod, 10));
     g_assert_true (wp_spa_pod_get_id (pod, &value));
     g_assert_cmpuint (value, ==, 10);
@@ -84,7 +86,8 @@ test_spa_pod_basic (void)
     gint value = 0;
     g_assert_true (wp_spa_pod_get_int (pod, &value));
     g_assert_cmpint (value, ==, -12);
-    g_assert_cmpstr ("Int", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Int", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_int (pod, 9999));
     g_assert_true (wp_spa_pod_get_int (pod, &value));
     g_assert_cmpint (value, ==, 9999);
@@ -103,7 +106,8 @@ test_spa_pod_basic (void)
     long value = 0;
     g_assert_true (wp_spa_pod_get_long (pod, &value));
     g_assert_cmpint (value, ==, LONG_MAX);
-    g_assert_cmpstr ("Long", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Long", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_long (pod, LONG_MIN));
     g_assert_true (wp_spa_pod_get_long (pod, &value));
     g_assert_cmpuint (value, ==, LONG_MIN);
@@ -122,7 +126,8 @@ test_spa_pod_basic (void)
     float value = 0;
     g_assert_true (wp_spa_pod_get_float (pod, &value));
     g_assert_cmpfloat_with_epsilon (value, 3.14, 0.001);
-    g_assert_cmpstr ("Float", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Float", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_float (pod, 1.0));
     g_assert_true (wp_spa_pod_get_float (pod, &value));
     g_assert_cmpfloat_with_epsilon (value, 1.0, 0.001);
@@ -141,7 +146,8 @@ test_spa_pod_basic (void)
     double value = 0;
     g_assert_true (wp_spa_pod_get_double (pod, &value));
     g_assert_cmpfloat_with_epsilon (value, 2.718281828, 0.0000000001);
-    g_assert_cmpstr ("Double", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Double", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_double (pod, 2.0));
     g_assert_true (wp_spa_pod_get_double (pod, &value));
     g_assert_cmpfloat_with_epsilon (value, 2.0, 0.0000000001);
@@ -161,7 +167,8 @@ test_spa_pod_basic (void)
     g_assert_true (wp_spa_pod_get_string (pod, &value));
     g_assert_nonnull (value);
     g_assert_cmpstr (value, ==, "WirePlumber");
-    g_assert_cmpstr ("String", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:String", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_autoptr (WpSpaPod) other = wp_spa_pod_new_string ("Other");
     g_assert_nonnull (other);
     g_assert_true (wp_spa_pod_set_pod (pod, other));
@@ -182,7 +189,8 @@ test_spa_pod_basic (void)
     g_assert_nonnull (value);
     g_assert_cmpmem (value, len, "bytes", 5);
     g_assert_cmpuint (len, ==, 5);
-    g_assert_cmpstr ("Bytes", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Bytes", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_autoptr (WpSpaPod) other = wp_spa_pod_new_bytes ("pod", 3);
     g_assert_true (wp_spa_pod_set_pod (pod, other));
     g_assert_true (wp_spa_pod_get_bytes (pod, &value, &len));
@@ -195,35 +203,26 @@ test_spa_pod_basic (void)
   /* Pointer */
   {
     gint i = 3;
-    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_pointer ("Int", &i);
+    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_pointer ("Spa:Pointer:Buffer", &i);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_pointer (pod));
-    const char *type_name = NULL;
     gconstpointer p = NULL;
-    g_assert_true (wp_spa_pod_get_pointer (pod, &type_name, &p));
-    g_assert_nonnull (type_name);
+    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
     g_assert_nonnull (p);
-    g_assert_cmpstr (type_name, ==, "Int");
     g_assert_true (p == &i);
     g_assert_cmpint (*(gint *)p, ==, 3);
-    g_assert_cmpstr ("Pointer", ==, wp_spa_pod_get_type_name (pod));
-    gboolean b = TRUE;
-    g_assert_true (wp_spa_pod_set_pointer (pod, "Bool", &b));
-    g_assert_true (wp_spa_pod_get_pointer (pod, &type_name, &p));
-    g_assert_nonnull (type_name);
-    g_assert_nonnull (p);
-    g_assert_cmpstr (type_name, ==, "Bool");
-    g_assert_true (p == &b);
-    g_assert_cmpint (*(gboolean *)p, ==, TRUE);
+    g_assert_cmpstr ("Spa:Pointer:Buffer", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
+
     float f = 1.1;
-    g_autoptr (WpSpaPod) other = wp_spa_pod_new_pointer ("Float", &f);
+    g_autoptr (WpSpaPod) other = wp_spa_pod_new_pointer ("Spa:Pointer:Meta", &f);
     g_assert_true (wp_spa_pod_set_pod (pod, other));
-    g_assert_true (wp_spa_pod_get_pointer (pod, &type_name, &p));
-    g_assert_nonnull (type_name);
+    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
     g_assert_nonnull (p);
-    g_assert_cmpstr (type_name, ==, "Float");
     g_assert_true (p == &f);
     g_assert_cmpfloat_with_epsilon (*(float *)p, 1.1, 0.01);
+    g_assert_cmpstr ("Spa:Pointer:Meta", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_equal (pod, other));
   }
 
@@ -235,7 +234,8 @@ test_spa_pod_basic (void)
     gint64 value = 0;
     g_assert_true (wp_spa_pod_get_fd (pod, &value));
     g_assert_cmpint (value, ==, 4);
-    g_assert_cmpstr ("Fd", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Fd", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_fd (pod, 1));
     g_assert_true (wp_spa_pod_get_fd (pod, &value));
     g_assert_cmpuint (value, ==, 1);
@@ -256,7 +256,8 @@ test_spa_pod_basic (void)
     g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
     g_assert_cmpint (width, ==, 1920);
     g_assert_cmpint (height, ==, 1080);
-    g_assert_cmpstr ("Rectangle", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Rectangle", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_rectangle (pod, 640, 480));
     g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
     g_assert_cmpint (width, ==, 640);
@@ -279,7 +280,8 @@ test_spa_pod_basic (void)
     g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
     g_assert_cmpint (num, ==, 16);
     g_assert_cmpint (denom, ==, 9);
-    g_assert_cmpstr ("Fraction", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Fraction", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     g_assert_true (wp_spa_pod_set_fraction (pod, 4, 3));
     g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
     g_assert_cmpint (num, ==, 4);
@@ -291,27 +293,26 @@ test_spa_pod_basic (void)
     g_assert_cmpint (denom, ==, 1);
     g_assert_true (wp_spa_pod_equal (pod, other));
   }
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_choice (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Static Enum */
   {
     g_autoptr (WpSpaPod) pod = wp_spa_pod_new_choice (
         "Enum", "i", 0, "i", 1, "i", 2, NULL);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_choice (pod));
-    g_assert_cmpstr ("Choice", ==, wp_spa_pod_get_type_name (pod));
-    g_assert_cmpstr ("Enum", ==, wp_spa_pod_get_choice_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Choice", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
+    g_assert_cmpstr ("Enum", ==,
+        wp_spa_id_value_short_name (wp_spa_pod_get_choice_type (pod)));
 
     g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
     g_assert_nonnull (child);
-    g_assert_cmpstr ("Int", ==, wp_spa_pod_get_type_name (child));
+    g_assert_cmpstr ("Spa:Int", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
     gint value = 1;
     g_assert_true (wp_spa_pod_get_int (child, &value));
     g_assert_cmpint (value, ==, 0);
@@ -326,12 +327,14 @@ test_spa_pod_choice (void)
         "default value", NULL);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_choice (pod));
-    g_assert_cmpstr ("Choice", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Choice", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
 
     {
       g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
       g_assert_nonnull (child);
-      g_assert_cmpstr ("String", ==, wp_spa_pod_get_type_name (child));
+      g_assert_cmpstr ("Spa:String", ==,
+          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
       const char *value = NULL;
       g_assert_true (wp_spa_pod_get_string (child, &value));
       g_assert_nonnull (value);
@@ -345,7 +348,8 @@ test_spa_pod_choice (void)
     {
       g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
       g_assert_nonnull (child);
-      g_assert_cmpstr ("String", ==, wp_spa_pod_get_type_name (child));
+      g_assert_cmpstr ("Spa:String", ==,
+          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
       const char *value = NULL;
       g_assert_true (wp_spa_pod_get_string (child, &value));
       g_assert_nonnull (value);
@@ -362,21 +366,18 @@ test_spa_pod_choice (void)
     g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_choice (pod));
-    g_assert_cmpstr ("Choice", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Choice", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
   }
 
   /* It is not possible to use the parser to get the contents of a choice, you
    * need to use the iterator API to achieve that. This is because there is no
    * `spa_pod_parser_get_choice` API in the SPA library */
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_array (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Dynamic */
   {
     WpSpaPodBuilder *b = wp_spa_pod_builder_new_array ();
@@ -388,13 +389,15 @@ test_spa_pod_array (void)
     g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_array (pod));
-    g_assert_cmpstr ("Array", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Array", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
     wp_spa_pod_builder_unref (b);
     g_assert_true (wp_spa_pod_is_array (pod));
 
     g_autoptr (WpSpaPod) child = wp_spa_pod_get_array_child (pod);
     g_assert_nonnull (child);
-    g_assert_cmpstr ("Bool", ==, wp_spa_pod_get_type_name (child));
+    g_assert_cmpstr ("Spa:Bool", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
     gboolean value = TRUE;
     g_assert_true (wp_spa_pod_get_boolean (child, &value));
     g_assert_false (value);
@@ -403,19 +406,15 @@ test_spa_pod_array (void)
   /* It is not possible to use the parser to get the contents of an array, you
    * need to use the iterator API to achieve that. This is because there is no
    * `spa_pod_parser_get_array` API in the SPA library. */
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_object (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Static */
   {
     g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
-        "Props", "Props",
+        "Spa:Pod:Object:Param:Props", "Props",
         "mute", "b", FALSE,
         "volume", "f", 0.5,
         "frequency", "i", 440,
@@ -424,8 +423,8 @@ test_spa_pod_object (void)
         NULL);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_object (pod));
-    g_assert_cmpstr ("Object", ==, wp_spa_pod_get_type_name (pod));
-    g_assert_cmpstr ("Props", ==, wp_spa_pod_get_object_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
 
     const char *id_name;
     gboolean mute = TRUE;
@@ -434,7 +433,7 @@ test_spa_pod_object (void)
     const char *device;
     gint64 device_fd;
     g_assert_true (wp_spa_pod_get_object (pod,
-        "Props", &id_name,
+        &id_name,
         "mute", "b", &mute,
         "volume", "f", &vol,
         "frequency", "i", &frequency,
@@ -452,7 +451,7 @@ test_spa_pod_object (void)
   /* Dynamic */
   {
     g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
-        "Props", "Props");
+        "Spa:Pod:Object:Param:Props", "Props");
     wp_spa_pod_builder_add_property (b, "mute");
     wp_spa_pod_builder_add_boolean (b, FALSE);
     wp_spa_pod_builder_add_property (b, "volume");
@@ -466,7 +465,8 @@ test_spa_pod_object (void)
     g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_object (pod));
-    g_assert_cmpstr ("Object", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
 
     const char *id_name;
     gboolean mute = TRUE;
@@ -474,8 +474,7 @@ test_spa_pod_object (void)
     gint frequency;
     const char *device;
     gint64 device_fd;
-    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (pod,
-        "Props", &id_name);
+    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (pod, &id_name);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_parser_get (p, "mute", "b", &mute, NULL));
     g_assert_true (wp_spa_pod_parser_get (p, "volume", "f", &vol, NULL));
@@ -490,15 +489,11 @@ test_spa_pod_object (void)
     g_assert_cmpstr (device, ==, "device-name");
     g_assert_cmpint (device_fd, ==, 5);
   }
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_struct (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Dynamic */
   {
     g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_struct ();
@@ -510,7 +505,7 @@ test_spa_pod_struct (void)
     wp_spa_pod_builder_add_double (b, 2.718281828);
     wp_spa_pod_builder_add_string (b, "WirePlumber");
     wp_spa_pod_builder_add_bytes (b, "bytes", 5);
-    wp_spa_pod_builder_add_pointer (b, "Struct", b);
+    wp_spa_pod_builder_add_pointer (b, "Spa:Pointer:Buffer", b);
     wp_spa_pod_builder_add_fd (b, 4);
     wp_spa_pod_builder_add_rectangle (b, 1920, 1080);
     wp_spa_pod_builder_add_fraction (b, 16, 9);
@@ -520,7 +515,7 @@ test_spa_pod_struct (void)
     }
     {
       g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
-        "Props", "Props",
+        "Spa:Pod:Object:Param:Props", "Props",
         "mute", "b", FALSE,
         NULL);
       wp_spa_pod_builder_add (b, "P", pod, NULL);
@@ -528,7 +523,8 @@ test_spa_pod_struct (void)
     g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_struct (pod));
-    g_assert_cmpstr ("Struct", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Struct", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
 
     g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_struct (pod);
     g_assert_nonnull (pod);
@@ -568,11 +564,8 @@ test_spa_pod_struct (void)
     g_assert_cmpuint (len_bytes, ==, 5);
 
     gconstpointer value_pointer;
-    const char *type_pointer;
-    g_assert_true (wp_spa_pod_parser_get_pointer (p, &type_pointer, &value_pointer));
-    g_assert_nonnull (type_pointer);
+    g_assert_true (wp_spa_pod_parser_get_pointer (p, &value_pointer));
     g_assert_nonnull (value_pointer);
-    g_assert_cmpstr (type_pointer, ==, "Struct");
     g_assert_true (value_pointer == b);
 
     gint64 value_fd;
@@ -604,28 +597,25 @@ test_spa_pod_struct (void)
     gboolean mute = TRUE;
 
     g_assert_true (wp_spa_pod_get_object (value_object,
-        "Props", &id_name,
+        &id_name,
         "mute", "b", &mute,
         NULL));
     g_assert_cmpstr (id_name, ==, "Props");
     g_assert_false (mute);
   }
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_sequence (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Static */
   {
     g_autoptr (WpSpaPod) pod = wp_spa_pod_new_sequence (0,
         10, "Properties", "l", 9999, NULL);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_sequence (pod));
-    g_assert_cmpstr ("Sequence", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
   }
 
   /* Dynamic */
@@ -636,14 +626,13 @@ test_spa_pod_sequence (void)
     g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
     g_assert_nonnull (pod);
     g_assert_true (wp_spa_pod_is_sequence (pod));
-    g_assert_cmpstr ("Sequence", ==, wp_spa_pod_get_type_name (pod));
+    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
+        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
   }
 
   /* It is not possible to use the parser to get the contents of a sequence, you
    * need to use the iterator API to achieve that. This is because there is no
    * `spa_pod_parser_get_sequence` API in the SPA library. */
-
-  wp_spa_type_deinit ();
 }
 
 static void
@@ -692,8 +681,6 @@ sequence_foreach (const GValue *item, gpointer data)
 static void
 test_spa_pod_iterator (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Choice */
   {
     g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice ("Enum");
@@ -794,7 +781,7 @@ test_spa_pod_iterator (void)
   /* Object */
   {
     g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
-        "Props", "Props");
+        "Spa:Pod:Object:Param:Props", "Props");
     wp_spa_pod_builder_add_property (b, "mute");
     wp_spa_pod_builder_add_boolean (b, FALSE);
     wp_spa_pod_builder_add_property (b, "device");
@@ -957,18 +944,14 @@ test_spa_pod_iterator (void)
     g_assert_true (wp_iterator_foreach (it, sequence_foreach, &offset_total));
     g_assert_cmpuint (offset_total, ==, 50);
   }
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_unique_owner (void)
 {
-  wp_spa_type_init (TRUE);
-
   /* Create an object */
   WpSpaPod *pod = wp_spa_pod_new_object (
-        "PropInfo", "PropInfo",
+        "Spa:Pod:Object:Param:PropInfo", "PropInfo",
         "id", "I", 1,
         "name", "s", "prop-info-name",
         NULL);
@@ -1024,21 +1007,17 @@ test_spa_pod_unique_owner (void)
 
   /* Destroy the property */
   g_value_unset (&next);
-
-  wp_spa_type_deinit ();
 }
 
 static void
 test_spa_pod_port_config (void)
 {
-  wp_spa_type_init (TRUE);
-
   const gint rate = 48000;
   const gint channels = 2;
 
   /* Build the format to make sure the types exist */
   g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
-     "Format", "Format");
+     "Spa:Pod:Object:Param:Format", "Format");
   wp_spa_pod_builder_add (builder,
      "mediaType",    "I", 0,
      "mediaSubtype", "I", 0,
@@ -1056,7 +1035,8 @@ test_spa_pod_port_config (void)
   g_assert_nonnull (format);
 
   /* Build the port config to make sure the types exist */
-  g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object ("PortConfig",  "PortConfig",
+  g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
+      "Spa:Pod:Object:Param:PortConfig", "PortConfig",
       "direction",  "I", 0,
       "mode",       "I", 0,
       "monitor",    "b", FALSE,
@@ -1064,8 +1044,6 @@ test_spa_pod_port_config (void)
       "format",     "P", format,
       NULL);
   g_assert_nonnull (pod);
-
-  wp_spa_type_deinit ();
 }
 
 int
diff --git a/tests/wp/spa-type.c b/tests/wp/spa-type.c
index 438ef8b5..ea6d4efc 100644
--- a/tests/wp/spa-type.c
+++ b/tests/wp/spa-type.c
@@ -12,143 +12,369 @@
 static void
 test_spa_type_basic (void)
 {
-  wp_spa_type_init (TRUE);
+  g_assert_cmpuint (WP_SPA_TYPE_INVALID, ==, SPA_ID_INVALID);
 
-  /* Make sure table sizes are not 0 */
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), >, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_PARAM), >, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_PROPS), >, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_PROP_INFO), >, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_CONTROL), >, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_CHOICE), >, 0);
+  {
+    WpSpaType type = SPA_TYPE_Int;
+    g_assert_cmpstr (wp_spa_type_name (type), ==, "Spa:Int");
+    g_assert_true (wp_spa_type_is_fundamental (type));
+    g_assert_cmpuint (wp_spa_type_parent (type), ==, SPA_TYPE_Int);
+  }
 
-  /* Make sure SPA_TYPE_OBJECT_Props type from WP_SPA_TYPE_TABLE_BASIC is registered */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    WpSpaTypeTable table = WP_SPA_TYPE_TABLE_BASIC;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, SPA_TYPE_OBJECT_Props,
-        &name, &nick, &table));
-    g_assert_cmpstr (name, ==, "Spa:Pod:Object:Param:Props");
-    g_assert_cmpstr (nick, ==, "Props");
-    g_assert_cmpuint (table, ==, WP_SPA_TYPE_TABLE_PROPS);
+    WpSpaType type = wp_spa_type_from_name ("Spa:Enum:ParamId");
+    g_assert_cmpuint (type, ==, WP_SPA_TYPE_INVALID);
+
+    WpSpaIdTable table = wp_spa_id_table_from_name ("Spa:Enum:ParamId");
+    g_assert_nonnull (table);
   }
 
-  /* Make sure SPA_PARAM_Props type from WP_SPA_TYPE_TABLE_PARAM is registered */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PARAM, SPA_PARAM_Props, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Enum:ParamId:Props");
-    g_assert_cmpstr (nick, ==, "Props");
+    WpSpaType type = SPA_TYPE_OBJECT_Props;
+    g_assert_cmpstr (wp_spa_type_name (type), ==, "Spa:Pod:Object:Param:Props");
+    g_assert_cmpuint (wp_spa_type_from_name (SPA_TYPE_INFO_Props), ==, type);
+    g_assert_true (wp_spa_type_is_object (type));
+    g_assert_false (wp_spa_type_is_fundamental (type));
+    g_assert_cmpuint (wp_spa_type_parent (type), ==, SPA_TYPE_Object);
+    g_assert_nonnull (wp_spa_type_get_object_id_values_table (type));
+    g_assert_true (wp_spa_type_get_object_id_values_table (type) ==
+        wp_spa_id_table_from_name ("Spa:Enum:ParamId"));
   }
 
-  /* Make sure SPA_PROP_mute type from WP_SPA_TYPE_TABLE_PROPS is registered */
+  /* enums */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PROPS, SPA_PROP_mute, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Pod:Object:Param:Props:mute");
-    g_assert_cmpstr (nick, ==, "mute");
+    WpSpaIdValue id = wp_spa_id_value_from_name ("Spa:Enum:ParamId:Props");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==, "Spa:Enum:ParamId:Props");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Props");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PARAM_Props);
+
+    g_assert_true (id == wp_spa_id_value_from_short_name (
+            "Spa:Enum:ParamId", "Props"));
+    g_assert_true (id == wp_spa_id_value_from_number (
+            "Spa:Enum:ParamId", SPA_PARAM_Props));
   }
 
-  /* Make sure SPA_PROP_INFO_id type from WP_SPA_TYPE_TABLE_PROP_INFO is registered */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_PROP_INFO, SPA_PROP_INFO_id, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Pod:Object:Param:PropInfo:id");
-    g_assert_cmpstr (nick, ==, "id");
+    WpSpaIdValue id =
+        wp_spa_id_value_from_name ("Spa:Enum:Control:Properties");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
+        "Spa:Enum:Control:Properties");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Properties");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CONTROL_Properties);
+
+    g_assert_true (id == wp_spa_id_value_from_short_name (
+            "Spa:Enum:Control", "Properties"));
+    g_assert_true (id == wp_spa_id_value_from_number (
+            "Spa:Enum:Control", SPA_CONTROL_Properties));
   }
 
-  /* Make sure SPA_CONTROL_Properties type from WP_SPA_TYPE_TABLE_CONTROL is registered */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_CONTROL, SPA_CONTROL_Properties, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Enum:Control:Properties");
-    g_assert_cmpstr (nick, ==, "Properties");
+    WpSpaIdValue id = wp_spa_id_value_from_name ("Spa:Enum:Choice:Enum");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==, "Spa:Enum:Choice:Enum");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Enum");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Enum);
+
+    g_assert_true (id == wp_spa_id_value_from_short_name (
+            "Spa:Enum:Choice", "Enum"));
+    g_assert_true (id == wp_spa_id_value_from_number (
+            "Spa:Enum:Choice", SPA_CHOICE_Enum));
   }
 
-  /* Make sure SPA_CHOICE_Enum type from WP_SPA_TYPE_TABLE_CHOICE is registered */
+  /* objects */
   {
-    const char *name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_CHOICE, SPA_CHOICE_Enum, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Enum:Choice:Enum");
-    g_assert_cmpstr (nick, ==, "Enum");
+    WpSpaIdValue id =
+        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:mute");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
+        "Spa:Pod:Object:Param:Props:mute");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "mute");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_mute);
+
+    g_assert_true (id == wp_spa_id_value_from_short_name (
+            SPA_TYPE_INFO_Props, "mute"));
+    g_assert_true (id == wp_spa_id_value_from_number (
+            SPA_TYPE_INFO_Props, SPA_PROP_mute));
   }
 
-  wp_spa_type_deinit ();
+  {
+    WpSpaIdValue id =
+        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:PropInfo:id");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
+        "Spa:Pod:Object:Param:PropInfo:id");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_id);
+
+    /* WpSpaIdValue is a pointer to static spa_type_info,
+       so it should be the same on all queries */
+    g_assert_true (id == wp_spa_id_value_from_short_name (
+            SPA_TYPE_INFO_PropInfo, "id"));
+    g_assert_true (id == wp_spa_id_value_from_number (
+            SPA_TYPE_INFO_PropInfo, SPA_PROP_INFO_id));
+  }
+
+  /* array value type check */
+  {
+    WpSpaIdValue id =
+        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:channelVolumes");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
+        "Spa:Pod:Object:Param:Props:channelVolumes");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "channelVolumes");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_channelVolumes);
+
+    g_assert_cmpuint (wp_spa_id_value_array_get_item_type (id, NULL), ==,
+        SPA_TYPE_Float);
+  }
+
+  {
+    WpSpaIdValue id =
+        wp_spa_id_value_from_name ("Spa:Pod:Object:Param:Props:channelMap");
+    g_assert_nonnull (id);
+    g_assert_cmpstr (wp_spa_id_value_name (id), ==,
+        "Spa:Pod:Object:Param:Props:channelMap");
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "channelMap");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_channelMap);
+
+    WpSpaIdTable table = NULL;
+    g_assert_cmpuint (wp_spa_id_value_array_get_item_type (id, &table), ==,
+        SPA_TYPE_Id);
+    g_assert_nonnull (table);
+    g_assert_true (table == wp_spa_id_table_from_name ("Spa:Enum:AudioChannel"));
+  }
 }
 
 static void
-test_spa_type_register (void)
+test_spa_type_iterate (void)
 {
-  wp_spa_type_init (FALSE);
+  {
+    WpSpaType type = wp_spa_type_from_name (SPA_TYPE_INFO_PropInfo);
+    g_assert_cmpuint (type, !=, WP_SPA_TYPE_INVALID);
+    g_assert_true (wp_spa_type_is_object (type));
 
-  /* Make sure no types are registered */
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), ==, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_PARAM), ==, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_PROPS), ==, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_CONTROL), ==, 0);
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_CHOICE), ==, 0);
+    WpSpaIdTable table = wp_spa_type_get_values_table (type);
+    g_autoptr (WpIterator) it = wp_spa_id_table_iterate (table);
+    g_auto (GValue) value = G_VALUE_INIT;
+    WpSpaIdValue id = NULL;
 
-  /* Register SPA_TYPE_Bool */
-  {
-    g_assert_true (wp_spa_type_register (WP_SPA_TYPE_TABLE_BASIC, "Spa:Bool", "spa-bool"));
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_START);
+    table = NULL;
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
+    g_assert_nonnull (table);
+    g_assert_true (table == wp_spa_id_table_from_name ("Spa:Enum:ParamId"));
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_id);
+    table = NULL;
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
+    g_assert_nonnull (table);
+    g_assert_true (table ==
+        wp_spa_id_table_from_name ("Spa:Pod:Object:Param:Props"));
+    g_value_unset (&value);
 
-    guint32 id = 0;
-    const char *name = NULL;
-    g_assert_true (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "spa-bool", &id, &name, NULL));
-    g_assert_cmpuint (id, ==, SPA_TYPE_Bool);
-    g_assert_cmpstr (name, ==, "Spa:Bool");
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "name");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_name);
+    table = NULL;
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_String);
+    g_assert_null (table);
+    g_value_unset (&value);
 
-    name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, id, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Spa:Bool");
-    g_assert_cmpstr (nick, ==, "spa-bool");
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "type");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_type);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Pod);
+    g_assert_null (table);
+    g_value_unset (&value);
 
-    g_assert_false (wp_spa_type_register (WP_SPA_TYPE_TABLE_BASIC, "Spa:Bool", "spa-bool"));
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "labels");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_labels);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Struct);
+    g_assert_null (table);
+    g_value_unset (&value);
 
-    g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), ==, 1);
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "container");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_PROP_INFO_container);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, &table), ==, SPA_TYPE_Id);
+    g_assert_null (table);
+    g_value_unset (&value);
   }
 
-  /* Register Custom */
   {
-    g_assert_true (wp_spa_type_register (WP_SPA_TYPE_TABLE_BASIC, "Wp:Bool", "wp-bool"));
+    WpSpaIdTable table = wp_spa_id_table_from_name ("Spa:Enum:Choice");
+    g_assert_nonnull (table);
 
-    guint32 id = 0;
-    const char *name = NULL;
-    g_assert_true (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "wp-bool", &id, &name, NULL));
-    g_assert_cmpuint (id, ==, SPA_TYPE_VENDOR_Other + 1);
-    g_assert_cmpstr (name, ==, "Wp:Bool");
+    g_autoptr (WpIterator) it = wp_spa_id_table_iterate (table);
+    g_auto (GValue) value = G_VALUE_INIT;
+    WpSpaIdValue id = NULL;
 
-    name = NULL;
-    const char *nick = NULL;
-    g_assert_true (wp_spa_type_get_by_id (WP_SPA_TYPE_TABLE_BASIC, id, &name, &nick, NULL));
-    g_assert_cmpstr (name, ==, "Wp:Bool");
-    g_assert_cmpstr (nick, ==, "wp-bool");
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "None");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_None);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
 
-    g_assert_false (wp_spa_type_register (WP_SPA_TYPE_TABLE_BASIC, "Wp:Bool", "wp-bool"));
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Range");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Range);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
 
-    g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), ==, 2);
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Step");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Step);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Enum");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Enum);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Flags");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, SPA_CHOICE_Flags);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
   }
+}
 
-  /* Unregister SPA_TYPE_Bool */
-  g_assert_true (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "spa-bool", NULL, NULL, NULL));
-  wp_spa_type_unregister (WP_SPA_TYPE_TABLE_BASIC, "spa-bool");
-  g_assert_false (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "spa-bool", NULL, NULL, NULL));
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), ==, 1);
+static void
+test_spa_type_register (void)
+{
+  static const struct spa_type_info custom_enum_info[] = {
+    { 0, SPA_TYPE_Int, "Spa:Enum:CustomEnum:Invalid", NULL  },
+    { 1, SPA_TYPE_Int, "Spa:Enum:CustomEnum:Valid", NULL  },
+    { 0, 0, NULL, NULL }
+  };
+
+  static const struct spa_type_info custom_obj_info[] = {
+    { 0, SPA_TYPE_Id, "Spa:Pod:Object:CustomObj:", custom_enum_info },
+    { 1, SPA_TYPE_Int, "Spa:Pod:Object:CustomObj:id", NULL },
+    { 2, SPA_TYPE_String, "Spa:Pod:Object:CustomObj:name", NULL },
+    { 3, SPA_TYPE_Float, "Spa:Pod:Object:CustomObj:volume", NULL },
+    { 4, SPA_TYPE_Rectangle, "Spa:Pod:Object:CustomObj:box", NULL },
+    { 5, SPA_TYPE_Bytes, "Spa:Pod:Object:CustomObj:data", NULL },
+    { 0, 0, NULL, NULL },
+  };
+
+  wp_spa_dynamic_type_init ();
+
+  WpSpaIdTable enum_table =
+      wp_spa_dynamic_id_table_register ("Spa:Enum:CustomEnum", custom_enum_info);
+  WpSpaType obj_type = wp_spa_dynamic_type_register ("Spa:Pod:Object:CustomObj",
+      SPA_TYPE_Object, custom_obj_info);
+
+  g_assert_nonnull (enum_table);
+  g_assert_true (obj_type != WP_SPA_TYPE_INVALID);
+
+  g_assert_true (enum_table ==
+      wp_spa_id_table_from_name ("Spa:Enum:CustomEnum"));
+
+  {
+    g_autoptr (WpIterator) it = wp_spa_id_table_iterate (enum_table);
+    g_auto (GValue) value = G_VALUE_INIT;
+    WpSpaIdValue id = NULL;
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Invalid");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 0);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "Valid");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 1);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
 
-  /* Unregister Custom */
-  g_assert_true (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "wp-bool", NULL, NULL, NULL));
-  wp_spa_type_unregister (WP_SPA_TYPE_TABLE_BASIC, "wp-bool");
-  g_assert_false (wp_spa_type_get_by_nick (WP_SPA_TYPE_TABLE_BASIC, "wp-bool", NULL, NULL, NULL));
-  g_assert_cmpuint (wp_spa_type_get_table_size (WP_SPA_TYPE_TABLE_BASIC), ==, 0);
+    g_assert_false (wp_iterator_next (it, &value));
+  }
+
+  g_assert_cmpstr (wp_spa_type_name (obj_type), ==, "Spa:Pod:Object:CustomObj");
+  g_assert_true (wp_spa_type_is_object (obj_type));
+  g_assert_false (wp_spa_type_is_fundamental (obj_type));
+  g_assert_cmpuint (wp_spa_type_parent (obj_type), ==, SPA_TYPE_Object);
+  g_assert_cmpuint (obj_type, ==,
+      wp_spa_type_from_name ("Spa:Pod:Object:CustomObj"));
+  g_assert_true (enum_table ==
+      wp_spa_type_get_object_id_values_table (obj_type));
+
+  {
+    WpSpaIdTable table = wp_spa_type_get_values_table (obj_type);
+    g_autoptr (WpIterator) it = wp_spa_id_table_iterate (table);
+    g_auto (GValue) value = G_VALUE_INIT;
+    WpSpaIdValue id = NULL;
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 0);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Id);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "id");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 1);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Int);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "name");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 2);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_String);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "volume");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 3);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Float);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "box");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 4);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Rectangle);
+    g_value_unset (&value);
+
+    g_assert_true (wp_iterator_next (it, &value));
+    id = g_value_get_pointer (&value);
+    g_assert_cmpstr (wp_spa_id_value_short_name (id), ==, "data");
+    g_assert_cmpuint (wp_spa_id_value_number (id), ==, 5);
+    g_assert_cmpuint (wp_spa_id_value_get_value_type (id, NULL), ==, SPA_TYPE_Bytes);
+    g_value_unset (&value);
+
+    g_assert_false (wp_iterator_next (it, &value));
+  }
 
-  wp_spa_type_deinit ();
+  wp_spa_dynamic_type_deinit ();
 }
 
 int
@@ -158,6 +384,7 @@ main (int argc, char *argv[])
   g_log_set_writer_func (wp_log_writer_default, NULL, NULL);
 
   g_test_add_func ("/wp/spa-type/basic", test_spa_type_basic);
+  g_test_add_func ("/wp/spa-type/iterate", test_spa_type_iterate);
   g_test_add_func ("/wp/spa-type/register", test_spa_type_register);
 
   return g_test_run ();
diff --git a/tools/wpctl.c b/tools/wpctl.c
index 9873bee7..8684ec04 100644
--- a/tools/wpctl.c
+++ b/tools/wpctl.c
@@ -107,7 +107,7 @@ print_controls (WpPipewireObject * pwobj)
 
   it = wp_pipewire_object_enum_params_sync (pwobj, "Props", NULL);
   if (!it || !wp_iterator_next (it, &value) ||
-      !wp_spa_pod_get_object (g_value_get_boxed (&value), "Props", NULL,
+      !wp_spa_pod_get_object (g_value_get_boxed (&value), NULL,
           "volume", "f", &volume,
           "mute", "b", &mute,
           NULL))
@@ -654,14 +654,16 @@ set_volume_run (WpCtl * self)
 
   it = wp_pipewire_object_enum_params_sync (proxy, "Props", NULL);
   if (!it || !wp_iterator_next (it, &value) ||
-      !wp_spa_pod_get_object (g_value_get_boxed (&value), "Props", NULL,
+      !wp_spa_pod_get_object (g_value_get_boxed (&value), NULL,
           "volume", "f", &volume, NULL)) {
     printf ("Object '%d' does not support volume\n", cmdline.set_volume.id);
     goto out;
   }
 
   wp_pipewire_object_set_param (proxy, "Props", 0, wp_spa_pod_new_object (
-          "Props", "Props", "volume", "f", cmdline.set_volume.volume, NULL));
+          "Spa:Pod:Object:Param:Props", "Props",
+          "volume", "f", cmdline.set_volume.volume,
+          NULL));
   wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
   return;
 
@@ -741,7 +743,7 @@ set_mute_run (WpCtl * self)
 
   it = wp_pipewire_object_enum_params_sync (proxy, "Props", NULL);
   if (!it || !wp_iterator_next (it, &value) ||
-      !wp_spa_pod_get_object (g_value_get_boxed (&value), "Props", NULL,
+      !wp_spa_pod_get_object (g_value_get_boxed (&value), NULL,
           "mute", "b", &mute, NULL)) {
     printf ("Object '%d' does not support mute\n", cmdline.set_mute.id);
     goto out;
@@ -753,7 +755,9 @@ set_mute_run (WpCtl * self)
     mute = !!cmdline.set_mute.mute;
 
   wp_pipewire_object_set_param (proxy, "Props", 0, wp_spa_pod_new_object (
-          "Props", "Props", "mute", "b", mute, NULL));
+          "Spa:Pod:Object:Param:Props", "Props",
+          "mute", "b", mute,
+          NULL));
   wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
   return;
 
@@ -810,7 +814,7 @@ set_profile_run (WpCtl * self)
   }
   wp_pipewire_object_set_param (proxy, "Profile", 0,
       wp_spa_pod_new_object (
-        "Profile", "Profile",
+        "Spa:Pod:Object:Param:Profile", "Profile",
         "index", "i", cmdline.set_profile.index,
         NULL));
   wp_core_sync (self->core, NULL, (GAsyncReadyCallback) async_quit, self);
-- 
GitLab