// LATEST

Capture Video Frames in Qt6

Import a video file and capture a specific frame

Brief intro

The Qt Multimedia module in Qt 6 replaced the one in Qt 5.x. Existing Qt 5 Multimedia code can be migrated with limited effort.

That means you can use QVideoSink to capture a single frame.

Preparation

Include Multimedia.

CMake example:

find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia)
target_link_libraries(QtTest PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt6::Multimedia)

Headers you need:

#include <QMediaPlayer>
#include <QVideoSink>
#include <QVideoFrame>

Usage

Capture a single frame

Add in .h private:

QMediaPlayer* m_player = nullptr;

Sample usage in .cpp:

#include "mainwindow.h"
#include "./ui_mainwindow.h"

#include <QFileDialog>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_player = new QMediaPlayer(this);
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_pushButton_clicked()
{
    QVideoSink* videoSink = new QVideoSink(this);
    m_player->setVideoOutput(videoSink);
    // load video file
    QString str = QFileDialog::getOpenFileName();
    m_player->setSource(QUrl(str));
    // connect QVideoSink videoFrameChanged signal
    connect(videoSink, &QVideoSink::videoFrameChanged, [&](const QVideoFrame &frame) {
            // get frame timestamp
            m_player->setPosition(5000);
            QImage image = frame.toImage(); // convert QVideoFrame to QImage
            image.save("screenshot_at_5_seconds.png"); // save image
            m_player->stop(); // stop playback
        }
    );
    // start playback
    m_player->play();
}

Capture multiple frames

Add a loop in .h:

QEventLoop loop;

If you need a looped capture, use a local event loop:

// every 7 seconds
for (int i = 0; i <= m_player->duration(); i += 7000)
{
    m_player->setPosition(i);
    // use a local event loop to wait for frame capture
    bool frameCaptured = false;
    // connect video frame capture signal
    connect(videoSink, &QVideoSink::videoFrameChanged, this, [&](const QVideoFrame &frame) mutable {
        if (!frameCaptured && frame.isValid()) {
            frameCaptured = true; // set to true after first capture
            QImage image = frame.toImage();
            ui->label->setPixmap(QPixmap::fromImage(image));
            if (!image.isNull()) {
                // save image as jpg
                image.save(QString::number(i) + ".jpg", "JPG");
            }
            // disconnect signal and stop waiting
            disconnect(videoSink, &QVideoSink::videoFrameChanged, this, nullptr);
        }
    });
    // start local event loop and wait for frame capture
    loop.exec();
}

No comments yet

Share your thoughts and keep the conversation clear and kind.