// Сохранность FIFO, десятисекундный burst, догон при продолжающемся входе и повторное накопление. #include "call/call_jitter.h" #include "call/call_jitter_window.h" #include "call/call_audio.h" #include "../../../lib/opus_codec.h" #include "../../../lib/debug_config.h" #include #include #include #include static int failures; #define CHECK(c) do { if (!(c)) { std::printf("FAIL line %d: %s\n", __LINE__, #c); failures++; } } while (0) struct decoder { std::vector seen; }; static int decode(void* arg, const uint8_t* enc, int len, int16_t* pcm, int cap) { auto* d = static_cast(arg); int id = -1; if (len == sizeof(id)) std::memcpy(&id, enc, sizeof(id)); d->seen.push_back(id); for (int i = 0; i < cap; i++) pcm[i] = (int16_t)(8000 * std::sin(2 * 3.141592653589793 * 440 * i / 48000)); return cap; } struct broken_decoder { int attempts = 0; }; static int decode_bad(void* arg, const uint8_t*, int, int16_t*, int) { ++static_cast(arg)->attempts; return -1; } static void push(call_jitter* j, int id, int64_t received_ms = -1) { call_jitter_push(j, (const uint8_t*)&id, sizeof(id), (uint32_t)(id * 20), received_ms < 0 ? id * 20 : received_ms, nullptr); } static void test_network_recovery() { for (int block : {240, 480, 777, 960}) { decoder d; auto* j = call_jitter_create(decode, &d, 1000); std::vector output(block); int next = 0, largest_reserve = 0; uint32_t stable_underruns = 0; call_jitter_diagnostics state{}; for (int64_t time = 0; time < 30 * 48000; time += block) { // Один перебой 80мс, затем приходит накопленная пачка и продолжается ровная доставка. bool stalled = time >= 48000 && time < 48000 + 3840; while (!stalled && next * 960ll <= time) push(j, next++, time / 48); int n = call_jitter_pull(j, output.data(), block); if (n < block) call_jitter_silence(j, block - n); call_jitter_get_diagnostics(j, &state, 0); largest_reserve = std::max(largest_reserve, state.reserve_ms); if (time < 3 * 48000) stable_underruns = state.underruns; else CHECK(state.underruns == stable_underruns); } CHECK(largest_reserve >= 60 && state.reserve_ms == 20); CHECK(state.delay_mode == 0 && state.phase_samples == 0 && state.tempo_ppm == 1000000); CHECK(state.underruns > 0 && state.underruns <= 2); DEBUG_INFO(DEBUG_CATEGORY_CALL, "recovery block=%d max_reserve=%dms final=%dms phase=%u underruns=%u", block, largest_reserve, state.reserve_ms, state.phase_samples, state.underruns); call_jitter_destroy(j); } } static void test_audio_clock() { const uint64_t id = 987; CHECK(call_audio_start(id) == 0); auto* encoder = opus_codec_encoder_create(48000, 1); CHECK(encoder); int delay = 0; CHECK(opus_codec_encoder_delay(encoder, &delay) == 0); int16_t voice[960], output[480]; uint8_t packet[512]; int sent = 0, valid = 0; uint64_t written = 0; int64_t previous = -1; call_clock_snapshot clock{}; CHECK(call_audio_get_clock(id, 0, &clock) == 0 && !clock.valid); uint64_t epoch = clock.epoch; // Начальный burst заставляет настоящий jitter менять темп; PTS не зависит от send_ts. for (int tick = 0; tick < 4000; tick++) { int count = tick == 0 ? 30 : (tick % 2 == 0 ? 1 : 0); for (int i = 0; i < count; i++, sent++) { for (int s = 0; s < 960; s++) voice[s] = (int16_t)(8000 * std::sin((sent * 960ll + s) * 0.09)); int len = opus_codec_encode(encoder, voice, 960, packet, sizeof(packet)); CHECK(len > 0); call_capture_time timing{1000000 + sent * 20000ll, 9, (uint32_t)delay}; call_audio_on_media(nullptr, id, (uint16_t)sent, 0, packet, len, &timing); } CHECK(call_audio_pull_pcm(id, output, 480) == 480); written += 480; int64_t now = tick * 10000ll; call_audio_playback_progress(id, epoch, written, now, now, 1); CHECK(call_audio_get_clock(id, now, &clock) == 0 && clock.written_samples == written); if (clock.valid) { valid++; CHECK(clock.source_epoch == 9 && clock.pts_us >= previous); CHECK(clock.pts_us <= 1000000 + sent * 20000ll); CHECK(clock.pts_us > 1000000 + sent * 20000ll - 700000); previous = clock.pts_us; } } CHECK(valid > 3900); // Underrun и местные тоны продолжают device-position, но удерживают source-PTS. int64_t held = -1; for (int tick = 4000; tick < 4300; tick++) { CHECK(call_audio_pull_pcm(id, output, 480) == 480); written += 480; call_audio_playback_progress(id, epoch, written, tick * 10000ll, tick * 10000ll, 1); CHECK(call_audio_get_clock(id, tick * 10000ll, &clock) == 0 && clock.valid); if (tick == 4200) held = clock.pts_us; if (tick > 4200) CHECK(clock.pts_us == held); } call_audio_reset_io(id, 1); CHECK(call_audio_get_clock(id, 43000000, &clock) == 0 && clock.epoch != epoch && !clock.valid); call_audio_playback_progress(id, epoch, written, 43000000, 43000000, 1); CHECK(call_audio_get_clock(id, 43000000, &clock) == 0 && !clock.valid); call_audio_stop(); opus_codec_encoder_destroy(encoder); std::printf("audio clock: real Opus, burst/tempo, device progress, local tones and reset checked\n"); } int main() { { call_jitter_window w; w.observe(0, 100); w.observe(500, 400); CHECK(w.low == 100 && w.high == 400 && w.reserve == 300); w.observe(1000, 2000); CHECK(w.reserve == 1000); w.observe(10000, 2000); CHECK(w.low == 2000 && w.reserve == 20); w.observe(30000, 80); CHECK(w.low == 80 && w.high == 80 && w.reserve == 20); for (int cap : {500, 2000}) { call_jitter_window limited; limited.max_reserve = cap; limited.observe(0, 0); limited.observe(500, 5000); CHECK(limited.reserve == cap); } } { call_jitter_window arrival; // Разные часы и переполнение uint32 не создают джиттер при ровной доставке. uint32_t sent = UINT32_MAX - 39; for (int i = 0; i < 100; i++) arrival.observe_arrival(900000 + i * 20, sent + (uint32_t)(i * 20)); CHECK(arrival.high == 0 && arrival.low == 0 && arrival.reserve == 20); arrival.observe_arrival(902200, sent + 2000); // задержка доставки +200мс CHECK(arrival.reserve == 200); arrival.observe_arrival(902220, sent + 2220); // очередь пришла пачкой CHECK(arrival.reserve == 200); arrival.observe_arrival(920000, sent + 20000); // старый burst ушёл из окна CHECK(arrival.reserve == 20); // Небольшой джиттер больше минимума тоже учитывается, без прежнего порога 60мс. arrival.observe_arrival(920055, sent + 20020); CHECK(arrival.reserve == 35); arrival.observe_arrival(931055, sent + 31020); CHECK(arrival.reserve == 20); } debug_set_level(DEBUG_LEVEL_WARN); debug_enable_console(1); test_audio_clock(); test_network_recovery(); int16_t pcm[480]; int depth, tempo; uint32_t dropped, under; { decoder d; auto* j = call_jitter_create(decode, &d, 1000); CHECK(j); push(j, 0); // Сохраняем запас 20мс после первого блока, а не расходуем его при старте. CHECK(call_jitter_pull(j, pcm, 480) == 0); push(j, 1); CHECK(call_jitter_pull(j, pcm, 480) > 0); call_jitter_diagnostics diagnostic{}; call_jitter_get_diagnostics(j, &diagnostic, 0); CHECK(diagnostic.resumed == 1 && diagnostic.pcm_samples == 480 && diagnostic.reserve_ms == 20); CHECK(diagnostic.encoded_ms >= 0 && diagnostic.processing_ms >= 0 && diagnostic.arrival_spread_ms == 0); call_jitter_get_diagnostics(j, &diagnostic, 0); CHECK(diagnostic.resumed == 1); // Снимок playback не потребляет события для uasync. call_jitter_get_diagnostics(j, &diagnostic, 1); CHECK(diagnostic.resumed == 1); call_jitter_get_diagnostics(j, &diagnostic, 0); CHECK(diagnostic.resumed == 0); call_jitter_range range; push(j, 2); for (int i = 3; i < 503; i++) push(j, i, 10060); // 10с сетевой задержки, затем пачка call_jitter_get_stats(j, nullptr, nullptr, nullptr, nullptr, &range); CHECK(range.reserve_ms == 1000); int next = 503, highest = 100; for (int tick = 0; tick < 6000; tick++) { if (tick % 2 == 0) push(j, next++, 10060 + tick * 10); // непрерывный live call_jitter_pull(j, pcm, 480); call_jitter_get_stats(j, &depth, &tempo, &dropped, &under, nullptr); if (tempo > highest) highest = tempo; } call_jitter_get_stats(j, nullptr, nullptr, nullptr, nullptr, &range); CHECK(range.reserve_ms == 20 && range.max_ms >= 10000); CHECK(range.min_ms <= range.max_ms); CHECK(highest >= 150); CHECK(depth < 200); CHECK(tempo <= 110); CHECK(dropped == 0); for (size_t i = 0; i < d.seen.size(); i++) CHECK(d.seen[i] == (int)i); for (int i = 0; i < 100; i++) call_jitter_pull(j, pcm, 480); size_t before = d.seen.size(); // После опустошения ждём полный блок сверх сетевого запаса; скрытого накопления DSP больше нет. push(j, next++); CHECK(call_jitter_pull(j, pcm, 480) == 0); CHECK(d.seen.size() == before); push(j, next++); int refill = call_jitter_pull(j, pcm, 480); if (refill != 480) { call_jitter_get_stats(j, &depth, nullptr, nullptr, nullptr, &range); DEBUG_ERROR(DEBUG_CATEGORY_CALL, "refill check: pcm=%d remaining=%dms reserve=%dms decoded=%zu", refill, depth, range.reserve_ms, d.seen.size() - before); } CHECK(refill == 480); CHECK(d.seen.size() == before + 1); for (int i = 0; i < 50; i++) push(j, next++); CHECK(call_jitter_pull(j, pcm, 480) > 0); call_jitter_destroy(j); } { decoder d; auto* j = call_jitter_create(decode, &d, 1000); // Устройство готовилось 1.2с, но сеть всё время доставляла кадры равномерно. for (int i = 0; i < 60; i++) push(j, i); call_jitter_range range; call_jitter_get_stats(j, &depth, nullptr, nullptr, nullptr, &range); CHECK(depth == 1200 && range.reserve_ms == 20); CHECK(call_jitter_pull(j, pcm, 480) == 480); // первый pull сразу воспроизводит речь int next = 60, short_pulls = 0; for (int tick = 0; tick < 6000; tick++) { if (tick % 2 == 0) push(j, next++); int produced = call_jitter_pull(j, pcm, 480); if (produced != 480 && ++short_pulls <= 3) { call_jitter_diagnostics state{}; call_jitter_get_diagnostics(j, &state, 0); DEBUG_ERROR(DEBUG_CATEGORY_CALL, "steady delivery short pull tick=%d pcm=%d depth_before=%d encoded=%d processing=%d reserve=%d tempo=%d spread=%lld", tick, produced, state.depth_ms, state.encoded_ms, state.processing_ms, state.reserve_ms, state.tempo_x100, (long long)state.arrival_spread_ms); } call_jitter_get_stats(j, &depth, &tempo, &dropped, &under, &range); CHECK(range.reserve_ms == 20); // догон не увеличивает сетевой резерв } CHECK(short_pulls == 0); CHECK(depth < 150 && tempo <= 110 && dropped == 0 && under == 0); for (size_t i = 0; i < d.seen.size(); i++) CHECK(d.seen[i] == (int)i); call_jitter_destroy(j); } { decoder d; auto* j = call_jitter_create(decode, &d, 1000); for (int i = 0; i < CALL_JITTER_MAX_FRAMES; i++) push(j, i); call_jitter_get_stats(j, &depth, nullptr, &dropped, nullptr, nullptr); CHECK(depth == 15000 && dropped == 0); push(j, CALL_JITTER_MAX_FRAMES); call_jitter_get_stats(j, &depth, nullptr, &dropped, nullptr, nullptr); CHECK(depth == 15000 && dropped == 1); CHECK(call_jitter_pull(j, pcm, 480) > 0); CHECK(d.seen.front() == 1); call_jitter_destroy(j); } for (int samples : {240, 480, 960}) { decoder d; auto* j = call_jitter_create(decode, &d, 1000); for (int i = 0; i < 500; i++) push(j, i); std::vector output(samples); int elapsed = 0, previous = 100; for (; elapsed < 500; ) { CHECK(call_jitter_pull(j, output.data(), samples) == samples); elapsed += samples * 1000 / 48000; call_jitter_get_stats(j, nullptr, &tempo, nullptr, nullptr, nullptr); int expected = (int)std::lround(100 + 60.0 * elapsed / 500); CHECK(std::abs(tempo - expected) <= 1); previous = tempo; } CHECK(tempo == 160); bool slowing = false; for (int tick = 0; tick < 3000 * 960 / samples; tick++) { call_jitter_pull(j, output.data(), samples); call_jitter_get_stats(j, &depth, &tempo, &dropped, nullptr, nullptr); int max_step = (int)std::ceil(120.0 * samples / 48000); CHECK(std::abs(tempo - previous) <= max_step); if (tempo < previous) slowing = true; previous = tempo; } CHECK(slowing && tempo <= 110 && dropped == 0); call_jitter_destroy(j); } { const uint64_t id = 123; CHECK(call_audio_start(id) == 0); auto* encoder = opus_codec_encoder_create(48000, 1); CHECK(encoder); int16_t voice[960]; for (int i = 0; i < 960; i++) voice[i] = (int16_t)(8000 * std::sin(2 * 3.141592653589793 * 440 * i / 48000)); uint8_t encoded[512]; int len = opus_codec_encode(encoder, voice, 960, encoded, sizeof(encoded)); CHECK(len > 0); // Подготовка микрофона дольше 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, nullptr); 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, nullptr); CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); bool audible = false; for (auto value : pcm) if (value) audible = true; CHECK(audible); // После начала воспроизведения настоящий перебой по-прежнему включает тон. audible = false; 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); // Малого запаса недостаточно для восстановления: сигнал продолжает звучать. call_audio_on_media(nullptr, id, 6, 0, encoded, len, nullptr); 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, nullptr); CHECK(call_audio_pull_pcm(id, pcm, 480) == 480); call_audio_begin_end(id); // явное завершение прекращает догон и включает финальный тон CHECK(call_audio_pull_pcm(id, pcm, 480) > 0); call_audio_release(id); opus_codec_encoder_destroy(encoder); } { // После 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, nullptr); std::vector output(777); 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(std::abs((int)value) <= 5); } bool audible = false; 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); } { broken_decoder decoder; auto* jitter = call_jitter_create(decode_bad, &decoder, 1000); for (int i = 0; i < CALL_JITTER_MAX_FRAMES; ++i) push(jitter, i); CHECK(call_jitter_pull(jitter, pcm, 480) == 0); CHECK(decoder.attempts > 0 && decoder.attempts <= 9); call_jitter_reset(jitter); int depth = 0; call_jitter_get_stats(jitter, &depth, nullptr, nullptr, nullptr, nullptr); CHECK(depth <= 1000); call_jitter_destroy(jitter); } std::printf("call_jitter: %s\n", failures ? "FAILED" : "PASSED"); return failures ? 1 : 0; }