Простой спрособ подключения произвольного видеоисточника в Qml

в 7:29, , рубрики: c++, QML, qt, QtMultimedia

Преамбула

Все нижеизложенное приводится в контексте Qt версии 5.3.1 (как наиболее актуальной на данный момент), но имеет смысл в контексте любой версии ветки 5.x, а возможно даже 4.8.x (не проверял за ненадобностью).

Операционная система — Windows, среда разработки — QtCreator 3.1.2 в связке с MinGW и gcc 4.8.2 От использования других платформ/IDE/компиляторов суть не меняется.

В качестве иточника видеоданных был выбран самый простой из доступных вариантов, а именно — рабочий стол. Т.е. приложение будет отображать копию всего что происходит на рабочем столе. В многомониторных конфигурациях, в качестве источника, будем использовать основной экран.

Итак, приступим

В качестве отправной точки можно почитать документацию: «Video Overview:Working with Low Level Video Frames».

Несколько тезисов, которые необходимо почерпнуть из этой статьи:

  • Источник видеоданных должен являться потомком QObject;
  • Он должен объявлять property videoSurface типа QAbstractVideoSurface*;
  • Он должен вызывать QAbstractVideoSurface::start, с передачей QVideoSurfaceFormat, перед началом воспроизведения;
  • Он должен вызывать QAbstractVideoSurface::present, с передачей QVideoFrame, для отображения каждого кадра;
  • Он должен вызывать QAbstractVideoSurface::stop, по завершении воспроизведения ролика.

Пишем код

Создаем новый проект «Qt Quick Application». Важно выбрать именно этот тип тип приложения, поскольку в дальнейшем мы будем создавать Qml компоненту с использованием C++.

Далее создаем класс, потомок QObject, и начинаем его расширять.

Посколку то, что получилось в итоге, достаточно просто и немногословно, не буду лить много воды, а просто приведу код, с некоторыми комментариями:

DesktopVideoProducer.h:

#pragma once

#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>

class DesktopVideoProducer : public QObject
{
    Q_OBJECT
public:
    //Для того чтобы класс был доступен для использования в Qml его необходимо регистрировать
    static void registerQmlType();

    explicit DesktopVideoProducer( QObject *parent = 0 );
    ~DesktopVideoProducer();

   //то самое property, упомянутое выше
    Q_PROPERTY( QAbstractVideoSurface* videoSurface READ videoSurface WRITE setVideoSurface )

    QAbstractVideoSurface* videoSurface() const;
    void setVideoSurface( QAbstractVideoSurface* s );

protected:
    void timerEvent( QTimerEvent* );

private:
    void closeSurface();

private:
    QAbstractVideoSurface* _surface;
    QVideoSurfaceFormat _format;
};

DesktopVideoProducer.cpp:

#include "DesktopVideoProducer.h"

#include <QtQml/qqml.h>

#include <QApplication>
#include <QScreen>
#include <QDesktopWidget>

void DesktopVideoProducer::registerQmlType()
{
    //Регистрируем наш класс в составе пакета DesktopVideoProducer,
    //под версией 0.1, под именем DesktopVideoProducer.
    //Нижележащая строчка является подсказкой для парсера типов QtCreator,
    //и она не обязательна.
    // @uri DesktopVideoProducer
    qmlRegisterType<DesktopVideoProducer>(
        "DesktopVideoProducer", 0, 1,
        "DesktopVideoProducer" );
}

DesktopVideoProducer::DesktopVideoProducer( QObject *parent )
    : QObject( parent ), _surface( 0 )
{
    startTimer( 1000 / 15 ); //15 fps
}

DesktopVideoProducer::~DesktopVideoProducer()
{
    closeSurface();
}

QAbstractVideoSurface* DesktopVideoProducer::videoSurface() const
{
    return _surface;
}

void DesktopVideoProducer::setVideoSurface( QAbstractVideoSurface* s )
{
    closeSurface();
    _surface = s;
}

void DesktopVideoProducer::closeSurface()
{
    if( _surface && _surface->isActive() )
        _surface->stop();
}

void DesktopVideoProducer::timerEvent( QTimerEvent* )
{
    if( !_surface )
        return;

    QScreen* screen = QGuiApplication::primaryScreen();
    QDesktopWidget* desktop = QApplication::desktop();

    if( !screen || !desktop )
        return;

    //Получим screenshot и преобразуем в экземпляр класса подходящий для QVideoFrame
    QPixmap screenPixmap = screen->grabWindow( desktop->screen()->winId() );
    QImage screenImage = screenPixmap.toImage();
    QVideoFrame::PixelFormat pixelFormat =
        QVideoFrame::pixelFormatFromImageFormat( screenImage.format() );

    //если формат кадра по какой-то причине поменялся (или это первый кадр)-
    //выполним повторную (первичную) инициализацию surface
    if( screenPixmap.size() != _format.frameSize() ||
        pixelFormat != _format.pixelFormat() )
    {
        closeSurface();
        _format =
            QVideoSurfaceFormat( screenPixmap.size(),
                                 pixelFormat );
        _surface->start( _format );
    }

    //передадим полученный кадр на отрисовку
    _surface->present( QVideoFrame( screenImage ) );
}

main.qml:

import QtQuick 2.2
import QtQuick.Window 2.1
import QtMultimedia 5.0
import DesktopVideoProducer 0.1

Window {
    visible: true
    width: 360
    height: 360

    DesktopVideoProducer {
        id: videoProducer;
    }

    VideoOutput {
        anchors.fill: parent;
        source: videoProducer;
    }
}

main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>

#include"DesktopVideoProducer.h"

int main(int argc, char *argv[])
{
    //зарегистрируем DesktopVideoProducer для использования в Qml
    DesktopVideoProducer::registerQmlType();

    //для возможности вызова QApplication::desktop() QGuiApplication недостаточно
    //QGuiApplication app(argc, argv);
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    return app.exec();
}

P.S.: Полный проект доступен для скачивания с GitHub.

Автор: RSATom

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js