diff --git a/src/Makefile.am b/src/Makefile.am index ecacd801..35ff3ace 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -185,6 +185,8 @@ utun_CORE_SOURCES = \ call/call.c \ call/call_video_rate.c \ call/call_video_rate.h \ + call/call_video.c \ + call/call_video.h \ call/call_headless.c \ radio/radio.c \ radio/radio_headless.c \ @@ -338,6 +340,8 @@ libutun_a_SOURCES = \ call/call.c \ call/call_video_rate.c \ call/call_video_rate.h \ + call/call_video.c \ + call/call_video.h \ call/call_headless.c \ radio/radio.c \ radio/radio_headless.c \ diff --git a/tests/Makefile.am b/tests/Makefile.am index 40906eee..6f43b584 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -11,6 +11,7 @@ EXTRA_DIST = fixtures/reality_openssl_hello.hex data/silero_speech.pcm data/sile check_PROGRAMS = \ test_route_tx_stats \ test_call_video_rate \ + test_call_video \ test_memory_pool_and_config \ test_serialize \ test_reed_solomon \ @@ -680,6 +681,9 @@ test_route_tx_stats_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_call_video_rate_SOURCES = test_call_video_rate.c test_call_video_rate_LDADD = $(LIBUTUN) $(COMMON_LIBS) + +test_call_video_SOURCES = test_call_video.c +test_call_video_LDADD = $(LIBUTUN) $(COMMON_LIBS) test_call_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/src/dm -I$(top_srcdir)/src/call -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib test_call_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS) diff --git a/tests/test_call_video.c b/tests/test_call_video.c new file mode 100644 index 00000000..9b39b2cf --- /dev/null +++ b/tests/test_call_video.c @@ -0,0 +1,90 @@ +#include "call/call_video.h" +#include "debug_config.h" +#include +#include +#include +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "video:%d %s\n", __LINE__, #x); exit(1); } } while (0) + +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); + 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); + 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); + 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 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.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; +} +static void roundtrip(void) { + uint8_t data[5001], wire[CALL_VIDEO_HEADER_BYTES + CALL_VIDEO_FRAGMENT_BYTES]; + for (unsigned i = 0; i < sizeof(data); i++) data[i] = (uint8_t)i; + memset(data, 0, 4); + struct call_video_receiver *r = call_video_receiver_create(); CHECK(r); + struct call_video_frame f = frame(1, 1, data, sizeof(data), 1), parsed, complete; + CHECK(call_video_receiver_needs_key(r)); + for (unsigned offset = 0; offset < sizeof(data); offset += f.length) { + f.offset = offset; f.data = data + offset; + 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(call_video_fragment_parse(wire, length, &parsed) == 0); + CHECK(parsed.frame_id == f.frame_id && parsed.pts_us == f.pts_us && parsed.offset == offset); + int done = call_video_receiver_push(r, &parsed, offset, &complete); + CHECK(done == (offset + f.length == sizeof(data) ? 1 : 0)); + } + CHECK(!call_video_receiver_needs_key(r) && complete.size == sizeof(data) && complete.length == sizeof(data)); + 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; + 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); + call_video_receiver_destroy(r); + puts("video: endian wire, fragmented KEY, delta frames, ordered format switch PASS"); +} +static void rejected(void) { + uint8_t data[2400] = {0}, wire[CALL_VIDEO_HEADER_BYTES + CALL_VIDEO_FRAGMENT_BYTES]; + struct call_video_receiver *r = call_video_receiver_create(); CHECK(r); + struct call_video_frame f = frame(1, 1, data, sizeof(data), 1), out = {0}, saved; + CHECK(call_video_receiver_push(r, &f, 0, &out) == 0); + f.offset = 1201; f.length = 1199; + CHECK(call_video_receiver_push(r, &f, 10000, &out) == -1 && call_video_receiver_needs_key(r)); + f = frame(2, 1, data, sizeof(data), 1); CHECK(call_video_receiver_push(r, &f, 20000, &out) == 0); + f.offset = 1200; CHECK(call_video_receiver_push(r, &f, 2020001, &out) == 0); + CHECK(call_video_receiver_needs_key(r)); + f = frame(3, 1, data, 8, 1); CHECK(call_video_receiver_push(r, &f, 2030000, &out) == 1); + CHECK(call_video_receiver_push(r, &f, 2040000, &out) == -1); // Replay. + call_video_receiver_reset(r); CHECK(call_video_receiver_needs_key(r)); + data[0] = 0xff; CHECK(call_video_receiver_push(r, &f, 0, &out) == -1); // Неверный config_length. + data[0] = 0; + 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[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); + puts("video: gap, timeout, replay, malformed KEY, truncated/oversized packets, atomic parse PASS"); +} +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); + capabilities(); roundtrip(); rejected(); return 0; +}