added experimental gstreamer+cairo+rsvg based renderer
This commit is contained in:
parent
2224b23c71
commit
2054bc1e97
3 changed files with 172 additions and 5 deletions
138
gstrenderer.py
Normal file
138
gstrenderer.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version('Gst', '1.0')
|
||||
from gi.repository import Gst
|
||||
gi.require_version('GstWebRTC', '1.0')
|
||||
from gi.repository import GstWebRTC
|
||||
gi.require_version('GstSdp', '1.0')
|
||||
from gi.repository import GstSdp
|
||||
gi.require_version('Rsvg', '2.0')
|
||||
from gi.repository import Rsvg
|
||||
import cairo
|
||||
from ctypes import *
|
||||
from contextlib import contextmanager
|
||||
import time
|
||||
|
||||
Gst.init(None)
|
||||
|
||||
class GstMapInfo(Structure):
|
||||
_fields_ = [("memory", c_void_p), # GstMemory *memory
|
||||
("flags", c_int), # GstMapFlags flags
|
||||
("data", POINTER(c_byte)), # guint8 *data
|
||||
("size", c_size_t), # gsize size
|
||||
("maxsize", c_size_t), # gsize maxsize
|
||||
("user_data", c_void_p * 4), # gpointer user_data[4]
|
||||
("_gst_reserved", c_void_p * 4)]
|
||||
|
||||
libgst = CDLL("libgstreamer-1.0.so.0")
|
||||
|
||||
GST_MAP_INFO_POINTER = POINTER(GstMapInfo)
|
||||
|
||||
# gst_buffer_map
|
||||
libgst.gst_buffer_map.argtypes = [c_void_p, GST_MAP_INFO_POINTER, c_int]
|
||||
libgst.gst_buffer_map.restype = c_int
|
||||
|
||||
# gst_buffer_unmap
|
||||
libgst.gst_buffer_unmap.argtypes = [c_void_p, GST_MAP_INFO_POINTER]
|
||||
libgst.gst_buffer_unmap.restype = None
|
||||
|
||||
# gst_mini_object_is_writable
|
||||
libgst.gst_mini_object_is_writable.argtypes = [c_void_p]
|
||||
libgst.gst_mini_object_is_writable.restype = c_int
|
||||
|
||||
def map_gst_buffer(pbuffer, flags):
|
||||
if pbuffer is None:
|
||||
raise TypeError("Cannot pass NULL to map_gst_buffer")
|
||||
|
||||
ptr = hash(pbuffer)
|
||||
if flags & 2 and libgst.gst_mini_object_is_writable(ptr) == 0:
|
||||
raise ValueError("Writable array requested but buffer is not writeable")
|
||||
|
||||
mapping = GstMapInfo()
|
||||
success = libgst.gst_buffer_map(ptr, mapping, flags)
|
||||
|
||||
if not success:
|
||||
raise RuntimeError("Couldn't map buffer")
|
||||
|
||||
try:
|
||||
# Cast POINTER(c_byte) to POINTER to array of c_byte with size mapping.size
|
||||
# Returns not pointer but the object to which pointer points
|
||||
return cast(mapping.data, POINTER(c_byte * mapping.size)).contents
|
||||
finally:
|
||||
libgst.gst_buffer_unmap(ptr, mapping)
|
||||
|
||||
class GstRenderer(object):
|
||||
def __init__(self, width, height, output):
|
||||
self.width, self.height = width, height
|
||||
|
||||
self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.width, self.height)
|
||||
self.context = cairo.Context(self.surface)
|
||||
self.context.scale(1.0, 1.0)
|
||||
|
||||
pipeline = "appsrc name=input emit-signals=false format=time do-timestamp=false is-live=false block=false caps=video/x-raw,width=%d,height=%d,format=BGRA,framerate=25/1,pixel-aspect-ratio=1/1,interlace-mode=progressive" % (self.width, self.height)
|
||||
pipeline += " ! videoconvert"
|
||||
pipeline += " ! queue"
|
||||
if output.endswith(".mov"):
|
||||
pipeline += " ! qtmux"
|
||||
else:
|
||||
raise Exception("Unsupported file ending for gst renderer: %s" % output)
|
||||
|
||||
# if output.endswith(".ts"):
|
||||
# pipeline += " ! mpeg2enc format=5 aspect=3 framerate=3 bitrate=10000"
|
||||
# pipeline += " ! mpegvideoparse"
|
||||
# pipeline += " ! mpegtsmux"
|
||||
# elif output.endswith(".mkv"):
|
||||
# pipeline += " ! x264enc"
|
||||
# pipeline += " ! matroskamux"
|
||||
# else:
|
||||
# raise Exception("Unsupported file ending for gst renderer: %s" % output)
|
||||
|
||||
pipeline += " ! filesink name=filesink"
|
||||
|
||||
self.framebuf = Gst.Buffer.new_wrapped(b'\x00' * (4*width*height))
|
||||
self.framebuf.duration = 10**9 / 25
|
||||
self.framebuf.dts = 0
|
||||
self.framebuf.pts = 0
|
||||
self.mapped_framebuf = map_gst_buffer(self.framebuf, Gst.MapFlags.READ | Gst.MapFlags.WRITE)
|
||||
|
||||
self.pipe = Gst.parse_launch(pipeline)
|
||||
self.appsrc = self.pipe.get_by_name("input")
|
||||
self.filesink = self.pipe.get_by_name("filesink")
|
||||
self.filesink.set_property("location", output)
|
||||
|
||||
self.bus = self.pipe.get_bus()
|
||||
self.bus.add_signal_watch()
|
||||
|
||||
self.pipe.set_state(Gst.State.PLAYING)
|
||||
|
||||
def render_frame(self, svg):
|
||||
handle = Rsvg.Handle()
|
||||
svghandle = handle.new_from_data(svg.encode())
|
||||
|
||||
width, height = svghandle.get_dimensions().width, svghandle.get_dimensions().height
|
||||
self.context.scale(self.width / width, self.height / height)
|
||||
svghandle.render_cairo(self.context)
|
||||
|
||||
frame = bytes(self.surface.get_data())
|
||||
memmove(self.mapped_framebuf, frame, len(frame))
|
||||
|
||||
self.framebuf.dts += self.framebuf.duration
|
||||
self.framebuf.pts = self.framebuf.dts
|
||||
|
||||
self.appsrc.emit("push-buffer", self.framebuf)
|
||||
|
||||
def close(self):
|
||||
self.appsrc.emit("end-of-stream")
|
||||
self.pipe.send_event(Gst.Event.new_eos())
|
||||
|
||||
while True:
|
||||
message = self.bus.pop()
|
||||
if not message:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
if message.type == Gst.MessageType.EOS:
|
||||
break
|
||||
|
||||
|
||||
self.pipe.set_state(Gst.State.NULL)
|
Loading…
Add table
Add a link
Reference in a new issue