diff --git a/examples/grid_record.c b/examples/grid_record.c index 525a4ca..03609eb 100644 --- a/examples/grid_record.c +++ b/examples/grid_record.c @@ -95,6 +95,7 @@ int main(int argc, char **argv) int fps = 25, bitrate = 4000, max_seconds = 0; int out_w = 1920, out_h = 1080; int border_thickness = 0; /* 0 = без border'ов */ + int intra_refresh = 0; /* 1 = intra refresh вместо IDR (low-latency multi-source) */ cfc_composer_cell_t cells[MAX_CELLS] = { 0 }; static char cell_keys[MAX_CELLS][64]; int num_cells = 0; @@ -135,10 +136,11 @@ int main(int argc, char **argv) {"mqtt-instance", required_argument, 0, 'I'}, /* instance ID для топиков */ {"mqtt-user", required_argument, 0, 'U'}, {"mqtt-pass", required_argument, 0, 'P'}, + {"intra-refresh", no_argument, 0, 'R'}, {0, 0, 0, 0}, }; int c; - while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:", opts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "o:c:f:b:W:H:s:r:i:t:C:M:I:U:P:R", opts, NULL)) != -1) { switch (c) { case 'o': out_path = optarg; break; case 'c': @@ -175,6 +177,7 @@ int main(int argc, char **argv) case 'I': mqtt_instance = optarg; break; case 'U': mqtt_user = optarg; break; case 'P': mqtt_pass = optarg; break; + case 'R': intra_refresh = 1; break; case 't': { if (num_texts >= MAX_CELLS) { fprintf(stderr, "max %d texts\n", MAX_CELLS); return 1; } /* Опциональный prefix "id=NAME:" — задаёт control-plane ID. */ @@ -391,7 +394,12 @@ int main(int argc, char **argv) .gop_size = fps, .num_b_frames = 0, .preset = "ll", + .intra_refresh = intra_refresh, + .intra_refresh_period = fps, /* полный цикл за 1 секунду */ }; + if (intra_refresh) { + fprintf(stderr, "[grid_record] intra refresh ON (period=%d кадров)\n", fps); + } cfc_encoder_t *enc = NULL; if (cfc_encoder_create(&ecfg, &enc) != 0) { fprintf(stderr, "cfc_encoder_create failed\n"); diff --git a/include/cuframes_composer/nvenc.h b/include/cuframes_composer/nvenc.h index d40fbf4..4be270a 100644 --- a/include/cuframes_composer/nvenc.h +++ b/include/cuframes_composer/nvenc.h @@ -47,6 +47,13 @@ typedef struct cfc_encoder_config { /* Пресет — соответствует NV_ENC_TUNING_INFO + preset GUID. * "ll" = low-latency, "p4" = preset P4 (баланс), "p7" = highest quality. */ const char *preset; /* "ll", "p4", "p7" — по умолчанию "ll" */ + + /* Intra refresh — вместо периодических IDR кодирует N столбцов intra-блоков + * в каждом кадре, за period кадров полный refresh. Ровный bitrate без + * spike'ов, идеально для low-latency multi-source RTSP push. + * 0 = выключено (GOP-based с IDR раз в gop_size). */ + int32_t intra_refresh; /* 0 = off, 1 = on */ + int32_t intra_refresh_period; /* кадров на полный цикл (например 25 = 1 сек @ 25fps) */ } cfc_encoder_config_t; typedef struct cfc_encoder cfc_encoder_t; diff --git a/src/nvenc.c b/src/nvenc.c index b606a2e..951c933 100644 --- a/src/nvenc.c +++ b/src/nvenc.c @@ -268,6 +268,22 @@ int cfc_encoder_create(const cfc_encoder_config_t *cfg, cfc_encoder_t **out) ec.encodeCodecConfig.h264Config.repeatSPSPPS = 1; ec.encodeCodecConfig.h264Config.idrPeriod = ec.gopLength; + /* Intra refresh — отключает периодические IDR, вместо этого N столбцов + * пикселей кодируются как intra в каждом кадре. За intra_refresh_period + * кадров полный refresh. Результат — ровный bitrate без spike'ов, идеально + * для multi-source RTSP push в mediamtx с маленьким writeQueueSize. + * Trade-off: новый клиент ждёт ~1 период для построения reference frame + * (для CCTV приемлемо). См. также NVENC SDK §Picture Encoding/Intra Refresh. */ + if (cfg->intra_refresh) { + int period = cfg->intra_refresh_period > 0 ? cfg->intra_refresh_period : 25; + ec.encodeCodecConfig.h264Config.enableIntraRefresh = 1; + ec.encodeCodecConfig.h264Config.intraRefreshPeriod = period; + ec.encodeCodecConfig.h264Config.intraRefreshCnt = period; + /* Отключаем периодические IDR — они не нужны при intra refresh. */ + ec.encodeCodecConfig.h264Config.idrPeriod = NVENC_INFINITE_GOPLENGTH; + ec.gopLength = NVENC_INFINITE_GOPLENGTH; + } + /* 4) Init encoder. */ NV_ENC_INITIALIZE_PARAMS ip = { 0 }; ip.version = NV_ENC_INITIALIZE_PARAMS_VER;