3#include <QtCore/QCoreApplication>
6#include <QtCore/QFileInfo>
8#include <QtCore/QMutex>
9#include <QtCore/QPointer>
10#include <QtCore/QStandardPaths>
11#include <QtCore/QStringList>
12#include <QtGui/QGuiApplication>
13#include <QtGui/QScreen>
14#include <QtGui/QWindow>
15#include <QtMultimedia/QVideoSink>
16#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
17#include <QtQuick/QQuickItem>
18#include <QtQuick/QQuickWindow>
19#include <QtQuick/QSGRendererInterface>
21#include <QtCore/QJniEnvironment>
22#include <QtCore/QJniObject>
36#if defined(QGC_HAS_ANY_GPU_PATH)
51#include <android/asset_manager.h>
52#include <android/asset_manager_jni.h>
60#ifdef QGC_GST_STATIC_BUILD
63extern void gst_init_static_plugins(
void);
73void _registerPlugins()
77 static std::once_flag s_pluginsRegistered;
78 std::call_once(s_pluginsRegistered, [] {
79#ifdef QGC_GST_STATIC_BUILD
82 gst_init_static_plugins();
84 GST_PLUGIN_STATIC_REGISTER(qgc);
91bool requireFactory(
const char* name,
const char* hint)
95 qCCritical(GStreamerLog) << name <<
"factory not found —" << hint;
98 qCDebug(GStreamerLog) << name <<
"factory available";
104 GstRegistry* registry = gst_registry_get();
106 qCCritical(GStreamerLog) <<
"Failed to get GStreamer registry";
110 qCDebug(GStreamerLog) <<
"Installed GStreamer plugins:";
112 qCDebug(GStreamerLog) <<
" " << gst_plugin_get_name(plugin) << gst_plugin_get_version(plugin);
116 const auto hasPlugin = [registry](
const char* name) {
117 const GstObjectPtr plugin(GST_OBJECT(gst_registry_find_plugin(registry, name)));
118 return plugin !=
nullptr;
122 static constexpr std::array<const char*, 12> kRequiredPlugins = {
123 "qgc",
"coreelements",
"isomp4",
"matroska",
"multifile",
"opengl",
124 "playback",
"rtp",
"rtpmanager",
"rtsp",
"tcp",
"udp",
126 for (
const char* name : kRequiredPlugins) {
127 if (!hasPlugin(name)) {
128 qCCritical(GStreamerLog) <<
"Required GStreamer plugin not found:" << name;
133 if (!hasPlugin(
"videoconvertscale") && !(hasPlugin(
"videoconvert") && hasPlugin(
"videoscale"))) {
134 qCCritical(GStreamerLog) <<
"Required GStreamer plugin not found: videoconvertscale (or videoconvert+videoscale)";
142 const gchar* desc = gst_plugin_get_description(p);
143 if (!desc || !g_str_has_prefix(desc,
"BLACKLIST"))
145 const gchar* filename = gst_plugin_get_filename(p);
146 qCWarning(GStreamerLog) <<
"Blacklisted plugin:" << gst_plugin_get_name(p)
147 <<
"file:" << (filename ? filename :
"(null)");
166bool _initGstRuntime(
const QStringList& args,
const Environment::ValidationResult& env)
169 qCCritical(GStreamerLog) <<
"Invalid GStreamer environment configuration:" << env.error;
175 QByteArrayList argStorage;
176 argStorage.reserve(args.size());
177 for (
const QString& arg : args) {
178 argStorage.append(arg.toUtf8());
181 QVarLengthArray<char*, 16> argv;
182 for (QByteArray& arg : argStorage) {
183 argv.append(arg.data());
186 int argc = argv.size();
187 char** argvPtr = argv.data();
188 GError*
error =
nullptr;
190 if (!gst_init_check(&argc, &argvPtr, &
error)) {
191 qCCritical(GStreamerLog) <<
"Failed to initialize GStreamer:" << (
error ?
error->message :
"unknown error");
192 g_clear_error(&
error);
203 if (!gst_is_initialized()) {
204 qCCritical(GStreamerLog) <<
"completeInit called but gst_init() has not been called";
210 guint major, minor, micro, nano;
211 gst_version(&major, &minor, µ, &nano);
212 qCDebug(GStreamerLog) <<
"GStreamer initialized:" << major <<
"." << minor <<
"." << micro;
214#ifdef QGC_GST_BUILD_VERSION_MAJOR
215 if (major != QGC_GST_BUILD_VERSION_MAJOR || minor != QGC_GST_BUILD_VERSION_MINOR) {
216 qCWarning(GStreamerLog) <<
"GStreamer version mismatch: built against" << QGC_GST_BUILD_VERSION_MAJOR <<
"."
217 << QGC_GST_BUILD_VERSION_MINOR <<
"but runtime is" << major <<
"." << minor <<
"."
226 if (GstRegistry* reg = gst_registry_get()) {
232 if (!_verifyPlugins()) {
233 qCCritical(GStreamerLog) <<
"Plugin verification failed";
239 if (!requireFactory(
"qgcqvideosink",
"sink bin will fail to construct")) {
242 if (!requireFactory(
"qgcvideosinkbin",
243 "qgc plugin registered but element exposure failed. Likely link-time symbol "
244 "stripping (iOS LTO / Android R8) removed the GST_ELEMENT_REGISTER side effect; "
245 "add gstqgcelements.cc to a -force_load / keep rule.")) {
250 qCCritical(GStreamerLog)
251 <<
"GStreamer external plugin loader failed. Check GST_PLUGIN_SCANNER and bundled runtime paths.";
265 gst_debug_remove_log_function(gst_debug_log_default);
267 if (!_initGstRuntime(arguments, envResult)) {
279 const gboolean disablePar =
config.disablePixelAspectRatio ? TRUE : FALSE;
280 const char*
const conversion =
config.conversionElement.isEmpty() ? nullptr :
config.conversionElement.constData();
285 qCCritical(GStreamerLog) <<
"qgcvideosinkbin factory not found";
289#if defined(QGC_HAS_ANY_GPU_PATH)
292 gst_element_factory_create_full(factory.get(),
"gpu-zerocopy",
config.gpuZeroCopy ? TRUE : FALSE,
293 "conversion-element", conversion,
"disable-par", disablePar, NULL);
295 GstElement* videoSinkBin = gst_element_factory_create_full(factory.get(),
"conversion-element", conversion,
296 "disable-par", disablePar, NULL);
299 qCCritical(GStreamerLog) <<
"qgcvideosinkbin element creation failed";
309 gst_clear_object(&videoSink);
319 if (!sinkBin || !videoSink || !controllerParent) {
321 qCWarning(GStreamerLog) <<
"setupQVideoSinkElement: null sinkBin, videoSink, or controllerParent";
331 c->prepareForRelease();
343 qCCritical(GStreamerLog) <<
"setupQVideoSinkElement: bin has no qgcqvideosink child";
346 GstElement*
const elementRaw = GST_ELEMENT(element.get());
348#if defined(QGC_HAS_ANY_GPU_PATH)
361 controller->setVideoSink(QPointer<QVideoSink>(videoSink));
362 controller->setActive(
true);
369 if (!sink || !widget || !receiver) {
373 auto* videoOutput = qobject_cast<QQuickVideoOutput*>(widget);
375 qCWarning(GStreamerLog) <<
"Widget is not a VideoOutput, cannot connect qgcqvideosink";
379 QVideoSink* videoSink = videoOutput->videoSink();
381 qCWarning(GStreamerLog) <<
"setupQVideoSinkElement failed";
389 if (!fact || !context)
392 [](
const QVariant& value) {
setDebugLevel(value.toInt()); });
398 case QSGRendererInterface::Software:
400 case QSGRendererInterface::OpenGL:
402 case QSGRendererInterface::Direct3D11:
404 case QSGRendererInterface::Direct3D12:
406 case QSGRendererInterface::Vulkan:
408 case QSGRendererInterface::Metal:
419 case QSGRendererInterface::OpenGL:
420#if defined(QGC_HAS_GST_GLMEMORY_GPU_PATH) || defined(QGC_HAS_GST_DMABUF_GPU_PATH)
421 return "GLMemory/DMABuf";
425 case QSGRendererInterface::Direct3D11:
426#if defined(QGC_HAS_GST_D3D11_GPU_PATH)
431 case QSGRendererInterface::Direct3D12:
434 return "CPU (D3D12 import disabled)";
435 case QSGRendererInterface::Metal:
436#if defined(QGC_HAS_GST_IOSURFACE_GPU_PATH)
437 return "IOSurface/VideoToolbox";
441 case QSGRendererInterface::Vulkan:
443 return "CPU (Vulkan import dormant)";
454 QSGRendererInterface::GraphicsApi api = QQuickWindow::graphicsApi();
455#if defined(QGC_HAS_ANY_GPU_PATH)
457 switch (rhi->backend()) {
458 case QRhi::OpenGLES2: api = QSGRendererInterface::OpenGL;
break;
459 case QRhi::D3D11: api = QSGRendererInterface::Direct3D11;
break;
460 case QRhi::D3D12: api = QSGRendererInterface::Direct3D12;
break;
461 case QRhi::Metal: api = QSGRendererInterface::Metal;
break;
462 case QRhi::Vulkan: api = QSGRendererInterface::Vulkan;
break;
467 qCInfo(GStreamerLog) <<
"Resolved RHI backend:" <<
graphicsApiName(api) <<
"→ zero-copy path:"
475 static constexpr std::array<std::pair<VideoDecoderOptions, const char*>, 5> kFamilyPrefixes = {{
483 QList<VideoDecoderOptions> families;
485 if (!families.contains(f))
489 GList* decoderFactories = gst_element_factory_list_get_elements(
490 static_cast<GstElementFactoryListType
>(GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO),
492 for (GList* node = decoderFactories; node !=
nullptr; node = node->next) {
493 GstElementFactory* factory = GST_ELEMENT_FACTORY(node->data);
496 const gchar* name = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
500 if (g_str_has_prefix(name,
"msdk")) {
504 if (g_str_has_prefix(name,
"d3d11") || g_str_has_prefix(name,
"d3d12") || g_str_has_prefix(name,
"dxva")) {
510 if (g_str_has_prefix(name,
"vaapi")) {
513 for (
const auto& [family, prefix] : kFamilyPrefixes) {
514 if (g_str_has_prefix(name, prefix)) {
520 gst_plugin_feature_list_free(decoderFactories);
G_BEGIN_DECLS GST_PLUGIN_STATIC_DECLARE(qgc)
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
A Fact is used to hold a single value within the system.
void rawValueChanged(const QVariant &value)
static QList< QGCQVideoSinkController * > controllersOf(const QObject *receiver)
A receiver's owning controllers — direct children only, never a deep QObject-tree walk.
static void syncActiveToWindowVisibility(QObject *receiver, QQuickVideoOutput *videoOutput)
void gst_qgc_q_video_sink_set_hw_context(GstQgcQVideoSink *self, const HwVideoBufferContext &ctx)
GstElement * gst_qgc_video_sink_bin_get_qvideosink(GstQgcVideoSinkBin *self)
Returns the internal qgcqvideosink element, transfer-full (caller unrefs); NULL if not yet constructe...
gboolean gst_qgc_video_sink_bin_get_gpu_zerocopy(GstElement *bin)
Whether the bin built its GPU zero-copy pipeline (mirrors "gpu-zerocopy"); NULL-safe (FALSE).
ValidationResult prepareEnvironment()
void bindDebugLevelFact(Fact *fact, QObject *context)
void configureDebugLogging()
void onMainWindowReady(QQuickWindow *window)
bool changeFeatureRank(GstRegistry *registry, const char *featureName, uint16_t rank)
VideoReceiver * createVideoReceiver(QObject *parent)
void setDebugLevel(int level)
void forEachPlugin(GstRegistry *registry, const std::function< void(GstPlugin *)> &visitor)
std::unique_ptr< GstElementFactory, GstObjectDeleter > GstFactoryPtr
bool didExternalPluginLoaderFail()
void releaseVideoSink(void *sink)
QList< VideoDecoderOptions > availableDecoderFamilies()
GstFactoryPtr adoptFactory(GstElementFactory *factory) noexcept
void * createVideoSink(const VideoSinkConfig &config)
bool initialize(const QStringList &arguments, const Environment::ValidationResult &envResult)
std::unique_ptr< GstObject, GstObjectDeleter > GstObjectPtr
void redirectGLibLogging()
static const char * zeroCopyFamilyForApi(QSGRendererInterface::GraphicsApi api)
void attachAppSink(QObject *receiver, void *sink, QQuickItem *widget)
bool setupQVideoSinkElement(void *sinkBin, QVideoSink *videoSink, QObject *controllerParent)
void resetExternalPluginLoaderFailure()
@ ForceVideoDecoderVulkan
@ ForceVideoDecoderNVIDIA
@ ForceVideoDecoderVideoToolbox
@ ForceVideoDecoderDirectX3D
static const char * graphicsApiName(QSGRendererInterface::GraphicsApi api)
Environment::ValidationResult prepareEnvironment()
void onPipelineRestart() noexcept
Pipeline-restart hook; re-arms one-shot priming latches so a restart can prime on the next NEED_CONTE...
void connectMainWindow(QQuickWindow *window) noexcept
Wire the main QQuickWindow into the RHI-capture path so snapshots follow its QRhi; no-op without GPU.
QRhi * cachedRhi() noexcept
Cached QRhi* maintained by sceneGraph signals; safe from any thread via acquire ordering.