Package overlaysrc as a standalone plugin
This packages overlaysrc as a standard reusable plugin that any user
application can build upon.
Change-Id: I8146dee55c733b9e4cfc9cafd768cff170214bf9
diff --git a/debian/control b/debian/control
index 4bae460..14b3038 100644
--- a/debian/control
+++ b/debian/control
@@ -2,12 +2,12 @@
Maintainer: Coral <coral-support@google.com>
Section: python
Priority: optional
-Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9)
+Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9), libgstreamer1.0-dev, pkg-config
Standards-Version: 3.9.8
Homepage: https://coral.withgoogle.com/
Package: python3-edgetpuvision
-Architecture: all
+Architecture: any
Depends: ${misc:Depends},
${python3:Depends},
gir1.2-glib-2.0,
@@ -18,6 +18,7 @@
gstreamer1.0-plugins-bad,
gstreamer1.0-plugins-good,
gstreamer1.0-plugins-ugly,
+ gstreamer1.0-python3-plugin-loader,
python3-cairo,
python3-edgetpu,
python3-gi,
diff --git a/debian/rules b/debian/rules
index 66fdb8d..f30fb7e 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,8 +1,13 @@
#!/usr/bin/make -f
-# This file was automatically generated by stdeb 0.8.5 at
-# Wed, 16 Jan 2019 21:25:55 +0000
+PLUGINSDIR := $(shell pkg-config --variable=pluginsdir gstreamer-1.0)
+DESTDIR := debian/python3-edgetpuvision/$(PLUGINSDIR)/python
+
export PYBUILD_NAME=edgetpuvision
%:
dh $@ --with python3 --buildsystem=pybuild
+override_dh_install:
+ dh_install
+ install -d $(DESTDIR)
+ install -g 0 -o 0 plugins/*.py $(DESTDIR)
diff --git a/edgetpuvision/gst_native.py b/edgetpuvision/gst_native.py
index e358d77..71584dc 100644
--- a/edgetpuvision/gst_native.py
+++ b/edgetpuvision/gst_native.py
@@ -21,38 +21,17 @@
gi.require_version('Gdk', '3.0')
gi.require_version('GObject', '2.0')
gi.require_version('Gst', '1.0')
-gi.require_version('GstBase', '1.0')
-gi.require_version('GstVideo', '1.0')
-from gi.repository import Gdk, GObject, Gst, GstBase, GstVideo
+from gi.repository import Gdk, GObject, Gst
Gdk.init([])
-# Gst.Buffer.map(Gst.MapFlags.WRITE) is broken, this is a workaround. See
-# http://lifestyletransfer.com/how-to-make-gstreamer-buffer-writable-in-python/
-# https://gitlab.gnome.org/GNOME/gobject-introspection/issues/69
-class GstMapInfo(ctypes.Structure):
- _fields_ = [('memory', ctypes.c_void_p), # GstMemory *memory
- ('flags', ctypes.c_int), # GstMapFlags flags
- ('data', ctypes.POINTER(ctypes.c_byte)), # guint8 *data
- ('size', ctypes.c_size_t), # gsize size
- ('maxsize', ctypes.c_size_t), # gsize maxsize
- ('user_data', ctypes.c_void_p * 4), # gpointer user_data[4]
- ('_gst_reserved', ctypes.c_void_p * 4)] # GST_PADDING
-
# ctypes imports for missing or broken introspection APIs.
libgst = ctypes.CDLL('libgstreamer-1.0.so.0')
libgst.gst_context_writable_structure.restype = ctypes.c_void_p
libgst.gst_context_writable_structure.argtypes = [ctypes.c_void_p]
libgst.gst_structure_set.restype = ctypes.c_void_p
libgst.gst_structure_set.argtypes = [ctypes.c_void_p, ctypes.c_char_p,
- ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
-GST_MAP_INFO_POINTER = ctypes.POINTER(GstMapInfo)
-libgst.gst_buffer_map.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER, ctypes.c_int]
-libgst.gst_buffer_map.restype = ctypes.c_int
-libgst.gst_buffer_unmap.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER]
-libgst.gst_buffer_unmap.restype = None
-libgst.gst_mini_object_is_writable.argtypes = [ctypes.c_void_p]
-libgst.gst_mini_object_is_writable.restype = ctypes.c_int
+ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
libgdk = ctypes.CDLL('libgdk-3.so.0')
libgdk.gdk_wayland_window_get_wl_surface.restype = ctypes.c_void_p
@@ -60,33 +39,6 @@
libgdk.gdk_wayland_display_get_wl_display.restype = ctypes.c_void_p
libgdk.gdk_wayland_display_get_wl_display.argtypes = [ctypes.c_void_p]
-libcairo = ctypes.CDLL('libcairo.so.2')
-libcairo.cairo_image_surface_create_for_data.restype = ctypes.c_void_p
-libcairo.cairo_image_surface_create_for_data.argtypes = [ctypes.c_void_p,
- ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int]
-libcairo.cairo_surface_flush.restype = None
-libcairo.cairo_surface_flush.argtypes = [ctypes.c_void_p]
-libcairo.cairo_surface_destroy.restype = None
-libcairo.cairo_surface_destroy.argtypes = [ctypes.c_void_p]
-libcairo.cairo_format_stride_for_width.restype = ctypes.c_int
-libcairo.cairo_format_stride_for_width.argtypes = [ctypes.c_int, ctypes.c_int]
-libcairo.cairo_create.restype = ctypes.c_void_p
-libcairo.cairo_create.argtypes = [ctypes.c_void_p]
-libcairo.cairo_destroy.restype = None
-libcairo.cairo_destroy.argtypes = [ctypes.c_void_p]
-
-librsvg = ctypes.CDLL('librsvg-2.so.2')
-librsvg.rsvg_handle_new_from_data.restype = ctypes.c_void_p
-librsvg.rsvg_handle_new_from_data.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
-librsvg.rsvg_handle_render_cairo.restype = ctypes.c_bool
-librsvg.rsvg_handle_render_cairo.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-librsvg.rsvg_handle_close.restype = ctypes.c_bool
-librsvg.rsvg_handle_close.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-
-libgobject = ctypes.CDLL('libgobject-2.0.so.0')
-libgobject.g_object_unref.restype = None
-libgobject.g_object_unref.argtypes = [ctypes.c_void_p]
-
def set_display_contexts(sink, widget):
handle = libgdk.gdk_wayland_window_get_wl_surface(hash(widget.get_window()))
sink.set_window_handle(handle)
@@ -98,190 +50,3 @@
hash(GObject.TYPE_POINTER), wl_display, 0)
sink.set_context(context)
-@contextlib.contextmanager
-def _gst_buffer_map(buffer, flags):
- ptr = hash(buffer)
- if flags & Gst.MapFlags.WRITE and libgst.gst_mini_object_is_writable(ptr) == 0:
- raise ValueError('Buffer not writable')
-
- mapping = GstMapInfo()
- success = libgst.gst_buffer_map(ptr, mapping, flags)
- if not success:
- raise RuntimeError('gst_buffer_map failed')
- try:
- yield ctypes.cast(mapping.data, ctypes.POINTER(ctypes.c_byte * mapping.size)).contents
- finally:
- libgst.gst_buffer_unmap(ptr, mapping)
-
-class OverlaySource(GstBase.BaseSrc):
- __gstmetadata__ = ('<longname>', '<class>', '<description>', '<author>')
- __gsttemplates__ = (Gst.PadTemplate.new('src',
- Gst.PadDirection.SRC,
- Gst.PadPresence.ALWAYS,
- Gst.Caps.from_string(
- 'video/x-raw,format=BGRA,framerate=0/1'
- )))
-
- @staticmethod
- def _plugin_init(plugin):
- gtype = GObject.type_register(OverlaySource)
- Gst.Element.register(plugin, 'overlaysrc', 0, gtype)
- return True
-
- @staticmethod
- def plugin_register():
- version = Gst.version()
- Gst.Plugin.register_static(
- version[0], version[1], # GStreamer version
- '', # name
- '', # description
- OverlaySource._plugin_init, # init_func
- '', # version
- 'unknown', # license
- '', # source
- '', # package
- '' # origin
- )
-
- def __init__(self):
- GstBase.BaseSrc.__init__(self)
- self.set_format(Gst.Format.TIME)
- self.set_do_timestamp(False)
- self.set_live(True)
- self.cond = threading.Condition()
- self.width = 0
- self.height = 0
- self.min_stride = 0
- self.flushing = False
- self.eos = False
- self.svg = None
- self.pts = 0
-
-
- def do_decide_allocation(self, query):
- if query.get_n_allocation_pools() > 0:
- pool, size, min_buffers, max_buffers = query.parse_nth_allocation_pool(0)
- query.set_nth_allocation_pool(0, pool, size, min_buffers, min(max_buffers, 3))
- return GstBase.BaseSrc.do_decide_allocation(self, query)
-
- def do_event(self, event):
- if event.type == Gst.EventType.SEEK:
- _, _, flags, _, _, _, _ = event.parse_seek()
- if flags | Gst.SeekFlags.FLUSH:
- self.send_event(Gst.Event.new_flush_start())
- self.send_event(Gst.Event.new_flush_stop(True))
- return True
- return GstBase.BaseSrc.do_event(self, event)
-
- def set_eos(self):
- with self.cond:
- self.eos = True
-
- def do_start (self):
- self.set_svg(None, 0)
- return True
-
- def do_stop (self):
- self.set_svg(None, 0)
- return True
-
- def set_svg(self, svg, pts):
- with self.cond:
- self.svg = svg
- self.pts = pts
- self.eos = False
- self.cond.notify_all()
-
- def set_flushing(self, flushing):
- with self.cond:
- self.flushing = flushing
- self.cond.notify_all()
-
- def do_set_caps(self, caps):
- structure = caps.get_structure(0)
- self.width = structure.get_value('width')
- self.height = structure.get_value('height')
- self.min_stride = libcairo.cairo_format_stride_for_width(
- int(cairo.FORMAT_ARGB32), self.width)
- return True
-
- def do_unlock(self):
- self.set_flushing(True)
- return True
-
- def do_unlock_stop(self):
- self.set_flushing(False)
- return True
-
- def get_flow_return_locked(self, default=None):
- if self.eos:
- self.eos = False
- self.svg = None
- return Gst.FlowReturn.EOS
- if self.flushing:
- return Gst.FlowReturn.FLUSHING
- return default
-
- def do_fill(self, offset, size, buf):
- with self.cond:
- result = self.get_flow_return_locked()
- if result:
- return result
-
- while self.svg is None:
- self.cond.wait()
- result = self.get_flow_return_locked()
- if result:
- return result
-
- assert self.svg is not None
- svg = self.svg
- pts = self.pts
- self.svg = None
-
- self.render_svg(svg, buf)
- buf.pts = pts
-
- with self.cond:
- return self.get_flow_return_locked(Gst.FlowReturn.OK)
-
- def render_svg(self, svg, buf):
- meta = GstVideo.buffer_get_video_meta(buf)
- if meta:
- assert meta.n_planes == 1
- assert meta.width == self.width
- assert meta.height == self.height
- assert meta.stride[0] >= self.min_stride
- stride = meta.stride[0]
- else:
- stride = self.min_stride
-
- with _gst_buffer_map(buf, Gst.MapFlags.WRITE) as mapped:
- assert len(mapped) >= stride * self.height
-
- # Fill with transparency.
- ctypes.memset(ctypes.addressof(mapped), 0, ctypes.sizeof(mapped))
-
- # If svg is '' (can't be None here) we return 100% transparency.
- if not svg:
- return
-
- surface = libcairo.cairo_image_surface_create_for_data(
- ctypes.addressof(mapped),
- int(cairo.FORMAT_ARGB32),
- self.width,
- self.height,
- stride)
-
- # Render the SVG overlay.
- data = svg.encode('utf-8')
- context = libcairo.cairo_create(surface)
- handle = librsvg.rsvg_handle_new_from_data(data, len(data), 0)
- librsvg.rsvg_handle_render_cairo(handle, context)
- librsvg.rsvg_handle_close(handle, 0)
- libgobject.g_object_unref(handle)
- libcairo.cairo_surface_flush(surface)
- libcairo.cairo_surface_destroy(surface)
- libcairo.cairo_destroy(context)
-
-OverlaySource.plugin_register()
diff --git a/edgetpuvision/gstreamer.py b/edgetpuvision/gstreamer.py
index 6e78f22..8e0e95e 100644
--- a/edgetpuvision/gstreamer.py
+++ b/edgetpuvision/gstreamer.py
@@ -231,7 +231,7 @@
command=custom_command)
overlay = pipeline.get_by_name('overlay')
if overlay:
- overlay.set_svg(svg, pts)
+ overlay.emit('queue-svg', svg, pts)
if save_frame:
images.put((data, layout.inference_size, svg))
diff --git a/edgetpuvision/pipelines.py b/edgetpuvision/pipelines.py
index 3a59d93..8d3f795 100644
--- a/edgetpuvision/pipelines.py
+++ b/edgetpuvision/pipelines.py
@@ -71,10 +71,7 @@
Filter('imagefreeze'),
Filter('glupload'),
Pad('mixer')],
- [Source('overlay', name='overlay'),
- Caps('video/x-raw', format='BGRA', width=layout.render_size.width, height=layout.render_size.height),
- Filter('glupload'),
- Queue(max_size_buffers=1),
+ [Source('glsvgoverlay', name='overlay', width=layout.render_size.width, height=layout.render_size.height),
Pad('mixer')],
[Pad('t'),
Queue(),
@@ -90,13 +87,9 @@
Filter('glupload'),
Tee(name='t')],
[Pad('t'),
- Filter('glupload'),
Queue(),
Pad('mixer')],
- [Source('overlay', name='overlay'),
- Caps('video/x-raw', format='BGRA', width=layout.render_size.width, height=layout.render_size.height),
- Filter('glupload'),
- Queue(max_size_buffers=1),
+ [Source('glsvgoverlay', name='overlay', width=layout.render_size.width, height=layout.render_size.height),
Pad('mixer')],
[Pad('t'),
Queue(max_size_buffers=1, leaky='downstream'),
@@ -111,13 +104,9 @@
Filter('glupload'),
Tee(name='t')],
[Pad('t'),
- Filter('glupload'),
Queue(),
Pad('mixer')],
- [Source('overlay', name='overlay'),
- Caps('video/x-raw', format='BGRA', width=layout.render_size.width, height=layout.render_size.height),
- Filter('glupload'),
- Queue(max_size_buffers=1),
+ [Source('glsvgoverlay', name='overlay', width=layout.render_size.width, height=layout.render_size.height),
Pad('mixer')],
[Pad(name='t'),
Queue(max_size_buffers=1, leaky='downstream'),
diff --git a/plugins/glsvgoverlaysrc.py b/plugins/glsvgoverlaysrc.py
new file mode 100644
index 0000000..1759a95
--- /dev/null
+++ b/plugins/glsvgoverlaysrc.py
@@ -0,0 +1,369 @@
+# Copyright 2019 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cairo
+import collections
+import contextlib
+import ctypes
+import os
+import threading
+import time
+
+import gi
+gi.require_version('GObject', '2.0')
+gi.require_version('GLib', '2.0')
+gi.require_version('Gst', '1.0')
+gi.require_version('GstBase', '1.0')
+gi.require_version('GstVideo', '1.0')
+from gi.repository import GObject, GLib, Gst, GstBase, GstVideo
+
+# Gst.Buffer.map(Gst.MapFlags.WRITE) is broken, this is a workaround. See
+# http://lifestyletransfer.com/how-to-make-gstreamer-buffer-writable-in-python/
+# https://gitlab.gnome.org/GNOME/gobject-introspection/issues/69
+class GstMapInfo(ctypes.Structure):
+ _fields_ = [('memory', ctypes.c_void_p), # GstMemory *memory
+ ('flags', ctypes.c_int), # GstMapFlags flags
+ ('data', ctypes.POINTER(ctypes.c_byte)), # guint8 *data
+ ('size', ctypes.c_size_t), # gsize size
+ ('maxsize', ctypes.c_size_t), # gsize maxsize
+ ('user_data', ctypes.c_void_p * 4), # gpointer user_data[4]
+ ('_gst_reserved', ctypes.c_void_p * 4)] # GST_PADDING
+
+# ctypes imports for missing or broken introspection APIs.
+libgst = ctypes.CDLL('libgstreamer-1.0.so.0')
+GST_MAP_INFO_POINTER = ctypes.POINTER(GstMapInfo)
+libgst.gst_buffer_map.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER, ctypes.c_int]
+libgst.gst_buffer_map.restype = ctypes.c_int
+libgst.gst_buffer_unmap.argtypes = [ctypes.c_void_p, GST_MAP_INFO_POINTER]
+libgst.gst_buffer_unmap.restype = None
+libgst.gst_mini_object_is_writable.argtypes = [ctypes.c_void_p]
+libgst.gst_mini_object_is_writable.restype = ctypes.c_int
+
+libcairo = ctypes.CDLL('libcairo.so.2')
+libcairo.cairo_image_surface_create_for_data.restype = ctypes.c_void_p
+libcairo.cairo_image_surface_create_for_data.argtypes = [ctypes.c_void_p,
+ ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int]
+libcairo.cairo_surface_flush.restype = None
+libcairo.cairo_surface_flush.argtypes = [ctypes.c_void_p]
+libcairo.cairo_surface_destroy.restype = None
+libcairo.cairo_surface_destroy.argtypes = [ctypes.c_void_p]
+libcairo.cairo_format_stride_for_width.restype = ctypes.c_int
+libcairo.cairo_format_stride_for_width.argtypes = [ctypes.c_int, ctypes.c_int]
+libcairo.cairo_create.restype = ctypes.c_void_p
+libcairo.cairo_create.argtypes = [ctypes.c_void_p]
+libcairo.cairo_destroy.restype = None
+libcairo.cairo_destroy.argtypes = [ctypes.c_void_p]
+
+librsvg = ctypes.CDLL('librsvg-2.so.2')
+librsvg.rsvg_handle_new_from_data.restype = ctypes.c_void_p
+librsvg.rsvg_handle_new_from_data.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.c_void_p]
+librsvg.rsvg_handle_render_cairo.restype = ctypes.c_bool
+librsvg.rsvg_handle_render_cairo.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+librsvg.rsvg_handle_close.restype = ctypes.c_bool
+librsvg.rsvg_handle_close.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+libgobject = ctypes.CDLL('libgobject-2.0.so.0')
+libgobject.g_object_unref.restype = None
+libgobject.g_object_unref.argtypes = [ctypes.c_void_p]
+
+@contextlib.contextmanager
+def _gst_buffer_map(buffer, flags):
+ ptr = hash(buffer)
+ if flags & Gst.MapFlags.WRITE and libgst.gst_mini_object_is_writable(ptr) == 0:
+ raise ValueError('Buffer not writable')
+
+ mapping = GstMapInfo()
+ success = libgst.gst_buffer_map(ptr, mapping, flags)
+ if not success:
+ raise RuntimeError('gst_buffer_map failed')
+ try:
+ yield ctypes.cast(mapping.data, ctypes.POINTER(ctypes.c_byte * mapping.size)).contents
+ finally:
+ libgst.gst_buffer_unmap(ptr, mapping)
+
+DEFAULT_IS_LIVE = True
+DEFAULT_HEIGHT = 640
+DEFAULT_WIDTH = 480
+
+class SvgOverlaySource(GstBase.PushSrc):
+ __gstmetadata__ = ('SVG overlay source',
+ 'Source',
+ 'Renders SVG using rsvg & cairo',
+ 'Coral <coral-support@google.com>')
+ __gsttemplates__ = (Gst.PadTemplate.new('src',
+ Gst.PadDirection.SRC,
+ Gst.PadPresence.ALWAYS,
+ Gst.Caps.from_string(
+ 'video/x-raw,format=BGRA,framerate=0/1'
+ )))
+
+ def __init__(self):
+ GstBase.PushSrc.__init__(self)
+ self.set_format(Gst.Format.TIME)
+ self.set_do_timestamp(False)
+ self.set_live(DEFAULT_IS_LIVE)
+ self.cond = threading.Condition()
+ self.width = DEFAULT_WIDTH
+ self.height = DEFAULT_HEIGHT
+ self.min_stride = 0
+ self.flushing = False
+ self.eos = False
+ self.queue = collections.deque()
+ self.print_fps = int(os.environ.get('PRINT_FPS', '0'))
+ self.fps_start = 0
+ self.input_frames = 0
+ self.output_frames = 0
+
+ def reset(self):
+ with self.cond:
+ self.eos = False
+ self.queue.clear()
+
+ def do_decide_allocation(self, query):
+ if query.get_n_allocation_pools() > 0:
+ pool, size, min_buffers, max_buffers = query.parse_nth_allocation_pool(0)
+ query.set_nth_allocation_pool(0, pool, size, min_buffers, min(max_buffers, 3))
+ return GstBase.BaseSrc.do_decide_allocation(self, query)
+
+ def do_event(self, event):
+ if event.type == Gst.EventType.SEEK:
+ _, _, flags, _, _, _, _ = event.parse_seek()
+ if flags | Gst.SeekFlags.FLUSH:
+ self.send_event(Gst.Event.new_flush_start())
+ self.send_event(Gst.Event.new_flush_stop(True))
+ self.reset()
+ return True
+ return GstBase.BaseSrc.do_event(self, event)
+
+ def do_gst_base_src_query(self, query):
+ if query.type == Gst.QueryType.LATENCY:
+ query.set_latency(self.is_live, 0, Gst.CLOCK_TIME_NONE)
+ return True
+ else:
+ return GstBase.BaseSrc.do_query(self, query)
+
+ def do_start (self):
+ self.reset()
+ return True
+
+ def do_stop (self):
+ self.reset()
+ return True
+
+ def set_eos(self):
+ with self.cond:
+ self.eos = True
+
+ def queue_svg(self, svg:str, pts:GObject.TYPE_UINT64):
+ with self.cond:
+ self.input_frames += 1
+ if self.is_live:
+ self.queue.clear()
+ self.queue.append((svg, pts))
+ self.cond.notify_all()
+
+ def set_flushing(self, flushing):
+ with self.cond:
+ self.flushing = flushing
+ self.cond.notify_all()
+
+ def do_fixate(self, caps):
+ s = caps.get_structure(0).copy()
+ s.fixate_field_nearest_int("width", self.width)
+ s.fixate_field_nearest_int("height", self.height)
+ result = Gst.Caps.new_empty()
+ result.append_structure(s)
+ result = result.fixate()
+ return result
+
+ def do_set_caps(self, caps):
+ structure = caps.get_structure(0)
+ self.width = structure.get_value('width')
+ self.height = structure.get_value('height')
+ self.min_stride = libcairo.cairo_format_stride_for_width(
+ int(cairo.FORMAT_ARGB32), self.width)
+ return True
+
+ def do_unlock(self):
+ self.set_flushing(True)
+ return True
+
+ def do_unlock_stop(self):
+ self.set_flushing(False)
+ return True
+
+ def get_flow_return_locked(self, default=None):
+ if not self.queue and self.eos:
+ self.eos = False
+ return Gst.FlowReturn.EOS
+ if self.flushing:
+ return Gst.FlowReturn.FLUSHING
+ return default
+
+ def do_gst_push_src_fill(self, buf):
+ with self.cond:
+ result = self.get_flow_return_locked()
+ if result:
+ return result
+
+ while not self.queue:
+ self.cond.wait()
+ result = self.get_flow_return_locked()
+ if result:
+ return result
+
+ assert self.queue
+ svg, pts = self.queue.popleft()
+
+ self.render_svg(svg, buf)
+ buf.pts = pts
+
+ with self.cond:
+ self.output_frames += 1
+ if not self.fps_start:
+ self.fps_start = time.monotonic()
+
+ elapsed = time.monotonic() - self.fps_start
+ if self.print_fps and elapsed > self.print_fps:
+ print('gloverlaysrc: in {} ({:.2f} fps), out {} ({:.2f} fps)'.format(
+ self.input_frames, self.input_frames / elapsed,
+ self.output_frames, self.output_frames / elapsed))
+ self.fps_start = time.monotonic()
+ self.input_frames = 0
+ self.output_frames = 0
+ return self.get_flow_return_locked(Gst.FlowReturn.OK)
+
+ def render_svg(self, svg, buf):
+ meta = GstVideo.buffer_get_video_meta(buf)
+ if meta:
+ assert meta.n_planes == 1
+ assert meta.width == self.width
+ assert meta.height == self.height
+ assert meta.stride[0] >= self.min_stride
+ stride = meta.stride[0]
+ else:
+ stride = self.min_stride
+
+ with _gst_buffer_map(buf, Gst.MapFlags.WRITE) as mapped:
+ assert len(mapped) >= stride * self.height
+
+ # Fill with transparency.
+ ctypes.memset(ctypes.addressof(mapped), 0, ctypes.sizeof(mapped))
+
+ surface = libcairo.cairo_image_surface_create_for_data(
+ ctypes.addressof(mapped),
+ int(cairo.FORMAT_ARGB32),
+ self.width,
+ self.height,
+ stride)
+
+ # Render the SVG overlay.
+ data = svg.encode('utf-8')
+ context = libcairo.cairo_create(surface)
+ handle = librsvg.rsvg_handle_new_from_data(data, len(data), 0)
+ librsvg.rsvg_handle_render_cairo(handle, context)
+ librsvg.rsvg_handle_close(handle, 0)
+ libgobject.g_object_unref(handle)
+ libcairo.cairo_surface_flush(surface)
+ libcairo.cairo_surface_destroy(surface)
+ libcairo.cairo_destroy(context)
+
+class GlSvgOverlaySource(Gst.Bin):
+ __gstmetadata__ = ('GL SVG overlay source',
+ 'Source',
+ 'Renders SVG to OpenGL textures',
+ 'Coral <coral-support@google.com>')
+ __gsttemplates__ = (Gst.PadTemplate.new('src',
+ Gst.PadDirection.SRC,
+ Gst.PadPresence.ALWAYS,
+ Gst.Caps.from_string(
+ 'video/x-raw(memory:GLMemory),format=RGBA,framerate=0/1'
+ )))
+ __gproperties__ = {
+ "is-live": (bool,
+ "Is live",
+ "Whether to act as a live source",
+ DEFAULT_IS_LIVE,
+ GObject.ParamFlags.READWRITE
+ ),
+ "width": (int,
+ "Frame width",
+ "Frame width, also settable via caps negotiation",
+ 1,
+ GLib.MAXINT,
+ DEFAULT_WIDTH,
+ GObject.ParamFlags.READWRITE
+ ),
+ "height": (int,
+ "Frame height",
+ "Frame height, also settable via caps negotiation",
+ 1,
+ GLib.MAXINT,
+ DEFAULT_WIDTH,
+ GObject.ParamFlags.READWRITE
+ ),
+ }
+
+
+ def __init__(self):
+ GstBase.PushSrc.__init__(self)
+ self.src = SvgOverlaySource()
+ self.queue = Gst.ElementFactory.make('queue')
+ self.upload = Gst.ElementFactory.make('glupload')
+ self.convert = Gst.ElementFactory.make('glcolorconvert')
+ self.filter = Gst.ElementFactory.make('capsfilter')
+
+ self.add(self.src)
+ self.add(self.queue)
+ self.add(self.upload)
+ self.add(self.convert)
+ self.add(self.filter)
+
+ self.src.link_pads('src', self.queue, 'sink')
+ self.queue.link_pads('src', self.upload, 'sink')
+ self.upload.link_pads('src', self.convert, 'sink')
+ self.convert.link_pads('src', self.filter, 'sink')
+ pad = Gst.GhostPad('src', self.filter.get_static_pad('src'))
+ self.add_pad(pad)
+ self.filter.set_property('caps', Gst.Caps.from_string('video/x-raw(memory:GLMemory),format=RGBA'))
+
+ def do_get_property(self, prop):
+ if prop.name == 'is-live':
+ return self.src.is_live
+ elif prop.name == 'width':
+ return self.src.width
+ elif prop.name == 'height':
+ return self.src.height
+ else:
+ raise AttributeError('Unknown property %s' % prop.name)
+
+ def do_set_property(self, prop, value):
+ if prop.name == 'is-live':
+ self.src.set_live(value)
+ elif prop.name == 'width':
+ self.src.width = value
+ elif prop.name == 'height':
+ self.src.height = value
+ else:
+ raise AttributeError('Unknown property %s' % prop.name)
+
+ @GObject.Signal(flags=GObject.SignalFlags.ACTION | GObject.SignalFlags.RUN_LAST)
+ def set_eos(self):
+ self.src.set_eos()
+
+ @GObject.Signal(flags=GObject.SignalFlags.ACTION | GObject.SignalFlags.RUN_LAST)
+ def queue_svg(self, svg:str, pts:GObject.TYPE_UINT64):
+ self.src.queue_svg(svg, pts)
+
+__gstelementfactory__ = ("glsvgoverlaysrc", Gst.Rank.NONE, GlSvgOverlaySource)