diff --git a/src/nvenc.c b/src/nvenc.c index 73cfeb1..0de93b4 100644 --- a/src/nvenc.c +++ b/src/nvenc.c @@ -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; } diff --git a/src/source.c b/src/source.c index dff89f5..0bcea0d 100644 --- a/src/source.c +++ b/src/source.c @@ -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'ом. */