VideoManager.cc 20.2 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9 10 11 12 13
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/


#include <QQmlContext>
#include <QQmlEngine>
#include <QSettings>
14
#include <QUrl>
15
#include <QDir>
16 17

#ifndef QGC_DISABLE_UVC
18
#include <QCameraInfo>
19
#endif
20 21 22

#include "ScreenToolsController.h"
#include "VideoManager.h"
23 24 25
#include "QGCToolbox.h"
#include "QGCCorePlugin.h"
#include "QGCOptions.h"
26
#include "MultiVehicleManager.h"
27
#include "Settings/SettingsManager.h"
28
#include "Vehicle.h"
29
#include "QGCCameraManager.h"
30 31 32 33

QGC_LOGGING_CATEGORY(VideoManagerLog, "VideoManagerLog")

//-----------------------------------------------------------------------------
34 35
VideoManager::VideoManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
36 37 38 39 40 41
{
}

//-----------------------------------------------------------------------------
VideoManager::~VideoManager()
{
42 43 44 45
    delete _videoReceiver;
    _videoReceiver = nullptr;
    delete _thermalVideoReceiver;
    _thermalVideoReceiver = nullptr;
46 47 48 49 50 51 52 53
}

//-----------------------------------------------------------------------------
void
VideoManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);
   QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
54 55
   qmlRegisterUncreatableType<VideoManager> ("QGroundControl.VideoManager", 1, 0, "VideoManager", "Reference only");
   qmlRegisterUncreatableType<VideoReceiver>("QGroundControl",              1, 0, "VideoReceiver","Reference only");
56 57

   // TODO: Those connections should be Per Video, not per VideoManager.
58 59
   _videoSettings = toolbox->settingsManager()->videoSettings();
   QString videoSource = _videoSettings->videoSource()->rawValue().toString();
60 61 62
   connect(_videoSettings->videoSource(),   &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
   connect(_videoSettings->udpPort(),       &Fact::rawValueChanged, this, &VideoManager::_udpPortChanged);
   connect(_videoSettings->rtspUrl(),       &Fact::rawValueChanged, this, &VideoManager::_rtspUrlChanged);
63
   connect(_videoSettings->tcpUrl(),        &Fact::rawValueChanged, this, &VideoManager::_tcpUrlChanged);
64
   connect(_videoSettings->aspectRatio(),   &Fact::rawValueChanged, this, &VideoManager::_aspectRatioChanged);
65 66
   MultiVehicleManager *pVehicleMgr = qgcApp()->toolbox()->multiVehicleManager();
   connect(pVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &VideoManager::_setActiveVehicle);
67

68
#if defined(QGC_GST_STREAMING)
69 70
#ifndef QGC_DISABLE_UVC
   // If we are using a UVC camera setup the device name
Gus Grubba's avatar
Gus Grubba committed
71
   _updateUVC();
72
#endif
73 74 75

    emit isGStreamerChanged();
    qCDebug(VideoManagerLog) << "New Video Source:" << videoSource;
76
    _videoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
77
    _videoReceiver->setUnittestMode(qgcApp()->runningUnitTests());
78
    _thermalVideoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    _thermalVideoReceiver->setUnittestMode(qgcApp()->runningUnitTests());
    _videoReceiver->moveToThread(qgcApp()->thread());
    _thermalVideoReceiver->moveToThread(qgcApp()->thread());

    // Those connects are temporary: In a perfect world those connections are going to be done on the Qml
    // but because currently the videoReceiver is created in the C++ world, this is easier.
    // The fact returning a QVariant is a quite annoying to use proper signal / slot connection.
    _updateSettings();

    auto appSettings = toolbox->settingsManager()->appSettings();
    for (auto *videoReceiver : { _videoReceiver, _thermalVideoReceiver}) {
        // First, Setup the current values from the settings.
        videoReceiver->setRtspTimeout(_videoSettings->rtspTimeout()->rawValue().toInt());
        videoReceiver->setStreamEnabled(_videoSettings->streamEnabled()->rawValue().toBool());
        videoReceiver->setRecordingFormatId(_videoSettings->recordingFormat()->rawValue().toInt());
        videoReceiver->setStreamConfigured(_videoSettings->streamConfigured());

        connect(_videoSettings->rtspTimeout(), &Fact::rawValueChanged,
            videoReceiver, [videoReceiver](const QVariant &value) {
                videoReceiver->setRtspTimeout(value.toInt());
            }
        );

        connect(_videoSettings->streamEnabled(), &Fact::rawValueChanged,
                videoReceiver, [videoReceiver](const QVariant &value) {
                    videoReceiver->setStreamEnabled(value.toBool());
            }
        );

        connect(_videoSettings->recordingFormat(), &Fact::rawValueChanged,
            videoReceiver, [videoReceiver](const QVariant &value) {
                videoReceiver->setRecordingFormatId(value.toInt());
            }
        );

        // Why some options are facts while others aren't?
        connect(_videoSettings, &VideoSettings::streamConfiguredChanged, videoReceiver, &VideoReceiver::setStreamConfigured);

        // Fix those.
        // connect(appSettings, &Fact::rawValueChanged, videoReceiver, &VideoReceiver::setVideoPath);
        // connect(appSettings->videoSavePath(), &Fact::rawValueChanged, videoReceiver, &VideoReceiver::setImagePath);

        // Connect the video receiver with the rest of the app.
        connect(videoReceiver, &VideoReceiver::restartTimeout, this, &VideoManager::restartVideo);
        connect(videoReceiver, &VideoReceiver::sendMessage, qgcApp(), &QGCApplication::showMessage);
124
        connect(videoReceiver, &VideoReceiver::beforeRecording, this, &VideoManager::cleanupOldVideos);
125 126
    }

127 128
    _updateSettings();
    if(isGStreamer()) {
129
        startVideo();
130
        _subtitleWriter.setVideoReceiver(_videoReceiver);
131
    } else {
132
        stopVideo();
133 134
    }

135
#endif
136 137
}

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
QStringList VideoManager::videoMuxes()
{
    return {"matroskamux", "qtmux", "mp4mux"};
}

QStringList VideoManager::videoExtensions()
{
    return {"mkv", "mov", "mp4"};
}

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();
    QDir videoDir = QDir(savePath);
    videoDir.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
    videoDir.setSorting(QDir::Time);

    QStringList nameFilters;
    for(const QString& extension : videoExtensions()) {
        nameFilters << QString("*.") + extension;
    }
    videoDir.setNameFilters(nameFilters);
    //-- get the list of videos stored
    QFileInfoList vidList = videoDir.entryInfoList();
    if(!vidList.isEmpty()) {
        uint64_t total   = 0;
        //-- Settings are stored using MB
        uint64_t maxSize = _videoSettings->maxVideoSize()->rawValue().toUInt() * 1024 * 1024;
        //-- Compute total used storage
        for(int i = 0; i < vidList.size(); i++) {
            total += vidList[i].size();
        }
        //-- Remove old movies until max size is satisfied.
        while(total >= maxSize && !vidList.isEmpty()) {
            total -= vidList.last().size();
            qCDebug(VideoReceiverLog) << "Removing old video file:" << vidList.last().filePath();
            QFile file (vidList.last().filePath());
            file.remove();
            vidList.removeLast();
        }
    }
#endif
}

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
//-----------------------------------------------------------------------------
void
VideoManager::startVideo()
{
    if(_videoReceiver) _videoReceiver->start();
    if(_thermalVideoReceiver) _thermalVideoReceiver->start();
}

//-----------------------------------------------------------------------------
void
VideoManager::stopVideo()
{
    if(_videoReceiver) _videoReceiver->stop();
    if(_thermalVideoReceiver) _thermalVideoReceiver->stop();
}

203 204 205
//-----------------------------------------------------------------------------
double VideoManager::aspectRatio()
{
206
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
207
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        if(pInfo) {
            qCDebug(VideoManagerLog) << "Primary AR: " << pInfo->aspectRatio();
            return pInfo->aspectRatio();
        }
    }
    return _videoSettings->aspectRatio()->rawValue().toDouble();
}

//-----------------------------------------------------------------------------
double VideoManager::thermalAspectRatio()
{
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->thermalStreamInstance();
        if(pInfo) {
            qCDebug(VideoManagerLog) << "Thermal AR: " << pInfo->aspectRatio();
            return pInfo->aspectRatio();
        }
    }
    return 1.0;
}

//-----------------------------------------------------------------------------
double VideoManager::hfov()
{
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
        if(pInfo) {
            return pInfo->hfov();
        }
    }
    return 1.0;
}

//-----------------------------------------------------------------------------
double VideoManager::thermalHfov()
{
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->thermalStreamInstance();
246 247 248 249 250 251 252
        if(pInfo) {
            return pInfo->aspectRatio();
        }
    }
    return _videoSettings->aspectRatio()->rawValue().toDouble();
}

253 254 255 256 257 258 259 260 261 262 263 264 265
//-----------------------------------------------------------------------------
bool
VideoManager::hasThermal()
{
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->thermalStreamInstance();
        if(pInfo) {
            return true;
        }
    }
    return false;
}

266 267 268 269
//-----------------------------------------------------------------------------
bool
VideoManager::autoStreamConfigured()
{
Gus Grubba's avatar
Gus Grubba committed
270
#if defined(QGC_GST_STREAMING)
271
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
272 273 274
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
        if(pInfo) {
            return !pInfo->uri().isEmpty();
275 276
        }
    }
Gus Grubba's avatar
Gus Grubba committed
277
#endif
278
    return false;
279 280
}

Gus Grubba's avatar
Gus Grubba committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
//-----------------------------------------------------------------------------
void
VideoManager::_updateUVC()
{
#ifndef QGC_DISABLE_UVC
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
    QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
    for (const QCameraInfo &cameraInfo: cameras) {
        if(cameraInfo.description() == videoSource) {
            _videoSourceID = cameraInfo.deviceName();
            emit videoSourceIDChanged();
            qCDebug(VideoManagerLog) << "Found USB source:" << _videoSourceID << " Name:" << videoSource;
            break;
        }
    }
#endif
}

299 300 301
//-----------------------------------------------------------------------------
void
VideoManager::_videoSourceChanged()
302
{
Gus Grubba's avatar
Gus Grubba committed
303
    _updateUVC();
304 305
    emit hasVideoChanged();
    emit isGStreamerChanged();
306
    emit isAutoStreamChanged();
307
    restartVideo();
308 309
}

310 311 312
//-----------------------------------------------------------------------------
void
VideoManager::_udpPortChanged()
313
{
314
    restartVideo();
315 316
}

317 318
//-----------------------------------------------------------------------------
void
319
VideoManager::_rtspUrlChanged()
320
{
321
    restartVideo();
322 323
}

324 325 326 327
//-----------------------------------------------------------------------------
void
VideoManager::_tcpUrlChanged()
{
328
    restartVideo();
329 330
}

331 332 333 334
//-----------------------------------------------------------------------------
bool
VideoManager::hasVideo()
{
335 336 337
    if(autoStreamConfigured()) {
        return true;
    }
338
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
339
    return !videoSource.isEmpty() && videoSource != VideoSettings::videoSourceNoVideo && videoSource != VideoSettings::videoDisabled;
340 341 342 343 344 345 346
}

//-----------------------------------------------------------------------------
bool
VideoManager::isGStreamer()
{
#if defined(QGC_GST_STREAMING)
347
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
348
    return
Gus Grubba's avatar
Gus Grubba committed
349 350
        videoSource == VideoSettings::videoSourceUDPH264 ||
        videoSource == VideoSettings::videoSourceUDPH265 ||
351
        videoSource == VideoSettings::videoSourceRTSP ||
352
        videoSource == VideoSettings::videoSourceTCP ||
353 354
        videoSource == VideoSettings::videoSourceMPEGTS ||
        autoStreamConfigured();
355 356 357 358 359
#else
    return false;
#endif
}

360 361 362 363 364 365 366 367 368
//-----------------------------------------------------------------------------
#ifndef QGC_DISABLE_UVC
bool
VideoManager::uvcEnabled()
{
    return QCameraInfo::availableCameras().count() > 0;
}
#endif

369 370 371 372 373 374 375 376 377 378 379 380 381 382
//-----------------------------------------------------------------------------
void
VideoManager::setfullScreen(bool f)
{
    if(f) {
        //-- No can do if no vehicle or connection lost
        if(!_activeVehicle || _activeVehicle->connectionLost()) {
            f = false;
        }
    }
    _fullScreen = f;
    emit fullScreenChanged();
}

383 384 385
//-----------------------------------------------------------------------------
#if defined(QGC_GST_STREAMING)
GstElement*
386
VideoManager::_makeVideoSink(gpointer widget)
387
{
388
    GstElement* sink;
389

390 391 392 393
    if ((sink = gst_element_factory_make("qgcvideosinkbin", nullptr)) != nullptr) {
        g_object_set(sink, "widget", widget, NULL);
    } else {
        qCritical() << "VideoManager::_makeVideoSink() failed. Error with gst_element_factory_make('qgcvideosinkbin')";
394 395 396 397 398 399 400 401 402 403 404
    }

    return sink;
}
#endif

//-----------------------------------------------------------------------------
void
VideoManager::_initVideo()
{
#if defined(QGC_GST_STREAMING)
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
    QQuickItem* root = qgcApp()->mainRootWindow();

    if (root == nullptr) {
        qCDebug(VideoManagerLog) << "VideoManager::_makeVideoSink() failed. No root window";
        return;
    }

    QQuickItem* widget = root->findChild<QQuickItem*>("videoContent");

    if (widget != nullptr) {
        _videoReceiver->setVideoSink(_makeVideoSink(widget));
    } else {
        qCDebug(VideoManagerLog) << "VideoManager::_makeVideoSink() failed. 'videoContent' widget not found";
    }

    widget = root->findChild<QQuickItem*>("thermalVideo");

    if (widget != nullptr) {
        _thermalVideoReceiver->setVideoSink(_makeVideoSink(widget));
    } else {
        qCDebug(VideoManagerLog) << "VideoManager::_makeVideoSink() failed. 'thermalVideo' widget not found";
    }
427 428 429
#endif
}

430
//-----------------------------------------------------------------------------
431 432
void
VideoManager::_updateSettings()
433 434 435
{
    if(!_videoSettings || !_videoReceiver)
        return;
436
    //-- Auto discovery
437
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
438
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
439
        if(pInfo) {
440
            qCDebug(VideoManagerLog) << "Configure primary stream: " << pInfo->uri();
441 442
            switch(pInfo->type()) {
                case VIDEO_STREAM_TYPE_RTSP:
443 444 445
                    _videoReceiver->setUri(pInfo->uri());
                    _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceRTSP);
                    break;
446 447
                case VIDEO_STREAM_TYPE_TCP_MPEG:
                    _videoReceiver->setUri(pInfo->uri());
448
                    _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceTCP);
449 450 451
                    break;
                case VIDEO_STREAM_TYPE_RTPUDP:
                    _videoReceiver->setUri(QStringLiteral("udp://0.0.0.0:%1").arg(pInfo->uri()));
452
                    _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceUDPH264);
453 454 455
                    break;
                case VIDEO_STREAM_TYPE_MPEG_TS_H264:
                    _videoReceiver->setUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(pInfo->uri()));
456
                    _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceMPEGTS);
457 458 459 460 461
                    break;
                default:
                    _videoReceiver->setUri(pInfo->uri());
                    break;
            }
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
            //-- Thermal stream (if any)
            QGCVideoStreamInfo* pTinfo = _activeVehicle->dynamicCameras()->thermalStreamInstance();
            if(pTinfo) {
                qCDebug(VideoManagerLog) << "Configure secondary stream: " << pTinfo->uri();
                switch(pTinfo->type()) {
                    case VIDEO_STREAM_TYPE_RTSP:
                    case VIDEO_STREAM_TYPE_TCP_MPEG:
                        _thermalVideoReceiver->setUri(pTinfo->uri());
                        break;
                    case VIDEO_STREAM_TYPE_RTPUDP:
                        _thermalVideoReceiver->setUri(QStringLiteral("udp://0.0.0.0:%1").arg(pTinfo->uri()));
                        break;
                    case VIDEO_STREAM_TYPE_MPEG_TS_H264:
                        _thermalVideoReceiver->setUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(pTinfo->uri()));
                        break;
                    default:
                        _thermalVideoReceiver->setUri(pTinfo->uri());
                        break;
                }
            }
482 483 484
            return;
        }
    }
485
    QString source = _videoSettings->videoSource()->rawValue().toString();
Gus Grubba's avatar
Gus Grubba committed
486
    if (source == VideoSettings::videoSourceUDPH264)
487
        _videoReceiver->setUri(QStringLiteral("udp://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
Gus Grubba's avatar
Gus Grubba committed
488 489
    else if (source == VideoSettings::videoSourceUDPH265)
        _videoReceiver->setUri(QStringLiteral("udp265://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
490 491 492
    else if (source == VideoSettings::videoSourceMPEGTS)
        _videoReceiver->setUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
    else if (source == VideoSettings::videoSourceRTSP)
493
        _videoReceiver->setUri(_videoSettings->rtspUrl()->rawValue().toString());
494
    else if (source == VideoSettings::videoSourceTCP)
495
        _videoReceiver->setUri(QStringLiteral("tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
496 497
}

498
//-----------------------------------------------------------------------------
499
void
500
VideoManager::restartVideo()
501
{
502
#if defined(QGC_GST_STREAMING)
503
    qCDebug(VideoManagerLog) << "Restart video streaming";
504
    stopVideo();
505
    _updateSettings();
506
    startVideo();
507
    emit aspectRatioChanged();
508 509
#endif
}
510 511 512 513 514 515

//----------------------------------------------------------------------------------------
void
VideoManager::_setActiveVehicle(Vehicle* vehicle)
{
    if(_activeVehicle) {
516
        disconnect(_activeVehicle, &Vehicle::connectionLostChanged, this, &VideoManager::_connectionLostChanged);
517 518 519 520 521
        if(_activeVehicle->dynamicCameras()) {
            QGCCameraControl* pCamera = _activeVehicle->dynamicCameras()->currentCameraInstance();
            if(pCamera) {
                pCamera->stopStream();
            }
522
            disconnect(_activeVehicle->dynamicCameras(), &QGCCameraManager::streamChanged, this, &VideoManager::restartVideo);
523
        }
524 525 526
    }
    _activeVehicle = vehicle;
    if(_activeVehicle) {
527
        connect(_activeVehicle, &Vehicle::connectionLostChanged, this, &VideoManager::_connectionLostChanged);
528
        if(_activeVehicle->dynamicCameras()) {
529
            connect(_activeVehicle->dynamicCameras(), &QGCCameraManager::streamChanged, this, &VideoManager::restartVideo);
530 531 532 533
            QGCCameraControl* pCamera = _activeVehicle->dynamicCameras()->currentCameraInstance();
            if(pCamera) {
                pCamera->resumeStream();
            }
534
        }
535 536 537
    } else {
        //-- Disable full screen video if vehicle is gone
        setfullScreen(false);
538
    }
539
    emit autoStreamConfiguredChanged();
540
    restartVideo();
541 542
}

543 544 545 546 547 548 549 550 551 552
//----------------------------------------------------------------------------------------
void
VideoManager::_connectionLostChanged(bool connectionLost)
{
    if(connectionLost) {
        //-- Disable full screen video if connection is lost
        setfullScreen(false);
    }
}

553 554
//----------------------------------------------------------------------------------------
void
555
VideoManager::_aspectRatioChanged()
556
{
557
    emit aspectRatioChanged();
558
}