NVencs输出比特流不可读

时间:2016-07-29 11:51:18

标签: c++ cuda nvenc

我有一个与Nvidias NVenc API相关的问题。我想使用API​​来编码一些OpenGL图形。我的问题是,API在整个程序中没有报告错误,一切似乎都没问题。但是生成的输出不可读,例如, VLC。如果我尝试播放生成的文件,VLC会将黑屏闪烁约0.5秒,然后结束播放。 视频的长度为0,Vid的大小似乎也很小。 分辨率为1280 * 720,5秒录制的大小仅为700kb。这是现实吗?

申请流程如下:

  1. 渲染到辅助帧缓冲区
  2. 将Framebuffer下载到两个PBO之一(glReadPixels())
  3. 映射前一帧的PBO,以获得Cuda可理解的指针。
  4. 调用一个简单的CudaKernel将OpenGLs RGBA转换为ARGB,这应该是NVenc根据this可以理解的(第18页)。内核读取PBO的内容,并将转换后的内容写入CudaArray(使用cudaMalloc创建),并使用NVenc注册为InputResource。
  5. 转换后的数组的内容被编码。完成事件加上相应的输出比特流缓冲区排队。
  6. 辅助线程侦听排队的输出事件,如果发出一个事件信号,则输出比特流被映射并写入硬盘。
  7. NVenc-Encoder的初始化:

    InitParams* ip = new InitParams();
    m_initParams = ip;
    memset(ip, 0, sizeof(InitParams));
    ip->version = NV_ENC_INITIALIZE_PARAMS_VER;
    ip->encodeGUID = m_encoderGuid;  //Used Codec
    ip->encodeWidth = width; // Frame Width
    ip->encodeHeight = height; // Frame Height
    ip->maxEncodeWidth = 0; // Zero means no dynamic res changes
    ip->maxEncodeHeight = 0; 
    ip->darWidth = width; // Aspect Ratio
    ip->darHeight = height; 
    ip->frameRateNum = 60; // 60 fps
    ip->frameRateDen = 1; 
    ip->reportSliceOffsets = 0; // According to programming guide
    ip->enableSubFrameWrite = 0;
    ip->presetGUID = m_presetGuid; // Used Preset for Encoder Config
    
    NV_ENC_PRESET_CONFIG presetCfg; // Load the Preset Config
    memset(&presetCfg, 0, sizeof(NV_ENC_PRESET_CONFIG));
    presetCfg.version = NV_ENC_PRESET_CONFIG_VER;
    presetCfg.presetCfg.version = NV_ENC_CONFIG_VER;
    CheckApiError(m_apiFunctions.nvEncGetEncodePresetConfig(m_Encoder,
        m_encoderGuid, m_presetGuid, &presetCfg));
    memcpy(&m_encodingConfig, &presetCfg.presetCfg, sizeof(NV_ENC_CONFIG));
    // And add information about Bitrate etc
    m_encodingConfig.rcParams.averageBitRate = 500000;
    m_encodingConfig.rcParams.maxBitRate = 600000;
    m_encodingConfig.rcParams.rateControlMode = NV_ENC_PARAMS_RC_MODE::NV_ENC_PARAMS_RC_CBR;
    ip->encodeConfig = &m_encodingConfig;
    ip->enableEncodeAsync = 1; // Async Encoding
    ip->enablePTD = 1; // Encoder handles picture ordering
    

    CudaResource的注册

    m_cuContext->SetCurrent(); // Make the clients cuCtx current
    NV_ENC_REGISTER_RESOURCE res;
    memset(&res, 0, sizeof(NV_ENC_REGISTER_RESOURCE));
    NV_ENC_REGISTERED_PTR resPtr; // handle to the cuda resource for future use
    res.bufferFormat = m_inputFormat; // Format is ARGB
    res.height = m_height;
    res.width = m_width;
    // NOTE: I've set the pitch to the width of the frame, because the resource is a non-pitched 
    //cudaArray. Is this correct? Pitch = 0 would produce no output.
    res.pitch = pitch; 
    res.resourceToRegister = (void*) (uintptr_t) resourceToRegister; //CUdevptr to resource
    res.resourceType = 
        NV_ENC_INPUT_RESOURCE_TYPE::NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR;
    res.version = NV_ENC_REGISTER_RESOURCE_VER;
    CheckApiError(m_apiFunctions.nvEncRegisterResource(m_Encoder, &res));
    m_registeredInputResources.push_back(res.registeredResource);
    

    编码

    m_cuContext->SetCurrent(); // Make Clients context current
    MapInputResource(id); //Map the CudaInputResource
    NV_ENC_PIC_PARAMS temp;
    memset(&temp, 0, sizeof(NV_ENC_PIC_PARAMS));
    temp.version = NV_ENC_PIC_PARAMS_VER;
    unsigned int currentBufferAndEvent = m_counter % m_registeredEvents.size(); //Counter is inc'ed in every Frame
    temp.bufferFmt = m_currentlyMappedInputBuffer.mappedBufferFmt;
    temp.inputBuffer = m_currentlyMappedInputBuffer.mappedResource; //got set by MapInputResource
    temp.completionEvent = m_registeredEvents[currentBufferAndEvent];
    temp.outputBitstream = m_registeredOutputBuffers[currentBufferAndEvent];
    temp.inputWidth = m_width;
    temp.inputHeight = m_height;
    temp.inputPitch = m_width;
    temp.inputTimeStamp = m_counter;
    temp.pictureStruct = NV_ENC_PIC_STRUCT_FRAME; // According to samples
    temp.qpDeltaMap = NULL;
    temp.qpDeltaMapSize = 0;
    
    EventWithId latestEvent(currentBufferAndEvent,
        m_registeredEvents[currentBufferAndEvent]);
    PushBackEncodeEvent(latestEvent); // Store the Event with its ID in a Queue
    
    CheckApiError(m_apiFunctions.nvEncEncodePicture(m_Encoder, &temp));
    m_counter++;
    UnmapInputResource(id); // Unmap
    
    每一个小小的暗示,在哪里看,都非常感激。我想出了可能出错的想法。

    非常感谢!

1 个答案:

答案 0 :(得分:2)

在nvidia论坛hall822的帮助下,我设法解决了这个问题。

主要错误是我注册了我的cuda资源,其间距等于帧的大小。我使用Framebuffer-Renderbuffer绘制我的内容。这个数据是一个普通的无节点数组。我的第一个想法,给出等于零的音高,失败了。编码器什么也没做。下一个想法是将其设置为帧的宽度,四分之一的图像被编码。

// NOTE: I've set the pitch to the width of the frame, because the resource is a non-pitched 
//cudaArray. Is this correct? Pitch = 0 would produce no output.
res.pitch = pitch; 

回答这个问题:是的,这是正确的。但音高以字节为单位。因为我正在编码RGBA-Frames,正确的音调必须是FRAME_WIDTH * 4

第二个错误是我的颜色通道不对(请参阅我的开场白中的第4点)。 NVidia枚举表示编码器需要ARGB格式的频道,但实际上是 BGRA ,因此始终为255的alpha通道污染了蓝色通道。

编辑:这可能是因为NVidia在内部使用小端。我正在写作 我的像素数据到一个字节数组,选择其他类型如int32可能允许一个人传递实际的ARGB数据。