arduino-audio-tools
Loading...
Searching...
No Matches
QualityAnalysisStream.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4
7
8namespace audio_tools {
9
14enum class QualityIssue : uint8_t {
15 Click,
16 Dropout,
20};
21
27using QualityCallback = void (*)(QualityIssue issue, uint32_t count);
28
34 uint32_t click_count = 0;
35 uint32_t dropout_count = 0;
36 uint32_t clipping_count = 0;
37 uint32_t total_samples = 0;
38
40 float rms_level = 0.0f;
42 float noise_floor = 0.0f;
44 float snr_db = 0.0f;
47 float tonality_confidence = 0.0f;
51 bool signal_present = false;
53 uint32_t signal_lost_count = 0;
56
57 void clear() {
58 click_count = 0;
59 dropout_count = 0;
61 total_samples = 0;
62 rms_level = 0.0f;
63 noise_floor = 0.0f;
64 snr_db = 0.0f;
66 signal_present = false;
69 }
70};
71
116 public:
120
123 return begin();
124 }
125
126 bool begin() override {
128 resetState();
130 initTonality();
131 return ModifyingStream::begin();
132 }
133
140
141 void setOutput(Print& out) override { p_out = &out; }
142
143 void setStream(Stream& io) override {
144 p_out = &io;
145 p_io = &io;
146 }
147
148 size_t write(const uint8_t* data, size_t len) override {
149 analyze(data, len);
150 if (p_out != nullptr) return p_out->write(data, len);
151 return len;
152 }
153
154 size_t readBytes(uint8_t* data, size_t len) override {
155 if (p_io == nullptr) return 0;
156 size_t result = p_io->readBytes(data, len);
157 analyze(data, result);
158 return result;
159 }
160
161 int available() override {
162 return p_io != nullptr ? p_io->available() : 0;
163 }
164
165 int availableForWrite() override {
166 return p_out != nullptr ? p_out->availableForWrite() : DEFAULT_BUFFER_SIZE;
167 }
168
169 void flush() override {
170 if (p_out != nullptr) p_out->flush();
171 }
172
174 void setClickThreshold(float ratio) { click_threshold = ratio; }
175
177 void setDropoutMinSamples(int samples) { dropout_min_samples = samples; }
178
180 void setSilenceThreshold(float ratio) { silence_threshold = ratio; }
181
183 void setClippingMinSamples(int samples) { clipping_min_samples = samples; }
184
186 void setClippingMargin(float ratio) { clipping_margin = ratio; }
187
189 void setNoiseFloorBlockSize(int samples) {
190 if (samples > 0) rms_block_size = samples;
191 }
192
197 void setNoiseFloorTracking(float attack, float release) {
198 noise_floor_attack = attack;
199 noise_floor_release = release;
200 }
201
204 void setSignalMargin(float db) { signal_margin_db = db; }
205
208 void setSignalPresenceMinBlocks(int blocks) {
209 if (blocks > 0) signal_present_min_blocks = blocks;
210 }
211
215
219
224 void setTonalityEnabled(bool enabled) {
225 tonality_enabled = enabled;
226 if (!enabled) {
227 p_tonality = nullptr;
228 } else if (owns_tonality) {
229 auto_tonality.setBufferSize(0); // force re-derive from current info
232 }
233 }
234
239 void setTonalityBufferSize(int samples) {
243 }
244 }
245
248 void setTonalityMinConfidence(float min_confidence) {
249 tonality_min_confidence = min_confidence;
250 }
251
260 float min_confidence = 0.3f, int channel = 0) {
261 owns_tonality = false;
262 tonality_enabled = true;
263 p_tonality = &detector;
264 tonality_min_confidence = min_confidence;
265 tonality_channel = channel;
266 }
267
271 owns_tonality = true;
272 initTonality();
273 }
274
278 void setReporting(int period_ms, Print& output) {
279 report_period_ms = period_ms;
280 p_report = &output;
281 }
282
285
287 const QualityStats& stats() const { return stats_data; }
288
290 void clearStats() {
292 resetState();
293 }
294
295 protected:
296 Print* p_out = nullptr;
297 Stream* p_io = nullptr;
298
301
302 Print* p_report = nullptr;
304 uint32_t last_report_time = 0;
305
306 float click_threshold = 0.5f;
308 float silence_threshold = 0.01f;
310 float clipping_margin = 0.01f;
311
312 int rms_block_size = 256;
313 float noise_floor_attack = 0.3f;
314 float noise_floor_release = 0.01f;
315 float signal_margin_db = 6.0f;
319 bool tonality_enabled = true;
320 bool owns_tonality = true;
323
324 float max_value = 32767.0f;
325 // per-channel previous sample for click detection
327 bool has_prev_sample = false;
328 // dropout detection
330 bool in_dropout = false;
331 // clipping detection
333 bool in_clipping = false;
334 // signal vs. noise detection
335 double rms_sum_sq = 0;
340
341 void resetState() {
342 int ch = info.channels > 0 ? info.channels : 1;
344 for (int i = 0; i < ch; i++) prev_sample[i] = 0;
345 has_prev_sample = false;
347 in_dropout = false;
349 in_clipping = false;
350 rms_sum_sq = 0;
351 rms_block_count = 0;
355 }
356
361 if (!tonality_enabled) {
362 p_tonality = nullptr;
363 return;
364 }
365 if (!owns_tonality) return; // external detector managed by the caller
366 if (info.sample_rate <= 0) return;
367 auto_tonality.setBufferSize(0); // re-derive buffer size from info
370 }
371
372 void reportIfDue() {
373 if (p_report == nullptr || report_period_ms <= 0) return;
374 uint32_t now = millis();
375 if (now - last_report_time >= (uint32_t)report_period_ms) {
376 last_report_time = now;
377 printReport();
378 }
379 }
380
381 void printReport() {
382 char msg[220];
383 if (p_tonality != nullptr) {
384 snprintf(msg, sizeof(msg),
385 "Quality: clicks=%lu, dropouts=%lu, clipping=%lu, samples=%lu, "
386 "rms=%.1f, noise_floor=%.1f, snr=%.1fdB, tonality=%.2f, signal=%s",
387 (unsigned long)stats_data.click_count,
388 (unsigned long)stats_data.dropout_count,
389 (unsigned long)stats_data.clipping_count,
390 (unsigned long)stats_data.total_samples,
393 stats_data.signal_present ? "yes" : "no");
394 } else {
395 snprintf(msg, sizeof(msg),
396 "Quality: clicks=%lu, dropouts=%lu, clipping=%lu, samples=%lu, "
397 "rms=%.1f, noise_floor=%.1f, snr=%.1fdB, signal=%s",
398 (unsigned long)stats_data.click_count,
399 (unsigned long)stats_data.dropout_count,
400 (unsigned long)stats_data.clipping_count,
401 (unsigned long)stats_data.total_samples,
403 (double)stats_data.snr_db,
404 stats_data.signal_present ? "yes" : "no");
405 }
406 p_report->println(msg);
407 }
408
409 void analyze(const uint8_t* data, size_t len) {
410 if (data == nullptr || len == 0) return;
411 // Feed the built-in tonality detector with the same raw audio (an
412 // externally-owned one set via setTonalitySource() is fed by the caller)
413 if (owns_tonality && tonality_enabled && p_tonality != nullptr) {
414 p_tonality->write(data, len);
415 }
416 switch (info.bits_per_sample) {
417 case 8:
418 analyzeT<int8_t>(data, len);
419 break;
420 case 16:
421 analyzeT<int16_t>(data, len);
422 break;
423 case 24:
424 analyzeT<int24_t>(data, len);
425 break;
426 case 32:
427 analyzeT<int32_t>(data, len);
428 break;
429 default:
430 LOGE("QualityAnalysisStream: unsupported bits_per_sample %d",
432 break;
433 }
434 reportIfDue();
435 }
436
437 template <typename T>
438 void analyzeT(const uint8_t* buffer, size_t size) {
439 T* samples = (T*)buffer;
440 int count = size / sizeof(T);
441 int channels = info.channels > 0 ? info.channels : 1;
442 float abs_click = click_threshold * max_value;
443 float abs_silence = silence_threshold * max_value;
444 float abs_clip = max_value * (1.0f - clipping_margin);
445
446 for (int i = 0; i < count; i++) {
447 float val = static_cast<float>(static_cast<int>(samples[i]));
448 float abs_val = val < 0 ? -val : val;
449 int ch = i % channels;
450
452
453 // Click / pop detection
454 if (has_prev_sample) {
455 float delta = val - prev_sample[ch];
456 if (delta < 0) delta = -delta;
457 if (delta > abs_click) {
460 }
461 }
462 prev_sample[ch] = val;
463
464 // Dropout detection
465 if (abs_val <= abs_silence) {
468 in_dropout = true;
470 if (callback)
472 }
473 } else {
475 in_dropout = false;
476 }
477
478 // Clipping detection
479 if (abs_val >= abs_clip) {
482 in_clipping = true;
484 if (callback)
486 }
487 } else {
489 in_clipping = false;
490 }
491
492 // Signal vs. noise (RMS / noise-floor) detection
493 rms_sum_sq += (double)val * (double)val;
497 }
498 }
499
500 // Mark that we have valid previous samples after the first buffer
501 if (count >= channels) has_prev_sample = true;
502 }
503
507 float block_rms = sqrt((float)(rms_sum_sq / rms_block_count));
508 rms_sum_sq = 0;
509 rms_block_count = 0;
510
512 stats_data.noise_floor = block_rms;
514 } else if (block_rms < stats_data.noise_floor) {
517 } else {
520 }
521
522 stats_data.rms_level = block_rms;
523 float floor = stats_data.noise_floor > 1.0f ? stats_data.noise_floor : 1.0f;
524 float level = block_rms > 1.0f ? block_rms : 1.0f;
525 stats_data.snr_db = 20.0f * log10(level / floor);
526
527 if (p_tonality != nullptr) {
529 }
530
531 float threshold = stats_data.noise_floor * pow(10.0f, signal_margin_db / 20.0f);
532 bool level_ok = block_rms > threshold;
533 bool tonal_ok = p_tonality == nullptr ||
535 if (level_ok && tonal_ok) {
542 if (callback)
545 }
546 } else {
553 if (callback)
555 }
556 }
557 }
558};
559
560} // namespace audio_tools
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define DEFAULT_BUFFER_SIZE
Definition avr.h:20
Definition Arduino.h:56
virtual int availableForWrite()
Definition Arduino.h:128
virtual size_t write(const uint8_t *data, size_t len)
Definition Arduino.h:120
virtual void flush()
Definition Arduino.h:130
Definition Arduino.h:136
virtual size_t readBytes(uint8_t *data, size_t len)
Definition Arduino.h:140
virtual int available()
Definition Arduino.h:139
virtual void setAudioInfo(AudioInfo info)=0
Defines the input AudioInfo.
AudioInfo info
Definition BaseStream.h:171
virtual bool begin()
Definition BaseStream.h:40
Detects frequency using autocorrelation on audio samples.
Definition FrequencyDetectorAutoCorrelation.h:22
virtual size_t write(const uint8_t *data, size_t len) override
Writes bytes to the output stream and processes them for frequency detection.
Definition FrequencyDetectorAutoCorrelation.h:146
float confidence(int channel)
Returns a tonality/periodicity confidence for the given channel, in the range 0.0 (no periodic struct...
Definition FrequencyDetectorAutoCorrelation.h:197
void setBufferSize(int bufferSize)
Sets the analysis buffer size explicitly (in samples). If not called, begin() derives a size that cov...
Definition FrequencyDetectorAutoCorrelation.h:74
bool begin(AudioInfo info)
Initialize with audio configuration.
Definition FrequencyDetectorAutoCorrelation.h:64
Abstract class: Objects can be put into a pipleline.
Definition AudioStreams.h:68
static int64_t maxValue(int value_bits_per_sample)
provides the biggest number for the indicated number of bits
Definition AudioTypes.h:303
Analyzes audio stream quality by detecting clicks/pops, gaps/dropouts, clipping/corruption,...
Definition QualityAnalysisStream.h:115
QualityStats stats_data
Definition QualityAnalysisStream.h:299
int report_period_ms
Definition QualityAnalysisStream.h:303
int consecutive_clipped
Definition QualityAnalysisStream.h:332
uint32_t last_report_time
Definition QualityAnalysisStream.h:304
void flush() override
Definition QualityAnalysisStream.h:169
void setOutput(Print &out) override
Defines/Changes the output target.
Definition QualityAnalysisStream.h:141
FrequencyDetectorAutoCorrelation auto_tonality
Definition QualityAnalysisStream.h:317
int clipping_min_samples
Definition QualityAnalysisStream.h:309
void clearTonalitySource()
Definition QualityAnalysisStream.h:270
QualityCallback callback
Definition QualityAnalysisStream.h:300
void setCallback(QualityCallback cb)
Register a callback for quality issue notifications.
Definition QualityAnalysisStream.h:284
const QualityStats & stats() const
Access the accumulated quality statistics.
Definition QualityAnalysisStream.h:287
float tonalityConfidence() const
Definition QualityAnalysisStream.h:218
void analyzeT(const uint8_t *buffer, size_t size)
Definition QualityAnalysisStream.h:438
void reportIfDue()
Definition QualityAnalysisStream.h:372
int consecutive_silent
Definition QualityAnalysisStream.h:329
float clipping_margin
Definition QualityAnalysisStream.h:310
bool noise_floor_initialized
Definition QualityAnalysisStream.h:337
void setSilenceThreshold(float ratio)
Samples below this ratio of max value are considered silent.
Definition QualityAnalysisStream.h:180
void setTonalityEnabled(bool enabled)
Definition QualityAnalysisStream.h:224
int dropout_min_samples
Definition QualityAnalysisStream.h:307
void setSignalPresenceMinBlocks(int blocks)
Definition QualityAnalysisStream.h:208
size_t readBytes(uint8_t *data, size_t len) override
Definition QualityAnalysisStream.h:154
bool in_dropout
Definition QualityAnalysisStream.h:330
double rms_sum_sq
Definition QualityAnalysisStream.h:335
bool in_clipping
Definition QualityAnalysisStream.h:333
float silence_threshold
Definition QualityAnalysisStream.h:308
void setTonalityBufferSize(int samples)
Definition QualityAnalysisStream.h:239
int signal_present_min_blocks
Definition QualityAnalysisStream.h:316
int available() override
Definition QualityAnalysisStream.h:161
void setNoiseFloorBlockSize(int samples)
Number of samples per RMS/noise-floor block (default 256)
Definition QualityAnalysisStream.h:189
Print * p_report
Definition QualityAnalysisStream.h:302
float signal_margin_db
Definition QualityAnalysisStream.h:315
void resetState()
Definition QualityAnalysisStream.h:341
size_t write(const uint8_t *data, size_t len) override
Definition QualityAnalysisStream.h:148
int tonality_channel
Definition QualityAnalysisStream.h:322
FrequencyDetectorAutoCorrelation * p_tonality
Definition QualityAnalysisStream.h:318
float noise_floor_release
Definition QualityAnalysisStream.h:314
QualityAnalysisStream(Stream &stream)
Definition QualityAnalysisStream.h:119
Vector< float > prev_sample
Definition QualityAnalysisStream.h:326
void setClippingMargin(float ratio)
Clipping is detected when sample >= max_value * (1 - margin)
Definition QualityAnalysisStream.h:186
int availableForWrite() override
Definition QualityAnalysisStream.h:165
void analyze(const uint8_t *data, size_t len)
Definition QualityAnalysisStream.h:409
QualityAnalysisStream(Print &print)
Definition QualityAnalysisStream.h:118
void setDropoutMinSamples(int samples)
Minimum consecutive near-silent samples to count as a dropout.
Definition QualityAnalysisStream.h:177
bool has_prev_sample
Definition QualityAnalysisStream.h:327
bool tonality_enabled
Definition QualityAnalysisStream.h:319
void setTonalitySource(FrequencyDetectorAutoCorrelation &detector, float min_confidence=0.3f, int channel=0)
Definition QualityAnalysisStream.h:259
void setTonalityMinConfidence(float min_confidence)
Definition QualityAnalysisStream.h:248
void initTonality()
Definition QualityAnalysisStream.h:360
int consecutive_noise_blocks
Definition QualityAnalysisStream.h:339
Print * p_out
Definition QualityAnalysisStream.h:296
bool isSignalPresent() const
Definition QualityAnalysisStream.h:214
float tonality_min_confidence
Definition QualityAnalysisStream.h:321
void processRmsBlock()
Definition QualityAnalysisStream.h:506
int rms_block_size
Definition QualityAnalysisStream.h:312
void setNoiseFloorTracking(float attack, float release)
Definition QualityAnalysisStream.h:197
int rms_block_count
Definition QualityAnalysisStream.h:336
float max_value
Definition QualityAnalysisStream.h:324
float noise_floor_attack
Definition QualityAnalysisStream.h:313
void clearStats()
Reset all statistics and detection state.
Definition QualityAnalysisStream.h:290
void printReport()
Definition QualityAnalysisStream.h:381
bool begin() override
Definition QualityAnalysisStream.h:126
void setAudioInfo(AudioInfo info) override
Defines the input AudioInfo.
Definition QualityAnalysisStream.h:134
void setReporting(int period_ms, Print &output)
Definition QualityAnalysisStream.h:278
void setStream(Stream &io) override
Defines/Changes the input & output.
Definition QualityAnalysisStream.h:143
int consecutive_signal_blocks
Definition QualityAnalysisStream.h:338
void setSignalMargin(float db)
Definition QualityAnalysisStream.h:204
void setClickThreshold(float ratio)
Sample-to-sample jump threshold as ratio of max value (0.0 to 1.0)
Definition QualityAnalysisStream.h:174
bool owns_tonality
Definition QualityAnalysisStream.h:320
float click_threshold
Definition QualityAnalysisStream.h:306
bool begin(AudioInfo info)
Definition QualityAnalysisStream.h:121
Stream * p_io
Definition QualityAnalysisStream.h:297
void setClippingMinSamples(int samples)
Minimum consecutive samples at max to count as clipping.
Definition QualityAnalysisStream.h:183
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
bool resize(size_t newSize, T value)
Definition Vector.h:266
QualityIssue
Quality issues detected by QualityAnalysisStream.
Definition QualityAnalysisStream.h:14
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
void(*)(QualityIssue issue, uint32_t count) QualityCallback
Callback for quality issue notifications.
Definition QualityAnalysisStream.h:27
uint32_t millis()
Returns the milliseconds since the start.
Definition Arduino.h:260
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:56
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:58
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:60
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:62
Statistics collected by QualityAnalysisStream.
Definition QualityAnalysisStream.h:33
uint32_t signal_detected_count
Number of times the signal transitioned from lost/absent to present.
Definition QualityAnalysisStream.h:55
float rms_level
Most recent block RMS level (linear, same scale as the sample values)
Definition QualityAnalysisStream.h:40
float snr_db
Signal-to-noise ratio of the most recent block, in dB.
Definition QualityAnalysisStream.h:44
uint32_t total_samples
Definition QualityAnalysisStream.h:37
bool signal_present
Definition QualityAnalysisStream.h:51
uint32_t signal_lost_count
Number of times the signal transitioned from present to lost.
Definition QualityAnalysisStream.h:53
float tonality_confidence
Definition QualityAnalysisStream.h:47
uint32_t dropout_count
Definition QualityAnalysisStream.h:35
void clear()
Definition QualityAnalysisStream.h:57
uint32_t click_count
Definition QualityAnalysisStream.h:34
float noise_floor
Slowly tracked noise floor estimate (linear, same scale as rms_level)
Definition QualityAnalysisStream.h:42
uint32_t clipping_count
Definition QualityAnalysisStream.h:36