b8661f4017
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.
115 lines
6.0 KiB
C
115 lines
6.0 KiB
C
/* 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 */
|