mirror of https://github.com/thesofproject/sof.git
cmocka: add eq_fir unit test
This test uses module interface for eq_fir. The use of decoded 16 bit FIR blob coefficients decreases a lot the mismatch between float reference FIR vs. SOF fixed point implementation. This patch adds the script tools/tune/eq/cmocka_data_eq_fir.m that generates the files cmocka_fir_coef_2ch.h and cmocka_fir_ref.h. The script debug_files_plot.m can be used to visualize the unit test result and error for data that is generated if macro DEBUG_FILES is defined. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com> Signed-off-by: Andrula Song <xiaoyuan.song@intel.com> Signed-off-by: Rander Wang <rander.wang@intel.com>
This commit is contained in:
parent
880f9d72ba
commit
b97fd67ef0
|
@ -19,3 +19,6 @@ endif()
|
|||
if(CONFIG_COMP_IIR)
|
||||
add_subdirectory(eq_iir)
|
||||
endif()
|
||||
if(CONFIG_COMP_FIR)
|
||||
add_subdirectory(eq_fir)
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmocka_test(eq_fir_process
|
||||
eq_fir_process.c
|
||||
)
|
||||
|
||||
target_include_directories(eq_fir_process PRIVATE ${PROJECT_SOURCE_DIR}/src/audio)
|
||||
|
||||
# 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_eq_fir STATIC
|
||||
${PROJECT_SOURCE_DIR}/src/audio/eq_fir/eq_fir.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/eq_fir/eq_fir_generic.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/eq_fir/eq_fir_hifi2ep.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/eq_fir/eq_fir_hifi3.c
|
||||
${PROJECT_SOURCE_DIR}/src/math/fir_generic.c
|
||||
${PROJECT_SOURCE_DIR}/src/math/fir_hifi2ep.c
|
||||
${PROJECT_SOURCE_DIR}/src/math/fir_hifi3.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/generic.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/buffer.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/component.c
|
||||
${PROJECT_SOURCE_DIR}/src/audio/data_blob.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}/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_eq_fir)
|
||||
|
||||
target_link_libraries(audio_for_eq_fir PRIVATE sof_options)
|
||||
|
||||
target_link_libraries(eq_fir_process PRIVATE audio_for_eq_fir)
|
|
@ -0,0 +1,44 @@
|
|||
/* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Copyright(c) 2022 Intel Corporation. All rights reserved.
|
||||
*/
|
||||
|
||||
uint32_t fir_coef_2ch[146] = {
|
||||
0x00464f53, 0x00000000, 0x00000228, 0x03016001,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00000228, 0x00010002, 0x00000000, 0x00000000,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x000000fc,
|
||||
0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
||||
0x00010001, 0x00010001, 0x00010001, 0x00010001,
|
||||
0x00020001, 0x00020002, 0x00020002, 0x00030003,
|
||||
0x00030003, 0x00040003, 0x00040004, 0x00050005,
|
||||
0x00060005, 0x00060006, 0x00070007, 0x00080008,
|
||||
0x00090009, 0x000a000a, 0x000b000b, 0x000c000c,
|
||||
0x000d000d, 0x000f000e, 0x0010000f, 0x00120011,
|
||||
0x00130012, 0x00150014, 0x00170016, 0x00190018,
|
||||
0x001b001a, 0x001e001d, 0x0021001f, 0x00240022,
|
||||
0x00280026, 0x002b0029, 0x002f002d, 0x00340032,
|
||||
0x00380036, 0x003d003b, 0x00420040, 0x00480045,
|
||||
0x004d004b, 0x00540051, 0x005b0057, 0x0062005e,
|
||||
0x00680065, 0x006f006c, 0x00760072, 0x007d0079,
|
||||
0x00860081, 0x0092008c, 0x009f0098, 0x00ac00a5,
|
||||
0x00b800b2, 0x00c600be, 0x00d800ce, 0x00ea00e1,
|
||||
0x00f500ef, 0x00fc00fc, 0x00ff0118, 0x0170012f,
|
||||
0x043900c3, 0xe9ae02d3, 0xe9ae6cbf, 0x043902d3,
|
||||
0x017000c3, 0x00ff012f, 0x00fc0118, 0x00f500fc,
|
||||
0x00ea00ef, 0x00d800e1, 0x00c600ce, 0x00b800be,
|
||||
0x00ac00b2, 0x009f00a5, 0x00920098, 0x0086008c,
|
||||
0x007d0081, 0x00760079, 0x006f0072, 0x0068006c,
|
||||
0x00620065, 0x005b005e, 0x00540057, 0x004d0051,
|
||||
0x0048004b, 0x00420045, 0x003d0040, 0x0038003b,
|
||||
0x00340036, 0x002f0032, 0x002b002d, 0x00280029,
|
||||
0x00240026, 0x00210022, 0x001e001f, 0x001b001d,
|
||||
0x0019001a, 0x00170018, 0x00150016, 0x00130014,
|
||||
0x00120012, 0x00100011, 0x000f000f, 0x000d000e,
|
||||
0x000c000d, 0x000b000c, 0x000a000b, 0x0009000a,
|
||||
0x00080009, 0x00070008, 0x00060007, 0x00060006,
|
||||
0x00050005, 0x00040005, 0x00040004, 0x00030003,
|
||||
0x00030003, 0x00020003, 0x00020002, 0x00020002,
|
||||
0x00010001, 0x00010001, 0x00010001, 0x00010001,
|
||||
0x00000001, 0x00000000
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
% debug_files_plot() - plot optional debug output
|
||||
|
||||
% SPDX-License-Identifier: BSD-3-Clause
|
||||
%
|
||||
% Copyright(c) 2022 Intel Corporation. All rights reserved.
|
||||
|
||||
load ../../../../../build_ut/test/cmocka/src/audio/eq_fir/fir_test_16.txt;
|
||||
iref = fir_test_16(:,1);
|
||||
iout = fir_test_16(:,2);
|
||||
ref = reshape(iref, size(iref, 1)/2, 2);
|
||||
out = reshape(iout, size(iout, 1)/2, 2);
|
||||
figure;
|
||||
subplot(2,1,1);
|
||||
plot(ref)
|
||||
hold on
|
||||
plot(out)
|
||||
hold off
|
||||
grid on
|
||||
title('16 bit FIR');
|
||||
subplot(2,1,2);
|
||||
plot(ref - out);
|
||||
grid on
|
||||
|
||||
load ../../../../../build_ut/test/cmocka/src/audio/eq_fir/fir_test_24.txt;
|
||||
iref = fir_test_24(:,1);
|
||||
iout = fir_test_24(:,2);
|
||||
ref = reshape(iref, size(iref, 1)/2, 2);
|
||||
out = reshape(iout, size(iout, 1)/2, 2);
|
||||
figure;
|
||||
subplot(2,1,1);
|
||||
plot(ref)
|
||||
hold on
|
||||
plot(out)
|
||||
hold off
|
||||
grid on
|
||||
title('24 bit FIR');
|
||||
subplot(2,1,2);
|
||||
plot(ref - out);
|
||||
grid on
|
||||
|
||||
load ../../../../../build_ut/test/cmocka/src/audio/eq_fir/fir_test_32.txt;
|
||||
iref = fir_test_32(:,1);
|
||||
iout = fir_test_32(:,2);
|
||||
ref = reshape(iref, size(iref, 1)/2, 2);
|
||||
out = reshape(iout, size(iout, 1)/2, 2);
|
||||
figure;
|
||||
subplot(2,1,1);
|
||||
plot(ref)
|
||||
hold on
|
||||
plot(out)
|
||||
hold off
|
||||
grid on
|
||||
title('32 bit FIR');
|
||||
subplot(2,1,2);
|
||||
plot(ref - out);
|
||||
grid on
|
|
@ -0,0 +1,510 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
//
|
||||
// Copyright(c) 2022 Intel Corporation. All rights reserved.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
#include <cmocka.h>
|
||||
#include <kernel/header.h>
|
||||
#include <sof/audio/component_ext.h>
|
||||
#include <sof/audio/eq_fir/eq_fir.h>
|
||||
#include <sof/audio/module_adapter/module/generic.h>
|
||||
|
||||
#include "../../util.h"
|
||||
#include "../../../include/cmocka_chirp_2ch.h"
|
||||
#include "cmocka_fir_ref.h"
|
||||
#include "cmocka_fir_coef_2ch.h"
|
||||
|
||||
/* Allow some small error for fixed point */
|
||||
#define ERROR_TOLERANCE_S16 1
|
||||
#define ERROR_TOLERANCE_S24 2
|
||||
#define ERROR_TOLERANCE_S32 4
|
||||
|
||||
/* 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))
|
||||
|
||||
/* Export optionally data to files for easier debug */
|
||||
#undef DEBUG_FILES
|
||||
#ifdef DEBUG_FILES
|
||||
FILE *debug_fh_16;
|
||||
FILE *debug_fh_24;
|
||||
FILE *debug_fh_32;
|
||||
#endif
|
||||
|
||||
struct buffer_fill {
|
||||
int idx;
|
||||
} buffer_fill_data;
|
||||
|
||||
struct buffer_verify {
|
||||
int idx;
|
||||
} buffer_verify_data;
|
||||
|
||||
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_eq_fir_interface_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct sof_ipc_comp_process *create_eq_fir_comp_ipc(struct test_data *td)
|
||||
{
|
||||
struct sof_ipc_comp_process *ipc;
|
||||
struct sof_eq_fir_config *eq;
|
||||
size_t ipc_size = sizeof(struct sof_ipc_comp_process);
|
||||
struct sof_abi_hdr *blob = (struct sof_abi_hdr *)fir_coef_2ch;
|
||||
const struct sof_uuid uuid = {
|
||||
.a = 0x43a90ce7, .b = 0xf3a5, .c = 0x41df,
|
||||
.d = {0xac, 0x06, 0xba, 0x98, 0x65, 0x1a, 0xe6, 0xa3}
|
||||
};
|
||||
|
||||
ipc = calloc(1, ipc_size + blob->size + SOF_UUID_SIZE);
|
||||
memcpy_s(ipc + 1, SOF_UUID_SIZE, &uuid, SOF_UUID_SIZE);
|
||||
eq = (struct sof_eq_fir_config *)((char *)(ipc + 1) + SOF_UUID_SIZE);
|
||||
ipc->comp.hdr.size = ipc_size + SOF_UUID_SIZE;
|
||||
ipc->comp.type = SOF_COMP_EQ_FIR;
|
||||
ipc->config.hdr.size = sizeof(struct sof_ipc_comp_config);
|
||||
ipc->size = blob->size;
|
||||
ipc->comp.ext_data_length = SOF_UUID_SIZE;
|
||||
memcpy_s(eq, blob->size, blob->data, blob->size);
|
||||
return ipc;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* allocate new sink buffer */
|
||||
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;
|
||||
int ret;
|
||||
|
||||
td = test_malloc(sizeof(*td));
|
||||
if (!td)
|
||||
return -EINVAL;
|
||||
|
||||
td->params = test_malloc(sizeof(*params));
|
||||
if (!td->params)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy_s(td->params, sizeof(*td->params), params, sizeof(*params));
|
||||
ipc = create_eq_fir_comp_ipc(td);
|
||||
buffer_fill_data.idx = 0;
|
||||
buffer_verify_data.idx = 0;
|
||||
|
||||
dev = comp_new((struct sof_ipc_comp *)ipc);
|
||||
free(ipc);
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
td->dev = dev;
|
||||
dev->frames = params->frames;
|
||||
mod = comp_get_drvdata(dev);
|
||||
|
||||
prepare_sink(td, mod);
|
||||
prepare_source(td, mod);
|
||||
|
||||
/* allocate intermediate buffers */
|
||||
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;
|
||||
|
||||
ret = module_prepare(mod);
|
||||
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_get_drvdata(td->dev);
|
||||
|
||||
test_free(mod->input_buffers);
|
||||
test_free(mod->output_buffers);
|
||||
test_free(mod->stream_params);
|
||||
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_get_drvdata(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 = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list);
|
||||
ss = &sb->stream;
|
||||
frames = MIN(audio_stream_get_free_frames(ss), frames_max);
|
||||
samples = frames * ss->channels;
|
||||
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 / ss->channels;
|
||||
}
|
||||
|
||||
static void verify_sink_s16(struct test_data *td)
|
||||
{
|
||||
struct processing_module *mod = comp_get_drvdata(td->dev);
|
||||
struct comp_dev *dev = td->dev;
|
||||
struct comp_buffer *sb;
|
||||
struct audio_stream *ss;
|
||||
int32_t delta;
|
||||
int32_t ref;
|
||||
int32_t out;
|
||||
int16_t *x;
|
||||
int samples;
|
||||
int i;
|
||||
|
||||
sb = list_first_item(&dev->bsink_list, struct comp_buffer, source_list);
|
||||
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;
|
||||
ref = sat_int16(Q_SHIFT_RND(fir_ref_2ch[buffer_verify_data.idx++], 31, 15));
|
||||
delta = ref - out;
|
||||
if (delta > ERROR_TOLERANCE_S16 || delta < -ERROR_TOLERANCE_S16)
|
||||
assert_int_equal(out, ref);
|
||||
#ifdef DEBUG_FILES
|
||||
fprintf(debug_fh_16, "%d %d\n", ref, out);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#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_get_drvdata(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 = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list);
|
||||
ss = &sb->stream;
|
||||
frames = MIN(audio_stream_get_free_frames(ss), frames_max);
|
||||
samples = frames * ss->channels;
|
||||
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 / ss->channels;
|
||||
}
|
||||
|
||||
static void verify_sink_s24(struct test_data *td)
|
||||
{
|
||||
struct processing_module *mod = comp_get_drvdata(td->dev);
|
||||
struct comp_dev *dev = td->dev;
|
||||
struct comp_buffer *sb;
|
||||
struct audio_stream *ss;
|
||||
int32_t delta;
|
||||
int32_t ref;
|
||||
int32_t out;
|
||||
int32_t *x;
|
||||
int samples;
|
||||
int i;
|
||||
|
||||
sb = list_first_item(&dev->bsink_list, struct comp_buffer, source_list);
|
||||
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 */
|
||||
ref = sat_int24(Q_SHIFT_RND(fir_ref_2ch[buffer_verify_data.idx++], 31, 23));
|
||||
delta = ref - out;
|
||||
if (delta > ERROR_TOLERANCE_S24 || delta < -ERROR_TOLERANCE_S24)
|
||||
assert_int_equal(out, ref);
|
||||
#ifdef DEBUG_FILES
|
||||
fprintf(debug_fh_24, "%d %d\n", ref, out);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#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_get_drvdata(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 = list_first_item(&dev->bsource_list, struct comp_buffer, sink_list);
|
||||
ss = &sb->stream;
|
||||
frames = MIN(audio_stream_get_free_frames(ss), frames_max);
|
||||
samples = frames * ss->channels;
|
||||
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 / ss->channels;
|
||||
}
|
||||
|
||||
static void verify_sink_s32(struct test_data *td)
|
||||
{
|
||||
struct processing_module *mod = comp_get_drvdata(td->dev);
|
||||
struct comp_dev *dev = td->dev;
|
||||
struct comp_buffer *sb;
|
||||
struct audio_stream *ss;
|
||||
int64_t delta;
|
||||
int32_t ref;
|
||||
int32_t out;
|
||||
int32_t *x;
|
||||
int samples;
|
||||
int i;
|
||||
|
||||
sb = list_first_item(&dev->bsink_list, struct comp_buffer, source_list);
|
||||
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;
|
||||
ref = fir_ref_2ch[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);
|
||||
#ifdef DEBUG_FILES
|
||||
fprintf(debug_fh_32, "%d %d\n", ref, out);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#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_eq_fir(void **state)
|
||||
{
|
||||
struct test_data *td = *state;
|
||||
struct processing_module *mod = comp_get_drvdata(td->dev);
|
||||
|
||||
struct comp_buffer *source = td->source;
|
||||
struct comp_buffer *sink = td->sink;
|
||||
int ret;
|
||||
int frames;
|
||||
|
||||
while (td->continue_loop) {
|
||||
frames = frames_jitter(td->params->frames);
|
||||
switch (source->stream.frame_fmt) {
|
||||
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;
|
||||
case SOF_IPC_FRAME_S24_3LE:
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
mod->input_buffers[0].consumed = 0;
|
||||
mod->output_buffers[0].size = 0;
|
||||
|
||||
ret = module_process(mod, mod->input_buffers, 1, mod->output_buffers, 1);
|
||||
assert_int_equal(ret, 0);
|
||||
|
||||
comp_update_buffer_consume(source, mod->input_buffers[0].consumed);
|
||||
comp_update_buffer_produce(sink, mod->output_buffers[0].size);
|
||||
|
||||
switch (sink->stream.frame_fmt) {
|
||||
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 ret;
|
||||
int i;
|
||||
|
||||
struct CMUnitTest tests[ARRAY_SIZE(parameters)];
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(parameters); i++) {
|
||||
tests[i].name = "test_audio_eq_fir";
|
||||
tests[i].test_func = test_audio_eq_fir;
|
||||
tests[i].setup_func = setup;
|
||||
tests[i].teardown_func = teardown;
|
||||
tests[i].initial_state = ¶meters[i];
|
||||
}
|
||||
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
|
||||
#ifdef DEBUG_FILES
|
||||
debug_fh_16 = fopen("fir_test_16.txt", "w");
|
||||
debug_fh_24 = fopen("fir_test_24.txt", "w");
|
||||
debug_fh_32 = fopen("fir_test_32.txt", "w");
|
||||
#endif
|
||||
ret = cmocka_run_group_tests(tests, setup_group, NULL);
|
||||
|
||||
#ifdef DEBUG_FILES
|
||||
fclose(debug_fh_16);
|
||||
fclose(debug_fh_24);
|
||||
fclose(debug_fh_32);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
% Create a chirp waveform and export test EQ coefficients and reference output
|
||||
%
|
||||
% Usage:
|
||||
% cmocka_data_eq_fir()
|
||||
|
||||
% SPDX-License-Identifier: BSD-3-Clause
|
||||
%
|
||||
% Copyright (c) 2021, Intel Corporation. All rights reserved.
|
||||
|
||||
function cmocka_data_eq_fir()
|
||||
|
||||
% Output files and paths
|
||||
chirp_fn = '../../../test/cmocka/include/cmocka_chirp_2ch.h';
|
||||
ref_fn = '../../../test/cmocka/src/audio/eq_fir/cmocka_fir_ref.h';
|
||||
coef_fn = '../../../test/cmocka/src/audio/eq_fir/cmocka_fir_coef_2ch.h';
|
||||
|
||||
% Input data
|
||||
fs = 48e3;
|
||||
t = 100e-3;
|
||||
scale = 2^31;
|
||||
[x, yi] = get_chirp(fs, t);
|
||||
export_c_int32t(chirp_fn, 'chirp_2ch', 'CHIRP_2CH_LENGTH',yi)
|
||||
|
||||
% Compute a test EQ
|
||||
eq = test_response(coef_fn, 'fir_coef_2ch', fs);
|
||||
|
||||
% Filter input data
|
||||
ref(:,1) = filter(eq.b_fir, 1, x(:,1));
|
||||
ref(:,2) = filter(eq.b_fir, 1, x(:,2));
|
||||
refi = scale_saturate(ref, scale);
|
||||
export_c_int32t(ref_fn, 'fir_ref_2ch', 'FIR_REF_2CH_LENGTH', refi)
|
||||
|
||||
figure;
|
||||
plot(yi/scale)
|
||||
grid on;
|
||||
|
||||
figure;
|
||||
plot(ref)
|
||||
grid on;
|
||||
|
||||
figure;
|
||||
plot(refi / scale)
|
||||
grid on;
|
||||
|
||||
end
|
||||
|
||||
|
||||
function xi = scale_saturate(x, scale)
|
||||
|
||||
imax = scale - 1;
|
||||
imin = -scale;
|
||||
xi = round(scale * x);
|
||||
xi = min(xi, imax);
|
||||
xi = max(xi, imin);
|
||||
|
||||
end
|
||||
|
||||
function [x, yi] = get_chirp(fs, t_chirp)
|
||||
|
||||
channels = 2;
|
||||
f0 = 100;
|
||||
f1 = 20e3;
|
||||
a = 1 + 1e-5; % Ensure max and min int values are produced
|
||||
scale = 2^31;
|
||||
imax = scale - 1;
|
||||
imin = -scale;
|
||||
|
||||
n = round(fs * t_chirp);
|
||||
t = (0:(n - 1)) / fs;
|
||||
x(:, 1) = a * chirp(t, f0, t_chirp, f1, 'logarithmic', 0);
|
||||
x(:, 2) = a * chirp(t, f0, t_chirp, f1, 'logarithmic', 90);
|
||||
x = min(x, 1.0);
|
||||
x = max(x, -1.0);
|
||||
yi = scale_saturate(x, scale);
|
||||
|
||||
end
|
||||
|
||||
%% -------------------
|
||||
%% EQ design functions
|
||||
%% -------------------
|
||||
|
||||
function eq = test_response(fn, vn, fs)
|
||||
|
||||
%% Get EQ
|
||||
blob_fn = '../../ctl/eq_fir_loudness.txt';
|
||||
eq = eq_blob_plot(blob_fn, 'fir', fs, [], 0);
|
||||
|
||||
%% Quantize and pack filter coefficients plus shifts etc.
|
||||
bq = eq_fir_blob_quant(eq.b_fir);
|
||||
|
||||
%% Build blob
|
||||
channels_in_config = 2; % Setup max 2 channels EQ
|
||||
assign_response = [0 0]; % Same response for L and R
|
||||
num_responses = 1; % One response
|
||||
bm = eq_fir_blob_merge(channels_in_config, ...
|
||||
num_responses, ...
|
||||
assign_response, ...
|
||||
bq);
|
||||
|
||||
%% Pack and write file
|
||||
bp = eq_fir_blob_pack(bm);
|
||||
export_c_eq_uint32t(fn, bp, vn, 0);
|
||||
|
||||
end
|
|
@ -5,12 +5,19 @@ function eq = eq_blob_plot(blobfn, eqtype, fs, f, doplot)
|
|||
% Plot frequency response of IIR or FIR EQ coefficients blob
|
||||
%
|
||||
% Inputs
|
||||
% blobfn - filename of the blob
|
||||
% eqtype - 'iir' or 'fir', if omitted done via string search from blobfn
|
||||
% fs - sample rate, defaults to 48 kHz if omitted
|
||||
% f - frequency vector
|
||||
% doplot - 0 or 1, don't plot if 0
|
||||
%
|
||||
% blobfn - filename of the blob
|
||||
% eqtype - 'iir' or 'fir', if omitted done via string search from blobfn
|
||||
% fs - sample rate, defaults to 48 kHz if omitted
|
||||
% f - frequency vector
|
||||
% dpplot
|
||||
% Output
|
||||
% eq.f - frequency vector
|
||||
% eq.m - magnitude response
|
||||
% eq.gd - group delay
|
||||
% eq.b_fir - FIR coefficients
|
||||
% eq.b - IIR numerator coefficients
|
||||
% eq.a - IIR denominator coefficients
|
||||
%
|
||||
% Examples
|
||||
% eq_blob_plot('../../topology/topology1/m4/eq_iir_coef_loudness.m4', 'iir');
|
||||
|
@ -19,7 +26,7 @@ function eq = eq_blob_plot(blobfn, eqtype, fs, f, doplot)
|
|||
|
||||
% SPDX-License-Identifier: BSD-3-Clause
|
||||
%
|
||||
% Copyright (c) 2016-2020, Intel Corporation. All rights reserved.
|
||||
% Copyright (c) 2016-2022, Intel Corporation. All rights reserved.
|
||||
%
|
||||
% Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
|
||||
|
||||
|
@ -68,11 +75,14 @@ switch lower(eqtype)
|
|||
hd = eq_fir_blob_decode(blob);
|
||||
eq.m = zeros(length(eq.f), hd.channels_in_config);
|
||||
for i = 1:hd.channels_in_config
|
||||
teq = eq_fir_blob_decode(blob, hd.assign_response(i));
|
||||
h = freqz(teq.b, 1, eq.f, fs);
|
||||
decoded_eq = eq_fir_blob_decode(blob, hd.assign_response(i));
|
||||
eq.b_fir = decoded_eq.b;
|
||||
eq.b = 1;
|
||||
eq.a = 1;
|
||||
h = freqz(eq.b_fir, 1, eq.f, fs);
|
||||
eq.m(:,i) = 20*log10(abs(h));
|
||||
if do_group_delay
|
||||
gd = grpdelay(teq.b, 1, eq.f, fs) / fs;
|
||||
gd = grpdelay(eq.b_fir, 1, eq.f, fs) / fs;
|
||||
eq.gd(:,i) = gd;
|
||||
end
|
||||
end
|
||||
|
@ -81,8 +91,10 @@ switch lower(eqtype)
|
|||
hd = eq_iir_blob_decode(blob);
|
||||
eq.m = zeros(length(eq.f), hd.channels_in_config);
|
||||
for i = 1:hd.channels_in_config
|
||||
teq = eq_iir_blob_decode(blob, hd.assign_response(i));
|
||||
h = freqz(teq.b, teq.a, eq.f, fs);
|
||||
decoded_eq = eq_iir_blob_decode(blob, hd.assign_response(i));
|
||||
eq.b = decoded_eq.b;
|
||||
eq.a = decodec_eq.a;
|
||||
h = freqz(eq.b, eq.a, eq.f, fs);
|
||||
eq.m(:,i) = 20*log10(abs(h));
|
||||
if do_group_delay
|
||||
gd = grpdelay(teq.b, teq.a, eq.f, fs) / fs;
|
||||
|
|
Loading…
Reference in New Issue