VideoManager.cc 28.7 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 71
    for (int i = 0; i < 2; i++) {
        if (_videoReceiver[i] != nullptr) {
            delete _videoReceiver[i];
            _videoReceiver[i] = nullptr;
        }
72
#if defined(QGC_GST_STREAMING)
73
        if (_videoSink[i] != nullptr) {
74 75 76 77 78
            // FIXME: AV: we need some interaface for video sink with .release() call
            // Currently VideoManager is destroyed after corePlugin() and we are crashing on app exit
            // calling qgcApp()->toolbox()->corePlugin()->releaseVideoSink(_videoSink[i]);
            // As for now let's call GStreamer::releaseVideoSink() directly
            GStreamer::releaseVideoSink(_videoSink[i]);
79 80
            _videoSink[i] = nullptr;
        }
81
#endif
82
    }
83 84 85 86 87 88 89 90
}

//-----------------------------------------------------------------------------
void
VideoManager::setToolbox(QGCToolbox *toolbox)
{
   QGCTool::setToolbox(toolbox);
   QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
91 92
   qmlRegisterUncreatableType<VideoManager> ("QGroundControl.VideoManager", 1, 0, "VideoManager", "Reference only");
   qmlRegisterUncreatableType<VideoReceiver>("QGroundControl",              1, 0, "VideoReceiver","Reference only");
93 94

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

106
#if defined(QGC_GST_STREAMING)
107 108
#ifndef QGC_DISABLE_UVC
   // If we are using a UVC camera setup the device name
Gus Grubba's avatar
Gus Grubba committed
109
   _updateUVC();
110
#endif
111 112 113

    emit isGStreamerChanged();
    qCDebug(VideoManagerLog) << "New Video Source:" << videoSource;
114
#if defined(QGC_GST_STREAMING)
115 116
    _videoReceiver[0] = toolbox->corePlugin()->createVideoReceiver(this);
    _videoReceiver[1] = toolbox->corePlugin()->createVideoReceiver(this);
117

118
    connect(_videoReceiver[0], &VideoReceiver::streamingChanged, this, [this](bool active){
119 120
        _streaming = active;
        emit streamingChanged();
121 122 123 124 125
    });

    connect(_videoReceiver[0], &VideoReceiver::onStartComplete, this, [this](VideoReceiver::STATUS status) {
        if (status == VideoReceiver::STATUS_OK) {
            _videoStarted[0] = true;
126 127 128 129 130 131
            if (_videoSink[0] != nullptr) {
                // It is absolytely ok to have video receiver active (streaming) and decoding not active
                // It should be handy for cases when you have many streams and want to show only some of them
                // NOTE that even if decoder did not start it is still possible to record video
                _videoReceiver[0]->startDecoding(_videoSink[0]);
            }
132 133 134 135 136 137 138 139 140 141 142
        } else if (status == VideoReceiver::STATUS_INVALID_URL) {
            // Invalid URL - don't restart
        } else if (status == VideoReceiver::STATUS_INVALID_STATE) {
            // Already running
        } else {
            _restartVideo(0);
        }
    });

    connect(_videoReceiver[0], &VideoReceiver::onStopComplete, this, [this](VideoReceiver::STATUS) {
        _videoStarted[0] = false;
143 144 145
        _startReceiver(0);
    });

146
    connect(_videoReceiver[0], &VideoReceiver::decodingChanged, this, [this](bool active){
147 148 149 150
        _decoding = active;
        emit decodingChanged();
    });

151
    connect(_videoReceiver[0], &VideoReceiver::recordingChanged, this, [this](bool active){
152 153 154 155 156 157 158
        _recording = active;
        if (!active) {
            _subtitleWriter.stopCapturingTelemetry();
        }
        emit recordingChanged();
    });

159
    connect(_videoReceiver[0], &VideoReceiver::recordingStarted, this, [this](){
160 161 162
        _subtitleWriter.startCapturingTelemetry(_videoFile);
    });

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

168 169 170 171
    //connect(_videoReceiver, &VideoReceiver::onTakeScreenshotComplete, this, [this](VideoReceiver::STATUS status){
    //    if (status == VideoReceiver::STATUS_OK) {
    //    }
    //});
172

173 174
    // 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
175 176 177 178
    if (_videoReceiver[1] != nullptr) {
        connect(_videoReceiver[1], &VideoReceiver::onStartComplete, this, [this](VideoReceiver::STATUS status) {
            if (status == VideoReceiver::STATUS_OK) {
                _videoStarted[1] = true;
179 180 181
                if (_videoSink[1] != nullptr) {
                    _videoReceiver[1]->startDecoding(_videoSink[1]);
                }
182 183 184 185 186 187 188
            } else if (status == VideoReceiver::STATUS_INVALID_URL) {
                // Invalid URL - don't restart
            } else if (status == VideoReceiver::STATUS_INVALID_STATE) {
                // Already running
            } else {
                _restartVideo(1);
            }
189 190
        });

191 192
        connect(_videoReceiver[1], &VideoReceiver::onStopComplete, this, [this](VideoReceiver::STATUS) {
            _videoStarted[1] = false;
193 194
            _startReceiver(1);
        });
195
    }
196
#endif
197 198
    _updateSettings(0);
    _updateSettings(1);
199
    if(isGStreamer()) {
200
        startVideo();
201
    } else {
202
        stopVideo();
203 204
    }

205
#endif
206 207
}

208
void VideoManager::_cleanupOldVideos()
209 210 211 212 213 214 215 216 217 218 219 220
{
#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;
221 222 223

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

226 227 228 229 230 231 232 233 234 235 236 237 238 239
    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();
240
            qCDebug(VideoManagerLog) << "Removing old video file:" << vidList.last().filePath();
241 242 243 244 245 246 247 248
            QFile file (vidList.last().filePath());
            file.remove();
            vidList.removeLast();
        }
    }
#endif
}

249 250 251 252
//-----------------------------------------------------------------------------
void
VideoManager::startVideo()
{
253 254 255 256 257
    if (qgcApp()->runningUnitTests()) {
        return;
    }

    if(!_videoSettings->streamEnabled()->rawValue().toBool() || !_videoSettings->streamConfigured()) {
258
        qCDebug(VideoManagerLog) << "Stream not enabled/configured";
259 260 261
        return;
    }

262 263
    _startReceiver(0);
    _startReceiver(1);
264 265 266 267 268 269
}

//-----------------------------------------------------------------------------
void
VideoManager::stopVideo()
{
270 271 272
    if (qgcApp()->runningUnitTests()) {
        return;
    }
273

274 275
    _stopReceiver(1);
    _stopReceiver(0);
276 277
}

278 279 280 281 282 283
void
VideoManager::startRecording(const QString& videoFile)
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
284
#if defined(QGC_GST_STREAMING)
285
    if (!_videoReceiver[0]) {
286
        qgcApp()->showAppMessage(tr("Video receiver is not ready."));
287 288 289 290 291 292
        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) {
293
        qgcApp()->showAppMessage(tr("Invalid video format defined."));
294 295
        return;
    }
296
    QString ext = kFileExtension[fileFormat - VideoReceiver::FILE_FORMAT_MIN];
297 298 299 300 301 302

    //-- Disk usage maintenance
    _cleanupOldVideos();

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

303
    if (savePath.isEmpty()) {
304
        qgcApp()->showAppMessage(tr("Unabled to record video. Video save path must be specified in Settings."));
305 306 307 308 309
        return;
    }

    _videoFile = savePath + "/"
            + (videoFile.isEmpty() ? QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss") : videoFile)
310 311 312 313 314 315 316 317 318 319
            + ".";
    QString videoFile2 = _videoFile + "2." + ext;
    _videoFile += ext;

    if (_videoReceiver[0] && _videoStarted[0]) {
        _videoReceiver[0]->startRecording(_videoFile, fileFormat);
    }
    if (_videoReceiver[1] && _videoStarted[1]) {
        _videoReceiver[1]->startRecording(videoFile2, fileFormat);
    }
320

321 322 323
#else
    Q_UNUSED(videoFile)
#endif
324 325 326 327 328 329 330 331
}

void
VideoManager::stopRecording()
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
332
#if defined(QGC_GST_STREAMING)
333

334 335 336 337 338
    for (int i = 0; i < 2; i++) {
        if (_videoReceiver[i]) {
            _videoReceiver[i]->stopRecording();
        }
    }
339
#endif
340 341 342 343 344 345 346 347
}

void
VideoManager::grabImage(const QString& imageFile)
{
    if (qgcApp()->runningUnitTests()) {
        return;
    }
348
#if defined(QGC_GST_STREAMING)
349
    if (!_videoReceiver[0]) {
350 351 352
        return;
    }

353 354 355 356 357 358
    if (imageFile.isEmpty()) {
        _imageFile = qgcApp()->toolbox()->settingsManager()->appSettings()->photoSavePath();
        _imageFile += + "/" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh.mm.ss.zzz") + ".jpg";
    } else {
        _imageFile = imageFile;
    }
359 360 361

    emit imageFileChanged();

362
    _videoReceiver[0]->takeScreenshot(_imageFile);
363 364 365
#else
    Q_UNUSED(imageFile)
#endif
366 367
}

368 369 370
//-----------------------------------------------------------------------------
double VideoManager::aspectRatio()
{
371 372
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->currentStreamInstance();
373 374 375 376 377
        if(pInfo) {
            qCDebug(VideoManagerLog) << "Primary AR: " << pInfo->aspectRatio();
            return pInfo->aspectRatio();
        }
    }
378
    // FIXME: AV: use _videoReceiver->videoSize() to calculate AR (if AR is not specified in the settings?)
379 380 381 382 383 384
    return _videoSettings->aspectRatio()->rawValue().toDouble();
}

//-----------------------------------------------------------------------------
double VideoManager::thermalAspectRatio()
{
385 386
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->thermalStreamInstance();
387 388 389 390 391 392 393 394 395 396 397
        if(pInfo) {
            qCDebug(VideoManagerLog) << "Thermal AR: " << pInfo->aspectRatio();
            return pInfo->aspectRatio();
        }
    }
    return 1.0;
}

//-----------------------------------------------------------------------------
double VideoManager::hfov()
{
398 399
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->currentStreamInstance();
400 401 402 403 404 405 406 407 408 409
        if(pInfo) {
            return pInfo->hfov();
        }
    }
    return 1.0;
}

//-----------------------------------------------------------------------------
double VideoManager::thermalHfov()
{
410 411
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->thermalStreamInstance();
412 413 414 415 416 417 418
        if(pInfo) {
            return pInfo->aspectRatio();
        }
    }
    return _videoSettings->aspectRatio()->rawValue().toDouble();
}

419 420 421 422
//-----------------------------------------------------------------------------
bool
VideoManager::hasThermal()
{
423 424
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->thermalStreamInstance();
425 426 427 428 429 430 431
        if(pInfo) {
            return true;
        }
    }
    return false;
}

432 433 434 435 436 437 438
//-----------------------------------------------------------------------------
QString
VideoManager::imageFile()
{
    return _imageFile;
}

439 440 441 442
//-----------------------------------------------------------------------------
bool
VideoManager::autoStreamConfigured()
{
Gus Grubba's avatar
Gus Grubba committed
443
#if defined(QGC_GST_STREAMING)
444 445
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->currentStreamInstance();
446 447
        if(pInfo) {
            return !pInfo->uri().isEmpty();
448 449
        }
    }
Gus Grubba's avatar
Gus Grubba committed
450
#endif
451
    return false;
452 453
}

Gus Grubba's avatar
Gus Grubba committed
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
//-----------------------------------------------------------------------------
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
}

472 473 474
//-----------------------------------------------------------------------------
void
VideoManager::_videoSourceChanged()
475
{
Gus Grubba's avatar
Gus Grubba committed
476
    _updateUVC();
477 478
    emit hasVideoChanged();
    emit isGStreamerChanged();
479
    emit isAutoStreamChanged();
480
    _restartVideo(0);
481 482
}

483 484 485
//-----------------------------------------------------------------------------
void
VideoManager::_udpPortChanged()
486
{
487
    _restartVideo(0);
488 489
}

490 491
//-----------------------------------------------------------------------------
void
492
VideoManager::_rtspUrlChanged()
493
{
494
    _restartVideo(0);
495 496
}

497 498 499 500
//-----------------------------------------------------------------------------
void
VideoManager::_tcpUrlChanged()
{
501
    _restartVideo(0);
502 503
}

504 505 506 507
//-----------------------------------------------------------------------------
void
VideoManager::_lowLatencyModeChanged()
{
508
    _restartAllVideos();
509 510
}

511 512 513 514
//-----------------------------------------------------------------------------
bool
VideoManager::hasVideo()
{
515 516 517
    if(autoStreamConfigured()) {
        return true;
    }
518
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
519
    return !videoSource.isEmpty() && videoSource != VideoSettings::videoSourceNoVideo && videoSource != VideoSettings::videoDisabled;
520 521 522 523 524 525 526
}

//-----------------------------------------------------------------------------
bool
VideoManager::isGStreamer()
{
#if defined(QGC_GST_STREAMING)
527
    QString videoSource = _videoSettings->videoSource()->rawValue().toString();
528 529 530 531 532 533 534 535
    return videoSource == VideoSettings::videoSourceUDPH264 ||
            videoSource == VideoSettings::videoSourceUDPH265 ||
            videoSource == VideoSettings::videoSourceRTSP ||
            videoSource == VideoSettings::videoSourceTCP ||
            videoSource == VideoSettings::videoSourceMPEGTS ||
            videoSource == VideoSettings::videoSource3DRSolo ||
            videoSource == VideoSettings::videoSourceParrotDiscovery ||
            autoStreamConfigured();
536 537 538 539 540
#else
    return false;
#endif
}

541 542 543 544 545 546 547 548 549
//-----------------------------------------------------------------------------
#ifndef QGC_DISABLE_UVC
bool
VideoManager::uvcEnabled()
{
    return QCameraInfo::availableCameras().count() > 0;
}
#endif

550 551 552 553 554 555
//-----------------------------------------------------------------------------
void
VideoManager::setfullScreen(bool f)
{
    if(f) {
        //-- No can do if no vehicle or connection lost
556
        if(!_activeVehicle || _activeVehicle->vehicleLinkManager()->communicationLost()) {
557 558 559 560 561 562 563
            f = false;
        }
    }
    _fullScreen = f;
    emit fullScreenChanged();
}

564
//-----------------------------------------------------------------------------
565 566 567 568
void
VideoManager::_initVideo()
{
#if defined(QGC_GST_STREAMING)
569 570 571
    QQuickItem* root = qgcApp()->mainRootWindow();

    if (root == nullptr) {
572
        qCDebug(VideoManagerLog) << "mainRootWindow() failed. No root window";
573 574 575 576 577
        return;
    }

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

578 579
    if (widget != nullptr && _videoReceiver[0] != nullptr) {
        _videoSink[0] = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget);
580 581 582 583 584
        if (_videoSink[0] != nullptr) {
            if (_videoStarted[0]) {
                _videoReceiver[0]->startDecoding(_videoSink[0]);
            }
        } else {
585
            qCDebug(VideoManagerLog) << "createVideoSink() failed";
586
        }
587
    } else {
588
        qCDebug(VideoManagerLog) << "video receiver disabled";
589 590 591 592
    }

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

593 594
    if (widget != nullptr && _videoReceiver[1] != nullptr) {
        _videoSink[1] = qgcApp()->toolbox()->corePlugin()->createVideoSink(this, widget);
595 596 597 598 599
        if (_videoSink[1] != nullptr) {
            if (_videoStarted[1]) {
                _videoReceiver[1]->startDecoding(_videoSink[1]);
            }
        } else {
600
            qCDebug(VideoManagerLog) << "createVideoSink() failed";
601
        }
602
    } else {
603
        qCDebug(VideoManagerLog) << "thermal video receiver disabled";
604
    }
605 606 607
#endif
}

608
//-----------------------------------------------------------------------------
609
bool
610
VideoManager::_updateSettings(unsigned id)
611
{
612
    if(!_videoSettings)
613
        return false;
614 615 616 617 618 619 620

    const bool lowLatencyStreaming  =_videoSettings->lowLatencyMode()->rawValue().toBool();

    bool settingsChanged = _lowLatencyStreaming[id] != lowLatencyStreaming;

    _lowLatencyStreaming[id] = lowLatencyStreaming;

621
    //-- Auto discovery
622

623 624
    if(_activeVehicle && _activeVehicle->cameraManager()) {
        QGCVideoStreamInfo* pInfo = _activeVehicle->cameraManager()->currentStreamInstance();
625
        if(pInfo) {
626 627 628
            if (id == 0) {
                qCDebug(VideoManagerLog) << "Configure primary stream:" << pInfo->uri();
                switch(pInfo->type()) {
629
                    case VIDEO_STREAM_TYPE_RTSP:
630
                        if ((settingsChanged |= _updateVideoUri(id, pInfo->uri()))) {
631 632 633
                            _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceRTSP);
                        }
                        break;
634
                    case VIDEO_STREAM_TYPE_TCP_MPEG:
635
                        if ((settingsChanged |= _updateVideoUri(id, pInfo->uri()))) {
636 637
                            _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceTCP);
                        }
638 639
                        break;
                    case VIDEO_STREAM_TYPE_RTPUDP:
640
                        if ((settingsChanged |= _updateVideoUri(id, QStringLiteral("udp://0.0.0.0:%1").arg(pInfo->uri())))) {
641 642
                            _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceUDPH264);
                        }
643 644
                        break;
                    case VIDEO_STREAM_TYPE_MPEG_TS_H264:
645
                        if ((settingsChanged |= _updateVideoUri(id, QStringLiteral("mpegts://0.0.0.0:%1").arg(pInfo->uri())))) {
646 647
                            _toolbox->settingsManager()->videoSettings()->videoSource()->setRawValue(VideoSettings::videoSourceMPEGTS);
                        }
648 649
                        break;
                    default:
650
                        settingsChanged |= _updateVideoUri(id, pInfo->uri());
651 652 653
                        break;
                }
            }
654
            else if (id == 1) { //-- Thermal stream (if any)
655
                QGCVideoStreamInfo* pTinfo = _activeVehicle->cameraManager()->thermalStreamInstance();
656 657 658 659 660
                if (pTinfo) {
                    qCDebug(VideoManagerLog) << "Configure secondary stream:" << pTinfo->uri();
                    switch(pTinfo->type()) {
                        case VIDEO_STREAM_TYPE_RTSP:
                        case VIDEO_STREAM_TYPE_TCP_MPEG:
661
                            settingsChanged |= _updateVideoUri(id, pTinfo->uri());
662 663
                            break;
                        case VIDEO_STREAM_TYPE_RTPUDP:
664
                            settingsChanged |= _updateVideoUri(id, QStringLiteral("udp://0.0.0.0:%1").arg(pTinfo->uri()));
665 666
                            break;
                        case VIDEO_STREAM_TYPE_MPEG_TS_H264:
667
                            settingsChanged |= _updateVideoUri(id, QStringLiteral("mpegts://0.0.0.0:%1").arg(pTinfo->uri()));
668 669
                            break;
                        default:
670
                            settingsChanged |= _updateVideoUri(id, pTinfo->uri());
671 672 673 674
                            break;
                    }
                }
            }
675
            return settingsChanged;
676 677
        }
    }
678
    QString source = _videoSettings->videoSource()->rawValue().toString();
Gus Grubba's avatar
Gus Grubba committed
679
    if (source == VideoSettings::videoSourceUDPH264)
680
        settingsChanged |= _updateVideoUri(0, QStringLiteral("udp://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
Gus Grubba's avatar
Gus Grubba committed
681
    else if (source == VideoSettings::videoSourceUDPH265)
682
        settingsChanged |= _updateVideoUri(0, QStringLiteral("udp265://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
683
    else if (source == VideoSettings::videoSourceMPEGTS)
684
        settingsChanged |= _updateVideoUri(0, QStringLiteral("mpegts://0.0.0.0:%1").arg(_videoSettings->udpPort()->rawValue().toInt()));
685
    else if (source == VideoSettings::videoSourceRTSP)
686
        settingsChanged |= _updateVideoUri(0, _videoSettings->rtspUrl()->rawValue().toString());
687
    else if (source == VideoSettings::videoSourceTCP)
688
        settingsChanged |= _updateVideoUri(0, QStringLiteral("tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
689 690 691 692
    else if (source == VideoSettings::videoSource3DRSolo)
        settingsChanged |= _updateVideoUri(0, QStringLiteral("udp://0.0.0.0:5600"));
    else if (source == VideoSettings::videoSourceParrotDiscovery)
        settingsChanged |= _updateVideoUri(0, QStringLiteral("udp://0.0.0.0:8888"));
693

694
    return settingsChanged;
695 696
}

697
//-----------------------------------------------------------------------------
698
bool
699
VideoManager::_updateVideoUri(unsigned id, const QString& uri)
700 701 702 703
{
#if defined(QGC_GST_TAISYNC_ENABLED) && (defined(__android__) || defined(__ios__))
    //-- Taisync on iOS or Android sends a raw h.264 stream
    if (isTaisync()) {
704 705 706 707 708 709 710 711 712 713
        if (id == 0) {
            return _updateVideoUri(0, QString("tsusb://0.0.0.0:%1").arg(TAISYNC_VIDEO_UDP_PORT));
        } if (id == 1) {
            // FIXME: AV: TAISYNC_VIDEO_UDP_PORT is used by video stream, thermal stream should go via its own proxy
            if (!_videoUri[1].isEmpty()) {
                _videoUri[1].clear();
                return true;
            } else {
                return false;
            }
Andrew Voznytsa's avatar
Andrew Voznytsa committed
714
        }
715 716
    }
#endif
717
    if (uri == _videoUri[id]) {
718
        return false;
719 720
    }

721
    _videoUri[id] = uri;
722

723
    return true;
724 725
}

726
//-----------------------------------------------------------------------------
727
void
728
VideoManager::_restartVideo(unsigned id)
729
{
730 731 732 733
    if (qgcApp()->runningUnitTests()) {
        return;
    }

734
#if defined(QGC_GST_STREAMING)
735
    bool oldLowLatencyStreaming = _lowLatencyStreaming[id];
736 737
    QString oldUri = _videoUri[id];
    _updateSettings(id);
738
    bool newLowLatencyStreaming = _lowLatencyStreaming[id];
739 740
    QString newUri = _videoUri[id];

741 742
    // FIXME: AV: use _updateSettings() result to check if settings were changed
    if (oldUri == newUri && oldLowLatencyStreaming == newLowLatencyStreaming && _videoStarted[id]) {
743
        qCDebug(VideoManagerLog) << "No sense to restart video streaming, skipped"  << id;
744 745 746
        return;
    }

747
    qCDebug(VideoManagerLog) << "Restart video streaming"  << id;
748

749 750
    if (_videoStarted[id]) {
        _stopReceiver(id);
751
    } else {
752
        _startReceiver(id);
753
    }
754 755
#endif
}
756

757 758 759 760 761 762 763 764
//-----------------------------------------------------------------------------
void
VideoManager::_restartAllVideos()
{
    _restartVideo(0);
    _restartVideo(1);
}

765 766 767 768 769 770 771
//----------------------------------------------------------------------------------------
void
VideoManager::_startReceiver(unsigned id)
{
#if defined(QGC_GST_STREAMING)
    const unsigned timeout = _videoSettings->rtspTimeout()->rawValue().toUInt();

772
    if (id > 1) {
773
        qCDebug(VideoManagerLog) << "Unsupported receiver id" << id;
774
    } else if (_videoReceiver[id] != nullptr/* && _videoSink[id] != nullptr*/) {
775
        if (!_videoUri[id].isEmpty()) {
776
            _videoReceiver[id]->start(_videoUri[id], timeout, _lowLatencyStreaming[id] ? -1 : 0);
777
        }
778 779 780 781 782 783 784 785 786
    }
#endif
}

//----------------------------------------------------------------------------------------
void
VideoManager::_stopReceiver(unsigned id)
{
#if defined(QGC_GST_STREAMING)
787
    if (id > 1) {
788
        qCDebug(VideoManagerLog) << "Unsupported receiver id" << id;
789 790
    } else if (_videoReceiver[id] != nullptr) {
        _videoReceiver[id]->stop();
791 792 793 794
    }
#endif
}

795 796 797 798 799
//----------------------------------------------------------------------------------------
void
VideoManager::_setActiveVehicle(Vehicle* vehicle)
{
    if(_activeVehicle) {
800
        disconnect(_activeVehicle->vehicleLinkManager(), &VehicleLinkManager::communicationLostChanged, this, &VideoManager::_communicationLostChanged);
801 802
        if(_activeVehicle->cameraManager()) {
            QGCCameraControl* pCamera = _activeVehicle->cameraManager()->currentCameraInstance();
803 804 805
            if(pCamera) {
                pCamera->stopStream();
            }
806
            disconnect(_activeVehicle->cameraManager(), &QGCCameraManager::streamChanged, this, &VideoManager::_restartAllVideos);
807
        }
808 809 810
    }
    _activeVehicle = vehicle;
    if(_activeVehicle) {
811
        connect(_activeVehicle->vehicleLinkManager(), &VehicleLinkManager::communicationLostChanged, this, &VideoManager::_communicationLostChanged);
812 813 814
        if(_activeVehicle->cameraManager()) {
            connect(_activeVehicle->cameraManager(), &QGCCameraManager::streamChanged, this, &VideoManager::_restartAllVideos);
            QGCCameraControl* pCamera = _activeVehicle->cameraManager()->currentCameraInstance();
815 816 817
            if(pCamera) {
                pCamera->resumeStream();
            }
818
        }
819 820 821
    } else {
        //-- Disable full screen video if vehicle is gone
        setfullScreen(false);
822
    }
823
    emit autoStreamConfiguredChanged();
824
    _restartAllVideos();
825 826
}

827 828
//----------------------------------------------------------------------------------------
void
829
VideoManager::_communicationLostChanged(bool connectionLost)
830 831 832 833 834 835 836
{
    if(connectionLost) {
        //-- Disable full screen video if connection is lost
        setfullScreen(false);
    }
}

837 838
//----------------------------------------------------------------------------------------
void
839
VideoManager::_aspectRatioChanged()
840
{
841
    emit aspectRatioChanged();
842
}