释放 nvenc hwdevice_ctx 时出现 SegFault

问题描述 投票:0回答:1

对于一个项目,我创建了一个类,使用

renderbuffer
编码 OpenGL
h264_nvenc
对象的输出。不幸的是,整理不起作用,程序因段错误而崩溃。原因是访问不可访问的内存区域,在调用
av_buffer_unref( &_hwDeviceRefCtx )
时在最后几行(见下文)中发生了两次,并且在
avcodec_free_context( &_pCodecCtx )
期间也隐式发生了两次,但这两次调用都需要关闭。

(在本例中相关)

valgrind
-输出是

Invalid read of size 8
   at 0x48AD987: UnknownInlinedFun (buffer.c:121)
   by 0x48AD987: UnknownInlinedFun (buffer.c:144)
   by 0x48AD987: av_buffer_unref (buffer.c:139)
   by 0x5D06D7A: avcodec_close (avcodec.c:486)
   by 0x628DD7D: avcodec_free_context (options.c:175)
   by 0x10A863: main (main.cpp:115)
 Address 0x17812700 is 0 bytes inside a block of size 24 free'd
   at 0x484488F: free (vg_replace_malloc.c:985)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:127)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:144)
   by 0x48AD98F: av_buffer_unref (buffer.c:139)
   by 0x48BE098: hwframe_ctx_free (hwcontext.c:240)
   by 0x48AD9A6: UnknownInlinedFun (buffer.c:133)
   by 0x48AD9A6: UnknownInlinedFun (buffer.c:144)
   by 0x48AD9A6: av_buffer_unref (buffer.c:139)
   by 0x5D06D0A: UnknownInlinedFun (decode.c:1261)
   by 0x5D06D0A: avcodec_close (avcodec.c:465)
   by 0x628DD7D: avcodec_free_context (options.c:175)
   by 0x10A863: main (main.cpp:115)
 Block was alloc'd at
   at 0x4849366: posix_memalign (vg_replace_malloc.c:2099)
   by 0x48D9BD5: av_malloc (mem.c:105)
   by 0x48D9DAD: av_mallocz (mem.c:256)
   by 0x48AD8DD: UnknownInlinedFun (buffer.c:44)
   by 0x48AD8DD: av_buffer_create (buffer.c:64)
   by 0x48BDDEB: av_hwdevice_ctx_alloc (hwcontext.c:179)
   by 0x48BDF29: av_hwdevice_ctx_create (hwcontext.c:622)
   by 0x10A482: main (main.cpp:43)

Invalid free() / delete / delete[] / realloc()
   at 0x484488F: free (vg_replace_malloc.c:985)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:127)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:144)
   by 0x48AD98F: av_buffer_unref (buffer.c:139)
   by 0x5D06D7A: avcodec_close (avcodec.c:486)
   by 0x628DD7D: avcodec_free_context (options.c:175)
   by 0x10A863: main (main.cpp:115)
 Address 0x17812700 is 0 bytes inside a block of size 24 free'd
   at 0x484488F: free (vg_replace_malloc.c:985)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:127)
   by 0x48AD98F: UnknownInlinedFun (buffer.c:144)
   by 0x48AD98F: av_buffer_unref (buffer.c:139)
   by 0x48BE098: hwframe_ctx_free (hwcontext.c:240)
   by 0x48AD9A6: UnknownInlinedFun (buffer.c:133)
   by 0x48AD9A6: UnknownInlinedFun (buffer.c:144)
   by 0x48AD9A6: av_buffer_unref (buffer.c:139)
   by 0x5D06D0A: UnknownInlinedFun (decode.c:1261)
   by 0x5D06D0A: avcodec_close (avcodec.c:465)
   by 0x628DD7D: avcodec_free_context (options.c:175)
   by 0x10A863: main (main.cpp:115)
 Block was alloc'd at
   at 0x4849366: posix_memalign (vg_replace_malloc.c:2099)
   by 0x48D9BD5: av_malloc (mem.c:105)
   by 0x48D9DAD: av_mallocz (mem.c:256)
   by 0x48AD8DD: UnknownInlinedFun (buffer.c:44)
   by 0x48AD8DD: av_buffer_create (buffer.c:64)
   by 0x48BDDEB: av_hwdevice_ctx_alloc (hwcontext.c:179)
   by 0x48BDF29: av_hwdevice_ctx_create (hwcontext.c:622)
   by 0x10A482: main (main.cpp:43)

这也是重复的(由于调用

avcodec_free_context()
av_buffer_unref()
)。

问题是:我该如何解决这个问题?

(或多或少)最小(不是)工作示例如下

#include <string>

extern "C" {
  #include <libavutil/opt.h>
  #include <libavcodec/avcodec.h>
  #include <libavformat/avformat.h>
  #include <libavutil/hwcontext.h>
  #include <libavutil/pixdesc.h>
  #include <libavutil/hwcontext_cuda.h>
}

//(former) libx264 encoding based on https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/muxing.c
//update to h264_nvenc with a lot of help from https://stackoverflow.com/questions/49862610/opengl-to-ffmpeg-encode
//and some additional info of https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/vaapi_encode.c

int main() {
    const int _SrcImageWidth=640;
    const int _SrcImageHeight=480;
    
    const AVOutputFormat *_oFmt = nullptr;
    AVFormatContext *_oFmtCtx = nullptr;
    
    const AVCodec *_pCodec = nullptr;
    AVCodecContext *_pCodecCtx = nullptr;
    
    AVFrame* _frame;
    AVPacket* _packet;
    AVStream* _stream;
    
    AVBufferRef *_hwDeviceRefCtx = nullptr;
    const CUcontext* _cudaCtx;
    
    const std::string _OutFileName = "output.mkv";
    
    //constructor part
    int ret;

    //output format context      
    avformat_alloc_output_context2( &_oFmtCtx, nullptr, nullptr, _OutFileName.c_str() );
    _oFmt = _oFmtCtx->oformat;

    //hardware format context
    ret = av_hwdevice_ctx_create( &_hwDeviceRefCtx, AV_HWDEVICE_TYPE_CUDA, "NVIDIA GeForce RTX 4070", nullptr, 0 );

    //hardware frame context for device buffer allocation
    AVBufferRef* hwFrameRefCtx = av_hwframe_ctx_alloc( _hwDeviceRefCtx );
    AVHWFramesContext* hwFrameCtx = (AVHWFramesContext*) (hwFrameRefCtx->data);
    hwFrameCtx->width = _SrcImageWidth;
    hwFrameCtx->height = _SrcImageHeight;
    hwFrameCtx->sw_format = AV_PIX_FMT_0BGR32;
    hwFrameCtx->format = AV_PIX_FMT_CUDA;
    hwFrameCtx->device_ref = _hwDeviceRefCtx;
    hwFrameCtx->device_ctx = (AVHWDeviceContext*) _hwDeviceRefCtx->data;

    ret = av_hwframe_ctx_init( hwFrameRefCtx );

    //get cuda context
    const AVHWDeviceContext* hwDeviceCtx = (AVHWDeviceContext*)(_hwDeviceRefCtx->data);
    const AVCUDADeviceContext* cudaDeviceCtx = (AVCUDADeviceContext*)(hwDeviceCtx->hwctx);
    _cudaCtx = &(cudaDeviceCtx->cuda_ctx);

    //codec context
    _pCodec = avcodec_find_encoder_by_name( "h264_nvenc" );

    _packet = av_packet_alloc();

    _stream = avformat_new_stream( _oFmtCtx, nullptr );
    _stream->id = _oFmtCtx->nb_streams - 1;
    _pCodecCtx = avcodec_alloc_context3( _pCodec );

    _pCodecCtx->qmin = 18;
    _pCodecCtx->qmax = 20;
    _pCodecCtx->width = _SrcImageWidth;
    _pCodecCtx->height = _SrcImageHeight;
    _pCodecCtx->framerate = (AVRational) {25,1};
    _pCodecCtx->time_base = (AVRational) {1,25};
    _stream->time_base = _pCodecCtx->time_base;
    _pCodecCtx->gop_size = 12; //I-Frame every at most 12 frames
    _pCodecCtx->max_b_frames = 2;
    _pCodecCtx->pix_fmt = AV_PIX_FMT_CUDA; //required to use renderbuffer as src
    _pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    _pCodecCtx->sw_pix_fmt = AV_PIX_FMT_0BGR32; 
    _pCodecCtx->hw_device_ctx = _hwDeviceRefCtx;
    _pCodecCtx->hw_frames_ctx = av_buffer_ref( hwFrameRefCtx );
    av_opt_set(_pCodecCtx->priv_data, "preset", "p7", 0);
    av_opt_set(_pCodecCtx->priv_data, "rc", "vbr", 0);
    if( _oFmtCtx->oformat->flags & AVFMT_GLOBALHEADER ) {
        _pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    ret = avcodec_open2( _pCodecCtx, _pCodec, nullptr );
    avcodec_parameters_from_context( _stream->codecpar, _pCodecCtx );

    if (!(_oFmtCtx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&_oFmtCtx->pb, _OutFileName.c_str(), AVIO_FLAG_WRITE);
    }
    ret = avformat_write_header( _oFmtCtx, nullptr );

    //use hardware frame from above
    _frame = av_frame_alloc();
    ret = av_hwframe_get_buffer( _pCodecCtx->hw_frames_ctx, _frame, 0 );
    _frame->pts = 1;

    av_buffer_unref( &hwFrameRefCtx );

    //destructor part
    av_frame_free( &_frame );
    av_packet_free( &_packet );

    av_write_trailer( _oFmtCtx );
    avio_closep( &_oFmtCtx->pb );

    avformat_free_context( _oFmtCtx );

    avcodec_free_context( &_pCodecCtx );
    av_buffer_unref( &_hwDeviceRefCtx );

    return 0;
}

并使用(linux 用户)进行编译

g++ -lavutil -lavformat -lavcodec -lz -lavutil -lswscale -lswresample -lm -ggdb3 -I/opt/cuda/include main.cpp

提前致谢!

c++ ffmpeg nvenc
1个回答
0
投票

答案是一种 RTFM 时刻:在

doxygen
link
)的 AVCodecContext 文档中,有关于
hw_device_ctx
-member

的说明

如果编解码器设备不需要硬件帧或者任何使用的硬件帧将由 libavcodec 内部分配,则应使用此选项。如果用户希望提供任何用作编码器输入或解码器输出的帧,则应使用 hw_frames_ctx 。当在解码器的 get_format() 中设置 hw_frames_ctx 时,在解码关联的流段时将忽略该字段,但可以在接下来的另一个 get_format() 调用中再次使用。

对于编码器和解码器,应在调用 avcodec_open2() 之前设置该字段,并且此后不得写入。

请注意,某些解码器可能需要最初设置此字段才能支持 hw_frames_ctx - 在这种情况下,必须在同一设备上创建使用的所有帧上下文。

不幸的是我对第三段的关注较多,而对第一段的关注较少;删除线

_pCodecCtx->hw_device_ctx = _hwDeviceRefCtx;
解决了问题。

© www.soinside.com 2019 - 2024. All rights reserved.