QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
QGCQVideoSinkController.cc
Go to the documentation of this file.
2
3#include <QtCore/QMutexLocker>
4#include <QtCore/QThread>
5#include <QtGui/QWindow>
6#include <QtMultimedia/QVideoSink>
7#include <QtMultimediaQuick/private/qquickvideooutput_p.h>
8#include <QtQuick/QQuickWindow>
9#include <memory>
10
11#include "GstScoped.h"
12#include "QGCLoggingCategory.h"
13
14QGC_LOGGING_CATEGORY(QGCQVideoSinkControllerLog, "Video.GStreamer.QGCQVideoSinkController")
15
17 : QObject(parent), _element(element ? GST_ELEMENT(gst_object_ref(element)) : nullptr)
18{
19 _emitTimer.setInterval(1000);
20 _emitTimer.setSingleShot(false);
21 QObject::connect(&_emitTimer, &QTimer::timeout, this, &QGCQVideoSinkController::_onEmitTimer);
22 _emitTimer.start();
23}
24
26{
27 _releaseElementBinding();
28 if (_element) {
29 gst_object_unref(_element);
30 _element = nullptr;
31 }
32 _emitTimer.stop();
33}
34
35QList<QGCQVideoSinkController*> QGCQVideoSinkController::controllersOf(const QObject* receiver)
36{
37 return receiver ? receiver->findChildren<QGCQVideoSinkController*>(QString(), Qt::FindDirectChildrenOnly)
38 : QList<QGCQVideoSinkController*>{};
39}
40
41void QGCQVideoSinkController::syncActiveToWindowVisibility(QObject* receiver, QQuickVideoOutput* videoOutput)
42{
43 if (!receiver || !videoOutput)
44 return;
45
46 auto applyVisibility = [receiver](QWindow* win) {
47 const QWindow::Visibility v = win ? win->visibility() : QWindow::Hidden;
48 const bool active = win && (v != QWindow::Hidden && v != QWindow::Minimized);
49 for (auto* c : controllersOf(receiver))
50 c->setActive(active);
51 };
52 // Track the previous connection so windowChanged drops it before wiring the new window,
53 // else an old hidden window keeps gating the live receiver.
54 auto prevConn = std::make_shared<QMetaObject::Connection>();
55 auto wireWindow = [applyVisibility, prevConn, receiver](QQuickWindow* qw) {
56 if (*prevConn) {
57 QObject::disconnect(*prevConn);
58 *prevConn = QMetaObject::Connection{};
59 }
60 if (!qw) {
61 applyVisibility(nullptr);
62 return;
63 }
64 applyVisibility(qw);
65 *prevConn = QObject::connect(qw, &QWindow::visibilityChanged, receiver,
66 [applyVisibility, qw](QWindow::Visibility) { applyVisibility(qw); });
67 };
68 wireWindow(videoOutput->window());
69 QObject::connect(videoOutput, &QQuickVideoOutput::windowChanged, receiver, wireWindow);
70}
71
73{
74 return _element;
75}
76
77void QGCQVideoSinkController::updateNegotiation(const QString& format, const QSize& resolution)
78{
79 if (thread() != QThread::currentThread()) {
80 qCCritical(QGCQVideoSinkControllerLog) << "called from wrong thread";
81 return;
82 }
83 if (_bindingReleased)
84 return;
85 bool changed = false;
86 {
87 QMutexLocker locker(&_stateMutex);
88 if (format != _negotiatedFormat || resolution != _negotiatedResolution) {
89 _negotiatedFormat = format;
90 _negotiatedResolution = resolution;
91 changed = true;
92 }
93 }
94 if (changed) {
95 qCDebug(QGCQVideoSinkControllerLog).noquote()
96 << "Negotiation update: format=" << format << "size=" << resolution;
97 emit negotiationChanged();
98 }
99}
100
102{
103 if (thread() != QThread::currentThread()) {
104 qCCritical(QGCQVideoSinkControllerLog) << "called from wrong thread";
105 return;
106 }
107 if (!_element || _bindingReleased)
108 return;
109 // Pipeline-level latency was recalculated upstream (e.g. RTSP jitter-buffer reconfigure);
110 // re-query the sink so GstBaseSink re-primes its cached latency for the new depth.
111 const GStreamer::GstQueryPtr query = GStreamer::adoptQuery(gst_query_new_latency());
112 if (!gst_element_query(_element, query.get())) {
113 qCDebug(QGCQVideoSinkControllerLog) << "Latency query not handled by sink element";
114 }
115}
116
118{
119 if (thread() != QThread::currentThread()) {
120 qCCritical(QGCQVideoSinkControllerLog) << "called from wrong thread";
121 return;
122 }
123 if (!_element || _bindingReleased)
124 return;
125 g_object_set(_element, "active", active ? TRUE : FALSE, nullptr);
126}
127
128void QGCQVideoSinkController::setVideoSink(QPointer<QVideoSink> sink)
129{
130 if (thread() != QThread::currentThread()) {
131 qCCritical(QGCQVideoSinkControllerLog) << "called from wrong thread";
132 return;
133 }
134 if (!_element)
135 return;
136 if (_sinkDestroyedConnection) {
137 QObject::disconnect(_sinkDestroyedConnection);
138 _sinkDestroyedConnection = {};
139 }
140 QVideoSink* raw = sink.data();
141 if (!raw) {
142 // Caller's QVideoSink was destroyed between the call site and here — clear the
143 // element's snapshot and gate show_frame until a replacement sink is installed.
144 g_object_set(_element, "active", FALSE, "qvideosink", static_cast<gpointer>(nullptr), nullptr);
145 _bindingReleased = true;
146 return;
147 }
148 g_object_set(_element, "qvideosink", static_cast<gpointer>(raw), nullptr);
149 _sinkDestroyedConnection = QObject::connect(raw, &QObject::destroyed, this, [this]() {
150 setVideoSink(QPointer<QVideoSink>());
151 });
152 _bindingReleased = false;
153}
154
156{
157 if (thread() != QThread::currentThread()) {
158 qCCritical(QGCQVideoSinkControllerLog) << "called from wrong thread";
159 return;
160 }
161 _releaseElementBinding();
162 _emitTimer.stop();
163}
164
166{
167 if (!_element || _bindingReleased)
168 return 0;
169 guint64 delivered = 0;
170 g_object_get(_element, "frames-delivered", &delivered, nullptr);
171 return static_cast<quint64>(delivered);
172}
173
175{
176 QMutexLocker locker(&_stateMutex);
177 return _negotiatedFormat;
178}
179
181{
182 QMutexLocker locker(&_stateMutex);
183 return _negotiatedResolution;
184}
185
186void QGCQVideoSinkController::_releaseElementBinding() noexcept
187{
188 if (!_element || _bindingReleased)
189 return;
190
191 if (_sinkDestroyedConnection) {
192 QObject::disconnect(_sinkDestroyedConnection);
193 _sinkDestroyedConnection = {};
194 }
195 g_object_set(_element, "active", FALSE, "qvideosink", static_cast<gpointer>(nullptr), nullptr);
196 _bindingReleased = true;
197}
198
199void QGCQVideoSinkController::_onEmitTimer()
200{
201 if (!_element || _bindingReleased)
202 return;
203 guint64 delivered = 0;
204 g_object_get(_element, "frames-delivered", &delivered, nullptr);
205 if (delivered != _lastEmittedFrameTotal) {
206 _lastEmittedFrameTotal = delivered;
207 emit frameCountsChanged();
208 }
209}
std::atomic< quint64 > delivered
struct _GstElement GstElement
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static QList< QGCQVideoSinkController * > controllersOf(const QObject *receiver)
A receiver's owning controllers — direct children only, never a deep QObject-tree walk.
const GstElement * element() const noexcept
void updateNegotiation(const QString &format, const QSize &resolution)
void setVideoSink(QPointer< QVideoSink > sink)
quint64 frameCount() const noexcept
static void syncActiveToWindowVisibility(QObject *receiver, QQuickVideoOutput *videoOutput)
GstQueryPtr adoptQuery(GstQuery *query) noexcept
Definition GstScoped.h:36
std::unique_ptr< GstQuery, GstQueryDeleter > GstQueryPtr
Definition GstScoped.h:24