Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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 <QtMultimedia/private/qvideoframe_p.h>
9#include <QtGui/rhi/qrhi.h>
10#include <QtCore/qcoreapplication.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtCore/private/qfactoryloader_p.h>
14#include <QtCore/private/quniquehandle_p.h>
15
16#include <common/qgst_debug_p.h>
17#include <common/qgstreamermetadata_p.h>
18#include <common/qgstreamervideosink_p.h>
19#include <common/qgstutils_p.h>
20#include <common/qgstvideobuffer_p.h>
21
22#include <gst/video/video.h>
23#include <gst/video/gstvideometa.h>
24
25
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(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
32# include <gst/allocators/gstdmabuf.h>
33#endif
34
35// NOLINTBEGIN(readability-convert-member-functions-to-static)
36
37Q_STATIC_LOGGING_CATEGORY(qLcGstVideoRenderer, "qt.multimedia.gstvideorenderer");
38
39QT_BEGIN_NAMESPACE
40
41QGstVideoRenderer::QGstVideoRenderer(QGstreamerVideoSink *sink)
42 : m_sink(sink), m_surfaceCaps(createSurfaceCaps(sink))
43{
44 QObject::connect(
45 sink, &QGstreamerVideoSink::aboutToBeDestroyed, this,
46 [this] {
47 QMutexLocker locker(&m_sinkMutex);
48 m_sink = nullptr;
49 },
50 Qt::DirectConnection);
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>()
61 << QVideoFrameFormat::Format_YUV420P
62 << QVideoFrameFormat::Format_YUV422P
63 << QVideoFrameFormat::Format_YV12
64 << QVideoFrameFormat::Format_UYVY
65 << QVideoFrameFormat::Format_YUYV
66 << QVideoFrameFormat::Format_NV12
67 << QVideoFrameFormat::Format_NV21
68 << QVideoFrameFormat::Format_AYUV
69 << QVideoFrameFormat::Format_P010
70 << QVideoFrameFormat::Format_XRGB8888
71 << QVideoFrameFormat::Format_XBGR8888
72 << QVideoFrameFormat::Format_RGBX8888
73 << QVideoFrameFormat::Format_BGRX8888
74 << QVideoFrameFormat::Format_ARGB8888
75 << QVideoFrameFormat::Format_ABGR8888
76 << QVideoFrameFormat::Format_RGBA8888
77 << QVideoFrameFormat::Format_BGRA8888
78 << QVideoFrameFormat::Format_Y8
79 << QVideoFrameFormat::Format_Y16
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(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
86 if (sink->eglDisplay() && sink->eglImageTargetTexture2D()) {
87 caps.addPixelFormats(formats, GST_CAPS_FEATURE_MEMORY_DMABUF);
88 }
89# endif
90 }
91#endif
92 caps.addPixelFormats(formats);
93 return caps;
94}
95
96void QGstVideoRenderer::customEvent(QEvent *event)
97{
98QT_WARNING_PUSH
99QT_WARNING_DISABLE_GCC("-Wswitch") // case value not in enumerated type ‘QEvent::Type’
100
101 switch (event->type()) {
102 case renderFramesEvent: {
103 // LATER: we currently show every frame. however it may be reasonable to drop frames
104 // here if the queue contains more than one frame
105 while (std::optional<RenderBufferState> nextState = m_bufferQueue.dequeue())
106 handleNewBuffer(std::move(*nextState));
107 return;
108 }
109 case stopEvent: {
110 m_currentPipelineFrame = {};
111 updateCurrentVideoFrame(m_currentVideoFrame);
112 return;
113 }
114
115 default:
116 return;
117 }
118QT_WARNING_POP
119}
120
121
122void QGstVideoRenderer::handleNewBuffer(RenderBufferState state)
123{
124 auto videoBuffer = std::make_unique<QGstVideoBuffer>(state.buffer, state.videoInfo, m_sink,
125 state.format, state.memoryFormat);
126 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(videoBuffer), state.format);
127 QGstUtils::setFrameTimeStampsFromBuffer(&frame, state.buffer.get());
128 m_currentPipelineFrame = std::move(frame);
129
130 if (!m_isActive) {
131 qCDebug(qLcGstVideoRenderer) << " showing empty video frame";
132 updateCurrentVideoFrame({});
133 return;
134 }
135
136 updateCurrentVideoFrame(m_currentPipelineFrame);
137}
138
140{
141 return m_surfaceCaps;
142}
143
145{
146 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::start" << caps;
147
148 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
149 if (optionalFormatAndVideoInfo) {
150 std::tie(m_format, m_videoInfo) = std::move(*optionalFormatAndVideoInfo);
151 } else {
152 m_format = {};
153 m_videoInfo = {};
154 }
155 m_capsMemoryFormat = caps.memoryFormat();
156
157 // NOTE: m_format will not be fully populated until GST_EVENT_TAG is processed
158
159 return true;
160}
161
163{
164 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::stop";
165
166 m_bufferQueue.clear();
167 QCoreApplication::postEvent(this, new QEvent(stopEvent));
168}
169
171{
172 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::unlock";
173}
174
176{
177 if (!query || GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION)
178 return false;
179
180 GstCaps *queryCaps = nullptr;
181 gboolean needPool = false;
182 gst_query_parse_allocation(query, &queryCaps, &needPool);
183 GstVideoInfo info;
184 int size = 0;
185 if (queryCaps && gst_video_info_from_caps(&info, queryCaps)) {
186 size = info.size;
187 } else {
188 qWarning(qLcGstVideoRenderer) << "QGstVideoRenderer::proposeAllocation failed to "
189 "get size from query caps";
190 return true;
191 }
192
193 constexpr int defaultMinBuffers = 3;
194 static const int env
195 = qEnvironmentVariableIntValue("QT_GSTREAMER_PROPOSE_ALLOCATION_MIN_BUFFERS");
196 static const int minBuffers = env ? env : defaultMinBuffers;
197 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::proposeAllocation: "
198 "needPool:" << needPool
199 << "size:" << size
200 << "minBuffers:" << minBuffers;
201
202 // This call is needed to avoid enabling of copy threshold by v4l2 decoders, which can result
203 // in a mix of dmabuf and system memory buffers. The query sender should use its own buffer
204 // pool, and will only take the size value and our suggested minimum buffers into account.
205 // The driver can force a higher minimum if minBuffers is set too low, making 3 sufficient.
206 gst_query_add_allocation_pool(query, nullptr, size, minBuffers, 0);
207
208 return true;
209}
210
212{
213 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::render";
214
215 if (m_flushing) {
216 qCDebug(qLcGstVideoRenderer)
217 << " buffer received while flushing the sink ... discarding buffer";
218 return GST_FLOW_FLUSHING;
219 }
220
221 GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer);
222 if (meta) {
223 QRect vp(meta->x, meta->y, meta->width, meta->height);
224 if (m_format.viewport() != vp) {
225 qCDebug(qLcGstVideoRenderer)
226 << Q_FUNC_INFO << " Update viewport on Metadata: [" << meta->height << "x"
227 << meta->width << " | " << meta->x << "x" << meta->y << "]";
228 // Update viewport if data is not the same
229 m_format.setViewport(vp);
230 }
231 }
232
233 // Some gst elements, like v4l2h264dec, can provide Direct Memory Access buffers (DMA-BUF)
234 // without specifying it in their caps. So we check the memory format manually:
235 QGstCaps::MemoryFormat bufferMemoryFormat = [&] {
236 if (m_capsMemoryFormat != QGstCaps::CpuMemory)
237 return m_capsMemoryFormat;
238
239 [[maybe_unused]] GstMemory *mem = gst_buffer_peek_memory(buffer, 0);
240#if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
241 if (gst_is_dmabuf_memory(mem))
242 return QGstCaps::DMABuf;
243#endif
244#if QT_CONFIG(gstreamer_gl)
245 if (gst_is_gl_memory(mem))
246 return QGstCaps::GLTexture;
247#endif
248 return QGstCaps::CpuMemory;
249 }();
250
251 qCDebug(qLcGstVideoRenderer) << "m_capsMemoryFormat" << m_capsMemoryFormat
252 << "bufferMemoryFormat" << bufferMemoryFormat;
253
254 QVideoFrameFormat bufferVideoFrameFormat = m_format;
255
256 if (m_sink->eglDisplay()) {
257 // EGL seems to do implicit YUV->RGB conversion for UYVY and YUYV (YUY2), so we change the
258 // pixel format to Format_RGBA8888 to select an appropriate shader.
259 const bool setFormat_RGBA8888 =
260 (bufferMemoryFormat == QGstCaps::DMABuf
261 && (m_format.pixelFormat() == QVideoFrameFormat::Format_UYVY
262 || m_format.pixelFormat() == QVideoFrameFormat::Format_YUYV));
263 if (setFormat_RGBA8888) {
264 // TODO: Replace with new private setter of pixel format.
265 qCDebug(qLcGstVideoRenderer) << "Setting pixel format to Format_RGBA8888";
266 bufferVideoFrameFormat = QVideoFrameFormat(m_format.frameSize(),
267 QVideoFrameFormat::Format_RGBA8888);
268 bufferVideoFrameFormat.setStreamFrameRate(m_format.streamFrameRate());
269 bufferVideoFrameFormat.setColorRange(m_format.colorRange());
270 bufferVideoFrameFormat.setColorTransfer(m_format.colorTransfer());
271 bufferVideoFrameFormat.setColorSpace(m_format.colorSpace());
272 }
273 }
274
275 RenderBufferState state{
276 QGstBufferHandle{ buffer, QGstBufferHandle::NeedsRef },
277 bufferVideoFrameFormat,
278 m_videoInfo,
279 bufferMemoryFormat,
280 };
281
282 qCDebug(qLcGstVideoRenderer) << " sending video frame";
283
284 qsizetype sizeOfQueue = m_bufferQueue.enqueue(std::move(state));
285 if (sizeOfQueue == 1)
286 // we only need to wake up, if we don't have a pending frame
287 QCoreApplication::postEvent(this, new QEvent(renderFramesEvent));
288
289 return GST_FLOW_OK;
290}
291
292bool QGstVideoRenderer::query(GstQuery *query)
293{
294#if QT_CONFIG(gstreamer_gl)
295 if (GST_QUERY_TYPE(query) == GST_QUERY_CONTEXT) {
296 const gchar *type = nullptr;
297 gst_query_parse_context_type(query, &type);
298
299 QLatin1StringView typeStr(type);
300 if (typeStr != QLatin1StringView("gst.gl.GLDisplay")
301 && typeStr != QLatin1StringView("gst.gl.local_context")) {
302 return false;
303 }
304
305 QMutexLocker locker(&m_sinkMutex);
306 if (!m_sink)
307 return false;
308
309 auto *gstGlContext = typeStr == QLatin1StringView("gst.gl.GLDisplay")
310 ? m_sink->gstGlDisplayContext() : m_sink->gstGlLocalContext();
311 if (!gstGlContext)
312 return false;
313
314 gst_query_set_context(query, gstGlContext);
315
316 return true;
317 }
318#else
319 Q_UNUSED(query);
320#endif
321 return false;
322}
323
324void QGstVideoRenderer::gstEvent(GstEvent *event)
325{
326 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEvent:" << event;
327
328 switch (GST_EVENT_TYPE(event)) {
329 case GST_EVENT_TAG:
330 return gstEventHandleTag(event);
331 case GST_EVENT_EOS:
332 return gstEventHandleEOS(event);
333 case GST_EVENT_FLUSH_START:
334 return gstEventHandleFlushStart(event);
335 case GST_EVENT_FLUSH_STOP:
336 return gstEventHandleFlushStop(event);
337
338 default:
339 return;
340 }
341}
342
343void QGstVideoRenderer::setActive(bool isActive)
344{
345 if (isActive == m_isActive)
346 return;
347
348 m_isActive = isActive;
349 if (isActive)
350 updateCurrentVideoFrame(m_currentPipelineFrame);
351 else
352 updateCurrentVideoFrame({});
353}
354
355void QGstVideoRenderer::updateCurrentVideoFrame(QVideoFrame frame)
356{
357 m_currentVideoFrame = std::move(frame);
358 if (m_sink)
359 m_sink->setVideoFrame(m_currentVideoFrame);
360}
361
362void QGstVideoRenderer::gstEventHandleTag(GstEvent *event)
363{
364 GstTagList *taglist = nullptr;
365 gst_event_parse_tag(event, &taglist);
366 if (!taglist)
367 return;
368
369 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::gstEventHandleTag:" << taglist;
370
371 QGString value;
372 if (!gst_tag_list_get_string(taglist, GST_TAG_IMAGE_ORIENTATION, &value))
373 return;
374
375 RotationResult parsed = parseRotationTag(value.get());
376
377 m_format.setMirrored(parsed.flip);
378 m_format.setRotation(parsed.rotation);
379}
380
381void QGstVideoRenderer::gstEventHandleEOS(GstEvent *)
382{
383 stop();
384}
385
386void QGstVideoRenderer::gstEventHandleFlushStart(GstEvent *)
387{
388 // "data is to be discarded"
389 m_flushing = true;
390 m_bufferQueue.clear();
391}
392
393void QGstVideoRenderer::gstEventHandleFlushStop(GstEvent *)
394{
395 // "data is allowed again"
396 m_flushing = false;
397}
398
401
402#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
403
405{
406 setSink(sink);
407 QGstVideoRendererSink *gstSink = reinterpret_cast<QGstVideoRendererSink *>(
408 g_object_new(QGstVideoRendererSink::get_type(), nullptr));
409
410 return QGstVideoRendererSinkElement{
411 gstSink,
412 QGstElement::NeedsRef,
413 };
414}
415
417{
418 gvrs_current_sink = sink;
419}
420
421GType QGstVideoRendererSink::get_type()
422{
423 static const GTypeInfo info =
424 {
425 sizeof(QGstVideoRendererSinkClass), // class_size
426 base_init, // base_init
427 nullptr, // base_finalize
428 class_init, // class_init
429 nullptr, // class_finalize
430 nullptr, // class_data
431 sizeof(QGstVideoRendererSink), // instance_size
432 0, // n_preallocs
433 instance_init, // instance_init
434 nullptr // value_table
435 };
436
437 static const GType type = g_type_register_static(GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink",
438 &info, GTypeFlags(0));
439
440 return type;
441}
442
443void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
444{
445 Q_UNUSED(class_data);
446
447 gvrs_sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class));
448
449 GstVideoSinkClass *video_sink_class = reinterpret_cast<GstVideoSinkClass *>(g_class);
450 video_sink_class->show_frame = QGstVideoRendererSink::show_frame;
451
452 GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class);
453 base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
454 base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
455 base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
456 base_sink_class->stop = QGstVideoRendererSink::stop;
457 base_sink_class->unlock = QGstVideoRendererSink::unlock;
458 base_sink_class->query = QGstVideoRendererSink::query;
459 base_sink_class->event = QGstVideoRendererSink::event;
460
461 GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class);
462 element_class->change_state = QGstVideoRendererSink::change_state;
463 gst_element_class_set_metadata(element_class,
464 "Qt built-in video renderer sink",
465 "Sink/Video",
466 "Qt default built-in video renderer sink",
467 "The Qt Company");
468
469 GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class);
470 object_class->finalize = QGstVideoRendererSink::finalize;
471}
472
473void QGstVideoRendererSink::base_init(gpointer g_class)
474{
475 static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
476 "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
477 "video/x-raw, "
478 "framerate = (fraction) [ 0, MAX ], "
479 "width = (int) [ 1, MAX ], "
480 "height = (int) [ 1, MAX ]"));
481
482 gst_element_class_add_pad_template(
483 GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
484}
485
486void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class)
487{
488 Q_UNUSED(g_class);
489 VO_SINK(instance);
490
491 Q_ASSERT(gvrs_current_sink);
492
493 sink->renderer = new QGstVideoRenderer(gvrs_current_sink);
494 sink->renderer->moveToThread(gvrs_current_sink->thread());
495 gvrs_current_sink = nullptr;
496}
497
498void QGstVideoRendererSink::finalize(GObject *object)
499{
500 VO_SINK(object);
501
502 delete sink->renderer;
503
504 // Chain up
505 G_OBJECT_CLASS(gvrs_sink_parent_class)->finalize(object);
506}
507
508GstStateChangeReturn QGstVideoRendererSink::change_state(
509 GstElement *element, GstStateChange transition)
510{
511 GstStateChangeReturn ret =
512 GST_ELEMENT_CLASS(gvrs_sink_parent_class)->change_state(element, transition);
513 qCDebug(qLcGstVideoRenderer) << "QGstVideoRenderer::change_state:" << transition << ret;
514 return ret;
515}
516
517GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter)
518{
519 VO_SINK(base);
520
521 QGstCaps caps = sink->renderer->caps();
522 if (filter)
523 caps = QGstCaps(gst_caps_intersect(caps.caps(), filter), QGstCaps::HasRef);
524
525 return caps.release();
526}
527
528gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *gcaps)
529{
530 VO_SINK(base);
531 auto caps = QGstCaps(gcaps, QGstCaps::NeedsRef);
532
533 qCDebug(qLcGstVideoRenderer) << "set_caps:" << caps;
534
535 if (!caps) {
536 sink->renderer->stop();
537 return TRUE;
538 }
539
540 return sink->renderer->start(caps);
541}
542
543gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query)
544{
545 VO_SINK(base);
546 return sink->renderer->proposeAllocation(query);
547}
548
549gboolean QGstVideoRendererSink::stop(GstBaseSink *base)
550{
551 VO_SINK(base);
552 sink->renderer->stop();
553 return TRUE;
554}
555
556gboolean QGstVideoRendererSink::unlock(GstBaseSink *base)
557{
558 VO_SINK(base);
559 sink->renderer->unlock();
560 return TRUE;
561}
562
563GstFlowReturn QGstVideoRendererSink::show_frame(GstVideoSink *base, GstBuffer *buffer)
564{
565 VO_SINK(base);
566 return sink->renderer->render(buffer);
567}
568
569gboolean QGstVideoRendererSink::query(GstBaseSink *base, GstQuery *query)
570{
571 VO_SINK(base);
572 if (sink->renderer->query(query))
573 return TRUE;
574
575 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->query(base, query);
576}
577
578gboolean QGstVideoRendererSink::event(GstBaseSink *base, GstEvent * event)
579{
580 VO_SINK(base);
581 sink->renderer->gstEvent(event);
582 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->event(base, event);
583}
584
593
595{
596 qGstVideoRendererSink()->renderer->setActive(isActive);
597}
598
600{
601 return reinterpret_cast<QGstVideoRendererSink *>(element());
602}
603
604QT_END_NAMESPACE
MemoryFormat memoryFormat() const
Definition qgst.cpp:551
static QGstCaps create()
Definition qgst.cpp:578
MemoryFormat
Definition qgst_p.h:343
@ CpuMemory
Definition qgst_p.h:343
QGstVideoRendererSinkElement(QGstVideoRendererSink *, RefMode)
QGstVideoRendererSink * qGstVideoRendererSink() const
static QGstVideoRendererSinkElement createSink(QGstreamerVideoSink *surface)
GstFlowReturn render(GstBuffer *)
~QGstVideoRenderer() override
QGstVideoRenderer(QGstreamerVideoSink *)
void customEvent(QEvent *) override
This event handler can be reimplemented in a subclass to receive custom events.
bool start(const QGstCaps &)
bool proposeAllocation(GstQuery *)
void setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer)
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
#define VO_SINK(s)
static GstVideoSinkClass * gvrs_sink_parent_class
static thread_local QGstreamerVideoSink * gvrs_current_sink