diff --git a/src/call/call_video_bitstream.c b/src/call/call_video_bitstream.c new file mode 100644 index 00000000..d156d4e6 --- /dev/null +++ b/src/call/call_video_bitstream.c @@ -0,0 +1,220 @@ +#include "call_video_bitstream.h" +#include "../../lib/debug_config.h" +#include "../../lib/mem.h" +#include + +struct bits { const uint8_t *data; size_t size, at; int error; }; +static unsigned get(struct bits *b, unsigned count) { + if (count > 32 || b->at + count > b->size * 8) { b->error = 1; return 0; } + unsigned value = 0; + while (count--) { value = (value << 1) | ((b->data[b->at / 8] >> (7 - b->at % 8)) & 1); ++b->at; } + return value; +} +static unsigned ue(struct bits *b) { + unsigned zeros = 0; + while (!b->error && !get(b, 1)) if (++zeros > 20) { b->error = 1; break; } + return b->error ? 0 : (1u << zeros) - 1 + get(b, zeros); +} +static int se(struct bits *b) { unsigned v = ue(b); return v & 1 ? (int)((v + 1) / 2) : -(int)(v / 2); } +static int bad(const char *reason) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "video bitstream rejected reason=%s", reason); return -1; +} + +/* RBSP без emulation prevention; некорректные escape и размеры отклоняются. */ +static int rbsp(const uint8_t *data, size_t size, uint8_t *out, struct bits *bits) { + unsigned zeros = 0; size_t n = 0; + for (size_t i = 0; i < size; ++i) { + if (zeros == 2 && data[i] == 3) { + if (i + 1 == size || data[i + 1] > 3) return bad("emulation prevention"); + zeros = 0; continue; + } + out[n++] = data[i]; zeros = data[i] == 0 ? zeros + 1 : 0; + if (zeros > 2) zeros = 2; + } + *bits = (struct bits){out, n, 0, 0}; return 0; +} +static size_t start_code(const uint8_t *data, size_t size, size_t at) { + for (; at + 2 < size; ++at) if (!data[at] && !data[at + 1] && data[at + 2] == 1) return at; + return size; +} + +/* HRD AVC: проверяем заявленные BR/CPB по уровню, не обещаем их при отсутствии HRD. */ +static void avc_hrd(struct bits *b, unsigned level) { + unsigned count = ue(b) + 1, br_scale = get(b, 4), cpb_scale = get(b, 4); + if (count > 32) { b->error = 1; return; } + static const unsigned levels[] = {10,11,12,13,20,21,22,30,31,32,40,41,42,50,51,52,60,61,62}; + static const unsigned br[] = {64,192,384,768,2000,4000,4000,10000,14000,20000,20000,50000,50000, + 135000,240000,240000,240000,480000,800000}; + static const unsigned cpb[] = {175,500,1000,2000,2000,4000,4000,10000,14000,20000,25000,62500,62500, + 135000,240000,240000,240000,480000,800000}; + unsigned index = 0; while (index < sizeof(levels) / sizeof(*levels) && levels[index] != level) ++index; + if (index == sizeof(levels) / sizeof(*levels)) { b->error = 1; return; } + for (unsigned i = 0; i < count; ++i) { + uint64_t bitrate = ((uint64_t)ue(b) + 1) << (6 + br_scale); + uint64_t buffer = ((uint64_t)ue(b) + 1) << (4 + cpb_scale); + get(b, 1); + if (bitrate > (uint64_t)br[index] * 1200 || buffer > (uint64_t)cpb[index] * 1200) b->error = 1; + } + get(b, 20); +} +static int avc_sps(struct bits *b, struct call_video_bitstream *s, unsigned *id) { + unsigned profile = get(b, 8), constraints = get(b, 8); s->level = get(b, 8); + if (profile != 66 || (constraints & 3)) return bad("AVC profile/constraints"); + s->profile = constraints & 64 ? CALL_VIDEO_CONSTRAINED_BASELINE : CALL_VIDEO_BASELINE; + *id = ue(b); if (*id > 31 || ue(b) > 12) return bad("AVC SPS id/frame_num"); + unsigned poc = ue(b); + if (poc == 0) { if (ue(b) > 12) return bad("AVC POC size"); } + else if (poc == 1) { + get(b, 1); se(b); se(b); unsigned count = ue(b); + if (count > 255) return bad("AVC POC cycle"); + for (unsigned i = 0; i < count; ++i) se(b); + } else if (poc != 2) return bad("AVC POC type"); + s->dpb = ue(b); get(b, 1); + unsigned w = ue(b) + 1, h = ue(b) + 1; + if (w > 256 || h > 256 || !get(b, 1)) return bad("AVC coded geometry/interlace"); + get(b, 1); unsigned left = 0, right = 0, top = 0, bottom = 0; + if (get(b, 1)) { left = ue(b); right = ue(b); top = ue(b); bottom = ue(b); } + if ((uint64_t)(left + right) * 2 >= w * 16 || (uint64_t)(top + bottom) * 2 >= h * 16) return bad("AVC crop"); + s->width = w * 16 - (left + right) * 2; s->height = h * 16 - (top + bottom) * 2; + unsigned limit = call_video_dpb_limit(CALL_VIDEO_H264, s->level, w * 16, h * 16); + if (!limit || s->dpb > limit) return bad("AVC DPB"); + if (get(b, 1)) { + if (get(b, 1)) { if (get(b, 8) == 255) { get(b, 16); get(b, 16); } } + if (get(b, 1)) get(b, 1); + if (get(b, 1)) { get(b, 4); if (get(b, 1)) get(b, 24); } + if (get(b, 1)) { ue(b); ue(b); } + if (get(b, 1)) { get(b, 32); get(b, 32); get(b, 1); } + unsigned nal = get(b, 1); if (nal) avc_hrd(b, s->level); + unsigned vcl = get(b, 1); if (vcl) avc_hrd(b, s->level); + if (nal || vcl) get(b, 1); + get(b, 1); + if (get(b, 1)) { + get(b, 1); ue(b); ue(b); ue(b); ue(b); + unsigned reorder = ue(b), buffer = ue(b); + if (reorder || buffer > limit || buffer < s->dpb) return bad("AVC reorder/DPB restriction"); + } + } + return b->error ? bad("truncated AVC SPS/HRD") : 0; +} +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. + unsigned progressive = get(b, 1), 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 || !progressive || interlaced) return bad("HEVC profile/tier/progressive"); + return b->error ? bad("truncated HEVC PTL") : 0; +} +static int hevc_sps(struct bits *b, struct call_video_bitstream *s, unsigned *id, unsigned *vps) { + *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"); + unsigned w = ue(b), h = ue(b), left = 0, right = 0, top = 0, bottom = 0; + if (!w || !h || w > 4096 || h > 4096) return bad("HEVC coded geometry"); + if (get(b, 1)) { left = ue(b); right = ue(b); top = ue(b); bottom = ue(b); } + if ((uint64_t)(left + right) * 2 >= w || (uint64_t)(top + bottom) * 2 >= h) return bad("HEVC crop"); + s->width = w - (left + right) * 2; s->height = h - (top + bottom) * 2; + unsigned luma = ue(b), chroma = ue(b), poc = ue(b); + if (luma || chroma || poc > 12) return bad("HEVC bit depth/POC"); + get(b, 1); s->dpb = ue(b) + 1; + 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 b->error ? bad("truncated HEVC SPS") : 0; +} + +/* Одна активная VPS/SPS/PPS; повтор идентичных параметров в AU разрешён. + * IDR и PPS id проверяются по slice header, а не по флагу платформенного API. */ +int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct call_video_bitstream *out) { + if ((codec != 1 && codec != 2) || !data || !out || length < 9 || length > CALL_VIDEO_MAX_FRAME) return bad("KEY arguments"); + uint32_t config = (uint32_t)data[0] << 24 | (uint32_t)data[1] << 16 | (uint32_t)data[2] << 8 | data[3]; + if (!config || config > 65536 || config >= length - 4) return bad("KEY configuration length"); + uint8_t *scratch = u_malloc(length); + 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; + data += 4; length -= 4; + size_t at = start_code(data, length, 0); + if (at > 1) { bad("Annex B prefix"); goto done; } + while (at < length) { + size_t begin = at + 3, end = start_code(data, length, begin); + at = end; + while (end > begin && data[end - 1] == 0) --end; + unsigned header = codec == 1 ? 1 : 2; + if (end <= begin + header || (data[begin] & 128)) { bad("NAL header/size"); goto done; } + unsigned type = codec == 1 ? data[begin] & 31 : (data[begin] >> 1) & 63; + if (codec == 2 && ((data[begin] & 1) || data[begin + 1] != 1)) { bad("HEVC NAL layer/temporal id"); goto done; } + struct bits b; + if (rbsp(data + begin + header, end - begin - header, scratch, &b)) goto done; + if (codec == 1 && type == 7) { + struct call_video_bitstream parsed = {0}; unsigned id; + if (avc_sps(&b, &parsed, &id)) goto done; + if (have_sps && (memcmp(&parsed, &stream, sizeof(parsed)) || id != sps_id)) { bad("multiple AVC SPS"); goto done; } + stream = parsed; sps_id = id; have_sps = 1; + } else if (codec == 2 && type == 32) { + unsigned id = get(&b, 4); get(&b, 2); + if (get(&b, 6) || get(&b, 3) || !get(&b, 1) || get(&b, 16) != 65535 || hevc_ptl(&b, &vp)) { + bad("HEVC VPS layers/PTL"); goto done; + } + get(&b, 1); unsigned dpb = ue(&b) + 1, reorder = ue(&b); ue(&b); + if (b.error || reorder || dpb > 16 || (have_vps && id != vps_id)) { bad("HEVC VPS DPB"); goto done; } + vp.dpb = dpb; 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)) { + bad("multiple HEVC SPS"); goto done; + } + stream = parsed; sps_id = id; sps_vps = vps; 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)) { + bad("PPS/SPS reference"); goto done; + } + if (codec == 1) { + unsigned entropy = get(&b, 1), fields = get(&b, 1), groups = ue(&b); + if (entropy || fields || groups || ue(&b) > 15 || ue(&b) > 15) { bad("AVC PPS tools"); goto done; } + 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); + } + if (b.error) { bad("truncated PPS"); goto done; } + pps_id = id; have_pps = 1; + } else if ((codec == 1 && type >= 1 && type <= 5) || (codec == 2 && type <= 31)) { + if (!have_sps || !have_pps || begin < config) { bad("slice before configuration"); goto done; } + if (codec == 1) { + unsigned mb = ue(&b), slice = ue(&b), pps = ue(&b); + if (type != 5 || slice > 9 || slice % 5 != 2 || pps != pps_id || (!slices && mb) || (slices && mb <= previous_mb)) { + bad("AVC KEY is not independent ordered IDR"); goto done; + } + previous_mb = mb; + } else { + 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; } } + } + if (b.error) { bad("truncated IDR"); goto done; } + ++slices; + } + } + if (!slices || !have_sps || !have_pps || (codec == 2 && (!have_vps || sps_vps != vps_id + || vp.profile != stream.profile || vp.level != stream.level || stream.dpb > vp.dpb))) { + bad("missing/inconsistent parameter sets or IDR"); goto done; + } + *out = stream; result = 0; +done: + u_free(scratch); return result; +} +int call_video_check_key(const struct call_video_format *f, const uint8_t *data, size_t length) { + struct call_video_bitstream s; + if (!f || call_video_inspect_key(f->codec, data, length, &s)) return -1; + if (s.width != f->width || s.height != f->height || s.level > f->level + || (s.profile != f->profile && !(f->codec == 1 && f->profile == 1 && s.profile == 2))) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "video KEY mismatch actual=%ux%u profile=%u level=%u dpb=%u expected=%ux%u profile=%u level=%u", + s.width, s.height, s.profile, s.level, s.dpb, f->width, f->height, f->profile, f->level); return -1; + } + return 0; +} diff --git a/src/call/call_video_bitstream.h b/src/call/call_video_bitstream.h new file mode 100644 index 00000000..647785a6 --- /dev/null +++ b/src/call/call_video_bitstream.h @@ -0,0 +1,20 @@ +/* Общая проверка независимого KEY (configuration + Annex B AU). Не переписывает поток. + * Проверяет ограниченный набор для звонков: AVC Baseline/CB, HEVC Main/Main tier, + * progressive 8-bit 4:2:0, один spatial/temporal layer, без reorder. DPB — по Annex A. + * Результат публикуется целиком только после успешной проверки VPS/SPS/PPS и IDR. + * Все функции доступны worker-потокам; состояния и платформенных объектов нет. */ +#ifndef CALL_VIDEO_BITSTREAM_H +#define CALL_VIDEO_BITSTREAM_H +#include "call_video.h" +#ifdef __cplusplus +extern "C" { +#endif +struct call_video_bitstream { + unsigned width, height, level, profile, dpb; +}; +int call_video_inspect_key(int codec, const uint8_t *data, size_t length, struct call_video_bitstream *out); +int call_video_check_key(const struct call_video_format *format, const uint8_t *data, size_t length); +#ifdef __cplusplus +} +#endif +#endif