Upload rendered overlays as textures and display with glimagesink.
Change-Id: Ia62ba8028185a6760564123e5ec1ea70add1aa2f
diff --git a/debian/control b/debian/control
index e1a6b9f..179d058 100644
--- a/debian/control
+++ b/debian/control
@@ -14,10 +14,12 @@
gstreamer1.0-plugins-bad,
gstreamer1.0-plugins-good,
gstreamer1.0-plugins-ugly,
+ python3-cairo,
python3-numpy,
python3-protobuf,
python3-pil,
python3-gi,
+ python3-gi-cairo,
python3-gst-1.0,
python3-edgetpu,
weston-imx
diff --git a/edgetpuvision/apps.py b/edgetpuvision/apps.py
index 3aa052c..aacfcfc 100644
--- a/edgetpuvision/apps.py
+++ b/edgetpuvision/apps.py
@@ -43,7 +43,7 @@
default='/dev/video0:YUY2:1280x720:30/1')
parser.add_argument('--downscale', type=float, default=2.0,
help='Downscale factor for .mp4 file rendering')
- parser.add_argument('--display', type=Display, choices=Display, default=Display.FULLSCREEN,
+ parser.add_argument('--displaymode', type=Display, choices=Display, default=Display.FULLSCREEN,
help='Display mode')
add_render_gen_args(parser)
args = parser.parse_args()
@@ -51,5 +51,5 @@
if not run_gen(render_gen(args),
source=args.source,
downscale=args.downscale,
- display=args.display):
+ display=args.displaymode):
print('Invalid source argument:', args.source)
diff --git a/edgetpuvision/camera.py b/edgetpuvision/camera.py
index 4b1c914..a5de10f 100644
--- a/edgetpuvision/camera.py
+++ b/edgetpuvision/camera.py
@@ -10,7 +10,6 @@
def __init__(self, render_size, inference_size):
self._layout = gstreamer.make_layout(inference_size, render_size)
- self._loop = gstreamer.loop()
self._thread = None
self.render_overlay = None
@@ -36,12 +35,13 @@
pipeline = self.make_pipeline(format, profile, inline_headers, bitrate, intra_period)
- self._thread = threading.Thread(target=gstreamer.run_loop,
- args=(self._loop, pipeline, self._layout, render_overlay, signals))
+ self._thread = threading.Thread(target=gstreamer.run_pipeline,
+ args=(pipeline, self._layout, render_overlay,
+ gstreamer.Display.NONE, signals))
self._thread.start()
def stop_recording(self):
- self._loop.quit()
+ gstreamer.quit()
self._thread.join()
def make_pipeline(self, fmt, profile, inline_headers, bitrate, intra_period):
diff --git a/edgetpuvision/gst_native.py b/edgetpuvision/gst_native.py
new file mode 100644
index 0000000..0e9dab3
--- /dev/null
+++ b/edgetpuvision/gst_native.py
@@ -0,0 +1,211 @@
+import cairo
+import contextlib
+import ctypes
+
+import gi
+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
+
+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
+
+libgdk = ctypes.CDLL('libgdk-3.so.0')
+libgdk.gdk_wayland_window_get_wl_surface.restype = ctypes.c_void_p
+libgdk.gdk_wayland_window_get_wl_surface.argtypes = [ctypes.c_void_p]
+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_surface_status.restype = ctypes.c_int
+libcairo.cairo_surface_status.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]
+libcairo.cairo_scale.restype = None
+libcairo.cairo_scale.argtypes = [ctypes.c_void_p, ctypes.c_double, ctypes.c_double]
+
+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')
+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)
+
+ wl_display = libgdk.gdk_wayland_display_get_wl_display(hash(Gdk.Display.get_default()))
+ context = Gst.Context.new('GstWaylandDisplayHandleContextType', True)
+ structure = libgst.gst_context_writable_structure(hash(context))
+ libgst.gst_structure_set(structure, ctypes.c_char_p('display'.encode()),
+ 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)
+
+# GStreamer Element that attaches VideoOverlayComposition to buffers passing by.
+class OverlayInjector(GstBase.BaseTransform):
+ __gstmetadata__ = ('<longname>', '<class>', '<description>', '<author>')
+ __gsttemplates__ = (Gst.PadTemplate.new('src',
+ Gst.PadDirection.SRC,
+ Gst.PadPresence.ALWAYS,
+ Gst.Caps.new_any()),
+ Gst.PadTemplate.new('sink',
+ Gst.PadDirection.SINK,
+ Gst.PadPresence.ALWAYS,
+ Gst.Caps.new_any()))
+
+ @staticmethod
+ def _plugin_init(plugin):
+ gtype = GObject.type_register(OverlayInjector)
+ Gst.Element.register(plugin, 'overlayinjector', 0, gtype)
+ return True
+
+ @staticmethod
+ def plugin_register():
+ version = Gst.version()
+ Gst.Plugin.register_static(
+ version[0], version[1], # GStreamer version
+ '', # name
+ '', # description
+ OverlayInjector._plugin_init, # init_func
+ '', # version
+ 'unknown', # license
+ '', # source
+ '', # package
+ '' # origin
+ )
+
+ def __init__(self):
+ GstBase.BaseTransform.__init__(self)
+ GstBase.BaseTransform.set_in_place(self, True)
+ self.render_size = None
+ self.svg = None
+ self.rendered_svg = None
+ self.composition = None
+ self.scale_factor = 0.75
+
+ def set_svg(self, svg, render_size):
+ self.svg = svg
+ self.render_size = render_size
+
+ def do_transform_ip(self, frame_buf):
+ self.render()
+ if self.composition:
+ # Note: Buffer IS writable (ref is 1 in native land). However gst-python
+ # took an additional ref so it's now 2 and gst_buffer_is_writable
+ # returns false. We can't modify the buffer without fiddling with refcount.
+ if frame_buf.mini_object.refcount != 2:
+ return Gst.FlowReturn.ERROR
+ frame_buf.mini_object.refcount -= 1
+ GstVideo.buffer_add_video_overlay_composition_meta(frame_buf, self.composition)
+ frame_buf.mini_object.refcount += 1
+ return Gst.FlowReturn.OK
+
+
+ def render(self):
+ if not self.svg:
+ self.composition = None
+ self.rendered_svg = None
+ return
+
+ if self.svg == self.rendered_svg:
+ return
+
+ overlay_size = self.render_size * self.scale_factor
+ stride = libcairo.cairo_format_stride_for_width(
+ int(cairo.FORMAT_ARGB32), overlay_size.width)
+ overlay_buffer = Gst.Buffer.new_allocate(None,
+ stride * overlay_size.height)
+ with _gst_buffer_map(overlay_buffer, Gst.MapFlags.WRITE) as mapped:
+ # Fill with transparency and create surface from buffer.
+ ctypes.memset(ctypes.addressof(mapped), 0, ctypes.sizeof(mapped))
+ surface = libcairo.cairo_image_surface_create_for_data(
+ ctypes.addressof(mapped),
+ int(cairo.FORMAT_ARGB32),
+ overlay_size.width,
+ overlay_size.height,
+ stride)
+
+ # Render the SVG overlay.
+ data = self.svg.encode('utf-8')
+ context = libcairo.cairo_create(surface)
+ libcairo.cairo_scale(context, self.scale_factor, self.scale_factor)
+ 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)
+
+ # Attach overlay to VideoOverlayComposition.
+ GstVideo.buffer_add_video_meta(overlay_buffer,
+ GstVideo.VideoFrameFlags.NONE, GstVideo.VideoFormat.BGRA,
+ overlay_size.width, overlay_size.height)
+ rect = GstVideo.VideoOverlayRectangle.new_raw(overlay_buffer,
+ 0, 0, self.render_size.width, self.render_size.height,
+ GstVideo.VideoOverlayFormatFlags.PREMULTIPLIED_ALPHA)
+ self.composition = GstVideo.VideoOverlayComposition.new(rect)
+ self.rendered_svg = self.svg
+
+OverlayInjector.plugin_register()
diff --git a/edgetpuvision/gstreamer.py b/edgetpuvision/gstreamer.py
index cc561ca..64adcba 100644
--- a/edgetpuvision/gstreamer.py
+++ b/edgetpuvision/gstreamer.py
@@ -6,6 +6,7 @@
import os
import pathlib
import queue
+import signal
import sys
import termios
import threading
@@ -14,22 +15,23 @@
import numpy as np
import gi
+gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0')
gi.require_version('GObject', '2.0')
gi.require_version('Gst', '1.0')
gi.require_version('GstBase', '1.0')
gi.require_version('GstPbutils', '1.0')
-
-from gi.repository import GLib, GObject, Gst, GstBase
+from gi.repository import GLib, GObject, Gst, GstBase, Gtk
GObject.threads_init()
-Gst.init(None)
+Gst.init([])
+Gtk.init([])
from gi.repository import GstPbutils # Must be called after Gst.init().
from PIL import Image
-from .gst import *
+from .gst_native import set_display_contexts
from .pipelines import *
COMMAND_SAVE_FRAME = ' '
@@ -121,9 +123,6 @@
assert len(streams) == 1
return streams[0]
-def loop():
- return GLib.MainLoop()
-
@contextlib.contextmanager
def pull_sample(sink):
sample = sink.emit('pull-sample')
@@ -135,61 +134,29 @@
buf.unmap(mapinfo)
def new_sample_callback(process):
- def callback(sink, pipeline, loop):
+ def callback(sink, pipeline):
with pull_sample(sink) as (sample, data):
process(data, caps_size(sample.get_caps()))
return Gst.FlowReturn.OK
return callback
-def on_bus_message(bus, message, loop):
+def on_bus_message(bus, message):
if message.type == Gst.MessageType.EOS:
- loop.quit()
+ Gtk.main_quit()
elif message.type == Gst.MessageType.WARNING:
err, debug = message.parse_warning()
sys.stderr.write('Warning: %s: %s\n' % (err, debug))
elif message.type == Gst.MessageType.ERROR:
err, debug = message.parse_error()
sys.stderr.write('Error: %s: %s\n' % (err, debug))
- loop.quit()
- return True
-
-def run_pipeline(loop, pipeline, signals):
- # Create pipeline
- pipeline = describe(pipeline)
- print(pipeline)
- pipeline = Gst.parse_launch(pipeline)
-
- # Attach signals
- for name, signals in signals.items():
- component = pipeline.get_by_name(name)
- if component:
- for signal_name, signal_handler in signals.items():
- component.connect(signal_name, signal_handler, pipeline, loop)
-
- # Set up a pipeline bus watch to catch errors.
- bus = pipeline.get_bus()
- bus.add_signal_watch()
- bus.connect('message', on_bus_message, loop)
-
- # Run pipeline.
- pipeline.set_state(Gst.State.PLAYING)
- try:
- loop.run()
- except KeyboardInterrupt:
- pass
- finally:
- pipeline.set_state(Gst.State.NULL)
-
- # Process all pending operations on the loop.
- while loop.get_context().iteration(False):
- pass
+ Gtk.main_quit()
def on_keypress(fd, flags, commands):
for ch in sys.stdin.read():
commands.put(ch)
return True
-def on_new_sample(sink, pipeline, loop, render_overlay, layout, images, commands):
+def on_new_sample(sink, pipeline, render_overlay, layout, images, commands):
with pull_sample(sink) as (sample, data):
custom_command = None
save_frame = False
@@ -202,7 +169,7 @@
print('Render size: %d x %d' % layout.render_size)
print('Inference size: %d x %d' % layout.inference_size)
elif command == COMMAND_QUIT:
- loop.quit()
+ Gtk.main_quit()
else:
custom_command = command
@@ -210,7 +177,7 @@
command=custom_command)
overlay = pipeline.get_by_name('overlay')
if overlay:
- overlay.set_property('data', svg)
+ overlay.set_svg(svg, layout.render_size)
if save_frame:
images.put((data, layout.inference_size, svg))
@@ -230,7 +197,7 @@
result = get_pipeline(source, inference_size, downscale, display)
if result:
layout, pipeline = result
- run_loop(loop(), pipeline, layout, render_overlay)
+ run_pipeline(pipeline, layout, render_overlay, display)
return True
return False
@@ -254,7 +221,7 @@
if display is Display.NONE:
return camera_headless_pipeline(fmt, layout)
else:
- return camera_display_pipeline(fmt, layout, display is Display.FULLSCREEN)
+ return camera_display_pipeline(fmt, layout)
def file_pipline(is_image, filename, layout, display):
if display is Display.NONE:
@@ -265,14 +232,55 @@
else:
fullscreen = display is Display.FULLSCREEN
if is_image:
- return image_display_pipeline(filename, layout, fullscreen)
+ return image_display_pipeline(filename, layout)
else:
- return video_display_pipeline(filename, layout, fullscreen)
+ return video_display_pipeline(filename, layout)
-def run_loop(loop, pipeline, layout, render_overlay, signals=None):
+def quit():
+ Gtk.main_quit()
+
+def run_pipeline(pipeline, layout, render_overlay, display, signals=None):
signals = signals or {}
commands = queue.Queue()
+ # Create pipeline
+ pipeline = describe(pipeline)
+ print(pipeline)
+ pipeline = Gst.parse_launch(pipeline)
+
+ if display is not Display.NONE:
+ # Workaround for https://gitlab.gnome.org/GNOME/gtk/issues/844 in gtk3 < 3.24.
+ widget_draws = 123
+ def on_widget_draw(widget, cairo):
+ nonlocal widget_draws
+ if widget_draws:
+ widget.queue_draw()
+ widget_draws -= 1
+ return False
+
+ # Needed to account for window chrome etc.
+ def on_widget_configure(widget, event, glsink):
+ allocation = widget.get_allocation()
+ glsink.set_render_rectangle(allocation.x, allocation.y,
+ allocation.width, allocation.height)
+ return False
+
+ window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
+ window.set_default_size(layout.render_size.width, layout.render_size.height)
+ if display is Display.FULLSCREEN:
+ window.fullscreen()
+
+ drawing_area = Gtk.DrawingArea()
+ window.add(drawing_area)
+ drawing_area.realize()
+
+ glsink = pipeline.get_by_name('glsink')
+ set_display_contexts(glsink, drawing_area)
+ drawing_area.connect('draw', on_widget_draw)
+ drawing_area.connect('configure-event', on_widget_configure, glsink)
+ window.connect('delete-event', Gtk.main_quit)
+ window.show_all()
+
with contextlib.ExitStack() as stack:
images = stack.enter_context(Worker(save_frame))
@@ -281,11 +289,36 @@
GLib.io_add_watch(sys.stdin.fileno(), GLib.IO_IN, on_keypress, commands)
stack.enter_context(term_raw_mode(sys.stdin.fileno()))
- run_pipeline(loop, pipeline, {'appsink':
+ signals = {'appsink':
{'new-sample': functools.partial(on_new_sample,
render_overlay=functools.partial(render_overlay, layout=layout),
layout=layout,
images=images,
commands=commands)},
**signals
- })
+ }
+
+ for name, signals in signals.items():
+ component = pipeline.get_by_name(name)
+ if component:
+ for signal_name, signal_handler in signals.items():
+ component.connect(signal_name, signal_handler, pipeline)
+
+ # Set up a pipeline bus watch to catch errors.
+ bus = pipeline.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', on_bus_message)
+
+ # Run pipeline.
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
+ pipeline.set_state(Gst.State.PLAYING)
+ try:
+ Gtk.main()
+ except KeyboardInterrupt:
+ pass
+ finally:
+ pipeline.set_state(Gst.State.NULL)
+
+ # Process all pending MainContext operations.
+ while GLib.MainContext.default().iteration(False):
+ pass
diff --git a/edgetpuvision/pipelines.py b/edgetpuvision/pipelines.py
index 3596ca6..d79bf2b 100644
--- a/edgetpuvision/pipelines.py
+++ b/edgetpuvision/pipelines.py
@@ -13,14 +13,25 @@
framerate='%d/%d' % fmt.framerate),
]
-def display_sink(fullscreen, sync=False):
- return Sink('kms' if fullscreen else 'wayland', sync=sync),
+def display_sink(sync=False):
+ return Sink('glimage', sync=sync, name='glsink'),
def h264_sink():
return Sink('app', name='h264sink', emit_signals=True, max_buffers=1, drop=False, sync=False)
-def inference_pipeline(layout):
+def inference_pipeline(layout, stillimage=False):
size = max_inner_size(layout.render_size, layout.inference_size)
+ if stillimage:
+ return [
+ Filter('videoconvert'),
+ Filter('videoscale'),
+ Caps('video/x-raw', format='RGB', width=size.width, height=size.height),
+ Filter('videobox', autocrop=True),
+ Caps('video/x-raw', width=layout.inference_size.width, height=layout.inference_size.height),
+ Filter('imagefreeze'),
+ Sink('app', name='appsink', emit_signals=True, max_buffers=1, drop=True, sync=False),
+ ]
+
return [
Filter('glfilterbin', filter='glcolorscale'),
Caps('video/x-raw', format='RGBA', width=size.width, height=size.height),
@@ -32,26 +43,24 @@
]
# Display
-def image_display_pipeline(filename, layout, fullscreen):
+def image_display_pipeline(filename, layout):
return (
[decoded_file_src(filename),
Tee(name='t')],
[Pad('t'),
Queue(),
- Filter('imagefreeze'),
Filter('videoconvert'),
Filter('videoscale'),
- Caps('video/x-raw', width=layout.render_size.width, height=layout.render_size.height),
- Filter('rsvgoverlay', name='overlay'),
- display_sink(fullscreen)],
+ Caps('video/x-raw', format='RGBA', width=layout.render_size.width, height=layout.render_size.height),
+ Filter('imagefreeze'),
+ Filter('overlayinjector', name='overlay'),
+ display_sink()],
[Pad('t'),
Queue(),
- Filter('imagefreeze'),
- Filter('glupload'),
- inference_pipeline(layout)],
+ inference_pipeline(layout, stillimage=True)],
)
-def video_display_pipeline(filename, layout, fullscreen):
+def video_display_pipeline(filename, layout,):
return (
[decoded_file_src(filename),
Filter('glupload'),
@@ -59,15 +68,15 @@
[Pad('t'),
Queue(max_size_buffers=1),
Filter('glfilterbin', filter='glcolorscale'),
- Filter('rsvgoverlay', name='overlay'),
+ Filter('overlayinjector', name='overlay'),
Caps('video/x-raw', width=layout.render_size.width, height=layout.render_size.height),
- display_sink(fullscreen)],
+ display_sink()],
[Pad('t'),
Queue(max_size_buffers=1, leaky='downstream'),
inference_pipeline(layout)],
)
-def camera_display_pipeline(fmt, layout, fullscreen):
+def camera_display_pipeline(fmt, layout):
return (
[v4l2_src(fmt),
Filter('glupload'),
@@ -75,8 +84,8 @@
[Pad(name='t'),
Queue(max_size_buffers=1, leaky='downstream'),
Filter('glfilterbin', filter='glcolorscale'),
- Filter('rsvgoverlay', name='overlay'),
- display_sink(fullscreen)],
+ Filter('overlayinjector', name='overlay'),
+ display_sink()],
[Pad(name='t'),
Queue(max_size_buffers=1, leaky='downstream'),
inference_pipeline(layout)],
@@ -142,4 +151,4 @@
[Pad('t'),
Queue(),
inference_pipeline(layout)],
- )
\ No newline at end of file
+ )