Accept valid HEVC PPS flags and verify call GPU probes fully
This commit is contained in:
1 parent
0730fb6bdd
commit
f8827a9466
2 files changed
+50
-16
No files matched your search
@@ -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;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <deque>
|
||||
extern "C" {
|
||||
#include "../../../src/stream/stream_media.h"
|
||||
#include "../../../src/call/call_video_bitstream.h"
|
||||
#ifdef HAVE_FFMPEG
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/bsf.h>
|
||||
@@ -86,6 +87,14 @@ AVPixelFormat softwareFormat(const AVCodec* codec, bool hardware = false) {
|
||||
bool verifiedProfile(const LiveVideoConfig& config, const QList<LiveEncodedFrame>& 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<const uint8_t*>(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<const uint8_t*>(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,
|
||||
|
||||
Reference in new issue
Block a user