From 7122d70ea66ed06e7a747ad2c19fa4b50f086a66 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 19 Oct 2026 23:32:29 +0200 Subject: [PATCH] Negotiate HEVC Main High tier with explicit level limits --- src/call/call_video.c | 11 +++++++---- src/call/call_video.h | 5 +++-- src/call/call_video_bitstream.c | 28 +++++++++++++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/call/call_video.c b/src/call/call_video.c index cf0a3ee9..e7008aa1 100644 --- a/src/call/call_video.c +++ b/src/call/call_video.c @@ -46,7 +46,8 @@ static int limit_valid(const struct call_video_limit *l, int codec) { 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->maximum_bps >= CALL_VIDEO_RATE_MIN_BPS && l->maximum_bps <= CALL_VIDEO_RATE_MAX_BPS - && l->profiles && !(l->profiles & ~(codec == CALL_VIDEO_H264 ? 3u : 1u)) && level_find(codec, l->level); + && l->profiles && !(l->profiles & ~3u) && level_find(codec, l->level) + && !(codec == CALL_VIDEO_H265 && (l->profiles & CALL_VIDEO_MAIN_HIGH_TIER) && l->level < 120); } int call_video_caps_valid(const struct call_video_caps *caps) { if (!caps) return 0; @@ -71,7 +72,7 @@ static int fits(const struct call_video_limit *l, const struct call_video_format int call_video_choose(const struct call_video_caps *sender, const struct call_video_caps *receiver, struct call_video_format *format) { if (!call_video_caps_valid(sender) || !call_video_caps_valid(receiver) || !format || format->codec > 2 - || format->level || format->profile > 2 || (format->codec == CALL_VIDEO_H265 && format->profile > 1)) { + || format->level || format->profile > 2) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video choose invalid capabilities/request"); return -1; } struct call_video_format f = *format; @@ -94,7 +95,8 @@ int call_video_choose(const struct call_video_caps *sender, const struct call_vi unsigned profiles = sender->encode[codec - 1].profiles & receiver->decode[codec - 1].profiles; if (format->profile) profiles &= format->profile; if (!profiles) continue; - f.profile = profiles & CALL_VIDEO_CONSTRAINED_BASELINE ? CALL_VIDEO_CONSTRAINED_BASELINE : 1; + f.profile = codec == CALL_VIDEO_H264 ? (profiles & CALL_VIDEO_CONSTRAINED_BASELINE ? CALL_VIDEO_CONSTRAINED_BASELINE : 1) + : (profiles & CALL_VIDEO_MAIN ? CALL_VIDEO_MAIN : CALL_VIDEO_MAIN_HIGH_TIER); 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++) { @@ -114,7 +116,8 @@ int call_video_format_valid(const struct call_video_format *f) { && 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->maximum_bps >= CALL_VIDEO_RATE_MIN_BPS && f->maximum_bps <= CALL_VIDEO_RATE_MAX_BPS - && (f->profile == 1 || (f->codec == CALL_VIDEO_H264 && f->profile == 2)))) return 0; + && (f->profile == 1 || f->profile == 2))) return 0; + if (f->codec == CALL_VIDEO_H265 && f->profile == CALL_VIDEO_MAIN_HIGH_TIER && f->level < 120) 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. diff --git a/src/call/call_video.h b/src/call/call_video.h index 7099c12e..03700fc5 100644 --- a/src/call/call_video.h +++ b/src/call/call_video.h @@ -1,5 +1,5 @@ /* Общий контракт видеотрека. Все операции выполняет владелец в uasync-потоке. - * Кодеки: H.264 Baseline/Constrained Baseline, H.265 Main (Main tier), 8 bit, + * Кодеки: H.264 Baseline/Constrained Baseline, H.265 Main (Main/High tier), 8 bit, * 4:2:0, progressive, без B-кадров/reorder. DPB ограничен уровнем потока. * Поколение меняется только при смене формата/камеры; bitrate не меняет поколение. * KEY: config_length:4BE + Annex B configuration + Annex B access unit (как uAV2). @@ -23,6 +23,7 @@ extern "C" { #define CALL_VIDEO_BASELINE 1u #define CALL_VIDEO_CONSTRAINED_BASELINE 2u #define CALL_VIDEO_MAIN 1u +#define CALL_VIDEO_MAIN_HIGH_TIER 2u /* Нулевой limit означает отсутствие проверенного encoder/decoder данного профиля. * Индексы 0=H.264, 1=H.265. Лимиты независимы для каждого направления. @@ -32,7 +33,7 @@ extern "C" { struct call_video_limit { uint16_t width, height, fps; uint32_t pixels_per_second, maximum_bps; - uint8_t profiles; // Маска профилей данного codec; decode Baseline включает CB явно. + uint8_t profiles; // Маска режимов: AVC Baseline/CB; HEVC Main с Main/High tier. Приём каждого проверяется отдельно. uint8_t level; }; struct call_video_caps { struct call_video_limit encode[2], decode[2]; }; diff --git a/src/call/call_video_bitstream.c b/src/call/call_video_bitstream.c index 2db063c3..3f14ffdd 100644 --- a/src/call/call_video_bitstream.c +++ b/src/call/call_video_bitstream.c @@ -96,8 +96,8 @@ static int hevc_ptl(struct bits *b, struct call_video_bitstream *s) { unsigned space = get(b, 2), tier = get(b, 1), profile = get(b, 5); get(b, 32); // profile compatibility; принимаем только явный Main. get(b, 1); unsigned interlaced = get(b, 1); get(b, 2); - get(b, 32); get(b, 12); s->level = get(b, 8); s->profile = CALL_VIDEO_MAIN; - if (space || tier || profile != 1 || interlaced) { + get(b, 32); get(b, 12); s->level = get(b, 8); s->profile = tier ? CALL_VIDEO_MAIN_HIGH_TIER : CALL_VIDEO_MAIN; + if (space || (tier && s->level < 120) || profile != 1 || interlaced) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video HEVC PTL rejected space=%u profile=%u tier=%u level=%u interlaced=%u", space, profile, tier, s->level, interlaced); return -1; } @@ -343,10 +343,32 @@ int call_video_probe_key(int codec, int profile, unsigned width, unsigned height size = codec == 1 ? sizeof(call_video_fullhd_1920_1080_1) : sizeof(call_video_fullhd_1920_1080_2); } } else if (wide != 1280 || short_side != 720) return bad("probe key geometry"); - if ((codec != 1 && codec != 2) || (profile != 1 && !(codec == 1 && profile == 2)) + if ((codec != 1 && codec != 2) || (profile != 1 && profile != 2) || !out || capacity < size) return bad("probe key arguments"); memcpy(out, data, size); if (codec == 1 && profile == 1) out[10] &= ~64u; + if (codec == CALL_VIDEO_H265 && profile == CALL_VIDEO_MAIN_HIGH_TIER) { + // Только эталонные пробы: Main tools и Main BR/CPB также соответствуют High tier level 4. + // Повышение декларации уровня не меняет entropy payload. Производственные VPS/SPS не переписываем. + unsigned patched = 0; + size_t at = start_code(out, size, 4); + while (at < size) { + size_t begin = at + 3, end = start_code(out, size, begin); at = end; + unsigned type = (out[begin] >> 1) & 63; + if (type != 32 && type != 33) continue; + unsigned profile_at = type == 32 ? 6 : 3, raw = 0, zeros = 0; + for (size_t i = begin; i < end; ++i) { + if (zeros == 2 && out[i] == 3) { zeros = 0; continue; } + if (raw == profile_at) { + if (out[i] != 1) return bad("probe HEVC profile"); + out[i] |= 32; + } + if (raw == profile_at + 11) { out[i] = 120; ++patched; break; } + zeros = out[i] == 0 ? zeros + 1 : 0; ++raw; + } + } + if (patched != 2) return bad("probe HEVC PTL count"); + } return (int)size; }