Phase 1 smoke test зелёный: фикс frame_busy + staging buffer
После live-теста на R9-88.23 с реальным publisher'ом cam-parking (Dahua 1920x1080 @ 25fps) выявлены и поправлены два блокера: 1) source.c: release_current_frame ПЕРЕД cuframes_subscriber_next. cuframes валидирует frame_busy в начале next (consumer.c:334) и возвращает CUFRAMES_ERR_INVALID_ARG если предыдущий frame не release'нут. Прошлая реализация делала release ПОСЛЕ next — получалось много invalid_arg и каждые ~1с поток re-subscribe'ился, throughput падал до 0.87 fps. После фикса — стабильные 25.1 fps. 2) nvenc.c: добавлен staging buffer cuMemAlloc + cuMemcpy2D в encode_frame. NVENC nvEncRegisterResource возвращает RESOURCE_REGISTER_FAILED для VMM-mapped указателей cuframes v0.4 (CUdeviceptr из cuMemMap). Это известное ограничение NVENC SDK для VMM-памяти. Pre-copy в собственный cuMemAlloc-буфер решает проблему и сохраняет всё в VRAM (cudaMemcpyDeviceToDevice, без CPU bounce). Phase 1.1 будет research как получить настоящий zero-copy (cuMemExportToShareableHandle?). Smoke test результаты (cuframes-dev image + NVIDIA_DRIVER_CAPABILITIES video, /run/cuframes mount + cuframes-ipc-anchor IPC namespace): 300 кадров, 12 IDR, 6.9 МБ за 11.9с (25.1 fps) достигнут лимит 15с flush encoder ffprobe: 377 кадров, 1920x1080 yuv420p H.264 High@4.0 ffmpeg decode → PNG/JPG → реальное изображение с камеры
This commit is contained in:
+86
-4
@@ -64,6 +64,16 @@ struct cfc_encoder {
|
||||
/* Output bitstream buffer (один на Phase 1, потом pool) */
|
||||
NV_ENC_OUTPUT_PTR output_bitstream;
|
||||
|
||||
/* Staging buffer — линейная CUDA-память выделенная через cuMemAlloc,
|
||||
* в которую копируем NV12 кадр перед NVENC encode. Нужно потому что
|
||||
* NVENC nvEncRegisterResource не принимает VMM-mapped указатели cuframes
|
||||
* напрямую (NV_ENC_ERR_RESOURCE_REGISTER_FAILED) — это известное
|
||||
* ограничение SDK, см. Phase 1.1 research. */
|
||||
CUdeviceptr staging_ptr;
|
||||
size_t staging_size;
|
||||
int staging_pitch;
|
||||
NV_ENC_REGISTERED_PTR staging_regptr;
|
||||
|
||||
/* Кеш зарегистрированных входных буферов */
|
||||
registered_resource_t registered[CFC_MAX_REGISTERED_RESOURCES];
|
||||
int registered_count;
|
||||
@@ -275,9 +285,38 @@ int cfc_encoder_create(const cfc_encoder_config_t *cfg, cfc_encoder_t **out)
|
||||
fail_session);
|
||||
enc->output_bitstream = cb.bitstreamBuffer;
|
||||
|
||||
/* 6) Выделить staging buffer (legacy cuMemAlloc, 256-byte aligned pitch).
|
||||
* NV12 layout: Y plane (pitch * h) + interleaved UV (pitch * h/2). */
|
||||
enc->staging_pitch = ((cfg->width + 255) & ~255);
|
||||
enc->staging_size = (size_t)enc->staging_pitch * cfg->height +
|
||||
(size_t)enc->staging_pitch * (cfg->height / 2);
|
||||
CUresult cr = cuMemAlloc(&enc->staging_ptr, enc->staging_size);
|
||||
if (cr != CUDA_SUCCESS) {
|
||||
const char *es = NULL; cuGetErrorString(cr, &es);
|
||||
fprintf(stderr, "[cfc/nvenc] cuMemAlloc(%zu) failed: %s\n",
|
||||
enc->staging_size, es ? es : "?");
|
||||
rc = -1; goto fail_session;
|
||||
}
|
||||
|
||||
/* 7) Зарегистрировать staging buffer в NVENC один раз. */
|
||||
NV_ENC_REGISTER_RESOURCE sreg = { 0 };
|
||||
sreg.version = NV_ENC_REGISTER_RESOURCE_VER;
|
||||
sreg.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
|
||||
sreg.width = cfg->width;
|
||||
sreg.height = cfg->height;
|
||||
sreg.pitch = enc->staging_pitch;
|
||||
sreg.resourceToRegister = (void *)enc->staging_ptr;
|
||||
sreg.bufferFormat = enc->input_format;
|
||||
sreg.bufferUsage = NV_ENC_INPUT_IMAGE;
|
||||
NVE_CHECK(g_nvenc_funcs.nvEncRegisterResource(enc->session, &sreg),
|
||||
fail_staging);
|
||||
enc->staging_regptr = sreg.registeredResource;
|
||||
|
||||
*out = enc;
|
||||
return 0;
|
||||
|
||||
fail_staging:
|
||||
cuMemFree(enc->staging_ptr);
|
||||
fail_session:
|
||||
g_nvenc_funcs.nvEncDestroyEncoder(enc->session);
|
||||
fail_alloc:
|
||||
@@ -292,12 +331,49 @@ int cfc_encoder_encode_frame(cfc_encoder_t *enc, CUdeviceptr ptr, int pitch,
|
||||
{
|
||||
if (!enc) return -1;
|
||||
|
||||
NV_ENC_REGISTERED_PTR regptr = get_or_register(enc, ptr, pitch);
|
||||
if (!regptr) return -1;
|
||||
/* Phase 1: копируем NV12 кадр из VMM-указателя cuframes в свой staging
|
||||
* буфер cuMemAlloc. Pitch источника может отличаться от staging — копируем
|
||||
* по строкам. Это временное решение до Phase 1.1 (research zero-copy VMM). */
|
||||
int src_pitch = pitch;
|
||||
int dst_pitch = enc->staging_pitch;
|
||||
|
||||
/* Y plane: height строк */
|
||||
CUDA_MEMCPY2D y_copy = { 0 };
|
||||
y_copy.srcMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
y_copy.srcDevice = ptr;
|
||||
y_copy.srcPitch = src_pitch;
|
||||
y_copy.dstMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
y_copy.dstDevice = enc->staging_ptr;
|
||||
y_copy.dstPitch = dst_pitch;
|
||||
y_copy.WidthInBytes = enc->width;
|
||||
y_copy.Height = enc->height;
|
||||
CUresult cr = cuMemcpy2D(&y_copy);
|
||||
if (cr != CUDA_SUCCESS) {
|
||||
const char *es = NULL; cuGetErrorString(cr, &es);
|
||||
fprintf(stderr, "[cfc/nvenc] Y plane copy failed: %s\n", es ? es : "?");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* UV plane: height/2 строк, начало после Y plane в src и dst */
|
||||
CUDA_MEMCPY2D uv_copy = { 0 };
|
||||
uv_copy.srcMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
uv_copy.srcDevice = ptr + (size_t)src_pitch * enc->height;
|
||||
uv_copy.srcPitch = src_pitch;
|
||||
uv_copy.dstMemoryType = CU_MEMORYTYPE_DEVICE;
|
||||
uv_copy.dstDevice = enc->staging_ptr + (size_t)dst_pitch * enc->height;
|
||||
uv_copy.dstPitch = dst_pitch;
|
||||
uv_copy.WidthInBytes = enc->width;
|
||||
uv_copy.Height = enc->height / 2;
|
||||
cr = cuMemcpy2D(&uv_copy);
|
||||
if (cr != CUDA_SUCCESS) {
|
||||
const char *es = NULL; cuGetErrorString(cr, &es);
|
||||
fprintf(stderr, "[cfc/nvenc] UV plane copy failed: %s\n", es ? es : "?");
|
||||
return -1;
|
||||
}
|
||||
|
||||
NV_ENC_MAP_INPUT_RESOURCE mp = { 0 };
|
||||
mp.version = NV_ENC_MAP_INPUT_RESOURCE_VER;
|
||||
mp.registeredResource = regptr;
|
||||
mp.registeredResource = enc->staging_regptr;
|
||||
|
||||
NVENCSTATUS s = g_nvenc_funcs.nvEncMapInputResource(enc->session, &mp);
|
||||
if (s != NV_ENC_SUCCESS) {
|
||||
@@ -313,7 +389,7 @@ int cfc_encoder_encode_frame(cfc_encoder_t *enc, CUdeviceptr ptr, int pitch,
|
||||
pp.bufferFmt = mp.mappedBufferFmt;
|
||||
pp.inputWidth = enc->width;
|
||||
pp.inputHeight = enc->height;
|
||||
pp.inputPitch = pitch;
|
||||
pp.inputPitch = enc->staging_pitch;
|
||||
pp.pictureStruct = NV_ENC_PIC_STRUCT_FRAME;
|
||||
pp.inputTimeStamp = (uint64_t)pts_ns;
|
||||
|
||||
@@ -401,6 +477,9 @@ int cfc_encoder_destroy(cfc_encoder_t *enc)
|
||||
pthread_mutex_unlock(&enc->registered_mu);
|
||||
pthread_mutex_destroy(&enc->registered_mu);
|
||||
|
||||
if (enc->staging_regptr) {
|
||||
g_nvenc_funcs.nvEncUnregisterResource(enc->session, enc->staging_regptr);
|
||||
}
|
||||
if (enc->output_bitstream) {
|
||||
g_nvenc_funcs.nvEncDestroyBitstreamBuffer(enc->session,
|
||||
enc->output_bitstream);
|
||||
@@ -408,6 +487,9 @@ int cfc_encoder_destroy(cfc_encoder_t *enc)
|
||||
if (enc->session) {
|
||||
g_nvenc_funcs.nvEncDestroyEncoder(enc->session);
|
||||
}
|
||||
if (enc->staging_ptr) {
|
||||
cuMemFree(enc->staging_ptr);
|
||||
}
|
||||
free(enc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+10
-5
@@ -166,15 +166,20 @@ static void *source_thread(void *arg)
|
||||
|
||||
case CFC_SOURCE_ACTIVE:
|
||||
case CFC_SOURCE_STALE: {
|
||||
/* cuframes требует release предыдущего frame ДО next (frame_busy
|
||||
* проверяется в начале subscriber_next; см. libcuframes/src/consumer.c
|
||||
* line 334). Поэтому release заранее. Это значит snapshot.ptr
|
||||
* может стать невалидным к моменту чтения caller'ом — Phase 2
|
||||
* это исправит double-buffering'ом. */
|
||||
release_current_frame(src);
|
||||
|
||||
cuframes_frame_t *frame = NULL;
|
||||
int r = cuframes_subscriber_next(src->sub, NULL, &frame,
|
||||
/* consumer_stream=NULL — default stream; sync через cuMemcpy2D
|
||||
* на default stream. Phase 2 — свой stream + waitEvent. */
|
||||
int r = cuframes_subscriber_next(src->sub, (void *)0, &frame,
|
||||
CFC_SOURCE_NEXT_TIMEOUT_MS);
|
||||
|
||||
if (r == CUFRAMES_OK) {
|
||||
/* Освобождаем предыдущий кадр (если был удержан caller'ом
|
||||
* во время предыдущего snapshot — поздно, но это Phase 1
|
||||
* упрощение, см. описание сверху). */
|
||||
release_current_frame(src);
|
||||
src->current_frame = frame;
|
||||
|
||||
/* Обновляем snapshot под mutex'ом. */
|
||||
|
||||
Reference in New Issue
Block a user