diff --git a/src/call/call_video_rate.c b/src/call/call_video_rate.c index 7a92054b..df1bd86a 100644 --- a/src/call/call_video_rate.c +++ b/src/call/call_video_rate.c @@ -84,7 +84,7 @@ int call_video_rate_step(struct call_video_rate *rate, const struct call_video_r uint32_t floor = s->applied_bps / 2; if (floor < s->minimum_bps) floor = s->minimum_bps; if (candidate < floor) candidate = floor; - if (candidate < s->applied_bps * 95ull / 100) { + if (candidate < s->applied_bps * 95ull / 100 || (candidate == s->minimum_bps && candidate < s->applied_bps)) { result.target_bps = (uint32_t)candidate; result.reason = CALL_VIDEO_RATE_DRAIN; } } else if (!result.excess_bytes && result.admit && s->produced_bytes && s->delivered_bytes diff --git a/tests/test_call_video_rate.c b/tests/test_call_video_rate.c index 96c0f295..772d5c8c 100644 --- a/tests/test_call_video_rate.c +++ b/tests/test_call_video_rate.c @@ -40,6 +40,58 @@ static void test_gates(void) { DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS rate: cap, pending, no route, queue bound, warmup, probe and local hold"); } +static void test_range(void) { + uint32_t minimum = 0, maximum = 0; + CHECK(!call_video_rate_parse_range(CALL_VIDEO_RATE_DEFAULT_RANGE, &minimum, &maximum)); + CHECK(minimum == 300000 && maximum == 4000000); + CHECK(!call_video_rate_parse_range("500:1500", &minimum, &maximum)); + CHECK(minimum == 500000 && maximum == 1500000); + const char *invalid[] = {"", "299:4000", "300:4001", "4000:300", "300", "300:4000x", + "-300:4000", "300:+4000", "300: 4000", "999999999999999999999999:4000"}; + for (unsigned i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) { + CHECK(call_video_rate_parse_range(invalid[i], &minimum, &maximum) < 0); + CHECK(minimum == 500000 && maximum == 1500000); + } + CHECK(!call_video_rate_parse_range("300:300", &minimum, &maximum)); + CHECK(minimum == maximum && minimum == 300000); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS rate: atomic range, default, fixed bitrate and malformed settings"); +} + +/* Сначала связь слабее нижней границы; после восстановления должны достичь потолка. */ +static void test_poor_then_good(void) { + struct call_video_rate rate; + struct call_video_rate_sample s = sample(0); + struct call_video_rate_result out = {.admit = 1}; + CHECK(!call_video_rate_parse_range(CALL_VIDEO_RATE_DEFAULT_RANGE, &s.minimum_bps, &s.maximum_bps)); + s.applied_bps = s.maximum_bps; + uint64_t produced[10] = {0}, delivered[10] = {0}, flight[2] = {0}; + uint64_t queue = 0, debt = 0, peak = 0; + int reached_floor = 0, paused = 0; + call_video_rate_init(&rate, 0); + for (unsigned i = 0; i < 2400; i++) { + uint64_t generated = out.admit ? s.applied_bps / 160 : 0; + queue += generated; debt += generated; + uint64_t acked = flight[i % 2]; debt -= acked; + uint64_t capacity = (i < 800 ? 1500000 / 10 : 6000000) / 160; + uint64_t sent = queue < capacity ? queue : capacity; + queue -= sent; flight[i % 2] = sent; + produced[i % 10] = generated; delivered[i % 10] = acked; + s.produced_bytes = s.delivered_bytes = 0; + for (unsigned j = 0; j < 10; j++) { s.produced_bytes += produced[j]; s.delivered_bytes += delivered[j]; } + s.now_us = s.window_end_us = (uint64_t)(i + 1) * 50000; + s.valid = i >= 9; s.debt_bytes = debt; + CHECK(!call_video_rate_step(&rate, &s, &out)); + s.applied_bps = out.target_bps; + CHECK(s.applied_bps >= 300000 && s.applied_bps <= 4000000); + if (i < 800 && s.applied_bps == 300000) reached_floor = 1; + if (!out.admit) paused = 1; + if (debt > peak) peak = debt; + } + CHECK(reached_floor && paused && s.applied_bps == 4000000 && peak <= s.debt_limit_bytes + 25000); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS rate: 150kbps -> 6Mbps floor=300000 final=%u peak_debt=%llu", + s.applied_bps, (unsigned long long)peak); +} + /* Нормальный inflight на длинном RTT не должен выглядеть как перегрузка. */ static void test_long_rtt(void) { struct call_video_rate rate; @@ -97,6 +149,6 @@ static void test_channel(int static_scene) { int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - test_gates(); test_long_rtt(); test_channel(0); test_channel(1); + test_range(); test_gates(); test_long_rtt(); test_channel(0); test_channel(1); test_poor_then_good(); return 0; }