diff --git a/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraImplementations.cpp b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraImplementations.cpp index ada8edddd1..42d62db179 100644 --- a/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraImplementations.cpp +++ b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraImplementations.cpp @@ -18,6 +18,7 @@ #include "CameraWidgetQt6.h" #include "CameraWidgetQt6.5.h" #include "CameraWidgetQt6_QQuickWidget.h" +#include "CameraWidgetQt6_QQuickView.h" namespace PokemonAutomation{ @@ -60,6 +61,10 @@ struct CameraBackends{ "qt6-QQuickWidget", "Qt6: QQuickWidget", std::make_unique() ); + m_backends.emplace_back( + "qt6-QQuickView", "Qt6: QQuickView", + std::make_unique() + ); } size_t items = 0; diff --git a/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.cpp b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.cpp new file mode 100644 index 0000000000..79985ae889 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.cpp @@ -0,0 +1,266 @@ +/* Camera Widget (Qt6 QQuickView) + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common/Qt/Redispatch.h" +#include "VideoFrameQt.h" +#include "QFormatAggregator.h" +#include "MediaServicesQt6.h" +#include "CameraWidgetQt6_QQuickView.h" + +// REMOVE +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace CameraQt6_QQuickView{ + + + + + + +std::vector CameraBackend::get_all_cameras() const{ + const auto cameras = GlobalMediaServices::instance().get_all_cameras(); + std::vector ret; + for (const auto& info : cameras){ + ret.emplace_back(info.id().toStdString()); + } + return ret; +} +std::string CameraBackend::get_camera_name(const CameraInfo& info) const{ + const auto cameras = GlobalMediaServices::instance().get_all_cameras(); + for (const auto& camera : cameras){ + if (camera.id().toStdString() == info.device_name()){ + return camera.description().toStdString(); + } + } + std::cout << "Error: no such camera for CameraInfo: " << info.device_name() << std::endl; + return ""; +} +std::unique_ptr CameraBackend::make_video_source( + Logger& logger, + const CameraInfo& info, + Resolution resolution, + VideoFormat format, + FramesPerSecond fps +) const{ + return std::make_unique(logger, info, resolution, format, fps); +} + + + + + + +CameraVideoSource::~CameraVideoSource(){ + if (!m_capture){ + return; + } + try{ + m_logger.log("Stopping Camera..."); + }catch (...){} + +// cout << "Stopping Camera..." << endl; + + run_on_main_thread_and_wait([&]{ + m_metaobject.reset(); +// m_camera->stop(); + m_capture.reset(); + m_video_sink.reset(); + m_camera.reset(); + }); +} +CameraVideoSource::CameraVideoSource( + Logger& logger, + const CameraInfo& info, + Resolution desired_resolution, + VideoFormat desired_format, + FramesPerSecond desired_fps +) + : VideoSource(logger, true) + , m_logger(logger) + , m_last_frame(logger) + , m_snapshot_manager(logger, m_last_frame) +{ + if (!info){ + return; + } + m_logger.log("Starting Camera: Backend = CameraQt6QVideoSink"); + + run_on_main_thread_and_wait([&]{ + init(info, desired_resolution, desired_format, desired_fps); + }); +} +void CameraVideoSource::init( + const CameraInfo& info, + Resolution desired_resolution, + VideoFormat desired_format, + FramesPerSecond desired_fps +){ + m_metaobject.reset(new QObject()); + + auto cameras = QMediaDevices::videoInputs(); + const QCameraDevice* device = nullptr; + for (const auto& camera : cameras){ + if (camera.id().toStdString() == info.device_name()){ + device = &camera; + break; + } + } + if (device == nullptr){ + m_logger.log("Camera not found: " + info.device_name(), COLOR_RED); + return; + } + + QCameraFormat format = build_format_set( + m_logger, + m_formats, + *device, + desired_resolution, + desired_format, + desired_fps + ); + if (format.isNull()){ + return; + } + + get_format(format, m_resolution, m_format, m_fps); + m_logger.log( + "Resolution: " + m_resolution.to_string() + + ", Format: " + VideoFormat_database().find(m_format)->display + + ", FPS: " + std::to_string(m_fps) + ); + + m_camera.reset(new QCameraThread(m_logger, *device, format)); + m_video_sink.reset(new QVideoSink()); + m_capture.reset(new QMediaCaptureSession()); + m_capture->setCamera(&m_camera->camera()); + m_capture->setVideoSink(m_video_sink.get()); + +#if 0 + connect(m_camera.get(), &QCamera::errorOccurred, this, [&](){ + if (m_camera->error() != QCamera::NoError){ + m_logger.log("QCamera error: " + m_camera->errorString().toStdString()); + } + }); +#endif + m_metaobject->connect( + m_video_sink.get(), &QVideoSink::videoFrameChanged, + &m_camera->camera(), [&](const QVideoFrame& frame){ + // This runs on the QCamera's thread. So it is off the critical path. + + WallClock now = current_time(); + if (!m_last_frame.push_frame(frame, now)){ + return; + } + report_source_frame(std::make_shared(now, frame)); + } + ); +} + + +QWidget* CameraVideoSource::make_display_QtWidget(QWidget* parent){ + return new CameraVideoDisplay(parent, *this); +} + + + +CameraVideoDisplay::~CameraVideoDisplay(){ + m_source.remove_source_frame_listener(*this); +} +CameraVideoDisplay::CameraVideoDisplay(QWidget* parent, CameraVideoSource& source) + : QWidget(parent) + , m_source(source) +{ + cout << "CameraVideoDisplay()" << endl; + + const char QML_CONTENTS[] = + "import QtQuick\n" + "import QtMultimedia\n" + "Item {\n" + " id: root\n" + " anchors.fill: parent\n" + " VideoOutput {\n" + " id: videoOutput\n" + " anchors.fill: parent\n" + " fillMode: VideoOutput.PreserveAspectFit\n" + " }\n" + " Component.onCompleted: {\n" + " if (typeof videoSourceBackend !== \"undefined\") {\n" + " videoSourceBackend.registerQmlSink(videoOutput.videoSink)\n" + " }\n" + " }\n" + "}\n"; + + + QQuickView* view = new QQuickView(); + view->setResizeMode(QQuickView::SizeRootObjectToView); + + // Pass your sink/source data directly to the QML context root properties + view->rootContext()->setContextProperty("videoSourceBackend", this); + + QQmlComponent component(view->engine()); + component.setData(QByteArray(QML_CONTENTS), QUrl()); + + QObject* rootObject = component.create(view->rootContext()); + view->setContent(QUrl(), &component, rootObject); + + // Wrap the native QML window inside a C++ QWidget container! + QWidget* container = QWidget::createWindowContainer(view, this); + container->setMinimumSize(80, 45); + container->setFocusPolicy(Qt::NoFocus); + + this->setLayout(new QVBoxLayout(nullptr)); + this->layout()->setContentsMargins(0, 0, 0, 0); + this->layout()->addWidget(container); + + m_source.add_source_frame_listener(*this); +} +void CameraVideoDisplay::on_frame(std::shared_ptr frame){ + if (m_sink == nullptr){ + return; + } +// cout << "setVideoFrame()" << endl; + m_sink->setVideoFrame(frame->frame); +} +Q_INVOKABLE void CameraVideoDisplay::registerQmlSink(QObject* qml_sink){ + cout << "registerQmlSink(): " << qml_sink << endl; + if (!qml_sink){ + return; + } + + // Safely cast the generic QML object back to a functional QVideoSink pointer + QVideoSink* target_sink = qobject_cast(qml_sink); + cout << "target_sink = " << target_sink << endl; + if (target_sink) { + // Forward frames arriving from your QCamera capture device right into this QML sink! + // Now, every time your capture thread hooks frame details, it routes them to targetSink. + m_sink = target_sink; + } +} + + + + + + + + + + +} +} diff --git a/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.h b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.h new file mode 100644 index 0000000000..8649a9b719 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/VideoPipeline/Backends/CameraWidgetQt6_QQuickView.h @@ -0,0 +1,153 @@ +/* Camera Widget (Qt6 QQuickView) + * + * From: https://github.com/PokemonAutomation/ + * + * Use QML QQuickView. (work in progress) + * + * This is hardware accelerated. But it gets hidden by the VideoOverlay. + * + */ + +#ifndef PokemonAutomation_VideoPipeline_Qt6_QQuickView_H +#define PokemonAutomation_VideoPipeline_Qt6_QQuickView_H + +#include +#include +#include +#include +#include +#include "CommonFramework/Tools/StatAccumulator.h" +#include "CommonFramework/VideoPipeline/VideoSource.h" +#include "CommonFramework/VideoPipeline/CameraInfo.h" +#include "QCameraThread.h" +#include "QVideoFrameCache.h" +#include "SnapshotManager.h" +#include "CameraImplementations.h" + +class QCamera; +class QVideoSink; + +namespace PokemonAutomation{ +namespace CameraQt6_QQuickView{ + + +class CameraBackend : public PokemonAutomation::CameraBackend{ +public: + // Get all cameras' info. + // Note: to avoid freezing the UI while launching the application, the camera backend when + // constructed, use a separate thread to query and load camera info. If you call + // `get_all_cameras()` immidiately after the backend is constructed, it may not give you + // all cameras' info. + virtual std::vector get_all_cameras() const override; + virtual std::string get_camera_name(const CameraInfo& info) const override; + + virtual std::unique_ptr make_video_source( + Logger& logger, + const CameraInfo& info, + Resolution resolution, + VideoFormat format, + FramesPerSecond fps + ) const override; +}; + + + +class CameraVideoSource : public VideoSource{ +public: + virtual ~CameraVideoSource(); + CameraVideoSource( + Logger& logger, + const CameraInfo& info, + Resolution desired_resolution, + VideoFormat desired_format, + FramesPerSecond desired_fps + ); + + virtual Resolution current_resolution() const override{ + return m_resolution; + } + virtual VideoFormat current_format() const override{ + return m_format; + } + virtual FramesPerSecond current_fps() const override{ + return m_fps; + } + virtual const VideoFormatSet& supported_formats() const override{ + return m_formats; + } + + virtual VideoSnapshot snapshot_latest_blocking() override{ + return m_snapshot_manager.snapshot_latest_blocking(); + } + virtual VideoSnapshot snapshot_recent_nonblocking(WallClock min_time) override{ + return m_snapshot_manager.snapshot_recent_nonblocking(min_time); + } + + virtual QWidget* make_display_QtWidget(QWidget* parent) override; + +private: + void init( + const CameraInfo& info, + Resolution desired_resolution, + VideoFormat desired_format, + FramesPerSecond desired_fps + ); +// void set_video_output(QGraphicsVideoItem& item); + + +private: + friend class CameraVideoDisplay; + + std::unique_ptr m_metaobject; + + Logger& m_logger; + Resolution m_resolution; + VideoFormat m_format; + FramesPerSecond m_fps; + + std::unique_ptr m_camera; + std::unique_ptr m_video_sink; + std::unique_ptr m_capture; + + VideoFormatSet m_formats; + + +private: + QVideoFrameCache m_last_frame; + SnapshotManager m_snapshot_manager; +}; + + + +class CameraVideoDisplay : public QWidget, private VideoFrameListener{ + Q_OBJECT +public: + virtual ~CameraVideoDisplay(); + CameraVideoDisplay(QWidget* parent, CameraVideoSource& source); + + virtual void on_frame(std::shared_ptr frame) override; + Q_INVOKABLE void registerQmlSink(QObject* qmlSink); + +private: + CameraVideoSource& m_source; + QVideoSink* m_sink = nullptr; + LifetimeSanitizer m_sanitizer; +}; + + + + + + + + + + + + + + + +} +} +#endif