在FFmpeg中正确分配和填充帧

7

我正在用一张BGR图像填充一个Frame以进行编码,但似乎出现了内存泄漏。我认为我找到了问题的根源,但它似乎是一个库的问题而不是我的错误。由于FFmpeg是一个如此成熟的库,我想我可能在错误地使用它,我想得到指导,了解正确的使用方法。

我正在使用以下代码来分配一个Frame

AVFrame *bgrFrame = av_frame_alloc();

后来,我使用以下代码在Frame中分配图像:

av_image_alloc(bgrFrame->data, bgrFrame->linesize, bgrFrame->width, bgrFrame->height, AV_PIX_FMT_BGR24, 32);

然后我使用以下方法填充分配的图像:

av_image_fill_pointers(bgrFrame->data, AV_PIX_FMT_BGR24, bgrFrame->height, originalBGRImage.data, bgrFrame->linesize);

originalBGRImage是一个OpenCV的Mat时。

显然,这段代码存在内存泄漏。在av_image_alloc()av_image_fill_pointers()中,会在同一指针上分配内存(通过bgrFrame->data[0]的变化可以看出)。

如果我调用

av_freep(&bgrFrame->data[0]);

在使用av_image_alloc()时,一切正常,但是如果我在av_image_fill_pointers()之后调用它,程序就会崩溃,尽管bgrFrame->data[0]不是NULL,这让我很好奇。
查看FFmpeg的av_image_alloc()源码,我发现它内部调用了两次av_image_fill_pointers(),第一次分配缓冲区buff......而稍后在av_image_fill_pointers()源码中,data[0]被图像指针替换,这就是内存泄漏的来源,因为data[0]原来持有av_image_alloc()上一次调用的buf
因此,最终的问题是: 如何正确地用图像填充帧?

由于数据已经存在,您可以使用avpicture_fill,它不会分配内存。无论如何,您应该只分配一个帧并重复使用它。此外,请检查您是否真正需要av_freepav_free - SpamBot
但据我所知,我需要为编码提供一个框架,那么我如何将AVPicture转换为AVFrame? - Michel Feinstein
3个回答

4

你应该在一开始就分配好你的框架。

AVFrame* alloc_picture(enum PixelFormat pix_fmt, int width, int height)
{
AVFrame *f = avcodec_alloc_frame();
if (!f)
    return NULL;
int size = avpicture_get_size(pix_fmt, width, height);
uint8_t *buffer = (uint8_t *) av_malloc(size);
if (!buffer) {
    av_free(f);
    return NULL;
}
avpicture_fill((AVPicture *)f, buffer, pix_fmt, width, height);
return f;
}

是的,可以使用 cast (AVPicture*) https://dev59.com/8WIj5IYBdhLWcg3wOCuS#20498359 。 在随后的帧中,您可以将数据写入该帧。由于 OpenCV 的原始数据是BGR格式,而您需要RGB或YUV格式,因此可以使用sws_scale函数进行转换。在我的应用程序中,我进行了垂直镜像处理:

struct SwsContext* convertCtx = sws_getContext(width, height, PIX_FMT_RGB24, c->width, c->height, c->pix_fmt, SWS_FAST_BILINEAR, NULL, NULL, NULL);
avpicture_fill(&pic_raw, (uint8_t*)pixelBuffer, PIX_FMT_RGB24, width, height);
// flip
pic_raw.data[0] += (height - 1) * pic_raw.linesize[0]; 
pic_raw.linesize[0] *= -1;
sws_scale(convertCtx, pic_raw.data, pic_raw.linesize, 0, height, f->data, f->linesize);
out_size = avcodec_encode_video(c, outputBuffer, outputBufferSize, f);

(您可以根据需要适应PIX_FMT_RGB24,并从cv :: Mat读取而无需复制数据。)

我使用av_fill_arrays()使其正常工作,并传递我的BGR图像指针和已分配的帧(仅分配一次)。我还使用sws_scale()进行转换,唯一缺少的部分实际上是av_fill_arrays() - Michel Feinstein

0

av_fill_arrays()可以完成这项工作。它将填充帧的data[]linesizes[],但不会重新分配任何内存。


我在寻找“av_fill_arrays”,但是找不到它。我找不到源代码或文档。它已经过时了吗? - Darkwonder
我不知道,上次我使用它是在2016年,但我可以在谷歌上找到av_image_fill_arrays,也许是一样的吗? - Michel Feinstein

0

回答有些晚了,但我花了很多时间,想要分享一下。
在文档中

 /**
    * AVBuffer references backing the data for this frame. All the pointers in
    * data and extended_data must point inside one of the buffers in buf or
    * extended_buf. This array must be filled contiguously -- if buf[i] is
    * non-NULL then buf[j] must also be non-NULL for all j < i.
    *
    * There may be at most one AVBuffer per data plane, so for video this array
    * always contains all the references. For planar audio with more than
    * AV_NUM_DATA_POINTERS channels, there may be more buffers than can fit in
    * this array. Then the extra AVBufferRef pointers are stored in the
    * extended_buf array.
    */
AVBufferRef *buf[AV_NUM_DATA_POINTERS];

然后bufdata的"智能指针"(extended_bufextended_data的)
例如,我只使用图像的一行大小

int size = av_image_get_buffer_size(AVPixelFormat::AV_PIX_FMT_BGRA, width, height, 1);
AVBufferRef* dataref = av_buffer_alloc(size);//that for av_frame_unref

memcpy(dataref->data, your_buffer, size );

AVFrame* frame = av_frame_alloc();
av_image_fill_arrays(frame->data, frame->linesize, dataref->data, AVPixelFormat::AV_PIX_FMT_BGRA, source->width, source->height, 1
frame->buf[0] = dataref;

av_frame_unref会在引用计数为零时取消引用frame->buf并释放指针。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接