From 9b5c5fa6168113650dfef96b4a015c48ea9963c1 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 17 May 2024 16:29:11 -0700 Subject: [PATCH] topology: pre-processor: Introduce extends/overrides keywords for classes Add 2 new keywords for class definitions to allow new pipeline classes extend the definitions in the base class. This feature is useful for extending previous pipeline class definitions with the addition of one or more widgets without having to duplicate everything in the new class definition. For example: Consider a pipeline class definition as below. Note that only the widgets & routes are shown here. Class.Pipeline.mixout-gain-dai-copier-playback { Object.Widget { mixout."1" {} dai-copier."1" {} gain."1" {} pipeline."1" {} } Object.Base { !route [ { source mixout.$index.1 sink gain.$index.1 } ] } } If we want to extend this pipeline with the addition of an eqiir/eqfir, the extends keyword can be used as below: Class.Pipeline.mixout-gain-eqiir-eqfir-dai-copier-playback { extends "mixout-gain-dai-copier-playback" Object.Widget { eqiir.1 {} eqfir.1 {} } Object.Base { !route [ { source gain.$index.1 sink eqiir.$index.1 } { source eqiir.$index.1 sink eqfir.$index.1 } ] } } This allows for defining a new class without having to duplicate everything in the base class and just adding the new widgets/routes that extend the current pipeline definition in the base class. The extends keyword is useful when extending a pipeline while keeping the routes in the base class intact and adding new widgets at the end of the pipeline. But if we want to modify an existing pipeline class while modifying the order of widgets and/or inserting new widgets, we should use the overridess keyword instead. This allows for adding new widgets to the list of widgets in the base class definition while also allowing overriding the routes to allow inserting the new widgets and reordering the widgets in the base class. For example, if we want to add a drc widget between the gain and the eqiir modules in the above class, we can do the following: Class.Pipeline.mixout-efx-dai-copier-playback { overrides "mixout-gain-eqiir-eqfir-dai-copier-playback" Object.Widget { drc.1 {} } Object.Base { !route [ { source mixout.$index.1 sink gain.$index.1 } { source gain.$index.1 sink drc.$index.1 } { source drc.$index.1 sink eqiir.$index.1 } { source eqiir.$index.1 sink eqfir.$index.1 } ] } } Note that the routes array contains all the routes in the new class definition. Signed-off-by: Ranjani Sridharan --- topology/pre-processor.c | 161 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/topology/pre-processor.c b/topology/pre-processor.c index 01f8a5de5..e4c3826e4 100644 --- a/topology/pre-processor.c +++ b/topology/pre-processor.c @@ -921,6 +921,159 @@ static int pre_process_arrays(struct tplg_pre_processor *tplg_pp, snd_config_t * return 0; } +static int pre_process_class(struct tplg_pre_processor *tplg_pp, snd_config_t *top) +{ + snd_config_iterator_t i, next; + snd_config_t *classes; + int ret; + + ret = snd_config_search(top, "Class", &classes); + if (ret < 0) + return 0; + + snd_config_for_each(i, next, classes) { + snd_config_iterator_t i2, next2; + snd_config_t *n, *class_cfg, *base_class, *tmp, *base_class_cfg, *tmp1; + snd_config_t * widget_cfg; + const char *class_name, *base_class_name, *class_type; + char *string; + + n = snd_config_iterator_entry(i); + ret = snd_config_get_id(n, &class_type); + if (ret < 0) + return ret; + + /* only "Pipeline" classes support the extends/overrides keyword */ + if (strcmp(class_type, "Pipeline")) + continue; + + snd_config_for_each(i2, next2, n) { + class_cfg = snd_config_iterator_entry(i2); + bool override = false; + + /* check if the class extends/overrides a base class */ + ret = snd_config_search(class_cfg, "extends", &base_class); + if (ret < 0) { + ret = snd_config_search(class_cfg, "overrides", &base_class); + if (ret < 0) + continue; + override = true; + } + + ret = snd_config_get_id(class_cfg, &class_name); + if (ret < 0) + return ret; + + /* get the base class name */ + ret = snd_config_get_string(base_class, &base_class_name); + if (ret < 0) { + SNDERR("Invalid base class name for %s\n", class_name); + return ret; + } + + string = tplg_snprintf("Class.%s.%s", class_type, base_class_name); + if (!string) + return -ENOMEM; + + /* search base class config */ + ret = snd_config_search(top, string, &base_class_cfg); + free(string); + if (ret < 0) { + SNDERR("Cannot find class definition for %s\n", base_class_name); + return ret; + } + + /* create a temp node with the base class definition */ + ret = snd_config_copy(&tmp, base_class_cfg); + if(ret < 0) + return ret; + + /* create a temp node with the new class definition */ + ret = snd_config_copy(&tmp1, class_cfg); + if(ret < 0) { + snd_config_delete(tmp); + return ret; + } + + /* + * merge the base class config node with the new class config. + * The override flag when set indicates that the base class defaults in + * terms of pipeline params or routes must be overridden with the + * new class defaults. When overriding routes, a subclass must define all + * the routes in the pipeline. + */ + ret = snd_config_merge(tmp, tmp1, override); + if (ret < 0) { + SNDERR("Failed to merge base class definition for %s\n", + class_name); + snd_config_delete(tmp); + snd_config_delete(tmp1); + return ret; + } + + /* merge the new config into the class config */ + ret = snd_config_merge(class_cfg, tmp, true); + if (ret < 0) { + snd_config_delete(tmp); + return ret; + } + + /* delete the extends/overrides node from the class config */ + snd_config_delete(base_class); + + if (!override) + continue; + + /* + * since a merge with override removes all widgets from the base class, + * copy them back into the new class config + */ + ret = snd_config_search(base_class_cfg, "Object.Widget", &widget_cfg); + if (ret < 0) + continue; + + ret = snd_config_copy(&tmp, widget_cfg); + if (ret < 0) { + SNDERR("failed to copy widgets from the base class %s\n", + base_class_name); + return ret; + } + + ret = snd_config_search(class_cfg, "Object.Widget", &widget_cfg); + if (ret < 0) { + snd_config_delete(tmp); + continue; + } + + ret = snd_config_merge(widget_cfg, tmp, false); + if (ret < 0) { + SNDERR("failed to merge widgets from the base class %s to the sub class %s\n", + base_class_name, class_name); + return ret; + } + } + } + + return 0; +} + +static int pre_process_classes(struct tplg_pre_processor *tplg_pp, snd_config_t *top) +{ + int ret; + + if (snd_config_get_type(top) != SND_CONFIG_TYPE_COMPOUND) + return 0; + + /* process classes at this node */ + ret = pre_process_class(tplg_pp, top); + if (ret < 0) { + fprintf(stderr, "Failed to preprocess classes\n"); + return ret; + } + + return 0; +} + #endif /* version < 1.2.6 */ int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size, @@ -980,6 +1133,14 @@ int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_ fprintf(stderr, "Failed to process object arrays in input config\n"); goto err; } + + /* expand subclasses */ + err = pre_process_classes(tplg_pp, tplg_pp->input_cfg); + if (err < 0) { + fprintf(stderr, "Failed to process sub classes in input config\n"); + goto err; + } + #endif err = pre_process_config(tplg_pp, tplg_pp->input_cfg);