diff --git a/tests/test_call_video.c b/tests/test_call_video.c index e22021c3..8d00f625 100644 --- a/tests/test_call_video.c +++ b/tests/test_call_video.c @@ -156,7 +156,58 @@ static void bitstreams(void) { } puts("video: real AVC Baseline/CB and HEVC IDR, multi-picture DPB, atomic malformed parse PASS"); } +static void put_bits(uint8_t *data, unsigned *at, unsigned value, unsigned count) { + while (count--) { data[*at / 8] |= ((value >> count) & 1u) << (7 - *at % 8); ++*at; } +} +/* Только синтаксис заголовков: entropy payload здесь не нужен. SPS fixture: CTU32, 40*23 CTB, address=10 bits. */ +static size_t hevc_segment(uint8_t *data, unsigned first, unsigned enabled, unsigned dependent, + unsigned output, unsigned visible, unsigned extra, unsigned address) { + memset(data, 0, 16); data[2] = 1; data[3] = 40; data[4] = 1; + unsigned at = 40; + put_bits(data, &at, first, 1); put_bits(data, &at, 0, 1); put_bits(data, &at, 1, 1); // first, no_output_prior, PPS id=0. + if (!first) { + if (enabled) put_bits(data, &at, dependent, 1); + put_bits(data, &at, address, 10); + } + if (!dependent) { + put_bits(data, &at, 0, extra); put_bits(data, &at, 3, 3); // reserved bits, I slice ue(2). + if (output) put_bits(data, &at, visible, 1); + } + put_bits(data, &at, 1, 1); + return (at + 7) / 8; +} +static void hevc_pps_flags(void) { + uint8_t bytes[8192]; struct call_video_bitstream stream; + int size = call_video_probe_key(CALL_VIDEO_H265, CALL_VIDEO_MAIN, bytes, sizeof(bytes)); CHECK(size > 0); + size_t pps = 0, config = 4 + ((unsigned)bytes[2] << 8 | bytes[3]); + for (size_t i = 4; i + 5 < config; ++i) + if (!bytes[i] && !bytes[i + 1] && bytes[i + 2] == 1 && bytes[i + 3] == 68) pps = i + 5; + CHECK(pps && bytes[pps] == 0xc0); // Оба id=0, slice flags отсутствуют. + bytes[pps] |= 0x20; // Реальный IDR остаётся валиден, когда PPS разрешает dependent segments. + CHECK(!call_video_inspect_key(CALL_VIDEO_H265, bytes, size, &stream)); + for (unsigned flags = 0; flags < 4; ++flags) for (unsigned extra = 0; extra <= 3; extra += 3) { + unsigned dependent = flags & 1, output = flags >> 1; + bytes[pps] = 0xc0 | dependent << 5 | output << 4 | extra << 1; + size_t end = config + hevc_segment(bytes + config, 1, dependent, 0, output, 1, extra, 0); + CHECK(!call_video_inspect_key(CALL_VIDEO_H265, bytes, end, &stream)); + size_t next = end + hevc_segment(bytes + end, 0, dependent, 0, output, 1, extra, 1); + CHECK(!call_video_inspect_key(CALL_VIDEO_H265, bytes, next, &stream)); + next = end + hevc_segment(bytes + end, 0, dependent, dependent, output, 1, extra, 1); + CHECK(!call_video_inspect_key(CALL_VIDEO_H265, bytes, next, &stream)); + for (unsigned address = 0; address <= 1023; address += 1023) { + next = end + hevc_segment(bytes + end, 0, dependent, dependent, output, 1, extra, address); + CHECK(call_video_inspect_key(CALL_VIDEO_H265, bytes, next, &stream) < 0); + } + if (output) { + next = end + hevc_segment(bytes + end, 0, dependent, 0, output, 0, extra, 1); + CHECK(call_video_inspect_key(CALL_VIDEO_H265, bytes, next, &stream) < 0); + end = config + hevc_segment(bytes + config, 1, dependent, 0, output, 0, extra, 0); + CHECK(call_video_inspect_key(CALL_VIDEO_H265, bytes, end, &stream) < 0); + } + } + puts("video: HEVC PPS flags, independent/dependent IDR segments, output flag and CTB address validation PASS"); +} int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); - capabilities(); envelopes(); roundtrip(); rejected(); bitstreams(); return 0; + capabilities(); envelopes(); roundtrip(); rejected(); bitstreams(); hevc_pps_flags(); return 0; } diff --git a/tools/chatgui/tests/test_call_video_gpu.cpp b/tools/chatgui/tests/test_call_video_gpu.cpp index 295ce51f..f465fa98 100644 --- a/tools/chatgui/tests/test_call_video_gpu.cpp +++ b/tools/chatgui/tests/test_call_video_gpu.cpp @@ -23,7 +23,7 @@ static std::map contexts; static QByteArray selected; static unsigned opened = 0, software = 0; static int depth = 1; -static bool burst = false, badReplacement = false; +static bool burst = false, badReplacement = false, badProbe = false; extern "C" const AVCodec* __real_avcodec_find_encoder_by_name(const char*); extern "C" const AVCodec* __wrap_avcodec_find_encoder_by_name(const char* name) { @@ -56,6 +56,17 @@ extern "C" int __wrap_avcodec_open2(AVCodecContext* context, const AVCodec* code parameters += QByteArray::number(h264 || selected.endsWith("_qsv") ? context->level : context->level / 3); av_dict_set(options, h264 ? "x264-params" : "x265-params", parameters.constData(), 0); int result = __real_avcodec_open2(context, codec, options); + if (result >= 0 && !h264 && selected.endsWith("_amf")) { + // AMF разрешает dependent segments в PPS. Первый слайс остаётся независимым, CABAC не меняется. + auto* data = context->extradata; + for (int i = 0; i + 5 < context->extradata_size; ++i) { + if (!data[i] && !data[i + 1] && data[i + 2] == 1 && data[i + 3] == 68) { + if (data[i + 5] != 0xc0) return AVERROR_INVALIDDATA; + data[i + 5] |= 0x20; + if (badProbe) data[i + 4] = 2; // Неподдерживаемый temporal_id: отбрасывается общим call verifier. + } + } + } if (result >= 0) contexts[context].serial = ++opened; return result; } @@ -93,12 +104,19 @@ class GpuTest : public QObject { Q_OBJECT private slots: void initTestCase() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); av_log_set_level(AV_LOG_ERROR); } - void init() { selected.clear(); opened = software = 0; depth = 1; burst = badReplacement = false; QVERIFY(contexts.empty()); } + void init() { selected.clear(); opened = software = 0; depth = 1; burst = badReplacement = badProbe = false; QVERIFY(contexts.empty()); } void cleanup() { QVERIFY(contexts.empty()); } void gpuOnly() { CallVideoEncoder encoder; call_video_format format{1, 640, 360, 30, 0, 1, 31, 4000000, 2}; QVERIFY(!encoder.open(format, 1000000)); QCOMPARE(software, 0u); } + void incompatibleProbe() { + selected = "hevc_amf"; badProbe = true; + CallVideoEncoder encoder; call_video_format format{1, 640, 360, 30, 0, 2, 93, 4000000, 1}; + QVERIFY(!encoder.open(format, 1000000)); + QVERIFY2(encoder.error().contains("call bitstream contract"), qPrintable(encoder.error())); + QCOMPARE(software, 0u); + } void pipeline_data() { QTest::addColumn("backend"); QTest::addColumn("batch"); QTest::addColumn("reject"); for (auto name : {"h264_nvenc", "hevc_nvenc", "h264_amf", "hevc_amf", "h264_qsv", "hevc_qsv"})