From 6a0373ee73cd3ca3276ae97d7c6244384bd6b73e Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 21 Sep 2026 16:54:14 +0300 Subject: [PATCH] =?UTF-8?q?call:=20=D0=B4=D0=B8=D0=B0=D0=B3=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BA=D0=B0=20=D1=81=D1=82=D0=B0=D0=B2=D0=BE?= =?UTF-8?q?=D0=BA=20TX/RX=20=D0=B8=20=D0=B4=D0=B5=D1=82=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D0=BB=D0=BB=D0=B0=20=D0=B0=D1=83=D0=B4?= =?UTF-8?q?=D0=B8=D0=BE-=D0=B4=D0=B5=D0=B2=D0=B0=D0=B9=D1=81=D0=B0=20?= =?UTF-8?q?=D1=81=20=D1=85=D0=B0=D1=80=D0=B4-=D1=80=D0=B5=D1=81=D0=B8?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/call/call_audio.c | 29 +++++++++++++++ src/call/voice_jitter.cpp | 48 +++++++++++++++++++++++++ src/call/voice_jitter.h | 3 ++ tools/chatgui/src/call_audio_engine.cpp | 18 ++++++++++ tools/chatgui/src/call_audio_engine.h | 5 +++ 5 files changed, 103 insertions(+) diff --git a/src/call/call_audio.c b/src/call/call_audio.c index 64a26b47..b5ae771c 100644 --- a/src/call/call_audio.c +++ b/src/call/call_audio.c @@ -37,6 +37,10 @@ static opus_codec_decoder_t* g_decoder = NULL; static struct vj* g_vj = NULL; static struct call_tones* g_tones = NULL; +/* диагностика ставок (DEBUG_CATEGORY_DEBUG): под g_mtx */ +static uint32_t g_tx_feed = 0, g_tx_encode_fail = 0, g_rx_push = 0, g_rx_pull = 0; +static uint64_t g_rate_last_tb = 0; + static const struct call_audio_ops g_ops = { .on_media = call_audio_on_media, .get_stats = call_audio_get_stats, @@ -74,6 +78,7 @@ void call_audio_on_media(struct UTUN_INSTANCE* inst, uint64_t call_id, if (g_active && call_id == g_call_id && g_vj && opus && len > 0) { vj_push(g_vj, opus, len); g_last_media_tb = get_time_tb(); + g_rx_push++; } pthread_mutex_unlock(&g_mtx); } @@ -199,6 +204,20 @@ void call_audio_stop(void) { /* ── аудио-поток: TX / RX ── */ +/* Раз в ~1с логируем ставки TX/RX — диагностика «звонок не проходит» (DEBUG_CATEGORY_DEBUG). + * Вызывается из аудио-потока (pull_pcm); счётчики под g_mtx. */ +static void call_audio_rate_tick(uint64_t now_tb) { + uint32_t feed = 0, encfail = 0, push = 0, pull = 0, resync = 0; + pthread_mutex_lock(&g_mtx); + feed = g_tx_feed; encfail = g_tx_encode_fail; push = g_rx_push; pull = g_rx_pull; + g_tx_feed = g_tx_encode_fail = g_rx_push = g_rx_pull = 0; + if (g_vj) resync = vj_resyncs(g_vj); + g_rate_last_tb = now_tb; + pthread_mutex_unlock(&g_mtx); + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "%s: rates feed=%u/s encfail=%u push=%u/s pull=%u/s resync=%u", + CALL_AUDIO_ID, feed, encfail, push, pull, resync); +} + int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count) { if (!pcm || count != CALL_AUDIO_FRAME_SAMPLES) return -1; @@ -213,6 +232,8 @@ int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count) { return -1; } len = opus_codec_encode(g_encoder, pcm, count, opus, (int)sizeof(opus)); + g_tx_feed++; + if (len <= 0) g_tx_encode_fail++; ua = g_inst ? g_inst->ua : NULL; inst = g_inst; pthread_mutex_unlock(&g_mtx); @@ -248,11 +269,19 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) { j = g_vj; tones = g_tones; last_media = g_last_media_tb; + g_rx_pull++; } pthread_mutex_unlock(&g_mtx); if (!active || !j || !tones) return 0; + /* диагностика ставок: раз в ~1с (аудио-поток) */ + { + uint64_t now_tb = get_time_tb(); + if (g_rate_last_tb == 0) g_rate_last_tb = now_tb; + else if (now_tb - g_rate_last_tb >= 10000) call_audio_rate_tick(now_tb); + } + /* завершение: доигрываем тон (450мс), затем тишина. Тон стартует лениво * здесь (аудио-поток) — begin_end лишь выставляет флаг, чтобы не гонять * генератор тонов с GUI-потоком. */ diff --git a/src/call/voice_jitter.cpp b/src/call/voice_jitter.cpp index 45ddd362..2385a639 100644 --- a/src/call/voice_jitter.cpp +++ b/src/call/voice_jitter.cpp @@ -27,6 +27,8 @@ static const double kMaxDepthMs = 1000.0; /* при этой глубин static const double kEmaAlpha = 0.2; /* сглаживание глубины */ static const double kTempoStep = 0.01; /* макс. изменение темпа за один pull (без кликов) */ static const double kPrimeTimeoutMs = 200.0; /* pre-roll: принудительный prime через это время */ +static const double kStallGapMs = 80.0; /* gap между pull > это → сталь аудио-девайса */ +static const double kResyncDepthMs = 400.0; /* бэклог > это после сталла → хард-ресинк к live */ struct vj { int sr = 0; @@ -45,6 +47,12 @@ struct vj { int count = 0; uint32_t dropped = 0; uint32_t underruns = 0; + uint32_t resyncs = 0; + + /* детект сталла аудио-девайса (gap между pull) */ + std::chrono::steady_clock::time_point last_pull_tp; /* только аудио-поток */ + int first_pull = 1; /* только аудио-поток */ + int stalled = 0; /* только аудио-поток */ soundtouch::SoundTouch st; /* только аудио-поток */ uint8_t enc_buf[VJ_MAX_ENC]; /* scratch: вытащенный кодированный кадр */ @@ -168,9 +176,43 @@ void vj_push(struct vj* j, const uint8_t* enc, int len) { j->count++; } +/* Хард-ресинк к live: сбросить бэклог (кольцо + SoundTouch) и темп к 1.0. + * Применяется после сталла аудио-девайса, когда бэклог > kResyncDepthMs — один + * чистый срез вместо долгого догона темпом (chipmunk) + дропов по одному. */ +static void vj_hard_resync(struct vj* j) { + { std::lock_guard lk(j->mtx); j->count = 0; j->head = 0; } + j->st.clear(); + j->tempo = 1.0; + j->tempo_x100.store(100); + j->ema_depth_ms = j->target_ms; + j->resyncs++; +} + int vj_pull(struct vj* j, int16_t* out, int max_samples) { if (!j || !out || max_samples <= 0) return 0; + /* ── детект сталла аудио-девайса (gap между pull) + ресинк по его окончании ── */ + { + auto now = std::chrono::steady_clock::now(); + if (j->first_pull) { + j->first_pull = 0; + j->last_pull_tp = now; + } else { + double gap_ms = std::chrono::duration(now - j->last_pull_tp).count(); + j->last_pull_tp = now; + if (gap_ms > kStallGapMs) { + j->stalled = 1; /* сталь начался — просто флаг */ + } else if (j->stalled) { + j->stalled = 0; /* сталь кончился */ + int c; + { std::lock_guard lk(j->mtx); c = j->count; } + double depth_ms = (double)c * j->frame_ms + + (double)j->st.numSamples() * 1000.0 / (double)j->sr; + if (depth_ms > kResyncDepthMs) vj_hard_resync(j); + } + } + } + /* 0) pre-roll: не отдаём звук, пока глубина кольца < target_ms (или не истёк таймаут). * Состояние — только аудио-поток; кадры копятся в кольце продюсером (uasync). */ int cnt; @@ -256,4 +298,10 @@ uint32_t vj_dropped(struct vj* j) { return j->dropped; } +uint32_t vj_resyncs(struct vj* j) { + if (!j) return 0; + std::lock_guard lk(j->mtx); + return j->resyncs; +} + } // extern "C" diff --git a/src/call/voice_jitter.h b/src/call/voice_jitter.h index 74057788..20e7c3ea 100644 --- a/src/call/voice_jitter.h +++ b/src/call/voice_jitter.h @@ -69,6 +69,9 @@ void vj_get_stats(struct vj* j, int* depth_ms, int* tempo_x100, uint32_t* droppe /* Число дропнутых кадров при переполнении (для диагностики/тестов). */ uint32_t vj_dropped(struct vj* j); +/* Число хард-ресинков к live (после сталла аудио-девайса) — диагностика. */ +uint32_t vj_resyncs(struct vj* j); + #ifdef __cplusplus } #endif diff --git a/tools/chatgui/src/call_audio_engine.cpp b/tools/chatgui/src/call_audio_engine.cpp index ba00ebc7..f4441520 100644 --- a/tools/chatgui/src/call_audio_engine.cpp +++ b/tools/chatgui/src/call_audio_engine.cpp @@ -7,6 +7,7 @@ extern "C" { #include "call/call_audio.h" #include "../../lib/debug_config.h" +#include "../../lib/u_async.h" } #include @@ -164,6 +165,23 @@ void CallAudioEngine::dataCallback(const void* in, void* out, unsigned int frame const int16_t* in16 = (const int16_t*)in; int16_t* out16 = (int16_t*)out; + /* диагностика (DEBUG_CATEGORY_DEBUG): фактические параметры miniaudio-коллбэка */ + if (m_dbgFrames == 0) { + m_dbgFrames = frames; + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: callback frames=%u in=%s out=%s", + frames, in16 ? "yes" : "no", out16 ? "yes" : "no"); + } + m_dbgCbCount++; + { + uint64_t now = get_time_tb(); + if (m_dbgLastTb == 0) m_dbgLastTb = now; + else if (now - m_dbgLastTb >= 10000) { + DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "CallAudio: cb=%u/s frames=%u", m_dbgCbCount, frames); + m_dbgCbCount = 0; + m_dbgLastTb = now; + } + } + /* захват: копим 960 samples → encode+send (сервисный слой). mute → тишина. */ if (in16) { bool muted = m_muted.load(); diff --git a/tools/chatgui/src/call_audio_engine.h b/tools/chatgui/src/call_audio_engine.h index 58c60ff7..d5fb2c17 100644 --- a/tools/chatgui/src/call_audio_engine.h +++ b/tools/chatgui/src/call_audio_engine.h @@ -59,4 +59,9 @@ private: std::vector m_captureBuf; /* 960 samples, аудио-поток */ int m_capturePos = 0; + + /* диагностика (DEBUG_CATEGORY_DEBUG): фактические параметры miniaudio-коллбэка */ + unsigned int m_dbgFrames = 0; + uint32_t m_dbgCbCount = 0; + uint64_t m_dbgLastTb = 0; };