Browse Source

Add shared video parameter set and independent IDR validation

master
evgeny 5 days ago
parent
commit
53c6437301
  1. 220
      src/call/call_video_bitstream.c
  2. 20
      src/call/call_video_bitstream.h

220
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 <string.h>
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;
}

20
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
Loading…
Cancel
Save