From 620401d1179ff40077d8b3b0aaec004c65571b49 Mon Sep 17 00:00:00 2001 From: Sebastiano Carlucci Date: Tue, 7 Apr 2020 07:57:31 -0700 Subject: [PATCH 01/12] sof: eq_iir: modularize iir_df2t() function This commit extracts the biquad processing from iir_df2t(). It allows to reuse the biquad processing code independently of the equalizer implementation. Components such as crossover use biquads for processing input. But it could not do so because iir_df2t() was specific to single output audio processing. The motivation behind this change was to reuse the coefficients of the LR4 biquads for the crossover more freely. An LR4 filter is made of two biquads in series with same coefficients. Therefore to save memory, we can store one copy of each set of coefficient, and pass those to the biquad processing function. Signed-off-by: Sebastiano Carlucci --- src/audio/eq_iir/iir_generic.c | 35 ++--------------------- src/include/sof/audio/eq_iir/iir.h | 45 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/audio/eq_iir/iir_generic.c b/src/audio/eq_iir/iir_generic.c index 9dd47508533f..fde2d79cc63e 100644 --- a/src/audio/eq_iir/iir_generic.c +++ b/src/audio/eq_iir/iir_generic.c @@ -42,13 +42,9 @@ /* Series DF2T IIR */ -/* 32 bit data, 32 bit coefficients and 64 bit state variables */ - int32_t iir_df2t(struct iir_state_df2t *iir, int32_t x) { int32_t in; - int32_t tmp; - int64_t acc; int32_t out = 0; int i; int j; @@ -63,35 +59,8 @@ int32_t iir_df2t(struct iir_state_df2t *iir, int32_t x) in = x; for (j = 0; j < iir->biquads; j += iir->biquads_in_series) { for (i = 0; i < iir->biquads_in_series; i++) { - /* Compute output: Delay is Q3.61 - * Q2.30 x Q1.31 -> Q3.61 - * Shift Q3.61 to Q3.31 with rounding - */ - acc = ((int64_t)iir->coef[c + 4]) * in + iir->delay[d]; - tmp = (int32_t)Q_SHIFT_RND(acc, 61, 31); - - /* Compute first delay */ - acc = iir->delay[d + 1]; - acc += ((int64_t)iir->coef[c + 3]) * in; /* Coef b1 */ - acc += ((int64_t)iir->coef[c + 1]) * tmp; /* Coef a1 */ - iir->delay[d] = acc; - - /* Compute second delay */ - acc = ((int64_t)iir->coef[c + 2]) * in; /* Coef b2 */ - acc += ((int64_t)iir->coef[c]) * tmp; /* Coef a2 */ - iir->delay[d + 1] = acc; - - /* Apply gain Q2.14 x Q1.31 -> Q3.45 */ - acc = ((int64_t)iir->coef[c + 6]) * tmp; /* Gain */ - - /* Apply biquad output shift right parameter - * simultaneously with Q3.45 to Q3.31 conversion. Then - * saturate to 32 bits Q1.31 and prepare for next - * biquad. - */ - acc = Q_SHIFT_RND(acc, 45 + iir->coef[c + 5], 31); - in = sat_int32(acc); - + in = iir_process_biquad(in, &iir->coef[c], + &iir->delay[d]); /* Proceed to next biquad coefficients and delay * lines. */ diff --git a/src/include/sof/audio/eq_iir/iir.h b/src/include/sof/audio/eq_iir/iir.h index 86d39fed104a..b533d5e30bab 100644 --- a/src/include/sof/audio/eq_iir/iir.h +++ b/src/include/sof/audio/eq_iir/iir.h @@ -12,6 +12,7 @@ #include #include +#include struct sof_eq_iir_header_df2t; @@ -61,6 +62,50 @@ struct iir_state_df2t { int64_t *delay; /* Pointer to IIR delay line */ }; +/* + * \brief Returns the output of a biquad. + * + * 32 bit data, 32 bit coefficients and 64 bit state variables + * \param in input to the biquad Q1.31 + * \param coef coefficients of the biquad Q2.30 + * \param delay delay of the biquads Q3.61 + */ +static inline int32_t iir_process_biquad(int32_t in, int32_t *coef, + int64_t *delay) +{ + int32_t tmp; + int64_t acc; + + /* Compute output: Delay is Q3.61 + * Q2.30 x Q1.31 -> Q3.61 + * Shift Q3.61 to Q3.31 with rounding + */ + acc = ((int64_t)coef[4]) * in + delay[0]; + tmp = (int32_t)Q_SHIFT_RND(acc, 61, 31); + + /* Compute first delay */ + acc = delay[1]; + acc += ((int64_t)coef[3]) * in; /* Coef b1 */ + acc += ((int64_t)coef[1]) * tmp; /* Coef a1 */ + delay[0] = acc; + + /* Compute second delay */ + acc = ((int64_t)coef[2]) * in; /* Coef b2 */ + acc += ((int64_t)coef[0]) * tmp; /* Coef a2 */ + delay[1] = acc; + + /* Apply gain Q2.14 x Q1.31 -> Q3.45 */ + acc = ((int64_t)coef[6]) * tmp; /* Gain */ + + /* Apply biquad output shift right parameter + * simultaneously with Q3.45 to Q3.31 conversion. Then + * saturate to 32 bits Q1.31 and prepare for next + * biquad. + */ + acc = Q_SHIFT_RND(acc, 45 + coef[5], 31); + return sat_int32(acc); +} + int32_t iir_df2t(struct iir_state_df2t *iir, int32_t x); int iir_init_coef_df2t(struct iir_state_df2t *iir, From e079b493c61b686a854da97e9c712ae63ea23870 Mon Sep 17 00:00:00 2001 From: Sebastiano Carlucci Date: Thu, 16 Apr 2020 14:30:28 -0700 Subject: [PATCH 02/12] sof: crossover: Add Crossover component This commit adds Crossover to the list of SOF components. A crossover filter can be used to split an input to different frequency bands. The number of outputs should be set statically in the topology. The user then uses the control bytes to route the frequency bands to different outputs. (similar to the demux component). This commit adds support for the following formats: - S16_LE - S24_LE - S32_LE Signed-off-by: Sebastiano Carlucci --- CODEOWNERS | 1 + src/audio/CMakeLists.txt | 3 + src/audio/Kconfig | 8 + src/audio/crossover/CMakeLists.txt | 2 + src/audio/crossover/crossover.c | 855 ++++++++++++++++++++ src/audio/crossover/crossover_generic.c | 291 +++++++ src/audio/eq_iir/iir_generic.c | 3 +- src/include/ipc/topology.h | 1 + src/include/sof/audio/crossover/crossover.h | 160 ++++ src/include/sof/audio/eq_iir/iir.h | 6 +- src/include/user/crossover.h | 104 +++ 11 files changed, 1429 insertions(+), 5 deletions(-) create mode 100644 src/audio/crossover/CMakeLists.txt create mode 100644 src/audio/crossover/crossover.c create mode 100644 src/audio/crossover/crossover_generic.c create mode 100644 src/include/sof/audio/crossover/crossover.h create mode 100644 src/include/user/crossover.h diff --git a/CODEOWNERS b/CODEOWNERS index 63eeca904ed0..4de75c2c35f9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,7 @@ src/audio/tone.c @singalsu src/audio/kpb.c @mrajwa src/audio/mux/* @akloniex src/audio/dcblock* @cujomalainey @dgreid +src/audio/crossover* @cujomalainey @dgreid # platforms src/arch/xtensa/* @tlauda diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index c2b912accdfd..32dca24f0815 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -24,6 +24,9 @@ if(NOT BUILD_LIBRARY) if(CONFIG_COMP_DCBLOCK) add_subdirectory(dcblock) endif() + if(CONFIG_COMP_CROSSOVER) + add_subdirectory(crossover) + endif() if(CONFIG_COMP_TONE) add_local_sources(sof tone.c diff --git a/src/audio/Kconfig b/src/audio/Kconfig index 1dfcc0c4d617..360991144b32 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -105,6 +105,14 @@ config COMP_SEL help Select for SEL component +config COMP_CROSSOVER + bool "Crossover Filter component" + default y + help + Select for Crossover Filter component. A crossover can be used to + split a signal into two or more frequency ranges, so that the outputs + can be sent to drivers that are designed for those ranges. + config COMP_DCBLOCK bool "DC Blocking Filter component" default y diff --git a/src/audio/crossover/CMakeLists.txt b/src/audio/crossover/CMakeLists.txt new file mode 100644 index 000000000000..15a68035621e --- /dev/null +++ b/src/audio/crossover/CMakeLists.txt @@ -0,0 +1,2 @@ +add_local_sources(sof crossover.c) +add_local_sources(sof crossover_generic.c) diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c new file mode 100644 index 000000000000..217b7e368b44 --- /dev/null +++ b/src/audio/crossover/crossover.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2020 Google LLC. All rights reserved. +// +// Author: Sebastiano Carlucci + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct comp_driver comp_crossover; + +/* 948c9ad1-806a-4131-ad6c-b2bda9e35a9f */ +DECLARE_SOF_UUID("crossover", crossover_uuid, 0x948c9ad1, 0x806a, 0x4131, + 0xad, 0x6c, 0xb2, 0xbd, 0xa9, 0xe3, 0x5a, 0x9f); + +static void crossover_free_config(struct sof_crossover_config **config) +{ + rfree(*config); + *config = NULL; +} + +/** + * \brief Reset the state of an LR4 filter. + */ +static void crossover_reset_state_lr4(struct iir_state_df2t *lr4) +{ + rfree(lr4->delay); + + lr4->coef = NULL; + lr4->delay = NULL; +} + +/** + * \brief Reset the state (coefficients and delay) of the crossover filter + * of a single channel. + */ +static void crossover_reset_state_ch(struct crossover_state *ch_state) +{ + int i; + + for (i = 0; i < CROSSOVER_MAX_LR4; i++) { + crossover_reset_state_lr4(&ch_state->lowpass[i]); + crossover_reset_state_lr4(&ch_state->highpass[i]); + } +} + +/** + * \brief Reset the state (coefficients and delay) of the crossover filter + * across all channels + */ +static void crossover_reset_state(struct comp_data *cd) +{ + int i; + + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) + crossover_reset_state_ch(&cd->state[i]); +} + +/** + * \brief Returns the index i such that assign_sink[i] = pipe_id. + * + * The assign_sink array in the configuration maps to the pipeline ids. + * + * \return the position at which pipe_id is found in config->assign_sink. + * -1 if not found. + */ +static uint8_t crossover_get_stream_index(struct sof_crossover_config *config, + uint32_t pipe_id) +{ + int i; + uint32_t *assign_sink = config->assign_sink; + + for (i = 0; i < config->num_sinks; i++) + if (assign_sink[i] == pipe_id) + return i; + + comp_cl_err(&comp_crossover, "crossover_get_stream_index() error: couldn't find any assignment for connected pipeline %u", + pipe_id); + + return -1; +} + +/* + * \brief Aligns the sinks with their respective assignments + * in the configuration. + * + * Refer to sof/src/include/sof/crossover.h for more information on assigning + * sinks to an output. + * + * \param[out] sinks array where the sinks are assigned + * \return number of sinks assigned. This number should be equal to + * config->num_sinks if no errors were found. + */ +static int crossover_assign_sinks(struct comp_dev *dev, + struct sof_crossover_config *config, + struct comp_buffer **sinks) +{ + struct comp_buffer *sink; + struct list_item *sink_list; + int num_sinks = 0; + int i; + + // align sink streams with their respective configurations + list_for_item(sink_list, &dev->bsink_list) { + sink = container_of(sink_list, struct comp_buffer, source_list); + if (sink->sink->state == dev->state) { + // if no config is set, then assign the sinks in order + if (!config) { + sinks[num_sinks++] = sink; + continue; + } + + i = crossover_get_stream_index(config, + sink->pipeline_id); + + /* If this sink buffer is not assigned + * in the configuration. + */ + if (i < 0) { + comp_err(dev, "crossover_assign_sinks(), could not find sink %d in config", + sink->pipeline_id); + break; + } + + if (sinks[i]) { + comp_err(dev, "crossover_assign_sinks(), multiple sinks from pipeline %d are assigned", + sink->pipeline_id); + break; + } + + sinks[i] = sink; + num_sinks++; + } + } + + return num_sinks; +} + +/** + * \brief Sets the state of a single LR4 filter. + * + * An LR4 filter is built by cascading two biquads in series. + * + * \param coef struct containing the coefficients of a butterworth + * high/low pass filter. + * \param[out] lr4 initialized struct + */ +static int crossover_init_coef_lr4(struct sof_eq_iir_biquad_df2t *coef, + struct iir_state_df2t *lr4) +{ + /* Reuse the same coefficients for biquads so we only + * store one copy of it. The processing functions will + * feed the same coef array to both biquads. + */ + lr4->coef = (int32_t *)coef; + + /* LR4 filters are two 2nd order filters, so only need 4 delay slots + * delay[0..1] -> state for first biquad + * delay[2..3] -> state for second biquad + */ + lr4->delay = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, + sizeof(uint64_t) * CROSSOVER_NUM_DELAYS_LR4); + if (!lr4->delay) + return -ENOMEM; + + lr4->biquads = 2; + lr4->biquads_in_series = 2; + + return 0; +} + +/** + * \brief Initializes the crossover coefficients for one channel + */ +static int crossover_init_coef_ch(struct sof_eq_iir_biquad_df2t *coef, + struct crossover_state *ch_state, + int32_t num_sinks) +{ + int32_t i; + int32_t j = 0; + int32_t num_lr4s = num_sinks == CROSSOVER_2WAY_NUM_SINKS ? 1 : 3; + int err = 0; + + for (i = 0; i < num_lr4s; i++) { + /* Get the low pass coefficients */ + err = crossover_init_coef_lr4(&coef[j], + &ch_state->lowpass[i]); + if (err < 0) + return -EINVAL; + /* Get the high pass coefficients */ + err = crossover_init_coef_lr4(&coef[j + 1], + &ch_state->highpass[i]); + if (err < 0) + return -EINVAL; + j += 2; + } + + return 0; +} + +/** + * \brief Initializes the coefficients of the crossover filter + * and assign them to the first nch channels. + * + * \param nch number of channels in the audio stream. + */ +static int crossover_init_coef(struct comp_data *cd, int nch) +{ + struct sof_eq_iir_biquad_df2t *crossover; + struct sof_crossover_config *config = cd->config; + int ch, err; + + if (!config) { + comp_cl_err(&comp_crossover, "crossover_init_coef(), no config is set"); + return -EINVAL; + } + + /* Sanity checks */ + if (nch > PLATFORM_MAX_CHANNELS) { + comp_cl_err(&comp_crossover, "crossover_init_coef(), invalid channels count (%i)", + nch); + return -EINVAL; + } + + comp_cl_info(&comp_crossover, "crossover_init_coef(), initiliazing %i-way crossover", + config->num_sinks); + + /* Collect the coef array and assign it to every channel */ + crossover = config->coef; + for (ch = 0; ch < nch; ch++) { + err = crossover_init_coef_ch(crossover, &cd->state[ch], + config->num_sinks); + /* Free all previously allocated blocks in case of an error */ + if (err < 0) { + comp_cl_err(&comp_crossover, "crossover_init_coef(), could not assign coefficients to ch %d", + ch); + crossover_reset_state(cd); + return err; + } + } + + return 0; +} + +/** + * \brief Setup the state, coefficients and processing functions for crossover. + * + * \param dev component device + */ +static int crossover_setup(struct comp_data *cd, int nch) +{ + int ret = 0; + + /* Reset any previous state. */ + crossover_reset_state(cd); + + /* Assign LR4 coefficients from config */ + ret = crossover_init_coef(cd, nch); + + return ret; +} + +/** + * \brief Creates a Crossover Filter component. + * \return Pointer to Crossover Filter component device. + */ +static struct comp_dev *crossover_new(const struct comp_driver *drv, + struct sof_ipc_comp *comp) +{ + struct comp_dev *dev; + struct comp_data *cd; + struct sof_ipc_comp_process *crossover; + struct sof_ipc_comp_process *ipc_crossover = + (struct sof_ipc_comp_process *)comp; + size_t bs = ipc_crossover->size; + int ret; + + comp_cl_info(&comp_crossover, "crossover_new()"); + + /* Check that the coefficients blob size is sane. */ + if (bs > SOF_CROSSOVER_MAX_SIZE) { + comp_cl_err(&comp_crossover, "crossover_new(), blob size (%d) exceeds maximum allowed size (%i)", + bs, SOF_CROSSOVER_MAX_SIZE); + return NULL; + } + + dev = comp_alloc(drv, COMP_SIZE(struct sof_ipc_comp_process)); + if (!dev) + return NULL; + + crossover = COMP_GET_IPC(dev, sof_ipc_comp_process); + ret = memcpy_s(crossover, sizeof(*crossover), ipc_crossover, + sizeof(struct sof_ipc_comp_process)); + assert(!ret); + + cd = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, + SOF_MEM_CAPS_RAM, sizeof(*cd)); + if (!cd) { + rfree(dev); + return NULL; + } + + comp_set_drvdata(dev, cd); + + cd->crossover_process = NULL; + cd->crossover_split = NULL; + cd->config = NULL; + cd->config_new = NULL; + + if (bs) { + cd->config = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, + SOF_MEM_CAPS_RAM, bs); + if (!cd->config) { + rfree(dev); + rfree(cd); + return NULL; + } + + ret = memcpy_s(cd->config, bs, ipc_crossover->data, bs); + assert(!ret); + } + + dev->state = COMP_STATE_READY; + return dev; +} + +/** + * \brief Frees Crossover Filter component. + */ +static void crossover_free(struct comp_dev *dev) +{ + struct comp_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "crossover_free()"); + + crossover_free_config(&cd->config); + crossover_free_config(&cd->config_new); + + crossover_reset_state(cd); + + rfree(cd); + rfree(dev); +} + +/** + * \brief Verifies that the config is formatted correctly. + * + * The function can only be called after the buffers have been initialized. + */ +static int crossover_validate_config(struct comp_dev *dev, + struct sof_crossover_config *config) +{ + struct comp_buffer *sink; + struct list_item *sink_list; + uint32_t size = config->size; + int32_t num_assigned_sinks = 0; + uint8_t assigned_sinks[SOF_CROSSOVER_MAX_STREAMS] = {0}; + int i; + + if (size > SOF_CROSSOVER_MAX_SIZE || !size) { + comp_err(dev, "crossover_validate_config(), size %d is invalid", + size); + return -EINVAL; + } + + if (config->num_sinks > SOF_CROSSOVER_MAX_STREAMS || + config->num_sinks < 2) { + comp_err(dev, "crossover_validate_config(), invalid num_sinks %i, expected number between 2 and %i", + config->num_sinks, SOF_CROSSOVER_MAX_STREAMS); + return -EINVAL; + } + + // Align the crossover's sinks, to their respective configuation in + // the config. + list_for_item(sink_list, &dev->bsink_list) { + sink = container_of(sink_list, struct comp_buffer, source_list); + i = crossover_get_stream_index(config, sink->pipeline_id); + if (i < 0) { + comp_warn(dev, "crossover_validate_config(), could not assign sink %d", + sink->pipeline_id); + break; + } + + if (assigned_sinks[i]) { + comp_warn(dev, "crossover_validate_config(), multiple sinks from pipeline %d are assigned", + sink->pipeline_id); + break; + } + + assigned_sinks[i] = true; + num_assigned_sinks++; + } + + // config is invalid if the number of assigned sinks + // is different than what is configured + if (num_assigned_sinks != config->num_sinks) { + comp_err(dev, "crossover_validate_config(), number of assigned sinks %d, expected from config %d", + num_assigned_sinks, config->num_sinks); + return -EINVAL; + } + + return 0; +} + +static int crossover_verify_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + int ret; + + comp_dbg(dev, "crossover_verify_params()"); + + ret = comp_verify_params(dev, 0, params); + if (ret < 0) { + comp_err(dev, "crossover_verify_params() error: comp_verify_params() failed."); + return ret; + } + + return 0; +} + +/** + * \brief Sets Crossover Filter component audio stream parameters. + * \param[in,out] dev Crossover Filter base component device. + * \return Error code. + */ +static int crossover_params(struct comp_dev *dev, + struct sof_ipc_stream_params *params) +{ + int err = 0; + + comp_dbg(dev, "crossover_params()"); + + err = crossover_verify_params(dev, params); + if (err < 0) + comp_err(dev, "crossover_params(): pcm params verification failed"); + + return err; +} + +static int crossover_cmd_set_data(struct comp_dev *dev, + struct sof_ipc_ctrl_data *cdata) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct sof_crossover_config *request; + uint32_t bs; + int ret = 0; + + switch (cdata->cmd) { + case SOF_CTRL_CMD_BINARY: + comp_info(dev, "crossover_cmd_set_data(), SOF_CTRL_CMD_BINARY"); + + /* Find size from header */ + request = (struct sof_crossover_config *)cdata->data->data; + bs = request->size; + + /* Check that there is no work-in-progress previous request */ + if (cd->config_new) { + comp_err(dev, "crossover_cmd_set_data(), busy with previous"); + return -EBUSY; + } + + /* Allocate and make a copy of the blob */ + cd->config_new = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, + SOF_MEM_CAPS_RAM, bs); + if (!cd->config_new) { + comp_err(dev, "crossover_cmd_set_data(), alloc fail"); + return -EINVAL; + } + + /* Copy the configuration. If the component state is ready + * the Crossover will initialize in prepare(). + */ + ret = memcpy_s(cd->config_new, bs, request, bs); + assert(!ret); + + /* If component state is READY we can omit old configuration + * immediately. When in playback/capture the new configuration + * presence is checked in copy(). + */ + if (dev->state == COMP_STATE_READY) + crossover_free_config(&cd->config); + + /* If there is no existing configuration the received can + * be set to current immediately. It will be applied in + * prepare() when streaming starts. + */ + if (!cd->config) { + cd->config = cd->config_new; + cd->config_new = NULL; + } + + break; + default: + comp_err(dev, "crossover_cmd_set_data(), invalid command"); + ret = -EINVAL; + break; + } + + return ret; +} + +static int crossover_cmd_get_data(struct comp_dev *dev, + struct sof_ipc_ctrl_data *cdata, int max_size) +{ + struct comp_data *cd = comp_get_drvdata(dev); + uint32_t bs; + int ret = 0; + + switch (cdata->cmd) { + case SOF_CTRL_CMD_BINARY: + comp_info(dev, "crossover_cmd_get_data(), SOF_CTRL_CMD_BINARY"); + + /* Copy back to user space */ + if (cd->config) { + bs = cd->config->size; + comp_info(dev, "crossover_cmd_get_data(), size %u", + bs); + if (bs > SOF_CROSSOVER_MAX_SIZE || bs == 0 || + bs > max_size) + return -EINVAL; + ret = memcpy_s(cdata->data->data, + cdata->data->size, + cd->config, bs); + assert(!ret); + + cdata->data->abi = SOF_ABI_VERSION; + cdata->data->size = bs; + } else { + comp_err(dev, "crossover_cmd_get_data(), no config"); + ret = -EINVAL; + } + break; + default: + comp_err(dev, "crossover_cmd_get_data(), invalid command"); + ret = -EINVAL; + break; + } + return ret; +} + +/** + * \brief Handles incoming IPC commands for Crossover component. + */ +static int crossover_cmd(struct comp_dev *dev, int cmd, void *data, + int max_data_size) +{ + struct sof_ipc_ctrl_data *cdata = data; + int ret = 0; + + comp_info(dev, "crossover_cmd()"); + + switch (cmd) { + case COMP_CMD_SET_DATA: + ret = crossover_cmd_set_data(dev, cdata); + break; + case COMP_CMD_GET_DATA: + ret = crossover_cmd_get_data(dev, cdata, max_data_size); + break; + default: + comp_err(dev, "crossover_cmd(), invalid command"); + ret = -EINVAL; + } + + return ret; +} + +/** + * \brief Sets Crossover Filter component state. + * \param[in,out] dev Crossover Filter base component device. + * \param[in] cmd Command type. + * \return Error code. + */ +static int crossover_trigger(struct comp_dev *dev, int cmd) +{ + comp_info(dev, "crossover_trigger()"); + + return comp_set_state(dev, cmd); +} + +/** + * \brief Copies and processes stream data. + * \param[in,out] dev Crossover Filter base component device. + * \return Error code. + */ +static int crossover_copy(struct comp_dev *dev) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct comp_buffer *source; + struct comp_buffer *sinks[SOF_CROSSOVER_MAX_STREAMS] = { NULL }; + int i, ret; + uint32_t num_sinks; + uint32_t num_assigned_sinks = 0; + uint32_t frames = UINT_MAX; + uint32_t source_bytes, avail; + uint32_t flags = 0; + uint32_t sinks_bytes[SOF_CROSSOVER_MAX_STREAMS] = { 0 }; + + comp_dbg(dev, "crossover_copy()"); + + source = list_first_item(&dev->bsource_list, struct comp_buffer, + sink_list); + + /* Check for changed configuration */ + if (cd->config_new) { + crossover_free_config(&cd->config); + cd->config = cd->config_new; + cd->config_new = NULL; + ret = crossover_setup(cd, source->stream.channels); + if (ret < 0) { + comp_err(dev, "crossover_copy(), setup failed"); + return -EINVAL; + } + } + + /* Use the assign_sink array from the config to route + * the output to the corresponding sinks. + * It is possible for an assigned sink to be in a different + * state than the component. Therefore not all sinks are guaranteed + * to be assigned: sink[i] can be NULL, 0 <= i <= config->num_sinks + */ + num_assigned_sinks = crossover_assign_sinks(dev, cd->config, sinks); + if (cd->config && num_assigned_sinks != cd->config->num_sinks) + comp_dbg(dev, "crossover_copy(), number of assigned sinks (%i) does not match number of sinks in config (%i).", + num_assigned_sinks, cd->config->num_sinks); + + /* If no config is set then assign the number of sinks to the number + * of sinks that were assigned + */ + if (cd->config) + num_sinks = cd->config->num_sinks; + else + num_sinks = num_assigned_sinks; + + buffer_lock(source, &flags); + + /* check if source is active */ + if (source->source->state != dev->state) { + buffer_unlock(source, flags); + return -EINVAL; + } + + /* Find the number of frames to copy over */ + for (i = 0; i < num_sinks; i++) { + if (!sinks[i]) + continue; + buffer_lock(sinks[i], &flags); + avail = audio_stream_avail_frames(&source->stream, + &sinks[i]->stream); + frames = MIN(frames, avail); + buffer_unlock(sinks[i], flags); + } + + buffer_unlock(source, flags); + + source_bytes = frames * audio_stream_frame_bytes(&source->stream); + + for (i = 0; i < num_sinks; i++) { + if (!sinks[i]) + continue; + sinks_bytes[i] = frames * + audio_stream_frame_bytes(&sinks[i]->stream); + } + + /* Process crossover */ + buffer_invalidate(source, source_bytes); + cd->crossover_process(dev, source, sinks, num_sinks, frames); + + for (i = 0; i < num_sinks; i++) { + if (!sinks[i]) + continue; + buffer_writeback(sinks[i], sinks_bytes[i]); + comp_update_buffer_produce(sinks[i], sinks_bytes[i]); + } + comp_update_buffer_consume(source, source_bytes); + + return 0; +} + +/** + * \brief Prepares Crossover Filter component for processing. + * \param[in,out] dev Crossover Filter base component device. + * \return Error code. + */ +static int crossover_prepare(struct comp_dev *dev) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct sof_ipc_comp_config *config = dev_comp_config(dev); + struct comp_buffer *source, *sink; + struct list_item *sink_list; + int32_t sink_period_bytes; + int ret; + + comp_info(dev, "crossover_prepare()"); + + ret = comp_set_state(dev, COMP_TRIGGER_PREPARE); + if (ret < 0) + return ret; + + if (ret == COMP_STATUS_STATE_ALREADY_SET) + return PPL_STATUS_PATH_STOP; + + /* Crossover has a variable number of sinks.*/ + source = list_first_item(&dev->bsource_list, + struct comp_buffer, sink_list); + + /* get source data format */ + cd->source_format = source->stream.frame_fmt; + + /* Validate frame format and buffer size of sinks */ + list_for_item(sink_list, &dev->bsink_list) { + sink = container_of(sink_list, struct comp_buffer, source_list); + if (cd->source_format != sink->stream.frame_fmt) { + comp_err(dev, "crossover_prepare(): Source fmt %d and sink fmt %d are different for sink %d.", + cd->source_format, sink->stream.frame_fmt, + sink->pipeline_id); + ret = -EINVAL; + goto err; + } + + sink_period_bytes = audio_stream_period_bytes(&sink->stream, + dev->frames); + if (sink->stream.size < + config->periods_sink * sink_period_bytes) { + comp_err(dev, "crossover_prepare(), sink %d buffer size %d is insufficient", + sink->pipeline_id, sink->stream.size); + ret = -ENOMEM; + goto err; + } + } + + comp_info(dev, "crossover_prepare(), source_format=%d, sink_formats=%d, nch=%d", + cd->source_format, cd->source_format, + source->stream.channels); + + /* Initialize Crossover */ + if (cd->config && crossover_validate_config(dev, cd->config) < 0) { + /* If config is invalid then delete it.*/ + comp_err(dev, "crossover_prepare(), invalid binary config format"); + crossover_free_config(&cd->config); + } + + if (cd->config) { + ret = crossover_setup(cd, source->stream.channels); + if (ret < 0) { + comp_err(dev, "crossover_prepare(), setup failed"); + goto err; + } + + cd->crossover_process = + crossover_find_proc_func(cd->source_format); + if (!cd->crossover_process) { + comp_err(dev, "crossover_prepare(), No processing function matching frame_fmt %i", + cd->source_format); + ret = -EINVAL; + goto err; + } + + cd->crossover_split = + crossover_find_split_func(cd->config->num_sinks); + if (!cd->crossover_split) { + comp_err(dev, "crossover_prepare(), No split function matching num_sinks %i", + cd->config->num_sinks); + ret = -EINVAL; + goto err; + } + } else { + comp_info(dev, "crossover_prepare(), setting crossover to passthrough mode"); + + cd->crossover_process = + crossover_find_proc_func_pass(cd->source_format); + + if (!cd->crossover_process) { + comp_err(dev, "crossover_prepare(), No passthrough function matching frame_fmt %i", + cd->source_format); + ret = -EINVAL; + goto err; + } + } + + return 0; + +err: + comp_set_state(dev, COMP_TRIGGER_RESET); + return ret; +} + +/** + * \brief Resets Crossover Filter component. + * \param[in,out] dev Crossover Filter base component device. + * \return Error code. + */ +static int crossover_reset(struct comp_dev *dev) +{ + struct comp_data *cd = comp_get_drvdata(dev); + + comp_info(dev, "crossover_reset()"); + + crossover_reset_state(cd); + + comp_set_state(dev, COMP_TRIGGER_RESET); + + return 0; +} + +/** \brief Crossover Filter component definition. */ +static const struct comp_driver comp_crossover = { + .type = SOF_COMP_CROSSOVER, + .uid = SOF_UUID(crossover_uuid), + .ops = { + .create = crossover_new, + .free = crossover_free, + .params = crossover_params, + .cmd = crossover_cmd, + .trigger = crossover_trigger, + .copy = crossover_copy, + .prepare = crossover_prepare, + .reset = crossover_reset, + }, +}; + +static SHARED_DATA struct comp_driver_info comp_crossover_info = { + .drv = &comp_crossover, +}; + +static void sys_comp_crossover_init(void) +{ + comp_register(platform_shared_get(&comp_crossover_info, + sizeof(comp_crossover_info))); +} + +DECLARE_MODULE(sys_comp_crossover_init); diff --git a/src/audio/crossover/crossover_generic.c b/src/audio/crossover/crossover_generic.c new file mode 100644 index 000000000000..f25280762057 --- /dev/null +++ b/src/audio/crossover/crossover_generic.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2020 Google LLC. All rights reserved. +// +// Author: Sebastiano Carlucci + +#include +#include +#include +#include +#include +#include + +/* + * \brief Splits x into two based on the coefficients set in the lp + * and hp filters. The output of the lp is in y1, the output of + * the hp is in y2. + * + * As a side effect, this function mutates the delay values of both + * filters. + */ +static void crossover_generic_lr4_split(struct iir_state_df2t *lp, + struct iir_state_df2t *hp, + int32_t x, int32_t *y1, int32_t *y2) +{ + *y1 = crossover_generic_process_lr4(x, lp); + *y2 = crossover_generic_process_lr4(x, hp); +} + +/* + * \brief Splits input signal into two and merges it back to it's + * original form. + * + * With 3-way crossovers, one output goes through only one LR4 filter, + * whereas the other two go through two LR4 filters. This causes the signals + * to be out of phase. We need to pass the signal through another set of LR4 + * filters to align back the phase. + */ +static void crossover_generic_lr4_merge(struct iir_state_df2t *lp, + struct iir_state_df2t *hp, + int32_t x, int32_t *y) +{ + int32_t z1, z2; + + z1 = crossover_generic_process_lr4(x, lp); + z2 = crossover_generic_process_lr4(x, hp); + *y = sat_int32(((int64_t)z1) + z2); +} + +static void crossover_generic_split_2way(int32_t in, + int32_t out[], + struct crossover_state *state) +{ + crossover_generic_lr4_split(&state->lowpass[0], &state->highpass[0], + in, &out[0], &out[1]); +} + +static void crossover_generic_split_3way(int32_t in, + int32_t out[], + struct crossover_state *state) +{ + int32_t z1, z2; + + crossover_generic_lr4_split(&state->lowpass[0], &state->highpass[0], + in, &z1, &z2); + /* Realign the phase of z1 */ + crossover_generic_lr4_merge(&state->lowpass[1], &state->highpass[1], + z1, &out[0]); + crossover_generic_lr4_split(&state->lowpass[2], &state->highpass[2], + z2, &out[1], &out[2]); +} + +static void crossover_generic_split_4way(int32_t in, + int32_t out[], + struct crossover_state *state) +{ + int32_t z1, z2; + + crossover_generic_lr4_split(&state->lowpass[1], &state->highpass[1], + in, &z1, &z2); + crossover_generic_lr4_split(&state->lowpass[0], &state->highpass[0], + z1, &out[0], &out[1]); + crossover_generic_lr4_split(&state->lowpass[2], &state->highpass[2], + z2, &out[2], &out[3]); +} + +#if CONFIG_FORMAT_S16LE +static void crossover_s16_default_pass(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames) +{ + const struct audio_stream *source_stream = &source->stream; + int16_t *x; + int32_t *y; + int i, j; + int n = source_stream->channels * frames; + + for (i = 0; i < n; i++) { + x = audio_stream_read_frag_s16(source_stream, i); + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + y = audio_stream_read_frag_s16((&sinks[j]->stream), i); + *y = *x; + } + } +} +#endif // CONFIG_FORMAT_S16LE + +#if CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE +static void crossover_s32_default_pass(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames) +{ + const struct audio_stream *source_stream = &source->stream; + int32_t *x, *y; + int i, j; + int n = source_stream->channels * frames; + + for (i = 0; i < n; i++) { + x = audio_stream_read_frag_s32(source_stream, i); + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + y = audio_stream_read_frag_s32((&sinks[j]->stream), i); + *y = *x; + } + } +} +#endif // CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE + +#if CONFIG_FORMAT_S16LE +static void crossover_s16_default(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct crossover_state *state; + const struct audio_stream *source_stream = &source->stream; + struct audio_stream *sink_stream; + int16_t *x, *y; + int ch, i, j; + int idx = 0; + int nch = source_stream->channels; + int32_t out[num_sinks]; + + for (ch = 0; ch < nch; ch++) { + idx = ch; + state = &cd->state[ch]; + for (i = 0; i < frames; i++) { + x = audio_stream_read_frag_s16(source_stream, idx); + cd->crossover_split(*x << 16, out, state); + + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + sink_stream = &sinks[j]->stream; + y = audio_stream_read_frag_s16(sink_stream, + idx); + *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); + } + + idx += nch; + } + } +} +#endif // CONFIG_FORMAT_S16LE + +#if CONFIG_FORMAT_S24LE +static void crossover_s24_default(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct crossover_state *state; + const struct audio_stream *source_stream = &source->stream; + struct audio_stream *sink_stream; + int32_t *x, *y; + int ch, i, j; + int idx = 0; + int nch = source_stream->channels; + int32_t out[num_sinks]; + + for (ch = 0; ch < nch; ch++) { + idx = ch; + state = &cd->state[ch]; + for (i = 0; i < frames; i++) { + x = audio_stream_read_frag_s32(source_stream, idx); + cd->crossover_split(*x << 8, out, state); + + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + sink_stream = &sinks[j]->stream; + y = audio_stream_read_frag_s32(sink_stream, + idx); + *y = sat_int24(Q_SHIFT_RND(out[j], 31, 23)); + } + + idx += nch; + } + } +} +#endif // CONFIG_FORMAT_S24LE + +#if CONFIG_FORMAT_S32LE +static void crossover_s32_default(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames) +{ + struct comp_data *cd = comp_get_drvdata(dev); + struct crossover_state *state; + const struct audio_stream *source_stream = &source->stream; + struct audio_stream *sink_stream; + int32_t *x, *y; + int ch, i, j; + int idx = 0; + int nch = source_stream->channels; + int32_t out[num_sinks]; + + for (ch = 0; ch < nch; ch++) { + idx = ch; + state = &cd->state[0]; + for (i = 0; i < frames; i++) { + x = audio_stream_read_frag_s32(source_stream, idx); + cd->crossover_split(*x, out, state); + + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + sink_stream = &sinks[j]->stream; + y = audio_stream_read_frag_s32(sink_stream, + idx); + *y = out[j]; + } + + idx += nch; + } + } +} +#endif // CONFIG_FORMAT_S32LE + +const struct crossover_proc_fnmap crossover_proc_fnmap[] = { +/* { SOURCE_FORMAT , PROCESSING FUNCTION } */ +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, crossover_s16_default }, +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, crossover_s24_default }, +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, crossover_s32_default }, +#endif /* CONFIG_FORMAT_S32LE */ +}; + +const struct crossover_proc_fnmap crossover_proc_fnmap_pass[] = { +/* { SOURCE_FORMAT , PROCESSING FUNCTION } */ +#if CONFIG_FORMAT_S16LE + { SOF_IPC_FRAME_S16_LE, crossover_s16_default_pass }, +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE + { SOF_IPC_FRAME_S24_4LE, crossover_s32_default_pass }, +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE + { SOF_IPC_FRAME_S32_LE, crossover_s32_default_pass }, +#endif /* CONFIG_FORMAT_S32LE */ +}; + +const size_t crossover_proc_fncount = ARRAY_SIZE(crossover_proc_fnmap); + +const crossover_split crossover_split_fnmap[] = { + crossover_generic_split_2way, + crossover_generic_split_3way, + crossover_generic_split_4way, +}; + +const size_t crossover_split_fncount = ARRAY_SIZE(crossover_split_fnmap); diff --git a/src/audio/eq_iir/iir_generic.c b/src/audio/eq_iir/iir_generic.c index fde2d79cc63e..dadce43faf8f 100644 --- a/src/audio/eq_iir/iir_generic.c +++ b/src/audio/eq_iir/iir_generic.c @@ -59,8 +59,7 @@ int32_t iir_df2t(struct iir_state_df2t *iir, int32_t x) in = x; for (j = 0; j < iir->biquads; j += iir->biquads_in_series) { for (i = 0; i < iir->biquads_in_series; i++) { - in = iir_process_biquad(in, &iir->coef[c], - &iir->delay[d]); + in = iir_df2t_biquad(in, &iir->coef[c], &iir->delay[d]); /* Proceed to next biquad coefficients and delay * lines. */ diff --git a/src/include/ipc/topology.h b/src/include/ipc/topology.h index eab722262a74..65018d67cd25 100644 --- a/src/include/ipc/topology.h +++ b/src/include/ipc/topology.h @@ -46,6 +46,7 @@ enum sof_comp_type { SOF_COMP_DEMUX, SOF_COMP_ASRC, /**< Asynchronous sample rate converter */ SOF_COMP_DCBLOCK, + SOF_COMP_CROSSOVER, /* keep FILEREAD/FILEWRITE as the last ones */ SOF_COMP_FILEREAD = 10000, /**< host test based file IO */ SOF_COMP_FILEWRITE = 10001, /**< host test based file IO */ diff --git a/src/include/sof/audio/crossover/crossover.h b/src/include/sof/audio/crossover/crossover.h new file mode 100644 index 000000000000..8ebf70d3bf1d --- /dev/null +++ b/src/include/sof/audio/crossover/crossover.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Google LLC. All rights reserved. + * + * Author: Sebastiano Carlucci + */ +#ifndef __SOF_AUDIO_CROSSOVER_CROSSOVER_H__ +#define __SOF_AUDIO_CROSSOVER_CROSSOVER_H__ + +#include +#include +#include +#include + +struct comp_buffer; +struct comp_dev; + +/* Maximum number of LR4 highpass OR lowpass filters */ +#define CROSSOVER_MAX_LR4 3 +/* Number of delay slots allocated for LR4 Filters */ +#define CROSSOVER_NUM_DELAYS_LR4 4 + +/* Number of sinks for a 2 way crossover filter */ +#define CROSSOVER_2WAY_NUM_SINKS 2 +/* Number of sinks for a 3 way crossover filter */ +#define CROSSOVER_3WAY_NUM_SINKS 3 +/* Number of sinks for a 4 way crossover filter */ +#define CROSSOVER_4WAY_NUM_SINKS 4 + +/** + * The Crossover filter will have from 2 to 4 outputs. + * Diagram of a 4-way Crossover filter (6 LR4 Filters). + * + * o---- LR4 LO-PASS --> y1(n) + * | + * o--- LR4 LO-PASS --o + * | | + * | o--- LR4 HI-PASS --> y2(n) + * x(n) --- o + * | o--- LR4 LO-PASS --> y3(n) + * | | + * o--- LR4 HI-PASS --o + * | + * o--- LR4 HI-PASS --> y4(n) + * + * Refer to include/user/crossover.h for diagrams of 2-way and 3-way crossovers + * The low and high pass LR4 filters have opposite phase responses, causing + * the intermediary outputs to be out of phase by 180 degrees. + * For 2-way and 3-way, the phases of the signals need to be synchronized. + * + * Each LR4 is made of two butterworth filters in series with the same params. + * + * x(n) --> BIQUAD --> z(n) --> BIQUAD --> y(n) + * + * In total, we keep track of the state of at most 6 IIRs each made of two + * biquads in series. + * + */ + +/** + * Stores the state of one channel of the Crossover filter + */ +struct crossover_state { + /* Store the state for each LR4 filter. */ + struct iir_state_df2t lowpass[CROSSOVER_MAX_LR4]; + struct iir_state_df2t highpass[CROSSOVER_MAX_LR4]; +}; + +typedef void (*crossover_process)(const struct comp_dev *dev, + const struct comp_buffer *source, + struct comp_buffer *sinks[], + int32_t num_sinks, + uint32_t frames); + +typedef void (*crossover_split)(int32_t in, int32_t out[], + struct crossover_state *state); + +/* Crossover component private data */ +struct comp_data { + /**< filter state */ + struct crossover_state state[PLATFORM_MAX_CHANNELS]; + struct sof_crossover_config *config; /**< pointer to setup blob */ + struct sof_crossover_config *config_new; /**< pointer to new setup */ + enum sof_ipc_frame source_format; /**< source frame format */ + crossover_process crossover_process; /**< processing function */ + crossover_split crossover_split; /**< split function */ +}; + +struct crossover_proc_fnmap { + enum sof_ipc_frame frame_fmt; + crossover_process crossover_proc_func; +}; + +extern const struct crossover_proc_fnmap crossover_proc_fnmap[]; +extern const struct crossover_proc_fnmap crossover_proc_fnmap_pass[]; +extern const size_t crossover_proc_fncount; + +/** + * \brief Returns Crossover processing function. + */ +static inline crossover_process + crossover_find_proc_func(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < crossover_proc_fncount; i++) + if (src_fmt == crossover_proc_fnmap[i].frame_fmt) + return crossover_proc_fnmap[i].crossover_proc_func; + + return NULL; +} + +/** + * \brief Returns Crossover passthrough functions. + */ +static inline crossover_process + crossover_find_proc_func_pass(enum sof_ipc_frame src_fmt) +{ + int i; + + /* Find suitable processing function from map */ + for (i = 0; i < crossover_proc_fncount; i++) + if (src_fmt == crossover_proc_fnmap_pass[i].frame_fmt) + return crossover_proc_fnmap_pass[i].crossover_proc_func; + + return NULL; +} + +extern const crossover_split crossover_split_fnmap[]; +extern const size_t crossover_split_fncount; + +/** + * \brief Returns Crossover split function. + */ +static inline crossover_split crossover_find_split_func(int32_t num_sinks) +{ + if (num_sinks < CROSSOVER_2WAY_NUM_SINKS && + num_sinks > CROSSOVER_4WAY_NUM_SINKS) + return NULL; + // The functions in the map are offset by 2 indices. + return crossover_split_fnmap[num_sinks - CROSSOVER_2WAY_NUM_SINKS]; +} + +/* + * \brief Runs input in through the LR4 filter and returns it's output. + */ +static inline int32_t crossover_generic_process_lr4(int32_t in, + struct iir_state_df2t *lr4) +{ + int i; + + /* Cascade two biquads with same coefficients in series. */ + for (i = 0; i < lr4->biquads_in_series; i++) + in = iir_df2t_biquad(in, lr4->coef, &lr4->delay[2 * i]); + + return in; +} + +#endif // __SOF_AUDIO_CROSSOVER_CROSSOVER_H__ diff --git a/src/include/sof/audio/eq_iir/iir.h b/src/include/sof/audio/eq_iir/iir.h index b533d5e30bab..2f09b02527e9 100644 --- a/src/include/sof/audio/eq_iir/iir.h +++ b/src/include/sof/audio/eq_iir/iir.h @@ -63,15 +63,15 @@ struct iir_state_df2t { }; /* - * \brief Returns the output of a biquad. + * \brief Returns the output of a biquad using its df2t form. * * 32 bit data, 32 bit coefficients and 64 bit state variables * \param in input to the biquad Q1.31 * \param coef coefficients of the biquad Q2.30 * \param delay delay of the biquads Q3.61 */ -static inline int32_t iir_process_biquad(int32_t in, int32_t *coef, - int64_t *delay) +static inline int32_t iir_df2t_biquad(int32_t in, int32_t *coef, + int64_t *delay) { int32_t tmp; int64_t acc; diff --git a/src/include/user/crossover.h b/src/include/user/crossover.h new file mode 100644 index 000000000000..e0ae8ee9e174 --- /dev/null +++ b/src/include/user/crossover.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * + * Copyright(c) 2020 Google LLC. All rights reserved. + * + * Author: Sebastiano Carlucci + */ + +#ifndef __USER_CROSSOVER_H__ +#define __USER_CROSSOVER_H__ + +#include +#include + +/* Maximum Number of sinks allowed in config */ +#define SOF_CROSSOVER_MAX_STREAMS 4 + +/* Maximum number allowed in configuration blob */ +#define SOF_CROSSOVER_MAX_SIZE 1024 + + /* crossover_configuration + * uint32_t number_of_sinks <= 4 + * 1=passthrough, n=n-way cossover. + * uint32_t assign_sinks[SOF_CROSSOVER_MAX_STREAMS] + * Mapping between sink positions and the sink's pipeline id. To assign + * output j to sink i, set: assign_sinks[j] = i. Refer to the diagrams + * below for more information on the sink outputs. + * + * 4-way: + * o---- LR4 LP0 --> LOW assign_sink[0] + * | + * o--- LR4 LP1 --o + * | | + * | o---- LR4 HP0 --> MID_LOW assign_sink[1] + * x(n) --- o + * | o---- LR4 LP2 --> MID_HIGH assign_sink[2] + * | | + * o--- LR4 HP1 --o + * | + * o---- LR4 HP2 --> HIGH assign_sink[3] + * + * Merging is necessary for 3way to adjsut the phase of the + * outputs. + * 3-way: + * o---- LR4 LP1 --o + * | | + * o--- LR4 LP0 --o +-> LOW assign_sink[0] + * | | | + * | o---- LR4 HP1 --o + * x(n) --- o + * | o---- LR4 LP2 -----> MID assign_sink[1] + * | | + * o--- LR4 HP0 --o + * | + * o---- LR4 HP2 -----> HIGH assign_sink[2] + * + * 2-way: + * o--- LR4 LP0 ---> LOW assign_sink[0] + * | + * x(n) --- o + * | + * o--- LR4 HP0 ---> HIGH assign_sink[1] + * + * struct sof_eq_iir_biquad_df2t coef[(num_sinks - 1)*2] + * The coefficients data for the LR4s. Depending on many + * sinks are set, the number entries of this field can vary. + * Each entry of the array defines the coefficients for one biquad + * of the LR4 filter. The order the coefficients are assigned is: + * [LR4 LP0, LR4 HP0, LR4 LP1, LR4 HP1, LR4 LP2, LR4 HP2]. + * coef[0] = coefficients of LR4 LP0... + * coef[1] = coefficients of LR4 HP0... + * ... + * + * Since an LR4 is two biquads in series with same coefficients, + * the config takes the coefficients for one biquad and + * assigns it to both biquads of the LR4. + * + * <1st Low Pass LR4> + * int32_t coef_a2 Q2.30 format + * int32_t coef_a1 Q2.30 format + * int32_t coef_b2 Q2.30 format + * int32_t coef_b1 Q2.30 format + * int32_t coef_b0 Q2.30 format + * int32_t output_shift number of right shift (nve for left) + * int32_t output_gain Q2.14 format + * <1nd High Pass LR4> + * ... + * <2nd Low Pass LR4> + * <2nd High Pass LR4> + * ... + * ... At most 3 Low Pass LR4s and 3 High Pass LR4s ... + * + */ +struct sof_crossover_config { + uint32_t size; + uint32_t num_sinks; + + /* reserved */ + uint32_t reserved[4]; + + uint32_t assign_sink[SOF_CROSSOVER_MAX_STREAMS]; + struct sof_eq_iir_biquad_df2t coef[]; +}; + +#endif // __USER_CROSSOVER_H__ From e2a6bb98f6511ff28eef436a563ad1cced631c01 Mon Sep 17 00:00:00 2001 From: Sebastiano Carlucci Date: Thu, 16 Apr 2020 14:37:25 -0700 Subject: [PATCH 03/12] tools: topology: Add crossover topology files This commit adds the topology files for the crossover component. The control bytes are generated by the tools in tune/crossover. Signed-off-by: Sebastiano Carlucci --- tools/topology/m4/crossover.m4 | 49 +++++++++++ tools/topology/m4/crossover_coef_default.m4 | 19 +++++ tools/topology/sof/pipe-crossover-playback.m4 | 81 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 tools/topology/m4/crossover.m4 create mode 100644 tools/topology/m4/crossover_coef_default.m4 create mode 100644 tools/topology/sof/pipe-crossover-playback.m4 diff --git a/tools/topology/m4/crossover.m4 b/tools/topology/m4/crossover.m4 new file mode 100644 index 000000000000..df9f25dadd65 --- /dev/null +++ b/tools/topology/m4/crossover.m4 @@ -0,0 +1,49 @@ +divert(-1) + +dnl Define macro for crossover widget + +dnl Crossover name) +define(`N_CROSSOVER', `CROSSOVER'PIPELINE_ID`.'$1) + +dnl W_CROSSOVER(name, format, periods_sink, periods_source, kcontrol_list) +define(`W_CROSSOVER', +`SectionVendorTuples."'N_CROSSOVER($1)`_tuples_w" {' +` tokens "sof_comp_tokens"' +` tuples."word" {' +` SOF_TKN_COMP_PERIOD_SINK_COUNT' STR($3) +` SOF_TKN_COMP_PERIOD_SOURCE_COUNT' STR($4) +` }' +`}' +`SectionData."'N_CROSSOVER($1)`_data_w" {' +` tuples "'N_CROSSOVER($1)`_tuples_w"' +`}' +`SectionVendorTuples."'N_CROSSOVER($1)`_tuples_str" {' +` tokens "sof_comp_tokens"' +` tuples."string" {' +` SOF_TKN_COMP_FORMAT' STR($2) +` }' +`}' +`SectionVendorTuples."'N_CROSSOVER($1)`_crossover_process_tuples_str" {' +` tokens "sof_process_tokens"' +` tuples."string" {' +` SOF_TKN_PROCESS_TYPE' "CROSSOVER" +` }' +`}' +`SectionData."'N_CROSSOVER($1)`_data_str" {' +` tuples "'N_CROSSOVER($1)`_tuples_str"' +` tuples "'N_CROSSOVER($1)`_crossover_process_tuples_str"' +`}' +`SectionWidget."'N_CROSSOVER($1)`" {' +` index "'PIPELINE_ID`"' +` type "effect"' +` no_pm "true"' +` data [' +` "'N_CROSSOVER($1)`_data_w"' +` "'N_CROSSOVER($1)`_data_str"' +` ]' +` bytes [' + $5 +` ]' +`}') + +divert(0)dnl diff --git a/tools/topology/m4/crossover_coef_default.m4 b/tools/topology/m4/crossover_coef_default.m4 new file mode 100644 index 000000000000..25d86f4a5c5b --- /dev/null +++ b/tools/topology/m4/crossover_coef_default.m4 @@ -0,0 +1,19 @@ +# Exported Control Bytes 17-Apr-2020 +CONTROLBYTES_PRIV(CROSSOVER_priv, +` bytes "0x53,0x4f,0x46,0x00,0x00,0x00,0x00,0x00,' +` 0x60,0x00,0x00,0x00,0x00,0xf0,0x00,0x03,' +` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' +` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' +` 0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00,' +` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' +` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' +` 0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,' +` 0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,' +` 0x8e,0x6f,0xa8,0xc5,0xb3,0x81,0x14,0x7a,' +` 0xb0,0xc3,0x10,0x00,0x5f,0x87,0x21,0x00,' +` 0xb0,0xc3,0x10,0x00,0x00,0x00,0x00,0x00,' +` 0x00,0x40,0x00,0x00,0x8e,0x6f,0xa8,0xc5,' +` 0xb3,0x81,0x14,0x7a,0x89,0x04,0x1b,0x3d,' +` 0xee,0xf6,0xc9,0x85,0x89,0x04,0x1b,0x3d,' +` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00"' +) diff --git a/tools/topology/sof/pipe-crossover-playback.m4 b/tools/topology/sof/pipe-crossover-playback.m4 new file mode 100644 index 000000000000..b6fe30a843ca --- /dev/null +++ b/tools/topology/sof/pipe-crossover-playback.m4 @@ -0,0 +1,81 @@ +# Crossover Volume Pipeline +# +# Low Latency Playback with demux and volume. +# +# Pipeline Endpoints for connection are : +# +# B2 (DAI buffer) +# Playback Crossover +# +# host PCM_P -- B0 --> Crossover 0 --> B1 ---> DAI0 +# | +# pipeline n+1 --> DAI + +# Include topology builder +include(`utils.m4') +include(`buffer.m4') +include(`pcm.m4') +include(`crossover.m4') +include(`bytecontrol.m4') + +# +# Controls for Crossover +# + +define(CROSSOVER_priv, concat(`crossover_bytes_', PIPELINE_ID)) +define(MY_CROSSOVER_CTRL, concat(`crossover_control_', PIPELINE_ID)) +include(`crossover_coef_default.m4') +C_CONTROLBYTES(MY_CROSSOVER_CTRL, PIPELINE_ID, + CONTROLBYTES_OPS(bytes, 258 binds the control to bytes get/put handlers, 258, 258), + CONTROLBYTES_EXTOPS(258 binds the control to bytes get/put handlers, 258, 258), + , , , + CONTROLBYTES_MAX(, 1024), + , + CROSSOVER_priv) + +# +# Components and Buffers +# + +# Host "Low latency Playback" PCM +# with 2 sink and 0 source periods +W_PCM_PLAYBACK(PCM_ID, Low Latency Playback, 2, 0) + +# Crossover filter has 2 sink and source periods. TODO for controls +W_CROSSOVER(0, PIPELINE_FORMAT, 2, 2, LIST(` ', "MY_CROSSOVER_CTRL")) + +# Low Latency Buffers +W_BUFFER(0, COMP_BUFFER_SIZE(2, + COMP_SAMPLE_SIZE(PIPELINE_FORMAT), PIPELINE_CHANNELS, COMP_PERIOD_FRAMES(PCM_MAX_RATE, SCHEDULE_PERIOD)), + PLATFORM_HOST_MEM_CAP) +W_BUFFER(1, COMP_BUFFER_SIZE(DAI_PERIODS, + COMP_SAMPLE_SIZE(PIPELINE_FORMAT), PIPELINE_CHANNELS, COMP_PERIOD_FRAMES(PCM_MAX_RATE, SCHEDULE_PERIOD)), + PLATFORM_COMP_MEM_CAP) + + +# +# Pipeline Graph +# host PCM_P -- B0 --> Crossover 0 --> B1 ---> DAI +# | +# pipeline n+1 --> DAI + +P_GRAPH(pipe-ll-playback-PIPELINE_ID, PIPELINE_ID, + LIST(` ', + `dapm(N_BUFFER(0), N_PCMP(PCM_ID))', + `dapm(N_CROSSOVER(0), N_BUFFER(0))', + `dapm(N_BUFFER(1), N_CROSSOVER(0))')) +# +# Pipeline Source and Sinks +# +indir(`define', concat(`PIPELINE_SOURCE_', PIPELINE_ID), N_BUFFER(1)) +indir(`define', concat(`PIPELINE_CROSSOVER_', PIPELINE_ID), N_CROSSOVER(0)) +indir(`define', concat(`PIPELINE_PCM_', PIPELINE_ID), Low Latency Playback PCM_ID) + +# +# PCM Configuration +# + + +# PCM capabilities supported by FW +PCM_CAPABILITIES(Low Latency Playback PCM_ID, `S32_LE,S24_LE,S16_LE', 48000, 48000, 2, PIPELINE_CHANNELS, 2, 16, 192, 16384, 65536, 65536) + From f5ef44d672b0b9cbbd9d70547e9535045fff7730 Mon Sep 17 00:00:00 2001 From: Sebastiano Carlucci Date: Thu, 16 Apr 2020 17:09:32 -0700 Subject: [PATCH 04/12] tools: tune: Add tools to generate ctrl bytes for Crossover This commit adds the tools to generate the control bytes for the crossover component. To generate the control bytes, run the example_crossover.m script. The parameters of the crossover components are: - number of outputs - sink assignments (routing crossover output to different pipelines) - frequency cutoffs To tweak the parameters modify the values in example_crossover.m and run it. Refer to sof/src/include/user/crossover.h for more information on how the crossover config is structured and how sink assignments are done. Signed-off-by: Sebastiano Carlucci --- tools/tune/crossover/crossover_build_blob.m | 79 ++++++++++++ tools/tune/crossover/crossover_coef_quant.m | 31 +++++ tools/tune/crossover/crossover_gen_coefs.m | 114 ++++++++++++++++++ .../crossover/crossover_generate_config.m | 20 +++ tools/tune/crossover/crossover_plot_freq.m | 111 +++++++++++++++++ tools/tune/crossover/example_crossover.m | 54 +++++++++ 6 files changed, 409 insertions(+) create mode 100644 tools/tune/crossover/crossover_build_blob.m create mode 100644 tools/tune/crossover/crossover_coef_quant.m create mode 100644 tools/tune/crossover/crossover_gen_coefs.m create mode 100644 tools/tune/crossover/crossover_generate_config.m create mode 100644 tools/tune/crossover/crossover_plot_freq.m create mode 100644 tools/tune/crossover/example_crossover.m diff --git a/tools/tune/crossover/crossover_build_blob.m b/tools/tune/crossover/crossover_build_blob.m new file mode 100644 index 000000000000..eb6e2879a825 --- /dev/null +++ b/tools/tune/crossover/crossover_build_blob.m @@ -0,0 +1,79 @@ +function blob8 = crossover_build_blob(blob_struct, endian) + +%% Settings +bits_R = 32; %Q2.30 +qy_R = 30; + +if nargin < 2 + endian = 'little' +endif + +%% Shift values for little/big endian +switch lower(endian) + case 'little' + sh = [0 -8 -16 -24]; + case 'big' + sh = [-24 -16 -8 0]; + otherwise + error('Unknown endiannes'); +end + +%% Build Blob +% refer to sof/src/include/user/crossover.h for the config struct. +data_size = 4 * (2 + 4 + 4 + numel(blob_struct.all_coef)); +[abi_bytes, abi_size] = crossover_get_abi(data_size); + +blob_size = data_size + abi_size; +blob8 = uint8(zeros(1, blob_size)); + +% Pack Blob data +% Insert ABI Header +blob8(1:abi_size) = abi_bytes; +j = abi_size + 1; + +% Insert Data +blob8(j:j+3) = word2byte(data_size, sh); j=j+4; +blob8(j:j+3) = word2byte(blob_struct.num_sinks, sh); j=j+4; +blob8(j:j+3) = word2byte(0, sh); j=j+4; % Reserved +blob8(j:j+3) = word2byte(0, sh); j=j+4; % Reserved +blob8(j:j+3) = word2byte(0, sh); j=j+4; % Reserved +blob8(j:j+3) = word2byte(0, sh); j=j+4; % Reserved + +for i=1:4 + blob8(j:j+3) = word2byte(blob_struct.assign_sinks(i), sh); + j=j+4; +end + +for i=1:length(blob_struct.all_coef) + blob8(j:j+3) = word2byte(blob_struct.all_coef(i), sh); + j=j+4; +end + +endfunction + +function bytes = word2byte(word, sh) +bytes = uint8(zeros(1,4)); +bytes(1) = bitand(bitshift(word, sh(1)), 255); +bytes(2) = bitand(bitshift(word, sh(2)), 255); +bytes(3) = bitand(bitshift(word, sh(3)), 255); +bytes(4) = bitand(bitshift(word, sh(4)), 255); +end + +function [bytes, nbytes] = crossover_get_abi(setsize) + +%% Return current SOF ABI header +%% Use sof-ctl to write ABI header into a file +abifn = 'crossover_get_abi.bin'; +cmd = sprintf('sof-ctl -g %d -b -o %s', setsize, abifn); +system(cmd); + +%% Read file and delete it +fh = fopen(abifn, 'r'); +if fh < 0 + error("Failed to get ABI header. Is sof-ctl installed?"); +end +[bytes, nbytes] = fread(fh, inf, 'uint8'); +fclose(fh); +delete(abifn); + +end diff --git a/tools/tune/crossover/crossover_coef_quant.m b/tools/tune/crossover/crossover_coef_quant.m new file mode 100644 index 000000000000..1546845268db --- /dev/null +++ b/tools/tune/crossover/crossover_coef_quant.m @@ -0,0 +1,31 @@ +function crossover_quant = crossover_coef_quant(lowpass, highpass); + +bits_iir = 32; % Q2.30 +qf_iir = 30; + +addpath ./../eq + +if length(lowpass) != length(highpass) + error("length of lowpass and highpass array do not match"); +end + +n = length(lowpass); +crossover_quant.lp_coef = cell(1,n); +crossover_quant.hp_coef = cell(1,n); +for i = 1:n + lp = lowpass(i); + hp = highpass(i); + lp_a = eq_coef_quant(-lp.a(3:-1:2), bits_iir, qf_iir); + lp_b = eq_coef_quant(lp.b(3:-1:1), bits_iir, qf_iir); + hp_a = eq_coef_quant(-hp.a(3:-1:2), bits_iir, qf_iir); + hp_b = eq_coef_quant(hp.b(3:-1:1), bits_iir, qf_iir); + + crossover_quant.lp_coef(i) = [lp_a lp_b 0 16384]; + crossover_quant.hp_coef(i) = [hp_a hp_b 0 16384]; +end + +crossover_quant.lp_coef = cell2mat(crossover_quant.lp_coef); +crossover_quant.hp_coef = cell2mat(crossover_quant.hp_coef); + +rmpath ./../eq +end diff --git a/tools/tune/crossover/crossover_gen_coefs.m b/tools/tune/crossover/crossover_gen_coefs.m new file mode 100644 index 000000000000..10e00bf04d63 --- /dev/null +++ b/tools/tune/crossover/crossover_gen_coefs.m @@ -0,0 +1,114 @@ +function crossover = crossover_gen_coefs(fs, fc_low, fc_mid, fc_high); + +addpath ./../eq/ +switch nargin + case 2, crossover = crossover_generate_2way(fs, fc_low); + case 3, crossover = crossover_generate_3way(fs, fc_low, fc_mid); + case 4, crossover = crossover_generate_4way(fs, fc_low, fc_mid, fc_high); + otherwise, error("Invalid number of arguments"); +end +rmpath ./../eq +end + +function crossover_2way = crossover_generate_2way(fs, fc); + crossover_2way.lp = [lp_iir(fs, fc, 0)]; + crossover_2way.hp = [hp_iir(fs, fc, 0)]; +end + +function crossover_3way = crossover_generate_3way(fs, fc_low, fc_high); + % Duplicate one set of coefficients. The duplicate set will be used to merge back the + % output that is out of phase. + crossover_3way.lp = [lp_iir(fs, fc_low, 0) lp_iir(fs, fc_high, 0) lp_iir(fs, fc_high, 0)]; + crossover_3way.hp = [hp_iir(fs, fc_low, 0) hp_iir(fs, fc_high, 0) hp_iir(fs, fc_high, 0)]; +end + +function crossover_4way = crossover_generate_4way(fs, fc_low, fc_mid, fc_high); + crossover_4way.lp = [lp_iir(fs, fc_low, 0) lp_iir(fs, fc_mid, 0) lp_iir(fs, fc_high, 0)]; + crossover_4way.hp = [hp_iir(fs, fc_low, 0) hp_iir(fs, fc_mid, 0) hp_iir(fs, fc_high, 0)]; +end + +% Generate the a,b coefficients for a second order +% low pass butterworth filter +function lp = lp_iir(fs, fc, gain_db) +[lp.b, lp.a] = low_pass_2nd_resonance(fc, 0, fs); +end + +% Generate the a,b coefficients for a second order +% low pass butterworth filter +function hp = hp_iir(fs, fc, gain_db) +[hp.b, hp.a] = high_pass_2nd_resonance(fc, 0, fs); +end + +function [b, a] = high_pass_2nd_resonance(f, resonance, fs) + cutoff = f/(fs/2); + % Limit cutoff to 0 to 1. + cutoff = max(0.0, min(cutoff, 1.0)); + + if cutoff == 1 || cutoff == 0 + % When cutoff is one, the z-transform is 0. + % When cutoff is zero, we need to be careful because the above + % gives a quadratic divided by the same quadratic, with poles + % and zeros on the unit circle in the same place. When cutoff + % is zero, the z-transform is 1. + + b = [1 - cutoff, 0, 0]; + a = [1, 0, 0]; + return; + endif + + % Compute biquad coefficients for highpass filter + resonance = max(0.0, resonance); % can't go negative + g = 10.0^(0.05 * resonance); + d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + theta = pi * cutoff; + sn = 0.5 * d * sin(theta); + beta = 0.5 * (1 - sn) / (1 + sn); + gamma = (0.5 + beta) * cos(theta); + alpha = 0.25 * (0.5 + beta + gamma); + + b0 = 2 * alpha; + b1 = 2 * -2 * alpha; + b2 = 2 * alpha; + a1 = 2 * -gamma; + a2 = 2 * beta; + + b = [b0, b1, b2]; + a = [1.0, a1, a2]; +end + +function [b, a] = low_pass_2nd_resonance(f, resonance, fs) + cutoff = f/(fs/2); + % Limit cutoff to 0 to 1. + cutoff = max(0.0, min(cutoff, 1.0)); + + if cutoff == 1 || cutoff == 0 + % When cutoff is 1, the z-transform is 1. + % When cutoff is zero, nothing gets through the filter, so set + % coefficients up correctly. + + b = [cutoff, 0, 0]; + a = [1, 0, 0]; + return; + endif + + % Compute biquad coefficients for lowpass filter + resonance = max(0.0, resonance); % can't go negative + g = 10.0^(0.05 * resonance); + d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); + + theta = pi * cutoff; + sn = 0.5 * d * sin(theta); + beta = 0.5 * (1 - sn) / (1 + sn); + gamma = (0.5 + beta) * cos(theta); + alpha = 0.25 * (0.5 + beta - gamma); + + b0 = 2 * alpha; + b1 = 2 * 2 * alpha; + b2 = 2 * alpha; + a1 = 2 * -gamma; + a2 = 2 * beta; + + b = [b0, b1, b2]; + a = [1.0, a1, a2]; +end diff --git a/tools/tune/crossover/crossover_generate_config.m b/tools/tune/crossover/crossover_generate_config.m new file mode 100644 index 000000000000..b6fed758fcf8 --- /dev/null +++ b/tools/tune/crossover/crossover_generate_config.m @@ -0,0 +1,20 @@ +function config = crossover_generate_config(crossover_bqs, num_sinks, assign_sinks); + +config.num_sinks = num_sinks; +config.assign_sinks = assign_sinks; +% Interleave the coefficients for the low and high pass filters +% For 2 way crossover we have 1 pair of LR4s. +% For 3,4 way crossover we have 3 pair of LR4s. +if num_sinks == 2 + n = 1; +else + n = 3; +end +j = 1; +k = 1; +for i = 1:n + config.all_coef(k:k+6) = crossover_bqs.lp_coef(j:j+6); k = k+7; + config.all_coef(k:k+6) = crossover_bqs.hp_coef(j:j+6); k = k+7; + j = j+7; +end +end diff --git a/tools/tune/crossover/crossover_plot_freq.m b/tools/tune/crossover/crossover_plot_freq.m new file mode 100644 index 000000000000..c75e18536e8b --- /dev/null +++ b/tools/tune/crossover/crossover_plot_freq.m @@ -0,0 +1,111 @@ +function crossover_plot_freq(lp, hp, fs, num_sinks); +% Plot the transfer function of each sink. We need to reconstruct a filter +% that represents the path the samples go through for each sinks. +% Example 4-way crossover: +% +% o---- LR4 LO-PASS --> y1(n) +% | +% o--- LR4 LO-PASS --o +% | | +% | o--- LR4 HI-PASS --> y2(n) +% x(n) --- o +% | o--- LR4 LO-PASS --> y3(n) +% | | +% o--- LR4 HI-PASS --o +% | +% o--- LR4 HI-PASS --> y4(n) +% +% Then to plot the transferfn for y1 we would create a filter such as: +% x(n) ---> LR4 LO-PASS --> LR4 LO-PASS --> y1(n) + +f = linspace(1, fs/2, 500); + +if num_sinks == 2 + h1 = cascade_bqs_fr(f, fs, lp(1), lp(1)); + h2 = cascade_bqs_fr(f, fs, hp(1), hp(1)); + + subplot(2 ,1, 2) + freqz_plot(f, h1) + subplot(2, 1, 1) + plot_mag_resp(f, h1, h2) +end + +if num_sinks == 3 + % First LR4 Low Pass Filters + h1 = cascade_bqs_fr(f, fs, lp(1), lp(1)); + % Second LR4 Filters + tmp = cascade_bqs_fr(f, fs, lp(2), lp(2)); + tmp2 = cascade_bqs_fr(f, fs, hp(2), hp(2)); + % Merge the second LR4 Filters + tmp = tmp + tmp2; + % Cascade the First LR4 and the result of the previous merge + h1 = h1.*tmp; + + h2 = cascade_bqs_fr(f, fs, hp(1), hp(1), lp(3), lp(3)); + h3 = cascade_bqs_fr(f, fs, hp(1), hp(1), hp(3), hp(3)); + + subplot(2, 1, 2) + plot_phase_resp(f, h1, h2, h3) + subplot(2, 1, 1) + plot_mag_resp(f, h1, h2, h3) +end + +if num_sinks == 4 + h1 = cascade_bqs_fr(f, fs, lp(2), lp(2), lp(1), lp(1)); + h2 = cascade_bqs_fr(f, fs, lp(2), lp(2), hp(1), hp(1)); + h3 = cascade_bqs_fr(f, fs, hp(2), hp(2), lp(3), lp(3)); + h4 = cascade_bqs_fr(f, fs, hp(2), hp(2), hp(3), hp(3)); + + subplot(2, 1, 2) + plot_phase_resp(f, h1, h2, h3, h4) + subplot(2, 1, 1) + plot_mag_resp(f, h1, h2, h3, h4) +end +end + +function [h12, w] = cascade_bqs_fr(f, fs, varargin); +bq1 = varargin{1}; +bq2 = varargin{2}; +[h1, w] = freqz(bq1.b, bq1.a, f, fs); +[h2, w] = freqz(bq2.b, bq2.a, f, fs); +h12 = h1.*h2; +for i=3:length(varargin) + bq = varargin{i}; + [h1, w] = freqz(bq.b, bq.a, f, fs); + h12 = h12.*h1; +end +end + +function plot_phase_resp(f,varargin) +n = length(varargin); +labels = cellstr(n); +hold on +grid on +for i=1:n + h = varargin{i}; + semilogx(f, unwrap(arg(h)) * 180 / pi) + labels(i) = sprintf("out%d", i); +end +legend(labels, 'Location', 'NorthEast') +xlabel('Frequency (Hz)'); +ylabel('Phase Shift (degrees)'); +tstr = "Crossover Filter Phase Response"; +title(tstr); +end + +function plot_mag_resp(f,varargin) +n = length(varargin); +labels = cellstr(n); +hold on +grid on +for i=1:n + h = varargin{i}; + semilogx(f,20*log10(h)) + labels(i) = sprintf("out%d", i); +end +legend(labels, 'Location', 'NorthEast') +xlabel('Frequency (Hz)'); +ylabel('Magnitude (dB)'); +tstr = "Crossover Filter Magnitude Response"; +title(tstr); +end diff --git a/tools/tune/crossover/example_crossover.m b/tools/tune/crossover/example_crossover.m new file mode 100644 index 000000000000..3166f4a4f022 --- /dev/null +++ b/tools/tune/crossover/example_crossover.m @@ -0,0 +1,54 @@ +function example_crossover(); + +% Set the parameters here +tplg_fn = "../../topology/m4/crossover_coef_default.m4" % Control Bytes File +% Use those files with sof-ctl to update the component's configuration +blob_fn = "../../ctl/crossover_coef.blob" % Blob binary file +alsa_fn = "../../ctl/crossover_coef.txt" % ALSA CSV format file + +endian = "little"; + +% Sampling Frequency and Frequency cut-offs for crossover +fs = 48e3; +fc_low = 200; +fc_med = 1000; +fc_high = 3000; + +% 4 way crossover +num_sinks = 4; +% This array is an example on how to assign a buffer from pipeline 1 to output 0, +% buffer from pipeline 2 to output 1, etc... +% Refer to sof/src/inlude/user/crossover.h for more information on assigning +% buffers to outputs. +assign_sinks = zeros(1, 4); +assign_sinks(1) = 1; % sink[0] +assign_sinks(2) = 2; % sink[1] +assign_sinks(3) = 3; % sink[2] +assign_sinks(4) = 4; % sink[3] + +% Generate zeros, poles and gain for crossover with the given frequencies +%crossover = crossover_gen_coefs(fs, fc_low); % 2 way crossover +% crossover = crossover_gen_coefs(fs, fc_low, fc_med); % 3 way crossover +crossover = crossover_gen_coefs(fs, fc_low, fc_med, fc_high); % 4 way crossover + +% Convert the [a,b] coefficients to values usable with SOF +crossover_bqs = crossover_coef_quant(crossover.lp, crossover.hp); + +% Convert coefficients to sof_crossover_config struct +config = crossover_generate_config(crossover_bqs, num_sinks, assign_sinks); + +% Convert struct to binary blob +blob8 = crossover_build_blob(config, endian); + +% Generate output files +addpath ./../common + +tplg_write(tplg_fn, blob8, "CROSSOVER"); +blob_write(blob_fn, blob8); +alsactl_write(alsa_fn, blob8); + +% Plot Magnitude and Phase Response of each sink +crossover_plot_freq(crossover.lp, crossover.hp, fs, num_sinks); +rmpath ./../common + +endfunction From 0347b6bc6c79e6d23128b87d0aba98a42a108001 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Fri, 19 Jun 2020 15:00:04 +0800 Subject: [PATCH 05/12] Merged commits from "Testbench support for Crossover" This commit includes the whole change from Seb's PR: Testbench support for Crossover https://github.com/sebcarlucci/sof/pull/2/commits Signed-off-by: Pin-chih Lin --- src/audio/CMakeLists.txt | 4 +- src/audio/crossover/crossover.c | 16 +- src/audio/crossover/crossover_generic.c | 52 +++-- tools/test/topology/tplg-build.sh | 68 ++++--- .../testbench/include/testbench/common_test.h | 2 +- tools/testbench/testbench.c | 15 +- tools/topology/sof-glk-da7219.m4 | 190 +++++++----------- .../include/tplg_parser/topology.h | 1 + tools/tplg_parser/tplg_parser.c | 3 +- 9 files changed, 168 insertions(+), 183 deletions(-) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 32dca24f0815..2e2e9442143b 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -110,7 +110,8 @@ check_optimization(fma -mfma -DOPS_FMA) check_optimization(hifi2ep -mhifi2ep -DOPS_HIFI2EP) check_optimization(hifi3 -mhifi3 -DOPS_HIFI3) -set(sof_audio_modules volume src asrc eq-fir eq-iir dcblock) +#set(sof_audio_modules volume src asrc eq-fir eq-iir dcblock) +set(sof_audio_modules volume src asrc eq-fir eq-iir dcblock crossover) # sources for each module set(volume_sources volume/volume.c volume/volume_generic.c) @@ -119,6 +120,7 @@ set(asrc_sources asrc/asrc.c asrc/asrc_farrow.c asrc/asrc_farrow_generic.c) set(eq-fir_sources eq_fir/eq_fir.c eq_fir/fir.c) set(eq-iir_sources eq_iir/eq_iir.c eq_iir/iir.c eq_iir/iir_generic.c) set(dcblock_sources dcblock/dcblock.c dcblock/dcblock_generic.c) +set(crossover_sources crossover/crossover.c crossover/crossover_generic.c) foreach(audio_module ${sof_audio_modules}) # first compile with no optimizations diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 217b7e368b44..2d89c14c396f 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -643,9 +643,12 @@ static int crossover_copy(struct comp_dev *dev) * to be assigned: sink[i] can be NULL, 0 <= i <= config->num_sinks */ num_assigned_sinks = crossover_assign_sinks(dev, cd->config, sinks); - if (cd->config && num_assigned_sinks != cd->config->num_sinks) - comp_dbg(dev, "crossover_copy(), number of assigned sinks (%i) does not match number of sinks in config (%i).", - num_assigned_sinks, cd->config->num_sinks); + /* For testbench use, we do not expect the number of actual sink buffers to match + * the config + */ + //if (cd->config && num_assigned_sinks != cd->config->num_sinks) + // comp_dbg(dev, "crossover_copy(), number of assigned sinks (%i) does not match number of sinks in config (%i).", + // num_assigned_sinks, cd->config->num_sinks); /* If no config is set then assign the number of sinks to the number * of sinks that were assigned @@ -757,10 +760,15 @@ static int crossover_prepare(struct comp_dev *dev) source->stream.channels); /* Initialize Crossover */ + /* When running crossover on testbench, the config will always be + * invalid since we can only have one sink buffer. + * The number of assigned sinks will not match the number of actual sink buffers. + * Therefore, do not take any action. + */ if (cd->config && crossover_validate_config(dev, cd->config) < 0) { /* If config is invalid then delete it.*/ comp_err(dev, "crossover_prepare(), invalid binary config format"); - crossover_free_config(&cd->config); + //crossover_free_config(&cd->config); } if (cd->config) { diff --git a/src/audio/crossover/crossover_generic.c b/src/audio/crossover/crossover_generic.c index f25280762057..b5a4fbf1722d 100644 --- a/src/audio/crossover/crossover_generic.c +++ b/src/audio/crossover/crossover_generic.c @@ -145,29 +145,47 @@ static void crossover_s16_default(const struct comp_dev *dev, const struct audio_stream *source_stream = &source->stream; struct audio_stream *sink_stream; int16_t *x, *y; - int ch, i, j; + //int ch, i, j; + int i, j; int idx = 0; int nch = source_stream->channels; int32_t out[num_sinks]; - for (ch = 0; ch < nch; ch++) { - idx = ch; - state = &cd->state[ch]; - for (i = 0; i < frames; i++) { - x = audio_stream_read_frag_s16(source_stream, idx); - cd->crossover_split(*x << 16, out, state); - - for (j = 0; j < num_sinks; j++) { - if (!sinks[j]) - continue; - sink_stream = &sinks[j]->stream; - y = audio_stream_read_frag_s16(sink_stream, - idx); - *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); - } + //for (ch = 0; ch < nch; ch++) { + // idx = ch; + // state = &cd->state[ch]; + // for (i = 0; i < frames; i++) { + // x = audio_stream_read_frag_s16(source_stream, idx); + // cd->crossover_split(*x << 16, out, state); + + // for (j = 0; j < num_sinks; j++) { + // if (!sinks[j]) + // continue; + // sink_stream = &sinks[j]->stream; + // y = audio_stream_read_frag_s16(sink_stream, + // idx); + // *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); + // } + + // idx += nch; + // } + //} + /* For testbench, reroute all the outputs to the channels of one buffer */ + state = &cd->state[0]; + for (i = 0; i < frames; i++) { + x = audio_stream_read_frag_s16(source_stream, idx); + cd->crossover_split(*x << 16, out, state); - idx += nch; + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + sink_stream = &sinks[0]->stream; + y = audio_stream_read_frag_s16(sink_stream, + idx + j); + *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); } + + idx += nch; } } #endif // CONFIG_FORMAT_S16LE diff --git a/tools/test/topology/tplg-build.sh b/tools/test/topology/tplg-build.sh index a5ec7bf76bb4..5003f63ce250 100755 --- a/tools/test/topology/tplg-build.sh +++ b/tools/test/topology/tplg-build.sh @@ -136,25 +136,25 @@ function simple_test { echo "Preparing topology build input..." # Pre-process the simple tests -simple_test nocodec passthrough "NoCodec-2" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec passthrough "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s16le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] - -simple_test codec passthrough "SSP2-Codec" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test codec passthrough "SSP2-Codec" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test codec volume "SSP2-Codec" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test codec volume "SSP2-Codec" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test codec volume "SSP2-Codec" s24le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] -simple_test codec volume "SSP2-Codec" s16le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec passthrough "NoCodec-2" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec passthrough "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s16le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +# +#simple_test codec passthrough "SSP2-Codec" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test codec passthrough "SSP2-Codec" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test codec volume "SSP2-Codec" s16le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test codec volume "SSP2-Codec" s24le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test codec volume "SSP2-Codec" s24le SSP 2 s16le 20 16 1920000 19200000 I2S 0 SIMPLE_TESTS[@] +#simple_test codec volume "SSP2-Codec" s16le SSP 2 s24le 25 24 2400000 19200000 I2S 0 SIMPLE_TESTS[@] # for APL -APL_PROTOCOL_TESTS=(I2S LEFT_J DSP_A DSP_B) -APL_SSP_TESTS=(0 1 2 3 4 5) -APL_MODE_TESTS=(volume) -APL_FORMAT_TESTS=(s16le s24le s32le) -MCLK_IDS=(0 1) +#APL_PROTOCOL_TESTS=(I2S LEFT_J DSP_A DSP_B) +#APL_SSP_TESTS=(0 1 2 3 4 5) +#APL_MODE_TESTS=(volume) +#APL_FORMAT_TESTS=(s16le s24le s32le) +#MCLK_IDS=(0 1) for protocol in ${APL_PROTOCOL_TESTS[@]} do @@ -214,8 +214,10 @@ done # for processing algorithms -ALG_MODE_TESTS=(asrc eq-fir eq-iir src dcblock) -ALG_SIMPLE_TESTS=(test-capture test-playback) +#ALG_MODE_TESTS=(asrc eq-fir eq-iir src dcblock) +ALG_MODE_TESTS=(crossover dcblock) +#ALG_SIMPLE_TESTS=(test-capture test-playback) +ALG_SIMPLE_TESTS=(test-playback) ALG_PROTOCOL_TESTS=(I2S) ALG_SSP_TESTS=(5) ALG_MCLK_IDS=(0) @@ -237,20 +239,20 @@ do done # for CNL -simple_test nocodec passthrough "NoCodec-0" s16le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec passthrough "NoCodec-2" s24le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-0" s16le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-0" s16le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-0" s24le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-0" s24le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] - -simple_test nocodec passthrough "NoCodec-2" s16le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec passthrough "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s16le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s16le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec volume "NoCodec-2" s24le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] -simple_test nocodec src "NoCodec-4" s24le SSP 4 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec passthrough "NoCodec-0" s16le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec passthrough "NoCodec-2" s24le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-0" s16le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-0" s16le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-0" s24le SSP 0 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-0" s24le SSP 0 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +# +#simple_test nocodec passthrough "NoCodec-2" s16le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec passthrough "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s16le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s16le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s24le SSP 2 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec volume "NoCodec-2" s24le SSP 2 s16le 25 16 2400000 24000000 I2S 0 SIMPLE_TESTS[@] +#simple_test nocodec src "NoCodec-4" s24le SSP 4 s24le 25 24 2400000 24000000 I2S 0 SIMPLE_TESTS[@] # algorithms tests diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 409e3a6a5606..9ac2f435c2e5 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -18,7 +18,7 @@ #define MAX_LIB_NAME_LEN 256 /* number of widgets types supported in testbench */ -#define NUM_WIDGETS_SUPPORTED 7 +#define NUM_WIDGETS_SUPPORTED 6 struct testbench_prm { char *tplg_file; /* topology file to use */ diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index f5cb8391482d..9375c872cc54 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -19,12 +19,13 @@ /* shared library look up table */ struct shared_lib_table lib_table[NUM_WIDGETS_SUPPORTED] = { {"file", "", SOF_COMP_HOST, 0, NULL}, /* File must be first */ - {"volume", "libsof_volume.so", SOF_COMP_VOLUME, 0, NULL}, - {"src", "libsof_src.so", SOF_COMP_SRC, 0, NULL}, + //{"volume", "libsof_volume.so", SOF_COMP_VOLUME, 0, NULL}, + //{"src", "libsof_src.so", SOF_COMP_SRC, 0, NULL}, {"asrc", "libsof_asrc.so", SOF_COMP_ASRC, 0, NULL}, {"eq-fir", "libsof_eq-fir.so", SOF_COMP_EQ_FIR, 0, NULL}, {"eq-iir", "libsof_eq-iir.so", SOF_COMP_EQ_IIR, 0, NULL}, - {"dcblock", "libsof_dcblock.so", SOF_COMP_DCBLOCK, 0, NULL} + {"dcblock", "libsof_dcblock.so", SOF_COMP_DCBLOCK, 0, NULL}, + {"crossover", "libsof_crossover.so", SOF_COMP_CROSSOVER, 0, NULL} }; /* main firmware context */ @@ -130,7 +131,7 @@ static void parse_input_args(int argc, char **argv, struct testbench_prm *tp) int option = 0; int ret = 0; - while ((option = getopt(argc, argv, "hdi:o:t:b:a:r:R:")) != -1) { + while ((option = getopt(argc, argv, "hdi:o:t:b:a:r:R:c:")) != -1) { switch (option) { /* input sample file */ case 'i': @@ -168,6 +169,10 @@ static void parse_input_args(int argc, char **argv, struct testbench_prm *tp) tp->fs_out = atoi(optarg); break; + case 'c': + tp->channels = atoi(optarg); + break; + /* enable debug prints */ case 'd': debug = 1; @@ -275,7 +280,7 @@ int main(int argc, char **argv) n_in = frcd->fs.n; n_out = fwcd->fs.n; t_exec = (double)(toc - tic) / CLOCKS_PER_SEC; - c_realtime = (double)n_out / TESTBENCH_NCH / tp.fs_out / t_exec; + c_realtime = (double)n_out / tp.channels / tp.fs_out / t_exec; /* free all components/buffers in pipeline */ free_comps(); diff --git a/tools/topology/sof-glk-da7219.m4 b/tools/topology/sof-glk-da7219.m4 index 14f8f9cfbded..1ce6bf407478 100644 --- a/tools/topology/sof-glk-da7219.m4 +++ b/tools/topology/sof-glk-da7219.m4 @@ -23,11 +23,9 @@ include(`platform/intel/dmic.m4') # Define the pipelines # # PCM0 ----> volume (pipe 1) -----> SSP1 (speaker - maxim98357a, BE link 0) -# PCM1 <---> volume (pipe 2,3) <----> SSP2 (headset - da7219, BE link 1) -`# PCM99 <---- 'DMICPROC` <---- DMIC0 (dmic capture, BE link 2)' -# PCM5 ----> volume (pipe 5) -----> iDisp1 (HDMI/DP playback, BE link 3) -# PCM6 ----> Volume (pipe 6) -----> iDisp2 (HDMI/DP playback, BE link 4) -# PCM7 ----> volume (pipe 7) -----> iDisp3 (HDMI/DP playback, BE link 5) +# PCM5 ----> Crossover +# PCM6 ----> Crossover +# PCM7 ----> Crossover # dnl PIPELINE_PCM_ADD(pipeline, @@ -43,55 +41,6 @@ PIPELINE_PCM_ADD(sof/pipe-volume-playback.m4, 1000, 0, 0, 48000, 48000, 48000) -# Low Latency playback pipeline 2 on PCM 1 using max 2 channels of s32le. -# 1000us deadline on core 0 with priority 0 -PIPELINE_PCM_ADD(sof/pipe-volume-playback.m4, - 2, 1, 2, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - -# Low Latency capture pipeline 3 on PCM 1 using max 2 channels of s32le. -# 1000us deadline on core 0 with priority 0 -PIPELINE_PCM_ADD(sof/pipe-volume-capture.m4, - 3, 1, 2, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - -# if DMICPROC is not defined, default pipepline is volume -ifdef(`DMICPROC', , `define(DMICPROC, volume)') -define(DEF_PIPE_DMIC_CAPTURE, sof/pipe-DMICPROC-capture.m4) - -# Low Latency capture pipeline 4 on PCM 99 using max 4 channels of s32le. -# 1000us deadline on core 0 with priority 0 -PIPELINE_PCM_ADD(DEF_PIPE_DMIC_CAPTURE, - 4, 99, 4, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - -# Low Latency playback pipeline 5 on PCM 5 using max 2 channels of s32le. -# 1000us deadline on core 0 with priority 0 -# PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, -PIPELINE_PCM_ADD(sof/pipe-volume-playback.m4, - 5, 5, 2, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - -# Low Latency playback pipeline 6 on PCM 6 using max 2 channels of s32le. -# 1000us deadline on core 0 with priority 0 -# PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, -PIPELINE_PCM_ADD(sof/pipe-volume-playback.m4, - 6, 6, 2, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - -# Low Latency playback pipeline 7 on PCM 7 using max 2 channels of s32le. -# 1000us deadline on core 0 with priority 0 -# PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, -PIPELINE_PCM_ADD(sof/pipe-volume-playback.m4, - 7, 7, 2, s32le, - 1000, 0, 0, - 48000, 48000, 48000) - # # DAIs configuration # @@ -108,54 +57,75 @@ DAI_ADD(sof/pipe-dai-playback.m4, PIPELINE_SOURCE_1, 2, s16le, 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) -# playback DAI is SSP2 using 2 periods -# Buffers use s16le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-playback.m4, - 2, SSP, 2, SSP2-Codec, - PIPELINE_SOURCE_2, 2, s16le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) - -# capture DAI is SSP2 using 2 periods -# Buffers use s16le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-capture.m4, - 3, SSP, 2, SSP2-Codec, - PIPELINE_SINK_3, 2, s16le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) - -# capture DAI is DMIC0 using 2 periods -# Buffers use s32le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-capture.m4, - 4, DMIC, 0, dmic01, - PIPELINE_SINK_4, 2, s32le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) - -# playback DAI is iDisp1 using 2 periods -# Buffers use s32le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-playback.m4, - 5, HDA, 3, iDisp1, - PIPELINE_SOURCE_5, 2, s32le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +# currently this dai is here as "virtual" capture backend +W_DAI_IN(SSP, 10, SSP1-Codec, s32le, 3, 0) -# playback DAI is iDisp2 using 2 periods -# Buffers use s32le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-playback.m4, - 6, HDA, 4, iDisp2, - PIPELINE_SOURCE_6, 2, s32le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) - -# playback DAI is iDisp3 using 2 periods -# Buffers use s32le format, 1000us deadline on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-playback.m4, - 7, HDA, 5, iDisp3, - PIPELINE_SOURCE_7, 2, s32le, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +# Capture pipeline 10 on PCM 5 using max 2 channels of s32le. +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture-sched.m4, + 10, 5, 2, s32le, + 1000, 0, 0, + 48000, 48000, 48000, + SCHEDULE_TIME_DOMAIN_TIMER) + +# Capture pipeline 11 on PCM 6 using max 2 channels of s32le. +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture-sched.m4, + 11, 6, 2, s32le, + 1000, 0, 0, + 48000, 48000, 48000, + SCHEDULE_TIME_DOMAIN_TIMER) + +# Capture pipeline 12 on PCM 7 using max 2 channels of s32le. +PIPELINE_PCM_ADD(sof/pipe-passthrough-capture-sched.m4, + 12, 7, 2, s32le, + 1000, 0, 0, + 48000, 48000, 48000, + SCHEDULE_TIME_DOMAIN_TIMER) + +# Connect demux to capture +SectionGraph."PIPE_CAP" { + index "0" + + lines [ + # demux to capture + dapm(PIPELINE_SINK_10, PIPELINE_CROSSOVER_1) + # dapm(PIPELINE_SINK_11, PIPELINE_CROSSOVER_1) + # dapm(PIPELINE_SINK_12, PIPELINE_CROSSOVER_1) + ] +} + +# Connect virtual capture to dai +SectionGraph."PIPE_CAP_VIRT" { + index "10" + + lines [ + dapm(ECHO REF 10, `SSP1.IN') + ] +} + +# Connect virtual capture to dai +SectionGraph."PIPE_CAP_VIRT" { + index "11" + + lines [ + # mux to capture + dapm(ECHO REF 11, `SSP1.IN') + ] +} + +# Connect virtual capture to dai +SectionGraph."PIPE_CAP_VIRT" { + index "12" + + lines [ + # mux to capture + dapm(ECHO REF 12, `SSP1.IN') + ] +} PCM_PLAYBACK_ADD(Speakers, 0, PIPELINE_PCM_1) -PCM_DUPLEX_ADD(Headset, 1, PIPELINE_PCM_2, PIPELINE_PCM_3) -PCM_CAPTURE_ADD(DMIC, 99, PIPELINE_PCM_4) -PCM_PLAYBACK_ADD(HDMI1, 5, PIPELINE_PCM_5) -PCM_PLAYBACK_ADD(HDMI2, 6, PIPELINE_PCM_6) -PCM_PLAYBACK_ADD(HDMI3, 7, PIPELINE_PCM_7) +PCM_CAPTURE_ADD(EchoRef, 10, PIPELINE_PCM_10) +PCM_CAPTURE_ADD(EchoRef2, 11, PIPELINE_PCM_11) +PCM_CAPTURE_ADD(EchoRef3, 12, PIPELINE_PCM_12) # # BE configurations - overrides config in ACPI if present @@ -169,28 +139,6 @@ DAI_CONFIG(SSP, 1, 0, SSP1-Codec, SSP_TDM(2, 16, 3, 3), SSP_CONFIG_DATA(SSP, 1, 16, 1))) -#SSP 2 (ID: 1) with 19.2 MHz mclk with MCLK_ID 1, 1.92 MHz bclk -DAI_CONFIG(SSP, 2, 1, SSP2-Codec, - SSP_CONFIG(I2S, SSP_CLOCK(mclk, 19200000, codec_mclk_in), - SSP_CLOCK(bclk, 1920000, codec_slave), - SSP_CLOCK(fsync, 48000, codec_slave), - SSP_TDM(2, 20, 3, 3), - SSP_CONFIG_DATA(SSP, 2, 16, 1))) - -# dmic01 (id: 2) -DAI_CONFIG(DMIC, 0, 2, dmic01, - DMIC_CONFIG(1, 500000, 4800000, 40, 60, 48000, - DMIC_WORD_LENGTH(s32le), 400, DMIC, 0, - PDM_CONFIG(DMIC, 0, FOUR_CH_PDM0_PDM1))) - -# 3 HDMI/DP outputs (ID: 3,4,5) -DAI_CONFIG(HDA, 3, 3, iDisp1, - HDA_CONFIG(HDA_CONFIG_DATA(HDA, 3, 48000, 2))) -DAI_CONFIG(HDA, 4, 4, iDisp2, - HDA_CONFIG(HDA_CONFIG_DATA(HDA, 4, 48000, 2))) -DAI_CONFIG(HDA, 5, 5, iDisp3, - HDA_CONFIG(HDA_CONFIG_DATA(HDA, 5, 48000, 2))) - ## remove warnings with SST hard-coded routes VIRTUAL_WIDGET(ssp1 Tx, out_drv, 0) diff --git a/tools/tplg_parser/include/tplg_parser/topology.h b/tools/tplg_parser/include/tplg_parser/topology.h index d98acdb71496..6ca7b024ddc0 100644 --- a/tools/tplg_parser/include/tplg_parser/topology.h +++ b/tools/tplg_parser/include/tplg_parser/topology.h @@ -53,6 +53,7 @@ enum sof_ipc_process_type { SOF_PROCESS_MUX, SOF_PROCESS_DEMUX, SOF_PROCESS_DCBLOCK, + SOF_PROCESS_CROSSOVER, }; struct sof_topology_token { diff --git a/tools/tplg_parser/tplg_parser.c b/tools/tplg_parser/tplg_parser.c index b0a85199bd80..54818fd7eb72 100644 --- a/tools/tplg_parser/tplg_parser.c +++ b/tools/tplg_parser/tplg_parser.c @@ -33,7 +33,8 @@ static const struct sof_process_types sof_process[] = { {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX}, {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX}, - {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK} + {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK}, + {"CROSSOVER", SOF_PROCESS_CROSSOVER, SOF_COMP_CROSSOVER} }; static enum sof_ipc_process_type find_process(const char *name) From 79360bbcc7c853508bed26b06853c00586914247 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Thu, 2 Jul 2020 15:55:13 +0800 Subject: [PATCH 06/12] Fix Compile Error Signed-off-by: Pin-chih Lin --- src/include/kernel/header.h | 2 +- src/include/user/eq.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/include/kernel/header.h b/src/include/kernel/header.h index 1fa31b2d994e..134fa322a178 100644 --- a/src/include/kernel/header.h +++ b/src/include/kernel/header.h @@ -34,6 +34,6 @@ struct sof_abi_hdr { uint32_t abi; /**< SOF ABI version */ uint32_t reserved[4]; /**< reserved for future use */ uint32_t data[0]; /**< Component data - opaque to core */ -} __attribute__((packed)); +} __attribute__((packed, aligned(4))); #endif /* __KERNEL_HEADER_H__ */ diff --git a/src/include/user/eq.h b/src/include/user/eq.h index 57c45fcc1730..6a029e0b2bcc 100644 --- a/src/include/user/eq.h +++ b/src/include/user/eq.h @@ -151,7 +151,7 @@ struct sof_eq_iir_biquad_df2t { int32_t b0; /* Q2.30 */ int32_t output_shift; /* Number of right shifts */ int32_t output_gain; /* Q2.14 */ -} __attribute__((packed)); +} __attribute__((packed, aligned(4))); /* A full 22th order equalizer with 11 biquads cover octave bands 1-11 in * in the 0 - 20 kHz bandwidth. From 5d09dce0f26597ea67202b3ce7c911db4a212a17 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Thu, 2 Jul 2020 15:40:39 +0800 Subject: [PATCH 07/12] WIP: 2-way crossover test topology Signed-off-by: Pin-chih Lin --- tools/test/topology/test-multi-playback.m4 | 114 +++++++++++++++++++++ tools/test/topology/tplg-build.sh | 13 ++- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tools/test/topology/test-multi-playback.m4 diff --git a/tools/test/topology/test-multi-playback.m4 b/tools/test/topology/test-multi-playback.m4 new file mode 100644 index 000000000000..7db5b8bc111e --- /dev/null +++ b/tools/test/topology/test-multi-playback.m4 @@ -0,0 +1,114 @@ +# +# Topology for pass through pipeline +# + +# Include topology builder +include(`pipeline.m4') +include(`dai.m4') +include(`ssp.m4') +include(`utils.m4') + +# Include TLV library +include(`common/tlv.m4') + +# Include Token library +include(`sof/tokens.m4') + +# Include Apollolake DSP configuration +include(`platform/intel/bxt.m4') + +DEBUG_START + +# +# Machine Specific Config - !! MUST BE SET TO MATCH TEST MACHINE DRIVER !! +# +# TEST_PIPE_NAME - Pipe name +# TEST_DAI_LINK_NAME - BE DAI link name e.g. "NoCodec" +# TEST_DAI_PORT - SSP port number e.g. 2 +# TEST_DAI_FORMAT - SSP data format e.g s16le +# TEST_PIPE_FORMAT - Pipeline format e.g. s16le +# TEST_SSP_MCLK - SSP MCLK in Hz +# TEST_SSP_BCLK - SSP BCLK in Hz +# TEST_SSP_PHY_BITS - SSP physical slot size +# TEST_SSP_DATA_BITS - SSP data slot size +# TEST_SSP_MODE - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B +# + +# Apply a non-trivial filter blob IIR and FIR tests. TODO: Note that the +# PIPELINE_FILTERx notation will be updated in future for better flexibility. +define(PIPELINE_FILTER1, ifelse(TEST_PIPE_NAME, `eq-iir', `eq_iir_coef_loudness.m4')) +define(PIPELINE_FILTER2, ifelse(TEST_PIPE_NAME, `eq-fir', `eq_fir_coef_loudness.m4')) + +# +# Define the pipeline +# +# PCM0P --> TEST_PIPE_NAME --> SSP(TEST_DAI_PORT) +# + +# Playback pipeline 1 on PCM 0 using max 2 channels of s32le. +# Set 1000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-TEST_PIPE_NAME-playback.m4, + 1, 0, 2, s32le, + 1000, 0, 0, + 8000, 192000, 48000) + +# Playback pipeline 2 on PCM 1 using max 2 channels of s32le. +# Set 1000us deadline on core 0 with priority 1 +PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, + 2, 1, 2, s32le, + 1000, 1, 0, + 8000, 192000, 48000) + +# DAI configuration +# +# SSP port TEST_DAI_PORT is our only pipeline DAI +# + +# playback DAI is SSP TEST_DAI_PORT using 2 periods +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 +DAI_ADD(sof/pipe-dai-playback.m4, + 1, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, + PIPELINE_SOURCE_1, 2, TEST_DAI_FORMAT, + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +# playback DAI is SSP TEST_DAI_PORT using 2 periods +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 1 +DAI_ADD(sof/pipe-dai-playback.m4, + 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, + PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, + 1000, 1, 0, SCHEDULE_TIME_DOMAIN_TIMER) + +# connect pipelines together +SectionGraph."pipe-sof-second-pipe" { + index "0" + + lines [ + # keyword detect + dapm(PIPELINE_CROSSOVER_1, PIPELINE_PCM_2) + ] +} + +# PCM Passthrough +PCM_PLAYBACK_ADD(Passthrough, 0, PIPELINE_PCM_1) + +#PCM_PLAYBACK_ADD(Passthrough, 1, PIPELINE_PCM_2) + +# +# BE configurations - overrides config in ACPI if present +# +# Clocks masters wrt codec +# +# TEST_SSP_DATA_BITS bit I2S +# using TEST_SSP_PHY_BITS bit sample container on SSP TEST_DAI_PORT +# +DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI_PORT, 0, TEST_DAI_LINK_NAME, + SSP_CONFIG(TEST_SSP_MODE, + SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), + SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), + SSP_CLOCK(fsync, 48000, codec_slave), + SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), + SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI_PORT, + TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) + + +DEBUG_END diff --git a/tools/test/topology/tplg-build.sh b/tools/test/topology/tplg-build.sh index 5003f63ce250..5e737a808bc5 100755 --- a/tools/test/topology/tplg-build.sh +++ b/tools/test/topology/tplg-build.sh @@ -215,9 +215,11 @@ done # for processing algorithms #ALG_MODE_TESTS=(asrc eq-fir eq-iir src dcblock) -ALG_MODE_TESTS=(crossover dcblock) +ALG_MODE_TESTS=(dcblock) +ALG_MODE_MULTI_TESTS=(crossover) #ALG_SIMPLE_TESTS=(test-capture test-playback) ALG_SIMPLE_TESTS=(test-playback) +ALG_MULTI_TESTS=(test-multi-playback) ALG_PROTOCOL_TESTS=(I2S) ALG_SSP_TESTS=(5) ALG_MCLK_IDS=(0) @@ -235,6 +237,15 @@ do simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id ALG_SIMPLE_TESTS[@] done done + for mode in ${ALG_MODE_MULTI_TESTS[@]} + do + for mclk_id in ${ALG_MCLK_IDS[@]} + do + simple_test codec $mode "SSP${ssp}-Codec" s16le SSP $ssp s16le 16 16 1536000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s24le SSP $ssp s24le 32 24 3072000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] + done + done done done From b88376ab4e908309cb0fbc3caba9f4c30d621a8d Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Fri, 3 Jul 2020 16:53:02 +0800 Subject: [PATCH 08/12] WIP: testbench supports multiple output files user input as "-o output_file1,output_file2,..." to assign multiple output files. Signed-off-by: Pin-chih Lin --- .../testbench/include/testbench/common_test.h | 5 +- tools/testbench/testbench.c | 59 ++++++++++++++++--- tools/testbench/topology.c | 13 +++- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index 9ac2f435c2e5..fa875c405985 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -17,13 +17,16 @@ #define DEBUG_MSG_LEN 256 #define MAX_LIB_NAME_LEN 256 +#define MAX_OUTPUT_FILE_NUM 4 + /* number of widgets types supported in testbench */ #define NUM_WIDGETS_SUPPORTED 6 struct testbench_prm { char *tplg_file; /* topology file to use */ char *input_file; /* input file name */ - char *output_file; /* output file name */ + char *output_file[MAX_OUTPUT_FILE_NUM]; /* output file names */ + int output_file_num; /* number of output files */ char *bits_in; /* input bit format */ /* * input and output sample rate parameters diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 9375c872cc54..919d998fca22 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -39,6 +39,39 @@ struct sof *sof_get() return &sof; } +/* + * Parse output filenames from user input + * This function takes in the output filenames as an input in the format: + * "output_file1,output_file2,..." + * The max supported output filename number is 4, min is 1. + */ +static int parse_output_files(char *outputs, struct testbench_prm *tp) +{ + char *output_token = NULL; + char *token = strtok_r(outputs, ",", &output_token); + int index; + + for (index = 0; index < MAX_OUTPUT_FILE_NUM && token; index++) { + /* get output file name with current index */ + tp->output_file[index] = strdup(token); + + /* next output */ + token = strtok_r(NULL, ",", &output_token); + } + + if (index == MAX_OUTPUT_FILE_NUM && token) { + fprintf(stderr, "error: max output file number is %d\n", + MAX_OUTPUT_FILE_NUM); + for (index = 0; index < MAX_OUTPUT_FILE_NUM; index++) + free(tp->output_file[index]); + return -EINVAL; + } + + /* set total output file number */ + tp->output_file_num = index; + return 0; +} + /* * Parse shared library from user input * Currently only handles volume and src comp @@ -86,13 +119,14 @@ static int parse_libraries(char *libs) /* print usage for testbench */ static void print_usage(char *executable) { - printf("Usage: %s -i -o ", executable); - printf("-t -b "); + printf("Usage: %s -i ", executable); + printf("-o "); + printf("-t -b -c "); printf("-a \n"); printf("input_format should be S16_LE, S32_LE, S24_LE or FLOAT_LE\n"); printf("Example Usage:\n"); printf("%s -i in.txt -o out.txt -t test.tplg ", executable); - printf("-r 48000 -R 96000 "); + printf("-r 48000 -R 96000 -c 2"); printf("-b S16_LE -a vol=libsof_volume.so\n"); } @@ -138,9 +172,9 @@ static void parse_input_args(int argc, char **argv, struct testbench_prm *tp) tp->input_file = strdup(optarg); break; - /* output sample file */ + /* output sample files */ case 'o': - tp->output_file = strdup(optarg); + ret = parse_output_files(optarg, tp); break; /* topology file */ @@ -209,14 +243,17 @@ int main(int argc, char **argv) tp.fs_out = 0; tp.bits_in = 0; tp.input_file = NULL; - tp.output_file = NULL; + for (i = 0; i < MAX_OUTPUT_FILE_NUM; i++) + tp.output_file[i] = NULL; + tp.output_file_num = 0; tp.channels = TESTBENCH_NCH; /* command line arguments*/ parse_input_args(argc, argv, &tp); /* check args */ - if (!tp.tplg_file || !tp.input_file || !tp.output_file || !tp.bits_in) { + if (!tp.tplg_file || !tp.input_file || !tp.output_file_num || + !tp.bits_in) { print_usage(argv[0]); exit(EXIT_FAILURE); } @@ -294,7 +331,10 @@ int main(int argc, char **argv) printf("Input bit format: %s\n", tp.bits_in); printf("Input sample rate: %d\n", tp.fs_in); printf("Output sample rate: %d\n", tp.fs_out); - printf("Output written to file: \"%s\"\n", tp.output_file); + for (i = 0; i < tp.output_file_num; i++) { + printf("Output[%d] written to file: \"%s\"\n", + i, tp.output_file[i]); + } printf("Input sample count: %d\n", n_in); printf("Output sample count: %d\n", n_out); printf("Total execution time: %.2f us, %.2f x realtime\n", @@ -304,7 +344,8 @@ int main(int argc, char **argv) free(tp.bits_in); free(tp.input_file); free(tp.tplg_file); - free(tp.output_file); + for (i = 0; i < tp.output_file_num; i++) + free(tp.output_file[i]); /* close shared library objects */ for (i = 0; i < NUM_WIDGETS_SUPPORTED; i++) { diff --git a/tools/testbench/topology.c b/tools/testbench/topology.c index b216e6ab98f6..2e254567358b 100644 --- a/tools/testbench/topology.c +++ b/tools/testbench/topology.c @@ -22,6 +22,7 @@ FILE *file; char pipeline_string[DEBUG_MSG_LEN]; struct shared_lib_table *lib_table; +int output_file_index; const struct sof_dai_types sof_dais[] = { {"SSP", SOF_DAI_INTEL_SSP}, @@ -332,8 +333,13 @@ static int load_filewrite(struct sof *sof, int comp_id, int pipeline_id, return -EINVAL; } - /* configure filewrite */ - filewrite.fn = strdup(tp->output_file); + /* configure filewrite (only single output is valid so far) */ + if (!tp->output_file[output_file_index]) { + fprintf(stderr, "error: output[%d] file name is null\n", + output_file_index); + return -EINVAL; + } + filewrite.fn = strdup(tp->output_file[output_file_index]); tp->fw_id = comp_id; /* Set format from testbench command line*/ @@ -671,6 +677,9 @@ int parse_topology(struct sof *sof, struct shared_lib_table *library_table, { struct snd_soc_tplg_hdr *hdr; + /* initialize output file index */ + output_file_index = 0; + struct comp_info *temp_comp_list = NULL; char message[DEBUG_MSG_LEN]; int next_comp_id = 0; From f5c5d9cf5d1465125f678b92e76cf8216617653e Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Tue, 7 Jul 2020 16:42:45 +0800 Subject: [PATCH 09/12] WIP: continue on 2-way crossover test topology I refered sof-icl-rt711-rt1308-rt715-hdmi.m4 which utilized sof/pipe-volume-demux-playback. In this case, pipeline n+1 uses sof/pipe-dai-endpoint. Test topology compile produces no error. According to the output log from running testbench, topology parsing is finished and the route chain looks like what we imagined. output log: https://paste.googleplex.com/5981280506216448 However, testbench will hang after pipe triggered. I suspect it may be caused by somewhere in testbench lack of considering multiple sinks? still under investigation. Signed-off-by: Pin-chih Lin --- tools/test/topology/test-multi-playback.m4 | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/test/topology/test-multi-playback.m4 b/tools/test/topology/test-multi-playback.m4 index 7db5b8bc111e..c4c8572e7a7f 100644 --- a/tools/test/topology/test-multi-playback.m4 +++ b/tools/test/topology/test-multi-playback.m4 @@ -34,11 +34,6 @@ DEBUG_START # TEST_SSP_MODE - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B # -# Apply a non-trivial filter blob IIR and FIR tests. TODO: Note that the -# PIPELINE_FILTERx notation will be updated in future for better flexibility. -define(PIPELINE_FILTER1, ifelse(TEST_PIPE_NAME, `eq-iir', `eq_iir_coef_loudness.m4')) -define(PIPELINE_FILTER2, ifelse(TEST_PIPE_NAME, `eq-fir', `eq_fir_coef_loudness.m4')) - # # Define the pipeline # @@ -53,10 +48,10 @@ PIPELINE_PCM_ADD(sof/pipe-TEST_PIPE_NAME-playback.m4, 8000, 192000, 48000) # Playback pipeline 2 on PCM 1 using max 2 channels of s32le. -# Set 1000us deadline on core 0 with priority 1 -PIPELINE_PCM_ADD(sof/pipe-passthrough-playback.m4, +# Set 1000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-dai-endpoint.m4, 2, 1, 2, s32le, - 1000, 1, 0, + 1000, 0, 0, 8000, 192000, 48000) # DAI configuration @@ -72,11 +67,16 @@ DAI_ADD(sof/pipe-dai-playback.m4, 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) # playback DAI is SSP TEST_DAI_PORT using 2 periods -# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 1 +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 DAI_ADD(sof/pipe-dai-playback.m4, 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, - 1000, 1, 0, SCHEDULE_TIME_DOMAIN_TIMER) + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +#DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, +# 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, +# PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, +# 1000, 1, 0, SCHEDULE_TIME_DOMAIN_TIMER, +# PIPELINE_PLAYBACK_SCHED_COMP_1) # connect pipelines together SectionGraph."pipe-sof-second-pipe" { @@ -84,7 +84,7 @@ SectionGraph."pipe-sof-second-pipe" { lines [ # keyword detect - dapm(PIPELINE_CROSSOVER_1, PIPELINE_PCM_2) + dapm(PIPELINE_SOURCE_2, PIPELINE_CROSSOVER_1) ] } From 76d21881b754102fa59df07b5e59d2d11a08a767 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Thu, 9 Jul 2020 19:34:05 +0800 Subject: [PATCH 10/12] WIP: continue on 2-way crossover test topology 20200709 Testbench output log: https://paste.googleplex.com/5753664419397632 Signed-off-by: Pin-chih Lin --- src/audio/crossover/crossover.c | 6 ++ src/audio/crossover/crossover_generic.c | 77 +++++++++++---------- src/audio/pipeline.c | 1 + tools/test/topology/test-multi-playback.m4 | 24 ++++--- tools/testbench/testbench.c | 6 ++ tools/testbench/topology.c | 8 ++- tools/topology/m4/crossover_coef_default.m4 | 18 ++--- tools/tplg_parser/tplg_parser.c | 3 +- tools/tune/crossover/example_crossover.m | 7 +- 9 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 2d89c14c396f..4c126b16af63 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -667,6 +667,8 @@ static int crossover_copy(struct comp_dev *dev) } /* Find the number of frames to copy over */ + //printf("crossover_copy: src frames=%u\n", + // audio_stream_get_avail_frames(&source->stream)); for (i = 0; i < num_sinks; i++) { if (!sinks[i]) continue; @@ -674,9 +676,13 @@ static int crossover_copy(struct comp_dev *dev) avail = audio_stream_avail_frames(&source->stream, &sinks[i]->stream); frames = MIN(frames, avail); + printf("crossover_copy: sink[%d] frames avail=%u free=%u\n", + i, audio_stream_get_avail_frames(&sinks[i]->stream), + audio_stream_get_free_frames(&sinks[i]->stream)); buffer_unlock(sinks[i], flags); } + printf("crossover_copy: frames to be proc=%u\n", frames); buffer_unlock(source, flags); source_bytes = frames * audio_stream_frame_bytes(&source->stream); diff --git a/src/audio/crossover/crossover_generic.c b/src/audio/crossover/crossover_generic.c index b5a4fbf1722d..26358ce72cb6 100644 --- a/src/audio/crossover/crossover_generic.c +++ b/src/audio/crossover/crossover_generic.c @@ -145,48 +145,55 @@ static void crossover_s16_default(const struct comp_dev *dev, const struct audio_stream *source_stream = &source->stream; struct audio_stream *sink_stream; int16_t *x, *y; - //int ch, i, j; - int i, j; + int ch, i, j; + //int i, j; + //int i; int idx = 0; + //int idx_o = 0; int nch = source_stream->channels; int32_t out[num_sinks]; - //for (ch = 0; ch < nch; ch++) { - // idx = ch; - // state = &cd->state[ch]; - // for (i = 0; i < frames; i++) { - // x = audio_stream_read_frag_s16(source_stream, idx); - // cd->crossover_split(*x << 16, out, state); - - // for (j = 0; j < num_sinks; j++) { - // if (!sinks[j]) - // continue; - // sink_stream = &sinks[j]->stream; - // y = audio_stream_read_frag_s16(sink_stream, - // idx); - // *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); - // } - - // idx += nch; - // } - //} - /* For testbench, reroute all the outputs to the channels of one buffer */ - state = &cd->state[0]; - for (i = 0; i < frames; i++) { - x = audio_stream_read_frag_s16(source_stream, idx); - cd->crossover_split(*x << 16, out, state); + for (ch = 0; ch < nch; ch++) { + idx = ch; + state = &cd->state[ch]; + for (i = 0; i < frames; i++) { + x = audio_stream_read_frag_s16(source_stream, idx); + cd->crossover_split(*x << 16, out, state); - for (j = 0; j < num_sinks; j++) { - if (!sinks[j]) - continue; - sink_stream = &sinks[0]->stream; - y = audio_stream_read_frag_s16(sink_stream, - idx + j); - *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); - } + for (j = 0; j < num_sinks; j++) { + if (!sinks[j]) + continue; + sink_stream = &sinks[j]->stream; + y = audio_stream_read_frag_s16(sink_stream, + idx); + *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); + } - idx += nch; + idx += nch; + } } + /* For testbench, reroute all the outputs to the channels of one buffer */ + //state = &cd->state[0]; + //for (i = 0; i < frames; i++) { + // x = audio_stream_read_frag_s16(source_stream, idx); + // cd->crossover_split(*x << 16, out, state); + + // //for (j = 0; j < num_sinks; j++) { + // // if (!sinks[j]) + // // continue; + // // sink_stream = &sinks[0]->stream; + // // y = audio_stream_read_frag_s16(sink_stream, + // // idx + j); + // // *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); + // //} + + // sink_stream = &sinks[0]->stream; + // y = audio_stream_read_frag_s16(sink_stream, + // idx_o); + // *y = sat_int16(Q_SHIFT_RND(out[0], 31, 15)); + // idx_o += 1; + // idx += nch; + //} } #endif // CONFIG_FORMAT_S16LE diff --git a/src/audio/pipeline.c b/src/audio/pipeline.c index 537d6ca2ab74..c076b63d060b 100644 --- a/src/audio/pipeline.c +++ b/src/audio/pipeline.c @@ -1044,6 +1044,7 @@ static int pipeline_xrun_recover(struct pipeline *p) /* notify pipeline that this component requires buffers emptied/filled */ void pipeline_schedule_copy(struct pipeline *p, uint64_t start) { + pipe_info(p, "pipeline_schedule_copy"); /* disable system agent panic for DMA driven pipelines */ if (!pipeline_is_timer_driven(p)) sa_set_panic_on_delay(false); diff --git a/tools/test/topology/test-multi-playback.m4 b/tools/test/topology/test-multi-playback.m4 index c4c8572e7a7f..951e0a3d6d67 100644 --- a/tools/test/topology/test-multi-playback.m4 +++ b/tools/test/topology/test-multi-playback.m4 @@ -68,19 +68,19 @@ DAI_ADD(sof/pipe-dai-playback.m4, # playback DAI is SSP TEST_DAI_PORT using 2 periods # Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 -DAI_ADD(sof/pipe-dai-playback.m4, - 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, - PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, - 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) -#DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, +#DAI_ADD(sof/pipe-dai-playback.m4, # 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, # PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, -# 1000, 1, 0, SCHEDULE_TIME_DOMAIN_TIMER, -# PIPELINE_PLAYBACK_SCHED_COMP_1) +# 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, + 2, TEST_DAI_TYPE, 2, SSP2-Codec, + PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, + PIPELINE_PLAYBACK_SCHED_COMP_1) # connect pipelines together SectionGraph."pipe-sof-second-pipe" { - index "0" + index "2" lines [ # keyword detect @@ -110,5 +110,13 @@ DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI_PORT, 0, TEST_DAI_LINK_NAME, SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI_PORT, TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) +DAI_CONFIG(TEST_DAI_TYPE, 2, 1, SSP2-Codec, + SSP_CONFIG(TEST_SSP_MODE, + SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), + SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), + SSP_CLOCK(fsync, 48000, codec_slave), + SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), + SSP_CONFIG_DATA(TEST_DAI_TYPE, 2, + TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) DEBUG_END diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 919d998fca22..857fcdc6ae09 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -269,17 +269,23 @@ int main(int argc, char **argv) fprintf(stderr, "error: parsing topology\n"); exit(EXIT_FAILURE); } + printf("Test Pipeline:\n"); + printf("%s\n", pipeline); /* Get pointers to fileread and filewrite */ pcm_dev = ipc_get_comp_by_id(sof.ipc, tp.fw_id); + printf("debug: fw pcm_dev: type=%u, core=%u, id=%u\n", pcm_dev->type, pcm_dev->core, pcm_dev->id); fwcd = comp_get_drvdata(pcm_dev->cd); pcm_dev = ipc_get_comp_by_id(sof.ipc, tp.fr_id); + printf("debug: fr pcm_dev: type=%u, core=%u, id=%u\n", pcm_dev->type, pcm_dev->core, pcm_dev->id); frcd = comp_get_drvdata(pcm_dev->cd); /* Run pipeline until EOF from fileread */ pcm_dev = ipc_get_comp_by_id(sof.ipc, tp.sched_id); + printf("debug: sched pcm_dev: type=%u, core=%u, id=%u\n", pcm_dev->type, pcm_dev->core, pcm_dev->id); p = pcm_dev->cd->pipeline; ipc_pipe = &p->ipc_pipe; + printf("debug: ipc_pipe: comp_id=%u, pipe_id=%u, sched_id=%u\n", ipc_pipe->comp_id, ipc_pipe->pipeline_id, ipc_pipe->sched_id); /* input and output sample rate */ if (!tp.fs_in) diff --git a/tools/testbench/topology.c b/tools/testbench/topology.c index 2e254567358b..68e4caec8b19 100644 --- a/tools/testbench/topology.c +++ b/tools/testbench/topology.c @@ -272,6 +272,7 @@ static int load_fileread(void *dev, int comp_id, int pipeline_id, struct snd_soc_tplg_dapm_widget *widget, int dir, struct testbench_prm *tp) { + printf("debug: load_fileread @ comp_id=%d pipe_id=%d\n", comp_id, pipeline_id); struct sof *sof = (struct sof *)dev; struct sof_ipc_comp_file fileread; int size = widget->priv.size; @@ -320,6 +321,7 @@ static int load_filewrite(struct sof *sof, int comp_id, int pipeline_id, struct snd_soc_tplg_dapm_widget *widget, int dir, struct testbench_prm *tp) { + printf("debug: load_filewrite @ comp_id=%d pipe_id=%d\n", comp_id, pipeline_id); struct sof_ipc_comp_file filewrite; int size = widget->priv.size; int ret; @@ -339,8 +341,12 @@ static int load_filewrite(struct sof *sof, int comp_id, int pipeline_id, output_file_index); return -EINVAL; } + printf("debug: filewrite.fn: %s\n", tp->output_file[output_file_index]); filewrite.fn = strdup(tp->output_file[output_file_index]); - tp->fw_id = comp_id; + if (output_file_index == 0) { + tp->fw_id = comp_id; + } + output_file_index++; /* Set format from testbench command line*/ filewrite.rate = tp->fs_out; diff --git a/tools/topology/m4/crossover_coef_default.m4 b/tools/topology/m4/crossover_coef_default.m4 index 25d86f4a5c5b..97f5542572c1 100644 --- a/tools/topology/m4/crossover_coef_default.m4 +++ b/tools/topology/m4/crossover_coef_default.m4 @@ -1,19 +1,19 @@ -# Exported Control Bytes 17-Apr-2020 +# Exported Control Bytes 09-Jul-2020 CONTROLBYTES_PRIV(CROSSOVER_priv, ` bytes "0x53,0x4f,0x46,0x00,0x00,0x00,0x00,0x00,' -` 0x60,0x00,0x00,0x00,0x00,0xf0,0x00,0x03,' +` 0x60,0x00,0x00,0x00,0x00,0x00,0x01,0x03,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' -` 0x01,0x00,0x00,0x00,0x0a,0x00,0x00,0x00,' +` 0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,' ` 0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,' -` 0x8e,0x6f,0xa8,0xc5,0xb3,0x81,0x14,0x7a,' -` 0xb0,0xc3,0x10,0x00,0x5f,0x87,0x21,0x00,' -` 0xb0,0xc3,0x10,0x00,0x00,0x00,0x00,0x00,' -` 0x00,0x40,0x00,0x00,0x8e,0x6f,0xa8,0xc5,' -` 0xb3,0x81,0x14,0x7a,0x89,0x04,0x1b,0x3d,' -` 0xee,0xf6,0xc9,0x85,0x89,0x04,0x1b,0x3d,' +` 0x6f,0x82,0x53,0xc2,0x3e,0x77,0xa1,0x7d,' +` 0x95,0xc1,0x02,0x00,0x2a,0x83,0x05,0x00,' +` 0x95,0xc1,0x02,0x00,0x00,0x00,0x00,0x00,' +` 0x00,0x40,0x00,0x00,0x6f,0x82,0x53,0xc2,' +` 0x3e,0x77,0xa1,0x7d,0x34,0x7d,0xd3,0x3e,' +` 0x99,0x05,0x59,0x82,0x34,0x7d,0xd3,0x3e,' ` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00"' ) diff --git a/tools/tplg_parser/tplg_parser.c b/tools/tplg_parser/tplg_parser.c index 54818fd7eb72..fafdc8a45a5b 100644 --- a/tools/tplg_parser/tplg_parser.c +++ b/tools/tplg_parser/tplg_parser.c @@ -1139,7 +1139,8 @@ int load_widget(void *dev, int dev_type, struct comp_info *temp_comp_list, temp_comp_list[comp_index].type = widget->id; temp_comp_list[comp_index].pipeline_id = pipeline_id; - printf("debug: loading widget %s id %d\n", widget->name, widget->id); + //printf("debug: loading widget %s id %d\n", widget->name, widget->id); + printf("debug: loading widget %s id %d; comp id %d\n", widget->name, widget->id, comp_id); /* load widget based on type */ switch (widget->id) { diff --git a/tools/tune/crossover/example_crossover.m b/tools/tune/crossover/example_crossover.m index 3166f4a4f022..f9e2c792fbad 100644 --- a/tools/tune/crossover/example_crossover.m +++ b/tools/tune/crossover/example_crossover.m @@ -15,7 +15,7 @@ fc_high = 3000; % 4 way crossover -num_sinks = 4; +num_sinks = 2; % This array is an example on how to assign a buffer from pipeline 1 to output 0, % buffer from pipeline 2 to output 1, etc... % Refer to sof/src/inlude/user/crossover.h for more information on assigning @@ -27,9 +27,9 @@ assign_sinks(4) = 4; % sink[3] % Generate zeros, poles and gain for crossover with the given frequencies -%crossover = crossover_gen_coefs(fs, fc_low); % 2 way crossover +crossover = crossover_gen_coefs(fs, fc_low); % 2 way crossover % crossover = crossover_gen_coefs(fs, fc_low, fc_med); % 3 way crossover -crossover = crossover_gen_coefs(fs, fc_low, fc_med, fc_high); % 4 way crossover +%crossover = crossover_gen_coefs(fs, fc_low, fc_med, fc_high); % 4 way crossover % Convert the [a,b] coefficients to values usable with SOF crossover_bqs = crossover_coef_quant(crossover.lp, crossover.hp); @@ -49,6 +49,7 @@ % Plot Magnitude and Phase Response of each sink crossover_plot_freq(crossover.lp, crossover.hp, fs, num_sinks); +pause(5); rmpath ./../common endfunction From cdd53243688eaac9b2191836c0709bb569b2e5a4 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Tue, 14 Jul 2020 17:57:35 +0800 Subject: [PATCH 11/12] WIP: continue on 2-way crossover test topology 20200714 Testbench output log: https://paste.googleplex.com/6407279421161472 Signed-off-by: Pin-chih Lin --- src/audio/crossover/crossover.c | 8 ++++---- src/audio/pipeline.c | 11 ++++++++++- tools/test/topology/test-multi-playback.m4 | 20 ++++++++++---------- tools/testbench/testbench.c | 1 + 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 4c126b16af63..9e35f72b00bf 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -676,13 +676,13 @@ static int crossover_copy(struct comp_dev *dev) avail = audio_stream_avail_frames(&source->stream, &sinks[i]->stream); frames = MIN(frames, avail); - printf("crossover_copy: sink[%d] frames avail=%u free=%u\n", - i, audio_stream_get_avail_frames(&sinks[i]->stream), - audio_stream_get_free_frames(&sinks[i]->stream)); + //printf("crossover_copy: sink[%d] frames avail=%u free=%u\n", + // i, audio_stream_get_avail_frames(&sinks[i]->stream), + // audio_stream_get_free_frames(&sinks[i]->stream)); buffer_unlock(sinks[i], flags); } - printf("crossover_copy: frames to be proc=%u\n", frames); + //printf("crossover_copy: frames to be proc=%u\n", frames); buffer_unlock(source, flags); source_bytes = frames * audio_stream_frame_bytes(&source->stream); diff --git a/src/audio/pipeline.c b/src/audio/pipeline.c index c076b63d060b..9e9c1daa541c 100644 --- a/src/audio/pipeline.c +++ b/src/audio/pipeline.c @@ -164,6 +164,8 @@ static int pipeline_for_each_comp(struct comp_dev *current, list_for_item(clist, buffer_list) { buffer = buffer_from_list(clist, struct comp_buffer, dir); + //printf("buffer id=%u pipe_id=%u\n", buffer->id, buffer->pipeline_id); + /* execute operation on buffer */ if (ctx->buff_func) ctx->buff_func(buffer, ctx->buff_data); @@ -613,6 +615,7 @@ static void pipeline_comp_trigger_sched_comp(struct pipeline *p, return; /* add for later schedule */ + printf("pipeline_comp_trigger_sched_comp\n"); list_item_append(&p->list, &ctx->pipelines); } @@ -627,13 +630,17 @@ static int pipeline_comp_trigger(struct comp_dev *current, ppl_data->start->pipeline); int err; + printf("pipeline_comp_trigger(), current->comp.id = %u, dir = %u\n", + dev_comp_id(current), dir); pipe_cl_dbg("pipeline_comp_trigger(), current->comp.id = %u, dir = %u", dev_comp_id(current), dir); + printf("is_single_ppl=%d, is_same_sched=%d\n", is_single_ppl, is_same_sched); /* trigger should propagate to the connected pipelines, * which need to be scheduled together */ if (!is_single_ppl && !is_same_sched) { + printf("pipeline_comp_trigger(), current is from another pipeline\n"); pipe_dbg(current->pipeline, "pipeline_comp_trigger(), current is from another pipeline"); return 0; } @@ -694,12 +701,14 @@ static void pipeline_schedule_triggered(struct pipeline_walk_context *ctx, struct list_item *tlist; struct pipeline *p; uint32_t flags; + int i = 1; irq_local_disable(flags); list_for_item(tlist, &ctx->pipelines) { p = container_of(tlist, struct pipeline, list); - + printf("pipeline_schedule_triggered: pipeline number %d\n", i); + i++; switch (cmd) { case COMP_TRIGGER_PAUSE: case COMP_TRIGGER_STOP: diff --git a/tools/test/topology/test-multi-playback.m4 b/tools/test/topology/test-multi-playback.m4 index 951e0a3d6d67..82520e1b6a8b 100644 --- a/tools/test/topology/test-multi-playback.m4 +++ b/tools/test/topology/test-multi-playback.m4 @@ -54,6 +54,16 @@ PIPELINE_PCM_ADD(sof/pipe-dai-endpoint.m4, 1000, 0, 0, 8000, 192000, 48000) +# connect pipelines together +SectionGraph."pipe-sof-second-pipe" { + index "2" + + lines [ + # keyword detect + dapm(PIPELINE_SOURCE_2, PIPELINE_CROSSOVER_1) + ] +} + # DAI configuration # # SSP port TEST_DAI_PORT is our only pipeline DAI @@ -78,16 +88,6 @@ DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, PIPELINE_PLAYBACK_SCHED_COMP_1) -# connect pipelines together -SectionGraph."pipe-sof-second-pipe" { - index "2" - - lines [ - # keyword detect - dapm(PIPELINE_SOURCE_2, PIPELINE_CROSSOVER_1) - ] -} - # PCM Passthrough PCM_PLAYBACK_ADD(Passthrough, 0, PIPELINE_PCM_1) diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 857fcdc6ae09..683576521f3f 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -306,6 +306,7 @@ int main(int argc, char **argv) while (frcd->fs.reached_eof == 0) pipeline_schedule_copy(p, 0); + // should we add pipeline_schedule_copy to the second pipeline? if (!frcd->fs.reached_eof) printf("warning: possible pipeline xrun\n"); From 2b740fd7071453ffe36cd16774cc946ecef25174 Mon Sep 17 00:00:00 2001 From: Pin-chih Lin Date: Thu, 16 Jul 2020 14:21:38 +0800 Subject: [PATCH 12/12] WIP: continue on 2-way crossover test topology 20200716 Signed-off-by: Pin-chih Lin --- src/audio/CMakeLists.txt | 1 - src/audio/crossover/crossover.c | 16 +- src/audio/crossover/crossover_generic.c | 25 --- tools/test/topology/test-multi-playback.m4 | 15 +- tools/test/topology/test-playback.m4 | 186 +++++++++++++++++- tools/test/topology/tplg-build.sh | 70 ++++--- .../testbench/include/testbench/common_test.h | 3 +- tools/testbench/testbench.c | 25 ++- tools/testbench/topology.c | 4 + tools/topology/m4/crossover_coef_default.m4 | 20 +- tools/tplg_parser/tplg_parser.c | 8 +- tools/tune/crossover/example_crossover.m | 6 +- 12 files changed, 283 insertions(+), 96 deletions(-) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 2e2e9442143b..bdb9f420a1ea 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -110,7 +110,6 @@ check_optimization(fma -mfma -DOPS_FMA) check_optimization(hifi2ep -mhifi2ep -DOPS_HIFI2EP) check_optimization(hifi3 -mhifi3 -DOPS_HIFI3) -#set(sof_audio_modules volume src asrc eq-fir eq-iir dcblock) set(sof_audio_modules volume src asrc eq-fir eq-iir dcblock crossover) # sources for each module diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index 9e35f72b00bf..f1f29ff76092 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -643,12 +643,9 @@ static int crossover_copy(struct comp_dev *dev) * to be assigned: sink[i] can be NULL, 0 <= i <= config->num_sinks */ num_assigned_sinks = crossover_assign_sinks(dev, cd->config, sinks); - /* For testbench use, we do not expect the number of actual sink buffers to match - * the config - */ - //if (cd->config && num_assigned_sinks != cd->config->num_sinks) - // comp_dbg(dev, "crossover_copy(), number of assigned sinks (%i) does not match number of sinks in config (%i).", - // num_assigned_sinks, cd->config->num_sinks); + if (cd->config && num_assigned_sinks != cd->config->num_sinks) + comp_dbg(dev, "crossover_copy(), number of assigned sinks (%i) does not match number of sinks in config (%i).", + num_assigned_sinks, cd->config->num_sinks); /* If no config is set then assign the number of sinks to the number * of sinks that were assigned @@ -766,15 +763,10 @@ static int crossover_prepare(struct comp_dev *dev) source->stream.channels); /* Initialize Crossover */ - /* When running crossover on testbench, the config will always be - * invalid since we can only have one sink buffer. - * The number of assigned sinks will not match the number of actual sink buffers. - * Therefore, do not take any action. - */ if (cd->config && crossover_validate_config(dev, cd->config) < 0) { /* If config is invalid then delete it.*/ comp_err(dev, "crossover_prepare(), invalid binary config format"); - //crossover_free_config(&cd->config); + crossover_free_config(&cd->config); } if (cd->config) { diff --git a/src/audio/crossover/crossover_generic.c b/src/audio/crossover/crossover_generic.c index 26358ce72cb6..f25280762057 100644 --- a/src/audio/crossover/crossover_generic.c +++ b/src/audio/crossover/crossover_generic.c @@ -146,10 +146,7 @@ static void crossover_s16_default(const struct comp_dev *dev, struct audio_stream *sink_stream; int16_t *x, *y; int ch, i, j; - //int i, j; - //int i; int idx = 0; - //int idx_o = 0; int nch = source_stream->channels; int32_t out[num_sinks]; @@ -172,28 +169,6 @@ static void crossover_s16_default(const struct comp_dev *dev, idx += nch; } } - /* For testbench, reroute all the outputs to the channels of one buffer */ - //state = &cd->state[0]; - //for (i = 0; i < frames; i++) { - // x = audio_stream_read_frag_s16(source_stream, idx); - // cd->crossover_split(*x << 16, out, state); - - // //for (j = 0; j < num_sinks; j++) { - // // if (!sinks[j]) - // // continue; - // // sink_stream = &sinks[0]->stream; - // // y = audio_stream_read_frag_s16(sink_stream, - // // idx + j); - // // *y = sat_int16(Q_SHIFT_RND(out[j], 31, 15)); - // //} - - // sink_stream = &sinks[0]->stream; - // y = audio_stream_read_frag_s16(sink_stream, - // idx_o); - // *y = sat_int16(Q_SHIFT_RND(out[0], 31, 15)); - // idx_o += 1; - // idx += nch; - //} } #endif // CONFIG_FORMAT_S16LE diff --git a/tools/test/topology/test-multi-playback.m4 b/tools/test/topology/test-multi-playback.m4 index 82520e1b6a8b..9ff42a8dc22b 100644 --- a/tools/test/topology/test-multi-playback.m4 +++ b/tools/test/topology/test-multi-playback.m4 @@ -34,6 +34,9 @@ DEBUG_START # TEST_SSP_MODE - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B # +define(TEST_DAI2_LINK_NAME, `SSP0-Codec') +define(TEST_DAI2_PORT, `0') + # # Define the pipeline # @@ -78,12 +81,8 @@ DAI_ADD(sof/pipe-dai-playback.m4, # playback DAI is SSP TEST_DAI_PORT using 2 periods # Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 -#DAI_ADD(sof/pipe-dai-playback.m4, -# 2, TEST_DAI_TYPE, TEST_DAI_PORT, TEST_DAI_LINK_NAME, -# PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, -# 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, - 2, TEST_DAI_TYPE, 2, SSP2-Codec, + 2, TEST_DAI_TYPE, TEST_DAI2_PORT, TEST_DAI2_LINK_NAME, PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, PIPELINE_PLAYBACK_SCHED_COMP_1) @@ -91,8 +90,6 @@ DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, # PCM Passthrough PCM_PLAYBACK_ADD(Passthrough, 0, PIPELINE_PCM_1) -#PCM_PLAYBACK_ADD(Passthrough, 1, PIPELINE_PCM_2) - # # BE configurations - overrides config in ACPI if present # @@ -110,13 +107,13 @@ DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI_PORT, 0, TEST_DAI_LINK_NAME, SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI_PORT, TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) -DAI_CONFIG(TEST_DAI_TYPE, 2, 1, SSP2-Codec, +DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI2_PORT, 1, TEST_DAI2_LINK_NAME, SSP_CONFIG(TEST_SSP_MODE, SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), SSP_CLOCK(fsync, 48000, codec_slave), SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), - SSP_CONFIG_DATA(TEST_DAI_TYPE, 2, + SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI2_PORT, TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) DEBUG_END diff --git a/tools/test/topology/test-playback.m4 b/tools/test/topology/test-playback.m4 index 3f762e83e41d..543d5d09661c 100644 --- a/tools/test/topology/test-playback.m4 +++ b/tools/test/topology/test-playback.m4 @@ -19,6 +19,9 @@ include(`platform/intel/bxt.m4') DEBUG_START +dnl Produce uppercase for input string +define(`upcase', `translit(`$*', `a-z', `A-Z')') + # # Machine Specific Config - !! MUST BE SET TO MATCH TEST MACHINE DRIVER !! # @@ -32,6 +35,7 @@ DEBUG_START # TEST_SSP_PHY_BITS - SSP physical slot size # TEST_SSP_DATA_BITS - SSP data slot size # TEST_SSP_MODE - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B +# TEST_PIPE_AMOUNT - Total amount of pipelines e.g. 1, 2, 3, 4 # # Apply a non-trivial filter blob IIR and FIR tests. TODO: Note that the @@ -39,10 +43,42 @@ DEBUG_START define(PIPELINE_FILTER1, ifelse(TEST_PIPE_NAME, `eq-iir', `eq_iir_coef_loudness.m4')) define(PIPELINE_FILTER2, ifelse(TEST_PIPE_NAME, `eq-fir', `eq_fir_coef_loudness.m4')) +# Define HAS_PIPEn flags according to TEST_PIPE_AMOUNT. Those flags will be used to +# determine whether PIPELINE_n should be added. +ifelse(TEST_PIPE_AMOUNT, `2', +` +define(HAS_PIPE2) +') + +ifelse(TEST_PIPE_AMOUNT, `3', +` +define(HAS_PIPE2) +define(HAS_PIPE3) +') + +ifelse(TEST_PIPE_AMOUNT, `4', +` +define(HAS_PIPE2) +define(HAS_PIPE3) +define(HAS_PIPE4) +') + # -# Define the pipeline +# Define the pipeline(s) # -# PCM0P --> TEST_PIPE_NAME --> SSP(TEST_DAI_PORT) +# PCM0P --> BUF1.0 --> TEST_PIPE_NAME --> BUF1.1 --> SSP(TEST_DAI_PORT) +ifdef(`HAS_PIPE2', +` +# +---> BUF2.0 --> SSP(0 or 1) +') +ifdef(`HAS_PIPE3', +` +# +---> BUF3.0 --> SSP(2 or 3) +') +ifdef(`HAS_PIPE4', +` +# +---> BUF4.0 --> SSP(4 or 5) +') # # Playback pipeline 1 on PCM 0 using max 2 channels of s32le. @@ -52,10 +88,71 @@ PIPELINE_PCM_ADD(sof/pipe-TEST_PIPE_NAME-playback.m4, 1000, 0, 0, 8000, 192000, 48000) +# Generalize the pipeline junction name as PIPELINE_JUNCTION. +# e.g. TEST_PIPE_NAME=`crossover' --> PIPELINE_JUNCTION=`PIPELINE_CROSSOVER_1' +define(PIPELINE_JUNCTION, concat(concat(`PIPELINE_', upcase(TEST_PIPE_NAME)), `_1')) + +ifdef(`HAS_PIPE2', +` +# Playback pipeline 2 on PCM 1 using max 2 channels of s32le. +# Set 1000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-dai-endpoint.m4, + 2, 1, 2, s32le, + 1000, 0, 0, + 8000, 192000, 48000) + +# connect pipelines together +SectionGraph."pipe-sof-second-pipe" { + index "2" + + lines [ + # connect the second sink buffer + dapm(PIPELINE_SOURCE_2, PIPELINE_JUNCTION) + ] +} +') + +ifdef(`HAS_PIPE3', +` +# Playback pipeline 3 on PCM 2 using max 2 channels of s32le. +# Set 1000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-dai-endpoint.m4, + 3, 2, 2, s32le, + 1000, 0, 0, + 8000, 192000, 48000) + +# connect pipelines together +SectionGraph."pipe-sof-third-pipe" { + index "3" + + lines [ + # connect the third sink buffer + dapm(PIPELINE_SOURCE_3, PIPELINE_JUNCTION) + ] +} +') + +ifdef(`HAS_PIPE4', +` +# Playback pipeline 4 on PCM 3 using max 2 channels of s32le. +# Set 1000us deadline on core 0 with priority 0 +PIPELINE_PCM_ADD(sof/pipe-dai-endpoint.m4, + 4, 3, 2, s32le, + 1000, 0, 0, + 8000, 192000, 48000) + +# connect pipelines together +SectionGraph."pipe-sof-fourth-pipe" { + index "4" + + lines [ + # connect the fourth sink buffer + dapm(PIPELINE_SOURCE_4, PIPELINE_JUNCTION) + ] +} +') + # DAI configuration -# -# SSP port TEST_DAI_PORT is our only pipeline DAI -# # playback DAI is SSP TEST_DAI_PORT using 2 periods # Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 @@ -64,6 +161,48 @@ DAI_ADD(sof/pipe-dai-playback.m4, PIPELINE_SOURCE_1, 2, TEST_DAI_FORMAT, 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER) +ifdef(`HAS_PIPE2', +` +define(TEST_DAI2_PORT, ifelse(TEST_DAI_PORT, `0', `1', `0')) +define(TEST_DAI2_LINK_NAME, ifelse(TEST_DAI_PORT, `0', `SSP1-Codec', `SSP0-Codec')) + +# playback DAI is SSP TEST_DAI2_PORT using 2 periods +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 +DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, + 2, TEST_DAI_TYPE, TEST_DAI2_PORT, TEST_DAI2_LINK_NAME, + PIPELINE_SOURCE_2, 2, TEST_DAI_FORMAT, + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, + PIPELINE_PLAYBACK_SCHED_COMP_1) +') + +ifdef(`HAS_PIPE3', +` +define(TEST_DAI3_PORT, ifelse(TEST_DAI_PORT, `2', `3', `2')) +define(TEST_DAI3_LINK_NAME, ifelse(TEST_DAI_PORT, `2', `SSP3-Codec', `SSP2-Codec')) + +# playback DAI is SSP TEST_DAI3_PORT using 2 periods +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 +DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, + 3, TEST_DAI_TYPE, TEST_DAI3_PORT, TEST_DAI3_LINK_NAME, + PIPELINE_SOURCE_3, 2, TEST_DAI_FORMAT, + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, + PIPELINE_PLAYBACK_SCHED_COMP_1) +') + +ifdef(`HAS_PIPE4', +` +define(TEST_DAI4_PORT, ifelse(TEST_DAI_PORT, `4', `5', `4')) +define(TEST_DAI4_LINK_NAME, ifelse(TEST_DAI_PORT, `4', `SSP5-Codec', `SSP4-Codec')) + +# playback DAI is SSP TEST_DAI4_PORT using 2 periods +# Buffers use s24le format, with 48 frame per 1000us on core 0 with priority 0 +DAI_ADD_SCHED(sof/pipe-dai-sched-playback.m4, + 4, TEST_DAI_TYPE, TEST_DAI4_PORT, TEST_DAI4_LINK_NAME, + PIPELINE_SOURCE_4, 2, TEST_DAI_FORMAT, + 1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER, + PIPELINE_PLAYBACK_SCHED_COMP_1) +') + # PCM Passthrough PCM_PLAYBACK_ADD(Passthrough, 0, PIPELINE_PCM_1) @@ -73,7 +212,7 @@ PCM_PLAYBACK_ADD(Passthrough, 0, PIPELINE_PCM_1) # Clocks masters wrt codec # # TEST_SSP_DATA_BITS bit I2S -# using TEST_SSP_PHY_BITS bit sample container on SSP TEST_DAI_PORT +# using TEST_SSP_PHY_BITS bit sample container on SSP port(s) # DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI_PORT, 0, TEST_DAI_LINK_NAME, SSP_CONFIG(TEST_SSP_MODE, @@ -84,5 +223,40 @@ DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI_PORT, 0, TEST_DAI_LINK_NAME, SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI_PORT, TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) +ifdef(`HAS_PIPE2', +` +DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI2_PORT, 1, TEST_DAI2_LINK_NAME, + SSP_CONFIG(TEST_SSP_MODE, + SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), + SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), + SSP_CLOCK(fsync, 48000, codec_slave), + SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), + SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI2_PORT, + TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) +') + +ifdef(`HAS_PIPE3', +` +DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI3_PORT, 2, TEST_DAI3_LINK_NAME, + SSP_CONFIG(TEST_SSP_MODE, + SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), + SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), + SSP_CLOCK(fsync, 48000, codec_slave), + SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), + SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI3_PORT, + TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) +') + +ifdef(`HAS_PIPE4', +` +DAI_CONFIG(TEST_DAI_TYPE, TEST_DAI4_PORT, 3, TEST_DAI4_LINK_NAME, + SSP_CONFIG(TEST_SSP_MODE, + SSP_CLOCK(mclk, TEST_SSP_MCLK, codec_mclk_in), + SSP_CLOCK(bclk, TEST_SSP_BCLK, codec_slave), + SSP_CLOCK(fsync, 48000, codec_slave), + SSP_TDM(2, TEST_SSP_PHY_BITS, 3, 3), + SSP_CONFIG_DATA(TEST_DAI_TYPE, TEST_DAI4_PORT, + TEST_SSP_DATA_BITS, TEST_SSP_MCLK_ID))) +') DEBUG_END diff --git a/tools/test/topology/tplg-build.sh b/tools/test/topology/tplg-build.sh index 5e737a808bc5..3e14548fa088 100755 --- a/tools/test/topology/tplg-build.sh +++ b/tools/test/topology/tplg-build.sh @@ -34,21 +34,22 @@ M4_STRINGS="" # 3) be_name - BE DAI link name in machine driver, used for matching # 4) format - PCM sample format # 5) dai_type - dai type e.g. SSP/DMIC -# 5) dai_id - SSP port number -# 6) dai_format - SSP sample format -# 7) dai_phy_bits - SSP physical number of BLKCs per slot/channel -# 8) dai_data_bits - SSP number of valid data bits per slot/channel -# 9) dai_bclk - SSP BCLK in HZ -# 10) dai_mclk - SSP MCLK in HZ -# 11) SSP mode - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B -# 12) SSP mclk_id -# 13) Test pipelines +# 6) dai_id - SSP port number +# 7) dai_format - SSP sample format +# 8) dai_phy_bits - SSP physical number of BLKCs per slot/channel +# 9) dai_data_bits - SSP number of valid data bits per slot/channel +# 10) dai_bclk - SSP BCLK in HZ +# 11) dai_mclk - SSP MCLK in HZ +# 12) SSP mode - SSP mode e.g. I2S, LEFT_J, DSP_A and DSP_B +# 13) SSP mclk_id +# 14) total amount of pipeline - range from 1 to 4 +# 15) Test pipelines # function simple_test { if [ $5 == "SSP" ] then - TESTS=("${!14}") + TESTS=("${!15}") elif [ $5 == "DMIC" ] then TESTS=("${!15}") @@ -87,7 +88,12 @@ function simple_test { then TFILE="test-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" else - TFILE="$i-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" + if [ ${14} == "1" ] + then + TFILE="$i-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" + else + TFILE="$i-ssp$6-mclk-${13}-${12}-${14}way-$2-$4-$7-48k-$((${11} / 1000))k-$1" + fi fi #create input string for batch m4 processing M4_STRINGS+="-DTEST_PIPE_NAME=$2,-DTEST_DAI_LINK_NAME=$3\ @@ -95,7 +101,8 @@ function simple_test { -DTEST_PIPE_FORMAT=$4,-DTEST_SSP_BCLK=${10}\ -DTEST_SSP_MCLK=${11},-DTEST_SSP_PHY_BITS=$8\ -DTEST_SSP_DATA_BITS=$9,-DTEST_SSP_MODE=${12}\ - -DTEST_SSP_MCLK_ID=${13},-DTEST_DAI_TYPE=$5\ + -DTEST_SSP_MCLK_ID=${13},-DTEST_PIPE_AMOUNT=${14}\ + -DTEST_DAI_TYPE=$5\ $i.m4,$BUILD_OUTPUT/${TFILE}," #create input string for batch processing of conf files TEST_STRINGS+="$BUILD_OUTPUT/${TFILE}," @@ -108,7 +115,12 @@ function simple_test { then TFILE="test-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" else - TFILE="$i-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" + if [ ${14} == "1" ] + then + TFILE="$i-ssp$6-mclk-${13}-${12}-$2-$4-$7-48k-$((${11} / 1000))k-$1" + else + TFILE="$i-ssp$6-mclk-${13}-${12}-${14}way-$2-$4-$7-48k-$((${11} / 1000))k-$1" + fi fi echo "M4 pre-processing test $i -> ${TFILE}" m4 ${M4_FLAGS} \ @@ -123,6 +135,7 @@ function simple_test { -DTEST_SSP_DATA_BITS=$9 \ -DTEST_SSP_MODE=${12} \ -DTEST_SSP_MCLK_ID=${13} \ + -DTEST_PIPE_AMOUNT=${14} \ -DTEST_DAI_TYPE=$5 \ $i.m4 > "$BUILD_OUTPUT/${TFILE}.conf" echo "Compiling test $i -> $BUILD_OUTPUT/${TFILE}.tplg" @@ -215,35 +228,36 @@ done # for processing algorithms #ALG_MODE_TESTS=(asrc eq-fir eq-iir src dcblock) -ALG_MODE_TESTS=(dcblock) +ALG_MODE_SINGLE_TESTS=(dcblock) ALG_MODE_MULTI_TESTS=(crossover) #ALG_SIMPLE_TESTS=(test-capture test-playback) ALG_SIMPLE_TESTS=(test-playback) -ALG_MULTI_TESTS=(test-multi-playback) ALG_PROTOCOL_TESTS=(I2S) ALG_SSP_TESTS=(5) ALG_MCLK_IDS=(0) +ALG_MULTI_DAI_NUMBERS=(2 3 4) for protocol in ${ALG_PROTOCOL_TESTS[@]} do for ssp in ${ALG_SSP_TESTS[@]} do - for mode in ${ALG_MODE_TESTS[@]} + for mclk_id in ${ALG_MCLK_IDS[@]} do - for mclk_id in ${ALG_MCLK_IDS[@]} + for mode in ${ALG_MODE_SINGLE_TESTS[@]} do - simple_test codec $mode "SSP${ssp}-Codec" s16le SSP $ssp s16le 16 16 1536000 24576000 $protocol $mclk_id ALG_SIMPLE_TESTS[@] - simple_test codec $mode "SSP${ssp}-Codec" s24le SSP $ssp s24le 32 24 3072000 24576000 $protocol $mclk_id ALG_SIMPLE_TESTS[@] - simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id ALG_SIMPLE_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s16le SSP $ssp s16le 16 16 1536000 24576000 $protocol $mclk_id 1 ALG_SIMPLE_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s24le SSP $ssp s24le 32 24 3072000 24576000 $protocol $mclk_id 1 ALG_SIMPLE_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id 1 ALG_SIMPLE_TESTS[@] done - done - for mode in ${ALG_MODE_MULTI_TESTS[@]} - do - for mclk_id in ${ALG_MCLK_IDS[@]} + + for mode in ${ALG_MODE_MULTI_TESTS[@]} do - simple_test codec $mode "SSP${ssp}-Codec" s16le SSP $ssp s16le 16 16 1536000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] - simple_test codec $mode "SSP${ssp}-Codec" s24le SSP $ssp s24le 32 24 3072000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] - simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id ALG_MULTI_TESTS[@] + for dai_num in ${ALG_MULTI_DAI_NUMBERS[@]} + do + simple_test codec $mode "SSP${ssp}-Codec" s16le SSP $ssp s16le 16 16 1536000 24576000 $protocol $mclk_id $dai_num ALG_SIMPLE_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s24le SSP $ssp s24le 32 24 3072000 24576000 $protocol $mclk_id $dai_num ALG_SIMPLE_TESTS[@] + simple_test codec $mode "SSP${ssp}-Codec" s32le SSP $ssp s32le 32 32 3072000 24576000 $protocol $mclk_id $dai_num ALG_SIMPLE_TESTS[@] + done done done done @@ -272,7 +286,7 @@ then echo "Batch processing m4 files..." M4_STRINGS=${M4_STRINGS%?} #m4 processing - echo $M4_STRINGS | tr " " "," | tr '\n' '\0' | xargs -P0 -d ',' -n14 bash -c 'm4 "${@:1:${#}-1}" > ${14}.conf' m4 + echo $M4_STRINGS | tr " " "," | tr '\n' '\0' | xargs -P0 -d ',' -n15 bash -c 'm4 "${@:1:${#}-1}" > ${15}.conf' m4 #execute alsatplg to create topology binary TEST_STRINGS=${TEST_STRINGS%?} diff --git a/tools/testbench/include/testbench/common_test.h b/tools/testbench/include/testbench/common_test.h index fa875c405985..fffc7f3c6db6 100644 --- a/tools/testbench/include/testbench/common_test.h +++ b/tools/testbench/include/testbench/common_test.h @@ -20,7 +20,7 @@ #define MAX_OUTPUT_FILE_NUM 4 /* number of widgets types supported in testbench */ -#define NUM_WIDGETS_SUPPORTED 6 +#define NUM_WIDGETS_SUPPORTED 8 struct testbench_prm { char *tplg_file; /* topology file to use */ @@ -40,6 +40,7 @@ struct testbench_prm { int fr_id; int fw_id; int sched_id; + int max_pipeline_id; enum sof_ipc_frame frame_fmt; }; diff --git a/tools/testbench/testbench.c b/tools/testbench/testbench.c index 683576521f3f..e4cf150ced79 100644 --- a/tools/testbench/testbench.c +++ b/tools/testbench/testbench.c @@ -19,8 +19,8 @@ /* shared library look up table */ struct shared_lib_table lib_table[NUM_WIDGETS_SUPPORTED] = { {"file", "", SOF_COMP_HOST, 0, NULL}, /* File must be first */ - //{"volume", "libsof_volume.so", SOF_COMP_VOLUME, 0, NULL}, - //{"src", "libsof_src.so", SOF_COMP_SRC, 0, NULL}, + {"volume", "libsof_volume.so", SOF_COMP_VOLUME, 0, NULL}, + {"src", "libsof_src.so", SOF_COMP_SRC, 0, NULL}, {"asrc", "libsof_asrc.so", SOF_COMP_ASRC, 0, NULL}, {"eq-fir", "libsof_eq-fir.so", SOF_COMP_EQ_FIR, 0, NULL}, {"eq-iir", "libsof_eq-iir.so", SOF_COMP_EQ_IIR, 0, NULL}, @@ -229,6 +229,7 @@ int main(int argc, char **argv) struct testbench_prm tp; struct ipc_comp_dev *pcm_dev; struct pipeline *p; + struct pipeline *curr_p; struct sof_ipc_pipe_new *ipc_pipe; struct comp_dev *cd; struct file_comp_data *frcd, *fwcd; @@ -247,6 +248,7 @@ int main(int argc, char **argv) tp.output_file[i] = NULL; tp.output_file_num = 0; tp.channels = TESTBENCH_NCH; + tp.max_pipeline_id = 0; /* command line arguments*/ parse_input_args(argc, argv, &tp); @@ -304,9 +306,22 @@ int main(int argc, char **argv) tb_enable_trace(false); /* reduce trace output */ tic = clock(); - while (frcd->fs.reached_eof == 0) - pipeline_schedule_copy(p, 0); - // should we add pipeline_schedule_copy to the second pipeline? + while (frcd->fs.reached_eof == 0) { + /* + * schedule copy for all pipelines which have the same schedule + * component as the working one. + */ + for (i = 1; i <= tp.max_pipeline_id; i++) { + pcm_dev = ipc_get_comp_by_ppl_id(sof.ipc, + COMP_TYPE_PIPELINE, i); + if (pcm_dev) { + curr_p = pcm_dev->pipeline; + if (pipeline_is_same_sched_comp(p, curr_p)) { + pipeline_schedule_copy(curr_p, 0); + } + } + } + } if (!frcd->fs.reached_eof) printf("warning: possible pipeline xrun\n"); diff --git a/tools/testbench/topology.c b/tools/testbench/topology.c index 68e4caec8b19..d66f5658d4ba 100644 --- a/tools/testbench/topology.c +++ b/tools/testbench/topology.c @@ -744,6 +744,10 @@ int parse_topology(struct sof *sof, struct shared_lib_table *library_table, debug_print(message); + /* update max pipeline_id */ + if (hdr->index > tp->max_pipeline_id) + tp->max_pipeline_id = hdr->index; + num_comps += hdr->count; size = sizeof(struct comp_info) * num_comps; temp_comp_list = (struct comp_info *) diff --git a/tools/topology/m4/crossover_coef_default.m4 b/tools/topology/m4/crossover_coef_default.m4 index 97f5542572c1..c468efad80c7 100644 --- a/tools/topology/m4/crossover_coef_default.m4 +++ b/tools/topology/m4/crossover_coef_default.m4 @@ -1,10 +1,10 @@ -# Exported Control Bytes 09-Jul-2020 +# Exported Control Bytes 16-Jul-2020 CONTROLBYTES_PRIV(CROSSOVER_priv, ` bytes "0x53,0x4f,0x46,0x00,0x00,0x00,0x00,0x00,' -` 0x60,0x00,0x00,0x00,0x00,0x00,0x01,0x03,' +` 0xd0,0x00,0x00,0x00,0x00,0x00,0x01,0x03,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' -` 0x60,0x00,0x00,0x00,0x02,0x00,0x00,0x00,' +` 0xd0,0x00,0x00,0x00,0x04,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,' ` 0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,' @@ -15,5 +15,19 @@ CONTROLBYTES_PRIV(CROSSOVER_priv, ` 0x00,0x40,0x00,0x00,0x6f,0x82,0x53,0xc2,' ` 0x3e,0x77,0xa1,0x7d,0x34,0x7d,0xd3,0x3e,' ` 0x99,0x05,0x59,0x82,0x34,0x7d,0xd3,0x3e,' +` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,' +` 0xef,0xcd,0xd0,0xca,0x5d,0x8c,0x2e,0x74,' +` 0x6d,0x29,0x40,0x00,0xda,0x52,0x80,0x00,' +` 0x6d,0x29,0x40,0x00,0x00,0x00,0x00,0x00,' +` 0x00,0x40,0x00,0x00,0xef,0xcd,0xd0,0xca,' +` 0x5d,0x8c,0x2e,0x74,0x9c,0x6f,0x57,0x3a,' +` 0xc9,0x20,0x51,0x8b,0x9c,0x6f,0x57,0x3a,' +` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,' +` 0xd0,0x91,0x42,0xdb,0xb1,0x53,0x12,0x5d,' +` 0xa0,0xc6,0xea,0x01,0x3f,0x8d,0xd5,0x03,' +` 0xa0,0xc6,0xea,0x01,0x00,0x00,0x00,0x00,' +` 0x00,0x40,0x00,0x00,0xd0,0x91,0x42,0xdb,' +` 0xb1,0x53,0x12,0x5d,0x78,0xf0,0x73,0x30,' +` 0x10,0x1f,0x18,0x9f,0x78,0xf0,0x73,0x30,' ` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00"' ) diff --git a/tools/tplg_parser/tplg_parser.c b/tools/tplg_parser/tplg_parser.c index fafdc8a45a5b..182448ed7a13 100644 --- a/tools/tplg_parser/tplg_parser.c +++ b/tools/tplg_parser/tplg_parser.c @@ -1098,8 +1098,10 @@ int tplg_load_graph(int num_comps, int pipeline_id, strcat(pipeline_string, graph_elem->source); strcat(pipeline_string, "->"); - if (route_num == (count - 1)) + if (route_num == (count - 1)) { strcat(pipeline_string, graph_elem->sink); + strcat(pipeline_string, "\n"); + } free(graph_elem); return 0; @@ -1139,8 +1141,8 @@ int load_widget(void *dev, int dev_type, struct comp_info *temp_comp_list, temp_comp_list[comp_index].type = widget->id; temp_comp_list[comp_index].pipeline_id = pipeline_id; - //printf("debug: loading widget %s id %d\n", widget->name, widget->id); - printf("debug: loading widget %s id %d; comp id %d\n", widget->name, widget->id, comp_id); + printf("debug: loading comp_id %d: widget %s id %d\n", + comp_id, widget->name, widget->id); /* load widget based on type */ switch (widget->id) { diff --git a/tools/tune/crossover/example_crossover.m b/tools/tune/crossover/example_crossover.m index f9e2c792fbad..6e9c7e94d60b 100644 --- a/tools/tune/crossover/example_crossover.m +++ b/tools/tune/crossover/example_crossover.m @@ -15,7 +15,7 @@ fc_high = 3000; % 4 way crossover -num_sinks = 2; +num_sinks = 4; % This array is an example on how to assign a buffer from pipeline 1 to output 0, % buffer from pipeline 2 to output 1, etc... % Refer to sof/src/inlude/user/crossover.h for more information on assigning @@ -27,9 +27,9 @@ assign_sinks(4) = 4; % sink[3] % Generate zeros, poles and gain for crossover with the given frequencies -crossover = crossover_gen_coefs(fs, fc_low); % 2 way crossover +%crossover = crossover_gen_coefs(fs, fc_low); % 2 way crossover % crossover = crossover_gen_coefs(fs, fc_low, fc_med); % 3 way crossover -%crossover = crossover_gen_coefs(fs, fc_low, fc_med, fc_high); % 4 way crossover +crossover = crossover_gen_coefs(fs, fc_low, fc_med, fc_high); % 4 way crossover % Convert the [a,b] coefficients to values usable with SOF crossover_bqs = crossover_coef_quant(crossover.lp, crossover.hp);