diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 8d73cab9c0d5..dd5dc8ec1691 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -1222,19 +1222,19 @@ contains (CONFIG, DISABLE_VIDEOSTREAMING) { QT += \ opengl \ gui-private - include(src/VideoReceiver/VideoReceiver.pri) + include(src/VideoManager/VideoReceiver/VideoReceiver.pri) } !VideoEnabled { INCLUDEPATH += \ - src/VideoReceiver + src/VideoManager/VideoReceiver HEADERS += \ - src/VideoManager/GLVideoItemStub.h \ - src/VideoReceiver/VideoReceiver.h + src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.h \ + src/VideoManager/VideoReceiver/VideoReceiver.h SOURCES += \ - src/VideoManager/GLVideoItemStub.cc + src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.cc } #------------------------------------------------------------------------------------- diff --git a/src/API/QGCCorePlugin.cc b/src/API/QGCCorePlugin.cc index e30aba5d0e6a..6f8d9269c997 100644 --- a/src/API/QGCCorePlugin.cc +++ b/src/API/QGCCorePlugin.cc @@ -19,8 +19,8 @@ #include "JoystickManager.h" #if defined(QGC_GST_STREAMING) #include "GStreamer.h" -#include "VideoReceiver.h" #endif +#include "VideoReceiver.h" #include "HorizontalFactValueGrid.h" #include "InstrumentValueData.h" #include "QGCLoggingCategory.h" @@ -333,8 +333,7 @@ VideoReceiver* QGCCorePlugin::createVideoReceiver(QObject* parent) #if defined(QGC_GST_STREAMING) return GStreamer::createVideoReceiver(parent); #else - Q_UNUSED(parent) - return nullptr; + return QtMultimediaReceiver::createVideoReceiver(parent); #endif } @@ -343,9 +342,7 @@ void* QGCCorePlugin::createVideoSink(QObject* parent, QQuickItem* widget) #if defined(QGC_GST_STREAMING) return GStreamer::createVideoSink(parent, widget); #else - Q_UNUSED(parent) - Q_UNUSED(widget) - return nullptr; + return QtMultimediaReceiver::createVideoSink(parent, widget); #endif } @@ -354,7 +351,7 @@ void QGCCorePlugin::releaseVideoSink(void* sink) #if defined(QGC_GST_STREAMING) GStreamer::releaseVideoSink(sink); #else - Q_UNUSED(sink) + QtMultimediaReceiver::releaseVideoSink(sink); #endif } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b39a53b1b97..1a37cd520fa5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,6 @@ add_subdirectory(UTMSP) add_subdirectory(Vehicle) add_subdirectory(VehicleSetup) add_subdirectory(VideoManager) -add_subdirectory(VideoReceiver) add_subdirectory(Viewer3D) ####################################################### diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index 86c032b42064..dc14815adcf1 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -28,10 +28,6 @@ #include #include -#if defined(QGC_GST_STREAMING) -#include "GStreamer.h" -#endif - #include "QGCConfig.h" #include "QGCApplication.h" #include "CmdLineOptParser.h" @@ -242,16 +238,6 @@ QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) // Set up our logging filters QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(loggingOptions); -#if defined(QGC_GST_STREAMING) - // Gstreamer debug settings - int gstDebugLevel = 0; - if (settings.contains(AppSettings::gstDebugLevelName)) { - gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt(); - } - // Initialize Video Receiver - GStreamer::initialize(argc, argv, gstDebugLevel); -#endif - // We need to set language as early as possible prior to loading on JSON files. setLanguage(); diff --git a/src/Settings/CMakeLists.txt b/src/Settings/CMakeLists.txt index ac4b08feba6f..254033eb0a77 100644 --- a/src/Settings/CMakeLists.txt +++ b/src/Settings/CMakeLists.txt @@ -51,6 +51,7 @@ target_link_libraries(Settings API QmlControls Vehicle + VideoReceiver PUBLIC Qt6::Core Qt6::Qml diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 68f10959614a..a846883fe005 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -48,7 +48,6 @@ #include "VehicleBatteryFactGroup.h" #include "VehicleObjectAvoidance.h" #include "VideoManager.h" -#include "VideoReceiver.h" #include "VideoSettings.h" #ifdef CONFIG_UTM_ADAPTER @@ -1474,11 +1473,9 @@ void Vehicle::_updateArmed(bool armed) _trajectoryPoints->stop(); _flightTimerStop(); // Also handle Video Streaming - if(qgcApp()->toolbox()->videoManager()->videoReceiver()) { - if(_settingsManager->videoSettings()->disableWhenDisarmed()->rawValue().toBool()) { - _settingsManager->videoSettings()->streamEnabled()->setRawValue(false); - qgcApp()->toolbox()->videoManager()->videoReceiver()->stop(); - } + if(_settingsManager->videoSettings()->disableWhenDisarmed()->rawValue().toBool()) { + _settingsManager->videoSettings()->streamEnabled()->setRawValue(false); + qgcApp()->toolbox()->videoManager()->stopVideo(); } } } diff --git a/src/VideoManager/CMakeLists.txt b/src/VideoManager/CMakeLists.txt index df673a40f48c..581df93837c5 100644 --- a/src/VideoManager/CMakeLists.txt +++ b/src/VideoManager/CMakeLists.txt @@ -1,8 +1,8 @@ -find_package(Qt6 REQUIRED COMPONENTS Core Quick) +add_subdirectory(VideoReceiver) + +find_package(Qt6 REQUIRED COMPONENTS Core) qt_add_library(VideoManager STATIC - GLVideoItemStub.cc - GLVideoItemStub.h SubtitleWriter.cc SubtitleWriter.h VideoManager.cc @@ -14,23 +14,16 @@ target_link_libraries(VideoManager API Camera FactSystem + GStreamerReceiver QmlControls + QtMultimediaReceiver Settings Utilities Vehicle + VideoReceiver PUBLIC Qt6::Core - Qt6::Quick QGC - VideoReceiver ) -option(QGC_DISABLE_UVC "Disable UVC Devices" OFF) -if(QGC_DISABLE_UVC) - target_compile_definitions(VideoManager PUBLIC QGC_DISABLE_UVC) -else() - find_package(Qt6 REQUIRED COMPONENTS Multimedia) - target_link_libraries(VideoManager PRIVATE Qt6::Multimedia) -endif() - target_include_directories(VideoManager PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/VideoManager/SubtitleWriter.cc b/src/VideoManager/SubtitleWriter.cc index c3621b4cab5a..dc1324aed10a 100644 --- a/src/VideoManager/SubtitleWriter.cc +++ b/src/VideoManager/SubtitleWriter.cc @@ -26,6 +26,7 @@ #include #include #include +#include QGC_LOGGING_CATEGORY(SubtitleWriterLog, "SubtitleWriterLog") @@ -33,8 +34,9 @@ const int SubtitleWriter::_sampleRate = 1; // Sample rate in Hz for getting tele SubtitleWriter::SubtitleWriter(QObject* parent) : QObject(parent) + , _timer(new QTimer(this)) { - connect(&_timer, &QTimer::timeout, this, &SubtitleWriter::_captureTelemetry); + connect(_timer, &QTimer::timeout, this, &SubtitleWriter::_captureTelemetry); } void SubtitleWriter::startCapturingTelemetry(const QString& videoFile) @@ -93,13 +95,13 @@ void SubtitleWriter::startCapturingTelemetry(const QString& videoFile) // TODO: Find a good way to input title //stream << QStringLiteral("Dialogue: 0,0:00:00.00,999:00:00.00,Default,,0,0,0,,{\\pos(5,35)}%1\n"); - _timer.start(1000/_sampleRate); + _timer->start(1000/_sampleRate); } void SubtitleWriter::stopCapturingTelemetry() { qCDebug(SubtitleWriterLog) << "Stopping writing"; - _timer.stop(); + _timer->stop(); _file.close(); } diff --git a/src/VideoManager/SubtitleWriter.h b/src/VideoManager/SubtitleWriter.h index 0f7bffbfd149..7535e812be40 100644 --- a/src/VideoManager/SubtitleWriter.h +++ b/src/VideoManager/SubtitleWriter.h @@ -17,12 +17,12 @@ #pragma once #include -#include #include #include #include class Fact; +class QTimer; Q_DECLARE_LOGGING_CATEGORY(SubtitleWriterLog) @@ -43,7 +43,7 @@ private slots: void _captureTelemetry(); private: - QTimer _timer; + QTimer* _timer = nullptr; QList _facts; QTime _lastEndTime; QFile _file; diff --git a/src/VideoManager/VideoManager.cc b/src/VideoManager/VideoManager.cc index 6a9fa2b140ef..c39e0ce897af 100644 --- a/src/VideoManager/VideoManager.cc +++ b/src/VideoManager/VideoManager.cc @@ -7,22 +7,26 @@ * ****************************************************************************/ -#include "QGCApplication.h" #include "VideoManager.h" +#include "QGCApplication.h" #include "QGCToolbox.h" #include "QGCCorePlugin.h" #include "MultiVehicleManager.h" #include "SettingsManager.h" +#include "SubtitleWriter.h" #include "Vehicle.h" #include "QGCCameraManager.h" #include "QGCLoggingCategory.h" -#include #include "VideoReceiver.h" +#include "VideoSettings.h" + +#include +#include +#include +#include #if defined(QGC_GST_STREAMING) #include "GStreamer.h" -#include "VideoSettings.h" -#include #else #include "GLVideoItemStub.h" #endif @@ -31,22 +35,20 @@ #include #include #include -#include #endif QGC_LOGGING_CATEGORY(VideoManagerLog, "VideoManagerLog") -#if defined(QGC_GST_STREAMING) static const char* kFileExtension[VideoReceiver::FILE_FORMAT_MAX - VideoReceiver::FILE_FORMAT_MIN] = { "mkv", "mov", "mp4" }; -#endif //----------------------------------------------------------------------------- VideoManager::VideoManager(QGCApplication* app, QGCToolbox* toolbox) : QGCTool(app, toolbox) + , _subtitleWriter(new SubtitleWriter(this)) { #if !defined(QGC_GST_STREAMING) static bool once = false; @@ -69,7 +71,7 @@ VideoManager::~VideoManager() if (_videoSink[i] != nullptr) { // FIXME: AV: we need some interaface for video sink with .release() call // Currently VideoManager is destroyed after corePlugin() and we are crashing on app exit - // calling qgcApp()->toolbox()->corePlugin()->releaseVideoSink(_videoSink[i]); + // calling _toolbox->corePlugin()->releaseVideoSink(_videoSink[i]); // As for now let's call GStreamer::releaseVideoSink() directly GStreamer::releaseVideoSink(_videoSink[i]); _videoSink[i] = nullptr; @@ -96,19 +98,35 @@ VideoManager::setToolbox(QGCToolbox *toolbox) connect(_videoSettings->tcpUrl(), &Fact::rawValueChanged, this, &VideoManager::_tcpUrlChanged); connect(_videoSettings->aspectRatio(), &Fact::rawValueChanged, this, &VideoManager::_aspectRatioChanged); connect(_videoSettings->lowLatencyMode(),&Fact::rawValueChanged, this, &VideoManager::_lowLatencyModeChanged); - MultiVehicleManager *pVehicleMgr = qgcApp()->toolbox()->multiVehicleManager(); + MultiVehicleManager *pVehicleMgr = _toolbox->multiVehicleManager(); connect(pVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &VideoManager::_setActiveVehicle); -#if defined(QGC_GST_STREAMING) - GStreamer::blacklist(static_cast(_videoSettings->forceVideoDecoder()->rawValue().toInt())); #ifndef QGC_DISABLE_UVC // If we are using a UVC camera setup the device name _updateUVC(); #endif +#if defined(QGC_GST_STREAMING) + // Gstreamer debug settings + int gstDebugLevel = 0; + QSettings settings; + if (settings.contains(AppSettings::gstDebugLevelName)) { + gstDebugLevel = settings.value(AppSettings::gstDebugLevelName).toInt(); + } + // Initialize Video Receiver + QStringList args = _app->arguments(); + char* argv[args.size()]; + for (int i = 0; i < args.size(); i++) { + argv[i] = args[i].toUtf8().data(); + } + GStreamer::initialize(args.size(), argv, gstDebugLevel); + + GStreamer::blacklist(static_cast(_videoSettings->forceVideoDecoder()->rawValue().toInt())); + emit isGStreamerChanged(); qCDebug(VideoManagerLog) << "New Video Source:" << videoSource; -#if defined(QGC_GST_STREAMING) +#endif + _videoReceiver[0] = toolbox->corePlugin()->createVideoReceiver(this); _videoReceiver[1] = toolbox->corePlugin()->createVideoReceiver(this); @@ -157,14 +175,14 @@ VideoManager::setToolbox(QGCToolbox *toolbox) qCDebug(VideoManagerLog) << "Video 0 recording changed, active: " << (active ? "yes" : "no"); _recording = active; if (!active) { - _subtitleWriter.stopCapturingTelemetry(); + _subtitleWriter->stopCapturingTelemetry(); } emit recordingChanged(); }); connect(_videoReceiver[0], &VideoReceiver::recordingStarted, this, [this](){ qCDebug(VideoManagerLog) << "Video 0 recording started"; - _subtitleWriter.startCapturingTelemetry(_videoFile); + _subtitleWriter->startCapturingTelemetry(_videoFile); }); connect(_videoReceiver[0], &VideoReceiver::videoSizeChanged, this, [this](QSize size){ @@ -201,7 +219,7 @@ VideoManager::setToolbox(QGCToolbox *toolbox) _startReceiver(1); }); } -#endif + _updateSettings(0); _updateSettings(1); if(isGStreamer()) { @@ -209,18 +227,15 @@ VideoManager::setToolbox(QGCToolbox *toolbox) } else { stopVideo(); } - -#endif } void VideoManager::_cleanupOldVideos() { -#if defined(QGC_GST_STREAMING) //-- Only perform cleanup if storage limit is enabled if(!_videoSettings->enableStorageLimit()->rawValue().toBool()) { return; } - QString savePath = qgcApp()->toolbox()->settingsManager()->appSettings()->videoSavePath(); + QString savePath = _toolbox->settingsManager()->appSettings()->videoSavePath(); QDir videoDir = QDir(savePath); videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable); videoDir.setSorting(QDir::Time); @@ -251,14 +266,13 @@ void VideoManager::_cleanupOldVideos() vidList.removeLast(); } } -#endif } //----------------------------------------------------------------------------- void VideoManager::startVideo() { - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } @@ -275,7 +289,7 @@ VideoManager::startVideo() void VideoManager::stopVideo() { - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } @@ -286,19 +300,18 @@ VideoManager::stopVideo() void VideoManager::startRecording(const QString& videoFile) { - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } -#if defined(QGC_GST_STREAMING) if (!_videoReceiver[0]) { - qgcApp()->showAppMessage(tr("Video receiver is not ready.")); + _app->showAppMessage(tr("Video receiver is not ready.")); return; } const VideoReceiver::FILE_FORMAT fileFormat = static_cast(_videoSettings->recordingFormat()->rawValue().toInt()); if(fileFormat < VideoReceiver::FILE_FORMAT_MIN || fileFormat >= VideoReceiver::FILE_FORMAT_MAX) { - qgcApp()->showAppMessage(tr("Invalid video format defined.")); + _app->showAppMessage(tr("Invalid video format defined.")); return; } QString ext = kFileExtension[fileFormat - VideoReceiver::FILE_FORMAT_MIN]; @@ -306,10 +319,10 @@ VideoManager::startRecording(const QString& videoFile) //-- Disk usage maintenance _cleanupOldVideos(); - QString savePath = qgcApp()->toolbox()->settingsManager()->appSettings()->videoSavePath(); + QString savePath = _toolbox->settingsManager()->appSettings()->videoSavePath(); if (savePath.isEmpty()) { - qgcApp()->showAppMessage(tr("Unabled to record video. Video save path must be specified in Settings.")); + _app->showAppMessage(tr("Unabled to record video. Video save path must be specified in Settings.")); return; } @@ -325,41 +338,35 @@ VideoManager::startRecording(const QString& videoFile) if (_videoReceiver[1] && _videoStarted[1]) { _videoReceiver[1]->startRecording(videoFile2, fileFormat); } - -#else - Q_UNUSED(videoFile) -#endif } void VideoManager::stopRecording() { - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } -#if defined(QGC_GST_STREAMING) for (int i = 0; i < 2; i++) { if (_videoReceiver[i]) { _videoReceiver[i]->stopRecording(); } } -#endif } void VideoManager::grabImage(const QString& imageFile) { - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } -#if defined(QGC_GST_STREAMING) + if (!_videoReceiver[0]) { return; } if (imageFile.isEmpty()) { - _imageFile = qgcApp()->toolbox()->settingsManager()->appSettings()->photoSavePath(); + _imageFile = _toolbox->settingsManager()->appSettings()->photoSavePath(); _imageFile += + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss.zzz") + ".jpg"; } else { _imageFile = imageFile; @@ -368,9 +375,6 @@ VideoManager::grabImage(const QString& imageFile) emit imageFileChanged(); _videoReceiver[0]->takeScreenshot(_imageFile); -#else - Q_UNUSED(imageFile) -#endif } //----------------------------------------------------------------------------- @@ -448,14 +452,13 @@ VideoManager::imageFile() bool VideoManager::autoStreamConfigured() { -#if defined(QGC_GST_STREAMING) if(_activeVehicle && _activeVehicle->cameraManager()) { QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->currentStreamInstance(); if(pInfo) { return !pInfo->uri().isEmpty(); } } -#endif + return false; } @@ -481,16 +484,14 @@ VideoManager::_updateUVC() if (oldUvcVideoSrcID != _uvcVideoSourceID) { qCDebug(VideoManagerLog) << "UVC changed from [" << oldUvcVideoSrcID << "] to [" << _uvcVideoSourceID << "]"; -#if QT_CONFIG(permissions) QCameraPermission cameraPermission; if (qApp->checkPermission(cameraPermission) == Qt::PermissionStatus::Undetermined) { - qApp->requestPermission(cameraPermission, [](const QPermission &permission) { + qApp->requestPermission(cameraPermission, [this](const QPermission &permission) { if (permission.status() == Qt::PermissionStatus::Granted) { - qgcApp()->showRebootAppMessage(tr("Restart application for changes to take effect.")); + _app->showRebootAppMessage(tr("Restart application for changes to take effect.")); } }); } -#endif emit uvcVideoSourceIDChanged(); emit isUvcChanged(); } @@ -557,7 +558,6 @@ VideoManager::hasVideo() bool VideoManager::isGStreamer() { -#if defined(QGC_GST_STREAMING) QString videoSource = _videoSettings->videoSource()->rawValue().toString(); return videoSource == VideoSettings::videoSourceUDPH264 || videoSource == VideoSettings::videoSourceUDPH265 || @@ -570,9 +570,6 @@ VideoManager::isGStreamer() videoSource == VideoSettings::videoSourceHerelinkAirUnit || videoSource == VideoSettings::videoSourceHerelinkHotspot || autoStreamConfigured(); -#else - return false; -#endif } bool @@ -614,8 +611,7 @@ VideoManager::setfullScreen(bool f) void VideoManager::_initVideo() { -#if defined(QGC_GST_STREAMING) - QQuickWindow* root = qgcApp()->mainRootWindow(); + QQuickWindow* root = _app->mainRootWindow(); if (root == nullptr) { qCDebug(VideoManagerLog) << "mainRootWindow() failed. No root window"; @@ -625,7 +621,7 @@ VideoManager::_initVideo() QQuickItem* widget = root->findChild("videoContent"); if (widget != nullptr && _videoReceiver[0] != nullptr) { - _videoSink[0] = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget); + _videoSink[0] = _toolbox->corePlugin()->createVideoSink(this, widget); if (_videoSink[0] != nullptr) { if (_videoStarted[0]) { _videoReceiver[0]->startDecoding(_videoSink[0]); @@ -640,7 +636,7 @@ VideoManager::_initVideo() widget = root->findChild("thermalVideo"); if (widget != nullptr && _videoReceiver[1] != nullptr) { - _videoSink[1] = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget); + _videoSink[1] = _toolbox->corePlugin()->createVideoSink(this, widget); if (_videoSink[1] != nullptr) { if (_videoStarted[1]) { _videoReceiver[1]->startDecoding(_videoSink[1]); @@ -651,7 +647,6 @@ VideoManager::_initVideo() } else { qCDebug(VideoManagerLog) << "thermal video receiver disabled"; } -#endif } //----------------------------------------------------------------------------- @@ -779,15 +774,10 @@ VideoManager::_updateVideoUri(unsigned id, const QString& uri) void VideoManager::_restartVideo(unsigned id) { -#if !defined(QGC_GST_STREAMING) - Q_UNUSED(id); -#endif - - if (qgcApp()->runningUnitTests()) { + if (_app->runningUnitTests()) { return; } -#if defined(QGC_GST_STREAMING) bool oldLowLatencyStreaming = _lowLatencyStreaming[id]; QString oldUri = _videoUri[id]; _updateSettings(id); @@ -807,7 +797,6 @@ VideoManager::_restartVideo(unsigned id) } else { _startReceiver(id); } -#endif } //----------------------------------------------------------------------------- @@ -822,7 +811,6 @@ VideoManager::_restartAllVideos() void VideoManager::_startReceiver(unsigned id) { -#if defined(QGC_GST_STREAMING) const QString source = _videoSettings->videoSource()->rawValue().toString(); const unsigned rtsptimeout = _videoSettings->rtspTimeout()->rawValue().toUInt(); /* The gstreamer rtsp source will switch to tcp if udp is not available after 5 seconds. @@ -836,24 +824,17 @@ VideoManager::_startReceiver(unsigned id) _videoReceiver[id]->start(_videoUri[id], timeout, _lowLatencyStreaming[id] ? -1 : 0); } } -#else - Q_UNUSED(id); -#endif } //---------------------------------------------------------------------------------------- void VideoManager::_stopReceiver(unsigned id) { -#if defined(QGC_GST_STREAMING) if (id > 1) { qCDebug(VideoManagerLog) << "Unsupported receiver id" << id; } else if (_videoReceiver[id] != nullptr) { _videoReceiver[id]->stop(); } -#else - Q_UNUSED(id); -#endif } //---------------------------------------------------------------------------------------- @@ -904,3 +885,40 @@ VideoManager::_aspectRatioChanged() { emit aspectRatioChanged(); } + +// qputenv("QT_DISABLE_HW_TEXTURES_CONVERSION", "1"); +// qputenv("QT_FFMPEG_DECODING_HW_DEVICE_TYPES", ","); +// qputenv("QT_FFMPEG_DEBUG", "1"); +// qputenv("QSG_INFO", "1"); +// qputenv("QSG_RENDERER_DEBUG", "render"); +// qputenv("QSG_ATLAS_OVERLAY", "1"); +// qputenv("QSG_RENDER_TIMING", "1"); +// qputenv("QSG_VISUALIZE", ""); + +// QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); +// logger->initialize(); +// connect(logger, &QOpenGLDebugLogger::messageLogged, receiver, &LogHandler::handleLoggedMessage); +// logger->startLogging(); + +// #if defined(Q_OS_ANDROID) +// qputenv("QT_MEDIA_BACKEND", "android"); +// QQuickWindow::setGraphicsApi(QSGRendererInterface::VulkanRhi); +// qCDebug(AppLog) << config.deviceExtensions(); +// #elif defined(Q_OS_LINUX) +// qputenv("QT_MEDIA_BACKEND", "gstreamer"); +// QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi); +// #elif defined(Q_OS_WINDOWS) +// qputenv("QT_MEDIA_BACKEND", "ffmpeg"); +// // qputenv("QT_MEDIA_BACKEND", "windows"); +// // QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12); +// #endif + +// #if QT_DEBUG +// QQuickGraphicsDevice device = rootWindow()->graphicsDevice(); +// QQuickGraphicsConfiguration config = rootWindow()->graphicsConfiguration(); +// config.setDebugLayer(true); +// config.setDebugMarkers(true); +// config.setTimestamps(false); +// config.setPreferSoftwareDevice(true); +// rootWindow()->setGraphicsConfiguration(config); +// #endif diff --git a/src/VideoManager/VideoManager.h b/src/VideoManager/VideoManager.h index 7dec3b48de16..a9f55ee53a34 100644 --- a/src/VideoManager/VideoManager.h +++ b/src/VideoManager/VideoManager.h @@ -149,7 +149,7 @@ protected slots: protected: QString _videoFile; QString _imageFile; - SubtitleWriter _subtitleWriter; + SubtitleWriter* _subtitleWriter; VideoReceiver* _videoReceiver[2] = { nullptr, nullptr }; void* _videoSink[2] = { nullptr, nullptr }; QString _videoUri[2]; diff --git a/src/VideoManager/VideoReceiver/CMakeLists.txt b/src/VideoManager/VideoReceiver/CMakeLists.txt new file mode 100644 index 000000000000..9a1c613cb38b --- /dev/null +++ b/src/VideoManager/VideoReceiver/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt6 REQUIRED COMPONENTS Core) + +qt_add_library(VideoReceiver STATIC VideoReceiver.h) + +target_link_libraries(VideoReceiver + PUBLIC + Qt6::Core + INTERFACE + GStreamerReceiver + QtMultimediaReceiver +) + +add_subdirectory(GStreamer) +add_subdirectory(QtMultimedia) + +# target_sources(GStreamerReceiver PUBLIC VideoReceiver.h) +# target_sources(QtMultimediaReceiver PUBLIC VideoReceiver.h) +target_include_directories(GStreamerReceiver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(QtMultimediaReceiver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt b/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt new file mode 100644 index 000000000000..03f74e99268b --- /dev/null +++ b/src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt @@ -0,0 +1,45 @@ +add_subdirectory(${CMAKE_SOURCE_DIR}/libs/qmlglsink qmlglsink.build) + +if(NOT GST_QT6_PLUGIN_FOUND) + return() +endif() + +message(STATUS "Building GStreamer VideoReceiver") + +find_package(Qt6 REQUIRED COMPONENTS Core Quick) + +qt_add_library(GStreamerReceiver STATIC + GLVideoItemStub.cc + GLVideoItemStub.h + gstqgc.c + gstqgcvideosinkbin.c + GStreamer.cc + GStreamer.h + GstVideoReceiver.cc + GstVideoReceiver.h +) + +if(IOS) + target_sources(GStreamerReceiver + PRIVATE + gst_ios_init.h + gst_ios_init.m + ) +endif() + +target_link_libraries(GStreamerReceiver + PRIVATE + Qt6::Quick + qmlglsink + Utilities + PUBLIC + Qt6::Core + Settings +) + +target_include_directories(GStreamerReceiver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(GStreamerReceiver PUBLIC QGC_GST_STREAMING) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + target_compile_definitions(GStreamerReceiver PRIVATE QGC_INSTALL_RELEASE) +endif() diff --git a/src/VideoManager/GLVideoItemStub.cc b/src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.cc similarity index 100% rename from src/VideoManager/GLVideoItemStub.cc rename to src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.cc diff --git a/src/VideoManager/GLVideoItemStub.h b/src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.h similarity index 100% rename from src/VideoManager/GLVideoItemStub.h rename to src/VideoManager/VideoReceiver/GStreamer/GLVideoItemStub.h diff --git a/src/VideoReceiver/GStreamer.cc b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc similarity index 98% rename from src/VideoReceiver/GStreamer.cc rename to src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc index a3c546656a96..c6f0627bab8a 100644 --- a/src/VideoReceiver/GStreamer.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc @@ -19,6 +19,7 @@ #include "QGCLoggingCategory.h" #include +#include QGC_LOGGING_CATEGORY(GStreamerLog, "GStreamerLog") QGC_LOGGING_CATEGORY(GStreamerAPILog, "GStreamerAPILog") @@ -239,7 +240,7 @@ GStreamer::initialize(int argc, char* argv[], int debuglevel) void* GStreamer::createVideoSink(QObject* parent, QQuickItem* widget) { - Q_UNUSED(parent) + Q_UNUSED(parent); GstElement* sink; @@ -263,6 +264,5 @@ GStreamer::releaseVideoSink(void* sink) VideoReceiver* GStreamer::createVideoReceiver(QObject* parent) { - Q_UNUSED(parent) - return new GstVideoReceiver(nullptr); + return new GstVideoReceiver(parent); } diff --git a/src/VideoReceiver/GStreamer.h b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.h similarity index 91% rename from src/VideoReceiver/GStreamer.h rename to src/VideoManager/VideoReceiver/GStreamer/GStreamer.h index 29c4fdbb8b93..6543016bdd3d 100644 --- a/src/VideoReceiver/GStreamer.h +++ b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "Settings/VideoDecoderOptions.h" @@ -9,8 +8,10 @@ Q_DECLARE_LOGGING_CATEGORY(GStreamerLog) Q_DECLARE_LOGGING_CATEGORY(GStreamerAPILog) class VideoReceiver; +class QQuickItem; -class GStreamer { +class GStreamer +{ public: static void blacklist(VideoDecoderOptions option); static void initialize(int argc, char* argv[], int debuglevel); diff --git a/src/VideoReceiver/GstVideoReceiver.cc b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc similarity index 100% rename from src/VideoReceiver/GstVideoReceiver.cc rename to src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc diff --git a/src/VideoReceiver/GstVideoReceiver.h b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.h similarity index 91% rename from src/VideoReceiver/GstVideoReceiver.h rename to src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.h index 367e85ac7aaa..79144d411c06 100644 --- a/src/VideoReceiver/GstVideoReceiver.h +++ b/src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.h @@ -88,13 +88,13 @@ class GstVideoReceiver : public VideoReceiver ~GstVideoReceiver(void); public slots: - virtual void start(const QString& uri, unsigned timeout, int buffer = 0); - virtual void stop(void); - virtual void startDecoding(void* sink); - virtual void stopDecoding(void); - virtual void startRecording(const QString& videoFile, FILE_FORMAT format); - virtual void stopRecording(void); - virtual void takeScreenshot(const QString& imageFile); + void start(const QString& uri, unsigned timeout, int buffer = 0) override; + void stop(void) override; + void startDecoding(void* sink) override; + void stopDecoding(void) override; + void startRecording(const QString& videoFile, FILE_FORMAT format) override; + void stopRecording(void) override; + void takeScreenshot(const QString& imageFile) override; protected slots: virtual void _watchdog(void); @@ -167,7 +167,3 @@ protected slots: static const char* _kFileMux[FILE_FORMAT_MAX - FILE_FORMAT_MIN]; }; - -void* createVideoSink(void* widget); - -void initializeVideoReceiver(int argc, char* argv[], int debuglevel); diff --git a/src/VideoReceiver/README.md b/src/VideoManager/VideoReceiver/GStreamer/README.md similarity index 100% rename from src/VideoReceiver/README.md rename to src/VideoManager/VideoReceiver/GStreamer/README.md diff --git a/src/VideoReceiver/gst_ios_init.h b/src/VideoManager/VideoReceiver/GStreamer/gst_ios_init.h similarity index 100% rename from src/VideoReceiver/gst_ios_init.h rename to src/VideoManager/VideoReceiver/GStreamer/gst_ios_init.h diff --git a/src/VideoReceiver/gst_ios_init.m b/src/VideoManager/VideoReceiver/GStreamer/gst_ios_init.m similarity index 100% rename from src/VideoReceiver/gst_ios_init.m rename to src/VideoManager/VideoReceiver/GStreamer/gst_ios_init.m diff --git a/src/VideoReceiver/gstqgc.c b/src/VideoManager/VideoReceiver/GStreamer/gstqgc.c similarity index 100% rename from src/VideoReceiver/gstqgc.c rename to src/VideoManager/VideoReceiver/GStreamer/gstqgc.c diff --git a/src/VideoReceiver/gstqgcvideosinkbin.c b/src/VideoManager/VideoReceiver/GStreamer/gstqgcvideosinkbin.c similarity index 100% rename from src/VideoReceiver/gstqgcvideosinkbin.c rename to src/VideoManager/VideoReceiver/GStreamer/gstqgcvideosinkbin.c diff --git a/src/VideoManager/VideoReceiver/QtMultimedia/CMakeLists.txt b/src/VideoManager/VideoReceiver/QtMultimedia/CMakeLists.txt new file mode 100644 index 000000000000..056575bdf760 --- /dev/null +++ b/src/VideoManager/VideoReceiver/QtMultimedia/CMakeLists.txt @@ -0,0 +1,32 @@ +find_package(Qt6 REQUIRED COMPONENTS Core Multimedia QmlIntegration Quick) + +# MultimediaQuickPrivate + +qt_add_library(QtMultimediaReceiver STATIC + QtMultimediaReceiver.cc + QtMultimediaReceiver.h +) + +target_link_libraries(QtMultimediaReceiver + PRIVATE + # Qt6::MultimediaPrivate + Qt6::Quick + Utilities + PUBLIC + Qt6::Core + Qt6::Multimedia + Qt6::QmlIntegration +) + +target_include_directories(QtMultimediaReceiver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +option(QGC_DISABLE_UVC "Disable UVC Devices" OFF) +if(QGC_DISABLE_UVC) + target_compile_definitions(QtMultimediaReceiver PUBLIC QGC_DISABLE_UVC) +else() + target_sources(QtMultimediaReceiver + PRIVATE + UVCReceiver.cc + UVCReceiver.h + ) +endif() diff --git a/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.cc b/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.cc new file mode 100644 index 000000000000..7421a5432fdc --- /dev/null +++ b/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.cc @@ -0,0 +1,315 @@ +#include "QtMultimediaReceiver.h" +#include + +#include +#include +#include +#include +#include +#include +#include + + +QGC_LOGGING_CATEGORY(QtMultimediaReceiverLog, "qgc.video.qtmultimedia.qtmultimediareceiver") + +static void* createVideoSink(QObject* parent, QQuickItem* widget) +{ + Q_UNUSED(widget); + QVideoSink* const videoSink = new QVideoSink(parent); + /*if (widget) { + QQuickVideoOutput* const videoOutput = reinterpret_cast(widget); + *videoOutput->videoSink(); + }*/ + return videoSink; +} + +static void releaseVideoSink(void* sink) +{ + if (!sink) { + return; + } + + QVideoSink* const videoSink = reinterpret_cast(sink); + videoSink->deleteLater(); +} + +static VideoReceiver* createVideoReceiver(QObject* parent) +{ + return new QtMultimediaReceiver(parent); +} + +QtMultimediaReceiver::QtMultimediaReceiver(QObject* parent) + : VideoReceiver(parent) + , m_mediaPlayer(new QMediaPlayer(this)) + , m_captureSession(new QMediaCaptureSession(this)) + , m_mediaRecorder(new QMediaRecorder(this)) + , m_frameTimer(new QTimer(this)) +{ + m_captureSession->setRecorder(m_mediaRecorder); + + (void) connect(m_mediaPlayer, &QMediaPlayer::playingChanged, this, &QtMultimediaReceiver::streamingChanged); + (void) connect(m_mediaPlayer, &QMediaPlayer::hasVideoChanged, this, &QtMultimediaReceiver::decodingChanged); + (void) connect(m_mediaPlayer, &QMediaPlayer::playbackStateChanged, this, [this](QMediaPlayer::PlaybackState newState){ + if (newState == QMediaPlayer::PlaybackState::PlayingState) { + m_frameTimer->start(); + } else if (newState == QMediaPlayer::PlaybackState::StoppedState) { + m_frameTimer->stop(); + } + }); + (void) connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, [this](QMediaPlayer::MediaStatus status){ + switch (status) { + case QMediaPlayer::MediaStatus::LoadingMedia: + m_streamDevice = m_mediaPlayer->sourceDevice(); + break; + + default: + break; + } + }); + (void) connect(m_mediaPlayer, &QMediaPlayer::metaDataChanged, this, [](){ + /*const QMediaMetaData metaData = m_mediaPlayer->metaData(); + const QVariant resolution = metaData.value(QMediaMetaData::Key::Resolution); + const QSize videoSize = resolution.toSize();*/ + }); + (void) connect(m_mediaPlayer, &QMediaPlayer::bufferProgressChanged, this, [](float filled){ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << "Buffer Progress:" << filled; + }); + (void) connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this, [this](QMediaPlayer::Error error, const QString &errorString){ + switch (error) { + case QMediaPlayer::Error::NetworkError: + break; + + default: + break; + } + + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << errorString; + }); + + // m_mediaRecorder->setEncodingMode(QMediaRecorder::EncodingMode::AverageBitRateEncoding); + // m_mediaRecorder->setQuality(QMediaRecorder::Quality::HighQuality); + // m_mediaRecorder->setVideoBitRate() + m_mediaRecorder->setVideoFrameRate(0); + m_mediaRecorder->setVideoResolution(QSize()); + (void) connect(m_mediaRecorder, &QMediaRecorder::recorderStateChanged, this, [this](QMediaRecorder::RecorderState state){ + if (state == QMediaRecorder::RecorderState::RecordingState) { + emit recordingStarted(); + } + emit recordingChanged(m_mediaRecorder->recorderState() == QMediaRecorder::RecorderState::RecordingState); + }); + (void) connect(m_mediaRecorder, &QMediaRecorder::errorOccurred, this, [this](QMediaRecorder::Error error, const QString &errorString){ + switch (error) { + case QMediaRecorder::Error::OutOfSpaceError: + break; + + default: + break; + } + + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << errorString; + }); + + m_frameTimer->setSingleShot(true); + m_frameTimer->setTimerType(Qt::PreciseTimer); + (void) connect(m_frameTimer, &QTimer::timeout, this, &QtMultimediaReceiver::timeout); + + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << this; +} + +QtMultimediaReceiver::~QtMultimediaReceiver() +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO << this; +} + +// -1 - disable buffer and video sync +// 0 - default buffer length +// N - buffer length, ms +void QtMultimediaReceiver::start(const QString& uri, unsigned timeout, int buffer) +{ + Q_UNUSED(buffer); + + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (m_mediaPlayer->isPlaying()) { + qCDebug(QtMultimediaReceiverLog) << "Already running!"; + emit onStartComplete(STATUS_INVALID_STATE); + return; + } + + if (uri.isEmpty()) { + qCDebug(QtMultimediaReceiverLog) << "Failed because URI is not specified"; + emit onStartComplete(STATUS_INVALID_URL); + return; + } + m_mediaPlayer->setSource(uri); + + m_frameTimer->setInterval(timeout); + + // QAbstractVideoBuffer *buffer = m_videoSink->videoFrame()->videoBuffer(); + + /*if (!m_mediaPlayer->hasVideo()) { + emit onStartComplete(STATUS_FAIL); + }*/ + + m_mediaPlayer->play(); + + qCDebug(QtMultimediaReceiverLog) << "Starting"; + + emit onStartComplete(STATUS_OK); +} + +void QtMultimediaReceiver::stop() +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (!m_mediaPlayer->isPlaying()) { + qCDebug(QtMultimediaReceiverLog) << "Already stopped!"; + emit onStartComplete(STATUS_INVALID_STATE); + return; + } + + if (m_mediaPlayer->source().isEmpty()) { + qCWarning(QtMultimediaReceiverLog) << "Stop called on empty URI"; + emit onStopComplete(STATUS_FAIL); + return; + } + + m_mediaPlayer->stop(); + + qCDebug(QtMultimediaReceiverLog) << "Stopped"; + + emit onStopComplete(STATUS_OK); +} + +void QtMultimediaReceiver::startDecoding(void* sink) +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (sink == nullptr) { + qCCritical(QtMultimediaReceiverLog) << "VideoSink is NULL"; + emit onStartDecodingComplete(STATUS_FAIL); + return; + } + + if (m_videoSink) { + qCWarning(QtMultimediaReceiverLog) << "VideoSink is already set"; + } + + if (m_videoSizeUpdater) { + qCWarning(QtMultimediaReceiverLog) << "VideoSizeConnection is already set"; + } + + m_videoSink = reinterpret_cast(sink); + m_videoSizeUpdater = connect(m_videoSink, &QVideoSink::videoSizeChanged, this, [this](){ + emit videoSizeChanged(m_videoSink->videoSize()); + }); + m_videoFrameUpdater = connect(m_videoSink, &QVideoSink::videoFrameChanged, this, [this](const QVideoFrame &frame){ + if (frame.isValid()) { + m_frameTimer->start(); + } + }); + m_rhi = m_videoSink->rhi(); + m_videoSink->setSubtitleText(""); + + m_mediaPlayer->setVideoSink(m_videoSink); + + qCDebug(QtMultimediaReceiverLog) << "Decoding"; + + emit onStartDecodingComplete(STATUS_OK); +} + +void QtMultimediaReceiver::stopDecoding() +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (m_videoSink == nullptr) { + qCWarning(QtMultimediaReceiverLog) << "VideoSink is NULL"; + emit onStartDecodingComplete(STATUS_INVALID_STATE); + return; + } + + disconnect(m_videoSizeUpdater); + m_mediaPlayer->setVideoSink(nullptr); + m_videoSink = nullptr; + + qCDebug(QtMultimediaReceiverLog) << "Stopped Decoding"; + + emit onStopDecodingComplete(STATUS_OK); +} + +void QtMultimediaReceiver::startRecording(const QString& videoFile, FILE_FORMAT format) +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (!m_mediaRecorder->isAvailable()) { + qCWarning(QtMultimediaReceiverLog) << "Recording Unavailable"; + emit onStartRecordingComplete(STATUS_FAIL); + return; + } + + switch (format) { + case FILE_FORMAT_MKV: + m_mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::Matroska); + break; + + case FILE_FORMAT_MOV: + m_mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::QuickTime); + break; + + case FILE_FORMAT_MP4: + m_mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::MPEG4); + break; + + default: + // QMediaFormat::AVI, WMV, Ogg, WebM + m_mediaRecorder->setMediaFormat(QMediaFormat::FileFormat::UnspecifiedFormat); + break; + } + + m_mediaRecorder->setOutputLocation(QUrl::fromLocalFile(videoFile)); + + m_mediaRecorder->record(); + + qCDebug(QtMultimediaReceiverLog) << "Recording"; + + emit onStartRecordingComplete(STATUS_OK); +} + +void QtMultimediaReceiver::stopRecording() +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + m_mediaRecorder->stop(); + + qCDebug(QtMultimediaReceiverLog) << "Stopped Recording"; + + emit onStopRecordingComplete(STATUS_OK); +} + +void QtMultimediaReceiver::takeScreenshot(const QString& imageFile) +{ + qCDebug(QtMultimediaReceiverLog) << Q_FUNC_INFO; + + if (!m_videoSink) { + qCWarning(QtMultimediaReceiverLog) << "Video Sink is NULL"; + emit onTakeScreenshotComplete(STATUS_FAIL); + } + + const QVideoFrame frame = m_videoSink->videoFrame(); + if (frame.isValid() && frame.isReadable()) { + // const QVideoFrameFormat frameFormat = frame.surfaceFormat(); + const QImage screenshot = frame.toImage(); + } else { + qCWarning(QtMultimediaReceiverLog) << "Screenshot Frame is Invalid"; + emit onTakeScreenshotComplete(STATUS_FAIL); + } + + // QQuickItem* const quickItem = reinterpret_cast(m_mediaPlayer->videoOutput()); + // const QSize targetSize = m_mediaRecorder->videoResolution(); + // QSharedPointer screenshot = quickItem->grabToImage(targetSize); + // screenshot->saveToFile(imageFile); + + qCDebug(QtMultimediaReceiverLog) << "Screenshot"; + + emit onTakeScreenshotComplete(STATUS_NOT_IMPLEMENTED); +} diff --git a/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.h b/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.h new file mode 100644 index 000000000000..e99bfc22e2d0 --- /dev/null +++ b/src/VideoManager/VideoReceiver/QtMultimedia/QtMultimediaReceiver.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +#include "VideoReceiver.h" + +Q_DECLARE_LOGGING_CATEGORY(QtMultimediaReceiverLog) + +class QMediaPlayer; +class QVideoSink; +class QMediaCaptureSession; +class QMediaRecorder; +class QRhi; +class QTimer; +class QQuickItem; + +class QtMultimediaReceiver : public VideoReceiver +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + +public: + explicit QtMultimediaReceiver(QObject* parent = nullptr); + virtual ~QtMultimediaReceiver(); + + static void* createVideoSink(QObject* parent); + static void releaseVideoSink(void* sink); + static VideoReceiver* createVideoReceiver(QObject* parent); + +public slots: + void start(const QString& uri, unsigned timeout, int buffer = 0) override; + void stop() override; + void startDecoding(void* sink) override; + void stopDecoding() override; + void startRecording(const QString& videoFile, FILE_FORMAT format) override; + void stopRecording() override; + void takeScreenshot(const QString& imageFile) override; + +protected: + QMediaPlayer* m_mediaPlayer = nullptr; + QVideoSink* m_videoSink = nullptr; + QMediaCaptureSession* m_captureSession = nullptr; + QMediaRecorder* m_mediaRecorder = nullptr; + QMetaObject::Connection m_videoSizeUpdater; + QMetaObject::Connection m_videoFrameUpdater; + QTimer* m_frameTimer = nullptr; + QRhi* m_rhi = nullptr; + const QIODevice * m_streamDevice; + QQuickItem* m_videoOutput = nullptr; + // QQuickVideoOutput* m_videoOutput = nullptr; +}; diff --git a/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.cc b/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.cc new file mode 100644 index 000000000000..a996124e663d --- /dev/null +++ b/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.cc @@ -0,0 +1,84 @@ +#include "UVCReceiver.h" +#include "QtMultimediaReceiver.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +QGC_LOGGING_CATEGORY(UVCReceiverLog, "qgc.video.qtmultimedia.uvcreceiver") + +UVCReceiver::UVCReceiver(QObject* parent) + : QtMultimediaReceiver(parent) + , m_camera(new QCamera(this)) + , m_imageCapture(new QImageCapture(this)) +{ + m_captureSession->setCamera(m_camera); + m_captureSession->setImageCapture(m_imageCapture); + m_captureSession->setVideoSink(m_videoSink); + + connect(m_captureSession, &QMediaCaptureSession::cameraChanged, this, [this]{ + // adjustAspectRatio() + _checkPermission(); + }); + + qCDebug(UVCReceiverLog) << Q_FUNC_INFO << this; +} + +UVCReceiver::~UVCReceiver() +{ + qCDebug(UVCReceiverLog) << Q_FUNC_INFO << this; +} + +void UVCReceiver::adjustAspectRatio(qreal height) +{ + if (!m_videoOutput) { + return; + } + + const QCameraFormat cameraFormat = m_camera->cameraFormat(); + if (cameraFormat.isNull()) { + return; + } + + const QSize resolution = cameraFormat.resolution(); + if (resolution.isValid()) { + const qreal aspectRatio = resolution.width() / resolution.height(); + const qreal height = height * aspectRatio; + m_videoOutput->setHeight(height * aspectRatio); + } +} + +QCameraDevice UVCReceiver::findCameraDevice(const QString &cameraId) +{ + // QString videoSource = _videoSettings->videoSource()->rawValue().toString(); + const QList videoInputs = QMediaDevices::videoInputs(); + for (const QCameraDevice& camera : videoInputs) { + if (camera.description() == cameraId) { + return camera; + } + } + + return QMediaDevices::defaultVideoInput(); +} + +void UVCReceiver::_checkPermission() +{ + QCameraPermission cameraPermission; + if (qApp->checkPermission(cameraPermission) == Qt::PermissionStatus::Undetermined) { + qApp->requestPermission(cameraPermission, [this](const QPermission &permission) { + if (permission.status() == Qt::PermissionStatus::Granted) { + // qgcApp()->showRebootAppMessage(tr("Restart application for changes to take effect.")); + } + }); + } +} + +bool UVCReceiver::enabled() +{ + return QMediaDevices::videoInputs().count() > 0; +} diff --git a/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.h b/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.h new file mode 100644 index 000000000000..b891f4c62f26 --- /dev/null +++ b/src/VideoManager/VideoReceiver/QtMultimedia/UVCReceiver.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "QtMultimediaReceiver.h" + +Q_DECLARE_LOGGING_CATEGORY(UVCReceiverLog) + +class QCamera; +class QImageCapture; +class QQuickItem; + +class UVCReceiver : public QtMultimediaReceiver +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + +public: + explicit UVCReceiver(QObject* parent = nullptr); + ~UVCReceiver(); + + bool enabled(); + QCameraDevice findCameraDevice(const QString &cameraId); + +public slots: + void start(const QString& uri, unsigned timeout, int buffer = 0) final; + void stop() final; + void startDecoding(void* sink) final; + void stopDecoding() final; + void startRecording(const QString& videoFile, FILE_FORMAT format) final; + void stopRecording() final; + void takeScreenshot(const QString& imageFile) final; + + Q_INVOKABLE void adjustAspectRatio(qreal height); + +private: + void _checkPermission(); + + QCamera* m_camera = nullptr; + QImageCapture* m_imageCapture = nullptr; +}; diff --git a/src/VideoReceiver/VideoReceiver.h b/src/VideoManager/VideoReceiver/VideoReceiver.h similarity index 97% rename from src/VideoReceiver/VideoReceiver.h rename to src/VideoManager/VideoReceiver/VideoReceiver.h index 1cf04cdc9326..73425b31fb63 100644 --- a/src/VideoReceiver/VideoReceiver.h +++ b/src/VideoManager/VideoReceiver/VideoReceiver.h @@ -36,6 +36,7 @@ class VideoReceiver : public QObject FILE_FORMAT_MP4, FILE_FORMAT_MAX } FILE_FORMAT; + Q_ENUM(FILE_FORMAT) typedef enum { STATUS_OK = 0, @@ -44,11 +45,10 @@ class VideoReceiver : public QObject STATUS_INVALID_URL, STATUS_NOT_IMPLEMENTED } STATUS; - Q_ENUM(STATUS) signals: - void timeout(void); + void timeout(); void streamingChanged(bool active); void decodingChanged(bool active); void recordingChanged(bool active); diff --git a/src/VideoReceiver/VideoReceiver.pri b/src/VideoManager/VideoReceiver/VideoReceiver.pri similarity index 93% rename from src/VideoReceiver/VideoReceiver.pri rename to src/VideoManager/VideoReceiver/VideoReceiver.pri index c3c0427302ee..05f498bfaffb 100644 --- a/src/VideoReceiver/VideoReceiver.pri +++ b/src/VideoManager/VideoReceiver/VideoReceiver.pri @@ -144,21 +144,21 @@ VideoEnabled { } HEADERS += \ - $$PWD/GStreamer.h \ - $$PWD/GstVideoReceiver.h \ + $$PWD/GStreamer/GStreamer.h \ + $$PWD/GStreamer/GstVideoReceiver.h \ $$PWD/VideoReceiver.h SOURCES += \ - $$PWD/gstqgcvideosinkbin.c \ - $$PWD/gstqgc.c \ - $$PWD/GStreamer.cc \ - $$PWD/GstVideoReceiver.cc + $$PWD/GStreamer/gstqgcvideosinkbin.c \ + $$PWD/GStreamer/gstqgc.c \ + $$PWD/GStreamer/GStreamer.cc \ + $$PWD/GStreamer/GstVideoReceiver.cc - include($$PWD/../../qmlglsink.pri) + include($$PWD/../../../qmlglsink.pri) } else { LinuxBuild|MacBuild|iOSBuild|WindowsBuild|AndroidBuild { message("Skipping support for video streaming (GStreamer libraries not installed)") - message("Installation instructions here: https://github.com/mavlink/qgroundcontrol/blob/master/src/VideoReceiver/README.md") + message("Installation instructions here: https://github.com/mavlink/qgroundcontrol/blob/master/src/VideoManager/VideoReceiver/GStreamer/README.md") } else { message("Skipping support for video streaming (Unsupported platform)") } diff --git a/src/VideoReceiver/CMakeLists.txt b/src/VideoReceiver/CMakeLists.txt deleted file mode 100644 index 4b4bde0334bc..000000000000 --- a/src/VideoReceiver/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -find_package(Qt6 REQUIRED COMPONENTS Core) - -qt_add_library(VideoReceiver STATIC - VideoReceiver.h -) - -target_link_libraries(VideoReceiver - PUBLIC - Qt6::Core -) - -target_include_directories(VideoReceiver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_subdirectory(${CMAKE_SOURCE_DIR}/libs/qmlglsink qmlglsink.build) - -if(GST_QT6_PLUGIN_FOUND) - message(STATUS "Building VideoReceiver") - - find_package(Qt6 REQUIRED COMPONENTS Quick) - - target_sources(VideoReceiver - PRIVATE - gstqgc.c - gstqgcvideosinkbin.c - GStreamer.cc - GStreamer.h - GstVideoReceiver.cc - GstVideoReceiver.h - ) - - target_link_libraries(VideoReceiver - PRIVATE - qmlglsink - Utilities - PUBLIC - Qt6::Quick - Settings - ) - - target_compile_definitions(VideoReceiver PUBLIC QGC_GST_STREAMING) - - if(CMAKE_BUILD_TYPE STREQUAL "Release") - target_compile_definitions(VideoReceiver PRIVATE QGC_INSTALL_RELEASE) - endif() -endif()