Qt 6.x
The Qt SDK
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
4#include <qvideoframe.h>
5#include <qvideosink.h>
6#include <QDebug>
7#include <QMap>
8#include <QThread>
9#include <QEvent>
10#include <QCoreApplication>
11
12#include <private/qfactoryloader_p.h>
13#include "qgstvideobuffer_p.h"
15
17
18#include <gst/video/video.h>
19#include <gst/video/gstvideometa.h>
20#include <qloggingcategory.h>
21#include <qdebug.h>
22
23#include "qgstutils_p.h"
24
25#include <rhi/qrhi.h>
26#if QT_CONFIG(gstreamer_gl)
27#include <gst/gl/gl.h>
28#endif // #if QT_CONFIG(gstreamer_gl)
29
30// DMA support
31#if QT_CONFIG(linux_dmabuf)
32#include <gst/allocators/gstdmabuf.h>
33#endif
34
35//#define DEBUG_VIDEO_SURFACE_SINK
36
37static Q_LOGGING_CATEGORY(qLcGstVideoRenderer, "qt.multimedia.gstvideorenderer")
38
40
42 : m_sink(sink)
43{
44 createSurfaceCaps();
45}
46
48{
49}
50
51void QGstVideoRenderer::createSurfaceCaps()
52{
53 QRhi *rhi = m_sink->rhi();
54 Q_UNUSED(rhi);
55
56 auto caps = QGstCaps::create();
57
58 // All the formats that both we and gstreamer support
79 ;
80#if QT_CONFIG(gstreamer_gl)
81 if (rhi && rhi->backend() == QRhi::OpenGLES2) {
82 caps.addPixelFormats(formats, GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
83#if QT_CONFIG(linux_dmabuf)
84 if (m_sink->eglDisplay() && m_sink->eglImageTargetTexture2D()) {
85 // We currently do not handle planar DMA buffers, as it's somewhat unclear how to
86 // convert the planar EGLImage into something we can use from OpenGL
87 auto singlePlaneFormats = QList<QVideoFrameFormat::PixelFormat>()
101 ;
102 caps.addPixelFormats(singlePlaneFormats, GST_CAPS_FEATURE_MEMORY_DMABUF);
103 }
104#endif
105 }
106#endif
108
109 m_surfaceCaps = caps;
110}
111
113{
114 QMutexLocker locker(&m_mutex);
115
116 return m_surfaceCaps;
117}
118
120{
121 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::start" << caps.toString();
122 QMutexLocker locker(&m_mutex);
123
124 m_frameMirrored = false;
125 m_frameRotationAngle = QVideoFrame::Rotation0;
126
127 if (m_active) {
128 m_flush = true;
129 m_stop = true;
130 }
131
132 m_startCaps = caps;
133
134 /*
135 Waiting for start() to be invoked in the main thread may block
136 if gstreamer blocks the main thread until this call is finished.
137 This situation is rare and usually caused by setState(Null)
138 while pipeline is being prerolled.
139
140 The proper solution to this involves controlling gstreamer pipeline from
141 other thread than video surface.
142
143 Currently start() fails if wait() timed out.
144 */
145 if (!waitForAsyncEvent(&locker, &m_setupCondition, 1000) && !m_startCaps.isNull()) {
146 qWarning() << "Failed to start video surface due to main thread blocked.";
147 m_startCaps = {};
148 }
149
150 return m_active;
151}
152
154{
155 QMutexLocker locker(&m_mutex);
156
157 if (!m_active)
158 return;
159
160 m_flush = true;
161 m_stop = true;
162
163 m_startCaps = {};
164
165 waitForAsyncEvent(&locker, &m_setupCondition, 500);
166}
167
169{
170 QMutexLocker locker(&m_mutex);
171
172 m_setupCondition.wakeAll();
173 m_renderCondition.wakeAll();
174}
175
177{
179 QMutexLocker locker(&m_mutex);
180 return m_active;
181}
182
184{
185 QMutexLocker locker(&m_mutex);
186
187 m_flush = true;
188 m_renderBuffer = nullptr;
189 m_renderCondition.wakeAll();
190
191 notify();
192}
193
194GstFlowReturn QGstVideoRenderer::render(GstBuffer *buffer)
195{
196 QMutexLocker locker(&m_mutex);
197 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::render";
198
199 m_renderReturn = GST_FLOW_OK;
200 m_renderBuffer = buffer;
201
202 waitForAsyncEvent(&locker, &m_renderCondition, 300);
203
204 m_renderBuffer = nullptr;
205
206 return m_renderReturn;
207}
208
210{
211#if QT_CONFIG(gstreamer_gl)
212 if (GST_QUERY_TYPE(query) == GST_QUERY_CONTEXT) {
213 const gchar *type;
214 gst_query_parse_context_type(query, &type);
215
216 if (strcmp(type, "gst.gl.local_context") != 0)
217 return false;
218
219 auto *gstGlContext = m_sink->gstGlLocalContext();
220 if (!gstGlContext)
221 return false;
222
223 gst_query_set_context(query, gstGlContext);
224
225 return true;
226 }
227#else
229#endif
230 return false;
231}
232
234{
235 if (GST_EVENT_TYPE(event) != GST_EVENT_TAG)
236 return;
237
238 GstTagList *taglist = nullptr;
239 gst_event_parse_tag(event, &taglist);
240 if (!taglist)
241 return;
242
243 gchar *value = nullptr;
244 if (!gst_tag_list_get_string(taglist, GST_TAG_IMAGE_ORIENTATION, &value))
245 return;
246
247 constexpr const char rotate[] = "rotate-";
248 constexpr const char flipRotate[] = "flip-rotate-";
249 constexpr size_t rotateLen = sizeof(rotate) - 1;
250 constexpr size_t flipRotateLen = sizeof(flipRotate) - 1;
251
252 bool mirrored = false;
253 int rotationAngle = 0;
254
255 if (!strncmp(rotate, value, rotateLen)) {
256 rotationAngle = atoi(value + rotateLen);
257 } else if (!strncmp(flipRotate, value, flipRotateLen)) {
258 // To flip by horizontal axis is the same as to mirror by vertical axis
259 // and rotate by 180 degrees.
260 mirrored = true;
261 rotationAngle = (180 + atoi(value + flipRotateLen)) % 360;
262 }
263
264 QMutexLocker locker(&m_mutex);
265 m_frameMirrored = mirrored;
266 switch (rotationAngle) {
267 case 0: m_frameRotationAngle = QVideoFrame::Rotation0; break;
268 case 90: m_frameRotationAngle = QVideoFrame::Rotation90; break;
269 case 180: m_frameRotationAngle = QVideoFrame::Rotation180; break;
270 case 270: m_frameRotationAngle = QVideoFrame::Rotation270; break;
271 default: m_frameRotationAngle = QVideoFrame::Rotation0;
272 }
273}
274
276{
277 if (event->type() == QEvent::UpdateRequest) {
278 QMutexLocker locker(&m_mutex);
279
280 if (m_notified) {
281 while (handleEvent(&locker)) {}
282 m_notified = false;
283 }
284 return true;
285 }
286
287 return QObject::event(event);
288}
289
290bool QGstVideoRenderer::handleEvent(QMutexLocker<QMutex> *locker)
291{
292 if (m_flush) {
293 m_flush = false;
294 if (m_active) {
295 locker->unlock();
296
297 if (m_sink && !m_flushed)
298 m_sink->setVideoFrame(QVideoFrame());
299 m_flushed = true;
300 locker->relock();
301 }
302 } else if (m_stop) {
303 m_stop = false;
304
305 if (m_active) {
306 m_active = false;
307 m_flushed = true;
308 }
309 } else if (!m_startCaps.isNull()) {
310 Q_ASSERT(!m_active);
311
312 auto startCaps = m_startCaps;
313 m_startCaps = {};
314
315 if (m_sink) {
316 locker->unlock();
317
318 m_flushed = true;
319 m_format = startCaps.formatForCaps(&m_videoInfo);
320 memoryFormat = startCaps.memoryFormat();
321
322 locker->relock();
323 m_active = m_format.isValid();
324 } else if (m_active) {
325 m_active = false;
326 m_flushed = true;
327 }
328
329 } else if (m_renderBuffer) {
330 GstBuffer *buffer = m_renderBuffer;
331 m_renderBuffer = nullptr;
332 m_renderReturn = GST_FLOW_ERROR;
333
334 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::handleEvent(renderBuffer)" << m_active << m_sink;
335 if (m_active && m_sink) {
336 gst_buffer_ref(buffer);
337
338 locker->unlock();
339
340 m_flushed = false;
341
342 auto meta = gst_buffer_get_video_crop_meta (buffer);
343 if (meta) {
344 QRect vp(meta->x, meta->y, meta->width, meta->height);
345 if (m_format.viewport() != vp) {
346 qCDebug(qLcGstVideoRenderer) << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x" << meta->width << " | " << meta->x << "x" << meta->y << "]";
347 // Update viewport if data is not the same
348 m_format.setViewport(vp);
349 }
350 }
351
352 if (m_sink->inStoppedState()) {
353 qCDebug(qLcGstVideoRenderer) << " sending empty video frame";
354 m_sink->setVideoFrame(QVideoFrame());
355 } else {
356 QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, m_videoInfo, m_sink, m_format, memoryFormat);
357 QVideoFrame frame(videoBuffer, m_format);
359 frame.setMirrored(m_frameMirrored);
360 frame.setRotationAngle(m_frameRotationAngle);
361
362 qCDebug(qLcGstVideoRenderer) << " sending video frame";
363 m_sink->setVideoFrame(frame);
364 }
365
366 gst_buffer_unref(buffer);
367
368 locker->relock();
369
370 m_renderReturn = GST_FLOW_OK;
371 }
372
373 m_renderCondition.wakeAll();
374 } else {
375 m_setupCondition.wakeAll();
376
377 return false;
378 }
379 return true;
380}
381
382void QGstVideoRenderer::notify()
383{
384 if (!m_notified) {
385 m_notified = true;
387 }
388}
389
390bool QGstVideoRenderer::waitForAsyncEvent(
391 QMutexLocker<QMutex> *locker, QWaitCondition *condition, unsigned long time)
392{
393 if (QThread::currentThread() == thread()) {
394 while (handleEvent(locker)) {}
395 m_notified = false;
396
397 return true;
398 }
399
400 notify();
401
402 return condition->wait(&m_mutex, time);
403}
404
405static GstVideoSinkClass *gvrs_sink_parent_class;
407
408#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
409
411{
412 setSink(sink);
413 QGstVideoRendererSink *gstSink = reinterpret_cast<QGstVideoRendererSink *>(
414 g_object_new(QGstVideoRendererSink::get_type(), nullptr));
415
416 g_signal_connect(G_OBJECT(gstSink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), gstSink);
417
418 return gstSink;
419}
420
422{
424}
425
426GType QGstVideoRendererSink::get_type()
427{
428 static const GTypeInfo info =
429 {
430 sizeof(QGstVideoRendererSinkClass), // class_size
431 base_init, // base_init
432 nullptr, // base_finalize
433 class_init, // class_init
434 nullptr, // class_finalize
435 nullptr, // class_data
436 sizeof(QGstVideoRendererSink), // instance_size
437 0, // n_preallocs
438 instance_init, // instance_init
439 nullptr // value_table
440 };
441
442 static const GType type = []() {
443 const auto result = g_type_register_static(
444 GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink", &info, GTypeFlags(0));
445
446 // Register the sink type to be used in custom piplines.
447 // When surface is ready the sink can be used.
448 gst_element_register(nullptr, "qtvideosink", GST_RANK_PRIMARY, result);
449
450 return result;
451 }();
452
453 return type;
454}
455
456void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
457{
458 Q_UNUSED(class_data);
459
460 gvrs_sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class));
461
462 GstVideoSinkClass *video_sink_class = reinterpret_cast<GstVideoSinkClass *>(g_class);
463 video_sink_class->show_frame = QGstVideoRendererSink::show_frame;
464
465 GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class);
466 base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
467 base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
468 base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
469 base_sink_class->stop = QGstVideoRendererSink::stop;
470 base_sink_class->unlock = QGstVideoRendererSink::unlock;
471 base_sink_class->query = QGstVideoRendererSink::query;
472 base_sink_class->event = QGstVideoRendererSink::event;
473
474 GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class);
475 element_class->change_state = QGstVideoRendererSink::change_state;
476 gst_element_class_set_metadata(element_class,
477 "Qt built-in video renderer sink",
478 "Sink/Video",
479 "Qt default built-in video renderer sink",
480 "The Qt Company");
481
482 GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class);
483 object_class->finalize = QGstVideoRendererSink::finalize;
484}
485
486void QGstVideoRendererSink::base_init(gpointer g_class)
487{
488 static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
489 "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
490 "video/x-raw, "
491 "framerate = (fraction) [ 0, MAX ], "
492 "width = (int) [ 1, MAX ], "
493 "height = (int) [ 1, MAX ]"));
494
495 gst_element_class_add_pad_template(
496 GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
497}
498
499void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class)
500{
501 Q_UNUSED(g_class);
502 VO_SINK(instance);
503
505
507 sink->renderer->moveToThread(gvrs_current_sink->thread());
508 gvrs_current_sink = nullptr;
509}
510
511void QGstVideoRendererSink::finalize(GObject *object)
512{
513 VO_SINK(object);
514
515 delete sink->renderer;
516
517 // Chain up
518 G_OBJECT_CLASS(gvrs_sink_parent_class)->finalize(object);
519}
520
521void QGstVideoRendererSink::handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d)
522{
523 Q_UNUSED(o);
524 Q_UNUSED(p);
525 QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(d);
526
527 gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default
528 g_object_get(G_OBJECT(sink), "show-preroll-frame", &showPrerollFrame, nullptr);
529
530 if (!showPrerollFrame) {
531 GstState state = GST_STATE_VOID_PENDING;
532 GstClockTime timeout = 10000000; // 10 ms
533 gst_element_get_state(GST_ELEMENT(sink), &state, nullptr, timeout);
534 // show-preroll-frame being set to 'false' while in GST_STATE_PAUSED means
535 // the QMediaPlayer was stopped from the paused state.
536 // We need to flush the current frame.
537 if (state == GST_STATE_PAUSED)
538 sink->renderer->flush();
539 }
540}
541
542GstStateChangeReturn QGstVideoRendererSink::change_state(
543 GstElement *element, GstStateChange transition)
544{
545 QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(element);
546
547 gboolean showPrerollFrame = true; // "show-preroll-frame" property is true by default
548 g_object_get(G_OBJECT(element), "show-preroll-frame", &showPrerollFrame, nullptr);
549
550 // If show-preroll-frame is 'false' when transitioning from GST_STATE_PLAYING to
551 // GST_STATE_PAUSED, it means the QMediaPlayer was stopped.
552 // We need to flush the current frame.
553 if (transition == GST_STATE_CHANGE_PLAYING_TO_PAUSED && !showPrerollFrame)
554 sink->renderer->flush();
555
556 return GST_ELEMENT_CLASS(gvrs_sink_parent_class)->change_state(element, transition);
557}
558
559GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter)
560{
561 VO_SINK(base);
562
563 QGstCaps caps = sink->renderer->caps();
564 if (filter)
565 caps = QGstCaps(gst_caps_intersect(caps.get(), filter), QGstCaps::HasRef);
566
567 gst_caps_ref(caps.get());
568 return caps.get();
569}
570
571gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *gcaps)
572{
573 VO_SINK(base);
574
575 auto caps = QGstCaps(gcaps, QGstCaps::NeedsRef);
576
577 qCDebug(qLcGstVideoRenderer) << "set_caps:" << caps.toString();
578
579 if (caps.isNull()) {
580 sink->renderer->stop();
581
582 return TRUE;
583 } else if (sink->renderer->start(caps)) {
584 return TRUE;
585 } else {
586 return FALSE;
587 }
588}
589
590gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query)
591{
592 VO_SINK(base);
593 return sink->renderer->proposeAllocation(query);
594}
595
596gboolean QGstVideoRendererSink::stop(GstBaseSink *base)
597{
598 VO_SINK(base);
599 sink->renderer->stop();
600 return TRUE;
601}
602
603gboolean QGstVideoRendererSink::unlock(GstBaseSink *base)
604{
605 VO_SINK(base);
606 sink->renderer->unlock();
607 return TRUE;
608}
609
610GstFlowReturn QGstVideoRendererSink::show_frame(GstVideoSink *base, GstBuffer *buffer)
611{
612 VO_SINK(base);
613 return sink->renderer->render(buffer);
614}
615
616gboolean QGstVideoRendererSink::query(GstBaseSink *base, GstQuery *query)
617{
618 VO_SINK(base);
619 if (sink->renderer->query(query))
620 return TRUE;
621
622 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->query(base, query);
623}
624
625gboolean QGstVideoRendererSink::event(GstBaseSink *base, GstEvent * event)
626{
627 VO_SINK(base);
628 sink->renderer->gstEvent(event);
629 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->event(base, event);
630}
631
633
634#include "moc_qgstvideorenderersink_p.cpp"
static void postEvent(QObject *receiver, QEvent *event, int priority=Qt::NormalEventPriority)
\inmodule QtCore
Definition qcoreevent.h:45
@ UpdateRequest
Definition qcoreevent.h:113
@ NeedsRef
Definition qgst_p.h:163
@ HasRef
Definition qgst_p.h:163
QByteArray toString() const
Definition qgst_p.h:199
void addPixelFormats(const QList< QVideoFrameFormat::PixelFormat > &formats, const char *modifier=nullptr)
bool isNull() const
Definition qgst_p.h:197
static QGstCaps create()
Definition qgst_p.h:215
GstCaps * get() const
Definition qgst_p.h:202
static QGstVideoRendererSink * createSink(QGstreamerVideoSink *surface)
static void setSink(QGstreamerVideoSink *surface)
bool query(GstQuery *query)
bool proposeAllocation(GstQuery *query)
void gstEvent(GstEvent *event)
GstFlowReturn render(GstBuffer *buffer)
bool event(QEvent *event) override
This virtual function receives events to an object and should return true if the event e was recogniz...
bool start(const QGstCaps &caps)
GstContext * gstGlLocalContext() const
QFunctionPointer eglImageTargetTexture2D() const
Qt::HANDLE eglDisplay() const
Definition qlist.h:74
\inmodule QtCore
Definition qmutex.h:317
void unlock() noexcept
Unlocks this mutex locker.
Definition qmutex.h:323
void relock() noexcept
Relocks an unlocked mutex locker.
Definition qmutex.h:324
virtual bool event(QEvent *event)
This virtual function receives events to an object and should return true if the event e was recogniz...
Definition qobject.cpp:1363
QThread * thread() const
Returns the thread in which the object lives.
Definition qobject.cpp:1561
virtual void setVideoFrame(const QVideoFrame &frame)
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtGui
Definition qrhi.h:1767
Implementation backend() const
Definition qrhi.cpp:8289
@ OpenGLES2
Definition qrhi.h:1772
static QThread * currentThread()
Definition qthread.cpp:966
void setViewport(const QRect &viewport)
Sets the viewport of a video stream to viewport.
bool isValid() const
Identifies if a video surface format has a valid pixel format and frame size.
QRect viewport() const
Returns the viewport of a video stream.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
else opt state
[0]
EGLint EGLint * formats
void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer)
Combined button and popup list for selecting options.
#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 qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum condition
GLbitfield GLuint64 timeout
[4]
GLenum GLuint buffer
GLenum type
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
struct _cl_event * event
GLenum query
GLsizei GLenum GLboolean sink
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_UNUSED(x)
QFileInfo info(fileName)
[8]
QFrame frame
[0]