def paint_with_video_decoder(self, decoder_module, coding, img_data, x, y, width, height, options, callbacks): assert x == 0 and y == 0 assert hasattr( decoder_module, "Decoder" ), "decoder module %s does not have 'Decoder' factory function!" % decoder_module assert hasattr( decoder_module, "get_colorspaces" ), "decoder module %s does not have 'get_colorspaces' function!" % decoder_module factory = getattr(decoder_module, "Decoder") get_colorspaces = getattr(decoder_module, "get_colorspaces") try: self._decoder_lock.acquire() if self._backing is None: log("window %s is already gone!", self.wid) fire_paint_callbacks(callbacks, False) return False enc_width, enc_height = options.get("scaled_size", (width, height)) input_colorspace = options.get("csc") if not input_colorspace: # Backwards compatibility with pre 0.10.x clients # We used to specify the colorspace as an avutil PixelFormat constant old_csc_fmt = options.get("csc_pixel_format") input_colorspace = get_colorspace_from_avutil_enum(old_csc_fmt) assert input_colorspace is not None, "csc was not specified and we cannot find a colorspace from csc_pixel_format=%s" % old_csc_fmt #do we need a prep step for decoders that cannot handle the input_colorspace directly? decoder_colorspaces = get_colorspaces() decoder_colorspace = input_colorspace #if input_colorspace not in decoder_colorspaces: if input_colorspace not in decoder_colorspaces: log("colorspace not supported by %s directly", decoder_module) assert input_colorspace in ( "BGRA", "BGRX" ), "colorspace %s cannot be handled directly or via a csc preparation step!" % input_colorspace decoder_colorspace = "YUV444P" if self._csc_prep: if self._csc_prep.get_src_format() != input_colorspace: #this should not happen! log.warn("input colorspace has changed from %s to %s", self._csc_prep.get_src_format(), input_colorspace) self.do_clean_csc_prep() elif self._csc_prep.get_dst_format( ) not in decoder_colorspaces: #this should not happen! log.warn("csc prep colorspace %s is now invalid!?", self._csc_prep.get_dst_format()) self.do_clean_csc_prep() elif self._csc_prep.get_src_width( ) != enc_width or self._csc_prep.get_src_height( ) != enc_height: log("csc prep dimensions have changed from %s to %s", (self._csc_prep.get_src_width(), self._csc_prep.get_src_height()), (enc_width, enc_height)) self.do_clean_csc_prep() if self._csc_prep is None: from xpra.codecs.csc_swscale.colorspace_converter import ColorspaceConverter #@UnresolvedImport self._csc_prep = ColorspaceConverter() csc_speed = 0 #always best quality self._csc_prep.init_context(enc_width, enc_height, input_colorspace, width, height, decoder_colorspace, csc_speed) log("csc preparation step: %s", self._csc_prep) elif self._csc_prep: #no longer needed? self.do_clean_csc_prep() if self._video_decoder: if self._video_decoder.get_type() != coding: if DRAW_DEBUG: log.info( "paint_with_video_decoder: encoding changed from %s to %s", self._video_decoder.get_type(), coding) self.do_clean_video_decoder() elif self._video_decoder.get_width( ) != enc_width or self._video_decoder.get_height( ) != enc_height: if DRAW_DEBUG: log.info( "paint_with_video_decoder: window dimensions have changed from %s to %s", (self._video_decoder.get_width(), self._video_decoder.get_height()), (enc_width, enc_height)) self.do_clean_video_decoder() elif self._video_decoder.get_colorspace( ) != decoder_colorspace: if DRAW_DEBUG: log.info( "paint_with_video_decoder: colorspace changed from %s to %s", self._video_decoder.get_colorspace(), decoder_colorspace) self.do_clean_video_decoder() elif options.get("frame") == 0: if DRAW_DEBUG: log.info( "paint_with_video_decoder: first frame of new stream" ) self.do_clean_video_decoder() if self._video_decoder is None: if DRAW_DEBUG: log.info("paint_with_video_decoder: new %s(%s,%s,%s)", factory, width, height, decoder_colorspace) self._video_decoder = factory() self._video_decoder.init_context(enc_width, enc_height, decoder_colorspace) if DRAW_DEBUG: log.info("paint_with_video_decoder: info=%s", self._video_decoder.get_info()) img = self._video_decoder.decompress_image(img_data, options) if not img: raise Exception( "paint_with_video_decoder: wid=%s, %s decompression error on %s bytes of picture data for %sx%s pixels, options=%s" % (self.wid, coding, len(img_data), width, height, options)) self.do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) finally: self._decoder_lock.release() if self._backing is None: self.close_decoder(True) return False
def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, options, callbacks): rgb_format = "RGB" #we may want to be able to change this (RGBA, BGR, ..) #as some video formats like vpx can forward transparency #also we could skip the csc step in some cases: pixel_format = img.get_pixel_format() #to handle this, we would need the decoder to handle buffers allocation properly: assert pixel_format != rgb_format, "no csc needed! but we don't handle this scenario yet!" if self._csc_decoder is not None: if self._csc_decoder.get_src_format() != pixel_format: if DRAW_DEBUG: log.info( "do_video_paint csc: switching src format from %s to %s", self._csc_decoder.get_src_format(), pixel_format) self.do_clean_csc_decoder() elif self._csc_decoder.get_dst_format() != rgb_format: if DRAW_DEBUG: log.info( "do_video_paint csc: switching dst format from %s to %s", self._csc_decoder.get_dst_format(), rgb_format) self.do_clean_csc_decoder() elif self._csc_decoder.get_src_width( ) != enc_width or self._csc_decoder.get_src_height() != enc_height: if DRAW_DEBUG: log.info( "do_video_paint csc: switching src size from %sx%s to %sx%s", enc_width, enc_height, self._csc_decoder.get_src_width(), self._csc_decoder.get_src_height()) self.do_clean_csc_decoder() elif self._csc_decoder.get_dst_width( ) != width or self._csc_decoder.get_dst_height() != height: if DRAW_DEBUG: log.info( "do_video_paint csc: switching src size from %sx%s to %sx%s", width, height, self._csc_decoder.get_dst_width(), self._csc_decoder.get_dst_height()) self.do_clean_csc_decoder() if self._csc_decoder is None: from xpra.codecs.csc_swscale.colorspace_converter import ColorspaceConverter #@UnresolvedImport self._csc_decoder = ColorspaceConverter() #use higher quality csc to compensate for lower quality source #(which generally means that we downscaled via YUV422P or lower) #or when upscaling the video: q = options.get("quality", 50) csc_speed = int( min(100, 100 - q, 100.0 * (enc_width * enc_height) / (width * height))) self._csc_decoder.init_context(enc_width, enc_height, pixel_format, width, height, rgb_format, csc_speed) if DRAW_DEBUG: log.info("do_video_paint new csc decoder: %s", self._csc_decoder) rgb = self._csc_decoder.convert_image(img) if DRAW_DEBUG: log.info("do_video_paint rgb(%s)=%s", img, rgb) img.free() assert rgb.get_planes() == 0, "invalid number of planes for %s: %s" % ( rgb_format, rgb.get_planes()) #this will also take care of firing callbacks (from the UI thread): def paint(): data = rgb.get_pixels() rowstride = rgb.get_rowstride() self.do_paint_rgb24(data, x, y, width, height, rowstride, options, callbacks) rgb.free() self.idle_add(paint)
def test_push(): import time from PIL import Image, ImageDraw from xpra.codecs.v4l2.pusher import Pusher #@UnresolvedImport p = Pusher() W = 480 H = 640 p.init_context(W, H, W, "YUV420P", "/dev/video1") print("actual dimensions: %s - requested=%s" % ((p.get_width(), p.get_height()), (W, H))) from xpra.codecs.csc_swscale.colorspace_converter import ColorspaceConverter #@UnresolvedImport csc = ColorspaceConverter() csc.init_context(W, H, "BGRX", W, H, "YUV420P") print("csc=%s" % csc) from xpra.codecs.image_wrapper import ImageWrapper def h(v): return ("0" + ("%#x" % v)[2:])[-2:].upper() i = 0 while True: for r, g, b in ( (0, 255, 0), (0, 0, 255), (255, 255, 255), (255, 0, 0), (128, 128, 128), ): name = "%s%s%s" % (h(r), h(g), h(b)) print("testing with RGB: %s" % name) rgbx = (chr(r) + chr(g) + chr(b) + chr(255)) * (W * H) image = Image.frombytes("RGBA", (W, H), rgbx, "raw", "RGBA", W * 4, 1) draw = ImageDraw.Draw(image) if i % 3 == 0: draw.polygon([(W // 2, H // 4), (W // 4, H * 3 // 4), (W * 3 // 4, H * 3 // 4)], (0, 0, 0, 255)) elif i % 3 == 1: draw.rectangle([W // 8, H // 8, W * 7 // 8, H * 7 // 8], fill=(b, g, r, 255), outline=(128, 255, 128, 128)) draw.rectangle([W // 4, H // 4, W * 3 // 4, H * 3 // 4], fill=(g, r, b, 255), outline=(255, 128, 128, 128)) draw.rectangle( [W * 3 // 8, H * 3 // 8, W * 5 // 8, H * 5 // 8], fill=(r, g, b, 255), outline=(128, 128, 255, 128)) else: c = [255, 0, 0] * 2 for j in range(3): ci = (i + j) % 3 fc = tuple(c[(ci):(ci + 3)] + [255]) draw.rectangle([W * j // 3, 0, W * (j + 1) // 3, H], fill=fc, outline=(128, 255, 128, 128)) image.save("./%s.png" % name, "png") bgrx = image.tobytes('raw', "BGRA") import binascii #print("%s=%s" % (name, binascii.hexlify(bgrx))) with open("./latest.hex", "wb") as f: f.write(binascii.hexlify(bgrx)) bgrx_image = ImageWrapper(0, 0, W, H, bgrx, "BGRX", 32, W * 4, planes=ImageWrapper.PACKED) image = csc.convert_image(bgrx_image) for _ in range(100): #print(".") p.push_image(image) time.sleep(0.05) i += 1