Эффективное преобразование AVFrame в QImage

Мне нужно извлечь кадры из видео в моем приложении на основе Qt. Используя библиотеки ffmpeg, я могу получать кадры как AVFrames, которые мне нужно преобразовать в QImage для использования в других частях моего приложения. Это преобразование должно быть эффективным. Пока кажется, что sws_scale() — это правильная функция для использования, но я не уверен, какие исходные и целевые форматы пикселей должны быть указаны.


person S B    schedule 26.10.2012    source источник
comment
Преобразование в QImage будет не очень эффективным... qtcentre.org/ threads/9935-QImage-data-через-FFmpeg   -  person vipw    schedule 29.10.2012


Ответы (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 );
}

Дополнительные сведения см. по ссылке.

person kopalvich    schedule 09.04.2015
comment
Выглядит лучше... Я бы просто заменил mWidth * 3 на frameRGB-›linesize[0] - person Rafael Fontes; 16.04.2015
comment
Это выглядит намного лаконичнее по сравнению с моим предыдущим ответом. Отметить как лучший ответ. - person S B; 08.03.2016

Придумал следующий двухэтапный процесс, который сначала преобразует декодированный 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];
}

Если вы найдете более эффективный подход, дайте мне знать в комментариях

person S B    schedule 12.12.2012
comment
вы можете просто скопировать данные из вашего pFrameRGB в новый QImage (RGB888) и выполнить внутреннее преобразование - person Andi Krusch; 30.07.2014

Я думаю, что более простой подход:

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");
}
person agrau    schedule 06.03.2017
comment
Добро пожаловать в stackoverflow! Хотя код, представленный в вашем ответе, может быть решением вопроса, всегда полезно включить краткое объяснение того, что делает ваш код. - person morten.c; 06.03.2017
comment
Кажется, это также позволяет избежать копирования, производительность равна решению @mike_wei. - person Lucker10; 05.11.2018

Сегодня я протестировал непосредственную передачу 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);
person mike-wei    schedule 11.09.2016
comment
Это потрясающе, особенно если вам все равно нужно конвертировать в RGB. Полчаса ушло на FHD кадр! (8 мс -> 4 мс) - person Lucker10; 05.11.2018

Я только что обнаружил, что scanLine просто ищет через буфер. Все, что вам нужно, это использовать AV_PIX_FMT_RGB32 для AVFrame и QImage::FORMAT_RGB32 для QImage.

Затем после декодирования просто выполните memcpy

memcpy(img.scanLine(0), pFrameRGB->data[0], pFrameRGB->linesize[0] * pFrameRGB->height());

person Rafael Fontes    schedule 23.07.2015

У меня были проблемы с другими предлагаемыми решениями, такими как:

  • Они не упомянули об освобождении ни AVFrame, ни SwsContext, ни выделенных буферов, что вызвало массовые утечки памяти (у меня были тысячи кадров для обработки). Все эти проблемы не могут быть легко решены, поскольку QImage опирается на базовые данные, а не копирует их. При непосредственном освобождении буфера QImage указывает на освобожденные данные и прерывается. Это можно решить, используя функцию очистки QImage для освобождения буфера, когда изображение больше не нужно, но с другими проблемами это все равно было нехорошо.
  • В некоторых случаях одно из предложений по передаче QImage.bits непосредственно в sws_scale не будет работать, поскольку QImage выровнен как минимум по 32 битам. Поэтому для определенных размеров он не будет соответствовать ожидаемой ширине по sws_scale и выводить каждую строку немного сдвинутым.
  • Третья проблема заключается в том, что они использовали устаревшие элементы AVPicture.

Я перечислил проблему в другом вопросе Преобразование AVFrame в QImage с преобразованием формата пикселей и, в конце концов, нашел решение, используя временный буфер, который можно было скопировать в QImage, а затем безопасно освободить.

Итак, посмотрите мой ответ для полностью работающей, эффективной реализации без устаревших вызовов функций: https://stackoverflow.com/a/68212609/7360943

person Théophane    schedule 01.07.2021