-
Notifications
You must be signed in to change notification settings - Fork 349
Audio: template_comp: Add a new SOF template component #9952
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| if(CONFIG_COMP_TEMPLATE_COMP STREQUAL "m") | ||
| add_subdirectory(llext ${PROJECT_BINARY_DIR}/template_comp_llext) | ||
| add_dependencies(app template_comp) | ||
| else() | ||
| add_local_sources(sof template.c) | ||
| add_local_sources(sof template-generic.c) | ||
|
|
||
| if(CONFIG_IPC_MAJOR_3) | ||
| add_local_sources(sof template-ipc3.c) | ||
| elseif(CONFIG_IPC_MAJOR_4) | ||
| add_local_sources(sof template-ipc4.c) | ||
| endif() | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| config COMP_TEMPLATE_COMP | ||
| tristate "Template_comp example component" | ||
| default y | ||
| help | ||
| Select for template_comp component. Reason for existence | ||
| is to provide a minimal component example and use as | ||
| placeholder in processing pipelines. As example processing | ||
| it swaps or reverses the channels when the switch control | ||
| is enabled. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Copyright (c) 2025 Intel Corporation. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| sof_llext_build("template_comp" | ||
| SOURCES ../template_comp.c | ||
| LIB openmodules | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #include <tools/rimage/config/platform.toml> | ||
| #define LOAD_TYPE "2" | ||
| #include "../template_comp.toml" | ||
|
|
||
| [module] | ||
| count = __COUNTER__ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // | ||
| // Copyright(c) 2025 Intel Corporation. | ||
|
|
||
| #include <sof/audio/module_adapter/module/generic.h> | ||
| #include <sof/audio/component.h> | ||
| #include <sof/audio/sink_api.h> | ||
| #include <sof/audio/sink_source_utils.h> | ||
| #include <sof/audio/source_api.h> | ||
| #include <stdint.h> | ||
| #include "template.h" | ||
|
|
||
| #if CONFIG_FORMAT_S16LE | ||
| /** | ||
| * template_comp_s16() - Process S16_LE format. | ||
| * @mod: Pointer to module data. | ||
| * @source: Source for PCM samples data. | ||
| * @sink: Sink for PCM samples data. | ||
| * @frames: Number of audio data frames to process. | ||
| * | ||
| * This is the processing function for 16-bit signed integer PCM formats. The | ||
| * audio samples in every frame are re-order to channels order defined in | ||
| * component data channel_map[]. | ||
| * | ||
| * Return: Value zero for success, otherwise an error code. | ||
| */ | ||
| static int template_comp_s16(const struct processing_module *mod, | ||
| struct sof_source *source, | ||
| struct sof_sink *sink, | ||
| uint32_t frames) | ||
| { | ||
| struct template_comp_comp_data *cd = module_get_private_data(mod); | ||
| int16_t *x, *x_start, *x_end; | ||
| int16_t *y, *y_start, *y_end; | ||
| size_t size; | ||
| int x_size, y_size; | ||
| int source_samples_without_wrap; | ||
| int samples_without_wrap; | ||
| int samples = frames * cd->channels; | ||
| int bytes = frames * cd->frame_bytes; | ||
| int ret; | ||
| int ch; | ||
| int i; | ||
|
|
||
| /* Get pointer to source data in circular buffer, get buffer start and size to | ||
| * check for wrap. The size in bytes is converted to number of s16 samples to | ||
| * control the samples process loop. If the number of bytes requested is not | ||
| * possible, an error is returned. | ||
| */ | ||
| ret = source_get_data(source, bytes, (void const **)&x, (void const **)&x_start, &size); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| x_size = size >> 1; /* Bytes to number of s16 samples */ | ||
|
|
||
| /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ | ||
| ret = sink_get_buffer(sink, bytes, (void **)&y, (void **)&y_start, &size); | ||
| if (ret) | ||
| return ret; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto, and in other formats below too of course
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, fixed now. |
||
|
|
||
| y_size = size >> 1; /* Bytes to number of s16 samples */ | ||
|
|
||
| /* Set helper pointers to buffer end for wrap check. Then loop until all | ||
| * samples are processed. | ||
| */ | ||
| x_end = x_start + x_size; | ||
| y_end = y_start + y_size; | ||
| while (samples) { | ||
| /* Find out samples to process before first wrap or end of data. */ | ||
| source_samples_without_wrap = x_end - x; | ||
| samples_without_wrap = y_end - y; | ||
| samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); | ||
| samples_without_wrap = MIN(samples_without_wrap, samples); | ||
|
|
||
| /* Since the example processing is for frames of audio channels, process | ||
| * with step of channels count. | ||
| */ | ||
| for (i = 0; i < samples_without_wrap; i += cd->channels) { | ||
| /* In inner loop process the frame. As example re-arrange the channels | ||
| * as defined in array channel_map[]. | ||
| */ | ||
| for (ch = 0; ch < cd->channels; ch++) { | ||
| *y = *(x + cd->channel_map[ch]); | ||
| y++; | ||
| } | ||
| x += cd->channels; | ||
| } | ||
|
|
||
| /* One of the buffers needs a wrap (or end of data), so check for wrap */ | ||
| x = (x >= x_end) ? x - x_size : x; | ||
| y = (y >= y_end) ? y - y_size : y; | ||
|
|
||
| /* Update processed samples count for next loop iteration. */ | ||
| samples -= samples_without_wrap; | ||
| } | ||
|
|
||
| /* Update the source and sink for bytes consumed and produced. Return success. */ | ||
| source_release_data(source, bytes); | ||
| sink_commit_buffer(sink, bytes); | ||
| return 0; | ||
| } | ||
| #endif /* CONFIG_FORMAT_S16LE */ | ||
|
|
||
| #if CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S32LE | ||
| /** | ||
| * template_comp_s32() - Process S32_LE or S24_4LE format. | ||
| * @mod: Pointer to module data. | ||
| * @source: Source for PCM samples data. | ||
| * @sink: Sink for PCM samples data. | ||
| * @frames: Number of audio data frames to process. | ||
| * | ||
| * Processing function for signed integer 32-bit PCM formats. The same | ||
| * function works for s24 and s32 formats since the samples values are | ||
| * not modified in computation. The audio samples in every frame are | ||
| * re-order to channels order defined in component data channel_map[]. | ||
| * | ||
| * Return: Value zero for success, otherwise an error code. | ||
| */ | ||
| static int template_comp_s32(const struct processing_module *mod, | ||
| struct sof_source *source, | ||
| struct sof_sink *sink, | ||
| uint32_t frames) | ||
| { | ||
| struct template_comp_comp_data *cd = module_get_private_data(mod); | ||
| int32_t *x, *x_start, *x_end; | ||
| int32_t *y, *y_start, *y_end; | ||
| size_t size; | ||
| int x_size, y_size; | ||
| int source_samples_without_wrap; | ||
| int samples_without_wrap; | ||
| int samples = frames * cd->channels; | ||
| int bytes = frames * cd->frame_bytes; | ||
| int ret; | ||
| int ch; | ||
| int i; | ||
|
|
||
| /* Get pointer to source data in circular buffer, get buffer start and size to | ||
| * check for wrap. The size in bytes is converted to number of s16 samples to | ||
| * control the samples process loop. If the number of bytes requested is not | ||
| * possible, an error is returned. | ||
| */ | ||
| ret = source_get_data(source, bytes, (void const **)&x, (void const **)&x_start, &size); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| x_size = size >> 2; /* Bytes to number of s32 samples */ | ||
|
|
||
| /* Similarly get pointer to sink data in circular buffer, buffer start and size. */ | ||
| ret = sink_get_buffer(sink, bytes, (void **)&y, (void **)&y_start, &size); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| y_size = size >> 2; /* Bytes to number of s32 samples */ | ||
|
|
||
| /* Set helper pointers to buffer end for wrap check. Then loop until all | ||
| * samples are processed. | ||
| */ | ||
| x_end = x_start + x_size; | ||
| y_end = y_start + y_size; | ||
| while (samples) { | ||
| /* Find out samples to process before first wrap or end of data. */ | ||
| source_samples_without_wrap = x_end - x; | ||
| samples_without_wrap = y_end - y; | ||
| samples_without_wrap = MIN(samples_without_wrap, source_samples_without_wrap); | ||
| samples_without_wrap = MIN(samples_without_wrap, samples); | ||
|
|
||
| /* Since the example processing is for frames of audio channels, process | ||
| * with step of channels count. | ||
| */ | ||
| for (i = 0; i < samples_without_wrap; i += cd->channels) { | ||
| /* In inner loop process the frame. As example re-arrange the channels | ||
| * as defined in array channel_map[]. | ||
| */ | ||
| for (ch = 0; ch < cd->channels; ch++) { | ||
| *y = *(x + cd->channel_map[ch]); | ||
| y++; | ||
| } | ||
| x += cd->channels; | ||
| } | ||
|
|
||
| /* One of the buffers needs a wrap (or end of data), so check for wrap */ | ||
| x = (x >= x_end) ? x - x_size : x; | ||
| y = (y >= y_end) ? y - y_size : y; | ||
|
|
||
| /* Update processed samples count for next loop iteration. */ | ||
| samples -= samples_without_wrap; | ||
| } | ||
|
|
||
| /* Update the source and sink for bytes consumed and produced. Return success. */ | ||
| source_release_data(source, bytes); | ||
| sink_commit_buffer(sink, bytes); | ||
| return 0; | ||
| } | ||
| #endif /* CONFIG_FORMAT_S32LE || CONFIG_FORMAT_S24LE */ | ||
|
|
||
| /* This struct array defines the used processing functions for | ||
| * the PCM formats | ||
| */ | ||
| const struct template_comp_proc_fnmap template_comp_proc_fnmap[] = { | ||
| #if CONFIG_FORMAT_S16LE | ||
| { SOF_IPC_FRAME_S16_LE, template_comp_s16 }, | ||
| #endif | ||
| #if CONFIG_FORMAT_S24LE | ||
| { SOF_IPC_FRAME_S24_4LE, template_comp_s32 }, | ||
| #endif | ||
| #if CONFIG_FORMAT_S32LE | ||
| { SOF_IPC_FRAME_S32_LE, template_comp_s32 }, | ||
| #endif | ||
| }; | ||
|
|
||
| /** | ||
| * template_comp_find_proc_func() - Find suitable processing function. | ||
| * @src_fmt: Enum value for PCM format. | ||
| * | ||
| * This function finds the suitable processing function to use for | ||
| * the used PCM format. If not found, return NULL. | ||
| * | ||
| * Return: Pointer to processing function for the requested PCM format. | ||
| */ | ||
| template_comp_func template_comp_find_proc_func(enum sof_ipc_frame src_fmt) | ||
| { | ||
| int i; | ||
|
|
||
| /* Find suitable processing function from map */ | ||
| for (i = 0; i < ARRAY_SIZE(template_comp_proc_fnmap); i++) | ||
| if (src_fmt == template_comp_proc_fnmap[i].frame_fmt) | ||
| return template_comp_proc_fnmap[i].template_comp_proc_func; | ||
|
|
||
| return NULL; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| // SPDX-License-Identifier: BSD-3-Clause | ||
| // | ||
| // Copyright(c) 2025 Intel Corporation. | ||
|
|
||
| #include <sof/audio/module_adapter/module/generic.h> | ||
| #include <sof/audio/component.h> | ||
| #include "template.h" | ||
|
|
||
| LOG_MODULE_DECLARE(template_comp, CONFIG_SOF_LOG_LEVEL); | ||
|
|
||
| /* This function handles the real-time controls. The ALSA controls have the | ||
| * param_id set to indicate the control type. The control ID, from topology, | ||
| * is used to separate the controls instances of same type. In control payload | ||
| * the num_elems defines to how many channels the control is applied to. | ||
| */ | ||
| __cold int template_comp_set_config(struct processing_module *mod, uint32_t param_id, | ||
| enum module_cfg_fragment_position pos, | ||
| uint32_t data_offset_size, const uint8_t *fragment, | ||
| size_t fragment_size, uint8_t *response, | ||
| size_t response_size) | ||
| { | ||
| struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)fragment; | ||
| struct template_comp_comp_data *cd = module_get_private_data(mod); | ||
| struct comp_dev *dev = mod->dev; | ||
|
|
||
| assert_can_be_cold(); | ||
|
|
||
| comp_dbg(dev, "template_comp_set_config()"); | ||
|
|
||
| switch (cdata->cmd) { | ||
| case SOF_CTRL_CMD_SWITCH: | ||
| if (cdata->index != 0) { | ||
| comp_err(dev, "Illegal switch control index = %d.", cdata->index); | ||
| return -EINVAL; | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto - you also save 2 levels of indentation
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, good finding. Note that I wanted to keep |
||
|
|
||
| if (cdata->num_elems != 1) { | ||
| comp_err(dev, "Illegal switch control num_elems = %d.", cdata->num_elems); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| cd->enable = cdata->chanv[0].value; | ||
| comp_info(dev, "Setting enable = %d.", cd->enable); | ||
| return 0; | ||
|
|
||
| case SOF_CTRL_CMD_ENUM: | ||
| comp_err(dev, "Illegal enum control, no support in this component."); | ||
| return -EINVAL; | ||
| case SOF_CTRL_CMD_BINARY: | ||
| comp_err(dev, "Illegal bytes control, no support in this component."); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| comp_err(dev, "Illegal control, unknown type."); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| __cold int template_comp_get_config(struct processing_module *mod, | ||
| uint32_t config_id, uint32_t *data_offset_size, | ||
| uint8_t *fragment, size_t fragment_size) | ||
| { | ||
| struct sof_ipc_ctrl_data *cdata = (struct sof_ipc_ctrl_data *)fragment; | ||
| struct template_comp_comp_data *cd = module_get_private_data(mod); | ||
| struct comp_dev *dev = mod->dev; | ||
|
|
||
| assert_can_be_cold(); | ||
|
|
||
| comp_info(dev, "template_comp_get_config()"); | ||
|
|
||
| switch (cdata->cmd) { | ||
| case SOF_CTRL_CMD_SWITCH: | ||
| if (cdata->index != 0) { | ||
| comp_err(dev, "Illegal switch control index = %d.", cdata->index); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| if (cdata->num_elems != 1) { | ||
| comp_err(dev, "Illegal switch control num_elems = %d.", cdata->num_elems); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| cdata->chanv[0].value = cd->enable; | ||
| return 0; | ||
|
|
||
| case SOF_CTRL_CMD_ENUM: | ||
| comp_err(dev, "Illegal enum control, no support in this component."); | ||
| return -EINVAL; | ||
|
|
||
| case SOF_CTRL_CMD_BINARY: | ||
| comp_err(dev, "Illegal bytes control, no support in this component."); | ||
| return -EINVAL; | ||
| } | ||
|
|
||
| comp_err(dev, "Illegal control, unknown type."); | ||
| return -EINVAL; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be better to put
if (ret) return ret;directly after line 50, beforex_sizecalculation. Particularly since this is a template, so a copy-paste sourceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, though I tried to make more compact looking code this way.