qt6d3d11: Add Direct3D11 Qt6 QML sink

Adding Direct3D11 backend Qt6 QML videosink element, qml6d3d11sink.
Implementation details are similar to the qt6 plugin in -good
but there are a few notable differences.

* qml6d3d11sink accepts all GstD3D11 supported video formats (e.g., NV12).
* Scene graph (owned by qml6d3d11sink) will hold dedicated and sharable
  RGBA texture which belongs to Qt6's Direct3D11 device, instead of sharing
  GStreamer's own texture with Qt6.
* All rendering operations will be done by using GStreamer's Direct3D11 device.
  Specifically, upstream texture will be copied (in case of RGBA)
  or converted to the above mentioned Qt6's sharable texture.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3707>
This commit is contained in:
Seungha Yang 2022-12-09 02:10:13 +09:00 committed by GStreamer Marge Bot
parent 9825cd8951
commit 7b4e1fd602
21 changed files with 2338 additions and 0 deletions

View file

@ -1,3 +1,4 @@
subprojects/gst-plugins-bad/ext/qt6d3d11
subprojects/gst-plugins-bad/gst-libs/gst/cuda
subprojects/gst-plugins-bad/gst-libs/gst/d3d11
subprojects/gst-plugins-bad/gst-libs/gst/va

View file

@ -48,6 +48,7 @@ subdir('openmpt')
subdir('openni2')
subdir('opus')
subdir('qroverlay')
subdir('qt6d3d11')
subdir('resindvd')
subdir('rsvg')
subdir('rtmp')

View file

@ -0,0 +1,664 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/d3d11/gstd3d11.h>
#include <gst/d3d11/gstd3d11-private.h>
#include "gstqml6d3d11sink.h"
#include "gstqt6d3d11videoitem.h"
#include <mutex>
GST_DEBUG_CATEGORY_STATIC (gst_qml6_d3d11_sink_debug);
#define GST_CAT_DEFAULT gst_qml6_d3d11_sink_debug
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
(GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, GST_D3D11_SINK_FORMATS) "; "
GST_VIDEO_CAPS_MAKE (GST_D3D11_SINK_FORMATS)));
enum
{
PROP_0,
PROP_WIDGET,
PROP_FORCE_ASPECT_RATIO,
};
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
struct GstQml6D3D11SinkPrivate
{
QSharedPointer < GstQt6D3D11VideoItemProxy > widget;
std::recursive_mutex lock;
GstVideoInfo info;
GstD3D11Device *device = nullptr;
gint64 adapter_luid = 0;
GstBuffer *prepared_buffer = nullptr;
GstBufferPool *pool = nullptr;
gboolean force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
};
struct _GstQml6D3D11Sink
{
GstVideoSink parent;
GstQml6D3D11SinkPrivate *priv;
};
static void
gst_qml6_d3d11_sink_navigation_init (GstNavigationInterface * iface);
static void gst_qml6_d3d11_sink_dispose (GObject * object);
static void gst_qml6_d3d11_sink_finalize (GObject * object);
static void gst_qml6_d3d11_sink_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_qml6_d3d11_sink_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static void gst_qml6_d3d11_sink_set_context (GstElement * element,
GstContext * context);
static gboolean gst_qml6_d3d11_sink_set_caps (GstBaseSink * sink,
GstCaps * caps);
static gboolean gst_qml6_d3d11_sink_start (GstBaseSink * sink);
static gboolean gst_qml6_d3d11_sink_stop (GstBaseSink * sink);
static gboolean gst_qml6_d3d11_sink_propose_allocation (GstBaseSink * sink,
GstQuery * query);
static gboolean gst_qml6_d3d11_sink_query (GstBaseSink * sink,
GstQuery * query);
static GstFlowReturn gst_qml6_d3d11_sink_prepare (GstBaseSink * sink,
GstBuffer * buf);
static GstFlowReturn gst_qml6_d3d11_sink_show_frame (GstVideoSink * sink,
GstBuffer * buf);
#define gst_qml6_d3d11_sink_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstQml6D3D11Sink, gst_qml6_d3d11_sink,
GST_TYPE_VIDEO_SINK, GST_DEBUG_CATEGORY_INIT (gst_qml6_d3d11_sink_debug,
"qml6d3d11sink", 0, "qml6d3d11sink");
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
gst_qml6_d3d11_sink_navigation_init);
gst_qt6_d3d11_video_item_init_once ());
static void
gst_qml6_d3d11_sink_class_init (GstQml6D3D11SinkClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstBaseSinkClass *sink_class = GST_BASE_SINK_CLASS (klass);
GstVideoSinkClass *videosink_class = GST_VIDEO_SINK_CLASS (klass);
object_class->dispose = gst_qml6_d3d11_sink_dispose;
object_class->finalize = gst_qml6_d3d11_sink_finalize;
object_class->set_property = gst_qml6_d3d11_sink_set_property;
object_class->get_property = gst_qml6_d3d11_sink_get_property;
g_object_class_install_property (object_class, PROP_WIDGET,
g_param_spec_pointer ("widget", "QQuickItem",
"The QQuickItem to place in the object hierarchy",
(GParamFlags) (GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS)));
g_object_class_install_property (object_class, PROP_FORCE_ASPECT_RATIO,
g_param_spec_boolean ("force-aspect-ratio",
"Force aspect ratio",
"When enabled, scaling will respect original aspect ratio",
DEFAULT_FORCE_ASPECT_RATIO,
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
gst_element_class_set_static_metadata (element_class,
"Qt6 Direct3D11 Video Sink", "Sink/Video",
"A video sink that renders to a QQuickItem for Qt6 using Direct3D11",
"Seungha Yang <seungha@centricular.com>");
gst_element_class_add_static_pad_template (element_class, &sink_template);
element_class->set_context =
GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_set_context);
sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_set_caps);
sink_class->start = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_start);
sink_class->stop = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_stop);
sink_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_propose_allocation);
sink_class->query = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_query);
sink_class->prepare = GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_prepare);
videosink_class->show_frame =
GST_DEBUG_FUNCPTR (gst_qml6_d3d11_sink_show_frame);
}
static void
gst_qml6_d3d11_sink_init (GstQml6D3D11Sink * self)
{
self->priv = new GstQml6D3D11SinkPrivate ();
}
static void
gst_qml6_d3d11_sink_dispose (GObject * object)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object);
GstQml6D3D11SinkPrivate *priv = self->priv;
gst_clear_object (&priv->device);
self->priv->widget.clear ();
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_qml6_d3d11_sink_finalize (GObject * object)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object);
delete self->priv;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_qml6_d3d11_sink_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
switch (prop_id) {
case PROP_FORCE_ASPECT_RATIO:
priv->force_aspect_ratio = g_value_get_boolean (value);
if (priv->widget)
priv->widget->SetForceAspectRatio (priv->force_aspect_ratio);
break;
case PROP_WIDGET:
{
GstQt6D3D11VideoItem *item =
(GstQt6D3D11VideoItem *) g_value_get_pointer (value);
if (item) {
priv->widget = item->Proxy ();
if (priv->widget) {
priv->widget->SetSink (GST_ELEMENT_CAST (self));
priv->widget->SetForceAspectRatio (priv->force_aspect_ratio);
}
} else {
priv->widget.clear ();
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_qml6_d3d11_sink_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (object);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
switch (prop_id) {
case PROP_FORCE_ASPECT_RATIO:
g_value_set_boolean (value, priv->force_aspect_ratio);
break;
case PROP_WIDGET:
if (priv->widget) {
g_value_set_pointer (value, priv->widget->Item ());
} else {
g_value_set_pointer (value, nullptr);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_qml6_d3d11_sink_set_context (GstElement * element, GstContext * context)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (element);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (priv->widget) {
gint64 adapter_luid = priv->widget->AdapterLuid ();
gst_d3d11_handle_set_context_for_adapter_luid (element,
context, adapter_luid, &priv->device);
}
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}
static gboolean
gst_qml6_d3d11_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
GstBufferPool *pool;
GstStructure *config;
GstD3D11AllocationParams *params;
if (!priv->widget) {
GST_ERROR_OBJECT (self, "Widget is not configured");
return FALSE;
}
if (!gst_video_info_from_caps (&priv->info, caps)) {
GST_ERROR_OBJECT (self, "Invalid caps");
return FALSE;
}
if (priv->pool) {
gst_buffer_pool_set_active (priv->pool, FALSE);
gst_clear_object (&priv->pool);
}
priv->widget->SetCaps (caps);
pool = gst_d3d11_buffer_pool_new (priv->device);
config = gst_buffer_pool_get_config (pool);
params = gst_d3d11_allocation_params_new (priv->device, &priv->info,
GST_D3D11_ALLOCATION_FLAG_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0);
gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
gst_d3d11_allocation_params_free (params);
gst_buffer_pool_config_set_params (config, caps, priv->info.size, 0, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (self, "Couldn't set pool config");
goto error;
}
if (!gst_buffer_pool_set_active (pool, TRUE)) {
GST_ERROR_OBJECT (self, "Couldn't set active");
goto error;
}
priv->pool = pool;
return TRUE;
error:
gst_clear_object (&pool);
return FALSE;
}
static gboolean
gst_qml6_d3d11_sink_start (GstBaseSink * sink)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
gint64 adapter_luid;
GST_DEBUG_OBJECT (self, "Start");
if (!priv->widget) {
GST_ERROR_OBJECT (self, "Widget is not configured");
return FALSE;
}
adapter_luid = priv->widget->AdapterLuid ();
if (!gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self),
adapter_luid, &priv->device)) {
GST_ERROR_OBJECT (self, "Couldn't create d3d11 device for luid %"
G_GINT64_FORMAT, adapter_luid);
return FALSE;
}
priv->adapter_luid = adapter_luid;
return TRUE;
}
static gboolean
gst_qml6_d3d11_sink_stop (GstBaseSink * sink)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
GST_DEBUG_OBJECT (self, "Stop");
if (priv->pool) {
gst_buffer_pool_set_active (priv->pool, FALSE);
gst_clear_object (&priv->pool);
}
gst_clear_buffer (&priv->prepared_buffer);
gst_clear_object (&priv->device);
return TRUE;
}
static gboolean
gst_qml6_d3d11_sink_propose_allocation (GstBaseSink * sink, GstQuery * query)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
GstCaps *caps;
GstBufferPool *pool = nullptr;
GstVideoInfo info;
guint size;
gboolean need_pool;
if (!priv->device) {
GST_ERROR_OBJECT (self, "No d3d11 device configured");
return FALSE;
}
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_WARNING_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (self, "Invalid caps");
return FALSE;
}
/* the normal size of a frame */
size = info.size;
if (need_pool) {
GstCapsFeatures *features;
GstStructure *config;
gboolean is_d3d11 = FALSE;
features = gst_caps_get_features (caps, 0);
if (features
&& gst_caps_features_contains (features,
GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
GST_DEBUG_OBJECT (self, "upstream support d3d11 memory");
pool = gst_d3d11_buffer_pool_new (priv->device);
is_d3d11 = TRUE;
} else {
pool = gst_video_buffer_pool_new ();
}
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
if (!is_d3d11) {
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
}
size = GST_VIDEO_INFO_SIZE (&info);
if (is_d3d11) {
GstD3D11AllocationParams *d3d11_params =
gst_d3d11_allocation_params_new (priv->device, &info,
GST_D3D11_ALLOCATION_FLAG_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0);
gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params);
gst_d3d11_allocation_params_free (d3d11_params);
}
gst_buffer_pool_config_set_params (config, caps, (guint) size, 2, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (pool, "Couldn't set config");
gst_object_unref (pool);
return FALSE;
}
/* d3d11 buffer pool will update buffer size based on allocated texture,
* get size from config again */
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr,
nullptr);
gst_structure_free (config);
}
gst_query_add_allocation_pool (query, pool, size, 2, 0);
if (pool)
gst_object_unref (pool);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return TRUE;
}
static gboolean
gst_qml6_d3d11_sink_query (GstBaseSink * sink, GstQuery * query)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
if (gst_d3d11_handle_context_query (GST_ELEMENT (self), query,
priv->device)) {
return TRUE;
}
break;
default:
break;
}
return GST_BASE_SINK_CLASS (parent_class)->query (sink, query);
}
static GstBuffer *
gst_qml6_d3d11_sink_do_system_copy (GstQml6D3D11Sink * self, GstBuffer * in_buf)
{
GstQml6D3D11SinkPrivate *priv = self->priv;
GstBuffer *out_buf;
GstVideoFrame in_frame, out_frame;
if (!gst_video_frame_map (&in_frame, &priv->info, in_buf, GST_MAP_READ)) {
GST_ERROR_OBJECT (self, "Couldn't map input buffer");
return nullptr;
}
if (gst_buffer_pool_acquire_buffer (priv->pool, &out_buf, nullptr)
!= GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire buffer");
gst_video_frame_unmap (&in_frame);
return nullptr;
}
if (!gst_video_frame_map (&out_frame, &priv->info, out_buf, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (self, "Couldn't map output buffer");
gst_video_frame_unmap (&in_frame);
gst_buffer_unref (out_buf);
return nullptr;
}
gst_video_frame_copy (&out_frame, &in_frame);
gst_video_frame_unmap (&in_frame);
gst_video_frame_unmap (&out_frame);
/* Do upload staging to default texture */
for (guint i = 0; i < gst_buffer_n_memory (out_buf); i++) {
GstMemory *mem = gst_buffer_peek_memory (out_buf, i);
GstMapInfo info;
gst_memory_map (mem, &info, (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
gst_memory_unmap (mem, &info);
}
return out_buf;
}
static GstBuffer *
gst_qml6_d3d11_sink_do_gpu_copy (GstQml6D3D11Sink * self, GstBuffer * in_buf)
{
GstQml6D3D11SinkPrivate *priv = self->priv;
GstBuffer *out_buf;
ID3D11DeviceContext *context;
if (gst_buffer_pool_acquire_buffer (priv->pool, &out_buf, nullptr)
!= GST_FLOW_OK) {
GST_ERROR_OBJECT (self, "Couldn't acquire buffer");
return nullptr;
}
GstD3D11DeviceLockGuard lk (priv->device);
context = gst_d3d11_device_get_device_context_handle (priv->device);
for (guint i = 0; i < gst_buffer_n_memory (in_buf); i++) {
GstMapInfo src_info, dst_info;
GstMemory *src_mem, *dst_mem;
D3D11_TEXTURE2D_DESC src_desc, dst_desc;
D3D11_BOX src_box;
guint subresource_idx;
ID3D11Texture2D *src_tex, *dst_tex;
src_mem = gst_buffer_peek_memory (in_buf, i);
dst_mem = gst_buffer_peek_memory (out_buf, i);
gst_memory_map (src_mem, &src_info,
(GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
gst_memory_map (dst_mem,
&dst_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11));
src_tex = (ID3D11Texture2D *) src_info.data;
dst_tex = (ID3D11Texture2D *) dst_info.data;
src_tex->GetDesc (&src_desc);
dst_tex->GetDesc (&dst_desc);
subresource_idx = GPOINTER_TO_UINT (src_info.user_data[0]);
src_box.left = 0;
src_box.top = 0;
src_box.front = 0;
src_box.back = 1;
src_box.right = MIN (src_desc.Width, dst_desc.Width);
src_box.bottom = MIN (src_desc.Height, dst_desc.Height);
context->CopySubresourceRegion (dst_tex, 0,
0, 0, 0, src_tex, subresource_idx, &src_box);
gst_memory_unmap (src_mem, &src_info);
gst_memory_unmap (dst_mem, &dst_info);
}
return out_buf;
}
static GstFlowReturn
gst_qml6_d3d11_sink_prepare (GstBaseSink * sink, GstBuffer * buf)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
GstBuffer *render_buf = nullptr;
GstMemory *mem;
GstD3D11Device *other_device = nullptr;
if (!priv->widget) {
GST_ERROR_OBJECT (self, "Widget is not configured");
return GST_FLOW_ERROR;
}
gst_clear_buffer (&priv->prepared_buffer);
mem = gst_buffer_peek_memory (buf, 0);
if (!gst_is_d3d11_memory (mem)) {
render_buf = gst_qml6_d3d11_sink_do_system_copy (self, buf);
} else {
GstD3D11Memory *dmem = GST_D3D11_MEMORY_CAST (mem);
other_device = dmem->device;
if (dmem->device != priv->device) {
gint64 luid;
/* different device, check if it's the same GPU */
g_object_get (dmem->device, "adapter-luid", &luid, nullptr);
if (luid != priv->adapter_luid) {
/* From other GPU, system copy */
render_buf = gst_qml6_d3d11_sink_do_system_copy (self, buf);
} else {
/* same GPU and sharable, increase ref */
render_buf = gst_buffer_ref (buf);
}
} else {
render_buf = gst_buffer_ref (buf);
}
}
if (!render_buf) {
GST_ERROR_OBJECT (self, "Couldn't prepare output buffer");
return GST_FLOW_ERROR;
}
priv->prepared_buffer = render_buf;
return GST_FLOW_OK;
}
static GstFlowReturn
gst_qml6_d3d11_sink_show_frame (GstVideoSink * sink, GstBuffer * buf)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (sink);
GstQml6D3D11SinkPrivate *priv = self->priv;
std::lock_guard < std::recursive_mutex > lk (priv->lock);
if (!priv->widget) {
GST_ERROR_OBJECT (self, "Widget is not configured");
return GST_FLOW_ERROR;
}
if (!priv->prepared_buffer) {
GST_ERROR_OBJECT (self, "No prepared sample");
return GST_FLOW_ERROR;
}
priv->widget->SetBuffer (priv->prepared_buffer);
return GST_FLOW_OK;
}
static void
gst_qml6_d3d11_sink_navigation_send_event (GstNavigation * navigation,
GstEvent * event)
{
GstQml6D3D11Sink *self = GST_QML6_D3D11_SINK (navigation);
if (event) {
gboolean handled;
gst_event_ref (event);
handled = gst_pad_push_event (GST_VIDEO_SINK_PAD (self), event);
if (!handled) {
gst_element_post_message (GST_ELEMENT_CAST (self),
gst_navigation_message_new_event (GST_OBJECT_CAST (self), event));
}
gst_event_unref (event);
}
}
static void
gst_qml6_d3d11_sink_navigation_init (GstNavigationInterface * iface)
{
iface->send_event_simple = gst_qml6_d3d11_sink_navigation_send_event;
}

View file

@ -0,0 +1,31 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_QML6_D3D11_SINK (gst_qml6_d3d11_sink_get_type())
G_DECLARE_FINAL_TYPE (GstQml6D3D11Sink, gst_qml6_d3d11_sink,
GST, QML6_D3D11_SINK, GstVideoSink);
G_END_DECLS

View file

@ -0,0 +1,257 @@
/* GStreamer
* Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstqsg6d3d11node.h"
#include <wrl.h>
GST_DEBUG_CATEGORY_EXTERN (gst_qt6_d3d11_debug);
#define GST_CAT_DEFAULT gst_qt6_d3d11_debug
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
GstQSG6D3D11Node::GstQSG6D3D11Node (QQuickItem * item, GstD3D11Device * device)
{
window_ = item->window ();
qt_device_ = (GstD3D11Device *) gst_object_ref (device);
frame_.buffer = nullptr;
gst_video_info_init (&render_info_);
gst_video_info_init (&info_);
setOwnsTexture (true);
resize (8, 8);
mapTexture ();
}
GstQSG6D3D11Node::~GstQSG6D3D11Node ()
{
GST_DEBUG ("Destroying %p", this);
unmapTexture ();
gst_clear_buffer (&sharable_);
gst_clear_buffer (&backbuffer_);
gst_clear_buffer (&buffer_);
gst_clear_caps (&caps_);
if (pool_) {
gst_buffer_pool_set_active (pool_, FALSE);
gst_object_unref (pool_);
}
gst_clear_object (&conv_);
gst_clear_object (&device_);
gst_clear_object (&qt_device_);
}
QSGTexture *
GstQSG6D3D11Node::texture () const
{
return QSGSimpleTextureNode::texture ();
}
void
GstQSG6D3D11Node::mapTexture ()
{
if (backbuffer_) {
gst_video_frame_map (&frame_, &render_info_, backbuffer_,
(GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
}
}
void
GstQSG6D3D11Node::unmapTexture ()
{
if (frame_.buffer)
gst_video_frame_unmap (&frame_);
frame_.buffer = nullptr;
}
void
GstQSG6D3D11Node::resize (guint width, guint height)
{
GstStructure *config;
GstD3D11AllocationParams *params;
GstCaps *caps;
GstD3D11Memory *dmem;
ID3D11Texture2D *tex;
if (pool_ && width == render_info_.width && height == render_info_.height)
return;
gst_clear_buffer (&sharable_);
gst_clear_buffer (&backbuffer_);
if (pool_) {
gst_buffer_pool_set_active (pool_, FALSE);
gst_clear_object (&pool_);
}
pool_ = gst_d3d11_buffer_pool_new (qt_device_);
gst_video_info_set_format (&render_info_, GST_VIDEO_FORMAT_RGBA,
width, height);
render_info_.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
caps = gst_video_info_to_caps (&render_info_);
params = gst_d3d11_allocation_params_new (qt_device_, &render_info_,
GST_D3D11_ALLOCATION_FLAG_DEFAULT,
D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX);
config = gst_buffer_pool_get_config (pool_);
gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
gst_d3d11_allocation_params_free (params);
gst_buffer_pool_config_set_params (config, caps, render_info_.size, 0, 0);
gst_caps_unref (caps);
gst_buffer_pool_set_config (pool_, config);
gst_buffer_pool_set_active (pool_, TRUE);
gst_buffer_pool_acquire_buffer (pool_, &backbuffer_, nullptr);
dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0);
tex = (ID3D11Texture2D *) gst_d3d11_memory_get_resource_handle (dmem);
auto qt_texture = QNativeInterface::QSGD3D11Texture::fromNative (tex, window_,
QSize (render_info_.width, render_info_.height),
QQuickWindow::TextureHasAlphaChannel);
setTexture (qt_texture);
}
/* only called from QT rendering thread */
void
GstQSG6D3D11Node::SetCaps (GstCaps * caps)
{
if (caps_ && caps && gst_caps_is_equal (caps_, caps))
return;
gst_clear_object (&conv_);
gst_clear_buffer (&sharable_);
gst_clear_buffer (&buffer_);
if (caps)
gst_video_info_from_caps (&info_, caps);
else
gst_video_info_init (&info_);
gst_caps_replace (&caps_, caps);
}
/* only called from QT rendering thread */
void
GstQSG6D3D11Node::SetBuffer (GstBuffer * buffer)
{
gboolean buffer_changed;
buffer_changed = gst_buffer_replace (&buffer_, buffer);
unmapTexture ();
if (buffer_ && buffer_changed) {
GstD3D11Memory *dmem;
resize (info_.width, info_.height);
dmem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0);
if (dmem->device != device_) {
gst_clear_object (&conv_);
gst_clear_buffer (&sharable_);
gst_clear_object (&device_);
device_ = (GstD3D11Device *) gst_object_ref (dmem->device);
}
gst_d3d11_device_lock (device_);
if (!conv_)
conv_ = gst_d3d11_converter_new (device_, &info_, &render_info_, nullptr);
if (!sharable_) {
GstMemory *mem_wrapped;
ID3D11Resource *resource;
ComPtr < IDXGIResource > dxgi_resource;
ComPtr < ID3D11Texture2D > shared_texture;
ID3D11Device *device_handle;
HANDLE handle;
device_handle = gst_d3d11_device_get_device_handle (dmem->device);
dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0);
resource = gst_d3d11_memory_get_resource_handle (dmem);
resource->QueryInterface (IID_PPV_ARGS (&dxgi_resource));
dxgi_resource->GetSharedHandle (&handle);
device_handle->OpenSharedResource (handle,
IID_PPV_ARGS (&shared_texture));
mem_wrapped = gst_d3d11_allocator_alloc_wrapped (nullptr, device_,
shared_texture.Get (), render_info_.size, nullptr, nullptr);
sharable_ = gst_buffer_new ();
gst_buffer_append_memory (sharable_, mem_wrapped);
}
if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_RGBA) {
GstMapInfo src_info, dst_info;
GstMemory *src_mem, *dst_mem;
D3D11_TEXTURE2D_DESC src_desc, dst_desc;
D3D11_BOX src_box;
ID3D11Texture2D *src_tex, *dst_tex;
ID3D11DeviceContext *context =
gst_d3d11_device_get_device_context_handle (device_);
src_mem = gst_buffer_peek_memory (buffer_, 0);
dst_mem = gst_buffer_peek_memory (sharable_, 0);
gst_memory_map (src_mem, &src_info,
(GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
gst_memory_map (dst_mem,
&dst_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11));
src_tex = (ID3D11Texture2D *) src_info.data;
dst_tex = (ID3D11Texture2D *) dst_info.data;
src_tex->GetDesc (&src_desc);
dst_tex->GetDesc (&dst_desc);
src_box.left = 0;
src_box.top = 0;
src_box.front = 0;
src_box.back = 1;
src_box.right = MIN (src_desc.Width, dst_desc.Width);
src_box.bottom = MIN (src_desc.Height, dst_desc.Height);
context->CopySubresourceRegion (dst_tex, 0,
0, 0, 0, src_tex, 0, &src_box);
gst_memory_unmap (src_mem, &src_info);
gst_memory_unmap (dst_mem, &dst_info);
} else {
gst_d3d11_converter_convert_buffer_unlocked (conv_, buffer_, sharable_);
}
gst_d3d11_device_unlock (device_);
markDirty (QSGNode::DirtyMaterial);
}
mapTexture ();
}

View file

@ -0,0 +1,62 @@
/* GStreamer
* Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/d3d11/gstd3d11.h>
#include <QtQuick/QQuickItem>
#include <QtQuick/QSGTexture>
#include <QtQuick/QSGTextureProvider>
#include <QtQuick/QSGSimpleTextureNode>
class GstQSG6D3D11Node : public QSGTextureProvider, public QSGSimpleTextureNode
{
Q_OBJECT
public:
GstQSG6D3D11Node (QQuickItem * item, GstD3D11Device * device);
~GstQSG6D3D11Node ();
QSGTexture *texture() const override;
void SetCaps (GstCaps * caps);
void SetBuffer (GstBuffer * buffer);
private:
void resize (guint width, guint height);
void mapTexture ();
void unmapTexture ();
private:
QQuickWindow *window_;
GstD3D11Device *qt_device_;
GstD3D11Device *device_ = nullptr;
GstBuffer *buffer_ = nullptr;
GstCaps *caps_ = nullptr;
GstBuffer *sharable_ = nullptr;
GstBuffer *backbuffer_ = nullptr;
GstVideoFrame frame_;
GstVideoInfo info_;
GstVideoInfo render_info_;
GstBufferPool *pool_ = nullptr;
GstD3D11Converter *conv_ = nullptr;
};

View file

@ -0,0 +1,630 @@
/* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/d3d11/gstd3d11.h>
#include <gst/d3d11/gstd3d11-private.h>
#include "gstqt6d3d11videoitem.h"
#include "gstqsg6d3d11node.h"
GST_DEBUG_CATEGORY_EXTERN (gst_qt6_d3d11_debug);
#define GST_CAT_DEFAULT gst_qt6_d3d11_debug
void
GstQt6D3D11VideoItemProxy::InvalidateRef (void)
{
std::lock_guard < std::mutex > lk (lock);
item = nullptr;
}
void
GstQt6D3D11VideoItemProxy::SetSink (GstElement * sink)
{
std::lock_guard < std::mutex > lk (lock);
if (!item)
return;
item->setSink (sink);
}
void
GstQt6D3D11VideoItemProxy::SetCaps (GstCaps * caps)
{
std::lock_guard < std::mutex > lk (lock);
if (!item)
return;
item->setCaps (caps);
}
void
GstQt6D3D11VideoItemProxy::SetBuffer (GstBuffer * buffer)
{
std::lock_guard < std::mutex > lk (lock);
if (!item)
return;
item->setBuffer (buffer);
}
void
GstQt6D3D11VideoItemProxy::SetForceAspectRatio (bool force)
{
std::lock_guard < std::mutex > lk (lock);
if (!item)
return item->setForceAspectRatio (force);
}
gint64
GstQt6D3D11VideoItemProxy::AdapterLuid ()
{
std::lock_guard < std::mutex > lk (lock);
if (!item)
return 0;
return item->AdapterLuid ();
}
GstQt6D3D11VideoItem *
GstQt6D3D11VideoItemProxy::Item (void)
{
std::lock_guard < std::mutex > lk (lock);
return item;
}
GstQt6D3D11VideoItem::GstQt6D3D11VideoItem ()
{
g_weak_ref_init (&sink_, nullptr);
connect (this, &QQuickItem::windowChanged, this,
&GstQt6D3D11VideoItem::handleWindowChanged);
proxy_ = QSharedPointer < GstQt6D3D11VideoItemProxy >
(new GstQt6D3D11VideoItemProxy (this));
setFlag (ItemHasContents, true);
setAcceptedMouseButtons (Qt::AllButtons);
setAcceptHoverEvents (true);
setAcceptTouchEvents (true);
gst_video_info_init (&info_);
GST_DEBUG ("%p New Qt6 Video Item", this);
}
GstQt6D3D11VideoItem::~GstQt6D3D11VideoItem ()
{
GST_DEBUG ("%p Destroying Qt6 Video Item", this);
proxy_->InvalidateRef ();
proxy_.clear ();
gst_clear_buffer (&buffer_);
gst_clear_caps (&caps_);
g_weak_ref_clear (&sink_);
}
QSharedPointer < GstQt6D3D11VideoItemProxy > GstQt6D3D11VideoItem::Proxy (void)
{
return proxy_;
}
void
GstQt6D3D11VideoItem::setSink (GstElement * sink)
{
std::lock_guard < std::mutex > lk (lock_);
g_weak_ref_set (&sink_, sink);
}
void
GstQt6D3D11VideoItem::setCaps (GstCaps * caps)
{
std::lock_guard < std::mutex > lk (lock_);
guint num, den;
gst_caps_replace (&caps_, caps);
gst_video_info_from_caps (&info_, caps);
gst_video_calculate_display_ratio (&num, &den, info_.width, info_.height,
info_.par_n, info_.par_d, 1, 1);
display_width_ = info_.width;
display_height_ = info_.height;
if (display_width_ % num == 0) {
display_height_ = gst_util_uint64_scale_int (display_width_, den, num);
} else {
display_width_ = gst_util_uint64_scale_int (display_height_, num, den);
}
update_par_ = true;
}
void
GstQt6D3D11VideoItem::setBuffer (GstBuffer * buffer)
{
std::lock_guard < std::mutex > lk (lock_);
gst_buffer_replace (&buffer_, buffer);
if (this->window_)
QMetaObject::invokeMethod (this->window_, "update", Qt::QueuedConnection);
}
void
GstQt6D3D11VideoItem::setForceAspectRatio (bool force)
{
std::lock_guard < std::mutex > lk (lock_);
if (force != force_aspect_ratio_)
update_par_ = true;
force_aspect_ratio_ = force;
}
gint64
GstQt6D3D11VideoItem::AdapterLuid (void)
{
std::lock_guard < std::mutex > lk (lock_);
if (!qt_device_)
return 0;
return luid_;
}
void
GstQt6D3D11VideoItem::handleWindowChanged (QQuickWindow * window)
{
if (window) {
connect (window, &QQuickWindow::beforeSynchronizing, this,
&GstQt6D3D11VideoItem::onBeforeSynchronizing, Qt::DirectConnection);
connect (window, &QQuickWindow::sceneGraphInvalidated, this,
&GstQt6D3D11VideoItem::onSceneGraphInvalidated, Qt::DirectConnection);
} else {
gst_clear_object (&qt_device_);
window_ = nullptr;
}
node_ = nullptr;
}
QSGNode *
GstQt6D3D11VideoItem::updatePaintNode (QSGNode * old_node,
UpdatePaintNodeData * data)
{
if (!qt_device_)
return old_node;
GstQSG6D3D11Node *new_node = static_cast < GstQSG6D3D11Node * >(old_node);
GstVideoRectangle src, dst, result;
std::lock_guard < std::mutex > lk (lock_);
GST_TRACE ("%p updatePaintNode", this);
if (!new_node)
new_node = new GstQSG6D3D11Node (this, qt_device_);
if (force_aspect_ratio_ && caps_) {
src.w = display_width_;
src.h = display_height_;
dst.x = boundingRect ().x ();
dst.y = boundingRect ().y ();
dst.w = boundingRect ().width ();
dst.h = boundingRect ().height ();
gst_video_sink_center_rect (src, dst, &result, TRUE);
} else {
result.x = boundingRect ().x ();
result.y = boundingRect ().y ();
result.w = boundingRect ().width ();
result.h = boundingRect ().height ();
}
new_node->setRect (QRectF (result.x, result.y, result.w, result.h));
node_ = new_node;
if (caps_ || !force_aspect_ratio_)
update_par_ = false;
else
update_par_ = true;
return new_node;
}
void
GstQt6D3D11VideoItem::onBeforeSynchronizing (void)
{
QSGRendererInterface *iface;
QQuickWindow *window;
ID3D11Device *device;
std::unique_lock < std::mutex > lk (lock_);
if (window_)
return;
GST_DEBUG ("%p Scene graph initialized", this);
window = this->window ();
if (!window) {
GST_WARNING ("Initialized scene graph has no associated window");
return;
}
iface = window->rendererInterface ();
if (!iface) {
GST_WARNING ("Couldn't get renderer interface");
return;
}
if (iface->graphicsApi () != QSGRendererInterface::Direct3D11) {
GST_WARNING ("Not a d3d11 api");
return;
}
device = reinterpret_cast < ID3D11Device * >(iface->getResource (window,
QSGRendererInterface::DeviceResource));
if (!device) {
GST_WARNING ("Couldn't get d3d11 device");
return;
}
qt_device_ = gst_d3d11_device_new_wrapped (device);
g_object_get (qt_device_, "adapter-luid", &luid_, nullptr);
window_ = window;
lk.unlock ();
connect (window, &QQuickWindow::beforeRendering, this,
&GstQt6D3D11VideoItem::onBeforeRendering, Qt::DirectConnection);
}
void
GstQt6D3D11VideoItem::onSceneGraphInvalidated (void)
{
std::unique_lock < std::mutex > lk (lock_);
GST_DEBUG ("%p Scene graph invalidated", this);
gst_clear_object (&qt_device_);
window_ = nullptr;
node_ = nullptr;
}
void
GstQt6D3D11VideoItem::onBeforeRendering (void)
{
std::lock_guard < std::mutex > lk (lock_);
if (update_par_ && caps_ && node_) {
GstVideoRectangle src, dst, result;
GST_DEBUG ("Updating node rect");
if (force_aspect_ratio_ && caps_) {
src.w = display_width_;
src.h = display_height_;
dst.x = boundingRect ().x ();
dst.y = boundingRect ().y ();
dst.w = boundingRect ().width ();
dst.h = boundingRect ().height ();
gst_video_sink_center_rect (src, dst, &result, TRUE);
} else {
result.x = boundingRect ().x ();
result.y = boundingRect ().y ();
result.w = boundingRect ().width ();
result.h = boundingRect ().height ();
}
node_->setRect (QRectF (result.x, result.y, result.w, result.h));
update_par_ = false;
}
if (node_) {
node_->SetCaps (caps_);
node_->SetBuffer (buffer_);
}
return;
}
void
GstQt6D3D11VideoItem::hoverEnterEvent (QHoverEvent * event)
{
mouse_hovering_ = true;
}
void
GstQt6D3D11VideoItem::hoverLeaveEvent (QHoverEvent * event)
{
mouse_hovering_ = false;
}
void
GstQt6D3D11VideoItem::fitStreamToAllocatedSize (GstVideoRectangle * result)
{
if (force_aspect_ratio_) {
GstVideoRectangle src, dst;
src.x = 0;
src.y = 0;
src.w = display_width_;
src.h = display_height_;
dst.x = 0;
dst.y = 0;
dst.w = width ();
dst.h = height ();
gst_video_sink_center_rect (src, dst, result, TRUE);
} else {
result->x = 0;
result->y = 0;
result->w = width ();
result->h = height ();
}
}
QPointF
GstQt6D3D11VideoItem::mapPointToStreamSize (QPointF pos)
{
gdouble stream_width, stream_height;
GstVideoRectangle result;
double stream_x, stream_y;
double x, y;
fitStreamToAllocatedSize (&result);
stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&info_);
stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&info_);
x = pos.x ();
y = pos.y ();
/* from display coordinates to stream coordinates */
if (result.w > 0)
stream_x = (x - result.x) / result.w * stream_width;
else
stream_x = 0.;
/* clip to stream size */
stream_x = CLAMP (stream_x, 0., stream_width);
/* same for y-axis */
if (result.h > 0)
stream_y = (y - result.y) / result.h * stream_height;
else
stream_y = 0.;
stream_y = CLAMP (stream_y, 0., stream_height);
return QPointF (stream_x, stream_y);
}
static GstNavigationModifierType
translateModifiers (Qt::KeyboardModifiers modifiers)
{
return (GstNavigationModifierType) (
((modifiers & Qt::KeyboardModifier::ShiftModifier) ?
GST_NAVIGATION_MODIFIER_SHIFT_MASK : 0) | ((modifiers &
Qt::KeyboardModifier::ControlModifier) ?
GST_NAVIGATION_MODIFIER_CONTROL_MASK : 0) | ((modifiers &
Qt::
KeyboardModifier::AltModifier) ? GST_NAVIGATION_MODIFIER_MOD1_MASK
: 0) | ((modifiers & Qt::
KeyboardModifier::MetaModifier) ?
GST_NAVIGATION_MODIFIER_META_MASK : 0));
}
static GstNavigationModifierType
translateMouseButtons (Qt::MouseButtons buttons)
{
return (GstNavigationModifierType) (
((buttons & Qt::LeftButton) ? GST_NAVIGATION_MODIFIER_BUTTON1_MASK : 0) |
((buttons & Qt::RightButton) ? GST_NAVIGATION_MODIFIER_BUTTON2_MASK : 0) |
((buttons & Qt::MiddleButton) ? GST_NAVIGATION_MODIFIER_BUTTON3_MASK : 0)
| ((buttons & Qt::BackButton) ? GST_NAVIGATION_MODIFIER_BUTTON4_MASK : 0)
| ((buttons & Qt::ForwardButton) ? GST_NAVIGATION_MODIFIER_BUTTON5_MASK :
0));
}
void
GstQt6D3D11VideoItem::hoverMoveEvent (QHoverEvent * event)
{
if (!mouse_hovering_)
return;
if (event->position () == event->oldPos ())
return;
std::lock_guard < std::mutex > lk (lock_);
if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN)
return;
GstElement *sink = (GstElement *) g_weak_ref_get (&sink_);
if (!sink)
return;
auto pos = mapPointToStreamSize (event->position ());
gst_navigation_send_event_simple (GST_NAVIGATION (sink),
gst_navigation_event_new_mouse_move (pos.x (), pos.y (),
translateModifiers (event->modifiers ())));
gst_object_unref (sink);
}
void
GstQt6D3D11VideoItem::touchEvent (QTouchEvent * event)
{
std::lock_guard < std::mutex > lk (lock_);
if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN)
return;
GstElement *sink = (GstElement *) g_weak_ref_get (&sink_);
if (!sink)
return;
if (event->type () == QEvent::TouchCancel) {
gst_navigation_send_event_simple (GST_NAVIGATION (sink),
gst_navigation_event_new_touch_cancel (translateModifiers
(event->modifiers ())));
} else {
const QList < QTouchEvent::TouchPoint > points = event->points ();
gboolean sent_event = FALSE;
for (int i = 0; i < points.count (); i++) {
GstEvent *nav_event;
QPointF pos = mapPointToStreamSize (points[i].position ());
switch (points[i].state ()) {
case QEventPoint::Pressed:
nav_event =
gst_navigation_event_new_touch_down ((guint) points[i].id (),
pos.x (), pos.y (), (gdouble) points[i].pressure (),
translateModifiers (event->modifiers ()));
break;
case QEventPoint::Updated:
nav_event =
gst_navigation_event_new_touch_motion ((guint) points[i].id (),
pos.x (), pos.y (), (gdouble) points[i].pressure (),
translateModifiers (event->modifiers ()));
break;
case QEventPoint::Released:
nav_event =
gst_navigation_event_new_touch_up ((guint) points[i].id (),
pos.x (), pos.y (), translateModifiers (event->modifiers ()));
break;
/* Don't send an event if the point did not change */
default:
nav_event = nullptr;
break;
}
if (nav_event) {
gst_navigation_send_event_simple (GST_NAVIGATION (sink), nav_event);
sent_event = TRUE;
}
}
/* Group simultaneous touch events with a frame event */
if (sent_event) {
gst_navigation_send_event_simple (GST_NAVIGATION (sink),
gst_navigation_event_new_touch_frame (translateModifiers
(event->modifiers ())));
}
}
gst_object_unref (sink);
}
void
GstQt6D3D11VideoItem::sendMouseEvent (QMouseEvent * event, gboolean is_press)
{
std::lock_guard < std::mutex > lk (lock_);
if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN)
return;
GstElement *sink = (GstElement *) g_weak_ref_get (&sink_);
if (!sink)
return;
gint button = 0;
switch (event->button ()) {
case Qt::LeftButton:
button = 1;
break;
case Qt::RightButton:
button = 2;
break;
case Qt::MiddleButton:
button = 3;
default:
break;
}
auto pos = mapPointToStreamSize (event->pos ());
GstEvent *navi;
GstNavigationModifierType type = (GstNavigationModifierType)
(translateModifiers (event->modifiers ()) |
translateMouseButtons (event->buttons ()));
if (is_press) {
navi = gst_navigation_event_new_mouse_button_press (button,
pos.x (), pos.y (), type);
} else {
navi = gst_navigation_event_new_mouse_button_release (button,
pos.x (), pos.y (), type);
}
gst_navigation_send_event_simple (GST_NAVIGATION (sink), navi);
gst_object_unref (sink);
}
void
GstQt6D3D11VideoItem::mousePressEvent (QMouseEvent * event)
{
sendMouseEvent (event, TRUE);
}
void
GstQt6D3D11VideoItem::mouseReleaseEvent (QMouseEvent * event)
{
sendMouseEvent (event, FALSE);
}
void
GstQt6D3D11VideoItem::wheelEvent (QWheelEvent * event)
{
std::lock_guard < std::mutex > lk (lock_);
if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_UNKNOWN)
return;
GstElement *sink = (GstElement *) g_weak_ref_get (&sink_);
if (!sink)
return;
auto pos = mapPointToStreamSize (event->position ());
auto delta = event->angleDelta ();
GstNavigationModifierType type = (GstNavigationModifierType)
(translateModifiers (event->modifiers ()) |
translateMouseButtons (event->buttons ()));
gst_navigation_send_event_simple (GST_NAVIGATION (sink),
gst_navigation_event_new_mouse_scroll (pos.x (), pos.y (),
delta.x (), delta.y (), type));
gst_object_unref (sink);
}
void
gst_qt6_d3d11_video_item_init_once (void)
{
GST_D3D11_CALL_ONCE_BEGIN {
qmlRegisterType < GstQt6D3D11VideoItem >
("org.freedesktop.gstreamer.Qt6D3D11VideoItem",
1, 0, "GstD3D11Qt6VideoItem");
} GST_D3D11_CALL_ONCE_END;
}

View file

@ -0,0 +1,119 @@
/* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/d3d11/gstd3d11.h>
#include <QQuickItem>
#include <QQuickWindow>
#include <mutex>
#include "gstqsg6d3d11node.h"
class GstQt6D3D11VideoItem;
class GstQt6D3D11VideoItemProxy : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
GstQt6D3D11VideoItemProxy (GstQt6D3D11VideoItem * videoItem)
: item (videoItem)
{
}
void InvalidateRef (void);
void SetSink (GstElement * sink);
void SetCaps (GstCaps * caps);
void SetBuffer (GstBuffer * buffer);
void SetForceAspectRatio (bool force);
gint64 AdapterLuid (void);
GstQt6D3D11VideoItem * Item (void);
private:
GstQt6D3D11VideoItem *item;
std::mutex lock;
};
class GstQt6D3D11VideoItem : public QQuickItem
{
friend class GstQt6D3D11VideoItemProxy;
Q_OBJECT
QML_ELEMENT
public:
GstQt6D3D11VideoItem ();
~GstQt6D3D11VideoItem ();
QSharedPointer<GstQt6D3D11VideoItemProxy> Proxy (void);
protected:
QSGNode * updatePaintNode (QSGNode * oldNode, UpdatePaintNodeData * data);
void hoverEnterEvent (QHoverEvent * event);
void hoverLeaveEvent (QHoverEvent * event);
void hoverMoveEvent (QHoverEvent * event);
void touchEvent (QTouchEvent * event);
void mousePressEvent (QMouseEvent * event);
void mouseReleaseEvent (QMouseEvent * event);
void wheelEvent (QWheelEvent * event);
private:
void setSink (GstElement * sink);
void setCaps (GstCaps * caps);
void setBuffer (GstBuffer * buffer);
void setForceAspectRatio (bool force);
gint64 AdapterLuid ();
void fitStreamToAllocatedSize (GstVideoRectangle * result);
QPointF mapPointToStreamSize (QPointF point);
void sendMouseEvent (QMouseEvent * event, gboolean is_press);
void clearResource ();
void clearFrameResource ();
bool setupShader ();
private slots:
void handleWindowChanged (QQuickWindow * window);
void onBeforeSynchronizing (void);
void onSceneGraphInvalidated (void);
void onBeforeRendering (void);
private:
QSharedPointer<GstQt6D3D11VideoItemProxy> proxy_;
QQuickWindow *window_ = nullptr;
GstCaps *caps_ = nullptr;
GstBuffer *buffer_ = nullptr;
bool force_aspect_ratio_ = true;
gint64 luid_ = 0;
GstVideoInfo info_;
int display_width_ = 0;
int display_height_ = 0;
GWeakRef sink_;
std::mutex lock_;
bool mouse_hovering_ = false;
bool update_par_ = false;
GstD3D11Device *qt_device_ = nullptr;
GstQSG6D3D11Node *node_ = nullptr;
};
void gst_qt6_d3d11_video_item_init_once (void);

View file

@ -0,0 +1,53 @@
gstqt6d3d11_sources = [
'gstqml6d3d11sink.cpp',
'gstqsg6d3d11node.cpp',
'gstqt6d3d11videoitem.cpp',
'plugin.cpp',
]
moc_headers = [
'gstqsg6d3d11node.h',
'gstqt6d3d11videoitem.h'
]
have_qt6d3d11 = false
qt6_option = get_option('qt6d3d11')
if qt6_option.disabled()
subdir_done()
endif
if not gstd3d11_dep.found()
if qt6_option.enabled()
error('qt6 plugin was enabled explicitly, but required d3d11 dep was not found')
else
subdir_done()
endif
endif
qt6_mod = import('qt6')
if not qt6_mod.has_tools()
if qt6_option.enabled()
error('qt6 plugin was enabled, but qt specific tools were not found')
endif
subdir_done()
endif
qt6qml_dep = dependency('qt6', modules : ['Core', 'Gui', 'Qml', 'Quick'],
required: qt6_option)
if not qt6qml_dep.found()
subdir_done()
endif
have_qt6d3d11 = true
moc_files = qt6_mod.preprocess(moc_headers : moc_headers)
gstqt6d3d11 = library('gstqt6d3d11', gstqt6d3d11_sources, moc_files,
c_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
cpp_args : gst_plugins_bad_args + ['-DGST_USE_UNSTABLE_API'],
include_directories: [configinc, libsinc],
dependencies : [gst_dep, gstvideo_dep, gstd3d11_dep, qt6qml_dep],
override_options : ['cpp_std=c++17'],
install: true,
install_dir : plugins_install_dir
)
plugins += [gstqt6d3d11]

View file

@ -0,0 +1,44 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gstqml6d3d11sink.h>
GST_DEBUG_CATEGORY (gst_qt6_d3d11_debug);
static gboolean
plugin_init (GstPlugin * plugin)
{
GST_DEBUG_CATEGORY_INIT (gst_qt6_d3d11_debug, "qt6d3d11", 0, "qt6d3d11");
gst_element_register (plugin, "qml6d3d11sink", GST_RANK_NONE,
GST_TYPE_QML6_D3D11_SINK);
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
qt6d3d11,
"Qt6 Direct3D11 plugin",
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View file

@ -149,6 +149,7 @@ option('opensles', type : 'feature', value : 'auto', description : 'OpenSL ES au
option('opus', type : 'feature', value : 'auto', description : 'OPUS audio parser plugin')
option('qroverlay', type : 'feature', value : 'auto', description : 'Element to set random data on a qroverlay')
option('qsv', type : 'feature', value : 'auto', description : 'Intel Quick Sync Video plugin')
option('qt6d3d11', type : 'feature', value : 'auto', description : 'Qt6 Direct3D11 plugin')
option('resindvd', type : 'feature', value : 'auto', description : 'Resin DVD playback plugin (GPL - only built if gpl option is also enabled!)')
option('rsvg', type : 'feature', value : 'auto', description : 'SVG overlayer and image decoder plugin')
option('rtmp', type : 'feature', value : 'auto', description : 'RTMP video network source and sink plugin')

View file

@ -14,6 +14,7 @@ subdir('mxf')
subdir('nvcodec')
subdir('opencv', if_found: opencv_dep)
subdir('qsv')
subdir('qt6d3d11')
subdir('uvch264')
subdir('va')
subdir('waylandsink')

View file

@ -0,0 +1,14 @@
if not have_qt6d3d11
subdir_done()
endif
qt6_example_deps = dependency('qt6',
modules : ['Core', 'Gui', 'Widgets', 'Qml', 'Quick'],
required: get_option('examples'))
if not qt6_example_deps.found()
subdir_done()
endif
subdir('qml6d3d11sink')
subdir('qml6d3d11sink-dyn-add')

View file

@ -0,0 +1,8 @@
qt_preprocessed = qt6_mod.preprocess(qresources : 'qml6d3d11sink-dyn-add.qrc')
executable('qml6d3d11sink-dyn-add', 'qml6d3d11sink-dyn-add.cpp', qt_preprocessed,
dependencies : [gst_dep, qt6_example_deps],
override_options : ['cpp_std=c++17'],
c_args : gst_plugins_bad_args,
cpp_args : gst_plugins_bad_args,
include_directories : [configinc],
install: false)

View file

@ -0,0 +1,101 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QQuickItem>
#include <QTimer>
#include <gst/gst.h>
static int
run_application (int argc, char ** argv, GstElement * pipeline)
{
QGuiApplication app(argc, argv);
int ret;
/* NOTE: Default API on Windows is D3D11 already */
QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D11);
QQmlApplicationEngine engine;
engine.load (QUrl (QStringLiteral ("qrc:/qml6d3d11sink-dyn-add.qml")));
QQuickWindow *rootObject;
rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());
gst_element_set_state (pipeline, GST_STATE_PLAYING);
GstElement *tee = gst_bin_get_by_name (GST_BIN (pipeline), "t");
/* Add the qml6d3d11sink element on timer callback */
QTimer::singleShot(5000, [&]()
{
GstElement *queue, *sink;
QQuickItem *videoItem = rootObject->findChild<QQuickItem *> ("videoItem");
queue = gst_element_factory_make ("queue", nullptr);
sink = gst_element_factory_make ("qml6d3d11sink", nullptr);
g_object_set (sink, "widget", videoItem, nullptr);
gst_println ("Adding new qml6d3d11sink to pipeline");
gst_bin_add_many (GST_BIN (pipeline), queue, sink, nullptr);
gst_element_sync_state_with_parent (sink);
gst_element_sync_state_with_parent (queue);
gst_element_link_many (tee, queue, sink, nullptr);
gst_object_unref (tee);
});
ret = app.exec();
gst_element_set_state (pipeline, GST_STATE_NULL);
return ret;
}
int
main(int argc, char *argv[])
{
GstElement *pipeline = nullptr;
GstElement *sink = nullptr;
int exit_code;
gst_init (&argc, &argv);
pipeline = gst_parse_launch ("d3d11testsrc ! "
"video/x-raw(memory:D3D11Memory),format=RGBA ! "
"tee name=t allow-not-linked=true ! queue ! fakesink sync=true", nullptr);
/* the plugin must be loaded before loading the qml file to register the
* GstD3D11Qt6VideoItem */
sink = gst_element_factory_make ("qml6d3d11sink", nullptr);
gst_object_unref (sink);
exit_code = run_application (argc, argv, pipeline);
gst_object_unref (pipeline);
gst_deinit ();
return exit_code;
}

View file

@ -0,0 +1,59 @@
import QtQuick 6.0
import QtQuick.Controls 6.0
import QtQuick.Dialogs 6.0
import QtQuick.Window 6.0
import org.freedesktop.gstreamer.Qt6D3D11VideoItem 1.0
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
x: 30
y: 30
color: "black"
Item {
anchors.fill: parent
GstD3D11Qt6VideoItem {
id: video
objectName: "videoItem"
anchors.centerIn: parent
width: parent.width
height: parent.height
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.3)
border.width: 1
border.color: "white"
anchors.bottom: video.bottom
anchors.bottomMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
width : parent.width - 30
height: parent.height - 30
radius: 8
MouseArea {
id: mousearea
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.opacity = 1.0
hidetimer.start()
}
}
Timer {
id: hidetimer
interval: 5000
onTriggered: {
parent.opacity = 0.0
stop()
}
}
}
}
}

View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>qml6d3d11sink-dyn-add.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,8 @@
qt_preprocessed = qt6_mod.preprocess(qresources : 'qml6d3d11sink.qrc')
executable('qml6d3d11sink', 'qml6d3d11sink.cpp', qt_preprocessed,
dependencies : [gst_dep, qt6_example_deps],
override_options : ['cpp_std=c++17'],
c_args : gst_plugins_bad_args,
cpp_args : gst_plugins_bad_args,
include_directories : [configinc],
install: false)

View file

@ -0,0 +1,215 @@
/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QQuickItem>
#include <QRunnable>
#include <gst/gst.h>
class PipelineRunner : public QRunnable
{
public:
PipelineRunner(GstElement * p);
~PipelineRunner();
void run ();
private:
GstElement *pipeline;
};
PipelineRunner::PipelineRunner (GstElement * p)
{
pipeline = (GstElement *) gst_object_ref (p);
}
PipelineRunner::~PipelineRunner ()
{
gst_object_unref (this->pipeline);
}
void
PipelineRunner::run ()
{
gst_element_set_state (this->pipeline, GST_STATE_PLAYING);
}
struct AppData
{
QGuiApplication *app;
GstElement *pipeline;
GMainLoop *loop;
GMainContext *context;
};
static gboolean
message_cb (GstBus * bus, GstMessage * msg, AppData * data)
{
gboolean do_exit = FALSE;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_println ("Got pipeline error");
do_exit = TRUE;
break;
case GST_MESSAGE_EOS:
gst_println ("Got pipeline EOS");
do_exit = TRUE;
break;
default:
break;
}
if (do_exit) {
g_main_loop_quit (data->loop);
data->app->quit ();
}
return G_SOURCE_CONTINUE;
}
static gpointer
pipeline_watch_thread (AppData * data)
{
GstBus *bus;
g_main_context_push_thread_default (data->context);
bus = gst_pipeline_get_bus (GST_PIPELINE (data->pipeline));
gst_bus_add_watch (bus, (GstBusFunc) message_cb, data);
g_main_loop_run (data->loop);
gst_bus_remove_watch (bus);
gst_object_unref (bus);
g_main_context_pop_thread_default (data->context);
return nullptr;
}
static int
run_application (int argc, char ** argv, GstElement * pipeline,
GstElement * sink)
{
QGuiApplication app (argc, argv);
GThread *bus_thread;
int ret;
/* NOTE: Default API on Windows is D3D11 already */
QQuickWindow::setGraphicsApi (QSGRendererInterface::Direct3D11);
QQmlApplicationEngine engine;
engine.load (QUrl (QStringLiteral ("qrc:/qml6d3d11sink.qml")));
QQuickItem *videoItem;
QQuickWindow *rootObject;
AppData app_data;
app_data.app = &app;
app_data.pipeline = pipeline;
app_data.context = g_main_context_new ();
app_data.loop = g_main_loop_new (app_data.context, FALSE);
/* find and set the videoItem on the sink */
rootObject = static_cast<QQuickWindow *> (engine.rootObjects().first());
videoItem = rootObject->findChild<QQuickItem *> ("videoItem");
g_assert (videoItem);
g_object_set (sink, "widget", videoItem, nullptr);
rootObject->scheduleRenderJob (new PipelineRunner (pipeline),
QQuickWindow::BeforeSynchronizingStage);
bus_thread = g_thread_new ("pipeline-watch-thread",
(GThreadFunc) pipeline_watch_thread, &app_data);
ret = app.exec();
g_main_loop_quit (app_data.loop);
g_thread_join (bus_thread);
g_main_loop_unref (app_data.loop);
g_main_context_unref (app_data.context);
gst_element_set_state (pipeline, GST_STATE_NULL);
return ret;
}
int
main(int argc, char *argv[])
{
GOptionContext *option_ctx;
gchar *uri = nullptr;
GError *err = nullptr;
gboolean ret;
GOptionEntry options[] = {
{"uri", 0, 0, G_OPTION_ARG_STRING, &uri, "URI to play", nullptr},
{nullptr, }
};
GstElement *pipeline = nullptr;
GstElement *sink = nullptr;
int exit_code;
option_ctx = g_option_context_new ("Qt6 Direct3D11 QML render");
g_option_context_add_main_entries (option_ctx, options, nullptr);
g_option_context_add_group (option_ctx, gst_init_get_option_group ());
ret = g_option_context_parse (option_ctx, &argc, &argv, &err);
g_option_context_free (option_ctx);
if (!ret) {
gst_printerrln ("option parsing failed: %s", err->message);
g_clear_error (&err);
return 1;
}
/* the plugin must be loaded before loading the qml file to register the
* GstD3D11Qt6VideoItem */
sink = gst_element_factory_make ("qml6d3d11sink", nullptr);
if (!uri) {
pipeline = gst_pipeline_new (nullptr);
GstElement *src = gst_element_factory_make ("videotestsrc", nullptr);
GstElement *upload = gst_element_factory_make ("d3d11upload", nullptr);
GstElement *capsfilter = gst_element_factory_make ("capsfilter", nullptr);
GstCaps *caps;
caps = gst_caps_from_string ("video/x-raw(memory:D3D11Memory),format=RGBA");
g_object_set (capsfilter, "caps", caps, nullptr);
gst_caps_unref (caps);
gst_bin_add_many (GST_BIN (pipeline),
src, upload, capsfilter, sink, nullptr);
gst_element_link_many (src, upload, capsfilter, sink, nullptr);
} else {
pipeline = gst_element_factory_make ("playbin", nullptr);
g_object_set (pipeline, "uri", uri, "video-sink", sink, nullptr);
}
exit_code = run_application (argc, argv, pipeline, sink);
gst_object_unref (pipeline);
gst_deinit ();
return exit_code;
}

View file

@ -0,0 +1,59 @@
import QtQuick 6.0
import QtQuick.Controls 6.0
import QtQuick.Dialogs 6.0
import QtQuick.Window 6.0
import org.freedesktop.gstreamer.Qt6D3D11VideoItem 1.0
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
x: 30
y: 30
color: "black"
Item {
anchors.fill: parent
GstD3D11Qt6VideoItem {
id: video
objectName: "videoItem"
anchors.centerIn: parent
width: parent.width
height: parent.height
}
Rectangle {
color: Qt.rgba(1, 1, 1, 0.3)
border.width: 1
border.color: "white"
anchors.bottom: video.bottom
anchors.bottomMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
width : parent.width - 30
height: parent.height - 30
radius: 8
MouseArea {
id: mousearea
anchors.fill: parent
hoverEnabled: true
onEntered: {
parent.opacity = 1.0
hidetimer.start()
}
}
Timer {
id: hidetimer
interval: 5000
onTriggered: {
parent.opacity = 0.0
stop()
}
}
}
}
}

View file

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>qml6d3d11sink.qml</file>
</qresource>
</RCC>