From 5487bc8c2065d7324f1638d42c53ca0a203c82aa Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 1 Oct 2026 02:08:17 +0300 Subject: [PATCH] call: suppress loss tone until first received PCM --- src/call/call_audio.c | 21 ++++++-- tools/chatgui/tests/test_call_jitter.cpp | 62 +++++++++++++++++++----- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/call/call_audio.c b/src/call/call_audio.c index 076f4c5c..01c19e43 100644 --- a/src/call/call_audio.c +++ b/src/call/call_audio.c @@ -34,6 +34,8 @@ static struct UTUN_INSTANCE* g_inst = NULL; static int g_active = 0; static int g_ending = 0; static int g_glitch_started = 0; /* только аудиопоток */ +static int g_playback_started = 0; /* первый PCM получен; только аудиопоток */ +static uint64_t g_start_tb = 0; /* для диагностики ожидания первого PCM */ static int g_ended_started = 0; /* только аудио-поток: тон завершения запущен */ static uint64_t g_call_id = 0; static int g_gap_samples = 0; /* только аудиопоток; насыщение на пороге */ @@ -184,6 +186,8 @@ int call_audio_start(uint64_t call_id) { g_ending = 0; g_ended_started = 0; g_glitch_started = 0; + g_playback_started = 0; + g_start_tb = get_time_tb(); g_gap_samples = 0; pthread_mutex_unlock(&g_mtx); @@ -218,6 +222,8 @@ void call_audio_release(uint64_t call_id) { g_active = 0; g_ending = 0; g_ended_started = 0; + g_playback_started = 0; + g_start_tb = 0; g_call_id = 0; g_gap_samples = 0; enc = g_encoder; g_encoder = NULL; @@ -373,18 +379,25 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) { } else { n = call_jitter_pull(j, out, max_samples); int previous_gap = g_gap_samples; - if (n > 0) g_gap_samples = 0; + if (n > 0) { + if (!g_playback_started) + DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: first PCM call=%016llx wait=%llums pcm=%d/%d", CALL_AUDIO_ID, + (unsigned long long)call_id, (unsigned long long)((get_time_tb() - g_start_tb) / 10), n, max_samples); + g_playback_started = 1; + g_gap_samples = 0; + } int missing = max_samples - n; - int quiet = CALL_STALL_SAMPLES - g_gap_samples; + /* Подготовка устройств и начальное накопление PCM не являются потерей звука. */ + int quiet = g_playback_started ? CALL_STALL_SAMPLES - g_gap_samples : missing; if (quiet > missing) quiet = missing; int tone_samples = missing - quiet; if (n > 0 && previous_gap > 0) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: PCM resumed call=%016llx gap=%dms pcm=%d/%d", CALL_AUDIO_ID, (unsigned long long)call_id, previous_gap * 1000 / CALL_AUDIO_SAMPLE_RATE, n, max_samples); - if (missing > 0 && g_gap_samples == 0) + if (missing > 0 && g_playback_started && g_gap_samples == 0) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "%s: PCM gap started call=%016llx pcm=%d/%d", CALL_AUDIO_ID, (unsigned long long)call_id, n, max_samples); - g_gap_samples += quiet; + if (g_playback_started) g_gap_samples += quiet; /* Новый PCM прерывает предыдущий цикл тона, даже если хвост блока снова пуст. */ if (g_glitch_started && (n > 0 || tone_samples == 0)) { g_glitch_started = 0; diff --git a/tools/chatgui/tests/test_call_jitter.cpp b/tools/chatgui/tests/test_call_jitter.cpp index 716de92a..bd9f3afe 100644 --- a/tools/chatgui/tests/test_call_jitter.cpp +++ b/tools/chatgui/tests/test_call_jitter.cpp @@ -124,22 +124,36 @@ int main() { uint8_t encoded[512]; int len = opus_codec_encode(encoder, voice, 960, encoded, sizeof(encoded)); CHECK(len > 0); - // Без входных пакетов первые 500мс выходного аудио — строго тишина. - for (int tick = 0; tick < 50; tick++) { + // Подготовка микрофона дольше 500мс не вызывает ложный сигнал потери звука. + for (int tick = 0; tick < 150; tick++) { CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); for (auto value : pcm) CHECK(value == 0); } + // Первый пакет ещё не заполнил запас PCM: продолжается тихое ожидание. + call_audio_on_media(nullptr, id, 0, 0, encoded, len); + CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); + for (auto value : pcm) CHECK(value == 0); + for (int i = 1; i < 6; i++) call_audio_on_media(nullptr, id, i, 0, encoded, len); CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); bool audible = false; for (auto value : pcm) if (value) audible = true; CHECK(audible); - // Один пакет не возобновляет PCM: писк продолжается при ожидании запаса. - call_audio_on_media(nullptr, id, 0, 0, encoded, len); - CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); + // После начала воспроизведения настоящий перебой по-прежнему включает тон. audible = false; - for (auto value : pcm) if (value) audible = true; + for (int tick = 0; tick < 120; tick++) { + CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); + if (tick >= 60) for (auto value : pcm) if (std::abs((int)value) > 1000) audible = true; + } CHECK(audible); - for (int i = 1; i < 6; i++) call_audio_on_media(nullptr, id, i, 0, encoded, len); + // Малого запаса недостаточно для восстановления: сигнал продолжает звучать. + call_audio_on_media(nullptr, id, 6, 0, encoded, len); + audible = false; + for (int tick = 0; tick < 100; tick++) { + CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); + for (auto value : pcm) if (std::abs((int)value) > 1000) audible = true; + } + CHECK(audible); + for (int i = 7; i < 30; i++) call_audio_on_media(nullptr, id, i, 0, encoded, len); CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); call_audio_begin_end(id); // явное завершение прекращает догон и включает финальный тон CHECK(call_audio_pull_pcm(id, pcm, 480) > 0); @@ -147,23 +161,45 @@ int main() { opus_codec_encoder_destroy(encoder); } { - // Порог проходит внутри блока: сначала ровно 500мс тишины, затем тон. + // После PCM порог 500мс работает и для блока, который не делит его нацело. CHECK(call_audio_start(124) == 0); + auto* encoder = opus_codec_encoder_create(48000, 1); + CHECK(encoder); + int16_t silence[960] = {}; + uint8_t encoded[512]; + int len = opus_codec_encode(encoder, silence, 960, encoded, sizeof(encoded)); + CHECK(len > 0); + for (int i = 0; i < 6; i++) call_audio_on_media(nullptr, 124, i, 0, encoded, len); std::vector output(777); - for (int tick = 0; tick < 30; tick++) { + under = 0; + for (int tick = 0; tick < 30 && !under; tick++) { + CHECK(call_audio_pull_pcm(124, output.data(), 777) == 777); + CHECK(call_audio_get_stats(124, nullptr, nullptr, nullptr, &under, nullptr) == 0); + } + CHECK(under > 0); + // Первый недобор занимает не более 777 сэмплов; ещё 29 блоков не достигают 500мс. + for (int tick = 0; tick < 29; tick++) { CHECK(call_audio_pull_pcm(124, output.data(), 777) == 777); - for (auto value : output) CHECK(value == 0); + for (auto value : output) CHECK(std::abs((int)value) <= 5); } - CHECK(call_audio_pull_pcm(124, output.data(), 777) == 777); - for (int i = 0; i < 690; i++) CHECK(output[i] == 0); bool audible = false; - for (int i = 690; i < 777; i++) if (output[i]) audible = true; + for (int tick = 0; tick < 2; tick++) { + CHECK(call_audio_pull_pcm(124, output.data(), 777) == 777); + for (auto value : output) if (std::abs((int)value) > 1000) audible = true; + } CHECK(audible); call_audio_release(124); + opus_codec_encoder_destroy(encoder); // Новый звонок не наследует включённый тон и отсчёт предыдущего. CHECK(call_audio_start(125) == 0); CHECK(call_audio_pull_pcm(125, output.data(), 777) == 777); for (auto value : output) CHECK(value == 0); + // Завершение звучит даже у звонка, который ещё не получил первого PCM. + call_audio_begin_end(125); + CHECK(call_audio_pull_pcm(125, output.data(), 777) == 777); + audible = false; + for (auto value : output) if (value) audible = true; + CHECK(audible); call_audio_release(125); } std::printf("call_jitter: %s\n", failures ? "FAILED" : "PASSED");