QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GstSourceFactory.cc
Go to the documentation of this file.
1#include "GstSourceFactory.h"
2
3#include <QtCore/QFile>
4#include <QtCore/QUrl>
5#include <gst/gst.h>
6#include <gst/rtsp/gstrtsptransport.h>
7
8#include "GStreamerHelpers.h"
10
11QGC_LOGGING_CATEGORY(GstSourceFactoryLog, "Video.GStreamer.GstSourceFactory")
12
13namespace {
14
15constexpr guint64 kRtspTcpTimeoutUs = G_GUINT64_CONSTANT(5000000);
16constexpr int kRtspRetry = 3;
17constexpr int kUdpBufferSizeBytes = 8 * 1024 * 1024;
18
19// Older Linux/system GStreamer needs an autoplug-query caps filter to keep parsebin on byte-stream output.
20#if defined(QGC_GST_ENABLE_LEGACY_PARSEBIN_CAPS_FILTER)
21gboolean filterParserCaps([[maybe_unused]] GstElement* bin, [[maybe_unused]] GstPad* pad,
22 [[maybe_unused]] GstElement* element, GstQuery* query, [[maybe_unused]] gpointer data)
23{
24 if (GST_QUERY_TYPE(query) != GST_QUERY_CAPS) {
25 return FALSE;
26 }
27
28 GstCaps* srcCaps;
29 gst_query_parse_caps(query, &srcCaps);
30 if (!srcCaps || gst_caps_is_any(srcCaps)) {
31 return FALSE;
32 }
33
34 GstCaps* sinkCaps = nullptr;
35 GstCaps* filter = nullptr;
36 GstStructure* structure = gst_caps_get_structure(srcCaps, 0);
37 if (gst_structure_has_name(structure, "video/x-h265")) {
38 filter = gst_caps_from_string("video/x-h265");
39 if (gst_caps_can_intersect(srcCaps, filter)) {
40 sinkCaps = gst_caps_from_string("video/x-h265,stream-format=hvc1");
41 }
42 gst_clear_caps(&filter);
43 } else if (gst_structure_has_name(structure, "video/x-h264")) {
44 filter = gst_caps_from_string("video/x-h264");
45 if (gst_caps_can_intersect(srcCaps, filter)) {
46 sinkCaps = gst_caps_from_string("video/x-h264,stream-format=avc");
47 }
48 gst_clear_caps(&filter);
49 }
50
51 if (sinkCaps) {
52 gst_query_set_caps_result(query, sinkCaps);
53 gst_clear_caps(&sinkCaps);
54 return TRUE;
55 }
56
57 return FALSE;
58}
59#endif
60
61void wrapWithGhostPad(GstElement* element, GstPad* pad, [[maybe_unused]] gpointer data)
62{
63 gchar* name = gst_pad_get_name(pad);
64 if (!name) {
65 qCCritical(GstSourceFactoryLog) << "gst_pad_get_name() failed";
66 return;
67 }
68
69 GstPad* ghostpad = gst_ghost_pad_new(name, pad);
70 if (!ghostpad) {
71 qCCritical(GstSourceFactoryLog) << "gst_ghost_pad_new() failed";
72 g_clear_pointer(&name, g_free);
73 return;
74 }
75
76 g_clear_pointer(&name, g_free);
77
78 (void) gst_pad_set_active(ghostpad, TRUE);
79
80 // gst_object_get_parent takes a ref (GST_ELEMENT_PARENT does not) — guards against a
81 // concurrent bin teardown finalizing the parent before gst_element_add_pad.
82 GstObject* parent = gst_object_get_parent(GST_OBJECT(element));
83 if (parent) {
84 if (!gst_element_add_pad(GST_ELEMENT(parent), ghostpad)) {
85 qCCritical(GstSourceFactoryLog) << "gst_element_add_pad() failed";
86 // add_pad only sinks the ghost pad's floating ref on success; release it on failure.
87 gst_object_unref(ghostpad);
88 }
89 gst_object_unref(parent);
90 } else {
91 qCWarning(GstSourceFactoryLog) << "wrapWithGhostPad: element has no parent — bin already torn down?";
92 gst_object_unref(ghostpad);
93 }
94}
95
96gboolean padProbe([[maybe_unused]] GstElement* element, GstPad* pad, gpointer user_data)
97{
98 int* probeRes = static_cast<int*>(user_data);
99 *probeRes |= 1;
100
101 GstCaps* filter = gst_caps_from_string("application/x-rtp");
102 if (filter) {
103 GstCaps* caps = gst_pad_query_caps(pad, nullptr);
104 if (caps) {
105 if (!gst_caps_is_any(caps) && gst_caps_can_intersect(caps, filter)) {
106 *probeRes |= 2;
107 }
108
109 gst_clear_caps(&caps);
110 }
111
112 gst_clear_caps(&filter);
113 }
114
115 return TRUE;
116}
117
118bool addStaticGhostPad(GstElement* element)
119{
120 GstPad* srcPad = gst_element_get_static_pad(element, "src");
121 if (!srcPad) {
122 qCCritical(GstSourceFactoryLog) << "gst_element_get_static_pad('src') failed";
123 return false;
124 }
125
126 wrapWithGhostPad(element, srcPad, nullptr);
127 gst_object_unref(srcPad);
128 return true;
129}
130
131bool validPort(int port)
132{
133 return port > 0 && port <= 65535;
134}
135
136} // namespace
137
139
140namespace {
141
142// Shared by the static and dynamic (pad-added) link paths so both apply the identical jitter-buffer
143// policy. rtx-delay/rtx-max-retries are fixed (not auto) so RF-link recovery is predictable.
144void configureJitterBuffer(GstElement* buffer, const Config& config, guint latencyMs)
145{
146 g_object_set(buffer, "latency", latencyMs, "do-lost", TRUE, "do-retransmission",
147 config.doRetransmission ? TRUE : FALSE, "drop-on-latency",
148 config.jitterBuffer == JitterBuffer::DropOnLatency ? TRUE : FALSE, nullptr);
150 // Fixed 25 ms rtx-delay + single retry: bounded recovery latency over a lossy RF link,
151 // instead of the auto(-1) heuristic that can stack retries and balloon playout latency.
152 g_object_set(buffer, "rtx-delay", 25, "rtx-max-retries", 1, nullptr);
153 }
154}
155
156// Heap-owned context for the dynamic (pad-added) link path; freed when @binParser is finalized.
157struct DynamicLinkContext
158{
160 Config config;
162 bool allowJitterBuffer; // false for RTSP (rtspsrc has its own internal jitterbuffer)
163};
164
165void linkPad(GstElement* element, GstPad* pad, gpointer data)
166{
167 const auto* ctx = static_cast<const DynamicLinkContext*>(data);
168
169 // tsdemux fires pad-added for audio/data pads too; only link video src pads so non-video
170 // pads don't trigger a spurious CRITICAL cascade on the expected link failure.
171 if (GST_PAD_DIRECTION(pad) != GST_PAD_SRC) {
172 return;
173 }
174
175 bool isVideo = false;
176 bool isRtp = false;
177 GstCaps* caps = gst_pad_get_current_caps(pad);
178 if (!caps) {
179 caps = gst_pad_query_caps(pad, nullptr);
180 }
181 if (caps) {
182 const guint n = gst_caps_get_size(caps);
183 for (guint i = 0; i < n; ++i) {
184 const GstStructure* st = gst_caps_get_structure(caps, i);
185 const gchar* sname = gst_structure_get_name(st);
186 if (!sname) {
187 continue;
188 }
189 if (g_str_has_prefix(sname, "video/")) {
190 isVideo = true;
191 } else if (g_str_equal(sname, "application/x-rtp")) {
192 isRtp = true;
193 // RTP carries video; the depayloader/parsebin downstream classifies the payload.
194 isVideo = true;
195 }
196 }
197 gst_clear_caps(&caps);
198 }
199 if (!isVideo) {
200 return;
201 }
202
203 // Link only the first matching video pad. A second pad-added (duplicate RTP pad, or a later
204 // tsdemux elementary stream) must not stack a second jitterbuffer or re-link an occupied sink.
205 if (GstPad* parserSink = gst_element_get_static_pad(ctx->binParser, "sink")) {
206 const gboolean alreadyLinked = gst_pad_is_linked(parserSink);
207 gst_object_unref(parserSink);
208 if (alreadyLinked) {
209 return;
210 }
211 }
212
213 // Insert a jitterbuffer ahead of the parser for RTP pads on non-RTSP dynamic sources, matching the
214 // static path's policy. RTSP is excluded: rtspsrc owns an internal jitterbuffer (no double-buffering).
215 GstElement* downstream = ctx->binParser;
216 GstElement* dynamicBuffer = nullptr;
217 const auto removeDynamicBuffer = [&dynamicBuffer] {
218 if (!dynamicBuffer) {
219 return;
220 }
221
222 GstObject* parent = gst_object_get_parent(GST_OBJECT(dynamicBuffer));
223 if (parent) {
224 gst_bin_remove(GST_BIN(parent), dynamicBuffer);
225 gst_object_unref(parent);
226 }
227 dynamicBuffer = nullptr;
228 };
229
230 if (isRtp && ctx->allowJitterBuffer && (ctx->config.jitterBuffer != JitterBuffer::None)) {
231 GstElement* bin = GST_ELEMENT(gst_object_get_parent(GST_OBJECT(ctx->binParser)));
232 if (bin) {
233 GstElement* buffer = gst_element_factory_make("rtpjitterbuffer", nullptr);
234 if (buffer && gst_bin_add(GST_BIN(bin), buffer)) {
235 configureJitterBuffer(buffer, ctx->config, ctx->latencyMs);
236 if (gst_element_sync_state_with_parent(buffer) && gst_element_link(buffer, ctx->binParser)) {
237 downstream = buffer;
238 dynamicBuffer = buffer;
239 } else {
240 qCWarning(GstSourceFactoryLog)
241 << "dynamic rtpjitterbuffer link/sync failed; linking pad straight to parser";
242 gst_bin_remove(GST_BIN(bin), buffer);
243 buffer = nullptr;
244 }
245 } else {
246 qCWarning(GstSourceFactoryLog) << "dynamic rtpjitterbuffer create/add failed; skipping jitter buffer";
247 if (buffer) {
248 gst_object_unref(buffer);
249 }
250 }
251 gst_object_unref(bin);
252 }
253 }
254
255 gchar* name = gst_pad_get_name(pad);
256 if (!name) {
257 qCWarning(GstSourceFactoryLog) << "gst_pad_get_name() failed";
258 removeDynamicBuffer();
259 return;
260 }
261
262 if (!gst_element_link_pads(element, name, downstream, "sink")) {
263 qCWarning(GstSourceFactoryLog) << "gst_element_link_pads() failed for" << name;
264 removeDynamicBuffer();
265 }
266
267 g_clear_pointer(&name, g_free);
268}
269
270// Each build* helper makes and configures the source element for one scheme family, logs the
271// specific failure, and returns the owning element (or nullptr). create() owns it from here on.
272
273GstElement* buildRtspSource(const QString& uri, const QUrl& sourceUrl, const Config& config, guint latencyMs)
274{
275 if (!GStreamer::isValidRtspUri(uri.toUtf8().constData())) {
276 qCCritical(GstSourceFactoryLog) << "Invalid RTSP URI:" << sourceUrl.toDisplayString(QUrl::RemoveUserInfo);
277 return nullptr;
278 }
279
280 GstElement* source = gst_element_factory_make("rtspsrc", "source");
281 if (!source) {
282 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('rtspsrc') failed";
283 return nullptr;
284 }
285
286 QUrl cleanUrl(sourceUrl);
287 cleanUrl.setUserInfo(QString());
288 const QByteArray cleanLocation = cleanUrl.toEncoded();
289
290 // protocols mask enables TCP-interleaved fallback when UDP is blocked; without it
291 // firewalled networks hang until tcp-timeout instead of negotiating TCP.
292 constexpr GstRTSPLowerTrans kRtspProtocols =
293 static_cast<GstRTSPLowerTrans>(GST_RTSP_LOWER_TRANS_UDP | GST_RTSP_LOWER_TRANS_TCP);
294
295 // do-retransmission forwards to rtspsrc's internal rtpjitterbuffer (added 1.6);
296 // drop-on-latency=TRUE unless jitterBuffer==Buffered (opt out of bounded playout).
297 const gboolean dropOnLatency = (config.jitterBuffer == JitterBuffer::Buffered) ? FALSE : TRUE;
298 g_object_set(source, "location", cleanLocation.constData(), "latency", latencyMs, "do-rtcp", TRUE,
299 "do-retransmission", config.doRetransmission ? TRUE : FALSE, "tcp-timeout", kRtspTcpTimeoutUs,
300 "udp-reconnect", TRUE, "drop-on-latency", dropOnLatency, "retry", kRtspRetry, "protocols",
301 kRtspProtocols, nullptr);
302
303 const QString rtspUser = sourceUrl.userName(QUrl::FullyDecoded);
304 const QString rtspPassword = sourceUrl.password(QUrl::FullyDecoded);
305 if (!rtspUser.isEmpty()) {
306 g_object_set(source, "user-id", rtspUser.toUtf8().constData(), "user-pw",
307 rtspPassword.toUtf8().constData(), nullptr);
308 }
309 return source;
310}
311
312GstElement* buildTcpSource(const QUrl& sourceUrl)
313{
314 const int port = sourceUrl.port();
315 if (!validPort(port)) {
316 qCCritical(GstSourceFactoryLog) << "Invalid TCP port" << port << "in" << sourceUrl.toDisplayString(QUrl::RemoveUserInfo);
317 return nullptr;
318 }
319 const QString host = sourceUrl.host();
320 if (host.isEmpty()) {
321 qCCritical(GstSourceFactoryLog) << "Missing host in TCP URI" << sourceUrl.toDisplayString(QUrl::RemoveUserInfo);
322 return nullptr;
323 }
324
325 GstElement* source = gst_element_factory_make("tcpclientsrc", "source");
326 if (!source) {
327 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('tcpclientsrc') failed";
328 return nullptr;
329 }
330
331 g_object_set(source, "host", host.toUtf8().constData(), "port", static_cast<int>(port), nullptr);
332 return source;
333}
334
335GstElement* buildUdpSource(const QUrl& sourceUrl, bool isUdpH264, bool isUdpH265)
336{
337 const int port = sourceUrl.port();
338 if (!validPort(port)) {
339 qCCritical(GstSourceFactoryLog) << "Invalid UDP port" << port << "in" << sourceUrl.toDisplayString(QUrl::RemoveUserInfo);
340 return nullptr;
341 }
342
343 GstElement* source = gst_element_factory_make("udpsrc", "source");
344 if (!source) {
345 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('udpsrc') failed";
346 return nullptr;
347 }
348
349 // Normalize udp265:///mpegts:// to udp:// for udpsrc, preserving query params.
350 QUrl udpUrl(sourceUrl);
351 udpUrl.setScheme(QStringLiteral("udp"));
352 udpUrl.setUserInfo(QString());
353 const QByteArray udpUri = udpUrl.toEncoded();
354
355 // SO_RCVBUF above net.core.rmem_max needs CAP_NET_ADMIN; without it gstudpsrc fails with EPERM
356 // (scary warning) then clamps. Request the kernel-permitted max instead (Linux/Android only).
357 int udpBufferSize = kUdpBufferSizeBytes;
358#ifdef Q_OS_LINUX
359 // rmem_max is a boot-time sysctl; parse once and cache (0 = unreadable, leave request unclamped).
360 static const int s_rmemMax = [] {
361 QFile rmemMaxFile(QStringLiteral("/proc/sys/net/core/rmem_max"));
362 if (rmemMaxFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
363 bool ok = false;
364 const int sysMax = rmemMaxFile.readAll().trimmed().toInt(&ok);
365 if (ok && (sysMax > 0)) {
366 return sysMax;
367 }
368 }
369 return 0;
370 }();
371 if ((s_rmemMax > 0) && (s_rmemMax < udpBufferSize)) {
372 qCDebug(GstSourceFactoryLog) << "Clamping UDP buffer-size from" << udpBufferSize << "to net.core.rmem_max"
373 << s_rmemMax;
374 udpBufferSize = s_rmemMax;
375 }
376#endif
377 g_object_set(source, "uri", udpUri.constData(), "buffer-size", udpBufferSize, nullptr);
378
379 GstCaps* caps = nullptr;
380 if (isUdpH264) {
381 caps = gst_caps_from_string(
382 "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264");
383 } else if (isUdpH265) {
384 caps = gst_caps_from_string(
385 "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H265");
386 }
387
388 if ((isUdpH264 || isUdpH265) && !caps) {
389 qCCritical(GstSourceFactoryLog) << "gst_caps_from_string() failed";
390 gst_object_unref(source);
391 return nullptr;
392 }
393
394 if (caps) {
395 g_object_set(source, "caps", caps, nullptr);
396 gst_clear_caps(&caps);
397 }
398 return source;
399}
400
401// Wire upstream → (optional rtpjitterbuffer) → binParser, topology chosen by RTP probe (MPEG-TS
402// links via pad-added). Created elements join @p bin; returns false (logged) on failure.
403bool linkSourceToParser(GstElement* bin, GstElement* upstream, GstElement* binParser, const Config& config,
404 guint latencyMs, bool isMpegTs, bool isRtsp)
405{
406 // MPEG-TS exposes elementary streams via tsdemux dynamic src pads (none at NULL state) and
407 // isn't raw RTP, so skip the RTP probe and link via pad-added when the video pad appears.
408 int probeRes = 0;
409 if (!isMpegTs) {
410 (void) gst_element_foreach_src_pad(upstream, padProbe, &probeRes);
411 }
412
413 if (probeRes & 1) {
414 if ((probeRes & 2) && config.jitterBuffer != JitterBuffer::None) {
415 GstElement* buffer = gst_element_factory_make("rtpjitterbuffer", nullptr);
416 if (!buffer) {
417 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('rtpjitterbuffer') failed";
418 return false;
419 }
420
421 configureJitterBuffer(buffer, config, latencyMs);
422
423 if (!gst_bin_add(GST_BIN(bin), buffer)) {
424 qCCritical(GstSourceFactoryLog) << "gst_bin_add(rtpjitterbuffer) failed";
425 gst_object_unref(buffer);
426 return false;
427 }
428 // bin owns buffer now; a link failure below is cleaned up via the caller's bin teardown.
429 if (!gst_element_link_many(upstream, buffer, binParser, nullptr)) {
430 qCCritical(GstSourceFactoryLog) << "gst_element_link_many(source, rtpjitterbuffer, parser) failed";
431 return false;
432 }
433 } else {
434 if (!gst_element_link(upstream, binParser)) {
435 qCCritical(GstSourceFactoryLog) << "gst_element_link(source, parser) failed";
436 return false;
437 }
438 }
439 } else {
440 // linkPad applies the jitter-buffer policy itself when an application/x-rtp pad appears on a
441 // non-RTSP source. Context lifetime is tied to binParser (freed on its finalize).
442 auto* ctx = new DynamicLinkContext{binParser, config, latencyMs, !isRtsp};
443 g_object_set_data_full(G_OBJECT(binParser), "qgc-dynamic-link-ctx", ctx,
444 [](gpointer p) { delete static_cast<DynamicLinkContext*>(p); });
445 (void) g_signal_connect(upstream, "pad-added", G_CALLBACK(linkPad), ctx);
446 }
447
448 // pad-added fires on the streaming thread after create() returns the NULL-state bin, so it
449 // can't race construction; wrapWithGhostPad ref-checks the parent against teardown.
450 (void) g_signal_connect(binParser, "pad-added", G_CALLBACK(wrapWithGhostPad), nullptr);
451 return true;
452}
453
454bool linkUdpRtpToDepayAndParser(GstElement* bin, GstElement* source, GstElement* depay, GstElement* parser,
455 const Config& config, guint latencyMs)
456{
458 if (!gst_element_link_many(source, depay, parser, nullptr)) {
459 qCCritical(GstSourceFactoryLog) << "gst_element_link_many(source, depay, parser) failed";
460 return false;
461 }
462 return true;
463 }
464
465 GstElement* buffer = gst_element_factory_make("rtpjitterbuffer", nullptr);
466 if (!buffer) {
467 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('rtpjitterbuffer') failed";
468 return false;
469 }
470
471 configureJitterBuffer(buffer, config, latencyMs);
472
473 if (!gst_bin_add(GST_BIN(bin), buffer)) {
474 qCCritical(GstSourceFactoryLog) << "gst_bin_add(rtpjitterbuffer) failed";
475 gst_object_unref(buffer);
476 return false;
477 }
478
479 if (!gst_element_link_many(source, buffer, depay, parser, nullptr)) {
480 qCCritical(GstSourceFactoryLog) << "gst_element_link_many(source, rtpjitterbuffer, depay, parser) failed";
481 return false;
482 }
483
484 return true;
485}
486
487} // namespace
488
489GstElement* create(const QString& uri, const Config& config)
490{
491 if (uri.isEmpty()) {
492 qCCritical(GstSourceFactoryLog) << "Failed because URI is not specified";
493 return nullptr;
494 }
495
496 const guint latencyMs = (config.latencyMs < 0) ? 0u : static_cast<guint>(config.latencyMs);
497
498 const QUrl sourceUrl(uri);
499 const QString scheme = sourceUrl.scheme().toLower();
500
501 const bool isRtsp = scheme.startsWith(QLatin1String("rtsp"));
502 const bool isUdpH264 = (scheme == QLatin1String("udp"));
503 const bool isUdpH265 = (scheme == QLatin1String("udp265"));
504 const bool isUdpMPEGTS = (scheme == QLatin1String("mpegts"));
505 const bool isTcpMPEGTS = (scheme == QLatin1String("tcp"));
506
507 if (!isRtsp && !isUdpH264 && !isUdpH265 && !isUdpMPEGTS && !isTcpMPEGTS) {
508 qCWarning(GstSourceFactoryLog) << "Unsupported URI scheme:" << scheme << "in" << sourceUrl.toDisplayString(QUrl::RemoveUserInfo);
509 return nullptr;
510 }
511
512 // Owning locals until gst_bin_add*, then nulled (non-owning alias used downstream) so the
513 // unconditional gst_clear_object cleanup at the bottom stays safe.
514 GstElement* source = nullptr;
515 GstElement* parser = nullptr;
516 GstElement* rtpDepay = nullptr;
517 GstElement* tsdemux = nullptr;
518 GstElement* bin = nullptr;
519 GstElement* srcbin = nullptr;
520
521 do {
522 if (isRtsp) {
523 source = buildRtspSource(uri, sourceUrl, config, latencyMs);
524 } else if (isTcpMPEGTS) {
525 source = buildTcpSource(sourceUrl);
526 } else { // isUdpH264 || isUdpH265 || isUdpMPEGTS
527 source = buildUdpSource(sourceUrl, isUdpH264, isUdpH265);
528 }
529 if (!source) {
530 break;
531 }
532
533 bin = gst_bin_new("sourcebin");
534 if (!bin) {
535 qCCritical(GstSourceFactoryLog) << "gst_bin_new('sourcebin') failed";
536 break;
537 }
538
539 parser = gst_element_factory_make(isUdpH265 ? "h265parse" : "parsebin", "parser");
540 if (!parser) {
541 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make("
542 << (isUdpH265 ? "'h265parse'" : "'parsebin'") << ") failed";
543 break;
544 }
545
546 if (isUdpH265) {
547 // UDP H.265 has no SDP/RTSP control plane. Make depayloading and parser config explicit
548 // so hardware decoders, especially Android MediaCodec, see repeated VPS/SPS/PPS at IDR
549 // boundaries instead of depending on parsebin's defaults after startup or reconnect.
550 rtpDepay = gst_element_factory_make("rtph265depay", nullptr);
551 if (!rtpDepay) {
552 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('rtph265depay') failed";
553 break;
554 }
555 g_object_set(parser, "config-interval", -1, nullptr);
556 }
557
558 // Older Linux/system GStreamer misnegotiates parser->decoder caps; force avc/hvc1 there only.
559 // Bundled 1.28+ SDKs do not need it, and the forced caps break byte-stream HW decoders.
560#if defined(QGC_GST_ENABLE_LEGACY_PARSEBIN_CAPS_FILTER)
561 if (!isUdpH265) {
562 (void) g_signal_connect(parser, "autoplug-query", G_CALLBACK(filterParserCaps), nullptr);
563 }
564#endif
565
566 // Add individually so ownership is unambiguous on failure: gst_bin_add sinks the ref only
567 // on success, so the local stays owning (and gets unref'd by the cleanup block) when it fails.
568 if (!gst_bin_add(GST_BIN(bin), source)) {
569 qCCritical(GstSourceFactoryLog) << "gst_bin_add(source) failed";
570 break;
571 }
572 GstElement* upstream = source;
573 source = nullptr;
574
575 if (rtpDepay && !gst_bin_add(GST_BIN(bin), rtpDepay)) {
576 qCCritical(GstSourceFactoryLog) << "gst_bin_add(rtph265depay) failed";
577 break;
578 }
579 GstElement* depay = rtpDepay;
580 rtpDepay = nullptr;
581
582 if (!gst_bin_add(GST_BIN(bin), parser)) {
583 qCCritical(GstSourceFactoryLog) << "gst_bin_add(parser) failed";
584 break;
585 }
586 GstElement* binParser = parser;
587 parser = nullptr;
588
589 // Android can't determine MPEG2-TS via parsebin, so create tsdemux explicitly.
590 if (isTcpMPEGTS || isUdpMPEGTS) {
591 tsdemux = gst_element_factory_make("tsdemux", nullptr);
592 if (!tsdemux) {
593 qCCritical(GstSourceFactoryLog) << "gst_element_factory_make('tsdemux') failed";
594 break;
595 }
596
597 if (!gst_bin_add(GST_BIN(bin), tsdemux)) {
598 qCCritical(GstSourceFactoryLog) << "gst_bin_add(tsdemux) failed";
599 break;
600 }
601 GstElement* demux = tsdemux;
602 tsdemux = nullptr;
603
604 if (!gst_element_link(upstream, demux)) {
605 qCCritical(GstSourceFactoryLog) << "gst_element_link(source, tsdemux) failed";
606 break;
607 }
608
609 upstream = demux;
610 }
611
612 if (isUdpH265) {
613 if (!linkUdpRtpToDepayAndParser(bin, upstream, depay, binParser, config, latencyMs)) {
614 break;
615 }
616 if (!addStaticGhostPad(binParser)) {
617 break;
618 }
619 } else {
620 if (!linkSourceToParser(bin, upstream, binParser, config, latencyMs, isTcpMPEGTS || isUdpMPEGTS, isRtsp)) {
621 break;
622 }
623 }
624
625 srcbin = bin;
626 bin = nullptr;
627 } while (0);
628
629 gst_clear_object(&bin);
630 gst_clear_object(&parser);
631 gst_clear_object(&rtpDepay);
632 gst_clear_object(&tsdemux);
633 gst_clear_object(&source);
634
635 return srcbin;
636}
637
638} // namespace GStreamer::SourceFactory
Config config
bool allowJitterBuffer
guint latencyMs
GstElement * binParser
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
GstElement * create(const QString &uri, const Config &config)
@ None
No rtpjitterbuffer element; lowest latency, no reordering.
@ Buffered
rtpjitterbuffer with drop-on-latency=FALSE.
@ DropOnLatency
rtpjitterbuffer with drop-on-latency=TRUE.
bool isValidRtspUri(const gchar *uri_str)