1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qgstreamervideooverlay_p.h"
41
42#include <QtGui/qguiapplication.h>
43#include "qgstutils_p.h"
44
45#if !GST_CHECK_VERSION(1,0,0)
46#include <gst/interfaces/xoverlay.h>
47#else
48#include <gst/video/videooverlay.h>
49#endif
50
51#include <QtMultimedia/private/qtmultimediaglobal_p.h>
52
53QT_BEGIN_NAMESPACE
54
55struct ElementMap
56{
57 const char *qtPlatform;
58 const char *gstreamerElement;
59};
60
61// Ordered by descending priority
62static const ElementMap elementMap[] =
63{
64#if QT_CONFIG(gstreamer_gl)
65 { .qtPlatform: "xcb", .gstreamerElement: "glimagesink" },
66#endif
67 { .qtPlatform: "xcb", .gstreamerElement: "vaapisink" },
68 { .qtPlatform: "xcb", .gstreamerElement: "xvimagesink" },
69 { .qtPlatform: "xcb", .gstreamerElement: "ximagesink" }
70};
71
72class QGstreamerSinkProperties
73{
74public:
75 virtual ~QGstreamerSinkProperties()
76 {
77 }
78
79 virtual bool hasShowPrerollFrame() const = 0;
80 virtual void reset() = 0;
81 virtual int brightness() const = 0;
82 virtual bool setBrightness(int brightness) = 0;
83 virtual int contrast() const = 0;
84 virtual bool setContrast(int contrast) = 0;
85 virtual int hue() const = 0;
86 virtual bool setHue(int hue) = 0;
87 virtual int saturation() const = 0;
88 virtual bool setSaturation(int saturation) = 0;
89 virtual Qt::AspectRatioMode aspectRatioMode() const = 0;
90 virtual void setAspectRatioMode(Qt::AspectRatioMode mode) = 0;
91};
92
93class QXVImageSinkProperties : public QGstreamerSinkProperties
94{
95public:
96 QXVImageSinkProperties(GstElement *sink)
97 : m_videoSink(sink)
98 {
99 m_hasForceAspectRatio = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "force-aspect-ratio");
100 m_hasBrightness = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "brightness");
101 m_hasContrast = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "contrast");
102 m_hasHue = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "hue");
103 m_hasSaturation = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "saturation");
104 m_hasShowPrerollFrame = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), property_name: "show-preroll-frame");
105 }
106
107 bool hasShowPrerollFrame() const override
108 {
109 return m_hasShowPrerollFrame;
110 }
111
112 void reset() override
113 {
114 setAspectRatioMode(m_aspectRatioMode);
115 setBrightness(m_brightness);
116 setContrast(m_contrast);
117 setHue(m_hue);
118 setSaturation(m_saturation);
119 }
120
121 int brightness() const override
122 {
123 int brightness = 0;
124 if (m_hasBrightness)
125 g_object_get(G_OBJECT(m_videoSink), first_property_name: "brightness", &brightness, nullptr);
126
127 return brightness / 10;
128 }
129
130 bool setBrightness(int brightness) override
131 {
132 m_brightness = brightness;
133 if (m_hasBrightness)
134 g_object_set(G_OBJECT(m_videoSink), first_property_name: "brightness", brightness * 10, nullptr);
135
136 return m_hasBrightness;
137 }
138
139 int contrast() const override
140 {
141 int contrast = 0;
142 if (m_hasContrast)
143 g_object_get(G_OBJECT(m_videoSink), first_property_name: "contrast", &contrast, nullptr);
144
145 return contrast / 10;
146 }
147
148 bool setContrast(int contrast) override
149 {
150 m_contrast = contrast;
151 if (m_hasContrast)
152 g_object_set(G_OBJECT(m_videoSink), first_property_name: "contrast", contrast * 10, nullptr);
153
154 return m_hasContrast;
155 }
156
157 int hue() const override
158 {
159 int hue = 0;
160 if (m_hasHue)
161 g_object_get(G_OBJECT(m_videoSink), first_property_name: "hue", &hue, nullptr);
162
163 return hue / 10;
164 }
165
166 bool setHue(int hue) override
167 {
168 m_hue = hue;
169 if (m_hasHue)
170 g_object_set(G_OBJECT(m_videoSink), first_property_name: "hue", hue * 10, nullptr);
171
172 return m_hasHue;
173 }
174
175 int saturation() const override
176 {
177 int saturation = 0;
178 if (m_hasSaturation)
179 g_object_get(G_OBJECT(m_videoSink), first_property_name: "saturation", &saturation, nullptr);
180
181 return saturation / 10;
182 }
183
184 bool setSaturation(int saturation) override
185 {
186 m_saturation = saturation;
187 if (m_hasSaturation)
188 g_object_set(G_OBJECT(m_videoSink), first_property_name: "saturation", saturation * 10, nullptr);
189
190 return m_hasSaturation;
191 }
192
193 Qt::AspectRatioMode aspectRatioMode() const override
194 {
195 Qt::AspectRatioMode mode = Qt::KeepAspectRatio;
196 if (m_hasForceAspectRatio) {
197 gboolean forceAR = false;
198 g_object_get(G_OBJECT(m_videoSink), first_property_name: "force-aspect-ratio", &forceAR, nullptr);
199 if (!forceAR)
200 mode = Qt::IgnoreAspectRatio;
201 }
202
203 return mode;
204 }
205
206 void setAspectRatioMode(Qt::AspectRatioMode mode) override
207 {
208 m_aspectRatioMode = mode;
209 if (m_hasForceAspectRatio) {
210 g_object_set(G_OBJECT(m_videoSink),
211 first_property_name: "force-aspect-ratio",
212 (mode == Qt::KeepAspectRatio),
213 nullptr);
214 }
215 }
216
217protected:
218
219 GstElement *m_videoSink = nullptr;
220 bool m_hasForceAspectRatio = false;
221 bool m_hasBrightness = false;
222 bool m_hasContrast = false;
223 bool m_hasHue = false;
224 bool m_hasSaturation = false;
225 bool m_hasShowPrerollFrame = false;
226 Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
227 int m_brightness = 0;
228 int m_contrast = 0;
229 int m_hue = 0;
230 int m_saturation = 0;
231};
232
233class QVaapiSinkProperties : public QXVImageSinkProperties
234{
235public:
236 QVaapiSinkProperties(GstElement *sink)
237 : QXVImageSinkProperties(sink)
238 {
239 // Set default values.
240 m_contrast = 1;
241 m_saturation = 1;
242 }
243
244 int brightness() const override
245 {
246 gfloat brightness = 0;
247 if (m_hasBrightness)
248 g_object_get(G_OBJECT(m_videoSink), first_property_name: "brightness", &brightness, nullptr);
249
250 return brightness * 100; // [-1,1] -> [-100,100]
251 }
252
253 bool setBrightness(int brightness) override
254 {
255 m_brightness = brightness;
256 if (m_hasBrightness) {
257 gfloat v = brightness / 100.0; // [-100,100] -> [-1,1]
258 g_object_set(G_OBJECT(m_videoSink), first_property_name: "brightness", v, nullptr);
259 }
260
261 return m_hasBrightness;
262 }
263
264 int contrast() const override
265 {
266 gfloat contrast = 1;
267 if (m_hasContrast)
268 g_object_get(G_OBJECT(m_videoSink), first_property_name: "contrast", &contrast, nullptr);
269
270 return (contrast - 1) * 100; // [0,2] -> [-100,100]
271 }
272
273 bool setContrast(int contrast) override
274 {
275 m_contrast = contrast;
276 if (m_hasContrast) {
277 gfloat v = (contrast / 100.0) + 1; // [-100,100] -> [0,2]
278 g_object_set(G_OBJECT(m_videoSink), first_property_name: "contrast", v, nullptr);
279 }
280
281 return m_hasContrast;
282 }
283
284 int hue() const override
285 {
286 gfloat hue = 0;
287 if (m_hasHue)
288 g_object_get(G_OBJECT(m_videoSink), first_property_name: "hue", &hue, nullptr);
289
290 return hue / 180 * 100; // [-180,180] -> [-100,100]
291 }
292
293 bool setHue(int hue) override
294 {
295 m_hue = hue;
296 if (m_hasHue) {
297 gfloat v = hue / 100.0 * 180; // [-100,100] -> [-180,180]
298 g_object_set(G_OBJECT(m_videoSink), first_property_name: "hue", v, nullptr);
299 }
300
301 return m_hasHue;
302 }
303
304 int saturation() const override
305 {
306 gfloat saturation = 1;
307 if (m_hasSaturation)
308 g_object_get(G_OBJECT(m_videoSink), first_property_name: "saturation", &saturation, nullptr);
309
310 return (saturation - 1) * 100; // [0,2] -> [-100,100]
311 }
312
313 bool setSaturation(int saturation) override
314 {
315 m_saturation = saturation;
316 if (m_hasSaturation) {
317 gfloat v = (saturation / 100.0) + 1; // [-100,100] -> [0,2]
318 g_object_set(G_OBJECT(m_videoSink), first_property_name: "saturation", v, nullptr);
319 }
320
321 return m_hasSaturation;
322 }
323};
324
325static bool qt_gst_element_is_functioning(GstElement *element)
326{
327 GstStateChangeReturn ret = gst_element_set_state(element, state: GST_STATE_READY);
328 if (ret == GST_STATE_CHANGE_SUCCESS) {
329 gst_element_set_state(element, state: GST_STATE_NULL);
330 return true;
331 }
332
333 return false;
334}
335
336static GstElement *findBestVideoSink()
337{
338 GstElement *choice = 0;
339 QString platform = QGuiApplication::platformName();
340
341 // We need a native window ID to use the GstVideoOverlay interface.
342 // Bail out if the Qt platform plugin in use cannot provide a sensible WId.
343 if (platform != QLatin1String("xcb"))
344 return 0;
345
346 // First, try some known video sinks, depending on the Qt platform plugin in use.
347 for (quint32 i = 0; i < (sizeof(elementMap) / sizeof(ElementMap)); ++i) {
348#if QT_CONFIG(gstreamer_gl)
349 if (!QGstUtils::useOpenGL() && qstrcmp(str1: elementMap[i].gstreamerElement, str2: "glimagesink") == 0)
350 continue;
351#endif
352 if (platform == QLatin1String(elementMap[i].qtPlatform)
353 && (choice = gst_element_factory_make(factoryname: elementMap[i].gstreamerElement, name: nullptr))) {
354
355 if (qt_gst_element_is_functioning(element: choice))
356 return choice;
357
358 gst_object_unref(object: choice);
359 choice = 0;
360 }
361 }
362
363 // If none of the known video sinks are available, try to find one that implements the
364 // GstVideoOverlay interface and has autoplugging rank.
365 GList *list = qt_gst_video_sinks();
366 for (GList *item = list; item != nullptr; item = item->next) {
367 GstElementFactory *f = GST_ELEMENT_FACTORY(item->data);
368
369 if (!gst_element_factory_has_interface(factory: f, QT_GSTREAMER_VIDEOOVERLAY_INTERFACE_NAME))
370 continue;
371
372 if (GstElement *el = gst_element_factory_create(factory: f, name: nullptr)) {
373 if (qt_gst_element_is_functioning(element: el)) {
374 choice = el;
375 break;
376 }
377
378 gst_object_unref(object: el);
379 }
380 }
381
382 gst_plugin_feature_list_free(list);
383
384 return choice;
385}
386
387QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent, const QByteArray &elementName)
388 : QObject(parent)
389 , QGstreamerBufferProbe(QGstreamerBufferProbe::ProbeCaps)
390{
391 GstElement *sink = nullptr;
392 if (!elementName.isEmpty())
393 sink = gst_element_factory_make(factoryname: elementName.constData(), name: nullptr);
394 else
395 sink = findBestVideoSink();
396
397 setVideoSink(sink);
398}
399
400QGstreamerVideoOverlay::~QGstreamerVideoOverlay()
401{
402 if (m_videoSink) {
403 delete m_sinkProperties;
404 GstPad *pad = gst_element_get_static_pad(element: m_videoSink, name: "sink");
405 removeProbeFromPad(pad);
406 gst_object_unref(GST_OBJECT(pad));
407 gst_object_unref(GST_OBJECT(m_videoSink));
408 }
409}
410
411GstElement *QGstreamerVideoOverlay::videoSink() const
412{
413 return m_videoSink;
414}
415
416void QGstreamerVideoOverlay::setVideoSink(GstElement *sink)
417{
418 if (!sink)
419 return;
420
421 if (m_videoSink)
422 gst_object_unref(GST_OBJECT(m_videoSink));
423
424 m_videoSink = sink;
425 qt_gst_object_ref_sink(GST_OBJECT(m_videoSink));
426
427 GstPad *pad = gst_element_get_static_pad(element: m_videoSink, name: "sink");
428 addProbeToPad(pad);
429 gst_object_unref(GST_OBJECT(pad));
430
431 QString sinkName(QLatin1String(GST_OBJECT_NAME(sink)));
432 bool isVaapi = sinkName.startsWith(s: QLatin1String("vaapisink"));
433 delete m_sinkProperties;
434 m_sinkProperties = isVaapi ? new QVaapiSinkProperties(sink) : new QXVImageSinkProperties(sink);
435
436 if (m_sinkProperties->hasShowPrerollFrame())
437 g_signal_connect(m_videoSink, "notify::show-preroll-frame",
438 G_CALLBACK(showPrerollFrameChanged), this);
439}
440
441QSize QGstreamerVideoOverlay::nativeVideoSize() const
442{
443 return m_nativeVideoSize;
444}
445
446void QGstreamerVideoOverlay::setWindowHandle(WId id)
447{
448 m_windowId = id;
449
450 if (isActive())
451 setWindowHandle_helper(id);
452}
453
454void QGstreamerVideoOverlay::setWindowHandle_helper(WId id)
455{
456#if GST_CHECK_VERSION(1,0,0)
457 if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
458 gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), handle: id);
459#else
460 if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
461# if GST_CHECK_VERSION(0,10,31)
462 gst_x_overlay_set_window_handle(GST_X_OVERLAY(m_videoSink), id);
463# else
464 gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), id);
465# endif
466#endif
467
468 // Properties need to be reset when changing the winId.
469 m_sinkProperties->reset();
470 }
471}
472
473void QGstreamerVideoOverlay::expose()
474{
475 if (!isActive())
476 return;
477
478#if !GST_CHECK_VERSION(1,0,0)
479 if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink))
480 gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink));
481#else
482 if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
483 gst_video_overlay_expose(GST_VIDEO_OVERLAY(m_videoSink));
484 }
485#endif
486}
487
488void QGstreamerVideoOverlay::setRenderRectangle(const QRect &rect)
489{
490 int x = -1;
491 int y = -1;
492 int w = -1;
493 int h = -1;
494
495 if (!rect.isEmpty()) {
496 x = rect.x();
497 y = rect.y();
498 w = rect.width();
499 h = rect.height();
500 }
501
502#if GST_CHECK_VERSION(1,0,0)
503 if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink))
504 gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(m_videoSink), x, y, width: w, height: h);
505#elif GST_CHECK_VERSION(0, 10, 29)
506 if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink))
507 gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), x, y , w , h);
508#else
509 Q_UNUSED(x)
510 Q_UNUSED(y)
511 Q_UNUSED(w)
512 Q_UNUSED(h)
513#endif
514}
515
516bool QGstreamerVideoOverlay::processSyncMessage(const QGstreamerMessage &message)
517{
518 GstMessage* gm = message.rawMessage();
519
520#if !GST_CHECK_VERSION(1,0,0)
521 if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
522 gst_structure_has_name(gm->structure, "prepare-xwindow-id")) {
523#else
524 if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
525 gst_structure_has_name(structure: gst_message_get_structure(message: gm), name: "prepare-window-handle")) {
526#endif
527 setWindowHandle_helper(m_windowId);
528 return true;
529 }
530
531 return false;
532}
533
534bool QGstreamerVideoOverlay::processBusMessage(const QGstreamerMessage &message)
535{
536 GstMessage* gm = message.rawMessage();
537
538 if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED &&
539 GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) {
540
541 updateIsActive();
542 }
543
544 return false;
545}
546
547void QGstreamerVideoOverlay::probeCaps(GstCaps *caps)
548{
549 QSize size = QGstUtils::capsCorrectedResolution(caps);
550 if (size != m_nativeVideoSize) {
551 m_nativeVideoSize = size;
552 emit nativeVideoSizeChanged();
553 }
554}
555
556bool QGstreamerVideoOverlay::isActive() const
557{
558 return m_isActive;
559}
560
561void QGstreamerVideoOverlay::updateIsActive()
562{
563 if (!m_videoSink)
564 return;
565
566 GstState state = GST_STATE(m_videoSink);
567 gboolean showPreroll = true;
568
569 if (m_sinkProperties->hasShowPrerollFrame())
570 g_object_get(G_OBJECT(m_videoSink), first_property_name: "show-preroll-frame", &showPreroll, nullptr);
571
572 bool newIsActive = (state == GST_STATE_PLAYING || (state == GST_STATE_PAUSED && showPreroll));
573
574 if (newIsActive != m_isActive) {
575 m_isActive = newIsActive;
576 emit activeChanged();
577 }
578}
579
580void QGstreamerVideoOverlay::showPrerollFrameChanged(GObject *, GParamSpec *, QGstreamerVideoOverlay *overlay)
581{
582 overlay->updateIsActive();
583}
584
585Qt::AspectRatioMode QGstreamerVideoOverlay::aspectRatioMode() const
586{
587 return m_sinkProperties->aspectRatioMode();
588}
589
590void QGstreamerVideoOverlay::setAspectRatioMode(Qt::AspectRatioMode mode)
591{
592 m_sinkProperties->setAspectRatioMode(mode);
593}
594
595int QGstreamerVideoOverlay::brightness() const
596{
597 return m_sinkProperties->brightness();
598}
599
600void QGstreamerVideoOverlay::setBrightness(int brightness)
601{
602 if (m_sinkProperties->setBrightness(brightness))
603 emit brightnessChanged(brightness);
604}
605
606int QGstreamerVideoOverlay::contrast() const
607{
608 return m_sinkProperties->contrast();
609}
610
611void QGstreamerVideoOverlay::setContrast(int contrast)
612{
613 if (m_sinkProperties->setContrast(contrast))
614 emit contrastChanged(contrast);
615}
616
617int QGstreamerVideoOverlay::hue() const
618{
619 return m_sinkProperties->hue();
620}
621
622void QGstreamerVideoOverlay::setHue(int hue)
623{
624 if (m_sinkProperties->setHue(hue))
625 emit hueChanged(hue);
626}
627
628int QGstreamerVideoOverlay::saturation() const
629{
630 return m_sinkProperties->saturation();
631}
632
633void QGstreamerVideoOverlay::setSaturation(int saturation)
634{
635 if (m_sinkProperties->setSaturation(saturation))
636 emit saturationChanged(saturation);
637}
638
639QT_END_NAMESPACE
640

source code of qtmultimedia/src/gsttools/qgstreamervideooverlay.cpp