QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GStreamerEnvironment.cc
Go to the documentation of this file.
2
3#include <QtCore/QCoreApplication>
4#include <QtCore/QDir>
5#include <QtCore/QFile>
6#include <QtCore/QFileInfo>
7#include <QtCore/QMutex>
8#include <QtCore/QStandardPaths>
9#include <QtCore/QStringList>
10#include <QtCore/QThread>
11
12#include "QGCLoggingCategory.h"
13
14#ifdef Q_OS_LINUX
15#include <dlfcn.h>
16#endif
17
18#ifdef Q_OS_ANDROID
19#include <QtCore/QCoreApplication>
20#include <QtCore/QJniEnvironment>
21#include <QtCore/QJniObject>
22#include <android/asset_manager.h>
23#include <android/asset_manager_jni.h>
24#endif
25
26#include <gst/gst.h>
27
28QGC_LOGGING_CATEGORY(GStreamerEnvLog, "Video.GStreamer.Environment")
29
30namespace {
31
32static bool s_envPathsValid = true;
33static QString s_envPathsError;
34// Serializes the whole env mutation: qputenv/setenv aren't thread-safe vs gst_init's getenv.
35// Also guards s_envPathsValid/s_envPathsError — every access is under this lock (plain types suffice).
36static QMutex s_setEnvVarsMutex;
37
38// Single source of truth for the managed plugin-path/scanner vars: keeps _clearManagedGstEnvVars
39// and logDiagnostics from drifting. Each site layers its own extras (GIO/PTP, registry) on top.
40constexpr const char* kManagedGstPathVars[] = {
41 "GST_PLUGIN_PATH", "GST_PLUGIN_PATH_1_0", "GST_PLUGIN_SYSTEM_PATH", "GST_PLUGIN_SYSTEM_PATH_1_0",
42 "GST_PLUGIN_SCANNER", "GST_PLUGIN_SCANNER_1_0",
43};
44
45// Caller must hold s_setEnvVarsMutex.
46void _resetEnvValidation()
47{
48 s_envPathsError.clear();
49 s_envPathsValid = true;
50}
51
52QString _cleanJoin(const QString& base, const QString& relative)
53{
54 return QDir::cleanPath(QDir(base).filePath(relative));
55}
56
57void _setGstEnv(const char* name, const QString& value)
58{
59 qputenv(name, value.toUtf8());
60 qCDebug(GStreamerEnvLog) << " " << name << "=" << value;
61}
62
63// Set @p var to the first existing candidate, but only if unset (explicit user/system value wins).
64// @p nativePath uses native separators + 8-bit encoding, required for OpenSSL SSL_CERT_FILE on Windows.
65[[maybe_unused]] void _setCaBundleIfUnset(const char* var, const QStringList& candidates, bool nativePath = false)
66{
67 if (!qEnvironmentVariableIsEmpty(var)) {
68 return;
69 }
70 for (const QString& path : candidates) {
71 if (QFileInfo::exists(path)) {
72 if (nativePath) {
73 const QByteArray encoded = QFile::encodeName(QDir::toNativeSeparators(path));
74 qputenv(var, encoded);
75 qCDebug(GStreamerEnvLog) << " " << var << "=" << encoded;
76 } else {
77 _setGstEnv(var, path);
78 }
79 return;
80 }
81 }
82}
83
84[[maybe_unused]] void _setTmpDirVars(const QString& dir)
85{
86 _setGstEnv("TMP", dir);
87 _setGstEnv("TEMP", dir);
88 _setGstEnv("TMPDIR", dir);
89}
90
91#ifdef Q_OS_ANDROID
92// Extract an APK asset to destPath; skip rewrite when sizes already match
93// (asset bytes are immutable per APK build, so size match ⇒ same content).
94bool _extractApkAsset(const char* assetPath, const QString& destPath)
95{
96 QJniObject ctx = QNativeInterface::QAndroidApplication::context();
97 if (!ctx.isValid()) {
98 qCWarning(GStreamerEnvLog) << "Cannot resolve Android Context for asset extraction";
99 return false;
100 }
101
102 QJniObject jAssetMgr = ctx.callObjectMethod("getAssets", "()Landroid/content/res/AssetManager;");
103 if (!jAssetMgr.isValid()) {
104 qCWarning(GStreamerEnvLog) << "Context.getAssets() returned null";
105 return false;
106 }
107
108 QJniEnvironment env;
109 AAssetManager* am = AAssetManager_fromJava(env.jniEnv(), jAssetMgr.object());
110 if (!am) {
111 qCWarning(GStreamerEnvLog) << "AAssetManager_fromJava failed";
112 return false;
113 }
114
115 AAsset* asset = AAssetManager_open(am, assetPath, AASSET_MODE_BUFFER);
116 if (!asset) {
117 qCDebug(GStreamerEnvLog) << "APK asset not present:" << assetPath;
118 return false;
119 }
120 const off_t assetLen = AAsset_getLength(asset);
121
122 const QFileInfo destInfo(destPath);
123 if (destInfo.exists() && destInfo.size() == assetLen) {
124 AAsset_close(asset);
125 return true;
126 }
127
128 QDir().mkpath(destInfo.absolutePath());
129 QFile out(destPath);
130 if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
131 qCWarning(GStreamerEnvLog) << "Cannot open" << destPath << "for write:" << out.errorString();
132 AAsset_close(asset);
133 return false;
134 }
135
136 const void* buf = AAsset_getBuffer(asset);
137 bool ok = false;
138 if (buf && assetLen > 0) {
139 ok = (out.write(static_cast<const char*>(buf), assetLen) == assetLen);
140 }
141 out.close();
142 // close() flushes; a flush failure surfaces via error() after close.
143 ok = ok && (out.error() == QFileDevice::NoError);
144 AAsset_close(asset);
145 if (!ok) {
146 qCWarning(GStreamerEnvLog) << "Failed writing asset" << assetPath << "to" << destPath;
147 QFile::remove(destPath);
148 }
149 return ok;
150}
151#endif
152
153#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
154
155// Caller must hold s_setEnvVarsMutex.
156void _setEnvValidationError(const QString& error)
157{
158 s_envPathsError = error;
159 s_envPathsValid = false;
160 qCCritical(GStreamerEnvLog) << error;
161}
162
163void _unsetEnv(const char* name)
164{
165 if (qEnvironmentVariableIsSet(name)) {
166 qunsetenv(name);
167 qCDebug(GStreamerEnvLog) << " unset" << name;
168 }
169}
170
171void _setGstEnvIfExists(const char* name, const QString& path)
172{
173 if (QFileInfo::exists(path)) {
174 _setGstEnv(name, path);
175 }
176}
177
178bool _isExecutableFile(const QString& path)
179{
180 const QFileInfo fileInfo(path);
181 return fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable();
182}
183
184QString _firstExistingPath(const QStringList& paths)
185{
186 for (const QString& path : paths) {
187 if (QFileInfo::exists(path)) {
188 return path;
189 }
190 }
191
192 return {};
193}
194
195#if defined(Q_OS_MACOS)
196QString _joinExistingPaths(const QStringList& paths)
197{
198 QStringList existing;
199 existing.reserve(paths.size());
200
201 for (const QString& path : paths) {
202 if (QFileInfo::exists(path) && !existing.contains(path)) {
203 existing.append(path);
204 }
205 }
206
207 return existing.join(QDir::listSeparator());
208}
209#endif
210
211void _clearManagedGstEnvVars()
212{
213 static constexpr const char* gioPtpVars[] = {
214 "GIO_EXTRA_MODULES", "GIO_MODULE_DIR", "GIO_USE_VFS", "GST_PTP_HELPER_1_0", "GST_PTP_HELPER",
215 };
216
217 for (const char* name : gioPtpVars) {
218 _unsetEnv(name);
219 }
220 for (const char* name : kManagedGstPathVars) {
221 _unsetEnv(name);
222 }
223}
224
225void _setGstEnvIfExecutable(const char* name, const QString& path)
226{
227 if (_isExecutableFile(path)) {
228 _setGstEnv(name, path);
229 } else {
230 _unsetEnv(name);
231 }
232}
233
234void _sanitizePythonEnvForScanner()
235{
236 static constexpr const char* varsToUnset[] = {
237 "PYTHONHOME", "PYTHONPATH", "VIRTUAL_ENV", "CONDA_PREFIX", "CONDA_DEFAULT_ENV", "PYTHONUSERBASE",
238 };
239
240 for (const char* name : varsToUnset) {
241 _unsetEnv(name);
242 }
243
244 _setGstEnv("PYTHONNOUSERSITE", QStringLiteral("1"));
245}
246
247void _applyGstEnvVars(const QString& pluginDir, const QString& gioModDir, const QString& scannerPath,
248 const QString& ptpPath)
249{
250 qCDebug(GStreamerEnvLog) << "Applying GStreamer environment:";
251
252 _sanitizePythonEnvForScanner();
253 _clearManagedGstEnvVars();
254 _setGstEnv("GST_REGISTRY_REUSE_PLUGIN_SCANNER", QStringLiteral("no"));
255 _setGstEnv("GST_REGISTRY_FORK", QStringLiteral("no"));
256 _setGstEnvIfExists("GIO_EXTRA_MODULES", gioModDir);
257 _setGstEnvIfExecutable("GST_PTP_HELPER_1_0", ptpPath);
258 _setGstEnvIfExecutable("GST_PTP_HELPER", ptpPath);
259 _setGstEnvIfExecutable("GST_PLUGIN_SCANNER_1_0", scannerPath);
260 _setGstEnvIfExecutable("GST_PLUGIN_SCANNER", scannerPath);
261 _setGstEnv("GST_PLUGIN_SYSTEM_PATH_1_0", pluginDir);
262 _setGstEnv("GST_PLUGIN_SYSTEM_PATH", pluginDir);
263 _setGstEnv("GST_PLUGIN_PATH_1_0", pluginDir);
264 _setGstEnv("GST_PLUGIN_PATH", pluginDir);
265}
266
267#if defined(Q_OS_LINUX)
268bool _systemGioIsNew()
269{
270 // Bare soname first (ldconfig/LD_LIBRARY_PATH, works on NixOS/Guix non-FHS), then
271 // hardcoded paths where the bare name fails.
272 static constexpr const char* kGioSoPaths[] = {
273 "libgio-2.0.so.0",
274 "/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0",
275 "/usr/lib/aarch64-linux-gnu/libgio-2.0.so.0",
276 "/usr/lib/arm-linux-gnueabihf/libgio-2.0.so.0",
277 "/usr/lib64/libgio-2.0.so.0",
278 "/usr/lib/libgio-2.0.so.0",
279 };
280
281 for (const char* path : kGioSoPaths) {
282 void* handle = dlopen(path, RTLD_LAZY | RTLD_NOLOAD);
283 if (!handle) {
284 handle = dlopen(path, RTLD_LAZY);
285 }
286 if (!handle) {
287 continue;
288 }
289 const bool found = (dlsym(handle, "g_task_set_static_name") != nullptr);
290 dlclose(handle);
291 return found;
292 }
293
294 return false;
295}
296
297void _applyGioCompatOverride(const QString& gioModDir)
298{
299 if (gioModDir.isEmpty()) {
300 return;
301 }
302
303 // GIO 2.76+ needs bundled modules via GIO_MODULE_DIR with VFS forced local (AppImage launcher logic).
304 if (_systemGioIsNew()) {
305 _unsetEnv("GIO_EXTRA_MODULES");
306 _setGstEnv("GIO_MODULE_DIR", gioModDir);
307 _setGstEnv("GIO_USE_VFS", QStringLiteral("local"));
308 }
309}
310#endif
311
312void _warnIfScannerMissing(const QString& platformLabel, const QString& scannerPath)
313{
314 if (scannerPath.isEmpty()) {
315 qCWarning(GStreamerEnvLog) << "GStreamer:" << platformLabel
316 << "bundled gst-plugin-scanner not found; GStreamer will use in-process scanning";
317 } else if (!_isExecutableFile(scannerPath)) {
318 qCWarning(GStreamerEnvLog) << "GStreamer:" << platformLabel
319 << "gst-plugin-scanner is not executable:" << scannerPath;
320 }
321}
322
323#if defined(Q_OS_MACOS)
324void _reportMissingMacFrameworkPlugins(const QString& bundleFrameworkRoot)
325{
326 _setEnvValidationError(
327 QStringLiteral("GStreamer: bundled macOS framework found but plugin directory is missing under %1")
328 .arg(bundleFrameworkRoot));
329}
330#endif
331
332bool _validateBundledDesktopPaths(const QString& platformLabel, const QString& pluginDirs, const QString& scannerPath)
333{
334 if (pluginDirs.isEmpty()) {
335 _setEnvValidationError(QStringLiteral("GStreamer: %1 bundled plugin directory is missing.").arg(platformLabel));
336 return false;
337 }
338
339 _warnIfScannerMissing(platformLabel, scannerPath);
340 return true;
341}
342
343#endif // !Q_OS_ANDROID && !Q_OS_IOS
344
345void _setGstEnvVars()
346{
347 _resetEnvValidation();
348
349 const QString appDir = QCoreApplication::applicationDirPath();
350 qCDebug(GStreamerEnvLog) << "App directory:" << appDir;
351
352#if defined(Q_OS_MACOS)
353 const QString frameworkDir = _cleanJoin(appDir, "../Frameworks/GStreamer.framework");
354 QString rootDir = _firstExistingPath({
355 _cleanJoin(frameworkDir, "Versions/1.0"),
356 _cleanJoin(frameworkDir, "Versions/Current"),
357 frameworkDir,
358 });
359 if (rootDir.isEmpty()) {
360 rootDir = _cleanJoin(frameworkDir, "Versions/1.0");
361 }
362
363#if defined(QGC_GST_MACOS_FRAMEWORK)
364 // Framework builds prefer framework paths over app-relative paths
365 const QString pluginDirs = _joinExistingPaths({
366 _cleanJoin(rootDir, "lib/gstreamer-1.0"),
367 _cleanJoin(appDir, "../lib/gstreamer-1.0"),
368 });
369 const QString gioMod = _firstExistingPath({
370 _cleanJoin(rootDir, "lib/gio/modules"),
371 _cleanJoin(appDir, "../lib/gio/modules"),
372 });
373#else
374 // Non-framework (Homebrew) builds prefer app-relative paths
375 const QString pluginDirs = _joinExistingPaths({
376 _cleanJoin(appDir, "../lib/gstreamer-1.0"),
377 _cleanJoin(rootDir, "lib/gstreamer-1.0"),
378 });
379 const QString gioMod = _firstExistingPath({
380 _cleanJoin(appDir, "../lib/gio/modules"),
381 _cleanJoin(rootDir, "lib/gio/modules"),
382 });
383#endif
384
385 const QString scanner = _firstExistingPath({
386 _cleanJoin(appDir, "../libexec/gstreamer-1.0/gst-plugin-scanner"),
387 _cleanJoin(rootDir, "libexec/gstreamer-1.0/gst-plugin-scanner"),
388 });
389 const QString ptp = _firstExistingPath({
390 _cleanJoin(appDir, "../libexec/gstreamer-1.0/gst-ptp-helper"),
391 _cleanJoin(rootDir, "libexec/gstreamer-1.0/gst-ptp-helper"),
392 });
393 const bool hasBundledFramework = QFileInfo::exists(frameworkDir);
394
395 bool validBundlePaths = true;
396 if (pluginDirs.isEmpty()) {
397 if (hasBundledFramework) {
398 _reportMissingMacFrameworkPlugins(rootDir);
399 validBundlePaths = false;
400 }
401 } else {
402 validBundlePaths = _validateBundledDesktopPaths(QStringLiteral("macOS"), pluginDirs, scanner);
403 }
404
405 if (!pluginDirs.isEmpty() && validBundlePaths) {
406 _applyGstEnvVars(pluginDirs, gioMod, scanner, ptp);
407 }
408
409#if defined(QGC_GST_MACOS_FRAMEWORK)
410 if (hasBundledFramework) {
411 _setGstEnv("GTK_PATH", rootDir);
412 }
413#endif
414
415 // libgioopenssl's compiled-in CA path is the Cerbero prefix; repoint OpenSSL at the
416 // bundled file (framework Versions/1.0/etc vs non-framework Contents/Resources/etc).
417 _setCaBundleIfUnset("SSL_CERT_FILE", {
418 _cleanJoin(rootDir, "etc/ssl/certs/ca-certificates.crt"),
419 _cleanJoin(appDir, "../Resources/etc/ssl/certs/ca-certificates.crt"),
420 });
421
422#elif defined(Q_OS_WIN)
423 const QString libDir = _cleanJoin(appDir, "../lib");
424 const QString pluginDir = _cleanJoin(libDir, "gstreamer-1.0");
425 const QString gioMod = _cleanJoin(libDir, "gio/modules");
426 const QString libexecDir = _cleanJoin(appDir, "../libexec");
427 const QString scanner = _cleanJoin(libexecDir, "gstreamer-1.0/gst-plugin-scanner.exe");
428 const QString ptp = _cleanJoin(libexecDir, "gstreamer-1.0/gst-ptp-helper.exe");
429
430 if (QFileInfo::exists(pluginDir) && _validateBundledDesktopPaths(QStringLiteral("Windows"), pluginDir, scanner)) {
431 _applyGstEnvVars(pluginDir, gioMod, scanner, ptp);
432
433 // Put the app's bin dir on PATH so child processes (gst-plugin-scanner.exe) can locate
434 // GStreamer DLLs installed alongside the main executable.
435 const QByteArray curPath = qgetenv("PATH");
436 const QByteArray binDir = QDir::toNativeSeparators(appDir).toUtf8();
437 bool alreadyOnPath = false;
438 for (const QByteArray &entry : curPath.split(';')) {
439 // Windows paths are case-insensitive; compare accordingly to avoid duplicate prepends.
440 if (entry.compare(binDir, Qt::CaseInsensitive) == 0) {
441 alreadyOnPath = true;
442 break;
443 }
444 }
445 if (!alreadyOnPath) {
446 qputenv("PATH", binDir + ";" + curPath);
447 }
448
449 // gioopenssl.dll's compiled-in CA path is relative to the Cerbero SDK root and breaks once
450 // detached; point OpenSSL at the bundled CA file (installed by gstreamer_install_windows_sdk).
451 _setCaBundleIfUnset("SSL_CERT_FILE", {_cleanJoin(appDir, "../etc/ssl/certs/ca-certificates.crt")},
452 /*nativePath=*/true);
453 }
454
455#elif defined(Q_OS_IOS)
456 // Static plugins — no GST_PLUGIN_PATH; bundle resources read-only, scratch in sandbox. CA
457 // bundle (MACOSX_PACKAGE_LOCATION) is consumed by qgc_load_gio_modules_and_ca() at static init.
458 {
459 const QString resources = appDir;
460 const QString docs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
461 const QString cache = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
462 const QString tmp = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
463
464 if (!tmp.isEmpty()) {
465 _setTmpDirVars(tmp);
466 }
467 if (!cache.isEmpty()) {
468 _setGstEnv("XDG_CACHE_HOME", cache);
469 _setGstEnv("XDG_CONFIG_HOME", cache);
470 // Writable-cache registry, no fork rescan: sandbox rejects fork, static plugins need no scanner.
471 _setGstEnv("GST_REGISTRY", _cleanJoin(cache, "registry.bin"));
472 _setGstEnv("GST_REGISTRY_FORK", QStringLiteral("no"));
473 }
474 // Tutorial 5 binds XDG_RUNTIME_DIR to the read-only bundle; libGStreamer never writes there on iOS.
475 if (!resources.isEmpty()) {
476 _setGstEnv("XDG_RUNTIME_DIR", resources);
477 _setGstEnv("XDG_DATA_DIRS", resources);
478 _setGstEnv("XDG_CONFIG_DIRS", resources);
479 _setGstEnv("XDG_DATA_HOME", resources);
480 }
481 if (!docs.isEmpty()) {
482 _setGstEnv("HOME", docs);
483 }
484
485 if (!resources.isEmpty()) {
486 _setCaBundleIfUnset("CA_CERTIFICATES", {_cleanJoin(resources, "ssl/certs/ca-certificates.crt")});
487 }
488 }
489
490#elif defined(Q_OS_ANDROID)
491 // Static plugins — no GST_PLUGIN_PATH; APK assets are read-only, so the CA bundle is extracted
492 // to filesDir on first launch (we run gst_init from C++, not Java's GStreamer.init).
493 {
494 const QString filesDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
495 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
496
497 if (!filesDir.isEmpty()) {
498 _setGstEnv("HOME", filesDir);
499 _setGstEnv("FONTCONFIG_PATH", _cleanJoin(filesDir, "fontconfig"));
500 const QString caBundle = _cleanJoin(filesDir, "ssl/certs/ca-certificates.crt");
501 if (_extractApkAsset("ssl/certs/ca-certificates.crt", caBundle) &&
502 qEnvironmentVariableIsEmpty("CA_CERTIFICATES")) {
503 _setGstEnv("CA_CERTIFICATES", caBundle);
504 }
505 _setGstEnv("XDG_DATA_DIRS", filesDir);
506 _setGstEnv("XDG_CONFIG_DIRS", filesDir);
507 _setGstEnv("XDG_CONFIG_HOME", filesDir);
508 _setGstEnv("XDG_DATA_HOME", filesDir);
509 }
510
511 if (!cacheDir.isEmpty()) {
512 _setTmpDirVars(cacheDir);
513 _setGstEnv("XDG_CACHE_HOME", cacheDir);
514 _setGstEnv("XDG_RUNTIME_DIR", cacheDir);
515 _setGstEnv("GST_REGISTRY", _cleanJoin(cacheDir, "registry.bin"));
516 }
517
518 _setGstEnv("GST_REGISTRY_REUSE_PLUGIN_SCANNER", QStringLiteral("no"));
519 }
520
521#elif defined(Q_OS_LINUX)
522 // AppRun sets GStreamer env vars before launch; apply fallbacks only with no external override.
523 if (!qEnvironmentVariableIsSet("GST_PLUGIN_PATH_1_0") && !qEnvironmentVariableIsSet("GST_PLUGIN_PATH") &&
524 !qEnvironmentVariableIsSet("GST_PLUGIN_SYSTEM_PATH_1_0") &&
525 !qEnvironmentVariableIsSet("GST_PLUGIN_SYSTEM_PATH")) {
526 const QString libDir = _cleanJoin(appDir, "../lib");
527 const QString libexecDir = _cleanJoin(appDir, "../libexec");
528 const QString pluginDir = _cleanJoin(libDir, "gstreamer-1.0");
529 const QString gioMod = _cleanJoin(libDir, "gio/modules");
530 const QString scanner = _firstExistingPath({
531 _cleanJoin(libDir, "gstreamer1.0/gstreamer-1.0/gst-plugin-scanner"),
532 _cleanJoin(libexecDir, "gstreamer-1.0/gst-plugin-scanner"),
533 });
534 const QString ptp = _firstExistingPath({
535 _cleanJoin(libDir, "gstreamer1.0/gstreamer-1.0/gst-ptp-helper"),
536 _cleanJoin(libexecDir, "gstreamer-1.0/gst-ptp-helper"),
537 });
538
539 if (QFileInfo::exists(pluginDir) && _validateBundledDesktopPaths(QStringLiteral("Linux"), pluginDir, scanner)) {
540 _applyGstEnvVars(pluginDir, gioMod, scanner, ptp);
541 _applyGioCompatOverride(gioMod);
542 }
543 }
544#endif
545}
546
547} // anonymous namespace
548
550
552{
553 // qputenv is not thread-safe against concurrent getenv; this must run on the GUI thread before
554 // the QtConcurrent init worker spawns. Warn loudly rather than corrupt the environment silently.
555 if (QCoreApplication::instance() && (QThread::currentThread() != QCoreApplication::instance()->thread())) {
556 qCWarning(GStreamerEnvLog) << "prepareEnvironment() called off the GUI thread; qputenv is not thread-safe";
557 }
558 const QMutexLocker setEnvLocker(&s_setEnvVarsMutex);
559 _setGstEnvVars();
560 return {s_envPathsValid, s_envPathsError};
561}
562
564{
565 // Echo GST_PLUGIN_PATH first (most-misconfigured); _1_0-suffixed wins per gstregistry.c lookup order.
566 const QByteArray pluginPath =
567 qEnvironmentVariableIsSet("GST_PLUGIN_PATH_1_0") ? qgetenv("GST_PLUGIN_PATH_1_0") : qgetenv("GST_PLUGIN_PATH");
568 if (!pluginPath.isEmpty()) {
569 qCCritical(GStreamerEnvLog) << "Check GST_PLUGIN_PATH=" << pluginPath;
570 } else {
571 qCCritical(GStreamerEnvLog) << "GST_PLUGIN_PATH is not set";
572 }
573
574 qCCritical(GStreamerEnvLog) << "GStreamer environment diagnostics:";
575 const auto dumpVar = [](const char* var) {
576 const QByteArray val = qgetenv(var);
577 qCCritical(GStreamerEnvLog) << " " << var << "=" << (val.isEmpty() ? "(unset)" : val.constData());
578 };
579 for (const char* var : kManagedGstPathVars) {
580 dumpVar(var);
581 }
582 dumpVar("GST_REGISTRY_REUSE_PLUGIN_SCANNER");
583}
584
585} // namespace GStreamer::Environment
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
ValidationResult prepareEnvironment()