Browse Source

call: suppress loss tone until first received PCM

master
evgeny 2 weeks ago
parent
commit
5487bc8c20
  1. 21
      src/call/call_audio.c
  2. 62
      tools/chatgui/tests/test_call_jitter.cpp

21
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;

62
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<int16_t> 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");

Loading…
Cancel
Save