Skip to content

feat(VideoManager): add HTTP MJPEG and WebSocket JPEG sources - #13594

Closed
alireza787b wants to merge 9 commits into
mavlink:masterfrom
alireza787b:feature/http-websocket-video-streaming
Closed

feat(VideoManager): add HTTP MJPEG and WebSocket JPEG sources#13594
alireza787b wants to merge 9 commits into
mavlink:masterfrom
alireza787b:feature/http-websocket-video-streaming

Conversation

@alireza787b

@alireza787b alireza787b commented Oct 25, 2025

Copy link
Copy Markdown
Contributor

Description

Adds two generic network JPEG video sources to QGroundControl:

  • Multipart MJPEG over HTTP or HTTPS using GStreamer.
  • One complete JPEG frame per binary WebSocket message over WS or WSS using Qt WebSockets and GStreamer appsrc.

The existing video settings surface now exposes source-specific URLs plus optional None, Basic, or Bearer authentication, an in-memory session credential, an owner-only credential file on supported Unix desktop systems, an exact Origin header, and a custom CA certificate. Credentials are accepted only over HTTPS or WSS. URL user-info and common secret query parameters are rejected, diagnostics are redacted, authenticated HTTP redirects are disabled, and WebSocket redirects are not followed.

The receiver validates JPEG framing and dimensions before decode, applies bounded frame and decoded-image limits, and keeps source failures on the existing QGC reconnect path. HTTP and WebSocket JPEG streams can be recorded as MKV or MOV. MP4 is rejected for these sources because the parsed JPEG stream is not compatible with that container. Source disconnect while recording waits for splitmuxsink fragment finalization before teardown.

The change also includes user documentation and camera-free synthetic HTTP/WebSocket test servers under test/VideoStreaming/. It is generic and does not contain source-product-specific behavior. WebRTC and codec negotiation are out of scope.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/Build changes
  • Other

Testing

  • Tested locally
  • Added/updated unit tests
  • Tested with simulator (SITL)
  • Tested with hardware

Exact pre-promotion candidate: ab5213f4f69c5b07494f101226db384b48af1e4f.

  • Linux CI: 183 unit tests and 49 integration tests passed, including the GStreamer HTTP/WebSocket/TLS/JPEG/recording suite and source-EOS recording finalization.
  • Windows CI: AMD64, ARM64, and cross-compiled ARM64 builds passed. The AMD64 installer was created, installed, and its installed executable verified with the build SDK removed from PATH.
  • Pre-commit, CodeQL, documentation, Doxygen, link, CI-script, custom-build, and iOS checks passed on the same candidate.
  • Camera-free manual fixtures:
    cd test/VideoStreaming
    python3 -m venv .venv
    . .venv/bin/activate
    pip install -r requirements.txt
    python http_mjpeg_server.py --host 127.0.0.1 --port 5077
    python websocket_jpeg_server.py --host 127.0.0.1 --port 5078

This PR remains draft pending manual Windows playback, reconnect, source-switch, and MKV/MOV recording evidence from the exact installer.

Platforms Tested

  • Linux
  • Windows
  • macOS
  • Android
  • iOS

Windows build/install/launch is automated above; the network-video receiver workflow is still awaiting manual Windows validation. Other platform build checks are tracked in CI and are not presented as runtime tests.

Flight Stacks Tested

Not applicable; this change does not exercise vehicle control.

  • PX4
  • ArduPilot

Screenshots

Not applicable. The controls use QGroundControl's existing settings components.

Checklist

  • I have read the Contribution Guidelines
  • I have read the Code of Conduct
  • My code follows the project's coding standards
  • I have added tests that prove my fix/feature works
  • New and existing unit tests pass locally

The complete unit and integration suites passed in the exact-head Linux CI run linked above; no local full QGC build is claimed.

Related Issues

No linked issue. This update also addresses the maintainer request in this PR for a reproducible test path that does not require a camera.


By submitting this pull request, I confirm that my contribution is made under the terms of the project's dual license (Apache 2.0 and GPL v3).

@HTRamsey

Copy link
Copy Markdown
Member

Pretty cool, although I think you're missing some more places where the WebSockets lib needs to be added. One example being the Vagrantfile

@alireza787b

Copy link
Copy Markdown
Contributor Author

Pretty cool, although I think you're missing some more places where the WebSockets lib needs to be added. One example being the Vagrantfile

Thanks for catching that! You're absolutely right - I've now added qtwebsockets to all the additional Qt installation locations:

  • Vagrantfile (deploy/vagrant/)
  • install-qt-debian.sh
  • install-qt-windows.ps1
  • install-qt-macos.sh

This ensures consistency across all development environments (CI workflows, Vagrant, and manual setups). The changes have been pushed -
let me know if I've missed anywhere else!

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

How can this be tested without needing to buy some sort of camera that supports this. With other gstreamer based feed we can simulate streams using gstreamer to validate things work.

@alireza787b

alireza787b commented Oct 25, 2025

Copy link
Copy Markdown
Contributor Author

How can this be tested without needing to buy some sort of camera that supports this. With other gstreamer based feed we can simulate streams using gstreamer to validate things work.

@DonLakeFlyer, I've included synthetic test servers that follow the ADSB simulator pattern and don't require a camera or video files to run.

How to Test Without a Camera

  1. Navigate to the Test Directory:

    cd test/VideoStreaming
  2. Install Dependencies:

    pip install -r requirements.txt
  3. Run the Test Server:

    Protocol Command Endpoint Notes
    HTTP MJPEG python http_mjpeg_server.py http://127.0.0.1:5077/video_feed Generates a synthetic test pattern.
    WebSocket python websocket_video_server.py ws://127.0.0.1:5077/ws/video_feed Implements the complete QGC/PixEagle WebSocket protocol (metadata, binary data, ping/pong, quality adjustment).

The test servers will generate synthetic test patterns for validation. The project's README also contains GStreamer CLI alternatives if you prefer command-line tools for simulation.


Real-World Testing

For more comprehensive, real-world testing, you can use PixEagle, which works well with webcams, video files, or the simulator sources mentioned above:

➡️ PixEagle Repository

@HTRamsey

Copy link
Copy Markdown
Member

@alireza787b Interesting feature, we'll get back to it when one of us has more time to test it out

@github-actions

github-actions Bot commented Feb 14, 2026

Copy link
Copy Markdown
Contributor

⚠️ Build results unavailable — artifact download from one or more platform workflows failed (likely artifact retention expiry or transient API error). The combined report cannot be generated for this run.

See the Build Results workflow run for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new network-based video sources to QGroundControl’s video pipeline (HTTP/HTTPS MJPEG and WebSocket-fed JPEG via GStreamer appsrc), along with new Video settings and test servers to validate the streams.

Changes:

  • Introduces HTTP MJPEG (souphttpsrc → multipartdemux → jpegparse) and WebSocket (appsrc → jpegdec) source creation in the GStreamer receiver.
  • Adds new Video settings/Facts and updates the Video settings UI to configure HTTP/WebSocket URLs and some connection parameters.
  • Adds Python HTTP MJPEG and WebSocket test servers plus a small README/requirements set.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test/VideoStreaming/http_mjpeg_server.py Adds a local Flask-based MJPEG test server for manual validation.
test/VideoStreaming/websocket_video_server.py Adds a local WebSocket JPEG-frame test server for manual validation.
test/VideoStreaming/requirements.txt Adds Python dependencies for the test servers.
test/VideoStreaming/README.md Documents how to run the test servers and expected protocol.
src/VideoManager/VideoReceiver/GStreamer/QGCWebSocketVideoSource.h Declares a Qt WebSocket-to-GStreamer appsrc bridge.
src/VideoManager/VideoReceiver/GStreamer/QGCWebSocketVideoSource.cc Implements WebSocket connection/reconnect/heartbeat and pushing frames to appsrc.
src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.h Adds HTTP/WebSocket source helpers and stream settings structs.
src/VideoManager/VideoReceiver/GStreamer/GstVideoReceiver.cc Routes http(s)/ws(s) URIs to new source builders and captures settings.
src/VideoManager/VideoReceiver/GStreamer/CMakeLists.txt Adds optional gstapp + Qt6 WebSockets build wiring for WebSocket support.
src/VideoManager/VideoManager.cc Wires new video sources into URI selection and timeout behavior.
src/UI/AppSettings/VideoSettings.qml Adds URL inputs and basic HTTP/WebSocket settings groups to the UI.
src/Settings/VideoSettings.h Adds new setting Facts and new source string constants.
src/Settings/VideoSettings.cc Registers new sources and setting Facts; adds streamConfigured logic for new URLs.
src/Settings/Video.SettingsGroup.json Adds new HTTP/WebSocket settings metadata entries.

Comment on lines +32 to +44
signals:
void connected();
void disconnected();
void errorOccurred(const QString &error);

private slots:
void _onConnected();
void _onDisconnected();
void _onBinaryMessageReceived(const QByteArray &message);
void _onTextMessageReceived(const QString &message);
void _onError();
void _onSslErrors(const QList<QSslError> &errors);
void _sendHeartbeat();

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QGCWebSocketVideoSource.h uses QSslError/QString/QByteArray/QList in signals/slots but doesn’t include the required headers (e.g., <QtNetwork/QSslError>, <QtCore/QString>, <QtCore/QByteArray>, <QtCore/QList>). This can break compilation depending on include order; add the proper includes (or a complete-type include for QSslError).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added the required includes (QSslError, QString, QByteArray, QList) and sorted them alphabetically per QGC convention.

Comment on lines +231 to +233
GST_BUFFER_PTS(buffer) = gst_util_uint64_scale(_framesReceived, GST_SECOND, 30);
GST_BUFFER_DTS(buffer) = GST_BUFFER_PTS(buffer);
GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale(1, GST_SECOND, 30);

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appsrc is configured with do-timestamp=TRUE, but _pushFrameToAppsrc sets PTS/DTS/duration using a hard-coded 30 FPS. This will produce incorrect timestamps whenever the server FPS differs and can conflict with appsrc’s timestamping. Prefer leaving timestamps unset (GST_CLOCK_TIME_NONE) and letting appsrc timestamp, or compute timestamps from actual arrival time / negotiated FPS.

Suggested change
GST_BUFFER_PTS(buffer) = gst_util_uint64_scale(_framesReceived, GST_SECOND, 30);
GST_BUFFER_DTS(buffer) = GST_BUFFER_PTS(buffer);
GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale(1, GST_SECOND, 30);
// Let appsrc timestamp the buffer (do-timestamp=TRUE) instead of forcing a hard-coded 30 FPS.
GST_BUFFER_PTS(buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_DTS(buffer) = GST_CLOCK_TIME_NONE;
GST_BUFFER_DURATION(buffer) = GST_CLOCK_TIME_NONE;

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Switched to GST_CLOCK_TIME_NONE for PTS/DTS/DURATION and relying on do-timestamp=TRUE to timestamp from the pipeline clock. This is the correct pattern for a live source with variable frame rate.

Comment on lines +127 to +145
void QGCWebSocketVideoSource::_onBinaryMessageReceived(const QByteArray &message)
{
if (message.isEmpty()) {
return;
}

_pushFrameToAppsrc(message);
_expectingBinaryFrame = false;
}

void QGCWebSocketVideoSource::_onTextMessageReceived(const QString &message)
{
// Text messages are JSON metadata from PixEagle-compatible servers
// Format: {"type":"frame","size":N,"quality":Q}
if (message.contains(QStringLiteral("frame"))) {
_expectingBinaryFrame = true;
}
}

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_onTextMessageReceived sets _expectingBinaryFrame but _onBinaryMessageReceived ignores it and always pushes any binary message into jpegdec. Since protocol metadata (including declared size) is effectively ignored (_expectedFrameSize is never used), non-frame binary messages or size mismatches can be fed to the decoder. Consider enforcing the expected state/size before pushing to appsrc.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the dead protocol state (_expectingBinaryFrame, _expectedFrameSize). Replaced with a lightweight JPEG SOI marker check (0xFFD8) on incoming binary messages — protocol-agnostic and prevents non-JPEG data from reaching jpegdec.

Comment on lines +59 to +82
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
pkg_check_modules(GSTREAMER_APP QUIET IMPORTED_TARGET gstreamer-app-1.0)
endif()
if(TARGET PkgConfig::GSTREAMER_APP)
set_target_properties(PkgConfig::GSTREAMER_APP PROPERTIES IMPORTED_GLOBAL TRUE)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE PkgConfig::GSTREAMER_APP)
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE QGC_GST_APP_AVAILABLE)

target_sources(${CMAKE_PROJECT_NAME}
PRIVATE
QGCWebSocketVideoSource.cc
QGCWebSocketVideoSource.h
)

find_package(Qt6 ${QGC_QT_MINIMUM_VERSION}...${QGC_QT_MAXIMUM_VERSION}
COMPONENTS WebSockets
)
if(TARGET Qt6::WebSockets)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6::WebSockets)
else()
message(WARNING "Qt6::WebSockets not found - WebSocket video streaming will not be available")
endif()
endif()

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMake adds QGCWebSocketVideoSource.* and defines QGC_GST_APP_AVAILABLE based only on gstreamer-app being found, but the source unconditionally includes QtWebSockets headers. If gstapp is present but Qt6::WebSockets is not, this will fail to compile/link. Gate adding these sources/defines on BOTH dependencies (gstapp + Qt6::WebSockets), or otherwise disable WebSocket support cleanly.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. WebSocket sources and QGC_GST_APP_AVAILABLE are now only added when both gstreamer-app-1.0 and Qt6::WebSockets are found. If either is missing, a status message explains which dependency is unavailable.

Comment thread src/UI/AppSettings/VideoSettings.qml Outdated
Comment on lines 89 to 135
SettingsGroupLayout {
Layout.fillWidth: true
heading: qsTr("HTTP Stream Settings")
visible: _isHTTP

LabelledFactTextField {
Layout.fillWidth: true
label: qsTr("Connection Timeout")
fact: _videoSettings.httpTimeout
}

LabelledFactTextField {
Layout.fillWidth: true
label: qsTr("Retry Attempts")
fact: _videoSettings.httpRetryAttempts
}

FactCheckBoxSlider {
Layout.fillWidth: true
text: qsTr("Keep-Alive")
fact: _videoSettings.httpKeepAlive
}
}

SettingsGroupLayout {
Layout.fillWidth: true
heading: qsTr("WebSocket Stream Settings")
visible: _isWebSocket

LabelledFactTextField {
Layout.fillWidth: true
label: qsTr("Connection Timeout")
fact: _videoSettings.websocketTimeout
}

LabelledFactTextField {
Layout.fillWidth: true
label: qsTr("Reconnect Delay")
fact: _videoSettings.websocketReconnectDelay
}

LabelledFactTextField {
Layout.fillWidth: true
label: qsTr("Heartbeat Interval")
fact: _videoSettings.websocketHeartbeat
}
}

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VideoSettings.qml exposes only a subset of the newly-added HTTP/WebSocket tuning Facts (e.g., httpBufferSize/httpUserAgent/adaptiveQuality/minQuality/maxQuality/websocketBufferFrames are defined in settings but have no UI controls here). Either add the missing controls or remove/keep them internal to avoid confusing “hidden” settings.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the unused settings (httpBufferSize, httpUserAgent, adaptiveQuality, minQuality, maxQuality, websocketBufferFrames). PixEagle's adaptive quality runs server-side (bandwidth EWMA + encoding time + CPU monitoring via AdaptiveQualityEngine), so no client-side quality controls are needed. Buffer size and user-agent remain as hardcoded struct defaults in the GStreamer receiver. Will re-add as user-facing settings if/when client-initiated quality negotiation is implemented.

Comment on lines +55 to +107
async def video_handler(websocket):
"""Handle a single WebSocket video client."""
print(f"Client connected: {websocket.remote_address}")
frame_number = 0
fps = 30
quality = 85
frame_interval = 1.0 / fps

try:
while True:
start = time.monotonic()

frame = generate_test_frame(640, 480, frame_number, fps)
_, jpeg = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality])
jpeg_bytes = jpeg.tobytes()

# Send JSON metadata first
metadata = json.dumps({
"type": "frame",
"size": len(jpeg_bytes),
"quality": quality,
"frame": frame_number,
"timestamp": time.time()
})
await websocket.send(metadata)

# Send binary JPEG frame
await websocket.send(jpeg_bytes)

frame_number += 1

elapsed = time.monotonic() - start
remaining = frame_interval - elapsed
if remaining > 0:
await asyncio.sleep(remaining)

except websockets.exceptions.ConnectionClosed:
print(f"Client disconnected: {websocket.remote_address}")


async def main(port):
print(f"Starting WebSocket video server on ws://0.0.0.0:{port}/ws/video_feed")
async with websockets.serve(video_handler, "0.0.0.0", port):
await asyncio.Future()


if __name__ == '__main__':
parser = argparse.ArgumentParser(description='WebSocket Video Test Server')
parser.add_argument('--port', type=int, default=5078, help='Server port (default: 5078)')
parser.add_argument('--fps', type=int, default=30, help='Frames per second (default: 30)')
args = parser.parse_args()

asyncio.run(main(args.port))

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--fps is accepted on the command line but ignored: video_handler hard-codes fps=30 and main() doesn’t pass args.fps through. This makes the test server behavior not match the documented usage. Thread the selected FPS into the handler (e.g., via a closure/global) and use it for frame_interval and frame generation.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The --fps argument is now threaded through to the handler via functools.partial.

Comment thread src/Settings/VideoSettings.h Outdated
Comment on lines +30 to +33
DEFINE_SETTINGFACT(adaptiveQuality)
DEFINE_SETTINGFACT(minQuality)
DEFINE_SETTINGFACT(maxQuality)
DEFINE_SETTINGFACT(websocketBufferFrames)

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These WebSocket adaptive-quality related Facts are introduced but aren’t referenced anywhere outside settings (no implementation or UI). If adaptive quality isn’t implemented yet, consider removing these Facts until they’re wired up, or add the missing logic/UI so they have an effect.

Suggested change
DEFINE_SETTINGFACT(adaptiveQuality)
DEFINE_SETTINGFACT(minQuality)
DEFINE_SETTINGFACT(maxQuality)
DEFINE_SETTINGFACT(websocketBufferFrames)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Adaptive quality is handled entirely server-side by PixEagle's AdaptiveQualityEngine — no client-side settings needed.

Comment on lines 138 to 144
@@ -131,6 +139,8 @@ void GstVideoReceiver::start(uint32_t timeout)
"message-forward", TRUE,
nullptr);

_captureStreamSettings();

_source = _makeSource(_uri);

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_captureStreamSettings() reads SettingsManager/Fact values from the GStreamer worker thread (start() runs on the worker when _needDispatch() is true). Settings/Facts are QObjects owned by the main thread, so this is unsafe cross-thread access. Capture these settings on the caller (main) thread and pass plain values into the worker, or fetch them via a blocking queued invoke to the main thread.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. _captureStreamSettings() now executes before _needDispatch() dispatches to the worker thread, ensuring Fact values are read on the main thread. The worker then uses the captured plain-value structs (_httpSettings, _wsSettings) safely.

Comment thread src/Settings/Video.SettingsGroup.json Outdated
Comment on lines +34 to +40
{
"name": "httpUrl",
"shortDesc": "HTTP Video URL",
"longDesc": "HTTP/HTTPS URL for MJPEG video stream. Format: http://host:port/path (e.g., http://192.168.1.100:5077/video_feed for PixEagle, or http://camera-ip/mjpeg for IP cameras).",
"type": "string",
"default": ""
},

Copilot AI Mar 5, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says the default “Video Display Fit” is changed to “Fit Width”, but the metadata here still indicates enumValues 0=Fit Width and default is 1 (Fit Height). If the default is intended to be Fit Width, update the default accordingly (and ensure any related code/UI matches).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON default for videoFit was never changed in this PR — it remains 1 (Fit Height) as before. Corrected the PR description to remove the incorrect claim. No code change needed.

@alireza787b

Copy link
Copy Markdown
Contributor Author

Addressing all 9 review comments plus additional improvements:

Thread safety:

  • Capture stream settings on main thread before dispatching to GStreamer worker

Build correctness:

  • CMake: gate WebSocket sources on both gstreamer-app-1.0 AND Qt6::WebSockets

A/V correctness:

  • Use GST_CLOCK_TIME_NONE with do-timestamp=TRUE instead of hardcoded 30 FPS timestamps

Code quality:

  • Add missing Qt headers for types used in signals/slots
  • Replace dead protocol state with JPEG SOI marker validation
  • Extract _createAndConnectWebSocket() to eliminate code duplication
  • Fix _wsSource stop/deleteLater race and null pointer after ownership transfer
  • Fix test server --fps passthrough

YAGNI removals:

  • Remove adaptiveQuality, minQuality, maxQuality, websocketBufferFrames, httpBufferSize, httpUserAgent settings — adaptive quality is handled server-side by PixEagle's AdaptiveQualityEngine (bandwidth EWMA + encoding time + CPU monitoring). Buffer size and user-agent kept as hardcoded defaults.

Note: videoFit default was not changed — PR description corrected. WebRTC support intentionally excluded (separate PR — requires ICE/STUN/TURN/SDP signaling, fundamentally different architecture).

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

@HTRamsey Not sure what you want to do with this...

@alireza787b

alireza787b commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Compatibility status update from the PixEagle side:

I audited this PR against PixEagle current media/security defaults. The local test paths still make sense: same-host QGC/PixEagle usage with http://127.0.0.1:5077/video_feed and ws://127.0.0.1:5077/ws/video_feed is the supported direct-development path. PixEagle now permits a native same-host WebSocket client with no browser Origin only when both the socket peer and Host authority are loopback.

The remote PixEagle examples such as 192.168.x.x:5077 should not be treated as plug-and-play anymore. PixEagle media endpoints are local-first, reject query-string tokens, and require scoped media:read auth for non-loopback clients. This PR currently exposes URL/timeouts only, with no configurable Origin/Authorization/header support for HTTP souphttpsrc or QWebSocket, so direct remote PixEagle HTTP/WS needs follow-up design before it is safe to advertise.

For field/ground-station PixEagle video today, the recommended path remains H.264/RTP/UDP output to QGC. Future QGC work should likely add reviewed optional Origin/auth header support plus credential redaction before claiming authenticated remote PixEagle HTTP/WS compatibility.

I did not build or mutate the QGC branch in this audit; GitHub currently reports this PR as dirty/not rebaseable, so rebase/build cleanup is still needed separately.

@alireza787b

alireza787b commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up clarification from the PixEagle side after the remote-host media/security review:

This QGC feature should remain generic. Normal HTTP/HTTPS MJPEG and WebSocket sources such as IP cameras, lab MJPEG servers, and custom WebSocket video servers should continue to work as URL-only sources when those sources do not require authentication.

PixEagle should be treated as one stricter source profile on top of that generic capability, not hard-coded into QGC core behavior. Same-host PixEagle development remains the loopback path: http://127.0.0.1:5077/video_feed and ws://127.0.0.1:5077/ws/video_feed. Remote PixEagle running on a companion computer should not be advertised as plug-and-play anonymous HTTP/WS; it needs future optional generic Authorization/Origin/TLS settings plus credential redaction before direct remote PixEagle HTTP/WS is claimed. Field QGC video from PixEagle still uses H.264/RTP/UDP today.

No QGC branch changes are included in this comment; this is just the compatibility boundary I will use for the follow-up implementation work.

alireza787b added a commit to alireza787b/qgroundcontrol that referenced this pull request Jun 23, 2026
Reconcile PR mavlink#13594 with current QGroundControl and add generic authenticated HTTP MJPEG/WebSocket JPEG support with strict TLS, credential redaction, bounded input, recording guards, and focused tests.
Comment thread src/Settings/VideoSettings.cc Fixed
@alireza787b
alireza787b marked this pull request as draft June 25, 2026 01:17
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 24.30939% with 548 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.21%. Comparing base (f29efd3) to head (b98848b).
⚠️ Report is 105 commits behind head on master.

Files with missing lines Patch % Lines
...anager/VideoReceiver/GStreamer/GstVideoReceiver.cc 10.71% 136 Missing and 64 partials ⚠️
src/Settings/VideoSettings.cc 37.99% 40 Missing and 102 partials ⚠️
...VideoReceiver/GStreamer/QGCWebSocketVideoSource.cc 27.90% 56 Missing and 37 partials ⚠️
src/VideoManager/VideoManager.cc 4.41% 65 Missing ⚠️
...anager/VideoReceiver/GStreamer/GStreamerLogging.cc 36.36% 10 Missing and 11 partials ⚠️
src/VideoManager/VideoReceiver/VideoReceiver.cc 38.09% 12 Missing and 1 partial ⚠️
.../VideoManager/VideoReceiver/GStreamer/GStreamer.cc 12.50% 4 Missing and 3 partials ⚠️
src/VideoManager/VideoReceiver/VideoReceiver.h 14.28% 6 Missing ⚠️
.../VideoReceiver/GStreamer/QGCWebSocketVideoSource.h 0.00% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (24.30%) is below the target coverage (30.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #13594      +/-   ##
==========================================
+ Coverage   25.47%   30.21%   +4.74%     
==========================================
  Files         769      775       +6     
  Lines       65912    67674    +1762     
  Branches    30495    31552    +1057     
==========================================
+ Hits        16788    20450    +3662     
+ Misses      37285    33220    -4065     
- Partials    11839    14004    +2165     
Flag Coverage Δ
unittests 30.21% <24.30%> (+4.74%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/Settings/VideoSettings.h 100.00% <100.00%> (+85.71%) ⬆️
src/Utilities/Network/QGCNetworkHelper.h 12.50% <ø> (+12.50%) ⬆️
src/VideoManager/VideoManager.h 37.50% <ø> (+37.50%) ⬆️
...Manager/VideoReceiver/GStreamer/GstVideoReceiver.h 0.00% <ø> (ø)
.../VideoReceiver/GStreamer/QGCWebSocketVideoSource.h 0.00% <0.00%> (ø)
src/VideoManager/VideoReceiver/VideoReceiver.h 11.53% <14.28%> (-6.65%) ⬇️
.../VideoManager/VideoReceiver/GStreamer/GStreamer.cc 13.62% <12.50%> (-0.37%) ⬇️
src/VideoManager/VideoReceiver/VideoReceiver.cc 38.09% <38.09%> (ø)
...anager/VideoReceiver/GStreamer/GStreamerLogging.cc 27.58% <36.36%> (+8.23%) ⬆️
src/VideoManager/VideoManager.cc 8.69% <4.41%> (+6.79%) ⬆️
... and 3 more

... and 375 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 27065dc...b98848b. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@DonLakeFlyer DonLakeFlyer added this to the Release V5.2 milestone Jul 6, 2026
@alireza787b
alireza787b force-pushed the feature/http-websocket-video-streaming branch from b98848b to ab5213f Compare July 14, 2026 13:38
@github-actions github-actions Bot added the github_actions Pull requests that update GitHub Actions code label Jul 14, 2026
@alireza787b alireza787b changed the title Feature/http websocket video streaming - Add HTTP/HTTPS MJPEG and WebSocket Video Streaming Support feat(VideoManager): add HTTP MJPEG and WebSocket JPEG sources Jul 14, 2026
@alireza787b

Copy link
Copy Markdown
Contributor Author

Draft refresh is now available at ab5213f4f69c5b07494f101226db384b48af1e4f, rebased directly onto current master.

The PR description has been replaced to match the implementation and test evidence. The receiver is generic:

  • multipart MJPEG over HTTP/HTTPS;
  • one complete JPEG per binary message over WS/WSS;
  • optional None/Basic/Bearer authentication, in-memory session credentials, exact Origin, custom CA, and an owner-only credential file on supported Unix desktops;
  • strict transport and URL-secret validation, redacted diagnostics, redirect restrictions, and bounded JPEG validation;
  • MKV/MOV recording, with unsupported JPEG-to-MP4 combinations rejected before recording.

The camera-free fixtures remain under test/VideoStreaming/. The same head passed 183 unit tests and 49 integration tests, including GStreamer network-video, TLS, validation, reconnect, recording, and source-EOS finalization coverage: https://github.com/alireza787b/qgroundcontrol/actions/runs/29334848451

All Windows build variants passed, and CI created, installed, and verified the AMD64 installer with the build SDK removed from PATH: https://github.com/alireza787b/qgroundcontrol/actions/runs/29334848113

Upstream PR CI is now running on the promoted head. I am keeping the PR in draft until manual Windows playback, reconnect, source-switch, and MKV/MOV recording evidence is attached. Architecture, security, and testability review is welcome in the meantime.

@alireza787b

Copy link
Copy Markdown
Contributor Author

Thank you for the earlier feedback and testing direction on this draft.

I have replaced this broad proposal with smaller, independently reviewable
changes:

The current QGC build configuration already provisions Qt WebSockets through
its shared module list, including Vagrant and Docker consumers. Both transport
PRs include camera-free delivery and failure tests plus user documentation.
The integrated candidate was manually exercised on Linux, Windows, and
Android, including the Windows software-frame correction in #14728.

The transport PRs intentionally support unauthenticated endpoints only. The
Basic/Bearer credential, Origin, custom-CA, and JPEG recording expansion from
this draft is not being carried into this review sequence. If that security
contract is proposed later, it should be a separate change with an explicit
secret-storage and lifecycle design.

I am closing this draft as superseded so review can continue on the focused
sequence above.

@alireza787b

Copy link
Copy Markdown
Contributor Author

Superseded by the focused PR sequence linked above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake Docs github_actions Pull requests that update GitHub Actions code QML size/XL Tests Video

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants