From f8827a946657ddc68d8b965b4c77182ccb5913c3 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 18 Oct 2026 17:19:57 +0200 Subject: [PATCH] Accept valid HEVC PPS flags and verify call GPU probes fully --- src/call/call_video_bitstream.c | 48 +++++++++++++++++------- tools/chatgui/src/live_video_encoder.cpp | 18 +++++++-- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/call/call_video_bitstream.c b/src/call/call_video_bitstream.c index 3b80ea78..e9f74fb3 100644 --- a/src/call/call_video_bitstream.c +++ b/src/call/call_video_bitstream.c @@ -123,11 +123,12 @@ static void hevc_hrd(struct bits *b, unsigned level) { } } /* Проходим до VUI: именно field_seq_flag определяет поля при неопределённых source flags. */ -static int hevc_sps_tail(struct bits *b, unsigned poc, unsigned level) { +static int hevc_sps_tail(struct bits *b, unsigned poc, unsigned level, unsigned *ctb_log2) { unsigned min_cb = ue(b) + 3, diff_cb = ue(b), min_tb = ue(b) + 2, diff_tb = ue(b); unsigned inter = ue(b), intra = ue(b); if (min_cb > 6 || diff_cb > 3 || min_cb + diff_cb < 4 || min_cb + diff_cb > 6 || min_tb > min_cb || diff_tb > 3 || min_tb + diff_tb > 5 || inter > 4 || intra > 4) return bad("HEVC block sizes"); + *ctb_log2 = min_cb + diff_cb; if (get(b, 1) && get(b, 1)) { for (unsigned size = 0; size < 4; ++size) for (unsigned matrix = 0; matrix < 6; matrix += size == 3 ? 3 : 1) { if (!get(b, 1)) ue(b); @@ -174,7 +175,7 @@ static int hevc_sps_tail(struct bits *b, unsigned poc, unsigned level) { if (get(b, 1) && get(b, 8)) return bad("HEVC SPS extensions"); return b->error ? bad("truncated HEVC SPS/VUI/HRD") : 0; } -static int hevc_sps(struct bits *b, struct call_video_bitstream *s, unsigned *id, unsigned *vps) { +static int hevc_sps(struct bits *b, struct call_video_bitstream *s, unsigned *id, unsigned *vps, unsigned *ctbs) { *vps = get(b, 4); if (get(b, 3) || !get(b, 1) || hevc_ptl(b, s)) return bad("HEVC layers/PTL"); *id = ue(b); if (*id > 15 || ue(b) != 1) return bad("HEVC SPS id/chroma"); @@ -189,7 +190,11 @@ static int hevc_sps(struct bits *b, struct call_video_bitstream *s, unsigned *id unsigned reorder = ue(b); ue(b); unsigned limit = call_video_dpb_limit(CALL_VIDEO_H265, s->level, w, h); if (!limit || s->dpb > limit || reorder) return bad("HEVC DPB/reorder"); - return hevc_sps_tail(b, poc, s->level); + unsigned ctb_log2; + if (hevc_sps_tail(b, poc, s->level, &ctb_log2)) return -1; + unsigned edge = 1u << ctb_log2; + *ctbs = ((w + edge - 1) / edge) * ((h + edge - 1) / edge); + return 0; } /* Одна активная VPS/SPS/PPS; повтор идентичных параметров в AU разрешён. @@ -202,7 +207,7 @@ int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct if (!scratch) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video RBSP allocation failed bytes=%zu", length); return -1; } struct call_video_bitstream stream = {0}, vp = {0}; unsigned sps_id = 0, pps_id = 0, vps_id = 0, sps_vps = 0, have_sps = 0, have_pps = 0, have_vps = 0, slices = 0; - unsigned extra_bits = 0, previous_mb = 0; int result = -1; + unsigned extra_bits = 0, dependent_slices = 0, output_flag = 0, ctbs = 0, previous_mb = 0; int result = -1; data += 4; length -= 4; size_t at = start_code(data, length, 0); if (at > 1) { bad("Annex B prefix"); goto done; } @@ -241,12 +246,12 @@ int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct if (get(&b, 1) || b.error) { bad("HEVC VPS extension/truncation/HRD"); goto done; } vp = parsed; vps_id = id; have_vps = 1; } else if (codec == 2 && type == 33) { - struct call_video_bitstream parsed = {0}; unsigned id, vps; - if (hevc_sps(&b, &parsed, &id, &vps)) goto done; - if (have_sps && (memcmp(&parsed, &stream, sizeof(parsed)) || id != sps_id || vps != sps_vps)) { + struct call_video_bitstream parsed = {0}; unsigned id, vps, blocks; + if (hevc_sps(&b, &parsed, &id, &vps, &blocks)) goto done; + if (have_sps && (memcmp(&parsed, &stream, sizeof(parsed)) || id != sps_id || vps != sps_vps || blocks != ctbs)) { bad("multiple HEVC SPS"); goto done; } - stream = parsed; sps_id = id; sps_vps = vps; have_sps = 1; + stream = parsed; sps_id = id; sps_vps = vps; ctbs = blocks; have_sps = 1; } else if ((codec == 1 && type == 8) || (codec == 2 && type == 34)) { unsigned id = ue(&b), sps = ue(&b); if (!have_sps || sps != sps_id || id > (codec == 1 ? 255u : 63u) || (have_pps && id != pps_id)) { @@ -258,8 +263,11 @@ int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct get(&b, 1); get(&b, 2); se(&b); se(&b); se(&b); get(&b, 2); if (get(&b, 1)) { bad("AVC redundant pictures"); goto done; } } else { - if (get(&b, 1) || get(&b, 1)) { bad("HEVC dependent slices/output flag"); goto done; } - extra_bits = get(&b, 3); + unsigned dependent = get(&b, 1), output = get(&b, 1), extra = get(&b, 3); + if (have_pps && (dependent != dependent_slices || output != output_flag || extra != extra_bits)) { + bad("inconsistent HEVC PPS slice flags"); goto done; + } + dependent_slices = dependent; output_flag = output; extra_bits = extra; } if (b.error) { bad("truncated PPS"); goto done; } pps_id = id; have_pps = 1; @@ -275,7 +283,20 @@ int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct if (type != 19 && type != 20) { bad("HEVC KEY is not IDR"); goto done; } unsigned first = get(&b, 1); get(&b, 1); if (ue(&b) != pps_id || (!slices && !first) || (slices && first)) { bad("HEVC IDR slice/PPS"); goto done; } - if (first) { get(&b, extra_bits); if (ue(&b) != 2) { bad("HEVC IDR slice type"); goto done; } } + // Разрешение dependent segments в PPS не делает первый слайс зависимым. + unsigned dependent = 0; + if (!first) { + if (dependent_slices) dependent = get(&b, 1); + unsigned address_bits = 0; + for (unsigned remaining = ctbs - 1; remaining; remaining >>= 1) ++address_bits; + unsigned address = get(&b, address_bits); + if (!address || address >= ctbs) { bad("HEVC IDR segment address"); goto done; } + } + if (!dependent) { + get(&b, extra_bits); + if (ue(&b) != 2) { bad("HEVC IDR slice type"); goto done; } + if (output_flag && !get(&b, 1)) { bad("HEVC IDR picture output disabled"); goto done; } + } } if (b.error) { bad("truncated IDR"); goto done; } ++slices; @@ -285,8 +306,9 @@ int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct || vp.profile != stream.profile || vp.level != stream.level || stream.dpb > vp.dpb))) { bad("missing/inconsistent parameter sets or IDR"); goto done; } - DEBUG_TRACE(DEBUG_CATEGORY_CALL, "video bitstream verified codec=%d profile=%u level=%u size=%ux%u dpb=%u slices=%u", - codec, stream.profile, stream.level, stream.width, stream.height, stream.dpb, slices); + DEBUG_TRACE(DEBUG_CATEGORY_CALL, + "video bitstream verified codec=%d profile=%u level=%u size=%ux%u dpb=%u slices=%u dependent=%u output_flag=%u", + codec, stream.profile, stream.level, stream.width, stream.height, stream.dpb, slices, dependent_slices, output_flag); *out = stream; result = 0; done: u_free(scratch); return result; diff --git a/tools/chatgui/src/live_video_encoder.cpp b/tools/chatgui/src/live_video_encoder.cpp index a5df8ef3..f281e608 100644 --- a/tools/chatgui/src/live_video_encoder.cpp +++ b/tools/chatgui/src/live_video_encoder.cpp @@ -7,6 +7,7 @@ #include extern "C" { #include "../../../src/stream/stream_media.h" +#include "../../../src/call/call_video_bitstream.h" #ifdef HAVE_FFMPEG #include #include @@ -86,6 +87,14 @@ AVPixelFormat softwareFormat(const AVCodec* codec, bool hardware = false) { bool verifiedProfile(const LiveVideoConfig& config, const QList& output) { if (output.size() != 1 || !output.front().key) return false; const auto& data = output.front().data; + call_video_format format{1, uint16_t(config.width), uint16_t(config.height), uint16_t(config.fps), + uint16_t(config.rotation), uint8_t(config.codec), uint8_t(config.level), uint32_t(config.bitrate), + uint8_t(config.codec == STREAM_MEDIA_H264 ? CALL_VIDEO_CONSTRAINED_BASELINE : CALL_VIDEO_MAIN)}; + if (call_video_check_key(&format, reinterpret_cast(data.constData()), data.size())) { + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "live call KEY verification failed codec=%d size=%dx%d level=%d bytes=%lld", + config.codec, config.width, config.height, config.level, (long long)data.size()); + return false; + } const uint8_t *header, *frame; size_t headerSize, frameSize; if (stream_media_key_parse(reinterpret_cast(data.constData()), data.size(), &header, &headerSize, &frame, &frameSize) != STREAM_OK) return false; @@ -284,6 +293,12 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) { return false; } verified = {frame}; + if (constrained && !verifiedProfile(config, verified)) { + m->error = "Encoder output violates the call bitstream contract"; + DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "call GPU probe rejected KEY name=%s pts=%llu attempt=%d", + name.constData(), (unsigned long long)pts, attempt); + return false; + } if (constrained) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "call GPU probe name=%s pts=%llu pipeline_frames=%d time_base=%d/%d", name.constData(), (unsigned long long)pts, attempt, c->time_base.num, c->time_base.den); @@ -316,9 +331,6 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) { break; } } - if (constrained && !verifiedProfile(config, output)) { - m->error = "Encoder output violates the requested profile"; break; - } continue; } DEBUG_INFO(DEBUG_CATEGORY_VIDEO,