Files
cuframes-composer/include/cuframes_composer/nvenc.h
T
gx b8661f4017 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.
2026-06-03 07:05:35 +01:00

115 lines
6.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* cuframes-composer — обвязка вокруг NVIDIA NVENC API.
*
* Динамически грузит libnvidia-encode.so через dlopen, чтобы пакет
* cuframes-composer оставался под LGPL-2.1+ без статической линковки
* проприетарного SDK. См. дизайн-документ часть 1.6.
*
* Принимает на вход CUdeviceptr на NV12 frame (zero-copy через
* NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR + nvEncRegisterResource).
* Выдаёт сжатый H.264 bitstream через callback (caller записывает его
* в файл / RTP-пакетизирует / etc).
*
* Lifecycle:
* create(cfg) — open session, init encoder
* encode_frame(...) — на каждый входной кадр (один CUdeviceptr)
* flush(...) — в конце потока, чтобы вытащить остатки B-кадров
* destroy(...) — закрыть session, выгрузить SDK
*
* Поток должен быть single-threaded для одного encoder'а (NVENC API
* не реентрабельный для одной сессии).
*
* Лицензия: LGPL-2.1+
*/
#ifndef CUFRAMES_COMPOSER_NVENC_H
#define CUFRAMES_COMPOSER_NVENC_H
#include <cuda.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Параметры кодировщика. Для Phase 1 минимальный набор; в будущих фазах
* будут расширяться для RTSP (rate control, GOP, intra-refresh, и т.п.). */
typedef struct cfc_encoder_config {
CUcontext cuda_ctx; /* CUDA-контекст, в котором лежат входные VMM-буферы */
int32_t width; /* ширина кадра в пикселях */
int32_t height; /* высота кадра в пикселях */
int32_t fps_num; /* числитель частоты кадров (25) */
int32_t fps_den; /* знаменатель частоты кадров (1) */
int32_t bitrate_kbps; /* битрейт в килобитах в секунду (5000 = 5 Мбит/с) */
int32_t gop_size; /* интервал между keyframe'ами в кадрах (25 = 1 IDR в секунду) */
int32_t num_b_frames; /* B-кадры (0 для low-latency RTSP) */
/* Пресет — соответствует 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;
/* Callback для записанного H.264 bitstream'а. Вызывается синхронно из
* cfc_encoder_encode_frame / cfc_encoder_flush. Указатель действителен только
* на время вызова (буфер NVENC будет разблокирован после возврата). */
typedef void (*cfc_encoder_output_cb)(
const uint8_t *bitstream, /* данные H.264 (Annex B byte stream) */
size_t size, /* размер в байтах */
int64_t pts_ns, /* presentation timestamp (передан в encode_frame) */
int is_idr, /* 1 если кадр IDR (keyframe) */
void *user /* user data, переданный в encode_frame */
);
/* Создать encoder. Возвращает 0 при успехе. */
int cfc_encoder_create(const cfc_encoder_config_t *cfg, cfc_encoder_t **out);
/* Закодировать один кадр. ptr — CUdeviceptr на начало NV12 frame в VRAM,
* pitch — ширина строки в байтах (для NV12 равна width).
*
* Callback может быть вызван 0 или 1 раз для одного encode_frame: NVENC
* может буферизовать кадры внутри (особенно при B-кадрах). Чтобы вытащить
* последние буферизованные — вызвать flush в конце потока. */
int cfc_encoder_encode_frame(
cfc_encoder_t *enc,
CUdeviceptr ptr,
int pitch,
int64_t pts_ns,
cfc_encoder_output_cb cb,
void *user
);
/* Завершить поток. Передаёт NVENC флаг end-of-stream, выдаёт оставшиеся
* закодированные кадры через callback. После flush encoder можно либо
* destroy, либо использовать снова с новым GOP'ом. */
int cfc_encoder_flush(
cfc_encoder_t *enc,
cfc_encoder_output_cb cb,
void *user
);
/* Выдать SPS/PPS — заголовки H.264 sequence/picture parameter sets.
* Нужны для записи в начало MP4-контейнера или для отправки в SDP при RTSP. */
int cfc_encoder_get_sequence_params(
cfc_encoder_t *enc,
uint8_t *out, /* буфер caller'а */
size_t *inout_size /* при вызове — размер буфера, при возврате — реальный размер */
);
/* Закрыть encoder, выгрузить SDK. */
int cfc_encoder_destroy(cfc_encoder_t *enc);
#ifdef __cplusplus
}
#endif
#endif /* CUFRAMES_COMPOSER_NVENC_H */