#include #include #include "ql_rtos.h" #include "hal_api.h" #include "net_diag.h" #include "net_diag_voice.h" static struct { unsigned int round, seen; unsigned long started, stamp[VOICE_STAGE_COUNT]; char first_url[128]; } voice; static const char *stages[] = { "确认用户语音", "录音结束", "结束包已入发送队列(不代表服务端收到)", "收到END_ACK", "收到TTS_START", "收到首段音频URL", "首段开始下载", "首段下载完成", "首PCM提交播放接口(不等于实际出声)" }; void net_diag_voice_begin(unsigned int round) { if (!net_diag_is_enabled()) return; ql_rtos_task_sched_lock(); memset(&voice, 0, sizeof(voice)); voice.round = round; voice.started = (unsigned long)hal_os_up_time_ms(); ql_rtos_task_sched_unlock(); printf("[现场诊断][%lu ms][对话#%u] 开始录音;分段计时起点\r\n", voice.started, round); } void net_diag_voice_mark(unsigned int round, int stage) { unsigned long now, since_start, total = 0, tail = 0, server = 0, wait_url = 0, queue = 0, download = 0, pcm = 0; unsigned int bit, seen; if (!net_diag_is_enabled() || stage < 0 || stage >= VOICE_STAGE_COUNT) return; now = (unsigned long)hal_os_up_time_ms(); bit = 1U << stage; ql_rtos_task_sched_lock(); if (!voice.round || (round && round != voice.round) || (voice.seen & bit)) { ql_rtos_task_sched_unlock(); return; } round = voice.round; voice.seen |= bit; voice.stamp[stage] = now; since_start = now - voice.started; seen = voice.seen; #define HAVE(s) ((seen & (1U << (s))) != 0) if (stage == VOICE_FIRST_PCM && HAVE(VOICE_RECORD_STOP) && HAVE(VOICE_END_QUEUED) && HAVE(VOICE_TTS_START) && HAVE(VOICE_FIRST_URL) && HAVE(VOICE_DOWNLOAD_BEGIN) && HAVE(VOICE_DOWNLOAD_DONE)) { total = now - voice.stamp[VOICE_RECORD_STOP]; tail = voice.stamp[VOICE_END_QUEUED] - voice.stamp[VOICE_RECORD_STOP]; server = voice.stamp[VOICE_TTS_START] - voice.stamp[VOICE_END_QUEUED]; wait_url = voice.stamp[VOICE_FIRST_URL] - voice.stamp[VOICE_TTS_START]; queue = voice.stamp[VOICE_DOWNLOAD_BEGIN] - voice.stamp[VOICE_FIRST_URL]; download = voice.stamp[VOICE_DOWNLOAD_DONE] - voice.stamp[VOICE_DOWNLOAD_BEGIN]; pcm = now - voice.stamp[VOICE_DOWNLOAD_DONE]; } ql_rtos_task_sched_unlock(); printf("[现场诊断][%lu ms][对话#%u] %s;距录音开始=%lu ms\r\n", now, round, stages[stage], since_start); /* Nonstandard server ordering is printed as events, never unsigned deltas. */ if (total && tail <= total && server <= total && wait_url <= total && queue <= total && download <= total && pcm <= total) printf("[现场诊断][%lu ms][对话#%u][耗时汇总] 录音结束到首PCM=%lu ms;收尾入队=%lu 等TTS通知=%lu 等首URL=%lu 播放队列=%lu 下载=%lu 解码提交=%lu(均ms;含网络与服务端处理,尚未分离)\r\n", now, round, total, tail, server, wait_url, queue, download, pcm); #undef HAVE } void net_diag_voice_url(unsigned int round, const char *url) { int accepted = 0; if (!net_diag_is_enabled() || !url) return; ql_rtos_task_sched_lock(); if (round == voice.round && !voice.first_url[0] && strlen(url) < sizeof(voice.first_url)) { strcpy(voice.first_url, url); accepted = 1; } ql_rtos_task_sched_unlock(); if (accepted) net_diag_voice_mark(round, VOICE_FIRST_URL); } unsigned int net_diag_audio_begin(const char *url) { unsigned int round = 0; if (!net_diag_is_enabled() || !url) return 0; ql_rtos_task_sched_lock(); if (voice.first_url[0] && strcmp(url, voice.first_url) == 0) round = voice.round; ql_rtos_task_sched_unlock(); if (round) net_diag_voice_mark(round, VOICE_DOWNLOAD_BEGIN); return round; } void net_diag_audio_done(unsigned int round, int bytes, int result) { if (!round || !net_diag_is_enabled()) return; printf("[现场诊断][%lu ms][对话#%u][首段下载] 字节=%d 返回=%d\r\n", (unsigned long)hal_os_up_time_ms(), round, bytes, result); if (result >= 0 && bytes > 0) net_diag_voice_mark(round, VOICE_DOWNLOAD_DONE); } void net_diag_vad_stats(unsigned int frames, unsigned int speech, unsigned int peak) { unsigned int round; if (!net_diag_is_enabled()) return; ql_rtos_task_sched_lock(); round = voice.round; ql_rtos_task_sched_unlock(); printf("[现场诊断][%lu ms][对话#%u][语音检测] 总帧=%u 活跃录音帧=%u 首次确认前连续语音峰值=%u/20;帧长20ms\r\n", (unsigned long)hal_os_up_time_ms(), round, frames, speech, peak); }