2015-10-03 2 views
3

Я ударил полную кирпичную стену, пытаясь использовать NVEnc для потока кадров OpenGL как H264. Я был в этом конкретном вопросе около 8 часов без какого-либо прогресса.nvEncRegisterResource() не работает с -23

Проблема заключается в вызове nvEncRegisterResource(), который неизменно терпит неудачу с кодом -23 (значение перечисления NV_ENC_ERR_RESOURCE_REGISTER_FAILED, задокументированное как «не удалось зарегистрировать ресурс» - благодаря NVidia).

Я пытаюсь выполнить процедуру, изложенную в this document из Университета Осло (стр. 54, «OpenGL interop»), поэтому я знаю, что это должно работать, хотя, к сожалению, указанный документ не предоставляет сам код.

Идея довольно проста:

  1. карту текстуры, полученный с помощью OpenGL кадра объекта буфера в CUDA;
  2. копировать текстуру в буфер (ранее выделенный) CUDA;
  3. карту, что буфер в качестве входного ресурса NVEnc
  4. использовать этот входной ресурс в качестве источника для кодирования

Как я сказал, проблема заключается в стадии (3). Вот соответствующие фрагменты коды (я опускаю обработку ошибок для краткости.)

// Round up width and height 
priv->encWidth = (_resolution.w + 31) & ~31, priv->encHeight = (_resolution.h + 31) & ~31; 

// Allocate CUDA "pitched" memory to match the input texture (YUV, one byte per component) 
cuErr = cudaMallocPitch(&priv->cudaMemPtr, &priv->cudaMemPitch, 3 * priv->encWidth, priv->encHeight); 

Это должно выделить на устройстве CUDA память («стан» разнообразие, хотя я пробовал, не стан тоже без любое изменение в результатах.)

// Register the CUDA buffer as an input resource 
NV_ENC_REGISTER_RESOURCE regResParams = { 0 }; 
regResParams.version = NV_ENC_REGISTER_RESOURCE_VER; 
regResParams.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; 
regResParams.width = priv->encWidth; 
regResParams.height = priv->encHeight; 
regResParams.bufferFormat = NV_ENC_BUFFER_FORMAT_YUV444_PL; 
regResParams.resourceToRegister = priv->cudaMemPtr; 
regResParams.pitch = priv->cudaMemPitch; 
encStat = nvEncApi.nvEncRegisterResource(priv->nvEncoder, &regResParams); 
//     ^^^ FAILS 
priv->nvEncInpRes = regResParams.registeredResource; 

Это кирпичная стена. Независимо от того, что я пытаюсь, nvEncRegisterResource() терпит неудачу.

Следует отметить, что я скорее думаю (хотя, может быть, и ошибаюсь), что выполнил все необходимые инициализации. Вот код, который создает и активизирует контекст CUDA:

// Pop the current context 
cuRes = cuCtxPopCurrent(&priv->cuOldCtx); 

// Create a context for the device 
priv->cuCtx = nullptr; 
cuRes = cuCtxCreate(&priv->cuCtx, CU_CTX_SCHED_BLOCKING_SYNC, priv->cudaDevice); 

// Push our context 
cuRes = cuCtxPushCurrent(priv->cuCtx); 

.. с последующим созданием сеанса кодирования:

// Create an NV Encoder session 
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS nvEncSessParams = { 0 }; 
nvEncSessParams.apiVersion = NVENCAPI_VERSION; 
nvEncSessParams.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER; 
nvEncSessParams.deviceType = NV_ENC_DEVICE_TYPE_CUDA; 
nvEncSessParams.device = priv->cuCtx; // nullptr 
auto encStat = nvEncApi.nvEncOpenEncodeSessionEx(&nvEncSessParams, &priv->nvEncoder); 

И, наконец, код инициализации кодировщик:

// Configure the encoder via preset 
NV_ENC_PRESET_CONFIG presetConfig = { 0 }; 
GUID codecGUID = NV_ENC_CODEC_H264_GUID; 
GUID presetGUID = NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID; 
presetConfig.version = NV_ENC_PRESET_CONFIG_VER; 
presetConfig.presetCfg.version = NV_ENC_CONFIG_VER; 
encStat = nvEncApi.nvEncGetEncodePresetConfig(priv->nvEncoder, codecGUID, presetGUID, &presetConfig); 

NV_ENC_INITIALIZE_PARAMS initParams = { 0 }; 
initParams.version = NV_ENC_INITIALIZE_PARAMS_VER; 
initParams.encodeGUID = codecGUID; 
initParams.encodeWidth = priv->encWidth; 
initParams.encodeHeight = priv->encHeight; 
initParams.darWidth = 1; 
initParams.darHeight = 1; 
initParams.frameRateNum = 25; // TODO: make this configurable 
initParams.frameRateDen = 1; // ditto 
// .max_surface_count = (num_mbs >= 8160) ? 32 : 48; 
// .buffer_delay ? necessary 
initParams.enableEncodeAsync = 0; 
initParams.enablePTD = 1; 
initParams.presetGUID = presetGUID; 
memcpy(&priv->nvEncConfig, &presetConfig.presetCfg, sizeof(priv->nvEncConfig)); 
initParams.encodeConfig = &priv->nvEncConfig; 
encStat = nvEncApi.nvEncInitializeEncoder(priv->nvEncoder, &initParams); 

Все указанные выше инициализации сообщают об успехе.

Я был бы чрезвычайно благодарен всем, кто может заставить меня пройти через это препятствие.


EDIT: вот полный код для воспроизведения проблемы. Единственное наблюдаемое отличие от исходного кода заключается в том, что cuPopContext() возвращает ошибку (которую можно игнорировать) здесь - возможно, моя оригинальная программа создает такой контекст, как побочный эффект использования OpenGL. В противном случае код ведет себя точно так же, как это делает оригинал. Я построил код с Visual Studio 2013, необходимо связать следующий файл библиотеки (адаптируется путем, если не на C :): C:\Program Files (x86)\NVIDIA GPU Computing Toolkit\CUDA\v7.5\lib\Win32\cuda.lib

Вы также должны убедиться, что C:\Program Files (x86)\NVIDIA GPU Computing Toolkit\CUDA\v7.5\include\ (или аналогичный) находится в заголовочном пути ,

NEW EDIT: изменен код, чтобы использовать интерфейс драйвера CUDA, вместо того, чтобы смешивать его с API-интерфейсом. Все тот же код ошибки.

#ifdef _WIN32 
#include <Windows.h> 
#endif 
#include <cassert> 
#include <GL/gl.h> 
#include <iostream> 
#include <string> 

#include <stdexcept> 
#include <string> 

#include <cuda.h> 
//#include <cuda_runtime.h> 
#include <cuda_gl_interop.h> 
#include <nvEncodeAPI.h> 

// NV Encoder API --------------------------------------------------- 

#if defined(_WIN32) 
#define LOAD_FUNC(l, s) GetProcAddress(l, s) 
#define DL_CLOSE_FUNC(l) FreeLibrary(l) 
#else 
#define LOAD_FUNC(l, s) dlsym(l, s) 
#define DL_CLOSE_FUNC(l) dlclose(l) 
#endif 

typedef NVENCSTATUS(NVENCAPI* PNVENCODEAPICREATEINSTANCE)(NV_ENCODE_API_FUNCTION_LIST *functionList); 

struct NVEncAPI : public NV_ENCODE_API_FUNCTION_LIST { 
public: 
    // ~NVEncAPI() { cleanup(); } 

    void init() { 
#if defined(_WIN32) 
     if (sizeof(void*) == 8) { 
      nvEncLib = LoadLibrary(TEXT("nvEncodeAPI64.dll")); 
     } 
     else { 
      nvEncLib = LoadLibrary(TEXT("nvEncodeAPI.dll")); 
     } 
     if (nvEncLib == NULL) throw std::runtime_error("Failed to load NVidia Encoder library: " + std::to_string(GetLastError())); 
#else 
     nvEncLib = dlopen("libnvidia-encode.so.1", RTLD_LAZY); 
     if (nvEncLib == nullptr) 
      throw std::runtime_error("Failed to load NVidia Encoder library: " + std::string(dlerror())); 
#endif 
     auto nvEncodeAPICreateInstance = (PNVENCODEAPICREATEINSTANCE) LOAD_FUNC(nvEncLib, "NvEncodeAPICreateInstance"); 

     version = NV_ENCODE_API_FUNCTION_LIST_VER; 
     NVENCSTATUS encStat = nvEncodeAPICreateInstance(static_cast<NV_ENCODE_API_FUNCTION_LIST *>(this)); 
    } 

    void cleanup() { 
#if defined(_WIN32) 
     if (nvEncLib != NULL) { 
      FreeLibrary(nvEncLib); 
      nvEncLib = NULL; 
     } 
#else 
     if (nvEncLib != nullptr) { 
      dlclose(nvEncLib); 
      nvEncLib = nullptr; 
     } 
#endif 
    } 

private: 

#if defined(_WIN32) 
    HMODULE nvEncLib; 
#else 
    void* nvEncLib; 
#endif 
    bool init_done; 
}; 

static NVEncAPI nvEncApi; 

// Encoder class ---------------------------------------------------- 

class Encoder { 
public: 
    typedef unsigned int uint_t; 
    struct Size { uint_t w, h; }; 

    Encoder() { 
     CUresult cuRes = cuInit(0); 
     nvEncApi.init(); 
    } 

    void init(const Size & resolution, uint_t texture) { 

     NVENCSTATUS encStat; 
     CUresult cuRes; 

     texSize = resolution; 
     yuvTex = texture; 

     // Purely for information 
     int devCount = 0; 
     cuRes = cuDeviceGetCount(&devCount); 

     // Initialize NVEnc 
     initEncodeSession();   // start an encoding session 
     initEncoder(); 

     // Register the YUV texture as a CUDA graphics resource 
     // CODE COMMENTED OUT AS THE INPUT TEXTURE IS NOT NEEDED YET (TO MY UNDERSTANDING) AT SETUP TIME 
     //cudaGraphicsGLRegisterImage(&priv->cudaInpTexRes, priv->yuvTex, GL_TEXTURE_2D, cudaGraphicsRegisterFlagsReadOnly); 

     // Allocate CUDA "pitched" memory to match the input texture (YUV, one byte per component) 
     encWidth = (texSize.w + 31) & ~31, encHeight = (texSize.h + 31) & ~31; 
     cuRes = cuMemAllocPitch(&cuDevPtr, &cuMemPitch, 4 * encWidth, encHeight, 16); 

     // Register the CUDA buffer as an input resource 
     NV_ENC_REGISTER_RESOURCE regResParams = { 0 }; 
     regResParams.version = NV_ENC_REGISTER_RESOURCE_VER; 
     regResParams.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; 
     regResParams.width = encWidth; 
     regResParams.height = encHeight; 
     regResParams.bufferFormat = NV_ENC_BUFFER_FORMAT_YUV444_PL; 
     regResParams.resourceToRegister = (void*) cuDevPtr; 
     regResParams.pitch = cuMemPitch; 
     encStat = nvEncApi.nvEncRegisterResource(nvEncoder, &regResParams); 
     assert(encStat == NV_ENC_SUCCESS); // THIS IS THE POINT OF FAILURE 
     nvEncInpRes = regResParams.registeredResource; 
    } 

    void cleanup() { /* OMITTED */ } 

    void encode() { 
     // THE FOLLOWING CODE WAS NEVER REACHED YET BECAUSE OF THE ISSUE. 
     // INCLUDED HERE FOR REFERENCE. 

     CUresult cuRes; 
     NVENCSTATUS encStat; 

     cuRes = cuGraphicsResourceSetMapFlags(cuInpTexRes, CU_GRAPHICS_MAP_RESOURCE_FLAGS_READ_ONLY); 

     cuRes = cuGraphicsMapResources(1, &cuInpTexRes, 0); 

     CUarray mappedArray; 
     cuRes = cuGraphicsSubResourceGetMappedArray(&mappedArray, cuInpTexRes, 0, 0); 

     cuRes = cuMemcpyDtoA(mappedArray, 0, cuDevPtr, 4 * encWidth * encHeight); 

     NV_ENC_MAP_INPUT_RESOURCE mapInputResParams = { 0 }; 
     mapInputResParams.version = NV_ENC_MAP_INPUT_RESOURCE_VER; 
     mapInputResParams.registeredResource = nvEncInpRes; 
     encStat = nvEncApi.nvEncMapInputResource(nvEncoder, &mapInputResParams); 

     // TODO: encode... 

     cuRes = cuGraphicsUnmapResources(1, &cuInpTexRes, 0); 
    } 

private: 
    struct PrivateData; 

    void initEncodeSession() { 

     CUresult cuRes; 
     NVENCSTATUS encStat; 

     // Pop the current context 
     cuRes = cuCtxPopCurrent(&cuOldCtx); // THIS IS ALLOWED TO FAIL (it doesn't 

     // Create a context for the device 
     cuCtx = nullptr; 
     cuRes = cuCtxCreate(&cuCtx, CU_CTX_SCHED_BLOCKING_SYNC, 0); 

     // Push our context 
     cuRes = cuCtxPushCurrent(cuCtx); 

     // Create an NV Encoder session 
     NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS nvEncSessParams = { 0 }; 
     nvEncSessParams.apiVersion = NVENCAPI_VERSION; 
     nvEncSessParams.version = NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER; 
     nvEncSessParams.deviceType = NV_ENC_DEVICE_TYPE_CUDA; 
     nvEncSessParams.device = cuCtx; 
     encStat = nvEncApi.nvEncOpenEncodeSessionEx(&nvEncSessParams, &nvEncoder); 
    } 

    void Encoder::initEncoder() 
    { 
     NVENCSTATUS encStat; 

     // Configure the encoder via preset 
     NV_ENC_PRESET_CONFIG presetConfig = { 0 }; 
     GUID codecGUID = NV_ENC_CODEC_H264_GUID; 
     GUID presetGUID = NV_ENC_PRESET_LOW_LATENCY_DEFAULT_GUID; 
     presetConfig.version = NV_ENC_PRESET_CONFIG_VER; 
     presetConfig.presetCfg.version = NV_ENC_CONFIG_VER; 
     encStat = nvEncApi.nvEncGetEncodePresetConfig(nvEncoder, codecGUID, presetGUID, &presetConfig); 

     NV_ENC_INITIALIZE_PARAMS initParams = { 0 }; 
     initParams.version = NV_ENC_INITIALIZE_PARAMS_VER; 
     initParams.encodeGUID = codecGUID; 
     initParams.encodeWidth = texSize.w; 
     initParams.encodeHeight = texSize.h; 
     initParams.darWidth = texSize.w; 
     initParams.darHeight = texSize.h; 
     initParams.frameRateNum = 25; 
     initParams.frameRateDen = 1; 
     initParams.enableEncodeAsync = 0; 
     initParams.enablePTD = 1; 
     initParams.presetGUID = presetGUID; 
     memcpy(&nvEncConfig, &presetConfig.presetCfg, sizeof(nvEncConfig)); 
     initParams.encodeConfig = &nvEncConfig; 
     encStat = nvEncApi.nvEncInitializeEncoder(nvEncoder, &initParams); 
    } 

    //void cleanupEncodeSession(); 
    //void cleanupEncoder; 

    Size     texSize; 

    GLuint     yuvTex; 
    uint_t     encWidth, encHeight; 
    CUdeviceptr    cuDevPtr; 
    size_t     cuMemPitch; 
    NV_ENC_CONFIG   nvEncConfig; 
    NV_ENC_INPUT_PTR  nvEncInpBuf; 
    NV_ENC_REGISTERED_PTR nvEncInpRes; 
    CUdevice    cuDevice; 
    CUcontext    cuCtx, cuOldCtx; 
    void     *nvEncoder; 
    CUgraphicsResource  cuInpTexRes; 
}; 


int main(int argc, char *argv[]) 
{ 
    Encoder encoder; 

    encoder.init({1920, 1080}, 0); // OMITTED THE TEXTURE AS IT IS NOT NEEDED TO REPRODUCE THE ISSUE 

    return 0; 
} 
+1

Вы должны предоставить [полный код] (http://stackoverflow.com/help/mcve), который ведет к отказу регистра буфера NVENC. SO [ожидает этого] (http://stackoverflow.com/help/on-topic) для таких вопросов («почему этот код не работает?»). Например, я даже не могу вывести высоту и ширину буфера, о котором вы просите. И я не хочу играть 20 вопросов. –

+0

@RobertCrovella: Я попытаюсь придумать минимальный пример. Размер текстуры - 1920x1080. – JPNotADragon

+0

@RobertCrovella: Я сделал код доступным через общедоступную папку DropBox. – JPNotADragon

ответ

5

После сравнения образца NVidia NvEncoderCudaInterop с моим минимальным кодом, я, наконец, нашел предмет, который делает разницу между успехом и провалом: его параметр из NV_ENC_REGISTER_RESOURCE структуры, передаваемой в nvEncRegisterResource()pitch.

Я не видел документально нигде, но для этого значения существует жесткий предел, который я определил экспериментально на уровне 2560. Все, что выше этого, приведет к NV_ENC_ERR_RESOURCE_REGISTER_FAILED.

Не похоже, что пройденный шаг был рассчитан другим вызовом API, cuMemAllocPitch().

(Другое дело, что не хватало моего кода был «замок» и отпирание контекст CUDA для текущего потока через cuCtxPushCurrent() и cuCtxPopCurrent() Совершено в образце с помощью класса RAII.).


EDIT:

Я работал над проблемой, выполнив что-то, для чего у меня была другая причина: использование NV12 в качестве входного формата для кодировщика вместо YUV444.

С NV12 параметр pitch падает ниже предела 2560, потому что размер байта на строку равен ширине, поэтому в моем случае 1920 байт.

Это было необходимо (в то время), потому что моя видеокарта была GTX 760 с графическим процессором «Kepler», который (как я уже не знал) поддерживает только NV12 в качестве формата ввода для NVEnc. С тех пор я обновился до GTX 970, но, как я только узнал, предел 2560 по-прежнему существует.

Это заставляет задуматься, как именно можно использовать NVEnc с YUV444. Единственная возможность, которая приходит мне на ум, - использовать , а не -pitched память, которая кажется странной. Я буду благодарен за комментарии от людей, которые на самом деле использовали NVEnc с YUV444.


EDIT # 2 - ОЖИДАНИЕ ДАЛЬНЕЙШЕГО UPDATE:

Новая информация всплыла в виде другого SO вопроса: NVencs Output Bitstream is not readable

Вполне возможно, что мой ответ до сих пор не так. Теперь кажется, что шаг должен устанавливаться не только при регистрации ресурса CUDA, но и при отправке его в кодировщик через nvEncEncodePicture(). Я не могу проверить это прямо сейчас, но в следующий раз я буду работать над этим проектом.

Смежные вопросы