AVFrame到QImage的高效转换
Posted
技术标签:
【中文标题】AVFrame到QImage的高效转换【英文标题】:Efficient conversion of AVFrame to QImage 【发布时间】:2012-10-26 14:22:37 【问题描述】:我需要从基于 Qt 的应用程序中的视频中提取帧。使用 ffmpeg 库,我可以将帧作为 AVFrame 获取,我需要将其转换为 QImage 以在我的应用程序的其他部分中使用。这种转换需要高效。到目前为止,sws_scale()
似乎是正确使用的函数,但我不确定要指定什么源和目标像素格式。
【问题讨论】:
转换为 QImage 效率不会很高...qtcentre.org/threads/9935-QImage-data-via-FFmpeg 【参考方案1】:提出了以下两步过程,首先将解码的AVFame
转换为 RGB 颜色空间中的另一个AVFrame
,然后再转换为QImage
。它可以工作并且相当快。
src_frame = get_decoded_frame();
AVFrame *pFrameRGB = avcodec_alloc_frame(); // intermediate pframe
if(pFrameRGB==NULL)
;// Handle error
int numBytes= avpicture_get_size(PIX_FMT_RGB24,
is->video_st->codec->width, is->video_st->codec->height);
uint8_t *buffer = (uint8_t*)malloc(numBytes);
avpicture_fill((AVPicture*)pFrameRGB, buffer, PIX_FMT_RGB24,
is->video_st->codec->width, is->video_st->codec->height);
int dst_fmt = PIX_FMT_RGB24;
int dst_w = is->video_st->codec->width;
int dst_h = is->video_st->codec->height;
// TODO: cache following conversion context for speedup,
// and recalculate only on dimension changes
SwsContext *img_convert_ctx_temp;
img_convert_ctx_temp = sws_getContext(
is->video_st->codec->width, is->video_st->codec->height,
is->video_st->codec->pix_fmt,
dst_w, dst_h, (PixelFormat)dst_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
QImage *myImage = new QImage(dst_w, dst_h, QImage::Format_RGB32);
sws_scale(img_convert_ctx_temp,
src_frame->data, src_frame->linesize, 0, is->video_st->codec->height,
pFrameRGB->data,
pFrameRGB->linesize);
uint8_t *src = (uint8_t *)(pFrameRGB->data[0]);
for (int y = 0; y < dst_h; y++)
QRgb *scanLine = (QRgb *) myImage->scanLine(y);
for (int x = 0; x < dst_w; x=x+1)
scanLine[x] = qRgb(src[3*x], src[3*x+1], src[3*x+2]);
src += pFrameRGB->linesize[0];
如果您找到更有效的方法,请在 cmets 中告诉我
【讨论】:
您可以将 pFrameRGB 中的数据复制到新的 QImage (RGB888) 并在内部进行转换【参考方案2】:我知道,为时已晚,但也许有人会发现它有用。从here我得到了做同样转换的线索,看起来有点短。
所以我创建了 QImage,它被重复用于每个解码帧:
QImage img( width, height, QImage::Format_RGB888 );
创建帧RGB:
frameRGB = av_frame_alloc();
//Allocate memory for the pixels of a picture and setup the AVPicture fields for it.
avpicture_alloc( ( AVPicture *) frameRGB, AV_PIX_FMT_RGB24, width, height);
第一帧解码后,我以这种方式创建转换上下文 SwsContext(它将用于所有下一帧):
mImgConvertCtx = sws_getContext( codecContext->width, codecContext->height, codecContext->pix_fmt, width, height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
最后对每一个解码的帧进行转换:
if( 1 == framesFinished && nullptr != imgConvertCtx )
//conversion frame to frameRGB
sws_scale(imgConvertCtx, frame->data, frame->linesize, 0, codecContext->height, frameRGB->data, frameRGB->linesize);
//setting QImage from frameRGB
for( int y = 0; y < height; ++y )
memcpy( img.scanLine(y), frameRGB->data[0]+y * frameRGB->linesize[0], mWidth * 3 );
详情请参阅link。
【讨论】:
看起来更好...我将 mWidth * 3 替换为 frameRGB->linesize[0] 与我之前的答案相比,这看起来非常简洁。标记为最佳答案。【参考方案3】:我认为更简单的方法:
void takeSnapshot(AVCodecContext* dec_ctx, AVFrame* frame)
SwsContext* img_convert_ctx;
img_convert_ctx = sws_getContext(dec_ctx->width,
dec_ctx->height,
dec_ctx->pix_fmt,
dec_ctx->width,
dec_ctx->height,
AV_PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
AVFrame* frameRGB = av_frame_alloc();
avpicture_alloc((AVPicture*)frameRGB,
AV_PIX_FMT_RGB24,
dec_ctx->width,
dec_ctx->height);
sws_scale(img_convert_ctx,
frame->data,
frame->linesize, 0,
dec_ctx->height,
frameRGB->data,
frameRGB->linesize);
QImage image(frameRGB->data[0],
dec_ctx->width,
dec_ctx->height,
frameRGB->linesize[0],
QImage::Format_RGB888);
image.save("capture.png");
【讨论】:
欢迎来到***!虽然您的答案中提供的代码可能是问题的解决方案,但最好简要说明您的代码的作用。 这似乎也避免了复制,性能等于@mike_wei的解决方案【参考方案4】:今天,我测试了直接将image->bit()
传递到swscale
,终于成功了,所以不需要复制到内存。例如:
/* 1. Get frame and QImage to show */
struct my_frame *frame = get_frame(source);
QImage *myImage = new QImage(dst_w, dst_h, QImage::Format_RGBA8888);
/* 2. Convert and write into image buffer */
uint8_t *dst[] = myImage->bits();
int linesizes[4];
av_image_fill_linesizes(linesizes, AV_PIX_FMT_RGBA, frame->width);
sws_scale(myswscontext, frame->data, (const int*)frame->linesize,
0, frame->height, dst, linesizes);
【讨论】:
这太棒了,尤其是如果您无论如何都必须转换为 RGB。 FHD帧花了一半时间! (8ms -> 4ms)【参考方案5】:我刚刚发现 scanLine 只是在缓冲区中寻找..您所需要的只是将 AV_PIX_FMT_RGB32 用于 AVFrame,将 QImage::FORMAT_RGB32 用于 QImage。
然后解码后做一个 memcpy
memcpy(img.scanLine(0), pFrameRGB->data[0], pFrameRGB->linesize[0] * pFrameRGB->height());
【讨论】:
【参考方案6】:我对其他建议的解决方案有疑问:
他们没有提到释放 AVFrame、SwsContext 或分配的缓冲区,这会导致大量内存泄漏(我有数千帧要处理)。这些问题都不能轻易解决,因为 QImage 依赖于底层数据,并且不会复制它。如果直接释放缓冲区,QImage 指向释放的数据并中断。这可以通过使用 QImage 的 cleanupFunction 在不再需要图像时释放缓冲区来解决,但是对于其他问题,无论如何它都不好。 在某些情况下,将 QImage.bits 直接传递给 sws_scale 的建议之一将不起作用,因为 QImage 至少是 32 位对齐的。因此,对于某些尺寸,它与 sws_scale 的预期宽度不匹配,并且输出的每条线都会移动一点。 第三个问题是他们使用了已弃用的 AVPicture 元素。我在另一个问题Converting an AVFrame to QImage with conversion of pixel format中列出了问题,最后找到了使用临时缓冲区的解决方案,可以将其复制到QImage,然后安全释放。
因此,请参阅我的答案,以获得完全有效、高效且没有弃用函数调用的实现:https://***.com/a/68212609/7360943
【讨论】:
以上是关于AVFrame到QImage的高效转换的主要内容,如果未能解决你的问题,请参考以下文章
Libav AVFrame 到 Opencv Mat 到 AVPacket 的转换