From 55918ad0f26153642bf515fcd38dacb6e1acbdad Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Mon, 11 May 2026 22:17:53 +0300 Subject: [PATCH 1/3] audio: src: move filter and delay line allocation to init phase Refactor SRC and SRC-lite so that filter stage setup and delay line allocation happen during the module init callback instead of prepare. This ensures the bulk of SRC memory allocation occurs while the vregion allocator is still in its lifetime phase, before the interim heap is created. The allocations then persist across prepare/reset cycles without needing to be re-allocated each time. A new setup_stages() callback is added to struct comp_data, set by each variant (src.c, src_lite.c) to point at its own coefficient tables. The common src_allocate_delay_lines() is factored out of the old prepare path into src_common.c. For IPC4, src_init_stages() calls setup_stages() and src_allocate_delay_lines() at init time. The prepare path (src_prepare_do) only validates rates and sets downstream params. For IPC3, src_init_stages() is a no-op and src_prepare_do() retains the original behavior of doing full setup at prepare time, since IPC3 cannot be tested at this time. Signed-off-by: Jyri Sarha --- src/audio/src/src.c | 52 ++++++++++++++++--------- src/audio/src/src_common.c | 80 ++++++++++++++++++++++++++++++++++++++ src/audio/src/src_common.h | 5 +++ src/audio/src/src_ipc3.c | 24 ++++++++++++ src/audio/src/src_ipc4.c | 63 ++++++++++++++++++++++++++++++ src/audio/src/src_lite.c | 53 +++++++++++++++---------- 6 files changed, 239 insertions(+), 38 deletions(-) diff --git a/src/audio/src/src.c b/src/audio/src/src.c index 9870ad911ca8..9c16bc887903 100644 --- a/src/audio/src/src.c +++ b/src/audio/src/src.c @@ -34,19 +34,16 @@ LOG_MODULE_DECLARE(src, CONFIG_SOF_LOG_LEVEL); -static int src_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. + */ +static int src_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -54,27 +51,46 @@ static int src_prepare(struct processing_module *mod, a->max_fir_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_FIR_DELAY_SIZE); a->max_out_delay_size_xnch = (PLATFORM_MAX_CHANNELS * MAX_OUT_DELAY_SIZE); - src_get_source_sink_params(mod->dev, sources[0], sinks[0]); - ret = src_param_set(mod->dev, cd); if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} - ret = src_params_general(mod, sources[0], sinks[0]); +static int src_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; + + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_setup_stages; + + return src_init_stages(mod); +} + +static int src_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_do_prepare(mod, sources[0], sinks[0]); } static const struct module_interface src_interface = { - .init = src_init, + .init = src_do_init, .prepare = src_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process, diff --git a/src/audio/src/src_common.c b/src/audio/src/src_common.c index 3a9c7dac280f..e4ccf76ae3ba 100644 --- a/src/audio/src/src_common.c +++ b/src/audio/src/src_common.c @@ -574,6 +574,86 @@ int src_params_general(struct processing_module *mod, return 0; } +/* Allocate delay lines and initialize the polyphase SRC filter. + * Assumes that cd->param rate table pointers (in_fs, out_fs, etc.) + * and stage pointers (stage1, stage2) are already set up via + * cd->setup_stages(). + */ +int src_allocate_delay_lines(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + size_t delay_lines_size; + int32_t *buffer_start; + int n; + int ret; + + /* For LL modules dev->period is already set from the pipeline. + * Compute dev->frames so buffer sizing works. + */ + if (!dev->frames) + component_set_nearest_period_frames(dev, cd->sink_rate); + + if (!cd->sink_rate) { + comp_err(dev, "zero sink rate"); + return -EINVAL; + } + + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + /* Allocate needed memory for delay lines */ + ret = src_buffer_lengths(dev, cd, cd->channels_count); + if (ret < 0) { + comp_err(dev, "src_buffer_lengths() failed"); + return ret; + } + + delay_lines_size = ALIGN_UP(sizeof(int32_t) * cd->param.total, 8); + if (delay_lines_size == 0) { + comp_err(dev, "delay_lines_size = 0"); + return -EINVAL; + } + + mod_free(mod, cd->delay_lines); + + cd->delay_lines = mod_alloc(mod, delay_lines_size); + if (!cd->delay_lines) { + comp_err(dev, "failed to alloc cd->delay_lines, delay_lines_size = %zu", + delay_lines_size); + return -ENOMEM; + } + + memset(cd->delay_lines, 0, delay_lines_size); + buffer_start = cd->delay_lines + ALIGN_UP(cd->param.sbuf_length, 2); + + /* Initialize SRC for actual sample rate */ + n = src_polyphase_init(&cd->src, &cd->param, buffer_start); + + /* Reset stage buffer */ + cd->sbuf_r_ptr = cd->delay_lines; + cd->sbuf_w_ptr = cd->delay_lines; + cd->sbuf_avail = 0; + + switch (n) { + case 0: + cd->src_func = src_copy_sxx; + break; + case 1: + cd->src_func = src_1s; + break; + case 2: + cd->src_func = src_2s; + break; + default: + comp_info(dev, "missing coefficients for requested rates combination"); + cd->src_func = src_fallback; + return -EINVAL; + } + + return 0; +} + int src_param_set(struct comp_dev *dev, struct comp_data *cd) { struct src_param *a = &cd->param; diff --git a/src/audio/src/src_common.h b/src/audio/src/src_common.h index 98f29a263131..7e1dde6e080c 100644 --- a/src/audio/src/src_common.h +++ b/src/audio/src/src_common.h @@ -167,6 +167,7 @@ struct comp_data { int (*src_func)(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); void (*polyphase_func)(struct src_stage_prm *s); + int (*setup_stages)(struct processing_module *mod); }; #if CONFIG_IPC_MAJOR_4 @@ -218,6 +219,7 @@ static inline int src_fallback(struct comp_data *cd, int src_allocate_copy_stages(struct processing_module *mod, struct src_param *prm, const struct src_stage *stage_src1, const struct src_stage *stage_src2); +int src_allocate_delay_lines(struct processing_module *mod); int src_rate_check(const void *spec); int src_set_params(struct processing_module *mod, struct sof_sink *sink); @@ -227,6 +229,9 @@ int src_prepare_general(struct processing_module *mod, struct sof_source *source, struct sof_sink *sink); int src_init(struct processing_module *mod); +int src_init_stages(struct processing_module *mod); +int src_do_prepare(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink); int src_copy_sxx(struct comp_data *cd, struct sof_source *source, struct sof_sink *sink); diff --git a/src/audio/src/src_ipc3.c b/src/audio/src/src_ipc3.c index 541efcdb24be..fbc12909e4eb 100644 --- a/src/audio/src/src_ipc3.c +++ b/src/audio/src/src_ipc3.c @@ -195,3 +195,27 @@ int src_init(struct processing_module *mod) return 0; } +/* IPC3: No filter allocation at init, change ipc3 behavior as little as possible */ +int src_init_stages(struct processing_module *mod) +{ + return 0; +} + +/* IPC3: Full filter setup at prepare time */ +int src_do_prepare(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + ret = src_params_general(mod, source, sink); + if (ret < 0) + return ret; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_ipc4.c b/src/audio/src/src_ipc4.c index 91f286347a5f..11095843501e 100644 --- a/src/audio/src/src_ipc4.c +++ b/src/audio/src/src_ipc4.c @@ -246,3 +246,66 @@ __cold int src_init(struct processing_module *mod) return 0; } +/* Called after src_init() and setup_stages callback is set. + * Allocate filter stages and delay lines at init time. + */ +int src_init_stages(struct processing_module *mod) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + ret = cd->setup_stages(mod); + if (ret < 0) + return ret; + + /* For DP modules, dev->period is not yet set at init time (it's + * computed in src_set_params at prepare). Derive it here from the + * IPC config's output buffer size so that delay line allocation + * uses correct buffer sizes. + */ + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP && !dev->frames) { + uint32_t frame_bytes = cd->channels_count * cd->sample_container_bytes; + + if (frame_bytes && cd->sink_rate) { + dev->period = 1000000ULL * cd->ipc_config.base.obs / + ((uint64_t)frame_bytes * cd->sink_rate); + dev->period /= LL_TIMER_PERIOD_US; + dev->period *= LL_TIMER_PERIOD_US; + component_set_nearest_period_frames(dev, cd->sink_rate); + } + } + + return src_allocate_delay_lines(mod); +} + +/* At prepare time just verify rates and set downstream params */ +int src_do_prepare(struct processing_module *mod, + struct sof_source *source, struct sof_sink *sink) +{ + struct comp_data *cd = module_get_private_data(mod); + struct comp_dev *dev = mod->dev; + int ret; + + if (cd->source_rate != cd->ipc_config.base.audio_fmt.sampling_frequency || + cd->sink_rate != cd->ipc_config.sink_rate) { + comp_err(mod->dev, "rate mismatch: source %u/%u sink %u/%u", + cd->source_rate, + cd->ipc_config.base.audio_fmt.sampling_frequency, + cd->sink_rate, cd->ipc_config.sink_rate); + return -EINVAL; + } + + ret = src_set_params(mod, sink); + if (ret < 0) { + comp_err(mod->dev, "set params failed."); + return ret; + } + + /* Update frame counts with final dev->frames from src_set_params */ + cd->source_frames = dev->frames * cd->source_rate / cd->sink_rate; + cd->sink_frames = dev->frames; + + return src_prepare_general(mod, source, sink); +} + diff --git a/src/audio/src/src_lite.c b/src/audio/src/src_lite.c index 9d5593ff34ca..31f8ab0acf7e 100644 --- a/src/audio/src/src_lite.c +++ b/src/audio/src/src_lite.c @@ -14,24 +14,16 @@ LOG_MODULE_REGISTER(src_lite, CONFIG_SOF_LOG_LEVEL); -/* - * This function is 100% identical to src_prepare(), but it's - * assigning different coefficient arrays because it's including - * different headers. +/* Set rate table pointers, compute rate indices, and copy filter stages. + * Must be in src_lite.c because src_table1/2, src_in_fs, etc. come from + * the coefficient headers included by this file. */ -static int src_lite_prepare(struct processing_module *mod, - struct sof_source **sources, int num_of_sources, - struct sof_sink **sinks, int num_of_sinks) +static int src_lite_setup_stages(struct processing_module *mod) { struct comp_data *cd = module_get_private_data(mod); struct src_param *a = &cd->param; int ret; - comp_info(mod->dev, "entry"); - - if (num_of_sources != 1 || num_of_sinks != 1) - return -EINVAL; - a->in_fs = src_in_fs; a->out_fs = src_out_fs; a->num_in_fs = NUM_IN_FS; @@ -43,21 +35,42 @@ static int src_lite_prepare(struct processing_module *mod, if (ret < 0) return ret; - ret = src_allocate_copy_stages(mod, a, - src_table1[a->idx_out][a->idx_in], - src_table2[a->idx_out][a->idx_in]); - if (ret < 0) - return ret; + return src_allocate_copy_stages(mod, a, + src_table1[a->idx_out][a->idx_in], + src_table2[a->idx_out][a->idx_in]); +} + +static int src_lite_do_init(struct processing_module *mod) +{ + struct comp_data *cd; + int ret; - ret = src_params_general(mod, sources[0], sinks[0]); + ret = src_init(mod); if (ret < 0) return ret; - return src_prepare_general(mod, sources[0], sinks[0]); + cd = module_get_private_data(mod); + cd->setup_stages = src_lite_setup_stages; + + return src_init_stages(mod); +} + +static int src_lite_prepare(struct processing_module *mod, + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) +{ + comp_info(mod->dev, "entry"); + + if (num_of_sources != 1 || num_of_sinks != 1) + return -EINVAL; + + src_get_source_sink_params(mod->dev, sources[0], sinks[0]); + + return src_do_prepare(mod, sources[0], sinks[0]); } const struct module_interface src_lite_interface = { - .init = src_init, + .init = src_lite_do_init, .prepare = src_lite_prepare, .process = src_process, .is_ready_to_process = src_is_ready_to_process, From 79d755661b381ee6b0e7bdf23d38f946c729fb75 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 2 Jul 2026 19:40:57 +0300 Subject: [PATCH 2/3] audio: src: preserve filter setup across reset cycles Now that filter stage setup and delay line allocation happen at init time, src_reset() must not destroy that state. Replace the call to src_polyphase_reset() (which NULLs stage pointers and zeroes sizes) with the new src_polyphase_reset_state() that only: - zeroes the delay line memory contents (clearing filter history), - resets fir_wp / out_rp pointers to their initial positions, - resets the inter-stage buffer pointers and counter. The filter stage descriptors, delay line pointers, and their sizes are preserved so the next prepare/trigger cycle can resume without re-allocating or re-initializing the filters. Signed-off-by: Jyri Sarha --- src/audio/src/src_common.c | 7 ++++++- src/audio/src/src_common.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/audio/src/src_common.c b/src/audio/src/src_common.c index e4ccf76ae3ba..36122cc530cd 100644 --- a/src/audio/src/src_common.c +++ b/src/audio/src/src_common.c @@ -755,7 +755,12 @@ int src_reset(struct processing_module *mod) comp_info(mod->dev, "entry"); cd->src_func = src_fallback; - src_polyphase_reset(&cd->src); + src_polyphase_reset_state(&cd->src); + + /* Reset stage buffer */ + cd->sbuf_r_ptr = cd->delay_lines; + cd->sbuf_w_ptr = cd->delay_lines; + cd->sbuf_avail = 0; return 0; } diff --git a/src/audio/src/src_common.h b/src/audio/src/src_common.h index 7e1dde6e080c..2ef82fe20358 100644 --- a/src/audio/src/src_common.h +++ b/src/audio/src/src_common.h @@ -128,6 +128,37 @@ static inline void src_polyphase_reset(struct polyphase_src *src) src_state_reset(&src->state2); } +/** + * src_polyphase_reset_state - reset filter state while preserving structure + * + * Zeros the delay line contents and resets read/write pointers to their + * initial positions. The filter stage pointers, delay line allocations, + * and sizes are preserved. + */ +static inline void src_polyphase_reset_state(struct polyphase_src *src) +{ + struct src_state *s1 = &src->state1; + struct src_state *s2 = &src->state2; + + if (s1->fir_delay && s1->fir_delay_size) + memset(s1->fir_delay, 0, sizeof(int32_t) * s1->fir_delay_size); + + if (s1->out_delay && s1->out_delay_size) + memset(s1->out_delay, 0, sizeof(int32_t) * s1->out_delay_size); + + s1->fir_wp = s1->fir_delay ? &s1->fir_delay[s1->fir_delay_size - 1] : NULL; + s1->out_rp = s1->out_delay; + + if (s2->fir_delay && s2->fir_delay_size) + memset(s2->fir_delay, 0, sizeof(int32_t) * s2->fir_delay_size); + + if (s2->out_delay && s2->out_delay_size) + memset(s2->out_delay, 0, sizeof(int32_t) * s2->out_delay_size); + + s2->fir_wp = s2->fir_delay ? &s2->fir_delay[s2->fir_delay_size - 1] : NULL; + s2->out_rp = s2->out_delay; +} + int src_polyphase(struct polyphase_src *src, int32_t x[], int32_t y[], int n_in); From 056152e069fc02d0817658d4af7335b1264c9849 Mon Sep 17 00:00:00 2001 From: Jyri Sarha Date: Thu, 2 Jul 2026 19:50:28 +0300 Subject: [PATCH 3/3] audio: src: validate stream parameters at prepare time Check that the source stream rate and channel count at prepare match what was used to initialize the filter stages and delay lines. Since these allocations now happen at init and persist across prepare/reset cycles, a mismatch would cause incorrect processing or buffer overruns. Also compare rates against the actual source stream rather than only the IPC config, catching cases where the upstream pipeline delivers a different rate than originally configured. Signed-off-by: Jyri Sarha --- src/audio/src/src_ipc4.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/audio/src/src_ipc4.c b/src/audio/src/src_ipc4.c index 11095843501e..79a312418e5c 100644 --- a/src/audio/src/src_ipc4.c +++ b/src/audio/src/src_ipc4.c @@ -285,14 +285,21 @@ int src_do_prepare(struct processing_module *mod, { struct comp_data *cd = module_get_private_data(mod); struct comp_dev *dev = mod->dev; + unsigned int source_channels = source_get_channels(source); + unsigned int source_rate = source_get_rate(source); int ret; - if (cd->source_rate != cd->ipc_config.base.audio_fmt.sampling_frequency || + if (cd->source_rate != source_rate || cd->sink_rate != cd->ipc_config.sink_rate) { - comp_err(mod->dev, "rate mismatch: source %u/%u sink %u/%u", - cd->source_rate, - cd->ipc_config.base.audio_fmt.sampling_frequency, - cd->sink_rate, cd->ipc_config.sink_rate); + comp_err(dev, "rate mismatch: init %u/%u, stream %u/%u", + cd->source_rate, cd->sink_rate, + source_rate, cd->ipc_config.sink_rate); + return -EINVAL; + } + + if (cd->channels_count != (int)source_channels) { + comp_err(dev, "channels mismatch: init %d, stream %u", + cd->channels_count, source_channels); return -EINVAL; }