Skip to content

Commit 019c165

Browse files
committed
Test-case: Add to check-alsabat.sh additional audio test
This patch adds run of check_wav_file_snr.m script for alsabat capture file to analyze with STFT spectra the audio quality. Dropped signal-to-noise ratio in a single FFT indicates presence of glitch. A low SNR result triggers run of additional glitch periodicity finder. Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
1 parent ba2f6c5 commit 019c165

2 files changed

Lines changed: 371 additions & 1 deletion

File tree

test-case/check-alsabat.sh

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
rm -f /tmp/bat.wav.*
2020

2121
# shellcheck source=case-lib/lib.sh
22-
source "$(dirname "${BASH_SOURCE[0]}")"/../case-lib/lib.sh
22+
TESTDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
23+
source "$TESTDIR/case-lib/lib.sh"
2324

2425
OPT_NAME['p']='pcm_p' OPT_DESC['p']='pcm for playback. Example: hw:0,0'
2526
OPT_HAS_ARG['p']=1 OPT_VAL['p']=''
@@ -95,6 +96,22 @@ function __upload_wav_file
9596
done
9697
}
9798

99+
function check_wav_file_snr
100+
{
101+
for file in /tmp/bat.wav.*
102+
do
103+
if test -s "$file"; then
104+
dlogi "Checking wav file $file"
105+
cd "$TESTDIR"/tools || {
106+
die "Failed to enter directory tools"
107+
}
108+
octave --silent --no-gui --eval "check_wav_file_snr('$file', $frequency, '$LOG_ROOT/')" || {
109+
die "Error: Script check_wav_file_snr.m found issues in $file"
110+
}
111+
fi
112+
done
113+
}
114+
98115
# check the PCMs before alsabat test
99116
dlogi "check the PCMs before alsabat test"
100117
aplay -Dplug$pcm_p -d 1 /dev/zero -q || die "Failed to play on PCM: $pcm_p"
@@ -119,4 +136,6 @@ alsabat -C$pcm_c -c $channel_c -r $rate -f $format -F $frequency -k $sigmak || {
119136
exit 1
120137
}
121138

139+
check_wav_file_snr
140+
122141
wait $playPID

tools/check_wav_file_snr.m

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
function pass = check_wav_file_snr(fn, test_tone_f, logdir)
2+
3+
if nargin < 3
4+
logdir = '/tmp';
5+
end
6+
7+
if nargin < 2
8+
% Automatic find of tone frequency
9+
test_tone_f = 0;
10+
end
11+
12+
if nargin < 1
13+
error('Need to provide file name to analyze');
14+
end
15+
16+
%fp = '/home/singalsu/Downloads/';
17+
%wf = 'bat.wav.3Vbz0k'; % total mess
18+
%wf = 'bat.wav.zhJhMf'; % glitch in right channel
19+
%wf = 'bat.wav.Wodg37'; % low quality
20+
%wf = 'bat.wav.WR5of5'; % Low signal level but OK
21+
%wf = 'bat.wav.05IgGp'; % good
22+
%wf = 'bat.wav.3UiPoZ'; % good
23+
%fn = fullfile(fp, wf);
24+
25+
param.t_ignore_from_start = 30e-3;
26+
param.t_ignore_from_end = 0;
27+
param.t_step = 1.0e-3;
28+
param.n_fft = 1024;
29+
param.max_snr_drop = 6;
30+
param.min_snr = 50;
31+
test_passed = true;
32+
do_plot = 0;
33+
do_print = 0;
34+
nfig = 0;
35+
36+
% Read audio
37+
fprintf(1, 'Loading file %s\n', fn);
38+
[x, fs, frames, channels] = read_audio(fn);
39+
[~, basefn, extfn] = fileparts(fn);
40+
41+
for ch = 1 : channels
42+
% Sanity check
43+
if ~signal_found(x(:, ch))
44+
continue;
45+
end
46+
47+
% STFT
48+
[stft, f, t, param] = compute_stft(x(:, ch), fs, param);
49+
50+
% Plot
51+
if do_plot
52+
fnstr = sprintf('%s%s', basefn, extfn);
53+
idstr = sprintf('%s%s ch%d', fnstr, ch);
54+
nfig = nfig + 1;
55+
plot_specgram(stft, f, t, idstr);
56+
end
57+
58+
% Check frequency, get STFT frequency index
59+
[signal_idx, tone_f] = find_test_tone(stft, param, f, t, test_tone_f);
60+
61+
% Get levels and SNR estimates
62+
meas = get_tone_levels(stft, param, f, t, signal_idx);
63+
64+
% Plot SNR
65+
if do_plot
66+
plot_levels(meas, t, idstr);
67+
nfig = nfig + 1;
68+
end
69+
70+
% Checks for levels data
71+
meas = check_tone_levels(stft, param, meas, t);
72+
73+
% If poor SNR check if there's periodic glitches
74+
meas = check_glitch_periodicity(x(:,ch), param, meas, ch);
75+
76+
% Plot glitch waveform
77+
if do_plot && meas.num_glitches > 0
78+
plot_glitch(x(:, ch), param, meas, idstr);
79+
nfig = nfig + 1;
80+
end
81+
82+
all_meas(ch) = meas;
83+
if meas.success == false
84+
test_passed = false;
85+
end
86+
end
87+
88+
fprintf(1, 'Ch, Pass, Frequency, SNR min, SNR avg, Signal avg, Noise avg, Noise max, Num glitch, 1st glitch\n');
89+
90+
for ch = 1 : channels
91+
meas = all_meas(ch);
92+
fprintf(1, '%d, %2d, %5.0f, %6.1f, %6.1f, %6.1f, %6.1f, %6.1f, %6d, %8.3f\n', ...
93+
ch, meas.success, tone_f, meas.min_snr_db, meas.mean_snr_db, ...
94+
meas.mean_signal_db, meas.mean_noise_db, ...
95+
meas.max_noise_db, meas.num_glitches, meas.t_glitch);
96+
end
97+
98+
if do_plot && do_print && ~test_passed
99+
for i = 1 : nfig
100+
pfn = sprintf('%s/analyze_%s_%d.png', logdir, fnstr, i);
101+
figure(i);
102+
print(pfn, '-dpng');
103+
end
104+
end
105+
106+
if test_passed
107+
fprintf(1, 'Passed\n');
108+
else
109+
error('Failed');
110+
end
111+
112+
end
113+
114+
%
115+
% Helper functions
116+
%
117+
118+
function meas = check_glitch_periodicity(x, param, meas, ch)
119+
120+
if meas.success
121+
return
122+
end
123+
124+
i1 = round(param.fs * param.t_ignore_from_start);
125+
i2 = length(x) - round(param.fs * param.t_ignore_from_end);
126+
[b, a] = butter(2, 0.95, 'high');
127+
y = abs(filter(b, a, x));
128+
z = y(i1:i2);
129+
thr = mean(z) + std(z);
130+
[~, locs]= findpeaks(z, 'MinPeakHeight', thr);
131+
dlocs = diff(locs);
132+
if isempty(dlocs)
133+
return
134+
end
135+
136+
[counts, centers] = hist(dlocs);
137+
[counts, i] = sort(counts, 'descend');
138+
centers = centers(i) * 1e3 / param.fs;
139+
thr = mean(counts) + std(counts);
140+
idx = find(counts > thr);
141+
if ~isempty(idx)
142+
fprintf(1, 'Ch%d periodic glitches possibly every', ch)
143+
for i = 1 : length(idx)
144+
fprintf(' %.1f ms (n = %d)', centers(idx(i)), counts(idx(i)));
145+
end
146+
fprintf(1, '\n');
147+
end
148+
149+
end
150+
151+
function success = signal_found(x)
152+
153+
% All zeros or DC
154+
if abs(min(x) - max(x)) < eps
155+
success = 0;
156+
else
157+
success = 1;
158+
end
159+
160+
end
161+
162+
function meas = get_tone_levels(stft, param, f, t, signal_idx)
163+
164+
signal_i1 = signal_idx - param.win_spread;
165+
signal_i2 = signal_idx + param.win_spread;
166+
if signal_i1 < 1
167+
error('Too low tone frequency, increase FFT length');
168+
end
169+
170+
signal_db = zeros(param.n_stft, 1);
171+
noise_db = zeros(param.n_stft, 1);
172+
snr_db = zeros(param.n_stft, 1);
173+
for i = 1 : param.n_stft
174+
% Integrate signal power
175+
p_signal = sum(stft(signal_i1 : signal_i2, i));
176+
177+
% Integrate noise power, but replace DC and signal with
178+
% average noise level.
179+
noise = stft(:, i);
180+
noise_avg = mean(noise(signal_i2 : end));
181+
noise(1 : param.win_spread) = noise_avg;
182+
noise(signal_i1 : signal_i2) = noise_avg;
183+
p_noise = sum(noise);
184+
185+
% Sign, noise, and "SNR" as dB
186+
signal_db(i) = 10*log10(p_signal);
187+
noise_db(i) = 10*log10(p_noise);
188+
snr_db(i) = signal_db(i) - noise_db(i);
189+
end
190+
191+
meas.noise_db = noise_db - param.win_gain;
192+
meas.signal_db = signal_db - param.win_gain;
193+
meas.snr_db = signal_db - noise_db;
194+
195+
i1 = find(t > param.t_ignore_from_start, 1, 'first');
196+
i2 = find(t <= t(end) - param.t_ignore_from_end, 1, 'last');
197+
198+
meas.mean_signal_db = mean(meas.signal_db(i1 :i2));
199+
meas.mean_noise_db = mean(meas.noise_db(i1 :i2));
200+
meas.mean_snr_db = mean(meas.snr_db(i1 :i2));
201+
meas.max_noise_db = max(meas.noise_db(i1 :i2));
202+
meas.min_snr_db = min(meas.snr_db(i1 :i2));
203+
204+
end
205+
206+
function meas = check_tone_levels(stft, param, meas, t)
207+
208+
meas.t_glitch = 0;
209+
meas.num_glitches = 0;
210+
meas.success = true;
211+
212+
% Find glitches from SNR curve drops
213+
i1 = find(t > param.t_ignore_from_start, 1, 'first');
214+
i2 = find(t <= t(end) - param.t_ignore_from_end, 1, 'last');
215+
idx = find(meas.snr_db(i1:i2) < meas.mean_snr_db - param.max_snr_drop);
216+
217+
if ~isempty(idx)
218+
idx = idx + i1 - 1;
219+
didx = diff(idx);
220+
meas.num_glitches = 1 + length(find(didx > 2));
221+
start_idx = idx(1);
222+
cidx = find(didx(1:end) > 1, 1, 'first');
223+
if isempty(cidx)
224+
end_idx = idx(end);
225+
else
226+
end_idx = idx(cidx);
227+
end
228+
meas.t_glitch = param.t_step * mean([start_idx end_idx] - 1) + ...
229+
0.5 * param.n_fft / param.fs;
230+
meas.success = false;
231+
end
232+
233+
if meas.min_snr_db < param.min_snr
234+
meas.success = false;
235+
end
236+
237+
end
238+
239+
function [signal_idx, tone_f] = find_test_tone(stft, param, f, t, test_tone_f)
240+
241+
if test_tone_f > 0
242+
err_ms = (f - test_tone_f) .^ 2;
243+
signal_idx = find(err_ms == min(err_ms));
244+
tone_f = f(signal_idx);
245+
return
246+
end
247+
248+
i1 = find(t > param.t_ignore_from_start, 1, 'first');
249+
i2 = find(t <= t(end) - param.t_ignore_from_end, 1, 'last');
250+
251+
signal_idx_all = zeros(i2 - i1 + 1, 1);
252+
for i = i1 : i2
253+
signal_idx_all(i - i1 + 1) = find(stft(:, i) == max(stft(:, i)),1, 'first');
254+
end
255+
256+
signal_idx = round(mean(signal_idx_all));
257+
tone_f = f(signal_idx);
258+
259+
end
260+
261+
function [x, fs, frames, channels] = read_audio(fn)
262+
263+
[x, fs] = audioread(fn);
264+
sx = size(x);
265+
frames = sx(1);
266+
channels = sx(2);
267+
268+
end
269+
270+
function [stft, f, t, param] = compute_stft(x, fs, param)
271+
272+
sx = size(x);
273+
if sx(2) > 1
274+
error('One channel only');
275+
end
276+
277+
frames = sx(1);
278+
win = kaiser(param.n_fft, 20);
279+
param.win_spread = 7;
280+
param.win_gain = -13.0379;
281+
param.fs = fs;
282+
283+
param.n_step = fs * param.t_step;
284+
param.n_stft = floor((frames - param.n_fft) / param.n_step);
285+
n_half_fft = param.n_fft / 2 + 1;
286+
scale = 1 / param.n_fft;
287+
f = linspace(0, fs/2, n_half_fft);
288+
t = (0 : (param.n_stft - 1)) * param.t_step;
289+
stft = zeros(n_half_fft, param.n_stft);
290+
291+
for i = 1 : param.n_stft
292+
i1 = (i - 1) * param.n_step + 1;
293+
i2 = i1 + param.n_fft - 1;
294+
s1 = fft(x(i1 : i2) .* win) * scale;
295+
s2 = s1(1 : n_half_fft);
296+
s = s2 .* conj(s2);
297+
stft(:, i) = s;
298+
end
299+
300+
end
301+
302+
function fh = plot_specgram(stft, f, t, tstr)
303+
304+
fh = figure;
305+
clims = [-160 0];
306+
imagesc(1e3 * t, f, 10*log10(stft), clims)
307+
set(gca, 'ydir', 'normal');
308+
colorbar;
309+
grid on;
310+
title(tstr, 'interpreter', 'none');
311+
xlabel('Time (ms)');
312+
ylabel('Frequency (Hz)');
313+
314+
end
315+
316+
function fh = plot_levels(meas, t, idstr)
317+
318+
t_ms = 1e3 * t;
319+
fh = figure;
320+
subplot(3, 1, 1);
321+
plot(t_ms, meas.snr_db);
322+
grid on;
323+
ylabel('SNR (dB)');
324+
title(idstr, 'interpreter', 'none');
325+
subplot(3, 1, 2);
326+
plot(t_ms, meas.signal_db);
327+
grid on;
328+
ylabel('Signal (dBFS)');
329+
subplot(3, 1, 3);
330+
plot(t_ms, meas.noise_db);
331+
grid on;
332+
ylabel('Noise (dBFS)');
333+
xlabel('Time (ms)');
334+
335+
end
336+
337+
function fh = plot_glitch(x, param, meas, idstr)
338+
339+
fh = figure;
340+
t_ms = 1e3 * (0:(length(x) - 1)) / param.fs;
341+
plot(t_ms, x);
342+
t_start = meas.t_glitch - param.t_step;
343+
t_end = meas.t_glitch + param.t_step;
344+
ax = axis();
345+
axis([1e3 * t_start 1e3 * t_end ax(3:4)]);
346+
grid on;
347+
title(idstr, 'interpreter', 'none');
348+
xlabel('Time (ms)');
349+
ylabel('PCM sample values');
350+
351+
end

0 commit comments

Comments
 (0)