diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index 7e8c5d1d8d02..d0e8159deaf2 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -180,10 +180,10 @@ set(volume_sources volume/volume.c volume/volume_generic.c) set(src_sources src/src.c src/src_generic.c) 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/eq_fir_generic.c) -set(eq-iir_sources eq_iir/eq_iir.c eq_iir/iir.c) +set(eq-iir_sources eq_iir/eq_iir.c) set(dcblock_sources dcblock/dcblock.c dcblock/dcblock_generic.c) set(crossover_sources crossover/crossover.c crossover/crossover_generic.c) -set(tdfb_sources tdfb/tdfb.c tdfb/tdfb_generic.c) +set(tdfb_sources tdfb/tdfb.c tdfb/tdfb_generic.c tdfb/tdfb_direction.c) set(drc_sources drc/drc.c drc/drc_generic.c drc/drc_math_generic.c) set(multiband_drc_sources multiband_drc/multiband_drc.c multiband_drc/multiband_drc_generic.c crossover/crossover.c crossover/crossover_generic.c drc/drc.c drc/drc_generic.c drc/drc_math_generic.c) diff --git a/src/audio/Kconfig b/src/audio/Kconfig index fd90d2dd2729..d7a050ddae07 100644 --- a/src/audio/Kconfig +++ b/src/audio/Kconfig @@ -387,6 +387,9 @@ endif # COMP_ASRC config COMP_TDFB bool "TDFB component" select MATH_FIR + select MATH_IIR_DF2T + select SQRT_FIXED + select CORDIC_FIXED default y help Select for time domain fixed beamformer (TDFB) component. The diff --git a/src/audio/crossover/crossover.c b/src/audio/crossover/crossover.c index be8129256b0f..59ea8f533305 100644 --- a/src/audio/crossover/crossover.c +++ b/src/audio/crossover/crossover.c @@ -11,13 +11,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include diff --git a/src/audio/crossover/crossover_generic.c b/src/audio/crossover/crossover_generic.c index 2ec350434238..5a5a22c89b75 100644 --- a/src/audio/crossover/crossover_generic.c +++ b/src/audio/crossover/crossover_generic.c @@ -6,10 +6,10 @@ #include #include -#include #include #include #include +#include /* * \brief Splits x into two based on the coefficients set in the lp diff --git a/src/audio/eq_iir/CMakeLists.txt b/src/audio/eq_iir/CMakeLists.txt index 397b6081d4e6..498946370528 100644 --- a/src/audio/eq_iir/CMakeLists.txt +++ b/src/audio/eq_iir/CMakeLists.txt @@ -1,3 +1,3 @@ # SPDX-License-Identifier: BSD-3-Clause -add_local_sources(sof eq_iir.c iir.c) +add_local_sources(sof eq_iir.c) diff --git a/src/audio/eq_iir/eq_iir.c b/src/audio/eq_iir/eq_iir.c index c7f1e0484d70..16a045e7bdb0 100644 --- a/src/audio/eq_iir/eq_iir.c +++ b/src/audio/eq_iir/eq_iir.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/src/audio/multiband_drc/multiband_drc_generic.c b/src/audio/multiband_drc/multiband_drc_generic.c index cfb2b55dcf40..3f981af7a543 100644 --- a/src/audio/multiband_drc/multiband_drc_generic.c +++ b/src/audio/multiband_drc/multiband_drc_generic.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/src/audio/tdfb/CMakeLists.txt b/src/audio/tdfb/CMakeLists.txt index 98e48dbfc018..b6bff9661a1f 100644 --- a/src/audio/tdfb/CMakeLists.txt +++ b/src/audio/tdfb/CMakeLists.txt @@ -1,3 +1,3 @@ # SPDX-License-Identifier: BSD-3-Clause -add_local_sources(sof tdfb.c tdfb_generic.c tdfb_hifiep.c tdfb_hifi3.c) +add_local_sources(sof tdfb.c tdfb_generic.c tdfb_hifiep.c tdfb_hifi3.c tdfb_direction.c) diff --git a/src/audio/tdfb/tdfb.c b/src/audio/tdfb/tdfb.c index 5662a9e01610..b215cbd2d9f2 100644 --- a/src/audio/tdfb/tdfb.c +++ b/src/audio/tdfb/tdfb.c @@ -4,6 +4,11 @@ // // Author: Seppo Ingalsuo +/* Note: The script tools/tune/tdfb/example_all.sh can be used to re-calculate + * all the beamformer topology data files if need. It also creates the additional + * data files for simulated tests with testbench. Matlab or Octave is needed. + */ + #include #include #include @@ -179,6 +184,17 @@ static int16_t *tdfb_filter_seek(struct sof_tdfb_config *config, int num_filters return coefp; } +static int wrap_180(int a) +{ + if (a > 180) + return ((a + 180) % 360) - 180; + + if (a < -180) + return 180 - ((180 - a) % 360); + + return a; +} + static int tdfb_init_coef(struct tdfb_comp_data *cd, int source_nch, int sink_nch) { @@ -275,15 +291,10 @@ static int tdfb_init_coef(struct tdfb_comp_data *cd, int source_nch, /* Skip to requested coefficient set */ min_delta = 360; min_delta_idx = 0; - target_az = cd->az_value * config->angle_enum_mult + config->angle_enum_offs; - if (target_az > 180) - target_az -= 360; - - if (target_az < -180) - target_az += 360; + target_az = wrap_180(cd->az_value * config->angle_enum_mult + config->angle_enum_offs); for (i = 0; i < config->num_angles; i++) { - delta = ABS(target_az - cd->filter_angles[i].azimuth); + delta = ABS(target_az - wrap_180(cd->filter_angles[i].azimuth)); if (delta < min_delta) { min_delta = delta; min_delta_idx = i; @@ -479,7 +490,7 @@ static void tdfb_free(struct comp_dev *dev) ipc_msg_free(cd->msg); tdfb_free_delaylines(cd); comp_data_blob_handler_free(cd->model_handler); - + tdfb_direction_free(cd); rfree(cd->ctrl_data); rfree(cd); rfree(dev); @@ -653,11 +664,6 @@ static int tdfb_cmd(struct comp_dev *dev, int cmd, void *data, return -EINVAL; } -/* Placeholder function for sound direction estimate */ -static void update_direction_of_arrival(struct tdfb_comp_data *cd) -{ -} - static void tdfb_process(struct comp_dev *dev, struct comp_buffer *source, struct comp_buffer *sink, int frames, uint32_t source_bytes, uint32_t sink_bytes) @@ -673,6 +679,11 @@ static void tdfb_process(struct comp_dev *dev, struct comp_buffer *source, /* calc new free and available */ comp_update_buffer_consume(source, source_bytes); comp_update_buffer_produce(sink, sink_bytes); + + /* Update sound direction estimate */ + tdfb_direction_estimate(cd, frames, source->stream.channels); + comp_dbg(dev, "tdfb_dint %u %d %d %d", cd->direction.trigger, cd->direction.level, + (int32_t)(cd->direction.level_ambient >> 32), cd->direction.az_slow); } /* copy and process stream data from source to sink buffers */ @@ -720,6 +731,7 @@ static int tdfb_copy(struct comp_dev *dev) * optimized filter function loads the successive input samples from * internal delay line with a 64 bit load operation. */ + cl.frames = MIN(cl.frames, cd->max_frames); if (cl.frames >= 2) { n = (cl.frames >> 1) << 1; @@ -729,10 +741,11 @@ static int tdfb_copy(struct comp_dev *dev) n * cl.sink_frame_bytes); } - /* TODO: Update direction of arrival estimate */ - update_direction_of_arrival(cd); - if (cd->direction_updates && cd->direction_change) + if (cd->direction_updates && cd->direction_change) { send_get_ctl_ipc(dev); + cd->direction_change = false; + comp_dbg(dev, "tdfb_dupd %d %d", cd->az_value_estimate, cd->direction.az_slow); + } return 0; } @@ -761,19 +774,40 @@ static int tdfb_prepare(struct comp_dev *dev) /* Initialize filter */ cd->config = comp_get_data_blob(cd->model_handler, NULL, NULL); - if (cd->config) { - ret = tdfb_setup(cd, sourceb->stream.channels, sinkb->stream.channels); - if (ret < 0) { - comp_err(dev, "tdfb_prepare() error: tdfb_setup failed."); - goto err; - } + if (!cd->config) { + ret = -EINVAL; + goto err; + } - /* Clear in/out buffers */ - memset(cd->in, 0, TDFB_IN_BUF_LENGTH * sizeof(int32_t)); - memset(cd->out, 0, TDFB_IN_BUF_LENGTH * sizeof(int32_t)); + ret = tdfb_setup(cd, sourceb->stream.channels, sinkb->stream.channels); + if (ret < 0) { + comp_err(dev, "tdfb_prepare() error: tdfb_setup failed."); + goto err; + } - ret = set_func(dev); - return ret; + /* Clear in/out buffers */ + memset(cd->in, 0, TDFB_IN_BUF_LENGTH * sizeof(int32_t)); + memset(cd->out, 0, TDFB_IN_BUF_LENGTH * sizeof(int32_t)); + + ret = set_func(dev); + if (ret) + goto err; + + /* The max. amount of processing need to be limited for sound direction + * processing. Max frames is used in tdfb_direction_init() and copy(). + */ + cd->max_frames = Q_MULTSR_32X32(dev->frames, TDFB_MAX_FRAMES_MULT_Q14, 0, 14, 0); + comp_info(dev, "dev_frames = %d, max_frames = %d", dev->frames, cd->max_frames); + + /* Initialize tracking */ + ret = tdfb_direction_init(cd, sourceb->stream.rate, sourceb->stream.channels); + if (!ret) { + comp_info(dev, "max_lag = %d, xcorr_size = %d", + cd->direction.max_lag, cd->direction.d_size); + comp_info(dev, "line_array = %d, a_step = %d, a_offs = %d", + (int)cd->direction.line_array, cd->config->angle_enum_mult, + cd->config->angle_enum_offs); + return 0; } err: diff --git a/src/audio/tdfb/tdfb_direction.c b/src/audio/tdfb/tdfb_direction.c new file mode 100644 index 000000000000..a9c72e5fc958 --- /dev/null +++ b/src/audio/tdfb/tdfb_direction.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// Author: Seppo Ingalsuo + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Generic definitions */ +#define COEF_DEG_TO_RAD Q_CONVERT_FLOAT(0.017453, 15) /* Q1.15 */ +#define COEF_RAD_TO_DEG Q_CONVERT_FLOAT(57.296, 9) /* Q6.9 */ +#define PI_Q12 Q_CONVERT_FLOAT(3.1416, 12) +#define PIMUL2_Q12 Q_CONVERT_FLOAT(6.2832, 12) +#define PIDIV2_Q12 Q_CONVERT_FLOAT(1.5708, 12) + +/* Sound levels filtering related, these form a primitive voice activity + * detector. Sound levels below ambient estimate times threshold (kind of dB offset) + * are not scanned for sound direction. + */ +#define SLOW_LEVEL_SHIFT 12 +#define FAST_LEVEL_SHIFT 1 +#define POWER_THRESHOLD Q_CONVERT_FLOAT(15.849, 10) /* 12 dB, value is 10^(dB/10) */ + +/* Iteration parameters, smaller step and larger iterations is more accurate but + * consumes more cycles. + */ +#define AZ_STEP Q_CONVERT_FLOAT(0.6, 12) /* Radians as Q4.12 (~34 deg) */ +#define AZ_ITERATIONS 8 /* loops in min err search */ +#define SOURCE_DISTANCE Q_CONVERT_FLOAT(3.0, 12) /* source distance in m Q4.12 */ + +/* Sound direction angle filtering */ +#define SLOW_AZ_C1 Q_CONVERT_FLOAT(0.02, 15) +#define SLOW_AZ_C2 Q_CONVERT_FLOAT(0.98, 15) + +/* Threshold for notifying user space, no more often than every 200 ms */ +#define CONTROL_UPDATE_MIN_TIME Q_CONVERT_FLOAT(0.2, 16) + +/* Emphasis filters for sound direction (IIR). These coefficients were + * created with the script tools/tune/tdfb/example_direction_emphasis.m + * from output files tdfb_iir_emphasis_48k.h and tdfb_iir_emphasis_16k.h. + */ + +uint32_t iir_emphasis_48k[20] = { + 0x00000002, 0x00000002, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xc8cf47b5, 0x7689916a, + 0x1dc95968, 0xc46d4d30, 0x1dc95968, 0x00000000, + 0x00004000, 0xe16f20ea, 0x51e57f66, 0x01966267, + 0x032cc4ce, 0x01966267, 0xfffffffe, 0x00004222 +}; + +uint32_t iir_emphasis_16k[20] = { + 0x00000002, 0x00000002, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xd6f418ae, 0x63e7b85c, + 0x19ae069b, 0xcca3f2ca, 0x19ae069b, 0x00000000, + 0x00004000, 0xf504f334, 0x00000000, 0x09651419, + 0x12ca2831, 0x09651419, 0xfffffffe, 0x0000414c +}; + +/** + * \brief This function copies data to sound direction estimation + * + * Function to copy data from component source to cross correlation buffer. The copy + * operation includes emphasis filtering. The copy is skipped if tracking is not + * enabled. + * + * \param[in,out] cd Component data + * \param[in] ch_count Number of channels in audio stream + * \param[in,out] ch Current channel for emphasis, incremented + * and reset to zero after channel_count -1. + * \param[in] x Input PCM sample + */ +void tdfb_direction_copy_emphasis(struct tdfb_comp_data *cd, int ch_count, int *ch, int32_t x) +{ + int32_t y; + + if (!cd->direction_updates) + return; + + y = iir_df2t(&cd->direction.emphasis[*ch], x); + *cd->direction.wp = sat_int16(Q_SHIFT_RND(y, 31, 18)); /* 18 dB boost after high-pass */ + cd->direction.wp++; + tdfb_cinc_s16(&cd->direction.wp, cd->direction.d_end, cd->direction.d_size); + (*ch)++; + if (*ch == ch_count) + *ch = 0; +} + +/* Use function y = sqrt_int16(x), where x is Q4.12 unsigned, y is Q4.12 + * TODO: Add scaling of input to Q4.12 with a suitable N^2 or (1/N)^2 value + * to increase sqrt() computation precision and avoid MIN() function to + * saturate for large distances (over 4m). The current virtual source + * distance 3m allows max 1m size array for this to work correctly without + * hitting the saturation. + */ +static inline int16_t tdfb_mic_distance_sqrt(int32_t x) +{ + int32_t xs; + + xs = Q_SHIFT_RND(x, 24, 12); + xs = MIN(xs, UINT16_MAX); + return sqrt_int16((uint16_t)xs); +} + +static int16_t max_mic_distance(struct tdfb_comp_data *cd) +{ + int16_t d; + int16_t dx; + int16_t dy; + int16_t dz; + int32_t d2; + int32_t d2_max = 0; + int i; + int j; + + /* Max lag based on largest array dimension. Microphone coordinates are Q4.12 meters */ + for (i = 0; i < cd->config->num_mic_locations; i++) { + for (j = 0; i < cd->config->num_mic_locations; i++) { + if (j == i) + continue; + + dx = cd->mic_locations[i].x - cd->mic_locations[j].x; + dy = cd->mic_locations[i].y - cd->mic_locations[j].y; + dz = cd->mic_locations[i].z - cd->mic_locations[j].z; + + /* d2 is Q8.24 meters */ + d2 = dx * dx + dy * dy + dz * dz; + if (d2 > d2_max) + d2_max = d2; + } + } + + /* Squared distance is Q8.24, return Q4.12 meters */ + d = tdfb_mic_distance_sqrt(d2_max); + return d; +} + +static bool line_array_mode_check(struct tdfb_comp_data *cd) +{ + int32_t px, py, pz; + int16_t a, b, c, d, e, f; + int i; + int num_mic_locations = cd->config->num_mic_locations; + + if (num_mic_locations == 2) + return true; + + /* Cross product of vectors AB and AC is (0, 0, 0) if they are co-linear. + * Form vector AB(a,b,c) from x(i+1) - x(i), y(i+1) - y(i), z(i+1) - z(i) + * Form vector AC(d,e,f) from x(i+2) - x(i), y(i+2) - y(1), z(i+2) - z(i) + */ + for (i = 0; i < num_mic_locations - 3; i++) { + a = cd->mic_locations[i + 1].x - cd->mic_locations[i].x; + b = cd->mic_locations[i + 1].y - cd->mic_locations[i].y; + c = cd->mic_locations[i + 1].z - cd->mic_locations[i].z; + d = cd->mic_locations[i + 2].x - cd->mic_locations[i].x; + e = cd->mic_locations[i + 2].y - cd->mic_locations[i].y; + f = cd->mic_locations[i + 2].z - cd->mic_locations[i].z; + cross_product_s16(&px, &py, &pz, a, b, c, d, e, f); + + /* Allow small room for rounding error with |x| > 1 in check */ + if (ABS(px) > 1 || ABS(py) > 1 || ABS(pz) > 1) + return false; + } + + return true; +} + +int tdfb_direction_init(struct tdfb_comp_data *cd, int32_t fs, int ch_count) +{ + struct sof_eq_iir_header_df2t *filt; + int64_t *delay; + int32_t d_max; + int32_t t_max; + size_t size; + int n; + int i; + + /* Select emphasis response per sample rate */ + switch (fs) { + case 16000: + filt = (struct sof_eq_iir_header_df2t *)iir_emphasis_16k; + break; + case 48000: + filt = (struct sof_eq_iir_header_df2t *)iir_emphasis_48k; + break; + default: + return -EINVAL; + } + + /* Allocate delay lines for IIR filters and initialize them */ + size = ch_count * iir_delay_size_df2t(filt); + delay = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, size); + if (!delay) + return -ENOMEM; + + cd->direction.df2t_delay = delay; + for (i = 0; i < ch_count; i++) { + iir_init_coef_df2t(&cd->direction.emphasis[i], filt); + iir_init_delay_df2t(&cd->direction.emphasis[i], &delay); + } + + /* Unit delay length in Q1.31 seconds */ + cd->direction.unit_delay = Q_SHIFT_LEFT(1LL, 0, 31) / fs; + + /* Get max possible mic-mic distance, then from max distance to max time t = d_max / v, + * t is Q1.15, d is Q4.12, v is Q9.0 + */ + d_max = max_mic_distance(cd); + t_max = Q_SHIFT_LEFT(d_max, 12, 15) / SPEED_OF_SOUND; + + /* Calculate max lag to search and allocate delay line for cross correlation + * compute. Add one to make sure max possible lag is in search window. + */ + cd->direction.max_lag = Q_MULTSR_32X32(fs, t_max, 0, 15, 0) + 1; + n = (cd->max_frames + (2 * cd->direction.max_lag + 1)) * ch_count; + cd->direction.d_size = n * sizeof(int16_t); + cd->direction.d = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, cd->direction.d_size); + if (!cd->direction.d) + goto err_free_iir; + + /* Set needed pointers to xcorr delay line, advance write pointer by max_lag to keep read + * always behind write + */ + cd->direction.d_end = cd->direction.d + n; + cd->direction.rp = cd->direction.d; + cd->direction.wp = cd->direction.d + ch_count * (cd->direction.max_lag + 1); + + /* xcorr result is temporary but too large for stack so it is allocated here */ + cd->direction.r_size = (2 * cd->direction.max_lag + 1) * sizeof(int32_t); + cd->direction.r = rzalloc(SOF_MEM_ZONE_RUNTIME, 0, SOF_MEM_CAPS_RAM, cd->direction.r_size); + if (!cd->direction.r) + goto err_free_all; + + /* Check for line array mode */ + cd->direction.line_array = line_array_mode_check(cd); + + /* Initialize direction to zero radians, set initial step sign to +1 */ + cd->direction.az = 0; + cd->direction.step_sign = 1; + return 0; + +err_free_all: + rfree(cd->direction.d); + cd->direction.d = NULL; + +err_free_iir: + rfree(cd->direction.df2t_delay); + cd->direction.df2t_delay = NULL; + return -ENOMEM; +} + +void tdfb_direction_free(struct tdfb_comp_data *cd) +{ + rfree(cd->direction.df2t_delay); + rfree(cd->direction.d); + rfree(cd->direction.r); +} + +/* Measure level of one channel */ +static void level_update(struct tdfb_comp_data *cd, int frames, int ch_count, int channel) +{ + int64_t tmp = 0; + int64_t ambient; + int64_t level; + int16_t s; + int16_t *p = cd->direction.rp + channel; + int shift; + int thr; + int n; + + /* Calculate mean square level */ + for (n = 0; n < frames; n++) { + s = *p; + p += ch_count; + tdfb_cinc_s16(&p, cd->direction.d_end, cd->direction.d_size); + tmp += ((int64_t)s * s); + } + + /* Calculate mean square power */ + cd->direction.level = sat_int32(tmp / frames); + + /* Slow track minimum level and use it as ambient noise level estimate */ + level = ((int64_t)cd->direction.level) << 32; + ambient = cd->direction.level_ambient; + shift = (level < ambient) ? FAST_LEVEL_SHIFT : SLOW_LEVEL_SHIFT; + ambient = (level >> shift) - (ambient >> shift) + ambient; + thr = sat_int32(Q_MULTS_32X32(ambient >> 32, POWER_THRESHOLD, 31, 10, 31)); + cd->direction.level_ambient = ambient; + cd->direction.trigger <<= 1; + if (cd->direction.level > thr) + cd->direction.trigger |= 1; + + /* Levels update runs always when tracking, increment frame counter until wrap */ + cd->direction.frame_count_since_control += frames; + if (cd->direction.frame_count_since_control < 0) + cd->direction.frame_count_since_control = INT32_MAX; +} + +static int find_max_value_index(int32_t *v, int length) +{ + int idx = 0; + int i; + + for (i = 1; i < length; i++) { + if (v[i] > v[idx]) + idx = i; + } + + return idx; +} + +static void time_differences(struct tdfb_comp_data *cd, int frames, int ch_count) +{ + int64_t r; + int16_t *x; + int16_t *y; + int r_max_idx; + int max_lag = cd->direction.max_lag; + int c; + int k; + int i; + int32_t maxval = 0; + + /* Calculate xcorr for channel 0 vs. 1 .. (ch_count-1). Scan -maxlag .. +maxlag*/ + for (c = 1; c < ch_count; c++) { + for (k = -max_lag; k <= max_lag; k++) { + y = cd->direction.rp; /* First channel */ + x = y + k * ch_count + c; /* other channel C */ + tdfb_cinc_s16(&x, cd->direction.d_end, cd->direction.d_size); + tdfb_cdec_s16(&x, cd->direction.d, cd->direction.d_size); + r = 0; + for (i = 0; i < frames; i++) { + if (maxval < *y) + maxval = *y; + + r += (int32_t)*x * *y; + y += ch_count; + tdfb_cinc_s16(&y, cd->direction.d_end, cd->direction.d_size); + x += ch_count; + tdfb_cinc_s16(&x, cd->direction.d_end, cd->direction.d_size); + } + /* Scale for max. 20 ms 48 kHz frame */ + cd->direction.r[k + max_lag] = sat_int32(((r >> 8) + 1) >> 1); + } + r_max_idx = find_max_value_index(&cd->direction.r[0], 2 * max_lag + 1); + cd->direction.timediff[c - 1] = (int32_t)(r_max_idx - max_lag) * + cd->direction.unit_delay; + } + cd->direction.rp += frames * ch_count; + tdfb_cinc_s16(&cd->direction.rp, cd->direction.d_end, cd->direction.d_size); +} + +static int16_t distance_from_source(struct tdfb_comp_data *cd, int mic_n, + int16_t x, int16_t y, int16_t z) +{ + int32_t d2; + int16_t dx; + int16_t dy; + int16_t dz; + int16_t d; + + dx = x - cd->mic_locations[mic_n].x; + dy = y - cd->mic_locations[mic_n].y; + dz = z - cd->mic_locations[mic_n].z; + + /* d2 is Q8.24 meters */ + d2 = dx * dx + dy * dy + dz * dz; + + /* Squared distance is Q8.24, return Q4.12 meters */ + d = tdfb_mic_distance_sqrt(d2); + return d; +} + +static void theoretical_time_differences(struct tdfb_comp_data *cd, int16_t az) +{ + int16_t d[PLATFORM_MAX_CHANNELS]; + int16_t src_x; + int16_t src_y; + int16_t sin_az; + int16_t cos_az; + int32_t delta_d; + int n_mic = cd->config->num_mic_locations; + int i; + + sin_az = sin_fixed_16b(Q_SHIFT_LEFT((int32_t)az, 12, 28)); /* Q1.15 */ + cos_az = cos_fixed_16b(Q_SHIFT_LEFT((int32_t)az, 12, 28)); /* Q1.15 */ + src_x = Q_MULTSR_32X32((int32_t)cos_az, SOURCE_DISTANCE, 15, 12, 12); + src_y = Q_MULTSR_32X32((int32_t)sin_az, SOURCE_DISTANCE, 15, 12, 12); + + for (i = 0; i < n_mic; i++) + d[i] = distance_from_source(cd, i, src_x, src_y, 0); + + for (i = 0; i < n_mic - 1; i++) { + delta_d = d[i + 1] - d[0]; /* Meters Q4.12 */ + cd->direction.timediff_iter[i] = + (int32_t)((((int64_t)delta_d) << 19) / SPEED_OF_SOUND); + } +} + +static int64_t mean_square_time_difference_err(struct tdfb_comp_data *cd) +{ + int64_t err = 0; + int32_t delta; + int i; + + for (i = 0; i < cd->config->num_mic_locations - 1; i++) { + delta = cd->direction.timediff[i] - cd->direction.timediff_iter[i]; + err += (int64_t)delta * delta; + } + + return err; +} + +static int unwrap_radians(int radians) +{ + int a = radians; + + if (a > PI_Q12) + a -= PIMUL2_Q12; + + if (a < -PI_Q12) + a += PIMUL2_Q12; + + return a; +} + +static void iterate_source_angle(struct tdfb_comp_data *cd) +{ + int64_t err_prev; + int64_t err; + int32_t ds1; + int32_t ds2; + int az_slow; + int i; + int az_step = AZ_STEP * cd->direction.step_sign; + int az = cd->direction.az_slow; + + /* Start next iteration opposite direction */ + cd->direction.step_sign *= -1; + + /* Get theoretical time differences for previous angle */ + theoretical_time_differences(cd, az); + err_prev = mean_square_time_difference_err(cd); + + for (i = 0; i < AZ_ITERATIONS; i++) { + az += az_step; + theoretical_time_differences(cd, az); + err = mean_square_time_difference_err(cd); + if (err > err_prev) { + az_step = -(az_step >> 1); + if (az_step == 0) + break; + } + + err_prev = err; + } + + az = unwrap_radians(az); + if (cd->direction.line_array) { + /* Line array azimuth angle is -90 .. +90 */ + if (az > PIDIV2_Q12) + az = PI_Q12 - az; + + if (az < -PIDIV2_Q12) + az = -PI_Q12 - az; + } + + cd->direction.az = az; + + /* Avoid low-pass filtering to zero the angle in 360 degree arrays + * due to discontinuity at -180 or +180 degree point and estimation + * noise. Try to camp on either side of the circle. + */ + ds1 = cd->direction.az_slow - az; + ds1 = ds1 * ds1; + ds2 = cd->direction.az_slow - (az - 360); + ds2 = ds2 * ds2; + if (ds2 < ds1) { + az -= 360; + } else { + ds2 = cd->direction.az_slow - (az + 360); + ds2 = ds2 * ds2; + if (ds2 < ds1) + az += 360; + } + + az_slow = Q_MULTSR_32X32((int32_t)az, SLOW_AZ_C1, 12, 15, 12) + + Q_MULTSR_32X32((int32_t)cd->direction.az_slow, SLOW_AZ_C2, 12, 15, 12); + cd->direction.az_slow = unwrap_radians(az_slow); +} + +static void updates_when_no_trigger(struct tdfb_comp_data *cd, int frames, int ch_count) +{ + cd->direction.rp += frames * ch_count; + tdfb_cinc_s16(&cd->direction.rp, cd->direction.d_end, cd->direction.d_size); +} + +static int convert_angle_to_enum(struct tdfb_comp_data *cd) +{ + int16_t deg; + int new_az_value; + + /* Update azimuth enum with radians to degrees to enum step conversion. First + * convert radians to deg, subtract angle offset, and make angles positive. + */ + deg = Q_MULTS_32X32((int32_t)cd->direction.az_slow, COEF_RAD_TO_DEG, 12, 9, 0) - + cd->config->angle_enum_offs; + + /* Divide and round to enum angle scale, remove duplicate 0 and 360 degree angle + * representation to have single zero degree enum. + */ + new_az_value = ((2 * deg / cd->config->angle_enum_mult) + 1) >> 1; + if (new_az_value * cd->config->angle_enum_mult == 360) + new_az_value -= 360 / cd->config->angle_enum_mult; + + return new_az_value; +} + +/* Placeholder function for sound direction estimate */ +void tdfb_direction_estimate(struct tdfb_comp_data *cd, int frames, int ch_count) +{ + int32_t time_since; + int new_az_value; + + if (!cd->direction_updates) + return; + + /* Update levels, skip rest of estimation if level does not exceed well ambient */ + level_update(cd, frames, ch_count, 0); + if (!(cd->direction.trigger & 1)) { + updates_when_no_trigger(cd, frames, ch_count); + return; + } + + /* Compute time differences of ch_count vs. reference channel 1 */ + time_differences(cd, frames, ch_count); + + /* Determine direction angle */ + iterate_source_angle(cd); + + /* Convert radians to enum*/ + new_az_value = convert_angle_to_enum(cd); + + /* Time after last control as Q16.16, unit delay is Q1.131, count is Q0. */ + time_since = Q_MULTS_32X32((int64_t)cd->direction.unit_delay, + cd->direction.frame_count_since_control, 31, 0, 16); + + /* Need a new enum value, sufficient time since last update and four measurement frames + * to update user space. + */ + if (new_az_value != cd->az_value_estimate && time_since > CONTROL_UPDATE_MIN_TIME && + (cd->direction.trigger & 0x15) == 0x15) { + cd->az_value_estimate = new_az_value; + cd->direction.frame_count_since_control = 0; + cd->direction_change = true; + } +} diff --git a/src/audio/tdfb/tdfb_generic.c b/src/audio/tdfb/tdfb_generic.c index 055bf72d44b5..b4603e7a35a3 100644 --- a/src/audio/tdfb/tdfb_generic.c +++ b/src/audio/tdfb/tdfb_generic.c @@ -35,6 +35,7 @@ void tdfb_fir_s16(struct tdfb_comp_data *cd, int out_nch = sink->channels; int idx_in = 0; int idx_out = 0; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -44,6 +45,7 @@ void tdfb_fir_s16(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { x = audio_stream_read_frag_s16(source, idx_in++); cd->in[i] = *x << 16; + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, *x << 16); } /* Run and mix all filters to their output channel */ @@ -98,6 +100,7 @@ void tdfb_fir_s24(struct tdfb_comp_data *cd, int out_nch = sink->channels; int idx_in = 0; int idx_out = 0; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -107,6 +110,7 @@ void tdfb_fir_s24(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { x = audio_stream_read_frag_s32(source, idx_in++); cd->in[i] = *x << 8; + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, *x << 8); } /* Run and mix all filters to their output channel */ @@ -161,6 +165,7 @@ void tdfb_fir_s32(struct tdfb_comp_data *cd, int out_nch = sink->channels; int idx_in = 0; int idx_out = 0; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -170,6 +175,7 @@ void tdfb_fir_s32(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { x = audio_stream_read_frag_s32(source, idx_in++); cd->in[i] = *x; + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, *x); } /* Run and mix all filters to their output channel */ diff --git a/src/audio/tdfb/tdfb_hifi3.c b/src/audio/tdfb/tdfb_hifi3.c index b8e80121fefd..aabcf43ca9b4 100644 --- a/src/audio/tdfb/tdfb_hifi3.c +++ b/src/audio/tdfb/tdfb_hifi3.c @@ -36,6 +36,7 @@ void tdfb_fir_s16(struct tdfb_comp_data *cd, int k; int in_nch = source->channels; int out_nch = sink->channels; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -46,6 +47,7 @@ void tdfb_fir_s16(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { AE_L16_XC(d, x, sizeof(int16_t)); cd->in[i] = (ae_int32)AE_CVT32X2F16_32(d); + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, cd->in[i]); } /* Run and mix all filters to their output channel */ @@ -108,6 +110,7 @@ void tdfb_fir_s24(struct tdfb_comp_data *cd, int k; int in_nch = source->channels; int out_nch = sink->channels; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -118,6 +121,7 @@ void tdfb_fir_s24(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { AE_L32_XC(d, x, sizeof(int32_t)); cd->in[i] = AE_SLAI32(d, 8); + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, cd->in[i]); } for (i = 0; i < cfg->num_filters; i++) { @@ -179,6 +183,7 @@ void tdfb_fir_s32(struct tdfb_comp_data *cd, int k; int in_nch = source->channels; int out_nch = sink->channels; + int emp_ch = 0; for (j = 0; j < (frames >> 1); j++) { /* Clear output mix*/ @@ -189,6 +194,7 @@ void tdfb_fir_s32(struct tdfb_comp_data *cd, for (i = 0; i < 2 * in_nch; i++) { AE_L32_XC(d, x, sizeof(int32_t)); cd->in[i] = d; + tdfb_direction_copy_emphasis(cd, in_nch, &emp_ch, cd->in[i]); } for (i = 0; i < cfg->num_filters; i++) { diff --git a/src/include/sof/audio/eq_iir/iir.h b/src/include/sof/audio/eq_iir/iir.h deleted file mode 100644 index 0471a143a0fc..000000000000 --- a/src/include/sof/audio/eq_iir/iir.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * - * Copyright(c) 2017 Intel Corporation. All rights reserved. - * - * Author: Seppo Ingalsuo - * Liam Girdwood - * Keyon Jie - */ - -#ifndef __SOF_AUDIO_EQ_IIR_IIR_H__ -#define __SOF_AUDIO_EQ_IIR_IIR_H__ - -#include -#include -#include -#include - -struct sof_eq_iir_header_df2t; - -int iir_init_coef_df2t(struct iir_state_df2t *iir, - struct sof_eq_iir_header_df2t *config); - -int iir_delay_size_df2t(struct sof_eq_iir_header_df2t *config); - -void iir_init_delay_df2t(struct iir_state_df2t *iir, int64_t **delay); - -void iir_mute_df2t(struct iir_state_df2t *iir); - -void iir_unmute_df2t(struct iir_state_df2t *iir); - -void iir_reset_df2t(struct iir_state_df2t *iir); - -#endif /* __SOF_AUDIO_EQ_IIR_IIR_H__ */ diff --git a/src/include/sof/audio/tdfb/tdfb_comp.h b/src/include/sof/audio/tdfb/tdfb_comp.h index 167fdd4fc05f..4e97f4dc9b62 100644 --- a/src/include/sof/audio/tdfb/tdfb_comp.h +++ b/src/include/sof/audio/tdfb/tdfb_comp.h @@ -13,6 +13,7 @@ #include #include #include +#include #include /* Select optimized code variant when xt-xcc compiler is used */ @@ -50,8 +51,35 @@ #define TDFB_GET_CTRL_DATA_SIZE (sizeof(struct sof_ipc_ctrl_data) + \ sizeof(struct sof_ipc_ctrl_value_chan)) +/* Process max 10% more frames than one period */ +#define TDFB_MAX_FRAMES_MULT_Q14 Q_CONVERT_FLOAT(1.10, 14) + /* TDFB component private data */ +struct tdfb_direction_data { + struct iir_state_df2t emphasis[PLATFORM_MAX_CHANNELS]; + int32_t timediff[PLATFORM_MAX_CHANNELS]; + int32_t timediff_iter[PLATFORM_MAX_CHANNELS]; + int64_t level_ambient; + uint32_t trigger; + int32_t level; + int32_t unit_delay; /* Q1.31 seconds */ + int32_t frame_count_since_control; + int64_t *df2t_delay; + int32_t *r; + int16_t *d; + int16_t *d_end; + int16_t *wp; + int16_t *rp; + int16_t step_sign; + int16_t az_slow; + int16_t az; + int16_t max_lag; + size_t d_size; + size_t r_size; + int line_array:1; /* Limit scan to -90 to 90 degrees */ +}; + struct tdfb_comp_data { struct fir_state_32x16 fir[SOF_TDFB_FIR_MAX_COUNT]; /**< FIR state */ struct comp_data_blob_handler *model_handler; @@ -60,6 +88,7 @@ struct tdfb_comp_data { struct sof_tdfb_mic_location *mic_locations; struct sof_ipc_ctrl_data *ctrl_data; struct ipc_msg *msg; + struct tdfb_direction_data direction; int32_t in[TDFB_IN_BUF_LENGTH]; /**< input samples buffer */ int32_t out[TDFB_IN_BUF_LENGTH]; /**< output samples mix buffer */ int32_t *fir_delay; /**< pointer to allocated RAM */ @@ -69,10 +98,11 @@ struct tdfb_comp_data { int16_t az_value; /**< beam steer azimuth as in control enum */ int16_t az_value_estimate; /**< beam steer azimuth as in control enum */ size_t fir_delay_size; /**< allocated size */ - int direction_updates:1; /**< set true if direction angle control is updated */ - int direction_change:1; /**< set if direction value has significant change */ - int beam_on:1; /**< set true if beam is off */ - int update:1; /**< set true if control enum has been received */ + unsigned int max_frames; /**< max frames to process */ + bool direction_updates:1; /**< set true if direction angle control is updated */ + bool direction_change:1; /**< set if direction value has significant change */ + bool beam_on:1; /**< set true if beam is off */ + bool update:1; /**< set true if control enum has been received */ void (*tdfb_func)(struct tdfb_comp_data *cd, const struct audio_stream *source, struct audio_stream *sink, @@ -97,4 +127,21 @@ void tdfb_fir_s32(struct tdfb_comp_data *cd, struct audio_stream *sink, int frames); #endif -#endif /* __SOF_AUDIO_EQ_FIR_FIR_CONFIG_H__ */ +int tdfb_direction_init(struct tdfb_comp_data *cd, int32_t fs, int channels); +void tdfb_direction_copy_emphasis(struct tdfb_comp_data *cd, int channels, int *channel, int32_t x); +void tdfb_direction_estimate(struct tdfb_comp_data *cd, int frames, int channels); +void tdfb_direction_free(struct tdfb_comp_data *cd); + +static inline void tdfb_cinc_s16(int16_t **ptr, int16_t *end, size_t size) +{ + if (*ptr >= end) + *ptr = (int16_t *)((uint8_t *)*ptr - size); +} + +static inline void tdfb_cdec_s16(int16_t **ptr, int16_t *start, size_t size) +{ + if (*ptr < start) + *ptr = (int16_t *)((uint8_t *)*ptr + size); +} + +#endif /* __SOF_AUDIO_TDFB_CONFIG_H__ */ diff --git a/src/include/sof/math/iir_df2t.h b/src/include/sof/math/iir_df2t.h index 1836368f593b..598e5ba36d50 100644 --- a/src/include/sof/math/iir_df2t.h +++ b/src/include/sof/math/iir_df2t.h @@ -58,6 +58,21 @@ struct iir_state_df2t { int64_t *delay; /* Pointer to IIR delay line */ }; +struct sof_eq_iir_header_df2t; + +int iir_init_coef_df2t(struct iir_state_df2t *iir, + struct sof_eq_iir_header_df2t *config); + +int iir_delay_size_df2t(struct sof_eq_iir_header_df2t *config); + +void iir_init_delay_df2t(struct iir_state_df2t *iir, int64_t **delay); + +void iir_mute_df2t(struct iir_state_df2t *iir); + +void iir_unmute_df2t(struct iir_state_df2t *iir); + +void iir_reset_df2t(struct iir_state_df2t *iir); + int32_t iir_df2t(struct iir_state_df2t *iir, int32_t x); /* Inline functions with or without HiFi3 intrinsics */ diff --git a/src/include/sof/math/numbers.h b/src/include/sof/math/numbers.h index d4bfe82a55ac..1cbe4048d167 100644 --- a/src/include/sof/math/numbers.h +++ b/src/include/sof/math/numbers.h @@ -54,6 +54,32 @@ static inline int ceil_divide(int a, int b) return c; } +/** + * \brief Cross product function + * + * Calculate cross product for vectors AB(a, b, c) and AC(d, e, f), where A, B, and C + * are points of a triangle in 3D space. Cross product is used in computational + * geometry. Cross product AB x AC is (b * f - c * e, c * d - a * f, a * e - b * d) + * + * \param[out] px x-axis component of cross product vector + * \param[out] py y-axis component of cross product vector + * \param[out] pz z-axis component of cross product vector + * \param[in] a x-axis component of vector AB + * \param[in] b y-axis component of vector AB + * \param[in] c z-axis component of vector AB + * \param[in] d x-axis component of vector AC + * \param[in] e y-axis component of vector AC + * \param[in] f z-axis component of vector AC + */ +static inline void cross_product_s16(int32_t *px, int32_t *py, int32_t *pz, + int16_t a, int16_t b, int16_t c, + int16_t d, int16_t e, int16_t f) +{ + *px = (int32_t)b * f - (int32_t)c * e; + *py = (int32_t)c * d - (int32_t)a * f; + *pz = (int32_t)a * e - (int32_t)b * d; +} + /* Find indices of equal values in a vector of integer values */ int find_equal_int16(int16_t idx[], int16_t vec[], int n, int vec_length, int max_results); @@ -93,4 +119,7 @@ uint32_t crc32(uint32_t base, const void *data, uint32_t bytes); #define INT_MAX(N) ((int64_t)((1ULL << ((N) - 1)) - 1)) #define INT_MIN(N) ((int64_t)(-((1ULL << ((N) - 1)) - 1) - 1)) +/* Speed of sound (m/s) in 20 C temperature at standard atmospheric pressure */ +#define SPEED_OF_SOUND 343 + #endif /* __SOF_MATH_NUMBERS_H__ */ diff --git a/src/math/CMakeLists.txt b/src/math/CMakeLists.txt index 943f6dfb8d9f..34059337d846 100644 --- a/src/math/CMakeLists.txt +++ b/src/math/CMakeLists.txt @@ -35,5 +35,5 @@ if(CONFIG_MATH_FFT) endif() if(CONFIG_MATH_IIR_DF2T) - add_local_sources(sof iir_df2t_generic.c iir_df2t_hifi3.c) + add_local_sources(sof iir_df2t_generic.c iir_df2t_hifi3.c iir.c) endif() diff --git a/src/audio/eq_iir/iir.c b/src/math/iir.c similarity index 97% rename from src/audio/eq_iir/iir.c rename to src/math/iir.c index ea7644d9db65..0c5ca5e6b0ae 100644 --- a/src/audio/eq_iir/iir.c +++ b/src/math/iir.c @@ -7,8 +7,8 @@ // Keyon Jie #include -#include #include +#include #include #include #include diff --git a/test/cmocka/src/audio/eq_iir/CMakeLists.txt b/test/cmocka/src/audio/eq_iir/CMakeLists.txt index f0667df59cab..ac9ae0c93aa6 100644 --- a/test/cmocka/src/audio/eq_iir/CMakeLists.txt +++ b/test/cmocka/src/audio/eq_iir/CMakeLists.txt @@ -13,7 +13,7 @@ add_compile_options(-DUNIT_TEST) add_library(audio_for_eq_iir STATIC ${PROJECT_SOURCE_DIR}/src/audio/eq_iir/eq_iir.c - ${PROJECT_SOURCE_DIR}/src/audio/eq_iir/iir.c + ${PROJECT_SOURCE_DIR}/src/math/iir.c ${PROJECT_SOURCE_DIR}/src/math/iir_df2t_generic.c ${PROJECT_SOURCE_DIR}/src/math/iir_df2t_hifi3.c ${PROJECT_SOURCE_DIR}/src/audio/buffer.c diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 4375266c9701..93e7d34b78c8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -627,7 +627,7 @@ zephyr_library_sources_ifdef(CONFIG_COMP_FIR zephyr_library_sources_ifdef(CONFIG_COMP_IIR ${SOF_MATH_PATH}/iir_df2t_generic.c ${SOF_MATH_PATH}/iir_df2t_hifi3.c - ${SOF_AUDIO_PATH}/eq_iir/iir.c + ${SOF_MATH_PATH}/iir.c ${SOF_AUDIO_PATH}/eq_iir/eq_iir.c )