You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
13 KiB
269 lines
13 KiB
// Сохранность 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 <cmath> |
|
#include <cstdio> |
|
#include <cstring> |
|
#include <vector> |
|
|
|
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<int> seen; }; |
|
static int decode(void* arg, const uint8_t* enc, int len, int16_t* pcm, int cap) { |
|
auto* d = static_cast<decoder*>(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<broken_decoder*>(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); |
|
} |
|
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 == 60); |
|
w.observe(30000, 80); CHECK(w.low == 80 && w.high == 80 && w.reserve == 60); |
|
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 == 60); |
|
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 == 60); |
|
} |
|
debug_set_level(DEBUG_LEVEL_WARN); |
|
debug_enable_console(1); |
|
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); push(j, 1); |
|
CHECK(call_jitter_pull(j, pcm, 480) == 0); |
|
push(j, 2); |
|
CHECK(call_jitter_pull(j, pcm, 480) > 0); |
|
call_jitter_range range; |
|
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 == 60 && 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(); |
|
// Один кадр плюс остаток SoundTouch ниже 60мс; два кадра уже могут заполнить запас. |
|
push(j, next++); |
|
int refill = call_jitter_pull(j, pcm, 480); |
|
if (refill) { |
|
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 == 0); |
|
CHECK(d.seen.size() == before); |
|
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 == 60); |
|
CHECK(call_jitter_pull(j, pcm, 480) == 480); // первый pull сразу воспроизводит речь |
|
int next = 60; |
|
for (int tick = 0; tick < 6000; tick++) { |
|
if (tick % 2 == 0) push(j, next++); |
|
CHECK(call_jitter_pull(j, pcm, 480) == 480); |
|
call_jitter_get_stats(j, &depth, &tempo, &dropped, &under, &range); |
|
CHECK(range.reserve_ms == 60); // догон не увеличивает сетевой резерв |
|
} |
|
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<int16_t> 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); |
|
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); |
|
// После начала воспроизведения настоящий перебой по-прежнему включает тон. |
|
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); |
|
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); |
|
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); |
|
std::vector<int16_t> 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; |
|
}
|
|
|