19#ifdef QGC_GST_STREAMING
22#if defined(QGC_HAS_ANY_GPU_PATH)
25#include <QtMultimedia/QVideoSink>
26#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
29#include <QtConcurrent/QtConcurrent>
30#include <QtCore/QApplicationStatic>
32#include <QtCore/QEventLoop>
33#include <QtCore/QFutureWatcher>
34#include <QtCore/QPointer>
35#include <QtCore/QRunnable>
36#include <QtCore/QTimer>
37#include <QtQml/QQmlEngine>
38#include <QtQuick/QQuickItem>
39#include <QtQuick/QQuickWindow>
51bool VideoManager::_shouldSkipGStreamerForUnitTests()
61 qCDebug(VideoManagerLog) <<
this;
63 (void) qRegisterMetaType<VideoReceiver::STATUS>(
"STATUS");
65#ifdef QGC_GST_STREAMING
66 _gstreamerDisabledForUnitTests = _shouldSkipGStreamerForUnitTests();
67 if (_gstreamerDisabledForUnitTests) {
68 qCInfo(VideoManagerLog) <<
"Skipping GStreamer initialization for unit tests";
75 qCDebug(VideoManagerLog) <<
this;
80 return _videoManagerInstance();
85#ifdef QGC_GST_STREAMING
86 if (_gstreamerDisabledForUnitTests) {
87 _initState = InitState::GstReady;
88 qCInfo(VideoManagerLog) <<
"GStreamer initialization disabled for unit tests";
92 if (_initState != InitState::NotStarted) {
93 qCWarning(VideoManagerLog) <<
"GStreamer init already started";
97 _initState = InitState::Pending;
102 _gstInitFuture.then(
this, [
this](
bool success) {
103 _onGstInitComplete(success);
104 }).onCanceled(
this, [
this] {
105 _onGstInitComplete(
false);
112#ifdef QGC_GST_STREAMING
113 if (_gstreamerDisabledForUnitTests) {
117 if (_initState == InitState::NotStarted) {
121 switch (_initState) {
122 case InitState::Failed:
124 case InitState::GstReady:
125 case InitState::Running:
131 if (!_gstInitFuture.isValid()) {
132 qCCritical(VideoManagerLog) <<
"waitForGStreamerInit: no valid future";
138 timer.setSingleShot(
true);
139 QFutureWatcher<bool> watcher;
140 (void) connect(&watcher, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit);
141 (void) connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
143 watcher.setFuture(_gstInitFuture);
144 if (!watcher.isFinished()) {
145 timer.start(timeoutMs);
149 if (!watcher.isFinished()) {
150 qCCritical(VideoManagerLog) <<
"Timed out waiting for GStreamer init";
154 const bool success = watcher.result();
155 if (_initState == InitState::Pending || _initState == InitState::QmlReady) {
156 _onGstInitComplete(success);
158 return _initState != InitState::Failed;
168 qCDebug(VideoManagerLog) <<
"Video Manager already initialized";
173 qCCritical(VideoManagerLog) <<
"Failed To Init Video Manager - mainWindow is NULL";
176 _mainWindow = mainWindow;
178#if defined(QGC_HAS_ANY_GPU_PATH)
182 (void) connect(_videoSettings->videoSource(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
183 (void) connect(_videoSettings->udpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
184 (void) connect(_videoSettings->rtspUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
185 (void) connect(_videoSettings->tcpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
187 (void) connect(_videoSettings->lowLatencyMode(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) { Q_UNUSED(value); _restartAllVideos(); });
189#ifdef QGC_GST_STREAMING
199#ifdef QGC_GST_STREAMING
200 if (_initState == InitState::NotStarted) {
205 _mainWindow->scheduleRenderJob(
206 QRunnable::create([
this] {
207 QMetaObject::invokeMethod(
this, &VideoManager::_initAfterQmlIsReady, Qt::QueuedConnection);
209 QQuickWindow::AfterSynchronizingStage);
214void VideoManager::_initAfterQmlIsReady()
217 qCCritical(VideoManagerLog) <<
"_initAfterQmlIsReady called with NULL mainWindow";
221 qCDebug(VideoManagerLog) <<
"_initAfterQmlIsReady";
223#ifdef QGC_GST_STREAMING
224 switch (_initState) {
225 case InitState::Pending:
226 _initState = InitState::QmlReady;
227 qCDebug(VideoManagerLog) <<
"QML ready, waiting for GStreamer";
229 case InitState::GstReady:
230 _initState = InitState::Running;
231 qCDebug(VideoManagerLog) <<
"QML ready, GStreamer already done — creating receivers";
233 case InitState::Failed:
234 qCWarning(VideoManagerLog) <<
"QML ready but GStreamer init failed";
237 qCWarning(VideoManagerLog) <<
"_initAfterQmlIsReady: unexpected state" <<
static_cast<int>(_initState);
241 _createVideoReceivers();
244void VideoManager::_onGstInitComplete(
bool success)
247 _initState = InitState::Failed;
248 qCCritical(VideoManagerLog) <<
"GStreamer initialization failed";
252#ifdef QGC_GST_STREAMING
253 if (_videoSettings) {
255 _videoSettings->forceVideoDecoder()->rawValue().toInt());
260 switch (_initState) {
261 case InitState::Pending:
262 _initState = InitState::GstReady;
263 qCDebug(VideoManagerLog) <<
"GStreamer ready, waiting for QML";
265 case InitState::QmlReady:
266 _initState = InitState::Running;
267 qCDebug(VideoManagerLog) <<
"GStreamer ready, QML already done — creating receivers";
268 _createVideoReceivers();
271 qCWarning(VideoManagerLog) <<
"_onGstInitComplete: unexpected state" <<
static_cast<int>(_initState);
276void VideoManager::_createVideoReceivers()
278#ifdef QGC_UNITTEST_BUILD
279 if (_createVideoReceiversForTest) {
280 _createVideoReceiversForTest();
284 static const QStringList videoStreamList = {
288 for (
const QString &streamName : videoStreamList) {
295 _initVideoReceiver(receiver, _mainWindow);
301 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
306void VideoManager::_cleanupOldVideos()
313 QDir videoDir = QDir(savePath);
314 videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
315 videoDir.setSorting(QDir::Time);
317 QStringList nameFilters;
322 videoDir.setNameFilters(nameFilters);
323 QFileInfoList vidList = videoDir.entryInfoList();
324 if (vidList.isEmpty()) {
329 for (
const QFileInfo &video : std::as_const(vidList)) {
330 total += video.size();
334 while ((total >= maxSize) && !vidList.isEmpty()) {
335 const QFileInfo info = vidList.takeLast();
336 total -= info.size();
337 const QString path = info.filePath();
338 qCDebug(VideoManagerLog) <<
"Removing old video file:" << path;
339 (void) QFile::remove(path);
354 if (savePath.isEmpty()) {
355 QGC::showAppMessage(tr(
"Unabled to record video. Video save path must be specified in Settings."));
359 const QString videoFileUrl = videoFile.isEmpty() ? QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss") : videoFile;
362 const QString videoFileNameTemplate = savePath +
"/" + videoFileUrl +
".%1" + ext;
364 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
366 qCDebug(VideoManagerLog) <<
"Video receiver is not ready.";
369 const QString streamName = (receiver->
name() == QStringLiteral(
"videoContent")) ?
"" : (receiver->
name() +
".");
370 const QString videoFileName = videoFileNameTemplate.arg(streamName);
377 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
386 _imageFile += QStringLiteral(
"/") + QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss.zzz") + QStringLiteral(
".jpg");
393 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
409 return _videoSettings->aspectRatio()->rawValue().toDouble();
429 return pInfo->
hfov();
441 return pInfo->
hfov();
445 return _videoSettings->aspectRatio()->rawValue().toDouble();
462 return (_videoSettings->streamEnabled()->rawValue().toBool() && _videoSettings->
streamConfigured());
472#ifdef QGC_GST_STREAMING
497 if (on != _fullScreen) {
505 static const QStringList videoSourceList = {
517 const QString videoSource = _videoSettings->videoSource()->rawValue().toString();
521void VideoManager::_videoSourceChanged()
523 bool changed =
false;
524 if (_activeVehicle) {
526 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
534 changed |= _updateSettings(receiver);
537 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
539 changed |= _updateSettings(receiver);
554 qCDebug(VideoManagerLog) <<
"New Video Source:" << _videoSettings->videoSource()->rawValue().toString();
562 const QString oldUvcVideoSrcID = _uvcVideoSourceID;
565 _uvcVideoSourceID = QString();
570 if (oldUvcVideoSrcID != _uvcVideoSourceID) {
571 qCDebug(VideoManagerLog) <<
"UVC changed from [" << oldUvcVideoSrcID <<
"] to [" << _uvcVideoSourceID <<
"]";
572 if (!_uvcVideoSourceID.isEmpty()) {
588 return !pInfo->
uri().isEmpty();
602 qCDebug(VideoManagerLog) << QString(
"Configure stream (%1):").arg(receiver->
name()) << pInfo->
uri();
605 switch (pInfo->
type()) {
606 case VIDEO_STREAM_TYPE_RTSP:
610 _videoSettings->rtspUrl()->setRawValue(url);
613 case VIDEO_STREAM_TYPE_TCP_MPEG:
617 case VIDEO_STREAM_TYPE_RTPUDP:
618 if (pInfo->
encoding() == VIDEO_STREAM_ENCODING_H265) {
620 url = pInfo->
uri().contains(
"udp265://") ? pInfo->
uri() : QStringLiteral(
"udp265://0.0.0.0:%1").arg(pInfo->
uri());
623 url = pInfo->
uri().contains(
"udp://") ? pInfo->
uri() : QStringLiteral(
"udp://0.0.0.0:%1").arg(pInfo->
uri());
626 case VIDEO_STREAM_TYPE_MPEG_TS:
628 url = pInfo->
uri().contains(
"mpegts://") ? pInfo->
uri() : QStringLiteral(
"mpegts://0.0.0.0:%1").arg(pInfo->
uri());
631 qCWarning(VideoManagerLog) <<
"Unknown VIDEO_STREAM_TYPE";
637 const bool settingsChanged = _updateVideoUri(receiver, url);
638 if (settingsChanged) {
640 _videoSettings->videoSource()->setRawValue(source);
646 return settingsChanged;
649bool VideoManager::_updateVideoUri(
VideoReceiver *receiver,
const QString &uri)
652 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
656 if ((uri == receiver->
uri()) && !receiver->
uri().isNull()) {
660 qCDebug(VideoManagerLog) <<
"New Video URI" << uri;
670 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
674 bool settingsChanged =
false;
676 const bool lowLatency = _videoSettings->lowLatencyMode()->rawValue().toBool();
679 settingsChanged =
true;
683 return settingsChanged;
686 settingsChanged |= _updateUVC(receiver);
687 settingsChanged |= _updateAutoStream(receiver);
689 const QString source = _videoSettings->videoSource()->rawValue().toString();
691 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
693 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp265://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
695 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"mpegts://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
697 settingsChanged |= _updateVideoUri(receiver, _videoSettings->rtspUrl()->rawValue().toString());
699 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
701 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:5600"));
703 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:8888"));
705 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.42.1:554/live"));
707 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.0.10:8554/H264Video"));
709 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.43.1:8554/fpv_stream"));
711 settingsChanged |= _updateVideoUri(receiver, QString());
713 settingsChanged |= _updateVideoUri(receiver, QString());
715 qCCritical(VideoManagerLog) <<
"Video source URI \"" << source <<
"\" is not supported. Please add support!";
719 return settingsChanged;
722void VideoManager::_setActiveVehicle(
Vehicle *vehicle)
724 qCDebug(VideoManagerLog) << Q_FUNC_INFO <<
"new vehicle" << vehicle <<
"old active vehicle" << _activeVehicle;
726 if (_activeVehicle) {
737 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
743 _activeVehicle = vehicle;
744 if (_activeVehicle) {
746 if (_activeVehicle->cameraManager()) {
754 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
755 if (_activeVehicle->cameraManager()) {
757 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->thermalStreamInstance());
759 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->currentStreamInstance());
771void VideoManager::_communicationLostChanged(
bool connectionLost)
773 if (connectionLost) {
778void VideoManager::_restartAllVideos()
780 for (
VideoReceiver *videoReceiver : std::as_const(_videoReceivers)) {
781 _restartVideo(videoReceiver);
788 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
792 qCDebug(VideoManagerLog) <<
"Restart video receiver" << receiver->
name();
795 _stopReceiver(receiver);
798 _startReceiver(receiver);
805 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
816 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
817 _stopReceiver(receiver);
824 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
829 qCDebug(VideoManagerLog) <<
"VideoReceiver is already started" << receiver->
name();
833 if (receiver->
uri().isEmpty()) {
834 qCDebug(VideoManagerLog) <<
"VideoUri is NULL" << receiver->
name();
838 const QString source = _videoSettings->videoSource()->rawValue().toString();
844 receiver->
start(timeout);
847void VideoManager::_initVideoReceiver(
VideoReceiver *receiver, QQuickWindow *window)
849 if (_videoReceivers.contains(receiver)) {
850 qCWarning(VideoManagerLog) <<
"Receiver already initialized";
853 QQuickItem *widget = window->findChild<QQuickItem*>(receiver->
name());
855 qCCritical(VideoManagerLog) <<
"stream widget not found" << receiver->
name();
861 qCCritical(VideoManagerLog) <<
"createVideoSink() failed" << receiver->
name();
865#ifdef QGC_GST_STREAMING
866 if (sink && widget) {
867 auto *videoOutput = qobject_cast<QQuickVideoOutput *>(widget);
869 QVideoSink *videoSink = videoOutput->videoSink();
871 qCWarning(VideoManagerLog) <<
"setupAppSinkAdapter failed" << receiver->
name();
877 auto applyVisibility = [receiver](QWindow *win) {
879 const QWindow::Visibility v = win->visibility();
880 const bool active = (v != QWindow::Hidden && v != QWindow::Minimized);
886 auto prevConn = std::make_shared<QMetaObject::Connection>();
887 auto wireWindow = [receiver, applyVisibility, prevConn](QQuickWindow *qw) {
889 QObject::disconnect(*prevConn);
890 *prevConn = QMetaObject::Connection{};
894 *prevConn = QObject::connect(qw, &QWindow::visibilityChanged, receiver,
895 [applyVisibility, qw](QWindow::Visibility) { applyVisibility(qw); });
897 if (QQuickWindow *qw = videoOutput->window()) wireWindow(qw);
898 QObject::connect(videoOutput, &QQuickVideoOutput::windowChanged, receiver, wireWindow);
900 qCWarning(VideoManagerLog) <<
"Widget is not a VideoOutput, cannot connect appsink" << receiver->
name();
906 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"Start complete, status:" << status;
910 if (receiver->
sink()) {
918 _restartVideo(receiver);
924 qCDebug(VideoManagerLog) <<
"Stop complete" << receiver->
name() << receiver->
uri() <<
", status:" << status;
927 qCDebug(VideoManagerLog) <<
"Invalid video URL. Not restarting";
929 QTimer::singleShot(1000, receiver, [
this, receiver]() {
930 qCDebug(VideoManagerLog) <<
"Restarting video receiver" << receiver->
name() << receiver->
uri();
931 _startReceiver(receiver);
937 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"streaming changed, active:" << (active ?
"yes" :
"no");
945 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"decoding changed, active:" << (active ?
"yes" :
"no");
953 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording changed, active:" << (active ?
"yes" :
"no");
964 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording started";
971 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"resized. New resolution:" << size.width() <<
"x" << size.height();
980 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot taken";
982 qCWarning(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot failed";
988 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"stream info:" << (videoStreamInfo ?
"received" :
"lost");
990 (void) _updateAutoStream(receiver);
993 (void) _updateSettings(receiver);
995 _videoReceivers.append(receiver);
998 _startReceiver(receiver);
1004 qCDebug(VideoManagerLog) <<
"startVideo";
1007 qCDebug(VideoManagerLog) <<
"Stream not enabled/configured";
1011 _restartAllVideos();
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * kFileExtension[VideoReceiver::FILE_FORMAT_MAX+1]
Q_APPLICATION_STATIC(VideoManager, _videoManagerInstance)
void rawValueChanged(const QVariant &value)
Abstract base class for all camera controls: real and simulated.
virtual Q_INVOKABLE void stopStream()=0
virtual Q_INVOKABLE void resumeStream()=0
static MultiVehicleManager * instance()
void activeVehicleChanged(Vehicle *activeVehicle)
QGCVideoStreamInfo * currentStreamInstance()
QGCVideoStreamInfo * thermalStreamInstance()
virtual void * createVideoSink(QQuickItem *widget, QObject *parent)
Allows the plugin to override the creation of VideoSink.
virtual VideoReceiver * createVideoReceiver(QObject *parent)
Allows the plugin to override the creation of VideoReceiver.
virtual void releaseVideoSink(void *sink)
Allows the plugin to override the release of VideoSink.
static QGCCorePlugin * instance()
Encapsulates the contents of a VIDEO_STREAM_INFORMATION message.
qreal aspectRatio() const
Provides access to all app settings.
static SettingsManager * instance()
VideoSettings * videoSettings() const
AppSettings * appSettings() const
void stopCapturingTelemetry()
void startCapturingTelemetry(const QString &videoFile, QSize size)
static QString getSourceId()
static void checkPermission()
void communicationLostChanged(bool communicationLost)
bool communicationLost() const
QGCCameraManager * cameraManager()
VehicleLinkManager * vehicleLinkManager()
bool isStreamSource() const
static bool gstreamerEnabled()
void setfullScreen(bool on)
static bool qtmultimediaEnabled()
Q_INVOKABLE void stopRecording()
void uvcVideoSourceIDChanged()
Q_INVOKABLE void startVideo()
bool autoStreamConfigured() const
Q_INVOKABLE void startRecording(const QString &videoFile=QString())
double thermalAspectRatio() const
void isStreamSourceChanged()
static VideoManager * instance()
VideoManager(QObject *parent=nullptr)
void autoStreamConfiguredChanged()
void startGStreamerInit()
void init(QQuickWindow *mainWindow)
Q_INVOKABLE void stopVideo()
double aspectRatio() const
double thermalHfov() const
void imageFileChanged(const QString &filename)
bool waitForGStreamerInit(int timeoutMs=60000)
void recordingChanged(bool recording)
void isAutoStreamChanged()
QString imageFile() const
void aspectRatioChanged()
Q_INVOKABLE void grabImage(const QString &imageFile=QString())
void recordingStarted(const QString &filename)
void setLowLatency(bool lowLatency)
void setName(const QString &name)
void videoSizeChanged(QSize size)
void streamingChanged(bool active)
virtual void stopRecording()=0
virtual void startRecording(const QString &videoFile, FILE_FORMAT format)=0
void recordingChanged(bool active)
void videoStreamInfoChanged()
virtual void setSink(void *sink)
virtual void start(uint32_t timeout)=0
void onTakeScreenshotComplete(STATUS status)
void decodingChanged(bool active)
void onStartComplete(STATUS status)
void setStarted(bool started)
void setUri(const QString &uri)
static bool isValidFileFormat(FILE_FORMAT format)
virtual void setWidget(QQuickItem *widget)
virtual void startDecoding(void *sink)=0
void setVideoStreamInfo(QGCVideoStreamInfo *videoStreamInfo)
void onStopComplete(STATUS status)
virtual void takeScreenshot(const QString &imageFile)=0
QGCVideoStreamInfo * videoStreamInfo()
static constexpr const char * videoSource3DRSolo
static constexpr const char * videoSourceParrotDiscovery
static constexpr const char * videoSourceTCP
static constexpr const char * videoSourceUDPH264
static constexpr const char * videoSourceHerelinkHotspot
static constexpr const char * videoSourceRTSP
static constexpr const char * videoDisabled
static constexpr const char * videoSourceYuneecMantisG
static constexpr const char * videoSourceUDPH265
static constexpr const char * videoSourceNoVideo
static constexpr const char * videoSourceMPEGTS
static constexpr const char * videoSourceHerelinkAirUnit
void setDebugLevel(int level)
void setCodecPriorities(VideoDecoderOptions option)
void setAppSinkAdaptersActive(QObject *adapterParent, bool active)
void prepareEnvironment()
bool setupAppSinkAdapter(void *sinkBin, QVideoSink *videoSink, QObject *adapterParent)
Connect the appsink inside sinkBin to videoSink. Returns true on success.
void connectWindow(QQuickWindow *window)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.