Commit ce3883c1 authored by George Kiagiadakis's avatar George Kiagiadakis
Browse files

lib: add WpSpaProps, a helper to deal with SPA_PARAM_Props & SPA_PARAM_PropInfo

parent 44e498ca
......@@ -14,6 +14,7 @@ wp_lib_sources = [
'proxy-link.c',
'proxy-node.c',
'proxy-port.c',
'spa-props.c',
]
wp_lib_headers = [
......
......@@ -93,6 +93,64 @@ void wp_proxy_augment_error (WpProxy * self, GError * error);
void wp_proxy_register_async_task (WpProxy * self, int seq, GTask * task);
GTask * wp_proxy_find_async_task (WpProxy * self, int seq, gboolean steal);
/* spa props */
struct spa_pod;
struct spa_pod_builder;
typedef struct _WpSpaProps WpSpaProps;
struct _WpSpaProps
{
GList *entries;
};
void wp_spa_props_clear (WpSpaProps * self);
void wp_spa_props_register_pod (WpSpaProps * self,
guint32 id, const gchar *name, const struct spa_pod *type);
gint wp_spa_props_register_from_prop_info (WpSpaProps * self,
const struct spa_pod * prop_info);
const struct spa_pod * wp_spa_props_get_stored (WpSpaProps * self, guint32 id);
gint wp_spa_props_store_pod (WpSpaProps * self, guint32 id,
const struct spa_pod * value);
gint wp_spa_props_store_from_props (WpSpaProps * self,
const struct spa_pod * props, GArray * changed_ids);
GPtrArray * wp_spa_props_build_all_pods (WpSpaProps * self,
struct spa_pod_builder * b);
struct spa_pod * wp_spa_props_build_update (WpSpaProps * self, guint32 id,
const struct spa_pod * value, struct spa_pod_builder * b);
const struct spa_pod * wp_spa_props_build_pod_valist (gchar * buffer,
gsize size, va_list args);
static inline const struct spa_pod *
wp_spa_props_build_pod (gchar * buffer, gsize size, ...)
{
const struct spa_pod *ret;
va_list args;
va_start (args, size);
ret = wp_spa_props_build_pod_valist (buffer, size, args);
va_end (args);
return ret;
}
#define wp_spa_props_register(self, id, name, ...) \
({ \
gchar b[512]; \
wp_spa_props_register_pod (self, id, name, \
wp_spa_props_build_pod (b, sizeof (b), ##__VA_ARGS__, NULL)); \
})
#define wp_spa_props_store(self, id, ...) \
({ \
gchar b[512]; \
wp_spa_props_store_pod (self, id, \
wp_spa_props_build_pod (b, sizeof (b), ##__VA_ARGS__, NULL)); \
})
G_END_DECLS
#endif
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "private.h"
#include <spa/pod/pod.h>
#include <spa/pod/builder.h>
#include <spa/pod/parser.h>
#include <spa/param/props.h>
#include <spa/utils/defs.h>
#include <spa/utils/result.h>
struct entry
{
guint32 id;
gchar *name;
struct spa_pod *type;
struct spa_pod *value;
};
struct entry *
entry_new (void)
{
struct entry *e = g_slice_new0 (struct entry);
return e;
}
static void
entry_free (struct entry *e)
{
g_free (e->name);
free (e->type);
free (e->value);
g_slice_free (struct entry, e);
}
void
wp_spa_props_clear (WpSpaProps * self)
{
g_list_free_full (self->entries, (GDestroyNotify) entry_free);
self->entries = NULL;
}
void
wp_spa_props_register_pod (WpSpaProps * self,
guint32 id, const gchar *name, const struct spa_pod *type)
{
struct entry *e = entry_new ();
e->id = id;
e->name = g_strdup (name);
e->type = spa_pod_copy (type);
if (!spa_pod_is_choice (type))
e->value = spa_pod_copy (type);
else
e->value = spa_pod_copy (SPA_POD_CHOICE_CHILD (type));
self->entries = g_list_append (self->entries, e);
}
gint
wp_spa_props_register_from_prop_info (WpSpaProps * self,
const struct spa_pod * prop_info)
{
guint32 id;
gchar *name;
const struct spa_pod *type;
int res;
res = spa_pod_parse_object (prop_info,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id (&id),
SPA_PROP_INFO_name, SPA_POD_String (&name),
SPA_PROP_INFO_type, SPA_POD_Pod (&type));
if (res < 0) {
g_debug ("Bad prop info object");
return res;
}
wp_spa_props_register_pod (self, id, name, type);
return 0;
}
// get <-- cached
const struct spa_pod *
wp_spa_props_get_stored (WpSpaProps * self, guint32 id)
{
GList *l = self->entries;
while (l && ((struct entry *) l->data)->id != id)
l = g_list_next (l);
return l ? ((struct entry *) l->data)->value : NULL;
}
// exported set --> cache + update(variant to pod -> push)
gint
wp_spa_props_store_pod (WpSpaProps * self, guint32 id,
const struct spa_pod * value)
{
GList *l = self->entries;
struct entry * e;
uint32_t expected_type;
while (l && ((struct entry *) l->data)->id != id)
l = g_list_next (l);
if (!l)
return -ESRCH;
e = (struct entry *) l->data;
expected_type = spa_pod_is_choice (e->type) ?
SPA_POD_CHOICE_VALUE_TYPE (e->type) : SPA_POD_TYPE (e->type);
if (SPA_POD_TYPE (value) != expected_type)
return -EINVAL;
#define GET_VAL(pod, type) ((struct spa_pod_##type *) pod)->value
switch (SPA_POD_TYPE (value)) {
//TODO bounds checking on integer types
case SPA_TYPE_Id:
if (GET_VAL (e->value, id) != GET_VAL (value, id)) {
GET_VAL (e->value, id) = GET_VAL (value, id);
return 1;
}
break;
case SPA_TYPE_Bool:
if (GET_VAL (e->value, bool) != GET_VAL (value, bool)) {
GET_VAL (e->value, bool) = GET_VAL (value, bool);
return 1;
}
break;
case SPA_TYPE_Int:
if (GET_VAL (e->value, int) != GET_VAL (value, int)) {
GET_VAL (e->value, int) = GET_VAL (value, int);
return 1;
}
break;
case SPA_TYPE_Long:
if (GET_VAL (e->value, long) != GET_VAL (value, long)) {
GET_VAL (e->value, long) = GET_VAL (value, long);
return 1;
}
break;
case SPA_TYPE_Fd:
if (GET_VAL (e->value, fd) != GET_VAL (value, fd)) {
GET_VAL (e->value, fd) = GET_VAL (value, fd);
return 1;
}
break;
case SPA_TYPE_Float:
if (GET_VAL (e->value, float) != GET_VAL (value, float)) {
GET_VAL (e->value, float) = GET_VAL (value, float);
return 1;
}
break;
case SPA_TYPE_Double:
if (GET_VAL (e->value, double) != GET_VAL (value, double)) {
GET_VAL (e->value, double) = GET_VAL (value, double);
return 1;
}
break;
case SPA_TYPE_Rectangle:
if (GET_VAL (e->value, rectangle).width != GET_VAL (value, rectangle).width ||
GET_VAL (e->value, rectangle).height != GET_VAL (value, rectangle).height) {
GET_VAL (e->value, rectangle) = GET_VAL (value, rectangle);
return 1;
}
break;
case SPA_TYPE_Fraction:
if (GET_VAL (e->value, fraction).num != GET_VAL (value, fraction).num ||
GET_VAL (e->value, fraction).denom != GET_VAL (value, fraction).denom) {
GET_VAL (e->value, fraction) = GET_VAL (value, fraction);
return 1;
}
break;
default:
g_clear_pointer (&e->value, free);
e->value = spa_pod_copy (value);
return 1;
}
#undef GET_VAL
return 0;
}
// exported event set --> pod to variant -> cache
// proxy event param --> pod to variant -> cache
gint
wp_spa_props_store_from_props (WpSpaProps * self, const struct spa_pod * props,
GArray * changed_ids)
{
const struct spa_pod_object *obj;
const struct spa_pod_prop *iter;
gint ret, count = 0;
g_return_val_if_fail (!changed_ids ||
g_array_get_element_size (changed_ids) == sizeof (uint32_t), -EINVAL);
if (!spa_pod_is_object_type (props, SPA_TYPE_OBJECT_Props))
return -EINVAL;
obj = (const struct spa_pod_object *) props;
iter = spa_pod_prop_first (&obj->body);
for (iter = spa_pod_prop_first (&obj->body);
spa_pod_prop_is_inside (&obj->body, obj->pod.size, iter);
iter = spa_pod_prop_next (iter)) {
if ((ret = wp_spa_props_store_pod (self, iter->key, &iter->value)) < 0) {
g_debug ("error storing property 0x%x: %s", iter->key,
spa_strerror (ret));
} else if (ret == 1 && changed_ids) {
g_array_append_val (changed_ids, iter->key);
count++;
}
}
return count;
}
// for exported update / prop_info + props
GPtrArray *
wp_spa_props_build_all_pods (WpSpaProps * self, struct spa_pod_builder * b)
{
GPtrArray *res = g_ptr_array_new ();
GList *l;
struct spa_pod_frame f;
struct spa_pod *pod;
/* Props */
spa_pod_builder_push_object (b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
for (l = self->entries; l != NULL; l = g_list_next (l)) {
struct entry * e = (struct entry *) l->data;
if (e->value) {
spa_pod_builder_prop (b, e->id, 0);
spa_pod_builder_primitive (b, e->value);
}
}
pod = spa_pod_builder_pop (b, &f);
g_ptr_array_add (res, pod);
/* PropInfo */
for (l = self->entries; l != NULL; l = g_list_next (l)) {
struct entry * e = (struct entry *) l->data;
pod = spa_pod_builder_add_object (b,
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
SPA_PROP_INFO_id, SPA_POD_Id (e->id),
SPA_PROP_INFO_name, SPA_POD_String (e->name),
SPA_PROP_INFO_type, SPA_POD_Pod (e->type));
g_ptr_array_add (res, pod);
}
return res;
}
// proxy set --> value to props object -> push
struct spa_pod *
wp_spa_props_build_update (WpSpaProps * self, guint32 id,
const struct spa_pod * value, struct spa_pod_builder * b)
{
struct spa_pod_frame f;
spa_pod_builder_push_object (b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
spa_pod_builder_prop (b, id, 0);
spa_pod_builder_primitive (b, value);
return spa_pod_builder_pop (b, &f);
}
const struct spa_pod *
wp_spa_props_build_pod_valist (gchar * buffer, gsize size, va_list args)
{
struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buffer, size);
struct spa_pod_frame f;
void *pod;
spa_pod_builder_push_struct (&b, &f);
spa_pod_builder_addv (&b, args);
pod = spa_pod_builder_pop (&b, &f);
return SPA_POD_CONTENTS (struct spa_pod_struct, pod);
}
......@@ -15,3 +15,9 @@ test(
executable('test-proxy', 'proxy.c', dependencies: common_deps),
env: common_env,
)
test(
'test-spa-props',
executable('test-spa-props', 'spa-props.c', dependencies: common_deps),
env: common_env,
)
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/* private functions, they should be hidden in the shared library */
#include "wp/spa-props.c"
#include <spa/pod/iter.h>
static void
test_spa_props_set_get (void)
{
WpSpaProps props = {0};
const struct spa_pod *pod;
float float_value = 0.0;
const gchar *string_value = NULL;
wp_spa_props_register (&props, SPA_PROP_volume, "Volume",
SPA_POD_CHOICE_RANGE_Float (1.0, 0.0, 10.0));
wp_spa_props_register (&props, SPA_PROP_START_CUSTOM + 1, "Test property",
SPA_POD_String ("default value"));
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_volume));
g_assert_cmpint (spa_pod_get_float (pod, &float_value), ==, 0);
g_assert_cmpfloat_with_epsilon (float_value, 1.0, 0.001);
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_START_CUSTOM + 1));
g_assert_cmpint (spa_pod_get_string (pod, &string_value), ==, 0);
g_assert_cmpstr (string_value, ==, "default value");
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_volume,
SPA_POD_Float (0.8)), ==, 1);
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_START_CUSTOM + 1,
SPA_POD_String ("test value")), ==, 1);
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_volume));
g_assert_cmpint (spa_pod_get_float (pod, &float_value), ==, 0);
g_assert_cmpfloat_with_epsilon (float_value, 0.8, 0.001);
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_START_CUSTOM + 1));
g_assert_cmpint (spa_pod_get_string (pod, &string_value), ==, 0);
g_assert_cmpstr (string_value, ==, "test value");
wp_spa_props_clear (&props);
}
static void
test_spa_props_build_all (void)
{
WpSpaProps props = {0};
gchar buffer[512];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buffer, sizeof (buffer));
struct spa_pod *pod;
float float_value = 0.0;
const gchar *string_value = NULL;
guint32 id;
g_autoptr (GPtrArray) arr = NULL;
wp_spa_props_register (&props, SPA_PROP_volume, "Volume",
SPA_POD_CHOICE_RANGE_Float (1.0, 0.0, 10.0));
wp_spa_props_register (&props, SPA_PROP_START_CUSTOM + 1, "Test property",
SPA_POD_String ("default value"));
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_volume,
SPA_POD_Float (0.8)), ==, 1);
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_START_CUSTOM + 1,
SPA_POD_String ("test value")), ==, 1);
arr = wp_spa_props_build_all_pods (&props, &b);
g_assert_nonnull (arr);
g_assert_cmpint (arr->len, ==, 3);
pod = g_ptr_array_index (arr, 0);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_Props));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_Props));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_volume, SPA_POD_Float (&float_value),
SPA_PROP_START_CUSTOM + 1, SPA_POD_String (&string_value)),
==, 2);
g_assert_cmpfloat_with_epsilon (float_value, 0.8, 0.001);
g_assert_cmpstr (string_value, ==, "test value");
pod = g_ptr_array_index (arr, 1);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_PropInfo));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_PropInfo));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id (&id),
SPA_PROP_INFO_name, SPA_POD_String (&string_value),
SPA_PROP_INFO_type, SPA_POD_Pod (&pod)),
==, 3);
g_assert_cmpuint (id, ==, SPA_PROP_volume);
g_assert_cmpstr (string_value, ==, "Volume");
g_assert_nonnull (pod);
/* https://gitlab.freedesktop.org/pipewire/pipewire/issues/196
g_assert_true (spa_pod_is_choice (pod));
g_assert_true (SPA_POD_CHOICE_VALUE_TYPE (pod) == SPA_TYPE_Float); */
g_assert_true (SPA_POD_TYPE (pod) == SPA_TYPE_Float);
pod = g_ptr_array_index (arr, 2);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_PropInfo));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_PropInfo));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id (&id),
SPA_PROP_INFO_name, SPA_POD_String (&string_value),
SPA_PROP_INFO_type, SPA_POD_Pod (&pod)),
==, 3);
g_assert_cmpuint (id, ==, SPA_PROP_START_CUSTOM + 1);
g_assert_cmpstr (string_value, ==, "Test property");
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_string (pod));
wp_spa_props_clear (&props);
}
static void
test_spa_props_store_from_props (void)
{
WpSpaProps props = {0};
gchar buffer[512];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buffer, sizeof (buffer));
const struct spa_pod *pod;
float float_value = 0.0;
const gchar *string_value = NULL;
g_autoptr (GArray) arr = g_array_new (FALSE, FALSE, sizeof (guint32));
wp_spa_props_register (&props, SPA_PROP_volume, "Volume",
SPA_POD_CHOICE_RANGE_Float (1.0, 0.0, 10.0));
wp_spa_props_register (&props, SPA_PROP_START_CUSTOM + 1, "Test property",
SPA_POD_String ("default value"));
pod = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
SPA_PROP_volume, SPA_POD_Float (0.8),
SPA_PROP_START_CUSTOM + 1, SPA_POD_String ("test value"));
g_assert_cmpint (wp_spa_props_store_from_props (&props, pod, arr), ==, 2);
g_assert_cmpint (arr->len, ==, 2);
g_assert_cmpint (((guint32 *)arr->data)[0], ==, SPA_PROP_volume);
g_assert_cmpint (((guint32 *)arr->data)[1], ==, SPA_PROP_START_CUSTOM + 1);
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_volume));
g_assert_cmpint (spa_pod_get_float (pod, &float_value), ==, 0);
g_assert_cmpfloat_with_epsilon (float_value, 0.8, 0.001);
g_assert_nonnull (pod = wp_spa_props_get_stored (&props, SPA_PROP_START_CUSTOM + 1));
g_assert_cmpint (spa_pod_get_string (pod, &string_value), ==, 0);
g_assert_cmpstr (string_value, ==, "test value");
wp_spa_props_clear (&props);
}
static void
test_spa_props_register_from_prop_info (void)
{
WpSpaProps props = {0};
gchar buffer[512];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT (buffer, sizeof (buffer));
const struct spa_pod *pod;
float float_value = 0.0;
const gchar *string_value = NULL;
g_autoptr (GPtrArray) arr = NULL;
guint32 id;
pod = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
SPA_PROP_INFO_id, SPA_POD_Id (SPA_PROP_volume),
SPA_PROP_INFO_name, SPA_POD_String ("Volume"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float (1.0, 0.0, 10.0));
g_assert_cmpint (wp_spa_props_register_from_prop_info (&props, pod), ==, 0);
pod = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
SPA_PROP_INFO_id, SPA_POD_Id (SPA_PROP_START_CUSTOM + 1),
SPA_PROP_INFO_name, SPA_POD_String ("Test property"),
SPA_PROP_INFO_type, SPA_POD_String ("default value"));
g_assert_cmpint (wp_spa_props_register_from_prop_info (&props, pod), ==, 0);
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_volume,
SPA_POD_Float (0.8)), ==, 1);
g_assert_cmpint (wp_spa_props_store (&props, SPA_PROP_START_CUSTOM + 1,
SPA_POD_String ("test value")), ==, 1);
arr = wp_spa_props_build_all_pods (&props, &b);
g_assert_nonnull (arr);
g_assert_cmpint (arr->len, ==, 3);
pod = g_ptr_array_index (arr, 0);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_Props));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_Props));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_volume, SPA_POD_Float (&float_value),
SPA_PROP_START_CUSTOM + 1, SPA_POD_String (&string_value)),
==, 2);
g_assert_cmpfloat_with_epsilon (float_value, 0.8, 0.001);
g_assert_cmpstr (string_value, ==, "test value");
pod = g_ptr_array_index (arr, 1);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_PropInfo));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_PropInfo));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id (&id),
SPA_PROP_INFO_name, SPA_POD_String (&string_value),
SPA_PROP_INFO_type, SPA_POD_Pod (&pod)),
==, 3);
g_assert_cmpuint (id, ==, SPA_PROP_volume);
g_assert_cmpstr (string_value, ==, "Volume");
g_assert_nonnull (pod);
/* https://gitlab.freedesktop.org/pipewire/pipewire/issues/196
g_assert_true (spa_pod_is_choice (pod));
g_assert_true (SPA_POD_CHOICE_VALUE_TYPE (pod) == SPA_TYPE_Float); */
g_assert_true (SPA_POD_TYPE (pod) == SPA_TYPE_Float);
pod = g_ptr_array_index (arr, 2);
g_assert_nonnull (pod);
g_assert_true (spa_pod_is_object_type (pod, SPA_TYPE_OBJECT_PropInfo));
g_assert_true (spa_pod_is_object_id (pod, SPA_PARAM_PropInfo));
g_assert_cmpint (spa_pod_parse_object (pod,
SPA_TYPE_OBJECT_PropInfo, NULL,
SPA_PROP_INFO_id, SPA_POD_Id (&id),
SPA_PROP_INFO_name, SPA_POD_String (&string_value),
SPA_PROP_INFO_type, SPA_POD_Pod (&pod)),
==, 3);