-
Notifications
You must be signed in to change notification settings - Fork 349
Audio: Selector: Updates to use selector/micsel component for multi-channel audio up/downmix in SOF topologies #10039
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
8501b10
e8222ce
b74b5c6
0a70397
8b278b4
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 |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
|
|
||
| #include <sof/audio/buffer.h> | ||
| #include <sof/audio/component.h> | ||
| #include <sof/audio/format.h> | ||
| #include <sof/audio/selector.h> | ||
| #include <sof/common.h> | ||
| #include <ipc/stream.h> | ||
|
|
@@ -191,7 +192,7 @@ static void process_frame_s16le(int16_t dst[], int dst_channels, | |
| accum += (int32_t)src[j] * (int32_t)coeffs_config->coeffs[i][j]; | ||
|
|
||
| /* shift out 10 LSbits with rounding to get 16-bit result */ | ||
| dst[i] = (int16_t)((accum + (1 << 9)) >> 10); | ||
| dst[i] = sat_int16((accum + (1 << 9)) >> 10); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -240,7 +241,80 @@ static void sel_s16le(struct processing_module *mod, struct input_stream_buffer | |
| } | ||
| #endif /* CONFIG_FORMAT_S16LE */ | ||
|
|
||
| #if CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE | ||
| #if CONFIG_FORMAT_S24LE | ||
| /** | ||
| * \brief Mixing routine for 24-bit, m channel input x n channel output single 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(int32_t dst[], int dst_channels, | ||
| int32_t src[], int src_channels, | ||
| struct ipc4_selector_coeffs_config *coeffs_config) | ||
| { | ||
| int64_t accum; | ||
| int i, j; | ||
|
|
||
| for (i = 0; i < dst_channels; i++) { | ||
| accum = 0; | ||
| for (j = 0; j < src_channels; j++) | ||
| accum += (int64_t)src[j] * (int64_t)coeffs_config->coeffs[i][j]; | ||
|
|
||
| /* accum is Q1.23 * Q6.10 --> Q7.33, shift right by 10 and | ||
| * saturate to get Q1.23. | ||
| */ | ||
| dst[i] = sat_int24((accum + (1 << 9)) >> 10); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * \brief Channel selection for 24-bit, m channel input x n channel output data format. | ||
| * \param[in] mod Selector base module device. | ||
| * \param[in,out] bsource Source buffer. | ||
| * \param[in,out] bsink Sink buffer. | ||
| * \param[in] frames Number of frames to process. | ||
| */ | ||
| static void sel_s24le(struct processing_module *mod, struct input_stream_buffer *bsource, | ||
| struct output_stream_buffer *bsink, uint32_t frames) | ||
| { | ||
| struct comp_data *cd = module_get_private_data(mod); | ||
| struct audio_stream *source = bsource->data; | ||
| struct audio_stream *sink = bsink->data; | ||
| int32_t *src = audio_stream_get_rptr(source); | ||
| int32_t *dest = audio_stream_get_wptr(sink); | ||
| int nmax; | ||
| int i; | ||
| int n; | ||
| int processed = 0; | ||
| int source_frame_bytes = audio_stream_frame_bytes(source); | ||
| int sink_frame_bytes = audio_stream_frame_bytes(sink); | ||
| int n_chan_source = MIN(SEL_SOURCE_CHANNELS_MAX, audio_stream_get_channels(source)); | ||
| int n_chan_sink = MIN(SEL_SINK_CHANNELS_MAX, audio_stream_get_channels(sink)); | ||
|
|
||
| while (processed < frames) { | ||
|
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. looks like a simple
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. I don't like that syntax for this usage, n varies every iteration. Packs two lines but same instructions but with ambiguity for random code reader of when the increment is evaluated. I copied this from similar s32 function. Also this will change when the component is converted to source sink API.
Member
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. The for loop is easier to vectorize for the CC as we can increment by a constant, not an issue for this PR, but long term we need to factor this in.
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. At least we can remove the divisions from hot code in next improvements with source sink API. The convert to the new API would be in 2H tasks I think. |
||
| n = frames - processed; | ||
| nmax = audio_stream_bytes_without_wrap(source, src) / source_frame_bytes; | ||
| n = MIN(n, nmax); | ||
| nmax = audio_stream_bytes_without_wrap(sink, dest) / sink_frame_bytes; | ||
| n = MIN(n, nmax); | ||
| for (i = 0; i < n; i++) { | ||
| process_frame_s24le(dest, n_chan_sink, src, n_chan_source, | ||
| &cd->coeffs_config); | ||
| src += audio_stream_get_channels(source); | ||
| dest += audio_stream_get_channels(sink); | ||
| } | ||
| src = audio_stream_wrap(source, src); | ||
| dest = audio_stream_wrap(sink, dest); | ||
| processed += n; | ||
| } | ||
|
|
||
| module_update_buffer_position(bsource, bsink, frames); | ||
| } | ||
| #endif /* CONFIG_FORMAT_S24LE */ | ||
|
|
||
| #if CONFIG_FORMAT_S32LE | ||
| /** | ||
| * \brief Mixing routine for 32-bit, m channel input x n channel output single frame. | ||
| * \param[out] dst Sink buffer. | ||
|
|
@@ -262,7 +336,7 @@ static void process_frame_s32le(int32_t dst[], int dst_channels, | |
| accum += (int64_t)src[j] * (int64_t)coeffs_config->coeffs[i][j]; | ||
|
|
||
| /* shift out 10 LSbits with rounding to get 32-bit result */ | ||
| dst[i] = (int32_t)((accum + (1 << 9)) >> 10); | ||
| dst[i] = sat_int32((accum + (1 << 9)) >> 10); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -309,7 +383,7 @@ static void sel_s32le(struct processing_module *mod, struct input_stream_buffer | |
|
|
||
| module_update_buffer_position(bsource, bsink, frames); | ||
| } | ||
| #endif /* CONFIG_FORMAT_S24LE || CONFIG_FORMAT_S32LE */ | ||
| #endif /* CONFIG_FORMAT_S32LE */ | ||
| #endif | ||
|
|
||
| const struct comp_func_map func_table[] = { | ||
|
|
@@ -334,7 +408,7 @@ const struct comp_func_map func_table[] = { | |
| {SOF_IPC_FRAME_S16_LE, 0, sel_s16le}, | ||
| #endif | ||
| #if CONFIG_FORMAT_S24LE | ||
| {SOF_IPC_FRAME_S24_4LE, 0, sel_s32le}, | ||
| {SOF_IPC_FRAME_S24_4LE, 0, sel_s24le}, | ||
| #endif | ||
| #if CONFIG_FORMAT_S32LE | ||
| {SOF_IPC_FRAME_S32_LE, 0, sel_s32le}, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| % Export configuration blobs for Selector | ||
|
|
||
| % SPDX-License-Identifier: BSD-3-Clause | ||
| % | ||
| % Copyright (c) 2025, Intel Corporation. | ||
|
|
||
| function sof_selector_blobs() | ||
|
|
||
| % See ITU-R BS.775-4 for mix coefficient values | ||
| sof_selector_paths(true); | ||
|
|
||
| % Matrix for 1:1 pass-through | ||
| sel.rsvd0 = 0; | ||
| sel.rsvd1 = 0; | ||
| sel.coeffs = diag(ones(8, 1)); | ||
| write_blob(sel, "passthrough"); | ||
|
|
||
| % Stereo to mono downmix | ||
| sel.coeffs = zeros(8,8); | ||
| sel.coeffs(1, 1) = 0.7071; | ||
| sel.coeffs(1, 2) = 0.7071; | ||
| write_blob(sel, "downmix_stereo_to_mono"); | ||
|
|
||
| % 5.1 to stereo downmix | ||
| fl = 1; fr = 2; fc = 3; lfe = 4; sl = 5; sr = 6; | ||
| m = zeros(8,8); | ||
| m(1, fl) = 1.0000; m(1, fr) = 0.0000; m(1, fc) = 0.7071; m(1, sl) = 0.7071; m(1, sr) = 0.0000; | ||
| m(2, fl) = 0.0000; m(2, fr) = 1.0000; m(2, fc) = 0.7071; m(2, sl) = 0.0000; m(2, sr) = 0.7071; | ||
| sel.coeffs = m; | ||
| write_blob(sel, "downmix_51_to_stereo"); | ||
| sel.coeffs(1, lfe) = 10^(+4/20); % +10 dB, attenuate by -6 dB to left | ||
| sel.coeffs(2, lfe) = 10^(+4/20); % +10 dB, attenuate by -6 dB to right | ||
| write_blob(sel, "downmix_51_to_stereo_with_lfe"); | ||
|
|
||
| % 5.1 to mono downmix | ||
| fl = 1; fr = 2; fc = 3; lfe = 4; sl = 5; sr = 6; | ||
| m = zeros(8,8); | ||
| m(1, fl) = 0.7071; m(1, fr) = 0.7071; m(1, fc) = 1.0000; m(1, sl) = 0.5000; m(1, sr) = 0.5000; | ||
| sel.coeffs = m; | ||
| write_blob(sel, "downmix_51_to_mono"); | ||
| sel.coeffs(1, lfe) = 10^(+10/20); | ||
| write_blob(sel, "downmix_51_to_mono_with_lfe"); | ||
|
|
||
| % 7.1 to 5.1 downmix | ||
| fl8 = 1; fr8 = 2; fc8 = 3; lfe8 = 4; bl8 = 5; br8 = 6; sl8 = 7; sr8 = 8; | ||
| fl6 = 1; fr6 = 2; fc6 = 3; lfe6 = 4; sl6 = 5; sr6 = 6; | ||
| m = zeros(8,8); | ||
| m(fl6, fl8) = 1; | ||
| m(fr6, fr8) = 1; | ||
| m(fc6, fc8) = 1; | ||
| m(sl6, sl8) = 1; | ||
| m(sr6, sr8) = 1; | ||
| m(sl6, bl8) = 1; | ||
| m(sr6, br8) = 1; | ||
| m(lfe6, lfe8) = 1; | ||
| sel.coeffs = m; | ||
| write_blob(sel, "downmix_71_to_51"); | ||
|
|
||
| % 7.1 to 5.1 downmix | ||
| fl = 1; fr = 2; fc = 3; lfe = 4; bl = 5; br = 6; sl = 7; sr = 8; | ||
| m = zeros(8,8); | ||
| m(1, fl) = 1.0000; m(1, fr) = 0.0000; m(1, fc) = 0.7071; m(1, sl) = 0.7071; m(1, sr) = 0.0000; m(1, bl) = 0.7071; m(1, br) = 0.0000; | ||
| m(2, fl) = 0.0000; m(2, fr) = 1.0000; m(2, fc) = 0.7071; m(2, sl) = 0.0000; m(2, sr) = 0.7071; m(2, bl) = 0.0000; m(2, br) = 0.7071; | ||
| sel.coeffs = m; | ||
| write_blob(sel, "downmix_71_to_stereo"); | ||
| sel.coeffs(1, lfe) = 10^(+4/20); % +10 dB, attenuate by -6 dB to left | ||
| sel.coeffs(2, lfe) = 10^(+4/20); % +10 dB, attenuate by -6 dB to right | ||
| write_blob(sel, "downmix_71_to_stereo_with_lfe"); | ||
|
|
||
| % 7.1 to mono downmix | ||
| fl = 1; fc = 3; fr = 2; sr = 8; br = 6; bl = 5; sl = 7; lfe = 4; | ||
| m = zeros(8,8); | ||
| m(1, fl) = 0.7071; m(1, fr) = 0.7071; m(1, fc) = 1.0000; m(1, sl) = 0.5000; m(1, sr) = 0.5000; m(1, bl) = 0.5000; m(1, br) = 0.5000; | ||
| sel.coeffs = m; | ||
| write_blob(sel, "downmix_71_to_mono"); | ||
| m(1, lfe) = 10^(+19/20); % +10 dB | ||
| write_blob(sel, "downmix_71_to_mono_with_lfe"); | ||
|
|
||
| % mono to stereo upmix | ||
| sel.coeffs = zeros(8,8); | ||
| sel.coeffs(1, 1) = 10^(-3/20); | ||
| sel.coeffs(2, 1) = 10^(-3/20); | ||
| write_blob(sel, "upmix_mono_to_stereo"); | ||
|
|
||
| % mono to 5.1 / 7.1 upmix | ||
| fc = 3 | ||
| sel.coeffs = zeros(8,8); | ||
| sel.coeffs(fc, 1) = 1; | ||
| write_blob(sel, "upmix_mono_to_51"); | ||
| write_blob(sel, "upmix_mono_to_71"); | ||
|
|
||
| % stereo to 5.1 / 7.1 upmix | ||
| fl = 1; fr = 2; | ||
| sel.coeffs = zeros(8,8); | ||
| sel.coeffs(fl, 1) = 1; | ||
| sel.coeffs(fr, 2) = 1; | ||
| write_blob(sel, "upmix_stereo_to_51"); | ||
| write_blob(sel, "upmix_stereo_to_71"); | ||
|
|
||
| sof_selector_paths(false); | ||
| end | ||
|
|
||
| function write_blob(sel, blobname) | ||
| str_config = "selector_config"; | ||
| str_exported = "Exported with script sof_selector_blobs.m"; | ||
| str_howto = "cd tools/tune/selector; octave sof_selector_blobs.m" | ||
| sof_tools = '../../../../tools'; | ||
| sof_tplg = fullfile(sof_tools, 'topology'); | ||
| sof_tplg_selector = fullfile(sof_tplg, 'topology2/include/components/micsel'); | ||
|
|
||
| sel | ||
|
|
||
lgirdwood marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| sum_coefs = sum(sel.coeffs, 2)' | ||
| max_sum_coef = max(sum_coefs) | ||
| if max_sum_coef > 1 | ||
| scale = 1 / max_sum_coef; | ||
| else | ||
| scale = 1; | ||
| end | ||
|
|
||
| scale | ||
| sel.coeffs = scale .* sel.coeffs'; | ||
|
|
||
| blob8 = sof_selector_build_blob(sel); | ||
| tplg2_fn = sprintf("%s/%s.conf", sof_tplg_selector, blobname); | ||
| sof_check_create_dir(tplg2_fn); | ||
| sof_tplg2_write(tplg2_fn, blob8, str_config, str_exported, str_howto); | ||
| end | ||
|
|
||
| function sof_selector_paths(enable) | ||
|
|
||
| common = '../../../../tools/tune/common'; | ||
| if enable | ||
| addpath(common); | ||
| else | ||
| rmpath(common); | ||
| end | ||
| end | ||
|
|
||
| function blob8 = sof_selector_build_blob(sel) | ||
|
|
||
| s = size(sel.coeffs); | ||
| blob_type = 0; | ||
| blob_param_id = 0; % IPC4_SELECTOR_COEFFS_CONFIG_ID | ||
| data_length = s(1) * s(2) + 2; | ||
| data_size = 2 * data_length; % int16_t matrix | ||
| coeffs_vec = reshape(sel.coeffs, 1, []); % convert to row vector | ||
| coeffs_q10 = int16(round(coeffs_vec .* 1024)); % Q6.10 | ||
| ipc_ver = 4; | ||
| [abi_bytes, abi_size] = sof_get_abi(data_size, ipc_ver, blob_type, blob_param_id); | ||
| blob_size = data_size + abi_size; | ||
| blob8 = uint8(zeros(1, blob_size)); | ||
| blob8(1:abi_size) = abi_bytes; | ||
| j = abi_size + 1; | ||
|
|
||
| % header | ||
| blob8(j:j+1) = int16_to_byte(int16(sel.rsvd0)); | ||
| j = j + 2; | ||
| blob8(j:j+1) = int16_to_byte(int16(sel.rsvd1)); | ||
| j = j + 2; | ||
|
|
||
| % coeffs matrix | ||
| for i = 1:(s(1) * s(2)) | ||
| blob8(j:j+1) = int16_to_byte(coeffs_q10(i)); | ||
| j = j + 2; | ||
| end | ||
| end | ||
|
|
||
| function bytes = int16_to_byte(word) | ||
| sh = [0 -8]; | ||
| bytes = uint8(zeros(1,2)); | ||
| bytes(1) = bitand(bitshift(word, sh(1)), 255); | ||
| bytes(2) = bitand(bitshift(word, sh(2)), 255); | ||
| end | ||
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.
Should add micsel test run to scripts/host-testbench.sh to get it tested regularly.
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.
also can we add to nocodec topology ?
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.
Yes, it would be currently useful as the only way to validate in a real device more complex channels changes. With HDA and SDW we are limited with DAI side to 2ch.