QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MockVideoStreamServer.cc
Go to the documentation of this file.
1// GStreamer/GLib headers must precede any Qt header: Qt's `signals` keyword macro
2// clashes with a struct member named `signals` in glib's gdbusintrospection.h
3// (pulled in transitively by rtsp-server.h).
4#include <gst/gst.h>
5
6#ifdef QGC_GST_RTSP_SERVER
7#include <glib.h>
8#include <gst/rtsp-server/rtsp-server.h>
9#endif
10
12
13#include "QGCLoggingCategory.h"
14
15QGC_LOGGING_CATEGORY(MockVideoStreamServerLog, "VideoManager.MockVideoStreamServer")
16
17namespace {
18
19// Shared test pattern source. videotestsrc's "ball" pattern makes motion obvious so a
20// stalled pipeline is easy to spot by eye.
21constexpr const char *kTestSource =
22 "videotestsrc is-live=true pattern=ball ! "
23 "video/x-raw,width=1280,height=720,framerate=30/1 ! "
24 "videoconvert";
25
26constexpr const char *kH264Encoder = "x264enc tune=zerolatency bitrate=2000 key-int-max=30";
27constexpr const char *kH265Encoder = "x265enc tune=zerolatency bitrate=2000";
28
29bool factoryExists(const char *name)
30{
31 GstElementFactory *factory = gst_element_factory_find(name);
32 if (factory) {
33 gst_object_unref(factory);
34 return true;
35 }
36 return false;
37}
38
39// GStreamer init is owned by QGC's central VideoBackend/GStreamer::initialize() path
40// (environment prep, static plugin registration, logging). Never call gst_init() here —
41// running it first would bypass that setup and turn the central init into a no-op.
42bool gstInitialized()
43{
44 if (!gst_is_initialized()) {
45 qCWarning(MockVideoStreamServerLog) << "GStreamer not initialized; QGC's central GStreamer init must run before serving a mock stream";
46 return false;
47 }
48 return true;
49}
50
51} // namespace
52
57
59{
60 if (!gstInitialized()) {
61 return false;
62 }
63
64 switch (type) {
66 return factoryExists("x264enc") && factoryExists("videotestsrc");
69 // mpegtsmux ships in gst-plugins-bad and can be absent even when the encoder is present.
70 return factoryExists("x264enc") && factoryExists("videotestsrc") && factoryExists("mpegtsmux");
72 return factoryExists("x265enc") && factoryExists("videotestsrc");
74#ifdef QGC_GST_RTSP_SERVER
75 return factoryExists("x264enc") && factoryExists("videotestsrc");
76#else
77 return false;
78#endif
79 }
80
81 return false;
82}
83
84bool MockVideoStreamServer::start(StreamType type, const QString &host, quint16 port)
85{
86 if (_running) {
87 stop();
88 }
89
90 if (!isStreamTypeAvailable(type)) {
91 qCWarning(MockVideoStreamServerLog) << "Requested stream type is not available in this GStreamer install";
92 return false;
93 }
94
95 if (type == StreamType::RtspH264) {
96#ifdef QGC_GST_RTSP_SERVER
97 if (!_startRtsp(host, port)) {
98 return false;
99 }
100 _servedUri = QStringLiteral("rtsp://%1:%2/test").arg(host, QString::number(port));
101 _running = true;
102 qCDebug(MockVideoStreamServerLog) << "Serving" << _servedUri;
103 return true;
104#else
105 qCWarning(MockVideoStreamServerLog) << "RTSP requested but gst-rtsp-server is not available";
106 return false;
107#endif
108 }
109
110 QString launch;
111 QString uri;
112 const QString portString = QString::number(port);
113 switch (type) {
115 launch = QStringLiteral("%1 ! %2 ! rtph264pay config-interval=1 pt=96 ! udpsink host=%3 port=%4")
116 .arg(kTestSource, kH264Encoder, host, portString);
117 uri = QStringLiteral("udp://%1:%2").arg(host, portString);
118 break;
120 launch = QStringLiteral("%1 ! %2 ! rtph265pay config-interval=1 pt=96 ! udpsink host=%3 port=%4")
121 .arg(kTestSource, kH265Encoder, host, portString);
122 uri = QStringLiteral("udp265://%1:%2").arg(host, portString);
123 break;
125 launch = QStringLiteral("%1 ! %2 ! mpegtsmux ! udpsink host=%3 port=%4")
126 .arg(kTestSource, kH264Encoder, host, portString);
127 uri = QStringLiteral("mpegts://%1:%2").arg(host, portString);
128 break;
130 launch = QStringLiteral("%1 ! %2 ! mpegtsmux ! tcpserversink host=%3 port=%4")
131 .arg(kTestSource, kH264Encoder, host, portString);
132 uri = QStringLiteral("tcp://%1:%2").arg(host, portString);
133 break;
135 return false; // handled above
136 }
137
138 if (!_startPipeline(launch)) {
139 return false;
140 }
141
142 _servedUri = uri;
143 _running = true;
144 qCDebug(MockVideoStreamServerLog) << "Serving" << _servedUri << "via" << launch;
145 return true;
146}
147
148bool MockVideoStreamServer::_startPipeline(const QString &launch)
149{
150 GError *error = nullptr;
151 GstElement *pipeline = gst_parse_launch(launch.toUtf8().constData(), &error);
152 if (error) {
153 qCWarning(MockVideoStreamServerLog) << "Failed to build pipeline:" << error->message;
154 g_error_free(error);
155 if (pipeline) {
156 gst_object_unref(pipeline);
157 }
158 return false;
159 }
160
161 if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
162 qCWarning(MockVideoStreamServerLog) << "Failed to set pipeline to PLAYING";
163 gst_object_unref(pipeline);
164 return false;
165 }
166
167 _pipeline = pipeline;
168 return true;
169}
170
172{
173 if (_pipeline) {
174 gst_element_set_state(_pipeline, GST_STATE_NULL);
175 gst_object_unref(_pipeline);
176 _pipeline = nullptr;
177 }
178
179#ifdef QGC_GST_RTSP_SERVER
180 _stopRtsp();
181#endif
182
183 _running = false;
184 _servedUri.clear();
185}
186
187#ifdef QGC_GST_RTSP_SERVER
188bool MockVideoStreamServer::_startRtsp(const QString &host, quint16 port)
189{
190 Q_UNUSED(host); // RTSP server binds all interfaces; client connects via the advertised host.
191
192 _rtspContext = g_main_context_new();
193
194 GstRTSPServer *server = gst_rtsp_server_new();
195 const QByteArray service = QByteArray::number(port);
196 gst_rtsp_server_set_service(server, service.constData());
197
198 GstRTSPMountPoints *mounts = gst_rtsp_server_get_mount_points(server);
199 GstRTSPMediaFactory *factory = gst_rtsp_media_factory_new();
200 const QString launch = QStringLiteral("( %1 ! %2 ! rtph264pay name=pay0 pt=96 )")
201 .arg(kTestSource, kH264Encoder);
202 gst_rtsp_media_factory_set_launch(factory, launch.toUtf8().constData());
203 gst_rtsp_media_factory_set_shared(factory, TRUE);
204 gst_rtsp_mount_points_add_factory(mounts, "/test", factory);
205 g_object_unref(mounts);
206
207 if (gst_rtsp_server_attach(server, _rtspContext) == 0) {
208 qCWarning(MockVideoStreamServerLog) << "Failed to attach RTSP server on port" << port;
209 gst_object_unref(server);
210 g_main_context_unref(_rtspContext);
211 _rtspContext = nullptr;
212 return false;
213 }
214
215 _rtspServer = server;
216 _rtspLoop = g_main_loop_new(_rtspContext, FALSE);
217
218 _rtspThread = std::thread([this]() {
219 g_main_context_push_thread_default(_rtspContext);
220 g_main_loop_run(_rtspLoop);
221 g_main_context_pop_thread_default(_rtspContext);
222 });
223
224 return true;
225}
226
227void MockVideoStreamServer::_stopRtsp()
228{
229 if (_rtspLoop) {
230 g_main_loop_quit(_rtspLoop);
231 }
232 if (_rtspThread.joinable()) {
233 _rtspThread.join();
234 }
235 if (_rtspLoop) {
236 g_main_loop_unref(_rtspLoop);
237 _rtspLoop = nullptr;
238 }
239 if (_rtspServer) {
240 gst_object_unref(_rtspServer);
241 _rtspServer = nullptr;
242 }
243 if (_rtspContext) {
244 g_main_context_unref(_rtspContext);
245 _rtspContext = nullptr;
246 }
247}
248#endif // QGC_GST_RTSP_SERVER
struct _GstElement GstElement
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
@ MpegTsTcp
MPEG-TS over TCP -> tcp://.
@ RtpUdpH265
RTP/H.265 over UDP -> udp265://.
@ RtpUdpH264
RTP/H.264 over UDP -> udp://.
@ RtspH264
RTSP (H.264) -> rtsp://.
@ MpegTsUdp
MPEG-TS over UDP -> mpegts://.
void stop()
Stop the stream and release all resources. Safe to call when not running.
static bool isStreamTypeAvailable(StreamType type)
bool start(StreamType type, const QString &host, quint16 port)