23 Commits

Author SHA1 Message Date
Rafał Dzięgiel
232a00aa59 plugin: Add "clapperdmabufimport" element 2022-04-16 19:51:15 +02:00
Rafał Dzięgiel
23b57cd326 Use new "clappersink" element
Use brand new clapper video sink for video output. Also add "CLAPPER_USE_LEGACY_SINK" env
to still allow the usage of old video sink if any problems arise.
2022-04-12 11:48:45 +02:00
Rafał Dzięgiel
9a19e10542 plugin: Add "clapperglimport" element
A Clapper import element that imports GStreamer `GLMemory` into `GdkTexture` for improved performance where OpenGL is available
2022-04-12 09:47:27 +02:00
Rafał Dzięgiel
0061e133f9 plugin: Add clapper GStreamer plugin
Add new GStreamer plugin that consists of multiple elements for Clapper video player.

The main difference is that unlike the old one, this does not operate using `GtkGLArea`
anymore, but processes and displays `GdkTextures` directly through `GtkPicture` widget.
Also this one is installed like any other GStreamer plugin, thus can be used via
`gst-launch-1.0` binary or even used by other GTK4 apps if they wish to integrate it.

This commit adds new video sink that uses a new kind of memory as input, called
`ClapperGdkMemory`. With it comes a simple dedicated memory allocator, buffer pool
and a `clapperimport` element that converts system mapped memory frames into
`GdkTextures` that are later passed in our `ClapperGdkMemory` for the sink to display.
2022-04-12 09:43:20 +02:00
Rafał Dzięgiel
f771e0320c Merge pull request #240 from Rafostar/pkgs
Update Flatpak, remove RPM specfiles
2022-04-01 11:05:22 +02:00
Rafał Dzięgiel
72a64a41d9 Update README.md 2022-04-01 09:42:06 +02:00
Rafał Dzięgiel
2818d3c91b flatpak: Enable network access for CI GTK4 builds
Make sure CI can satisfy latest GTK4 dependency requirements by allowing it to download libs it needs as fallback projects
2022-04-01 09:27:13 +02:00
Rafał Dzięgiel
ffb481b52b pkgs: Remove RPM build files from git
Clapper is now available in official repos on both Fedora and openSUSE. Considering that, I will not have time to properly maintain them here, so they go away.
2022-04-01 09:27:10 +02:00
Rafał Dzięgiel
c5c289d466 flatpak: Do not build libsoup3 anymore
It is now included in GNOME 42 runtime
2022-04-01 09:27:07 +02:00
Rafał Dzięgiel
3074051b3d flatpak: Build recent GTK 4.6.2 version
We are gonna move into depending on at least 4.6.0, build it for CI to pass
2022-04-01 09:26:58 +02:00
Rafał Dzięgiel
980e1d9e1a flatpak: Update git actions runtime to 42 2022-03-31 16:28:30 +02:00
Rafał Dzięgiel
045e4fc2c4 flatpak: Sync with Flathub 2022-03-31 16:20:47 +02:00
Rafał Dzięgiel
52aa7710dc Update README.md 2022-03-30 11:21:01 +02:00
Rafał Dzięgiel
5101fce5a7 Merge pull request #228 from Rafostar/nightly-fix
Flatpak nightly fixes
2022-03-11 00:30:05 +01:00
Rafał Dzięgiel
e0daf8435a flatpak-nightly: Use "--wrap-mode=nodownload" for GStreamer build
We do not have net access during build of GStreamer, nor we want to pull anything, so disable downloads so it will not try
2022-03-10 22:55:31 +01:00
Rafał Dzięgiel
9f18295728 flatpak-nightly: Do not disable GStreamer asserts and checks
We may disable them for stable releases, but should be left to default on nightly
2022-03-10 22:55:27 +01:00
Rafał Dzięgiel
8ce977505e actions: nightly: Manually install SDK extensions via workflow
The github actions Flatpak action only allows to specify single source
for installing missing dependencies. This makes impossible to build
some things from "gnome-nightly" while others from "flathub" repo.

Work around this limitation by manually installing missing SDK
extensions from Flathub prior to using this git action.
2022-03-10 22:55:24 +01:00
Rafał Dzięgiel
8ac839c9aa flatpak-nightly: Disable cloning GStreamer submodules
It seems that now GStreamer ships its testsuite (which we do not need) as a submodule. Do not try to clone it.
2022-03-10 22:55:09 +01:00
Rafał Dzięgiel
8fa2036265 actions: Allow runs on workflow_dispatch 2022-03-10 09:32:11 +01:00
Rafał Dzięgiel
1d5bb1e6aa Merge pull request #227 from Rafostar/nightly-rust
flatpak-nightly: Build dav1d decoder from gst-plugins-rs
2022-03-09 22:09:22 +01:00
Rafał Dzięgiel
9ec87c1b58 flatpak-nightly: Build dav1d decoder from gst-plugins-rs 2022-03-09 20:53:18 +01:00
Rafał Dzięgiel
16c0f8baae Merge pull request #226 from Rafostar/speed-fix
Fix end time calculation with with non-1x speed
2022-03-06 16:52:40 +01:00
Rafał Dzięgiel
c94d21fc53 Fix end time calculation with with non-1x speed 2022-03-06 15:51:06 +01:00
45 changed files with 4593 additions and 301 deletions

View File

@@ -1,4 +1,5 @@
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
name: "Flatpak Nightly"
@@ -25,8 +26,12 @@ jobs:
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: Prepare Runtime
run: |
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.rust-nightly/${{ matrix.arch }}/21.08
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.llvm13/${{ matrix.arch }}/21.08
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4
name: "Build"
name: Build
with:
bundle: com.github.rafostar.Clapper.flatpak
manifest-path: pkgs/flatpak/com.github.rafostar.Clapper-nightly.json

View File

@@ -1,4 +1,5 @@
on:
workflow_dispatch:
push:
branches:
- master
@@ -11,7 +12,7 @@ jobs:
name: "Flatpak"
runs-on: ubuntu-latest
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-41
image: bilelmoussaoui/flatpak-github-actions:gnome-42
options: --privileged
strategy:
matrix:

View File

@@ -2,6 +2,8 @@
[![Flatpak](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml/badge.svg?event=push)](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml)
[![Flatpak Nightly](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml/badge.svg?event=schedule)](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml)
[![Crowdin](https://badges.crowdin.net/clapper/localized.svg)](https://crowdin.com/project/clapper)
[![Matrix](https://img.shields.io/matrix/clapper-player:matrix.org?label=matrix)](https://matrix.to/#/#clapper-player:matrix.org)
[![Donate](https://img.shields.io/liberapay/receives/Clapper.svg?logo=liberapay)](https://liberapay.com/Clapper)
A GNOME media player built using [GJS](https://gitlab.gnome.org/GNOME/gjs) with [GTK4](https://www.gtk.org) toolkit.
The media player uses [GStreamer](https://gstreamer.freedesktop.org/) as a media backend and renders everything via [OpenGL](https://www.opengl.org).
@@ -38,17 +40,13 @@ List of patches used in this version can be found [here](https://github.com/Rafo
<img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/>
</a>
## Packages
#### Fedora & openSUSE
Pre-built packages are available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).<br>
Those are automatically built on each git commit, and are thus considered unstable.
## Packages in Linux Distributions
[![Packaging status](https://repology.org/badge/vertical-allrepos/clapper.svg)](https://repology.org/project/clapper/versions)
#### Arch Linux
You can get Clapper from the AUR:
* [clapper](https://aur.archlinux.org/packages/clapper) (stable version)
* [clapper-git](https://aur.archlinux.org/packages/clapper-git)
Pre-built RPM packages are also available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).<br>
Those are automatically built on each git commit, thus are considered unstable.
## Installation from source code
## Installation from Source Code
```sh
meson builddir --prefix=/usr/local
sudo meson install -C builddir

View File

@@ -34,36 +34,13 @@ gstclapper_defines = [
'-DGST_USE_UNSTABLE_API',
'-DHAVE_GTK_GL',
]
gtk_deps = [gstgl_dep, gstglproto_dep]
have_gtk_gl_windowing = false
gtk4_dep = dependency('gtk4', required: true)
if not gtk4_dep.version().version_compare('>=4.0.0')
error('GTK4 version on this system is too old')
if not get_option('lib')
subdir_done()
endif
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
gtk_x11_dep = dependency('gtk4-x11', required: false)
if gtk_x11_dep.found()
gtk_deps += gtk_x11_dep
if gst_gl_have_platform_glx
gtk_deps += gstglx11_dep
endif
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
if gtk_wayland_dep.found()
gtk_deps += [gtk_wayland_dep, gstglwayland_dep]
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_platform_egl
gtk_deps += gstglegl_dep
if not gir.found()
error('Clapper lib requires GI bindings to be compiled')
endif
if not have_gtk_gl_windowing

1
lib/gst/meson.build vendored
View File

@@ -1 +1,2 @@
subdir('clapper')
subdir('plugin')

374
lib/gst/plugin/gstclapperbaseimport.c vendored Normal file
View File

@@ -0,0 +1,374 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperbaseimport.h"
#include "gstclappergdkmemory.h"
#include "gstclappergdkbufferpool.h"
#define GST_CAT_DEFAULT gst_clapper_base_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_base_import_parent_class
G_DEFINE_TYPE (GstClapperBaseImport, gst_clapper_base_import, GST_TYPE_BASE_TRANSFORM);
static void
gst_clapper_base_import_init (GstClapperBaseImport *self)
{
g_mutex_init (&self->lock);
gst_video_info_init (&self->in_info);
gst_video_info_init (&self->out_info);
}
static void
gst_clapper_base_import_finalize (GObject *object)
{
GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (object);
GST_TRACE ("Finalize");
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static GstStateChangeReturn
gst_clapper_base_import_change_state (GstElement *element, GstStateChange transition)
{
GST_DEBUG_OBJECT (element, "Changing state: %s => %s",
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}
static gboolean
gst_clapper_base_import_start (GstBaseTransform *bt)
{
GST_INFO_OBJECT (bt, "Start");
return TRUE;
}
static gboolean
gst_clapper_base_import_stop (GstBaseTransform *bt)
{
GST_INFO_OBJECT (bt, "Stop");
return TRUE;
}
static GstCaps *
gst_clapper_base_import_transform_caps (GstBaseTransform *bt,
GstPadDirection direction, GstCaps *caps, GstCaps *filter)
{
GstCaps *result, *tmp;
tmp = (direction == GST_PAD_SINK)
? gst_pad_get_pad_template_caps (GST_BASE_TRANSFORM_SRC_PAD (bt))
: gst_pad_get_pad_template_caps (GST_BASE_TRANSFORM_SINK_PAD (bt));
if (filter) {
GST_DEBUG ("Intersecting with filter caps: %" GST_PTR_FORMAT, filter);
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (tmp);
} else {
result = tmp;
}
GST_DEBUG ("Returning %s caps: %" GST_PTR_FORMAT,
(direction == GST_PAD_SINK) ? "src" : "sink", result);
return result;
}
static gboolean
_structure_filter_cb (GQuark field_id, GValue *value,
G_GNUC_UNUSED gpointer user_data)
{
const gchar *str = g_quark_to_string (field_id);
if (!strcmp (str, "format")
|| !strcmp (str, "width")
|| !strcmp (str, "height")
|| !strcmp (str, "pixel-aspect-ratio")
|| !strcmp (str, "framerate"))
return TRUE;
return FALSE;
}
static GstCaps *
gst_clapper_base_import_fixate_caps (GstBaseTransform *bt,
GstPadDirection direction, GstCaps *caps, GstCaps *othercaps)
{
GstCaps *fixated;
fixated = (!gst_caps_is_any (caps))
? gst_caps_fixate (gst_caps_ref (caps))
: gst_caps_copy (caps);
if (direction == GST_PAD_SINK) {
guint i, n = gst_caps_get_size (fixated);
for (i = 0; i < n; i++) {
GstCapsFeatures *features;
GstStructure *structure;
gboolean had_overlay_comp;
features = gst_caps_get_features (fixated, i);
had_overlay_comp = gst_caps_features_contains (features,
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
features = gst_caps_features_new (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY, NULL);
if (had_overlay_comp)
gst_caps_features_add (features, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
gst_caps_set_features (fixated, i, features);
/* Remove fields that do not apply to our memory */
if ((structure = gst_caps_get_structure (fixated, i))) {
gst_structure_filter_and_map_in_place (structure,
(GstStructureFilterMapFunc) _structure_filter_cb, NULL);
}
}
}
GST_DEBUG ("Fixated %s caps: %" GST_PTR_FORMAT,
(direction == GST_PAD_SRC) ? "sink" : "src", fixated);
return fixated;
}
static gboolean
gst_clapper_base_import_set_caps (GstBaseTransform *bt,
GstCaps *incaps, GstCaps *outcaps)
{
GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt);
gboolean has_sink_info, has_src_info;
if ((has_sink_info = gst_video_info_from_caps (&self->in_info, incaps)))
GST_INFO_OBJECT (self, "Set sink caps: %" GST_PTR_FORMAT, incaps);
if ((has_src_info = gst_video_info_from_caps (&self->out_info, outcaps)))
GST_INFO_OBJECT (self, "Set src caps: %" GST_PTR_FORMAT, outcaps);
return (has_sink_info && has_src_info);
}
static gboolean
gst_clapper_base_import_import_propose_allocation (GstBaseTransform *bt,
GstQuery *decide_query, GstQuery *query)
{
GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt);
GstClapperBaseImportClass *bi_class = GST_CLAPPER_BASE_IMPORT_GET_CLASS (self);
GstBufferPool *pool = NULL;
GstCaps *caps;
GstVideoInfo info;
guint size;
gboolean need_pool;
if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (bt,
decide_query, query))
return FALSE;
/* Passthrough, nothing to do */
if (!decide_query)
return TRUE;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_DEBUG_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_DEBUG_OBJECT (self, "Invalid caps specified");
return FALSE;
}
/* Normal size of a frame */
size = GST_VIDEO_INFO_SIZE (&info);
if (need_pool) {
GstStructure *config = NULL;
GST_DEBUG_OBJECT (self, "Need to create upstream pool");
pool = bi_class->create_upstream_pool (self, &config);
if (pool) {
/* If we did not get config, use default one */
if (!config)
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, 2, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
gst_object_unref (pool);
GST_DEBUG_OBJECT (self, "Failed to set config");
return FALSE;
}
} else if (config) {
GST_WARNING_OBJECT (self, "Got pool config without a pool to apply it!");
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_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return TRUE;
}
static gboolean
gst_clapper_base_import_decide_allocation (GstBaseTransform *bt, GstQuery *query)
{
GstClapperBaseImport *self = GST_CLAPPER_BASE_IMPORT_CAST (bt);
GstBufferPool *pool = NULL;
GstCaps *caps;
GstVideoInfo info;
guint size = 0, min = 0, max = 0;
gboolean update_pool, need_pool = TRUE;
gst_query_parse_allocation (query, &caps, NULL);
if (!caps) {
GST_DEBUG_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_DEBUG_OBJECT (self, "Invalid caps specified");
return FALSE;
}
if ((update_pool = gst_query_get_n_allocation_pools (query) > 0)) {
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
if (pool) {
if ((need_pool = !GST_IS_CLAPPER_GDK_BUFFER_POOL (pool)))
gst_clear_object (&pool);
}
} else {
size = GST_VIDEO_INFO_SIZE (&info);
}
if (need_pool) {
GstStructure *config;
GST_DEBUG_OBJECT (self, "Creating new downstream pool");
pool = gst_clapper_gdk_buffer_pool_new ();
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_config_set_params (config, caps, size, min, max);
if (!gst_buffer_pool_set_config (pool, config)) {
gst_object_unref (pool);
GST_DEBUG_OBJECT (self, "Failed to set config");
return FALSE;
}
}
if (update_pool)
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
else
gst_query_add_allocation_pool (query, pool, size, min, max);
if (pool)
gst_object_unref (pool);
return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (bt, query);
}
static GstBufferPool *
gst_clapper_base_import_create_upstream_pool (GstClapperBaseImport *self, GstStructure **config)
{
GST_FIXME_OBJECT (self, "Need to create upstream buffer pool");
return NULL;
}
static void
gst_clapper_base_import_class_init (GstClapperBaseImportClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass;
GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperbaseimport", 0,
"Clapper Base Import");
gobject_class->finalize = gst_clapper_base_import_finalize;
gstelement_class->change_state = gst_clapper_base_import_change_state;
gstbasetransform_class->passthrough_on_same_caps = TRUE;
gstbasetransform_class->transform_ip_on_passthrough = FALSE;
gstbasetransform_class->start = gst_clapper_base_import_start;
gstbasetransform_class->stop = gst_clapper_base_import_stop;
gstbasetransform_class->transform_caps = gst_clapper_base_import_transform_caps;
gstbasetransform_class->fixate_caps = gst_clapper_base_import_fixate_caps;
gstbasetransform_class->set_caps = gst_clapper_base_import_set_caps;
gstbasetransform_class->propose_allocation = gst_clapper_base_import_import_propose_allocation;
gstbasetransform_class->decide_allocation = gst_clapper_base_import_decide_allocation;
bi_class->create_upstream_pool = gst_clapper_base_import_create_upstream_pool;
}
/*
* Maps input video frame and output memory from in/out buffers
* using flags passed to this method.
*
* Remember to unmap both using `gst_video_frame_unmap` and
* `gst_memory_unmap` when done with the data.
*/
gboolean
gst_clapper_base_import_map_buffers (GstClapperBaseImport *self,
GstBuffer *in_buf, GstBuffer *out_buf, GstMapFlags in_flags, GstMapFlags out_flags,
GstVideoFrame *frame, GstMapInfo *info, GstMemory **mem)
{
GST_LOG_OBJECT (self, "Transforming from %" GST_PTR_FORMAT
" into %" GST_PTR_FORMAT, in_buf, out_buf);
if (G_UNLIKELY (!gst_video_frame_map (frame, &self->in_info, in_buf, in_flags))) {
GST_ERROR_OBJECT (self, "Could not map input buffer for reading");
return FALSE;
}
*mem = gst_buffer_peek_memory (out_buf, 0);
if (G_UNLIKELY (!gst_memory_map (*mem, info, out_flags))) {
GST_ERROR_OBJECT (self, "Could not map output memory for writing");
gst_video_frame_unmap (frame);
return FALSE;
}
return TRUE;
}

69
lib/gst/plugin/gstclapperbaseimport.h vendored Normal file
View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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/base/gstbasetransform.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_BASE_IMPORT (gst_clapper_base_import_get_type())
#define GST_IS_CLAPPER_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_BASE_IMPORT))
#define GST_IS_CLAPPER_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_BASE_IMPORT))
#define GST_CLAPPER_BASE_IMPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImportClass))
#define GST_CLAPPER_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImport))
#define GST_CLAPPER_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_BASE_IMPORT, GstClapperBaseImportClass))
#define GST_CLAPPER_BASE_IMPORT_CAST(obj) ((GstClapperBaseImport *)(obj))
#define GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj) (&GST_CLAPPER_BASE_IMPORT_CAST(obj)->lock)
#define GST_CLAPPER_BASE_IMPORT_LOCK(obj) g_mutex_lock (GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj))
#define GST_CLAPPER_BASE_IMPORT_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_BASE_IMPORT_GET_LOCK(obj))
typedef struct _GstClapperBaseImport GstClapperBaseImport;
typedef struct _GstClapperBaseImportClass GstClapperBaseImportClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperBaseImport, gst_object_unref)
#endif
struct _GstClapperBaseImport
{
GstBaseTransform parent;
GMutex lock;
GstVideoInfo in_info, out_info;
};
struct _GstClapperBaseImportClass
{
GstBaseTransformClass parent_class;
GstBufferPool * (* create_upstream_pool) (GstClapperBaseImport *bi,
GstStructure **config);
};
GType gst_clapper_base_import_get_type (void);
gboolean gst_clapper_base_import_map_buffers (GstClapperBaseImport *bi,
GstBuffer *in_buf, GstBuffer *out_buf, GstMapFlags in_flags, GstMapFlags out_flags,
GstVideoFrame *frame, GstMapInfo *info, GstMemory **mem);
G_END_DECLS

View File

@@ -0,0 +1,464 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/allocators/gstdmabuf.h>
#include <gst/gl/egl/gsteglimage.h>
#include "gstclapperdmabufbaseimport.h"
#include "gstgtkutils.h"
#define GST_CAT_DEFAULT gst_clapper_dmabuf_base_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static const GLfloat vertices[] = {
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
};
static const GLushort indices[] = {
0, 1, 2, 0, 2, 3
};
/* GTK4 renders things upside down ¯\_(ツ)_/¯ */
static const gfloat vertical_flip_matrix[] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
typedef struct
{
GstClapperDmabufBaseImport *dmabuf_bi;
GLuint id;
guint width;
guint height;
} GstClapperDmabufTexData;
#define parent_class gst_clapper_dmabuf_base_import_parent_class
G_DEFINE_TYPE (GstClapperDmabufBaseImport, gst_clapper_dmabuf_base_import, GST_TYPE_CLAPPER_GL_BASE_IMPORT);
static void
gst_clapper_dmabuf_base_import_init (GstClapperDmabufBaseImport *self)
{
g_mutex_init (&self->lock);
self->gst_tex_target = GST_GL_TEXTURE_TARGET_EXTERNAL_OES;
self->gl_tex_target = gst_gl_texture_target_to_gl (self->gst_tex_target);
}
static void
gst_clapper_dmabuf_base_import_finalize (GObject *object)
{
GstClapperDmabufBaseImport *self = GST_CLAPPER_DMABUF_BASE_IMPORT_CAST (object);
gst_clear_object (&self->shader);
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_dmabuf_base_import_bind_buffer (GstClapperDmabufBaseImport *self)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
const GstGLFuncs *gl = gl_bi->gst_context->gl_vtable;
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
/* Load the vertex position */
gl->VertexAttribPointer (self->attr_position, 3, GL_FLOAT, GL_FALSE,
5 * sizeof (GLfloat), (void *) 0);
/* Load the texture coordinate */
gl->VertexAttribPointer (self->attr_texture, 2, GL_FLOAT, GL_FALSE,
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
gl->EnableVertexAttribArray (self->attr_position);
gl->EnableVertexAttribArray (self->attr_texture);
}
static void
gst_clapper_dmabuf_base_import_unbind_buffer (GstClapperDmabufBaseImport *self)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
const GstGLFuncs *gl = gl_bi->gst_context->gl_vtable;
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
gl->DisableVertexAttribArray (self->attr_position);
gl->DisableVertexAttribArray (self->attr_texture);
}
static gboolean
prepare_on_main (GstClapperDmabufBaseImport *self)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
GstGLSLStage *frag_stage, *vert_stage;
GError *error = NULL;
gchar *frag_str;
const GstGLFuncs *gl;
GST_CLAPPER_GL_BASE_IMPORT_LOCK (gl_bi);
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
if (!((vert_stage = gst_glsl_stage_new_with_string (gl_bi->gst_context,
GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
gst_gl_shader_string_vertex_mat4_vertex_transform)))) {
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
GST_ERROR ("Failed to retrieve vertex shader for texture target");
return FALSE;
}
frag_str = gst_gl_shader_string_fragment_external_oes_get_default (
gl_bi->gst_context, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY);
frag_stage = gst_glsl_stage_new_with_string (gl_bi->gst_context,
GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, frag_str);
g_free (frag_str);
if (!frag_stage) {
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
GST_ERROR ("Failed to retrieve fragment shader for texture target");
return FALSE;
}
GST_CLAPPER_DMABUF_BASE_IMPORT_LOCK (self);
if (!((self->shader = gst_gl_shader_new_link_with_stages (gl_bi->gst_context,
&error, vert_stage, frag_stage, NULL)))) {
GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK (self);
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
GST_ERROR ("Failed to initialize shader: %s", error->message);
g_clear_error (&error);
return FALSE;
}
self->attr_position =
gst_gl_shader_get_attribute_location (self->shader, "a_position");
self->attr_texture =
gst_gl_shader_get_attribute_location (self->shader, "a_texcoord");
gl = gl_bi->gst_context->gl_vtable;
if (gl->GenVertexArrays) {
gl->GenVertexArrays (1, &self->vao);
gl->BindVertexArray (self->vao);
}
gl->GenBuffers (1, &self->vertex_buffer);
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices, GL_STATIC_DRAW);
if (gl->GenVertexArrays) {
gst_clapper_dmabuf_base_import_bind_buffer (self);
gl->BindVertexArray (0);
}
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
self->prepared = TRUE;
GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK (self);
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
return TRUE;
}
static gboolean
ensure_prepared (GstClapperDmabufBaseImport *self)
{
GST_CLAPPER_DMABUF_BASE_IMPORT_LOCK (self);
if (self->prepared) {
GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK (self);
return TRUE;
}
if (self->gst_tex_target != GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
/* We do not need shaders if texture target is 2D */
self->prepared = TRUE;
GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK (self);
return TRUE;
}
GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK (self);
if (!(! !gst_gtk_invoke_on_main (
(GThreadFunc) (GCallback) prepare_on_main, self))) {
GST_ERROR_OBJECT (self, "Could not ensure prepared");
return FALSE;
}
return TRUE;
}
static GstStateChangeReturn
gst_clapper_dmabuf_base_import_change_state (GstElement *element, GstStateChange transition)
{
GstClapperDmabufBaseImport *self = GST_CLAPPER_DMABUF_BASE_IMPORT_CAST (element);
GstStateChangeReturn ret;
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!ensure_prepared (self))
return GST_STATE_CHANGE_FAILURE;
break;
default:
break;
}
return ret;
}
static gboolean
_dmabuf_into_texture (GstClapperDmabufBaseImport *self, gint *fds, GstVideoInfo *v_info,
gsize *offsets, GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
GstEGLImage *image;
const GstGLFuncs *gl;
image = gst_egl_image_from_dmabuf_direct_target (gl_bi->gst_context,
fds, offsets, v_info, self->gst_tex_target);
/* If HW colorspace conversion failed and there is only one
* plane, we can just make it into single EGLImage as is */
if (!image && GST_VIDEO_INFO_N_PLANES (v_info) == 1)
image = gst_egl_image_from_dmabuf (gl_bi->gst_context,
fds[0], v_info, 0, offsets[0]);
if (!image)
return FALSE;
gl = gl_bi->gst_context->gl_vtable;
gl->GenTextures (1, &tex_data->id);
tex_data->width = GST_VIDEO_INFO_WIDTH (v_info);
tex_data->height = GST_VIDEO_INFO_HEIGHT (v_info);
gl->BindTexture (self->gl_tex_target, tex_data->id);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->EGLImageTargetTexture2D (self->gl_tex_target, gst_egl_image_get_image (image));
gl->BindTexture (GL_TEXTURE_2D, 0);
gst_egl_image_unref (image);
return TRUE;
}
static gboolean
_oes_texture_into_2d (GstClapperDmabufBaseImport *self, GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
GLuint framebuffer, tex_id;
GLenum status;
const GstGLFuncs *gl;
gl = gl_bi->gst_context->gl_vtable;
gl->GenFramebuffers (1, &framebuffer);
gl->BindFramebuffer (GL_FRAMEBUFFER, framebuffer);
gl->GenTextures (1, &tex_id);
gl->BindTexture (GL_TEXTURE_2D, tex_id);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->TexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, tex_data->width, tex_data->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
gl->FramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, tex_id, 0);
status = gl->CheckFramebufferStatus (GL_FRAMEBUFFER);
if (G_UNLIKELY (status != GL_FRAMEBUFFER_COMPLETE)) {
GST_ERROR ("Invalid framebuffer status: %u", status);
gl->BindTexture (GL_TEXTURE_2D, 0);
gl->DeleteTextures (1, &tex_id);
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
gl->DeleteFramebuffers (1, &framebuffer);
return FALSE;
}
gl->Viewport (0, 0, tex_data->width, tex_data->height);
gst_gl_shader_use (self->shader);
if (gl->BindVertexArray)
gl->BindVertexArray (self->vao);
gst_clapper_dmabuf_base_import_bind_buffer (self);
gl->ActiveTexture (GL_TEXTURE0);
gl->BindTexture (self->gl_tex_target, tex_data->id);
gst_gl_shader_set_uniform_1i (self->shader, "tex", 0);
gst_gl_shader_set_uniform_matrix_4fv (self->shader,
"u_transformation", 1, FALSE, vertical_flip_matrix);
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
if (gl->BindVertexArray)
gl->BindVertexArray (0);
else
gst_clapper_dmabuf_base_import_unbind_buffer (self);
gl->BindTexture (self->gl_tex_target, 0);
/* Replace External OES texture with newly created 2D */
gl->DeleteTextures (1, &tex_data->id);
tex_data->id = tex_id;
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
gl->DeleteFramebuffers (1, &framebuffer);
return TRUE;
}
static void
_tex_data_free (GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (tex_data->dmabuf_bi);
if (G_LIKELY (tex_data->id > 0)) {
const GstGLFuncs *gl;
GST_CLAPPER_GL_BASE_IMPORT_LOCK (gl_bi);
gl = gl_bi->gst_context->gl_vtable;
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
gl->DeleteTextures (1, &tex_data->id);
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
}
gst_object_unref (tex_data->dmabuf_bi);
g_slice_free (GstClapperDmabufTexData, tex_data);
}
GdkTexture *
gst_clapper_dmabuf_base_import_fds_into_texture (GstClapperDmabufBaseImport *self, gint *fds, gsize *offsets)
{
GstClapperBaseImport *bi = GST_CLAPPER_BASE_IMPORT_CAST (self);
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (self);
GdkTexture *texture = NULL;
GstClapperDmabufTexData *tex_data;
tex_data = g_slice_new (GstClapperDmabufTexData);
tex_data->dmabuf_bi = gst_object_ref (self);
GST_CLAPPER_GL_BASE_IMPORT_LOCK (gl_bi);
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
if (!_dmabuf_into_texture (self, fds, &bi->in_info, offsets, tex_data))
goto finish;
/* GTK4 does not support External OES textures.
* Make it into 2D using framebuffer + shader */
if (self->gst_tex_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
if (G_UNLIKELY (!_oes_texture_into_2d (self, tex_data)))
goto finish;
}
texture = gdk_gl_texture_new (gl_bi->gdk_context,
tex_data->id,
tex_data->width,
tex_data->height,
(GDestroyNotify) _tex_data_free,
tex_data);
finish:
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
return texture;
}
static void
gst_clapper_dmabuf_base_import_class_init (GstClapperDmabufBaseImportClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperdmabufbaseimport", 0,
"Clapper DMABuf Base Import");
gobject_class->finalize = gst_clapper_dmabuf_base_import_finalize;
gstelement_class->change_state = gst_clapper_dmabuf_base_import_change_state;
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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/gl/gstglfuncs.h>
#include "gstclapperglbaseimport.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT (gst_clapper_dmabuf_base_import_get_type())
#define GST_IS_CLAPPER_DMABUF_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT))
#define GST_IS_CLAPPER_DMABUF_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT))
#define GST_CLAPPER_DMABUF_BASE_IMPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT, GstClapperDmabufBaseImportClass))
#define GST_CLAPPER_DMABUF_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT, GstClapperDmabufBaseImport))
#define GST_CLAPPER_DMABUF_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT, GstClapperDmabufBaseImportClass))
#define GST_CLAPPER_DMABUF_BASE_IMPORT_CAST(obj) ((GstClapperDmabufBaseImport *)(obj))
#define GST_CLAPPER_DMABUF_BASE_IMPORT_GET_LOCK(obj) (&GST_CLAPPER_DMABUF_BASE_IMPORT_CAST(obj)->lock)
#define GST_CLAPPER_DMABUF_BASE_IMPORT_LOCK(obj) g_mutex_lock (GST_CLAPPER_DMABUF_BASE_IMPORT_GET_LOCK(obj))
#define GST_CLAPPER_DMABUF_BASE_IMPORT_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_DMABUF_BASE_IMPORT_GET_LOCK(obj))
typedef struct _GstClapperDmabufBaseImport GstClapperDmabufBaseImport;
typedef struct _GstClapperDmabufBaseImportClass GstClapperDmabufBaseImportClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperDmabufBaseImport, gst_object_unref)
#endif
struct _GstClapperDmabufBaseImport
{
GstClapperGLBaseImport parent;
GMutex lock;
gboolean prepared;
GstGLTextureTarget gst_tex_target;
guint gl_tex_target;
GstGLShader *shader;
GLuint vao;
GLuint vertex_buffer;
GLint attr_position;
GLint attr_texture;
};
struct _GstClapperDmabufBaseImportClass
{
GstClapperGLBaseImportClass parent_class;
};
GType gst_clapper_dmabuf_base_import_get_type (void);
GdkTexture * gst_clapper_dmabuf_base_import_fds_into_texture (GstClapperDmabufBaseImport *dmabuf_bi, gint *fds, gsize *offsets);
G_END_DECLS

180
lib/gst/plugin/gstclapperdmabufimport.c vendored Normal file
View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/allocators/gstdmabuf.h>
#include "gstclapperdmabufimport.h"
#include "gstclappergdkmemory.h"
#define GST_CAT_DEFAULT gst_clapper_dmabuf_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#ifndef GST_CAPS_FEATURE_MEMORY_DMABUF
#define GST_CAPS_FEATURE_MEMORY_DMABUF "memory:DMABuf"
#endif
static GstStaticPadTemplate gst_clapper_dmabuf_import_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_DMABUF,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS ", NV12 }")
"; "
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_DMABUF ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS ", NV12 }")));
static GstStaticPadTemplate gst_clapper_dmabuf_import_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS ", NV12 }")
"; "
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS ", NV12 }")));
#define parent_class gst_clapper_dmabuf_import_parent_class
G_DEFINE_TYPE (GstClapperDmabufImport, gst_clapper_dmabuf_import, GST_TYPE_CLAPPER_DMABUF_BASE_IMPORT);
GST_ELEMENT_REGISTER_DEFINE (clapperdmabufimport, "clapperdmabufimport", GST_RANK_NONE,
GST_TYPE_CLAPPER_DMABUF_IMPORT);
static gboolean
verify_dmabuf_memory (GstBuffer *buffer, GstVideoInfo *info, gint *fds, gsize *offsets)
{
guint i, n_planes = GST_VIDEO_INFO_N_PLANES (info);
for (i = 0; i < n_planes; i++) {
GstMemory *memory;
gsize plane_size, mem_skip;
guint mem_idx, length;
plane_size = gst_gl_get_plane_data_size (info, NULL, i);
if (!gst_buffer_find_memory (buffer,
GST_VIDEO_INFO_PLANE_OFFSET (info, i),
plane_size, &mem_idx, &length, &mem_skip)) {
GST_DEBUG ("Could not find memory %u", i);
return FALSE;
}
/* We cannot have more then one DMABuf per plane */
if (length != 1) {
GST_DEBUG ("Data for plane %u spans %u memories", i, length);
return FALSE;
}
memory = gst_buffer_peek_memory (buffer, mem_idx);
offsets[i] = memory->offset + mem_skip;
fds[i] = gst_dmabuf_memory_get_fd (memory);
}
return TRUE;
}
static GstBufferPool *
gst_clapper_dmabuf_import_create_upstream_pool (GstClapperBaseImport *bi, GstStructure **config)
{
return NULL;
}
static GstFlowReturn
gst_clapper_dmabuf_import_transform (GstBaseTransform *bt,
GstBuffer *in_buf, GstBuffer *out_buf)
{
GstClapperDmabufBaseImport *dmabuf_bi = GST_CLAPPER_DMABUF_BASE_IMPORT_CAST (bt);
GstClapperBaseImport *bi = GST_CLAPPER_BASE_IMPORT_CAST (bt);
GstMapInfo info;
GstMemory *memory;
GstVideoMeta *meta;
GstFlowReturn ret = GST_FLOW_ERROR;
GST_LOG_OBJECT (bt, "Transforming from %" GST_PTR_FORMAT
" into %" GST_PTR_FORMAT, in_buf, out_buf);
if ((meta = gst_buffer_get_video_meta (in_buf))) {
guint i;
GST_VIDEO_INFO_WIDTH (&bi->in_info) = meta->width;
GST_VIDEO_INFO_HEIGHT (&bi->in_info) = meta->height;
for (i = 0; i < meta->n_planes; i++) {
GST_VIDEO_INFO_PLANE_OFFSET (&bi->in_info, i) = meta->offset[i];
GST_VIDEO_INFO_PLANE_STRIDE (&bi->in_info, i) = meta->stride[i];
}
}
memory = gst_buffer_peek_memory (out_buf, 0);
if (G_LIKELY (gst_memory_map (memory, &info, GST_MAP_WRITE))) {
gint fds[GST_VIDEO_MAX_PLANES];
gsize offsets[GST_VIDEO_MAX_PLANES];
if (verify_dmabuf_memory (in_buf, &bi->in_info, fds, offsets)) {
GstClapperGdkMemory *clapper_memory = GST_CLAPPER_GDK_MEMORY_CAST (memory);
if (G_LIKELY ((clapper_memory->texture = gst_clapper_dmabuf_base_import_fds_into_texture (
dmabuf_bi, fds, offsets))))
ret = GST_FLOW_OK;
}
gst_memory_unmap (memory, &info);
}
return ret;
}
static void
gst_clapper_dmabuf_import_init (GstClapperDmabufImport *self)
{
}
static void
gst_clapper_dmabuf_import_class_init (GstClapperDmabufImportClass *klass)
{
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass;
GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperdmabufimport", 0,
"Clapper DMABuf Import");
gstbasetransform_class->transform = gst_clapper_dmabuf_import_transform;
bi_class->create_upstream_pool = gst_clapper_dmabuf_import_create_upstream_pool;
gst_element_class_set_metadata (gstelement_class,
"Clapper DMABuf import",
"Filter/Video", "Imports DMABuf into ClapperGdkMemory",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_dmabuf_import_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_dmabuf_import_src_template);
}

38
lib/gst/plugin/gstclapperdmabufimport.h vendored Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperdmabufbaseimport.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_DMABUF_IMPORT (gst_clapper_dmabuf_import_get_type())
G_DECLARE_FINAL_TYPE (GstClapperDmabufImport, gst_clapper_dmabuf_import, GST, CLAPPER_DMABUF_IMPORT, GstClapperDmabufBaseImport)
#define GST_CLAPPER_DMABUF_IMPORT_CAST(obj) ((GstClapperDmabufImport *)(obj))
struct _GstClapperDmabufImport
{
GstClapperDmabufBaseImport parent;
};
GST_ELEMENT_REGISTER_DECLARE (clapperdmabufimport);
G_END_DECLS

173
lib/gst/plugin/gstclappergdkbufferpool.c vendored Normal file
View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclappergdkbufferpool.h"
#define GST_CAT_DEFAULT gst_clapper_gdk_buffer_pool_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gdk_buffer_pool_parent_class
G_DEFINE_TYPE (GstClapperGdkBufferPool, gst_clapper_gdk_buffer_pool, GST_TYPE_BUFFER_POOL);
static void
gst_clapper_gdk_buffer_pool_init (GstClapperGdkBufferPool *pool)
{
}
static const gchar **
gst_clapper_gdk_buffer_pool_get_options (GstBufferPool *pool)
{
static const gchar *options[] = {
GST_BUFFER_POOL_OPTION_VIDEO_META,
NULL
};
return options;
}
static gboolean
gst_clapper_gdk_buffer_pool_set_config (GstBufferPool *pool, GstStructure *config)
{
GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (pool);
GstCaps *caps = NULL;
guint size, min_buffers, max_buffers;
GstVideoInfo info;
GstClapperGdkMemory *clapper_mem;
if (!gst_buffer_pool_config_get_params (config, &caps, &size,
&min_buffers, &max_buffers)) {
GST_WARNING_OBJECT (self, "Invalid buffer pool config");
return FALSE;
}
if (!caps || !gst_video_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (pool, "Could not parse caps into video info");
return FALSE;
}
gst_clear_object (&self->allocator);
self->allocator = GST_CLAPPER_GDK_ALLOCATOR_CAST (
gst_allocator_find (GST_CLAPPER_GDK_MEMORY_TYPE_NAME));
if (G_UNLIKELY (!self->allocator)) {
GST_ERROR_OBJECT (self, "ClapperGdkAllocator is unavailable");
return FALSE;
}
clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST (
gst_clapper_gdk_allocator_alloc (self->allocator, &info));
if (G_UNLIKELY (!clapper_mem)) {
GST_ERROR_OBJECT (self, "Cannot create ClapperGdkMemory");
return FALSE;
}
gst_buffer_pool_config_set_params (config, caps,
GST_VIDEO_INFO_SIZE (&clapper_mem->info), min_buffers, max_buffers);
gst_memory_unref (GST_MEMORY_CAST (clapper_mem));
self->info = info;
GST_DEBUG_OBJECT (self, "Set buffer pool config: %" GST_PTR_FORMAT, config);
return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config);
}
static GstFlowReturn
gst_clapper_gdk_buffer_pool_alloc (GstBufferPool *pool, GstBuffer **buffer,
GstBufferPoolAcquireParams *params)
{
GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (pool);
GstMemory *mem;
GstClapperGdkMemory *clapper_mem;
mem = gst_clapper_gdk_allocator_alloc (self->allocator, &self->info);
if (G_UNLIKELY (!mem)) {
GST_ERROR_OBJECT (self, "Cannot create ClapperGdkMemory");
return GST_FLOW_ERROR;
}
clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST (mem);
*buffer = gst_buffer_new ();
gst_buffer_append_memory (*buffer, mem);
gst_buffer_add_video_meta_full (*buffer, GST_VIDEO_FRAME_FLAG_NONE,
GST_VIDEO_INFO_FORMAT (&self->info), GST_VIDEO_INFO_WIDTH (&self->info),
GST_VIDEO_INFO_HEIGHT (&self->info), GST_VIDEO_INFO_N_PLANES (&self->info),
clapper_mem->info.offset, clapper_mem->info.stride);
GST_TRACE_OBJECT (self, "Allocated %" GST_PTR_FORMAT, *buffer);
return GST_FLOW_OK;
}
static void
gst_clapper_gdk_buffer_reset_buffer (GstBufferPool *pool, GstBuffer *buffer)
{
GstClapperGdkMemory *clapper_mem;
GST_TRACE ("Reset %" GST_PTR_FORMAT, buffer);
clapper_mem = GST_CLAPPER_GDK_MEMORY_CAST (gst_buffer_peek_memory (buffer, 0));
g_clear_object (&clapper_mem->texture);
return GST_BUFFER_POOL_CLASS (parent_class)->reset_buffer (pool, buffer);
}
static void
gst_clapper_gdk_buffer_pool_dispose (GObject *object)
{
GstClapperGdkBufferPool *self = GST_CLAPPER_GDK_BUFFER_POOL_CAST (object);
gst_clear_object (&self->allocator);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_gdk_buffer_pool_class_init (GstClapperGdkBufferPoolClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstBufferPoolClass *bufferpool_class = (GstBufferPoolClass *) klass;
gobject_class->dispose = gst_clapper_gdk_buffer_pool_dispose;
bufferpool_class->get_options = gst_clapper_gdk_buffer_pool_get_options;
bufferpool_class->set_config = gst_clapper_gdk_buffer_pool_set_config;
bufferpool_class->alloc_buffer = gst_clapper_gdk_buffer_pool_alloc;
bufferpool_class->reset_buffer = gst_clapper_gdk_buffer_reset_buffer;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergdkbufferpool", 0,
"Clapper Gdk Buffer Pool");
}
GstBufferPool *
gst_clapper_gdk_buffer_pool_new (void)
{
GstClapperGdkBufferPool *self;
self = g_object_new (GST_TYPE_CLAPPER_GDK_BUFFER_POOL, NULL);
gst_object_ref_sink (self);
return GST_BUFFER_POOL_CAST (self);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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/gstbufferpool.h>
#include <gst/video/video.h>
#include "gstclappergdkmemory.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GDK_BUFFER_POOL (gst_clapper_gdk_buffer_pool_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGdkBufferPool, gst_clapper_gdk_buffer_pool, GST, CLAPPER_GDK_BUFFER_POOL, GstBufferPool)
#define GST_CLAPPER_GDK_BUFFER_POOL_CAST(obj) ((GstClapperGdkBufferPool *)(obj))
struct _GstClapperGdkBufferPool
{
GstBufferPool parent;
GstClapperGdkAllocator *allocator;
GstVideoInfo info;
};
GstBufferPool * gst_clapper_gdk_buffer_pool_new (void);
G_END_DECLS

141
lib/gst/plugin/gstclappergdkmemory.c vendored Normal file
View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclappergdkmemory.h"
#include "gstgtkutils.h"
#define GST_CAT_DEFAULT gst_clapper_gdk_allocator_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static GstAllocator *_gst_clapper_gdk_allocator = NULL;
#define parent_class gst_clapper_gdk_allocator_parent_class
G_DEFINE_TYPE (GstClapperGdkAllocator, gst_clapper_gdk_allocator, GST_TYPE_ALLOCATOR);
static void
gst_clapper_gdk_allocator_free (GstAllocator *self, GstMemory *memory)
{
GstClapperGdkMemory *mem = GST_CLAPPER_GDK_MEMORY_CAST (memory);
GST_TRACE_OBJECT (self, "Freeing ClapperGdkMemory: %" GST_PTR_FORMAT, mem);
g_clear_object (&mem->texture);
g_free (mem);
}
static gpointer
gst_clapper_gdk_mem_map_full (GstMemory *memory, GstMapInfo *info, gsize maxsize)
{
GstClapperGdkMemory *mem = GST_CLAPPER_GDK_MEMORY_CAST (memory);
return &mem->texture;
}
static void
gst_clapper_gdk_mem_unmap_full (GstMemory *memory, GstMapInfo *info)
{
/* NOOP */
}
static GstMemory *
gst_clapper_gdk_mem_copy (GstMemory *memory, gssize offset, gssize size)
{
return NULL;
}
static GstMemory *
gst_clapper_gdk_mem_share (GstMemory *memory, gssize offset, gssize size)
{
return NULL;
}
static gboolean
gst_clapper_gdk_mem_is_span (GstMemory *mem1, GstMemory *mem2, gsize *offset)
{
return FALSE;
}
static void
gst_clapper_gdk_allocator_init (GstClapperGdkAllocator *self)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (self);
alloc->mem_type = GST_CLAPPER_GDK_MEMORY_TYPE_NAME;
alloc->mem_map_full = gst_clapper_gdk_mem_map_full;
alloc->mem_unmap_full = gst_clapper_gdk_mem_unmap_full;
alloc->mem_copy = gst_clapper_gdk_mem_copy;
alloc->mem_share = gst_clapper_gdk_mem_share;
alloc->mem_is_span = gst_clapper_gdk_mem_is_span;
GST_OBJECT_FLAG_SET (self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}
static void
gst_clapper_gdk_allocator_class_init (GstClapperGdkAllocatorClass *klass)
{
GstAllocatorClass *allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = NULL;
allocator_class->free = GST_DEBUG_FUNCPTR (gst_clapper_gdk_allocator_free);
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergdkallocator", 0,
"Clapper Gdk Allocator");
}
void
gst_clapper_gdk_memory_init_once (void)
{
static gsize _alloc_init = 0;
if (g_once_init_enter (&_alloc_init)) {
_gst_clapper_gdk_allocator = GST_ALLOCATOR_CAST (
g_object_new (GST_TYPE_CLAPPER_GDK_ALLOCATOR, NULL));
gst_object_ref_sink (_gst_clapper_gdk_allocator);
gst_allocator_register (GST_CLAPPER_GDK_MEMORY_TYPE_NAME, _gst_clapper_gdk_allocator);
g_once_init_leave (&_alloc_init, 1);
}
}
gboolean
gst_is_clapper_gdk_memory (GstMemory *memory)
{
return (memory != NULL && memory->allocator != NULL
&& GST_IS_CLAPPER_GDK_ALLOCATOR (memory->allocator));
}
GstMemory *
gst_clapper_gdk_allocator_alloc (GstClapperGdkAllocator *self, const GstVideoInfo *info)
{
GstClapperGdkMemory *mem;
mem = g_new0 (GstClapperGdkMemory, 1);
mem->info = *info;
gst_memory_init (GST_MEMORY_CAST (mem), 0, GST_ALLOCATOR_CAST (self),
NULL, GST_VIDEO_INFO_SIZE (info), 0, 0, GST_VIDEO_INFO_SIZE (info));
GST_TRACE_OBJECT (self, "Allocated new ClapperGdkMemory: %" GST_PTR_FORMAT, mem);
return GST_MEMORY_CAST (mem);
}

67
lib/gst/plugin/gstclappergdkmemory.h vendored Normal file
View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/gstmemory.h>
#include <gst/gstallocator.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GDK_ALLOCATOR (gst_clapper_gdk_allocator_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGdkAllocator, gst_clapper_gdk_allocator, GST, CLAPPER_GDK_ALLOCATOR, GstAllocator)
#define GST_CLAPPER_GDK_ALLOCATOR_CAST(obj) ((GstClapperGdkAllocator *)(obj))
#define GST_CLAPPER_GDK_MEMORY_CAST(mem) ((GstClapperGdkMemory *)(mem))
#define GST_CLAPPER_GDK_MEMORY_TYPE_NAME "gst.clapper.gdk.memory"
#define GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY "memory:ClapperGdkMemory"
#define GST_CLAPPER_GDK_MEMORY_FORMATS \
"RGBA64_LE, RGBA64_BE, ABGR, BGRA, " \
"ARGB, RGBA, BGRx, RGBx, BGR, RGB" \
/* Formats that `GdkGLTexture` supports */
#define GST_CLAPPER_GDK_GL_TEXTURE_FORMATS \
"RGBA64_LE, RGBA64_BE, RGBA, RGBx, RGB" \
typedef struct _GstClapperGdkMemory GstClapperGdkMemory;
struct _GstClapperGdkMemory
{
GstMemory mem;
GdkTexture *texture;
GstVideoInfo info;
};
struct _GstClapperGdkAllocator
{
GstAllocator parent;
};
void gst_clapper_gdk_memory_init_once (void);
gboolean gst_is_clapper_gdk_memory (GstMemory *memory);
GstMemory * gst_clapper_gdk_allocator_alloc (GstClapperGdkAllocator *allocator, const GstVideoInfo *info);
G_END_DECLS

414
lib/gst/plugin/gstclapperglbaseimport.c vendored Normal file
View File

@@ -0,0 +1,414 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/gl/gstglfuncs.h>
#include "gstclapperglbaseimport.h"
#include "gstgtkutils.h"
#if GST_CLAPPER_GL_HAVE_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#include <gst/gl/wayland/gstgldisplay_wayland.h>
#endif
#if GST_CLAPPER_GL_HAVE_X11
#include <gdk/x11/gdkx.h>
#endif
#if GST_CLAPPER_GL_HAVE_X11_GLX
#include <gst/gl/x11/gstgldisplay_x11.h>
#endif
#if GST_CLAPPER_GL_HAVE_X11_EGL
#include <gst/gl/egl/gstgldisplay_egl.h>
#endif
#define GST_CAT_DEFAULT gst_clapper_gl_base_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gl_base_import_parent_class
G_DEFINE_TYPE (GstClapperGLBaseImport, gst_clapper_gl_base_import, GST_TYPE_CLAPPER_BASE_IMPORT);
static void
gst_clapper_gl_base_import_init (GstClapperGLBaseImport *self)
{
g_mutex_init (&self->lock);
}
static void
gst_clapper_gl_base_import_finalize (GObject *object)
{
GstClapperGLBaseImport *self = GST_CLAPPER_GL_BASE_IMPORT_CAST (object);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_context);
gst_clear_object (&self->wrapped_context);
gst_clear_object (&self->gst_display);
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static GstGLContext *
wrap_current_gl (GstGLDisplay *display, GdkGLAPI gdk_gl_api, GstGLPlatform platform)
{
GstGLAPI gst_gl_api = GST_GL_API_NONE;
switch (gdk_gl_api) {
case GDK_GL_API_GL:
gst_gl_api = GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
break;
case GDK_GL_API_GLES:
gst_gl_api = GST_GL_API_GLES2;
break;
default:
g_assert_not_reached ();
break;
}
if (gst_gl_api != GST_GL_API_NONE) {
guintptr gl_handle;
gst_gl_display_filter_gl_api (display, gst_gl_api);
if ((gl_handle = gst_gl_context_get_current_gl_context (platform)))
return gst_gl_context_new_wrapped (display, gl_handle, platform, gst_gl_api);
}
return NULL;
}
static gboolean
retrieve_gl_context_on_main (GstClapperGLBaseImport *self)
{
GstClapperGLBaseImportClass *gl_bi_class = GST_CLAPPER_GL_BASE_IMPORT_GET_CLASS (self);
GdkDisplay *gdk_display;
GdkGLContext *gdk_context;
GError *error = NULL;
GdkGLAPI gdk_gl_api;
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
gint gl_major = 0, gl_minor = 0;
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return FALSE;
}
gdk_display = gdk_display_get_default ();
if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) {
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
error ? error->message : "No error set by Gdk");
g_clear_error (&error);
return FALSE;
}
if (!gl_bi_class->gdk_context_realize (self, gdk_context)) {
GST_ERROR_OBJECT (self, "Could not realize Gdk context: %" GST_PTR_FORMAT,
gdk_context);
g_object_unref (gdk_context);
return FALSE;
}
gdk_gl_api = gdk_gl_context_get_api (gdk_context);
GST_CLAPPER_GL_BASE_IMPORT_LOCK (self);
self->gdk_context = gdk_context;
#if GST_CLAPPER_GL_HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
struct wl_display *wayland_display =
gdk_wayland_display_get_wl_display (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_wayland_new_with_display (wayland_display);
}
#endif
#if GST_CLAPPER_GL_HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display)) {
gpointer display_ptr;
#if GST_CLAPPER_GL_HAVE_X11_EGL
display_ptr = gdk_x11_display_get_egl_display (gdk_display);
if (display_ptr) {
self->gst_display = (GstGLDisplay *)
gst_gl_display_egl_new_with_egl_display (display_ptr);
}
#endif
#if GST_CLAPPER_GL_HAVE_X11_GLX
if (!self->gst_display) {
display_ptr = gdk_x11_display_get_xdisplay (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_x11_new_with_display (display_ptr);
}
}
#endif
#endif
/* Fallback to generic display */
if (G_UNLIKELY (!self->gst_display)) {
GST_WARNING_OBJECT (self, "Unknown Gdk display!");
self->gst_display = gst_gl_display_new ();
}
#if GST_CLAPPER_GL_HAVE_WAYLAND
if (GST_IS_GL_DISPLAY_WAYLAND (self->gst_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on Wayland");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_HAVE_X11_EGL
if (GST_IS_GL_DISPLAY_EGL (self->gst_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on x11");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_HAVE_X11_GLX
if (GST_IS_GL_DISPLAY_X11 (self->gst_display)) {
platform = GST_GL_PLATFORM_GLX;
GST_INFO_OBJECT (self, "Using GLX on x11");
goto have_display;
}
#endif
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
GST_ERROR_OBJECT (self, "Unsupported GL platform");
return FALSE;
have_display:
gdk_gl_context_make_current (self->gdk_context);
self->wrapped_context = wrap_current_gl (self->gst_display, gdk_gl_api, platform);
if (!self->wrapped_context) {
GST_ERROR ("Could not retrieve Gdk OpenGL context");
gdk_gl_context_clear_current ();
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
return FALSE;
}
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context);
gst_gl_context_activate (self->wrapped_context, TRUE);
if (!gst_gl_context_fill_info (self->wrapped_context, &error)) {
GST_ERROR ("Failed to fill Gdk context info: %s", error->message);
g_clear_error (&error);
gst_gl_context_activate (self->wrapped_context, FALSE);
gst_clear_object (&self->wrapped_context);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
return FALSE;
}
gst_gl_context_get_gl_version (self->wrapped_context, &gl_major, &gl_minor);
GST_INFO ("Using OpenGL%s %i.%i", (gdk_gl_api == GDK_GL_API_GLES) ? " ES" : "",
gl_major, gl_minor);
/* Deactivate in both places */
gst_gl_context_activate (self->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
return TRUE;
}
static gboolean
ensure_gl_context (GstClapperGLBaseImport *self)
{
GstGLDisplay *gst_display = NULL;
GstGLContext *gst_context = NULL;
GError *error = NULL;
gboolean has_gdk_contexts;
GST_CLAPPER_GL_BASE_IMPORT_LOCK (self);
has_gdk_contexts = (self->gdk_context && self->wrapped_context);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
if (!has_gdk_contexts) {
if (!(! !gst_gtk_invoke_on_main (
(GThreadFunc) (GCallback) retrieve_gl_context_on_main, self))) {
GST_ERROR_OBJECT (self, "Could not retrieve Gdk GL context");
return FALSE;
}
}
GST_CLAPPER_GL_BASE_IMPORT_LOCK (self);
gst_display = gst_object_ref (self->gst_display);
/* GstGLDisplay operations require object lock to be held */
GST_OBJECT_LOCK (gst_display);
if (!self->gst_context) {
GST_TRACE_OBJECT (self, "Creating new GstGLContext");
if (!gst_gl_display_create_context (gst_display, self->wrapped_context,
&self->gst_context, &error)) {
GST_WARNING ("Could not create OpenGL context: %s",
error ? error->message : "Unknown");
g_clear_error (&error);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
return FALSE;
}
}
gst_context = gst_object_ref (self->gst_context);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
/* Calls `set_context` internally, so we cannot be locked here */
gst_gl_display_add_context (gst_display, gst_context);
gst_gl_element_propagate_display_context (GST_ELEMENT_CAST (self), gst_display);
GST_OBJECT_UNLOCK (gst_display);
gst_object_unref (gst_display);
gst_object_unref (gst_context);
return TRUE;
}
static void
gst_clapper_gl_base_import_set_context (GstElement *element, GstContext *context)
{
GstClapperGLBaseImport *self = GST_CLAPPER_GL_BASE_IMPORT_CAST (element);
GST_DEBUG_OBJECT (self, "Set context");
GST_CLAPPER_GL_BASE_IMPORT_LOCK (self);
gst_gl_handle_set_context (element, context, &self->gst_display,
&self->wrapped_context);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}
static GstStateChangeReturn
gst_clapper_gl_base_import_change_state (GstElement *element, GstStateChange transition)
{
GstClapperGLBaseImport *self = GST_CLAPPER_GL_BASE_IMPORT_CAST (element);
GstStateChangeReturn ret;
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!ensure_gl_context (self))
return GST_STATE_CHANGE_FAILURE;
break;
default:
break;
}
return ret;
}
static gboolean
gst_clapper_gl_base_import_query (GstBaseTransform *bt,
GstPadDirection direction, GstQuery *query)
{
GstClapperGLBaseImport *self = GST_CLAPPER_GL_BASE_IMPORT_CAST (bt);
gboolean res;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:
GST_CLAPPER_GL_BASE_IMPORT_LOCK (self);
res = gst_gl_handle_context_query (GST_ELEMENT_CAST (self), query,
self->gst_display, self->gst_context, self->wrapped_context);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (self);
break;
default:
res = GST_BASE_TRANSFORM_CLASS (parent_class)->query (bt, direction, query);
break;
}
return res;
}
static gboolean
gst_clapper_gl_base_import_gdk_context_realize (GstClapperGLBaseImport *self, GdkGLContext *gdk_context)
{
GError *error = NULL;
gboolean success;
GST_DEBUG_OBJECT (self, "Realizing GdkGLContext with default implementation");
gdk_gl_context_set_allowed_apis (gdk_context, GDK_GL_API_GLES);
if (!(success = gdk_gl_context_realize (gdk_context, &error))) {
GST_WARNING_OBJECT (self, "Could not realize Gdk context with GLES: %s", error->message);
g_clear_error (&error);
}
if (!success) {
gdk_gl_context_set_allowed_apis (gdk_context, GDK_GL_API_GL);
if (!(success = gdk_gl_context_realize (gdk_context, &error))) {
GST_WARNING_OBJECT (self, "Could not realize Gdk context with GL: %s", error->message);
g_clear_error (&error);
}
}
return success;
}
static void
gst_clapper_gl_base_import_class_init (GstClapperGLBaseImportClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass;
GstClapperGLBaseImportClass *gl_bi_class = (GstClapperGLBaseImportClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglbaseimport", 0,
"Clapper GL Base Import");
gobject_class->finalize = gst_clapper_gl_base_import_finalize;
gstelement_class->set_context = gst_clapper_gl_base_import_set_context;
gstelement_class->change_state = gst_clapper_gl_base_import_change_state;
gstbasetransform_class->query = gst_clapper_gl_base_import_query;
gl_bi_class->gdk_context_realize = gst_clapper_gl_base_import_gdk_context_realize;
}

75
lib/gst/plugin/gstclapperglbaseimport.h vendored Normal file
View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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/gl/gl.h>
#include <gtk/gtk.h>
#include "gstclapperbaseimport.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_BASE_IMPORT (gst_clapper_gl_base_import_get_type())
#define GST_IS_CLAPPER_GL_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORT))
#define GST_IS_CLAPPER_GL_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_GL_BASE_IMPORT))
#define GST_CLAPPER_GL_BASE_IMPORT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORT, GstClapperGLBaseImportClass))
#define GST_CLAPPER_GL_BASE_IMPORT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORT, GstClapperGLBaseImport))
#define GST_CLAPPER_GL_BASE_IMPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_GL_BASE_IMPORT, GstClapperGLBaseImportClass))
#define GST_CLAPPER_GL_BASE_IMPORT_CAST(obj) ((GstClapperGLBaseImport *)(obj))
#define GST_CLAPPER_GL_BASE_IMPORT_GET_LOCK(obj) (&GST_CLAPPER_GL_BASE_IMPORT_CAST(obj)->lock)
#define GST_CLAPPER_GL_BASE_IMPORT_LOCK(obj) g_mutex_lock (GST_CLAPPER_GL_BASE_IMPORT_GET_LOCK(obj))
#define GST_CLAPPER_GL_BASE_IMPORT_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_GL_BASE_IMPORT_GET_LOCK(obj))
#define GST_CLAPPER_GL_HAVE_WAYLAND (GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND))
#define GST_CLAPPER_GL_HAVE_X11 (GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11))
#define GST_CLAPPER_GL_HAVE_X11_GLX (GST_CLAPPER_GL_HAVE_X11 && GST_GL_HAVE_PLATFORM_GLX)
#define GST_CLAPPER_GL_HAVE_X11_EGL (GST_CLAPPER_GL_HAVE_X11 && GST_GL_HAVE_PLATFORM_EGL)
typedef struct _GstClapperGLBaseImport GstClapperGLBaseImport;
typedef struct _GstClapperGLBaseImportClass GstClapperGLBaseImportClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperGLBaseImport, gst_object_unref)
#endif
struct _GstClapperGLBaseImport
{
GstClapperBaseImport parent;
GMutex lock;
GdkGLContext *gdk_context;
GstGLContext *gst_context;
GstGLContext *wrapped_context;
GstGLDisplay *gst_display;
};
struct _GstClapperGLBaseImportClass
{
GstClapperBaseImportClass parent_class;
gboolean (* gdk_context_realize) (GstClapperGLBaseImport *gl_bi,
GdkGLContext *gdk_context);
};
GType gst_clapper_gl_base_import_get_type (void);
G_END_DECLS

178
lib/gst/plugin/gstclapperglimport.c vendored Normal file
View File

@@ -0,0 +1,178 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperglimport.h"
#include "gstclappergdkmemory.h"
#define GST_CAT_DEFAULT gst_clapper_gl_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static GstStaticPadTemplate gst_clapper_gl_import_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_GL_MEMORY,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS " }") ", "
"texture-target = (string) { " GST_GL_TEXTURE_TARGET_2D_STR " }"
"; "
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS " }") ", "
"texture-target = (string) { " GST_GL_TEXTURE_TARGET_2D_STR " }"));
static GstStaticPadTemplate gst_clapper_gl_import_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS " }")
"; "
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_CLAPPER_GDK_GL_TEXTURE_FORMATS " }")));
#define parent_class gst_clapper_gl_import_parent_class
G_DEFINE_TYPE (GstClapperGLImport, gst_clapper_gl_import, GST_TYPE_CLAPPER_GL_BASE_IMPORT);
GST_ELEMENT_REGISTER_DEFINE (clapperglimport, "clapperglimport", GST_RANK_NONE,
GST_TYPE_CLAPPER_GL_IMPORT);
static GstBufferPool *
gst_clapper_gl_import_create_upstream_pool (GstClapperBaseImport *bi, GstStructure **config)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (bi);
GstBufferPool *pool;
GstGLContext *context;
GST_DEBUG_OBJECT (bi, "Creating new pool");
GST_CLAPPER_GL_BASE_IMPORT_LOCK (gl_bi);
context = gst_object_ref (gl_bi->gst_context);
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
pool = gst_gl_buffer_pool_new (context);
gst_object_unref (context);
*config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_GL_SYNC_META);
return pool;
}
static void
video_frame_unmap_and_free (GstVideoFrame *frame)
{
gst_video_frame_unmap (frame);
g_slice_free (GstVideoFrame, frame);
}
static GstFlowReturn
gst_clapper_gl_import_transform (GstBaseTransform *bt,
GstBuffer *in_buf, GstBuffer *out_buf)
{
GstClapperGLBaseImport *gl_bi = GST_CLAPPER_GL_BASE_IMPORT_CAST (bt);
GstClapperBaseImport *bi = GST_CLAPPER_BASE_IMPORT_CAST (bt);
GstVideoFrame *frame;
GstMapInfo info;
GstMemory *memory;
GstClapperGdkMemory *clapper_memory;
GstGLSyncMeta *sync_meta;
frame = g_slice_new (GstVideoFrame);
if (!gst_clapper_base_import_map_buffers (bi, in_buf, out_buf,
GST_MAP_READ | GST_MAP_GL, GST_MAP_WRITE, frame, &info, &memory)) {
g_slice_free (GstVideoFrame, frame);
return GST_FLOW_ERROR;
}
clapper_memory = GST_CLAPPER_GDK_MEMORY_CAST (memory);
GST_CLAPPER_GL_BASE_IMPORT_LOCK (gl_bi);
/* Must have context active here for both sync meta
* and Gdk texture format auto-detection to work */
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->wrapped_context, TRUE);
sync_meta = gst_buffer_get_gl_sync_meta (in_buf);
/* Wait for all previous OpenGL commands to complete,
* before we start using the input texture */
if (sync_meta) {
gst_gl_sync_meta_set_sync_point (sync_meta, gl_bi->gst_context);
gst_gl_sync_meta_wait (sync_meta, gl_bi->wrapped_context);
}
/* Keep input data alive as long as necessary,
* unmap only after texture is destroyed */
clapper_memory->texture = gdk_gl_texture_new (
gl_bi->gdk_context,
*(guint *) GST_VIDEO_FRAME_PLANE_DATA (frame, 0),
GST_VIDEO_FRAME_WIDTH (frame),
GST_VIDEO_FRAME_HEIGHT (frame),
(GDestroyNotify) video_frame_unmap_and_free,
frame);
gst_gl_context_activate (gl_bi->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_CLAPPER_GL_BASE_IMPORT_UNLOCK (gl_bi);
gst_memory_unmap (memory, &info);
return GST_FLOW_OK;
}
static void
gst_clapper_gl_import_init (GstClapperGLImport *self)
{
}
static void
gst_clapper_gl_import_class_init (GstClapperGLImportClass *klass)
{
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass;
GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglimport", 0,
"Clapper GL Import");
gstbasetransform_class->transform = gst_clapper_gl_import_transform;
bi_class->create_upstream_pool = gst_clapper_gl_import_create_upstream_pool;
gst_element_class_set_metadata (gstelement_class,
"Clapper GL import",
"Filter/Video", "Imports GL memory into ClapperGdkMemory",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_gl_import_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_gl_import_src_template);
}

38
lib/gst/plugin/gstclapperglimport.h vendored Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperglbaseimport.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_IMPORT (gst_clapper_gl_import_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGLImport, gst_clapper_gl_import, GST, CLAPPER_GL_IMPORT, GstClapperGLBaseImport)
#define GST_CLAPPER_GL_IMPORT_CAST(obj) ((GstClapperGLImport *)(obj))
struct _GstClapperGLImport
{
GstClapperGLBaseImport parent;
};
GST_ELEMENT_REGISTER_DECLARE (clapperglimport);
G_END_DECLS

128
lib/gst/plugin/gstclapperimport.c vendored Normal file
View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperimport.h"
#include "gstclappergdkmemory.h"
#include "gstgtkutils.h"
#define GST_CAT_DEFAULT gst_clapper_import_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static GstStaticPadTemplate gst_clapper_import_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
GST_VIDEO_CAPS_MAKE ("{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }")));
static GstStaticPadTemplate gst_clapper_import_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY,
"{ " GST_CLAPPER_GDK_MEMORY_FORMATS " }")));
#define parent_class gst_clapper_import_parent_class
G_DEFINE_TYPE (GstClapperImport, gst_clapper_import, GST_TYPE_CLAPPER_BASE_IMPORT);
GST_ELEMENT_REGISTER_DEFINE (clapperimport, "clapperimport", GST_RANK_NONE,
GST_TYPE_CLAPPER_IMPORT);
static GstBufferPool *
gst_clapper_import_create_upstream_pool (GstClapperBaseImport *bi, GstStructure **config)
{
GstClapperImport *self = GST_CLAPPER_IMPORT_CAST (bi);
GstBufferPool *pool;
GST_DEBUG_OBJECT (self, "Creating new upstream pool");
pool = gst_video_buffer_pool_new ();
*config = gst_buffer_pool_get_config (pool);
return pool;
}
static void
video_frame_unmap_and_free (GstVideoFrame *frame)
{
gst_video_frame_unmap (frame);
g_slice_free (GstVideoFrame, frame);
}
static GstFlowReturn
gst_clapper_import_transform (GstBaseTransform *bt,
GstBuffer *in_buf, GstBuffer *out_buf)
{
GstClapperBaseImport *bi = GST_CLAPPER_BASE_IMPORT_CAST (bt);
GstVideoFrame *frame;
GstMapInfo info;
GstMemory *memory;
frame = g_slice_new (GstVideoFrame);
if (!gst_clapper_base_import_map_buffers (bi, in_buf, out_buf,
GST_MAP_READ, GST_MAP_WRITE, frame, &info, &memory)) {
g_slice_free (GstVideoFrame, frame);
return GST_FLOW_ERROR;
}
/* Keep frame data alive as long as necessary,
* unmap only after bytes are destroyed */
GST_CLAPPER_GDK_MEMORY_CAST (memory)->texture = gst_video_frame_into_gdk_texture (
frame, (GDestroyNotify) video_frame_unmap_and_free);
gst_memory_unmap (memory, &info);
return GST_FLOW_OK;
}
static void
gst_clapper_import_init (GstClapperImport *self)
{
}
static void
gst_clapper_import_class_init (GstClapperImportClass *klass)
{
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseTransformClass *gstbasetransform_class = (GstBaseTransformClass *) klass;
GstClapperBaseImportClass *bi_class = (GstClapperBaseImportClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimport", 0,
"Clapper Import");
gstbasetransform_class->transform = gst_clapper_import_transform;
bi_class->create_upstream_pool = gst_clapper_import_create_upstream_pool;
gst_element_class_set_metadata (gstelement_class,
"Clapper import",
"Filter/Video", "Imports RAW video data into ClapperGdkMemory",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_import_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_import_src_template);
}

38
lib/gst/plugin/gstclapperimport.h vendored Normal file
View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperbaseimport.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_IMPORT (gst_clapper_import_get_type())
G_DECLARE_FINAL_TYPE (GstClapperImport, gst_clapper_import, GST, CLAPPER_IMPORT, GstClapperBaseImport)
#define GST_CLAPPER_IMPORT_CAST(obj) ((GstClapperImport *)(obj))
struct _GstClapperImport
{
GstClapperBaseImport parent;
};
GST_ELEMENT_REGISTER_DECLARE (clapperimport);
G_END_DECLS

635
lib/gst/plugin/gstclapperpaintable.c vendored Normal file
View File

@@ -0,0 +1,635 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperpaintable.h"
#include "gstclappergdkmemory.h"
#include "gstgtkutils.h"
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define GST_CAT_DEFAULT gst_clapper_paintable_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef struct
{
GdkTexture *texture;
GstVideoOverlayRectangle *rectangle;
gint x, y;
guint width, height;
gboolean used;
} GstClapperGdkOverlay;
static void
gst_clapper_gdk_overlay_free (GstClapperGdkOverlay *overlay)
{
GST_TRACE ("Freeing overlay: %" GST_PTR_FORMAT, overlay);
g_object_unref (overlay->texture);
gst_video_overlay_rectangle_unref (overlay->rectangle);
g_slice_free (GstClapperGdkOverlay, overlay);
}
static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface);
static void gst_clapper_paintable_dispose (GObject *object);
static void gst_clapper_paintable_finalize (GObject *object);
#define parent_class gst_clapper_paintable_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gst_clapper_paintable_iface_init));
static void
gst_clapper_paintable_class_init (GstClapperPaintableClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0,
"Clapper Paintable");
gobject_class->dispose = gst_clapper_paintable_dispose;
gobject_class->finalize = gst_clapper_paintable_finalize;
}
static void
gst_clapper_paintable_init (GstClapperPaintable *self)
{
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
g_mutex_init (&self->lock);
gst_video_info_init (&self->v_info);
g_weak_ref_init (&self->widget, NULL);
self->overlays = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_gdk_overlay_free);
gdk_rgba_parse (&self->bg, "black");
}
static void
gst_clapper_paintable_dispose (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
g_source_remove (self->draw_id);
self->draw_id = 0;
}
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_paintable_finalize (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_TRACE ("Finalize");
GST_CLAPPER_PAINTABLE_LOCK (self);
g_weak_ref_clear (&self->widget);
gst_clear_buffer (&self->pending_buffer);
gst_clear_buffer (&self->buffer);
g_ptr_array_unref (self->overlays);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static gboolean
calculate_display_par (GstClapperPaintable *self, GstVideoInfo *info)
{
gint width, height, par_n, par_d, req_par_n, req_par_d;
gboolean success;
width = GST_VIDEO_INFO_WIDTH (info);
height = GST_VIDEO_INFO_HEIGHT (info);
/* Cannot apply aspect ratio if there is no video */
if (width == 0 || height == 0)
return FALSE;
par_n = GST_VIDEO_INFO_PAR_N (info);
par_d = GST_VIDEO_INFO_PAR_D (info);
req_par_n = self->par_n;
req_par_d = self->par_d;
if (par_n == 0)
par_n = 1;
/* Use defaults if user set zero */
if (req_par_n == 0 || req_par_d == 0) {
req_par_n = DEFAULT_PAR_N;
req_par_d = DEFAULT_PAR_D;
}
GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d);
if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num,
&self->display_ratio_den, width, height, par_n, par_d,
req_par_n, req_par_d))) {
GST_ERROR_OBJECT (self, "Could not calculate display ratio values");
}
return success;
}
static void
invalidate_paintable_size_internal (GstClapperPaintable *self)
{
gint video_width, video_height;
guint display_ratio_num, display_ratio_den;
GST_CLAPPER_PAINTABLE_LOCK (self);
video_width = GST_VIDEO_INFO_WIDTH (&self->v_info);
video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
display_ratio_num = self->display_ratio_num;
display_ratio_den = self->display_ratio_den;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
if (video_height % display_ratio_den == 0) {
GST_LOG ("Keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
} else if (video_width % display_ratio_num == 0) {
GST_LOG ("Keeping video width");
self->display_width = video_width;
self->display_height = (guint)
gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num);
} else {
GST_LOG ("Approximating while keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
}
self->display_aspect_ratio = ((gdouble) self->display_width
/ (gdouble) self->display_height);
GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d",
self->display_width, self->display_height);
gdk_paintable_invalidate_size ((GdkPaintable *) self);
}
static gboolean
invalidate_paintable_size_on_main_cb (GstClapperPaintable *self)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
invalidate_paintable_size_internal (self);
return G_SOURCE_REMOVE;
}
static void
comp_frame_unmap_and_free (GstVideoFrame *frame)
{
gst_video_frame_unmap (frame);
g_slice_free (GstVideoFrame, frame);
}
static GstClapperGdkOverlay *
_get_cached_overlay (GPtrArray *overlays, GstVideoOverlayRectangle *rectangle, guint *index)
{
guint i;
for (i = 0; i < overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (overlays, i);
if (overlay->rectangle != rectangle)
continue;
*index = i;
return overlay;
}
return NULL;
}
static void
gst_clapper_paintable_prepare_overlays (GstClapperPaintable *self)
{
GstVideoOverlayCompositionMeta *comp_meta;
guint num_overlays, i;
/* As long as this is called from main thread, no need to lock here */
if (G_UNLIKELY (!self->buffer)
|| !(comp_meta = gst_buffer_get_video_overlay_composition_meta (self->buffer))) {
guint n_pending = self->overlays->len;
/* Remove all cached overlays if new buffer does not have any */
if (n_pending > 0) {
GST_TRACE ("No overlays in buffer, removing all cached ones");
g_ptr_array_remove_range (self->overlays, 0, n_pending);
}
return;
}
GST_LOG_OBJECT (self, "Preparing overlays...");
/* Mark all old overlays as unused */
for (i = 0; i < self->overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i);
overlay->used = FALSE;
}
num_overlays = gst_video_overlay_composition_n_rectangles (comp_meta->overlay);
for (i = 0; i < num_overlays; i++) {
GdkTexture *texture;
GstBuffer *comp_buffer;
GstVideoFrame *comp_frame;
GstVideoMeta *vmeta;
GstVideoInfo vinfo;
GstVideoOverlayRectangle *rectangle;
GstClapperGdkOverlay *overlay;
GstVideoOverlayFormatFlags flags, alpha_flags = 0;
gint comp_x, comp_y;
guint comp_width, comp_height, cached_index;
rectangle = gst_video_overlay_composition_get_rectangle (comp_meta->overlay, i);
if ((overlay = _get_cached_overlay (self->overlays, rectangle, &cached_index))) {
overlay->used = TRUE;
/* Place overlay at expected position */
if (i != cached_index) {
GST_LOG ("Rearranging overlay position: %u => %u", cached_index, i);
overlay = g_ptr_array_steal_index_fast (self->overlays, cached_index);
g_ptr_array_insert (self->overlays, i, overlay);
}
GST_TRACE ("Reusing cached overlay: %" GST_PTR_FORMAT, overlay);
continue;
}
if (G_UNLIKELY (!gst_video_overlay_rectangle_get_render_rectangle (rectangle,
&comp_x, &comp_y, &comp_width, &comp_height))) {
GST_WARNING ("Invalid overlay rectangle dimensions: %" GST_PTR_FORMAT, rectangle);
continue;
}
flags = gst_video_overlay_rectangle_get_flags (rectangle);
if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)
alpha_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA;
comp_buffer = gst_video_overlay_rectangle_get_pixels_unscaled_argb (rectangle, alpha_flags);
comp_frame = g_slice_new (GstVideoFrame);
/* Update overlay video info from video meta */
if ((vmeta = gst_buffer_get_video_meta (comp_buffer))) {
gst_video_info_set_format (&vinfo, vmeta->format, vmeta->width, vmeta->height);
vinfo.stride[0] = vmeta->stride[0];
}
if (G_UNLIKELY (!gst_video_frame_map (comp_frame, &vinfo, comp_buffer, GST_MAP_READ))) {
g_slice_free (GstVideoFrame, comp_frame);
return;
}
if ((texture = gst_video_frame_into_gdk_texture (
comp_frame, (GDestroyNotify) comp_frame_unmap_and_free))) {
overlay = g_slice_new (GstClapperGdkOverlay);
overlay->texture = texture;
overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle);
overlay->x = comp_x;
overlay->y = comp_y;
overlay->width = comp_width;
overlay->height = comp_height;
overlay->used = TRUE;
GST_TRACE ("Created overlay: %"
GST_PTR_FORMAT ", x: %i, y: %i, width: %u, height: %u",
overlay, overlay->x, overlay->y, overlay->width, overlay->height);
g_ptr_array_insert (self->overlays, i, overlay);
}
}
/* Remove all overlays that are not going to be used */
for (i = self->overlays->len; i > 0; i--) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i - 1);
if (!overlay->used) {
GST_TRACE ("Removing unused cached overlay: %" GST_PTR_FORMAT, overlay);
g_ptr_array_remove (self->overlays, overlay);
}
}
if (G_UNLIKELY (num_overlays != self->overlays->len)) {
GST_WARNING_OBJECT (self, "Some overlays could not be prepared, %u != %u",
num_overlays, self->overlays->len);
}
GST_LOG_OBJECT (self, "Prepared overlays: %u", self->overlays->len);
}
static gboolean
update_paintable_on_main_cb (GstClapperPaintable *self)
{
gboolean size_changed;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* Check if we will need to invalidate size */
if ((size_changed = self->pending_resize))
self->pending_resize = FALSE;
gst_clear_buffer (&self->buffer);
self->buffer = self->pending_buffer;
self->pending_buffer = NULL;
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
gst_clapper_paintable_prepare_overlays (self);
if (size_changed)
invalidate_paintable_size_internal (self);
GST_LOG_OBJECT (self, "Invalidate paintable contents");
gdk_paintable_invalidate_contents ((GdkPaintable *) self);
return G_SOURCE_REMOVE;
}
GstClapperPaintable *
gst_clapper_paintable_new (void)
{
return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL);
}
void
gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget)
{
g_weak_ref_set (&self->widget, widget);
}
void
gst_clapper_paintable_set_buffer (GstClapperPaintable *self, GstBuffer *buffer)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_TRACE ("Already have pending buffer, skipping %" GST_PTR_FORMAT, buffer);
return;
}
gst_buffer_replace (&self->pending_buffer, buffer);
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) update_paintable_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
gboolean
gst_clapper_paintable_set_video_info (GstClapperPaintable *self, GstVideoInfo *v_info)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (gst_video_info_is_equal (&self->v_info, v_info)) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
/* Reject info if values would cause integer overflow */
if (G_UNLIKELY (!calculate_display_par (self, v_info))) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return FALSE;
}
self->pending_resize = TRUE;
self->v_info = *v_info;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
void
gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self,
gint par_n, gint par_d)
{
gboolean success;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* No change */
if (self->par_n == par_n && self->par_d == par_d) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->par_n = par_n;
self->par_d = par_d;
/* Check if we can accept new values. This will update
* display `ratio_num` and `ratio_den` only when successful */
success = calculate_display_par (self, &self->v_info);
/* If paintable update is queued, wait for it, otherwise invalidate
* size only for change to be applied even when paused */
if (!success || self->draw_id > 0) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
/*
* GdkPaintableInterface
*/
static void
gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self,
GdkSnapshot *snapshot, gdouble width, gdouble height,
gint widget_width, gint widget_height)
{
GstMemory *memory;
GstMapInfo info;
gfloat scale_x, scale_y;
guint i;
scale_x = (gfloat) width / self->display_width;
scale_y = (gfloat) height / self->display_height;
/* Apply black borders when keeping aspect ratio */
if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) {
if (widget_height - height > 0) {
gdouble bar_height = (widget_height - height) / 2;
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -bar_height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bar_height + 0.5));
} else if (widget_width - width > 0) {
gdouble bar_width = (widget_width - width) / 2;
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -bar_width, height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, bar_width + 0.5, height));
}
}
/* Buffer is accessed only from main thread, so no locking required */
if (!self->buffer) {
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
return;
}
GST_TRACE ("Snapshot %" GST_PTR_FORMAT, self->buffer);
memory = gst_buffer_peek_memory (self->buffer, 0);
/* If we cannot map, just draw black */
if (G_UNLIKELY (!gst_memory_map (memory, &info, GST_MAP_READ))) {
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
GST_WARNING_OBJECT (self, "Could not map %" GST_PTR_FORMAT, self->buffer);
return;
}
gtk_snapshot_append_texture (snapshot,
GST_CLAPPER_GDK_MEMORY_CAST (memory)->texture,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gst_memory_unmap (memory, &info);
/* FIXME: Draw black BG here when import format has-alpha */
//gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
/* Finally append prepared overlays */
for (i = 0; i < self->overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i);
gtk_snapshot_append_texture (snapshot, overlay->texture,
&GRAPHENE_RECT_INIT (overlay->x * scale_x, overlay->y * scale_y,
overlay->width * scale_x, overlay->height * scale_y));
}
}
static void
gst_clapper_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot, gdouble width, gdouble height)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
GtkWidget *widget;
gint widget_width = 0, widget_height = 0;
if ((widget = g_weak_ref_get (&self->widget))) {
widget_width = gtk_widget_get_width (widget);
widget_height = gtk_widget_get_height (widget);
g_object_unref (widget);
}
gst_clapper_paintable_snapshot_internal (self, snapshot,
width, height, widget_width, widget_height);
}
static GdkPaintable *
gst_clapper_paintable_get_current_image (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
if (self->buffer) {
GtkSnapshot *snapshot;
GdkPaintable *ret;
snapshot = gtk_snapshot_new ();
/* Snapshot without widget size in order to get
* paintable without black borders */
gst_clapper_paintable_snapshot_internal (self, snapshot,
self->display_width, self->display_height, 0, 0);
if ((ret = gtk_snapshot_free_to_paintable (snapshot, NULL)))
return ret;
}
return gdk_paintable_new_empty (0, 0);
}
static gint
gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_width;
}
static gint
gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_height;
}
static gdouble
gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_aspect_ratio;
}
static void
gst_clapper_paintable_iface_init (GdkPaintableInterface *iface)
{
iface->snapshot = gst_clapper_paintable_snapshot;
iface->get_current_image = gst_clapper_paintable_get_current_image;
iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio;
}

74
lib/gst/plugin/gstclapperpaintable.h vendored Normal file
View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_PAINTABLE (gst_clapper_paintable_get_type())
G_DECLARE_FINAL_TYPE (GstClapperPaintable, gst_clapper_paintable, GST, CLAPPER_PAINTABLE, GObject)
#define GST_CLAPPER_PAINTABLE_CAST(obj) ((GstClapperPaintable *)(obj))
#define GST_CLAPPER_PAINTABLE_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->lock)
#define GST_CLAPPER_PAINTABLE_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
#define GST_CLAPPER_PAINTABLE_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
struct _GstClapperPaintable
{
GObject parent;
GMutex lock;
GstBuffer *pending_buffer, *buffer;
GPtrArray *overlays;
GstVideoInfo v_info;
GdkRGBA bg;
GWeakRef widget;
/* Sink properties */
gint par_n, par_d;
/* Resize */
gboolean pending_resize;
guint display_ratio_num;
guint display_ratio_den;
/* GdkPaintableInterface */
gint display_width;
gint display_height;
gdouble display_aspect_ratio;
/* Pending draw signal id */
guint draw_id;
};
GstClapperPaintable * gst_clapper_paintable_new (void);
void gst_clapper_paintable_set_widget (GstClapperPaintable *paintable, GtkWidget *widget);
void gst_clapper_paintable_set_buffer (GstClapperPaintable *paintable, GstBuffer *buffer);
gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *paintable, GstVideoInfo *v_info);
void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *paintable, gint par_n, gint par_d);
G_END_DECLS

838
lib/gst/plugin/gstclappersink.c vendored Normal file
View File

@@ -0,0 +1,838 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclappersink.h"
#include "gstclappergdkmemory.h"
#include "gstclappergdkbufferpool.h"
#include "gstgtkutils.h"
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define DEFAULT_KEEP_LAST_FRAME FALSE
#define WINDOW_CSS_CLASS_NAME "clappersinkwindow"
enum
{
PROP_0,
PROP_WIDGET,
PROP_FORCE_ASPECT_RATIO,
PROP_PIXEL_ASPECT_RATIO,
PROP_KEEP_LAST_FRAME,
PROP_LAST
};
#define GST_CAT_DEFAULT gst_clapper_sink_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static GstStaticPadTemplate gst_clapper_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_CLAPPER_GDK_MEMORY,
"{ " GST_CLAPPER_GDK_MEMORY_FORMATS ", NV12 }")
"; "
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_CLAPPER_GDK_MEMORY ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_CLAPPER_GDK_MEMORY_FORMATS ", NV12 }")));
static void gst_clapper_sink_navigation_interface_init (
GstNavigationInterface *iface);
#define parent_class gst_clapper_sink_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink, GST_TYPE_VIDEO_SINK,
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
gst_clapper_sink_navigation_interface_init));
GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE,
GST_TYPE_CLAPPER_SINK);
static void
window_clear_no_lock (GstClapperSink *self)
{
if (!self->window)
return;
GST_TRACE_OBJECT (self, "Window clear");
if (self->window_destroy_id) {
g_signal_handler_disconnect (self->window, self->window_destroy_id);
self->window_destroy_id = 0;
}
self->window = NULL;
}
static void
widget_clear_no_lock (GstClapperSink *self)
{
if (!self->widget)
return;
GST_TRACE_OBJECT (self, "Widget clear");
if (self->widget_destroy_id) {
g_signal_handler_disconnect (self->widget, self->widget_destroy_id);
self->widget_destroy_id = 0;
}
g_clear_object (&self->widget);
}
static void
widget_destroy_cb (GtkWidget *widget, GstClapperSink *self)
{
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
window_destroy_cb (GtkWidget *window, GstClapperSink *self)
{
GST_DEBUG_OBJECT (self, "Window destroy");
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
window_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
calculate_stream_coords (GstClapperSink *self, GtkWidget *widget,
gdouble x, gdouble y, gdouble *stream_x, gdouble *stream_y)
{
GstVideoRectangle result;
gint scaled_width, scaled_height, scale_factor;
gint video_width, video_height;
gboolean force_aspect_ratio;
GST_CLAPPER_SINK_LOCK (self);
video_width = GST_VIDEO_INFO_WIDTH (&self->v_info);
video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
force_aspect_ratio = self->force_aspect_ratio;
GST_CLAPPER_SINK_UNLOCK (self);
scale_factor = gtk_widget_get_scale_factor (widget);
scaled_width = gtk_widget_get_width (widget) * scale_factor;
scaled_height = gtk_widget_get_height (widget) * scale_factor;
if (force_aspect_ratio) {
GstVideoRectangle src, dst;
src.x = 0;
src.y = 0;
src.w = gdk_paintable_get_intrinsic_width ((GdkPaintable *) self->paintable);
src.h = gdk_paintable_get_intrinsic_height ((GdkPaintable *) self->paintable);
dst.x = 0;
dst.y = 0;
dst.w = scaled_width;
dst.h = scaled_height;
gst_video_center_rect (&src, &dst, &result, TRUE);
} else {
result.x = 0;
result.y = 0;
result.w = scaled_width;
result.h = scaled_height;
}
/* Display coordinates to stream coordinates */
*stream_x = (result.w > 0)
? (x - result.x) / result.w * video_width
: 0;
*stream_y = (result.h > 0)
? (y - result.y) / result.h * video_height
: 0;
/* Clip to stream size */
*stream_x = CLAMP (*stream_x, 0, video_width);
*stream_y = CLAMP (*stream_y, 0, video_height);
GST_LOG ("Transform coords %fx%f => %fx%f", x, y, *stream_x, *stream_y);
}
static void
gst_clapper_sink_widget_motion_event (GtkEventControllerMotion *motion,
gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
gdouble stream_x, stream_y;
if ((x == self->last_pos_x && y == self->last_pos_y)
|| GST_STATE (self) < GST_STATE_PLAYING)
return;
self->last_pos_x = x;
self->last_pos_y = y;
widget = gtk_event_controller_get_widget ((GtkEventController *) motion);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"mouse-move\", x: %f, y: %f", stream_x, stream_y);
gst_navigation_send_mouse_event ((GstNavigation *) self, "mouse-move",
0, stream_x, stream_y);
}
static void
gst_clapper_sink_widget_button_event (GtkGestureClick *click,
gint n_press, gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
GdkEvent *event;
gdouble stream_x, stream_y;
GdkEventType event_type;
const gchar *event_name;
if (GST_STATE (self) < GST_STATE_PLAYING)
return;
event = gtk_event_controller_get_current_event ((GtkEventController *) click);
event_type = gdk_event_get_event_type (event);
/* FIXME: Touchscreen handling should probably use new touch events from GStreamer 1.22 */
event_name = (event_type == GDK_BUTTON_PRESS || event_type == GDK_TOUCH_BEGIN)
? "mouse-button-press"
: (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END)
? "mouse-button-release"
: NULL;
/* Can be NULL on touch */
if (!event_name)
return;
widget = gtk_event_controller_get_widget ((GtkEventController *) click);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"%s\", x: %f, y: %f", event_name, stream_x, stream_y);
/* Gesture is set to handle only primary button, so we do not have to check */
gst_navigation_send_mouse_event ((GstNavigation *) self, event_name,
1, stream_x, stream_y);
}
/* Must call from main thread only with a lock */
static GtkWidget *
gst_clapper_sink_get_widget (GstClapperSink *self)
{
if (G_UNLIKELY (!self->widget)) {
GtkEventController *controller;
GtkGesture *gesture;
/* Make sure GTK is initialized */
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return NULL;
}
self->widget = gtk_picture_new ();
/* Otherwise widget in grid will appear as a 1x1px
* video which might be misleading for users */
gtk_widget_set_hexpand (self->widget, TRUE);
gtk_widget_set_vexpand (self->widget, TRUE);
gtk_widget_set_focusable (self->widget, TRUE);
gtk_widget_set_can_focus (self->widget, TRUE);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion",
G_CALLBACK (gst_clapper_sink_widget_motion_event), self);
gtk_widget_add_controller (self->widget, controller);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
gtk_widget_add_controller (self->widget, GTK_EVENT_CONTROLLER (gesture));
/* TODO: Implement touch events once we depend on GStreamer 1.22 */
/* Take floating ref */
g_object_ref_sink (self->widget);
/* Set back pointer */
gst_clapper_paintable_set_widget (self->paintable, self->widget);
/* Set earlier remembered property */
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
gtk_picture_set_paintable (GTK_PICTURE (self->widget), GDK_PAINTABLE (self->paintable));
self->widget_destroy_id = g_signal_connect (self->widget,
"destroy", G_CALLBACK (widget_destroy_cb), self);
}
return self->widget;
}
static GtkWidget *
gst_clapper_sink_obtain_widget (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
widget = gst_clapper_sink_get_widget (self);
if (widget)
g_object_ref (widget);
GST_CLAPPER_SINK_UNLOCK (self);
return widget;
}
static void
gst_clapper_sink_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_WIDGET:
if (self->widget) {
g_value_set_object (value, self->widget);
} else {
GtkWidget *widget;
GST_CLAPPER_SINK_UNLOCK (self);
widget = gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_sink_obtain_widget, self);
GST_CLAPPER_SINK_LOCK (self);
g_value_set_object (value, widget);
g_object_unref (widget);
}
break;
case PROP_FORCE_ASPECT_RATIO:
g_value_set_boolean (value, self->force_aspect_ratio);
break;
case PROP_PIXEL_ASPECT_RATIO:
gst_value_set_fraction (value, self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
g_value_set_boolean (value, self->keep_last_frame);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_FORCE_ASPECT_RATIO:
self->force_aspect_ratio = g_value_get_boolean (value);
if (self->widget) {
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
}
break;
case PROP_PIXEL_ASPECT_RATIO:
self->par_n = gst_value_get_fraction_numerator (value);
self->par_d = gst_value_get_fraction_denominator (value);
gst_clapper_paintable_set_pixel_aspect_ratio (self->paintable,
self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
self->keep_last_frame = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_navigation_send_event (GstNavigation *navigation,
GstStructure *structure)
{
GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation);
GstEvent *event;
GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure);
event = gst_event_new_navigation (structure);
if (G_LIKELY (event)) {
GstPad *pad;
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
if (G_LIKELY (pad)) {
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
/* If upstream didn't handle the event we'll post a message with it
* for the application in case it wants to do something with it */
gst_element_post_message (GST_ELEMENT_CAST (sink),
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
}
gst_object_unref (pad);
}
gst_event_unref (event);
}
}
static gboolean
gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GstBufferPool *pool = NULL;
GstCaps *caps;
GstVideoInfo info;
guint size;
gboolean need_pool;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_DEBUG_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_DEBUG_OBJECT (self, "Invalid caps specified");
return FALSE;
}
/* Normal size of a frame */
size = GST_VIDEO_INFO_SIZE (&info);
if (need_pool) {
GstStructure *config;
GST_DEBUG_OBJECT (self, "Creating new pool");
pool = gst_clapper_gdk_buffer_pool_new ();
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_config_set_params (config, caps, size, 2, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
gst_object_unref (pool);
GST_DEBUG_OBJECT (self, "Failed to set config");
return FALSE;
}
}
gst_query_add_allocation_pool (query, pool, size, 2, 0);
if (pool)
gst_object_unref (pool);
gst_query_add_allocation_meta (query,
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
/* We also support various metadata */
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return TRUE;
}
static gboolean
gst_clapper_sink_start_on_main (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
/* Make sure widget is created */
if (!(widget = gst_clapper_sink_get_widget (self))) {
GST_CLAPPER_SINK_UNLOCK (self);
return FALSE;
}
/* When no toplevel window, make our own */
if (G_UNLIKELY (!gtk_widget_get_root (widget) && !self->window)) {
GtkWidget *toplevel, *parent;
GtkCssProvider *provider;
gchar *win_title;
if ((parent = gtk_widget_get_parent (widget))) {
GtkWidget *temp_parent;
while ((temp_parent = gtk_widget_get_parent (parent)))
parent = temp_parent;
}
toplevel = (parent) ? parent : widget;
self->window = (GtkWindow *) gtk_window_new ();
gtk_widget_add_css_class (GTK_WIDGET (self->window), WINDOW_CSS_CLASS_NAME);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider,
"." WINDOW_CSS_CLASS_NAME " { background: none; }", -1);
gtk_style_context_add_provider_for_display (
gdk_display_get_default (), GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window",
gtk_get_major_version (),
gtk_get_minor_version (),
gtk_get_micro_version ());
gtk_window_set_default_size (self->window, 640, 480);
gtk_window_set_title (self->window, win_title);
gtk_window_set_child (self->window, toplevel);
g_free (win_title);
self->window_destroy_id = g_signal_connect (self->window,
"destroy", G_CALLBACK (window_destroy_cb), self);
}
GST_CLAPPER_SINK_UNLOCK (self);
return TRUE;
}
static gboolean
window_present_on_main_idle (GtkWindow *window)
{
GST_INFO ("Presenting window");
gtk_window_present (window);
return G_SOURCE_REMOVE;
}
static gboolean
gst_clapper_sink_start (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GST_INFO_OBJECT (self, "Start");
if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_start_on_main, self)))) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("GtkWidget could not be created"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop_on_main (GstClapperSink *self)
{
GtkWindow *window = NULL;
GST_CLAPPER_SINK_LOCK (self);
if (self->window)
window = g_object_ref (self->window);
GST_CLAPPER_SINK_UNLOCK (self);
if (window) {
gtk_window_destroy (window);
g_object_unref (window);
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean has_window;
GST_INFO_OBJECT (self, "Stop");
GST_CLAPPER_SINK_LOCK (self);
has_window = (self->window != NULL);
GST_CLAPPER_SINK_UNLOCK (self);
if (G_UNLIKELY (has_window)) {
return (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_stop_on_main, self));
}
return TRUE;
}
static GstStateChangeReturn
gst_clapper_sink_change_state (GstElement *element, GstStateChange transition)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (element);
GST_DEBUG_OBJECT (self, "Changing state: %s => %s",
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_CLAPPER_SINK_LOCK (self);
if (!self->keep_last_frame)
gst_clapper_paintable_set_buffer (self->paintable, NULL);
GST_CLAPPER_SINK_UNLOCK (self);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (self->window && !self->presented_window)) {
g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) window_present_on_main_idle,
g_object_ref (self->window), (GDestroyNotify) g_object_unref);
self->presented_window = TRUE;
}
GST_CLAPPER_SINK_UNLOCK (self);
break;
default:
break;
}
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}
static void
gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer,
GstClockTime *start, GstClockTime *end)
{
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
return;
*start = GST_BUFFER_TIMESTAMP (buffer);
if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
*end = *start + GST_BUFFER_DURATION (buffer);
} else {
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gint fps_n, fps_d;
GST_CLAPPER_SINK_LOCK (self);
fps_n = GST_VIDEO_INFO_FPS_N (&self->v_info);
fps_d = GST_VIDEO_INFO_FPS_D (&self->v_info);
GST_CLAPPER_SINK_UNLOCK (self);
if (fps_n > 0)
*end = *start + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
}
}
static GstCaps *
gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter)
{
GstCaps *result, *tmp;
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
if (filter) {
GST_DEBUG ("Intersecting with filter caps: %" GST_PTR_FORMAT, filter);
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (tmp);
} else {
result = tmp;
}
GST_DEBUG ("Returning caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean res;
GST_INFO_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps);
GST_CLAPPER_SINK_LOCK (self);
if (!gst_video_info_from_caps (&self->v_info, caps)) {
GST_CLAPPER_SINK_UNLOCK (self);
return FALSE;
}
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return FALSE;
}
res = gst_clapper_paintable_set_video_info (self->paintable, &self->v_info);
GST_CLAPPER_SINK_UNLOCK (self);
return res;
}
static GstFlowReturn
gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
GST_TRACE ("Got %" GST_PTR_FORMAT, buffer);
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return GST_FLOW_ERROR;
}
gst_clapper_paintable_set_buffer (self->paintable, buffer);
GST_CLAPPER_SINK_UNLOCK (self);
return GST_FLOW_OK;
}
static void
gst_clapper_sink_init (GstClapperSink *self)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self);
/* HACK: install here instead of class init to avoid GStreamer
* plugin scanner GObject type conflicts with older GTK versions */
if (!g_object_class_find_property (gobject_class, "widget")) {
g_object_class_install_property (gobject_class, PROP_WIDGET,
g_param_spec_object ("widget", "GTK Widget",
"The GtkWidget to place in the widget hierarchy",
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
self->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
g_mutex_init (&self->lock);
gst_video_info_init (&self->v_info);
self->paintable = gst_clapper_paintable_new ();
}
static void
gst_clapper_sink_dispose (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
window_clear_no_lock (self);
widget_clear_no_lock (self);
g_clear_object (&self->paintable);
GST_CLAPPER_SINK_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_sink_finalize (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_TRACE ("Finalize");
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_sink_class_init (GstClapperSinkClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersink", 0,
"Clapper Sink");
gobject_class->get_property = gst_clapper_sink_get_property;
gobject_class->set_property = gst_clapper_sink_set_property;
gobject_class->dispose = gst_clapper_sink_dispose;
gobject_class->finalize = gst_clapper_sink_finalize;
g_object_class_install_property (gobject_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,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
"The pixel aspect ratio of the device",
DEFAULT_PAR_N, DEFAULT_PAR_D,
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME,
g_param_spec_boolean ("keep-last-frame", "Keep last frame",
"Keep showing last video frame after playback instead of black screen",
DEFAULT_KEEP_LAST_FRAME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gst_clapper_sink_change_state;
gstbasesink_class->get_caps = gst_clapper_sink_get_caps;
gstbasesink_class->set_caps = gst_clapper_sink_set_caps;
gstbasesink_class->get_times = gst_clapper_sink_get_times;
gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation;
gstbasesink_class->start = gst_clapper_sink_start;
gstbasesink_class->stop = gst_clapper_sink_stop;
gstvideosink_class->show_frame = gst_clapper_sink_show_frame;
gst_element_class_set_metadata (gstelement_class,
"Clapper video sink",
"Sink/Video", "A GTK4 video sink used by Clapper media player",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
gst_element_class_add_static_pad_template (gstelement_class,
&gst_clapper_sink_template);
}
/*
* GstNavigationInterface
*/
static void
gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface)
{
/* TODO: Port to "send_event_simple" once we depend on GStreamer 1.22 */
iface->send_event = gst_clapper_sink_navigation_send_event;
}

70
lib/gst/plugin/gstclappersink.h vendored Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/gstvideosink.h>
#include <gst/video/video.h>
#include "gstclapperpaintable.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type())
G_DECLARE_FINAL_TYPE (GstClapperSink, gst_clapper_sink, GST, CLAPPER_SINK, GstVideoSink)
#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass))
#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink *)(obj))
#define GST_CLAPPER_SINK_GET_LOCK(obj) (&GST_CLAPPER_SINK_CAST(obj)->lock)
#define GST_CLAPPER_SINK_LOCK(obj) g_mutex_lock (GST_CLAPPER_SINK_GET_LOCK(obj))
#define GST_CLAPPER_SINK_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_SINK_GET_LOCK(obj))
struct _GstClapperSink
{
GstVideoSink parent;
GMutex lock;
GstClapperPaintable *paintable;
GstVideoInfo v_info;
GtkWidget *widget;
GtkWindow *window;
gboolean presented_window;
/* Properties */
gboolean force_aspect_ratio;
gint par_n, par_d;
gboolean keep_last_frame;
/* Position coords */
gdouble last_pos_x;
gdouble last_pos_y;
gulong widget_destroy_id;
gulong window_destroy_id;
};
GST_ELEMENT_REGISTER_DECLARE (clappersink);
G_END_DECLS

131
lib/gst/plugin/gstgtkutils.c vendored Normal file
View File

@@ -0,0 +1,131 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstgtkutils.h"
struct invoke_context
{
GThreadFunc func;
gpointer data;
GMutex lock;
GCond cond;
gboolean fired;
gpointer res;
};
static gboolean
gst_gtk_invoke_func (struct invoke_context *info)
{
g_mutex_lock (&info->lock);
info->res = info->func (info->data);
info->fired = TRUE;
g_cond_signal (&info->cond);
g_mutex_unlock (&info->lock);
return G_SOURCE_REMOVE;
}
gpointer
gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
{
GMainContext *main_context = g_main_context_default ();
struct invoke_context info;
g_mutex_init (&info.lock);
g_cond_init (&info.cond);
info.fired = FALSE;
info.func = func;
info.data = data;
g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
&info);
g_mutex_lock (&info.lock);
while (!info.fired)
g_cond_wait (&info.cond, &info.lock);
g_mutex_unlock (&info.lock);
g_mutex_clear (&info.lock);
g_cond_clear (&info.cond);
return info.res;
}
/* For use with `GdkMemoryTexture` only! */
GdkMemoryFormat
gst_video_format_to_gdk_memory_format (GstVideoFormat format)
{
switch (format) {
case GST_VIDEO_FORMAT_RGBA64_LE:
case GST_VIDEO_FORMAT_RGBA64_BE:
return GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
case GST_VIDEO_FORMAT_RGBA:
return GDK_MEMORY_R8G8B8A8;
case GST_VIDEO_FORMAT_BGRA:
return GDK_MEMORY_B8G8R8A8;
case GST_VIDEO_FORMAT_ARGB:
return GDK_MEMORY_A8R8G8B8;
case GST_VIDEO_FORMAT_ABGR:
return GDK_MEMORY_A8B8G8R8;
case GST_VIDEO_FORMAT_RGBx:
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_BGRx:
return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_RGB:
return GDK_MEMORY_R8G8B8;
case GST_VIDEO_FORMAT_BGR:
return GDK_MEMORY_B8G8R8;
default:
break;
}
/* This should never happen as long as above switch statement
* is updated when new formats are added to caps */
g_assert_not_reached ();
/* Fallback format */
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
}
GdkTexture *
gst_video_frame_into_gdk_texture (GstVideoFrame *frame, GDestroyNotify free_func)
{
GdkTexture *texture;
GBytes *bytes;
bytes = g_bytes_new_with_free_func (
GST_VIDEO_FRAME_PLANE_DATA (frame, 0),
GST_VIDEO_FRAME_HEIGHT (frame) * GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0),
free_func, frame);
texture = gdk_memory_texture_new (
GST_VIDEO_FRAME_WIDTH (frame),
GST_VIDEO_FRAME_HEIGHT (frame),
gst_video_format_to_gdk_memory_format (GST_VIDEO_FRAME_FORMAT (frame)),
bytes,
GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0));
g_bytes_unref (bytes);
return texture;
}

33
lib/gst/plugin/gstgtkutils.h vendored Normal file
View File

@@ -0,0 +1,33 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 <glib.h>
#include <gtk/gtk.h>
#include <gst/video/video.h>
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
GdkMemoryFormat gst_video_format_to_gdk_memory_format (GstVideoFormat format);
GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame, GDestroyNotify free_func);

48
lib/gst/plugin/gstplugin.c vendored Normal file
View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.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 "gstclapperimport.h"
#include "gstclapperglimport.h"
#include "gstclapperdmabufimport.h"
#include "gstclappersink.h"
#include "gstclappergdkmemory.h"
static gboolean
plugin_init (GstPlugin *plugin)
{
gboolean res = FALSE;
res |= GST_ELEMENT_REGISTER (clapperimport, plugin);
res |= GST_ELEMENT_REGISTER (clapperglimport, plugin);
res |= GST_ELEMENT_REGISTER (clapperdmabufimport, plugin);
res |= GST_ELEMENT_REGISTER (clappersink, plugin);
if (res)
gst_clapper_gdk_memory_init_once ();
return res;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
clapper, "Clapper elements", plugin_init, VERSION, "LGPL",
GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

71
lib/gst/plugin/meson.build vendored Normal file
View File

@@ -0,0 +1,71 @@
gst_plugins_libdir = join_paths(prefix, libdir, 'gstreamer-1.0')
gst_clapper_plugin_args = [
'-DHAVE_CONFIG_H',
'-DGST_USE_UNSTABLE_API',
]
gst_clapper_plugin_deps = [
gtk4_dep,
gst_dep,
gstbase_dep,
gstvideo_dep,
gstallocators_dep,
]
if get_option('default_library') == 'static'
gst_clapper_plugin_args += ['-DGST_STATIC_COMPILATION']
endif
gst_clapper_plugin_option = get_option('gst-plugin')
if gst_clapper_plugin_option.disabled()
subdir_done()
endif
foreach dep : gst_clapper_plugin_deps
if not dep.found()
if gst_clapper_plugin_option.enabled()
error('GStreamer plugin was enabled, but required dependencies were not found')
endif
subdir_done()
endif
endforeach
if not have_gtk_gl_windowing
if gst_clapper_plugin_option.enabled()
error('GTK4 widget requires GL windowing')
else
subdir_done()
endif
endif
if not gtk4_dep.version().version_compare('>=4.6.0')
if gst_clapper_plugin_option.enabled()
error('GTK4 version on this system is too old, plugin needs 4.6.0+')
else
subdir_done()
endif
endif
gst_clapper_plugin_sources = [
'gstclappergdkmemory.c',
'gstclappergdkbufferpool.c',
'gstclapperbaseimport.c',
'gstclapperglbaseimport.c',
'gstclapperdmabufbaseimport.c',
'gstclapperimport.c',
'gstclapperglimport.c',
'gstclapperdmabufimport.c',
'gstclappersink.c',
'gstclapperpaintable.c',
'gstgtkutils.c',
'gstplugin.c',
]
library('gstclapper',
gst_clapper_plugin_sources,
c_args: gst_clapper_plugin_args,
include_directories: configinc,
dependencies: gst_clapper_plugin_deps + gtk_deps,
install: true,
install_dir: gst_plugins_libdir,
)

48
lib/meson.build vendored
View File

@@ -1,5 +1,5 @@
glib_req = '>= 2.56.0'
gst_req = '>= 1.18.0'
gst_req = '>= 1.20.0'
api_version = '1.0'
libversion = meson.project_version()
@@ -132,7 +132,7 @@ cdata.set('SIZEOF_SHORT', cc.sizeof('short'))
cdata.set('SIZEOF_VOIDP', cc.sizeof('void*'))
cdata.set_quoted('VERSION', libversion)
cdata.set_quoted('PACKAGE', 'gst-plugins-clapper')
cdata.set_quoted('PACKAGE', 'clapper')
cdata.set_quoted('PACKAGE_VERSION', libversion)
cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/Rafostar/clapper/issues/new')
cdata.set_quoted('PACKAGE_NAME', 'GStreamer Clapper Libs')
@@ -184,7 +184,7 @@ foreach extra_arg : warning_flags
endif
endforeach
cdata.set_quoted('GST_PACKAGE_NAME', 'GStreamer Plugins Clapper')
cdata.set_quoted('GST_PACKAGE_NAME', 'gst-plugin-clapper')
cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper')
# Mandatory GST deps
@@ -200,6 +200,8 @@ gsttag_dep = dependency('gstreamer-tag-1.0', version: gst_req,
fallback: ['gst-plugins-base', 'tag_dep'])
gstvideo_dep = dependency('gstreamer-video-1.0', version: gst_req,
fallback: ['gst-plugins-base', 'video_dep'])
gstallocators_dep = dependency('gstreamer-allocators-1.0', version: gst_req,
fallback : ['gst-plugins-base', 'allocators_dep'])
# GStreamer OpenGL
gstgl_dep = dependency('gstreamer-gl-1.0', version: gst_req,
@@ -251,21 +253,49 @@ giounix_dep = dependency('gio-unix-2.0', version: glib_req, fallback: ['glib', '
cdata.set('DISABLE_ORC', 1)
cdata.set('GST_ENABLE_EXTRA_CHECKS', get_option('devel-checks'))
cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', 'Unknown')
configinc = include_directories('.')
libsinc = include_directories('gst')
gir = find_program('g-ir-scanner', required: true)
if not gir.found()
error('Clapper requires GI bindings to be compiled')
endif
gir = find_program('g-ir-scanner')
gir_init_section = ['--add-init-section=extern void gst_init(gint*,gchar**);' + \
'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
'gst_init(NULL,NULL);', '--quiet'
]
gtk_deps = [gstgl_dep, gstglproto_dep]
have_gtk_gl_windowing = false
gtk4_dep = dependency('gtk4', required: true)
if not gtk4_dep.version().version_compare('>=4.0.0')
error('GTK4 version on this system is too old')
endif
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
gtk_x11_dep = dependency('gtk4-x11', required: false)
if gtk_x11_dep.found()
gtk_deps += gtk_x11_dep
if gst_gl_have_platform_glx
gtk_deps += gstglx11_dep
endif
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
if gtk_wayland_dep.found()
gtk_deps += [gtk_wayland_dep, gstglwayland_dep]
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_platform_egl
gtk_deps += gstglegl_dep
endif
subdir('gst')
configure_file(output: 'config.h', configuration: cdata)

View File

@@ -19,7 +19,7 @@ datadir = join_paths(get_option('prefix'), get_option('datadir'))
pkglibdir = join_paths(libdir, meson.project_name())
pkgdatadir = join_paths(datadir, meson.project_name())
if get_option('lib')
if get_option('lib') or not get_option('gst-plugin').disabled()
subdir('lib')
endif

View File

@@ -8,6 +8,11 @@ option('lib',
value: true,
description: 'Build GstClapper lib'
)
option('gst-plugin',
type: 'feature',
value: 'enabled',
description: 'Build GStreamer plugin (includes GTK video sink element)'
)
option('devel-checks',
type: 'boolean',
value: false,

View File

@@ -3,6 +3,10 @@
"runtime": "org.gnome.Platform",
"runtime-version": "master",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly",
"org.freedesktop.Sdk.Extension.llvm13"
],
"command": "com.github.rafostar.Clapper",
"finish-args": [
"--share=ipc",
@@ -19,6 +23,10 @@
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=GST_VAAPI_ALL_DRIVERS=1"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-nightly/bin:/usr/lib/sdk/llvm13/bin",
"prepend-ld-library-path": "/usr/lib/sdk/llvm13/lib"
},
"modules": [
"flathub/shared-modules/gudev/gudev.json",
"flathub/lib/libsass.json",
@@ -32,8 +40,8 @@
"flathub/lib/libdvdnav.json",
"flathub/lib/libass.json",
"flathub/lib/ffmpeg.json",
"testing/libsoup3.json",
"testing/gstreamer.json",
"testing/gst-plugins-rs.json",
"testing/gtuber.json",
{
"name": "clapper",

View File

@@ -1,7 +1,7 @@
{
"app-id": "com.github.rafostar.Clapper",
"runtime": "org.gnome.Platform",
"runtime-version": "41",
"runtime-version": "42",
"sdk": "org.gnome.Sdk",
"command": "com.github.rafostar.Clapper",
"finish-args": [
@@ -33,9 +33,8 @@
"flathub/lib/libass.json",
"flathub/lib/ffmpeg.json",
"flathub/lib/uchardet.json",
"testing/libsoup3.json",
"flathub/gstreamer-1.0/gstreamer.json",
"flathub/lib/gtk4.json",
"testing/gtk4.json",
"flathub/lib/libadwaita.json",
"testing/gtuber.json",
{

View File

@@ -0,0 +1,23 @@
{
"name": "gst-plugins-rs",
"buildsystem": "simple",
"build-options": {
"build-args": [
"--share=network"
],
"env": {
"CARGO_HOME": "/run/build/gst-plugins-rs/cargo"
}
},
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git",
"branch": "main"
}
],
"build-commands": [
"cargo install cargo-c",
"cargo cinstall --prefix=/app -p gst-plugin-dav1d"
]
}

View File

@@ -2,7 +2,7 @@
"name": "gstreamer",
"buildsystem": "meson",
"config-opts": [
"--wrap-mode=nofallback",
"--wrap-mode=nodownload",
"-Dbase=enabled",
"-Dgood=enabled",
@@ -26,26 +26,12 @@
"-Dgpl=enabled",
"-Dgstreamer:benchmarks=disabled",
"-Dgstreamer:gobject-cast-checks=disabled",
"-Dgstreamer:glib-asserts=disabled",
"-Dgstreamer:glib-checks=disabled",
"-Dgstreamer:extra-checks=disabled",
"-Dgst-plugins-base:gobject-cast-checks=disabled",
"-Dgst-plugins-base:glib-asserts=disabled",
"-Dgst-plugins-base:glib-checks=disabled",
"-Dgst-plugins-base:gl_api=opengl,gles2",
"-Dgst-plugins-base:gl_platform=egl,glx",
"-Dgst-plugins-good:gobject-cast-checks=disabled",
"-Dgst-plugins-good:glib-asserts=disabled",
"-Dgst-plugins-good:glib-checks=disabled",
"-Dgst-plugins-good:gtk3=disabled",
"-Dgst-plugins-bad:gobject-cast-checks=disabled",
"-Dgst-plugins-bad:glib-asserts=disabled",
"-Dgst-plugins-bad:glib-checks=disabled",
"-Dgst-plugins-bad:extra-checks=disabled",
"-Dgst-plugins-bad:vulkan=disabled",
"-Dgst-plugins-bad:webrtc=disabled",
"-Dgst-plugins-bad:wasapi=disabled",
@@ -57,16 +43,14 @@
"-Dgst-plugins-bad:v4l2codecs=enabled",
"-Dgst-plugins-bad:va=enabled",
"-Dgst-plugins-ugly:gobject-cast-checks=disabled",
"-Dgst-plugins-ugly:glib-asserts=disabled",
"-Dgst-plugins-ugly:glib-checks=disabled",
"-Dgst-plugins-ugly:mpeg2dec=enabled"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git",
"branch": "main"
"branch": "main",
"disable-submodules": true
}
]
}

View File

@@ -0,0 +1,31 @@
From b413ee2c7d458c7005d3d3d1da8822cd86893ac0 Mon Sep 17 00:00:00 2001
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
Date: Fri, 4 Dec 2020 19:25:34 +0100
Subject: [PATCH] popover: Call unrealize on hide
When popover is shown "realize" method is called which creates a new
surface for popup. Unfortunately this causes performance drop on Wayland until that
surface is destroyed what happens inside "unrealize" method during popover destruction.
This commit changes default behavior in a way that surface will be destroyed
when popover is closed and app will ragain the performance it lost when
popover was shown.
---
gtk/gtkpopover.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 504dcd6cc1..a7a764d483 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -951,6 +951,7 @@ gtk_popover_hide (GtkWidget *widget)
gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), FALSE);
_gtk_widget_set_visible_flag (widget, FALSE);
+ gtk_widget_unrealize (widget);
gtk_widget_unmap (widget);
g_signal_emit (widget, signals[CLOSED], 0);
}
--
GitLab

View File

@@ -0,0 +1,37 @@
{
"name": "gtk",
"buildsystem": "meson",
"build-options": {
"build-args": [
"--share=network"
]
},
"config-opts": [
"--buildtype=release",
"-Dwin32-backend=false",
"-Dmacos-backend=false",
"-Dmedia-ffmpeg=disabled",
"-Dprint-cups=disabled",
"-Dintrospection=enabled",
"-Ddemos=false",
"-Dbuild-examples=false",
"-Dbuild-tests=false"
],
"cleanup": [
"/bin/gtk4-builder-tool",
"/bin/gtk4-encode-symbolic-svg"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/gtk.git",
"tag": "4.6.2",
"commit": "aec7ca82007dbe07faee6be084d20758ebac2b91"
},
{
"type": "patch",
"path": "gtk4-popover-unrealize.patch"
}
]
}

View File

@@ -1,24 +0,0 @@
{
"name": "libsoup3",
"buildsystem": "meson",
"config-opts": [
"-Dintrospection=enabled",
"-Dvapi=disabled",
"-Dtests=false",
"-Dsysprof=disabled",
"-Dhttp2_tests=disabled",
"-Dpkcs11_tests=disabled"
],
"cleanup": [
"/include",
"/lib/pkgconfig"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/libsoup.git",
"tag": "3.0.4",
"commit": "25a728020c4b53b5db4c4c675070e92f947fbd4d"
}
]
}

3
pkgs/rpm/.gitignore vendored
View File

@@ -1,3 +0,0 @@
.osc/
clapper/
.lock

View File

@@ -1 +0,0 @@
addFilter("explicit-lib-dependency")

View File

@@ -1,193 +0,0 @@
#
# spec file for package clapper
#
# Copyright (C) 2020 sp1rit
# Copyright (C) 2020-21 Rafostar
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
%define debug_package %{nil}
%global appname com.github.rafostar.Clapper
%global gst_version 1.18.0
%global gtk4_version 4.0.0
%global meson_version 0.50
%global glib2_version 2.56.0
%global soup_version 3.0.0
Name: clapper
Version: 0.4.1
Release: 1%{?dist}
Summary: Simple and modern GNOME media player
License: GPL-3.0
URL: https://github.com/Rafostar/clapper
BuildRoot: %{_builddir}/%{name}-%{version}-build
Source0: _service
BuildRequires: meson >= %{meson_version}
BuildRequires: gtk4-devel >= %{gtk4_version}
BuildRequires: glib2-devel >= %{glib2_version}
BuildRequires: gobject-introspection-devel
BuildRequires: gjs
BuildRequires: gcc-c++
BuildRequires: desktop-file-utils
BuildRequires: hicolor-icon-theme
Requires: gjs
Requires: gtk4 >= %{gtk4_version}
Requires: libadwaita
Requires: hicolor-icon-theme
%if 0%{?suse_version}
# SUSE recommends group tag, while Fedora discourages their use
Group: Productivity/Multimedia/Video/Players
BuildRequires: update-desktop-files
BuildRequires: gstreamer-devel >= %{gst_version}
BuildRequires: gstreamer-plugins-base-devel >= %{gst_version}
BuildRequires: Mesa-libGLESv2-devel
BuildRequires: Mesa-libGLESv3-devel
Requires: libsoup-devel >= %{soup_version}
Requires: gstreamer >= %{gst_version}
Requires: gstreamer-plugins-base >= %{gst_version}
Requires: gstreamer-plugins-good >= %{gst_version}
Requires: gstreamer-plugins-bad >= %{gst_version}
# Popular video decoders
Recommends: gstreamer-plugins-libav >= %{gst_version}
# CD Playback
Suggests: gstreamer-plugins-ugly
# Intel/AMD video acceleration
Suggests: gstreamer-plugins-vaapi
%else
BuildRequires: glibc-all-langpacks
BuildRequires: gstreamer1-devel >= %{gst_version}
BuildRequires: gstreamer1-plugins-base-devel >= %{gst_version}
BuildRequires: mesa-libGL-devel
BuildRequires: mesa-libGLES-devel
BuildRequires: mesa-libGLU-devel
BuildRequires: mesa-libEGL-devel
Requires: libsoup3-devel
Requires: gstreamer1 >= %{gst_version}
Requires: gstreamer1-plugins-base >= %{gst_version}
Requires: gstreamer1-plugins-good >= %{gst_version}
Requires: gstreamer1-plugins-bad-free >= %{gst_version}
# ASS subtitles (assrender)
Recommends: gstreamer1-plugins-bad-free-extras >= %{gst_version}
# CD Playback
Suggests: gstreamer1-plugins-ugly-free
# Intel/AMD video acceleration
Suggests: gstreamer1-vaapi
%endif
%description
A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering.
%prep
%setup -q -n %{_sourcedir}/%{name}-%{version} -T -D
%build
%meson
%meson_build
%install
%meson_install
%if 0%{?suse_version}
%suse_update_desktop_file %{appname}
%endif
%check
desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
%files
%license COPYING
%doc README.md
%{_bindir}/%{appname}*
%{_bindir}/clapper
%{_datadir}/%{appname}/
%{_datadir}/icons/hicolor/*/apps/*.svg
%{_datadir}/glib-2.0/schemas/%{appname}.gschema.xml
%{_datadir}/mime/packages/%{appname}.xml
%{_datadir}/applications/*.desktop
%{_datadir}/metainfo/*.metainfo.xml
%{_datadir}/gir-1.0/GstClapper-1.0.gir
%{_datadir}/locale/*/LC_MESSAGES/%{appname}.mo
%{_libdir}/%{appname}/
%changelog
* Thu Feb 17 2022 Rafostar <rafostar.github@gmail.com> - 0.4.1-2
- Require libsoup3
* Mon Dec 20 2021 Rafostar <rafostar.github@gmail.com> - 0.4.1-1
- New version
* Sun Sep 12 2021 Rafostar <rafostar.github@gmail.com> - 0.4.0-1
- New version
* Thu Aug 26 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-4
- Install translations
* Thu Aug 26 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-3
- Install clapper symlink
* Mon Aug 23 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-2
- Require libadwaita
* Fri Jun 18 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-1
- New version
* Mon Apr 19 2021 Rafostar <rafostar.github@gmail.com> - 0.2.1-1
- New version
* Tue Apr 13 2021 Rafostar <rafostar.github@gmail.com> - 0.2.0-1
- New version
* Fri Feb 26 2021 Rafostar <rafostar.github@gmail.com> - 0.1.0-1
- New version
* Sun Feb 7 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-10
- Install gstclapper libs to app named subdirectory
* Fri Feb 5 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-9
- Update build with gstclapper libs support
* Thu Jan 21 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-8
- Use metainfo instead of deprecated appdata
* Mon Jan 18 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-7
- Remove gjs-1.0 files
* Sun Dec 20 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-6
- Include additional app binaries
* Sat Oct 31 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-5
- Added metainfo
* Sun Oct 25 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-4
- Added gschema
* Wed Oct 14 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-3
- Update to GTK4
* Sat Sep 19 22:02:00 CEST 2020 sp1rit - 0.0.0-2
- Added suse_update_desktop_file macro for SuSE packages
* Fri Sep 18 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-1
- Initial package

View File

@@ -1,4 +1,4 @@
const { Adw, Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
const { Adw, Gdk, Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
const ByteArray = imports.byteArray;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
@@ -16,14 +16,31 @@ class ClapperPlayer extends GstClapper.Clapper
{
_init()
{
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
const glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
glsinkbin.sink = gtk4plugin.video_sink;
let vsink = null;
const use_legacy_sink = GLib.getenv('CLAPPER_USE_LEGACY_SINK');
if(!use_legacy_sink || use_legacy_sink != '1') {
vsink = Gst.parse_bin_from_description('glupload ! glcolorconvert'
+ ' ! clapperglimport ! clappersink name=clappersink', true);
if(vsink)
this.clappersink = vsink.get_by_name('clappersink');
}
if(!vsink) {
vsink = Gst.ElementFactory.make('glsinkbin', null);
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
warn('using legacy video sink');
this.clappersink = gtk4plugin.video_sink;
vsink.sink = this.clappersink;
}
super._init({
signal_dispatcher: new GstClapper.ClapperGMainContextSignalDispatcher(),
video_renderer: new GstClapper.ClapperVideoOverlayVideoRenderer({
video_sink: glsinkbin,
video_sink: vsink,
}),
mpris: new GstClapper.ClapperMpris({
own_name: `org.mpris.MediaPlayer2.${Misc.appName}`,
@@ -36,7 +53,7 @@ class ClapperPlayer extends GstClapper.Clapper
use_pipewire: settings.get_boolean('use-pipewire'),
});
this.widget = gtk4plugin.video_sink.widget;
this.widget = this.clappersink.widget;
this.widget.add_css_class('videowidget');
this.visualization_enabled = false;
@@ -615,7 +632,7 @@ class ClapperPlayer extends GstClapper.Clapper
switch(key) {
case 'after-playback':
this.widget.keep_last_frame = (settings.get_int(key) === 1);
this.clappersink.keep_last_frame = (settings.get_int(key) === 1);
break;
case 'seeking-mode':
switch(settings.get_int(key)) {

View File

@@ -345,7 +345,8 @@ class ClapperWidget extends Gtk.Grid
const currTime = GLib.DateTime.new_now_local();
const endTime = currTime.add_seconds(
this.controls.positionAdjustment.get_upper() - this.controls.currentPosition
(this.controls.positionAdjustment.get_upper() - this.controls.currentPosition)
/ this.controls.elapsedButton.speedScale.get_value()
);
const nextUpdate = this.revealerTop.setTimes(currTime, endTime, this.isSeekable);