Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 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,17 @@ 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 | WebSocket JPEG 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.
WebSocket JPEG requires a full `ws://` or `wss://` URL and one complete JPEG
image in each binary message.
- **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
23 changes: 22 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,21 +4,42 @@ 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 / WebSocket JPEG 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
- **WebSocket JPEG URL** — full `ws://` or `wss://` address of a JPEG message 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.

WebSocket JPEG expects exactly one complete JPEG image in each binary message.
Text messages are ignored. Messages larger than 16 MiB, images over 8,192
pixels in either dimension, and images exceeding the 64 MiB decoded-surface
budget are rejected. The receiver drops older queued images when decoding
falls behind. `wss://` certificates are verified using QGroundControl's
configured trust store.

This source supports unauthenticated endpoints only. Authorization headers are
not supported, credentials embedded in the URL are rejected, and query values
are stored as ordinary settings. Use unencrypted `ws://` only on a trusted
network.

## Settings

- **Aspect Ratio** — aspect ratio for scaling video in the display widget (default: 16:9; set to 0.0 to disable scaling)
- **Stop recording when disarmed** — automatically stop recording when the vehicle disarms
- **Low Latency Mode** — minimize video decode latency at the cost of some smoothness
- **Network Video Timeout** — time before a stalled RTSP, HTTP MJPEG, or WebSocket JPEG source is considered unavailable
- **Auto-reconnect on stream loss** — restart a timed-out or failed stream with exponential backoff
- **Force CPU video path** — disable hardware-accelerated rendering (use if video displays incorrectly)
- **Force video decoder priority** — override the automatic decoder selection: Default / Software / Hardware / NVIDIA / VA-API / DirectX 3D11 / VideoToolbox / Intel / Vulkan

Expand Down
14 changes: 13 additions & 1 deletion src/AppSettings/pages/Video.SettingsUI.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,21 @@
},
{
"heading": "Connection",
"keywords": ["rtsp", "tcp", "udp", "mpegts", "video url", "stream url"],
"keywords": ["rtsp", "http", "https", "mjpeg", "websocket", "ws", "wss", "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.webSocketJpegUrl",
"showWhen": "videoSource === QGroundControl.settingsManager.videoSettings.webSocketJpegVideoSource"
},
{
"setting": "videoSettings.tcpUrl",
"showWhen": "videoSource === QGroundControl.settingsManager.videoSettings.tcpVideoSource"
Expand Down Expand Up @@ -61,6 +69,10 @@
"setting": "videoSettings.rtpJitterLatencyMs",
"showWhen": "!autoStreamConfig && isStreamSource && rtpLatencyVisible && QGroundControl.settingsManager.videoSettings.rtpJitterLatencyMs.userVisible"
},
{
"setting": "videoSettings.rtspTimeout",
"showWhen": "!autoStreamConfig && (videoSource === QGroundControl.settingsManager.videoSettings.rtspVideoSource || videoSource === QGroundControl.settingsManager.videoSettings.httpMjpegVideoSource || videoSource === QGroundControl.settingsManager.videoSettings.webSocketJpegVideoSource)"
},
{
"setting": "videoSettings.rtspAutoReconnect",
"showWhen": "!autoStreamConfig && isStreamSource"
Expand Down
31 changes: 25 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, WebSocket JPEG, or connected USB camera).",
"longDesc": "Source for video. UDP, TCP, RTSP, HTTP multipart MJPEG, WebSocket JPEG and UVC cameras may be supported depending on the ground station build.",
"type": "string",
"default": "",
"label": "Source",
Expand All @@ -29,6 +29,24 @@
"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": "webSocketJpegUrl",
"shortDesc": "Full URL for a WebSocket JPEG stream.",
"longDesc": "Full ws:// or wss:// URL. Each binary WebSocket message must contain exactly one complete JPEG image. Text messages are ignored. URL-embedded credentials are not supported.",
"type": "string",
"default": "",
"label": "WebSocket JPEG URL",
"keywords": "websocket,ws,wss,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 +135,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,websocket,ws,wss,network,video timeout"
},
{
"name": "streamEnabled",
Expand Down Expand Up @@ -170,7 +189,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
33 changes: 33 additions & 0 deletions src/Settings/VideoSettings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ 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(videoSourceWebSocketJPEG);
}
videoSourceList.append(videoSourceUDPH264);
videoSourceList.append(videoSourceUDPH265);
videoSourceList.append(videoSourceTCP);
Expand Down Expand Up @@ -220,6 +224,24 @@ 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, webSocketJpegUrl)
{
if (!_webSocketJpegUrlFact) {
_webSocketJpegUrlFact = _createSettingsFact(webSocketJpegUrlName);
connect(_webSocketJpegUrlFact, &Fact::valueChanged, this, &VideoSettings::_configChanged);
}
return _webSocketJpegUrlFact;
}

DECLARE_SETTINGSFACT_NO_FUNC(VideoSettings, tcpUrl)
{
if (!_tcpUrlFact) {
Expand Down Expand Up @@ -251,6 +273,17 @@ bool VideoSettings::streamConfigured(void)
qCDebug(VideoSettingsLog) << "Testing configuration for RTSP Stream:" << rtspUrl()->rawValue().toString();
return !rtspUrl()->rawValue().toString().isEmpty();
}
//-- If HTTP MJPEG, check for URL
if (vSource == videoSourceHTTPMJPEG) {
qCDebug(VideoSettingsLog) << "Testing configuration for HTTP MJPEG Stream";
return !httpMjpegUrl()->rawValue().toString().isEmpty();
}
//-- If WebSocket JPEG, check for URL
if (vSource == videoSourceWebSocketJPEG) {
qCDebug(VideoSettingsLog) << "Testing configuration for WebSocket JPEG Stream:"
<< QGCNetworkHelper::redactedUrlForLogging(webSocketJpegUrl()->rawValue().toString());
return !webSocketJpegUrl()->rawValue().toString().isEmpty();
}
//-- If TCP, check for URL
if(vSource == videoSourceTCP) {
qCDebug(VideoSettingsLog) << "Testing configuration for TCP Stream:" << tcpUrl()->rawValue().toString();
Expand Down
10 changes: 10 additions & 0 deletions src/Settings/VideoSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class VideoSettings : public SettingsGroup
DEFINE_SETTINGFACT(udpUrl)
DEFINE_SETTINGFACT(tcpUrl)
DEFINE_SETTINGFACT(rtspUrl)
DEFINE_SETTINGFACT(httpMjpegUrl)
DEFINE_SETTINGFACT(webSocketJpegUrl)
DEFINE_SETTINGFACT(aspectRatio)
DEFINE_SETTINGFACT(videoFit)
DEFINE_SETTINGFACT(gridLines)
Expand All @@ -37,6 +39,8 @@ 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 webSocketJpegVideoSource READ webSocketJpegVideoSource 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 +49,9 @@ class VideoSettings : public SettingsGroup

bool streamConfigured ();
QString rtspVideoSource () { return videoSourceRTSP; }
QString httpMjpegVideoSource() { return videoSourceHTTPMJPEG; }

QString webSocketJpegVideoSource() { return videoSourceWebSocketJPEG; }
QString udp264VideoSource () { return videoSourceUDPH264; }
QString udp265VideoSource () { return videoSourceUDPH265; }
QString tcpVideoSource () { return videoSourceTCP; }
Expand All @@ -59,6 +66,9 @@ 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* videoSourceWebSocketJPEG =
QT_TRANSLATE_NOOP("VideoSettings", "WebSocket JPEG 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
15 changes: 14 additions & 1 deletion src/VideoManager/VideoManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ void VideoManager::init(QQuickWindow *mainWindow)
(void) connect(_videoSettings->videoSource(), &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->udpUrl(), &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->rtspUrl(), &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->httpMjpegUrl(), &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->webSocketJpegUrl(), &Fact::rawValueChanged, this,
&VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->tcpUrl(), &Fact::rawValueChanged, this, &VideoManager::_videoSourceChanged);
(void) connect(_videoSettings->aspectRatio(), &Fact::rawValueChanged, this, &VideoManager::aspectRatioChanged);
(void) connect(_videoSettings->lowLatencyMode(), &Fact::rawValueChanged, this, [this](const QVariant &value) { Q_UNUSED(value); _restartAllVideos(); });
Expand Down Expand Up @@ -500,6 +503,8 @@ bool VideoManager::isStreamSource() const
VideoSettings::videoSourceUDPH264,
VideoSettings::videoSourceUDPH265,
VideoSettings::videoSourceRTSP,
VideoSettings::videoSourceHTTPMJPEG,
VideoSettings::videoSourceWebSocketJPEG,
VideoSettings::videoSourceTCP,
VideoSettings::videoSourceMPEGTS,
VideoSettings::videoSource3DRSolo,
Expand Down Expand Up @@ -701,6 +706,10 @@ bool VideoManager::_updateSettings(VideoReceiver *receiver)
settingsChanged |= _updateVideoUri(receiver, QStringLiteral("mpegts://%1").arg(_videoSettings->udpUrl()->rawValue().toString()));
} else if (source == VideoSettings::videoSourceRTSP) {
settingsChanged |= _updateVideoUri(receiver, _videoSettings->rtspUrl()->rawValue().toString());
} else if (source == VideoSettings::videoSourceHTTPMJPEG) {
settingsChanged |= _updateVideoUri(receiver, _videoSettings->httpMjpegUrl()->rawValue().toString());
} else if (source == VideoSettings::videoSourceWebSocketJPEG) {
settingsChanged |= _updateVideoUri(receiver, _videoSettings->webSocketJpegUrl()->rawValue().toString());
} else if (source == VideoSettings::videoSourceTCP) {
settingsChanged |= _updateVideoUri(receiver, QStringLiteral("tcp://%1").arg(_videoSettings->tcpUrl()->rawValue().toString()));
} else if (source == VideoSettings::videoSource3DRSolo) {
Expand Down Expand Up @@ -842,7 +851,11 @@ void VideoManager::_startReceiver(VideoReceiver *receiver)
}

const QString source = _videoSettings->videoSource()->rawValue().toString();
const uint32_t timeout = ((source == VideoSettings::videoSourceRTSP) ? _videoSettings->rtspTimeout()->rawValue().toUInt() : 3);
const bool usesNetworkTimeout = (source == VideoSettings::videoSourceRTSP) ||
(source == VideoSettings::videoSourceHTTPMJPEG) ||
(source == VideoSettings::videoSourceWebSocketJPEG);
// Keep the existing Fact/persistence key for compatibility while its UI meaning expands to timeout-based sources.
const uint32_t timeout = usesNetworkTimeout ? _videoSettings->rtspTimeout()->rawValue().toUInt() : 3;

receiver->start(timeout);
}
Expand Down
10 changes: 10 additions & 0 deletions src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ if(NOT QGC_ENABLE_GST_VIDEOSTREAMING)
return()
endif()

find_package(Qt6
${QGC_QT_MINIMUM_VERSION}...${QGC_QT_MAXIMUM_VERSION}
REQUIRED
COMPONENTS WebSockets
)

# Linux adds Allocators (gstreamer-allocators-1.0) for the DMABuf header probe in Orchestrator.cmake.
# RtspServer (gstreamer-rtsp-server-1.0) drives the MockLink test video stream server, which is
# Debug-only (see Comms/MockLink/CMakeLists.txt) — so only request it in Debug to keep the
Expand Down Expand Up @@ -107,8 +113,12 @@ target_sources(${CMAKE_PROJECT_NAME}
GstVideoReceiver.h
QGCQVideoSinkController.cc
QGCQVideoSinkController.h
QGCWebSocketVideoSource.cc
QGCWebSocketVideoSource.h
)

target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6::WebSockets)

# iOS xcframework ships no CA list / no keychain-via-OpenSSL; bundle a Mozilla NSS extract for
# libgioopenssl (fetched at configure time, IOS.cmake). On the app target for MACOSX_PACKAGE_LOCATION.
if(IOS AND GStreamer_IOS_CA_BUNDLE)
Expand Down
Loading
Loading