对于一个项目,我创建了一个类,使用
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
提前致谢!
答案是一种 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;
解决了问题。