Мне нужно извлечь кадры из видео в моем приложении на основе Qt. Используя библиотеки ffmpeg, я могу получать кадры как AVFrames, которые мне нужно преобразовать в QImage для использования в других частях моего приложения. Это преобразование должно быть эффективным. Пока кажется, что sws_scale()
— это правильная функция для использования, но я не уверен, какие исходные и целевые форматы пикселей должны быть указаны.
Эффективное преобразование AVFrame в QImage
Ответы (6)
Знаю, уже поздно, но может кому пригодится. Из здесь я понял, как сделать то же самое преобразование, которое выглядит немного короче.
Поэтому я создал 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 );
}
Дополнительные сведения см. по ссылке.
Придумал следующий двухэтапный процесс, который сначала преобразует декодированный AVFame
в другой AVFrame
в цветовом пространстве RGB, а затем в 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];
}
Если вы найдете более эффективный подход, дайте мне знать в комментариях
Я думаю, что более простой подход:
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");
}
Сегодня я протестировал непосредственную передачу 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);
Я только что обнаружил, что scanLine просто ищет через буфер. Все, что вам нужно, это использовать AV_PIX_FMT_RGB32 для AVFrame и QImage::FORMAT_RGB32 для QImage.
Затем после декодирования просто выполните memcpy
memcpy(img.scanLine(0), pFrameRGB->data[0], pFrameRGB->linesize[0] * pFrameRGB->height());
У меня были проблемы с другими предлагаемыми решениями, такими как:
- Они не упомянули об освобождении ни AVFrame, ни SwsContext, ни выделенных буферов, что вызвало массовые утечки памяти (у меня были тысячи кадров для обработки). Все эти проблемы не могут быть легко решены, поскольку QImage опирается на базовые данные, а не копирует их. При непосредственном освобождении буфера QImage указывает на освобожденные данные и прерывается. Это можно решить, используя функцию очистки QImage для освобождения буфера, когда изображение больше не нужно, но с другими проблемами это все равно было нехорошо.
- В некоторых случаях одно из предложений по передаче QImage.bits непосредственно в sws_scale не будет работать, поскольку QImage выровнен как минимум по 32 битам. Поэтому для определенных размеров он не будет соответствовать ожидаемой ширине по sws_scale и выводить каждую строку немного сдвинутым.
- Третья проблема заключается в том, что они использовали устаревшие элементы AVPicture.
Я перечислил проблему в другом вопросе Преобразование AVFrame в QImage с преобразованием формата пикселей и, в конце концов, нашел решение, используя временный буфер, который можно было скопировать в QImage, а затем безопасно освободить.
Итак, посмотрите мой ответ для полностью работающей, эффективной реализации без устаревших вызовов функций: https://stackoverflow.com/a/68212609/7360943