sof/tools/test/audio/process_test.m

408 lines
10 KiB
Matlab

function [n_fail, n_pass, n_na] = process_test(comp, bits_in_list, bits_out_list, fs)
% process_test - test objective audio quality parameters
%
% process_test(comp, bits_in_list, bits_out_list, fs)
% SPDX-License-Identifier: BSD-3-Clause
% Copyright(c) 2017-202 Intel Corporation. All rights reserved.
% Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
%% Defaults for call parameters
if nargin < 1
comp = 'EQIIR';
end
if nargin < 2
bits_in_list = [16 24 32];
end
if nargin < 3
bits_out_list = [16 24 32];
end
if nargin < 4
fs = 48e3;
end
%% Paths
t.blobpath = '../../topology/m4';
plots = 'plots';
reports = 'reports';
%% Defaults for test
t.comp = comp; % Pass component name from func arguments
t.fmt = 'raw'; % Can be 'raw' (fast binary) or 'txt' (debug)
t.iirblob = 'eq_iir_coef_loudness.m4'; % Use loudness type response
t.firblob = 'eq_fir_coef_loudness.m4'; % Use loudness type response
t.fs = fs; % Sample rate from func arguments
t.nch = 2; % Number of channels
t.ch = [1 2]; % Test channel 1 and 2
t.bits_in = bits_in_list; % Input word length from func arguments
t.bits_out = bits_out_list; % Output word length from func arguments
t.full_test = 1; % 0 is quick check only, 1 is full set
%% Show graphics or not. With visible plot windows Octave may freeze if too
% many windows are kept open. As workaround setting close windows to
% 1 shows only flashing windows while the test proceeds. With
% visibility set to to 0 only console text is seen. The plots are
% exported into plots directory in png format and can be viewed from
% there.
t.plot_close_windows = 1; % Workaround for visible windows if Octave hangs
t.plot_visible = 'off'; % Use off for batch tests and on for interactive
t.files_delete = 1; % Set to 0 to inspect the audio data files
%% Prepare
addpath('std_utils');
addpath('test_utils');
addpath('../../tune/eq');
mkdir_check(plots);
mkdir_check(reports);
n_meas = 5;
n_bits_in = length(bits_in_list);
n_bits_out = length(bits_out_list);
r.bits_in_list = bits_in_list;
r.bits_out_list = bits_out_list;
r.g = NaN(n_bits_in, n_bits_out);
r.dr = NaN(n_bits_in, n_bits_out);
r.thdnf = NaN(n_bits_in, n_bits_out);
r.pf = -ones(n_bits_in, n_bits_out, n_meas);
r.n_fail = 0;
r.n_pass = 0;
r.n_skipped = 0;
r.n_na = 0;
%% Loop all modes to test
for b = 1:n_bits_out
for a = 1:n_bits_in
v = -ones(n_meas,1); % Set pass/fail test verdict to not executed
tn = 1;
t.bits_in = bits_in_list(a);
t.bits_out = bits_out_list(b);
v(1) = chirp_test(t);
if v(1) ~= -1 && t.full_test
[v(2) g] = g_test(t);
[v(3) dr] = dr_test(t);
[v(4) thdnf] = thdnf_test(t);
v(5) = fr_test(t);
% TODO: Collect results for all channels, now get worst-case
r.g(a, b) = g(1);
r.dr(a, b) = min(dr);
r.thdnf(a, b) = max(thdnf);
r.pf(a, b, :) = v;
end
%% Done, store pass/fail
if v(1) ~= -1
idx = find(v > 0);
r.n_fail = r.n_fail + length(idx);
idx = find(v == 0);
r.n_pass = r.n_pass + length(idx);
idx = find(v == -1);
r.n_skipped = r.n_skipped + length(idx);
idx = find(v == -2);
r.n_na = r.n_na + length(idx);
end
end
end
%% Print table with test summary: Gain
fn = sprintf('%s/g_%s.txt', reports, t.comp);
print_val(t.comp, 'Gain (dB)', fn, bits_in_list, bits_out_list, r.g, r.pf);
%% Print table with test summary: DR
fn = sprintf('%s/dr_%s.txt', reports, t.comp);
print_val(t.comp, 'Dynamic range (dB CCIR-RMS)', fn, bits_in_list, bits_out_list, r.dr, r.pf);
%% Print table with test summary: THD+N vs. frequency
fn = sprintf('%s/thdnf_%s.txt', reports, t.comp);
print_val(t.comp, 'Worst-case THD+N vs. frequency', fn, bits_in_list, bits_out_list, r.thdnf, r.pf);
%% Print table with test summary: pass/fail
fn = sprintf('%s/pf_%s.txt', reports, t.comp);
print_pf(t.comp', fn, bits_in_list, bits_out_list, r.pf, 'Fails chirp/gain/DR/THD+N/FR');
fprintf('\n');
fprintf('Number of passed tests = %d\n', r.n_pass);
fprintf('Number of failed tests = %d\n', r.n_fail);
fprintf('Number of non-applicable tests = %d\n', r.n_na);
fprintf('Number of skipped tests = %d\n', r.n_skipped);
if r.n_fail > 0 || r.n_pass < 1
fprintf('\nERROR: TEST FAILED!!!\n');
else
fprintf('\nTest passed.\n');
end
n_fail = r.n_fail;
n_pass = r.n_pass;
n_na = r.n_na;
%% Done
end
%%
%% Test execution with help of common functions
%%
function fail = chirp_test(t)
fprintf('Spectrogram test %d Hz ...\n', t.fs);
% Create input file
test = test_defaults(t);
test = chirp_test_input(test);
% Run test
test = test_run_process(test, t);
% Analyze
test = chirp_test_analyze(test);
test_result_print(t, 'Continuous frequency sweep', 'chirpf', test);
% Delete files unless e.g. debugging and need data to run
delete_check(t.files_delete, test.fn_in);
delete_check(t.files_delete, test.fn_out);
fail = test.fail;
end
%% Reference: AES17 6.2.2 Gain
function [fail, g_db] = g_test(t)
test = test_defaults(t);
% Create input file
test = g_test_input(test);
% Run test
test = test_run_process(test, t);
% Measure
test = g_spec(test, t);
test = g_test_measure(test);
% Get output parameters
fail = test.fail;
g_db = test.g_db;
delete_check(t.files_delete, test.fn_in);
delete_check(t.files_delete, test.fn_out);
end
%% Reference: AES17 6.4.1 Dynamic range
function [fail, dr_db] = dr_test(t)
test = test_defaults(t);
% Create input file
test = dr_test_input(test);
% Run test
test = test_run_process(test, t);
% Measure
test = dr_test_measure(test);
% Get output parameters
fail = test.fail;
dr_db = test.dr_db;
delete_check(t.files_delete, test.fn_in);
delete_check(t.files_delete, test.fn_out);
end
%% Reference: AES17 6.3.2 THD+N ratio vs. frequency
function [fail, thdnf] = thdnf_test(t)
test = test_defaults(t);
% Create input file
test = thdnf_test_input(test);
% Run test
test = test_run_process(test, t);
% Measure
test = thdnf_mask(test, t);
test = thdnf_test_measure(test);
% For EQ use the -20dBFS result and ignore possible -1 dBFS fail
thdnf = max(test.thdnf_low);
fail = test.fail;
delete_check(t.files_delete, test.fn_in);
delete_check(t.files_delete, test.fn_out);
% Print
test_result_print(t, 'THD+N ratio vs. frequency', 'THDNF', test);
end
%% Reference: AES17 6.2.3 Frequency response
function fail = fr_test(t)
test = test_defaults(t);
% Create input file
test = fr_test_input(test);
% Run test
test = test_run_process(test, t);
% Measure
test = fr_mask(test, t);
test = fr_test_measure(test);
fail = test.fail;
delete_check(t.files_delete, test.fn_in);
delete_check(t.files_delete, test.fn_out);
% Print
test_result_print(t, 'Frequency response', 'FR', test);
end
%% Helper functions
function test = thdnf_mask(test, prm)
min_bits = min(test.bits_in, test.bits_out);
test.thdnf_mask_f = [50 400 test.f_max];
test.thdnf_mask_hi = [-40 -50 -50];
end
function test = g_spec(test, prm)
switch lower(test.comp)
case 'eqiir'
blob = fullfile(prm.blobpath, prm.iirblob);
h = eq_blob_plot(blob, 'iir', test.fs, test.f, 0);
case 'eqfir'
blob = fullfile(prm.blobpath, prm.firblob);
h = eq_blob_plot(blob, 'fir', test.fs, test.f, 0);
otherwise
test.g_db_expect = zeros(1, test.nch);
return
end
test.g_db_expect = h.m(:, test.ch);
end
function test = fr_mask(test, prm)
switch lower(test.comp)
case 'eqiir'
blob = fullfile(prm.blobpath, prm.iirblob);
h = eq_blob_plot(blob, 'iir', test.fs, test.f, 0);
case 'eqfir'
blob = fullfile(prm.blobpath, prm.firblob);
h = eq_blob_plot(blob, 'fir', test.fs, test.f, 0);
otherwise
% Define a generic mask for frequency response, generally
% all processing at 8 kHz or above should pass, if not
% or need for tighter criteria define per component other
% target.
test.fr_mask_fhi = [20 test.f_max];
test.fr_mask_flo = [200 400 3500 3600 ];
for i = 1:test.nch
test.fr_mask_mhi(:,i) = [ 1 1 ];
test.fr_mask_mlo(:,i) = [-10 -1 -1 -10];
end
return
end
% Create mask from theoretical frequency response calculated from decoded
% response in h and align mask to be relative to 997 Hz response
i997 = find(test.f > 997, 1, 'first')-1;
j = 1;
for channel = test.ch
m = h.m(:, channel) - h.m(i997, channel);
test.fr_mask_flo = test.f;
test.fr_mask_fhi = test.f;
test.fr_mask_mlo(:,j) = m - test.fr_rp_max_db;
test.fr_mask_mhi(:,j) = m + test.fr_rp_max_db;
j = j + 1;
end
end
function test = test_defaults(t)
test.comp = t.comp;
test.fmt = t.fmt;
test.bits_in = t.bits_in;
test.bits_out = t.bits_out;
test.nch = t.nch;
test.ch = t.ch;
test.fs = t.fs;
test.plot_visible = t.plot_visible;
% Misc
test.quick = 0;
test.att_rec_db = 0;
% Plotting
test.plot_channels_combine = 1;
test.plot_thdn_axis = [];
test.plot_fr_axis = [];
test.plot_passband_zoom = 0;
% Test constraints
test.f_start = 20;
test.f_end = test.fs * 0.41667; % 20 kHz @ 48 kHz
test.fu = test.fs * 0.41667; % 20 kHz @ 48 kHz
test.f_max = 0.999*t.fs/2; % Measure up to min. Nyquist frequency
test.fs1 = test.fs;
test.fs2 = test.fs;
% Pass criteria
test.g_db_tol = 0.1; % Allow 0.1 dB gain variation
test.thdnf_max = []; % Set per component
test.dr_db_min = 80; % Min. DR
test.fr_rp_max_db = 0.5; % Allow 0.5 dB frequency response ripple
end
function test = test_run_process(test, t)
switch lower(test.comp)
case {'eqiir', 'eqfir', 'dcblock', 'volume', 'tdfb'}
test.ex = sprintf('./%s_run.sh', lower(test.comp));
otherwise
error('Unknown component');
end
test.arg = { num2str(test.bits_in) num2str(test.bits_out) ...
num2str(test.fs), test.fn_in, test.fn_out };
delete_check(1, test.fn_out);
test = test_run(test);
end
function test_result_print(t, testverbose, testacronym, test)
tstr = sprintf('%s %s %d-%d %d Hz', ...
testverbose, t.comp, t.bits_in, t.bits_out, t.fs);
for i = 1:length(test.ph)
title(test.ph(i), tstr);
end
for i = 1:length(test.fh)
figure(test.fh(i));
set(test.fh(i), 'visible', test.plot_visible);
pfn = sprintf('plots/%s_%s_%d_%d_%d_%d.png', ...
testacronym, t.comp, ...
t.bits_in, t.bits_out, t.fs, i);
print(pfn, '-dpng');
end
end