radio: add activation controls, live VAD meters and signal diagnostics
This commit is contained in:
1 parent
fddbba217d
commit
7f3247bfcc
23 files changed
+818
-182
No files matched your search
+127
-7
@@ -17,7 +17,7 @@
|
||||
#include "../lib/silero_vad.h"
|
||||
#include "../lib/audio_compressor.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../src/radio/radio_vad.h"
|
||||
#include "../src/radio/radio_auto.h"
|
||||
|
||||
#define PI 3.14159265358979323846
|
||||
|
||||
@@ -198,8 +198,8 @@ static void test_speech(const char* model_path) {
|
||||
if (!vad) return;
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
silero_vad_reset(vad);
|
||||
struct radio_vad_fsm fsm;
|
||||
radio_vad_fsm_reset(&fsm);
|
||||
struct radio_auto_fsm fsm;
|
||||
radio_auto_fsm_reset(&fsm);
|
||||
uint64_t now = 1;
|
||||
float max_prob = 0;
|
||||
int starts = 0, stops = 0;
|
||||
@@ -223,7 +223,7 @@ static void test_speech(const char* model_path) {
|
||||
if (probability > max_prob) max_prob = probability;
|
||||
}
|
||||
now += 320;
|
||||
int transition = radio_vad_fsm_update(&fsm, probability, 0.5f, 2, 2000, 0, now);
|
||||
int transition = radio_auto_fsm_update(&fsm, probability, 0.5f, 0.5f, 2, 2000, 0, now);
|
||||
if (transition == 1 && window < SPEECH_WINDOWS) starts++;
|
||||
if (transition == -1 && window >= SPEECH_WINDOWS) stops++;
|
||||
}
|
||||
@@ -260,8 +260,8 @@ static void test_compressed_speech(float amplitude, int max_gain_db) {
|
||||
printf("[silero_vad] independent AGC speech amplitude=%.3f max_gain=%ddB + low noise\n", amplitude, max_gain_db);
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
audio_compressor_reset(ac); silero_vad_reset(vad);
|
||||
struct radio_vad_fsm fsm;
|
||||
radio_vad_fsm_reset(&fsm);
|
||||
struct radio_auto_fsm fsm;
|
||||
radio_auto_fsm_reset(&fsm);
|
||||
uint32_t noise = 1;
|
||||
float peak = 0, noise_peak = 0;
|
||||
int window_len = 0, window_idx = 0, starts = 0, stops = 0;
|
||||
@@ -293,7 +293,7 @@ static void test_compressed_speech(float amplitude, int max_gain_db) {
|
||||
int speech = end_frame > NOISE_FRAMES && end_frame <= NOISE_FRAMES + SPEECH_SAMPLES * 3;
|
||||
if (speech && probability > peak) peak = probability;
|
||||
if (end_frame <= NOISE_FRAMES && probability > noise_peak) noise_peak = probability;
|
||||
int transition = radio_vad_fsm_update(&fsm, probability, 0.5f, 2, 2000, 0, (uint64_t)(window_idx + 1) * 320);
|
||||
int transition = radio_auto_fsm_update(&fsm, probability, 0.5f, 0.5f, 2, 2000, 0, (uint64_t)(window_idx + 1) * 320);
|
||||
if (transition > 0) starts++;
|
||||
if (transition < 0) stops++;
|
||||
window_len = 0; ++window_idx;
|
||||
@@ -309,6 +309,111 @@ static void test_compressed_speech(float amplitude, int max_gain_db) {
|
||||
audio_compressor_destroy(ac); silero_vad_destroy(vad);
|
||||
}
|
||||
|
||||
/* Диагностика: один и тот же PCM повторяется без reset, виден эффект истории AGC/Silero. */
|
||||
static void probe_repeated_speech(float amplitude, int noise_amplitude, int max_gain_db) {
|
||||
enum { FRAMES = 153600, REPEATS = 4, SPEECH_START = 61440, SPEECH_FRAMES = 16384 * 3 };
|
||||
int16_t speech[16384], pcm[960];
|
||||
FILE* file = fopen(TEST_SILERO_SPEECH_PATH, "rb");
|
||||
CHECK(file != NULL, "probe fixture open"); if (!file) return;
|
||||
size_t size = fread(speech, sizeof(*speech), 16384, file); fclose(file);
|
||||
CHECK(size == 16384, "probe fixture size"); if (size != 16384) return;
|
||||
struct audio_compressor* ac = audio_compressor_create();
|
||||
silero_vad_t* vad = silero_vad_create_default();
|
||||
CHECK(ac && vad, "probe create");
|
||||
if (!ac || !vad) { audio_compressor_destroy(ac); silero_vad_destroy(vad); return; }
|
||||
audio_compressor_config_t cfg = {0};
|
||||
cfg.sample_rate = 48000; cfg.channels = 1; cfg.block_duration_ms = 20; cfg.lookback_ms = 200;
|
||||
cfg.max_gain_db = max_gain_db < 0 ? 0 : max_gain_db; cfg.rise_rate_per_sec = 4; cfg.target_level = 0.25f;
|
||||
CHECK(audio_compressor_configure(ac, &cfg) == 0, "probe configure");
|
||||
audio_compressor_set_enabled(ac, max_gain_db >= 0);
|
||||
float window[512]; int used = 0;
|
||||
for (int repeat = 0; repeat < REPEATS; ++repeat) {
|
||||
uint32_t random = 1, clips = audio_compressor_clip_count(ac);
|
||||
float peak_probability = 0, probability_sum = 0, noise_peak = 0, gain_at_start = 0;
|
||||
int speech_windows = 0, high_windows = 0;
|
||||
for (int pos = 0; pos < FRAMES; pos += 960) {
|
||||
for (int i = 0; i < 960; ++i) {
|
||||
random = random * 1664525u + 1013904223u;
|
||||
int value = noise_amplitude * ((int)(random >> 16) - 32768) / 32768;
|
||||
int offset = pos + i - SPEECH_START;
|
||||
if (offset >= 0 && offset < SPEECH_FRAMES) value += (int)(speech[offset / 3] * amplitude);
|
||||
pcm[i] = (int16_t)(value > 32767 ? 32767 : value < -32768 ? -32768 : value);
|
||||
}
|
||||
if (pos == SPEECH_START) gain_at_start = audio_compressor_gain_db(ac);
|
||||
CHECK(audio_compressor_process_frame(ac, pcm, pcm, 960) == 0, "probe process frame");
|
||||
for (int i = 0; i < 960; i += 3) {
|
||||
window[used++] = ((float)pcm[i] + pcm[i + 1] + pcm[i + 2]) / (3 * 32768.0f);
|
||||
if (used != 512) continue;
|
||||
used = 0; float probability = 0;
|
||||
CHECK(silero_vad_process(vad, window, &probability) == 0, "probe VAD");
|
||||
int end = pos + i + 3;
|
||||
if (end > SPEECH_START && end <= SPEECH_START + SPEECH_FRAMES) {
|
||||
if (probability > peak_probability) peak_probability = probability;
|
||||
probability_sum += probability; ++speech_windows;
|
||||
if (probability >= 0.8f) ++high_windows;
|
||||
} else if (end <= SPEECH_START && probability > noise_peak) noise_peak = probability;
|
||||
}
|
||||
}
|
||||
printf("VAD_PROBE amplitude=%.2f noise=%d gain_limit=%d repeat=%d start_gain=%.1f clips=%u speech_peak=%.4f "
|
||||
"speech_mean=%.4f high=%d/%d noise_peak=%.4f\n", amplitude, noise_amplitude, max_gain_db, repeat,
|
||||
gain_at_start, audio_compressor_clip_count(ac) - clips, peak_probability, probability_sum / speech_windows,
|
||||
high_windows, speech_windows, noise_peak);
|
||||
}
|
||||
audio_compressor_destroy(ac); silero_vad_destroy(vad);
|
||||
}
|
||||
|
||||
|
||||
/* PCM mono s16le/48кГц: две модели получают одинаковый выход AGC; сброс отличается только в указанном окне. */
|
||||
static void probe_stream(const char* path, unsigned reset_window, int max_gain_db) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
CHECK(file != NULL, "stream open: %s", path); if (!file) return;
|
||||
struct audio_compressor* ac = audio_compressor_create();
|
||||
silero_vad_t* history = silero_vad_create_default();
|
||||
silero_vad_t* reset = silero_vad_create_default();
|
||||
CHECK(ac && history && reset, "stream create");
|
||||
if (!ac || !history || !reset) goto done;
|
||||
audio_compressor_config_t cfg = {0};
|
||||
cfg.sample_rate = 48000; cfg.channels = 1; cfg.block_duration_ms = 20; cfg.lookback_ms = 200;
|
||||
cfg.max_gain_db = max_gain_db < 0 ? 0 : max_gain_db; cfg.rise_rate_per_sec = 4; cfg.target_level = 0.25f;
|
||||
int rc = audio_compressor_configure(ac, &cfg);
|
||||
CHECK(rc == 0, "stream compressor configure"); if (rc != 0) goto done;
|
||||
audio_compressor_set_enabled(ac, max_gain_db >= 0);
|
||||
uint8_t bytes[1920]; int16_t pcm[960]; float window[512];
|
||||
unsigned used = 0, index = 0;
|
||||
size_t count;
|
||||
while ((count = fread(bytes, 1, sizeof(bytes), file)) != 0) {
|
||||
CHECK(count == sizeof(bytes), "stream requires full 20ms frames, tail=%zu", count);
|
||||
if (count != sizeof(bytes)) break;
|
||||
for (int i = 0; i < 960; ++i) {
|
||||
int value = bytes[2*i] | ((int)bytes[2*i+1] << 8);
|
||||
pcm[i] = (int16_t)(value >= 32768 ? value - 65536 : value);
|
||||
}
|
||||
rc = audio_compressor_process_frame(ac, pcm, pcm, 960);
|
||||
CHECK(rc == 0, "stream compressor frame"); if (rc != 0) break;
|
||||
for (int i = 0; i < 960; i += 3) {
|
||||
window[used++] = ((float)pcm[i] + pcm[i+1] + pcm[i+2]) / (3 * 32768.0f);
|
||||
if (used != 512) continue;
|
||||
used = 0;
|
||||
if (index == reset_window) silero_vad_reset(reset);
|
||||
float p = 0, q = 0;
|
||||
int a = silero_vad_process(history, window, &p), b = silero_vad_process(reset, window, &q);
|
||||
CHECK(a == 0 && b == 0 && isfinite(p) && isfinite(q), "stream inference window=%u", index);
|
||||
if (a || b) goto done;
|
||||
if (index < reset_window) CHECK(fabsf(p - q) < 1e-6f, "stream models differ before reset window=%u", index);
|
||||
double energy = 0;
|
||||
for (int j = 0; j < 512; ++j) energy += window[j] * window[j];
|
||||
printf("VAD_STREAM gain_limit=%d window=%u time_ms=%u reset_window=%u history=%.6f reset=%.6f "
|
||||
"rms=%.6f gain=%.2f clips=%u\n", max_gain_db, index, index * 32, reset_window, p, q,
|
||||
sqrt(energy / 512), audio_compressor_gain_db(ac), audio_compressor_clip_count(ac));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
CHECK(!ferror(file), "stream read failed");
|
||||
CHECK(index > reset_window, "stream ended before reset window=%u total=%u", reset_window, index);
|
||||
done:
|
||||
fclose(file); audio_compressor_destroy(ac); silero_vad_destroy(history); silero_vad_destroy(reset);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const char* model_path = (argc > 1) ? argv[1] : "../lib/silero_vad.onnx";
|
||||
|
||||
@@ -318,6 +423,21 @@ int main(int argc, char** argv) {
|
||||
|
||||
printf("Silero VAD test, model: %s\n", model_path);
|
||||
|
||||
if (argc > 2 && !strcmp(argv[2], "--probe")) {
|
||||
const int gains[] = {-1, 1, 15, 30}, noises[] = {0, 300, 2000};
|
||||
for (int a = 0; a < 2; ++a) for (int n = 0; n < 3; ++n) for (int g = 0; g < 4; ++g)
|
||||
probe_repeated_speech(a ? 1.0f : 0.1f, noises[n], gains[g]);
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
if (argc > 2 && !strcmp(argv[2], "--probe-stream")) {
|
||||
unsigned reset_window = 0; char extra;
|
||||
if (argc != 5 || sscanf(argv[4], "%u%c", &reset_window, &extra) != 1 || reset_window > 1000000) {
|
||||
fprintf(stderr, "Usage: %s model --probe-stream mono48k.s16le reset_window\n", argv[0]); return 1;
|
||||
}
|
||||
const int gains[] = {-1, 1, 15};
|
||||
for (int i = 0; i < 3; ++i) probe_stream(argv[3], reset_window, gains[i]);
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
test_lifecycle_and_errors(model_path);
|
||||
test_silence(model_path);
|
||||
test_pcm16_matches_float(model_path);
|
||||
|
||||
Reference in new issue
Block a user