diff --git a/src/arch/xtensa/configs/unit_test_defconfig b/src/arch/xtensa/configs/unit_test_defconfig index afe031acedba..f7703eb7f26c 100644 --- a/src/arch/xtensa/configs/unit_test_defconfig +++ b/src/arch/xtensa/configs/unit_test_defconfig @@ -1,2 +1,3 @@ CONFIG_METEORLAKE=y CONFIG_COMP_DRC=y +CONFIG_COMP_DCBLOCK=y diff --git a/src/audio/dcblock/dcblock.c b/src/audio/dcblock/dcblock.c index 15e6139b7a89..8e5b104cb9c2 100644 --- a/src/audio/dcblock/dcblock.c +++ b/src/audio/dcblock/dcblock.c @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -140,25 +142,63 @@ static int dcblock_set_config(struct processing_module *mod, uint32_t config_id, /** * \brief Copies and processes stream data. - * \param[in,out] dev DC Blocking Filter module. + * \param[in,out] mod DC Blocking Filter module. * \return Error code. */ static int dcblock_process(struct processing_module *mod, - struct input_stream_buffer *input_buffers, - int num_input_buffers, - struct output_stream_buffer *output_buffers, - int num_output_buffers) + struct sof_source **sources, int num_of_sources, + struct sof_sink **sinks, int num_of_sinks) { struct comp_data *cd = module_get_private_data(mod); - struct audio_stream *source = input_buffers[0].data; - struct audio_stream *sink = output_buffers[0].data; - uint32_t frames = input_buffers[0].size; - - comp_dbg(mod->dev, "entry"); - - cd->dcblock_func(cd, source, sink, frames); + struct comp_dev *dev = mod->dev; + struct sof_source *source = sources[0]; + struct sof_sink *sink = sinks[0]; + struct cir_buf_source source_buf; + struct cir_buf_sink sink_buf; + size_t source_frame_bytes = source_get_frame_bytes(source); + size_t sink_frame_bytes = sink_get_frame_bytes(sink); + size_t source_bytes, sink_bytes, bytes; + uint32_t frames = source_get_data_frames_available(source); + uint32_t sink_frames = sink_get_free_frames(sink); + int ret; + + comp_dbg(dev, "entry"); + + frames = MIN(frames, sink_frames); + frames = MIN(frames, dev->frames); + if (!frames) + return 0; + + source_bytes = frames * source_frame_bytes; + sink_bytes = frames * sink_frame_bytes; + + /* acquire the source and sink circular buffer views once */ + ret = source_get_data(source, source_bytes, &source_buf.ptr, + &source_buf.buf_start, &bytes); + if (ret) + return ret; + source_buf.buf_end = (const char *)source_buf.buf_start + bytes; + + ret = sink_get_buffer(sink, sink_bytes, &sink_buf.ptr, + &sink_buf.buf_start, &bytes); + if (ret) { + source_release_data(source, 0); + return ret; + } + sink_buf.buf_end = (char *)sink_buf.buf_start + bytes; + + ret = cd->dcblock_func(cd, &source_buf, &sink_buf, frames); + if (ret) { + /* Undo the acquire without consuming source data or + * publishing partially-written output. + */ + source_release_data(source, 0); + sink_commit_buffer(sink, 0); + return ret; + } - module_update_buffer_position(&input_buffers[0], &output_buffers[0], frames); + source_release_data(source, source_bytes); + sink_commit_buffer(sink, sink_bytes); return 0; } @@ -172,27 +212,35 @@ static int dcblock_prepare(struct processing_module *mod, struct sof_sink **sinks, int num_of_sinks) { struct comp_data *cd = module_get_private_data(mod); - struct comp_buffer *sourceb, *sinkb; struct comp_dev *dev = mod->dev; size_t data_size; comp_info(dev, "entry"); /* DC Filter component will only ever have one source and sink buffer */ - sourceb = comp_dev_get_first_data_producer(dev); - sinkb = comp_dev_get_first_data_consumer(dev); - if (!sourceb || !sinkb) { - comp_err(dev, "no source or sink buffer"); - return -ENOTCONN; + if (num_of_sources != 1 || num_of_sinks != 1) { + comp_err(dev, "Invalid number of sources/sinks"); + return -EINVAL; } dcblock_params(mod); /* get source data format */ - cd->source_format = audio_stream_get_frm_fmt(&sourceb->stream); + cd->source_format = source_get_frm_fmt(sources[0]); + + /* get sink data format */ + cd->sink_format = sink_get_frm_fmt(sinks[0]); + + /* The processing uses a single channel count as the frame stride for + * both source and sink, so they must match to avoid corrupt output. + */ + if (source_get_channels(sources[0]) != sink_get_channels(sinks[0])) { + comp_err(dev, "mismatch source/sink stream channels"); + return -EINVAL; + } - /* get sink data format and period bytes */ - cd->sink_format = audio_stream_get_frm_fmt(&sinkb->stream); + /* get channel count for processing */ + cd->channels = source_get_channels(sources[0]); dcblock_init_state(cd); cd->dcblock_func = dcblock_find_func(cd->source_format); @@ -238,7 +286,7 @@ static int dcblock_reset(struct processing_module *mod) static const struct module_interface dcblock_interface = { .init = dcblock_init, .prepare = dcblock_prepare, - .process_audio_stream = dcblock_process, + .process = dcblock_process, .set_configuration = dcblock_set_config, .get_configuration = dcblock_get_config, .reset = dcblock_reset, diff --git a/src/audio/dcblock/dcblock.h b/src/audio/dcblock/dcblock.h index eaf09da22bf2..dca1308b80ba 100644 --- a/src/audio/dcblock/dcblock.h +++ b/src/audio/dcblock/dcblock.h @@ -12,11 +12,17 @@ #include #include #include +#include +#include #include #include struct audio_stream; struct comp_dev; +struct sof_source; +struct sof_sink; +struct cir_buf_source; +struct cir_buf_sink; struct dcblock_state { int32_t x_prev; /**< state variable referring to x[n-1] */ @@ -30,10 +36,10 @@ struct dcblock_state { struct comp_data; -typedef void (*dcblock_func)(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames); +typedef int (*dcblock_func)(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames); /* DC Blocking Filter component private data */ struct comp_data { @@ -48,6 +54,7 @@ struct comp_data { enum sof_ipc_frame source_format; enum sof_ipc_frame sink_format; + int channels; /**< number of channels */ dcblock_func dcblock_func; /**< processing function */ }; diff --git a/src/audio/dcblock/dcblock_generic.c b/src/audio/dcblock/dcblock_generic.c index 4ff5fa11315b..0c8468868949 100644 --- a/src/audio/dcblock/dcblock_generic.c +++ b/src/audio/dcblock/dcblock_generic.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "dcblock.h" @@ -36,119 +37,141 @@ static int32_t dcblock_generic(struct dcblock_state *state, } #if CONFIG_FORMAT_S16LE -static void dcblock_s16_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +/** + * dcblock_s16_default() - Process S16_LE format. + * @cd: DC blocking filter component private data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * Return: Value zero for success, otherwise an error code. + */ +static int dcblock_s16_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - struct dcblock_state *state; - int16_t *x = audio_stream_get_rptr(source); - int16_t *y = audio_stream_get_wptr(sink); - int32_t R; + const int16_t *x = source->ptr; + int16_t *y = sink->ptr; + int samples_without_wrap; + int nch = cd->channels; + int remaining_samples = frames * nch; int32_t tmp; - int idx; - int ch; - int i, n, nmax; - int nch = audio_stream_get_channels(source); - int samples = nch * frames; - - while (samples) { - nmax = audio_stream_samples_without_wrap_s16(source, x); - n = MIN(samples, nmax); - nmax = audio_stream_samples_without_wrap_s16(sink, y); - n = MIN(n, nmax); - for (ch = 0; ch < nch; ch++) { - state = &cd->state[ch]; - R = cd->R_coeffs[ch]; - idx = ch; - for (i = 0; i < n; i += nch) { - tmp = dcblock_generic(state, R, x[idx] << 16); - y[idx] = sat_int16(Q_SHIFT_RND(tmp, 31, 15)); - idx += nch; - } + int ch = 0; + int i; + + while (remaining_samples) { + samples_without_wrap = cir_buf_samples_without_wrap_s16(x, source->buf_end); + samples_without_wrap = MIN(samples_without_wrap, + cir_buf_samples_without_wrap_s16(y, sink->buf_end)); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + tmp = dcblock_generic(&cd->state[ch], cd->R_coeffs[ch], + *x << 16); + *y = sat_int16(Q_SHIFT_RND(tmp, 31, 15)); + x++; + y++; + if (++ch == nch) + ch = 0; } - samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap((void *)x, source->buf_start, source->buf_end); + y = cir_buf_wrap(y, sink->buf_start, sink->buf_end); + remaining_samples -= samples_without_wrap; } + return 0; } #endif /* CONFIG_FORMAT_S16LE */ #if CONFIG_FORMAT_S24LE -static void dcblock_s24_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +/** + * dcblock_s24_default() - Process S24_4LE format. + * @cd: DC blocking filter component private data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * Return: Value zero for success, otherwise an error code. + */ +static int dcblock_s24_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - struct dcblock_state *state; - int32_t *x = audio_stream_get_rptr(source); - int32_t *y = audio_stream_get_wptr(sink); - int32_t R; + const int32_t *x = source->ptr; + int32_t *y = sink->ptr; + int samples_without_wrap; + int nch = cd->channels; + int remaining_samples = frames * nch; int32_t tmp; - int idx; - int ch; - int i, n, nmax; - int nch = audio_stream_get_channels(source); - int samples = nch * frames; - - while (samples) { - nmax = audio_stream_samples_without_wrap_s24(source, x); - n = MIN(samples, nmax); - nmax = audio_stream_samples_without_wrap_s24(sink, y); - n = MIN(n, nmax); - for (ch = 0; ch < nch; ch++) { - state = &cd->state[ch]; - R = cd->R_coeffs[ch]; - idx = ch; - for (i = 0; i < n; i += nch) { - tmp = dcblock_generic(state, R, x[idx] << 8); - y[idx] = sat_int24(Q_SHIFT_RND(tmp, 31, 23)); - idx += nch; - } + int ch = 0; + int i; + + while (remaining_samples) { + samples_without_wrap = cir_buf_samples_without_wrap_s32(x, source->buf_end); + samples_without_wrap = MIN(samples_without_wrap, + cir_buf_samples_without_wrap_s32(y, sink->buf_end)); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + tmp = dcblock_generic(&cd->state[ch], cd->R_coeffs[ch], + *x << 8); + *y = sat_int24(Q_SHIFT_RND(tmp, 31, 23)); + x++; + y++; + if (++ch == nch) + ch = 0; } - samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap((void *)x, source->buf_start, source->buf_end); + y = cir_buf_wrap(y, sink->buf_start, sink->buf_end); + remaining_samples -= samples_without_wrap; } + return 0; } #endif /* CONFIG_FORMAT_S24LE */ #if CONFIG_FORMAT_S32LE -static void dcblock_s32_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +/** + * dcblock_s32_default() - Process S32_LE format. + * @cd: DC blocking filter component private data. + * @source: Source for PCM samples data. + * @sink: Sink for PCM samples data. + * @frames: Number of audio data frames to process. + * + * Return: Value zero for success, otherwise an error code. + */ +static int dcblock_s32_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - struct dcblock_state *state; - int32_t *x = audio_stream_get_rptr(source); - int32_t *y = audio_stream_get_wptr(sink); - int32_t R; - int idx; - int ch; - int i, n, nmax; - int nch = audio_stream_get_channels(source); - int samples = nch * frames; - - while (samples) { - nmax = audio_stream_samples_without_wrap_s32(source, x); - n = MIN(samples, nmax); - nmax = audio_stream_samples_without_wrap_s32(sink, y); - n = MIN(n, nmax); - for (ch = 0; ch < nch; ch++) { - state = &cd->state[ch]; - R = cd->R_coeffs[ch]; - idx = ch; - for (i = 0; i < n; i += nch) { - y[idx] = dcblock_generic(state, R, x[idx]); - idx += nch; - } + const int32_t *x = source->ptr; + int32_t *y = sink->ptr; + int samples_without_wrap; + int nch = cd->channels; + int remaining_samples = frames * nch; + int ch = 0; + int i; + + while (remaining_samples) { + samples_without_wrap = cir_buf_samples_without_wrap_s32(x, source->buf_end); + samples_without_wrap = MIN(samples_without_wrap, + cir_buf_samples_without_wrap_s32(y, sink->buf_end)); + samples_without_wrap = MIN(samples_without_wrap, remaining_samples); + for (i = 0; i < samples_without_wrap; i++) { + *y = dcblock_generic(&cd->state[ch], cd->R_coeffs[ch], + *x); + x++; + y++; + if (++ch == nch) + ch = 0; } - samples -= n; - x = audio_stream_wrap(source, x + n); - y = audio_stream_wrap(sink, y + n); + x = cir_buf_wrap((void *)x, source->buf_start, source->buf_end); + y = cir_buf_wrap(y, sink->buf_start, sink->buf_end); + remaining_samples -= samples_without_wrap; } + + return 0; } #endif /* CONFIG_FORMAT_S32LE */ diff --git a/src/audio/dcblock/dcblock_hifi3.c b/src/audio/dcblock/dcblock_hifi3.c index d49a1ccca986..fe95dfd67216 100644 --- a/src/audio/dcblock/dcblock_hifi3.c +++ b/src/audio/dcblock/dcblock_hifi3.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "dcblock.h" @@ -29,35 +30,44 @@ static inline ae_int32x2 dcblock_cal(ae_int32x2 R, ae_int32x2 state_x, ae_int32 return AE_ROUND32F64SSYM(AE_SLAI64S(out, 1)); } -/* Setup circular for component source */ -static inline void dcblock_set_circular(const struct audio_stream *source) +/* Set source as circular buffer 0 for the strided per-channel reads */ +static inline void dcblock_set_circular(void *src_begin, void *src_end) { - /* Set source as circular buffer 0 */ - AE_SETCBEGIN0(audio_stream_get_addr(source)); - AE_SETCEND0(audio_stream_get_end_addr(source)); + AE_SETCBEGIN0(src_begin); + AE_SETCEND0(src_end); } #if CONFIG_FORMAT_S16LE -static void dcblock_s16_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s16_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int16 *src = audio_stream_get_rptr(source); - ae_int16 *dst = audio_stream_get_wptr(sink); - ae_int16 *in; - ae_int16 *out; + ae_int16 *src = (ae_int16 *)source->ptr; + ae_int16 *dst = (ae_int16 *)sink->ptr; + ae_int16 *x_start = (ae_int16 *)source->buf_start; + ae_int16 *y_start = (ae_int16 *)sink->buf_start; + ae_int16 *x_end = (ae_int16 *)source->buf_end; + ae_int16 *y_end = (ae_int16 *)sink->buf_end; + ae_int16 *in, *out; ae_int32x2 R, state_x, state_y, sample; ae_int16x4 in_sample, out_sample; - int ch, i, n; - int nch = audio_stream_get_channels(source); + int x_size = x_end - x_start; + int y_size = y_end - y_start; + int nch = cd->channels; + int remaining = frames * nch; const int inc = nch * sizeof(ae_int16); - int samples = nch * frames; + int ch, i, n; + + /* Set source as circular buffer 0 for the strided per-channel reads */ + dcblock_set_circular(x_start, x_end); - dcblock_set_circular(source); - while (samples) { - n = audio_stream_samples_without_wrap_s16(sink, dst); - n = MIN(n, samples); + while (remaining) { + /* The sink uses linear stores, so limit the chunk to the sink + * contiguous space. The source wraps via circular addressing. + */ + n = y_end - dst; + n = MIN(n, remaining); for (ch = 0; ch < nch; ch++) { in = src + ch; out = dst + ch; @@ -77,34 +87,46 @@ static void dcblock_s16_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } - samples -= n; - dst = audio_stream_wrap(sink, dst + n); - src = audio_stream_wrap(source, src + n); + remaining -= n; + dst += n; + if (dst >= y_end) + dst -= y_size; + src += n; + if (src >= x_end) + src -= x_size; } + + return 0; } #endif /* CONFIG_FORMAT_S16LE */ #if CONFIG_FORMAT_S24LE -static void dcblock_s24_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s24_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int32 *src = audio_stream_get_rptr(source); - ae_int32 *dst = audio_stream_get_wptr(sink); - ae_int32 *in; - ae_int32 *out; + ae_int32 *src = (ae_int32 *)source->ptr; + ae_int32 *dst = (ae_int32 *)sink->ptr; + ae_int32 *x_start = (ae_int32 *)source->buf_start; + ae_int32 *y_start = (ae_int32 *)sink->buf_start; + ae_int32 *x_end = (ae_int32 *)source->buf_end; + ae_int32 *y_end = (ae_int32 *)sink->buf_end; + ae_int32 *in, *out; ae_int32x2 R, state_x, state_y; ae_int32x2 in_sample, out_sample; - int ch, i, n; - int nch = audio_stream_get_channels(source); + int x_size = x_end - x_start; + int y_size = y_end - y_start; + int nch = cd->channels; + int remaining = frames * nch; const int inc = nch * sizeof(ae_int32); - int samples = nch * frames; + int ch, i, n; - dcblock_set_circular(source); - while (samples) { - n = audio_stream_samples_without_wrap_s24(sink, dst); - n = MIN(n, samples); + dcblock_set_circular(x_start, x_end); + + while (remaining) { + n = y_end - dst; + n = MIN(n, remaining); for (ch = 0; ch < nch; ch++) { in = src + ch; out = dst + ch; @@ -124,34 +146,46 @@ static void dcblock_s24_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } - samples -= n; - dst = audio_stream_wrap(sink, dst + n); - src = audio_stream_wrap(source, src + n); + remaining -= n; + dst += n; + if (dst >= y_end) + dst -= y_size; + src += n; + if (src >= x_end) + src -= x_size; } + + return 0; } #endif /* CONFIG_FORMAT_S24LE */ #if CONFIG_FORMAT_S32LE -static void dcblock_s32_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s32_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int32 *src = audio_stream_get_rptr(source); - ae_int32 *dst = audio_stream_get_wptr(sink); - ae_int32 *in; - ae_int32 *out; + ae_int32 *src = (ae_int32 *)source->ptr; + ae_int32 *dst = (ae_int32 *)sink->ptr; + ae_int32 *x_start = (ae_int32 *)source->buf_start; + ae_int32 *y_start = (ae_int32 *)sink->buf_start; + ae_int32 *x_end = (ae_int32 *)source->buf_end; + ae_int32 *y_end = (ae_int32 *)sink->buf_end; + ae_int32 *in, *out; ae_int32x2 R, state_x, state_y; ae_int32x2 in_sample; - int ch, i, n; - int nch = audio_stream_get_channels(source); + int x_size = x_end - x_start; + int y_size = y_end - y_start; + int nch = cd->channels; + int remaining = frames * nch; const int inc = nch * sizeof(ae_int32); - int samples = nch * frames; + int ch, i, n; + + dcblock_set_circular(x_start, x_end); - dcblock_set_circular(source); - while (samples) { - n = audio_stream_samples_without_wrap_s32(sink, dst); - n = MIN(n, samples); + while (remaining) { + n = y_end - dst; + n = MIN(n, remaining); for (ch = 0; ch < nch; ch++) { in = src + ch; out = dst + ch; @@ -167,10 +201,16 @@ static void dcblock_s32_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } - samples -= n; - dst = audio_stream_wrap(sink, dst + n); - src = audio_stream_wrap(source, src + n); + remaining -= n; + dst += n; + if (dst >= y_end) + dst -= y_size; + src += n; + if (src >= x_end) + src -= x_size; } + + return 0; } #endif /* CONFIG_FORMAT_S32LE */ diff --git a/src/audio/dcblock/dcblock_hifi4.c b/src/audio/dcblock/dcblock_hifi4.c index f6ff06a55a63..50a61acda505 100644 --- a/src/audio/dcblock/dcblock_hifi4.c +++ b/src/audio/dcblock/dcblock_hifi4.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "dcblock.h" @@ -29,37 +30,40 @@ static inline ae_int32x2 dcblock_cal(ae_int32x2 R, ae_int32x2 state_x, ae_int32 return AE_ROUND32F64SSYM(AE_SLAI64S(out, 1)); } -/* Setup circular for component sink and source */ -static inline void dcblock_set_circular(const struct audio_stream *source, - const struct audio_stream *sink) +/* Set source as circular buffer 0 and sink as circular buffer 1 */ +static inline void dcblock_set_circular(void *src_begin, void *src_end, + void *sink_begin, void *sink_end) { - /* Set source as circular buffer 0 */ - AE_SETCBEGIN0(audio_stream_get_addr(source)); - AE_SETCEND0(audio_stream_get_end_addr(source)); - - /* Set sink as circular buffer 1 */ - AE_SETCBEGIN1(audio_stream_get_addr(sink)); - AE_SETCEND1(audio_stream_get_end_addr(sink)); + AE_SETCBEGIN0(src_begin); + AE_SETCEND0(src_end); + AE_SETCBEGIN1(sink_begin); + AE_SETCEND1(sink_end); } #if CONFIG_FORMAT_S16LE -static void dcblock_s16_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s16_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int16 *in; - ae_int16 *out; + ae_int16 *src = (ae_int16 *)source->ptr; + ae_int16 *dst = (ae_int16 *)sink->ptr; + ae_int16 *x_start = (ae_int16 *)source->buf_start; + ae_int16 *y_start = (ae_int16 *)sink->buf_start; + ae_int16 *x_end = (ae_int16 *)source->buf_end; + ae_int16 *y_end = (ae_int16 *)sink->buf_end; + ae_int16 *in, *out; ae_int32x2 R, state_x, state_y, sample; ae_int16x4 in_sample, out_sample; - int ch, i; - int nch = audio_stream_get_channels(source); + int nch = cd->channels; const int inc = nch * sizeof(ae_int16); + int ch, i; + + dcblock_set_circular(x_start, x_end, y_start, y_end); - dcblock_set_circular(source, sink); for (ch = 0; ch < nch; ch++) { - in = (ae_int16 *)audio_stream_get_rptr(source) + ch; - out = (ae_int16 *)audio_stream_get_wptr(sink) + ch; + in = src + ch; + out = dst + ch; state_x = cd->state[ch].x_prev; state_y = cd->state[ch].y_prev; R = cd->R_coeffs[ch]; @@ -76,28 +80,35 @@ static void dcblock_s16_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } + + return 0; } #endif /* CONFIG_FORMAT_S16LE */ #if CONFIG_FORMAT_S24LE -static void dcblock_s24_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s24_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int32 *in; - ae_int32 *out; + ae_int32 *src = (ae_int32 *)source->ptr; + ae_int32 *dst = (ae_int32 *)sink->ptr; + ae_int32 *x_start = (ae_int32 *)source->buf_start; + ae_int32 *y_start = (ae_int32 *)sink->buf_start; + ae_int32 *x_end = (ae_int32 *)source->buf_end; + ae_int32 *y_end = (ae_int32 *)sink->buf_end; + ae_int32 *in, *out; ae_int32x2 R, state_x, state_y; ae_int32x2 in_sample, out_sample; - int ch, i; - int nch = audio_stream_get_channels(source); + int nch = cd->channels; const int inc = nch * sizeof(ae_int32); + int ch, i; - dcblock_set_circular(source, sink); - for (ch = 0; ch < nch; ch++) { - in = (ae_int32 *)audio_stream_get_rptr(source) + ch; - out = (ae_int32 *)audio_stream_get_wptr(sink) + ch; + dcblock_set_circular(x_start, x_end, y_start, y_end); + for (ch = 0; ch < nch; ch++) { + in = src + ch; + out = dst + ch; state_x = cd->state[ch].x_prev; state_y = cd->state[ch].y_prev; R = cd->R_coeffs[ch]; @@ -114,28 +125,35 @@ static void dcblock_s24_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } + + return 0; } #endif /* CONFIG_FORMAT_S24LE */ #if CONFIG_FORMAT_S32LE -static void dcblock_s32_default(struct comp_data *cd, - const struct audio_stream *source, - const struct audio_stream *sink, - uint32_t frames) +static int dcblock_s32_default(struct comp_data *cd, + struct cir_buf_source *source, + struct cir_buf_sink *sink, + uint32_t frames) { - ae_int32 *in; - ae_int32 *out; + ae_int32 *src = (ae_int32 *)source->ptr; + ae_int32 *dst = (ae_int32 *)sink->ptr; + ae_int32 *x_start = (ae_int32 *)source->buf_start; + ae_int32 *y_start = (ae_int32 *)sink->buf_start; + ae_int32 *x_end = (ae_int32 *)source->buf_end; + ae_int32 *y_end = (ae_int32 *)sink->buf_end; + ae_int32 *in, *out; ae_int32x2 R, state_x, state_y; ae_int32x2 in_sample; - int ch, i; - int nch = audio_stream_get_channels(source); + int nch = cd->channels; const int inc = nch * sizeof(ae_int32); + int ch, i; - dcblock_set_circular(source, sink); - for (ch = 0; ch < nch; ch++) { - in = (ae_int32 *)audio_stream_get_rptr(source) + ch; - out = (ae_int32 *)audio_stream_get_wptr(sink) + ch; + dcblock_set_circular(x_start, x_end, y_start, y_end); + for (ch = 0; ch < nch; ch++) { + in = src + ch; + out = dst + ch; state_x = cd->state[ch].x_prev; state_y = cd->state[ch].y_prev; R = cd->R_coeffs[ch]; @@ -148,6 +166,8 @@ static void dcblock_s32_default(struct comp_data *cd, cd->state[ch].x_prev = state_x; cd->state[ch].y_prev = state_y; } + + return 0; } #endif /* CONFIG_FORMAT_S32LE */ diff --git a/test/cmocka/src/audio/CMakeLists.txt b/test/cmocka/src/audio/CMakeLists.txt index d199fd18a0ea..a5b300b6f232 100644 --- a/test/cmocka/src/audio/CMakeLists.txt +++ b/test/cmocka/src/audio/CMakeLists.txt @@ -25,3 +25,6 @@ endif() if(CONFIG_COMP_DRC) add_subdirectory(drc) endif() +if(CONFIG_COMP_DCBLOCK) + add_subdirectory(dcblock) +endif() diff --git a/test/cmocka/src/audio/dcblock/CMakeLists.txt b/test/cmocka/src/audio/dcblock/CMakeLists.txt new file mode 100644 index 000000000000..494d59217f7d --- /dev/null +++ b/test/cmocka/src/audio/dcblock/CMakeLists.txt @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: BSD-3-Clause + +cmocka_test(dcblock_math_test + dcblock_math_test.c + ${PROJECT_SOURCE_DIR}/src/audio/dcblock/dcblock_generic.c + ${PROJECT_SOURCE_DIR}/src/math/numbers.c +) + +target_include_directories(dcblock_math_test PRIVATE ${PROJECT_SOURCE_DIR}/src/audio) +target_include_directories(dcblock_math_test PRIVATE ${PROJECT_SOURCE_DIR}/src/audio/dcblock) + +cmocka_test(dcblock_process + dcblock_process.c +) + +target_include_directories(dcblock_process PRIVATE ${PROJECT_SOURCE_DIR}/src/audio) +target_include_directories(dcblock_process PRIVATE ${PROJECT_SOURCE_DIR}/src/audio/dcblock) + +# make small version of libaudio so we don't have to care +# about unused missing references + +add_compile_options(-DUNIT_TEST) + +add_library(audio_for_dcblock STATIC + ${PROJECT_SOURCE_DIR}/src/audio/dcblock/dcblock.c + ${PROJECT_SOURCE_DIR}/src/audio/dcblock/dcblock_generic.c + ${PROJECT_SOURCE_DIR}/src/audio/dcblock/dcblock_ipc3.c + ${PROJECT_SOURCE_DIR}/src/math/numbers.c + ${PROJECT_SOURCE_DIR}/src/audio/module_adapter/module_adapter.c + ${PROJECT_SOURCE_DIR}/src/audio/module_adapter/module_adapter_ipc3.c + ${PROJECT_SOURCE_DIR}/src/audio/module_adapter/module/generic.c + ${PROJECT_SOURCE_DIR}/src/audio/buffers/comp_buffer.c + ${PROJECT_SOURCE_DIR}/src/audio/buffers/audio_buffer.c + ${PROJECT_SOURCE_DIR}/src/audio/source_api_helper.c + ${PROJECT_SOURCE_DIR}/src/audio/sink_api_helper.c + ${PROJECT_SOURCE_DIR}/src/audio/sink_source_utils.c + ${PROJECT_SOURCE_DIR}/src/audio/audio_stream.c + ${PROJECT_SOURCE_DIR}/src/audio/component.c + ${PROJECT_SOURCE_DIR}/src/audio/data_blob.c + ${PROJECT_SOURCE_DIR}/src/module/audio/source_api.c + ${PROJECT_SOURCE_DIR}/src/module/audio/sink_api.c + ${PROJECT_SOURCE_DIR}/src/ipc/ipc3/helper.c + ${PROJECT_SOURCE_DIR}/src/ipc/ipc-common.c + ${PROJECT_SOURCE_DIR}/src/ipc/ipc-helper.c + ${PROJECT_SOURCE_DIR}/src/lib/objpool.c + ${PROJECT_SOURCE_DIR}/test/cmocka/src/notifier_mocks.c + ${PROJECT_SOURCE_DIR}/src/audio/pipeline/pipeline-graph.c + ${PROJECT_SOURCE_DIR}/src/audio/pipeline/pipeline-params.c + ${PROJECT_SOURCE_DIR}/src/audio/pipeline/pipeline-schedule.c + ${PROJECT_SOURCE_DIR}/src/audio/pipeline/pipeline-stream.c + ${PROJECT_SOURCE_DIR}/src/audio/pipeline/pipeline-xrun.c +) +sof_append_relative_path_definitions(audio_for_dcblock) + +target_link_libraries(audio_for_dcblock PRIVATE sof_options) + +target_link_libraries(dcblock_process PRIVATE audio_for_dcblock) diff --git a/test/cmocka/src/audio/dcblock/dcblock_math_test.c b/test/cmocka/src/audio/dcblock/dcblock_math_test.c new file mode 100644 index 000000000000..6de4179e6f7b --- /dev/null +++ b/test/cmocka/src/audio/dcblock/dcblock_math_test.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "dcblock.h" + +#define TEST_CHANNELS 2 +#define TEST_FRAMES 256 + +/* + * The dcblock processing functions operate on struct cir_buf_source / + * struct cir_buf_sink circular buffer views. The tests use static, non + * wrapping sample buffers, so the views simply span the whole array. + */ +static void cir_buf_src_setup(struct cir_buf_source *s, const void *data, size_t bytes) +{ + s->buf_start = data; + s->buf_end = (const char *)data + bytes; + s->ptr = data; +} + +static void cir_buf_snk_setup(struct cir_buf_sink *s, void *data, size_t bytes) +{ + s->buf_start = data; + s->buf_end = (char *)data + bytes; + s->ptr = data; +} + +/* Q2.30 coefficient close to 1.0 used for the DC removal test cases. */ +#define R_COEF_NEAR_ONE 1063004406 /* ~0.99 in Q2.30 */ + +/* + * Reference DC blocking filter implemented in floating point. Mirrors the + * fixed point recurrence y[n] = x[n] - x[n-1] + R * y[n-1] where R is the + * Q2.30 coefficient converted to a real number. + */ +struct ref_state { + double x_prev; + double y_prev; +}; + +static double dcblock_ref(struct ref_state *s, double r, double x) +{ + double y = x - s->x_prev + r * s->y_prev; + + s->x_prev = x; + s->y_prev = y; + return y; +} + +/* Fill the source buffer with a per-channel sinusoid plus a DC offset. */ +static void gen_input(double *ref_in, int channels, int frames, double dc) +{ + int ch, i; + + for (i = 0; i < frames; i++) { + for (ch = 0; ch < channels; ch++) { + double phase = 2.0 * M_PI * (i + 1) * (ch + 1) / 64.0; + + ref_in[i * channels + ch] = dc + 0.3 * sin(phase); + } + } +} + +/* + * Runs the S32 processing function and compares it to the floating point + * reference. Returns the mean absolute value of the last quarter of the + * output which is used to check DC convergence. + */ +static double run_s32_case(int32_t r_coeff, double dc, double tol_rel) +{ + struct comp_data cd; + struct cir_buf_source csrc; + struct cir_buf_sink csnk; + int32_t src[TEST_FRAMES * TEST_CHANNELS]; + int32_t dst[TEST_FRAMES * TEST_CHANNELS]; + double ref_in[TEST_FRAMES * TEST_CHANNELS]; + struct ref_state rstate[TEST_CHANNELS]; + double r = (double)r_coeff / (double)ONE_Q2_30; + double tail_abs_sum = 0.0; + int tail_count = 0; + dcblock_func func; + int ch, i; + + memset(&cd, 0, sizeof(cd)); + cd.channels = TEST_CHANNELS; + for (ch = 0; ch < TEST_CHANNELS; ch++) + cd.R_coeffs[ch] = r_coeff; + + memset(rstate, 0, sizeof(rstate)); + gen_input(ref_in, TEST_CHANNELS, TEST_FRAMES, dc); + + for (i = 0; i < TEST_FRAMES * TEST_CHANNELS; i++) + src[i] = (int32_t)round(ref_in[i] * 2147483647.0); + + cir_buf_src_setup(&csrc, src, sizeof(src)); + cir_buf_snk_setup(&csnk, dst, sizeof(dst)); + + func = dcblock_find_func(SOF_IPC_FRAME_S32_LE); + assert_non_null(func); + assert_int_equal(func(&cd, &csrc, &csnk, TEST_FRAMES), 0); + + for (i = 0; i < TEST_FRAMES; i++) { + for (ch = 0; ch < TEST_CHANNELS; ch++) { + int idx = i * TEST_CHANNELS + ch; + double refy = dcblock_ref(&rstate[ch], r, + (double)src[idx] / 2147483648.0); + double outy = (double)dst[idx] / 2147483648.0; + double delta = fabs(refy - outy); + + if (delta > tol_rel) { + printf("s32 mismatch idx %d ref %g out %g delta %g\n", + idx, refy, outy, delta); + assert_true(delta <= tol_rel); + } + + if (i >= TEST_FRAMES * 3 / 4) { + tail_abs_sum += fabs(outy); + tail_count++; + } + } + } + + return tail_count ? tail_abs_sum / tail_count : 0.0; +} + +/* Passthrough: R = 1.0 gives y[n] = x[n] - x[n-1], a pure differentiator. */ +static void test_dcblock_passthrough(void **state) +{ + (void)state; + + /* With no DC and R=1.0 the reference matches bit-close. */ + run_s32_case(ONE_Q2_30, 0.0, 1.0e-6); +} + +/* A strong DC offset must be attenuated towards zero in steady state. */ +static void test_dcblock_dc_removal(void **state) +{ + (void)state; + + double tail = run_s32_case(R_COEF_NEAR_ONE, 0.5, 1.0e-6); + + /* The residual DC plus small sinusoid must be well below the input DC. */ + printf("dc_removal tail mean abs = %g\n", tail); + assert_true(tail < 0.25); +} + +/* Full scale input must not overflow (saturation path in dcblock_generic). */ +static void test_dcblock_saturation(void **state) +{ + (void)state; + + struct comp_data cd; + struct cir_buf_source csrc; + struct cir_buf_sink csnk; + int32_t src[TEST_FRAMES * TEST_CHANNELS]; + int32_t dst[TEST_FRAMES * TEST_CHANNELS]; + dcblock_func func; + int i; + + memset(&cd, 0, sizeof(cd)); + cd.channels = TEST_CHANNELS; + for (i = 0; i < TEST_CHANNELS; i++) + cd.R_coeffs[i] = ONE_Q2_30; + + /* Alternating +full/-full scale is the worst case for the difference. */ + for (i = 0; i < TEST_FRAMES * TEST_CHANNELS; i++) + src[i] = (i & 1) ? INT32_MAX : INT32_MIN; + + cir_buf_src_setup(&csrc, src, sizeof(src)); + cir_buf_snk_setup(&csnk, dst, sizeof(dst)); + + func = dcblock_find_func(SOF_IPC_FRAME_S32_LE); + assert_non_null(func); + assert_int_equal(func(&cd, &csrc, &csnk, TEST_FRAMES), 0); + + for (i = 0; i < TEST_FRAMES * TEST_CHANNELS; i++) { + assert_true(dst[i] <= INT32_MAX); + assert_true(dst[i] >= INT32_MIN); + } +} + +/* Bit-exactness check against the floating point reference for S32. */ +static void test_dcblock_bitexact_s32(void **state) +{ + (void)state; + + run_s32_case(R_COEF_NEAR_ONE, 0.1, 1.0e-6); +} + +/* + * Runs the S16 processing function and compares it to the floating point + * reference. The component internally works in Q1.31, so the tolerance must + * account for the 16-bit output quantization step (2 LSB of S16). + */ +static void run_s16_case(int32_t r_coeff, double dc) +{ + struct comp_data cd; + struct cir_buf_source csrc; + struct cir_buf_sink csnk; + int16_t src[TEST_FRAMES * TEST_CHANNELS]; + int16_t dst[TEST_FRAMES * TEST_CHANNELS]; + double ref_in[TEST_FRAMES * TEST_CHANNELS]; + struct ref_state rstate[TEST_CHANNELS]; + double r = (double)r_coeff / (double)ONE_Q2_30; + double tol = 2.0 / 32768.0; + dcblock_func func; + int ch, i; + + memset(&cd, 0, sizeof(cd)); + cd.channels = TEST_CHANNELS; + for (ch = 0; ch < TEST_CHANNELS; ch++) + cd.R_coeffs[ch] = r_coeff; + + memset(rstate, 0, sizeof(rstate)); + gen_input(ref_in, TEST_CHANNELS, TEST_FRAMES, dc); + + for (i = 0; i < TEST_FRAMES * TEST_CHANNELS; i++) + src[i] = (int16_t)round(ref_in[i] * 32767.0); + + cir_buf_src_setup(&csrc, src, sizeof(src)); + cir_buf_snk_setup(&csnk, dst, sizeof(dst)); + + func = dcblock_find_func(SOF_IPC_FRAME_S16_LE); + assert_non_null(func); + assert_int_equal(func(&cd, &csrc, &csnk, TEST_FRAMES), 0); + + for (i = 0; i < TEST_FRAMES; i++) { + for (ch = 0; ch < TEST_CHANNELS; ch++) { + int idx = i * TEST_CHANNELS + ch; + double refy = dcblock_ref(&rstate[ch], r, + (double)src[idx] / 32768.0); + double outy = (double)dst[idx] / 32768.0; + double delta = fabs(refy - outy); + + if (delta > tol) { + printf("s16 mismatch idx %d ref %g out %g delta %g\n", + idx, refy, outy, delta); + assert_true(delta <= tol); + } + } + } +} + +/* Bit-exactness check against the floating point reference for S16. */ +static void test_dcblock_bitexact_s16(void **state) +{ + (void)state; + + run_s16_case(R_COEF_NEAR_ONE, 0.1); +} + +/* + * Runs the S24 (in 32-bit container) processing function and compares it to + * the floating point reference with a tolerance of 2 LSB of S24. + */ +static void run_s24_case(int32_t r_coeff, double dc) +{ + struct comp_data cd; + struct cir_buf_source csrc; + struct cir_buf_sink csnk; + int32_t src[TEST_FRAMES * TEST_CHANNELS]; + int32_t dst[TEST_FRAMES * TEST_CHANNELS]; + double ref_in[TEST_FRAMES * TEST_CHANNELS]; + struct ref_state rstate[TEST_CHANNELS]; + double r = (double)r_coeff / (double)ONE_Q2_30; + double tol = 2.0 / 8388608.0; + dcblock_func func; + int ch, i; + + memset(&cd, 0, sizeof(cd)); + cd.channels = TEST_CHANNELS; + for (ch = 0; ch < TEST_CHANNELS; ch++) + cd.R_coeffs[ch] = r_coeff; + + memset(rstate, 0, sizeof(rstate)); + gen_input(ref_in, TEST_CHANNELS, TEST_FRAMES, dc); + + for (i = 0; i < TEST_FRAMES * TEST_CHANNELS; i++) + src[i] = (int32_t)round(ref_in[i] * 8388607.0); + + cir_buf_src_setup(&csrc, src, sizeof(src)); + cir_buf_snk_setup(&csnk, dst, sizeof(dst)); + + func = dcblock_find_func(SOF_IPC_FRAME_S24_4LE); + assert_non_null(func); + func(&cd, &csrc, &csnk, TEST_FRAMES); + + for (i = 0; i < TEST_FRAMES; i++) { + for (ch = 0; ch < TEST_CHANNELS; ch++) { + int idx = i * TEST_CHANNELS + ch; + double refy = dcblock_ref(&rstate[ch], r, + (double)src[idx] / 8388608.0); + double outy = (double)dst[idx] / 8388608.0; + double delta = fabs(refy - outy); + + if (delta > tol) { + printf("s24 mismatch idx %d ref %g out %g delta %g\n", + idx, refy, outy, delta); + assert_true(delta <= tol); + } + } + } +} + +/* Bit-exactness check against the floating point reference for S24. */ +static void test_dcblock_bitexact_s24(void **state) +{ + (void)state; + + run_s24_case(R_COEF_NEAR_ONE, 0.1); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_dcblock_passthrough), + cmocka_unit_test(test_dcblock_dc_removal), + cmocka_unit_test(test_dcblock_saturation), + cmocka_unit_test(test_dcblock_bitexact_s32), + cmocka_unit_test(test_dcblock_bitexact_s16), + cmocka_unit_test(test_dcblock_bitexact_s24), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/test/cmocka/src/audio/dcblock/dcblock_process.c b/test/cmocka/src/audio/dcblock/dcblock_process.c new file mode 100644 index 000000000000..12f7def51959 --- /dev/null +++ b/test/cmocka/src/audio/dcblock/dcblock_process.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dcblock.h" + +#include "../../util.h" +#include "../../../include/cmocka_chirp_2ch.h" + +/* Allow a few LSB of error due to the internal Q-format rounding. */ +#define ERROR_TOLERANCE_S16 2 +#define ERROR_TOLERANCE_S24 4 +#define ERROR_TOLERANCE_S32 2048 + +/* Q2.30 DC blocking coefficient (~0.98) used for all channels. */ +#define R_COEF 1052266987 + +/* Thresholds for frames count jitter for rand() function */ +#define THR_RAND_PLUS_ONE ((RAND_MAX >> 1) + (RAND_MAX >> 2)) +#define THR_RAND_MINUS_ONE ((RAND_MAX >> 1) - (RAND_MAX >> 2)) + +struct buffer_state { + int idx; +}; + +static struct buffer_state buffer_fill_data; +static struct buffer_state buffer_verify_data; + +/* Floating point reference filter state, one per channel. */ +struct ref_state { + double x_prev; + double y_prev; +}; + +static struct ref_state ref_states[PLATFORM_MAX_CHANNELS]; + +static double dcblock_ref(int ch, double x) +{ + struct ref_state *s = &ref_states[ch]; + double r = (double)R_COEF / (double)ONE_Q2_30; + double y = x - s->x_prev + r * s->y_prev; + + /* The fixed point filter saturates the Q1.31 state after every + * sample (sat_int32). The DC blocker has a passband gain slightly + * above one, so without this clamp the reference state would drift + * away from the implementation for near full-scale input. + */ + if (y > 2147483647.0 / 2147483648.0) + y = 2147483647.0 / 2147483648.0; + else if (y < -1.0) + y = -1.0; + + s->x_prev = x; + s->y_prev = y; + return y; +} + +struct test_parameters { + uint32_t channels; + uint32_t frames; + uint32_t buffer_size_mult; + uint32_t source_format; + uint32_t sink_format; +}; + +struct test_data { + struct comp_dev *dev; + struct comp_buffer *sink; + struct comp_buffer *source; + struct test_parameters *params; + struct processing_module *mod; + bool continue_loop; +}; + +static int setup_group(void **state) +{ + sys_comp_init(sof_get()); + sys_comp_module_dcblock_interface_init(); + return 0; +} + +static struct sof_ipc_comp_process *create_dcblock_comp_ipc(struct test_data *td) +{ + struct sof_ipc_comp_process *ipc; + size_t ipc_size = sizeof(struct sof_ipc_comp_process); + const struct sof_uuid uuid = SOF_REG_UUID(dcblock); + + ipc = calloc(1, ipc_size + SOF_UUID_SIZE); + memcpy_s(ipc + 1, SOF_UUID_SIZE, &uuid, SOF_UUID_SIZE); + ipc->comp.hdr.size = ipc_size + SOF_UUID_SIZE; + ipc->comp.type = SOF_COMP_MODULE_ADAPTER; + ipc->config.hdr.size = sizeof(struct sof_ipc_comp_config); + ipc->size = 0; + ipc->comp.ext_data_length = SOF_UUID_SIZE; + return ipc; +} + +/* Send a configuration blob holding the per-channel R coefficients. */ +static int dcblock_send_config(struct processing_module *mod) +{ + const struct module_interface *const ops = mod->dev->drv->adapter_ops; + const size_t coeff_size = PLATFORM_MAX_CHANNELS * sizeof(int32_t); + size_t cdata_size = sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_abi_hdr) + coeff_size; + struct sof_ipc_ctrl_data *cdata; + int32_t coeffs[PLATFORM_MAX_CHANNELS]; + int ret; + int i; + + cdata = calloc(1, cdata_size); + if (!cdata) + return -ENOMEM; + + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) + coeffs[i] = R_COEF; + + cdata->cmd = SOF_CTRL_CMD_BINARY; + cdata->num_elems = coeff_size; + cdata->data[0].magic = SOF_ABI_MAGIC; + cdata->data[0].type = 0; + cdata->data[0].size = coeff_size; + cdata->data[0].abi = SOF_ABI_VERSION; + memcpy_s(cdata->data[0].data, coeff_size, coeffs, coeff_size); + + ret = ops->set_configuration(mod, 0, MODULE_CFG_FRAGMENT_SINGLE, + coeff_size, (const uint8_t *)cdata, + coeff_size, NULL, 0); + + free(cdata); + return ret; +} + +static void prepare_sink(struct test_data *td, struct processing_module *mod) +{ + struct test_parameters *parameters = td->params; + struct module_data *md = &mod->priv; + size_t size; + size_t free; + + size = parameters->frames * get_frame_bytes(parameters->sink_format, parameters->channels) * + parameters->buffer_size_mult; + + md->mpd.out_buff_size = parameters->frames * get_frame_bytes(parameters->sink_format, + parameters->channels); + + td->sink = create_test_sink(td->dev, 0, parameters->sink_format, + parameters->channels, size); + free = audio_stream_get_free_bytes(&td->sink->stream); + assert_int_equal(free, size); +} + +static void prepare_source(struct test_data *td, struct processing_module *mod) +{ + struct test_parameters *parameters = td->params; + struct module_data *md = &mod->priv; + size_t size; + size_t free; + + md->mpd.in_buff_size = parameters->frames * get_frame_bytes(parameters->source_format, + parameters->channels); + + size = parameters->frames * get_frame_bytes(parameters->source_format, + parameters->channels) * parameters->buffer_size_mult; + + td->source = create_test_source(td->dev, 0, parameters->source_format, + parameters->channels, size); + free = audio_stream_get_free_bytes(&td->source->stream); + assert_int_equal(free, size); +} + +static int setup(void **state) +{ + struct test_parameters *params = *state; + struct processing_module *mod; + struct test_data *td; + struct sof_ipc_comp_process *ipc; + struct comp_dev *dev; + struct sof_source *sources[1]; + struct sof_sink *sinks[1]; + int ret; + int i; + + td = test_malloc(sizeof(*td)); + if (!td) + return -EINVAL; + + td->params = test_malloc(sizeof(*params)); + if (!td->params) { + test_free(td); + return -EINVAL; + } + + memcpy_s(td->params, sizeof(*td->params), params, sizeof(*params)); + ipc = create_dcblock_comp_ipc(td); + buffer_fill_data.idx = 0; + buffer_verify_data.idx = 0; + for (i = 0; i < PLATFORM_MAX_CHANNELS; i++) { + ref_states[i].x_prev = 0.0; + ref_states[i].y_prev = 0.0; + } + + dev = comp_new((struct sof_ipc_comp *)ipc); + free(ipc); + if (!dev) + return -EINVAL; + + td->dev = dev; + dev->frames = params->frames; + mod = comp_mod(dev); + + ret = dcblock_send_config(mod); + if (ret) + return ret; + + prepare_sink(td, mod); + prepare_source(td, mod); + + mod->input_buffers = test_malloc(sizeof(struct input_stream_buffer)); + mod->input_buffers[0].data = &td->source->stream; + mod->output_buffers = test_malloc(sizeof(struct output_stream_buffer)); + mod->output_buffers[0].data = &td->sink->stream; + mod->stream_params = test_malloc(sizeof(struct sof_ipc_stream_params)); + mod->stream_params->channels = params->channels; + mod->period_bytes = get_frame_bytes(params->source_format, params->channels) * 48000 / 1000; + + sources[0] = audio_buffer_get_source(&td->source->audio_buffer); + sinks[0] = audio_buffer_get_sink(&td->sink->audio_buffer); + ret = module_prepare(mod, sources, 1, sinks, 1); + if (ret) + return ret; + + td->continue_loop = true; + + *state = td; + return 0; +} + +static int teardown(void **state) +{ + struct test_data *td = *state; + struct processing_module *mod = comp_mod(td->dev); + + test_free(mod->input_buffers); + test_free(mod->output_buffers); + test_free(mod->stream_params); + mod->stream_params = NULL; + test_free(td->params); + free_test_source(td->source); + free_test_sink(td->sink); + comp_free(td->dev); + test_free(td); + return 0; +} + +#if CONFIG_FORMAT_S16LE +static void fill_source_s16(struct test_data *td, int frames_max) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int16_t *x; + int bytes_total; + int samples; + int frames; + int i; + int samples_processed = 0; + + sb = comp_dev_get_first_data_producer(dev); + ss = &sb->stream; + frames = MIN(audio_stream_get_free_frames(ss), frames_max); + samples = frames * audio_stream_get_channels(ss); + for (i = 0; i < samples; i++) { + x = audio_stream_write_frag_s16(ss, i); + *x = sat_int16(Q_SHIFT_RND(chirp_2ch[buffer_fill_data.idx++], 31, 15)); + samples_processed++; + if (buffer_fill_data.idx == CHIRP_2CH_LENGTH) { + td->continue_loop = false; + break; + } + } + + if (samples_processed > 0) { + bytes_total = samples_processed * audio_stream_sample_bytes(ss); + comp_update_buffer_produce(sb, bytes_total); + } + + mod->input_buffers[0].size = samples_processed / audio_stream_get_channels(ss); +} + +static void verify_sink_s16(struct test_data *td) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int nch = td->params->channels; + int32_t delta; + int32_t ref; + int32_t out; + int16_t *x; + int16_t in; + int samples; + int i; + + sb = comp_dev_get_first_data_consumer(dev); + ss = &sb->stream; + samples = mod->output_buffers[0].size >> 1; + for (i = 0; i < samples; i++) { + x = audio_stream_read_frag_s16(ss, i); + out = *x; + in = sat_int16(Q_SHIFT_RND(chirp_2ch[buffer_verify_data.idx], 31, 15)); + ref = (int32_t)round(dcblock_ref(buffer_verify_data.idx % nch, + (double)in / 32768.0) * 32768.0); + ref = sat_int16(ref); + buffer_verify_data.idx++; + delta = ref - out; + if (delta > ERROR_TOLERANCE_S16 || delta < -ERROR_TOLERANCE_S16) + assert_int_equal(out, ref); + } +} +#endif /* CONFIG_FORMAT_S16LE */ + +#if CONFIG_FORMAT_S24LE +static void fill_source_s24(struct test_data *td, int frames_max) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int32_t *x; + int bytes_total; + int samples; + int frames; + int i; + int samples_processed = 0; + + sb = comp_dev_get_first_data_producer(dev); + ss = &sb->stream; + frames = MIN(audio_stream_get_free_frames(ss), frames_max); + samples = frames * audio_stream_get_channels(ss); + for (i = 0; i < samples; i++) { + x = audio_stream_write_frag_s32(ss, i); + *x = sat_int24(Q_SHIFT_RND(chirp_2ch[buffer_fill_data.idx++], 31, 23)); + samples_processed++; + if (buffer_fill_data.idx == CHIRP_2CH_LENGTH) { + td->continue_loop = false; + break; + } + } + + if (samples_processed > 0) { + bytes_total = samples_processed * audio_stream_sample_bytes(ss); + comp_update_buffer_produce(sb, bytes_total); + } + + mod->input_buffers[0].size = samples_processed / audio_stream_get_channels(ss); +} + +static void verify_sink_s24(struct test_data *td) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int nch = td->params->channels; + int32_t delta; + int32_t ref; + int32_t out; + int32_t *x; + int32_t in; + int samples; + int i; + + sb = comp_dev_get_first_data_consumer(dev); + ss = &sb->stream; + samples = mod->output_buffers[0].size >> 2; + for (i = 0; i < samples; i++) { + x = audio_stream_read_frag_s32(ss, i); + out = (*x << 8) >> 8; /* Make sure there's no 24 bit overflow */ + in = sat_int24(Q_SHIFT_RND(chirp_2ch[buffer_verify_data.idx], 31, 23)); + ref = (int32_t)round(dcblock_ref(buffer_verify_data.idx % nch, + (double)in / 8388608.0) * 8388608.0); + ref = sat_int24(ref); + buffer_verify_data.idx++; + delta = ref - out; + if (delta > ERROR_TOLERANCE_S24 || delta < -ERROR_TOLERANCE_S24) + assert_int_equal(out, ref); + } +} +#endif /* CONFIG_FORMAT_S24LE */ + +#if CONFIG_FORMAT_S32LE +static void fill_source_s32(struct test_data *td, int frames_max) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int32_t *x; + int bytes_total; + int samples; + int frames; + int i; + int samples_processed = 0; + + sb = comp_dev_get_first_data_producer(dev); + ss = &sb->stream; + frames = MIN(audio_stream_get_free_frames(ss), frames_max); + samples = frames * audio_stream_get_channels(ss); + for (i = 0; i < samples; i++) { + x = audio_stream_write_frag_s32(ss, i); + *x = chirp_2ch[buffer_fill_data.idx++]; + samples_processed++; + if (buffer_fill_data.idx == CHIRP_2CH_LENGTH) { + td->continue_loop = false; + break; + } + } + + if (samples_processed > 0) { + bytes_total = samples_processed * audio_stream_sample_bytes(ss); + comp_update_buffer_produce(sb, bytes_total); + } + + mod->input_buffers[0].size = samples_processed / audio_stream_get_channels(ss); +} + +static void verify_sink_s32(struct test_data *td) +{ + struct processing_module *mod = comp_mod(td->dev); + struct comp_dev *dev = td->dev; + struct comp_buffer *sb; + struct audio_stream *ss; + int nch = td->params->channels; + int64_t delta; + int32_t ref; + int32_t out; + int32_t *x; + int32_t in; + int samples; + int i; + + sb = comp_dev_get_first_data_consumer(dev); + ss = &sb->stream; + samples = mod->output_buffers[0].size >> 2; + for (i = 0; i < samples; i++) { + x = audio_stream_read_frag_s32(ss, i); + out = *x; + in = chirp_2ch[buffer_verify_data.idx]; + ref = (int32_t)round(dcblock_ref(buffer_verify_data.idx % nch, + (double)in / 2147483648.0) * 2147483648.0); + buffer_verify_data.idx++; + delta = (int64_t)ref - (int64_t)out; + if (delta > ERROR_TOLERANCE_S32 || delta < -ERROR_TOLERANCE_S32) + assert_int_equal(out, ref); + } +} +#endif /* CONFIG_FORMAT_S32LE */ + +static int frames_jitter(int frames) +{ + int r = rand(); + + if (r > THR_RAND_PLUS_ONE) + return frames + 1; + else if (r < THR_RAND_MINUS_ONE) + return frames - 1; + else + return frames; +} + +static void test_audio_dcblock(void **state) +{ + struct test_data *td = *state; + struct processing_module *mod = comp_mod(td->dev); + struct comp_buffer *source = td->source; + struct comp_buffer *sink = td->sink; + struct sof_source *sources[1]; + struct sof_sink *sinks[1]; + uint32_t avail_before; + int ret; + int frames; + + sources[0] = audio_buffer_get_source(&source->audio_buffer); + sinks[0] = audio_buffer_get_sink(&sink->audio_buffer); + + while (td->continue_loop) { + frames = frames_jitter(td->params->frames); + switch (audio_stream_get_frm_fmt(&source->stream)) { + case SOF_IPC_FRAME_S16_LE: + fill_source_s16(td, frames); + break; + case SOF_IPC_FRAME_S24_4LE: + fill_source_s24(td, frames); + break; + case SOF_IPC_FRAME_S32_LE: + fill_source_s32(td, frames); + break; + default: + assert(0); + break; + } + + /* Process exactly the frames just filled. The source/sink API + * updates the buffer read/write positions internally, so record + * the sink fill level to know how many bytes were produced. + */ + td->dev->frames = mod->input_buffers[0].size; + avail_before = audio_stream_get_avail_bytes(&sink->stream); + ret = module_process_sink_src(mod, sources, 1, sinks, 1); + assert_int_equal(ret, 0); + mod->output_buffers[0].size = + audio_stream_get_avail_bytes(&sink->stream) - avail_before; + + switch (audio_stream_get_frm_fmt(&sink->stream)) { + case SOF_IPC_FRAME_S16_LE: + verify_sink_s16(td); + break; + case SOF_IPC_FRAME_S24_4LE: + verify_sink_s24(td); + break; + case SOF_IPC_FRAME_S32_LE: + verify_sink_s32(td); + break; + default: + assert(0); + break; + } + + comp_update_buffer_consume(sink, mod->output_buffers[0].size); + } +} + +static struct test_parameters parameters[] = { +#if CONFIG_FORMAT_S16LE + { 2, 48, 2, SOF_IPC_FRAME_S16_LE, SOF_IPC_FRAME_S16_LE }, +#endif /* CONFIG_FORMAT_S16LE */ +#if CONFIG_FORMAT_S24LE + { 2, 48, 2, SOF_IPC_FRAME_S24_4LE, SOF_IPC_FRAME_S24_4LE }, +#endif /* CONFIG_FORMAT_S24LE */ +#if CONFIG_FORMAT_S32LE + { 2, 48, 2, SOF_IPC_FRAME_S32_LE, SOF_IPC_FRAME_S32_LE }, +#endif /* CONFIG_FORMAT_S32LE */ +}; + +int main(void) +{ + int i; + + struct CMUnitTest tests[ARRAY_SIZE(parameters)]; + + for (i = 0; i < ARRAY_SIZE(parameters); i++) { + tests[i].name = "test_audio_dcblock"; + tests[i].test_func = test_audio_dcblock; + tests[i].setup_func = setup; + tests[i].teardown_func = teardown; + tests[i].initial_state = ¶meters[i]; + } + + cmocka_set_message_output(CM_OUTPUT_TAP); + + return cmocka_run_group_tests(tests, setup_group, NULL); +}