23#include <QtConcurrent/QtConcurrent>
24#include <QtCore/QApplicationStatic>
25#include <QtCore/QCoreApplication>
27#include <QtCore/QEventLoop>
28#include <QtCore/QFutureWatcher>
29#include <QtCore/QRunnable>
30#include <QtCore/QTimer>
31#include <QtQml/QQmlEngine>
32#include <QtQuick/QQuickItem>
33#include <QtQuick/QQuickWindow>
50 qCDebug(VideoManagerLog) <<
this;
52 (void) qRegisterMetaType<VideoReceiver::STATUS>(
"STATUS");
56 if (_backendDisabledForTests) {
57 qCInfo(VideoManagerLog) <<
"Skipping video backend initialization for unit tests";
64 qCDebug(VideoManagerLog) <<
this;
69 return _videoManagerInstance();
76 if (_backendDisabledForTests) {
77 _initState.store(InitState::BackendReady);
78 qCInfo(VideoManagerLog) <<
"video initialization disabled for unit tests";
85 QMutexLocker lock(&_initFutureMutex);
86 InitState expected = InitState::NotStarted;
87 if (!_initState.compare_exchange_strong(expected, InitState::Pending)) {
88 qCWarning(VideoManagerLog) <<
"video init already started";
96 _backendInitFuture.then(
this, [
this](
bool success) {
97 _onBackendInitComplete(success);
98 }).onCanceled(
this, [
this] {
99 _onBackendInitComplete(
false);
107 if (_backendDisabledForTests) {
111 if (_initState.load() == InitState::NotStarted) {
115 switch (_initState.load()) {
116 case InitState::Failed:
118 case InitState::BackendReady:
119 case InitState::Running:
121 case InitState::NotStarted:
122 case InitState::Pending:
123 case InitState::QmlReady:
127 QFuture<bool> future;
129 QMutexLocker lock(&_initFutureMutex);
130 future = _backendInitFuture;
132 if (!future.isValid()) {
133 qCCritical(VideoManagerLog) <<
"waitForVideoBackendReady: no valid future";
139 timer.setSingleShot(
true);
140 QFutureWatcher<bool> watcher;
141 (void) connect(&watcher, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit);
142 (void) connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
144 watcher.setFuture(future);
145 if (!watcher.isFinished()) {
146 timer.start(timeout);
150 if (!watcher.isFinished()) {
151 qCCritical(VideoManagerLog) <<
"Timed out waiting for video init";
155 const bool success = watcher.result();
156 if (_initState.load() == InitState::Pending || _initState.load() == InitState::QmlReady) {
157 _onBackendInitComplete(success);
159 return _initState.load() != InitState::Failed;
165 qCDebug(VideoManagerLog) <<
"Video Manager already initialized";
170 qCCritical(VideoManagerLog) <<
"Failed To Init Video Manager - mainWindow is NULL";
173 _mainWindow = mainWindow;
177 (void) connect(_videoSettings->videoSource(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
178 (void) connect(_videoSettings->udpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
179 (void) connect(_videoSettings->rtspUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
180 (void) connect(_videoSettings->tcpUrl(), &
Fact::rawValueChanged,
this, &VideoManager::_videoSourceChanged);
182 (void) connect(_videoSettings->lowLatencyMode(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) { Q_UNUSED(value); _restartAllVideos(); });
185 (void) connect(_videoSettings->rtpJitterLatencyMs(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) { Q_UNUSED(value); _videoSourceChanged(); });
188 (void) connect(_videoSettings->rtspAutoReconnect(), &
Fact::rawValueChanged,
this, [
this](
const QVariant &value) {
189 const bool enabled = value.toBool();
190 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
191 receiver->setAutoReconnect(enabled);
203 _mainWindow->scheduleRenderJob(
204 QRunnable::create([
this] {
205 QMetaObject::invokeMethod(
this, &VideoManager::_initAfterQmlIsReady, Qt::QueuedConnection);
207 QQuickWindow::AfterSynchronizingStage);
212void VideoManager::_initAfterQmlIsReady()
215 qCCritical(VideoManagerLog) <<
"_initAfterQmlIsReady called with NULL mainWindow";
219 qCDebug(VideoManagerLog) <<
"_initAfterQmlIsReady";
222 switch (_initState.load()) {
223 case InitState::Pending:
224 _initState.store(InitState::QmlReady);
225 qCDebug(VideoManagerLog) <<
"QML ready, waiting for video";
227 case InitState::BackendReady:
228 _initState.store(InitState::Running);
229 qCDebug(VideoManagerLog) <<
"QML ready, video already done — creating receivers";
231 case InitState::Failed:
232 qCWarning(VideoManagerLog) <<
"QML ready but video init failed";
234 case InitState::NotStarted:
235 case InitState::QmlReady:
236 case InitState::Running:
237 qCWarning(VideoManagerLog) <<
"_initAfterQmlIsReady: unexpected state" <<
static_cast<int>(_initState.load());
241 _createVideoReceivers();
244void VideoManager::_onBackendInitComplete(
bool success)
247 _initState.store(InitState::Failed);
248 qCCritical(VideoManagerLog) <<
"video initialization failed";
257 switch (_initState.load()) {
258 case InitState::Pending:
259 _initState.store(InitState::BackendReady);
260 qCDebug(VideoManagerLog) <<
"video ready, waiting for QML";
262 case InitState::QmlReady:
263 _initState.store(InitState::Running);
264 qCDebug(VideoManagerLog) <<
"video ready, QML already done — creating receivers";
265 _createVideoReceivers();
268 qCWarning(VideoManagerLog) <<
"_onBackendInitComplete: unexpected state" <<
static_cast<int>(_initState.load());
273void VideoManager::_createVideoReceivers()
275#ifdef QGC_UNITTEST_BUILD
276 if (_createVideoReceiversForTest) {
277 _createVideoReceiversForTest();
281 static const QStringList videoStreamList = {
286 QStringList existing;
287 existing.reserve(_videoReceivers.size());
288 for (
const VideoReceiver *receiver : std::as_const(_videoReceivers)) {
289 existing.append(receiver->name());
292 for (
const QString &streamName : videoStreamList) {
295 if (existing.contains(streamName)) {
304 _initVideoReceiver(receiver, _mainWindow);
310 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
315void VideoManager::_cleanupOldVideos()
322 QDir videoDir = QDir(savePath);
323 videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
324 videoDir.setSorting(QDir::Time);
326 QStringList nameFilters;
331 videoDir.setNameFilters(nameFilters);
332 QFileInfoList vidList = videoDir.entryInfoList();
333 if (vidList.isEmpty()) {
338 for (
const QFileInfo &video : std::as_const(vidList)) {
339 total += video.size();
343 while ((total >= maxSize) && !vidList.isEmpty()) {
344 const QFileInfo info = vidList.takeLast();
345 total -= info.size();
346 const QString path = info.filePath();
347 qCDebug(VideoManagerLog) <<
"Removing old video file:" << path;
348 (void) QFile::remove(path);
363 if (savePath.isEmpty()) {
364 QGC::showAppMessage(tr(
"Unabled to record video. Video save path must be specified in Settings."));
368 const QString videoFileUrl = videoFile.isEmpty() ? QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss") : videoFile;
371 const QString videoFileNameTemplate = savePath +
"/" + videoFileUrl +
".%1" + ext;
373 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
375 qCDebug(VideoManagerLog) <<
"Video receiver is not ready.";
378 const QString streamName = (receiver->
name() == QStringLiteral(
"videoContent")) ?
"" : (receiver->
name() +
".");
379 const QString videoFileName = videoFileNameTemplate.arg(streamName);
386 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
395 _imageFile += QStringLiteral(
"/") + QDateTime::currentDateTime().toString(
"yyyy-MM-dd_hh.mm.ss.zzz") + QStringLiteral(
".jpg");
402 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
411 if (!_videoSize.isEmpty()) {
412 return static_cast<double>(_videoSize.width()) / _videoSize.height();
422 return _videoSettings->aspectRatio()->rawValue().toDouble();
442 return pInfo->
hfov();
454 return pInfo->
hfov();
458 return _videoSettings->aspectRatio()->rawValue().toDouble();
475 return (_videoSettings->streamEnabled()->rawValue().toBool() && _videoSettings->
streamConfigured());
491 if (on != _fullScreen) {
499 static const QStringList videoSourceList = {
511 const QString videoSource = _videoSettings->videoSource()->rawValue().toString();
515void VideoManager::_videoSourceChanged()
517 bool changed =
false;
518 if (_activeVehicle) {
520 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
528 changed |= _updateSettings(receiver);
531 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
533 changed |= _updateSettings(receiver);
548 qCDebug(VideoManagerLog) <<
"New Video Source:" << _videoSettings->videoSource()->rawValue().toString();
556 const QString oldUvcVideoSrcID = _uvcVideoSourceID;
559 _uvcVideoSourceID = QString();
564 if (oldUvcVideoSrcID != _uvcVideoSourceID) {
565 qCDebug(VideoManagerLog) <<
"UVC changed from [" << oldUvcVideoSrcID <<
"] to [" << _uvcVideoSourceID <<
"]";
566 if (!_uvcVideoSourceID.isEmpty()) {
582 return !pInfo->
uri().isEmpty();
596 qCDebug(VideoManagerLog) << QString(
"Configure stream (%1):").arg(receiver->
name()) << pInfo->
uri();
599 switch (pInfo->
type()) {
600 case VIDEO_STREAM_TYPE_RTSP:
604 _videoSettings->rtspUrl()->setRawValue(url);
607 case VIDEO_STREAM_TYPE_TCP_MPEG:
611 case VIDEO_STREAM_TYPE_RTPUDP:
612 if (pInfo->
encoding() == VIDEO_STREAM_ENCODING_H265) {
614 url = pInfo->
uri().contains(
"udp265://") ? pInfo->
uri() : QStringLiteral(
"udp265://0.0.0.0:%1").arg(pInfo->
uri());
617 url = pInfo->
uri().contains(
"udp://") ? pInfo->
uri() : QStringLiteral(
"udp://0.0.0.0:%1").arg(pInfo->
uri());
620 case VIDEO_STREAM_TYPE_MPEG_TS:
622 url = pInfo->
uri().contains(
"mpegts://") ? pInfo->
uri() : QStringLiteral(
"mpegts://0.0.0.0:%1").arg(pInfo->
uri());
625 qCWarning(VideoManagerLog) <<
"Unknown VIDEO_STREAM_TYPE";
631 const bool settingsChanged = _updateVideoUri(receiver, url);
632 if (settingsChanged) {
634 _videoSettings->videoSource()->setRawValue(source);
640 return settingsChanged;
643bool VideoManager::_updateVideoUri(
VideoReceiver *receiver,
const QString &uri)
646 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
650 if ((uri == receiver->
uri()) && !receiver->
uri().isNull()) {
654 qCDebug(VideoManagerLog) <<
"New Video URI" << uri;
664 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
668 bool settingsChanged =
false;
670 const bool lowLatency = _videoSettings->lowLatencyMode()->rawValue().toBool();
673 settingsChanged =
true;
676 const int rtpLatencyMs =
static_cast<int>(std::min(_videoSettings->rtpJitterLatencyMs()->rawValue().toUInt(),
static_cast<uint
>(INT_MAX)));
679 settingsChanged =
true;
682 const bool autoReconnect = _videoSettings->rtspAutoReconnect()->rawValue().toBool();
689 return settingsChanged;
692 settingsChanged |= _updateUVC(receiver);
693 settingsChanged |= _updateAutoStream(receiver);
695 const QString source = _videoSettings->videoSource()->rawValue().toString();
697 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
699 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp265://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
701 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"mpegts://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
703 settingsChanged |= _updateVideoUri(receiver, _videoSettings->rtspUrl()->rawValue().toString());
705 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
707 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:5600"));
709 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"udp://0.0.0.0:8888"));
711 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.42.1:554/live"));
713 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.0.10:8554/H264Video"));
715 settingsChanged |= _updateVideoUri(receiver, QStringLiteral(
"rtsp://192.168.43.1:8554/fpv_stream"));
717 settingsChanged |= _updateVideoUri(receiver, QString());
719 settingsChanged |= _updateVideoUri(receiver, QString());
721 qCCritical(VideoManagerLog) <<
"Video source URI \"" << source <<
"\" is not supported. Please add support!";
725 return settingsChanged;
728void VideoManager::_setActiveVehicle(
Vehicle *vehicle)
730 qCDebug(VideoManagerLog) << Q_FUNC_INFO <<
"new vehicle" << vehicle <<
"old active vehicle" << _activeVehicle;
732 if (_activeVehicle) {
743 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
749 _activeVehicle = vehicle;
750 if (_activeVehicle) {
752 if (_activeVehicle->cameraManager()) {
760 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
761 if (_activeVehicle->cameraManager()) {
763 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->thermalStreamInstance());
765 receiver->
setVideoStreamInfo(_activeVehicle->cameraManager()->currentStreamInstance());
777void VideoManager::_communicationLostChanged(
bool connectionLost)
779 if (connectionLost) {
784void VideoManager::_restartAllVideos()
786 for (
VideoReceiver *videoReceiver : std::as_const(_videoReceivers)) {
787 _restartVideo(videoReceiver);
794 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
798 qCDebug(VideoManagerLog) <<
"Restart video receiver" << receiver->
name();
801 _stopReceiver(receiver);
804 _startReceiver(receiver);
811 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
822 for (
VideoReceiver *receiver : std::as_const(_videoReceivers)) {
823 _stopReceiver(receiver);
830 qCDebug(VideoManagerLog) <<
"VideoReceiver is NULL";
835 qCDebug(VideoManagerLog) <<
"VideoReceiver is already started" << receiver->
name();
839 if (receiver->
uri().isEmpty()) {
840 qCDebug(VideoManagerLog) <<
"VideoUri is NULL" << receiver->
name();
844 const QString source = _videoSettings->videoSource()->rawValue().toString();
847 receiver->
start(timeout);
850void VideoManager::_initVideoReceiver(
VideoReceiver *receiver, QQuickWindow *window)
852 if (_videoReceivers.contains(receiver)) {
853 qCWarning(VideoManagerLog) <<
"Receiver already initialized";
858 _videoReceivers.append(receiver);
860 QQuickItem *widget = window->findChild<QQuickItem*>(receiver->
name());
862 qCCritical(VideoManagerLog) <<
"stream widget not found" << receiver->
name();
863 _videoReceivers.removeOne(receiver);
864 receiver->deleteLater();
871 qCCritical(VideoManagerLog) <<
"createVideoSink() failed" << receiver->
name();
872 _videoReceivers.removeOne(receiver);
873 receiver->deleteLater();
881 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"Start complete, status:" << status;
885 if (receiver->
sink()) {
894 QTimer::singleShot(1000, receiver, [
this, receiver]() {
895 _restartVideo(receiver);
902 qCDebug(VideoManagerLog) <<
"Stop complete" << receiver->
name() << receiver->
uri() <<
", status:" << status;
905 qCDebug(VideoManagerLog) <<
"Invalid video URL. Not restarting";
907 QTimer::singleShot(1000, receiver, [
this, receiver]() {
908 qCDebug(VideoManagerLog) <<
"Restarting video receiver" << receiver->
name() << receiver->
uri();
909 _startReceiver(receiver);
915 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"streaming changed, active:" << (active ?
"yes" :
"no");
923 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"decoding changed, active:" << (active ?
"yes" :
"no");
931 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording changed, active:" << (active ?
"yes" :
"no");
942 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"recording started";
949 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"resized. New resolution:" << size.width() <<
"x" << size.height();
959 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot taken";
961 qCWarning(VideoManagerLog) <<
"Video" << receiver->
name() <<
"screenshot failed";
967 qCDebug(VideoManagerLog) <<
"Video" << receiver->
name() <<
"stream info:" << (videoStreamInfo ?
"received" :
"lost");
969 (void) _updateAutoStream(receiver);
972 (void) _updateSettings(receiver);
975 _startReceiver(receiver);
981 qCDebug(VideoManagerLog) <<
"startVideo";
984 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
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)