VideoManager.cc 25.1 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 34 35 36
#if defined(QGC_GST_STREAMING)
#include "GStreamer.h"
#else
#include "GLVideoItemStub.h"
#endif

Andrew Voznytsa's avatar
Andrew Voznytsa committed
37 38 39 40
#ifdef QGC_GST_TAISYNC_ENABLED
#include "TaisyncHandler.h"
#endif

41 42
QGC_LOGGING_CATEGORY(VideoManagerLog, "VideoManagerLog")

43
#if defined(QGC_GST_STREAMING)
44 45 46 47 48
static const char* kFileExtension[VideoReceiver::FILE_FORMAT_MAX - VideoReceiver::FILE_FORMAT_MIN] = {
    "mkv",
    "mov",
    "mp4"
};
49
#endif
50

51
//-----------------------------------------------------------------------------
52 53
VideoManager::VideoManager(QGCApplication* app, QGCToolbox* toolbox)
    : QGCTool(app, toolbox)
54
{
55 56 57 58 59 60 61
#if !defined(QGC_GST_STREAMING)
    static bool once = false;
    if (!once) {
        qmlRegisterType<GLVideoItemStub>("org.freedesktop.gstreamer.GLVideoItem", 1, 0, "GstGLVideoItem");
        once = true;
    }
#endif
62 63 64 65 66
}

//-----------------------------------------------------------------------------
VideoManager::~VideoManager()
{
67 68 69 70
    delete _videoReceiver;
    _videoReceiver = nullptr;
    delete _thermalVideoReceiver;
    _thermalVideoReceiver = nullptr;
71
#if defined(QGC_GST_STREAMING)
72 73 74 75
    GStreamer::releaseVideoSink(_thermalVideoSink);
    _thermalVideoSink = nullptr;
    GStreamer::releaseVideoSink(_videoSink);
    _videoSink = nullptr;
76
#endif
77 78 79 80 81 82 83 84
}

//-----------------------------------------------------------------------------
void
VideoManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);
   QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
85 86
   qmlRegisterUncreatableType<VideoManager> ("QGroundControl.VideoManager", 1, 0, "VideoManager", "Reference only");
   qmlRegisterUncreatableType<VideoReceiver>("QGroundControl",              1, 0, "VideoReceiver","Reference only");
87 88

   // TODO: Those connections should be Per Video, not per VideoManager.
89 90
   _videoSettings = toolbox->settingsManager()->videoSettings();
   QString videoSource = _videoSettings->videoSource()->rawValue().toString();
91 92 93
   connect(_videoSettings->videoSource(),   &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
   connect(_videoSettings->udpPort(),       &Fact::rawValueChanged, this, &VideoManager::_udpPortChanged);
   connect(_videoSettings->rtspUrl(),       &Fact::rawValueChanged, this, &VideoManager::_rtspUrlChanged);
94
   connect(_videoSettings->tcpUrl(),        &Fact::rawValueChanged, this, &VideoManager::_tcpUrlChanged);
95
   connect(_videoSettings->aspectRatio(),   &Fact::rawValueChanged, this, &VideoManager::_aspectRatioChanged);
96
   connect(_videoSettings->lowLatencyMode(),&Fact::rawValueChanged, this, &VideoManager::_lowLatencyModeChanged);
97 98
   MultiVehicleManager *pVehicleMgr = qgcApp()->toolbox()->multiVehicleManager();
   connect(pVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &VideoManager::_setActiveVehicle);
99

100
#if defined(QGC_GST_STREAMING)
101 102
#ifndef QGC_DISABLE_UVC
   // If we are using a UVC camera setup the device name
Gus Grubba's avatar
Gus Grubba committed
103
   _updateUVC();
104
#endif
105 106 107

    emit isGStreamerChanged();
    qCDebug(VideoManagerLog) << "New Video Source:" << videoSource;
108
#if defined(QGC_GST_STREAMING)
109
    _videoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
110
    _thermalVideoReceiver = toolbox->corePlugin()->createVideoReceiver(this);
111

112 113 114 115 116
    connect(_videoReceiver, &VideoReceiver::timeout, this, [this](){
        if (!_enableVideoRestart) return;
        _startReceiver(0);
    });

117 118 119 120
    connect(_videoReceiver, &VideoReceiver::streamingChanged, this, [this](bool active){
        _streaming = active;
        emit streamingChanged();
        if (!_enableVideoRestart || active)  return;
121 122 123
        _startReceiver(0);
    });

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    connect(_videoReceiver, &VideoReceiver::decodingChanged, this, [this](bool active){
        _decoding = active;
        emit decodingChanged();
    });

    connect(_videoReceiver, &VideoReceiver::recordingChanged, this, [this](bool active){
        _recording = active;
        if (!active) {
            _subtitleWriter.stopCapturingTelemetry();
        }
        emit recordingChanged();
    });

    connect(_videoReceiver, &VideoReceiver::recordingStarted, this, [this](){
        _subtitleWriter.startCapturingTelemetry(_videoFile);
    });

    connect(_videoReceiver, &VideoReceiver::videoSizeChanged, this, [this](QSize size){
        _videoSize = ((quint32)size.width() << 16) | (quint32)size.height();
        emit videoSizeChanged();
    });

    connect(_videoReceiver, &VideoReceiver::onTakeScreenshotComplete, this, [this](VideoReceiver::STATUS status){
        if (status == VideoReceiver::STATUS_OK) {
        }
    });
150

151 152
    // FIXME: AV: I believe _thermalVideoReceiver should be handled just like _videoReceiver in terms of event
    // and I expect that it will be changed during multiple video stream activity
153
    if (_thermalVideoReceiver != nullptr) {
154 155 156 157 158
        connect(_thermalVideoReceiver, &VideoReceiver::timeout, this, [this](){
            if (!_enableVideoRestart) return;
            _startReceiver(1);
        });

159 160
        connect(_thermalVideoReceiver, &VideoReceiver::streamingChanged, this, [this](bool active){
            if (!_enableVideoRestart || active) return;
161 162
            _startReceiver(1);
        });
163
    }
164
#endif
165 166
    _updateSettings();
    if(isGStreamer()) {
167
        startVideo();
168
    } else {
169
        stopVideo();
170 171
    }

172
#endif
173 174
}

175
void VideoManager::_cleanupOldVideos()
176 177 178 179 180 181 182 183 184 185 186 187
{
#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;
188 189 190

    for(size_t i = 0; i < sizeof(kFileExtension) / sizeof(kFileExtension[0]); i += 1) {
        nameFilters << QString("*.") + kFileExtension[i];
191
    }
192

193 194 195 196 197 198 199 200 201 202 203 204 205 206
    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();
207
            qCDebug(VideoManagerLog) << "Removing old video file:" << vidList.last().filePath();
208 209 210 211 212 213 214 215
            QFile file (vidList.last().filePath());
            file.remove();
            vidList.removeLast();
        }
    }
#endif
}

216 217 218 219
//-----------------------------------------------------------------------------
void
VideoManager::startVideo()
{
220 221 222 223 224
    if (qgcApp()->runningUnitTests()) {
        return;
    }

    if(!_videoSettings->streamEnabled()->rawValue().toBool() || !_videoSettings->streamConfigured()) {
225
        qCDebug(VideoManagerLog) << "Stream not enabled/configured";
226 227 228
        return;
    }

229
    _enableVideoRestart = true;
230

231 232
    _startReceiver(0);
    _startReceiver(1);
233 234 235 236 237 238
}

//-----------------------------------------------------------------------------
void
VideoManager::stopVideo()
{
239 240 241
    if (qgcApp()->runningUnitTests()) {
        return;
    }
242

243
    _enableVideoRestart = false;
244

245 246
    _stopReceiver(1);
    _stopReceiver(0);
247 248
}

249 250 251 252 253 254
void
VideoManager::startRecording(const QString& videoFile)
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
255
#if defined(QGC_GST_STREAMING)
256
    if (!_videoReceiver) {
257
        qgcApp()->showAppMessage(tr("Video receiver is not ready."));
258 259 260 261 262 263
        return;
    }

    const VideoReceiver::FILE_FORMAT fileFormat = static_cast<VideoReceiver::FILE_FORMAT>(_videoSettings->recordingFormat()->rawValue().toInt());

    if(fileFormat < VideoReceiver::FILE_FORMAT_MIN || fileFormat >= VideoReceiver::FILE_FORMAT_MAX) {
264
        qgcApp()->showAppMessage(tr("Invalid video format defined."));
265 266 267 268 269 270 271 272 273
        return;
    }

    //-- Disk usage maintenance
    _cleanupOldVideos();

    QString savePath = qgcApp()->toolbox()->settingsManager()->appSettings()->videoSavePath();

    if(savePath.isEmpty()) {
274
        qgcApp()->showAppMessage(tr("Unabled to record video. Video save path must be specified in Settings."));
275 276 277 278 279 280 281 282
        return;
    }

    _videoFile = savePath + "/"
            + (videoFile.isEmpty() ? QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss") : videoFile)
            + "." + kFileExtension[fileFormat - VideoReceiver::FILE_FORMAT_MIN];

    _videoReceiver->startRecording(_videoFile, fileFormat);
283 284 285
#else
    Q_UNUSED(videoFile)
#endif
286 287 288 289 290 291 292 293
}

void
VideoManager::stopRecording()
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
294
#if defined(QGC_GST_STREAMING)
295 296 297 298 299
    if (!_videoReceiver) {
        return;
    }

    _videoReceiver->stopRecording();
300
#endif
301 302 303 304 305 306 307 308
}

void
VideoManager::grabImage(const QString& imageFile)
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
309
#if defined(QGC_GST_STREAMING)
310 311 312 313 314 315 316 317 318
    if (!_videoReceiver) {
        return;
    }

    _imageFile = imageFile;

    emit imageFileChanged();

    _videoReceiver->takeScreenshot(_imageFile);
319 320 321
#else
    Q_UNUSED(imageFile)
#endif
322 323
}

324 325 326
//-----------------------------------------------------------------------------
double VideoManager::aspectRatio()
{
327
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
328
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
329 330 331 332 333
        if(pInfo) {
            qCDebug(VideoManagerLog) << "Primary AR: " << pInfo->aspectRatio();
            return pInfo->aspectRatio();
        }
    }
334
    // FIXME: AV: use _videoReceiver->videoSize() to calculate AR (if AR is not specified in the settings?)
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    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();
368 369 370 371 372 373 374
        if(pInfo) {
            return pInfo->aspectRatio();
        }
    }
    return _videoSettings->aspectRatio()->rawValue().toDouble();
}

375 376 377 378 379 380 381 382 383 384 385 386 387
//-----------------------------------------------------------------------------
bool
VideoManager::hasThermal()
{
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->thermalStreamInstance();
        if(pInfo) {
            return true;
        }
    }
    return false;
}

388 389 390 391 392 393 394
//-----------------------------------------------------------------------------
QString
VideoManager::imageFile()
{
    return _imageFile;
}

395 396 397 398
//-----------------------------------------------------------------------------
bool
VideoManager::autoStreamConfigured()
{
Gus Grubba's avatar
Gus Grubba committed
399
#if defined(QGC_GST_STREAMING)
400
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
401 402 403
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
        if(pInfo) {
            return !pInfo->uri().isEmpty();
404 405
        }
    }
Gus Grubba's avatar
Gus Grubba committed
406
#endif
407
    return false;
408 409
}

Gus Grubba's avatar
Gus Grubba committed
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
//-----------------------------------------------------------------------------
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
}

428 429 430
//-----------------------------------------------------------------------------
void
VideoManager::_videoSourceChanged()
431
{
Gus Grubba's avatar
Gus Grubba committed
432
    _updateUVC();
433 434
    emit hasVideoChanged();
    emit isGStreamerChanged();
435
    emit isAutoStreamChanged();
436
    _restartVideo();
437 438
}

439 440 441
//-----------------------------------------------------------------------------
void
VideoManager::_udpPortChanged()
442
{
443
    _restartVideo();
444 445
}

446 447
//-----------------------------------------------------------------------------
void
448
VideoManager::_rtspUrlChanged()
449
{
450
    _restartVideo();
451 452
}

453 454 455 456
//-----------------------------------------------------------------------------
void
VideoManager::_tcpUrlChanged()
{
457
    _restartVideo();
458 459
}

460 461 462 463
//-----------------------------------------------------------------------------
void
VideoManager::_lowLatencyModeChanged()
{
DoinLakeFlyer's avatar
DoinLakeFlyer committed
464
    //restartVideo();
465 466
}

467 468 469 470
//-----------------------------------------------------------------------------
bool
VideoManager::hasVideo()
{
471 472 473
    if(autoStreamConfigured()) {
        return true;
    }
474
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
475
    return !videoSource.isEmpty() && videoSource != VideoSettings::videoSourceNoVideo && videoSource != VideoSettings::videoDisabled;
476 477 478 479 480 481 482
}

//-----------------------------------------------------------------------------
bool
VideoManager::isGStreamer()
{
#if defined(QGC_GST_STREAMING)
483
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
484
    return
Gus Grubba's avatar
Gus Grubba committed
485 486
        videoSource == VideoSettings::videoSourceUDPH264 ||
        videoSource == VideoSettings::videoSourceUDPH265 ||
487
        videoSource == VideoSettings::videoSourceRTSP ||
488
        videoSource == VideoSettings::videoSourceTCP ||
489 490
        videoSource == VideoSettings::videoSourceMPEGTS ||
        autoStreamConfigured();
491 492 493 494 495
#else
    return false;
#endif
}

496 497 498 499 500 501 502 503 504
//-----------------------------------------------------------------------------
#ifndef QGC_DISABLE_UVC
bool
VideoManager::uvcEnabled()
{
    return QCameraInfo::availableCameras().count() > 0;
}
#endif

505 506 507 508 509 510 511 512 513 514 515 516 517 518
//-----------------------------------------------------------------------------
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();
}

519 520 521 522
void
VideoManager::_initVideo()
{
#if defined(QGC_GST_STREAMING)
523 524 525
    QQuickItem* root = qgcApp()->mainRootWindow();

    if (root == nullptr) {
526
        qCDebug(VideoManagerLog) << "mainRootWindow() failed. No root window";
527 528 529 530 531
        return;
    }

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

532
    if (widget != nullptr && _videoReceiver != nullptr) {
533
        if ((_videoSink = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget)) != nullptr) {
534 535
            _videoReceiver->startDecoding(_videoSink);
        } else {
536
            qCDebug(VideoManagerLog) << "createVideoSink() failed";
537
        }
538
    } else {
539
        qCDebug(VideoManagerLog) << "video receiver disabled";
540 541 542 543
    }

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

544
    if (widget != nullptr && _thermalVideoReceiver != nullptr) {
545
        if ((_thermalVideoSink = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget)) != nullptr) {
546 547
            _thermalVideoReceiver->startDecoding(_thermalVideoSink);
        } else {
548
            qCDebug(VideoManagerLog) << "createVideoSink() failed";
549
        }
550
    } else {
551
        qCDebug(VideoManagerLog) << "thermal video receiver disabled";
552
    }
553 554 555
#endif
}

556
//-----------------------------------------------------------------------------
557
bool
558
VideoManager::_updateSettings()
559
{
560
    if(!_videoSettings)
561
        return false;
562
    //-- Auto discovery
563
    if(_activeVehicle && _activeVehicle->dynamicCameras()) {
564
        QGCVideoStreamInfo* pInfo = _activeVehicle->dynamicCameras()->currentStreamInstance();
565
        if(pInfo) {
566
            bool status = false;
567
            qCDebug(VideoManagerLog) << "Configure primary stream: " << pInfo->uri();
568 569
            switch(pInfo->type()) {
                case VIDEO_STREAM_TYPE_RTSP:
570 571 572
                    if (status = _updateVideoUri(pInfo->uri())) {
                        _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceRTSP);
                    }
573
                    break;
574
                case VIDEO_STREAM_TYPE_TCP_MPEG:
575 576 577
                    if (status = _updateVideoUri(pInfo->uri())) {
                        _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceTCP);
                    }
578 579
                    break;
                case VIDEO_STREAM_TYPE_RTPUDP:
580 581 582
                    if (status = _updateVideoUri(QStringLiteral("udp://0.0.0.0:%1").arg(pInfo->uri()))) {
                        _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceUDPH264);
                    }
583 584
                    break;
                case VIDEO_STREAM_TYPE_MPEG_TS_H264:
585 586 587
                    if (status = _updateVideoUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(pInfo->uri()))) {
                        _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceMPEGTS);
                    }
588 589
                    break;
                default:
590
                    status = _updateVideoUri(pInfo->uri());
591 592
                    break;
            }
593 594 595 596 597 598 599
            //-- 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:
600
                        status |= _updateThermalVideoUri(pTinfo->uri());
601 602
                        break;
                    case VIDEO_STREAM_TYPE_RTPUDP:
603
                        status |= _updateThermalVideoUri(QStringLiteral("udp://0.0.0.0:%1").arg(pTinfo->uri()));
604 605
                        break;
                    case VIDEO_STREAM_TYPE_MPEG_TS_H264:
606
                        status |= _updateThermalVideoUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(pTinfo->uri()));
607 608
                        break;
                    default:
609
                        status |= _updateThermalVideoUri(pTinfo->uri());
610 611 612
                        break;
                }
            }
613
            return status;
614 615
        }
    }
616
    QString source = _videoSettings->videoSource()->rawValue().toString();
Gus Grubba's avatar
Gus Grubba committed
617
    if (source == VideoSettings::videoSourceUDPH264)
618
        return _updateVideoUri(QStringLiteral("udp://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
Gus Grubba's avatar
Gus Grubba committed
619
    else if (source == VideoSettings::videoSourceUDPH265)
620
        return _updateVideoUri(QStringLiteral("udp265://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
621
    else if (source == VideoSettings::videoSourceMPEGTS)
622
        return _updateVideoUri(QStringLiteral("mpegts://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
623
    else if (source == VideoSettings::videoSourceRTSP)
624
        return _updateVideoUri(_videoSettings->rtspUrl()->rawValue().toString());
625
    else if (source == VideoSettings::videoSourceTCP)
626
        return _updateVideoUri(QStringLiteral("tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
627 628
}

629 630
bool
VideoManager::_updateVideoUri(const QString& uri)
631 632 633 634
{
#if defined(QGC_GST_TAISYNC_ENABLED) && (defined(__android__) || defined(__ios__))
    //-- Taisync on iOS or Android sends a raw h.264 stream
    if (isTaisync()) {
Andrew Voznytsa's avatar
Andrew Voznytsa committed
635
        return _updateVideoUri(QString("tsusb://0.0.0.0:%1").arg(TAISYNC_VIDEO_UDP_PORT));
636 637
    }
#endif
638 639 640 641
    if (uri == _videoUri) {
        return false;
    }

642
    _videoUri = uri;
643 644

    return true;
645 646
}

647
bool
648
VideoManager::_updateThermalVideoUri(const QString& uri)
649 650 651 652 653
{
#if defined(QGC_GST_TAISYNC_ENABLED) && (defined(__android__) || defined(__ios__))
    //-- Taisync on iOS or Android sends a raw h.264 stream
    if (isTaisync()) {
        // FIXME: AV: TAISYNC_VIDEO_UDP_PORT is used by video stream, thermal stream should go via its own proxy
Andrew Voznytsa's avatar
Andrew Voznytsa committed
654 655 656 657 658 659
        if (!_thermalVideoUri.isEmpty()) {
            _thermalVideoUri.clear();
            return true;
        } else {
            return false;
        }
660 661
    }
#endif
662 663
    if (uri == _thermalVideoUri) {
        return false;
664 665
    }

666
    _thermalVideoUri = uri;
667

668
    return true;
669 670
}

671
//-----------------------------------------------------------------------------
672
void
673
VideoManager::_restartVideo()
674
{
675
#if defined(QGC_GST_STREAMING)
676 677 678 679 680
    if (!_updateSettings()) {
        qCDebug(VideoManagerLog) << "No sense to restart video streaming, skipped";
        return;
    }

681
    qCDebug(VideoManagerLog) << "Restart video streaming";
682

683 684 685 686 687 688 689 690 691
    if (!_enableVideoRestart) {
        _enableVideoRestart = true;
        _startReceiver(0);
        _startReceiver(1);
    } else {
        _stopReceiver(1);
        _stopReceiver(0);
    }

692
//    emit aspectRatioChanged();
693 694
#endif
}
695

696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
//----------------------------------------------------------------------------------------
void
VideoManager::_startReceiver(unsigned id)
{
#if defined(QGC_GST_STREAMING)
    const unsigned timeout = _videoSettings->rtspTimeout()->rawValue().toUInt();

    if (id == 0 && _videoReceiver != nullptr) {
        _videoReceiver->start(_videoUri, timeout);
        if (_videoSink != nullptr) {
            _videoReceiver->startDecoding(_videoSink);
        }
    } else if (id == 1 && _thermalVideoReceiver != nullptr) {
        _thermalVideoReceiver->start(_thermalVideoUri, timeout);
        if (_thermalVideoSink != nullptr) {
            _thermalVideoReceiver->startDecoding(_thermalVideoSink);
        }
    } else if (id > 1) {
        qCDebug(VideoManagerLog) << "Unsupported receiver id" << id;
    }
#endif
}

//----------------------------------------------------------------------------------------
void
VideoManager::_stopReceiver(unsigned id)
{
#if defined(QGC_GST_STREAMING)
    if (id == 0 && _videoReceiver != nullptr) {
        _videoReceiver->stop();
    } else if (id == 1 && _thermalVideoReceiver != nullptr){
        _thermalVideoReceiver->stop();
    } else if (id > 1) {
        qCDebug(VideoManagerLog) << "Unsupported receiver id" << id;
    }
#endif
}

734 735 736 737 738
//----------------------------------------------------------------------------------------
void
VideoManager::_setActiveVehicle(Vehicle* vehicle)
{
    if(_activeVehicle) {
739
        disconnect(_activeVehicle, &Vehicle::connectionLostChanged, this, &VideoManager::_connectionLostChanged);
740 741 742 743 744
        if(_activeVehicle->dynamicCameras()) {
            QGCCameraControl* pCamera = _activeVehicle->dynamicCameras()->currentCameraInstance();
            if(pCamera) {
                pCamera->stopStream();
            }
745
            disconnect(_activeVehicle->dynamicCameras(), &QGCCameraManager::streamChanged, this, &VideoManager::_restartVideo);
746
        }
747 748 749
    }
    _activeVehicle = vehicle;
    if(_activeVehicle) {
750
        connect(_activeVehicle, &Vehicle::connectionLostChanged, this, &VideoManager::_connectionLostChanged);
751
        if(_activeVehicle->dynamicCameras()) {
752
            connect(_activeVehicle->dynamicCameras(), &QGCCameraManager::streamChanged, this, &VideoManager::_restartVideo);
753 754 755 756
            QGCCameraControl* pCamera = _activeVehicle->dynamicCameras()->currentCameraInstance();
            if(pCamera) {
                pCamera->resumeStream();
            }
757
        }
758 759 760
    } else {
        //-- Disable full screen video if vehicle is gone
        setfullScreen(false);
761
    }
762
    emit autoStreamConfiguredChanged();
763
    _restartVideo();
764 765
}

766 767 768 769 770 771 772 773 774 775
//----------------------------------------------------------------------------------------
void
VideoManager::_connectionLostChanged(bool connectionLost)
{
    if(connectionLost) {
        //-- Disable full screen video if connection is lost
        setfullScreen(false);
    }
}

776 777
//----------------------------------------------------------------------------------------
void
778
VideoManager::_aspectRatioChanged()
779
{
780
    emit aspectRatioChanged();
781
}