239 lines
17 KiB
C
239 lines
17 KiB
C
/* Границы FEC-пачек, все пары потерь, ненулевой offset начала, malformed wire и восстановление после GAP. */
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_ERROR); 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, 1}, .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, 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, 1}, .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];
|
|
size_t part_size = 0;
|
|
CHECK(stream_encoder_encode(encoder, 0, 1, 0, input, length, blocks, &part_size) == STREAM_OK && part_size < 1300);
|
|
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], part_size) == 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");
|
|
}
|
|
|
|
static unsigned revised_videos, revised_audios;
|
|
static uint32_t received_revision;
|
|
static void revised_frame(void *arg, const struct stream_media_format *format,
|
|
const struct stream_media_video *video, const struct stream_media_audio *audio) {
|
|
(void)arg;
|
|
if (video) { revised_videos++; received_revision = format->revision; CHECK(video->flags & STREAM_MEDIA_KEY); }
|
|
if (audio) revised_audios++;
|
|
}
|
|
|
|
/* Revision переживает потерю первого IDR; смена видео не сбрасывает PTS аудио и кадров. */
|
|
static void test_revisions(void) {
|
|
struct stream_media_receiver *r = NULL;
|
|
CHECK(stream_media_receiver_create(&r, revised_frame, NULL) == STREAM_OK);
|
|
struct stream_media_block b = {.format = {1280, 720, 90, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS, 1}, .video_count = 1};
|
|
b.video[0] = (struct stream_media_video){1, 1000000, 12, 0, 6, STREAM_MEDIA_KEY, expected_video};
|
|
uint8_t wire[7080]; size_t length;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 0);
|
|
b.format = (struct stream_media_format){1920, 1080, 270, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS, 2};
|
|
b.video_count = 0; b.audio_count = 1;
|
|
b.audio[0] = (struct stream_media_audio){1000000, 3, 960, (const uint8_t *)"ops"};
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_audios == 1);
|
|
b.video_count = 1; b.audio_count = 0;
|
|
b.video[0] = (struct stream_media_video){2, 1100000, 12, 0, 12, 0, expected_video};
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 0);
|
|
b.video[0] = (struct stream_media_video){3, 1200000, 12, 0, 6, STREAM_MEDIA_KEY, expected_video};
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK);
|
|
stream_media_receiver_gap(r);
|
|
b.video[0].offset = 6; b.video[0].data = expected_video + 6;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 0);
|
|
b.video[0] = (struct stream_media_video){4, 1300000, 12, 0, 12, STREAM_MEDIA_KEY, expected_video};
|
|
b.audio_count = 1; b.audio[0].pts_us = 1020000;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 1 && revised_audios == 2 && received_revision == 2);
|
|
b.format.revision = 1; b.video[0].frame_id = 5; b.video[0].pts_us = 1400000;
|
|
b.audio[0].pts_us = 1040000;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 1 && revised_audios == 2);
|
|
b.format.revision = 4; b.video[0].flags = 0;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 1 && revised_audios == 3);
|
|
b.video[0].frame_id = 6; b.video[0].pts_us = 1500000; b.video[0].flags = STREAM_MEDIA_KEY;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 2 && received_revision == 4 && revised_audios == 3);
|
|
b.format.width++;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_ERR_CONFLICT);
|
|
b.format.width--; b.format.revision = 5; b.format.audio_codec = 0; b.audio_count = 0;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_ERR_CONFLICT);
|
|
b.format.audio_codec = STREAM_MEDIA_OPUS; b.format.revision = 6; b.video[0].pts_us = 1400000;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(r, wire, length) == STREAM_OK && revised_videos == 2);
|
|
memset(wire + 28, 0, 4);
|
|
struct stream_media_block parsed;
|
|
CHECK(stream_media_parse(wire, length, &parsed) == STREAM_ERR_FORMAT);
|
|
stream_media_receiver_destroy(r);
|
|
}
|
|
|
|
static void stereo_frame(void *arg, const struct stream_media_format *format,
|
|
const struct stream_media_video *video, const struct stream_media_audio *audio) {
|
|
CHECK(!video && audio && audio->samples == 960 && format->audio_codec == STREAM_MEDIA_OPUS_STEREO);
|
|
(*(unsigned *)arg)++;
|
|
}
|
|
|
|
/* Стерео обозначается в wire явно; 960 — число кадров на канал, трек не меняется внутри эпохи. */
|
|
static void test_stereo(void) {
|
|
struct stream_media_block b = {.format = {1280, 720, 0, STREAM_MEDIA_H264, STREAM_MEDIA_OPUS_STEREO, 1}, .audio_count = 1};
|
|
b.audio[0] = (struct stream_media_audio){1000000, 3, 960, (const uint8_t *)"ops"};
|
|
uint8_t wire[7080]; size_t length = 0;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
struct stream_media_block parsed;
|
|
CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.format.audio_codec == STREAM_MEDIA_OPUS_STEREO);
|
|
unsigned received = 0; struct stream_media_receiver *receiver = NULL;
|
|
CHECK(stream_media_receiver_create(&receiver, stereo_frame, &received) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_OK && received == 1);
|
|
b.format.audio_codec = STREAM_MEDIA_OPUS; b.format.revision++; b.audio[0].pts_us += 20000;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK);
|
|
CHECK(stream_media_receiver_feed(receiver, wire, length) == STREAM_ERR_CONFLICT && received == 1);
|
|
b.format.audio_codec = STREAM_MEDIA_OPUS_STEREO; b.audio[0].samples = 1920;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_ERR_FORMAT);
|
|
b.audio[0].samples = 960; b.format.audio_codec = 3;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_ERR_FORMAT);
|
|
stream_media_receiver_destroy(receiver);
|
|
}
|
|
|
|
static void test_codecs(void) {
|
|
uint8_t wire[256], key[] = {0, 0, 0, 2, 11, 12, 13}; size_t length = 0;
|
|
struct stream_media_block b = {.format = {64, 64, 0, STREAM_MEDIA_H264, 0, 1}, .video_count = 1};
|
|
b.video[0] = (struct stream_media_video){1, 0, sizeof(key), 0, sizeof(key), STREAM_MEDIA_KEY, key};
|
|
struct stream_media_block parsed;
|
|
for (unsigned codec = STREAM_MEDIA_H264; codec <= STREAM_MEDIA_H266; codec++) {
|
|
b.format.video_codec = codec;
|
|
CHECK(stream_media_encode(&b, wire, sizeof(wire), &length) == STREAM_OK && !memcmp(wire, "uAV2", 4));
|
|
CHECK(stream_media_parse(wire, length, &parsed) == STREAM_OK && parsed.format.video_codec == codec);
|
|
}
|
|
wire[3] = '1'; CHECK(stream_media_parse(wire, length, &parsed) == STREAM_ERR_FORMAT);
|
|
const uint8_t *config, *frame; size_t config_size, frame_size;
|
|
CHECK(stream_media_key_parse(key, sizeof(key), &config, &config_size, &frame, &frame_size) == STREAM_OK);
|
|
CHECK(config_size == 2 && frame_size == 1 && config[0] == 11 && frame[0] == 13);
|
|
for (size_t n = 0; n < sizeof(key); n++) CHECK(stream_media_key_parse(key, n, &config, &config_size, &frame, &frame_size) < 0);
|
|
key[0] = 1; CHECK(stream_media_key_parse(key, sizeof(key), &config, &config_size, &frame, &frame_size) < 0);
|
|
memset(key, 0, 4); CHECK(stream_media_key_parse(key, sizeof(key), &config, &config_size, &frame, &frame_size) == STREAM_OK);
|
|
}
|
|
|
|
int main(void) {
|
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
|
|
/* Усечения проверяются массово; ожидаемые отказы не загромождают диагностический лог. */
|
|
debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DISABLED);
|
|
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(); test_revisions(); test_stereo(); test_codecs();
|
|
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;
|
|
}
|