24#include <QtConcurrent/QtConcurrent>
25#include <QtCore/QApplicationStatic>
26#include <QtCore/QCoreApplication>
28#include <QtCore/QEventLoop>
29#include <QtCore/QFutureWatcher>
30#include <QtCore/QRunnable>
31#include <QtCore/QTimer>
32#include <QtQml/QQmlEngine>
33#include <QtQuick/QQuickItem>
34#include <QtQuick/QQuickWindow>
51 qCDebug(VideoManagerLog) <<
this;
53 (void) qRegisterMetaType<VideoReceiver::STATUS>(
"STATUS");
57 if (_backendDisabledForTests) {
58 qCInfo(VideoManagerLog) <<
"Skipping video backend initialization for unit tests";
65 qCDebug(VideoManagerLog) <<
this;
70 return _videoManagerInstance();
77 if (_backendDisabledForTests) {
78 _initState.store(InitState::BackendReady);
79 qCInfo(VideoManagerLog) <<
"video initialization disabled for unit tests";
86 QMutexLocker lock(&_initFutureMutex);
87 InitState expected = InitState::NotStarted;
88 if (!_initState.compare_exchange_strong(expected, InitState::Pending)) {
89 qCWarning(VideoManagerLog) <<
"video init already started";
97 _backendInitFuture.then(
this, [
this](
bool success) {
98 _onBackendInitComplete(success);
99 }).onCanceled(
this, [
this] {
100 _onBackendInitComplete(
false);
108 if (_backendDisabledForTests) {
112 if (_initState.load() == InitState::NotStarted) {
116 switch (_initState.load()) {
117 case InitState::Failed:
119 case InitState::BackendReady:
120 case InitState::Running:
122 case InitState::NotStarted:
123 case InitState::Pending:
124 case InitState::QmlReady:
128 QFuture<bool> future;
130 QMutexLocker lock(&_initFutureMutex);
131 future = _backendInitFuture;
133 if (!future.isValid()) {
134 qCCritical(VideoManagerLog) <<
"waitForVideoBackendReady: no valid future";
140 timer.setSingleShot(
true);
141 QFutureWatcher<bool> watcher;
142 (void) connect(&watcher, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit);
143 (void) connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
145 watcher.setFuture(future);
146 if (!watcher.isFinished()) {
147 timer.start(timeout);
151 if (!watcher.isFinished()) {
152 qCCritical(VideoManagerLog) <<
"Timed out waiting for video init";
156 const bool success = watcher.result();
157 if (_initState.load() == InitState::Pending || _initState.load() == InitState::QmlReady) {
158 _onBackendInitComplete(success);
160 return _initState.load() != InitState::Failed;
166 qCDebug(VideoManagerLog) <<
"Video Manager already initialized";
171 qCCritical(VideoManagerLog) <<
"Failed To Init Video Manager - mainWindow is NULL";
174 _mainWindow = mainWindow;
178 (void) connect(_videoSettings->videoSource(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
179 (void) connect(_videoSettings->udpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
180 (void) connect(_videoSettings->rtspUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
181 (void) connect(_videoSettings->tcpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
183 (void) connect(_videoSettings->lowLatencyMode(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) { Q_UNUSED(value); _restartAllVideos(); });
186 (void) connect(_videoSettings->rtpJitterLatencyMs(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) { Q_UNUSED(value); _videoSourceChanged(); });
189 (void) connect(_videoSettings->rtspAutoReconnect(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) {
190 const bool enabled = value.toBool();
191 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
192 receiver->setAutoReconnect(enabled);
204 _mainWindow->scheduleRenderJob(
205 QRunnable::create([
this] {
206 QMetaObject::invokeMethod(
this, &VideoManager::_initAfterQmlIsReady, Qt::QueuedConnection);
208 QQuickWindow::AfterSynchronizingStage);
213void VideoManager::_initAfterQmlIsReady()
216 qCCritical(VideoManagerLog) <<
"_initAfterQmlIsReady called with NULL mainWindow";
220 qCDebug(VideoManagerLog) <<
"_initAfterQmlIsReady";
223 switch (_initState.load()) {
224 case InitState::Pending:
225 _initState.store(InitState::QmlReady);
226 qCDebug(VideoManagerLog) <<
"QML ready, waiting for video";
228 case InitState::BackendReady:
229 _initState.store(InitState::Running);
230 qCDebug(VideoManagerLog) <<
"QML ready, video already done — creating receivers";
232 case InitState::Failed:
233 qCWarning(VideoManagerLog) <<
"QML ready but video init failed";
235 case InitState::NotStarted:
236 case InitState::QmlReady:
237 case InitState::Running:
238 qCWarning(VideoManagerLog) <<
"_initAfterQmlIsReady: unexpected state" <<
static_cast<int>(_initState.load());
242 _createVideoReceivers();
245void VideoManager::_onBackendInitComplete(
bool success)
248 _initState.store(InitState::Failed);
249 qCCritical(VideoManagerLog) <<
"video initialization failed";
258 switch (_initState.load()) {
259 case InitState::Pending:
260 _initState.store(InitState::BackendReady);
261 qCDebug(VideoManagerLog) <<
"video ready, waiting for QML";
263 case InitState::QmlReady:
264 _initState.store(InitState::Running);
265 qCDebug(VideoManagerLog) <<
"video ready, QML already done — creating receivers";
266 _createVideoReceivers();
269 qCWarning(VideoManagerLog) <<
"_onBackendInitComplete: unexpected state" <<
static_cast<int>(_initState.load());
274void VideoManager::_createVideoReceivers()
276#ifdef QGC_UNITTEST_BUILD
277 if (_createVideoReceiversForTest) {
278 _createVideoReceiversForTest();
282 static const QStringList videoStreamList = {
287 QStringList existing;
288 existing.reserve(_videoReceivers.size());
289 for (
const VideoReceiver *receiver : std::as_const(_videoReceivers)) {
290 existing.append(receiver->name());
293 for (
const QString &streamName : videoStreamList) {
296 if (existing.contains(streamName)) {
305 _initVideoReceiver(receiver, _mainWindow);
311 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
316void VideoManager::_cleanupOldVideos()
323 QDir videoDir = QDir(savePath);
324 videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
325 videoDir.setSorting(QDir::Time);
327 QStringList nameFilters;
332 videoDir.setNameFilters(nameFilters);
333 QFileInfoList vidList = videoDir.entryInfoList();
334 if (vidList.isEmpty()) {
339 for (
const QFileInfo &video : std::as_const(vidList)) {
340 total += video.size();
344 while ((total >= maxSize) && !vidList.isEmpty()) {
345 const QFileInfo info = vidList.takeLast();
346 total -= info.size();
347 const QString path = info.filePath();
348 qCDebug(VideoManagerLog) <<
"Removing old video file:" << path;
349 (void) QFile::remove(path);
364 if (savePath.isEmpty()) {
365 QGC::showAppMessage(tr(
"Unable to record video. Video save path must be specified in Settings."));
369 const QString videoFileUrl = videoFile.isEmpty() ? QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss") : videoFile;
372 const QString videoFileNameTemplate = savePath +
"/" + videoFileUrl +
".%1" + ext;
374 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
376 qCDebug(VideoManagerLog) <<
"Video receiver is not ready.";
379 const QString streamName = (receiver->
name() == QStringLiteral(
"videoContent")) ?
"" : (receiver->
name() +
".");
380 const QString videoFileName = videoFileNameTemplate.arg(streamName);
387 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
396 _imageFile += QStringLiteral(
"/") + QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss.zzz") + QStringLiteral(
".jpg");
403 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
412 if (!_videoSize.isEmpty()) {
413 return static_cast<double>(_videoSize.width()) / _videoSize.height();
423 return _videoSettings->aspectRatio()->rawValue().toDouble();
443 return pInfo->
hfov();
455 return pInfo->
hfov();
459 return _videoSettings->aspectRatio()->rawValue().toDouble();
476 return (_videoSettings->streamEnabled()->rawValue().toBool() && _videoSettings->
streamConfigured());
492 if (on != _fullScreen) {
500 static const QStringList videoSourceList = {
512 const QString videoSource = _videoSettings->videoSource()->rawValue().toString();
516void VideoManager::_videoSourceChanged()
518 bool changed =
false;
519 if (_activeVehicle) {
521 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
529 changed |= _updateSettings(receiver);
532 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
534 changed |= _updateSettings(receiver);
549 qCDebug(VideoManagerLog) <<
"New Video Source:" << _videoSettings->videoSource()->rawValue().toString();
557 const QString oldUvcVideoSrcID = _uvcVideoSourceID;
560 _uvcVideoSourceID = QString();
565 if (oldUvcVideoSrcID != _uvcVideoSourceID) {
566 qCDebug(VideoManagerLog) <<
"UVC changed from [" << oldUvcVideoSrcID <<
"] to [" << _uvcVideoSourceID <<
"]";
567 if (!_uvcVideoSourceID.isEmpty()) {
583 return !pInfo->
uri().isEmpty();
597 qCDebug(VideoManagerLog) << QString(
"Configure stream (%1):").arg(receiver->
name())
601 switch (pInfo->
type()) {
602 case VIDEO_STREAM_TYPE_RTSP:
606 _videoSettings->rtspUrl()->setRawValue(url);
609 case VIDEO_STREAM_TYPE_TCP_MPEG:
613 case VIDEO_STREAM_TYPE_RTPUDP:
614 if (pInfo->
encoding() == VIDEO_STREAM_ENCODING_H265) {
616 url = pInfo->
uri().contains(
"udp265://") ? pInfo->
uri() : QStringLiteral(
"udp265://0.0.0.0:%1").arg(pInfo->
uri());
619 url = pInfo->
uri().contains(
"udp://") ? pInfo->
uri() : QStringLiteral(
"udp://0.0.0.0:%1").arg(pInfo->
uri());
622 case VIDEO_STREAM_TYPE_MPEG_TS:
624 url = pInfo->
uri().contains(
"mpegts://") ? pInfo->
uri() : QStringLiteral(
"mpegts://0.0.0.0:%1").arg(pInfo->
uri());
627 qCWarning(VideoManagerLog) <<
"Unknown VIDEO_STREAM_TYPE";
633 const bool settingsChanged = _updateVideoUri(receiver, url);
634 if (settingsChanged) {
636 _videoSettings->videoSource()->setRawValue(source);
642 return settingsChanged;
645bool VideoManager::_updateVideoUri(
VideoReceiver *receiver,
const QString &uri)
648 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
652 if ((uri == receiver->
uri()) && !receiver->
uri().isNull()) {
666 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
670 bool settingsChanged =
false;
672 const bool lowLatency = _videoSettings->lowLatencyMode()->rawValue().toBool();
675 settingsChanged =
true;
678 const int rtpLatencyMs =
static_cast<int>(std::min(_videoSettings->rtpJitterLatencyMs()->rawValue().toUInt(),
static_cast<uint
>(INT_MAX)));
681 settingsChanged =
true;
684 const bool autoReconnect = _videoSettings->rtspAutoReconnect()->rawValue().toBool();
691 return settingsChanged;
694 settingsChanged |= _updateUVC(receiver);
695 settingsChanged |= _updateAutoStream(receiver);
697 const QString source = _videoSettings->videoSource()->rawValue().toString();
699 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
701 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp265://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
703 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"mpegts://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
705 settingsChanged |= _updateVideoUri(receiver, _videoSettings->rtspUrl()->rawValue().toString());
707 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
709 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:5600"));
711 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:8888"));
713 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.42.1:554/live"));
715 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.0.10:8554/H264Video"));
717 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.43.1:8554/fpv_stream"));
719 settingsChanged |= _updateVideoUri(receiver, QString());
721 settingsChanged |= _updateVideoUri(receiver, QString());
723 qCCritical(VideoManagerLog) <<
"Video source URI \"" << source <<
"\" is not supported. Please add support!";
727 return settingsChanged;
730void VideoManager::_setActiveVehicle(
Vehicle *vehicle)
732 qCDebug(VideoManagerLog) << Q_FUNC_INFO <<
"new vehicle" << vehicle <<
"old active vehicle" << _activeVehicle;
734 if (_activeVehicle) {
745 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
751 _activeVehicle = vehicle;
752 if (_activeVehicle) {
754 if (_activeVehicle->cameraManager()) {
762 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
763 if (_activeVehicle->cameraManager()) {
765 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->thermalStreamInstance());
767 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->currentStreamInstance());
779void VideoManager::_communicationLostChanged(
bool connectionLost)
781 if (connectionLost) {
786void VideoManager::_restartAllVideos()
788 for (
VideoReceiver *videoReceiver : std::as_const(_videoReceivers)) {
789 _restartVideo(videoReceiver);
796 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
800 qCDebug(VideoManagerLog) <<
"Restart video receiver" << receiver->
name();
803 _stopReceiver(receiver);
806 _startReceiver(receiver);
813 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
824 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
825 _stopReceiver(receiver);
832 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
837 qCDebug(VideoManagerLog) <<
"VideoReceiver is already started" << receiver->
name();
841 if (receiver->
uri().isEmpty()) {
842 qCDebug(VideoManagerLog) <<
"VideoUri is NULL" << receiver->
name();
846 const QString source = _videoSettings->videoSource()->rawValue().toString();
849 receiver->
start(timeout);
852void VideoManager::_initVideoReceiver(
VideoReceiver *receiver, QQuickWindow *window)
854 if (_videoReceivers.contains(receiver)) {
855 qCWarning(VideoManagerLog) <<
"Receiver already initialized";
860 _videoReceivers.append(receiver);
862 QQuickItem *widget = window->findChild<QQuickItem*>(receiver->
name());
864 qCCritical(VideoManagerLog) <<
"stream widget not found" << receiver->
name();
865 _videoReceivers.removeOne(receiver);
866 receiver->deleteLater();
873 qCCritical(VideoManagerLog) <<
"createVideoSink() failed" << receiver->
name();
874 _videoReceivers.removeOne(receiver);
875 receiver->deleteLater();
883 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"Start complete, status:" << status;
887 if (receiver->
sink()) {
896 QTimer::singleShot(1000, receiver, [
this, receiver]() {
897 _restartVideo(receiver);
904 qCDebug(VideoManagerLog) <<
"Stop complete" << receiver->
name()
906 <<
", status:" << status;
909 qCDebug(VideoManagerLog) <<
"Invalid video URL. Not restarting";
911 QTimer::singleShot(1000, receiver, [
this, receiver]() {
912 qCDebug(VideoManagerLog) <<
"Restarting video receiver" << receiver->
name()
914 _startReceiver(receiver);
920 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"streaming changed, active:" << (active ?
"yes" :
"no");
928 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"decoding changed, active:" << (active ?
"yes" :
"no");
936 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording changed, active:" << (active ?
"yes" :
"no");
947 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording started";
954 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"resized. New resolution:" << size.width() <<
"x" << size.height();
964 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot taken";
966 qCWarning(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot failed";
972 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"stream info:" << (videoStreamInfo ?
"received" :
"lost");
974 (void) _updateAutoStream(receiver);
977 (void) _updateSettings(receiver);
980 _startReceiver(receiver);
986 qCDebug(VideoManagerLog) <<
"startVideo";
989 qCDebug(VideoManagerLog) <<
"Stream not enabled/configured";
#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
void setfullScreen(bool on)
void startVideoBackendInit()
Q_INVOKABLE void stopRecording()
bool waitForVideoBackendReady(std::chrono::milliseconds timeout=std::chrono::minutes(1))
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 init(QQuickWindow *mainWindow)
Q_INVOKABLE void stopVideo()
double aspectRatio() const
double thermalHfov() const
void imageFileChanged(const QString &filename)
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)
VideoSinkHandle sink() const
void videoStreamInfoChanged()
virtual void setSink(VideoSinkHandle sink)
virtual void start(uint32_t timeout)=0
void onTakeScreenshotComplete(STATUS status)
bool autoReconnect() const
void decodingChanged(bool active)
void onStartComplete(STATUS status)
virtual void startDecoding(VideoSinkHandle sink)=0
void setStarted(bool started)
void setUri(const QString &uri)
static bool isValidFileFormat(FILE_FORMAT format)
virtual void setWidget(QQuickItem *widget)
void setAutoReconnect(bool enabled)
void setVideoStreamInfo(QGCVideoStreamInfo *videoStreamInfo)
void setRtpJitterLatencyMs(int ms)
void onStopComplete(STATUS status)
int rtpJitterLatencyMs() const
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
void pruneUnavailableDecoders()
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
QString redactedUrlForLogging(const QUrl &url)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
EnvPrepResult prepareEnvironment()
bool initialize(const QStringList &arguments, const EnvPrepResult &envResult)
void applyDecoderPriorities(int rawOption)
void attachSink(QObject *receiver, void *sink, QQuickItem *widget)
void onMainWindowReady(QQuickWindow *window)
bool disabledForUnitTests()
True when the backend should be skipped under unit tests (opt back in with QGC_TEST_ENABLE_GSTREAMER)...
constexpr bool needsAsyncInit() noexcept
void bindDebugLevelFact(Fact *fact, QObject *context)