Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2bc5021
fix(VideoManager): redact stream URLs in diagnostics
alireza787b Jul 28, 2026
934e8bb
test(Utilities): cover redacted URL edge cases
alireza787b Jul 28, 2026
9a94cd8
test(Utilities): run URL redaction checks in CI
alireza787b Jul 28, 2026
5444d65
style(Utilities): keep redaction diff focused
alireza787b Jul 28, 2026
6f7cabd
test(Utilities): register URL redaction test
alireza787b Jul 28, 2026
8d1d634
fix(VideoManager): preserve useful stream diagnostics
alireza787b Jul 31, 2026
8de9465
test(UnitTest): handle early local HTTP requests
alireza787b Jul 28, 2026
e766d7b
test(UnitTest): wait for complete HTTP requests
alireza787b Jul 28, 2026
4a6ce65
test(UnitTest): address local HTTP fixture review
alireza787b Jul 31, 2026
82b3a80
feat(VideoManager): add HTTP MJPEG video source
alireza787b Jul 28, 2026
7b750ab
fix(VideoManager): align HTTP video TLS and tests
alireza787b Jul 28, 2026
9f587bf
test(VideoManager): stabilize MJPEG delivery coverage
alireza787b Jul 28, 2026
65c4627
fix(VideoManager): support older GLib connect flags
alireza787b Jul 28, 2026
2a94373
test(VideoManager): keep MJPEG fixture responsive
alireza787b Jul 28, 2026
34bcefb
test(VideoManager): serve MJPEG delivery synchronously
alireza787b Jul 28, 2026
2cc2389
style(VideoManager): format source factory changes
alireza787b Jul 31, 2026
3a103dd
test(Utilities): run redaction coverage in CI
alireza787b Jul 31, 2026
9739a6b
style(Utilities): follow configured include order
alireza787b Jul 31, 2026
af1201e
test(VideoManager): expect invalid URI warnings
alireza787b Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/build-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@
"app",
"coreelements",
"isomp4",
"jpeg",
"jpegformat",
"libav",
"matroska",
"mpegtsdemux",
"multipart",
"multifile",
"opengl",
"openh264",
Expand All @@ -49,6 +52,7 @@
"rtpmanager",
"rtsp",
"sdpelem",
"soup",
"tcp",
"typefindfunctions",
"udp",
Expand Down
4 changes: 4 additions & 0 deletions cmake/GStreamer/tests/test_plugin_policy.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ file(READ "${CMAKE_CURRENT_LIST_DIR}/../../../.github/build-config.json" QGC_BUI
gstreamer_plugins_for(PLATFORM windows OUT_VAR _plugins_windows_real)
qgc_test_assert_in_list("windows has d3d11" d3d11 _plugins_windows_real)
qgc_test_assert_in_list("windows has d3d12" d3d12 _plugins_windows_real)
qgc_test_assert_in_list("common has native JPEG decoder" jpeg _plugins_windows_real)
qgc_test_assert_in_list("common has HTTP MJPEG jpeg parser" jpegformat _plugins_windows_real)
qgc_test_assert_in_list("common has HTTP MJPEG multipart demuxer" multipart _plugins_windows_real)
qgc_test_assert_in_list("common has HTTP client source" soup _plugins_windows_real)
qgc_test_pass("plugins_for real windows d3d addenda")

set(_req videoconvertscale videoconvert videoscale x264enc)
Expand Down
4 changes: 3 additions & 1 deletion docs/en/qgc-user-guide/settings_view/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,15 @@ The _Video_ section is used to define the source and connection settings for vid

The settings are:

- **Video Source**: Video Stream Disabled | RTSP Video Stream | UDP h.264 Video Stream | UDP h.265 Video Stream | TCP-MPEG2 Video Stream | MPEG-TS Video Stream | Integrated Camera
- **Video Source**: Video Stream Disabled | RTSP Video Stream | HTTP MJPEG Video Stream | UDP h.264 Video Stream | UDP h.265 Video Stream | TCP-MPEG2 Video Stream | MPEG-TS Video Stream | Integrated Camera

::: info
If no video source is specified then no other video or _video recording_ settings will be displayed.
:::

- **URL/Port**: Connection type-specific stream address (may be port or URL).
HTTP MJPEG requires a full `http://` or `https://` URL serving
`multipart/x-mixed-replace` JPEG frames.
- **Aspect Ratio**: Aspect ratio for scaling video in video widget (set to 0.0 to ignore scaling)
- **Disabled When Disarmed**: Disable video feed when vehicle is disarmed.
- **Low Latency Mode**: Enabling low latency mode reduces the video stream latency, but may cause frame loss and choppy video (especially with a poor network connection). <!-- disables the internal jitter buffer -->
Expand Down
8 changes: 7 additions & 1 deletion docs/en/qgc-user-guide/settings_view/video.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ Configure video streaming and recording settings.

## Video Source

- **Source** — Video Stream Disabled / RTSP Video Stream / UDP h.264 / UDP h.265 / TCP-MPEG2 / MPEG-TS / Integrated Camera
- **Source** — Video Stream Disabled / RTSP Video Stream / HTTP MJPEG Video Stream / UDP h.264 / UDP h.265 / TCP-MPEG2 / MPEG-TS / Integrated Camera

## Connection

Connection settings vary by source type:

- **RTSP URL** — full RTSP stream address
- **HTTP MJPEG URL** — full `http://` or `https://` address of a multipart MJPEG stream
- **TCP URL** — TCP stream address
- **UDP URL** — UDP stream address and port (default: `0.0.0.0:5600`)

HTTP MJPEG expects a `multipart/x-mixed-replace` response containing JPEG frames.
It does not accept a web page, a single JPEG URL, or an arbitrary HTTP video file.
HTTPS certificate validation remains enabled in GStreamer, redirects are not followed, and URL user information
(`user:password@host`) is rejected.

## Settings

- **Aspect Ratio** — aspect ratio for scaling video in the display widget (default: 16:9; set to 0.0 to disable scaling)
Expand Down
6 changes: 5 additions & 1 deletion src/AppSettings/pages/Video.SettingsUI.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@
},
{
"heading": "Connection",
"keywords": ["rtsp", "tcp", "udp", "mpegts", "video url", "stream url"],
"keywords": ["rtsp", "http", "https", "mjpeg", "jpeg", "tcp", "udp", "mpegts", "video url", "stream url"],
"showWhen": "!sourceDisabled && !autoStreamConfig",
"controls": [
{
"setting": "videoSettings.rtspUrl",
"showWhen": "videoSource === QGroundControl.settingsManager.videoSettings.rtspVideoSource"
},
{
"setting": "videoSettings.httpMjpegUrl",
"showWhen": "videoSource === QGroundControl.settingsManager.videoSettings.httpMjpegVideoSource"
},
{
"setting": "videoSettings.tcpUrl",
"showWhen": "videoSource === QGroundControl.settingsManager.videoSettings.tcpVideoSource"
Expand Down
6 changes: 4 additions & 2 deletions src/Camera/VehicleCameraControl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1817,7 +1817,8 @@ void VehicleCameraControl::setCurrentStream(int stream)
if (stream != _currentStream && stream >= 0 && stream < _streamLabels.count()) {
QGCVideoStreamInfo* pInfo = currentStreamInstance();
if(pInfo) {
qCDebug(VehicleCameraControlLog) << "Stopping stream:" << pInfo->uri();
qCDebug(VehicleCameraControlLog)
<< "Stopping stream:" << QGCNetworkHelper::redactedUrlForLogging(pInfo->uri());
//-- Stop current stream
_vehicle->sendMavCommand(
_compID, // Target component
Expand All @@ -1829,7 +1830,8 @@ void VehicleCameraControl::setCurrentStream(int stream)
pInfo = currentStreamInstance();
if(pInfo) {
//-- Start new stream
qCDebug(VehicleCameraControlLog) << "Starting stream:" << pInfo->uri();
qCDebug(VehicleCameraControlLog)
<< "Starting stream:" << QGCNetworkHelper::redactedUrlForLogging(pInfo->uri());
_vehicle->sendMavCommand(
_compID, // Target component
MAV_CMD_VIDEO_START_STREAMING, // Command id
Expand Down
22 changes: 16 additions & 6 deletions src/Settings/Video.SettingsGroup.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"QGC.MetaData.Facts": [
{
"name": "videoSource",
"shortDesc": "Source for video stream (UDP, TCP, RTSP, or connected USB camera).",
"longDesc": "Source for video. UDP, TCP, RTSP and UVC Cameras may be supported depending on Vehicle and ground station version.",
"shortDesc": "Source for video stream (UDP, TCP, RTSP, HTTP MJPEG, or connected USB camera).",
"longDesc": "Source for video. UDP, TCP, RTSP, HTTP multipart MJPEG and UVC cameras may be supported depending on the ground station build.",
"type": "string",
"default": "",
"label": "Source",
Expand All @@ -29,6 +29,15 @@
"label": "RTSP URL",
"keywords": "rtsp,video url,stream url"
},
{
"name": "httpMjpegUrl",
"shortDesc": "Full URL for an HTTP multipart MJPEG stream.",
"longDesc": "Full http:// or https:// URL for a multipart MJPEG stream (multipart/x-mixed-replace with JPEG frames). User information in the URL is not supported.",
"type": "string",
"default": "",
"label": "HTTP MJPEG URL",
"keywords": "http,https,mjpeg,jpeg,video url,stream url"
},
{
"name": "tcpUrl",
"shortDesc": "Network address and port for TCP video stream (e.g. 192.168.143.200:3001).",
Expand Down Expand Up @@ -117,13 +126,14 @@
},
{
"name": "rtspTimeout",
"shortDesc": "RTSP Video Timeout",
"longDesc": "How long to wait before assuming RTSP link is gone.",
"shortDesc": "Network video timeout.",
"longDesc": "How long to wait before assuming a timeout-based network video source is unavailable.",
"type": "uint32",
"min": 1,
"units": "s",
"default": 8,
"label": "RTSP Video Timeout"
"label": "Network Video Timeout",
"keywords": "rtsp,http,mjpeg,network,video timeout"
},
{
"name": "streamEnabled",
Expand Down Expand Up @@ -170,7 +180,7 @@
"type": "bool",
"default": true,
"label": "Auto-reconnect on stream loss",
"keywords": "rtsp,reconnect,watchdog,recovery,advanced"
"keywords": "rtsp,http,mjpeg,stream,reconnect,watchdog,recovery,advanced"
},
{
"name": "forceVideoDecoder",
Expand Down
42 changes: 34 additions & 8 deletions src/Settings/VideoSettings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "VideoManager.h"

#include "QGCLoggingCategory.h"
#include "QGCNetworkHelper.h"
#include <QtCore/QSettings>
#include <QtCore/QVariantList>

Expand All @@ -20,6 +21,9 @@ DECLARE_SETTINGGROUP(Video, "Video")
// Setup enum values for videoSource settings into meta data
QVariantList videoSourceList;
videoSourceList.append(videoSourceRTSP);
if (kGstEnabled) {
videoSourceList.append(videoSourceHTTPMJPEG);
}
videoSourceList.append(videoSourceUDPH264);
videoSourceList.append(videoSourceUDPH265);
videoSourceList.append(videoSourceTCP);
Expand Down Expand Up @@ -220,6 +224,15 @@ DECLARE_SETTINGSFACT_NO_FUNC(VideoSettings, rtspUrl)
return _rtspUrlFact;
}

DECLARE_SETTINGSFACT_NO_FUNC(VideoSettings, httpMjpegUrl)
{
if (!_httpMjpegUrlFact) {
_httpMjpegUrlFact = _createSettingsFact(httpMjpegUrlName);
connect(_httpMjpegUrlFact, &Fact::valueChanged, this, &VideoSettings::_configChanged);
}
return _httpMjpegUrlFact;
}

DECLARE_SETTINGSFACT_NO_FUNC(VideoSettings, tcpUrl)
{
if (!_tcpUrlFact) {
Expand All @@ -243,23 +256,36 @@ bool VideoSettings::streamConfigured(void)
}
//-- If UDP, check for URL
if(vSource == videoSourceUDPH264 || vSource == videoSourceUDPH265) {
qCDebug(VideoSettingsLog) << "Testing configuration for UDP Stream:" << udpUrl()->rawValue().toString();
return !udpUrl()->rawValue().toString().isEmpty();
const QString url = udpUrl()->rawValue().toString();
qCDebug(VideoSettingsLog) << "Testing configuration for UDP Stream:"
<< QGCNetworkHelper::redactedUrlForLogging(url);
return !url.isEmpty();
}
//-- If RTSP, check for URL
if(vSource == videoSourceRTSP) {
qCDebug(VideoSettingsLog) << "Testing configuration for RTSP Stream:" << rtspUrl()->rawValue().toString();
return !rtspUrl()->rawValue().toString().isEmpty();
const QString url = rtspUrl()->rawValue().toString();
qCDebug(VideoSettingsLog) << "Testing configuration for RTSP Stream:"
<< QGCNetworkHelper::redactedUrlForLogging(url);
return !url.isEmpty();
}
//-- If HTTP MJPEG, check for URL
if (vSource == videoSourceHTTPMJPEG) {
qCDebug(VideoSettingsLog) << "Testing configuration for HTTP MJPEG Stream";
return !httpMjpegUrl()->rawValue().toString().isEmpty();
}
//-- If TCP, check for URL
if(vSource == videoSourceTCP) {
qCDebug(VideoSettingsLog) << "Testing configuration for TCP Stream:" << tcpUrl()->rawValue().toString();
return !tcpUrl()->rawValue().toString().isEmpty();
const QString url = tcpUrl()->rawValue().toString();
qCDebug(VideoSettingsLog) << "Testing configuration for TCP Stream:"
<< QGCNetworkHelper::redactedUrlForLogging(url);
return !url.isEmpty();
}
//-- If MPEG-TS, check for URL
if(vSource == videoSourceMPEGTS) {
qCDebug(VideoSettingsLog) << "Testing configuration for MPEG-TS Stream:" << udpUrl()->rawValue().toString();
return !udpUrl()->rawValue().toString().isEmpty();
const QString url = udpUrl()->rawValue().toString();
qCDebug(VideoSettingsLog) << "Testing configuration for MPEG-TS Stream:"
<< QGCNetworkHelper::redactedUrlForLogging(url);
return !url.isEmpty();
}
//-- If Herelink Air unit, good to go
if(vSource == videoSourceHerelinkAirUnit) {
Expand Down
4 changes: 4 additions & 0 deletions src/Settings/VideoSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class VideoSettings : public SettingsGroup
DEFINE_SETTINGFACT(udpUrl)
DEFINE_SETTINGFACT(tcpUrl)
DEFINE_SETTINGFACT(rtspUrl)
DEFINE_SETTINGFACT(httpMjpegUrl)
DEFINE_SETTINGFACT(aspectRatio)
DEFINE_SETTINGFACT(videoFit)
DEFINE_SETTINGFACT(gridLines)
Expand All @@ -37,6 +38,7 @@ class VideoSettings : public SettingsGroup

Q_PROPERTY(bool streamConfigured READ streamConfigured NOTIFY streamConfiguredChanged)
Q_PROPERTY(QString rtspVideoSource READ rtspVideoSource CONSTANT)
Q_PROPERTY(QString httpMjpegVideoSource READ httpMjpegVideoSource CONSTANT)
Q_PROPERTY(QString udp264VideoSource READ udp264VideoSource CONSTANT)
Q_PROPERTY(QString udp265VideoSource READ udp265VideoSource CONSTANT)
Q_PROPERTY(QString tcpVideoSource READ tcpVideoSource CONSTANT)
Expand All @@ -45,6 +47,7 @@ class VideoSettings : public SettingsGroup

bool streamConfigured ();
QString rtspVideoSource () { return videoSourceRTSP; }
QString httpMjpegVideoSource () { return videoSourceHTTPMJPEG; }
QString udp264VideoSource () { return videoSourceUDPH264; }
QString udp265VideoSource () { return videoSourceUDPH265; }
QString tcpVideoSource () { return videoSourceTCP; }
Expand All @@ -59,6 +62,7 @@ class VideoSettings : public SettingsGroup
static constexpr const char* videoSourceNoVideo = QT_TRANSLATE_NOOP("VideoSettings", "No Video Available");
static constexpr const char* videoDisabled = QT_TRANSLATE_NOOP("VideoSettings", "Video Stream Disabled");
static constexpr const char* videoSourceRTSP = QT_TRANSLATE_NOOP("VideoSettings", "RTSP Video Stream");
static constexpr const char* videoSourceHTTPMJPEG = QT_TRANSLATE_NOOP("VideoSettings", "HTTP MJPEG Video Stream");
static constexpr const char* videoSourceUDPH264 = QT_TRANSLATE_NOOP("VideoSettings", "UDP h.264 Video Stream");
static constexpr const char* videoSourceUDPH265 = QT_TRANSLATE_NOOP("VideoSettings", "UDP h.265 Video Stream");
static constexpr const char* videoSourceTCP = QT_TRANSLATE_NOOP("VideoSettings", "TCP-MPEG2 Video Stream");
Expand Down
34 changes: 34 additions & 0 deletions src/Utilities/Network/QGCNetworkHelper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,40 @@ QUrl urlWithoutQuery(const QUrl& url)
return url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
}

QString redactedUrlForLogging(const QUrl& url)
{
if (url.isEmpty()) {
return QStringLiteral("<empty-url>");
}
if (!url.isValid()) {
return QStringLiteral("<invalid-url>");
}

QUrl redactedUrl = url.adjusted(QUrl::RemoveUserInfo);
if (redactedUrl.hasQuery()) {
const auto queryItems = QUrlQuery(redactedUrl).queryItems(QUrl::FullyDecoded);
QUrlQuery redactedQuery;
for (const auto& queryItem : queryItems) {
redactedQuery.addQueryItem(queryItem.first, QStringLiteral("REDACTED"));
}
if (queryItems.isEmpty()) {
redactedUrl.setQuery(QStringLiteral("REDACTED"));
} else {
redactedUrl.setQuery(redactedQuery);
}
}
if (redactedUrl.hasFragment()) {
redactedUrl.setFragment(QStringLiteral("REDACTED"));
}

return redactedUrl.toDisplayString(QUrl::FullyEncoded);
}

QString redactedUrlForLogging(const QString& url)
{
return redactedUrlForLogging(QUrl(url));
}

// ============================================================================
// Request Configuration
// ============================================================================
Expand Down
5 changes: 5 additions & 0 deletions src/Utilities/Network/QGCNetworkHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ QUrl buildUrl(const QString& baseUrl, const QList<QPair<QString, QString>>& para
/// Get URL without query string and fragment
QUrl urlWithoutQuery(const QUrl& url);

/// Return a URL suitable for diagnostics. Stream identity is preserved while user info,
/// query values, and fragment content are redacted.
QString redactedUrlForLogging(const QUrl& url);
QString redactedUrlForLogging(const QString& url);

// ============================================================================
// Request Configuration
// ============================================================================
Expand Down
Loading
Loading