From d4e6bc598083723c198ecf7cff1e1d7f35209b1f Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 22:05:55 +0200 Subject: [PATCH] Negotiate call video level and bitrate VBV envelopes in the shared protocol --- src/call/call_video.c | 118 ++++++++++++++++++++++++++++++++++-------- src/call/call_video.h | 34 +++++++++--- 2 files changed, 121 insertions(+), 31 deletions(-) diff --git a/src/call/call_video.c b/src/call/call_video.c index b6c13a78..17ec3fb1 100644 --- a/src/call/call_video.c +++ b/src/call/call_video.c @@ -1,4 +1,5 @@ #include "call_video.h" +#include "call_video_rate.h" #include "../../lib/mem.h" #include "../../lib/debug_config.h" #include @@ -11,41 +12,105 @@ static uint64_t read_be(const uint8_t *p, unsigned count) { static void write_be(uint8_t *p, uint64_t value, unsigned count) { for (unsigned i = count; i; i--) { p[i - 1] = (uint8_t)value; value >>= 8; } } -static int limit_valid(const struct call_video_limit *l) { - if (!l->width && !l->height && !l->fps && !l->pixels_per_second) return 1; +/* H.264 Annex A (Baseline), H.265 Annex A (Main tier). Picture/rate в MB для AVC, + * в luma samples для HEVC; BR/CPB в кбит. Используем VCL-пределы без NAL-множителя. + * Сверено: FFmpeg libavcodec/h264_levels.c, h265_profile_level.c; x265 encoder/level.cpp. */ +struct video_level { uint8_t id; uint32_t picture, rate, kbps, cpb_kbits; }; +static const struct video_level avc_levels[] = { + {10, 99, 1485, 64, 175}, {11, 396, 3000, 192, 500}, {12, 396, 6000, 384, 1000}, + {13, 396, 11880, 768, 2000}, {20, 396, 11880, 2000, 2000}, {21, 792, 19800, 4000, 4000}, + {22, 1620, 20250, 4000, 4000}, {30, 1620, 40500, 10000, 10000}, {31, 3600, 108000, 14000, 14000}, + {32, 5120, 216000, 20000, 20000}, {40, 8192, 245760, 20000, 25000}, {41, 8192, 245760, 50000, 62500}, + {42, 8704, 522240, 50000, 62500}, {50, 22080, 589824, 135000, 135000}, {51, 36864, 983040, 240000, 240000}, + {52, 36864, 2073600, 240000, 240000}, {60, 139264, 4177920, 240000, 240000}, + {61, 139264, 8355840, 480000, 480000}, {62, 139264, 16711680, 800000, 800000} +}; +static const struct video_level hevc_levels[] = { + {30, 36864, 552960, 128, 350}, {60, 122880, 3686400, 1500, 1500}, {63, 245760, 7372800, 3000, 3000}, + {90, 552960, 16588800, 6000, 6000}, {93, 983040, 33177600, 10000, 10000}, + {120, 2228224, 66846720, 12000, 12000}, {123, 2228224, 133693440, 20000, 20000}, + {150, 8912896, 267386880, 25000, 25000}, {153, 8912896, 534773760, 40000, 40000}, + {156, 8912896, 1069547520, 60000, 60000}, {180, 35651584, 1069547520, 60000, 60000}, + {183, 35651584, 2139095040, 120000, 120000}, {186, 35651584, 4278190080u, 240000, 240000} +}; +static const struct video_level *level_find(int codec, uint8_t id) { + const struct video_level *levels = codec == CALL_VIDEO_H264 ? avc_levels : hevc_levels; + unsigned count = codec == CALL_VIDEO_H264 ? sizeof(avc_levels) / sizeof(*levels) : sizeof(hevc_levels) / sizeof(*levels); + if (codec != CALL_VIDEO_H264 && codec != CALL_VIDEO_H265) return NULL; + for (unsigned i = 0; i < count; i++) if (levels[i].id == id) return &levels[i]; + return NULL; +} +static int limit_valid(const struct call_video_limit *l, int codec) { + if (!l->width && !l->height && !l->fps && !l->pixels_per_second + && !l->maximum_bps && !l->maximum_vbv_bits && !l->level) return 1; return l->width >= 16 && l->width <= 4096 && l->height >= 16 && l->height <= 4096 - && l->fps >= 1 && l->fps <= 60 && l->pixels_per_second >= 256 && l->pixels_per_second <= 4096u * 4096u * 60u; + && l->fps >= 1 && l->fps <= 60 && l->pixels_per_second >= 256 && l->pixels_per_second <= 4096u * 4096u * 60u + && l->maximum_bps >= CALL_VIDEO_RATE_MIN_BPS && l->maximum_bps <= CALL_VIDEO_RATE_MAX_BPS + && l->maximum_vbv_bits && l->maximum_vbv_bits <= 16000000 && level_find(codec, l->level); } int call_video_caps_valid(const struct call_video_caps *caps) { if (!caps) return 0; - for (unsigned i = 0; i < 2; i++) if (!limit_valid(&caps->encode[i]) || !limit_valid(&caps->decode[i])) return 0; + for (unsigned i = 0; i < 2; i++) if (!limit_valid(&caps->encode[i], i + 1) || !limit_valid(&caps->decode[i], i + 1)) return 0; return 1; } -static int fits(const struct call_video_limit *l, uint16_t width, uint16_t height, uint16_t fps) { - return width <= l->width && height <= l->height && fps <= l->fps && (uint64_t)width * height * fps <= l->pixels_per_second; +static int fits(const struct call_video_limit *l, const struct call_video_format *f) { + return f->width <= l->width && f->height <= l->height && f->fps <= l->fps + && (uint64_t)f->width * f->height * f->fps <= l->pixels_per_second && f->level <= l->level + && f->maximum_bps <= l->maximum_bps && f->vbv_bits <= l->maximum_vbv_bits; } int call_video_choose(const struct call_video_caps *sender, const struct call_video_caps *receiver, - int requested, uint16_t width, uint16_t height, uint16_t fps) { - if (!call_video_caps_valid(sender) || !call_video_caps_valid(receiver) || requested < 0 || requested > 2 - || width < 16 || height < 16 || width > 4096 || height > 4096 || width % 2 || height % 2 || !fps || fps > 60) { - DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video choose invalid request codec=%d size=%ux%u fps=%u", requested, width, height, fps); + struct call_video_format *format) { + if (!call_video_caps_valid(sender) || !call_video_caps_valid(receiver) || !format || format->codec > 2 || format->level) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video choose invalid capabilities/request"); return -1; + } + struct call_video_format f = *format; + // Проверяем вход независимо от наличия codec у сторон, на самом высоком уровне. + f.codec = CALL_VIDEO_H265; f.level = 186; + if (!call_video_format_valid(&f)) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video choose invalid size=%ux%u fps=%u bitrate=%u vbv=%u", + f.width, f.height, f.fps, f.maximum_bps, f.vbv_bits); return -1; } for (int codec = 2; codec >= 1; codec--) { - if (requested && codec != requested) continue; - if (fits(&sender->encode[codec - 1], width, height, fps) && fits(&receiver->decode[codec - 1], width, height, fps)) return codec; + if (format->codec && codec != format->codec) continue; + f.codec = codec; + const struct video_level *levels = codec == CALL_VIDEO_H264 ? avc_levels : hevc_levels; + unsigned count = codec == CALL_VIDEO_H264 ? sizeof(avc_levels) / sizeof(*levels) : sizeof(hevc_levels) / sizeof(*levels); + for (unsigned i = 0; i < count; i++) { + f.level = levels[i].id; + if (!call_video_format_valid(&f) || !fits(&sender->encode[codec - 1], &f) || !fits(&receiver->decode[codec - 1], &f)) continue; + *format = f; + DEBUG_INFO(DEBUG_CATEGORY_CALL, "video selected codec=%u level=%u size=%ux%u fps=%u bitrate_cap=%u vbv=%u", + f.codec, f.level, f.width, f.height, f.fps, f.maximum_bps, f.vbv_bits); return codec; + } } - DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video no common mode codec=%d size=%ux%u fps=%u", requested, width, height, fps); + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video no common mode codec=%u size=%ux%u fps=%u bitrate_cap=%u vbv=%u", + format->codec, f.width, f.height, f.fps, f.maximum_bps, f.vbv_bits); return 0; } int call_video_format_valid(const struct call_video_format *f) { - return f && f->generation && f->codec >= 1 && f->codec <= 2 && f->width >= 16 && f->height >= 16 + if (!(f && f->generation && f->codec >= 1 && f->codec <= 2 && f->width >= 16 && f->height >= 16 && f->width <= 4096 && f->height <= 4096 && !(f->width % 2) && !(f->height % 2) - && f->fps >= 1 && f->fps <= 60 && (f->rotation == 0 || f->rotation == 90 || f->rotation == 180 || f->rotation == 270); + && f->fps >= 1 && f->fps <= 60 && (f->rotation == 0 || f->rotation == 90 || f->rotation == 180 || f->rotation == 270) + && f->maximum_bps >= CALL_VIDEO_RATE_MIN_BPS && f->maximum_bps <= CALL_VIDEO_RATE_MAX_BPS + && f->vbv_bits && f->vbv_bits <= 16000000 && (uint64_t)f->vbv_bits * f->fps >= f->maximum_bps)) return 0; + const struct video_level *l = level_find(f->codec, f->level); + if (!l) return 0; + // AVC — macroblocks; HEVC резервируем padding до CTU 64, чтобы не зависеть от backend min-CU. + uint64_t w = f->codec == CALL_VIDEO_H264 ? (f->width + 15u) / 16u : (f->width + 63u) / 64u * 64u; + uint64_t h = f->codec == CALL_VIDEO_H264 ? (f->height + 15u) / 16u : (f->height + 63u) / 64u * 64u; + return w * h <= l->picture && w * h * f->fps <= l->rate && w * w <= 8ull * l->picture && h * h <= 8ull * l->picture + && f->maximum_bps <= (uint64_t)l->kbps * 1000 && f->vbv_bits <= (uint64_t)l->cpb_kbits * 1000; +} +int call_video_format_supported(const struct call_video_caps *sender, const struct call_video_caps *receiver, + const struct call_video_format *f) { + return call_video_caps_valid(sender) && call_video_caps_valid(receiver) && call_video_format_valid(f) + && fits(&sender->encode[f->codec - 1], f) && fits(&receiver->decode[f->codec - 1], f); } int call_video_same_format(const struct call_video_format *a, const struct call_video_format *b) { return a->generation == b->generation && a->width == b->width && a->height == b->height - && a->fps == b->fps && a->rotation == b->rotation && a->codec == b->codec; + && a->fps == b->fps && a->rotation == b->rotation && a->codec == b->codec && a->level == b->level + && a->maximum_bps == b->maximum_bps && a->vbv_bits == b->vbv_bits; } int call_video_caps_encode(const struct call_video_caps *caps, uint8_t out[CALL_VIDEO_CAPS_BYTES]) { if (!out || !call_video_caps_valid(caps)) { @@ -53,8 +118,10 @@ int call_video_caps_encode(const struct call_video_caps *caps, uint8_t out[CALL_ } for (unsigned i = 0; i < 4; i++) { const struct call_video_limit *l = i < 2 ? &caps->encode[i] : &caps->decode[i - 2]; - write_be(out + i * 10, l->width, 2); write_be(out + i * 10 + 2, l->height, 2); - write_be(out + i * 10 + 4, l->fps, 2); write_be(out + i * 10 + 6, l->pixels_per_second, 4); + uint8_t *p = out + i * 20; + write_be(p, l->width, 2); write_be(p + 2, l->height, 2); + write_be(p + 4, l->fps, 2); write_be(p + 6, l->pixels_per_second, 4); + write_be(p + 10, l->maximum_bps, 4); write_be(p + 14, l->maximum_vbv_bits, 4); p[18] = l->level; p[19] = 0; } return 0; } @@ -65,8 +132,11 @@ int call_video_caps_parse(const uint8_t *data, size_t length, struct call_video_ struct call_video_caps caps = {0}; for (unsigned i = 0; i < 4; i++) { struct call_video_limit *l = i < 2 ? &caps.encode[i] : &caps.decode[i - 2]; - l->width = read_be(data + i * 10, 2); l->height = read_be(data + i * 10 + 2, 2); - l->fps = read_be(data + i * 10 + 4, 2); l->pixels_per_second = read_be(data + i * 10 + 6, 4); + const uint8_t *p = data + i * 20; + l->width = read_be(p, 2); l->height = read_be(p + 2, 2); + l->fps = read_be(p + 4, 2); l->pixels_per_second = read_be(p + 6, 4); + l->maximum_bps = read_be(p + 10, 4); l->maximum_vbv_bits = read_be(p + 14, 4); l->level = p[18]; + if (p[19]) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video capabilities reserved byte index=%u", i); return -1; } } if (!call_video_caps_valid(&caps)) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video capabilities invalid limits"); return -1; } *out = caps; return 0; @@ -84,8 +154,9 @@ int call_video_fragment_encode(const struct call_video_frame *f, uint8_t *out, s write_be(out + 16, f->frame_id, 8); write_be(out + 24, f->pts_us, 8); write_be(out + 32, f->format.width, 2); write_be(out + 34, f->format.height, 2); write_be(out + 36, f->format.fps, 2); write_be(out + 38, f->format.rotation, 2); - out[40] = f->format.codec; out[41] = f->flags; out[42] = out[43] = 0; + out[40] = f->format.codec; out[41] = f->flags; out[42] = f->format.level; out[43] = 0; write_be(out + 44, f->size, 4); write_be(out + 48, f->offset, 4); + write_be(out + 52, f->format.maximum_bps, 4); write_be(out + 56, f->format.vbv_bits, 4); memcpy(out + CALL_VIDEO_HEADER_BYTES, f->data, f->length); *length = CALL_VIDEO_HEADER_BYTES + f->length; return 0; } @@ -98,10 +169,11 @@ int call_video_fragment_parse(const uint8_t *data, size_t length, struct call_vi f.frame_id = read_be(data + 16, 8); f.pts_us = (int64_t)read_be(data + 24, 8); f.format.width = read_be(data + 32, 2); f.format.height = read_be(data + 34, 2); f.format.fps = read_be(data + 36, 2); f.format.rotation = read_be(data + 38, 2); - f.format.codec = data[40]; f.flags = data[41]; + f.format.codec = data[40]; f.flags = data[41]; f.format.level = data[42]; f.size = read_be(data + 44, 4); f.offset = read_be(data + 48, 4); + f.format.maximum_bps = read_be(data + 52, 4); f.format.vbv_bits = read_be(data + 56, 4); f.length = length - CALL_VIDEO_HEADER_BYTES; f.data = data + CALL_VIDEO_HEADER_BYTES; - if (data[42] || data[43] || !fragment_valid(&f)) { + if (data[43] || !fragment_valid(&f)) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video fragment invalid fields frame=%llu size=%u offset=%u bytes=%u", (unsigned long long)f.frame_id, f.size, f.offset, f.length); return -1; } diff --git a/src/call/call_video.h b/src/call/call_video.h index 765d4f8e..8f400487 100644 --- a/src/call/call_video.h +++ b/src/call/call_video.h @@ -1,5 +1,6 @@ /* Общий контракт видеотрека. Все операции выполняет владелец в uasync-потоке. - * Кодеки: H.264 Constrained Baseline / H.265 Main, 8 bit, 4:2:0, без B-кадров. + * Кодеки: H.264 Constrained Baseline / H.265 Main (Main tier), 8 bit, 4:2:0, + * progressive, один reference, без B-кадров. Profile/tier фиксированы протоколом. * Поколение меняется только при смене формата/камеры; bitrate не меняет поколение. * KEY: config_length:4BE + Annex B configuration + Annex B access unit (как uAV2). * Приём хранит один частичный кадр. Готовый view заимствован до следующего push/reset. @@ -17,14 +18,26 @@ extern "C" { #define CALL_VIDEO_KEY 1 #define CALL_VIDEO_MAX_FRAME (2u * 1024u * 1024u) #define CALL_VIDEO_FRAGMENT_BYTES 1200u -#define CALL_VIDEO_HEADER_BYTES 52u -#define CALL_VIDEO_CAPS_BYTES 40u +#define CALL_VIDEO_HEADER_BYTES 60u +#define CALL_VIDEO_CAPS_BYTES 80u /* Нулевой limit означает отсутствие проверенного encoder/decoder данного профиля. - * Индексы 0=H.264, 1=H.265. Лимиты независимы для каждого направления. */ -struct call_video_limit { uint16_t width, height, fps; uint32_t pixels_per_second; }; + * Индексы 0=H.264, 1=H.265. Лимиты независимы для каждого направления. + * level — максимальный AVC level_idc / HEVC general_level_idc (31 / 93 для 3.1). + * Поля — независимые верхние границы, не обещание поддержки их сочетания: + * выбранный режим дополнительно проверяется открытием платформенного codec. */ +struct call_video_limit { + uint16_t width, height, fps; + uint32_t pixels_per_second, maximum_bps, maximum_vbv_bits; + uint8_t level; +}; struct call_video_caps { struct call_video_limit encode[2], decode[2]; }; -struct call_video_format { uint64_t generation; uint16_t width, height, fps, rotation; uint8_t codec; }; +struct call_video_format { + uint64_t generation; + uint16_t width, height, fps, rotation; + uint8_t codec, level; + uint32_t maximum_bps, vbv_bits; // Верхний bitrate и размер VBV неизменны внутри generation. +}; struct call_video_frame { uint64_t call_id, frame_id; int64_t pts_us; @@ -35,9 +48,14 @@ struct call_video_frame { }; int call_video_caps_valid(const struct call_video_caps *caps); -/* requested=0: H.265, затем H.264. 0 — нет совместимого режима; -1 — неверные аргументы. */ +/* format задаёт generation/размер/FPS/rotation/maximum_bps/vbv_bits, level=0. + * codec=0: H.265, затем H.264. Успех: codec (1/2), заполняются codec и минимальный level. + * 0 — нет совместимого режима, -1 — неверные аргументы; на отказе format не меняется. */ int call_video_choose(const struct call_video_caps *sender, const struct call_video_caps *receiver, - int requested, uint16_t width, uint16_t height, uint16_t fps); + struct call_video_format *format); +/* Проверка уже выбранного/полученного режима, включая оба направленных CAPS и level. */ +int call_video_format_supported(const struct call_video_caps *sender, const struct call_video_caps *receiver, + const struct call_video_format *format); int call_video_format_valid(const struct call_video_format *format); int call_video_same_format(const struct call_video_format *a, const struct call_video_format *b); int call_video_caps_encode(const struct call_video_caps *caps, uint8_t out[CALL_VIDEO_CAPS_BYTES]);