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:
Rander Wang 2022-09-16 16:46:55 +08:00 committed by Liam Girdwood
parent 880f9d72ba
commit b97fd67ef0
8 changed files with 2391 additions and 11 deletions

View File

@ -19,3 +19,6 @@ endif()
if(CONFIG_COMP_IIR)
add_subdirectory(eq_iir)
endif()
if(CONFIG_COMP_FIR)
add_subdirectory(eq_fir)
endif()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 = &parameters[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;
}

View File

@ -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

View File

@ -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;