nvenc: intra refresh для low-latency multi-source push
Phase 5e revisited: visual artefacts в bottom rows 16-cell grid'а оказались не race condition в cuframes ring buffer (как первоначальная гипотеза), а burstiness IDR. Сложный grid (много границ между ячейками) генерит огромные IDR (~400 КБ), которые переполняют mediamtx writeQueueSize=256 и discard'ятся → VLC видит покалеченный bitstream. Правильное решение — canonical low-latency streaming pattern: вместо периодических IDR использовать NVENC intra refresh. Вместо одного gigant'а intra-кадра раз в секунду, кодируем N столбцов intra-блоков в каждом кадре. За intra_refresh_period кадров — полный refresh. Bitstream становится почти ровным — все кадры одинакового размера, никаких spike'ов. Это индустриальный стандарт для low-latency: WebRTC, Twitch low-latency mode, GeForce NOW, Zoom — все используют intra refresh без IDR. Trade-off: новый клиент ждёт ~1 период (1 сек) для построения reference frame. Для CCTV приемлемо. ffmpeg snapshot one-shot не работает на таких потоках (нужен полный warmup), но VLC/TV/Frigate handles штатно. Содержимое: - cfc_encoder_config_t: добавлены intra_refresh + intra_refresh_period. - nvenc.c: при enableIntraRefresh=1 устанавливается intraRefreshPeriod/intraRefreshCnt и идуs IDR period отключается через NVENC_INFINITE_GOPLENGTH. - examples/grid_record: флаг --intra-refresh (период = fps). Live-validated: rtsp://192.168.88.23:554/load16ir - 16-cell 1080p 4×4 6Mbps intra refresh ON - mediamtx tишина: ни одного 'reader is too slow' warning'а - VLC connect → чистая картинка во всех 16 ячейках - 100 кадров логи: '1 IDR' (только начальный) — после стартового никаких больше IDR не было, ровный bitstream Этот flag не default для случая single-source (Phase 1 simple_record) — там IDR-based GOP всё ещё лучше (полный keyframe = быстрый connect). Включать осознанно для multi-source grid'ов через --intra-refresh.
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
+16
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user