From 534f319d86e7411ed6eeb85d3246160da2247e23 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 22:08:48 +0200 Subject: [PATCH] Test directional codec envelopes and reject out-of-cap video in live calls --- tests/test_call.c | 26 +++++++++----- tests/test_call_video.c | 75 +++++++++++++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 22 deletions(-) diff --git a/tests/test_call.c b/tests/test_call.c index 9832584e..8128c432 100644 --- a/tests/test_call.c +++ b/tests/test_call.c @@ -137,7 +137,8 @@ static const struct call_video_ops video_ops = { static struct call_video_frame video_test_frame(uint64_t id, unsigned generation) { struct call_video_frame f = {0}; f.call_id = id; f.frame_id = generation; f.pts_us = sent_timing.pts_us + generation * 30000; - f.format = (struct call_video_format){generation, 640, 480, 30, 0, generation == 1 ? CALL_VIDEO_H265 : CALL_VIDEO_H264}; + f.format = (struct call_video_format){generation, 640, 480, 30, 0, + generation == 1 ? CALL_VIDEO_H265 : CALL_VIDEO_H264, generation == 1 ? 93 : 31, 4000000, 1000000}; f.flags = CALL_VIDEO_KEY; f.size = f.length = sizeof(g_video_data); f.data = g_video_data; return f; } @@ -505,15 +506,22 @@ static void call_tick(void* arg) { } for (unsigned i = 4; i < sizeof(g_video_data); i++) g_video_data[i] = (uint8_t)i; struct call_video_caps caps = {0}; - caps.encode[0] = caps.encode[1] = caps.decode[0] = caps.decode[1] = (struct call_video_limit){1280, 720, 30, 1280 * 720 * 30}; + caps.encode[0] = caps.encode[1] = caps.decode[0] = caps.decode[1] + = (struct call_video_limit){1280, 720, 30, 1280 * 720 * 30, 4000000, 1000000, 31}; + caps.encode[1].level = caps.decode[1].level = 93; if (call_video_configure(A, t->call_id, &caps, &video_ops, (void*)0) || call_video_configure(B, t->call_id, &caps, &video_ops, (void*)1)) { t->result = 2; break; } - if (call_video_configure_rate(A, t->call_id, 1, 800000, 400000)) { t->result = 2; break; } g_video_ready = 0; t->phase = P_VIDEO_SEND; break; } case P_VIDEO_SEND: { if (g_video_peer[0].decode[1].width != 1280 || g_video_peer[1].decode[1].width != 1280) break; struct call_video_frame f = video_test_frame(t->call_id, 1); + if (call_video_configure_rate(A, t->call_id, &f.format, 800000, 400000)) { t->result = 2; break; } + struct call_video_frame invalid = f; invalid.format.vbv_bits = 1000001; + if (call_video_send_frame(A, &invalid) != -1 + || call_video_configure_rate(A, t->call_id, &invalid.format, 800000, 400000) != -1) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video accepted VBV above peer limit"); t->result = 2; break; + } int result = call_video_send_frame(A, &f); if (result < 0) { fprintf(stderr, "video send failed\n"); t->result = 2; break; } if (result == 1) { t->phase_ticks = 0; t->phase = P_VIDEO_HOLD; } @@ -543,20 +551,22 @@ static void call_tick(void* arg) { fprintf(stderr, "video rate command/completion mismatch\n"); t->result = 2; break; } t->phase = P_VIDEO_RANGE_WAIT; break; - case P_VIDEO_RANGE_WAIT: + case P_VIDEO_RANGE_WAIT: { if (g_rate_commands < 2) break; if (g_rate_commands != 2 || g_rate_generation != 1 || g_rate_bps != 350000 || call_video_rate_applied(A, t->call_id, 1, g_rate_request, g_rate_bps)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video live range change did not converge to 350kbps"); t->result = 2; break; } + struct call_video_frame next = video_test_frame(t->call_id, 2); if (chat_setting_set(A, "call_video_bitrate_range_kbps", "500:1000") - || call_video_configure_rate(A, t->call_id, 2, 350000, 400000) != -1 + || call_video_configure_rate(A, t->call_id, &next.format, 350000, 400000) != -1 || chat_setting_set(A, "call_video_bitrate_range_kbps", "300:350")) { t->result = 2; break; } t->phase = P_VIDEO_SWITCH; break; + } case P_VIDEO_SWITCH: { - if (call_video_configure_rate(A, t->call_id, 2, 350000, 400000) - || call_video_rate_applied(A, t->call_id, 1, g_rate_request, g_rate_bps) != -1) { t->result = 2; break; } struct call_video_frame f = video_test_frame(t->call_id, 2); + if (call_video_configure_rate(A, t->call_id, &f.format, 350000, 400000) + || call_video_rate_applied(A, t->call_id, 1, g_rate_request, g_rate_bps) != -1) { t->result = 2; break; } if (call_video_send_frame(A, &f) != 1) { t->result = 2; break; } struct call_capture_time timing = sent_timing; timing.pts_us += 60000; uint8_t opus[16] = {2}; @@ -569,8 +579,8 @@ static void call_tick(void* arg) { // Завершение во время незавершённой сборки/паузы приёма не оставляет callbacks. g_video_ready = 0; { - if (call_video_configure_rate(A, t->call_id, 3, 350000, 400000)) { t->result = 2; break; } struct call_video_frame f = video_test_frame(t->call_id, 3); + if (call_video_configure_rate(A, t->call_id, &f.format, 350000, 400000)) { t->result = 2; break; } if (call_video_send_frame(A, &f) != 1) { t->result = 2; break; } } t->phase = P_HANGUP; break; diff --git a/tests/test_call_video.c b/tests/test_call_video.c index 9b39b2cf..9660cc08 100644 --- a/tests/test_call_video.c +++ b/tests/test_call_video.c @@ -5,28 +5,70 @@ #include #define CHECK(x) do { if (!(x)) { fprintf(stderr, "video:%d %s\n", __LINE__, #x); exit(1); } } while (0) +static struct call_video_format mode(int codec, unsigned width, unsigned height, unsigned fps) { + return (struct call_video_format){1, width, height, fps, 0, codec, 0, 4000000, 1000000}; +} +static int choose(const struct call_video_caps *a, const struct call_video_caps *b, int codec, unsigned w, unsigned h, unsigned fps) { + struct call_video_format f = mode(codec, w, h, fps); return call_video_choose(a, b, &f); +} static void capabilities(void) { struct call_video_caps a = {0}, b = {0}, decoded; - struct call_video_limit mode = {1920, 1080, 60, 1920u * 1080u * 30u}; - a.encode[0] = a.encode[1] = b.decode[0] = b.decode[1] = mode; - CHECK(call_video_choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H265); - CHECK(call_video_choose(&b, &a, 0, 1280, 720, 30) == 0); + struct call_video_limit limit = {1920, 1080, 60, 1920u * 1080u * 30u, 4000000, 1000000, 41}; + a.encode[0] = a.encode[1] = b.decode[0] = b.decode[1] = limit; + a.encode[1].level = b.decode[1].level = 123; + CHECK(choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H265); + CHECK(choose(&b, &a, 0, 1280, 720, 30) == 0); b.decode[1].pixels_per_second = 1280 * 720 * 15; - CHECK(call_video_choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H264); - CHECK(call_video_choose(&a, &b, CALL_VIDEO_H265, 1280, 720, 30) == 0); - CHECK(call_video_choose(&a, &b, 0, 1920, 1080, 60) == 0); - CHECK(call_video_choose(&a, &b, 0, 1279, 720, 30) == -1); + CHECK(choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H264); + CHECK(choose(&a, &b, CALL_VIDEO_H265, 1280, 720, 30) == 0); + CHECK(choose(&a, &b, 0, 1920, 1080, 60) == 0); + CHECK(choose(&a, &b, 0, 1279, 720, 30) == -1); uint8_t wire[CALL_VIDEO_CAPS_BYTES]; CHECK(call_video_caps_encode(&b, wire) == 0 && call_video_caps_parse(wire, sizeof(wire), &decoded) == 0); CHECK(decoded.decode[1].pixels_per_second == b.decode[1].pixels_per_second && !decoded.encode[0].width); + CHECK(decoded.decode[1].maximum_bps == 4000000 && decoded.decode[1].maximum_vbv_bits == 1000000 && decoded.decode[1].level == 123); + CHECK(call_video_caps_parse(wire, 40, &decoded) == -1); // Старый wire format не поддерживается. + wire[79] = 1; CHECK(call_video_caps_parse(wire, sizeof(wire), &decoded) == -1); wire[79] = 0; wire[0] = 0xff; struct call_video_caps saved = decoded; CHECK(call_video_caps_parse(wire, sizeof(wire), &decoded) == -1 && !memcmp(&saved, &decoded, sizeof(saved))); puts("video: directional capabilities, HEVC preference, AVC fallback, pixel-rate limits PASS"); } +static void envelopes(void) { + struct call_video_caps a = {0}, b = {0}; + a.encode[0] = b.decode[0] = (struct call_video_limit){1920, 1080, 60, 1920u * 1080u * 60u, 4000000, 16000000, 42}; + a.encode[1] = b.decode[1] = (struct call_video_limit){1920, 1080, 60, 1920u * 1080u * 60u, 4000000, 16000000, 123}; + struct call_video_format f = mode(0, 1280, 720, 30); + CHECK(call_video_choose(&a, &b, &f) == CALL_VIDEO_H265 && f.level == 93); + CHECK(call_video_format_supported(&a, &b, &f) && !call_video_format_supported(&b, &a, &f)); + b.decode[1].level = 90; + f = mode(0, 1280, 720, 30); CHECK(call_video_choose(&a, &b, &f) == CALL_VIDEO_H264 && f.level == 31); + f = mode(CALL_VIDEO_H265, 1280, 720, 30); struct call_video_format saved = f; + CHECK(call_video_choose(&a, &b, &f) == 0 && !memcmp(&f, &saved, sizeof(f))); + b.decode[1].level = 123; b.decode[1].maximum_bps = 3999999; + CHECK(choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H264); + b.decode[1].maximum_bps = 4000000; b.decode[1].maximum_vbv_bits = 999999; + CHECK(choose(&a, &b, 0, 1280, 720, 30) == CALL_VIDEO_H264); + b.decode[1].maximum_vbv_bits = 16000000; + f = mode(CALL_VIDEO_H264, 176, 144, 30); f.maximum_bps = 300000; f.vbv_bits = 75000; + CHECK(call_video_choose(&a, &b, &f) == CALL_VIDEO_H264 && f.level == 12); + f.level = 11; CHECK(!call_video_format_valid(&f)); // Геометрия проходит, битрейт — нет. + f = mode(CALL_VIDEO_H265, 1280, 720, 30); f.level = 93; f.vbv_bits = 10000001; + CHECK(!call_video_format_valid(&f)); // Main tier CPB level 3.1 = 10 Мбит. + f.level = 120; CHECK(call_video_format_valid(&f)); + f.vbv_bits = 1; CHECK(!call_video_format_valid(&f)); // Меньше среднего кадра при maximum_bps. + f = mode(CALL_VIDEO_H264, 1920, 1080, 60); + CHECK(call_video_choose(&a, &b, &f) == CALL_VIDEO_H264 && f.level == 42); + f.level = 41; CHECK(!call_video_format_valid(&f)); + f = mode(CALL_VIDEO_H265, 1920, 1080, 60); + CHECK(call_video_choose(&a, &b, &f) == CALL_VIDEO_H265 && f.level == 123); + f.level = 120; CHECK(!call_video_format_valid(&f)); + a.encode[0].level = 99; CHECK(!call_video_caps_valid(&a)); + puts("video: negotiated level, bitrate/VBV fallback, CPB/frame-rate boundaries, atomic selection PASS"); +} static struct call_video_frame frame(uint64_t id, uint64_t generation, const uint8_t *data, uint32_t size, int key) { struct call_video_frame f = {0}; f.call_id = 42; f.frame_id = id; f.pts_us = 1000000 + id * 33333; - f.format = (struct call_video_format){generation, 640, 480, 30, 90, CALL_VIDEO_H265}; + f.format = (struct call_video_format){generation, 640, 480, 30, 90, CALL_VIDEO_H265, 93, 4000000, 1000000}; f.size = size; f.length = size < CALL_VIDEO_FRAGMENT_BYTES ? size : CALL_VIDEO_FRAGMENT_BYTES; f.flags = key ? CALL_VIDEO_KEY : 0; f.data = data; return f; } @@ -42,9 +84,10 @@ static void roundtrip(void) { f.length = sizeof(data) - offset < CALL_VIDEO_FRAGMENT_BYTES ? sizeof(data) - offset : CALL_VIDEO_FRAGMENT_BYTES; size_t length = 0; CHECK(call_video_fragment_encode(&f, wire, sizeof(wire), &length) == 0); - CHECK(wire[7] == 42 && wire[40] == 2 && wire[39] == 90); + CHECK(wire[7] == 42 && wire[40] == 2 && wire[39] == 90 && wire[42] == 93); CHECK(call_video_fragment_parse(wire, length, &parsed) == 0); CHECK(parsed.frame_id == f.frame_id && parsed.pts_us == f.pts_us && parsed.offset == offset); + CHECK(parsed.format.maximum_bps == 4000000 && parsed.format.vbv_bits == 1000000 && parsed.format.level == 93); int done = call_video_receiver_push(r, &parsed, offset, &complete); CHECK(done == (offset + f.length == sizeof(data) ? 1 : 0)); } @@ -52,10 +95,14 @@ static void roundtrip(void) { CHECK(!memcmp(complete.data, data, sizeof(data))); f = frame(2, 1, data, 8, 0); CHECK(call_video_receiver_push(r, &f, 10000, &complete) == 1); f = frame(3, 2, data, 8, 0); CHECK(call_video_receiver_push(r, &f, 20000, &complete) == 0); - f = frame(4, 2, data, 8, 1); f.format.codec = CALL_VIDEO_H264; + f = frame(4, 2, data, 8, 1); f.format.codec = CALL_VIDEO_H264; f.format.level = 31; CHECK(call_video_receiver_push(r, &f, 30000, &complete) == 1 && complete.format.codec == CALL_VIDEO_H264); f = frame(5, 2, data, 8, 1); // Codec меняется без нового поколения. CHECK(call_video_receiver_push(r, &f, 40000, &complete) == -1); + f.format.codec = CALL_VIDEO_H264; f.format.level = 31; f.format.vbv_bits = 2000000; + CHECK(call_video_receiver_push(r, &f, 50000, &complete) == -1); // VBV тоже часть generation. + f.format.generation = 3; + CHECK(call_video_receiver_push(r, &f, 60000, &complete) == 1); call_video_receiver_destroy(r); puts("video: endian wire, fragmented KEY, delta frames, ordered format switch PASS"); } @@ -77,8 +124,8 @@ static void rejected(void) { size_t length = 0; CHECK(call_video_fragment_encode(&f, wire, sizeof(wire), &length) == 0); saved = out; for (size_t n = 0; n <= CALL_VIDEO_HEADER_BYTES; n++) CHECK(call_video_fragment_parse(wire, n, &out) == -1); - wire[42] = 1; CHECK(call_video_fragment_parse(wire, length, &out) == -1); - CHECK(!memcmp(&out, &saved, sizeof(saved))); wire[42] = 0; + wire[43] = 1; CHECK(call_video_fragment_parse(wire, length, &out) == -1); + CHECK(!memcmp(&out, &saved, sizeof(saved))); wire[43] = 0; wire[44] = 0xff; CHECK(call_video_fragment_parse(wire, length, &out) == -1); f.size = CALL_VIDEO_MAX_FRAME + 1; CHECK(call_video_receiver_push(r, &f, 0, &out) == -1); call_video_receiver_destroy(r); @@ -86,5 +133,5 @@ static void rejected(void) { } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); - capabilities(); roundtrip(); rejected(); return 0; + capabilities(); envelopes(); roundtrip(); rejected(); return 0; }