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 <QtGui/qguiapplication.h>
11#include <QtGui/qopenglcontext.h>
12#include <QtCore/qcoreapplication.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/private/qfactoryloader_p.h>
16#include <QtCore/private/quniquehandle_p.h>
18#include <common/qgst_debug_p.h>
19#include <common/qgstreamermetadata_p.h>
20#include <common/qgstreamervideosink_p.h>
21#include <common/qgstutils_p.h>
22#include <common/qgstvideobuffer_p.h>
24#include <gst/video/video.h>
25#include <gst/video/gstvideometa.h>
28#if QT_CONFIG(gstreamer_gl)
33#if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
34# include <gst/allocators/gstdmabuf.h>
43QGstVideoRenderer::QGstVideoRenderer(QGstreamerRelayVideoSink *sink)
44 : m_sink(sink), m_surfaceCaps(createSurfaceCaps(sink))
47 sink, &QGstreamerRelayVideoSink::aboutToBeDestroyed,
this,
49 QMutexLocker locker(&m_sinkMutex);
52 Qt::DirectConnection);
62 auto formats = QList<QVideoFrameFormat::PixelFormat>()
63 << QVideoFrameFormat::Format_YUV420P
64 << QVideoFrameFormat::Format_YUV422P
65 << QVideoFrameFormat::Format_YV12
66 << QVideoFrameFormat::Format_UYVY
67 << QVideoFrameFormat::Format_YUYV
68 << QVideoFrameFormat::Format_NV12
69 << QVideoFrameFormat::Format_NV21
70 << QVideoFrameFormat::Format_AYUV
71 << QVideoFrameFormat::Format_P010
72 << QVideoFrameFormat::Format_XRGB8888
73 << QVideoFrameFormat::Format_XBGR8888
74 << QVideoFrameFormat::Format_RGBX8888
75 << QVideoFrameFormat::Format_BGRX8888
76 << QVideoFrameFormat::Format_ARGB8888
77 << QVideoFrameFormat::Format_ABGR8888
78 << QVideoFrameFormat::Format_RGBA8888
79 << QVideoFrameFormat::Format_BGRA8888
80 << QVideoFrameFormat::Format_Y8
81 << QVideoFrameFormat::Format_Y16
83 caps.addPixelFormats(formats);
84#if QT_CONFIG(gstreamer_gl)
85 QRhi *rhi = sink->rhi();
86 if (rhi && rhi->backend() == QRhi::OpenGLES2) {
87 caps.addPixelFormats(formats, GST_CAPS_FEATURE_MEMORY_GL_MEMORY);
88# if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
89 if (sink->eglDisplay() && sink->eglImageTargetTexture2D()) {
90 caps.addPixelFormats(formats, GST_CAPS_FEATURE_MEMORY_DMABUF);
101QT_WARNING_DISABLE_GCC(
"-Wswitch")
103 switch (event->type()) {
104 case renderFramesEvent: {
107 while (std::optional<RenderBufferState> nextState = m_bufferQueue.dequeue())
108 handleNewBuffer(std::move(*nextState));
112 m_currentPipelineFrame = {};
113 updateCurrentVideoFrame(m_currentVideoFrame);
127 std::make_unique<QGstVideoBuffer>(state.buffer, state.videoInfo, state.format);
128 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(videoBuffer), state.format);
130 m_currentPipelineFrame = std::move(frame);
133 qCDebug(qLcGstVideoRenderer) <<
" showing empty video frame";
134 updateCurrentVideoFrame({});
138 updateCurrentVideoFrame(m_currentPipelineFrame);
143 return m_surfaceCaps;
148 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::start" << caps;
150 auto optionalVideoInfo = caps.videoInfo();
151 if (optionalVideoInfo) {
152 m_videoInfo = std::move(*optionalVideoInfo);
153 m_format = qVideoFrameFormatFromGstVideoInfo(m_videoInfo);
167 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::stop";
169 m_bufferQueue.clear();
170 QCoreApplication::postEvent(
this,
new QEvent(stopEvent));
175 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::unlock";
180 if (!query || GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION)
183 GstCaps *queryCaps =
nullptr;
184 gboolean needPool =
false;
185 gst_query_parse_allocation(query, &queryCaps, &needPool);
188 if (queryCaps && gst_video_info_from_caps(&info, queryCaps)) {
191 qWarning(qLcGstVideoRenderer) <<
"QGstVideoRenderer::proposeAllocation failed to "
192 "get size from query caps";
196 constexpr int defaultMinBuffers = 3;
198 = qEnvironmentVariableIntValue(
"QT_GSTREAMER_PROPOSE_ALLOCATION_MIN_BUFFERS");
199 static const int minBuffers = env ? env : defaultMinBuffers;
200 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::proposeAllocation: "
201 "needPool:" << needPool
203 <<
"minBuffers:" << minBuffers;
209 gst_query_add_allocation_pool(query,
nullptr, size, minBuffers, 0);
212 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE,
nullptr);
219 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::render";
222 qCDebug(qLcGstVideoRenderer)
223 <<
" buffer received while flushing the sink ... discarding buffer";
224 return GST_FLOW_FLUSHING;
227 GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buffer);
229 QRect vp(meta->x, meta->y, meta->width, meta->height);
230 if (m_format.viewport() != vp) {
231 qCDebug(qLcGstVideoRenderer)
232 << Q_FUNC_INFO <<
" Update viewport on Metadata: [" << meta->height <<
"x"
233 << meta->width <<
" | " << meta->x <<
"x" << meta->y <<
"]";
235 m_format.setViewport(vp);
243 return m_capsMemoryFormat;
245 return qMemoryFormatFromGstBuffer(buffer);
248 qCDebug(qLcGstVideoRenderer) <<
"m_capsMemoryFormat" << m_capsMemoryFormat
249 <<
"bufferMemoryFormat" << bufferMemoryFormat;
251 QVideoFrameFormat bufferVideoFrameFormat = m_format;
256 const bool setFormat_RGBA8888 = [&] {
257#if QT_CONFIG(gstreamer_gl_egl) && QT_CONFIG(linux_dmabuf)
258 if ((m_format.pixelFormat() == QVideoFrameFormat::Format_UYVY
259 || m_format.pixelFormat() == QVideoFrameFormat::Format_YUYV)
260 && bufferMemoryFormat == QGstCaps::DMABuf
261 && m_sink && m_sink->eglDisplay() && m_sink->eglImageTargetTexture2D()) {
263 QRhi *rhi = m_sink->rhi();
264 if (!rhi || rhi->backend() != QRhi::OpenGLES2)
267 auto *nativeHandles =
static_cast<
const QRhiGles2NativeHandles *>(rhi->nativeHandles());
268 QOpenGLContext *glContext = nativeHandles ? nativeHandles->context :
nullptr;
269 return glContext && glContext->isOpenGLES();
274 if (setFormat_RGBA8888) {
276 qCDebug(qLcGstVideoRenderer) <<
"Setting pixel format to Format_RGBA8888";
277 bufferVideoFrameFormat = QVideoFrameFormat(m_format.frameSize(),
278 QVideoFrameFormat::Format_RGBA8888);
279 bufferVideoFrameFormat.setStreamFrameRate(m_format.streamFrameRate());
280 bufferVideoFrameFormat.setColorRange(m_format.colorRange());
281 bufferVideoFrameFormat.setColorTransfer(m_format.colorTransfer());
282 bufferVideoFrameFormat.setColorSpace(m_format.colorSpace());
285 RenderBufferState state{
286 QGstBufferHandle{ buffer, QGstBufferHandle::NeedsRef },
287 bufferVideoFrameFormat,
292 qCDebug(qLcGstVideoRenderer) <<
" sending video frame";
294 qsizetype sizeOfQueue = m_bufferQueue.enqueue(std::move(state));
295 if (sizeOfQueue == 1)
297 QCoreApplication::postEvent(
this,
new QEvent(renderFramesEvent));
304#if QT_CONFIG(gstreamer_gl)
305 if (GST_QUERY_TYPE(query) == GST_QUERY_CONTEXT) {
306 const gchar *type =
nullptr;
307 gst_query_parse_context_type(query, &type);
309 QLatin1StringView typeStr(type);
310 if (typeStr != QLatin1StringView(
"gst.gl.GLDisplay")
311 && typeStr != QLatin1StringView(
"gst.gl.local_context")) {
315 QMutexLocker locker(&m_sinkMutex);
319 auto *gstGlContext = typeStr == QLatin1StringView(
"gst.gl.GLDisplay")
320 ? m_sink->gstGlDisplayContext() : m_sink->gstGlLocalContext();
324 gst_query_set_context(query, gstGlContext);
336 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::gstEvent:" << event;
338 switch (GST_EVENT_TYPE(event)) {
340 return gstEventHandleTag(event);
342 return gstEventHandleEOS(event);
343 case GST_EVENT_FLUSH_START:
344 return gstEventHandleFlushStart(event);
345 case GST_EVENT_FLUSH_STOP:
346 return gstEventHandleFlushStop(event);
355 if (isActive == m_isActive)
358 m_isActive = isActive;
360 updateCurrentVideoFrame(m_currentPipelineFrame);
362 updateCurrentVideoFrame({});
367 m_currentVideoFrame = std::move(frame);
369 m_sink->setVideoFrame(m_currentVideoFrame);
374 GstTagList *taglist =
nullptr;
375 gst_event_parse_tag(event, &taglist);
379 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::gstEventHandleTag:" << taglist;
382 if (!gst_tag_list_get_string(taglist, GST_TAG_IMAGE_ORIENTATION, &value))
387 m_format.setMirrored(parsed.flip);
388 m_format.setRotation(parsed.rotation);
400 m_bufferQueue.clear();
412#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
418 g_object_new(QGstVideoRendererSink::get_type(),
nullptr));
420 return QGstVideoRendererSinkElement{
422 QGstElement::NeedsRef,
433 static const GTypeInfo info =
447 static const GType type = g_type_register_static(GST_TYPE_VIDEO_SINK,
"QGstVideoRendererSink",
448 &info, GTypeFlags(0));
455 Q_UNUSED(class_data);
459 GstVideoSinkClass *video_sink_class =
reinterpret_cast<GstVideoSinkClass *>(g_class);
460 video_sink_class->show_frame = QGstVideoRendererSink::show_frame;
462 GstBaseSinkClass *base_sink_class =
reinterpret_cast<GstBaseSinkClass *>(g_class);
463 base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
464 base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
465 base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
466 base_sink_class->stop = QGstVideoRendererSink::stop;
467 base_sink_class->unlock = QGstVideoRendererSink::unlock;
468 base_sink_class->query = QGstVideoRendererSink::query;
469 base_sink_class->event = QGstVideoRendererSink::event;
471 GstElementClass *element_class =
reinterpret_cast<GstElementClass *>(g_class);
472 element_class->change_state = QGstVideoRendererSink::change_state;
473 gst_element_class_set_metadata(element_class,
474 "Qt built-in video renderer sink",
476 "Qt default built-in video renderer sink",
479 GObjectClass *object_class =
reinterpret_cast<GObjectClass *>(g_class);
480 object_class->finalize = QGstVideoRendererSink::finalize;
485 static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
486 "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
488 "framerate = (fraction) [ 0, MAX ], "
489 "width = (int) [ 1, MAX ], "
490 "height = (int) [ 1, MAX ]"));
492 gst_element_class_add_pad_template(
493 GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
512 delete sink->renderer;
515 G_OBJECT_CLASS(gvrs_sink_parent_class)->finalize(object);
519 GstElement *element, GstStateChange transition)
521 GstStateChangeReturn ret =
522 GST_ELEMENT_CLASS(gvrs_sink_parent_class)->change_state(element, transition);
523 qCDebug(qLcGstVideoRenderer) <<
"QGstVideoRenderer::change_state:" << transition << ret;
533 caps = QGstCaps(gst_caps_intersect(caps.caps(), filter), QGstCaps::HasRef);
535 return caps.release();
541 auto caps = QGstCaps(gcaps, QGstCaps::NeedsRef);
543 qCDebug(qLcGstVideoRenderer) <<
"set_caps:" << caps;
556 return sink->renderer->proposeAllocation(query);
576 return sink->renderer->render(buffer);
582 if (sink->renderer->query(query))
585 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->query(base, query);
591 sink->renderer->gstEvent(event);
592 return GST_BASE_SINK_CLASS(gvrs_sink_parent_class)->event(base, event);
611 return reinterpret_cast<QGstVideoRendererSink *>(element());
MemoryFormat memoryFormat() const
QGstVideoRendererSinkElement(QGstVideoRendererSink *, RefMode)
QGstVideoRendererSink * qGstVideoRendererSink() const
static QGstVideoRendererSinkElement createSink(QGstreamerRelayVideoSink *surface)
GstFlowReturn render(GstBuffer *)
~QGstVideoRenderer() override
void customEvent(QEvent *) override
This event handler can be reimplemented in a subclass to receive custom events.
bool start(const QGstCaps &)
QGstVideoRenderer(QGstreamerRelayVideoSink *)
void gstEvent(GstEvent *)
bool proposeAllocation(GstQuery *)
void setFrameTimeStampsFromBuffer(QVideoFrame *frame, GstBuffer *buffer)
static GstVideoSinkClass * gvrs_sink_parent_class
static QGstreamerRelayVideoSink * gvrs_current_sink
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)