From 245cf67a86a86c1530b3018d4ae93f75eeb014e0 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Thu, 8 May 2025 15:38:23 +0300 Subject: [PATCH 1/2] Tools: Testbench: Add sof-ctl simulation to control scripts With this change sof-ctl commands can be executed during sof-testbench4 simulation, e.g. sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' \ -s tools/ctl/ipc4/eq_iir/loudness.txt No other than switches -c and -s are supported for simulated sof-ctl. The blob file must be in normal comma separated ASCII uint32_t numbers format with nothing else in it. With this change the SOF processing components can be tested extensively for their controls during simulated audio streaming. Signed-off-by: Seppo Ingalsuo --- tools/testbench/include/testbench/utils.h | 2 + tools/testbench/utils.c | 146 +++++++++++++++++++++- tools/testbench/utils_ipc3.c | 5 + tools/testbench/utils_ipc4.c | 7 ++ 4 files changed, 158 insertions(+), 2 deletions(-) diff --git a/tools/testbench/include/testbench/utils.h b/tools/testbench/include/testbench/utils.h index 1d6df8faeeab..7a35c935d66b 100644 --- a/tools/testbench/include/testbench/utils.h +++ b/tools/testbench/include/testbench/utils.h @@ -23,6 +23,7 @@ #define TB_MAX_CTL_NAME_CHARS 128 #define TB_MAX_VOLUME_SIZE 120 #define TB_MAX_BYTES_DATA_SIZE 8192 +#define TB_MAX_BLOB_CONTENT_CHARS 32768 /* number of widgets types supported in testbench */ #define TB_NUM_WIDGETS_SUPPORTED 16 @@ -159,6 +160,7 @@ int tb_pipeline_reset(struct ipc *ipc, struct pipeline *p); int tb_pipeline_start(struct ipc *ipc, struct pipeline *p); int tb_pipeline_stop(struct ipc *ipc, struct pipeline *p); int tb_read_controls(struct testbench_prm *tp, int64_t *sleep_ns); +int tb_set_bytes_control(struct testbench_prm *tp, struct tb_ctl *ctl, uint32_t *data); int tb_set_enum_control(struct testbench_prm *tp, struct tb_ctl *ctl, char *control_params); int tb_set_reset_state(struct testbench_prm *tp); int tb_set_mixer_control(struct testbench_prm *tp, struct tb_ctl *ctl, char *control_params); diff --git a/tools/testbench/utils.c b/tools/testbench/utils.c index 71122b8a83af..f6efbbcbc387 100644 --- a/tools/testbench/utils.c +++ b/tools/testbench/utils.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -388,10 +389,141 @@ static int tb_parse_amixer(struct testbench_prm *tp, char *line) return ret; } +static int tb_parse_sofctl(struct testbench_prm *tp, char *line) +{ + struct tb_ctl *ctl; + uint32_t *blob_bin = NULL; + char *blob_name = NULL; + char *blob_str = NULL; + char *control_name = NULL; + char *end; + char *find_ctl_name_str = "-c name=\""; + char *find_end_str = "\" "; + char *find_set_switch_str = "-s"; + char *name_str; + char *rest; + char *token; + int copy_len; + int find_len = strlen(find_ctl_name_str); + int n = 0; + int ret = 0; + FILE *fh; + + name_str = strstr(line, find_ctl_name_str); + if (!name_str) { + fprintf(stderr, "error: no control name in script line: %s\n", line); + return -EINVAL; + } + + end = strstr(&name_str[find_len], find_end_str); + if (!end) { + fprintf(stderr, "error: no control name end quote in script line: %s\n", line); + return -EINVAL; + } + + copy_len = end - name_str - find_len; + control_name = strndup(name_str + find_len, copy_len); + if (!control_name) { + fprintf(stderr, "error: failed to duplicate control name.\n"); + return -errno; + } + + name_str = strstr(line, find_set_switch_str); + if (!name_str) { + fprintf(stderr, "error: no sof-ctl control set switch in command: %s.\n", + line); + ret = -EINVAL; + goto err; + } + + name_str += strlen(find_set_switch_str) + 1; + end = line + strlen(line); + copy_len = end - name_str; + blob_name = strndup(name_str, copy_len); + if (!blob_name) { + fprintf(stderr, "error: failed to duplicate blob name.\n"); + ret = -errno; + goto err; + } + + ctl = tb_find_control_by_name(tp, control_name); + if (!ctl) { + fprintf(stderr, "error: control %s not found in topology.\n", control_name); + ret = -EINVAL; + goto err; + } + + if (ctl->type != SND_SOC_TPLG_TYPE_BYTES) { + fprintf(stderr, "error: control %s type %d is not supported.\n", + control_name, ctl->type); + ret = -EINVAL; + goto err; + } + + blob_str = malloc(TB_MAX_BLOB_CONTENT_CHARS); + if (!blob_str) { + fprintf(stderr, "error: failed to allocate memory for blob file content.\n"); + ret = -ENOMEM; + goto err; + } + + blob_bin = malloc(TB_MAX_BYTES_DATA_SIZE); + if (!blob_bin) { + fprintf(stderr, "error: failed to allocate memory for blob data.\n"); + ret = -ENOMEM; + goto err; + } + + printf("Info: Setting control name '%s' to blob '%s'\n", control_name, blob_name); + fh = fopen(blob_name, "r"); + if (!fh) { + fprintf(stderr, "error: could not open file.\n"); + ret = -errno; + goto err; + } + + end = fgets(blob_str, TB_MAX_BLOB_CONTENT_CHARS, fh); + fclose(fh); + if (!end) { + fprintf(stderr, "error: failed to read data from blob file.\n"); + ret = -ENODATA; + goto err; + } + + rest = blob_str; + while ((token = strtok_r(rest, ",", &rest))) { + if (n == TB_MAX_BYTES_DATA_SIZE) { + fprintf(stderr, "error: data read exceeds max control data size.\n"); + ret = -EINVAL; + goto err; + } + + blob_bin[n] = atoi(token); + n++; + } + + if (n < 2) { + fprintf(stderr, "error: at least two values are required in the blob file.\n"); + ret = -EINVAL; + goto err; + } + + /* Ignore TLV header from beginning. */ + ret = tb_set_bytes_control(tp, ctl, &blob_bin[2]); + +err: + free(blob_str); + free(blob_bin); + free(blob_name); + free(control_name); + return ret; +} + int tb_read_controls(struct testbench_prm *tp, int64_t *sleep_ns) { char *sleep_cmd = "sleep "; char *amixer_cmd = "amixer "; + char *sofctl_cmd = "sof-ctl "; char *raw_line; char *line; int ret = 0; @@ -411,7 +543,7 @@ int tb_read_controls(struct testbench_prm *tp, int64_t *sleep_ns) if (line[0] == '#' || strlen(line) == 0) continue; - if (strncmp(line, sleep_cmd, sizeof(*sleep_cmd)) == 0) { + if (strncmp(line, sleep_cmd, strlen(sleep_cmd)) == 0) { ret = tb_parse_sleep(line, sleep_ns); if (ret) { fprintf(stderr, "error: failed parse of sleep command.\n"); @@ -420,12 +552,22 @@ int tb_read_controls(struct testbench_prm *tp, int64_t *sleep_ns) break; } - if (strncmp(line, amixer_cmd, sizeof(*amixer_cmd)) == 0) { + if (strncmp(line, amixer_cmd, strlen(amixer_cmd)) == 0) { ret = tb_parse_amixer(tp, line); if (ret) { fprintf(stderr, "error: failed parse of amixer command.\n"); break; } + continue; + } + + if (strncmp(line, sofctl_cmd, strlen(sofctl_cmd)) == 0) { + ret = tb_parse_sofctl(tp, line); + if (ret) { + fprintf(stderr, "error: failed parse of sof-ctl command.\n"); + break; + } + continue; } } diff --git a/tools/testbench/utils_ipc3.c b/tools/testbench/utils_ipc3.c index 3e9749f4a0dd..96804badaf21 100644 --- a/tools/testbench/utils_ipc3.c +++ b/tools/testbench/utils_ipc3.c @@ -435,4 +435,9 @@ int tb_set_mixer_control(struct testbench_prm *tp, struct tb_ctl *ctl, char *con return 0; } +int tb_set_bytes_control(struct testbench_prm *tp, struct tb_ctl *ctl, uint32_t *data) +{ + return 0; +} + #endif /* CONFIG_IPC_MAJOR_3 */ diff --git a/tools/testbench/utils_ipc4.c b/tools/testbench/utils_ipc4.c index 878f8b346596..53d2a3666d60 100644 --- a/tools/testbench/utils_ipc4.c +++ b/tools/testbench/utils_ipc4.c @@ -691,4 +691,11 @@ int tb_set_mixer_control(struct testbench_prm *tp, struct tb_ctl *ctl, char *con return ret; } +int tb_set_bytes_control(struct testbench_prm *tp, struct tb_ctl *ctl, uint32_t *data) +{ + return tb_send_bytes_data(&tp->ipc_tx, &tp->ipc_rx, + ctl->module_id, ctl->instance_id, + (struct sof_abi_hdr *)data); +} + #endif /* CONFIG_IPC_MAJOR_4 */ From 30ba23ed8862e9ad5a5a0ff3f879c915f2edcfb5 Mon Sep 17 00:00:00 2001 From: Seppo Ingalsuo Date: Fri, 9 May 2025 12:01:14 +0300 Subject: [PATCH 2/2] Tools: Testbench: Add example of sof-ctl usage to README.md This addition shows how to use the new bytes control set feature in control script. Signed-off-by: Seppo Ingalsuo --- tools/testbench/README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/testbench/README.md b/tools/testbench/README.md index ffe160881244..d4030adc426d 100644 --- a/tools/testbench/README.md +++ b/tools/testbench/README.md @@ -95,6 +95,41 @@ tools/testbench/build_testbench/install/bin/sof-testbench4 -r 48000 -c 2 -b S32_ sox --encoding signed-integer -L -r 48000 -c 2 -b 32 out.raw out.wav ``` +As second example apply in one second intervals bytes control blobs +for IIR equalizer. The impact is easiest to hear with pink noise +signal. Create the control script below and run the following commands +to create the input, run testbench, and convert to wav for examining +the output. + +``` +#!/bin/sh + +# Example test sequence for IIR equalizer, switch other processing off + +amixer -c0 cset name='Post Mixer Analog Playback DRC switch' off +amixer -c0 cset name='Post Mixer Analog Playback Volume' 45 +sof-ctl -c name='Post Mixer Analog Playback FIR Eq bytes' -s tools/ctl/ipc4/eq_fir/pass.txt +sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' -s tools/ctl/ipc4/eq_iir/pass.txt +sleep 1 +sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' -s tools/ctl/ipc4/eq_iir/loudness.txt +sleep 1 +sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' -s tools/ctl/ipc4/eq_iir/bandpass.txt +sleep 1 +sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' -s tools/ctl/ipc4/eq_iir/bassboost.txt +sleep 1 +sof-ctl -c name='Post Mixer Analog Playback IIR Eq bytes' -s tools/ctl/ipc4/eq_iir/highpass_50hz_0db_48khz.txt +``` + +``` +sox -n --encoding signed-integer -L -r 48000 -c 2 -b 32 in.raw synth 5 pinknoise norm -20 + +tools/testbench/build_testbench/install/bin/sof-testbench4 -r 48000 -c 2 -b S32_LE -p 1,2 \ + -t tools/build_tools/topology/topology2/production/sof-hda-generic.tplg \ + -i in.raw -o out.raw -s controls.sh + +sox --encoding signed-integer -L -r 48000 -c 2 -b 32 out.raw out.wav +``` + ### Run testbench with helper script The scripts/sof-testbench-helper.sh simplifies the task. See the help