Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgstvideorenderersink.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Jolla Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/qvideoframe.h>
7#include <QtMultimedia/qvideosink.h>
8#include <QtCore/private/qfactoryloader_p.h>
9#include <QtCore/private/quniquehandle_p.h>
10#include <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qmap.h>
15#include <QtCore/qthread.h>
16#include <QtGui/qevent.h>
17
20#include <common/qgst_debug_p.h>
21#include <common/qgstutils_p.h>
22
23#include <gst/video/video.h>
24#include <gst/video/gstvideometa.h>
25
26
27#include <rhi/qrhi.h>
28#if QT_CONFIG(gstreamer_gl)
29#include <gst/gl/gl.h>
30#endif // #if QT_CONFIG(gstreamer_gl)
31
32// DMA support
33#if QT_CONFIG(linux_dmabuf)
34#include <gst/allocators/gstdmabuf.h>
35#endif
36
37static Q_LOGGING_CATEGORY(qLcGstVideoRenderer, "qt.multimedia.gstvideorenderer")
38
40
42 : m_sink(sink), m_surfaceCaps(createSurfaceCaps(sink))
43{
46 [this] {
47 QMutexLocker locker(&m_sinkMutex);
48 m_sink = nullptr;
49 },
51}
52
54
55QGstCaps QGstVideoRenderer::createSurfaceCaps([[maybe_unused]] QGstreamerVideoSink *sink)
56{
58
59 // All the formats that both we and gstreamer support
60 auto formats = QList<QVideoFrameFormat::PixelFormat>()
80 ;
81#if QT_CONFIG(gstreamer_gl)
82 QRhi *rhi = sink->rhi();
83 if (rhi && rhi->backend() == QRhi::OpenGLES2) {
84 caps.addPixelFormats(formats, GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
85#if QT_CONFIG(linux_dmabuf)
86 if (sink->eglDisplay() && sink->eglImageTargetTexture2D()) {
87 // We currently do not handle planar DMA buffers, as it's somewhat unclear how to
88 // convert the planar EGLImage into something we can use from OpenGL
89 auto singlePlaneFormats = QList<QVideoFrameFormat::PixelFormat>()
103 ;
104 caps.addPixelFormats(singlePlaneFormats, GST_CAPS_FEATURE_MEMORY_DMABUF);
105 }
106#endif
107 }
108#endif
110 return caps;
111}
112
114{
115 return m_surfaceCaps;
116}
117
119{
120 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::start" << caps;
121
122 {
123 m_frameRotationAngle = QtVideo::Rotation::None;
124 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
125 if (optionalFormatAndVideoInfo) {
126 std::tie(m_format, m_videoInfo) = std::move(*optionalFormatAndVideoInfo);
127 } else {
128 m_format = {};
129 m_videoInfo = {};
130 }
131 m_memoryFormat = caps.memoryFormat();
132 }
133
134 return true;
135}
136
138{
139 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::stop";
140
141 QMetaObject::invokeMethod(this, [this] {
142 m_sink->setVideoFrame(QVideoFrame());
143 return;
144 });
145}
146
148{
149 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::unlock";
150}
151
153{
154 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::proposeAllocation";
155 return true;
156}
157
158GstFlowReturn QGstVideoRenderer::render(GstBuffer *buffer)
159{
160 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::render";
161
162 GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer);
163 if (meta) {
164 QRect vp(meta->x, meta->y, meta->width, meta->height);
165 if (m_format.viewport() != vp) {
166 qCDebug(qLcGstVideoRenderer)
167 << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x"
168 << meta->width << " | " << meta->x << "x" << meta->y << "]";
169 // Update viewport if data is not the same
170 m_format.setViewport(vp);
171 }
172 }
173
174 struct RenderBufferState
175 {
178 QGstCaps::MemoryFormat memoryFormat;
179 bool mirrored;
180 QtVideo::Rotation rotationAngle;
181 };
182
183 RenderBufferState state{
185 .format = m_format,
186 .memoryFormat = m_memoryFormat,
187 .mirrored = m_frameMirrored,
188 .rotationAngle = m_frameRotationAngle,
189 };
190
191 qCDebug(qLcGstVideoRenderer) << " sending video frame";
192
193 QMetaObject::invokeMethod(this, [this, state = std::move(state)]() mutable {
194 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer{
195 state.buffer, m_videoInfo, m_sink, state.format, state.memoryFormat,
196 };
197 QVideoFrame frame(videoBuffer, state.format);
199 frame.setMirrored(state.mirrored);
200 frame.setRotation(state.rotationAngle);
201
202 m_currentVideoFrame = std::move(frame);
203 if (!m_sink)
204 return;
205
206 if (m_sink->inStoppedState()) {
207 qCDebug(qLcGstVideoRenderer) << " showing empty video frame";
208 m_currentVideoFrame = {};
209 }
210
211 m_sink->setVideoFrame(m_currentVideoFrame);
212 });
213
214 return GST_FLOW_OK;
215}
216
218{
219#if QT_CONFIG(gstreamer_gl)
220 if (GST_QUERY_TYPE(query) == GST_QUERY_CONTEXT) {
221 const gchar *type;
222 gst_query_parse_context_type(query, &type);
223
224 if (strcmp(type, "gst.gl.local_context") != 0)
225 return false;
226
227 QMutexLocker locker(&m_sinkMutex);
228 if (!m_sink)
229 return false;
230
231 auto *gstGlContext = m_sink->gstGlLocalContext();
232 if (!gstGlContext)
233 return false;
234
235 gst_query_set_context(query, gstGlContext);
236
237 return true;
238 }
239#else
241#endif
242 return false;
243}
244
246{
247 switch (GST_EVENT_TYPE(event)) {
248 case GST_EVENT_TAG:
249 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: Tag";
250 return gstEventHandleTag(event);
251 case GST_EVENT_EOS:
252 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: EOS";
253 return gstEventHandleEOS(event);
254
255 default:
256 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent: unhandled event - " << event;
257 return;
258 }
259}
260
261void QGstVideoRenderer::gstEventHandleTag(GstEvent *event)
262{
263 GstTagList *taglist = nullptr;
264 gst_event_parse_tag(event, &taglist);
265 if (!taglist)
266 return;
267
269 if (!gst_tag_list_get_string(taglist, GST_TAG_IMAGE_ORIENTATION, &value))
270 return;
271
272 constexpr const char rotate[] = "rotate-";
273 constexpr const char flipRotate[] = "flip-rotate-";
274 constexpr size_t rotateLen = sizeof(rotate) - 1;
275 constexpr size_t flipRotateLen = sizeof(flipRotate) - 1;
276
277 bool mirrored = false;
278 int rotationAngle = 0;
279
280 if (!strncmp(rotate, value.get(), rotateLen)) {
281 rotationAngle = atoi(value.get() + rotateLen);
282 } else if (!strncmp(flipRotate, value.get(), flipRotateLen)) {
283 // To flip by horizontal axis is the same as to mirror by vertical axis
284 // and rotate by 180 degrees.
285 mirrored = true;
286 rotationAngle = (180 + atoi(value.get() + flipRotateLen)) % 360;
287 }
288
289 m_frameMirrored = mirrored;
290 switch (rotationAngle) {
291 case 0:
292 m_frameRotationAngle = QtVideo::Rotation::None;
293 break;
294 case 90:
295 m_frameRotationAngle = QtVideo::Rotation::Clockwise90;
296 break;
297 case 180:
298 m_frameRotationAngle = QtVideo::Rotation::Clockwise180;
299 break;
300 case 270:
301 m_frameRotationAngle = QtVideo::Rotation::Clockwise270;
302 break;
303 default:
304 m_frameRotationAngle = QtVideo::Rotation::None;
305 }
306}
307
308void QGstVideoRenderer::gstEventHandleEOS(GstEvent *)
309{
310 stop();
311}
312
313static GstVideoSinkClass *gvrs_sink_parent_class;
315
316#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
317
319{
320 setSink(sink);
321 QGstVideoRendererSink *gstSink = reinterpret_cast<QGstVideoRendererSink *>(
322 g_object_new(QGstVideoRendererSink::get_type(), nullptr));
323
324 return gstSink;
325}
326
331
332GType QGstVideoRendererSink::get_type()
333{
334 static const GTypeInfo info =
335 {
336 sizeof(QGstVideoRendererSinkClass), // class_size
337 base_init, // base_init
338 nullptr, // base_finalize
339 class_init, // class_init
340 nullptr, // class_finalize
341 nullptr, // class_data
342 sizeof(QGstVideoRendererSink), // instance_size
343 0, // n_preallocs
344 instance_init, // instance_init
345 nullptr // value_table
346 };
347
348 static const GType type = g_type_register_static(GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink",
349 &info, GTypeFlags(0));
350
351 return type;
352}
353
354void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
355{
356 Q_UNUSED(class_data);
357
358 gvrs_sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class));
359
360 GstVideoSinkClass *video_sink_class = reinterpret_cast<GstVideoSinkClass *>(g_class);
361 video_sink_class->show_frame = QGstVideoRendererSink::show_frame;
362
363 GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class);
364 base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
365 base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
366 base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
367 base_sink_class->stop = QGstVideoRendererSink::stop;
368 base_sink_class->unlock = QGstVideoRendererSink::unlock;
369 base_sink_class->query = QGstVideoRendererSink::query;
370 base_sink_class->event = QGstVideoRendererSink::event;
371
372 GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class);
373 element_class->change_state = QGstVideoRendererSink::change_state;
374 gst_element_class_set_metadata(element_class,
375 "Qt built-in video renderer sink",
376 "Sink/Video",
377 "Qt default built-in video renderer sink",
378 "The Qt Company");
379
380 GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class);
381 object_class->finalize = QGstVideoRendererSink::finalize;
382}
383
384void QGstVideoRendererSink::base_init(gpointer g_class)
385{
386 static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
387 "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
388 "video/x-raw, "
389 "framerate = (fraction) [ 0, MAX ], "
390 "width = (int) [ 1, MAX ], "
391 "height = (int) [ 1, MAX ]"));
392
393 gst_element_class_add_pad_template(
394 GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
395}
396
397void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class)
398{
399 Q_UNUSED(g_class);
400 VO_SINK(instance);
401
403
405 sink->renderer->moveToThread(gvrs_current_sink->thread());
406 gvrs_current_sink = nullptr;
407}
408
409void QGstVideoRendererSink::finalize(GObject *object)
410{
411 VO_SINK(object);
412
413 delete sink->renderer;
414
415 // Chain up
416 G_OBJECT_CLASS(gvrs_sink_parent_class)->finalize(object);
417}
418
419GstStateChangeReturn QGstVideoRendererSink::change_state(
420 GstElement *element, GstStateChange transition)
421{
422 return GST_ELEMENT_CLASS(gvrs_sink_parent_class)->change_state(element, transition);
423}
424
425GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter)
426{
427 VO_SINK(base);
428
429 QGstCaps caps = sink->renderer->caps();
430 if (filter)
431 caps = QGstCaps(gst_caps_intersect(caps.caps(), filter), QGstCaps::HasRef);
432
433 return caps.release();
434}
435
436gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *gcaps)
437{
438 VO_SINK(base);
439 auto caps = QGstCaps(gcaps, QGstCaps::NeedsRef);
440
441 qCDebug(qLcGstVideoRenderer) << "set_caps:" << caps;
442
443 if (caps.isNull()) {
444 sink->renderer->stop();
445 return TRUE;
446 }
447
448 return sink->renderer->start(caps);
449}
450
451gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query)
452{
453 VO_SINK(base);
454 return sink->renderer->proposeAllocation(query);
455}
456
457gboolean QGstVideoRendererSink::stop(GstBaseSink *base)
458{
459 VO_SINK(base);
460 sink->renderer->stop();
461 return TRUE;
462}
463
464gboolean QGstVideoRendererSink::unlock(GstBaseSink *base)
465{
466 VO_SINK(base);
467 sink->renderer->unlock();
468 return TRUE;
469}
470
471GstFlowReturn QGstVideoRendererSink::show_frame(GstVideoSink *base, GstBuffer *buffer)
472{
473 VO_SINK(base);
474 return sink->renderer->render(buffer);
475}
476
477gboolean QGstVideoRendererSink::query(GstBaseSink *base, GstQuery *query)
478{
479 VO_SINK(base);
480 if (sink->renderer->query(query))
481 return TRUE;
482
483 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->query(base, query);
484}
485
486gboolean QGstVideoRendererSink::event(GstBaseSink *base, GstEvent * event)
487{
488 VO_SINK(base);
489 sink->renderer->gstEvent(event);
490 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->event(base, event);
491}
492
void addPixelFormats(const QList< QVideoFrameFormat::PixelFormat > &formats, const char *modifier=nullptr)
Definition qgst.cpp:430
MemoryFormat memoryFormat() const
Definition qgst.cpp:506
std::optional< std::pair< QVideoFrameFormat, GstVideoInfo > > formatAndVideoInfo() const
Definition qgst.cpp:329
static QGstCaps create()
Definition qgst.cpp:531
GstCaps * caps() const
Definition qgst.cpp:526
MemoryFormat
Definition qgst_p.h:353
static QGstVideoRendererSink * createSink(QGstreamerVideoSink *surface)
static void setSink(QGstreamerVideoSink *surface)
GstFlowReturn render(GstBuffer *)
bool start(const QGstCaps &)
bool proposeAllocation(GstQuery *)
GstContext * gstGlLocalContext() const
\inmodule QtCore
Definition qmutex.h:313
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
virtual void setVideoFrame(const QVideoFrame &frame)
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:1804
Implementation backend() const
Definition qrhi.cpp:8651
@ OpenGLES2
Definition qrhi.h:1809
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
void setViewport(const QRect &viewport)
Sets the viewport of a video stream to viewport.
QRect viewport() const
Returns the viewport of a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
else opt state
[0]
EGLint EGLint * formats
void setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer)
Combined button and popup list for selecting options.
@ DirectConnection
#define Q_FUNC_INFO
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define VO_SINK(s)
static thread_local QGstreamerVideoSink * gvrs_current_sink
static GstVideoSinkClass * gvrs_sink_parent_class
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum GLuint buffer
GLenum type
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLint GLsizei GLsizei GLenum format
struct _cl_event * event
GLenum query
GLsizei GLenum GLboolean sink
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_UNUSED(x)
static const uint base
Definition qurlidna.cpp:20
QFrame frame
[0]
QHostInfo info
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...