BirdNet-goMikrofon/Arduino-audio-tools-main/arduino-audio-tools-main/examples/tests/adc/test-esp32-rx-v1-acceptance/test-esp32-rx-v1-acceptance.ino

269 lines
8.9 KiB
Arduino
Raw Permalink Normal View History

2026-09-26 01:20:55 +02:00
/**
* @file test-esp32-rx-v1-acceptance.ino
* @brief Acceptance test for the ESP32 ADC v1 FIFO reorder redesign.
*
* This sketch verifies the key behaviors introduced by the redesign:
* - large multichannel readBytes() windows complete across multiple ADC chunks
* - no request-sized temporary allocation is performed inside readBytes()
* - available() is driven by already formable frames when buffered data exists
* - auto-centering still runs on the completed returned block
*
* Suggested wiring for stronger validation:
* - connect ADC_CHANNEL_4 and ADC_CHANNEL_5 to the same analog source
* (for example the same signal generator output through the same divider)
* - with both channels driven from the same source, channel delta statistics
* should stay small and stable
*
* Expected pass criteria:
* - short_reads remains 0 for every request-size scenario
* - heap deltas remain near zero while request sizes change
* - available_before eventually reports non-zero values that are bounded to
* assembled frame readiness instead of scaling with the full request window
* - if both channels are tied together, avg_abs_delta remains small
*/
#include "Arduino.h"
#include "AudioTools.h"
#if defined(ESP32) && defined(USE_ANALOG) && !USE_LEGACY_I2S
#include "esp_heap_caps.h"
using namespace audio_tools;
AnalogDriverESP32V1 analog_driver;
AnalogAudioStream analog_in(analog_driver);
constexpr int kChannels = 2;
constexpr int kBitsPerSample = 16;
constexpr uint32_t kSampleRate = 24000;
constexpr uint16_t kBufferSize = 256;
constexpr uint8_t kBufferCount = 4;
constexpr uint32_t kScenarioPauseMs = 250;
constexpr uint32_t kWarmupDelayMs = 60;
constexpr uint32_t kWindowsPerScenario = 12;
constexpr size_t kRequestSizes[] = {256, 1024, 4096, 8192, 16384};
constexpr size_t kMaxRequestSize = 16384;
uint8_t sample_buffer[kMaxRequestSize];
size_t scenario_index = 0;
bool all_scenarios_completed = false;
uint32_t windows_completed = 0;
uint32_t short_reads = 0;
size_t min_bytes_read = kMaxRequestSize;
size_t max_bytes_read = 0;
int min_avail_before = 0x7fffffff;
int max_avail_before = 0;
int min_avail_after = 0x7fffffff;
int max_avail_after = 0;
uint32_t baseline_free_heap = 0;
uint32_t min_free_heap = 0xffffffffu;
uint32_t max_free_heap = 0;
uint32_t min_heap_delta = 0xffffffffu;
uint32_t max_heap_delta = 0;
double min_block_mean_ch0 = 1e9;
double max_block_mean_ch0 = -1e9;
double min_block_mean_ch1 = 1e9;
double max_block_mean_ch1 = -1e9;
double min_avg_abs_delta = 1e9;
double max_avg_abs_delta = 0.0;
size_t currentRequestSize() { return kRequestSizes[scenario_index]; }
uint32_t currentFreeHeap() {
return heap_caps_get_free_size(MALLOC_CAP_8BIT);
}
void resetScenarioStats() {
windows_completed = 0;
short_reads = 0;
min_bytes_read = currentRequestSize();
max_bytes_read = 0;
min_avail_before = 0x7fffffff;
max_avail_before = 0;
min_avail_after = 0x7fffffff;
max_avail_after = 0;
baseline_free_heap = currentFreeHeap();
min_free_heap = baseline_free_heap;
max_free_heap = baseline_free_heap;
min_heap_delta = 0;
max_heap_delta = 0;
min_block_mean_ch0 = 1e9;
max_block_mean_ch0 = -1e9;
min_block_mean_ch1 = 1e9;
max_block_mean_ch1 = -1e9;
min_avg_abs_delta = 1e9;
max_avg_abs_delta = 0.0;
}
void advanceScenario() {
scenario_index++;
if (scenario_index >= (sizeof(kRequestSizes) / sizeof(kRequestSizes[0]))) {
all_scenarios_completed = true;
}
}
void updateBlockStats(const int16_t *samples, size_t frame_count, double &mean0,
double &mean1, double &avg_abs_delta) {
mean0 = 0.0;
mean1 = 0.0;
avg_abs_delta = 0.0;
if (frame_count == 0) return;
double sum0 = 0.0;
double sum1 = 0.0;
double delta_sum = 0.0;
for (size_t i = 0; i < frame_count; ++i) {
int16_t ch0 = samples[(i * kChannels) + 0];
int16_t ch1 = samples[(i * kChannels) + 1];
sum0 += (double)ch0;
sum1 += (double)ch1;
delta_sum += abs((int)ch0 - (int)ch1);
}
mean0 = sum0 / (double)frame_count;
mean1 = sum1 / (double)frame_count;
avg_abs_delta = delta_sum / (double)frame_count;
if (mean0 < min_block_mean_ch0) min_block_mean_ch0 = mean0;
if (mean0 > max_block_mean_ch0) max_block_mean_ch0 = mean0;
if (mean1 < min_block_mean_ch1) min_block_mean_ch1 = mean1;
if (mean1 > max_block_mean_ch1) max_block_mean_ch1 = mean1;
if (avg_abs_delta < min_avg_abs_delta) min_avg_abs_delta = avg_abs_delta;
if (avg_abs_delta > max_avg_abs_delta) max_avg_abs_delta = avg_abs_delta;
}
bool applyScenario() {
analog_in.end();
auto cfg = analog_in.defaultConfig(RX_MODE);
cfg.sample_rate = kSampleRate;
cfg.channels = kChannels;
cfg.bits_per_sample = kBitsPerSample;
cfg.buffer_size = kBufferSize;
cfg.buffer_count = kBufferCount;
cfg.is_auto_center_read = true;
cfg.adc_channels[0] = ADC_CHANNEL_4;
cfg.adc_channels[1] = ADC_CHANNEL_5;
Serial.println();
Serial.println("ESP32 RX V1 acceptance test");
Serial.println("Large multichannel raw reads with heap and availability checks");
Serial.printf("sample_rate: %lu Hz/channel, channels: %d\n",
(unsigned long)cfg.sample_rate, cfg.channels);
Serial.printf("buffer_size: %u results, buffer_count: %u\n",
(unsigned)cfg.buffer_size, (unsigned)cfg.buffer_count);
Serial.printf("request_size: %u bytes\n", (unsigned)currentRequestSize());
Serial.println("optional: tie ADC_CHANNEL_4 and ADC_CHANNEL_5 together");
if (!analog_in.begin(cfg)) {
Serial.println("analog_in.begin failed");
return false;
}
delay(kWarmupDelayMs);
resetScenarioStats();
return true;
}
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
if (!applyScenario()) {
while (true) {
delay(1000);
}
}
}
void loop() {
if (all_scenarios_completed) {
delay(1000);
return;
}
int avail_before = analog_in.available();
size_t bytes_read = analog_in.readBytes(sample_buffer, currentRequestSize());
int avail_after = analog_in.available();
uint32_t free_heap = currentFreeHeap();
uint32_t heap_delta =
(baseline_free_heap > free_heap) ? (baseline_free_heap - free_heap)
: (free_heap - baseline_free_heap);
windows_completed++;
if (bytes_read != currentRequestSize()) {
short_reads++;
}
if (bytes_read < min_bytes_read) min_bytes_read = bytes_read;
if (bytes_read > max_bytes_read) max_bytes_read = bytes_read;
if (avail_before < min_avail_before) min_avail_before = avail_before;
if (avail_before > max_avail_before) max_avail_before = avail_before;
if (avail_after < min_avail_after) min_avail_after = avail_after;
if (avail_after > max_avail_after) max_avail_after = avail_after;
if (free_heap < min_free_heap) min_free_heap = free_heap;
if (free_heap > max_free_heap) max_free_heap = free_heap;
if (heap_delta < min_heap_delta) min_heap_delta = heap_delta;
if (heap_delta > max_heap_delta) max_heap_delta = heap_delta;
size_t frame_count = bytes_read / (kChannels * sizeof(int16_t));
double current_mean0 = 0.0;
double current_mean1 = 0.0;
double current_avg_abs_delta = 0.0;
updateBlockStats((const int16_t *)sample_buffer, frame_count, current_mean0,
current_mean1, current_avg_abs_delta);
Serial.printf(
"req=%u window=%lu/%lu bytes=%u short_reads=%lu "
"avail_before=%d avail_after=%d heap=%lu delta=%lu "
"mean0=%.1f mean1=%.1f avg_abs_delta=%.1f\n",
(unsigned)currentRequestSize(), (unsigned long)windows_completed,
(unsigned long)kWindowsPerScenario, (unsigned)bytes_read,
(unsigned long)short_reads, avail_before, avail_after,
(unsigned long)free_heap, (unsigned long)heap_delta, current_mean0,
current_mean1, current_avg_abs_delta);
if (windows_completed >= kWindowsPerScenario) {
Serial.printf(
"summary req=%u bytes[min/max]=%u/%u short_reads=%lu "
"avail_before[min/max]=%d/%d avail_after[min/max]=%d/%d "
"heap[min/max]=%lu/%lu delta[min/max]=%lu/%lu "
"mean0[min/max]=%.1f/%.1f mean1[min/max]=%.1f/%.1f "
"avg_abs_delta[min/max]=%.1f/%.1f\n",
(unsigned)currentRequestSize(), (unsigned)min_bytes_read,
(unsigned)max_bytes_read, (unsigned long)short_reads,
min_avail_before, max_avail_before, min_avail_after, max_avail_after,
(unsigned long)min_free_heap, (unsigned long)max_free_heap,
(unsigned long)min_heap_delta, (unsigned long)max_heap_delta,
min_block_mean_ch0, max_block_mean_ch0, min_block_mean_ch1,
max_block_mean_ch1, min_avg_abs_delta, max_avg_abs_delta);
delay(kScenarioPauseMs);
advanceScenario();
if (all_scenarios_completed) {
analog_in.end();
Serial.println("acceptance test completed all request-size scenarios");
while (true) {
delay(1000);
}
}
if (!applyScenario()) {
while (true) {
delay(1000);
}
}
}
}
#else
void setup() {}
void loop() {}
#endif