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
+40
-4
@@ -102,6 +102,8 @@ static int g_vad_pcm_frames = 0;
|
||||
static double g_vad_input_sum_sq = 0;
|
||||
static float g_vad_input_rms = 0, g_vad_output_rms = 0;
|
||||
static float g_vad_threshold = 0.5f;
|
||||
static float g_vad_probability;
|
||||
static uint64_t g_vad_updated_tb;
|
||||
static int g_auto_hangover_ms = 200;
|
||||
static int g_manual_ptt = 0; /* PTT override: зажата — детектор не вмешивается */
|
||||
static float g_vad_win[RADIO_VAD_WINDOW_16K]; /* накопитель окон 16кГц */
|
||||
@@ -131,6 +133,8 @@ static uint64_t g_tx_stats_tb = 0;
|
||||
static struct {
|
||||
uint64_t stats_tb, last_window_tb, total_windows, max_inference_tb, max_gap_tb;
|
||||
unsigned windows, speech, busy, confirm_resets, errors;
|
||||
unsigned clipped_samples, clipped_frames;
|
||||
float input_sample_peak, output_sample_peak;
|
||||
float min_probability, max_probability, max_input_rms, max_model_rms;
|
||||
int last_busy;
|
||||
} g_vad_diag;
|
||||
@@ -566,6 +570,7 @@ int radio_audio_capture_start(uint64_t group_id, int capture_channels, int auto_
|
||||
accfg.release_rate_per_sec, accfg.block_duration_ms, g_vad_compressor ? accfg.block_duration_ms : 0);
|
||||
}
|
||||
radio_audio_level_reset();
|
||||
g_vad_probability = 0; g_vad_updated_tb = 0;
|
||||
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0;
|
||||
g_vad_input_rms = g_vad_output_rms = 0;
|
||||
if (g_auto_mode) {
|
||||
@@ -616,6 +621,7 @@ void radio_audio_capture_stop(void) {
|
||||
if (g_vad) { silero_vad_destroy(g_vad); g_vad = NULL; }
|
||||
g_auto_mode = 0;
|
||||
radio_audio_level_reset();
|
||||
g_vad_probability = 0; g_vad_updated_tb = 0;
|
||||
g_manual_ptt = 0;
|
||||
g_vad_win_len = 0;
|
||||
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0;
|
||||
@@ -900,6 +906,7 @@ static int radio_audio_auto_block(uint64_t now) {
|
||||
(unsigned long long)(g_output_voice_end > g_output_played ? g_output_voice_end - g_output_played : 0),
|
||||
g_manual_ptt, g_transmitting, g_prefetch_frames);
|
||||
g_auto_blocked = busy;
|
||||
g_vad_updated_tb = 0;
|
||||
g_level_frames = 0; g_level_sum_sq = 0;
|
||||
g_vad_win_len = g_vad_decimation = 0; g_vad_sum = 0;
|
||||
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0;
|
||||
@@ -920,6 +927,7 @@ void radio_audio_capture_discontinuity(uint64_t group_id) {
|
||||
RADIO_AUDIO_ID, g_tx_pcm_frames, g_transmitting, g_vad_pcm_frames, audio_compressor_gain_db(g_vad_compressor));
|
||||
radio_audio_flush_tx();
|
||||
radio_audio_level_reset();
|
||||
g_vad_updated_tb = 0;
|
||||
g_vad_win_len = g_vad_decimation = 0; g_vad_sum = 0;
|
||||
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0;
|
||||
g_vad_input_rms = g_vad_output_rms = 0;
|
||||
@@ -942,10 +950,13 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
||||
g_vad_win[g_vad_win_len++] = g_vad_sum / (3.0f * 32768.0f);
|
||||
g_vad_decimation = 0; g_vad_sum = 0;
|
||||
if (g_vad_win_len != RADIO_VAD_WINDOW_16K) return;
|
||||
double sum_sq = 0;
|
||||
double sum_sq = 0, sum = 0;
|
||||
unsigned zeros = 0;
|
||||
float peak = 0;
|
||||
for (int i = 0; i < RADIO_VAD_WINDOW_16K; ++i) {
|
||||
sum_sq += (double)g_vad_win[i] * g_vad_win[i];
|
||||
sum += g_vad_win[i];
|
||||
if (g_vad_win[i] == 0) ++zeros;
|
||||
if (fabsf(g_vad_win[i]) > peak) peak = fabsf(g_vad_win[i]);
|
||||
}
|
||||
float model_rms = sqrtf((float)(sum_sq / RADIO_VAD_WINDOW_16K));
|
||||
@@ -965,11 +976,13 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
||||
if (inference_tb > g_vad_diag.max_inference_tb) g_vad_diag.max_inference_tb = inference_tb;
|
||||
if (gap_tb > g_vad_diag.max_gap_tb) g_vad_diag.max_gap_tb = gap_tb;
|
||||
if (result != 0) {
|
||||
g_vad_updated_tb = 0;
|
||||
++g_vad_diag.errors;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "VAD inference failed group=%016llx window=%llu rc=%d inference=%.1fms",
|
||||
(unsigned long long)g_group_id, (unsigned long long)g_vad_diag.total_windows, result, inference_tb / 10.0);
|
||||
}
|
||||
if (radio_audio_auto_block(now)) return; /* RX мог начаться, пока Silero работал без общего mutex. */
|
||||
if (result == 0) { g_vad_probability = probability; g_vad_updated_tb = now; }
|
||||
struct radio_auto_fsm before = g_auto_fsm;
|
||||
int busy = 0;
|
||||
int64_t rx_age_ms = g_last_rx_tb ? (int64_t)((now - g_last_rx_tb) / 10) : -1;
|
||||
@@ -995,8 +1008,11 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
||||
inference_tb / 10.0, gap_tb / 10.0);
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_VAD, "VAD window=%llu reason=%s probability=%.4f model_rms=%.6f peak=%.6f "
|
||||
"input_rms=%.6f agc_rms=%.6f confirm=%d talking=%d busy=%d manual=%d tx=%d inference=%.1fms gap=%.1fms",
|
||||
(unsigned long long)g_vad_diag.total_windows, reason, probability, model_rms, peak, g_vad_input_rms, g_vad_output_rms,
|
||||
"dc=%.6f zeros=%u/%d input_rms=%.6f agc_rms=%.6f gain=%.1fdB "
|
||||
"confirm=%d talking=%d busy=%d manual=%d tx=%d inference=%.1fms gap=%.1fms",
|
||||
(unsigned long long)g_vad_diag.total_windows, reason, probability, model_rms, peak,
|
||||
sum / RADIO_VAD_WINDOW_16K, zeros, RADIO_VAD_WINDOW_16K, g_vad_input_rms, g_vad_output_rms,
|
||||
audio_compressor_gain_db(g_vad_compressor),
|
||||
g_auto_fsm.confirm_count, g_auto_fsm.talking, busy, g_manual_ptt, g_transmitting, inference_tb / 10.0, gap_tb / 10.0);
|
||||
g_vad_diag.last_busy = busy;
|
||||
if (probability >= g_vad_threshold) ++g_vad_diag.speech;
|
||||
@@ -1008,6 +1024,14 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
||||
if (model_rms > g_vad_diag.max_model_rms) g_vad_diag.max_model_rms = model_rms;
|
||||
if (!g_vad_diag.stats_tb) g_vad_diag.stats_tb = now;
|
||||
if (now - g_vad_diag.stats_tb >= RADIO_AUDIO_STATS_TB) {
|
||||
if (g_vad_diag.clipped_samples) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_VAD, "VAD compressor clipping group=%016llx span=%.1fms samples=%u frames=%u "
|
||||
"input_peak=%.4f output_peak=%.4f gain=%.1fdB probability=%.3f..%.3f",
|
||||
(unsigned long long)g_group_id, (now - g_vad_diag.stats_tb) / 10.0,
|
||||
g_vad_diag.clipped_samples, g_vad_diag.clipped_frames, g_vad_diag.input_sample_peak,
|
||||
g_vad_diag.output_sample_peak, audio_compressor_gain_db(g_vad_compressor),
|
||||
g_vad_diag.min_probability, g_vad_diag.max_probability);
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "VAD summary group=%016llx span=%.1fms windows=%u audio=%ums speech=%u "
|
||||
"busy=%u confirm_resets=%u errors=%u probability=%.3f..%.3f threshold=%.3f reason=%s "
|
||||
"input_peak=%.1fdBFS model_peak=%.1fdBFS gain=%.1fdB clips=%u inference_max=%.1fms gap_max=%.1fms "
|
||||
@@ -1023,6 +1047,8 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
||||
g_vad_diag.windows = g_vad_diag.speech = g_vad_diag.busy = g_vad_diag.confirm_resets = g_vad_diag.errors = 0;
|
||||
g_vad_diag.min_probability = g_vad_diag.max_probability = g_vad_diag.max_input_rms = g_vad_diag.max_model_rms = 0;
|
||||
g_vad_diag.max_inference_tb = g_vad_diag.max_gap_tb = 0;
|
||||
g_vad_diag.clipped_samples = g_vad_diag.clipped_frames = 0;
|
||||
g_vad_diag.input_sample_peak = g_vad_diag.output_sample_peak = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,11 +1059,13 @@ static void radio_audio_vad_feed(const int16_t* frame) {
|
||||
for (int ch = 0; ch < g_capture_channels; ++ch) {
|
||||
double sample = (double)frame[ch] / 32768.0;
|
||||
g_vad_input_sum_sq += sample * sample;
|
||||
if (fabs(sample) > g_vad_diag.input_sample_peak) g_vad_diag.input_sample_peak = (float)fabs(sample);
|
||||
}
|
||||
if (++g_vad_pcm_frames != RADIO_AUDIO_FRAME_SAMPLES) return;
|
||||
int count = RADIO_AUDIO_FRAME_SAMPLES * g_capture_channels;
|
||||
g_vad_input_rms = sqrtf((float)(g_vad_input_sum_sq / count));
|
||||
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0;
|
||||
uint32_t clips_before = audio_compressor_clip_count(g_vad_compressor);
|
||||
if (audio_compressor_process_frame(g_vad_compressor, g_vad_pcm, g_vad_pcm, count) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "%s: VAD compressor frame failed count=%d — auto-PTT disabled until capture restart",
|
||||
RADIO_AUDIO_ID, count);
|
||||
@@ -1046,10 +1074,14 @@ static void radio_audio_vad_feed(const int16_t* frame) {
|
||||
radio_audio_update_transmission();
|
||||
return;
|
||||
}
|
||||
uint32_t clips = audio_compressor_clip_count(g_vad_compressor) - clips_before;
|
||||
g_vad_diag.clipped_samples += clips;
|
||||
if (clips) ++g_vad_diag.clipped_frames;
|
||||
double sum_sq = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
double sample = (double)g_vad_pcm[i] / 32768.0;
|
||||
sum_sq += sample * sample;
|
||||
if (fabs(sample) > g_vad_diag.output_sample_peak) g_vad_diag.output_sample_peak = (float)fabs(sample);
|
||||
}
|
||||
g_vad_output_rms = sqrtf((float)(sum_sq / count));
|
||||
for (int i = 0; i < count && !g_auto_blocked; i += g_capture_channels) radio_audio_vad_sample(g_vad_pcm + i);
|
||||
@@ -1092,10 +1124,14 @@ static void radio_audio_level_feed(const int16_t* frame) {
|
||||
|
||||
struct radio_audio_input_status radio_audio_get_input_status(void) {
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
struct radio_audio_input_status status = {g_level_dbfs, 0, 0};
|
||||
struct radio_audio_input_status status = {.level_dbfs = g_level_dbfs, .vad_probability = g_vad_probability};
|
||||
uint64_t now = get_time_tb();
|
||||
status.valid = g_active && g_encoder && g_level_updated_tb && now - g_level_updated_tb <= 2000;
|
||||
status.blocked = g_active && radio_audio_rx_busy(now);
|
||||
status.vad_valid = g_active && g_encoder && g_auto_mode == RADIO_AUTO_VAD && !status.blocked &&
|
||||
g_vad_updated_tb && now - g_vad_updated_tb <= 2000;
|
||||
status.vad_gain_db = audio_compressor_gain_db(g_vad_compressor);
|
||||
status.vad_compressor_enabled = audio_compressor_is_enabled(g_vad_compressor);
|
||||
pthread_mutex_unlock(&g_mtx);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ struct radio_audio_input_status {
|
||||
float level_dbfs; /* RMS за 20мс после AEC, до компрессоров; тишина = -120dBFS. */
|
||||
int valid; /* Есть PCM не старше 200мс. */
|
||||
int blocked; /* Чужая речь / дренаж вывода / защитная пауза. */
|
||||
float vad_probability; /* Вероятность речи 0..1 из последнего успешного окна Silero. */
|
||||
int vad_valid; /* VAD работает, эфир свободен, оценка не старше 200мс. */
|
||||
float vad_gain_db; /* Фактическое усиление отдельного компрессора VAD; 0 при выключенном. */
|
||||
int vad_compressor_enabled;
|
||||
};
|
||||
/* Любой поток: короткий снимок под mutex, без вызовов GUI из аудиопотока. */
|
||||
struct radio_audio_input_status radio_audio_get_input_status(void);
|
||||
|
||||
Reference in new issue
Block a user