From 88ee31e1dcedf581c0104cc666bbf2f9bab264bd Mon Sep 17 00:00:00 2001 From: Piotr Hoppe Date: Fri, 30 Oct 2026 19:41:25 +0100 Subject: [PATCH 1/2] audio: selector: fix IPC4 format handling Track container and valid sample formats for source and sink streams. Select processing functions using both formats, prevent invalid passthrough, and propagate the correct format and channel count. Signed-off-by: Piotr Hoppe --- src/audio/selector/selector.c | 13 +++++++++++-- src/audio/selector/selector_generic.c | 16 ++++++++++++---- src/include/sof/audio/selector.h | 9 +++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index e68845ad99ad..0189c7aaa789 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -642,10 +642,12 @@ static void build_config(struct comp_data *cd, struct module_config *cfg) &frame_fmt, &valid_fmt, cfg->base_cfg.audio_fmt.s_type); cd->source_format = frame_fmt; + cd->source_valid_format = valid_fmt; audio_stream_fmt_conversion(out_fmt->depth, out_fmt->valid_bit_depth, &frame_fmt, &valid_fmt, out_fmt->s_type); cd->sink_format = frame_fmt; + cd->sink_valid_format = valid_fmt; cd->config.in_channels_count = cfg->base_cfg.audio_fmt.channels_count; cd->config.out_channels_count = out_fmt->channels_count; @@ -746,7 +748,7 @@ static void set_selector_params(struct processing_module *mod, comp_dev_for_each_consumer(dev, sink_buf) { ipc4_update_buffer_format(sink_buf, out_fmt); - audio_stream_set_channels(&sink_buf->stream, params->channels); + audio_stream_set_channels(&sink_buf->stream, cd->config.out_channels_count); audio_stream_set_rate(&sink_buf->stream, params->rate); } @@ -788,9 +790,11 @@ static int selector_verify_params(struct processing_module *mod, /* apply input/output channels count according to stream direction */ if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { params->channels = out_channels; + params->frame_fmt = cd->sink_format; buffer = comp_dev_get_first_data_consumer(dev); } else { params->channels = in_channels; + params->frame_fmt = cd->source_format; buffer = comp_dev_get_first_data_producer(dev); } buffer_set_params(buffer, params, BUFFER_UPDATE_FORCE); @@ -988,7 +992,9 @@ static int selector_find_coefficients(struct processing_module *mod) /* The pass-through copy function can be used if coefficients are a unit matrix for * 1:1 stream copy. */ - if (source_channels == sink_channels) { + if (source_channels == sink_channels && + cd->source_format == cd->sink_format && + cd->source_valid_format == cd->sink_valid_format) { cd->passthrough = true; for (i = 0; i < sink_channels; i++) { for (j = 0; j < source_channels; j++) { @@ -1113,6 +1119,7 @@ static int selector_prepare(struct processing_module *mod, /* get sink data format and period bytes */ cd->sink_format = audio_stream_get_frm_fmt(&sinkb->stream); + cd->sink_valid_format = audio_stream_get_valid_fmt(&sinkb->stream); cd->sink_period_bytes = audio_stream_period_bytes(&sinkb->stream, dev->frames); sink_size = audio_stream_get_size(&sinkb->stream); md->mpd.in_buff_size = cd->source_period_bytes; @@ -1325,6 +1332,8 @@ static int sel_static_apply_enum(struct comp_dev *dev, uint32_t channel, int32_t * output channel counts match and coeffs form an identity matrix. */ if (cd->config.in_channels_count == cd->config.out_channels_count && + cd->source_format == cd->sink_format && + cd->source_valid_format == cd->sink_valid_format && cd->config.in_channels_count > 0) { cd->passthrough = true; for (uint32_t i = 0; i < cd->config.out_channels_count; i++) { diff --git a/src/audio/selector/selector_generic.c b/src/audio/selector/selector_generic.c index cb6eea84c833..6dd870cb70fd 100644 --- a/src/audio/selector/selector_generic.c +++ b/src/audio/selector/selector_generic.c @@ -440,13 +440,16 @@ const struct comp_func_map func_table[] = { #endif /* CONFIG_FORMAT_S32LE */ #else #if CONFIG_FORMAT_S16LE - {SOF_IPC_FRAME_S16_LE, 0, sel_s16le}, + {SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, + SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE, 0, sel_s16le}, #endif #if CONFIG_FORMAT_S24LE - {SOF_IPC_FRAME_S24_4LE, 0, sel_s24le}, + {SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, + SOF_IPC_FRAME_S24_4LE, SOF_IPC_FRAME_S24_4LE, 0, sel_s24le}, #endif #if CONFIG_FORMAT_S32LE - {SOF_IPC_FRAME_S32_LE, 0, sel_s32le}, + {SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, + SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, 0, sel_s32le}, #endif #endif }; @@ -480,8 +483,13 @@ sel_func sel_get_processing_function(struct processing_module *mod) for (i = 0; i < ARRAY_SIZE(func_table); i++) { if (cd->source_format != func_table[i].source) continue; + if (cd->sink_format != func_table[i].sink) + continue; + if (cd->source_valid_format != func_table[i].source_valid) + continue; + if (cd->sink_valid_format != func_table[i].sink_valid) + continue; - /* TODO: add additional criteria as needed */ return func_table[i].sel_func; } diff --git a/src/include/sof/audio/selector.h b/src/include/sof/audio/selector.h index 17c07e4f86b1..b64a7e33cc9a 100644 --- a/src/include/sof/audio/selector.h +++ b/src/include/sof/audio/selector.h @@ -129,6 +129,10 @@ struct comp_data { uint32_t sink_period_bytes; /**< sink number of period bytes */ enum sof_ipc_frame source_format; /**< source frame format */ enum sof_ipc_frame sink_format; /**< sink frame format */ +#if CONFIG_IPC_MAJOR_4 + enum sof_ipc_frame source_valid_format; /**< source valid sample format */ + enum sof_ipc_frame sink_valid_format; /**< sink valid sample format */ +#endif struct sof_sel_config config; /**< component configuration data */ sel_func sel_func; /**< channel selector processing function */ int num_configs; /**< Number of coefficients sets in configuration blob. */ @@ -139,6 +143,11 @@ struct comp_data { /** \brief Selector processing functions map. */ struct comp_func_map { uint16_t source; /**< source frame format */ +#if CONFIG_IPC_MAJOR_4 + uint16_t sink; /**< sink frame format */ + uint16_t source_valid; /**< source valid sample format */ + uint16_t sink_valid; /**< sink valid sample format */ +#endif uint32_t out_channels; /**< number of output stream channels */ sel_func sel_func; /**< selector processing function */ }; From b733cb0ce47c3980674a7395807876a42e7c2a34 Mon Sep 17 00:00:00 2001 From: Piotr Hoppe Date: Fri, 30 Oct 2026 19:47:49 +0100 Subject: [PATCH 2/2] audio: selector: add S16_LE and S24_4LE conversion Add bidirectional IPC4 sample conversion between S16_LE and S24_4LE. Apply selector coefficients with appropriate rounding and saturation, and register the new processing functions. Signed-off-by: Piotr Hoppe --- src/audio/selector/selector_generic.c | 133 ++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/audio/selector/selector_generic.c b/src/audio/selector/selector_generic.c index 6dd870cb70fd..734af0ec6af4 100644 --- a/src/audio/selector/selector_generic.c +++ b/src/audio/selector/selector_generic.c @@ -292,6 +292,133 @@ static void sel_s16le(struct processing_module *mod, } #endif /* CONFIG_FORMAT_S16LE */ +#if CONFIG_FORMAT_S16LE && CONFIG_FORMAT_S24LE +/** + * \brief Mixing routine with S16_LE to S24_4LE conversion for one frame. + * \param[out] dst Sink buffer. + * \param[in] dst_channels Number of sink channels. + * \param[in] src Source data. + * \param[in] src_channels Number of source channels. + * \param[in] coeffs_config IPC4 micsel config with Q10 coefficients. + */ +static void process_frame_s16le_to_s24le(int32_t *dst, unsigned int dst_channels, + const int16_t *src, unsigned int src_channels, + struct ipc4_selector_coeffs_config *coeffs_config) +{ + int64_t accum; + unsigned int i, j; + + for (i = 0; i < dst_channels; i++) { + accum = 0; + for (j = 0; j < src_channels; j++) + accum += (int32_t)src[j] * (int32_t)coeffs_config->coeffs[i][j]; + + /* Convert Q1.15 * Q6.10 (Q7.25) to Q1.23. */ + dst[i] = sat_int24(sat_int32((accum + (1 << 1)) >> 2)); + } +} + +/** + * \brief Channel selection with S16_LE to S24_4LE conversion. + * \param[in] mod Selector base module device. + * \param[in] source Source circular buffer view. + * \param[in,out] sink Sink circular buffer view. + * \param[in] frames Number of frames to process. + */ +static void sel_s16le_to_s24le(struct processing_module *mod, + const struct cir_buf_source *source, + struct cir_buf_sink *sink, size_t frames) +{ + struct comp_data *cd = module_get_private_data(mod); + const unsigned int src_channels = cd->config.in_channels_count; + const unsigned int dst_channels = cd->config.out_channels_count; + const unsigned int n_chan_source = MIN(SEL_SOURCE_CHANNELS_MAX, src_channels); + const unsigned int n_chan_sink = MIN(SEL_SINK_CHANNELS_MAX, dst_channels); + const int16_t *src = source->ptr; + int32_t *dst = sink->ptr; + size_t i, n, processed = 0; + + while (processed < frames) { + n = frames - processed; + n = MIN(n, cir_buf_samples_without_wrap_s16(src, source->buf_end) / src_channels); + n = MIN(n, cir_buf_samples_without_wrap_s32(dst, sink->buf_end) / dst_channels); + for (i = 0; i < n; i++) { + process_frame_s16le_to_s24le(dst, n_chan_sink, src, n_chan_source, + &cd->coeffs_config); + src += src_channels; + dst += dst_channels; + } + src = source_cir_buf_wrap(src, source->buf_start, source->buf_end); + dst = cir_buf_wrap(dst, sink->buf_start, sink->buf_end); + processed += n; + } +} +#endif /* CONFIG_FORMAT_S16LE && CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S16LE && CONFIG_FORMAT_S24LE +/** + * \brief Mixing routine with S24_4LE to S16_LE conversion for one frame. + * \param[out] dst Sink buffer. + * \param[in] dst_channels Number of sink channels. + * \param[in] src Source data. + * \param[in] src_channels Number of source channels. + * \param[in] coeffs_config IPC4 micsel config with Q10 coefficients. + */ +static void process_frame_s24le_to_s16le(int16_t *dst, unsigned int dst_channels, + const int32_t *src, unsigned int src_channels, + struct ipc4_selector_coeffs_config *coeffs_config) +{ + int64_t accum; + unsigned int i, j; + + for (i = 0; i < dst_channels; i++) { + accum = 0; + for (j = 0; j < src_channels; j++) + accum += (int64_t)sign_extend_s24(src[j]) * + (int32_t)coeffs_config->coeffs[i][j]; + + /* Convert Q1.23 * Q6.10 (Q7.33) to Q1.15. */ + dst[i] = sat_int16(Q_SHIFT_RND(accum, 33, 15)); + } +} + +/** + * \brief Channel selection with S24_4LE to S16_LE conversion. + * \param[in] mod Selector base module device. + * \param[in] source Source circular buffer view. + * \param[in,out] sink Sink circular buffer view. + * \param[in] frames Number of frames to process. + */ +static void sel_s24le_to_s16le(struct processing_module *mod, + const struct cir_buf_source *source, + struct cir_buf_sink *sink, size_t frames) +{ + struct comp_data *cd = module_get_private_data(mod); + const unsigned int src_channels = cd->config.in_channels_count; + const unsigned int dst_channels = cd->config.out_channels_count; + const unsigned int n_chan_source = MIN(SEL_SOURCE_CHANNELS_MAX, src_channels); + const unsigned int n_chan_sink = MIN(SEL_SINK_CHANNELS_MAX, dst_channels); + const int32_t *src = source->ptr; + int16_t *dst = sink->ptr; + size_t i, n, processed = 0; + + while (processed < frames) { + n = frames - processed; + n = MIN(n, cir_buf_samples_without_wrap_s32(src, source->buf_end) / src_channels); + n = MIN(n, cir_buf_samples_without_wrap_s16(dst, sink->buf_end) / dst_channels); + for (i = 0; i < n; i++) { + process_frame_s24le_to_s16le(dst, n_chan_sink, src, n_chan_source, + &cd->coeffs_config); + src += src_channels; + dst += dst_channels; + } + src = source_cir_buf_wrap(src, source->buf_start, source->buf_end); + dst = cir_buf_wrap(dst, sink->buf_start, sink->buf_end); + processed += n; + } +} +#endif /* CONFIG_FORMAT_S16LE && CONFIG_FORMAT_S24LE */ + #if CONFIG_FORMAT_S24LE /** * \brief Mixing routine for 24-bit, m channel input x n channel output single frame. @@ -451,6 +578,12 @@ const struct comp_func_map func_table[] = { {SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE, 0, sel_s32le}, #endif +#if CONFIG_FORMAT_S16LE && CONFIG_FORMAT_S24LE + {SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S32_LE, + SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S24_4LE, 0, sel_s16le_to_s24le}, + {SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S16_LE, + SOF_IPC_FRAME_S24_4LE, SOF_IPC_FRAME_S16_LE, 0, sel_s24le_to_s16le}, +#endif #endif };