From abf7ccf56654c5bdd080d29adc0fdfcdf989455f Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 14 Oct 2026 21:38:05 +0200 Subject: [PATCH] Probe x265 bitrate switching at scheduled IDR with encoder replacement --- tools/chatgui/tests/probe_call_x265.cpp | 75 +++++++++++++++++++++---- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/tools/chatgui/tests/probe_call_x265.cpp b/tools/chatgui/tests/probe_call_x265.cpp index 81e81ebb..1badf6c8 100644 --- a/tools/chatgui/tests/probe_call_x265.cpp +++ b/tools/chatgui/tests/probe_call_x265.cpp @@ -4,6 +4,7 @@ * Запускается вручную; код 2 означает, что backend не прошёл пробу адаптации. */ #include #include +#include #include #include #include "debug_config.h" @@ -12,7 +13,9 @@ struct Probe { const x265_api* api = nullptr; x265_param *parameters = nullptr, *actual = nullptr; x265_encoder* encoder = nullptr; + FILE* dump = nullptr; ~Probe() { + if (dump && fclose(dump)) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe output close failed"); if (encoder) api->encoder_close(encoder); if (parameters) api->param_free(parameters); if (actual) api->param_free(actual); @@ -21,11 +24,24 @@ struct Probe { int main(int argc, char** argv) { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - bool frameRc = argc == 2 && !strcmp(argv[1], "--frame-rc"); - if (argc > 1 && !frameRc) { - DEBUG_ERROR(DEBUG_CATEGORY_CALL, "Usage: probe_call_x265 [--frame-rc]"); return 1; + bool frameRc = false, keyReconfig = false, keyReopen = false; + const char* outputPath = nullptr; + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--output") && i + 1 < argc && !outputPath) outputPath = argv[++i]; + else if (!frameRc && !keyReconfig && !keyReopen && !strcmp(argv[i], "--frame-rc")) frameRc = true; + else if (!frameRc && !keyReconfig && !keyReopen && !strcmp(argv[i], "--keyframe-reconfig")) keyReconfig = true; + else if (!frameRc && !keyReconfig && !keyReopen && !strcmp(argv[i], "--keyframe-reopen")) keyReopen = true; + else { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, + "Usage: probe_call_x265 [--frame-rc|--keyframe-reconfig|--keyframe-reopen] [--output path]"); return 1; + } } + bool keyMode = keyReconfig || keyReopen; Probe p; p.api = x265_api_get(8); + if (outputPath) { + p.dump = fopen(outputPath, "wb"); + if (!p.dump) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe output open failed path=%s", outputPath); return 1; } + } if (!p.api || p.api->sizeof_param != sizeof(x265_param) || p.api->sizeof_picture != sizeof(x265_picture)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 8-bit API unavailable or ABI mismatch"); return 1; } @@ -39,7 +55,7 @@ int main(int argc, char** argv) { config->levelIdc = 31; config->bHighTier = 0; config->maxNumReferences = 1; config->bframes = 0; config->lookaheadDepth = 0; config->frameNumThreads = 1; config->keyframeMax = 30; config->keyframeMin = 1; config->bOpenGOP = 0; - config->bRepeatHeaders = 0; config->bEmitInfoSEI = 0; config->bEmitHRDSEI = 0; + config->bRepeatHeaders = keyMode || p.dump; config->bEmitInfoSEI = 0; config->bEmitHRDSEI = 0; config->rc.rateControlMode = X265_RC_ABR; config->rc.bitrate = 4000; config->rc.vbvMaxBitrate = 4000; config->rc.vbvBufferSize = 1000; config->logLevel = X265_LOG_WARNING; @@ -49,19 +65,40 @@ int main(int argc, char** argv) { } p.encoder = p.api->encoder_open(config); if (!p.encoder) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe open failed"); return 1; } - DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 probe version=%s frame_rc=%d size=640x360 fps=30 level=31 tier=main vbv_buffer_kbits=1000", - p.api->version_str, frameRc); + DEBUG_INFO(DEBUG_CATEGORY_CALL, + "x265 probe version=%s frame_rc=%d key_reconfig=%d key_reopen=%d size=640x360 fps=30 level=31 tier=main vbv_kbits=1000", + p.api->version_str, frameRc, keyReconfig, keyReopen); std::vector pixels(640 * 360 * 3 / 2, 128); uint32_t random = 1; bool passed = true; for (int phase = 0; phase < 3; phase++) { int requested = phase == 1 ? 300 : 4000; - p.api->encoder_parameters(p.encoder, config); - config->rc.bitrate = config->rc.vbvMaxBitrate = requested; - int result = phase ? p.api->encoder_reconfig(p.encoder, config) : 0; + if (!keyMode) { + p.api->encoder_parameters(p.encoder, config); + config->rc.bitrate = config->rc.vbvMaxBitrate = requested; + } + int result = phase && !keyMode ? p.api->encoder_reconfig(p.encoder, config) : 0; if (result) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 reconfig failed phase=%d result=%d", phase, result); return 1; } uint64_t bytes = 0; + int measureFrom = keyMode ? 60 : 30; for (int frame = 0; frame < 120; frame++) { + // Запрос между ключевыми кадрами; переключение только перед следующим плановым IDR. + if (keyMode && phase && frame == 7) + DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 queued bitrate phase=%d frame=%d requested_kbps=%d next_idr=30", phase, frame, requested); + if (keyMode && phase && frame == 30) { + p.api->encoder_parameters(p.encoder, config); + config->rc.bitrate = config->rc.vbvMaxBitrate = requested; + if (keyReopen) { + x265_encoder* candidate = p.api->encoder_open(config); + if (!candidate) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 reopen failed phase=%d", phase); return 1; } + p.api->encoder_close(p.encoder); p.encoder = candidate; + } else { + result = p.api->encoder_reconfig(p.encoder, config); + if (result) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 key reconfig failed phase=%d result=%d", phase, result); return 1; } + } + DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 applying at IDR phase=%d frame=%d requested_kbps=%d reopen=%d wait_frames=23", + phase, frame, requested, keyReopen); + } // Детерминированная сложная сцена: малая скорость не объясняется статичным изображением. for (int i = 0; i < 640 * 360; i++) { random = random * 1664525u + 1013904223u; @@ -73,25 +110,39 @@ int main(int argc, char** argv) { input.planes[2] = pixels.data() + 640 * 360 * 5 / 4; input.stride[0] = 640; input.stride[1] = input.stride[2] = 320; input.pts = (phase * 120 + frame) * 1000000ll / 30; + if (keyMode && frame % 30 == 0) input.sliceType = X265_TYPE_IDR; x265_nal* nals = nullptr; uint32_t count = 0; result = p.api->encoder_encode(p.encoder, &nals, &count, &input, &output); if (result != 1 || output.pts != input.pts) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe delay/error phase=%d frame=%d result=%d pts=%lld expected=%lld", phase, frame, result, (long long)output.pts, (long long)input.pts); return 1; } - if (frame >= 30) for (unsigned i = 0; i < count; i++) bytes += nals[i].sizeBytes; - if (frame < 3) { + bool idr = false, vps = false, sps = false, pps = false; + for (unsigned i = 0; i < count; i++) { + if (frame >= measureFrom) bytes += nals[i].sizeBytes; + idr |= nals[i].type == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nals[i].type == NAL_UNIT_CODED_SLICE_IDR_N_LP; + vps |= nals[i].type == NAL_UNIT_VPS; sps |= nals[i].type == NAL_UNIT_SPS; pps |= nals[i].type == NAL_UNIT_PPS; + if (p.dump && fwrite(nals[i].payload, 1, nals[i].sizeBytes, p.dump) != nals[i].sizeBytes) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe output write failed phase=%d frame=%d", phase, frame); return 1; + } + } + if (keyMode && frame % 30 == 0 && !(idr && vps && sps && pps)) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 incomplete IDR phase=%d frame=%d idr=%d vps=%d sps=%d pps=%d", + phase, frame, idr, vps, sps, pps); return 1; + } + if (frame < 3 || (keyMode && frame == 30)) { p.api->encoder_parameters(p.encoder, p.actual); DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 probe phase=%d frame=%d requested_kbps=%d getter_kbps=%d vbv_kbps=%d level=%d", phase, frame, requested, p.actual->rc.bitrate, p.actual->rc.vbvMaxBitrate, p.actual->levelIdc); } } - uint64_t measured = bytes * 8 / 3; + uint64_t measured = bytes * 8 * 30 / (120 - measureFrom); bool matches = measured >= (uint64_t)requested * 500 && measured <= (uint64_t)requested * 1500; DEBUG_INFO(DEBUG_CATEGORY_CALL, "x265 probe phase=%d target_bps=%d measured_bps=%llu acceptable=%d", phase, requested * 1000, (unsigned long long)measured, matches); if (!matches) passed = false; } + if (p.dump && fflush(p.dump)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 probe output flush failed"); return 1; } if (!passed) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "x265 backend unsuitable: measured bitrate does not follow commands"); return 2; }