121static std::atomic<bool> s_envPathsValid{
true};
122static QMutex s_envPathsMutex;
123static QString s_envPathsError;
125void _registerPlugins()
127#ifdef QGC_GST_STATIC_BUILD
128 GST_PLUGIN_STATIC_REGISTER(app);
129 GST_PLUGIN_STATIC_REGISTER(coreelements);
130 GST_PLUGIN_STATIC_REGISTER(isomp4);
131 GST_PLUGIN_STATIC_REGISTER(libav);
132 GST_PLUGIN_STATIC_REGISTER(matroska);
133 GST_PLUGIN_STATIC_REGISTER(mpegtsdemux);
134 GST_PLUGIN_STATIC_REGISTER(openh264);
135 GST_PLUGIN_STATIC_REGISTER(playback);
136 GST_PLUGIN_STATIC_REGISTER(rtp);
137 GST_PLUGIN_STATIC_REGISTER(rtpmanager);
138 GST_PLUGIN_STATIC_REGISTER(rtsp);
139 GST_PLUGIN_STATIC_REGISTER(sdpelem);
140 GST_PLUGIN_STATIC_REGISTER(tcp);
141 GST_PLUGIN_STATIC_REGISTER(typefindfunctions);
142 GST_PLUGIN_STATIC_REGISTER(udp);
143#ifdef GST_PLUGIN_videoconvertscale_FOUND
144 GST_PLUGIN_STATIC_REGISTER(videoconvertscale);
146#ifdef GST_PLUGIN_videoconvert_FOUND
147 GST_PLUGIN_STATIC_REGISTER(videoconvert);
149#ifdef GST_PLUGIN_videoscale_FOUND
150 GST_PLUGIN_STATIC_REGISTER(videoscale);
152 GST_PLUGIN_STATIC_REGISTER(videoparsersbad);
153 GST_PLUGIN_STATIC_REGISTER(vpx);
155#ifdef GST_PLUGIN_androidmedia_FOUND
156 GST_PLUGIN_STATIC_REGISTER(androidmedia);
158#ifdef GST_PLUGIN_applemedia_FOUND
159 GST_PLUGIN_STATIC_REGISTER(applemedia);
161#ifdef GST_PLUGIN_d3d_FOUND
162 GST_PLUGIN_STATIC_REGISTER(d3d);
164#ifdef GST_PLUGIN_d3d11_FOUND
165 GST_PLUGIN_STATIC_REGISTER(d3d11);
167#ifdef GST_PLUGIN_d3d12_FOUND
168 GST_PLUGIN_STATIC_REGISTER(d3d12);
170#ifdef GST_PLUGIN_dav1d_FOUND
171 GST_PLUGIN_STATIC_REGISTER(dav1d);
173#ifdef GST_PLUGIN_dxva_FOUND
174 GST_PLUGIN_STATIC_REGISTER(dxva);
176#ifdef GST_PLUGIN_nvcodec_FOUND
177 GST_PLUGIN_STATIC_REGISTER(nvcodec);
179#ifdef GST_PLUGIN_qsv_FOUND
180 GST_PLUGIN_STATIC_REGISTER(qsv);
182#ifdef GST_PLUGIN_va_FOUND
183 GST_PLUGIN_STATIC_REGISTER(va);
185#ifdef GST_PLUGIN_vulkan_FOUND
186 GST_PLUGIN_STATIC_REGISTER(vulkan);
190 GST_PLUGIN_STATIC_REGISTER(qgc);
193void _resetEnvValidation()
195 const QMutexLocker locker(&s_envPathsMutex);
196 s_envPathsError.clear();
197 s_envPathsValid.store(
true, std::memory_order_release);
201[[maybe_unused]] QString _cleanJoin(
const QString &base,
const QString &relative)
203 return QDir::cleanPath(QDir(base).filePath(relative));
206[[maybe_unused]]
void _setGstEnv(
const char *name,
const QString &value)
208 qputenv(name, value.toUtf8());
209 qCDebug(GStreamerLog) <<
" " << name <<
"=" << value;
212#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
214void _setEnvValidationError(
const QString &
error)
216 const QMutexLocker locker(&s_envPathsMutex);
217 s_envPathsError =
error;
218 s_envPathsValid.store(
false, std::memory_order_release);
219 qCCritical(GStreamerLog) <<
error;
222void _unsetEnv(
const char *name)
224 if (qEnvironmentVariableIsSet(name)) {
226 qCDebug(GStreamerLog) <<
" unset" << name;
230void _setGstEnvIfExists(
const char *name,
const QString &path)
232 if (QFileInfo::exists(path)) {
233 _setGstEnv(name, path);
237bool _isExecutableFile(
const QString &path)
239 const QFileInfo fileInfo(path);
240 return fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable();
243QString _firstExistingPath(
const QStringList &paths)
245 for (
const QString &path : paths) {
246 if (QFileInfo::exists(path)) {
254#if defined(Q_OS_MACOS)
255QString _joinExistingPaths(
const QStringList &paths)
257 QStringList existing;
258 existing.reserve(paths.size());
260 for (
const QString &path : paths) {
261 if (QFileInfo::exists(path) && !existing.contains(path)) {
262 existing.append(path);
266 return existing.join(QDir::listSeparator());
270void _clearManagedGstEnvVars()
272 static constexpr const char *varsToUnset[] = {
276 "GST_PTP_HELPER_1_0",
278 "GST_PLUGIN_SCANNER_1_0",
279 "GST_PLUGIN_SCANNER",
280 "GST_PLUGIN_SYSTEM_PATH_1_0",
281 "GST_PLUGIN_SYSTEM_PATH",
282 "GST_PLUGIN_PATH_1_0",
286 for (
const char *name : varsToUnset) {
291void _setGstEnvIfExecutable(
const char *name,
const QString &path)
293 if (_isExecutableFile(path)) {
294 _setGstEnv(name, path);
301void _sanitizePythonEnvForScanner()
303 static constexpr const char *varsToUnset[] = {
312 for (
const char *name : varsToUnset) {
316 _setGstEnv(
"PYTHONNOUSERSITE", QStringLiteral(
"1"));
319void _applyGstEnvVars(
const QString &pluginDir,
const QString &gioModDir,
320 const QString &scannerPath,
const QString &ptpPath)
322 qCDebug(GStreamerLog) <<
"Applying GStreamer environment:";
324 _sanitizePythonEnvForScanner();
325 _clearManagedGstEnvVars();
326 _setGstEnv(
"GST_REGISTRY_REUSE_PLUGIN_SCANNER", QStringLiteral(
"no"));
327 _setGstEnv(
"GST_REGISTRY_FORK", QStringLiteral(
"no"));
328 _setGstEnvIfExists(
"GIO_EXTRA_MODULES", gioModDir);
329 _setGstEnvIfExecutable(
"GST_PTP_HELPER_1_0", ptpPath);
330 _setGstEnvIfExecutable(
"GST_PTP_HELPER", ptpPath);
331 _setGstEnvIfExecutable(
"GST_PLUGIN_SCANNER_1_0", scannerPath);
332 _setGstEnvIfExecutable(
"GST_PLUGIN_SCANNER", scannerPath);
333 _setGstEnv(
"GST_PLUGIN_SYSTEM_PATH_1_0", pluginDir);
334 _setGstEnv(
"GST_PLUGIN_SYSTEM_PATH", pluginDir);
335 _setGstEnv(
"GST_PLUGIN_PATH_1_0", pluginDir);
336 _setGstEnv(
"GST_PLUGIN_PATH", pluginDir);
339#if defined(Q_OS_LINUX)
340bool _systemGioIsNew()
345 static constexpr const char *kGioSoPaths[] = {
347 "/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0",
348 "/usr/lib/aarch64-linux-gnu/libgio-2.0.so.0",
349 "/usr/lib/arm-linux-gnueabihf/libgio-2.0.so.0",
350 "/usr/lib64/libgio-2.0.so.0",
351 "/usr/lib/libgio-2.0.so.0",
354 for (
const char *path : kGioSoPaths) {
355 void *handle = dlopen(path, RTLD_LAZY | RTLD_NOLOAD);
357 handle = dlopen(path, RTLD_LAZY);
362 const bool found = (dlsym(handle,
"g_task_set_static_name") !=
nullptr);
370void _applyGioCompatOverride(
const QString &gioModDir)
372 if (gioModDir.isEmpty()) {
378 if (_systemGioIsNew()) {
379 _unsetEnv(
"GIO_EXTRA_MODULES");
380 _setGstEnv(
"GIO_MODULE_DIR", gioModDir);
381 _setGstEnv(
"GIO_USE_VFS", QStringLiteral(
"local"));
386void _warnIfScannerMissing(
const QString &platformLabel,
const QString &scannerPath)
388 if (scannerPath.isEmpty()) {
389 qCWarning(GStreamerLog) <<
"GStreamer:" << platformLabel
390 <<
"bundled gst-plugin-scanner not found; GStreamer will use in-process scanning";
391 }
else if (!_isExecutableFile(scannerPath)) {
392 qCWarning(GStreamerLog) <<
"GStreamer:" << platformLabel
393 <<
"gst-plugin-scanner is not executable:" << scannerPath;
397#if defined(Q_OS_MACOS)
398bool _validateMacBundlePaths(
const QString &bundleFrameworkRoot,
399 const QString &pluginDirs,
400 const QString &scannerPath)
402 if (pluginDirs.isEmpty()) {
403 _setEnvValidationError(QStringLiteral(
404 "GStreamer: bundled macOS framework found but plugin directory is missing under %1")
405 .arg(bundleFrameworkRoot));
409 _warnIfScannerMissing(QStringLiteral(
"macOS framework"), scannerPath);
414bool _validateBundledDesktopPaths(
const QString &platformLabel,
415 const QString &pluginDirs,
416 const QString &scannerPath)
418 if (pluginDirs.isEmpty()) {
419 _setEnvValidationError(QStringLiteral(
420 "GStreamer: %1 bundled plugin directory is missing.")
421 .arg(platformLabel));
425 _warnIfScannerMissing(platformLabel, scannerPath);
433 _resetEnvValidation();
435 const QString appDir = QCoreApplication::applicationDirPath();
436 qCDebug(GStreamerLog) <<
"App directory:" << appDir;
438#if defined(Q_OS_MACOS)
439 const QString frameworkDir = _cleanJoin(appDir,
"../Frameworks/GStreamer.framework");
440 QString rootDir = _firstExistingPath({
441 _cleanJoin(frameworkDir,
"Versions/1.0"),
442 _cleanJoin(frameworkDir,
"Versions/Current"),
445 if (rootDir.isEmpty()) {
446 rootDir = _cleanJoin(frameworkDir,
"Versions/1.0");
449#if defined(QGC_GST_MACOS_FRAMEWORK)
451 const QString pluginDirs = _joinExistingPaths({
452 _cleanJoin(rootDir,
"lib/gstreamer-1.0"),
453 _cleanJoin(appDir,
"../lib/gstreamer-1.0"),
455 const QString gioMod = _firstExistingPath({
456 _cleanJoin(rootDir,
"lib/gio/modules"),
457 _cleanJoin(appDir,
"../lib/gio/modules"),
461 const QString pluginDirs = _joinExistingPaths({
462 _cleanJoin(appDir,
"../lib/gstreamer-1.0"),
463 _cleanJoin(rootDir,
"lib/gstreamer-1.0"),
465 const QString gioMod = _firstExistingPath({
466 _cleanJoin(appDir,
"../lib/gio/modules"),
467 _cleanJoin(rootDir,
"lib/gio/modules"),
471 const QString scanner = _firstExistingPath({
472 _cleanJoin(appDir,
"../libexec/gstreamer-1.0/gst-plugin-scanner"),
473 _cleanJoin(rootDir,
"libexec/gstreamer-1.0/gst-plugin-scanner"),
475 const QString ptp = _firstExistingPath({
476 _cleanJoin(appDir,
"../libexec/gstreamer-1.0/gst-ptp-helper"),
477 _cleanJoin(rootDir,
"libexec/gstreamer-1.0/gst-ptp-helper"),
479 const bool hasBundledFramework = QFileInfo::exists(frameworkDir);
481 bool validBundlePaths =
true;
482 if (!pluginDirs.isEmpty()) {
483 validBundlePaths = _validateBundledDesktopPaths(QStringLiteral(
"macOS"), pluginDirs, scanner);
485 if (hasBundledFramework) {
486 validBundlePaths = validBundlePaths && _validateMacBundlePaths(rootDir, pluginDirs, scanner);
489 if (!pluginDirs.isEmpty() && validBundlePaths) {
490 _applyGstEnvVars(pluginDirs, gioMod, scanner, ptp);
493#if defined(QGC_GST_MACOS_FRAMEWORK)
494 if (hasBundledFramework) {
495 _setGstEnv(
"GTK_PATH", rootDir);
499#elif defined(Q_OS_WIN)
500 const QString libDir = _cleanJoin(appDir,
"../lib");
501 const QString pluginDir = _cleanJoin(libDir,
"gstreamer-1.0");
502 const QString gioMod = _cleanJoin(libDir,
"gio/modules");
503 const QString libexecDir = _cleanJoin(appDir,
"../libexec");
504 const QString scanner = _cleanJoin(libexecDir,
"gstreamer-1.0/gst-plugin-scanner.exe");
505 const QString ptp = _cleanJoin(libexecDir,
"gstreamer-1.0/gst-ptp-helper.exe");
507 if (QFileInfo::exists(pluginDir)
508 && _validateBundledDesktopPaths(QStringLiteral(
"Windows"), pluginDir, scanner)) {
509 _applyGstEnvVars(pluginDir, gioMod, scanner, ptp);
514 const QByteArray curPath = qgetenv(
"PATH");
515 const QByteArray binDir = QDir::toNativeSeparators(appDir).toUtf8();
516 if (!curPath.split(
';').contains(binDir)) {
517 qputenv(
"PATH", binDir +
";" + curPath);
521#elif defined(Q_OS_ANDROID)
526 const QString filesDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
527 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
529 if (!filesDir.isEmpty()) {
530 _setGstEnv(
"HOME", filesDir);
531 _setGstEnv(
"FONTCONFIG_PATH", _cleanJoin(filesDir,
"fontconfig"));
532 _setGstEnv(
"CA_CERTIFICATES", _cleanJoin(filesDir,
"ssl/certs/ca-certificates.crt"));
533 _setGstEnv(
"XDG_DATA_DIRS", filesDir);
534 _setGstEnv(
"XDG_CONFIG_DIRS", filesDir);
535 _setGstEnv(
"XDG_CONFIG_HOME", filesDir);
536 _setGstEnv(
"XDG_DATA_HOME", filesDir);
539 if (!cacheDir.isEmpty()) {
540 _setGstEnv(
"TMP", cacheDir);
541 _setGstEnv(
"TEMP", cacheDir);
542 _setGstEnv(
"TMPDIR", cacheDir);
543 _setGstEnv(
"XDG_CACHE_HOME", cacheDir);
544 _setGstEnv(
"XDG_RUNTIME_DIR", cacheDir);
545 _setGstEnv(
"GST_REGISTRY", _cleanJoin(cacheDir,
"registry.bin"));
548 _setGstEnv(
"GST_REGISTRY_REUSE_PLUGIN_SCANNER", QStringLiteral(
"no"));
551#elif defined(Q_OS_LINUX)
554 if (!qEnvironmentVariableIsSet(
"GST_PLUGIN_PATH_1_0")
555 && !qEnvironmentVariableIsSet(
"GST_PLUGIN_PATH")
556 && !qEnvironmentVariableIsSet(
"GST_PLUGIN_SYSTEM_PATH_1_0")
557 && !qEnvironmentVariableIsSet(
"GST_PLUGIN_SYSTEM_PATH")) {
558 const QString libDir = _cleanJoin(appDir,
"../lib");
559 const QString libexecDir = _cleanJoin(appDir,
"../libexec");
560 const QString pluginDir = _cleanJoin(libDir,
"gstreamer-1.0");
561 const QString gioMod = _cleanJoin(libDir,
"gio/modules");
562 const QString scanner = _firstExistingPath({
563 _cleanJoin(libDir,
"gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"),
564 _cleanJoin(libexecDir,
"gstreamer-1.0/gst-plugin-scanner"),
566 const QString ptp = _firstExistingPath({
567 _cleanJoin(libDir,
"gstreamer1.0/gstreamer-1.0/gst-ptp-helper"),
568 _cleanJoin(libexecDir,
"gstreamer-1.0/gst-ptp-helper"),
571 if (QFileInfo::exists(pluginDir)
572 && _validateBundledDesktopPaths(QStringLiteral(
"Linux"), pluginDir, scanner)) {
573 _applyGstEnvVars(pluginDir, gioMod, scanner, ptp);
574 _applyGioCompatOverride(gioMod);
583 GstRegistry *registry = gst_registry_get();
585 qCCritical(GStreamerLog) <<
"Failed to get GStreamer registry";
589 GList *plugins = gst_registry_get_plugin_list(registry);
591 qCDebug(GStreamerLog) <<
"Installed GStreamer plugins:";
592 for (GList *node = plugins; node !=
nullptr; node = node->next) {
593 GstPlugin *plugin =
static_cast<GstPlugin*
>(node->data);
595 qCDebug(GStreamerLog) <<
" " << gst_plugin_get_name(plugin)
596 << gst_plugin_get_version(plugin);
599 gst_plugin_list_free(plugins);
605 static constexpr const char *requiredPlugins[] = {
606 "qgc",
"coreelements",
"playback",
"rtp",
"rtpmanager",
"rtsp",
"tcp",
"udp",
608 for (
const char *name : requiredPlugins) {
609 GstPlugin *plugin = gst_registry_find_plugin(registry, name);
611 qCCritical(GStreamerLog) <<
"Required QGC plugin not found:" << name;
615 gst_clear_object(&plugin);
619 const QByteArray pluginPath = qEnvironmentVariableIsSet(
"GST_PLUGIN_PATH_1_0")
620 ? qgetenv(
"GST_PLUGIN_PATH_1_0")
621 : qgetenv(
"GST_PLUGIN_PATH");
623 if (!pluginPath.isEmpty()) {
624 qCCritical(GStreamerLog) <<
"Check GST_PLUGIN_PATH=" << pluginPath;
626 qCCritical(GStreamerLog) <<
"GST_PLUGIN_PATH is not set";
629 GList *allPlugins = gst_registry_get_plugin_list(registry);
630 for (GList *node = allPlugins; node !=
nullptr; node = node->next) {
631 GstPlugin *p =
static_cast<GstPlugin*
>(node->data);
633 const gchar *desc = gst_plugin_get_description(p);
634 const gchar *filename = gst_plugin_get_filename(p);
635 if (desc && g_str_has_prefix(desc,
"BLACKLIST")) {
636 qCWarning(GStreamerLog) <<
"Blacklisted plugin:" << gst_plugin_get_name(p)
637 <<
"file:" << (filename ? filename :
"(null)");
640 gst_plugin_list_free(allPlugins);
642 static constexpr const char *envDiagnostics[] = {
643 "GST_PLUGIN_PATH",
"GST_PLUGIN_PATH_1_0",
644 "GST_PLUGIN_SYSTEM_PATH",
"GST_PLUGIN_SYSTEM_PATH_1_0",
645 "GST_PLUGIN_SCANNER",
"GST_PLUGIN_SCANNER_1_0",
646 "GST_REGISTRY_REUSE_PLUGIN_SCANNER",
648 qCCritical(GStreamerLog) <<
"GStreamer environment diagnostics:";
649 for (
const char *var : envDiagnostics) {
650 const QByteArray val = qgetenv(var);
651 qCCritical(GStreamerLog) <<
" " << var <<
"=" << (val.isEmpty() ?
"(unset)" : val.constData());
658void _logDecoderRanks()
660 GList *factories = gst_element_factory_list_get_elements(
661 static_cast<GstElementFactoryListType
>(GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO),
665 qCDebug(GStreamerDecoderRanksLog) <<
"No video decoder factories found";
669 factories = g_list_sort(factories, [](gconstpointer lhs, gconstpointer rhs) -> gint {
670 const guint lhsRank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(lhs));
671 const guint rhsRank = gst_plugin_feature_get_rank(GST_PLUGIN_FEATURE(rhs));
672 if (lhsRank != rhsRank) {
673 return (lhsRank > rhsRank) ? -1 : 1;
675 return g_strcmp0(gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(lhs)),
676 gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(rhs)));
679 qCDebug(GStreamerDecoderRanksLog) <<
"Video decoder ranks:";
680 for (GList *node = factories; node !=
nullptr; node = node->next) {
681 GstElementFactory *factory = GST_ELEMENT_FACTORY(node->data);
682 GstPluginFeature *feature = GST_PLUGIN_FEATURE(factory);
683 const gchar *featureName = gst_plugin_feature_get_name(feature);
684 const guint rank = gst_plugin_feature_get_rank(feature);
685 const gchar *klass = gst_element_factory_get_klass(factory);
688 GstPlugin *plugin = gst_plugin_feature_get_plugin(feature);
689 const gchar *pluginName = plugin ? gst_plugin_get_name(plugin) :
"?";
691 qCDebug(GStreamerDecoderRanksLog).noquote()
692 << QStringLiteral(
" [%1] %2/%3 rank=%4 (%5)")
693 .arg(isHw ? QStringLiteral(
"HW") : QStringLiteral(
"SW"),
694 QString::fromUtf8(pluginName),
695 QString::fromUtf8(featureName))
697 .arg(QString::fromUtf8(klass));
700 gst_object_unref(plugin);
704 gst_plugin_feature_list_free(factories);
707void _configureDebugLogging()
709 gst_debug_remove_log_function(gst_debug_log_default);
712 if (!qEnvironmentVariableIsEmpty(
"GST_DEBUG")) {
717 if (settings.contains(AppSettings::gstDebugLevelName)) {
718 const int level = qBound(0, settings.value(AppSettings::gstDebugLevelName).toInt(),
719 static_cast<int>(GST_LEVEL_MEMDUMP));
720 gst_debug_set_default_threshold(
static_cast<GstDebugLevel
>(level));
728 if (!gst_is_initialized()) {
731 const int clamped = qBound(0, level,
static_cast<int>(GST_LEVEL_MEMDUMP));
732 gst_debug_set_default_threshold(
static_cast<GstDebugLevel
>(clamped));
733 qCDebug(GStreamerLog) <<
"GStreamer debug threshold set to" << clamped;
743bool _initGstRuntime()
745 if (!s_envPathsValid.load(std::memory_order_acquire)) {
746 const QMutexLocker locker(&s_envPathsMutex);
747 qCCritical(GStreamerLog) <<
"Invalid GStreamer environment configuration:" << s_envPathsError;
753 const QStringList args = QCoreApplication::arguments();
754 QByteArrayList argStorage;
755 argStorage.reserve(args.size());
756 for (
const QString &arg : args) {
757 argStorage.append(arg.toUtf8());
760 QVarLengthArray<char*, 16> argv;
761 for (QByteArray &arg : argStorage) {
762 argv.append(arg.data());
765 int argc = argv.size();
766 char **argvPtr = argv.data();
767 GError *
error =
nullptr;
773 if (!gst_init_check(&argc, &argvPtr, &
error)) {
774 qCCritical(GStreamerLog) <<
"Failed to initialize GStreamer:"
775 << (
error ?
error->message :
"unknown error");
776 g_clear_error(&
error);
791 if (!gst_is_initialized()) {
792 qCCritical(GStreamerLog) <<
"completeInit called but gst_init() has not been called";
796 _configureDebugLogging();
798 guint major, minor, micro, nano;
799 gst_version(&major, &minor, µ, &nano);
800 qCDebug(GStreamerLog) <<
"GStreamer initialized:" << major <<
"." << minor <<
"." << micro;
802#ifdef QGC_GST_BUILD_VERSION_MAJOR
803 if (major != QGC_GST_BUILD_VERSION_MAJOR || minor != QGC_GST_BUILD_VERSION_MINOR) {
804 qCWarning(GStreamerLog) <<
"GStreamer version mismatch: built against"
805 << QGC_GST_BUILD_VERSION_MAJOR <<
"." << QGC_GST_BUILD_VERSION_MINOR
806 <<
"but runtime is" << major <<
"." << minor <<
"." << micro;
812 if (!_verifyPlugins()) {
813 qCCritical(GStreamerLog) <<
"Plugin verification failed";
819 GstElementFactory *appsinkFactory = gst_element_factory_find(
"appsink");
820 if (!appsinkFactory) {
821 qCCritical(GStreamerLog) <<
"appsink factory not found — videoconvert→appsink path unavailable";
824 qCDebug(GStreamerLog) <<
"appsink factory available (videoconvert → appsink → QVideoSink)";
825 gst_object_unref(appsinkFactory);
828 qCCritical(GStreamerLog)
829 <<
"GStreamer external plugin loader failed. Check GST_PLUGIN_SCANNER and bundled runtime paths.";
843 gst_debug_remove_log_function(gst_debug_log_default);
845 if (!_initGstRuntime()) {
863 const QByteArray conversionElement = vs->videoConversionElement()->rawValue().toString().toUtf8();
864 const gboolean disablePar = vs->disablePixelAspectRatio()->rawValue().toBool() ? TRUE : FALSE;
865#if defined(QGC_HAS_ANY_GPU_PATH)
868 const bool forceCpu = vs->forceCpuVideoPath()->rawValue().toBool();
869 const bool swDecoder = vs->forceVideoDecoder()->rawValue().toInt()
871 const bool gpuZeroCopy = !forceCpu && !swDecoder;
872 if (GstElementFactory *factory = gst_element_factory_find(
"qgcvideosinkbin")) {
873 videoSinkBin = gst_element_factory_create_full(factory,
874 "gpu-zerocopy", gpuZeroCopy ? TRUE : FALSE,
875 "conversion-element",
876 conversionElement.isEmpty() ? nullptr : conversionElement.constData(),
877 "disable-par", disablePar,
879 gst_object_unref(factory);
882 if (GstElementFactory *factory = gst_element_factory_find(
"qgcvideosinkbin")) {
883 videoSinkBin = gst_element_factory_create_full(factory,
884 "conversion-element",
885 conversionElement.isEmpty() ?
nullptr : conversionElement.constData(),
886 "disable-par", disablePar,
888 gst_object_unref(factory);
892 qCCritical(GStreamerLog) <<
"gst_element_factory_make('qgcvideosinkbin') failed";
901 gst_clear_object(&videoSink);
911 if (!sinkBin || !videoSink || !adapterParent) {
914 qCWarning(GStreamerLog) <<
"setupAppSinkAdapter: null sinkBin, videoSink, or adapterParent";
922 QString(), Qt::FindDirectChildrenOnly);
935#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH)
936 GstGlContextBridge::rearm();
940 if (!adapter->setup(GST_ELEMENT(sinkBin), videoSink)) {
941 qCCritical(GStreamerLog) <<
"GstAppSinkAdapter::setup() failed";
942 adapter->deleteLater();
950 const qreal refreshHz = QGuiApplication::primaryScreen()
951 ? QGuiApplication::primaryScreen()->refreshRate() : 0.0;
952 if (refreshHz >= 1.0) {
953 adapter->setRefreshRate(refreshHz);
960 adapter->setSmoothingEnabled(
true, refreshHz);
963 if (
auto *gstReceiver = qobject_cast<GstVideoReceiver *>(adapterParent)) {
966 Qt::DirectConnection);
973 if (!adapterParent)
return;
975 QString(), Qt::FindDirectChildrenOnly);