From 7ca8491be3a730cba61ea0578b654a83bcdd552c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sun, 22 Nov 2020 10:00:58 +0100 Subject: [PATCH] media-session: store values as json Save settings as jason and use a small json compatible tokenizer to load settings instead of our own less flexible format. Save settings with a prefix and filter out entries without prefix Listen for changes in restore.stream metadata and update properties. --- src/examples/media-session/default-nodes.c | 33 ++- src/examples/media-session/default-profile.c | 62 ++-- src/examples/media-session/default-routes.c | 88 +++--- src/examples/media-session/json.h | 292 +++++++++++++++++++ src/examples/media-session/media-session.c | 9 +- src/examples/media-session/media-session.h | 4 +- src/examples/media-session/metadata.c | 3 +- src/examples/media-session/restore-stream.c | 144 +++++---- 8 files changed, 507 insertions(+), 128 deletions(-) create mode 100644 src/examples/media-session/json.h diff --git a/src/examples/media-session/default-nodes.c b/src/examples/media-session/default-nodes.c index 4ef2b04a0..8c9fc9bf7 100644 --- a/src/examples/media-session/default-nodes.c +++ b/src/examples/media-session/default-nodes.c @@ -40,9 +40,11 @@ #include "extensions/metadata.h" #include "media-session.h" +#include "json.h" #define NAME "default-nodes" #define SESSION_KEY "default-nodes" +#define PREFIX "default." #define SAVE_INTERVAL 1 @@ -119,7 +121,8 @@ static void remove_idle_timeout(struct impl *impl) int res; if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->properties)) < 0) + if ((res = sm_media_session_save_state(impl->session, + SESSION_KEY, PREFIX, impl->properties)) < 0) pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); pw_loop_destroy_source(main_loop, impl->idle_timeout); impl->idle_timeout = NULL; @@ -173,7 +176,7 @@ static int metadata_property(void *object, uint32_t subject, if (key == NULL) pw_properties_clear(impl->properties); else - pw_properties_set(impl->properties, key, name); + pw_properties_setf(impl->properties, key, "{ \"name\": \"%s\" }", name); add_idle_timeout(impl); } return 0; @@ -193,12 +196,31 @@ static void session_create(void *data, struct sm_object *object) return; spa_dict_for_each(it, &impl->properties->dict) { - struct find_data d = { impl, it->value, SPA_ID_INVALID }; + struct spa_json json[2]; + int len; + const char *value; + char name [1024] = "\0"; + struct find_data d; + + spa_json_init(&json[0], it->value, strlen(it->value)); + if (spa_json_enter_object(&json[0], &json[1]) <= 0) + continue; + + while ((len = spa_json_next(&json[1], &value)) > 0) { + if (strncmp(value, "\"name\"", len) == 0) { + if (spa_json_get_string(&json[1], name, sizeof(name)) <= 0) + continue; + } else { + if (spa_json_next(&json[1], &value) <= 0) + break; + } + } + d = (struct find_data){ impl, name, SPA_ID_INVALID }; if (find_name(&d, object)) { char val[16]; snprintf(val, sizeof(val)-1, "%u", d.id); pw_log_info("found %s with id:%s restore as %s", - it->value, val, it->key); + name, val, it->key); pw_metadata_set_property(impl->session->metadata, PW_ID_CORE, it->key, SPA_TYPE_INFO_BASE"Id", val); } @@ -260,7 +282,8 @@ int sm_default_nodes_start(struct sm_media_session *session) return -ENOMEM; } - if ((res = sm_media_session_load_state(impl->session, SESSION_KEY, impl->properties)) < 0) + if ((res = sm_media_session_load_state(impl->session, + SESSION_KEY, PREFIX, impl->properties)) < 0) pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); diff --git a/src/examples/media-session/default-profile.c b/src/examples/media-session/default-profile.c index 608e85c19..2cc72fde9 100644 --- a/src/examples/media-session/default-profile.c +++ b/src/examples/media-session/default-profile.c @@ -42,9 +42,11 @@ #include "extensions/metadata.h" #include "media-session.h" +#include "json.h" #define NAME "default-profile" #define SESSION_KEY "default-profile" +#define PREFIX "default.profile." #define SAVE_INTERVAL 1 @@ -67,7 +69,7 @@ struct device { uint32_t id; struct impl *impl; - char *name; + char *key; struct spa_hook listener; @@ -88,7 +90,8 @@ static void remove_idle_timeout(struct impl *impl) int res; if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->properties)) < 0) + if ((res = sm_media_session_save_state(impl->session, + SESSION_KEY, PREFIX, impl->properties)) < 0) pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); pw_loop_destroy_source(main_loop, impl->idle_timeout); impl->idle_timeout = NULL; @@ -137,30 +140,44 @@ static uint32_t find_profile_id(struct device *dev, const char *name) static int restore_profile(struct device *dev) { + struct spa_json it[2]; struct impl *impl = dev->impl; - const char *name; + const char *json, *value; + int len; uint32_t index = SPA_ID_INVALID; + char buf[1024], name[1024] = "\0"; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - name = pw_properties_get(impl->properties, dev->name); - if (name == NULL) + json = pw_properties_get(impl->properties, dev->key); + if (json == NULL) return -ENOENT; + spa_json_init(&it[0], json, strlen(json)); + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; + + while ((len = spa_json_next(&it[1], &value)) > 0) { + if (strncmp(value, "\"name\"", len) == 0) { + if (spa_json_get_string(&it[1], name, sizeof(name)) <= 0) + continue; + } else { + if (spa_json_next(&it[1], &value) <= 0) + break; + } + } pw_log_debug("device %d: find profile '%s'", dev->id, name); index = find_profile_id(dev, name); + if (index == SPA_ID_INVALID) + return -ENOENT; - if (index != SPA_ID_INVALID) { - char buf[1024]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - - pw_log_info("device %d: restore profile '%s' index %d", dev->id, name, index); - pw_device_set_param((struct pw_device*)dev->obj->obj.proxy, - SPA_PARAM_Profile, 0, - spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, - SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); - dev->active_profile = index; - } - return -ENOENT; + pw_log_info("device %d: restore profile '%s' index %d", dev->id, name, index); + pw_device_set_param((struct pw_device*)dev->obj->obj.proxy, + SPA_PARAM_Profile, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); + dev->active_profile = index; + return 0; } static int handle_profile(struct device *dev, struct sm_param *p) @@ -186,7 +203,7 @@ static int handle_profile(struct device *dev, struct sm_param *p) dev->active_profile = index; pw_log_debug("device %d: current profile %d %s", dev->id, index, name); - pw_properties_set(impl->properties, dev->name, name); + pw_properties_setf(impl->properties, dev->key, "{ \"name\": \"%s\" }", name); add_idle_timeout(impl); } return 0; @@ -241,7 +258,7 @@ static void session_create(void *data, struct sm_object *object) dev->obj = (struct sm_device*)object; dev->id = object->id; dev->impl = impl; - dev->name = strdup(name); + dev->key = spa_aprintf(PREFIX"%s", name); dev->obj->obj.mask |= SM_DEVICE_CHANGE_MASK_PARAMS; sm_object_add_listener(&dev->obj->obj, &dev->listener, &object_events, dev); @@ -250,7 +267,7 @@ static void session_create(void *data, struct sm_object *object) static void destroy_device(struct impl *impl, struct device *dev) { spa_hook_remove(&dev->listener); - free(dev->name); + free(dev->key); sm_object_remove_data((struct sm_object*)dev->obj, SESSION_KEY); } @@ -302,7 +319,8 @@ int sm_default_profile_start(struct sm_media_session *session) return -ENOMEM; } - if ((res = sm_media_session_load_state(impl->session, SESSION_KEY, impl->properties)) < 0) + if ((res = sm_media_session_load_state(impl->session, + SESSION_KEY, PREFIX, impl->properties)) < 0) pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl); diff --git a/src/examples/media-session/default-routes.c b/src/examples/media-session/default-routes.c index ef7fcda3c..ea1741507 100644 --- a/src/examples/media-session/default-routes.c +++ b/src/examples/media-session/default-routes.c @@ -42,9 +42,11 @@ #include "extensions/metadata.h" #include "media-session.h" +#include "json.h" #define NAME "default-routes" #define SESSION_KEY "default-routes" +#define PREFIX "default.route." #define SAVE_INTERVAL 1 @@ -85,7 +87,8 @@ static void remove_idle_timeout(struct impl *impl) int res; if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->to_save)) < 0) + if ((res = sm_media_session_save_state(impl->session, + SESSION_KEY, PREFIX, impl->to_save)) < 0) pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); pw_loop_destroy_source(main_loop, impl->idle_timeout); impl->idle_timeout = NULL; @@ -117,22 +120,23 @@ static char *serialize_props(struct device *dev, const struct spa_pod *param) struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; float val = 0.0f; - bool b = false; + bool b = false, comma = false; char *ptr; size_t size; FILE *f; f = open_memstream(&ptr, &size); + fprintf(f, "{ "); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: spa_pod_get_float(&prop->value, &val); - fprintf(f, "volume:%f ", val); + fprintf(f, "%s\"volume\": %f ", (comma ? ", " : ""), val); break; case SPA_PROP_mute: spa_pod_get_bool(&prop->value, &b); - fprintf(f, "mute:%d ", b ? 1 : 0); + fprintf(f, "%s\"mute\": %s ", (comma ? ", " : ""), b ? "true" : "false"); break; case SPA_PROP_channelVolumes: { @@ -142,30 +146,36 @@ static char *serialize_props(struct device *dev, const struct spa_pod *param) n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vals, SPA_AUDIO_MAX_CHANNELS); - fprintf(f, "volumes:%d", n_vals); + fprintf(f, "%s\"volumes\": [", (comma ? ", " : "")); for (i = 0; i < n_vals; i++) - fprintf(f, ",%f", vals[i]); + fprintf(f, "%s%f", (i == 0 ? " " : ", "), vals[i]); + fprintf(f, " ]"); break; } default: - break; + continue; } + comma = true; } + fprintf(f, " }"); fclose(f); return ptr; } static int restore_route(struct device *dev, const char *val, uint32_t index, uint32_t device_id) { - const char *p; - char *end; + struct spa_json it[3]; char buf[1024]; + const char *value; + int len; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[2]; struct spa_pod *param; - bool mute = false; - uint32_t i, n_vols = 0; - float *vols, vol; + + spa_json_init(&it[0], val, strlen(val)); + + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route); @@ -177,47 +187,38 @@ static int restore_route(struct device *dev, const char *val, uint32_t index, ui spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, SPA_PARAM_Route); - p = val; - while (*p) { - if (strstr(p, "volume:") == p) { - p += 7; - vol = strtof(p, &end); - if (end == p) - continue; + while ((len = spa_json_next(&it[1], &value)) > 0) { + if (strncmp(value, "\"volume\"", len) == 0) { + float vol; + if (spa_json_get_float(&it[1], &vol) <= 0) + continue; spa_pod_builder_prop(&b, SPA_PROP_volume, 0); spa_pod_builder_float(&b, vol); - p = end; } - else if (strstr(p, "mute:") == p) { - mute = p[5] == '0' ? false : true; + else if (strncmp(value, "\"mute\"", len) == 0) { + bool mute; + if (spa_json_get_bool(&it[1], &mute) <= 0) + continue; spa_pod_builder_prop(&b, SPA_PROP_mute, 0); spa_pod_builder_bool(&b, mute); - p+=6; } - else if (strstr(p, "volumes:") == p) { - p += 8; - n_vols = strtol(p, &end, 10); - if (end == p) - continue; - p = end; - if (n_vols >= SPA_AUDIO_MAX_CHANNELS) - continue; - vols = alloca(n_vols * sizeof(float)); - for (i = 0; i < n_vols && *p == ','; i++) { - p++; - vols[i] = strtof(p, &end); - if (end == p) - break; - p = end; - } - if (i != n_vols) + else if (strncmp(value, "\"volumes\"", len) == 0) { + uint32_t n_vols; + float vols[SPA_AUDIO_MAX_CHANNELS]; + + if (spa_json_enter_array(&it[1], &it[2]) <= 0) continue; + for (n_vols = 0; n_vols < SPA_AUDIO_MAX_CHANNELS; n_vols++) { + if (spa_json_get_float(&it[2], &vols[n_vols]) <= 0) + break; + } spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0); spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, n_vols, vols); } else { - p++; + if (spa_json_next(&it[1], &value) <= 0) + break; } } spa_pod_builder_pop(&b, &f[1]); @@ -252,7 +253,7 @@ static int handle_route(struct device *dev, struct sm_param *p) return res; } - snprintf(key, sizeof(key)-1, "%s:%s:%s", dev->name, + snprintf(key, sizeof(key)-1, PREFIX"%s:%s:%s", dev->name, direction == SPA_DIRECTION_INPUT ? "input" : "output", name); val = pw_properties_get(impl->to_restore, key); @@ -381,7 +382,8 @@ int sm_default_routes_start(struct sm_media_session *session) goto exit_free; } - if ((res = sm_media_session_load_state(impl->session, SESSION_KEY, impl->to_restore)) < 0) + if ((res = sm_media_session_load_state(impl->session, + SESSION_KEY, PREFIX, impl->to_restore)) < 0) pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); impl->to_save = pw_properties_copy(impl->to_restore); diff --git a/src/examples/media-session/json.h b/src/examples/media-session/json.h new file mode 100644 index 000000000..1b32df5df --- /dev/null +++ b/src/examples/media-session/json.h @@ -0,0 +1,292 @@ +/* Simple Plugin API + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPA_UTILS_JSON_H +#define SPA_UTILS_JSON_H + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif +#include +#include +#include + + +struct spa_json { + const char *cur; + const char *end; + struct spa_json *parent; + uint32_t state; + uint32_t depth; +}; + +#define SPA_JSON_INIT(data,size) (struct spa_json) { (data), (data)+(size), } + +static inline void spa_json_init(struct spa_json * iter, const char *data, size_t size) +{ + *iter = SPA_JSON_INIT(data, size); +} +#define SPA_JSON_ENTER(iter) (struct spa_json) { (iter)->cur, (iter)->end, (iter), } + +static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub) +{ + *sub = SPA_JSON_ENTER(iter); +} + +static inline int spa_json_next(struct spa_json * iter, const char **value) +{ + int utf8_remain = 0; + enum { __NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC }; + + for (; iter->cur < iter->end; iter->cur++) { + unsigned char cur = (unsigned char)*iter->cur; + again: + switch (iter->state) { + case __NONE: + iter->state = __STRUCT; + iter->depth = 0; + goto again; + case __STRUCT: + switch (cur) { + case '\t': case ' ': case '\r': case '\n': case ':': case ',': + continue; + case '"': + *value = iter->cur; + iter->state = __STRING; + continue; + case '[': case '{': + *value = iter->cur; + if (++iter->depth > 1) + continue; + iter->cur++; + return 1; + case '}': case ']': + if (iter->depth == 0) { + if (iter->parent) + iter->parent->cur = iter->cur; + return 0; + } + --iter->depth; + continue; + case '-': case 'a' ... 'z': case 'A' ... 'Z': case '0' ... '9': + *value = iter->cur; + iter->state = __BARE; + continue; + } + return -1; + case __BARE: + switch (cur) { + case '\t': case ' ': case '\r': case '\n': + case ':': case ',': case ']': case '}': + iter->state = __STRUCT; + if (iter->depth > 0) + goto again; + return iter->cur - *value; + default: + if (cur >= 32 && cur <= 126) + continue; + } + return -1; + case __STRING: + switch (cur) { + case '\\': + iter->state = __ESC; + continue; + case '"': + iter->state = __STRUCT; + if (iter->depth > 0) + continue; + iter->cur++; + return iter->cur - *value; + case 240 ... 247: + utf8_remain++; + SPA_FALLTHROUGH; + case 224 ... 239: + utf8_remain++; + SPA_FALLTHROUGH; + case 192 ... 223: + utf8_remain++; + iter->state = __UTF8; + continue; + default: + if (cur >= 32 && cur <= 126) + continue; + } + return -1; + case __UTF8: + switch (cur) { + case 128 ... 191: + if (--utf8_remain == 0) + iter->state = __STRING; + continue; + } + return -1; + case __ESC: + switch (cur) { + case '"': case '\\': case '/': case 'b': case 'f': + case 'n': case 'r': case 't': case 'u': + iter->state = __STRING; + continue; + } + return -1; + } + } + return iter->depth == 0 ? 0 : -1; +} + +static inline int spa_json_enter_container(struct spa_json *iter, struct spa_json *sub, char type) +{ + const char *value; + if (spa_json_next(iter, &value) < 0 || *value != type) + return -1; + spa_json_enter(iter, sub); + return 1; +} + +static inline int spa_json_enter_object(struct spa_json *iter, struct spa_json *sub) +{ + return spa_json_enter_container(iter, sub, '{'); +} + +static inline int spa_json_enter_array(struct spa_json *iter, struct spa_json *sub) +{ + return spa_json_enter_container(iter, sub, '['); +} + +static inline int spa_json_is_object(const char *val, int len) +{ + return len > 0 && *val == '{'; +} + +static inline bool spa_json_is_array(const char *val, int len) +{ + return len > 0 && *val == '['; +} + +static inline bool spa_json_is_float(const char *val, int len) +{ + char *end; + strtof(val, &end); + return end == val + len; +} + +static inline bool spa_json_is_string(const char *val, int len) +{ + return len > 1 && *val == '"'; +} + +static inline bool spa_json_is_null(const char *val, int len) +{ + return len == 4 && strcmp(val, "null") == 0; +} + +static inline bool spa_json_is_true(const char *val, int len) +{ + return len == 4 && strcmp(val, "true") == 0; +} + +static inline bool spa_json_is_false(const char *val, int len) +{ + return len == 5 && strcmp(val, "false") == 0; +} + +static inline bool spa_json_is_bool(const char *val, int len) +{ + return spa_json_is_true(val, len) || spa_json_is_false(val, len); +} + +static inline int spa_json_parse_float(const char *val, int len, float *result) +{ + char *end; + *result = strtof(val, &end); + return end == val + len; +} +static inline int spa_json_parse_bool(const char *val, int len, bool *result) +{ + if ((*result = spa_json_is_true(val, len))) + return 1; + if (!(*result = !spa_json_is_false(val, len))) + return 1; + return -1; +} + +static inline int spa_json_parse_string(const char *val, int len, char *result) +{ + const char *p; + if (!spa_json_is_string(val, len)) + return -1; + for (p = val+1; p < val + len-1; p++) { + if (*p == '\\') { + p++; + if (*p == 'n') + *result++ = '\n'; + else if (*p == 'r') + *result++ = '\r'; + else if (*p == 'b') + *result++ = '\b'; + else if (*p == 't') + *result++ = '\t'; + else + *result++ = *p; + } else + *result++ = *p; + } + *result++ = '\0'; + return 1; +} + +static inline int spa_json_get_float(struct spa_json *iter, float *res) +{ + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return -1; + return spa_json_parse_float(value, len, res); +} + +static inline int spa_json_get_bool(struct spa_json *iter, bool *res) +{ + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return -1; + return spa_json_parse_bool(value, len, res); +} + +static inline int spa_json_get_string(struct spa_json *iter, char *res, int maxlen) +{ + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0 || maxlen < len) + return -1; + return spa_json_parse_string(value, len, res); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_UTILS_JSON_H */ diff --git a/src/examples/media-session/media-session.c b/src/examples/media-session/media-session.c index 8696460f6..377d361b1 100644 --- a/src/examples/media-session/media-session.c +++ b/src/examples/media-session/media-session.c @@ -1816,7 +1816,7 @@ static int state_dir(struct sm_media_session *sess) return res; } int sm_media_session_load_state(struct sm_media_session *sess, - const char *name, struct pw_properties *props) + const char *name, const char *prefix, struct pw_properties *props) { int sfd, fd, count = 0; FILE *f; @@ -1847,14 +1847,15 @@ int sm_media_session_load_state(struct sm_media_session *sess, } *k = '\0'; val = ++p; - count += pw_properties_set(props, key, val); + if (prefix == NULL || strstr(key, prefix) == key) + count += pw_properties_set(props, key, val); } fclose(f); return count; } int sm_media_session_save_state(struct sm_media_session *sess, - const char *name, const struct pw_properties *props) + const char *name, const char *prefix, const struct pw_properties *props) { const struct spa_dict_item *it; char *tmp_name; @@ -1875,6 +1876,8 @@ int sm_media_session_save_state(struct sm_media_session *sess, f = fdopen(fd, "w"); spa_dict_for_each(it, &props->dict) { const char *p = it->key; + if (prefix != NULL && strstr(p, prefix) != p) + continue; while (*p) { if (*p == ' ' || *p == '\\') fputc('\\', f); diff --git a/src/examples/media-session/media-session.h b/src/examples/media-session/media-session.h index 38cf631e4..d8db66fd7 100644 --- a/src/examples/media-session/media-session.h +++ b/src/examples/media-session/media-session.h @@ -289,9 +289,9 @@ int sm_media_session_remove_links(struct sm_media_session *sess, const struct spa_dict *dict); int sm_media_session_load_state(struct sm_media_session *sess, - const char *name, struct pw_properties *props); + const char *name, const char *prefix, struct pw_properties *props); int sm_media_session_save_state(struct sm_media_session *sess, - const char *name, const struct pw_properties *props); + const char *name, const char *prefix, const struct pw_properties *props); #ifdef __cplusplus } diff --git a/src/examples/media-session/metadata.c b/src/examples/media-session/metadata.c index 4e75f79bf..d5f774521 100644 --- a/src/examples/media-session/metadata.c +++ b/src/examples/media-session/metadata.c @@ -209,7 +209,8 @@ static int impl_set_property(void *object, if (type == NULL) type = item->type; changed = change_item(item, type, value); - pw_log_info(NAME" %p: change id:%d key:%s type:%s value:%s", this, + if (changed) + pw_log_info(NAME" %p: change id:%d key:%s type:%s value:%s", this, subject, key, type, value); } diff --git a/src/examples/media-session/restore-stream.c b/src/examples/media-session/restore-stream.c index d51cfa1ec..0bbb70b18 100644 --- a/src/examples/media-session/restore-stream.c +++ b/src/examples/media-session/restore-stream.c @@ -42,9 +42,11 @@ #include "extensions/metadata.h" #include "media-session.h" +#include "json.h" #define NAME "restore-stream" #define SESSION_KEY "restore-stream" +#define PREFIX "restore.stream." #define SAVE_INTERVAL 1 @@ -58,9 +60,11 @@ struct impl { struct spa_source *idle_timeout; struct pw_metadata *metadata; - struct spa_hook meta_listener; + struct spa_hook metadata_listener; struct pw_properties *props; + + unsigned int sync:1; }; struct stream { @@ -81,7 +85,8 @@ static void remove_idle_timeout(struct impl *impl) int res; if (impl->idle_timeout) { - if ((res = sm_media_session_save_state(impl->session, SESSION_KEY, impl->props)) < 0) + if ((res = sm_media_session_save_state(impl->session, + SESSION_KEY, PREFIX, impl->props)) < 0) pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res)); pw_loop_destroy_source(main_loop, impl->idle_timeout); impl->idle_timeout = NULL; @@ -122,22 +127,23 @@ static char *serialize_props(struct stream *str, const struct spa_pod *param) struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; float val = 0.0f; - bool b = false; + bool b = false, comma = false; char *ptr; size_t size; FILE *f; f = open_memstream(&ptr, &size); + fprintf(f, "{ "); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: spa_pod_get_float(&prop->value, &val); - fprintf(f, "volume:%f ", val); + fprintf(f, "%s\"volume\": %f", (comma ? ", " : ""), val); break; case SPA_PROP_mute: spa_pod_get_bool(&prop->value, &b); - fprintf(f, "mute:%d ", b ? 1 : 0); + fprintf(f, "%s\"mute\": %s", (comma ? ", " : ""), b ? "true" : "false"); break; case SPA_PROP_channelVolumes: { @@ -147,19 +153,21 @@ static char *serialize_props(struct stream *str, const struct spa_pod *param) n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vals, SPA_AUDIO_MAX_CHANNELS); - fprintf(f, "volumes:%d", n_vals); + fprintf(f, "%s\"volumes\": [", (comma ? ", " : "")); for (i = 0; i < n_vals; i++) - fprintf(f, ",%f", vals[i]); - fprintf(f, " "); + fprintf(f, "%s%f", (i == 0 ? " ":", "), vals[i]); + fprintf(f, " ]"); break; } default: - break; + continue; } + comma = true; } if (str->obj->target_node != NULL) - fprintf(f, "target-node:%s", str->obj->target_node); + fprintf(f, "\"target-node\": \"%s\"", str->obj->target_node); + fprintf(f, " }"); fclose(f); return ptr; } @@ -168,10 +176,39 @@ static void sync_metadata(struct impl *impl) { const struct spa_dict_item *it; + impl->sync = true; spa_dict_for_each(it, &impl->props->dict) - pw_metadata_set_property(impl->metadata, 0, it->key, "Spa:String", it->value); + pw_metadata_set_property(impl->metadata, 0, it->key, "Spa:String:JSON", it->value); + impl->sync = false; } +static int metadata_property(void *object, uint32_t subject, + const char *key, const char *type, const char *value) +{ + struct impl *impl = object; + bool changed = false; + + if (impl->sync) + return 0; + + if (subject == PW_ID_CORE) { + if (key == NULL) + pw_properties_clear(impl->props); + else if (strstr(key, PREFIX) == key) { + changed = pw_properties_set(impl->props, key, value); + } + } + if (changed) + add_idle_timeout(impl); + + return 0; +} + +static const struct pw_metadata_events metadata_events = { + PW_VERSION_METADATA_EVENTS, + .property = metadata_property, +}; + static int handle_props(struct stream *str, struct sm_param *p) { struct impl *impl = str->impl; @@ -196,65 +233,64 @@ static int handle_props(struct stream *str, struct sm_param *p) static int restore_stream(struct stream *str, const char *val) { - const char *p; - char *end; + struct spa_json it[3]; + const char *value; + int len; char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[2]; struct spa_pod *param; - bool mute = false; - uint32_t i, n_vols = 0; - float *vols, vol; + + spa_json_init(&it[0], val, strlen(val)); + + if (spa_json_enter_object(&it[0], &it[1]) <= 0) + return -EINVAL; spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - p = val; - while (*p) { - if (strstr(p, "volume:") == p) { - p += 7; - vol = strtof(p, &end); - if (end == p) - continue; + + while ((len = spa_json_next(&it[1], &value)) > 0) { + if (strncmp(value, "\"volume\"", len) == 0) { + float vol; + if (spa_json_get_float(&it[1], &vol) <= 0) + continue; spa_pod_builder_prop(&b, SPA_PROP_volume, 0); spa_pod_builder_float(&b, vol); - p = end; } - else if (strstr(p, "mute:") == p) { - mute = p[5] == '0' ? false : true; + else if (strncmp(value, "\"mute\"", len) == 0) { + bool mute; + if (spa_json_get_bool(&it[1], &mute) <= 0) + continue; spa_pod_builder_prop(&b, SPA_PROP_mute, 0); spa_pod_builder_bool(&b, mute); - p+=6; } - else if (strstr(p, "volumes:") == p) { - p += 8; - n_vols = strtol(p, &end, 10); - if (end == p) - continue; - p = end; - if (n_vols >= SPA_AUDIO_MAX_CHANNELS) - continue; - vols = alloca(n_vols * sizeof(float)); - for (i = 0; i < n_vols && *p == ','; i++) { - p++; - vols[i] = strtof(p, &end); - if (end == p) - break; - p = end; - } - if (i != n_vols) + else if (strncmp(value, "\"volumes\"", len) == 0) { + uint32_t n_vols; + float vols[SPA_AUDIO_MAX_CHANNELS]; + + if (spa_json_enter_array(&it[1], &it[2]) <= 0) continue; + + for (n_vols = 0; n_vols < SPA_AUDIO_MAX_CHANNELS; n_vols++) { + if (spa_json_get_float(&it[2], &vols[n_vols]) <= 0) + break; + } spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0); spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, n_vols, vols); } - else if (strstr(p, "target-node:") == p) { - p += 12; - i = strlen(p); - pw_log_info("stream %d: target '%s'", str->obj->obj.id, p); + else if (strncmp(value, "\"target-node\"", len) == 0) { + char name[1024]; + + if (spa_json_get_string(&it[1], name, sizeof(name)) <= 0) + continue; + + pw_log_info("stream %d: target '%s'", str->obj->obj.id, name); free(str->obj->target_node); - str->obj->target_node = i > 0 ? strndup(p, i) : NULL; + str->obj->target_node = strdup(name); } else { - p++; + if (spa_json_next(&it[1], &value) <= 0) + break; } } param = spa_pod_builder_pop(&b, &f[0]); @@ -284,7 +320,7 @@ static void update_key(struct stream *str) key = NULL; for (i = 0; i < SPA_N_ELEMENTS(keys); i++) { if ((p = pw_properties_get(obj->props, keys[i]))) { - key = spa_aprintf("%s-%s:%s", str->media_class, keys[i], p); + key = spa_aprintf(PREFIX"%s.%s:%s", str->media_class, keys[i], p); break; } } @@ -421,7 +457,11 @@ int sm_restore_stream_start(struct sm_media_session *session) if (impl->metadata == NULL) goto exit_errno; - if ((res = sm_media_session_load_state(impl->session, SESSION_KEY, impl->props)) < 0) + pw_metadata_add_listener(impl->metadata, &impl->metadata_listener, + &metadata_events, impl); + + if ((res = sm_media_session_load_state(impl->session, + SESSION_KEY, PREFIX, impl->props)) < 0) pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res)); sync_metadata(impl);