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