diff --git a/tests/test_stream_media.c b/tests/test_stream_media.c new file mode 100644 index 00000000..2bb565a2 --- /dev/null +++ b/tests/test_stream_media.c @@ -0,0 +1,128 @@ +/* Границы FEC-пачек, все пары потерь, ненулевой offset начала, malformed wire и восстановление после GAP. */ +#include +#include + +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include "../lib/u_async.h" +#include "../src/stream/stream_source.h" +#include "../src/stream/stream_media.h" + +#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_STREAM, "stream media test line=%d: %s", __LINE__, #x); exit(1); } } while (0) + +static unsigned video_frames, audio_frames, batches; +static uint8_t expected_video[9000]; +static void frame(void *arg, const struct stream_media_format *format, + const struct stream_media_video *video, const struct stream_media_audio *audio) { + (void)arg; CHECK(format->width == 1280 && format->height == 720); + if (video) { CHECK(video->length == sizeof(expected_video) && !memcmp(video->data, expected_video, video->length)); video_frames++; } + if (audio) { CHECK(audio->length == 3 && !memcmp(audio->data, "ops", 3)); audio_frames++; } +} +static void ready(struct stream_source *source, uint64_t seq, void *arg) { + (void)source; (void)arg; CHECK(seq == batches++); +} + +/* Канонический формат и сохранение выходного view при ошибке всех классов полей. */ +static void test_media(void) { + struct stream_media_receiver *receiver = NULL; + CHECK(stream_media_receiver_create(&receiver, frame, NULL) == STREAM_OK); + struct stream_media_block b = {.format = {1280, 720, 90, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS}, .video_count = 1, .audio_count = 1}; + b.video[0] = (struct stream_media_video){1, 1000000, sizeof(expected_video), 0, 6000, STREAM_MEDIA_KEY, expected_video}; + b.audio[0] = (struct stream_media_audio){1000000, 3, 960, (const uint8_t *)"ops"}; + uint8_t wire[7080], saved[7080]; size_t length = 0; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 0 && audio_frames == 1); + memcpy(saved, wire, length); + struct stream_media_block parsed; + CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.first_frame_offset == 0); + const unsigned mutations[] = {0, 4, 5, 6, 7, 8, 12, 14, 16, 20, 24, 28, 32, 48, 52, 56, 60, 61, 72, 76, 78}; + for (unsigned i = 0; i < sizeof(mutations) / sizeof(mutations[0]); i++) { + memcpy(wire, saved, length); wire[mutations[i]] = 255; + struct stream_media_block before; memset(&before, 0xa5, sizeof(before)); parsed = before; + CHECK(stream_media_parse(wire, length, &parsed) < 0 && !memcmp(&parsed, &before, sizeof(before))); + } + for (size_t i = 0; i < length; i++) CHECK(stream_media_parse(saved, i, &parsed) < 0); + b.video[0].offset = 6000; b.video[0].length = 3000; b.video[0].data = expected_video + 6000; + b.audio_count = 0; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.first_frame_offset == UINT32_MAX); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 1); + stream_media_receiver_gap(receiver); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 1); + b.video[0] = (struct stream_media_video){2, 2000000, sizeof(expected_video), 0, 6000, 0, expected_video}; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 1); + b.video[0].flags = STREAM_MEDIA_KEY; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK); + stream_media_receiver_gap(receiver); + b.video[0].offset = 6000; b.video[0].length = 3000; b.video[0].data = expected_video + 6000; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 1); + b.video_count = 2; + b.video[1] = (struct stream_media_video){3, 3000000, sizeof(expected_video), 0, 3000, STREAM_MEDIA_KEY, expected_video}; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.first_frame_offset == 3000); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 1); + b.video_count = 1; b.video[0] = b.video[1]; + b.video[0].offset = 3000; b.video[0].length = 6000; b.video[0].data = expected_video + 3000; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && video_frames == 2); + b.format.width++; + CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK); + CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_ERR_CONFLICT); + stream_media_receiver_destroy(receiver); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "PASS media: large frames, nonzero start, malformed fields, GAP and format changes"); +} + +/* Две короткие записи остаются двумя пачками; каждый медиазаголовок переживает любую пару потерь 6+2. */ +static void test_batches(void) { + struct stream_descriptor d = {.group_id = 1, .source_node_id = 2, .source_epoch = 3, .n = 8, .r = 2, + .block_size = STREAM_RS_DEFAULT_SIZE}; + uint8_t key[32] = {1}; struct stream_encoder *encoder = NULL; + CHECK(stream_encoder_create(&encoder, &d, key) == STREAM_OK); + struct stream_media_block b = {.format = {1280, 720, 90, STREAM_MEDIA_H264, 0}, .video_count = 1}; + b.video[0] = (struct stream_media_video){1, 1000000, 100, 0, 100, STREAM_MEDIA_KEY, expected_video}; + uint8_t input[7080], output[7080]; size_t length = 0; + CHECK(stream_media_encode(&b, input, sizeof(input), &length) == STREAM_OK); + const uint8_t *blocks[RS_MAX_BLOCKS]; + CHECK(stream_encoder_encode(encoder, 0, 1, 0, input, length, blocks) == STREAM_OK); + for (unsigned a = 0; a < 8; a++) for (unsigned c = a + 1; c < 8; c++) { + struct stream_buffer *buffer = NULL; size_t decoded = 0; + CHECK(stream_buffer_create(&buffer, &d, stream_encoder_public_key(encoder), 8 * 1300) == STREAM_OK); + for (unsigned i = 0; i < 8; i++) if (i != a && i != c) CHECK(stream_buffer_put(buffer, blocks[i], 1300) == STREAM_OK); + CHECK(stream_buffer_decode(buffer, 0, output, sizeof(output), &decoded) == STREAM_OK); + CHECK(decoded == length && !memcmp(input, output, length)); + struct stream_media_block parsed; CHECK(stream_media_parse(output, decoded, &parsed) == STREAM_OK); + stream_buffer_destroy(buffer); + } + struct UASYNC *ua = uasync_create(); struct stream_source *source = NULL; + CHECK(ua && stream_source_create(&source, ua, &d, key, ready, NULL) == STREAM_OK); + CHECK(stream_source_write_block(source, input, length) == STREAM_OK && batches == 1); + CHECK(stream_source_write_block(source, input, length) == STREAM_OK && batches == 2); + size_t accepted = 10; + CHECK(stream_source_write(source, input, length, &accepted) == STREAM_ERR_ARGUMENT && accepted == 0); + CHECK(stream_source_finish(source) == STREAM_OK); + CHECK(stream_source_write_block(source, input, length) == STREAM_ERR_STATE); + stream_source_destroy(source); + CHECK(stream_source_create(&source, ua, &d, key, ready, NULL) == STREAM_ERR_ARGUMENT); + source = NULL; + CHECK(stream_source_create(&source, ua, &d, key, NULL, NULL) == STREAM_OK); + CHECK(stream_source_write(source, input, length, &accepted) == STREAM_OK && accepted == length); + CHECK(stream_source_write_block(source, input, length) == STREAM_ERR_ARGUMENT); + stream_source_destroy(source); uasync_destroy(ua, 0); stream_encoder_destroy(encoder); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "PASS batches: atomic boundaries, all FEC losses and mode isolation"); +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + /* Усечения проверяются массово; ожидаемые отказы не загромождают диагностический лог. */ + debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_NONE); + size_t before = u_get_allocated_count(); + for (unsigned i = 0; i < sizeof(expected_video); i++) expected_video[i] = (uint8_t)(i * 31 + 7); + test_media(); test_batches(); + CHECK(u_get_allocated_count() == before); + debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_INFO); + DEBUG_INFO(DEBUG_CATEGORY_STREAM, "PASS stream_media: protocol, FEC, frame recovery, boundaries, no leaks"); + return 0; +}